
UnityプロジェクトでのgRPC導入方法について
こんにちは。A.R.Tでクライアントエンジニアをしている藤尾です。
現在アプリボットでは、UnityプロジェクトへgRPCの本格導入を進めており、開発中のSEVEN’s CODEというタイトルでは実際に使用して実装を始めています。
アプリボットでは、下記のような構成でgRPCの導入を検討しています。
gRPCは現状Unityでは実験的にサポート状態なため、UnityEditor上では簡単に動作するのですが、iOSやAndroidの実機動作にはひと手間かかります。
まだ世の中にドキュメントも少ないため、参考になればと思い、この記事を執筆しました。
また、SEVEN’s CODEの「世界を解き明かすリズムアクション」というジャンルにかけ、今回の記事にあるキーワードが隠されているので、是非解き明かしてみてください。
そもそもgRPCとは
公式では以下のように紹介されています。
A high performance、open-source universal RPC framework
RPC(Remote Prodcedure Call)を実現するためにGoogleが開発したプロトコルで、インターフェイス定義言語(IDL)にprotocol bufffersを、トランスポート層にはhttp2を採用することで、ハイパフォーマンスな通信を実現しています。
なお、現在アプリボットでは、下記の環境での動作を確認しています。
ライブラリ | バージョン |
---|---|
gRPC | 1.12.0 |
protocol buffers | 1.12.0 |
Unity | 2018.1.0f2 |
.NET | 4.6 |
UnityEditor上で動かす
まずは、UnityEditor上でgRPCを動かしてみます。
こちらは、gRPC公式がQuickStartを用意していますので詳細は割愛します。
ただし1点注意点として、nugetで取得できるデフォルトのgPRCのバージョンが、1.8.0なので注意が必要です。
後々実機で動かすために必要なので、gRPCのレポジトリから1.12.0をクローンしてください。
QuickStartどおりビルドすると、下記の成果物が作成されます。
Google.Protobuf.dll
System.Interactive.Async.dll
libgrpc_csharp_ext.x64.dylib
Google.Protobuf.dll
とSystem.Interactive.Async.dll
は、そのままUnityProjectに取り込みます。
libgrpc_csharp_ext.x64.dylib
は、grpc_csharp_ext.bundle
として取り込みます。
Grpc.Coreは、ソースコードを変更するので、dllではなく、ソースコードとして取り込みます。
ソースコードの変更
先述の通り、Grpc.Core
はそのまま取り込むと、下記のようなエラーが発生します。
DefaultSslRootsOverride.cs
のvoid Override(NativeMethods native)
内の処理をコメントアウトします。
取り込んだ後はreferenceによってはLazyでエラーが起きるのでそうなったらreference確認してください
DefaultSslRootsOverride.csのvoid Override(NativeMethods native)内の処理をすべてコメントアウト
後のSSL対応で、再度変更します。
これで、UnityEditorで動作することが確認できました。
実機で動かす
次に、iOSとAndroidの実機で動作するように修正します。
UnityEditor上では、動的ライブラリで動作しますが、どちらのプラットフォームも、静的ライブラリが必要になります。
iOS
以下の静的ライブラリを作成します。
grpc_csharp_ext.a
libgrpc.a
cares.a
順番に作成していきます。
grpc_csharp_ext.a
gRPCプロジェクト直下で、以下を実行します。
$XCODE = "/PATH/TO/XCODE" // xcodeまでのPATH
gcc -arch arm64 -isysroot $XCODE/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.2.sdk -fembed-bitcode -I. -I./include -c -o grpc_csharp_ext.o src/csharp/ext/grpc_csharp_ext.c
ar -rsc grpc_csharp_ext.a grpc_csharp_ext.o
libgrpc.a
gPRCプロジェクト直下のMakefile
の下記の箇所を変更します。
IOSFLAGS = -arch arm64 -isysroot $XCODE/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.2.sdk -fembed-bitcode
CC_opt = $(DEFAULT_CC) $(IOSFLAGS)
CXX_opt = $(DEFAULT_CXX) $(IOSFLAGS)
CPPFLAGS += -g -Wall -Wno-long-long -Wno-unused-parameter -DOSATOMIC_USE_INLINED=1 -Wno-deprecated-declarations
編集後、下記のコマンドを実行します。
make
cares.a
c-aresレポジトリからソースコードを取得します。
レポジトリ直下で以下を実行します。
./buildconf
ROOTDIR=../../../unity-grpc/grpc // gRPCへのPATH
BUILD_SDKROOT=$XCODE/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.2.sdk
Option="-Os -arch arm64 -pipe -no-cpp-precomp -isysroot ${BUILD_SDKROOT} -fembed-bitcode -miphoneos-version-min=6.0 -I${ROOTDIR}/include" // bitcodeは6.0からなのでmiphoneos-version-minは6.0
CPPFLAGS="${Option}"
CXXFLAGS="${Option}"
./configure --host=arm-apple-darwin --prefix=${ROOTDIR} --enable-static --disable-shared
make
gRPCソースコードの修正
NativeLogRedirector.cs
を以下のように変更します。
[MonoPInvokeCallback(typeof(GprLogDelegate))]
private static void HandleWrite(IntPtr fileStringPtr, int line, ulong threadId, IntPtr severityStringPtr, IntPtr msgPtr)
上記を編集後、UnityからXcodeProjectを吐き出し、「Linked Frameworks and Libraries」にlibresolv.tbd
とlibz.tbd
を追加し、実機にビルドすることで、動作を確認できます。
Android
grpc/src/csharp/experimental
に移動し、build_native_ext_for_android.sh
を実行し、libgrpc_csharp_ext.so
を生成します。
このとき、NDKのパスを設定しないとビルドができないことに注意してください。
作成したlibgrpc_csharp_ext.so
をUnityへインポートしてください。
il2cpp対応
このままでは、Androidのil2cppには対応できていません。
NativeExtension.cs
を下記のように変更します。
private static NativeMethods LoadNativeMethodsUnity()
{
// (S)
switch (PlatformApis.GetUnityRuntimePlatform())
{
#if UNITY_IOS
case "IPhonePlayer":
return new NativeMethods(new NativeMethods.DllImportsFromStaticLib());
#endif
default:
// most other platforms load unity plugins as a shared library
return new NativeMethods(new NativeMethods.DllImportsFromSharedLib());
}
}
}
次に、NativeMethods.Generated.cs
を下記のように変更します。
#if UNITY_IOS
public NativeMethods(DllImportsFromStaticLib unusedInstance)
{
// (E)
this.grpcsharp_init = DllImportsFromStaticLib.grpcsharp_init;
this.grpcsharp_shutdown = DllImportsFromStaticLib.grpcsharp_shutdown;
// 省略
}
#endif
// 省略
#if UNITY_IOS
internal class DllImportsFromStaticLib
{
// (V)
private const string ImportName = "__Internal";
[DllImport(ImportName)]
public static extern void grpcsharp_init();
// 省略
}
#endif
これで、il2cppにも対応できました。
SSL対応
上記で実機動作の対応はできましたが、SSL通信ができません。
grpc/ext/roots.pem
を、Assets/StreamingAssets
へコピーし、DefaultSslRootsOverride.cs
を下記のように修正します。
const string RootsPemResourceName = "roots.pem";
public static void Override(NativeMethods native)
{
#if !UNITY_EDITOR
// (E)
var pemRootCerts = "";
var assetPath = UnityEngine.Application.streamingAssetsPath;
var path = Path.Combine(assetPath, RootsPemResourceName);
#if UNITY_IOS
using (var streamReader = new StreamReader(path))
{
pemRootCerts = streamReader.ReadToEnd();
}
#elif UNITY_ANDROID
var www = new WWW(path);
while (!www.isDone) {}
// (N)
using (var txtReader = new StringReader(www.text))
{
pemRootCerts = txtReader.ReadToEnd();
}
#endif
native.grpcsharp_override_default_ssl_roots(pemRootCerts);
#endif
}
利用方法
チャンネルの生成を下記のようにすることで、SSL通信できるようになります
var channel = new Channel("localhost", new SslCredentials());
まとめ
gRPCを、UnityEditorとiOS、Androidでそれぞれ動作させる手順と、SSL通信対応の手順について紹介しました。
また、SEVEN’s CODEの「世界を解き明かすリズムアクション」というジャンルにかけて、今回の記事にあるキーワードが隠されているので、是非解き明かしてみてください。
この記事へのコメントはありません。