Sunday, June 17, 2007

UDT (User Defined Types) and COM

During one of my recent projects I came across a typical problem of passing user-defined types from my COM component. The problem I had was that I was using Attributed ATL (one which even Microsoft seems to have abandoned ;) ). I came up with the following way to expose UDT from COM. Beware though, passing UDT supporting IDispatch interface is a different ball-game altogether and scripting languages don't readily support UDT's.

The way to send UDT using IDispatch involves wrapping it in a VARIANT but again for scripting, its a no-no. Passing UDT as an array is slightly more complicated. I will write about these some time in the near future.
There are various good articles on the net about this, but here is a simple, to the point approach.
First Declare your type in your header file, like this
[ uuid("FD27CE79-9C08-4afa-AEB0-BB5B3E2F6DEA")]
struct MiscInformation
{
        BSTR Name;
        BSTR Status;
        BSTR Source;
        INT Priority;
};

Now you will have to declare the type in your IDL file. But, because its an attributed project, here is how you do it.

Create a new idl file in you project and name it, for example structures.idl. The make an entry in the IDL file as follows:
[ uuid("FD27CE79-9C08-4afa-AEB0-BB5B3E2F6DEA")]
struct MiscInformation
{
        BSTR Name;
        BSTR Status;
        BSTR Source;
        INT Priority;
}

[ version(1.0), uuid(5C6ADADA-1583-4DCB-8F14-4CF9F5BC048C), helpstring("Test UDT 1.0 Type Library") ]
library TestUDT
{
    struct MiscInformation;
}

Once this is done you have to include this IDL file in the generated IDL file. Here is how I do it. In my main .cpp file I insert the line [importidl] on top. Like this:
[importidl("structures.idl")];
// The module attribute causes DllMain, DllRegisterServer and DllUnregisterServer to be automatically implemented for you[ module(dll, uuid = "{5C6ADADA-1583-4DCB-8F14-4CF9F5BC048C}",
         name = "TestUDT",
         helpstring = "TestUDT 1.0 Type Library",
         resource_name = "IDR_TESTUDT")  ]


class CTestUDTModule
{
public:
// Override CAtlDllModuleT members
};


This takes care to compile the right Type Information.

Now to use this UDT simply do it like this, for example:
    [id(33), helpstring("method GetMiscInformation")] 
HRESULT GetMiscInformation([out,retval] MiscInformation* pMiscInfo);

For creating a VARIANT type for the structure, I use the following function:
    VARIANT GetVariantForStruct(REFGUID rGuidTypeInfo, void* data)
    {
        VARIANT vnt;
        IRecordInfo *pRI;
        HRESULT hr;
   
        hr = GetRecordInfoFromGuids(LIBID_TestUDT, 1, 0, 0x409, rGuidTypeInfo, &pRI);
        VariantInit(&vnt);
        vnt.vt = VT_RECORD;
        vnt.pvRecord = data;
        vnt.pRecInfo = pRI;
        pRI = NULL;

        return vnt;

    }


This can take care of wrapping structure in a variant. Of-course there are other things to talk about like passing UDT in a SAFEARRAY, but I will talk about them sometime in future.

Any suggestions/comments are welcome.

2 comments:

Unknown said...

Good article Jalal. If possible, try to put sample code. that would be great.
-Abhishek, Germany

Vijaya kumar said...

structure using i cant get uuid . how to get from idl and then pass the value to c# . i need clear explanation . thank you .