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

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

.pdf
Скачиваний:
180
Добавлен:
24.05.2014
Размер:
26 Мб
Скачать
☆

580 Project 5 CREATING A COM COMPONENT USING ATL

NOTE

GUIDs are of four types:

IID. A GUID assigned to an interface. The syntax for defining the same is

IID_<unique identifier> = {<value>}.

CLSID. A GUID assigned to a class. The syntax for defining a CLSID is

CLSID_<unique identifier> = value.

ProgID. A user-friendly name assigned to an object and mapped to a class ID.

 

The syntax followed is <Program>.<Component>.<Version>. To convert a ProgID

 

to its equivalent class ID, you can use the C SIDFromProgID method, and to

 

achieve the reverse, you can use the ProgID romCLSID method.

 

 

 

 

Y

 

TLBID. A GUID assigned to a type library. The syntax is <TypeLib identifier>

 

= {<value>}.

 

L

 

 

F

 

 

 

 

 

 

M

 

Class dummyInterface: public IUnknown

 

{

 

A

 

public:

E

 

 

 

 

virtual HRESULT stdcall Display()=0;

 

 

 

T

 

 

//pure virtual function

}

Interface definition in Java

public interface dummyInterface extends com.ms.com.IUnknown

{

public abstract void Display();

}

Interface definition in Visual Basic

VERSION 1.0 CLASS

BEGIN

MultiUse = -1 True

END

Attribute VB_Name = “IdummyInterface”

Attribute VB_Creatable = True

Attribute VB_Exposed = True

Public Function Display()

End Function

Team-Fly®

INTRODUCTION TO COM Chapter 17 581

If you examine the preceding code snippets, you find that the interface definition varies from one language to another, because the syntax of one language differs from the others. The question that arises is how COM supports language interoperability. The answer from Microsoft is the Interface Definition Language (IDL).

IDL

The OSF initially developed IDL for distributed computing that was implemented using remote procedure calls (RPCs). IDL was designed to provide a standard that will be followed by the client and the server involved in RPC. Interfaces were addressing the same requirements; hence, Microsoft adopted IDL for designing COM interfaces. As a programmer of IDL, you need to understand that IDL is not a programming language but only a language to define interfaces. However, you still have the flexibility to implement interfaces using a programming language of your choice. To put it precisely, IDL is used to do the following:

Describe objects

Describe the interfaces implemented by objects

Declare the methods used by interfaces

Define the arguments of the methods

Define the type library (described later in the chapter)

Here’s a sample interface defined using IDL:

import “unknwn.idl”

[object, uuid[21000001-0000-0000-0000-000000000007)] interface SampleInterface:IUnknown

{

HRESULT SampleMethod([in] int x);

};

The definition of the IUnknown interface, as given in the Unknwn.h file, is as follows:

MIDL_INTERFACE(“00000000-0000-0000-C000-000000000046”)

IUnknown

