| I l @ ve RuBoard |
Summary
Chapters 5.1 and 5.2 have demonstrated the way .NET deals with assemblies and shared assemblies. DLL hell becomes a problem of the past when dealing with shared assemblies as the GAC is capable of hosting side-by-side versions of
|
| I l @ ve RuBoard |
| I l @ ve RuBoard |
Chapter 5.3. COM InteroperabilityIN THIS CHAPTER
|
| I l @ ve RuBoard |
| I l @ ve RuBoard |
The World of COM
There has always been a long-standing vision of multiple languages sharing common
Before venturing further, this chapter assumes a certain
|
| I l @ ve RuBoard |
| I l @ ve RuBoard |
.NET COM Support
For Managed code to
Figure 5.3.1. Generalized view of RCW.
An RCW has several responsibilities that shield the developer from
RCW's responsibilities include the following:
Preserving Object Identity
There is only one RCW for an underlying COM component. This allows the RCW to ensure the identity of the COM object by comparing objects against the
IUnknown
interface. A RCW will appear to implement several interfaces, as provided by the underlying COM object, but the RCW actually only hides the
QueryInterface
calls from the developer. When casting for an interface, the RCW checks for a cached interface of the
Maintaining Object Lifetime
A single instance of the RCW is necessary for the proper lifetime management of the underlying COM object. The RCW handles the necessary calls to
AddRef()
and
Release()
, thus ensuring proper reference counting and
Even though the RCW
Proxying Unmanaged Interfaces
A typical COM object consists of a coclass that implements one or more custom interfaces. By custom interface, I'm referring to any interface not defined by COM. Standard COM interfaces include
IUnknown
,
IDispatch
,
ISupportErrorInfo
,
IConnectionPoint
, and others. Custom interfaces refer to developer-defined interfaces such as
ILoan
,
IBank
, and
ISpy
. The RCW must provide some mechanism that allows Managed
Given the COM object described in Listing 5.3.1, a Managed client can invoke any interface method without an explicit cast for that interface. Listing 5.3.2
Listing 5.3.1 COM Component Definition
1: [uuid(...)]
2: interface IFoo : IDispatch {
3: [id(...)] HRESULT FooMethod( ... );
4: }
5:
6: [uuid(...)]
7: interface IBar : IDispatch {
8: [id(...] HRESULT BarMethod( ... );
9: }
10:
11: [uuid(...)]
12: coclass FooBar {
13: [default] interface IFoo;
14: interface IBar;
15: }
Listing 5.3.2 Managed Wrapper Code1: FooBar fb = new FooBar( ); 2: //Invoke IFoo.FooMethod 3: //No need for explicit IFoo cast 4: fb.FooMethod( ); 5: 6: //Invoke IBar.BarMethod 7: //No need for explicit IBar cast 8: fb.BarMethod( );
In the classic COM world, it would be necessary to QI for a particular interface to invoke the
Marshaling Method CallsTo invoke a COM Interface method, there are several tasks that need attention. Specifically, these tasks include the following:
Once again, .NET comes to the rescue because this functionality is provided by the generated RCW. Consuming Selected Interfaces
Because a Managed client has no need for
IUnknown
or
IDispatch
,
Some of the most common interfaces, such as ISupportErrorInfo , IConnectionPoint , and IEnumVARIANT , are mapped to Managed concepts. In the case of ISupportErrorInfo , the extend error information will be propagated into the exception being thrown. The IConnectionPoint interface maps to the concept of events and delegates within .NET. When importing a COM object that supports IConnectionPoint , all event methods will be converted to the proper event construct in C# along with the delegate definition. TlbImp.exe
Up to this point, the concept of RCW has merely been in the abstract without a true physical entity. When it is necessary for a Managed Client to consume a class COM object, there needs to exist a Managed Wrapper ”the RCW. This task is
The following is the basic syntax for TlbImp.exe : TlbImp.exe TypeLibName [/out:<FileName>] Simple ObjectTo gain a better understanding of the relationship between classic COM and .NET RCW, consider the following example COM object (see Listing 5.3.3). Listing 5.3.3 SimpleObject.h
1: // SimpleObject.h : Declaration of the CSimpleObject
2:
3: #pragma once
4: #include "resource.h" // main symbols
5:
6:
7: // ISimpleObject
8: [
9: object,
10: uuid("6D854C55-4549-44FB-9CDF-6079F56B232E"),
11: dual, helpstring("ISimpleObject Interface"),
12: pointer_default(unique)
13: ]
14: __interface ISimpleObject : IDispatch
15: {
16:
17:
18: [id(1), helpstring("method SayHello")] HRESULT SayHello([in] BSTR Name, [out,
SimpleObject.h defines a single interface ISimpleObject and a coclass CSimpleObject that implements the ISimpleObject interface. ISimpleObject defines a single method SayHello that takes a BSTR input and returns a BSTR. The RCW will marshal the BSTR type as a CLR string. Listing 5.3.4 provides the implementation for the SayHello method. Listing 5.3.4 SimpleObject.cpp
1: // SimpleObject.cpp : Implementation of CSimpleObject
2: #include "stdafx.h"
3: #include "SimpleObject.h"
4:
5: // CSimpleObject
6:
7: STDMETHODIMP CSimpleObject::SayHello(BSTR Name, BSTR* Message)
8: {
9: // TODO: Add your implementation code here
10: CComBSTR Msg( "Hello " ); Msg += Name;
11: *Message = ::SysAllocString( Msg.m_str );
12:
13: return S_OK;
14: }
15:
The
SayHello
method implementation merely creates a
To create a RCW for SimpleATL.dll , issue the following command: TlbImp SimpleATL.dll /out:SimpleATLImp.dll This will produce the necessary RCW for use by a .NET Managed Client. The Managed Wrapper can then be inspected with ILDASM; as depicted in Figure 5.3.2. Figure 5.3.2. Managed Wrapper in ILDASM.
Early BindingWith the RCW created, using the COM object is a simple matter of adding the appropriate reference to the project. When a reference is added to the project, the object is available for Early Binding. Early Binding allows for compile time type checking and method verification (see Listing 5.3.5). For a COM object to be available for Early Binding, it must support dual interfaces. For details on dual/dispatch interfaces, refer to a COM text. If a COM object only supports IDispatch , first ”the creator of the COM object should be flogged, and then the object can only be accessed with late binding and reflection. Listing 5.3.5 Early Binding
1: namespace EarlyBinding
2: {
3: using System;
4:
5: class Early
6: {
7: static void Main(string[] args)
8: {
9: SimpleATLImp.CSimpleObject o = new SimpleATLImp.CSimpleObject( );
10: Console.WriteLine(o.SayHello( "Richard" ));
11: }
12: }
13: }
14:
With the generated RCW, using a COM object requires no additional code as far as the Managed client is
Late BindingLate binding refers to the use of a COM objects IDispatch interface for runtime discovery of services. The purpose of an IDispatch interface is to provide an interface for use with scripting clients. Scripting clients, such as VBScript or JScript, are unable to make use of raw interfaces and require late bound IDispatch . .NET also provides the ability to make use of late binding through the IDispatch interface of a COM object (see Listing 5.3.6). Doing so, however, does not allow for compile time type checking. The developer must make use of the Reflection API to access instance methods and properties. Listing 5.3.6 Late Binding
1: namespace LateBinding
2: {
3: using System;
4: using System.Reflection;
5: using System.Runtime.InteropServices;
6:
7: class Late
8: {
9: static void Main(string[] args)
10: {
11: try
12: {
13: Type SimpleObjectType =
Obviously, using a COM object with late binding requires just a bit of code. The advantage of learning how to use late binding is the ability to dynamically load COM objects and use reflection to discover the services it provides. Also, you may end up having a COM object that only supports the IDispatch interface, in which case late binding is the only way. COM Inheritance? Blasphemy!You can't inherit from a COM coclass; that's against the rules of COM. COM states that only interface implementation is possible and not implementation inheritance. Not anymore! Because there exists a Managed RCW for the COM object, it, like any other object, can serve as a base class in .NET. Listing 5.3.7 highlights the extensibility of .NET and the advanced language interoperability. The details of COM have been abstracted away, thus freeing the developer to concentrate on other issues. Listing 5.3.7 Extending CSimpleObject
1: namespace Inherit
2: {
3: using System;
4:
5: /// <summary>
6: /// Use the CSimpleObject coclass as the base class
7: /// </summary>
8: public class SimpleInherit : SimpleATLImp.CSimpleObject
9: {
10:
11: /// <summary>
12: /// Extend the functionality of CSimpleObject
13: /// </summary>
14: public void NewMethod( )
15: {
16: Console.WriteLine("Cool, derived from COM coclass");
17: }
18: }
19:
20:
21: class COMdotNETStyle
22: {
23: static void Main(string[] args)
24: {
25: //Create new derived class.
26: SimpleInherit simpleInherit = new SimpleInherit( );
27:
28: //Invoke COM SayHelloMethod
29: string s = simpleInherit.SayHello("Me");
30: Console.WriteLine(s);
31:
32: //Invoke derived class method
33: simpleInherit.NewMethod( );
34: }
35: }
36: }
37:
IConnectionPoint
.NET introduces the notion of events and delegates that handle those events. In COM, there exists a source and a sink. These entities are attached through the
IConnectionPoint
interface in which the sink
Each event exposed by the COM object will have a delegate with the following naming convention: _I<EventInterface>_<EventName>EventHandler
Although it may not be pretty to look at, it does the job and saves you a considerable amount of work. To
Listing 5.3.8 _ ISourceObjectEvents Interface
1: // _ISourceObjectEvents
2: [
3: dispinterface,
4: uuid("F0507830-BD45-479D-849F-35E422A5C7FA"),
5: hidden,
6: helpstring("_ISourceObjectEvents Interface")
7: ]
8: __interface _ISourceObjectEvents
9: {
10:
11: [id(1), helpstring("method OnSomeEvent")] void OnSomeEvent([in] BSTR Message);
12: } ;
13:
The event method OnSomeEvent , line 11, will translate into an event/delegate pair when the RCW is created. The delegate will have the following, somewhat hideous, name: _ISourceObjectEvent_OnSomeEventEventHandler This delegate can then be created and attached to the event OnSomeEvent (see Listing 5.3.9). Listing 5.3.9 SimpleSink Source
1: namespace CSharpSink
2: {
3: using System;
4: using ATLSourceImp;
5:
6: /// <summary>
7: /// Summary description for Class1.
8: /// </summary>
9: class SimpleSink
10: {
11: static void Main(string[] args)
12: {
13:
14: CSourceObject source = new CSourceObject( );
15: source.OnSomeEvent +=
16: new _ ISourceObjectEvents_OnSomeEventEventHandler(OnSomeEvent);
17:
18: source.MakeEventFire( "Hello" );
19: }
20:
21: public static void OnSomeEvent( string Message )
22: {
23: Console.WriteLine("Have Event: { 0} ", Message );
24: }
25: }
26: }
27:
The event hookup is no different than with other .NET event/delegate pairs. The
CSimpleObject
class invokes the
OnSomeEvent
whenever the method is invoked. The COM
Interop
layer handles the marshaling of the COM event and directing the event to the proper delegate on your
Threading Issues
COM introduced a
A managed client creates threads within an STA. When using a COM object that also is STA, there is no penalty incurred when invoking methods on the various interfaces. However, if the COM object is MTA, it's time to pay the
To change the threading model, access the CurrentThread and set the ApartmentState as needed. Note that the ApartmentState can only be set once. Depending on the application, it might be beneficial to create a worker thread to access the classic COM object with the thread's ApartmentState set to the necessary Apartment type. Listing 5.3.10 changes the ApartmentState of the current thread to MTA to match that of the MTAObject being accessed. Listing 5.3.10 Changing Apartments1: Thread.CurrentThread.ApartmentState = ApartmentState.MTA; 2: //Create MTA Object MTAObject o = new MTAObject( ); 3: o.Foo( ); Making use of threading model changes, the overhead of marshalling COM calls across various apartments will be reduced. In server-side code where speed is critical, saving overhead is paramount, and knowing the threading model used by the COM object allows for maximum call efficiency. COM Types to .NET Types
The mapping of supported COM types to .NET types is
Table 5.3.1. COM to .NET Type Mappings
Although Table 5.3.1 is by no means complete, it does
|
| I l @ ve RuBoard |