In the last article, we looked at calling COM components from .NET. Apart from providing backward compatibility, the .NET framework also provides forward compatibility, which means that it is possible to expose a .NET assembly so that it can be consumed from COM. We will now shift our focus to this aspect of utilizing .NET assemblies from COM.
Calling .NET assemblies from COM
Let us now see how we can utilize .NET assemblies from COM (see Figure A).
|Utilizing .NET assemblies from COM|
Similar to the process of calling COM Objects from .NET, here we have a wrapper class over a .NET Assembly, which can be accessed from a COM-based application. This wrapper class is known as the “COM Callable Wrapper” (CCW). Let’s see how we can do this using Visual Studio.NET.
Start Visual Studio.NET and start a new project of type Class Library. Call it DotnetForCOM (for example). Add a default constructor to the class and some public methods, which you want available in COM. Once you have this ready, right-click on the project in the Solution Explorer and select Properties to bring up the project property pages. Here, choose Configuration properties and select the Build option. You will be presented with the screen displayed in Figure B.
|Working with property pages|
Check the checkbox Register For COM Interop (in Figure B). Now, when you build this project, the .NET assembly is automatically registered for COM Interop. You will now be able to use this assembly from COM, for example from Visual Basic 6.0. If you start a new Visual Basic 6 project and choose the Project Menu and References, this .NET assembly is available to reference from COM (see Figure C).
|Visual Basic project references|
Once this is done, you can access the functionality provided by the .NET assembly from Visual Basic 6.
Working outside Visual Studio.NET
Let us now see what we need to do if we are not using Visual Studio.NET. There are certain requirements for a .NET assembly to be exposed to COM.
Provide a default constructor
Firstly, .NET assembly must provide a default constructor. This is because COM Clients do not support parameterized constructors available in .NET, so make sure you provide a default constructor to the class. You can have constructors with parameters, as well, along with the default constructor, but the default constructor needs to be present to instantiate the .NET assembly from COM client.
Generating type libraries
Generate the type library for the .NET assembly, and make entries in the System Registry. There are two ways to achieve this:
The .NET Assembly must have a Strong Name and must reside in the Global Assembly Cache. For this purpose you will need to generate a key for the Assembly using the Strong Name tool (Sn.exe). Once this is done, you can add the assembly to the Global Assembly Cache (GAC) using the Global Assembly Cache Tool (Gacutil.exe). You can add an assembly to the GAC using:
gacutil –I SampleAssembly.dll
Once this has been done, you will be able to add a reference to the .NET assembly from COM and use it. The COM Callable Wrapper (CCW) is generated at the time when a call is given to the .NET Assembly from COM; it acts as a bridge between the managed and unmanaged boundaries.
Use attributes to change marshaling behavior
Attributes can be used to change the marshaling behavior applied by the marshalers. These attributes are of three types, including:
- Attributes applied by you at design time.
- Attributes applied by the Interop tools
- Attributes applied either by you or Interop.
Some of the commonly used attributes are GuidAttribute, ProgldAttribute, MarshalAsAttribute, and COMVisibleAttribute
GuidAttribute and ProgIdAttribute are used to specify the GUID and the ProgId of classes.
MarshalAsAttribute is optional since all data types have a default marshaling behavior associated with them. In the case of data types, such as String, where it is possible for the data type to be marshaled as multiple types, this attribute becomes necessary. The String data type in the .NET Framework could mean any one of the following types in unmanaged code:
The default behavior is to marshal the String as a Bstr. This attribute can be applied to change this behavior to any of the other unmanaged types.
COMVisibleAttribute is used to control the visibility of types within an assembly. The public types in an assembly are visible by default. So this attribute is used when it is necessary to hide certain types.
For comprehensive listing of all attributes, you can refer to MSDN.
Implications of Interop
Another important thing to keep in mind is that you incur a performance hit when you call a COM object from .NET, or vice versa, due to marshaling. Again, this performance hit is dependent on the data types. Simple data types like integer and byte do not incur much of a performance hit. This is because these data types have a common representation in both managed and unmanaged memory, and so the work done by the Interop Marshaler is minimal. String data type incurs a performance hit as the representation is different in managed and unmanaged memory. The simple data types, which have the same representation in both managed and unmanaged code, are known as blittable data types, and the other data types that have ambiguous representations are known as nonblittable data types. The Interop Marshaler provides support for all the intrinsic data types. For more complex data types, you will need to manually edit the MSIL code.
Use well-tested code over new code to avoid testing
The .NET Framework provides facilities to interoperate between existing COM and the emerging .NET assemblies. This has been provided by means of wrapper classes, which act as the bridge between managed and unmanaged code. It is left the developer to decide where he needs to use Interop and where he needs to write fresh code. A thumb rule is that utilizing well-tested code is better than newly written code that requires testing.