Previous Page TOC Next Page



Chapter 3


Creating COM Objects


by Weiying Chen


The Component Object Model(COM) is an open architecture for cross-platform development of client/server applications. It is the cornerstone for ActiveX technology and OLE 2.0.

This chapter presents the fundamental concept of COM, such as COM client/server architecture, COM server, and COM client. A set of fundamental COM interfaces are also examined in detail to describe their roles in creating COM objects.

To illustrate the fundamental blocks and the concept of the COM architecture, serial examples are built step-by-step to demonstrate how to create various COM servers and corresponding COM client applications. All source codes in examples are written in Microsoft VC++ 4.1.

Microsoft Active Template Library (ATL) simplifies the procedure of creating COM servers by providing commonly used templates. At the end of this chapter, an example demonstrates ATL creating COM objects.

COM Client/Server Architecture


The Component Object Model (COM) is an open architecture for cross-platform development of client/server applications. It is the cornerstone for ActiveX technology and OLE 2.0 as shown in Figure 3.1.

Figure 3.1. COM, ActiveX and OLE 2.0.

All ActiveX controls are COM objects. COM objects refer to any object that implements IUnknown interface.

ActiveX scripting provides a set of OLE interfaces for a scripting engine and a scripting engine host. All these interfaces inherit from IUnknown.

ActiveX Document provides a set of Document object and Document object container interfaces, which inherit from IUnknown interfaces.

ActiveX server-side scripting uses OLE technology.

OLE 2.0 is built on COM.

COM provides a client/server architecture. The COM client uses COM server via the COM library. Figure 3.2 illustrates.

Figure 3.2. COM client/server model.

COM Server


A COM server is a component that implements one or more COM Class objects. A COM Class object is a COM object that is creatable via a COM Class Factory object. A COM Class object has a CLSID associated with it. A COM object is anything that implements IUnknown interface. It is different from an object in object-oriented programming. In Object-Oriented programming, an object called OO object here is an entity that has state, behavior and identity. The OO object's state is represented by the value of the attributes in the object. But a COM object's state is implied by the interface; the state is not explicitly stated, because there are no public attributes exposed in the interface. The Interface is just a set of functions without any attributes.

The OO object's behavior is a sequence of messages sent to the object, which is a sequence of methods called on this object. But for a COM object, the object's behavior is defined as the interface it supports.

The OO object's identity is a way to look at the object, whereas for a COM object, the identity is defined by moving between interfaces exposed by the COM object, this is done by invoking IUnknown::QueryInterface interface.

Each COM object provides functionality via exposing interfaces. An interface is a group of related functions and provides some specific service. For example, the COM server in Figure 3.3 exposes two interfaces, one is IPrint, the other is IHelp. IPrint interface provides the print service, whereas IHelp supports the help service. Each interface groups its own functionality.

Figure 3.3. CPrint COM server.

In order to uniquely identify the class object provided by the COM server, a class identifier(CLSID) is used, whereas to identify the interface, an interface identifier(IID) is used.

A COM server is usually a .DLL or .EXE file. A DLL based COM server is called an in-process(in-proc for short) server because it loads into the same address space as the client. The client can make direct call to the object, which is faster and more efficient. But the crash of the DLL can destroy the client's address space.

An EXE based COM server is called an out-process(out-proc for short), because it runs in its own separate process space.

An EXE based COM server isolates from the address space of the caller, which makes it more reliable. If the server crashes, it will not destroy the address space of the client. But because it is in a separate process, all interface calls must be marshaled across process(data gets copied), which affects the performance.

Note: Now with Java, COM server could be a Java class.

COM Client


A COM client(client for short) is an application that uses COM server. A COM client asks COM to instantiate object in exactly the same manner regardless of the COM server types. This is done by invoking the COM function CoCreateInstance. After COM client retrieves the first pointer to the COM object, it can not distinguish from the interface whether the COM server being used is an in-proc, or out-proc server.

COM Client is an executable(EXE) application as compared with COM server that can be DLL based.

COM Library


The COM library provides an implementation of the Application Programming Interface (API). The specification also defines a set of interfaces that will be used by different COM objects.

The component in COM also supports the communication establishment between the client and server. This component provides location transparency for the client. In other words, the client does not need to know where the server locates; all these are taken care of by the COM library.

COM Library APIs Functions

