0

I have a proto3 gRPC service with these methods:

syntax = "proto3";

Service MyService
{
    rpc Foo (FooRequest) returns (FooResponse) {}
    rpc Bar (BarRequest) returns (BarResponse) {}
   // etc.
}    

I would like to access these methods dynamically in my C++ application, e.g.

google::protobuf::ServiceDescriptor serviceDescriptor;
google::protobuf::MethodDescriptor* myMethod = serviceDescriptor.FindMethodByName("Foo");

It is trivial to get a message descriptor using google::protobuf::MessageDescriptor = myMessage.GetDescriptor();, but there doesn't seem to be an equivalent method for a service.

How do I get a ServiceDescriptor for a Service in C++?

My instinct is that getting the ServiceDescriptor is required to get the list of gRPC methods, but if there is another way of accomplishing this I would also be interested.

Harry Williams
  • 310
  • 3
  • 11
  • Yes `ServiceDescriptor` gets you `MethodDescriptors` but you'll need to start from the root `FileDescriptor`(?). I've not done this in Java but Golang('s new Protobuf) SDK has a useful `protoreflect` module that I've used to do similar. See: https://pkg.go.dev/google.golang.org/protobuf/reflect/protoreflect#ServiceDescriptor. There appears to be a Java equivalent: https://javadoc.io/static/io.grpc/grpc-services/1.15.1/io/grpc/protobuf/services/ProtoReflectionService.html – DazWilkin Jul 22 '21 at 03:21

2 Answers2

2

If you are statically linking your Protobuf generated source file (e.g. MyService.pb.cc) you should be able to get a ServiceDescriptor from the generated_pool DescriptorPool.

#include <google/protobuf/descriptor.h>
int main(int argc, char** argv)
{
  const google::protobuf::DescriptorPool* p = google::protobuf::DescriptorPool::generated_pool();
  const google::protobuf::ServiceDescriptor* serviceDescriptor = p->FindServiceByName("MyService");
  const google::protobuf::MethodDescriptor* method = serviceDescriptor->FindMethodByName("Foo");
}

See also Protobuf message object creation by name

To get a full list of methods of a service, you can use int ServiceDescriptor::method_count() const and const MethodDescriptor* ServiceDescriptor::method(int index) const.

Stefan v.K.
  • 356
  • 2
  • 8
0

By using the trick of adding an Empty message before your service, you could get a FileDescriptor and then access the services. This is described in this part of the doc.

message MyMessage {}

service MyService {
    rpc MyMethod(google.protobuf.Empty) returns (google.protobuf.Empty) {}
}

and then in C++ you would do:

MyMessage msg;
const FileDescriptor *desc = msg.descriptor()->file();

for (int i = 0; i < desc->service_count(); ++i) {
   for (int j = 0; j < desc->service(i)->method_count(); ++j) {
      std::cout << desc->service(i)->method(j)->name() << std::endl;
   }
}

However, this is obviously a trick and shouldn't be used in production. I'm still searching for a better answer and I'll update this answer over time.

Update

Another solution you might have is to define a DescriptorPool. This is a more advanced solution but its apparently the recommended one.

auto st = new DiskSourceTree;

// add path(s) where the proto files are
st->MapPath("", "PATH_FOR_YOUR_PROTOS");
st->MapPath("", "PROTOBUF_INSTALL_PATH/include/"); // needed if you want to use Well Known Types

auto db = new SourceTreeDescriptorDatabase(st);
auto dp = std::make_unique<DescriptorPool>(new DescriptorPool(db));
FileDescriptor desc = dp->FindFileByName("your_file.proto");

After that you will be able to use desc in the same way as described in the previous version of my answer (the for loops part).

PATH_FOR_YOUR_PROTOS might be a directory or a file and the PROTOBUF_INSTALL_PATH is generally where you installed gRPC. This PROTOBUF_INSTALL_PATH should have an include directory in which you can find a google.

I highly recommend checking the importer.h documentation and the descriptor.h one

Clément Jean
  • 1,735
  • 1
  • 14
  • 32