|
|
This chapter described the features that Microsoft has implemented in .NET to allow you to use COM
You have seen how interop assemblies can be generated by adding a reference in a Visual Studio .NET project, by using the TlbImp.exe utility, or by using the TypeLibConverter class from code. The chapter covered the details of how COM types are converted, and introduced some of the issues involved in importing type libraries. You have also seen how ActiveX controls can be used in Windows Forms projects, just as if they were Windows Forms controls. At this point, you should be able to take an existing COM component, and use it in a .NET application.
The
|
|
|
|
Chapter 3 showed you how COM objects can be used in .NET code and how using COM objects facilitates the process of transitioning from COM-based code to .NET applications. This chapter will show how you can also present .NET components as COM objects so that they can be used in COM client code.
You saw in Chapter 3 how the Runtime Callable Wrapper (RCW) wraps COM components to expose them to .NET. This chapter looks at COM Callable Wrappers (CCWs), which are used to wrap .NET components to expose them to COM client code.
There is exactly one COM Callable Wrapper object per .NET object, although the CCW can be used by multiple COM clients at any one time. The .NET object can be directly used by other .NET
Figure 4-1:
A COM Callable Wrapper (CCW) can be used by multiple COM clients.
Because .NET components know nothing of COM or the rules that COM objects must obey, the CCW handles all the low-level operations that must be handled by COM components, which includes handling object identity, object lifetime, and COM interface issues.
COM Callable Wrappers are created on the unmanaged heap. They are created this way so that client code can refer to interface pointers directly and to ensure interface pointers on the CCW obey the COM rules for interfaces (for example, the rule that an object must use the same pointer for an interface throughout the lifetime of the component).
Another identity-
COM rules state that COM objects should maintain a reference count, which reflects the number of interface pointers being used by clients. When a client has finished with an interface pointer, it signals the fact by calling the interfaces
Release
method, which causes the COM object to decrement the reference count. When the reference count
In contrast, the lifetimes of .NET components are managed by the common language runtime, which keeps track of references held by clients. When there are no more client references, the component can be reclaimed by the garbage collector. In the .NET world, it isnt the responsibility of the component to manage its own lifetime.
The CCW acts as a COM object, with a reference count that reflects the interfaces handed out to COM clients. It also holds a reference on the .NET component. When the CCW has no more COM clients, its reference count drops to zero; at this point, it releases the reference it holds on the .NET component.
As well as exposing the
Figure 4-2:
A COM Callable Wrapper (CCW) implements a number of standard COM interfaces.
The COM interfaces that are always implemented by a CCW are listed in Table 4-1.
|
Interface |
Description |
|---|---|
|
IUnknown |
Provides the fundamental lifetime management and interface navigation functionality for COM interfaces. |
|
IDispatch |
Provides a mechanism for late binding to objects. |
|
IProvideClassInfo |
Provides a way for clients to obtain an ITypeInfo interface pointer on an object. |
|
ISupportErrorInfo |
Enables client code to determine whether a COM object supports the IErrorInfo interface. |
|
IErrorInfo |
Provides rich error information. All .NET components support this interface, passing GUID_NULL for the ID of the interface that raised the error. |
|
ITypeInfo |
Provides type information for the class. |
In addition, several other interfaces can be exposed, as listed in Table 4-2.
|
Interface |
Description |
|---|---|
|
IDispatchEx |
If the .NET component implements the
IExpando
interface, the CCW will implement
IDispatchEx
. This provides an extension to
IDispatch
that allows enumeration, addition, deletion, and case-sensitive calling of
|
|
IConnectionPointContainer and IConnectionPoint |
These interfaces will be implemented if the .NET component is a source of events. See the section Exposing .NET Events in COM later in the chapter for more details. |
|
IEnumVARIANT |
This interface provides a COM mechanism for iterating over collections and will be implemented by the CCW if the .NET component implements the IEnumerable .NET interface. |
A .NET component can override the standard implementation of any of these interfaces (except IDispatch and IUnknown ) by providing a custom implementation. The CCW will always provide the implementation of IDispatch and IUnknown .
All access to COM objects is via interface methods. It
If a .NET class implements interfaces, those interfaces will be exported as COM interfaces. This process is shown in Figure 4-2, where the .NET component implements
IMyInterface
. This interface is in
Where a .NET class implements methods directly, the export process can create an interface through which to make the class methods visible. This interface is called the
class interface
, and by default the export process will generate a pure dispatch interface (a
The type of interface generated for the class interface, and even determining whether it is generated at all, can be controlled by using the ClassInterface attribute on .NET classes. Examples in the section Generating and Using COM Callable Wrappers later in the chapter will show this attribute in use, as well as show what the generated class interface looks like.
Although using the class interface makes it easy for .NET programmers to expose .NET components as COM components, you need to be aware of possible problems.
The COM interface rules state that an interface is immutable once it has been definedit must not change its layout or composition. The class interface, however, is generated automatically from the .NET class definition, so the layout and composition of the interface could change if the
COM client code is normally written to assume that interfaces wont change, so if the evolution of a .NET component causes changes in the generated class interface, this could end up breaking client code. To get around this problem, switch off the generation of a class interface and define .NET interfaces for the methods you want exposed to COM.
In addition, you should beware of caching the dispatch IDs (
dispIds
) assigned to members in class interfaces. If the layout of the class changes, the dispIds assigned to members might change as well. This isnt a problem for
The way around this problem is to apply the
ClassInterfaceType.AutoDispatch
attribute to the interface. This technique generates a dispinterface for the class interface, but it
| Note |
These problems with dispatch IDs mean that the class interface is most useful for purely late-bound COM clients, such as scripting languages. These clients always query for the dispatch IDs of interface members at run time, so it doesnt matter to them whether these IDs have changed. |
Finally, if you do use the class interface, be careful using dual class interfaces (specified using the
ClassInterfaceType.AutoDual
attribute). A description of the interface will appear in the generated type library, which could
|
|