COM library API functions provide the functionality to the COM applications. COM applications are any application that uses COM. The following gives an example of API functions.


COM Fundamental Interfaces

COM predefines a set of interfaces to be used by client/server applications. Among these, IUnknown and IClassFactory interfaces are the most fundamental ones. IUnknown interface is required for any COM object. The QueryInterface method in IUnknown interface allows the client to access the object's identity and move between interfaces.

A class factory object is required for every object identified by a given CLSID. A class factory object implements the IClassFactory interface.

IUnknown Interface

IUnknown is the interface that any other interfaces inherit from. In other words, every interface except IUnknown inherits from IUnknown. Listing 3.1 illustrates the IUnknown interface definition.

Listing 3.1. IUnknown interface.


interface IUnknown

{

    HRESULT QueryInterface([in] REFIID riid, [out] void **ppv);

    ULONG AddRef();

    ULONG Release();

}

COM object must implement this interface. COM client will invoke the methods in the interface implemented by the COM object.

QueryInterface Method

QueryInterface provides the mechanism by which a client, having obtained one interface pointer on a particular object, can request additional pointers to other interfaces on the same object. The COM object exposes itself via a set of interfaces.

There are two parameters for QueryInterface. riid is IID of the interface requested. ppv is a return value. It is an indirect pointer to the interface. If the interface requested does not exist, ppv must be set to be NULL and an E_NOINTERFACE error code should be this method's return value.

The Listing 3.2 demonstrates an implementation of the QueryInterface method for CLowerStr class.

NOTE


The code listings in this chapter are from the example created in this chapter.

Listing 3.2. Example of QueryInterface Implementation


STDMETHODIMP CLowerStr::QueryInterface(REFIID iid, void **ppv)

{

    HRESULT hr;

    *ppv = NULL;

    if((iid == IID_IUnknown) || (iid == IID_ILowerStr) )

    {

        *ppv = (ILowerStr *)this;

        //increase reference count

        AddRef();

        hr = S_OK;

    }

    else

    {

        //if interface does not exist, *ppv set to be NULL, and E_NOINTERFACE returns.

        *ppv = NULL;

        hr = E_NOINTERFACE;

    }

    return hr;

}

Here, CLowerStr is an implementation of the ILowerStr interface, ILowerStr interface inherits from IUnknown. CLowerStr can be called a COM object because it implements IUnknown interface.

AddRef Method

AddRef method provides the technique for an object to keep track of the reference count. The reference count should be incremented whenever an interface pointer is queried.

Listing 3.3 shows an implementation of the AddRef method.

Listing 3.3. AddRef method implementation.


STDMETHODIMP_(ULONG) CLowerStr::AddRef()

{

    m_dwRef++;

    return m_dwRef;

}

m_dwRef is a reference count defined in the object CLowerStr. It is defined as a DWORD.

Release Method

The Release method decrements the reference count. If the reference count is zero, the object should destroyed since the object is no longer needed. The client application needs to invoke this method whenever the interface is not accessed.

Listing 3.4 demonstrates an implementation of the Release method.

Listing 3.4. Release method implementation.


STDMETHODIMP_(ULONG) CLowerStr::Release()

{

    m_dwRef--;

    if(m_dwRef == 0)

        delete this;

    return m_dwRef;

}

IClassFactory Interface

IClassFactory is the interface that class factory object inherits from. In other words, class factory implements the IClassFactory interface. Class factory object is required in COM to create an instance of the object. It is a rule. For example, when the client application uses the CLowerStr object, CLowerStr object has to be created via its class factory.

Look at Figure 3.4; COM server has a class factory, which creates an instance of the object.

Figure 3.4. Relationship between Class Factory object and object.

IClassFactory interface has two fundamental methods shown in Listing 3.5.

Listing 3.5. IClassFactory Interface


interface IClassFactory : IUnknown

{

    STDMETHODIMP CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv);

    STDMETHODIMP LockServer(BOOL fLock);

}

The CreateInstance method creates an instance of the object class. It has to be implemented by the class factory object to instantiate the object. This method will be used inside the CoCreateInstance function call. CoCreateInstance will first return a pointer to the IClassFactory and then invoke IClassFactory's CreateInstance method to create an object's instance and returns an indirect pointer to the object's requested interface. This method only needs to be implemented but never needs to be invoked by the application itself.

