Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Microsoft Visual C++ .NET Professional Projects - Premier Press

.pdf
Скачиваний:
180
Добавлен:
24.05.2014
Размер:
26 Мб
Скачать
☆
Applications that are capable of acting as a host for in-process servers are referred to as surrogate processes. MTS and COM+ services are the two classic examples of surrogate processes. Surrogate processes make it possible to run an inprocess server remotely.
SURROGATE PROCESS

570Project 5 CREATING A COM COMPONENT USING ATL

EXEs are robust, because crashing of the server doesn’t affect the client, and vice versa. This is due to the fact that they reside in different address spaces.

EXE, the server component, need not run in the client’s security context.

Figure 17-5 illustrates an out-of-process server.

 

 

 

 

 

 

Y

 

 

 

 

 

 

Client

 

 

 

IPC

 

L

Server

FIGURE 17-5 An out-of-process server

F

 

 

 

 

 

A

 

 

 

 

E

 

 

 

The out-of-process servers canMreside on either a local or a remote machine.

 

 

T

 

 

 

When the server resides on the same machine as that of a client but in a different address space, it is referred as a local server. When the server resides on a different machine that is connected to the client via a network, it is referred as a remote server. To implement remote servers, you use Distributed COM (DCOM). Tools, such as Microsoft Transaction Server (MTS), are used to implement DCOM.

Having learned about the types of COM servers, and before delving into the intricacies of designing a COM component, it is advisable to spend some time understanding the COM library.

The COM Library

As stated earlier, COM specifications form the heart of COM components. These specifications are part of the COM library. The COM library is a runtime library that includes the following:

API functions that aid you in creating COM clients and servers.

Services that help a component identify the location of the server; these services are part of the system registry.

A mechanism to allow an application to control the memory allocation within its process.

Team-Fly®

INTRODUCTION TO COM Chapter 17 571

The COM library encapsulates the process followed in dynamic loading of components. When a client application creates a component object, it passes the associated ID to the COM library. (You will learn about the ID later in this chapter.) The COM library then uses the ID to trace for the details of the associated component from the HKEY_CLASSES_ROOT key of the registry. Besides tracing the component, the COM library can also distinguish between in-process and out-of-process components and load them accordingly.

Now that you understand the role of the COM library, you will next learn about interfaces, the core of a component.

Interfaces

Remember the concepts of classes and objects of OOP? A class defines the attributes and methods of an object, which is an instance of the class. For example, class ‘employee’ defines the following attributes: Emp_Name and Emp_Code. It also defines the methods that you can use to work with these attributes; for example, Get_Emp_Code(). In a nutshell, to work with a class, you need to instantiate an object of the class that associates itself with the class. Then, using the object, you could invoke the required methods to manipulate the data. COM components also work along these lines. Then, how does it differ from the earlier objects? As previously mentioned, the new feature of COM, interfaces, is the differentiating factor.

NOTE

You can correlate interfaces with the abstract classes of C++. Remember, abstract classes allow you to implement inheritance in C++. Just as an abstract class has pure virtual functions — functions that have only the declaration (skeleton) and no definition (body) — to define a common behavior for all inherited classes, interfaces also use functions to define the common behavior for the COM components. However, unlike an abstract class, an interface will not declare any public, private, or protected data members.

In COM terminology, an interface is a set of logically related functions that defines the behavioral protocol between a client and a server. A COM object’s functionality can be accessed only through the interfaces that the component

572 Project 5 CREATING A COM COMPONENT USING ATL

exposes. The functionality of the interfaces is in turn implemented as functions, and these functions are referred to as methods.

Figure 17-6 displays a component with an interface defined.

Interface 1

My Object

FIGURE 17-6 Component with an interface

Besides regulating the communication path, interfaces also address the versioning problems faced with DLLs (discussed earlier). COM eliminates the problem of versioning by enforcing a rule that you cannot change the interface after it has been designed. Does that mean that there is no scope for enhancements? No. You can release another version of the interface after changes. Clients can then choose to continue to work with the older version or upgrade to the newer version.

NOTE

If you alter the functionality provided by an existing interface, the new interface will have the same name as the earlier interface but with a version number by convention. Therefore, if your earlier interface was IEncrypt, the new interface will be IEncrypt2. Once published and registered on a machine, any client compiled with the earlier version of the component will make use of IEncrypt while newer clients will use IEncrypt2.

Another significant feature of COM is that an object can support multiple interfaces simultaneously. This means that an application can work with different versions of the same component but it doesn’t require any recompilation of clients accessing the older version. Figure 17-7 displays a component with multiple interfaces.

Based on the preceding discussion, it is evident that the versioning problem is being handled by the support for immutable interfaces and support for multiple versions of the same interface.

