When COM+ was developed, COM was the prevalent component development technology. Therefore, COM+ was developed with COM in mind. COM+ recognizes only COM components. Any .NET component that is exposed to COM+ must be exposed as a COM component. In this section, I discuss the general problem of exposing a .NET component as a COM component. Once you do that, the .NET components can be used from the COM clients as well as making use of the COM+ services.
In order for COM+ (or any COM client) to find a .NET component, you need to register the .NET component as a COM server in the Windows Registry. This process is known as the assembly registration process, and it can be performed by using any of the following methods :
When using Visual Studio .NET, open the project's property pages and check the Register for COM Interop check box.
Use the Assembly Registration tool (regasm.exe) that ships as a part of the .NET Framework.
In a program, use the RegistrationServices class of the System.Runtime.InteropServices namespace.
However, before you use any of these options, you must sign the .NET assembly using the Strong Name tool (sn.exe). A strong name provides a unique identification to the assembly and avoids any possible conflicts with other programs. A strong name consists of the assembly's identity (its simple text name , version number, and culture information), a public key, and a digital signature.
Additionally, you can also assign an assembly and each of its classes distinct GUID values using the Guid attribute. If you don't assign a GUID, the registration methods mentioned previously will assign one automatically for each element of the assembly (such as the assembly itself, as well as its interfaces and classes). The GUID value is used to create a unique Registry key for the component, as shown in Figure 7.1.
Within each component's registry entry, there is a key named InProcServer32 . The value of the InProcServer32 key specifies the path of the DLL that COM should use to create the component by invoking the CoCreateInstance() API. However, if the DLL is a managed code library, COM does not know how to load the DLL and create objects. To resolve this problem, the assembly registration process takes a different approach. Instead of storing a path to the managed code DLL in the InProcServer32 key, the assembly registration process stores a path to the mscoree.dll file. This file is a special file in the .NET Framework that knows how to launch the CLR process and execute a managed DLL.
How does mscoree.dll know which managed code DLL to execute? The assembly registration process stores this information for mscoree.dll in another registry key named Assembly (see Figure 7.1). The Assembly key stores the identity of the managed code DLL but does not store its path. The mscoree.dll has the assembly name, but does mscoree.dll know where to find the code for this assembly for execution? The mscoree.dll instructs the CLR to locate the assembly. One of the first places where the CLR searches for a strongly named assembly is the Global Assembly Cache (GAC). If the CLR cannot find the assembly in the GAC, it tries to locate the assembly at several other places, including the path specified in the CodeBase registry key. I'll cover the complete process of locating an assembly in Chapter 10, "Deployment."
When mscoree.dll locates the assembly containing the requested type, it invokes a global method named DllGetClassObject() to create an instance of the type. This method also creates a COM Callable Wrapper (CCW) on-the-fly based on the type. You can think of the CCW as a proxy between the COM object and the .NET object. A CCW enables communication between the calling COM code and the managed code and handles any conversion between the data types and other messages between the COM types and the .NET types, as shown in Figure 7.2.
The CCW exposes an IDispatch interface that can be used by a COM client to access methods or properties of a managed object in the following two steps:
The COM client calls the IDispatch.GetIdsOfName() method on the CCW, passing it the name of the member you want to access. The method returns a unique dispatch ID (DispId) for the member.
COM client calls IDispatch.Invoke() and passes it the DispId as an argument. This method provides access to the members exposed by an object.
In the previous section, you saw that the CCW is created at runtime on-the-fly. The COM clients use a technique called late binding to access the object. In late binding, resolution of the type references is delayed until runtime. Although late binding is suitable for an interpreted environment, such as scripting languages, late binding is slow and does not support compile-time type checking.
The .NET Framework also allows the .NET programmers to export their components as COM type libraries. A type library is a persistent CCW that makes the .NET types available to the COM programs at compile time. COM programmers can use the type information stored inside CCW to compile their programs and take the benefit of early binding to the .NET types. Early binding significantly reduces the time required to access an object and additionally allows compilers to enforce type checking at compile time.
A .NET type can be exported as COM type library by using any of the following ways:
When using Visual Studio .NET, open the project's property pages and change the Register for COM Interop option to true.
By using the Assembly Registration tool (regasm.exe) with its /tlb option.
By using the Type Library Exporter tool (tlbexp.exe) that ships as a part of the .NET Framework SDK.
Programmatically by using the ConvertAssemblyToTypeLib() method of the TypeLibConverter class in the Systen.Runtime.InteropService namespace.
The default extension of a type library is .tlb . You can use this library to compile the COM clients. .NET assemblies continue to execute in the CLR even after exporting a type library.