Exposing VB .NET Objects to COM Clients

The majority of this chapter has dealt with the most common use of COM Interop—consuming COM objects from managed applications. There will be times, however, when the opposite will be true—unmanaged applications want to call managed code as they would any other COM object. Exposing .NET classes as COM objects enables any unmanaged COM client to use managed classes as COM servers. This type of functionality is useful on many levels, such as during the conversion of large, complex Windows DNA-based applications comprised of many COM objects running on one or more computers. Having the capability for .NET classes to present themselves as COM objects might allow you to re-implement parts of your application one component at a time. To the clients of the COM object you are migrating, the implementation appears to have changed. However, the interfaces would stay the same and thus be transparent.

At the heart of this is the COM Callable Wrapper (CCW). The job of the CCW is to create the COM interfaces that clients expect to find—IUnknown (which every COM object must have) and IDispatch (which allows clients to be late bound and accessible through scripting languages such as VBScript). Not only does the CCW expose IUnknown and IDispatch, it also exposes some additional COM interfaces such as ISupportErrorInfo, IProvideClassInfo, IDispatchEx, IconnectionPointContainer, and IObjectSafety.

Each of these interfaces has specific uses by COM clients. The CLR will only create one instance of a CCW for a .NET class. Like all COM objects, the CCW will maintain a reference count to track the clients who have established references to it. When the reference count on the CCW reaches zero, it will be terminated. Because it is allocated from an unmanaged heap, the CCW will be terminated immediately. When the CCW is released, the reference to the .NET object will also be released. If no other managed applications are using this instance of the .NET object, it will be collected during garbage collection.

Similar to an RCW, one of the biggest responsibilities of the CCR is to marshal arguments between unmanaged and managed environments. This is especially true for return values because COM clients expect to receive HRESULT codes from method calls. To solve this problem, the CCW passes return values into an additional argument passed to the method. It must, however, convert runtime exceptions to HRESULT codes, so it must catch all exceptions generated by the runtime and map them against negative HRESULT values for COM clients to understand.

The process of exposing a .NET component to COM clients is quite simple but involves a number of steps. Instead of showing you the easy way first, let's start by showing how you would accomplish this manually. First, create a new class that looks similar to the class in Listing 9.9.

Listing 9.9. A Simple Class with Public Members that Will Be Exposed to COM Clients
Public Class COMClass
    ' A public variable
    Public PublicField As Double
    ' A private variable
    Private PrivateField As String

    ' A read-write property
    Property ReadWriteProperty() As String
        Get

        End Get
        Set(ByVal Value As String)

        End Set
    End Property

    ' A read-only property with one argument
    ReadOnly Property ReadOnlyProperty(ByVal Index As Integer) As Boolean
        Get

        End Get
    End Property
End Class
				

This class really doesn't do much, but it demonstrates how you can make it accessible to COM clients.

In order for COM components to consume your .NET component, you need to specify some additional attributes of your class. Use the COMClass attribute to specify the CLSID, InterfaceID (IID), and EventsID for your class. Each of these IDs is represented with a GUID or Globally Unique Identifier (a 128-bit number that is intended to be unique for every component). Next, add public constants to your code to assign GUIDs to each of the required COM IDs, as demonstrated in Listing 9.10.

Listing 9.10. Using the COMClass Attribute to Assign Necessary GUIDs for the Exposed COM Interface.
<ComClass(COMClass.ClassId, COMClass.InterfaceId, COMClass.EventsId)> _
Public Class COMClass
    Public Const ClassId As String = "39696648-9058-441d-88C4-52E9A04C8A5B"
    Public Const InterfaceId As String = "57E40850-B6C6-4786-954D-402A0A43D0D6"
   Public Const EventsId As String = "E5580E61-8497-4523-AB47-E1C37E2C92F2"

The compiler will then automatically include metadata required to expose your .NET class to COM clients using the information provided to the COMClass attribute. These values are actually defined within the class and are string representations of GUIDs. Although I've provided some GUIDs already in my sample code, you should use the Create GUID utility launched from Visual Studio's Tools menu to create GUIDs for the ClassID, InterfaceID, and EventsID constants.

Next, ensure that the “Register for COM Interop” option is selected in the Project Properties dialog box (see Figure 9.4).

Figure 9.4. Setting the properties of a project to automatically register for COM Interop.


This setting will cause Visual Studio .NET to create a type library (.tlb file) and register your new COM component when you build your .NET component. To register your .NET assembly with COM on other machines, you can manually use the RegAsm tool (Register Assembly) that is included with the .NET Framework install. RegAsm takes the name of a DLL containing an assembly and registers all the classes that the DLL contains. This is similar to using the RegSvr32 on COM objects, but for .NET components instead of COM components.

You can also use RegAsm to automatically create a .REG file (a Registry import file) that contains all of the Registry entries that RegAsm would create to register the DLL using the /regfile option. This switch, however, cannot be used in conjunction with the /tlb switch. For example, the following command line will generate a file called ccwproject.reg.

regasm ccwproject.dll /regfile

The resulting .REG file can then be used with installation routines to help distribute your COM enabled assemblies.

Once you have completed all of these steps, you can now consume your .NET object from any COM client as you would any other COM object. You can unregister your .NET component by using RegAsm with the /u switch.

Of course, there are a few additional shortcuts you can take to make this a bit less painful. First, instead of adding all of the additional attributes required to instruct the compiler to add COM relevant metadata to the assembly by hand, you can use the COM class template when adding a class you want to be available to COM clients. Perform the following steps to use the COM Class template:

1.
Create or open a project.

2.
From the Project menu in Visual Studio, select Add Class to display the Add New Item dialog box.

3.
From the Add New Item dialog box, select the COM Class template from the list of templates (as depicted in Figure 9.5), and provide a name for the new class.

Figure 9.5. Using the COM Class template that comes with Visual Studio .NET.


You will notice that the template automatically adds the appropriate attributes and provides default values for the GUID-based IDs used by the COMClass attribute.

With the “Register for COM Interop” option set, every time you build your assembly, Visual Studio will do all of the dirty work for you. Of course, RegAsm does have more advanced options you can use from the command line so don't be surprised if you still have to use this tool in your projects.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.160.142