UE4とgRPCを連携させてexampleの実行をしてみた
UE4とgRPCを連携させてexampleの通信を実行しようとしたら色々と詰まったためここに記録します。
開発環境は下記のとおりです。
開発環境
- Windows10
- UE4.26.1
- gRPC v1.35.0
- Visual Studio 2019
- Python 3.8.7
- Git 2.30.0.windows.2
- CMake 3.19,4
- nasm 2.15.05
なおGit/CMake/nasmにはPATHが通っています。
gRPC側の準備
まずはじめにgRPCのcloneとビルドをします。
gRPC側で依存しているライブラリでUE4に使用されているライブラリがいくつかあるため、その場合はUE4付属のライブラリを使用してビルドするようにコマンドライン引数で指定します。
git clone -b v1.35.0 https://github.com/grpc/grpc
cd grpc
git submodule update --init
md .build & cd .build
SET UE_THIRD_PARTY_DIR=C:\Program Files\Epic Games\UE_4.26\Engine\Source\ThirdParty
cmake .. -G "Visual Studio 16 2019" ^
-DCMAKE_CXX_STANDARD_LIBRARIES="Crypt32.Lib User32.lib Advapi32.lib" ^
-DCMAKE_BUILD_TYPE=Release ^
-DCMAKE_CONFIGURATION_TYPES=Release ^
-Dprotobuf_BUILD_TESTS=OFF ^
-DgRPC_ZLIB_PROVIDER=package ^
-DZLIB_INCLUDE_DIR="%UE_THIRD_PARTY_DIR%\zlib\v1.2.8\include\Win64\VS2015" ^
-DZLIB_LIBRARY_DEBUG="%UE_THIRD_PARTY_DIR%\zlib\v1.2.8\lib\Win64\VS2015\Debug\zlibstatic.lib" ^
-DZLIB_LIBRARY_RELEASE="%UE_THIRD_PARTY_DIR%\zlib\v1.2.8\lib\Win64\VS2015\Release\zlibstatic.lib" ^
-DgRPC_SSL_PROVIDER=package ^
-DLIB_EAY_LIBRARY_DEBUG="%UE_THIRD_PARTY_DIR%\OpenSSL\1.1.1\Lib\Win64\VS2015\Debug\libcrypto.lib" ^
-DLIB_EAY_LIBRARY_RELEASE="%UE_THIRD_PARTY_DIR%\OpenSSL\1.1.1\Lib\Win64\VS2015\Release\libcrypto.lib" ^
-DLIB_EAY_DEBUG="%UE_THIRD_PARTY_DIR%\OpenSSL\1.1.1\Lib\Win64\VS2015\Debug\libcrypto.lib" ^
-DLIB_EAY_RELEASE="%UE_THIRD_PARTY_DIR%\OpenSSL\1.1.1\Lib\Win64\VS2015\Release\libcrypto.lib" ^
-DOPENSSL_INCLUDE_DIR="%UE_THIRD_PARTY_DIR%\OpenSSL\1.1.1\include\Win64\VS2015" ^
-DSSL_EAY_DEBUG="%UE_THIRD_PARTY_DIR%\OpenSSL\1.1.1\Lib\Win64\VS2015\Debug\libssl.lib" ^
-DSSL_EAY_LIBRARY_DEBUG="%UE_THIRD_PARTY_DIR%\OpenSSL\1.1.1\Lib\Win64\VS2015\Debug\libssl.lib" ^
-DSSL_EAY_LIBRARY_RELEASE="%UE_THIRD_PARTY_DIR%\OpenSSL\1.1.1\Lib\Win64\VS2015\Release\libssl.lib" ^
-DSSL_EAY_RELEASE="%UE_THIRD_PARTY_DIR%\OpenSSL\1.1.1\Lib\Win64\VS2015\Release\libssl.lib"
cmake --build . --target ALL_BUILD --config Release
ビルドが完了したら、protoc.exeを使用して.protoファイルから必要なヘッダーとソースを自動生成します。
cd GRPC_DIR/examples/protos
"GRPC_DIR/.build/third_party/protobuf/Release/protoc.exe" -I . --cpp_out=. ./helloworld.proto
"GRPC_DIR/.build/third_party/protobuf/Release/protoc.exe" -I . --grpc_out=. --plugin=protoc-gen-grpc="GRPC_DIR/.build/Release/grpc_cpp_plugin.exe" ./helloworld.proto
これでGRPC_DIR/examples/protosに以下の4つのファイルが生成されます。
- helloworld.pb.h
- helloworld.pb.cc
- helloworld.grpc.h
- helloworld.grpc.cc
サーバー側はpythonで実行するため、必要なモジュールを入れておきます。
py -m pip install --upgrade pip
py -m pip install grpcio
UE4側の準備
UE4を起動してHellogRPCというC++プロジェクトを作成します。
次にHellogRPCフォルダ直下にThirdPartyフォルダを作成し、その直下にIncludesフォルダとLibrariesフォルダを作成します。
IncludesフォルダにはGRPC_DIR/include直下にあるgrpc/grpc++/grpcppとGRPC_DIR/third_party/protobuf/src直下にあるgoogleフォルダをコピーします。
またLibrariesフォルダにはgrpcフォルダの中から下記の大量のライブラリ群をすべてコピーします。
- address_sorting.lib
- cares.lib
- gpr.lib
- grpc_unsecure.lib
- grpc++_unsecure.lib
- libprotobuf.lib
- upb.lib
- absl_base.lib
- absl_malloc_internal.lib
- absl_raw_logging_internal.lib
- absl_spinlock_wait.lib
- absl_throw_delegate.lib
- absl_time.lib
- absl_time_zone.lib
- absl_graphcycles_internal.lib
- absl_synchronization.lib
- absl_cord.lib
- absl_str_format_internal.lib
- absl_strings.lib
- absl_strings_internal.lib
- absl_status.lib
- absl_statusor.lib
- absl_bad_optional_access.lib
- absl_stacktrace.lib
- absl_symbolize.lib
- absl_int128.lib
次にHellogRPC.build.csを修正して、必要な定義の追加や各ライブラリの読み込みをするように変更します。
using System.IO;
using UnrealBuildTool;
public class HellogRPC : ModuleRules
{
private string ThirdPartyDirectory
{
get
{
return Path.GetFullPath(Path.Combine(ModuleDirectory, "../../ThirdParty/"));
}
}
private string ThirdPartyLibrariesDirectory
{
get
{
return Path.Combine(ThirdPartyDirectory, "Libraries");
}
}
public HellogRPC(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });
PrivateDependencyModuleNames.AddRange(new string[] { });
PublicDefinitions.Add("GOOGLE_PROTOBUF_NO_RTTI");
PublicDefinitions.Add("GPR_FORBID_UNREACHABLE_CODE");
PublicDefinitions.Add("GRPC_ALLOW_EXCEPTIONS=0");
PublicIncludePaths.Add(Path.Combine(ThirdPartyDirectory, "Includes"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "address_sorting.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "cares.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "gpr.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "grpc_unsecure.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "grpc++_unsecure.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "libprotobuf.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "upb.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "absl_base.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "absl_malloc_internal.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "absl_raw_logging_internal.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "absl_spinlock_wait.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "absl_throw_delegate.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "absl_time.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "absl_time_zone.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "absl_graphcycles_internal.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "absl_synchronization.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "absl_cord.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "absl_str_format_internal.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "absl_strings.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "absl_strings_internal.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "absl_status.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "absl_statusor.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "absl_bad_optional_access.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "absl_stacktrace.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "absl_symbolize.lib"));
PublicAdditionalLibraries.Add(Path.Combine(ThirdPartyLibrariesDirectory, "absl_int128.lib"));
AddEngineThirdPartyPrivateStaticDependencies(Target, "zlib");
}
}
先程自動生成したhelloworld.pb.h/helloworld.pb.cc/helloworld.grpc.pb.h/helloworld.grpc.pb.ccとGRPC_DIR/examples/cpp/helloworld/greeter_client.ccをHellogRPC/Source/HellogRPC直下へとコピーします。
またhelloworld.pb.cc/helloworld.grpc.pb.ccを下の例のように書き換えます。
#pragma warning(push)
#pragma warning (disable : 4005)
#pragma warning (disable : 4125)
#pragma warning (disable : 4582)
#pragma warning (disable : 4583)
#pragma warning (disable : 4647)
#pragma warning (disable : 4668)
#pragma warning (disable : 4800)
#pragma warning (disable : 4946)
// CODE HERE
#pragma warning(pop)
greeter_client.ccも下記のように書き換える。
/*
*
* Copyright 2015 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#pragma warning(push)
#pragma warning (disable : 4005)
#pragma warning (disable : 4125)
#pragma warning (disable : 4582)
#pragma warning (disable : 4583)
#pragma warning (disable : 4647)
#pragma warning (disable : 4668)
#pragma warning (disable : 4800)
#pragma warning (disable : 4946)
static void MemoryBarrier() {}
#pragma intrinsic(_InterlockedCompareExchange64)
#define InterlockedCompareExchangeAcquire64 _InterlockedCompareExchange64
#define InterlockedCompareExchangeRelease64 _InterlockedCompareExchange64
#define InterlockedCompareExchangeNoFence64 _InterlockedCompareExchange64
#define InterlockedCompareExchange64 _InterlockedCompareExchange64
#include <iostream>
#include <memory>
#include <string>
#include <grpcpp/grpcpp.h>
#ifdef BAZEL_BUILD
#include "examples/protos/helloworld.grpc.pb.h"
#else
#include "helloworld.grpc.pb.h"
#endif
using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
using helloworld::HelloRequest;
using helloworld::HelloReply;
using helloworld::Greeter;
class GreeterClient {
public:
GreeterClient(std::shared_ptr<Channel> channel)
: stub_(Greeter::NewStub(channel)) {}
// Assembles the client's payload, sends it and presents the response back
// from the server.
std::string SayHello(const std::string& user) {
// Data we are sending to the server.
HelloRequest request;
request.set_name(user);
// Container for the data we expect from the server.
HelloReply reply;
// Context for the client. It could be used to convey extra information to
// the server and/or tweak certain RPC behaviors.
ClientContext context;
// The actual RPC.
Status status = stub_->SayHello(&context, request, &reply);
// Act upon its status.
if (status.ok()) {
return reply.message();
} else {
std::cout << status.error_code() << ": " << status.error_message()
<< std::endl;
return "RPC failed";
}
}
private:
std::unique_ptr<Greeter::Stub> stub_;
};
// REMOVE MAIN FUNCTION
#pragma warning(pop)
次にUObjectを継承したMyObjectを作成し、コードを下の例のように書き換えます。
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "MyObject.generated.h"
UCLASS(BlueprintType)
class HELLOGRPC_API UMyObject : public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable)
FString SayHello();
};
#include "MyObject.h"
#include "greeter_client.cc"
FString UMyObject::SayHello()
{
GreeterClient* c = new GreeterClient(grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials()));
std::string user("world");
std::string reply = c->SayHello(user);
delete c;
return reply.c_str();
}
レベルブループリントのBeginPlayでMyObjectを生成し、SayHello関数を実行し、結果を表示します。
実践
コマンドラインからpythonのサーバーを起動します。
cd GRPC_DIR/examples/python/helloworld
py greeter_server.py
実行して左上にHello, world!と表示されれば成功です!
なんとかできればいいなと思っていること
マクロの再定義を警告を無視するようなやり方でやりくりしている部分を、うまく回避する方法を探したい。
自動生成したコードに手を付けなくてもいいように改善したい(なんのための自動生成なのかという感じが...)。
参考文献
この記事を執筆するにあたり下記プロジェクト・文献には大変お世話になりました、この場を借りてお礼申し上げます。