Interoperation with COM


The commercial success of any new software platform depends critically on how well it integrates with what already exists while providing new avenues for development of even better applications. For example, Windows 3.0 not only allowed existing DOS applications to run, but it also multitasked them better than any other product up to that point and provided a platform for writing Windows applications that were better than any DOS app. The canvas on which we paint is essentially never blank. How did God manage to create the world in only six days? He didn’t have any installed base to worry about being backward compatible with. (My editor points out that He also skimped on documentation.)

Windows has depended on COM for interapplication communication since 1993. Essentially all code for the Windows environment is neck-deep in COM and has been for an awfully long time in geek years. The .NET Framework has to support COM to have any chance of succeeding commercially. And it does, both as a .NET client using a COM server, and vice versa. Since it is more likely that new .NET code will have to interoperate with existing COM code than the reverse, I will describe that case first.

Backward compatibility is crucial in the development of any new system. Therefore, .NET supports interoperation with COM.

Using COM Objects from .NET

A .NET client accesses a COM server by means of a runtime callable wrapper (RCW), as shown in Figure 2-18. The RCW wraps the COM object and mediates between it and the common language runtime environment, making the COM object appear to .NET clients just as if it were a native .NET object and making the .NET client appear to the COM object just as if it were a standard COM client.

A .NET client accesses a COM object through a runtime callable wrapper (RCW).

click to expand
Figure 2-18: .NET client/COM object interaction via a runtime callable wrapper.

The developer of a .NET client generates the RCW in one of two ways. If you’re using Visual Studio .NET, simply right-click on the References section of your project and select Add Reference from the context menu. You will see the dialog box shown in Figure 2 19, which offers a choice of all the registered COM objects it finds on the system. Select the COM object for which you want to generate the RCW, and Visual Studio .NET will spit it out for you. If you’re not using Visual Studio .NET, the .NET SDK contains a command line tool called TlbImp.exe, the type library importer that performs the same task. The logic that reads the type library and generates the RCW code actually lives in a .NET run-time class called System.Runtime.InteropServices.TypeLibConverter. Both Visual Studio .NET and TlbImp.exe use this class internally, and you can too if you’re writing a development tool or feeling masochistic.

You can generate an RCW with a variety of development tools.

click to expand
Figure 2-19: Locating COM objects for RCW generation.

Figure 2-20 shows a sample .NET client program that uses a COM object server. You can download the samples and follow along from the book’s Web site. This sample contains a COM server, a COM client, and a .NET client so that you can compare the two. The source code is shown in Listing 2-12.

click to expand
Figure 2-20: Sample .NET client using a COM server.

Listing 2-12: Code listing of a .NET client using an RCW.

start example
Protected Sub Button1_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) ‘ Create an instance of the RCW that wraps our COM object. Dim RuntimeCallableWrapper As New ComUsedByDotNet.Class1() ‘ Call the method that gets the time. Label1.Text = RuntimeCallableWrapper.GetTimeFromCom(CheckBox1.Checked) ‘ Object becomes garbage when it goes out of scope, ‘ but is not actually released until next garbage collection. End Sub 
end example

After you generate the RCW as described in the preceding paragraph, you will probably want to import its namespace into the client program using the Imports statement, allowing you to refer to the object using its short name. You create the RCW object simply by using the new operator, as you would for any other .NET object. When it’s created, the RCW internally calls the native COM function CoCreateInstance, thereby creating the COM object that it wraps. Your .NET client program then calls methods on the RCW as if it were a native .NET object. The RCW automatically converts each call to the COM calling convention—for example, converting .NET strings into the BSTR strings that COM requires—and forwards it to the object. The RCW converts the results returned from the COM object into native .NET types before returning them to the client. Users of the COM support in Visual J++ will find this architecture familiar.

The RCW magically converts .NET calls into COM and COM results to .NET.

When you run the sample COM client program, you’ll notice (from dialog boxes that I place in the code) that the object is created when you click the button and then immediately destroyed. When you run the sample .NET client program, you’ll find that the object is created when you click the Get Time button, but that the object isn’t destroyed immediately. You would think it should be, as the wrapper object goes out of scope, but it isn’t, not even if you explicitly set the object reference to nothing. This is the .NET way of lazy resource recovery, described previously in the section about garbage collection. The RCW has gone out of scope and is no longer accessible to your program, but it doesn’t actually release the COM object that it wraps until the RCW is garbage collected and destroyed. This can be a problem, as most COM objects were not written with this life cycle in mind and thus might retain expensive resources that should be released as soon as the client is finished. You can solve this problem in one of two ways. The first, obviously, is by forcing an immediate garbage collection via the function System.GC.Collect. Calling this function will collect and reclaim all system resources that are no longer in use, including all the RCWs not currently in scope. The drawback to this approach is that the overhead of a full garbage collection can be high, and you may not want to pay it immediately just to shred one object. If you would like to blow away one particular COM object without affecting the others, you can do so via the function System.Runtime.InteropServices.Marshal.ReleaseComObject.

COM objects are actually destroyed when their RCWs are garbage collected.

The RCW mechanism described in the preceding paragraphs requires an object to be early-bound, by which I mean that the developer must have intimate knowledge of the object at development time to construct the wrapper class. Not all objects work this way. For example, scripting situations require late binding, in which a client reads the ProgID of an object and the method to call on it from script code at run time. Most COM objects support the IDispatch interface specifically to allow this type of late-bound access. Creating an RCW in advance is not possible in situations like this. Can .NET also handle it?