{

public:

582 Project 5 CREATING A COM COMPONENT USING ATL

BEGIN_INTERFACE

virtual HRESULT STDMETHODCALLTYPE QueryInterface( /* [in] */ REFIID riid,

/* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject) = 0;

virtual ULONG STDMETHODCALLTYPE AddRef( void) = 0;

virtual ULONG STDMETHODCALLTYPE Release( void) = 0; template<class Q>

HRESULT STDMETHODCALLTYPE QueryInterface(Q** pp)

{

return QueryInterface(__uuidof(Q), (void **)pp);

}

END_INTERFACE

};

NOTE

All COM interface definitions in IDL must begin with the “object” attribute. This attribute is used to differentiate a COM interface from that of an RPC interface. The unique identifier of the corresponding interface follows this. Developers have the flexibility to create interfaces of the same name because they can differentiate them by assigning unique identifiers to them.

Remember that IDL is a tool provided to programmers to design interfaces and are not part of COM.

The declaration for IDispatch in the Oaidl.h file is as follows:

MIDL_INTERFACE(“00020400-0000-0000-C000-000000000046”)

IDispatch : public IUnknown

{

public:

virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount( /* [out] */ UINT *pctinfo) = 0;

virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(

INTRODUCTION TO COM Chapter 17 583

/* [in] */ UINT iTInfo, /* [in] */ LCID lcid,

/* [out] */ ITypeInfo **ppTInfo) = 0;

virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames( /* [in] */ REFIID riid,

/* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames,

/* [in] */ LCID lcid,

/* [size_is][out] */ DISPID *rgDispId) = 0;

virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke( /* [in] */ DISPID dispIdMember,

/* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags,

/* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult,

/* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr) = 0;

};

The declaration for IClassFactory in the Unknwn.idl file is as follows:

interface IClassFactory : IUnknown

{

HRESULT CreateInstance ([in, unique] IUnknown *pUnkOuter, [in] REFIID riid, [out] void ** ppvObject);

HRESULT LockServer([in] BOOL flock);

}

TIP

You can locate the Unknwn.h file in the Program Files\Microsoft Visual Studio

.NET\Vc7\PlatformSDK\Include folder.

584 Project 5 CREATING A COM COMPONENT USING ATL

In the preceding code snippet, notice the use of a macro STDMETHODCALLTYPE. This macro, along with the STDMETHOD_ macro, allows you to specify the return type of methods. Almost all COM interface methods return a HRESULT value. If your COM methods return any value other than HRESULT, then you need to use the STDMETHOD_ macro to specify the desired return type.

HRESULT

As stated earlier, almost all COM methods return a HRESULT value. HRESULT is a 32-bit integer value. Internally, the HRESULT value is represented as a combination of the following four fields:

S. This is named the Severity field and indicates the success status of the method. It is a single-bit field. A 0 in this bit indicates success, while a 1 indicates an error.

R. This is a 2-bit field that is reserved for future use. This should be set to 0 by methods returning HRESULT values.

Facility. This is a 13-bit field that specifies the status code to which the method belongs.

Code. This is a 16-bit field that stores the result of the execution of the method

— either the error message or other results.

Although internally bits are assigned specific tasks, you need not manipulate these bits. Instead, you have macros that return the value stored in these bits. For instance, the SUCCEEDED macro returns the value in the S bit, which indicates whether or not the call to the method was successful.

The HRETURN values are predefined; for instance, the Winerro.h file contains all standard HRESULT error values with a description for the same.

coClass

By now, you are aware of the fact that interfaces are abstract. This is because the implementation of interfaces is not visible to the clients. The clients use the reference to the interface and access the exposed functionality without having to bother about the implementation details. However, as a programmer, you know that some code is behind this implementation. The precise implementation details are provided in a class named coClass. Typically, one coClass has many interfaces defined in it.

The Type Library

A type library is a binary header file that contains the definitions of the classes, the methods, and the properties of an interface exposed by the component. Unlike a C or a C++ header file that is bound to the language, a type library is based on binary language. As a result, type libraries enable the language interoperability feature of COM components. Various development environments, including Visual C++, Visual Basic, and MTS, support type libraries. IDL scripts that define the

INTRODUCTION TO COM Chapter 17 585

interfaces can easily be translated to their equivalent binary code using the MIDL compiler. The binary code generated is stored in a type library, which is a file with the extension .tlb. In simple terms, MIDL compiles the IDL scripts and generates the type library for a component. Following is a sample of a type library generated by MIDL:

import “unknwn.idl”

[object, uuid(21000001-0000-0000-0000-000000000007)] interface SampleInterface:IUnknown

{

HRESULT SampleMethod([in] int x);

[ uuid(21000001-0000-0000-0000-000000000007), helpstring(“Sample COM component Type Library”), version (1.0) ]

library Sample

{

importlib(“stdole32.tlb”);

// This allows you to use the standard COM types without including their definition

interface SampleInterface;

// This includes the corresponding TLB file

}

 

Role of the Registry in COM

All COM components are registered in the system registry, because when a client instantiates a COM object, the COM service needs to look out for the whereabouts of the component (DLL or EXE). Because the registry is a common and readily accessible location, the COM components are registered here. The entries for all COM classes are created under the HKEY_CLASSES_ROOT\<Application’s Program ID>\CLSID section. Each CLSID key further contains subkeys. These subkeys specify the location of the coClass of the component. The location of the type libraries is specified under the HKEY_CLASSES_ROOT\<Application’s Program ID>\TypeLib section. The HKEY_CLASSES_ROOT\<Application’s Program ID>\Interface section of the registry contains information about the various interfaces available on your computer. Figure 17-11 illustrates the HKEY_CLASSES_ROOT key of the registry.

To register a component, you can use the REGSVR32 utility. The command to issue to register a DLL is the following:

586 Project 5 CREATING A COM COMPONENT USING ATL

FIGURE 17-11 The HKEY_CLASSES_ROOT key of the registry

REGSVR32 Encrypt.dll

Encrypt.dll specifies the DLL file name that is to be registered.

The command to register an EXE is the following:

Encrypt.exe /REGSERVER

Encrypt.exe is the EXE file name that is to be registered.

Instantiating a COM Object

Any client application that wants to avail the services of another component (a COM server) has to create an instance of the server component. To accomplish this, typically, you use the CoCreateInstance method. In the following code snippet, I have instantiated a COM class, SampServer, and acquired the reference to a custom interface exposed by this component, named ISampInter:

ISampInter1* pInter1;

HRESULT hr;

CoCreateInstance (CLSID_SampServer, NULL,CLSCTX_SERVER, IID_ISampInter1, (void**)

&pInter1);

INTRODUCTION TO COM Chapter 17 587

A brief description of the parameters of the method follows. They are described in the sequence in which they are accepted by the method.

CLSID_SampServer. The ID of the class to which the server component belongs.

NULL. Indicates that this component is not using aggregation.

CLSCTX_SERVER. The class context in which the newly instantiated component will run.

IID_ISampInter1. The ID of the interface whose functionality you are accessing.

pInter1. The pointer to the interface to which the given ID points.

The pInter parameter can be used to access any of the methods provided by the

ISampInter1 interface.

Now assume that the same component exposes another interface, ISampInter2. To invoke the methods of this interface, you need to first obtain a pointer to it. This is where the QueryInterface method of the IUnknown interface enters the picture:

ISampInter2* pInter2;

hr = pInter1->QueryInterface (IID_ISampInter2, (void**) &pInter2);

if(SUCCEEDED(hr))

{

—

}

So far, you have looked into how to instantiate a COM object. If the component is an in-process server, you can use the new operator to instantiate it. What if it is a local or a remote out-of-process server? In this case, you use another predefined interface, IClassFactory, introduced earlier in the discussion of the types of interfaces.

TIP

Objects that don’t have any functionality of their own except that they can be used to instantiate other classes are called COM class objects. All COM class objects implement the IClassFactory interface and, hence, are referred to as class factories.

588 Project 5 CREATING A COM COMPONENT USING ATL

The IClassFactory provides two methods, CreateInstance and LockServer. The

CreateInstance method instantiates the COM object, while the LockServer method keeps the component in the memory so that instantiation is quicker. The client invokes a method, CoCreateInstance, that in turn invokes the CreateInstance function to complete the instantiation.

Now, let me explain how location transparency, one of the notable features of COM, is achieved. Some activities happen behind the scenes. What are they? Well, you are aware that a COM server component can be an in-process server or an out-of-process server and can be based on any COM-savvy language. Then, how is the instantiating process standardized?

Once a client requests access to a COM component, it invokes the Service Control Manager (SCM). The SCM then has to obtain a reference to the class factory object of the corresponding component. In case of an in-process server, the SCM does this by directly connecting to a class factory object using a well-known DLL method, whereas the SCM handles this differently in an out-of-process server.

When an out-of-process server component is instantiated, it must register a class factory object for each of its creatable coClasses with the COM library. The SCM maintains a class table that stores all class factory object references for each of the registered classes. When a request to an out-of-process server is placed, the SCM scans through the class table to locate a registered class factory object. Once the reference is obtained, the SCM activates it. If the required class is not registered, then the SCM waits for the server to register it and then revisits the table to repeat the same process.

After the SCM creates the out-of-process object, it must bind the object to the clients using a proxy/stub pair. The proxy/stub pair accomplishes the cross-process communication. The process of communicating through the proxy/stub pair is prevalently known as marshalling. In this process, the SCM first creates the stub in the object’s server process, and the stub then manages the real interface pointer. Next, the proxy object is created in the client’s process, and then it is connected to the stub. The proxy then interacts with the stub and supplies the interface pointer to the client. In simple terms, the proxy/stub pair acts as the mediator between the client and the server. Figure 17-12 illustrates the role of the proxy and the stub.

In a nutshell, marshalling is the process of bundling the methods’ properties of an interface across processes (and threads), thus enabling cross-process communication (remote procedure calls).

INTRODUCTION TO COM Chapter 17 589

 

Client

Client

Proxy

 

FIGURE 17-12 The role of the proxy and stub

 

Server

Stub

Object

Threading in COM

As a Visual C++ programmer, threading is not alien to you. Therefore, I will skip straight to explaining the role of threads in COM.

You likely have developed both singleand multithreaded applications. With that experience, you would definitely agree with the statement that programming multithreaded applications is a tedious task. One of the biggest tasks in creating multithreaded applications that you have to handle is synchronization. When multithreaded applications run asynchronously, you run into problems, such as data corruption, inconsistency, and so on, because more than one thread can access data simultaneously. As a result, a galore of synchronization classes, such as CSemaphore, CMutex, and so on, were introduced.

COM joined the league of multithreaded programming a little later. As a result, one crucial task that Microsoft had on hand was to enable smooth interaction between the existing thread-unsafe components and the multithreaded components. To handle this, Microsoft came up with the following threading models:

Single-threaded

Apartment threaded

Mixed

Free

The concept of apartments was new to threads. An apartment is an abstract concept. It is the context within which the threads need to execute. Apartments are of two types: single-threaded apartments (STAs) and multithreaded apartments

(MTAs).