Thursday, December 20, 2007

Can't pass structs between an MFC COM object and .NET/C#

A subset of MFC is designed for COM.  Much of this support is in the form of MACROS (e.g., OLECREATE, DISP_FUNCTION, etc...).  After struggling trying to pass a structure back and forth between a C# client and an MFC COM server I gave up and decided to try with an ATL COM Server.

 

ATL was designed for creating "small, fast COM components".  As it turns out, passing structures (and pretty much everything else) is much easier with ATL than it is with MFC COM.  ATL takes a different approach to supporting COM (mainly via templates).

 

It seems to me that MFC COM is only designed to work with VARIANT compatible data types (perhaps because it was targeted towards interoperability with VB).  The MFC  "Add a method" wizard won't even allow you to specify a parameter or return type that isn't VARIANT compatible.  This restriction is further implemented in the limitation on the parameter and return types that can be specified via the DISP_FUNCTION (or DISP_FUNCTION_ID) macros.

 

The .NET Interop Marshaler has built-in support for some of these (e.g., dates) but does not support VARIANTs of type VT_RECORD (see this kb article).  To store a structure in a VARIANT requires a VARIANT of type VT_RECORD.  Ergo, you can't pass structures back and forth between C# and an MFC COM Server.

 

See this excellent article (CLR Inside Out Introduction to COM Interop) detailing how to pass a structure back and forth between C# and COM Server written with ATL.

 

The IDL uses the same parameter types as the C++ implementation; e.g., if you have a function that takes a struct MyUDT *ptr then that method's signature in the IDL will take a struct MyUDT *ptr.  So, from C++ to IDL to C# we have:

 

C++ .h

STDMETHOD(MyFunc)(struct MyUDT *ptr);

 

C++ .cpp

STDMETHODIMP MyCOMServer::MyFunc(struct MyUDT *ptr) { ... }

 

IDL struct definition

same as in C++, e.g., struct MyUDT { int x; int y; }

 

IDL interface method declaration

[id(1), helpstring("method MyFunc")] HRESULT MyFunc(struct MyUDT *ptr);

 

C#

MyUDT udt = new MyUDT();

udt.x = 1;

udt.y = 2;

MyCOMServerClass sc = new MyCOMServerClass();

sc.MyFunc(ref udt);

 

If the structure is entirely made up of blitabble types then you don't even have the overhead of copying; the Interop Marshaler will just pin it.  If not then you will need to apply the [in, out] attribute to the parameter in the IDL so that the Interop Marshaler copies the unmanaged representation back to the managed representation when the method call ends (so that you can see any changes).

 

Any changes made to the struct in the unmanaged code will be visible in the managed code.

 

No comments :

Post a Comment