grpcでPythonからC++の関数を呼ぶ

C++で書かれた関数をPythonから呼べるようにする。

動機としては、C++で書かれた既存のシミュレーションコードがあって、そのシミュレーションをしながら対話的に可視化したり、状態を出力したりしたい。つまりデバッガのように対話的に動かせるようにしたい。

swigなどを使ってC++の関数を呼べるようにしても良いが、MPIで書かれたコードのように特殊な実行方法が必要なこともあるので、C++アプリ自体は単独で起動できるようにRPC経由で呼ぶように実装する。

```
C++(simulation code) -- C++(grpc server) -- Python (grpc client)
```

というように連携させる。

# grpcとは

Remote Procedure Callを独自のDSLで定義し、サーバーとクライアントを簡単に実装するためのツール。
protocol buffersを通信のプロトコルに利用している。

公式のドキュメントがとてもわかりやすい。
https://grpc.io/docs/

非同期I/O、streaming通信などもあるが、ここではもっともシンプルなRPCのみ定義する。

# インストール

公式では`make && make install`する方法が書かれているが、macの場合はbrewで簡単にインストールできる。

```
brew install protobuf grpc
```

でインストールできる。これでC++についてはOK。

さらにpythonのモジュールもインストールする。
ここではpipenvを使ってインストールしたが、どのような方法でインストールしてもよい。

```
pipenv install grpcio grpcio-tools googleapis-common-protos
```

以後、`pipenv shell`したシェルで実行する。

# RPCの実装手順

ここではC++のserverとPythonのclientを実装する。手順は以下の通り

1. rpcを定義する.protoファイルを作る。
2. (C++ server) protocコマンドでrpc定義の.protoファイルから、C++のファイルを作る（Serviceクラスが作られる）。
3. (C++ server) 先ほど作られたServiceクラスを継承して、実際にserver側で行われる処理を実装する。
4. (Python client) grpc-tools で.protoファイルからPythonのファイルを作る。Stubクラスが作られる。
5. (Python client) 先ほど作られたstubを使って、クライアントの処理を実装する。

以下、次のようなディレクトリ構造を仮定する

```
(root) /
    proto /  （.protoファイルを置くためのディレクトリ）
    server /   (C++で書かれたserverのソースを置くためのディレクトリ）
    client /    (Pythonで書かれたclientのソースを置くディレクトリ）
```


## RPCを定義する.protoファイルを作る

ここではランダムウォークをシミュレーションするserverを作ることにする。確率1/2で位置が+1または-1に移動する。
protoディレクトリにて、以下のようなprotoファイルを作る。

```proto:random_walker.proto
syntax = "proto3";

package random_walker;

service MyRandomWalker {
  rpc Update (NumSteps) returns (Empty) {}
  rpc GetPosition (Empty) returns (Position) {}
}

message Empty {
}

message NumSteps {
  int32 n = 1;
}

message Position {
  int32 x = 1;
}
```

- `rpc`というキーワードでrpc定義を行う。各RPCの引数と返り値は何かというのを定義する。
- 引数や返り値のデータ構造を`message`キーワードで定義する。（これはgrpcではなくprotocol buffers自体の仕様）
    - 引数や返り値がなくても何らかのデータを設定する必要がある。ここでは`Empty`という空のデータ構造を作っている。

## protoからC++のファイルを作成する

serverディレクトリで以下のコマンドを実行する。

