COM Interop using managed C++






2.56/5 (9 votes)
May 20, 2001
4 min read

125093

1390
A demoonstration of COM Interop, showing early and late binding to a COM component
Abstract
This sample demonstrates COM Interop using managed C++. Both early and late binding are demonstrated, and the utility TlbImp is used to import the type library from an unmanaged COM object
Introduction.
One of the design goals of .NET is 100% compatibility with COM clients and servers.
This is a good news for developers, because many companies have a lot of software based on COM technologies. Migrating to .NET still allows you to reuse all your COM objects without modifying them.
.NET interoperability supports both early and late binding.
1. Early binding.
With early binding you import complete type information about COM class at compile time. Compiler takes this information from metadata assembly, that is generated for COM class. The easest way to create metadata assembly from the exist COM type library is to use utility called TlbImp.exe.
TlbImp has the following command line syntax (some of the options are not shown, refer to MSDN for complete syntax):
TlbImp TlbFile /out: file
- TlbFile can be the name of any file containing a COM type library. It can be .tlb, .dll, .odl, .ocx, .exe file or any other file format with a type library embedded as a resource.
- /out: file - The name of the output file that the metadata definitions will be written to.
This demo uses the DClock COM object from the TimeServer.dll ATL server (do not
forget to register dll using regsvr32.exe utility).
This class has only one method GetTime
that takes one parameter bDate of
type short and returns a string with current time. The time format is: hh:mm:ss. If
bDate is not 0, then return string has date information as well,
and has a format: yy/mm/dd hh/mm/ss.
To create a metadata assembly for DClock class, use the following command line:
tlbimp.exe timeserver.dll /out:timeserverlib.dll
In our sample we use a managed C++ class as the client. To create the client you can use the Visual Studio AppWizard. Choose 'C++ managed Console application' as the target, I called the project DClockClient, so the main source is called DClockClient.cpp
Clients that use COM objects should import metadata. In C++ you should
use #using
directive. The syntax is:
#using "filename"
where filename is the name of the metadata assembly.
Metadata should be accessible at compile time as well as at run time, so for simplicity lets place timeserverlib.dll in the folder where your client code is located, as well as in the debug and release subfolders. As an alternative you can put the assembly in the folder with the client executable and reference it from source using a relative path.
Add the following line to DClockClient.cpp file
#using "timeserverlib.dll"
At runtime the program finds the metadata assembly, uses this information to create a Runtime Callable Wrapper (RCW) object that is responsible for:
- Preserving the objects identity
- Maintaining the objects lifetime
- Proxying custom interfaces
- Marshaling method calls
- Consuming selected interfaces
RCW itself is a managed object so it is garbage collected.
To import the library namespace, you should use the using
namespace directive.
Add the following line to your code
using namespace TimeServerLib;
Now you can use object the same way you use any managed CLR class.
The following code demonstrates this technique.
#using <mscorlib.dll> #using "timeserverlib.dll" using namespace System; using namespace TimeServerLib; int main(void) { DClock *pClock; String *strTime; pClock = new DClock; strTime = pClock->GetTime(0); Console::WriteLine(strTime); return 0; }
Let's create a managed C++ class to wrap the COM object. From a functionality point of view the wrapper class in this example does nothing and you can achieve the same result by instantiating the COM object directly in the main function. This just shows you another possible way of using COM objects.
To create a managed C++ class to wrap com object, add following code to DClockClient.cpp file:
__gc class CTimeEB { public: // Constructor. CTimeEB() { // Create instance of DClock COM object. // Realy we create Runtime Callable Wrapper (RCW) // which is responsible for DClock instaniating. pClock = new DClock; } ~CTimeEB() { // Our object is an instance of managed, means garbage collected // class, so it is not neccessary to call delete for our object. // It is just demonstrates that we can release its resources at // a well-defined point. delete pClock; } // Returns string that has current time. // If bAddDate is not 0, it returns date and time, // if bAddDate is 0, returns time only. String* PrintCurrentTime(short bAddDate) { // Get current time from COM server and concatinate it // with predefined string. return String::Concat(S"Current time is ", pClock->GetTime(bAddDate)); } private: DClock *pClock; };
CTimeEB
is a managed C++ class so there is nothing special in using
it in the code.
2. Late binding.
Late binding is implementing by using the Namespace Reflection mechanism.
To import metadata at runtime the Type
class from the System
namespace
is used. The Type
class has a static method GetTypeFromProgID("ProgID")
that returns
a Type
object for a COM object based on its ProgID.
To obtain an instance of a COM object we use the static member CreateInstance(Type type)
of
the Activator
class from System
namespace.
We pass the Type
object that we got at the previous step as a parameter.
Now we can call the methods of our COM object using the InvokeMethod
member of our Type
object.
The following code demonstrates this technique:
#using <mscorlib.dll> #using "timeserverlib.dll" using namespace System; using namespace TimeServerLib; int main(void) { Type *typ; Object *obj; Object* args[]; String *strTime; using namespace Reflection; // Get Type object for COM server. It uses ProgID of DClock class. typ = Type::GetTypeFromProgID("TimeServer.DClock"); // obtain instance of COM object. obj = Activator::CreateInstance(typ); // Create array of parameters. args = new Object*[1]; // Set parameter. args[0] =__box(1); // Call COM object method by its name. strTime = (String*)typ->InvokeMember("GetTime", BindingFlags::InvokeMethod, Type::DefaultBinder, obj, args); Console::WriteLine(strTime); return 0; }
The TimeLB
class demonstrates reusing COM objects in a managed C++ classes.
// Class to demonstrate late binding. __gc class CTimeLB { public: // Constructor CTimeLB() { // Get Type object for COM server. It uses ProgID of DClock class. typ = Type::GetTypeFromProgID("TimeServer.DClock"); // obtain instance of COM object. obj = Activator::CreateInstance(typ); // Create array of parameters. args = new Object*[1]; } // Returns string that has current time. // If bAddDate is not 0, it returns date and time, // if bAddDate is 0, returns time only. String* PrintCurrentTime(short bAddDate) { String *strTime; // Set parameter. args[0] =__box(1); // Call COM object method by its name. strTime = (String*)typ->InvokeMember("GetTime", BindingFlags::InvokeMethod, Type::DefaultBinder, obj, args); // Concatinate current time with predefined string. return String::Concat(S"Current time is ", strTime); } private: Type *typ; Object *obj; Object* args[]; };
History
Oct 18 2001 - updated for .NET beta 2