Using a .NET Component from a COM Client C# Help

So far, you have seen how to access a COM component from a .NET client. Equally interesting is to find a solution for accessing NET components in an old COM client that i using Visual Basic 6.0, or C++ with MFC, or ATL.

COM Callable Wrapper

If you want to access a COM component with a .NET client, you have to work with an RCW. To access a .NET component from a COM client application, you must use a COM callable wrapper (CCW).

a CCW that wraps a.NET class, and offers COM interfaces that a COM client expects to use.

The CCW offers itl1erfaces such as IUnl<nown,IDispatch, ISupportErrorlnfo, and others.

It also offers interfaces such as IConnectionPointContainer and IConnec’tionPoint for events.

A COM client gets what it expects from a COM object – although a .NET component is behind the scenes.

The wrapper deals with methods such as AddRef(), Release (), and Querylnterface ()from theinterface, whereas in the .NEI: object you can count on the garbage collector without the need to deal with reference counts.

Figure 24-15

Figure 24-15

creates the type library DotnetComponent.tlb You can view the type library with the utility OLE/COM Object Vieweroleview32. exe.

This tool is part of the Microsoft SDK, and you can start it from the Visual Studio 2008 Command Prompt.

Select File → View TypeLib to open the type library.

Now you can see the interface definition shown in the following code. The unique IDs will differ.

The name of the type library is created from the name of the assembly.

The header of the type library also defines the full name of the assembly in a custom attribute, and all the interfaces are forward  declared before they are defined.

In the following generated code, you can see that the interfaces IWelcome and IMath are defined as COM dual interfaces.

You can see all methods that have been declared in the C# code are listed here in the type library definition.

The parameters changed; the .NET types are mapped to COM types (forexample, from the String class to the BSTRtype), and the signature is changed, so that a HRESULTis returned.

Because the interfaces are Dual, dispatch IDs are also generated:

The coclass section marks the COM object itself.

The uuid in the header is the CLSID used to instantiate the object. The class DotnetComponent supports the interfaces _DotnetComponent, _Object.

IWelcome, and IMath ….,.objecist defined in the file mscorlib.tlb included in an earlier code section and offers the methods of the base class Object.

the default interface of the component is _DotnetComponent, which is defined after the coclass section as a dispatch interface.

In the interface declaration it is marked as dual, but because no methods are included, it is a dispatch interface.

With this interface, it is possible to access all methods of the component using late binding:

COM Interop Attributes

Applying attributes from the namespace System Runtime.

InteropServices to classes, interfaces, or methods allows you to change the implementation of the CCW.

The following table lists these attributes and a description.

Attribute Description Guid This attribute can be assigned to the assembly, interfaces, and classes. Using the Guid as an assembly attribute defines the type-library ID, applying it to interfaces defines the interface ID (lID), and setting the attribute to a class defines the class ID (CLSID).The unique IDs needed to be defined with this attribute can be created with the utility guidgen.
The CLSIDand type-library IDs are changed automatically with every build.
you don’t want ,fochange it with every build, you can fix it by using this attribute. The lID is only changed if the signature of the interface changes; for example, a method is added or removed, or some }!larameterschanged. Because with COM the lID should change with every new version of this interface, this is a very good default behavior, and usually there’s no need to apply the lID with the Guid attribute. The only time you want to apply a fixed lID for an interface is when the .NETinterface is an exact representation of an existing COM interface, and the COM client already expects this identifier.

ProgId ‘
ComVisible
InterfaceType
ClassInterface
DispId
InOut
….
This attribute can be applied to a class to specify what name should be used
when the object is configured in the registry.
This a~bute enables’ you to hide classes, interfaces, and delegates from COM
when set to false. This prevents a COM representation from being created.
This attribute, if set to a ComInterfaceType enumeration value, enables
you to modify the default dual interface type tha] is created for .NET
interfaces. ComInterfaceType has the values InterfaceIsDual,
InterfaceIsIDispatc;h, and InterfaceIsIUnknown. H you want to apply
a custom interface type to a .NET interface, set the attribute like this:
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown))
This attribute enables you to modify the default c!patch interface that is
created for a class. Class Interface accepts an argument of a .
ClassInter’faceType enumeration. The possible values are AutoDispatch,
AutoDual, and None.In the previous example, you have seen that the default
is AutoDispatch, because a dispatch interface is created. If the class
should only be accessible by the defined interfaces, apply the attribute
[ClassInterface (ClassInterfaceType.None) ) to the class.
This attribute can be used with dual and dispatch interfaces to define the
DispId of methods and properties.
COM allows specifying attributes to parameter types if the parameter should
be sent to the component [In), from the component to the client [Out), or in
both directions [In. Out). ‘
Parameters of COM methods may be optional. Parameters that should be optional can be marked with the Optional attribute.

Now, you can change the C# code to specify a dual interfacetype for the IWelcome interfaceand a custom interfacetype for the IMath interface.

