Microsoft Visual C++ .NET Professional Projects - Premier Press
.pdf
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:
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.
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);
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).

TIP