punkOuter indicates whether the object is being created as part of the aggregate. If there is no aggregation in the COM server, NULL should be provided, otherwise, a pointer to the controlling IUnknown of the aggregate should be provided.

riid is the IID of the interface queried by the client. If the punkOuter is NULL, the IID of the initializing interface should be provided. Otherwise, riid must be IUnknown.

ppv is a pointer to the pointer of the requested interface. If the object does not support the interface specified in riid, ppv should be set as NULL, and E_NOINTERFACE should be returned as the method's return value.

LockServer locks the server in memory. The class factory will be revoked when the lock count is decremented to zero. LockServer(TRUE) will increment the lock count and ensure that the class factory will not be revoked.

Listing 3.6 illustrates an example of the implementation of CreateInstance.

Listing 3.6. Sample CreateInstance method implementation.


STDMETHODIMP CLowerStrClassFactory::CreateInstance (IUnknown *pUnkOuter,REFIID iid,void **ppv)

{

   HRESULT hr;

   CLowerStr *pObj;

   *ppv = NULL;

   pObj = new CLowerStr;

   if (pObj)

   {

        hr=pObj->QueryInterface(iid,ppv);

        pObj->Release();

   }

   else

   {

       hr = E_OUTOFMEMORY;

        *ppv = NULL;

   }

   return hr;

}

CreateInstance first instantiates the CLowerStr object and then queries whether the iid interface exists in the CLowerStr object. If yes, ppv will return an indirect pointer to the interface, and the CLowerStr object will be released.

Listing 3.7 illustrates how to implement LockServer method.

Listing 3.7. Sample LockServer method implementation.


long g_cLock = -1;

STDMETHODIMP CLowerStrClassFactory::LockServer(BOOL fLock)

{

   if (fLock)

      g_cLock++;

   else

      g_cLock--;

   return S_OK;

}

The LockServer first checks fLock to see whether it is true; if yes, the g_cLock will be increased, otherwise, the g_clock will be decreased. This LockServer method will be invoked by the client application.

In the following section, a set of examples will be demonstrated to further illustrate the concept. First, an in-proc server will be created and used. Then this in-proc server will be built as an out-proc server and used. After that, ATL will be used to create this in-proc server.

Create and Use an In-Proc Server


The server(lst31.dll) is illustrated in Figure 3.5. For the complete project, please refer to the lst31 directory on the CD.

Figure 3.5. lst31.dll COM server

There is one interface ILowerStr exposed by the CLowerStr object. There is only one method called Lower in this interface.

virtual STDMETHODIMP Lower(char *lpInput, char**lpOutput) = 0;

This method accepts an input and converts the input string to lowercase and then returns the input string to the caller.

In particular, lpInput in Lower method indicates the input string.

lpOutput indicates the returned string that converts the strInput to uppercase.

Create lst31.dll COM Server


The following steps demonstrate how we implement this in-proc server.

  1. 1. Generate two GUIDs in DEFINE_GUID format, one for IID, the other for CLSID by using guidgen.exe. Replace the <<name>> in the code generated by guidgen.exe by IID of the interface and the CLSID of the object class.
  2. The CLSID and IID is a universal unique ID (UUID). CLSID stands for class identifier, whereas IID is for interface identifier. The use of this unique ID avoids the possibility of a naming collision among COM objects and interfaces.
  3. COM clients use these unique identifiers at runtime to locate the object and its interfaces. COM library uses these IDs to locate the COM server module path in the registry.
  4. UUID can be obtained through uuidcreate RPC function. There are tools that directly or indirectly use this function to generate the UUID, such as guidgen.exe, and uuidgen.exe.
  5. guidgen.exe is a window-based application and uuidgen.exe is a console based application. They both are contained in Microsoft Visual C++. uuidgen.exe only generates a UUID in a registry format, whereas guidgen.exe generates four formats as shown in Figure 3.6.

Figure 3.6. GUID Formats provided by guidgen.exe.

  1. Format 2: "DEFINE_GUID(...)" is used to identify a CLSID or IID. For example, a CLSID is defined in
  1. Format 4: "Registry Format" is used to build registry entry for the COM server.
  1. Format 1: IMPLEMENT_OLECREATE(...) and Format 3: static const struct GUID={...} are not used as often as Format 1 and 4.
  2. By convention, symbolic constants are used to identify a specific CLSID or IID. It is in the form of CLSID_<class name> or IID_<interface name>.