INTRODUCTION TO COM Chapter 17 573

NOTE

For those who are not comfortable with the concept of releasing different versions of interfaces for each change, COM resolves your apprehension by supporting coexistence of multiple versions.

The Needs Addressed by COM Interfaces

So far, you have learned about the concept of interfaces. A logical follow-up is to know the needs that a COM interface addresses.

As the gap between a client and a server expanded, because of the varied languages and platforms that they were running on, the need for a common mechanism to bridge the gap was felt. The goals for this common mechanism were the following:

Cross-language and cross-plat- form enabled

Designed using any language other than programming languages. However, a common syntax and standards should be defined.

A set of rules that governs the usage of interfaces and that means the same thing in all programming languages.

All of the preceding requirements were met by defining a COM interface.

INTERFACE VERSIONING

You might wonder why you have to add new interfaces all the time instead of altering the existing code. For example, consider the following interface:

interface IEncrypt

{

virtual void Encrypt();

virtual void Decrypt();

}

Assume that you want to add two more methods to the interface to provide a stronger 128-bit encryption. To accomplish this, you edit the exiting interface, IEncrypt, and add the following code:

interface IEncrypt

{

virtual void Encrypt(); virtual void Decrypt(); //The new methods

virtual void Encrypt128(); virtual void Decrypt128();

}

The interface will now expose the extra functions, Encrypt128() and Decrypt 128(). An old client accessing this DLL will call the Encrypt and Decrypt methods, and they would execute well enough. However, because the old client does not know about the two new methods, they are never called. On the other hand, a new client would know about the new methods and would call them. However, what will happen if a new client calls on an old component? The new client can get a reference to the IEncrypt interface and call the Encrypt() and Decrypt()

continues

574 Project 5 CREATING A COM COMPONENT USING ATL

INTERFACE VERSIONING (continued)

methods successfully. But things go wrong when the client tries to call any of the new methods. The reason is that the client doesn’t realize that it is still accessing the older component, and the additions to this component are not effective because the new methods are not registered. The only way to prevent this is to create a new interface each time an existing interface changes.

COM Interfaces

Interfaces give life to components because they define the functionality of the components that is visible to others who access them. All COM components must implement one interface, which is the IUnknown interface. The IUnknown interface acts as the base class for all other interface classes. You will learn more about the IUnknown interface in the following section.

Old

 

Interface 1

 

Client

 

 

 

Interface 2

 

 

New

 

 

Client

 

 

 

 

 

My Object

FIGURE 17-7 A component with multiple interfaces

Interfaces enable applications and other components to access the methods of a component through a virtual function table, also called the vtable. The vtable, shown in Figure 17-8, is an array of pointers to the member functions of the components, and an interface maintains a pointer to the vtable (the interface pointer). All instances of the object share its vtable. (Remember that COM components are also referred to as objects.) Given that all instances share the vtable, a unique pointer is required to pair the vtable with the appropriate instance, and the interface pointer accomplishes that.

When a client application has to access a component, it creates an instance of the interface and is assigned a pointer to a pointer in the vtable. There is a reference count variable maintained that tracks the number of interfaces instantiated for each object. With each instance created, the count is increased by one using AddRef(), and as the instances are released, the count is decreased using

Release().

 

INTRODUCTION TO COM

Chapter 17

575

vtable

IUnknown::QueryInterface

 

 

 

 

 

 

pointer

IUnknown::AddRef

 

 

 

 

IUnknown::Release

 

 

 

 

IDispatch::Member

 

 

 

Client

Methods

 

 

 

 

Custom methods

 

 

 

 

vtable

 

 

 

FIGURE 17-8 The vtable

 

 

 

 

Now that you are aware of the importance of the role of an interface and how it is implemented, you will next learn about the different types of interfaces.

IUnknown

The IUnknown interface, as discussed earlier, plays a pivotal role in a component’s life cycle, because all COM components should inherit from the IUnknown interface. The three main methods of the IUnknown interface are QueryInterface, AddRef, and Release, described here:

The QueryInterface method allows you to retrieve data about the interfaces exposed by an object. This method accepts two parameters, the GUID and the address of the variable that will store the pointer to the interface that the given GUID maps to. (You will learn more about GUIDs in the following section.) If the object doesn’t support the interface specified by the GUID, then this variable is set to NULL. Figure 17-9 depicts the role of the QueryInterface method.

The AddRef and Release methods track the number of instances created for an interface of an object and the number of instances released. The following code snippet illustrates the implementation of the AddRef and

Release methods:

ULONG __stdcall CSampleClass::AddRef()

{

return ++mRef;

}

ULONG __stdcall CSampleClass::Release()

576 Project 5 CREATING A COM COMPONENT USING ATL

{

mRef=mRef-1;

if (mRef == 0) { delete this;

return 0;

}

return mRef;

IUnknown *iu;

ImyInterface *pl;

iu->QueryInterface((IID_IMyInterface,(void**)&pl);

IUnknown

InsideCOM

IMyinterface

FIGURE 17-9 The QueryInterface method

CAUTION

All COM components must implement the IUnknown interface and the three methods

QueryInterface, AddRef, and Release.

IDispatch

The IDispatch interface inherits from the IUnknown interface. This interface provides support for automation; hence, to provide automation support, a COM component must implement this interface. The methods of the IDispatch inter-

face are GetTypeInfoCount, GetTypeInfo, GetIDsOfNames, and Invoke, described

here:

GetTypeInfoCount. This method retrieves the number of interfaces, exposed by an object, that provide type information.

TYPES OF INHERITANCE
Inheritance can be categorized into two types:
Implementation inheritance. In this type of inheritance, the derived class inherits the complete functionality (including the interface and implementation details) of the base class. One object is tightly bound with another; hence, any change in the base class results in changes in the derived classes.
Interface inheritance. In this type of inheritance, the derived class inherits only the interface of the base class. The implementation details are not inherited; hence, changes in the base class don’t affect the derived class. COM imple-

INTRODUCTION TO COM Chapter 17 577

GetTypeInfo. This method retrieves a description of the interfaces that provide type information.

GetIDsOfNames. This method retrieves the DISPID (Dispatch ID) associated with each of the methods and properties.

Invoke. This method enables you to access the methods and manipulate the properties of an object.

Components can also support dual interfaces. These components implement the IDispatch interface as well as define custom interfaces, thus exhibiting a dual behavior. The clients now have the option to access the object either through the IDispatch interface or through the cus-

tom interface defined. The process of accessing objects through custom interfaces is often referred as “v-table binding.” The difference between pure automationbased objects and objects supporting a dual interface is that if you are accessing an automation-based object that supports only the IDispatch interface, then you can access its functionality only through the IDispatch interface, whereas if you are accessing an object supporting a dual interface, then the client has the flexibility to access the object’s functionality using either the IDispatch interface or the custom interface.

NOTE

The IDispatch interface is a kind of surrogate interface because it doesn’t offer any functionality of its own.

578 Project 5 CREATING A COM COMPONENT USING ATL

IClassFactory

This interface is used to instantiate out-of-process local or remote components. Unlike the in-process components, you cannot use the new operator to instantiate these objects. Instead, you use the IClassFactory interface.

IOleLink and IOleObject

These interfaces are used to implement the linking and embedding of OLE objects.

IStream and IStorage

These interfaces are used to create and manage storage objects and are used in OLE applications that access data from varied sources.

GUID

Based on the discussion to this point, it is evident that components have a global reach, which means that uniqueness of each component is a must. To accomplish this, the Open Software Foundation (OSF) proposed an algorithm to generate unique identifiers, initially known as Universally Unique Identifiers (UUIDs), and later, in COM, as Globally Unique Identifiers (GUIDs).

A GUID is a 128-bit number that can be assigned to an interface, a class, or a library. The algorithm used to generate GUIDs is based on the following features:

The current date and time

The network adapter card address

The clock sequence

An automatically incremented counter

To generate GUIDs, you can either use the GUIDGEN tool (guidgen.exe) or invoke the CoCreateGuid method, which is a COM API, at run time.

NOTE

The guidgen.exe file is located in the (Drive):\Program Files\Microsoft Visual Studio

.NET\Common7\Tools\ folder.

INTRODUCTION TO COM Chapter 17 579

Figure 17-10 depicts the GUIDGEN tool.

FIGURE 17-10 The GUIDGENtool screen

Now consider an example that helps to explain the GUID concept. The IID of the IUnknown interface is

00000000-0000-0000-C000-000000000046

To remember such numbers is humanly impossible. As a workaround, you can # define the user-defined names of the classes and interfaces to their corresponding GUIDs. To do so, you can use the DEFINE_GUID macro as follows:

// {8DC1D7D5-D8F1-4bf4-9B58-B4EA2FBA530E} DEFINE_GUID(IID_IEncrypt,

0x8dc1d7d5, 0xd8f1, 0x4bf4, 0x9b, 0x58, 0xb4, 0xea, 0x2f, 0xba, 0x53, 0xe);

So far, you have learned about the basics of interfaces, their role in COM, their types, and the GUID that is used to refer to a component uniquely. Moving ahead, the chapter addresses another primary aspect of COM — language independence.

Consider the following code snippets, which define interfaces in different languages, C++, Java, and Visual Basic.

Interface definition in C++

//This is a custom interface because you have designed it