Tuesday, November 25, 2008

Query interface


What a COM client is looking for are useful functions that it can call to accomplish its goals. In COM you access a set of useful functions through an interface. An interface, in its simplest form, is nothing but a collection of one or more related functions. When we "get" an interface from a COM server, we're really getting a pointer to a set of functions.

You can obtain an interface pointer by using the CoCreateInstance() function. This is an extremely powerful function that interacts with the COM subsystem to do the following:


l Locate the server.

l Start, load, or connect to the server.

l Create a COM object on the server.

l Return a pointer to an interface to the COM object.


There are two data types important to finding and accessing interfaces: CLSID and IID. Both of these types are Globally Unique ID's (GUID's). GUID's are used to uniquely identify all COM classes and interfaces.

In order to get a specific class and interface you need its GUID. There are many ways to get a GUID. Commonly we'll get the CLSID and IID from the header files in the server. In our example, we've defined the GUIDs with #define statements at the beginning of the source code. There are also facilities to look up GUIDs using the common name of the interface. The function that gives us an interface pointer is CoCreateInstance.


hr = CoCreateInstance(

CLSID_BeepObj, // COM class id

NULL, // outer unknown

CLSCTX_INPROC_SERVER, // server INFO

IID_IBeepObj, // interface id

(void**)&IBeep ); // pointer to interface


The first parameter is a GUID that uniquely specifies a COM class that the client wants to use. This GUID is the COM class identifier, or CLSID. Every COM class on the planet has its own unique CLSID. COM will use this ID to automatically locate a server that can create the requested COM object. Once the server is connected, it will create the object. The second parameter is a pointer to what's called the 'outer unknown'. We're not using this parameter, so we pass in a NULL. The outer unknown will be important when we explore the concept known as "aggregation". Aggregation allows one interface to directly call another COM

interface without the client knowing it's happening. Aggregation and containment are two methods used by interfaces to call other interfaces. The third parameter defines the COM Class Context, or CLSCTX. This parameter controls the scope of the server. Depending on the value here, we control whether the server will be an In-Process Server, an EXE, or on a remote computer. The CLSCTX is a bit-mask, so you can combine

several values. We're using CLSCTX_INPROC_SERVER - the server will run on our local computer and connect to the client as a DLL. We've chosen an In-Process server in this example because it is the easiest to implement.

Normally the client wouldn't care about how the server was implemented. In this case it would use the value CLSCTX_SERVER, which will use either a local or in-process server, whichever is available.

Next is the interface identifier, or IID. This is another GUID - this time identifying the interface we're requesting. The IID we request must be one supported by the COM class specified with the CLSID. Again, the value of the IID is usually provided either by a header file, or by looking it up using the interface name.

The last parameter is a pointer to an interface. CoCreateInstance() will create the requested class object and interface, and return a pointer to the interface. This parameter is the whole reason for the CoCreateInstance call. We can then use the interface pointer to call methods on the server.


Executing methods on the interface.


CoCreateInstance() uses COM to create a pointer to the IBeep interface. We can pretend the interface is a pointer to a normal C++ class, but in reality it isn't. Actually, the interface pointer points to a structure called a VTABLE, which is a table of function addresses. We can use the -> operator to access the interface pointer. Because our example uses an In-Process server, it will load into our process as a DLL. Regardless of the details of the interface object, the whole purpose of getting this interface was to call a method on the server.


hr = IBeep->Beep(800);


Beep() executes on the server - it will cause the computer to beep. There are a lot simpler ways to get a computer to beep. If we had a remote server, one which is running on another computer, that computer would beep. Methods of an interface usually have parameters. These parameters must be of one of the types allowed by COM. There are many rules that control the parameters allowed for an interface. We will discuss these in detail in the section on MIDL, which is COM's interface definition tool.


Release the interface


It's an axiom of C++ programming that everything that gets allocated should be de-allocated. Because we didn't create the interface with new, we can't remove it with delete. All COM interfaces have a method called Release() which disconnects the object and deletes it. Releasing an interface is important because it allows the server to clean up. If you create an interface with CoCreateInstance, you'll need to call Release().


Summary

In this chapter we've looked at a simple COM client. COM is a client driven system. Everything is oriented to making component objects easily available to the client. You should be impressed at the simplicity of the client program. The four steps defined here allow you to use a huge number of components, in a wide range of applications.

Some of the steps, such as CoInitialize() and CoUninitialize() are elementary. Some of the other steps don't make a lot of sense at first glance. It is important for you to understand, at a high level, all of the things that are going on in this code. The details will clarify themselves as we go through further examples.

Visual C++ Version 5 and 6 simplify the client program further by using 'smart pointers" and the #import directive. We've presented this example in a low level C++ format to better illustrate the concepts. We'll discuss smart pointers and imports in a later section.

0 comments: