Does ComLight work for .NET client and 3rd party unmanaged server with COM compatible interfaces?
Hi, you pointed me towards your project in a github discussion about use-cases for cross-platform COM interop.
I am wondering if ComLight will fix the following scenario: I am building a .NET5 library for working with the VST3 (Steinberg) interface specification. The VST3 C++ plugin interface specification is for working with Audio/MIDI plugins inside a Digital Audio Workstation (DAW) - (usually) a multitrack recording application. The goal of my library is to make it possible to write VST3 plugins (and hosts) in .NET without having to know C++. The VST3 C++ interfaces are all COM compatible (derive from IUnknown, return HResult) and so I am looking for an easy way to do the marshalling.
The question is: will ComLight (.NET client) work if I have no control over the unmanaged COM interfaces?
@obiwanjacobi The short answer, it might.
I have not tested that way. Only tested the opposite way, consuming ComLight-based native DLLs with Microsoft’s COM interop in .NET Framework 4, and it did indeed worked.
You should try and see what happens.
Your (or rather Steinberg’s) COM objects may or may not support multithreading.
If they define custom HRESULT values and you want nice exception messages for these codes, use [CustomConventions] attribute, example: https://github.com/Const-me/Vrmac/blob/master/VrmacInterop/API/iGraphicsEngine.cs#L9 https://github.com/Const-me/Vrmac/blob/master/VrmacInterop/Utils/Errors/NativeErrorMessages.cs#L7-L9
Don’t use streams marshalling feature of the library. That’s the only part which introduces non-standard COM interfaces to the C++ side of the interop, iReadStream / iWriteStream: https://github.com/Const-me/ComLightInterop/blob/master/ComLightLib/streams.h
If they using C++ exceptions in addition to HRESULT, you’re out of luck, it won’t work because C++ compilers neglected to standardize that part of their ABI. The only way to handle C++ exceptions, catch them in C++ code built with the same or compatible compiler.
If they are passing COM objects to other COM objects, you should use concrete COM interface types there. System.Object probably won’t work. If their objects implement multiple interfaces you wanna call, mark both with [ComInterface] and use this class to cast: https://github.com/Const-me/ComLightInterop/blob/master/ComLight/ComLightCast.cs
P.S. If you’re porting that to Linux, don’t forget the long type in C is 8 bytes when building for 64-bit Linux. size_t and ptrditt_t types are easy to marshal, just use IntPtr on the C# side, but the long is harder if you want that thing to work across platforms.
I do think the Steinberg unmanaged COM interfaces are truly COM compatible - no custom HResults etc. I did see they use a VARIANT somewhere - I understand ComLight does not support that (yet?)...
Is there any way to check if ComLight is actually being used (and not the default CCW/RCW)?
I only removed [ComImport] from my annotated interface decl...
Here's my test project that seems to work on Windows (not on Linux yet, but that is probably a DNNE problem)...
The IPluginFactory interface is the only COM interface in this test project.
Edit: Another question I had was: Does ComLight provide any means to get an IntPtr that can be return to unmanaged code from a managed object instance (or interface)? Similar to Marshal.GetComInterfaceForObject - which is Windows-only.
no custom HResults etc
Custom HRESULTs are truly COM compatible, they are well specified: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/0642cb2f-2075-4469-918c-4441e69c548a
I did see they use a VARIANT somewhere - I understand ComLight does not support that (yet?)
Variant has 2 pieces, the structure and the memory manager for BSTR and SAFEARRAY subtypes. You can surely port the structure by writing a matching struct in C#, will work fine for primitive types like VT_UI4. Should also work fine for use cases when you don’t need to move ownership of the variant across languages. It’s impossible to port the memory manager I’m afraid, Linux doesn’t have ole32.dll with these SafeArrayDestroy and the rest of them.
I only removed [ComImport] from my annotated interface decl
It’s not used. You must use other annotations, and UnmanagedType.CustomMarshaler in the DLL imported methods who are creating the C++-implemented COM objects. See the demo: https://github.com/Const-me/ComLightInterop/blob/master/Demos/HelloWorldCS/HelloWorld.cs
Similar to Marshal.GetComInterfaceForObject
Normally happens automatically, when you’re passing C#-implemented objects into C++ implemented COM methods. One manual way is this static method: https://github.com/Const-me/ComLightInterop/blob/master/ComLight/ManagedWrapper.cs#L178-L187
If you next question gonna be about the other way: https://github.com/Const-me/ComLightInterop/blob/master/ComLight/NativeWrapper.cs#L117-L123 but it should also happen automatically as needed. Unless you’re using IntPtr arguments to pass COM object pointers.
So if I understand correctly I need to tag the ComLight custom marshaler at the point where the interface is 'exposed'. In my case that is an exported unmanaged method (by DDNE) that only support marshaling blitable types - 'IntPtr' in this case.
So where do I put my custom marshaler attribute?
@obiwanjacobi The code you have linked should work. Couple things.
-
you only need
addRef: trueif the C++ code calls Release() 1 more time than AddRef/QueryInterface. If it doesn’t call either of them, or calls them equal count of times, passfalsethere. -
Setting proper marshalling direction saves some CPU time. If that interface is only implemented in C#, pass
ToNativedirection in the custom attribute.