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:
CTimeEB()
{
pClock = new DClock;
}
~CTimeEB()
{
delete pClock;
}
String* PrintCurrentTime(short bAddDate)
{
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;
typ = Type::GetTypeFromProgID("TimeServer.DClock");
obj = Activator::CreateInstance(typ);
args = new Object*[1];
args[0] =__box(1);
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.
__gc class CTimeLB
{
public:
CTimeLB()
{
typ = Type::GetTypeFromProgID("TimeServer.DClock");
obj = Activator::CreateInstance(typ);
args = new Object*[1];
}
String* PrintCurrentTime(short bAddDate)
{
String *strTime;
args[0] =__box(1);
strTime = (String*)typ->InvokeMember("GetTime",
BindingFlags::InvokeMethod,
Type::DefaultBinder, obj, args);
return String::Concat(S"Current time is ", strTime);
}
private:
Type *typ;
Object *obj;
Object* args[];
};
History
Oct 18 2001 - updated for .NET beta 2