UnityプロジェクトでのgRPC導入方法について

こんにちは。A.R.Tでクライアントエンジニアをしている藤尾です。

現在アプリボットでは、UnityプロジェクトへgRPCの本格導入を進めており、開発中のSEVEN’s CODEというタイトルでは実際に使用して実装を始めています。

b854fe80-79bf-3efb-c6a1-a4d63797b340-1.png

アプリボットでは、下記のような構成でgRPCの導入を検討しています。

5d201f8e-1813-ab14-2f09-5dcfd21770ce.png

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.dllSystem.Interactive.Async.dllは、そのままUnityProjectに取り込みます。

libgrpc_csharp_ext.x64.dylibは、grpc_csharp_ext.bundleとして取り込みます。

Grpc.Coreは、ソースコードを変更するので、dllではなく、ソースコードとして取り込みます。

ソースコードの変更

先述の通り、Grpc.Coreはそのまま取り込むと、下記のようなエラーが発生します。

DefaultSslRootsOverride.csvoid 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.tbdlibz.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の「世界を解き明かすリズムアクション」というジャンルにかけて、今回の記事にあるキーワードが隠されているので、是非解き明かしてみてください。