Using Native COM Servers from .NET

[This documentation is for preview only, and is subject to change in later releases. Blank topics are included as placeholders.]

This section describes the available options for using existing COM components from .NET applications and outlines the advantages and disadvantages of each approach. Generally, the recommended method is C++ Interop.

Using TLBIMP

The Windows Software Development Kit (SDK) Tlbimp.exe (Type Library Importer) is a tool that exposes a COM type library as an assembly called an interop assembly. This assembly defines managed equivalents, or wrappers, for each COM interface in a given type library.

When methods of the interop assembly are called, a managed-to-unmanaged transition is executed and control is passed to the COM component. Likewise, when the unmanaged COM function returns, an unmanaged-to-managed transition is executed. By default, the COM HRESULT is checked for failure, and an exception is thrown if the HRESULT does not indicate success. Similarly, COM component initialization and interface queries are performed by the interop assembly and therefore hidden from the calling code.

Interop assemblies do not replace the COM components they represent; the unmanaged COM functions remain in the COM component, so the component must be installed and registered on target computers or calls to the interop assembly fail.

Using Tlbimp is the easiest way to use a COM component from managed code, but there are some serious disadvantages, especially for large and/or complex COM interfaces. These disadvantages are:

  • Tlbimp generates managed interfaces for every COM interface in the type library. There is no way to suppress this behavior, so the resulting assemblies can be very large. (For example, the Tlbimp-generated interop assembly for Mshtml.dll is more than 8 MB.) There is also no way to hide an interface that is meant only for use within the COM component.

  • Tlbimp supports a limited number of data types. Usually unsupported types are imported into the managed world as the generic, non type-safe IntPtr type, requiring error-prone and tedious marshaling code to use the assembly, but sometimes Tlbimp.exe cannot expose members of an interface at all.

  • Tlbimp generates a separate interop assembly, which it must be deployed with the final application.

If these disadvantages are acceptable, see How to: Use Native COM Servers with TLBIMP for an example.

Modifying MSIL

The disadvantages of Tlbimp can be mitigated somewhat by disassembling the interop assembly — using the Ildasm.exe (MSIL Disassembler) — editing the MSIL to remove unnecessary interface definitions and replace argument types, and then reassembling the MSIL with the Ilasm.exe (MSIL Assembler). This process is error-prone and requires knowledge of MSIL, unmanaged types, and .NET types. Furthermore, this has to be done again if the COM interfaces are updated.

C++ Interop

The disadvantages of Tlbimp — and of editing MSIL— can be avoided entirely in Visual C++ because, unlike Visual Basic and C#, Visual C++ can use COM objects directly using the usual COM mechanisms (such as CoCreateInstance and QueryInterface). This is possible due to C++ Interop features that cause the compiler to automatically insert the transition code to move from managed to unmanaged functions and back again.

Using C++ Interop, COM components can be used as they are normally used or they can be wrapped inside C++ classes. These wrapper classes are called custom runtime callable wrappers, or CRCWs, and they have two advantages over using COM directly in application code:

  • The resulting class can be used from languages other than Visual C++.

  • The details of the COM interface can be hidden from the managed client code. .NET data types can be used in place of native types, and the details of data marshaling can be performed transparently inside the CRCW.

Using Visual C++ to wrap COM interfaces is demonstrated in How to: Use Native COM Servers with CRCWs.

Regardless of whether COM is used directly or through a CRCW, argument types other than simple, blittable types must be marshaled. For information about data marshaling, see Using C++ Interop (Implicit PInvoke).

Note

MFC applications must be initialized as single threaded apartment (STA). If you call CoInitializeEx in your InitInstance override, specify COINIT_APARTMENTTHREADED (rather than COINIT_MULTITHREADED). For more information, see PRB: MFC Application Stops Responding When You Initialize the Application as a Multithreaded Apartment (828643) at https://support.microsoft.com/default.aspx?scid=kb;en-us;828643.

See Also

Other Resources

Native and .NET Interoperability