.NET And COM


COM is the predecessor technology to .NET. COM defines a component model where components can be written in different programming languages. A component written with C++ can be used from a Visual Basic client. Components can also be used locally inside a process, across processes, or across the network. Does this sound familiar? Of course, .NET has similar goals. However, the way in which these goals are achieved is different. The COM concepts became more and more complex to use and turned out not to be extensible enough. .NET fulfills similar goals as COM had, but introduces new concepts to make your job easier.

Even today, when using COM Interop the prerequisite is to know COM. It doesn't matter if .NET components are used by COM clients or COM components are used by .NET applications, you must know COM. So this section compares COM and .NET functionality.

If you already have a good grasp of COM technologies, this section may be a refresher to your COM knowledge. Otherwise it introduces you to the concepts of COM — now using .NET — that you can be happy not to deal with anymore in your daily business. However, all the problems that came with COM still apply when COM technology is integrated in .NET applications.

COM and .NET do have many similar concepts with very different solutions, including the following:

  • Metadata

  • Freeing memory

  • Interfaces

  • Method binding

  • Data types

  • Registration

  • Threading

  • Error handling

  • Event handling

Metadata

With COM, all information about the component is stored inside the type library. The type library includes information such as names and ids of interfaces, methods, and arguments. With .NET all this information can be found inside the assembly itself, as you saw in Chapter 11, "Reflection," and Chapter 15, "Assemblies." The problem with COM is that the type library is not extensible. With C++, IDL (inter- face definition language) files have been used to describe the interfaces and methods. Some of the IDL modifiers cannot be found inside the type library, because Visual Basic (and the Visual Basic team was responsible for the type library) couldn't use these IDL modifiers. With .NET this problem doesn't exist because the .NET metadata is extensible using custom attributes.

As a result of this behavior, some COM components have a type library and others don't. Where no type library is available, a C++ header file can be used that describes the interfaces and methods. With .NET it is easier using COM components that do have a type library, but it is also possible to use COM components without a type library. In that case it is necessary to redefine the COM interface by using C# code.

Freeing Memory

With .NET, memory is released by the garbage collector. This is completely different with COM. COM relies on reference counts.

The interface IUnknown, which is the interface that is required to be implemented by every COM object, offers three methods. Two of these methods are related to reference counts. The method AddRef() must be called by the client if another interface pointer is needed; this method increments the reference count. The method Release() decrements the reference count, and if the resulting reference count is 0, the object destroys itself to free memory.

Interfaces

Interfaces are the heart of COM. They distinguish between a contract used between the client and the object, and the implementation. The interface (the contract) defines the methods that are offered by the component and that can be used by the client. With .NET, interfaces play an important part, too.

COM distinguishes between three interface types: custom, dispatch, and dual interfaces.

Custom interfaces

Custom interfaces derive from the interface IUnknown. A custom interface defines the order of the methods in a virtual table (vtable), so that the client can access the methods of the interface directly. This also means that the client needs to know the vtable during development time, because binding to the methods happens by using memory addresses. As a conclusion, custom interfaces cannot be used by scripting clients. Figure 33-1 shows the vtable of the custom interface IMath that offers the methods Add() and Sub() in addition to the methods of the IUnknown interface.

image from book
Figure 33-1

Dispatch interfaces

Because a scripting client (and earlier Visual Basic clients) doesn't support custom interfaces, a different interface type is needed. With dispatch interfaces, the interface available for the client is always the IDispatch interface. IDispatch derives from IUnknown and offers four methods in addition to the IUnknown methods. The two most important methods are GetIDsOfNames() and Invoke(). As shown in Figure 33-2, with a dispatch interface two tables are needed. The first one maps the method or property name to a dispatch id; the second one maps the dispatch id to the implementation of the method or property.

image from book
Figure 33-2

When the client invokes a method in the component, at first it calls the method GetIDsOfNames(), passing the name of the method it wants to call. GetIDsOfNames() makes a lookup into the name-to-id table to return the dispatch id. This id is used by the client to call the Invoke()method.

Note

Usually, the two tables for the IDispatch interface are stored inside the type library, but this is not a requirement, and some components have the tables on other places.

Dual interfaces

As you can imagine, dispatch interfaces are a lot slower compared to custom interfaces. On the other hand, custom interfaces cannot be used by scripting clients. A dual interface can solve this dilemma. As you can see in Figure 33-3, a dual interface derives from IDispatch but offers the additional methods of the interface directly in the vtable. Scripting clients can use the IDispatch interface to invoke the methods, whereas clients aware of the vtable can call the methods directly.

image from book
Figure 33-3

Casting and QueryInterface

If a .NET class implements multiple interfaces, casts can be done to get one interface or another. With COM, the interface IUnknown offers a similar mechanism with the method QueryInterface(). As discussed in the previous section, the interface IUnknown is the base interface of every interface, so QueryInterface() is available anyway.

Method Binding

How a client maps to a method is defined with the terms early and late binding. Late binding means that the method to invoke is looked for during runtime. .NET uses the System.Reflection namespace to make this possible (see Chapter 11).

