from
http://progtutorials.tripod.com/COM.htm
- Creating a DLL Server in Purely C++ Code
- Step 1: create a housing for your DLL
- Step 2: define your interfaces
- Step 3: define guids for interfaces
- Step 4: create the coclass which implements the interfaces
- Step 5: Create one class factory for each coclass
- Step 6: implement exported functions
- Step 7: export the Dll functions
- Step 8: provide registration information
- Life Time Control of a Coclass in a DLL Server
- Life Time Control of a DLL Server
- EXE Server in Raw C++ Code
- Life Time Control of a Coclass and Class Factory in an EXE Server
- Life Tme Control of an EXE Server
- Nomal EXE Application vs. EXE Server
- Example of EXE Server's WinMain
1 Creating a DLL Server in Purely C++ Code
1.1 Step 1: create a housing for your DLL
Create a new source file with the name of the project, say CarDll.cpp, with two global variables:
//************* CarDll.cpp *******************
#include <windows.h>
ULONG g_lockCount = 0;
ULONG g_objCount = 0;
These two global variables are used for life time control of the DLL server (explained later). The <windows.h> header file is the one essential include file required in all Windows source code. It contains all of the definitions for Windows messages, constants, flag values, data structures, macros, and other mnemonics that permit the programmer to work without having to memorize thousands of hexadecimal values and their functions.
1.2 Step 2: define your interfaces
Create a header file in any name in an empty DLL project workspace, say "interface.h", and define your interfaces in it:
//*************** interface.h ***********************
#ifndef _INTERFACES
#define _INTERFACES
#include <unknwn.h> // Needed if WIN32_LEAN_AND_MEAN is defined in stdafx.h
#include <windows.h>
struct IRegistration : public IUnknown
{
virtual HRESULT __stdcall GetOwner(BSTR * pBstrOwner) = 0;
virtual HRESULT __stdcall SetOwner(BSTR bstrOwner) = 0;
};
struct IStatus : public IUnknown
{
virtual HRESULT __stdcall GetSpeed)(int * pnSpeed) = 0;
virtual HRESULT __stdcall SetSpeed)(int nSpeed) = 0;
};
#endif Calling convention __stdcall defines the way function call arguments are pushed into stack, poped out, and name-decorating convention that the compiler uses to identify individual functions.
1.3 Step 3: define guids for interfaces
Create a new header file in any name, say iid.h. Use guid generator guidgen.exe to directly generate new guids in correct format for all new interfaces, and assign a constant alias for each guid:
//**************** iid.h *********************
// {D427CA52-AF28-40a4-A5C2-97EA029DCD0F}
DEFINE_GUID(IID_IRegistration,
0xd427ca52, 0xaf28, 0x40a4, 0xa5, 0xc2, 0x97, 0xea, 0x2, 0x9d, 0xcd, 0xf);
// {D518B0BF-3EE1-4976-9B6A-9F3443A2A186}
DEFINE_GUID(IID_IStatus,
0xd518b0bf, 0x3ee1, 0x4976, 0x9b, 0x6a, 0x9f, 0x34, 0x43, 0xa2, 0xa1, 0x86);
// {2F481E63-C189-4d99-A705-9F3F2DFB7145}
DEFINE_GUID(CLSID_Car,
0x2f481e63, 0xc189, 0x4d99, 0xa7, 0x5, 0x9f, 0x3f, 0x2d, 0xfb, 0x71, 0x45); Create a corresponding source file iid.cpp. Simply include the following lines in the file:
//***************** iid.cpp *********************
#include "stdafx.h"
#include <windows.h>
#include <objbase.h> // Needed if WIN32_LEAN_AND_MEAN defined in stdafx.h
#include <initguid.h> // contains definition of DEFINE_GUID
#include "iid.h"
Header file <initguid.h> should be included before "iid.h". By putting the lines shown above into a cpp file and letting the client programming inserting this cpp file into his project, this precedence is guaranteed.
Note that when a client invokes for an IID or CLSID from the COM run time, the program will first loop up the guid definition file (iid.h in this case) to acquire the actual guid. That is to say, it is "0xd427ca52, 0xaf28, 0x40a4, 0xa5, 0xc2, 0x97, 0xea, 0x2, 0x9d, 0xcd, 0xf" not "IID_IRegistration" which is passed to the COM run time to be used to search in the System Registry. This is how the guid helps to maintain the global uniquenss of a coclass or interface.
1.4 Step 4: create the coclass which implements the interfaces
//**************** Car.h **********************
#include "interfaces.h"
class Car : public IRegistration, public IStatus
{
public:
Car();
virtual ~Car();
STDMETHODIMP QueryInterface(REFIID riid, void ** ppAny);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
STDMETHODIMP GetOwner(BSTR * pBstrOwner);
STDMETHODIMP SetOwner(BSTR bstrOwner);
STDMETHODIMP GetSpeed(int * pnSpeed);
STDMETHODIMP SetSpeed(int nSpeed);
private:
ULONG m_refCount;
char m_pcOwner[80];
int m_nSpeed;
};
//****************** Car.cpp ************************
#include "Car.h"
#include "iid.h"
extern ULONG g_lockCount;
extern ULONG g_objCount;
Car::Car()
{
m_refCount = 0;
g_objCount++;
::memset(m_pcOwner, 0, 80);
}
Car::~Car()
{
g_objCount--;
}
STDMETHODIMP_(ULONG) Car::AddRef()
{
return ++m_refCount;
}
STDMETHODIMP_(ULONG) Car::Release()
{
if(--m_refCount == 0)
{
delete this;
return 0;
}
else
return m_refCount;
}
STDMETHODIMP Car::QueryInterface(REFIID riid, void ** ppAny)
{
// IID_IUnknown is the REFIID of standard interface IUnknown
if(riid == IID_IUnknown)
{
// to avoid confusion caused by virtual inheritance
*ppAny = (IUnknown *)(IStatus *)this;
}
else if(riid == IID_IRegistration)
{
*ppAny = (IRegistration *)this;
}
else if(riid == IID_IStatus)
{
*ppAny = (IStatus *)this;
}
else
{
*ppAny = NULL;
return E_NOINTERFACE;
}
((IUnknown *)(*ppAny))->AddRef();
return S_OK;
}
STDMETHODIMP Car::GetOwner(BSTR * pBstrOwner)
{
mbstowcs(*pBstrOwner, m_pcOwner, 80);
return S_OK;
}
STDMETHODIMP Car::SetOwner(BSTR bstrOwner)
{
wcstombs(m_pcOwner, bstrOwner, 80);
return S_OK;
}
STDMETHODIMP Car::GetSpeed(int * pnSpeed)
{
*pnSpeed = m_nSpeed;
return S_OK;
}
STDMETHODIMP Car::SetSpeed(int nSpeed)
{
m_nSpeed = nSpeed;
return S_OK;
} Macro STDMETHODIMP and STDMETHODIMP_(Type) are used to convert the function into __stdcall calling convention:
#define STDMETHODIMP HRESULT STDMETHODCALLTYPE
#define STDMETHODIMP_(type) type STDMETHODCALLTYPE
Pay attention the cascaded if structure in the QueryInterface method. In iid.h, we stored some guids and assigned an alias for each of them. But actually nothing linked one guid/ alias to one interface. Now here we find the link.
1.5 Step 5: Create one class factory for each coclass
In a separate header and source file, create a corresponding class factory class for each coclass. When its CreateInstance method is called, it simply create an instance of the corresponding coclass, and query its QueryInterface method for the passed REFIID.
This is where a class factory is matched to its corresponding coclass.
//***************** CarClassFactory.h ***********************
#ifndef _CARCLASSFACTORY
#define _CARCLASSFACTORY
#include <windows.h>
class CarClassFactory : public IClassFactory
{
public:
CarClassFactory();
virtual ~CarClassFactory();
HRESULT __stdcall QueryInterface(REFIID riid, void ** ppAny);
ULONG __stdcall AddRef();
ULONG __stdcall Release();
HRESULT __stdcall CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, void ** ppAny);
HRESULT __stdcall LockServer(BOOL fLock);
private:
ULONG m_refCount;
};
#endif
//******************** CarClassFactory.cpp ***********************
#include "CarClassFactory.h"
#include "car.h"
extern ULONG g_lockCount;
extern ULONG g_objCount;
CarClassFactory::CarClassFactory()
{
m_refCount = 0;
g_objCount++;
}
CarClassFactory::~CarClassFactory()
{
g_objCount--;
}
ULONG __stdcall CarClassFactory::AddRef()
{
return ++m_refCount;
}
ULONG __stdcall CarClassFactory::Release()
{
if(--m_refCount == 0)
{
delete this;
return 0;
}
return m_refCount;
}
HRESULT __stdcall CarClassFactory::QueryInterface(REFIID riid, void ** ppAny)
{
// IID_IUnknown is the REFIID of standard interface IUnknown
if(riid == IID_IUnknown)
{
*ppAny = (IUnknown *)this;
}
else if(riid == IID_IClassFactory)
{
*ppAny = (IClassFactory *)this;
}
else
{
*ppAny = NULL;
return E_NOINTERFACE;
}
((IUnknown *)(*ppAny))->AddRef();
return S_OK;
}
HRESULT __stdcall CarClassFactory::CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, void ** ppAny)
{
if(pUnkOuter != NULL)
{
return CLASS_E_NOAGGREGATION;
}
Car * pCar = new Car();
HRESULT hr = pCar->QueryInterface(riid, ppAny);
if(FAILED(hr)) delete pCar;
return hr;
}
HRESULT __stdcall CarClassFactory::LockServer(BOOL fLock)
{
if(fLock)
g_lockCount++;
else
g_lockCount--;
return S_OK;
} Note that for this class factory class I did not use STDMETHOD macros, instead I directly used __stdcall.
1.6 Step 6: implement exported functions
The mission of the exported function DllGetClassObject is to hand to outside client the IClassFactory pointer which is implemented by the class factory. It takes the REFCLSID of a coclass, create an instance of its corresponding class factory and query its QueryInterface for IClassFactory interface pointer.
This is where the REFCLSID of the coclass is matched to its corresponding class factory.
#include "CarClassFactory.h"
#include "iid.h"
ULONG g_lockCount = 0;
ULONG g_objCount = 0;
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void ** ppAny)
{
if(rclsid != CLSID_Car)
return CLASS_E_CLASSNOTAVAILABLE;
CarClassFactory * pFactory = new CarClassFactory;
HRESULT hr = pFactory->QueryInterface(riid, ppAny);
if(FAILED(hr))
delete pFactory;
return hr;
}
STDAPI DllCanUnloadNow()
{
if(g_lockCount == 0 && g_objCount == 0)
return S_OK;
else
return S_FALSE;
}
1.7 Step 7: export the Dll functions
Create a new def file (under source code) with the same name as the project workspace, which is CarDll.def to export the Dll functions:
LIBRARY "CarDll"
EXPORTS
DllGetClassObject @1 PRIVATE
DllCanUnloadNow @2 PRIVATE
1.8 Step 8: provide registration information
Create a new reg file say CarDll.reg to merge the server information such as the GUIDs and the location into the System Registry:
REGEDIT
HKEY_CLASSES_ROOT\CarDll.Car\CLSID = {2F481E63-C189-4d99-A705-9F3F2DFB7145}
HKEY_CLASSES_ROOT\CLSID\{2F481E63-C189-4d99-A705-9F3F2DFB7145} = CarDll.Car
HKEY_CLASSES_ROOT\CLSID\{2F481E63-C189-4d99-A705-9F3F2DFB7145}\InprocServer32 = D:\My Documents\Visual C++\CarDLL\debug\CarDll.dll
Note that each line is a "key = value". There should be a space on each side of the equal sign, and no space (including tab) elsewhere.
Double-click this reg file to merge the server information into the System Registry.
2 Life Time Control of a Coclass in a DLL Server
As we can see from the above example, a COM object is initiated by a client through the exported function DllGetClassObject, but is deleted by itself. This is the same with class factories in a DLL.
Each coclass has a ULONG member m_refCount. In both the above raw C++ code and ATL¡¯s implementation, each time QueryInterface successfully hands out an interface pointer, it increments the coclass reference count by calling AddRef. Therefore, after you acqure an interface pointer with CoCreateInstance, you do not need to call AddRef. However, after you make a copy of the interface pointer, you should call AddRef. A function receiving an interface pointer as parameter is also making copy of the pointer and thus should also call AddRef.
Each time you (as a client or a function which receives an interface pointer as parameter) finishes using an interface, you should call its Release method. The coclass implementation of Release will decrement m_refCount, then check whether it reaches zero. If yes, it will delete itself.
The correct deletion of a COM object depends on the correct operation of the client. If one client calls AddRef or Release incorrectly, the COM object may be in memory when it should be deleted, or deleted when some one is still using it. Especially, if you call any method after the last Release call is made, you are stepping into OS¡¯s territory and your program will be shut down.
3 Life Time Control of a DLL Server
A DLL server is both loaded and unloaded by the COM rum time. When a client calls DllGetClassObject with the REFCLSID of a coclass in the server, SCM will look up the System Registry, find the directory of the server, load the server up, then call its exported function DllGetClassObject.
A DLL server has two global ULONG counts affecting its life time control: g_objCount and g_lockCount. The former one indicates the number of live COM objects in the server and the latter one the number of locks imposed by clients on the server.
The constructor of a coclass and a class factory in a DLL is supposed to increment the g_objCount. Its destructor is supposed to decrement g_objCount. On the other hand, for performance reason, a client may decide to to load the server into memory first, and create COM objects later. To lock the server, he calls its LockServer method passing TRUE, which will then increment the g_lockCount. To release the lock, the client calls the same method passing FALSE.
Only when both of these two locks reaches zero, will the DLL¡¯s exported function DllCanUnloadNow return true. SCM will call this function at intervals to check whether the server can be unloaded. If yes it will unload the DLL from the memory. If a client wants the SCM to check this function and unload the server immediately if possible, it can call API function CoFreeUnusedLibraries.
Because DLL class factories also have to delete themselves, the server can not be unloaded before all objects including class objects are done. That¡¯s why in DLL server class factories also affect the server reference counting.
4 EXE Server in Raw C++ Code
Because coclasses in EXE server are the same as those in DLL server, and class factories has only one difference that it does not affect any reference count, their code is omitted here:
DWORD g_allLocks;
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
CoInitialize(NULL);
// register the type lib
ITypeLib* pTLib = NULL;
LoadTypeLibEx(L"AnyEXETypeInfo.tlb", REGKIND_REGISTER, &pTLib);
pTLib->Release();
if(strstr(lpCmdLine, "/Embedding") || strstr(lpCmdLine, "-Embedding"))
{
AnyClassFactory cf; // class factories created as local objects
DWORD regID = 0;
CoRegisterClassObject(CLSID_Any,
(IClassFactory*)&cf,
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE,
®ID);
MSG ms;
while(GetMessage(&ms, 0, 0, 0))
{
TranslateMessage(&ms);
DispatchMessage(&ms);
}
CoRevokeClassObject(regID);
}
CoUninitialize();
return 0;
}
void Lock()
{
++g_allLocks;
}
void UnLock()
{
--g_allLocks;
if(g_allLocks == 0)
PostQuitMessage(0);
}
5 Life Time Control of a Coclass and Class Factory in an EXE Server
Coclasses in an EXE server works exactly like those in a DLL server.
Class objects in an EXE server has one difference from those in a DLL server: they don't affect any reference count ? neither their own nor the server¡¯s. This is because they are created in WinMain as local objects on the stack, so they do not need to be explicitly deleted. When all coclass objects are done, WinMain will end, and when WinMain ends, all class objects go out of scope.
So EXE class factories use dummy implementation for AddRef and Release: they simply returns constants like 10 or 20. The rest of the class factory are the same like those in a DLL server.
6 Life Tme Control of an EXE Server
A DLL server can not be run independently. It is started by the client¡¯s thread. It only has some exported functions such as DllGetClassObject for clients to call. It can not unload itself. In comparison, an EXE server has its main thread. When an EXE is started, the main thread runs global function WinMain. When it reaches its end bracket, the whole server process ends just like normal applications.
Unlike a DLL server which uses two global counts, an EXE server has only one global count g_allLocks for life time control.
Whenever a coclass is instantiated, its constructor will call global function Lock, which will increase the lock count. Whenever it is destroyed, its destructor will call a global UnLock, which will decrease the lock, then check whether it is zero. If yes, post an ¡°exit¡± message to end the dummy message look. Class factory's LockServer method also calls Lock and UnLock. So the global variable UnLock controls the EXE server life time.
Class factory's constructor and destructor doesn't call Lock and Unlock.
7 Nomal EXE Application vs. EXE Server
An EXE server can be designed as purely a server, or it can be designed to be able to server both as a server which is loaded as terminated by the COM run time, and an application with user interface which is started and ended by the user. How the program is run is decided by the command-line argument ¡°Embedding¡±.
When COM run time starts the EXE as a server, it will invoke it with argument ¡°Embedding¡±. So WinMain knows that it is launched as a server. It will create the class factory objects for all the coclasses the server contains, register each of them in the Running Object Table by calling CoRegisterClassObject, then enter a dummy message loop and wait infinitely for an ¡°Exit¡± message. When the server's global Unlock function discovers that the server lock reaches zero, it will post an ¡°Exit¡± message to end the message loop. Then function CoRevokeClassObject will be called for each class object. Then WinMain ends.
When user starts the EXE as an application, he will not put argument ¡°Embedding¡±. So WinMain knows that it is launched as an application. Instead of doing the above things, it will create and show its user interfaces.
8 Example of EXE Server's WinMain
To register and unregister a class object CoCarClassFactory to the running object table:
DWORD g_allLocks = 0;
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
CoInitialize(NULL);
if(strstr(lpCmdLine, "/Embedding") || strstr(lpCmdLine, "-Embedding"))
{
CoCarClassFactory carClassFactory;
DWORD regID = 0;
CoRegisterClassObject(CLSID_CoCar,
(IClassFactory*)&carClassFactory,
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE, ®ID);
MSG ms;
while(GetMessage(&ms, 0, 0, 0))
{
TranslateMessage(&ms);
DispatchMessage(&ms);
}
CoRevokeClassObject(regID);
}
else
{
// Work as a normal application
}
CoUninitialize();
return 0;
}
SeriousMoin v1 (koMoinMoin 1.0a4 Modified)