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

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read