Writing a Managed Wrapper for COM Components

Environment: Win2000 SP2, VS6 SP 5, VS.Net Professional

Introduction

Up until couple month ago, I was a convinced spectator to the whole .NET Revolution. I thought to myself that like any other new technology it would take some time until it becomes mature enough to use. But as I was learning more about .NET it became clear that this was something bigger then any of the previous Microsoft attempt's to revolutionize the whole industry.

By now, I am sure, that for many companies the typical architecture looks like this: presentation layer in VB or ASP, business layer in C++ / ATL / COM that talks to the database using ADO or OLEDB. Our company is no exception. So being a Microsoft software-based company you would have to adapt this technology eventually. Since we've decided to switch to .NET we took a gradual approach, meaning that we began to design and build all the new code in the .Net environment. Soon we had to face the main question: what do you do with all that "old" code. Specifically, what do you do with all the COM components written in C++? After all, we spent so much time in writing them, stabilizing, and tuning the performance.

Fortunately, Microsoft put a lot of time and effort in making sure that managed code can talk to non-managed code. You can still call your existing COM components from .NET program using RCW (Runtime Callable Wrapper). RCW will take care of marshalling the .NET calls into COM client calls. You can do this with very little development time. The performance hit should also be minimal in most cases. If your COM component is "heavy", i.e. if it does a lot of work inside, then the overhead is insignificant.

On the other hand, if your COM component contains some "chatty interfaces" where all it does just returns some values, the overhead can be significant compared to the component execution time. On the top of that, CLRs (Common Language Runtime) default behavior is to use Proxy / Stub combination for calling COM components, so even if your COM component is apartment-threaded you still pay the penalty of marshaling your calls. You can overwrite the CLRs default behavior with STAThreadAttribute, but not for all cases.

Moreover, if you create many .NET clients for your COM component it generates more problems because your managed code clients cannot take the full advantage of the .NET Framework features like: parametirized constructros, inheritance, or static methods.

Thus, if you decide not to use RCW there are basically two options: one is to fully migrate your code to .Net, and second option that works with C++ COM components is to write a managed code wrapper around it. I will give you an example of how easy it is to use the second option.

Writing a Wrapper for COM Components

Suppose that you have the following COM component written in C++ using ATL.

// SimpleATL.h : Declaration of the CSimpleATL

#ifndef __SIMPLEATL_H_
#define __SIMPLEATL_H_

#include "resource.h"       // main symbols
#include 

///////////////////////////////////////////////////////////
// CSimpleATL
class ATL_NO_VTABLE CSimpleATL : 
  public CComObjectRootEx<CCOMSINGLETHREADMODEL>,
  public CComCoClass<CSIMPLEATL &CLSID_SimpleATL,>,
  public IDispatchImpl<ISIMPLEATL &LIBID_SIMPLECOMLib
                    &IID_ISimpleATL,,>
{
public:
  CSimpleATL()
  {
  }

DECLARE_REGISTRY_RESOURCEID(IDR_SIMPLEATL)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CSimpleATL)
  COM_INTERFACE_ENTRY(ISimpleATL)
  COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

// ISimpleATL
public:
  STDMETHOD(get_ThreadID)(/*[out, retval]*/ long *pVal);
  STDMETHOD(GetManagerName)(/*[out, retval]*/ BSTR* pbstrName);
  STDMETHOD(SetManagerName)(/*[in]*/ BSTR bstrName);
private:
  _bstr_t     _bstrName;
};

#endif //__SIMPLEATL_H_

In order to write a managed code wrapper around your class, here is what you have to do:

1. In Visual Studio .Net create a blank solution; lets say it called COM Wrapper.

2. Add a new Visual C++ project to the solution using Managed C++ Class Library Template. Let's call it Simple .Net

3. Copy both SimpleATL.h and SimpleATL.cpp from your old ATL project directory to the new Simple.Net project directory.

4. Add the files to your project by selecting Add Existing Item from your project contact menu. Both files should appear in Solution Explorer window under the Simple.Net project.

5. Now it is time to do some modifications to the SimpleATL.h and SimpleATL.cpp. Specifically, you would have to delete a bunch of stuff, like macros and all the inheritance relationship. You dont need that anymore in your header file. What you need instead is additional include files -- atlctl.h and atlbase.h. Here is what the header looks like after the editing:

// SimpleATL.h : Declaration of the CSimpleATL

#ifndef __SIMPLEATL_H_
#define __SIMPLEATL_H_

#include <atlbase.h>
#include <atlctl.h>
#include <comdef.h>
////////////////////////////////////////////////////////////
// CSimpleATL
class CSimpleATL
{
public:
  CSimpleATL(){}

// ISimpleATL
public:
  STDMETHOD(get_ThreadID)(/*[out, retval]*/ long *pVal);
  STDMETHOD(SetManagerName)(/*[in]*/ BSTR bstrName);
  STDMETHOD(GetManagerName)(/*[out, retval]*/ BSTR* pbstrName);

private:
  _bstr_t     _bstrName;
};