With the class DotnetComponent, the attribute Classlnterface with the argument ClasslnterfaceType .None specifies  that no separate COM interface will be generated.

The attributesProgld and Guid specify a ProgID and a GUID:

Image

Rebuilding the”classlibrary and the type library changes the interface definition.

You can verify this with Oleview.exe.

As you can see in the following IDL code, the interface IWelcome isstilla dual interface, whereas the IMath interface now is a custom interfacethat is derived from IUnknown instead of IDispatch.

In the coclass section,the interface_DotnetComponent isremoved, and now IWelcome the new default interface,because it was the first interfacein the inheritance list of the class DotnetComponent: .

Image

COM Registration

Before the .NETcomponent can be used as a COM object, it is necessary to configure it in the registry.

Also, if you don’t want to copy the assembly into the same directory as the client application, it is necessary to install the assembly in the global assembly cache.

The global assembly cache itself is discussed.
To install the assembly in the global assembly cache, you must sign it with a strong name (using Visual Studio 2008,you can define a strong name in properties of the solution).

Then you can register the assembly in the global assembly cache:
gacutil -i dotnetcomponent.dll

Now, you can use the regasm utility to configure the component inside the registry. The option Itlb extracts the type library and also configures the type library in the registry: regasm dotnetcomponent.dll Itlb The information for the .NET component that is written to the registry is as follows.

The All COM configuration is in the hive HKEY_CLASSES_ROOT (HKCR).The key of the ProgID (in this example, it is Wrox. DotnetComponent) is written directly to this hive, along with the CLSID.

The key HKCR\CLSID\{CLSID)\InProcServer32 has the following entries:

o mscoree.dll- mscoree. d.ll represents the CCW.This is a real COM object that is responsible for hosting the .NET component. This COM object accesses the .NET component to offerCOM behavior for the client. The file mscoree. dll is loaded and instantiated from the client via the normal COM instantiation mechanism.

o ThreadingModel=Both – This is an attribute of the mscoree. dll COM object. This component
is programmed in a way to offer support both for STAand MTA.
o Assembly=dotnetcomponent, Version=1.0.0.0,Culture=neutral, PublicKeyToken= 5cd57c93b4d9c41a- The value of the Assembly stores the assembly full name, including the version number and the public key token, so that the assembly can be uniquely identified. The assembly registered here will be loaded by mscoree. dll.
o Class=Wrox.ProCSharp.COMInterop.Server.DotnetComponent – The name of the class will also be used by mscoree.dll.

This is the class that will be instantiated. .

o RuntimeVersion=v2.0.50727 – The registry entry RuntimeVersion specifies the version of the .NET runtime that will be used to host the .NET assembly.

In addition to the configurations shown here, all the interfaces and the type library are configured with their identifier.s… too.

Creating a COM Client

Now, it’s time to create a COM client.

Start by creating a simple C++ Win32 Console application project,
and name it COMClient. You can leave the default options selected, and click Finish in the project wizard At the beginning of the file COMClient. cpp, add a preprocessor command to include the <iostrea.ni> header file and to import the type library that you created for the .NET component.

The import statement creates a “smart pointer” class that makes it easier to deal with COM objects.

During a build process, the import statement creates.

tlh and  tli files that you can find in the debug directory of your project, which includes the smart pointer class. Then add using namespace directives to open the namespace std that will be used for writing output messages to the console, and the namespace DotnetComponent that is created inside the smart pointer class:

Image

In the _tmain () method, the first thing to do before any other COM call is the initialization of COM with the API call Co Initialize (). Colnitialize () creates and enters an STA for the thread.

The variable spWelcome is of type IWelcomePtr, which is a smart pointer.

The smart pointer method Createlnstance () accepts the ProgID.as an argument to create the COM object by using the COM API CoCreatelnstance ().

The operator -> is overridden with the smart pointer, so that you can invoke the methods of the COM object such as Greeting ():

image

The second interface supported by your .NET component is lMath, and there is also a smart pointer that wraps the COM interface: IMathPtr. You can directly assign one smart pointer to another as in spMath = spWelcome;.

In the implementation of the smart pointer (the = operator is overridden), the Querylnterface () method is called. With a reference to the IMath interface, you can call the Add ()
method.

Image

If an HRESULT error value is returned by the COM object (this is done by the CCW that returns HRESULT errors if the .NET component generates exceptions), the smart pointer wraps the HRESULT errors and generates _com_error exceptions instead. Errors are handled in the catch block.

At the end of the program, the COM DLLs are closed and unloaded using CoUninitialize ():

Image

Now you can run the application, and you will get outputs from the Greeting () and the Add () methods to the console.

Youcan also try to debug into the smart pointer class, where you can see the COM API calls directly.

If you get an exception that the component that cannot be found, check if the same version of the assembly that is configured in the registry is installed in the global assembly cache.

Posted on October 27, 2015 in Interoperability

Share the Story

Back to Top