```shell-session
$ protoc -I ../proto --cpp_out=. ../proto/random_walker.proto
$ protoc -I ../proto --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` ../proto/random_walker.proto
```

最初のコマンドで`message`キーワードが解釈され、データ構造に対応するC++のクラスが生成される。random_walker.pb.(h/cc) という名前のファイルが作られる。
中を見てみると次のようなクラスが作られていることがわかる。

```cpp
namespace random_walker {
  class Empty { ... };
  class NumSteps { ... };
  class Position { ... };
}
```

２つ目のコマンドで`rpc`キーワードが解釈され、RPCのserver/clientのテンプレートとなるC++のクラスが生成される。random_walker.grpc.pb.(h/cc)というファイルが作られる。
その中では以下のような`Service`というクラスが定義されている。その中では`Update`, `GetPosition`という先ほどRPCとして定義したものが、純粋仮想関数として宣言されている。
他にも色々なクラスが宣言されているが、今回の用途では`Service`のみ使えばよい。
（もしclientも作りたい場合は`static std::unique_ptr<Stub> NewStub(...)`も使うことになる）

```cpp:random_walker.grpc.pb.h
namespace random_walker {
    class Service : public ::grpc::Service {
   public:
    Service();
    virtual ~Service();
    virtual ::grpc::Status Update(::grpc::ServerContext* context, const ::random_walker::NumSteps* request, ::random_walker::Empty* response);
    virtual ::grpc::Status GetPosition(::grpc::ServerContext* context, const ::random_walker::Empty* request, ::random_walker::Position* response);
  };
```

## Serviceクラスを継承して、サーバーの処理を実装する

最小限のコードは以下の通りになる。

```cpp:server.cc
#include <iostream>
#include <cstdlib>
#include <string>

#include <grpc/grpc.h>
#include <grpcpp/server.h>
#include <grpcpp/server_builder.h>
#include <grpcpp/server_context.h>
#include <grpcpp/security/server_credentials.h>

#include "random_walker.grpc.pb.h"

class RandomWalkerImpl : public random_walker::MyRandomWalker::Service {
  public:
  RandomWalkerImpl() : random_walker::MyRandomWalker::Service() { pos = 0; }
  ::grpc::Status Update(::grpc::ServerContext* context, const ::random_walker::NumSteps* request, ::random_walker::Empty* response) {
    int32_t n = request->n();
    for(int32_t i=0; i<n; i++) {
      if( rand() % 2 == 0 ) { pos += 1; }
      else { pos -= 1; }
    }
    return grpc::Status::OK;
  }
  ::grpc::Status GetPosition(::grpc::ServerContext* context, const ::random_walker::Empty* request, ::random_walker::Position* response) {
    response->set_x(pos);
    return grpc::Status::OK;
  }
  private:
  int pos;
};

int main(int argc, char** argv) {

  std::string server_address("0.0.0.0:50051");
  RandomWalkerImpl service;

  grpc::ServerBuilder builder;
  builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
  builder.RegisterService(&service);
  std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
  std::cout << "Server listening on " << server_address << std::endl;
  server->Wait();

  return 0;
}
```

- Serverの処理の実装を`RandomWalkerImpl`クラスで定義。このクラスは先ほど生成された`Service`クラスを継承している。
- RandomWalkerImplクラスの仮想関数を定義。ここでは`Update`, `GetPosition`の２つを返す。
    - `request`にクライアントからのリクエストが入っている。`response`にクライアントに返すレスポンスをセットする。
    - 返り値は`grpc::Status::OK`
- main関数でserverを起動する。
    - この実装はほぼ定型句。そのままコピーすれば良い。

コンパイルコマンドは以下の通り。

```shell-session
$ g++ -std=c++11 `pkg-config --cflags protobuf grpc` -c -o random_walker.pb.o random_walker.pb.cc
$ g++ -std=c++11 `pkg-config --cflags protobuf grpc` -c -o random_walker.grpc.pb.o random_walker.grpc.pb.cc
$ g++ -std=c++11 `pkg-config --cflags protobuf grpc` -c -o server.o server.cc
$ g++ random_walker.grpc.pb.o random_walker.pb.o server.o -L/usr/local/lib `pkg-config --libs protobuf grpc++` -lgrpc++_reflection -ldl -o server
```

これでビルドすればserver側の実装は完了。

## .protoファイルからPythonのファイルのクライアントのコードを生成する

```shell-session
$ python -m grpc_tools.protoc -I../proto --python_out=. --grpc_python_out=. ../proto/random_walker.proto
```

これを実行すると"random_walker_pb2.py"と"random_walker_pb2_grpc.py"の２つのファイルが作られる。
前者はprotoファイル内の`message`で宣言されたクラス、後者は`rpc`で宣言されたクラスが作られる。
この中でclientを実装するために使うクラスは`MyRandomWalkerStub`

## クライアントの実装

最小限のクライアント実装は以下のようになる。

```py3:client.py
import grpc

import random_walker_pb2
import random_walker_pb2_grpc

def run():
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = random_walker_pb2_grpc.MyRandomWalkerStub(channel)
        stub.Update( random_walker_pb2.NumSteps(n=5) )
        res = stub.GetPosition(random_walker_pb2.Empty())
        print(res.x)

if __name__ == '__main__':
    run()
```

`MyRandomWalkerStub`クラスのインスタンスを初期化する。stubインスタンスは`GetPosition`, `Update`などのrpcで定義したメソッドを持っている。
あとは通常のメソッドコールのように使える。

これを使えばPythonのREPLから対話的にC++アプリを制御できる。

# 参考

- https://grpc.io/docs/tutorials/basic/c.html
- https://grpc.io/docs/tutorials/basic/python.html