This documentation is archived and is not being maintained.

Accessing COM Objects from the Runtime

Visual Studio .NET 2003

With the .NET Framework Type Library Importer tool (Tlbimp.exe), you can usually convert type definitions found in a COM type library into runtime metadata for equivalent types in a common language runtime assembly. It may not be possible to do this if the COM object's interface does not follow the COM rules.

Wrapping with a Runtime Callable Wrapper

When a .NET Framework client activates a COM object, the common language runtime generates a Runtime Callable Wrapper (RCW) as described in the .NET Framework Developer's Guide.

A RCW wraps a COM object for a managed client.

The wrapper is a managed proxy class for the COM object that provides data marshaling. For example, if a COM method returns a result with type BSTR, the RCW would convert it to type String *. This provides a way to reuse COM objects in a .NET Framework project. The topic Marshaling Data with COM Interop in the .NET Framework Developer's Guide has more details on data marshaling between COM and .NET Framework types.

Wrapping with Managed Extensions

However, using Tlbimp.exe may not be the best approach for some applications. It may not work on automation-compliant COM objects whose interfaces do not follow the COM rules strictly. It may also not work for more general forms of COM objects that have custom marshalers.

You may want to customize the interfaces exposed to the runtime instead of the one the RCW provides. A customized wrapper can yield a richer object model, and better marshaling performance.

Managed Extensions for C++ enables you to write a wrapper class for an underlying native C++ class. A .NET Framework client can then reuse the functionality of the COM object via the wrapper. You can customize the wrapper more directly and with more control than modifying an assembly generated by the Type Library Importer (Tlbimp.exe) generated with interop attributes. The topic Customizing Standard Wrappers in the .NET Framework Developer's Guide discusses the use of interop attributes.

Wrapping the underlying object enables you to decide which members of the class to wrap, and makes the overhead of the COM interface, RCW, and data marshaling between them unnecessary.

A Managed Extensions class wraps the underlying COM object for a managed client.

JrnlPost: An Example of Wrapping with Managed Extensions

The Managed Extensions sample JrnlPost illustrates using Managed Extensions to write a wrapper class for an underlying COM object. In this sample, ComJEPost is a COM object implemented in native C++. It provides a COM interface to the native C++ class called JEPost. The class netJEPost is a Managed Extensions class that directly wraps JEPost.

The declaration of netJEPost contains a native pointer pJEpost to an instance of the native C++ class JEPost.

public __gc class netJEPost {
   JE::JEPost* pJEpost;   // pointer to unmanaged business logic class.
   BOOL OpenTransaction(String* strDescr);
   BOOL AddEntry(String* strGLAccount, float fAmt);
   BOOL Verify();
   BOOL Commit();
   void Abort();

The constructor for netJEPost wraps the constructor of the native class:

netJEPost::netJEPost() {
   pJEpost = new JE::JEPost;   // create an instance of our business logic class

The underlying native C++ class JEPost contains a member function defined as follows:

BOOL JEPost::AddEntry(const wchar_t *wcszGLAccount, float fAmt) {
      // is the account number too long?
      if ( (!wcszGLAccount) || ( (wcslen(wcszGLAccount) + 1) > ACCOUNTSZ) ) {
         ::MessageBox(NULL, "Invalid GL Account number!", "AddEntry Error", MB_OK);
         return FALSE;      // yes, early out
      if (this->bTransactionIsOpen == FALSE) {   // has the transaction been opened?
         ::MessageBox(NULL, "Must open transaction first!", "AddEntry Error", MB_OK);
         return FALSE;                        // no, early out
      else {                                 // yes, add the entry
         wcscpy(JournalEntries[nNumEntries].wcszGLAccount, wcszGLAccount);
         JournalEntries[nNumEntries].fAmount = fAmt;
         return TRUE;

The AddEntry member function is wrapped in netJEPost by the following member function of the __gc class netJEPost. Note that the native wchar_t * type is marshaled as a String *, and that the native pointer pJEpost is used to access the corresponding member function AddEntry of the native class instance of JEPost.

BOOL netJEPost::AddEntry(String* strGLAccount, float fAmt) {
   // Convert the managed string that comes from our managed clients to an unmanaged
   // wchar_t * that will be passed to our native class.
   System::IntPtr intptrGLAccount = 
   // Call the AddEntry method on our instantiation of the native business 
   // logic class.
   BOOL bRet = pJEpost->AddEntry((wchar_t*) intptrGLAccount.ToPointer(), fAmt);
   // When we converted strDescr, memory was allocated by the StringToCoTaskMemAuto
   // function. We now call FreeHGlobal to free the allocated memory.
   return bRet;

For more details on wrapping native C++ classes, see Migration Guide Part I: Introduction to Wrapping C++ Classes.