Fortunately, it can. The .NET Framework supports late binding to the IDispatch interface supported by most COM objects. A sample late binding program is shown in Figure 2-21, and its code in Listing 2-13. You create a system type based on the object’s ProgID via the static method Type.GetTypeFromProgID. The static method Type.GetTypeFromCLSID (not shown) does the same thing based on a CLSID, if you have that instead of a ProgID. Once you have the type, you create the COM object using the method Activator.CreateInstance and call a method via the function Type.InvokeMember. These functions are part of .NET reflection, which I discuss in Chapter 11. It’s more work—late binding always is—but you can do it.

.NET also supports late binding without too much trouble.

click to expand
Figure 2-21: Sample late binding program.

Listing 2-13: Sample late binding code.

start example
Protected Sub Button1_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) ‘ Get system type name based on prog ID. Dim MyType As System.Type MyType = Type.GetTypeFromProgID(textBox1().Text) ‘ Use an activator to create object of that type. Dim MyObj As Object MyObj = Activator.CreateInstance(MyType) ‘ Assemble array of parameters to pass to COM object. Dim prms() As Object = {checkBox1().Checked} ‘ Call method on object by its name. label2().Text = MyType.InvokeMember("GetTimeFromCom", _ Reflection.BindingFlags.InvokeMethod, Nothing, MyObj, _ prms).ToString() End Sub 
end example

start sidebar
Tips from the Trenches

Clients report from the field that they don’t like using COM from their .NET programs. The code required to work with COM is easy to write, but the inclusion of even one badly behaved COM object can bring down an otherwise robust .NET application. For example, COM objects don’t allocate memory from the managed heap, even when you access them through an RCW from a .NET application. They therefore can leak memory that the .NET garbage collector can’t reclaim. The interoperation capability lets you develop test cases and pilot projects and use third party COM components whose vendors haven’t yet ported them to .NET. But if you use this capability as a long-term solution, you’ll be missing many of the benefits of .NET.

end sidebar

Using .NET Objects from COM

Suppose, on the other hand, you have a client that already speaks COM and now you want to make it use a .NET object instead. This is a somewhat less common scenario than the reverse situation that I’ve previously described because it presupposes new COM development in a .NET world. But I can easily see it occurring in the situation in which you have an existing client that uses 10 COM objects and you now want to add an 11th set of functionality that exists only as a .NET object—and you want all of them to look the same to the client for consistency. The .NET Framework supports this situation as well, by means of a COM callable wrapper (CCW), as shown in Figure 2-22. The CCW wraps up the .NET object and mediates between it and the common language runtime environment, making the .NET object appear to COM clients just as if it were a native COM object.

A COM client accesses a .NET object through a COM callable wrapper (CCW).

click to expand
Figure 2-22: COM callable wrapper.

To operate with a COM callable wrapper, a .NET component’s assembly must be signed with a strong name; otherwise the common language runtime won’t be able to definitively identify it. It must also reside in the GAC or, less commonly, in the client application’s directory. However, as was the case previously when building the shared component’s client, the component must also reside at registration time in a standard directory outside the GAC. Any .NET class that you want COM to create must provide a default constructor, by which I mean a constructor that requires no parameters. COM object creation functions don’t know how to pass parameters to the objects that they create, so you need to make sure your class doesn’t require this. Your class can have as many parameterized constructors as you want for the use of .NET clients, as long as you have one that requires none for the use of COM clients.

A .NET component must be signed, live in the GAC, and provide a default constructor to work with a COM client.

For a COM client to find the .NET object, we need to make the registry entries that COM requires. You do this with a utility program, called RegAsm.exe, that comes with the .NET Framework SDK. This program reads the metadata in a .NET class and makes registry entries that point the COM client to it. The sample code provides a batch file that does this for you. The registry entries that it makes are shown in Figure 2-23. Notice that the COM server for this operation is the intermediary DLL Mscoree.dll. The Class value of the InProcServer32 key tells this DLL which .NET class to create and wrap, and the Assembly entry tells it in which assembly it will find this class.

The SDK utility RegAsm.exe makes registry entries telling COM where to find the server for the .NET class.

click to expand
Figure 2-23: Registry entries made by RegAsm.exe.

A COM client accesses a .NET object as if it were a native COM object. When the client calls CoCreateInstance to create the object, the registry directs the request to the registered server, Mscoree.dll. This DLL inspects the requested CLSID, reads the registry to find the .NET class to create, and rolls a CCW on the fly based on that .NET class. The CCW converts native COM types to their .NET equivalents—for example, BSTRs to .NET Strings—and forwards them to the .NET object. It also converts the results back from .NET into COM, including any errors. The sample code for this chapter contains a COM client that accesses the shared time component assembly that we built previously in this chapter.

The sample code for this chapter contains a COM client using a .NET object.

A .NET developer could reasonably want some methods, interfaces, or classes to be available to COM clients and others not to be. Therefore, .NET provides a metadata attribute called System.Runtime.InteropServices.ComVisibleAttribute. (The .NET Framework allows you to use the abbreviated form of attribute class names, in this case ComVisible rather than ComVisibleAttribute. I’ll be using the short version from now on.) You can use this attribute on an assembly, a class, an interface, or an individual method. Items marked with this attribute set to False will not be visible to COM. The default common language runtime setting is True, so the absence of this attribute causes the item to be visible to COM. However, the Visual Studio .NET default behavior for assemblies is to set this attribute’s value to False in the AssemblyInfo.vb file. Settings made lower in the hierarchy override those made higher up. In the sample program, I set this attribute to True on my class, thereby making it visible to COM, as shown in the code that follows. If I wanted everything in the assembly visible to COM, I’d change it in AssemblyInfo.vb.

<System.Runtime.InteropServices.ComVisible(True)> Public Class Class1




Introducing Microsoft. NET
Introducing Microsoft .NET (Pro-Developer)
ISBN: 0735619182
EAN: 2147483647
Year: 2003
Pages: 110

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net