Note


I've used d: for my drive letter, but you should substitute the letter of the hard drive you installed the code to.

  1. For example, Listing 3.8 demonstrates how to use this format.

Listing 3.8. CLSID and IID.


   // {4F126D90-1319-11d0-A6AC-00AA00602553}

   DEFINE_GUID(CLSID_CLowerStr, 0x4f126d90, 0x1319, 0x11d0, 0xa6, 0xac, 0x0, 0xaa,

   

    // {4F126D91-1319-11d0-A6AC-00AA00602553}

   DEFINE_GUID(IID_ILowerStr, 0x4f126d91, 0x1319, 0x11d0, 0xa6, 0xac, 0x0, 0xaa,

   [icc[0x0, 0x60, 0x25, 0x53);
  1. For the interfaces defined in the COM, such as IUnknown, the IIDs are predefined, because every interface needs to have a IID associated with it.
  2. Listing 3.9 demonstrates the IID_ILowerStr, and CLSID_CLowerStr for the CLowerStr object.

Listing 3.9. IID_ILowerStr and CLSID_CLowerStr

  1. 2. Define the interface ILowerStr
  2. Listing 3.10 demonstrates the ILowerStr interface definition. Every interface in COM object requires inheritance from IUnknown to provide the interface navigation and reference counting capability.

Listing 3.10. ILowerStr Interface Definition

  1. 3. ILowerStr interface implementation
  2. Listing 3.11 demonstrates the ILowerStr interface implementation.

Listing 3.11. ILowerStr interface implementation.

  1. CLowerStr is the implementation of the ILowerStr interface. In the CLowerStr::QueryInterface(...) method, the AddRef() is invoked whenever an interface is successfully queried.
  2. AddRef() and Release() are the most standard implementation.
  3. Method Lower(...) accepts the input and converts the input string to lowercase.
  4. In the CLowerStr constructor, m_dwRef is initialized to 1 because this object is successfully instantiated by the client application.
  5. 4. Implement IClassFactory interface.
  6. Listing 3.12 demonstrates implementation of the class factory object for the CLowerStr object. This class factory object implements two methods (CreateInstance, and LockServer) defined in the IClassFactory interface, and three methods(QueryInterface, AddRef, and Release) defined in the IUnknown interface. Because IClassFactory inherits from IUnknown interface.

Listing 3.12. Class Factory implementation.

  1. The implementation of AddRef and Release are the same as in CLowerStr in Listing 3.11. QueryInterface is almost the same except the interface exposed by the CLowerStr is different from CLowerStrClassFactory. CLowerStr inherits from two interfaces, IUnknown and ILowerStr, whereas CLowerStrClassFactory inherits IUnknown and IClassFactory.
  2. 5 Define and implement export functions from Lst31 COM server.
  3. Because lst31.dll is an in-proc server, functions need to be exported to be accessed by the client application. For every in-proc server, the DllGetClassObject function needs to be exported so that COM library can access this function to create an instance of the COM object.
  4. Listing 3.13 demonstrates the module-definition (.DEF) file provided for Lst31 COM server.

Listing 3.13. Lst31 COM server .DEF FileLIBRARY CLowerSTR.

  1. Exporting DllGetClassObject is mandatory for every in-proc server. It is the function invoked by the COM library to create an instance of the COM object.
  2. Listing 3.14 demonstrates the implementation of the DllGetClassObject function.

Listing 3.14. lst31.dll's DllGetClassObject's implementation.

  1. DllGetClassObject returns the interface to the IClassFactory. This function has three parameters, rclsid is the input parameter, referring to the CLSID of the class object. iid is the input parameter that is the interface ID which the caller uses to communicate with the class object. In most cases, it is IID_IClassFactory. ppv is the return value; ppv is an indirect pointer to the IClassFactory interface of the class factory object.
  2. 6. Build lst31.dll.
  3. 7. Register Lst31 COM server by providing .REG file.
  4. Before a COM server can be used, the proper information such as CLSID and the full path of the DLL has to be stored in the registry under the HKEY_CLASSES_ROOT\CLSID. This is required for every COM server because the registry information will be accessed by the COM library to access the COM server location so that COM library can access the exported functions such as DllGetClassObject from the COM server.
  5. Listing 3.15 demonstrates the lst31.REG file for Lst31 COM server. Run regedit /s lst31.reg to register lst31.dll.

Listing 3.15. Lst31 COM server .REG file.

  1. For every COM server, all the information has to be stored under the string representation of the CLSID, which is an immediate subkey of the HKEY_CLASSES_ROOT\CLSID.
  2. The string representation of the CLSID is in the CLSID's registry format, denoted as {CLSID}. The value associated with the {CLSID} is the description of the COM object. The value associated with the InProcServer32 subkey is the full path to the 32 bit in-proc server. InProcServer32 subkey is defined by OLE to indicate the full path to the 32-bit in-proc server. There are other subkeys, such as Control and LocalServer32; subkeys are defined by OLE to serve their different purposes.
  3. Control subkey with no value indicates that the COM server is an OLE control. The value associated with LocalServer32 subkey indicates the full path to the local server.
  4. For more information on this, please refer to Chapter 7 in Windows NT Registry Guide by Addison-Wesley.
  5. For complete project, see lst31 directory in CD.

Use lst31.dll COM Server


The following example will demonstrate how to use the COM server just created (lst31.dll) in a console application. This console application is called lst31use.exe.

Note


COM server is an binary reusable component. It can be used in any applications such as Visual C++, and Visual Basic.

The following steps illustrate how to use lst31.dll.

  1. 1. Initialize the COM library by calling CoInitialize(NULL). OleInitialize can be called instead of CoInitialize, because OleInitialize initializes OLE library, which is a superset of the COM library.
  2. 2. Create an instance of the CLowerStr by invoking
  1. CLSID_CLowerStr specifies the CLSID of the CLowerStr object.
  2. 0 indicates that the object is not created as part of an aggregate.
  3. CLSCTX_INPROC_SERVER is one type of CLSCTX. It indicates that lst31.dll is an in-proc server. It will run in the same address space as the lst31use.exe. Listing 3.16 enumerates CLSCTX.

Listing 3.16. CLSCTX enumeration.

  1. CLSCTX_INPROC_HANDLER indicates that the COM server is an in-process handler.
  2. CLSCTX_LOCAL_SERVER indicates that the COM server runs on the same machine as the client but in a different process.
  3. CLSCTX_REMOTE_SERVER indicates that the COM server runs on a different machine from the client.
  4. IID_ILowerStr refers to the interface to communicate with Lst31 COM server.
  5. COM also predefines
  1. pLowerStr points to the location of the IID_ILowerStr interface pointer.
  2. 3. Check the returned value from CoCreateInstance in Step 2.
  3. For a robust system, I strongly suggest checking the returned value.
  4. 4, Invoke Lower method
  1. 5. Release ILowerStr interface.
  1. The pLowerStr needs to be released because it is no longer needed.
  2. 6 Invoke CoUninitialize() to uninitialize the COM library so that all COM resources and RPC connections can be released.
Note


OleUninitialize can be invoked to uninitialize OLE library. Because OLE library is a superset of COM library.

  1. Listing 3.17 demonstrates the complete program for lst31use application.

Listing 3.17. lst31use program.

  1. Before lst31.dll can be invoked, the interface ILowerStr needs to be included and so does the CLSID and IID declared by the COM server.
  1. This complete project is under the lst31use directory contained on the CD.

Create lst31 COM Server New Version


Assumed some new requirements coming up for lst31.dll COM server, a new version needs to be released. In a lot of circumstance, the client application that uses the dll needs to be recompiled if the library is statically linked. But the COM client using the old version of lst31.dll can use the new version of lst31.dll without any changes to source code and compilation.

Figure 3.7 illustrates the functionality provided by the new version of lst31.dll.

Figure 3.7. New version of lst31.dll.

There are two interfaces exposed by the lst31.dll. ILowerStr is the old interface, IHelpLowerStr is the new interface. This new interface includes one method HelpLower() which provides the help information, here for simplicity, S_OK will be returned.

The following steps demonstrate how to create the new lst31.dll COM server.

  1. 1. Generate a new IID for new interface IHelpLowerStr
  1. 2. Define the new interface IHelpLowerStr
  1. 3. Implement IHelpLowerStr by modifying CLowerStr class. The changes are highlighted in bold font in Listing 3.18.

Listing 3.18. New CLowerStr class.

  1. Because a new interface (IHelpLowerStr) is exposed by the CLowerStr object, QueryInterface needs to check IHelpLowerStr interface, too.
  2. 4. IUnknown] Rebuild lst311 project.
  3. 5. Create Registry setting

Following is the listing for the lst311.reg file.


REGEDIT

; CUpperStr Server Registration

HKEY_CLASSES_ROOT\CLSID\{4F126D90-1319-11d0-A6AC-00AA00602553} = CLowerStr Object

HKEY_CLASSES_ROOT\CLSID\{4F126D90-1319-11d0-A6AC-00AA00602553}

[icc]\InprocServer32 = d:\areview\ch3\lst311\debug\lst311.dll

Run lst31use.exe. It still works! But the value associated with the InprocServer32 has been changed, it is d:\areview\ch3\lst311\debug\lst311.dll now. The reason is that from the client perspective, nothing really cares what dll name is associated with the COM server. The client side is only interested in the CLSID exposed by the COM server. It is the COM library's role to locate the COM server and instantiate the COM object. The library also returns the interface pointer requested by the client application.

The complete project is located under the lst311 directory on the CD.

Create and Use an out-proc Server


To illustrate how to create an out-proc server, changes will be made to lst31.dll so that it is an out-proc server.

The out-proc server runs in a separate address space from the client application; all interface calls must be marshaled across process (data gets copied). It is more reliable compared with in-proc server because the corruption of the out-proc server will not influence the process space of the caller application, whereas in-proc server will destroy the caller's process space. The local out-proc server should be created when an application supports automation. A remote out-proc server should be used to take advantage of resources on another machine.

The following steps illustrate how to create Lst32 out-proc server.

  1. [1] Create the custom interface by using Interface Definition Language(IDL).
  2. Listing 3.19 demonstrates the lower.idl file for ILowerStr interface. Because all interface calls must be marshaled for out-proc server, ILowerStr interface must be marshaled.

Listing 3.19. lower.idl for ILowerStr interface

  1. An IDL file specifies the contract between the client and server using IDL.
  2. It consists of two parts, the interface header and the interface body.
  3. Table 3.1 illustrates the attributes for IDL. For more information on the IDL, please refer to the Microsoft RPC document.

Table 3.1. IDL attributes.

Keyword Meaning object COM interface object. uuida universal unique identifier associated with particular interface usage uuid(80FA6EE2-0120-11d0-A6A0-00AA00602553), dual Dual interface, which inherits from IDispatch, IUnknowndual, import Imports idl file of the base interface import oaidl.idl out Output parameter [out, retval]BSTR *pBSTR in input parameter in, out Data is sent to the object initialized and will be changed before sending it back. retval Designates the parameter that receives the return value helpstring Sets the help stringhelpstring("test only") pointer_default Specifies the default pointer attribute unique pointer Attribute,designates a pointer as a full pointer ref pointer attribute, Identifies a reference pointer.

  1. 2. Create makefile for lower.idl.
  2. Listing 3.20 demonstrates the makefile for compiling x.

Listing 3.20. Makefile for lower.idl.# Change CPU to MIPS or ALPHA for compiling on those platforms

  1. midl in Listing 3.20 stands for Microsoft IDL compiler. It takes the lower.idl file and generates the following files:
  2. lower_p.c contains proxy/stub code
  3. lower_i.c contains the actual definition of IIDs and CLSIDs.
  4. lower.h contains the definition for the interface
  5. dlldata.c regenerated by MIDL compiler on every idl file.
  6. The midl switch /m_ext will support Microsoft extensions to DCE IDL. These extensions include: Interface definition for OLE objects, multiple interfaces, enumeration, cpp_quote (quoted_string) and wide character types(wchar_t), and so on.
  7. Let's say that if the IDL file includes
  1. /m_ext switch needs to be turned on.
  2. /c_ext switch enables the use of C-language extensions in the IDL file. For instance, if // is used to comment the code, /c_ext switch needs to be on.
  3. 2. Lower.Def
  1. 3. Build proxystub dll.
  2. 4. Register custom interface.
  3. run command: regsvr32 lower.dll.
  4. The ILowerStr interface information will be written to the system registry under HKEY_CLASSES_ROOT\Interface
  5. For the complete lower.dll project, please refer to lst32\proxy.
  6. 5. Create a main entry point for Lst32.exe.

Listing 3.21 demonstrates a main program for lst32.exe.

Listing 3.21. Lst32.exe main program.

  1. CoRegisterClassObject is the function that needs to be called on startup. It registers OLE so that other applications can connect to this class object. There are 5 parameters in the function. In particular, REGCLS_SINGLEUSE indicates the types of connection to the class object. If an application has connected to the class object via CoGetClassObject, no other application can connect to it.
  2. There are other two connection types, they are REGCLS_MULTIPEUSE and REGCLS_MULTI_SEPARATE. REGCLS_MULTIPEUSE indicates that multiple applications can connect to the class object via CoGetClassObject, whereas REGCLS_MULTI_SEPARATE indicates that the application has separate control over the each copy of the class object context.
  3. g_dwRegister is a returned value, identifying the class object registered. It will be used in CoRevokeClassObject function call.
  1. CoRevokeClassObject function indicates that the class object, previously registered with OLE via CoRegisterClassObject function is no longer available for use.
  1. lst32.exe provides the self registration features. Self registration means that the COM server can register itself. An in-proc server registers by providing two entry points by the in-proc server, they are
  1. DllRegisterServer entry point adds or updates registry information for all the classes implemented by the in-proc server. The DllUnRegisterServer entry point removes all the information for the in-proc server from the registry.
  2. For an out-proc server, there is no way to publish well-known entry points. The self registration for out-proc server is supported by using special command-line flags. The command-line flags are
  1. /regserver argument should add the registry information for all classes implemented by the out-proc server and then exit. /unregister argument should do all the necessary uninstallation and then exit.
  2. lst32.exe supports the /regserver and /unregserver argument.
  3. 6. Register lst32.exe by running lst32 /regserver.
  4. Lst32.exe out-proc server needs to be registered before being used. Without proper information in the registry, the out-proc server will not be seen by any other COM applications or COM library.
  5. Because lst32.exe supports self registration, lst32.exe can be registered by invoking lst32 /regserver.

Use lst32.exe COM Server


A COM client will be created to use this out-proc (lst32.exe) COM server. Listing 3.22 demonstrates how to use lst32.exe.

Listing 3.22. Lst32use.exe program.


#include <windows.h>

#include <stdio.h>

#include <olectl.h>

#include <initguid.h>

#include <olectlid.h>

// the class ID of the server exe

// {4F126D90-1319-11d0-A6AC-00AA00602553}

const CLSID CLSID_CLowerStr =

{0x4f126d90, 0x1319, 0x11d0, {0xa6, 0xac, 0x0, 0xaa, 0x0, 0x60, 0x25, 0x53}};

 // {4F126D91-1319-11d0-A6AC-00AA00602553}

const CLSID IID_ILowerStr =

{0x4f126d91, 0x1319, 0x11d0, {0xa6, 0xac, 0x0, 0xaa, 0x0, 0x60, 0x25, 0x53}};

class ILowerStr: public IUnknown

{

public:

    virtual STDMETHODIMP Lower(char* lpInput, char **lpOutput)=0;

};

void __cdecl main(int argc, char *argv[])

{

    ILowerStr *pILowerStr = NULL;

    HRESULT hr;

    hr = CoInitialize(NULL);

    if (FAILED(hr))

    {

        printf("CoInitialize failed [0x%x]\n", hr);

        exit(1);

    }

    hr = CoCreateInstance(CLSID_CLowerStr, 0, CLSCTX_LOCAL_SERVER,

        IID_ILowerStr,(void**)&pILowerStr);

    if (FAILED(hr))

    {

        printf("CoCreateInstance failed [0x%x]\n", hr);

        if (hr == REGDB_E_CLASSNOTREG)

        {

            printf("Run lst32.exe /REGSERVER to install server program.\n");

        }

        exit(1);

    }

    char *lpOutput;

    pILowerStr->Lower("HELLO", &lpOutput);

    printf("this is it %s\n", lpOutput);

    pILowerStr->Release();

    CoUninitialize();

}

From Listing 3.22, no changes need to be made from lst31use.cpp to lst32use.cpp except in the CoCreateInstance activation call. The execution context changes from CLSCTX_INPROC_SERVER to CLSCTX_LOCAL_SERVER.

Create and Use an in-proc COM Server by Using ATL


Active Template Library is an OLE COM AppWizard providing the framework for building COM servers.

From the previous section, a lot of implementation, such as IUnknown and IClassFactory can be reused as noticed. ATL encapsulates these implementations in a template class so that the COM server functionality can be concentrated.

Create lst33.dll COM Server


lst33.dll will be created to illustrate the ATL. lst33.dll provides the same functionality as lst31.dll Version 1.

The following steps demonstrate how to create lst33.dll by using ATL.

  1. 1. From the File menu, choose new, and select project workspace.
  2. 2. In the new project workspace dialog box, select OLE COM Appwizard. Enter lst33 in the name text box. Click the Create… button. The dialog box in Figure 3.8 will be displayed.

Figure 3.8. OLE COM Appwizard: Step 1 of 2.

  1. 3. In the dialog box shown in Figure 3.8, select the generate IDL only option. Note: This option can be chosen only when MIDL version 3.0 or higher is available.
  2. 4. In dialog box shown in Figure 3.8, select the Choose Custom Interface option. Note: Custom interface refers to the fact that all the interfaces are inherited from IUnknown.
  3. 5 Click Finish button.

The OLE COM AppWizard will generate the new skeleton project with the following files.

This also conforms to the DCOM design, because all of the custom interface has been marshaled.

It will not be difficult to spot where the changes need to be made.

  1. 1. There is one interface in this method, the lst33.idl will be changed to add the Upper method.
  2. The changes in the skeleton code created by the ATL will be in bold font.
  1. 2. The method STDMETHOD(Lower) is added in the object definition.
  2. 3. Implement the method in the ILst33 interface by adding the following code in lst33obj.cpp.
  1. The implementation of the Lower method is the same as implemented in lst31.dll, and lst32.dll.
  2. After the custom interface has been implemented, the following steps need to follow to create this COM server.
  3. Run the command midl lst33.idl. This requires MIDL 3.0 or higher. Otherwise, the following error will be generated:

Microsoft MIDL Compiler Version 2.00.0102

  1. In order to avoid this error, MIDL 3.0 must be available. It is contained in the Win32 SDK.
  2. After lst33.idl is successfully compiled, the following files will be generated:
  1. b. Build the COM server project (lst33.mak).
  2. COM server lst33.dll will be generated.
  1. Before this COM server can be used, it needs to be registered first by running regsvr32 lst33.dll. Or the these steps can be followed to register the server after compile.
  2. 1. Choose Build|Setting.
  3. 2. Select Custom Build tab in the Project Setting dialog box displayed in Figure 3.9.

Figure 3.9. Project Setting—Custom Build.

  1. 3. In the Build command list box, enter
  1. 4. In the Output file list box, enter
  1. The Microsoft Developer Studio will perform the following Custom Build Step
  1. after successfully compiling lst33.dll.
  2. But this will cause lst33.dll to register every time the project is built. To avoid this, the custom build option can be deleted.

Use Lst33 COM Server


A console application will be created to use the lst33.dll COM server shown in Listing 3.23.

Listing 3.23. Lst33Use.exe program


#include <objbase.h>

#include <initguid.h>

#include <stdio.h>

// These equivalent definitions will be from lst33_i.c

#include "..\lst33\lst33.h"

void main()

{

    HRESULT hr;

     ILst33  * m_pILst33; // interface pointer

    CLSID    clsid;

    hr = CoInitialize(NULL);

    hr = CLSIDFromProgID(L"LST33.Lst33Object.1",&clsid);

    hr = CoCreateInstance(clsid,

                            NULL,

                            CLSCTX_INPROC_SERVER,

                            IID_ILst33,

                            (LPVOID*)&m_pILst33);

    if(FAILED(hr))

    {

        printf("can not create Lst33");

        CoUninitialize();

        return;

    }

    char *lpLowerString;

    m_pILst33->Lower("HELLO", & &lpLowerString);

    printf("the return string is %s\n", lpLowerString);

    m_pILst33->Release();

    CoUninitialize();

}

Here, CLSIDFromProgID is used to retrieve the CLSID of the class object by providing ProgId for this COM server. No code change is made compared with lst31use.cpp.

Summary


From the previous example, it is not difficult to see that COM supports

In order for Visual Basic application easily to use COM server, the IDispatch interface is defined in COM. For more information on this, please refer to Chapter 4, "Creating OLE Automation Server."

Previous Page Page Top TOC Next Page