
| Last Modification: December 08, 2000 |
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:
struct Data
{
DWORD nData1;
float fltData2;
};
struct MyStruct
{
int nCount;
[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:
struct Node
{
struct Data data;
[unique] struct Node *pNext;
};
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:
struct Node
{
struct Data data;
[ptr] struct Node *pNext;
[ptr] struct Node *pNext;
};
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)
...
#pragma pack(pop)
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!