HOWTO: Use structs in COM interfaces when Automation compatibility is not an issue
You can use any IDL type for the struct fields. You don't need to associate a GUID with the struct. Structs can be nested. About the only issue is using data pointers within the structs. When a struct is marshaled, all embedded pointers have to be marshaled as well. This amounts to copying the target locations of those pointers in the marshaling packet. Therefore, all pointers must be annotated, especially if they point to arrays. If an embedded pointer is not annotated with one of the three pointer types (ref, unique, and ptr), the pointer_default() attribute of the interface the struct is used in determines the type of the pointer. Since different interfaces can potentially use different default embedded pointer types, it is generally a good idea to always annotate the struct's embedded pointers. Here's an example:
[unique, size_is(nCount)] struct Data *aData;
When passing structs one should be careful what gets transmitted in the marshaling packet. In the above example the whole array of Data structs is transferred. This is expected and well annotated. However, there are bad struct designs as well. Consider the following single-linked list:
When the head of the list is passed, all elements in the list are replicated in the marshaling packet. Such architecture needs to be redesigned so the complete list does not get transferred on each method call (a possible design is to hide the list itself behind an object and expose methods on the object's interface for manipulating the list).
Rarely, but sometimes nevertheless, pointer aliasing has to be considered as well. In the examples so far no two pointers could point to the same memory address. However, consider this doubly- linked list:
True, this is a bad design, but nonetheless without the pointer type set to [ptr], the result would be disastrous.
When a struct is an [in] argument, it doesn't matter how the memory pointed to by the embedded pointers is allocated. However, for [out] and [in, out] arguments all memory pointed to by the embedded pointers of a struct must be allocated via the COM memory allocator (CoTaskMemAlloc). This is necessary so that the marshaling code can deallocate the data at the callee and reallocate it at the caller.
A final point: IDL doesn't define the struct packing byte alignment. The default for the VC compiler is 8. However, it is safer to manually inject a #pragma pack statement:
#pragma pack(push, 8)
If you manually set the byte alignment, don't use #import for importing the struct definition from a type library. It adds #pragma pack(push, 8) at the beginning of the generated TLH file. Your pragma is not persisted in the generated type library. Generally, it is not advised to use #import for non-Automation-compatible interfaces.
IMPORTANT: since your interfaces are not Automation-compatible, you have to build and register the proxy/stub DLL for their marshaling support!
HOWTO: Use structs in Automation-compatible interfaces
Structs, also known as User Defined Types (UDTs), can be used in Automation- compatible interfaces since Win NT4 SP4 and DCOM 1.2 for Win9x. An Automation- compatible struct may contain only primitive Automation types as its members. Nesting structs is not allowed, but VARIANT is allowed thus enabling nested structs (you can store a struct in a VARIANT). In order for a struct to be usable for Automation-compatible interfaces, the struct must be described in a type library and it must be declared with its own GUID:
[helpstring("A long value")]
// Later in the IDL file
[uuid(...), version(...), helpstring(...)]
For unknown reason, the MIDL compiler bundled with VC6 has not been updated even in SP4. Therefore, you need the latest Platform SDK installed on your computer (or at least no older than Platform SDK Jul'99) in order to compile the above IDL code and any IDL using structs with Automation.
NOTE: The current release of MIDL at the time of writing (5.03.0280) does not support the helpstring() attribute on the struct itself. helpstring() is supported on the struct fields, though.
Once your struct is declared as above, you can pass it via Automation- compatible interfaces. Keep in mind, however, that only struct pointers can be passed:
Neither IDL nor Automation define byte alignment for a struct. VB assumes 4-byte alignment, while #import in VC assumes 8-byte alignment. For most Automation structs this isn't an issue. However, if you use 8-byte types like double, this may make a difference:
For VB, the double field starts at the fourth byte of the struct, while for VC's #import it starts at the eight byte. This poses a significant problem. It can be solved by adding an inject_statement clause to #import:
IMPORTANT: Structs cannot be used by scripting clients!
Implementing structs in code is usually trivial - just use the struct directly. There are two specific struct uses when the code is fairly non-trivial - when using a safe array of structs and when stuffing the struct into a VARIANT. Let's note first that in Automation any struct is represented via an IRecordInfo interface pointer. The object exposing this interface is implemented by the Automation runtime and is acquired via the following two API functions:
When structs are used in say dual interfaces, this detail is hidden from the implementer and the user of the interface. Only the marshaler knows and uses it. However, when we need to pass structs in a VARIANT or a safe array, we have to delve into these details ourselves. Here's the C++ code for populating a VARIANT with a struct (error checking omitted):
Note that the VARIANT doesn't own the storage for the embedded struct, but VariantClear will free the content of the struct by calling IRecordInfo::RecordClear. Note also that the common VARIANT wrapper classes like CComVariant and _variant_t don't support structs. Finally, note that the mechanism for storing struct data in a VARIANT is not fully documented. For example it isn't clear how a struct can be returned in an [out] VARIANT argument without producing memory leaks.
Now let's see how to populate a safe array with structs. The following code is a trimmed down version of the MSDN example code (error checking omitted):
Unlike a VARIANT, a safe array owns the storage of its structs.
The UDT documentation is located at: mk:@MSITStore:D:\MSDN\Automat.Chm::/htm/chap12_3rcj.htm (Copy the link and paste it in the "Jump to URL..." box. Substitute the path with the path to your MSDN files. This link is obtained from MSDN Library Oct'2000.)