COM uses the IDispatch interface discussed earlier for late binding. Late binding is possible with dispatch and dual interfaces.

With COM, early binding has two different options. One way of early binding, also known as vtable binding, is using the vtable directly — this is possible with custom and dual interfaces. The second option of early binding is also known as id binding. Here the dispatch id is stored inside the client code, so during runtime only a call to Invoke() is necessary. GetIdsOfNames() is called during design time. With such clients it is important to remember that the dispatch id must not be changed.

Data Types

For dual and dispatch interfaces, the data types that can be used with COM are restricted to a list of automation-compatible data types. The Invoke() method of the IDispatch interface accepts an array of VARIANT data types. The VARIANT is a union of many different data types such as BYTE, SHORT, LONG, FLOAT, DOUBLE, BSTR, IUnknown*, IDispatch*, and so on. VARIANTs have been easy to use from Visual Basic, but it was complex to use them from C++. .NET has the Object class instead of VARIANTs.

With custom interfaces, all data types available with C++ can be used with COM. However, this also restricts the clients that can use this component to certain programming languages.

Registration

.NET distinguishes between private and shared assemblies, as discussed in Chapter 15. With COM, all components are globally available by a registry configuration.

All COM objects have a unique identifier that consists of a 128-bit number and is also known as class id (CLSID). The COM API call to create COM objects, CoCreateInstance(), just looks into the registry to find the CLSID and the path to the DLL or EXE to load the DLL or launch the EXE and instantiate the component.

Because such a 128-bit number cannot be easily remembered, many COM objects also have a prog id. The prog id is an easy-to-remember name, such as Excel.Application, that just maps to the CLSID.

In addition to the CLSID, COM objects also have a unique identifier for each interface (IID) and for the type library (typelib id).

Information in the registry is discussed in more detail later in the chapter.

Threading

COM uses apartment models to relieve the programmer from threading issues. However, this also adds some more complexity. Different apartment types have been added with different releases of the operating system. This section discusses the single-threaded apartment and the multi-threaded apartment.

Note

Threading with .NET is discussed in Chapter 13, "Threading."

Single-threaded apartment

The single-threaded apartment (STA) was introduced with Windows NT 3.51. With an STA, only one thread (the thread that created the instance) is allowed to access the component. However, it is legal to have multiple STAs inside one process, as shown in Figure 33-4.

image from book
Figure 33-4

In this figure, the inner rectangles with the lollipop represent COM components. Components and threads (curved arrows) are surrounded by apartments. The outer rectangle represents a process.

With STAs there's no need to protect instance variables from multiple thread access because this protection is provided by a COM facility, and only one thread accesses the component.

A COM object that is not programmed with thread safety marks the requirements for an STA in the registry with the registry key ThreadingModel set to Apartment.

Multi-threaded apartment

Windows NT 4.0 introduced the concept of a multi-threaded apartment (MTA). With an MTA, multiple threads can access the component simultaneously. Figure 33-5 shows a process with one MTA and two STAs.

image from book
Figure 33-5

A COM object programmed with thread-safety in mind marks the requirement for an MTA in the registry with the key ThreadingModel set to Free. The value Both is used for thread-safe COM objects that don't mind the apartment type.

Note

Visual Basic 6.0 didn't offer support for multi-threaded apartments.

Error Handling

With .NET, errors are generated by throwing exceptions. With the older COM technology, errors are defined by returning HRESULT values with the methods. An HRESULT value of S_OK means that the method was successful.

If a more detailed error message is offered by the COM component, the COM component implements the interface ISupportErrorInfo, where not only an error message but also a link to a help file and the source of the error is returned with an error information object on the return of the method. Objects that implement ISupportErrorInfo are automatically mapped to more detailed error information with an exception in .NET.

Event Handling

.NET offers an event-handling mechanism with the C# keywords event and delegate (see Chapter 6, "Delegates and Events"). Chapter 29, ".NET Remoting," discussed that the same mechanism is also available with .NET Remoting.

Figure 33-6 shows the COM event-handling architecture. With COM events, the component has to implement the interface IConnectionPointContainer and one or more connection point objects (CPOs) that implement the interface IConnectionPoint. The component also defines an outgoing interface — ICompletedEvents in Figure 33-6 — that is invoked by the CPO. The client must implement this outgoing interface in the sink object, which itself is a COM object. During runtime, the client queries the server for the interface IConnectionPointContainer. With the help of this interface, the client asks for a CPO with the method FindConnectionPoint() to get a pointer to IConnectionPoint returned. This interface pointer is used by the client to call the Advise() method, where a pointer to the sink object is passed to the server. In turn, the component can invoke methods inside the sink object of the client.

image from book
Figure 33-6

Later in this chapter, you learn how the .NET events and the COM events can be mapped so that COM events can be handled by a .NET client and vice versa.




Professional C# 2005
Pro Visual C++ 2005 for C# Developers
ISBN: 1590596080
EAN: 2147483647
Year: 2005
Pages: 351
Authors: Dean C. Wills

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