VC++ .NET Programming, Writing a Wrapper for COM
Components
By Alex Kravchenko
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.
#ifndef __SIMPLEATL_H_
#define __SIMPLEATL_H_
#include "resource.h" // main symbols
#include
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()
public:
STDMETHOD(get_ThreadID)( long *pVal);
STDMETHOD(GetManagerName)( BSTR* pbstrName);
STDMETHOD(SetManagerName)( BSTR bstrName);
private:
_bstr_t _bstrName;
};
#endif
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:
#ifndef __SIMPLEATL_H_
#define __SIMPLEATL_H_
#include <atlbase.h>
#include <atlctl.h>
#include <comdef.h>
class CSimpleATL
{
public:
CSimpleATL(){}
public:
STDMETHOD(get_ThreadID)( long *pVal);
STDMETHOD(SetManagerName)( BSTR bstrName);
STDMETHOD(GetManagerName)( BSTR* pbstrName);
private:
_bstr_t _bstrName;
};
#endif
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:
#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 ( 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 ( String* bstrName) {
IntPtr ptrBstr = Marshal::StringToBSTR(bstrName);
HRESULT hRes =
_pSimpleATL->SetManagerName((BSTR)ptrBstr.ToPointer());
if(FAILED(hRes)) {
Marshal::ThrowExceptionForHR(hRes);
}
}
void GetManagerName( 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