#endif //__SIMPLEATL_H_

As you can see it became a lot smaller than it used to be.
The only editing you have to do with the SimpleATL.cpp file is to delete the reference to SimpleCOM.h file, so the line: #include "SimpleCOM.h" should be gone.

6. Now it is time to create the actual wrapper class. Notice that Visual Studio .Net created the initial class definition with the key __gc, which means that this class considered a managed code. Add the #include statement for the SimpleATL.h file just above the using namespace System; statement. Add another namespace: using namespace System::Runtime::InteropServices; You need InteropServices for converting types from managed to unmanaged code and vice versa.

7. Add a private pointer to the CSimpleATL class. Class1 has to handle the lifetime of the object by instantiating CSimpleATL pointer in the constructor and deleting it inside the destructor.

8. Add a proxy function for every function that you would like to call from the CSimpleATL class. Here is how it turns out:

// SimpleNet.h

#pragma once

#include "SimpleATL.h"

using namespace System;
using namespace System::Runtime::InteropServices;

namespace SimpleNet
{
  public __gc class Class1
  {
  public:
    Class1() {_pSimpleATL = new CSimpleATL();}
    ~Class1() {delete _pSimpleATL;}

  public:
    void get_ThreadID (/*[out, retval]*/ Int32* pVal) {

      long res;
      HRESULT hRes = _pSimpleATL->get_ThreadID(&res);

      if(FAILED(hRes)) {
        Marshal::ThrowExceptionForHR(hRes);
      }
      else {
        IntPtr ptrInt((void*)&res);
        *pVal = Marshal::ReadInt32(ptrInt);
      }
    }
    void SetManagerName (/*[in]*/ String* bstrName) {

      IntPtr ptrBstr = Marshal::StringToBSTR(bstrName);

      HRESULT hRes = 
         _pSimpleATL->SetManagerName((BSTR)ptrBstr.ToPointer());
      if(FAILED(hRes)) {
        Marshal::ThrowExceptionForHR(hRes);
      }
    }
    void GetManagerName(/*[out, retval]*/ String** pbstrName) {

      BSTR pbstrTemp;
      HRESULT hRes = _pSimpleATL->GetManagerName(&pbstrTemp);
      if(FAILED(hRes)) {
        Marshal::ThrowExceptionForHR(hRes);
      }
      else {
        (*pbstrName) = Marshal::PtrToStringBSTR(pbstrTemp);
        Marshal::FreeBSTR(pbstrTemp);
      }
    }

  private:
    CSimpleATL*   _pSimpleATL;
  };
}

All the parameters to Class1 functions are of managed types now. There is some conversion required from managed to non-managed types and vice versa for what is called non-blittable types. VB BSTR for example, is considered a non-blittable type, therefore it requires conversion. This is where the Marshal class becomes handy. Not only it can convert types but it also can throw a .NET type exception based on COM HRESULT return. Pretty cool.

You can now call this code from any .NET application. All you have to do is to add a reference to the Simple.Net.dll in your .NET project, declare and instantiate the object for Class1, and start calling functions.

You have to decide for yourself whether you need to use RCW and call your components from managed code, merge your code into .NET, or write a wrapper. Note however, that the last option is only available for code written in C++. New Visual C++ compiler is the only compiler that can compile managed and unmanaged code at the same time.

References

Microsoft Corporation
.NET Framework Developer's Guide
Blittable and Non-Blittable Types

Steve Busby and Edward Jezierksi
Microsoft Corporation
August 2001
Microsoft .NET/COM Migration and Interoperability
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/bdadotnetarch001.asp

Stanley B. Lippman
MSDN Magazine
February 2002
Still in Love with C++
Modern Language Features Enhance the Visual C++ .NET Compiler
http://msdn.microsoft.com/msdnmag/issues/02/02/ModernC/ModernC.asp

Jeffrey Richter
Applied Microsoft .Net Framework Programming
Microsoft Press 2002

Downloads

Download demo project - 814 Kb


Comments

  • Cool

    Posted by Legacy on 05/07/2002 12:00am

    Originally posted by: Shaique

    Informative stuff, thanks bud
    

    Reply
  • Great stuff !

    Posted by Legacy on 04/19/2002 12:00am

    Originally posted by: Gast�n Nusimovich

    Alex:

    This is the right way to go for all of us. Thanks a lot for bringing some light to a rather cloudy aspect of the migration from Windows DNA (legacy ?) code to .NET development.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Agile methodologies give development and test teams the ability to build software at a faster rate than ever before. Combining DevOps with hybrid cloud architectures give teams not just the principles, but also the technology necessary to achieve their goals. By combining hybrid cloud and DevOps: IT departments maintain control, visibility, and security Dev/test teams remain agile and collaborative Organizational barriers are broken down Innovation and automation can thrive Download this white paper to …

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds