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.
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.
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.
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.
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 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 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 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 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.
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 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.
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 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.
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.
The following steps demonstrate how we implement this in-proc server.
Figure 3.6. GUID Formats provided by guidgen.exe.
DEFINE_GUID(CLSID_CLowerStr, 0x4f126d90, 0x1319, 0x11d0, 0xa6,
[icc]0xac, 0x0, 0xaa, 0x0, 0x60, 0x25, 0x53);
REGEDIT ; CUpperStr Server Registration HKEY_CLASSES_ROOT\CLSID\{4F126D90-1319-11d0-A6AC-00AA00602553} = [icc]CLowerStr Object HKEY_CLASSES_ROOT\CLSID\{4F126D90-1319-11d0-A6AC-00AA00602553} [icc]\InprocServer32 = d:\areview\ch3\lst31\debug\lst31.dll
I've used d: for my drive letter, but you should substitute the letter of the hard drive you installed the code to.
// {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);
Listing 3.9. IID_ILowerStr and CLSID_CLowerStr
// {4F126D90-1319-11d0-A6AC-00AA00602553} DEFINE_GUID(CLSID_CLowerStr, 0x4f126d90, 0x1319, 0x11d0, 0xa6, [icc]0xac, 0x0, 0xaa, 0x0, 0x60, 0x25, 0x53); // {4F126D91-1319-11d0-A6AC-00AA00602553} DEFINE_GUID(IID_ILowerStr, 0x4f126d91, 0x1319, 0x11d0, 0xa6, [[icc]0xac, 0x0, 0xaa, 0x0, 0x60, 0x25, 0x53);
Listing 3.10. ILowerStr Interface Definition
class ILowerStr : public IUnknown { public: virtual STDMETHODIMP Lower(char *lpInput, char**lpOutput) = 0; };
Listing 3.11. ILowerStr interface implementation.
class CLowerStr : public ILowerStr { public: STDMETHODIMP QueryInterface(REFIID iid, LPVOID *ppv); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); STDMETHODIMP Lower(char *lpString, char**lpOutput); CLowerStr(); ~CLowerStr(); private: DWORD m_dwRef; }; STDMETHODIMP CLowerStr::QueryInterface(REFIID iid, void **ppv) { HRESULT hr; *ppv = NULL; if((iid == IID_IUnknown) || (iid == IID_ILowerStr) ) { *ppv = (ILowerStr *)this; AddRef(); hr = S_OK; } else { *ppv = NULL; hr = E_NOINTERFACE; } return hr; } STDMETHODIMP_(ULONG) CLowerStr::AddRef() { m_dwRef++; return m_dwRef; } STDMETHODIMP_(ULONG) CLowerStr::Release() { m_dwRef--; if(m_dwRef == 0) delete this; return m_dwRef; } STDMETHODIMP CLowerStr::Lower(char *lpInput, char **pOutput) { int i; *pOutput = new char[strlen(lpInput) + 1]; for (i=0; i< strlen(lpInput); i++) { if(isupper(*(lpInput+i))) *(*pOutput+i) = _tolower(*(lpInput + i)); else *(*pOutput+i) = *(lpInput + i); } *(*pOutput+i) = '\0'; return S_OK; } CLowerStr::CLowerStr() { m_dwRef = 1; } CLowerStr::~CLowerStr() { }
Listing 3.12. Class Factory implementation.
class CLowerStrClassFactory:public IClassFactory { protected: DWORD m_dwRef; public: CLowerStrClassFactory(void); ~CLowerStrClassFactory(void); //IUnknown STDMETHODIMP QueryInterface (REFIID iid ,void **ppv); STDMETHODIMP_ (ULONG) AddRef(void); STDMETHODIMP_ (ULONG) Release(void); STDMETHODIMP CreateInstance(IUnknown *punkOuter,REFIID [icc]iid,void **ppv); STDMETHODIMP LockServer(BOOL); }; CLowerStrClassFactory::CLowerStrClassFactory() { m_dwRef=1; } CLowerStrClassFactory::~CLowerStrClassFactory() { } STDMETHODIMP CLowerStrClassFactory::QueryInterface (REFIID [icc]iid,void **ppv) HRESULT hr; *ppv = NULL; if (IID_IUnknown== iid || IID_IClassFactory== iid) { *ppv=this; AddRef(); hr = S_OK; } else { *ppv = NULL; hr = E_NOINTERFACE; } return hr; } STDMETHODIMP_(ULONG) CLowerStrClassFactory::AddRef(void) { return m_dwRef++; } STDMETHODIMP_(ULONG) CLowerStrClassFactory::Release(void) { m_dwRef--; if(m_dwRef == 0) delete this; return m_dwRef; } STDMETHODIMP CLowerStrClassFactory::CreateInstance (IUnknown [icc]*pUnkOuter,REFIID [icc]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; } STDMETHODIMP CLowerStrClassFactory::LockServer(BOOL fLock) { if (fLock) g_cLock++; else g_cLock--; return S_OK; }
Listing 3.13. Lst31 COM server .DEF FileLIBRARY CLowerSTR.
EXPORTS DllGetClassObject @1 DllCanUnloadNow @2
Listing 3.14. lst31.dll's DllGetClassObject's implementation.
long g_cLock = -1; long g_cObj = 0; STDAPI DllGetClassObject (REFCLSID rclsid,REFIID riid,void **ppv) { HRESULT hr; CLowerStrClassFactory *pObj; if (CLSID_CLowerStr!= rclsid) return ResultFromScode(E_FAIL); pObj = new CLowerStrClassFactory(); if (!pObj) return ResultFromScode (E_OUTOFMEMORY); hr= pObj->QueryInterface(riid,ppv); if (FAILED(hr)) delete pObj; return hr; } STDAPI DllCanUnloadNow (void) { SCODE sc; sc=(0L==g_cObj && 0L==g_cLock)? S_OK : S_FALSE; return ResultFromScode (sc); }
Listing 3.15. Lst31 COM server .REG file.
REGEDIT ; CUpperStr Server Registration HKEY_CLASSES_ROOT\CLSID\{4F126D90-1319-11d0-A6AC-00AA00602553} [icc] = CLowerStr Object HKEY_CLASSES_ROOT\CLSID\{4F126D90-1319-11d0-A6AC-00AA00602553} [icc]\InprocServer32 = d:\areview\ch3\lst31\debug\lst31.dll
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.
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.
CoCreateInstance(CLSID_CLowerStr 0, CLSCTX_INPROC_SERVER, IID_
[icc]ILowerStr, (void**)&pLoweStr)
Listing 3.16. CLSCTX enumeration.
typedef enum tagCLSCTX { CLSCTX_INPROC_SERVER = 1, CLSCTX_INPROC_HANDLER = 2, CLSCTX_LOCAL_SERVER = 4 CLSCTX_REMOTE_SERVER = 16 } CLSCTX;
#define CLSCTX_SERVER (CLSCTX_INPROC_SERVER| CLSCTX_LOCAL_
[icc]SERVER| CLSCTX_REMOTE_SERVER)
#define CLSCTX_ALL (CLSCTX_INPROC_HANDLER | CLSCTX_SERVER)
char *lpOutput; pILowerStr->Lower("hEllo World", &lpOutput); printf("the output is %s\n", lpOutput);
pLowerStr->Release();
OleUninitialize can be invoked to uninitialize OLE library. Because OLE library is a superset of COM library.
CoUnInitialize();
Listing 3.17. lst31use program.
#include <objbase.h> #include <initguid.h> #include <stdio.h> class ILowerStr : public IUnknown { public: virtual STDMETHODIMP Lower(char *lpInput, char**lpOutput) = 0; }; const CLSID CLSID_CLowerStr = {0x4f126d90, 0x1319, 0x11d0, {0xa6, 0xac, 0x0, 0xaa, 0x0, 0x60, [icc]0x25, 0x53}}; const CLSID IID_ILowerStr = {0x4f126d91, 0x1319, 0x11d0, {0xa6, 0xac, 0x0, 0xaa, 0x0, 0x60, [icc]0x25, 0x53}}; void main() { HRESULT hr; ILowerStr *pILowerStr; hr = CoInitialize(NULL); if(FAILED(hr)) { printf("CoInitialize failed[0x%x]\n", hr); exit(1); } //Create an instance of the COM object hr = CoCreateInstance(CLSID_CLowerStr, NULL, CLSCTX_INPROC_SERVER, IID_ILowerStr,(void**) &pILowerStr); if(FAILED(hr)) { printf("CoCreateInstance failed[0x%x]\n", hr); if(hr == REGDB_E_CLASSNOTREG) printf("please register the class\n"); exit(1); } char *lpOutput; pILowerStr->Lower("hEllo World", &lpOutput); printf("the output is %s\n", lpOutput); pILowerStr->Release(); CoUninitialize(); }
lst31.dll
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.
// {9EC1CA01-133F-11d0-A6AD-00AA00602553} DEFINE_GUID(IID_IHelpLowerStr, 0x9ec1ca01, 0x133f, 0x11d0, 0xa6, [icc]0xad, 0x0, 0xaa, 0x0, 0x60, 0x25, 0x53);
class IHelpLowerStr: public IUnknown { public: virtual STDMETHODIMP HelpLower() = 0; };
Listing 3.18. New CLowerStr class.
class IHelpLowerStr : public IUnknown { public: virtual STDMETHODIMP HelpLower()=0; }; class CLowerStr : public ILowerStr , public IHelpLowerStr { public: STDMETHODIMP QueryInterface(REFIID iid, LPVOID *ppv); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); STDMETHODIMP Lower(char *lpString, char**lpOutput); STDMETHODIMP HelpLower(); CLowerStr(); ~CLowerStr(); private: DWORD m_dwRef; }; STDMETHODIMP CLowerStr::QueryInterface(REFIID iid, void **ppv) { HRESULT hr; *ppv = NULL; if((iid == IID_IUnknown) || (iid == IID_ILowerStr) ) { *ppv = (ILowerStr *)this; AddRef(); hr = S_OK; } else if(iid == IID_IHelpLowerStr) { *ppv = (IHelpLowerStr *) this; AddRef(); hr = S_OK; } else { *ppv = NULL; hr = E_NOINTERFACE; } return hr; } STDMETHODIMP CLowerStr::HelpLower() { return S_OK; }
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.
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.
Listing 3.19. lower.idl for ILowerStr interface
[ object, uuid(4F126D91-1319-11d0-A6AC-00AA00602553), pointer_default(unique) ] interface ILowerStr: IUnknown { import "oaidl.idl"; HRESULT Lower([in,string] LPSTR inString, [out, string] LPSTR *outString); }
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.
Listing 3.20. Makefile for lower.idl.# Change CPU to MIPS or ALPHA for compiling on those platforms
CPU=i386 TARGETOS=BOTH !include <win32.mak> all: lower.dll .cxx.obj: $(cc) $(cflags) $(cvarsmt) $< .c.obj: $(cc) $(cflags) $(cvarsmt) $< #the files that make up the dll lower_i.obj : lower_i.c lower_p.obj : lower_p.c lower.h dlldata.obj : dlldata.c $(cc) $(cflags) $(cvarsmt) -DREGISTER_PROXY_DLL dlldata.c # run midl to produce the header files and the proxy file lower.h lower_p.c lower_i.c dlldata.c: lower.idl midl /ms_ext /c_ext lower.idl lower.dll: lower_p.obj lower_i.obj dlldata.obj lower.def $(link) \ -dll \ -entry:_DllMainCRTStartup$(DLLENTRY) \ -DEF:lower.def \ -out:lower.dll \ lower_p.obj lower_i.obj dlldata.obj rpcrt4.lib $(olelibs) # Clean up everything cleanall: clean @-del *.dll 2>nul # Clean up everything but the .EXEs clean: @-del *.obj 2>nul @-del dlldata.c 2>nul @-del *.h 2>nul @-del lower_?.* 2>nul @-del *.exp 2>nul @-del *.lib 2>nul @-del *.ilk 2>nul @-del *.pdb 2>nul @-del *.res 2>nul
typedef enum { A=1, B, C } BAD_ENUM;
LIBRARY LOWER DESCRIPTION 'Proxy/Stub DLL for ILowerStr interfaces' EXPORTS DllGetClassObject PRIVATE DllCanUnloadNow PRIVATE DllRegisterServer PRIVATE DllUnregisterServer PRIVATE
Listing 3.21 demonstrates a main program for lst32.exe.
Listing 3.21. Lst32.exe main program.
HRESULT RegisterClassFactory() { HRESULT hr; CLowerStrClassFactory *pClassFactory; pClassFactory = new CLowerStrClassFactory; if (pClassFactory != 0) { hr = CoRegisterClassObject(CLSID_CLowerStr, (IUnknown *) pClassFactory, CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE, &g_dwRegister); pClassFactory->Release(); hr = S_OK; } else { hr = E_OUTOFMEMORY; } return hr; }
HRESULT RevokeClassFactory() { HRESULT hr; hr = CoRevokeClassObject(g_dwRegister); return hr; }
void main(int argc, char *argv[]) { HRESULT hr = S_OK; int i; BOOL bRegisterServer = FALSE; BOOL bUnregisterServer = FALSE; MSG msg; for (i = 1; i < argc; i++) { if (_stricmp( argv[i], "/REGSERVER" ) == 0) { bRegisterServer = TRUE; } else if (_stricmp( argv[i], "/UNREGSERVER" ) == 0) { bUnregisterServer = TRUE; } } if(bRegisterServer) { RegisterLocalServer(CLSID_CLowerStr); return; } if(bUnregisterServer) { UnregisterLocalServer(CLSID_CLowerStr); return; } hr = CoInitialize(NULL); if (FAILED(hr)) { printf("CoInitialize failed [0x%x].\n", hr); return; } hr = RegisterClassFactory(); if (SUCCEEDED(hr)) { printf("Waiting for client to connect...\n"); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } RevokeClassFactory(); } else { printf("Failed to register class factory [0x%x].\n", hr); } CoUninitialize(); return; } HRESULT RegisterLocalServer(REFCLSID rclsid) { HRESULT hr; LONG lError; HKEY hKeyCLSID; HKEY hKeyClassID; HKEY hKey; // current key DWORD dwDisposition; char szServer[MAX_PATH]; char szClassID[39]; ULONG ulLength; ulLength = GetModuleFileNameA(0, szServer, sizeof(szServer)); if (ulLength == 0) { hr = HRESULT_FROM_WIN32(GetLastError()); return hr; } //create the CLSID key lError = RegCreateKeyExA( HKEY_CLASSES_ROOT, "CLSID", 0, "REG_SZ", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, 0, &hKeyCLSID, &dwDisposition); if (!lError) { //convert the class ID to a registry key name. sprintf(szClassID, "{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", rclsid.Data1, rclsid.Data2, rclsid.Data3, rclsid.Data4[0], rclsid.Data4[1], rclsid.Data4[2], rclsid.Data4[3], rclsid.Data4[4], rclsid.Data4[5], rclsid.Data4[6], rclsid.Data4[7]); //create key for the server class lError = RegCreateKeyExA(hKeyCLSID, szClassID, 0, "REG_SZ", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, 0, &hKeyClassID, &dwDisposition); if (!lError) { //create LocalServer32 key. lError = RegCreateKeyExA(hKeyClassID, "LocalServer32", 0, "REG_SZ", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, 0, &hKey, &dwDisposition); if (!lError) { //Set the server name. lError = RegSetValueExA(hKey, "", 0, REG_SZ, (const unsigned char *)szServer, strlen(szServer) + 1); RegFlushKey(hKey); RegCloseKey(hKey); } RegCloseKey(hKeyClassID); } RegCloseKey(hKeyCLSID); } if (!lError) hr = S_OK; else hr = HRESULT_FROM_WIN32(lError); return hr; } HRESULT UnregisterLocalServer(REFCLSID rclsid) { HRESULT hr; HKEY hKeyCLSID; HKEY hKeyClassID; long lError; char szClassID[39]; //open the CLSID key lError = RegOpenKeyExA( HKEY_CLASSES_ROOT, "CLSID", 0, KEY_ALL_ACCESS, &hKeyCLSID); if (!lError) { //convert the class ID to a registry key name. sprintf(szClassID, "{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", rclsid.Data1, rclsid.Data2, rclsid.Data3, rclsid.Data4[0], rclsid.Data4[1], rclsid.Data4[2], rclsid.Data4[3], rclsid.Data4[4], rclsid.Data4[5], rclsid.Data4[6], rclsid.Data4[7]); //open registry key for class ID string lError = RegOpenKeyExA( hKeyCLSID, szClassID, 0, KEY_ALL_ACCESS, &hKeyClassID); if (!lError) { //delete LocalServer32 key. lError = RegDeleteKeyA(hKeyClassID, "LocalServer32"); RegCloseKey(hKeyClassID); } lError = RegDeleteKeyA(hKeyCLSID, szClassID); RegCloseKey(hKeyCLSID); } if (!lError) hr = S_OK; else hr = HRESULT_FROM_WIN32(lError); return hr; }
HRESULT DllRegisterServer(void)
HRESULT DllUnRegisterServer(void);
/regserver argument and /unregister argument.
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.
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.
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.
Figure 3.8. OLE COM Appwizard: Step 1 of 2.
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.
interface ILst33 : IUnknown { import "oaidl.idl"; HRESULT Upper([in,string] LPSTR inputString, [out, string] LPSTR *pOutputString); }; [2] Implement this method for ILst33 interface, exposed by the lst33 object class by adding the definition in the lst33obj.h class CLst33Object : public ILst33, public CComObjectBase<&CLSID_Lst33> { public: CLst33Object() {} BEGIN_COM_MAP(CLst33Object) COM_INTERFACE_ENTRY(ILst33) END_COM_MAP() // Use DECLARE_NOT_AGGREGATABLE(CLst33Object) if you don't want your object // to support aggregation DECLARE_AGGREGATABLE(CLst33Object) // ILst33 public: STDMETHOD(Lower)( LPSTR bstrInput, LPSTR *pbstrOutput); };
STDMETHODIMP CLst33Object::Lower(LPSTR lpInput, LPSTR* pOutput) { int i; *pOutput = new char[strlen(lpInput) + 1]; for (i=0; i< strlen(lpInput); i++) { *(*pOutput+i) = *(lpInput + i)-'A'+'a'; } *(*pOutput+i) = '\0'; return S_OK; }
Microsoft MIDL Compiler Version 2.00.0102
Copyright Microsoft Corp 1991 -1995. All rights reserved. Processing .\lst33.idl .\lst33.idl(8) : error MIDL2141 : use of this attribute [icc] needs /ms_ext :[object] .\lst33.idl(10) : error MIDL2017 : syntax error :expecting an idl [icc] attribute near "helpstring" .\lst33.idl(10) : error MIDL2018 : cannot recover from earlier [icc] syntax errors; aborting compilation
lst33.tlb: type library lst33_p.c: proxy code dlldata.c: lst33_i.c
Figure 3.9. Project SettingCustom Build.
regsvr32 /s /c "$(TargetPath)" echo regsvr32 exec. time > $(OutDir)\regsvr32.trg
$(OutDir)\regsvr32.trg
egSvr32: DllRegisterServer in .\Debug\lst33.dll
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.
From the previous example, it is not difficult to see that COM supports
Versioning: Provides an interface without COM client's concern.
Network independence: An out-of-process COM server can run locally as the client application or remotely. A COM server can be DLL-based or EXE-based without the COM application doing extra work except informing the COM library about the execution context of the class object.
Language independence: A COM server can be used in C++ applications and any other applications such as Visual Basic. For a basic COM object that only supports IUnknown interface, there is a lot of work to be done so that Visual Basic can use the COM server. For examples on how to use basic COM objects in Visual Basic, please refer to "MFC/COM Objects 6: Using COM Objects from Visual Basic" in MSDN.
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."