前面的com学习是从原理上来理解com,原理看上去很简单,但是com是很强大的。它不仅是一套标准,微软还提供了com的开发库—com库。com库包装了com的底层的很多细节,开发者用com开发的时候只要调用com库中的函数,可以很方便进行开发。com库中为了提供一种通用的调用对象的函数,引入了类厂(classfactory)的概念。类厂是为了上面的函数的通用调用而屏蔽掉下面的具体差异。其实很简单,就是每个实现接口的对象对应一个类厂,这个类厂用来调用该对象。因为每个对象都有区别,调用它也可能会有区别,这样用户来调用这些对象都要为每个对象实现调用方法,因此就没有标准和通用性,而通过类厂来调用就不一样了,用户只要调用类厂来生成对象就行。不需要接触到对象,这样具体的差异就被屏蔽掉了。com中除了引入了类厂机制外,它还有引出函数,这是外界调用com对象的唯一通道。com对象中用DllGetClassObject来调用类厂,并将DllGetClassObject引出,客户程序有两个函数来调用这个引出函数,一个是CoCreateInstance,另一个是CoGetClassObject,而CoCreateInstance是用户来调用的,CoCreateInstance去调用CoGetClassObject,CoGetClassObject去调用DllGetClassObject,DllGetClassObject去调用类厂,类厂调用对象,这就形成了一套调用机制,而对用户来说也非常方便,只要调用CoCreateInstance就能调用com对象。com库中为了更方便调用,用到了注册表,就是将com对象注册到注册表中,这样用户调用的时候只要根据com对象在注册表中注册的名字直接来获得com对象标识符,并通过标识符来调用com对象。这是非常方便的。
程序例子:
com程序
代码:
接口:IOuter.h
#ifndef __IOuter_H__
#define __IOuter_H__
#include "Unknwn.h"
extern "C" const GUID IID_Outer;
class IOuter : public IUnknown
{
public:
virtual BOOL __stdcall Outer() = 0;
};
#endif
com对象:Object.h
#ifndef __Object_H__
#define __Object_H__
#ifndef __IOuter_H__
#include "IOuter.h"
#endif
class CObject : public IOuter
{
public:
CObject();
~CObject();
public:
virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv);
virtual ULONG __stdcall AddRef();
virtual ULONG __stdcall Release();
virtual BOOL __stdcall Outer();
private:
int m_Ref;
};
#endif
类厂实现:Factory.h
#ifndef __Factory_H__
#define __Factory_H__
#include "Unknwn.h"
class CObjectFactory : public IClassFactory
{
protected:
ULONG m_Ref;
public:
CObjectFactory();
~CObjectFactory();
HRESULT __stdcall QueryInterface(const IID& iid, void** ppv);
ULONG __stdcall AddRef();
ULONG __stdcall Release();
HRESULT __stdcall CreateInstance(IUnknown*, const IID& iid, void** ppv);
HRESULT __stdcall LockServer(BOOL);
};
#endif
Factory.cpp
#include "stdafx.h"
#include "Factory.h"
#include "Object.h"
extern ULONG g_LockNumber;
extern ULONG g_ObjectNumber;
CObjectFactory::CObjectFactory()
{
m_Ref = 0;
}
CObjectFactory::~CObjectFactory()
{
}
HRESULT CObjectFactory::QueryInterface(const IID& iid, void** ppv)
{
if(iid == IID_IUnknown)
{
*ppv = (IUnknown*) this;
((IUnknown*)(*ppv))->AddRef();
}
else if(iid == IID_IClassFactory)
{
*ppv = (IClassFactory*) this;
((IClassFactory*)(*ppv))->AddRef();
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
return S_OK;
}
ULONG CObjectFactory::AddRef()
{
m_Ref++;
return (ULONG) m_Ref;
}
ULONG CObjectFactory::Release()
{
m_Ref--;
if(m_Ref == 0)
{
delete this;
return 0;
}
return (ULONG) m_Ref;
}
HRESULT CObjectFactory::CreateInstance(IUnknown* pUnknownOuter, const IID& iid, void** ppv)
{
CObject* pObj;
HRESULT hr;
*ppv = NULL;
hr = E_OUTOFMEMORY;
if(pUnknownOuter != NULL) return CLASS_E_NOAGGREGATION;
pObj = new CObject();
if(pObj == NULL) return hr;
hr = pObj->QueryInterface(iid, ppv);
if(hr != S_OK)
{
g_ObjectNumber--;
delete pObj;
}
return hr;
}
HRESULT CObjectFactory::LockServer(BOOL bLock)
{
if(bLock) g_LockNumber++;
else g_LockNumber--;
return NOERROR;
}
注册表相关:Registry.h
#ifndef __Registry_H__
#define __Registry_H__
//
// Registry.h
// - Helper functions registering and unregistering a component.
//
// - These helper functions were borrowed and modifed from
// Dale Rogerson's book Inside COM.
// This function will register a component in the Registry.
// DllRegisterServer function should call this function.
HRESULT RegisterServer(const CLSID& clsid,
const char *szFileName,
const char* szProgID,
const char* szDescription,
const char* szVerIndProgID) ;
// This function will unregister a component. Components
// DllUnregisterServer function should call this function.
HRESULT UnregisterServer(const CLSID& clsid,
const char* szProgID,
const char* szVerIndProgID) ;
#endif
Registry.cpp
#include "stdafx.h"
#include <objbase.h>
#include <assert.h>
#include "Registry.h"
////////////////////////////////////////////////////////
//
// Internal helper functions prototypes
//
// - These helper functions were borrowed and modifed from
// Dale Rogerson's book Inside COM.
// Set the given key and its value.
BOOL SetKeyAndValue(const char* pszPath,
const char* szSubkey,
const char* szValue) ;
// Convert a CLSID into a char string.
void CLSIDtoString(const CLSID& clsid,
char* szCLSID,
int length) ;
// Delete szKeyChild and all of its descendents.
LONG DeleteKey(HKEY hKeyParent, const char* szKeyString) ;
////////////////////////////////////////////////////////
//
// Constants
//
// Size of a CLSID as a string
constint CLSID_STRING_SIZE = 39 ;
/////////////////////////////////////////////////////////
//
// Public function implementation
//
//
// Register the component in the registry.
//
HRESULT RegisterServer(const CLSID& clsid, // Class ID
const char *szFileName, // DLL module handle
const char* szProgID, // IDs
const char* szDescription, // Description String
const char* szVerIndProgID) // optional
{
// Convert the CLSID into a char.
char szCLSID[CLSID_STRING_SIZE] ;
CLSIDtoString(clsid, szCLSID, sizeof(szCLSID)) ;
// Build the key CLSID\\{...}
char szKey[64] ;
strcpy(szKey, "CLSID\\") ;
strcat(szKey, szCLSID) ;
// Add the CLSID to the registry.
SetKeyAndValue(szKey, NULL, szDescription) ;
// Add the server filename subkey under the CLSID key.
SetKeyAndValue(szKey, "InprocServer32", szFileName) ;
// Add the ProgID subkey under the CLSID key.
if (szProgID != NULL) {
SetKeyAndValue(szKey, "ProgID", szProgID) ;
SetKeyAndValue(szProgID, "CLSID", szCLSID) ;
}
if (szVerIndProgID) {
// Add the version-independent ProgID subkey under CLSID key.
SetKeyAndValue(szKey, "VersionIndependentProgID",
szVerIndProgID) ;
// Add the version-independent ProgID subkey under HKEY_CLASSES_ROOT.
SetKeyAndValue(szVerIndProgID, NULL, szDescription) ;
SetKeyAndValue(szVerIndProgID, "CLSID", szCLSID) ;
SetKeyAndValue(szVerIndProgID, "CurVer", szProgID) ;
// Add the versioned ProgID subkey under HKEY_CLASSES_ROOT.
SetKeyAndValue(szProgID, NULL, szDescription) ;
SetKeyAndValue(szProgID, "CLSID", szCLSID) ;
}
return S_OK ;
}
//
// Remove the component from the registry.
//
HRESULT UnregisterServer(const CLSID& clsid, // Class ID
const char* szProgID, // IDs
const char* szVerIndProgID) // Programmatic
{
// Convert the CLSID into a char.
char szCLSID[CLSID_STRING_SIZE] ;
CLSIDtoString(clsid, szCLSID, sizeof(szCLSID)) ;
// Build the key CLSID\\{...}
char szKey[64] ;
strcpy(szKey, "CLSID\\") ;
strcat(szKey, szCLSID) ;
// Delete the CLSID Key - CLSID\{...}
LONG lResult = DeleteKey(HKEY_CLASSES_ROOT, szKey) ;
// Delete the version-independent ProgID Key.
if (szVerIndProgID != NULL)
lResult = DeleteKey(HKEY_CLASSES_ROOT, szVerIndProgID) ;
// Delete the ProgID key.
if (szProgID != NULL)
lResult = DeleteKey(HKEY_CLASSES_ROOT, szProgID) ;
return S_OK ;
}
///////////////////////////////////////////////////////////
//
// Internal helper functions
//
// Convert a CLSID to a char string.
void CLSIDtoString(const CLSID& clsid,
char* szCLSID,
int length)
{
assert(length >= CLSID_STRING_SIZE) ;
// Get CLSID
LPOLESTR wszCLSID = NULL ;
HRESULT hr = StringFromCLSID(clsid, &wszCLSID) ;
assert(SUCCEEDED(hr)) ;
// Covert from wide characters to non-wide.
wcstombs(szCLSID, wszCLSID, length) ;
// Free memory.
CoTaskMemFree(wszCLSID) ;
}
//
// Delete a key and all of its descendents.
//
LONG DeleteKey(HKEY hKeyParent, // Parent of key to delete
const char* lpszKeyChild) // Key to delete
{
// Open the child.
HKEY hKeyChild ;
LONG lRes = RegOpenKeyEx(hKeyParent, lpszKeyChild, 0,
KEY_ALL_ACCESS, &hKeyChild) ;
if (lRes != ERROR_SUCCESS)
{
return lRes ;
}
// Enumerate all of the decendents of this child.
FILETIME time ;
char szBuffer[256] ;
DWORD dwSize = 256 ;
while (RegEnumKeyEx(hKeyChild, 0, szBuffer, &dwSize, NULL,
NULL, NULL, &time) == S_OK)
{
// Delete the decendents of this child.
lRes = DeleteKey(hKeyChild, szBuffer) ;
if (lRes != ERROR_SUCCESS)
{
// Cleanup before exiting.
RegCloseKey(hKeyChild) ;
return lRes;
}
dwSize = 256 ;
}
// Close the child.
RegCloseKey(hKeyChild) ;
// Delete this child.
return RegDeleteKey(hKeyParent, lpszKeyChild) ;
}
//
// Create a key and set its value.
//
BOOL SetKeyAndValue(const char* szKey,
const char* szSubkey,
const char* szValue)
{
HKEY hKey;
char szKeyBuf[1024] ;
// Copy keyname into buffer.
strcpy(szKeyBuf, szKey) ;
// Add subkey name to buffer.
if (szSubkey != NULL)
{
strcat(szKeyBuf, "\\") ;
strcat(szKeyBuf, szSubkey ) ;
}
// Create and open key and subkey.
long lResult = RegCreateKeyEx(HKEY_CLASSES_ROOT ,
szKeyBuf,
0, NULL, REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS, NULL,
&hKey, NULL) ;
if (lResult != ERROR_SUCCESS)
{
return FALSE ;
}
// Set the Value.
if (szValue != NULL)
{
RegSetValueEx(hKey, NULL, 0, REG_SZ,
(BYTE *)szValue,
strlen(szValue)+1) ;
}
RegCloseKey(hKey) ;
return TRUE ;
}
com对象实现:dllmain.cpp
#include "stdafx.h"
#include <comutil.h>
#include <stdio.h>
#include "objbase.h"
#include "olectl.h"
#include "Factory.h"
#include "Registry.h"
#include "Object.h"
ULONG g_LockNumber = 0;
ULONG g_ObjectNumber = 0;
HANDLE g_hModule;
// {AAF8166B-826D-41F3-A035-A3D540AD418D}
extern "C" const GUID CLSID_Object =
{ 0xaaf8166b, 0x826d, 0x41f3,
{ 0xa0, 0x35, 0xa3, 0xd5, 0x40, 0xad, 0x41, 0x8d } };
extern "C" const GUID IID_Outer =
{ 0xaaf8166c, 0x826d, 0x41f3,
{ 0xa0, 0x35, 0xa3, 0xd5, 0x40, 0xad, 0x41, 0x8d } };
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
g_hModule = hModule;
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
extern "C" HRESULT __stdcall DllGetClassObject(const CLSID& clsid, const IID& iid, void** ppv)
{
if(clsid == CLSID_Object)
{
CObjectFactory* pFactory = new CObjectFactory;
if(pFactory == NULL) return E_OUTOFMEMORY;
HRESULT result = pFactory->QueryInterface(iid, ppv);
return result;
}
else return CLASS_E_CLASSNOTAVAILABLE;
}
extern "C" HRESULT __stdcall DllCanUnloadNow(void)
{
if((g_ObjectNumber == 0) && (g_LockNumber == 0)) return S_OK;
else return S_FALSE;
}
extern "C" HRESULT __stdcall DllRegisterServer()
{
char szModule[1024];
DWORD dwResult = ::GetModuleFileNameA((HMODULE)g_hModule, szModule, 1024);
if(dwResult == 0) return SELFREG_E_CLASS;
return RegisterServer(CLSID_Object, szModule, "Object.Object", "Object Component", NULL);
}
extern "C" HRESULT __stdcall DllUnregisterServer()
{
return UnregisterServer(CLSID_Object, "Object.Object", NULL);
}
CObject::CObject()
{
m_Ref = 0;
g_ObjectNumber++;
}
CObject::~CObject()
{
}
HRESULT CObject::QueryInterface(const IID& iid, void** ppv)
{
if(iid == IID_IUnknown)
{
*ppv = (IOuter*) this;
((IOuter*)(*ppv))->AddRef();
}
else if(iid == IID_Outer)
{
*ppv = (IOuter*) this;
((IOuter*)(*ppv))->AddRef();
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
return S_OK;
}
ULONG CObject::AddRef()
{
m_Ref++;
return (ULONG) m_Ref;
}
ULONG CObject::Release()
{
m_Ref--;
if(m_Ref == 0)
{
g_ObjectNumber--;
delete this;
return 0;
}
return (ULONG) m_Ref;
}
BOOL CObject::Outer()
{
printf("Hello World!");
return true;
}
def模板实现:3_com.def
LIBRARY "Object"
EXPORTS
DllGetClassObject PRIVATE
DllCanUnloadNow PRIVATE
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE
解释:当然这里建的工程是动态库工程。类厂中有两个额外的函数,一个是CreateInstance,另一个是LockServer。CreateInstance是用来生成com对象的,LockServer则是用来控制类厂的生成周期的。因为一个类厂可能对应多个对象,只有当所有的com对象都释放掉的时候,才能释放类厂。注册表那个Registry.h和Registry.cpp则是用来注册表用的,它是固定的,任何程序都可以用。当然根据经验,在用vs 2010 开发com程序的时候可能会报一些错,这些错误要么是头文件中少了"stdafx.h",要么就是编码问题,在项目属性里修改编码就能解决。最后完成之后,运行,将.dll注册到注册表,注册命令regsvr32.exe xx.dll。当然xx.dll前面要有路径。
客户调用程序
代码:
接口声明:IOuter.h
#ifndef __IOuter_H__
#define __IOuter_H__
#include "Unknwn.h"
extern "C" const GUID IID_Outer;
class IOuter : public IUnknown
{
public:
virtual BOOL __stdcall Outer() = 0;
};
#endif
调用:call.cpp
#include "windows.h"
#include <stdio.h>
#include <comutil.h>
#include <conio.h>
#include "IOuter.h"
/*extern "C" const GUID CLSID_Object =
{ 0xaaf8166b, 0x826d, 0x41f3,
{ 0xa0, 0x35, 0xa3, 0xd5, 0x40, 0xad, 0x41, 0x8d } };*/
extern "C" const GUID IID_Outer =
{ 0xaaf8166c, 0x826d, 0x41f3,
{ 0xa0, 0x35, 0xa3, 0xd5, 0x40, 0xad, 0x41, 0x8d } };
void main()
{
IUnknown* pUnknown;
IOuter* pOuter;
BOOL bResult;
HRESULT hResult;
GUID objectCLSID;
if(CoInitialize(NULL) != S_OK)
{
printf("Initialize COM library failed!\n");
return;
}
hResult = ::CLSIDFromProgID(L"Object.Object", &objectCLSID);
if(hResult != S_OK)
{
printf("Can't find the Object CLSID!\n");
return;
}
hResult = CoCreateInstance(objectCLSID, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**) &pUnknown);
if(hResult != S_OK)
{
printf("Create object failed!\n");
return;
}
hResult = pUnknown->QueryInterface(IID_Outer, (void**) &pOuter);
if(hResult != S_OK)
{
pUnknown->Release();
printf("QueryInterface IOuter failed!\n");
return;
}
pUnknown->Release();
pOuter->Outer();
pOuter->Release();
getch();
return;
}
解释:CoInitialize这个是用来初始化com库的,在使用com库之前必须进行初始化。CLSIDFromProgID这个是根据注册表中的注册名字来获取com对象的唯一标识符。CoCreateInstance来生成com对象,并获得接口指针。
运行结果: