If you are the author of a COM component that is going to be used by .NET clients , you can design (or redesign) your component so that it interoperates more smoothly with .NET. This section provides a list of guidelines to help you write components with good interoperability. Ive divided the guidelines into five subjects: interfaces, data, error reporting, type libraries, and other considerations.
Avoid manually redefining COM interfaces in managed code. This task consumes time and rarely produces a managed interface compatible with the existing COM interface. Instead, use TlbImp.exe to maintain definition compatibility.
Avoid defining methods in a coclasss default interface that have the same names as members of System.Object (for example, Object , Equals , Finalize , GetHashCode , GetType , MemberwiseClone , and ToString ). Methods on the default interface of a component are added to the wrapper class, and if any name conflicts occur, the imported method will override the System.Object base class method.
Provide ways to explicitly release resources. Many COM components release resources when their reference count drops to zero, which occurs at the final call to Release . When used from .NET, however, the RCW holds a reference on the COM object it is managing and typically will not release that reference until it is garbage-collected . The .NET nondeterministic finalization mechanism prevents you from predicting exactly when this will be, so if it is important that resources are freed at a particular point, provide an explicit means to do so.
Avoid optional parameters in methods because C# cannot treat them as optional.
Use dual interfaces whenever possible because this allows early or late binding from .NET clients.
Use Automation-compatible types where possible. Avoid Variants, asking yourself why you arent using a more specific type.
Avoid using void* pointers to refer to interfaces. If an interface method can return a pointer to any interface, it is very common to use a void* pointer to accomplish this:
The second parameter will be marshaled as an IntPtr , which is not descriptive or easy to use. It would be better to amend the IDL to use IUnknown* instead and to specify [retval] :
HRESULTGetAnInterfacePointer([in]REFIIDtheInterfaceId, [out,retval,iid_is(theInterfaceId)]IUnknown**pTheInter face);
Now the second parameter will be marshaled as a Guid , which can then be cast to the appropriate interface type by the client.
Use blittable types (which have direct managed equivalents) where possible. Nonblittable types will require conversion during marshaling, and therefore will not perform as well as blittable types.
Avoid ANSI strings if possible because it is much more efficient to marshal Unicode string data.
Use SAFEARRAY s instead of variable-length arrays because of the problems with marshaling IDL arrays explained earlier. Also, wherever possible, use single-dimension arrays with a lower bound of zero. For more details, see the discussion earlier in the chapter in the section Converting Arrays.
Dont define structs with SAFEARRAY fields because the importer doesnt support them.
Avoid returning failure HRESULT s for informational purposes. HRESULT s are mapped to exceptions so that when an interop assembly receives a failure HRESULT from a component, it throws a corresponding HRESULT . There is an overhead associated with processing exceptions, but almost no overhead is incurred by the exception handling code when no exception occurs. For this reason, use HRESULT s only to signal errors. In addition, HRESULT s are processed by the interop assembly, but because successful HRESULT s wont result in an exception being thrown, you effectively lose the return value.
Always use rich error reporting from COM components, using the ISupportErrorInfo and IErrorInfo interfaces. Components written in C++ using ATL should have the ISupportErrorInfo check box checked on the Options page of the ATL Object Wizard, and should use the ATL CComModule::Error method to return error information using IErrorInfo . In Visual Basic 6 code, use the Error.Raise method with the optional Source , Description , HelpFile , and HelpContext fields:
'Raiseerror1000,withthesource "TheComponent" 'andthespecifiederrormessage. 'Thelasttwoparametersspecifyahelpfile 'andhelpcontextID Err.Raise1000,"TheComponent", "Therehasbeenanerror",_ helpfile.hlp,100
Provide and register type libraries, so that .NET programmers can use TlbImp to generate interop assemblies. Provide version and locale information in type libraries because this is propagated through to the interop assembly.
Avoid defining functions in IDL modules, since these are not imported when creating the interop assembly.
Implement IProvideClassInfo . Methods often contain interface pointers as arguments. The type library importer knows the type of the interface, but the type of the object that implements this interface might not be known. If a component implements IProvideClassInfo , the importer can query for type information; this enables it to wrap the interface pointer in a properly typed wrapper.
Make as few transitions across the managed/unmanaged boundary as possible because of the cost involved in this transition. This might mean redesigning the interface to your component to make calls more modular.