DescriptionPool / DynamicMessages in C# library

1,139 views
Skip to first unread message

Benjamin Krämer

unread,
Mar 8, 2016, 5:39:28 PM3/8/16
to Protocol Buffers
I have ported the MessageDifferencer from C++ to C# since I need to look for changes in various protos. The only thing missing is the unpacking of the Any messages.

Therefore I would like to get a descriptor for which I know the name and which is also compiled into the application. In C++, I can use the DynamicMessageFactory or look it up with DescriptorPool::generated_pool(). It seems, like there is no generated or central pool in C#. Each FileDescriptor seems to have it's own DescriptorPool containing only it's own descriptor with it's dependencies. Also the DependencyPool in C# is internal and the only way to access it seems to use the FindTypeByName method of the FileDescriptor.

Is it somehow possible to get the descriptor in C# or do I have to aggregate the DescriptorPool by myself?

Jon Skeet

unread,
Mar 9, 2016, 7:59:32 AM3/9/16
to Protocol Buffers
On Tuesday, 8 March 2016 22:39:28 UTC, Benjamin Krämer wrote:
I have ported the MessageDifferencer from C++ to C# since I need to look for changes in various protos. The only thing missing is the unpacking of the Any messages.

Therefore I would like to get a descriptor for which I know the name and which is also compiled into the application. In C++, I can use the DynamicMessageFactory or look it up with DescriptorPool::generated_pool(). It seems, like there is no generated or central pool in C#.

Indeed there isn't. It's not clear to me at what point messages would get registered - after all, a new assembly with extra messages could be loaded at any point.
 
Each FileDescriptor seems to have it's own DescriptorPool containing only it's own descriptor with it's dependencies. Also the DependencyPool in C# is internal and the only way to access it seems to use the FindTypeByName method of the FileDescriptor.

Is it somehow possible to get the descriptor in C# or do I have to aggregate the DescriptorPool by myself?

If you mean the descriptor proto - no, that's deliberately internal as it's a proto2 message; we know we don't modify it within the Google.Protobuf library, and have investigated all the differences in behaviour - basically we treat it as a proto3 message, with care. We definitely don't want to expose that to callers.

Jon

Benjamin Krämer

unread,
Mar 9, 2016, 12:15:23 PM3/9/16
to Protocol Buffers
Hi Jon,


Am Mittwoch, 9. März 2016 13:59:32 UTC+1 schrieb Jon Skeet:
Indeed there isn't. It's not clear to me at what point messages would get registered - after all, a new assembly with extra messages could be loaded at any point.

I'm not completely up to date on module initialization in CLR. Is there still no native language support for it? I only know this 7 year old workaround, but this is done after building: http://einaregilsson.com/module-initializers-in-csharp/ Also I just found this post about a nuget package: http://geertvanhorrik.com/2013/06/28/assembly-constructors-and-initializers-using-c/ Also not a good solution for including it in a library... Would be a bit sad if Microsoft still offers no support to access this CLR feature. I understand that static constructors are only called when the type is referenced for the first time.

Another possible (also if not as performant) option I successfully tried was reflection. https://github.com/Falco20019/protobuf/commit/74e5a82593787610f2207423bf3a8a8449a78813 By introducing a common interface, you can look them up in the AppDomain. Here is the test class I used to generate a TypeRegistry from all currently loaded assemblies. This is not cached but calculated on request since new assemblies can be introduced as you said.

public static TypeRegistry GetTypeRegistry()
{
   
List<FileDescriptor> descriptors = new List<FileDescriptor>();
   
AppDomain.CurrentDomain.GetAssemblies()
       
.SelectMany(s => s.GetTypes())
       
.Where(p => typeof(IReflection).IsAssignableFrom(p) && !p.IsInterface)
       
.ToList()
       
.ForEach(type =>
       
{
           
var pi = type.GetProperty("Descriptor", BindingFlags.Public | BindingFlags.Static);
           
var value = pi.GetValue(null);
            descriptors
.Add((FileDescriptor)value);
       
});

   
return TypeRegistry.FromFiles(descriptors);
}

Calling GetValue on the property initializes the type and returns the descriptor. If you only want to call your static constructor for all classes, you could use
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(type.TypeHandle);
This is better than invoking TypeInitializer which leads to the constructor being called multiple times (as you also wrote on StackOverflow 6 years ago :) ).

I also thought about just using IMessage for the lookup and use Descriptor to initialize the TypeRegistry with FromMessages, but this would finds a lot more types which would make the reflection part slower.

Jon Skeet

unread,
Mar 10, 2016, 8:06:30 AM3/10/16
to Protocol Buffers
Yes, that approach looks like it would handle all the currently-loaded assemblies. I'm not sure that it would help with assemblies which were referenced but hadn't yet been loaded.

Ultimately, it depends on what this is for - personally I'd expect that in most cases it would be fine just to explicitly state up-front which descriptors (or at least assemblies) were involved.

Jon
Reply all
Reply to author
Forward
0 new messages