Team LiB
Previous Section Next Section

COM to .NET

Exposing a .NET managed object as a COM object for consumption by COM clients follows a similar process as exposing a COM object for consumption by managed clients. This next section will show you the ins and outs of getting started with .NET and COM Interop by exposing a .NET managed component as a COM component.

The COM Callable Wrapper

Just as .NET components need a runtime callable wrapper in order to communicate across process boundaries with COM objects, COM objects need a COM callable wrapper (CCW) in order to communicate with .NET components. Unable to communicate directly with managed code, the CCW acts as a proxy that both provides access to the exposed component and handles the default marshalling behavior (refer to Table 13.1 for a list of data type marshalling).

Figure 13.3 illustrates the relative position of the COM callable wrapper in a typical communication process.

Figure 13.3. Illustration of the CCW.


.NET Code Attributes for COM Interop Programming

Even with the help of the CCW, it can be quite complicated to expose a .NET component to the COM world. One of the main reasons for that is because there are settings governing component behavior that are normally configured by developers within their COM components. Although you can create simple components that don't need any custom settings, it certainly helps to know what you can and can't modify. To get access to the various behavior modification properties of a COM component, the .NET Framework has created several code attributes that mark classes and assemblies for various levels of COM Interop.

Marshalling Data from COM to .NET

Table 13.2 contains a listing of some of the most common attributes used in COM Interop programming (a few of the less common attributes have been deliberately omitted). Although you might be able to avoid many of these by doing everything from within Visual Studio .NET, they're still good to know. This list is available in the MSDN library. If you are an old hand at COM programming, many of these attributes will make perfect sense to you. If you get more confused after seeing these attributes, you might benefit from curling up with a good book on COM. (Don Box's Essential COM is just that: essential.)

Table 13.2. COM Interop Programming Code Attributes

Attribute

Description

AutomationProxyAttribute

Indicates whether the type should be marshaled using the default automation marshaler or a custom proxy.

ClassInterfaceAttribute

Controls the type of class interface produced by the class: AutoDispatch, AutoDual, None.

CoClassAttribute

Identifies the original CLSID of a type imported from a type library. This is usually generated automatically by a tool, such as TlbImp.exe.

ComImportAttribute

If true, indicates that the coclass or interface was imported from a COM type library. This is usually applied automatically by tools.

ComRegisterFunctionAttribute

When used, this attribute indicates that the method should be called when the assembly is registered with COM, allowing developer-defined code to be executed.

ComUnregisterFunctionAttribute

Like ComRegisterFunctionAttribute, but this attribute allows the developer's code to be executed when the assembly is unregistered from the COM system.

ComVisibleAttribute

By default, every public managed types is visible once it has registered with COM. Applying this attribute will make a type invisible from COM.

GuidAttribute

Indicates the globally unique identifier for a given type or interface, or the entire assembly (type library when exposed via COM).

InAttribute

Indicates that data should be marshaled in. Can be used to indicate input parameters via attributes.

OutAttribute

Indicates outbound marshaling to the COM system. Can be used to attribute parameters.

InterfaceTypeAttribute

Controls how a managed interface is exposed to COM (Dual, IUnknown, or Dispatch only). Affects compatibility with non-C++ COM implementations such as VB6.

MarshalAsAttribute

Forces the marshaling system to use a particular data type when marshaling the indicated field or parameter.

OptionalAttribute

Indicates an optional parameter.

ProgIdAttribute

Contains the ProgID of the associated .NET class.

StructLayoutAttribute

Manually controls the physical size and position of class members. Often applied by a Interop tool, such as TlbImp.exe.


Interop Programming Example: COM Code Utilizing .NET Components

To consume a .NET component via COM from a COM client, there are a few things you need to do. First, you have to create the .NET component. Second, you have to expose or register that component with COM. Third, you need to write the COM client in an unmanaged language, such as VB6 or C++.

To get started with the .NET component, create a new C# class library called COMtoDotNet. Then create a single class called DataGrabber. Here you're going to simulate the activity of some kind of data-driven component by querying data out of the Northwind database . The code in Listing 13.3 shows the listing for DataGrabber.

Listing 13.3. The DataGrabber Class to Be Exposed to COM Clients
   using System;
   using System.Data;
   using System.Data.SqlClient;

   namespace COMtoDotNet
   {
     /// <summary>
     /// Summary description for Class1.
     /// </summary>
     public class DataGrabber
     {
       public DataGrabber()
       {

       }

       public string GetData()
       {
     SqlConnection conn = new SqlConnection(
            @"(set to your connection)");
          SqlDataAdapter da = new SqlDataAdapter(
           "SELECT * FROM Customers", conn );
          DataSet ds = new DataSet();
          da.Fill(ds, "Customers");
          return ds.GetXml();
       }
     }
   }

This code doesn't look too complicated. All you're doing is returning a string that contains the XML representation of the entire Customers table in the Northwind database.

Right-click the project and open the Properties window. Select the Configuration Properties node; on the Build tab, change the Register for COM Interop setting to true. This will allow Visual Studio .NET to automatically register your assembly with COM. If you want to register your assembly manually, you can open a command prompt and use the regasm utility.

After making sure that your DataGrabber class builds without error in the class library, it's time to move on to creating the COM client. You could use C++ to create a COM client, but VB6 is much easier to read and takes up much less room.

Open the VB6 IDE and create a new Windows application. Now you can add a reference to your newly created COM object. Open the Project menu and choose References. You'll see a check box list of various program IDs. Somewhere near the top, you will see a COM object called COMtoDotNet. Check that box and then click OK.

With the reference to the .NET component exposed via COM in place, you can write code that makes use of that component's methods. To get started, drop a button onto the form's surface; that button will be used to trigger the invocation of the managed COM component. The code for the form is shown in Listing 13.4.

Listing 13.4. The VB6 Client Consuming the .NET COM Object
Private Sub Command1_Click()
    Dim c2d As COMtoDotNet.DataGrabber
    Dim data As String

    Set c2d = New COMtoDotNet.DataGrabber
    data = c2d.GetData()
    Text1.Text = data
End Sub

It all looks remarkably easy. This fact is actually a source of contention among many programmers. As you'll see in the next section, COM Interop isn't always a good thing. And even when COM Interop is a good thing, it is always more complicated than the tools make it seem.

When to Use Interop

So far you've seen the simplest ways that you can make use of COM Interop. These methods will actually work for a majority of the situations you'll come across. However, you might encounter some situations in which you have to get deep down into the attributes and type libraries and write custom marshalling to get things working.

With that aside, you need to be able to decide when you should use COM Interop. The first thing that you should know is that COM Interop is slow. Each time a .NET component makes a call to a COM object or a COM object makes a call to a .NET component, a significant amount of latency and overhead occurs. If you find yourself in a situation in which you need to make many repeated calls to the same object on the other side of a callable wrapper, you might want to reconsider the use of COM Interop.

Another question that often comes up is whether you should rewrite your existing COM objects in .NET or use Interop to reuse the existing components. Obviously if those objects have to stay alive for backward compatibility, you will need to use Interop. However, if you can spare the time and effort to rewrite the components to take full advantage of not only the Framework itself but also the additional architectural structure available, it will be well worth your trouble to do so.

    Team LiB
    Previous Section Next Section