一、 创建用以发布的接口文件 .H
1 、用 GUIDGEN 创建 CLSID 和 IID
// {96ECE846 -90A 6-43e2-8060 -7A 6A 91C 3D900}
DEFINE_GUID(CLSID_DBSAMPLE,
0x96ece846, 0x 90a 6, 0x43e2, 0x80, 0x60, 0x 7a , 0x 6a , 0x91, 0xc3, 0xd9, 0x0);
// { 673C 20AD-6B0E-4e 5a -9D3D -1A 1625FEC336}
DEFINE_GUID(IID_IDB,
0x 673c 20ad, 0x6b0e, 0x4e 5a , 0x9d, 0x3d, 0x 1a , 0x16, 0x25, 0xfe, 0xc3, 0x36);
// {A 14C 7FDE -15F 0-4acb -9F 2E-D021AA1E7CA2}
DEFINE_GUID(IID_IDBAccess,
0xa 14c 7fde, 0x 15f 0, 0x4acb, 0x 9f , 0x2e, 0xd0, 0x21, 0xaa, 0x1e, 0x 7c , 0xa2);
2 、声明接口
a) 每个接口都从 IUnknown 派生
b) 为所有的成员函数添加 _stdcall ( COM 对象在 WIN32 下采用的标准调用约定)
class IDB : public IUnknown
{
//Interfaces
public:
//Interface for data access
virtual HRESULT _stdcall Read(short nTable,short nRow,LPWSTR lpszData)=0;
virtual HRESULT _stdcall Write(short nTable,short nRow,LPCWSTR lpszData)=0;
//Interface for database management
virtual HRESULT _stdcall Create(short &nTable,LPCWSTR lpszName)=0;
virtual HRESULT _stdcall Delete(short nTable)=0;
//Interface for database information
virtual HRESULT _stdcall GetNumTables(short &nNumTables)=0;
virtual HRESULT _stdcall GetTableName(short nTable,LPWSTR lpszName)=0;
virtual HRESULT _stdcall GetNumRows(short nTable,short &nRows)=0;
};
class IDBAccess : public IUnknown
{
public:
//Interface for data access
virtual HRESULT _stdcall Read(short nTable, short nRow, LPWSTR lpszData)=0;
virtual HRESULT _stdcall Write(short nTable, short nRow, LPCWSTR lpszData)=0;
};
3、 为了避免 GUIDs 的重复定义,使用 DEFINE_GUID 宏。该宏在 OBJBASE.H 的定义为:
#ifndef INITGUID
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
EXTERN_C const GUID FAR name
#else
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
EXTERN_C const GUID name \
= { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
#endif // INITGUID
源文件在包含接口文件 .H 文件之前必须包含 #define INITGUID, 这需要该源文件禁止预编译头文件工作,否则编译器将使用预编译头文件中的错误的宏展开。
在接口文件 .H 处,创建一个 .cpp 文件,在此文件定义 INITGUID ,并包含 OLE2.H 和接口文件 .H
#define INITGUID
#include "ole2.h"
#include "DBsrv.h"
将其加入对象工程,并禁止该文件的预编译头文件。
二、 创建对象程序
1、 创建对象程序头文件 .H
a) 包含接口文件 .H
b) 实现接口的类的声明。
i. 该类声明使用多继承,实现多接口
ii. 为了在隐式连接 DLL 并使用 COM 库加载 DLL 时,能在适当的时候卸载 DLL 。就应该让 DLL 的卸载由 COM 负责, COM 查询 DLL 看它是否是在使用。调用 DllCanUnloadNow() ,根据该函数返回值是 S_OK 或 S_FALSE ,来判断是否可以卸载 DLL 。所以在类厂和对象都增加一个计数变量 m_dwRefCount ,同时增加一个全局变量 g_dwRefCount ,无论什么时候引用了对象指针,不管是类厂还是对象,都调用 AddRef() 增加对象的引用计数和一个记录所有的引用指针的全局变量。 DllCanUnloadNow() 只需要检查全局引用计数变量的值是否为 0 ,如果为 0 就返回 S_OK , COM 就可以安全卸载 DLL 。
c) 类厂声明从标准类厂接口 IClassFactory 继承
d) 程序代码
#ifndef _DBSERVERIMP_INCLUDE
#define _DBSERVERIMP_INCLUDE
#include "..\Interface\DBsrv.h"
typedef long HRESULT;
class CDB : public IDB,public IDBAccess,public IDBManage,public IDBInfo
{
//Interfaces
public:
//Interface for data access
HRESULT _stdcall Read(short nTable,short nRow,LPWSTR lpszData);
HRESULT _stdcall Write(short nTable,short nRow,LPCWSTR lpszData);
//Interface for database management
HRESULT _stdcall Create(short &nTable,LPCWSTR lpszName);
HRESULT _stdcall Delete(short nTable);
//Interface for database information
HRESULT _stdcall GetNumTables(short &nNumTables);
HRESULT _stdcall GetTableName(short nTable,LPWSTR lpszName);
HRESULT _stdcall GetNumRows(short nTable,short &nRows);
HRESULT _stdcall QueryInterface(REFIID riid,void** ppObject);
ULONG _stdcall AddRef();
ULONG _stdcall Release();
//Implementation
private:
CPtrArray m_arrTables;//Array of pointers to CStringArray(the "database")
CStringArray m_arrNames;//Array of table names
ULONG m_dwRefCount;
public:
CDB();
~CDB();
};
extern ULONG g_dwRefCount;
class CDBSrvFactory : public IClassFactory
{
//Interface
public:
HRESULT _stdcall QueryInterface(REFIID riid,void** ppObject);
ULONG _stdcall AddRef();
ULONG _stdcall Release();
HRESULT _stdcall CreateInstance(IUnknown *pUnkOuter,REFIID riid,void** ppObject);
HRESULT _stdcall LockServer(BOOL fLock);
//Implementaiton
private:
ULONG m_dwRefCount;
public:
CDBSrvFactory();
};
#endif
2、 创建对象实现文件 .CPP
a) 加入
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
用来查明内存泄漏源(非必须)
b) 在对象的构造函数中,将 m_dwRefCount 初始化为 0
c) 实现 QueryInterface() 函数中,根据不同的 IID ,返回不同的接口指针
HRESULT CDB::QueryInterface(REFIID riid,void **ppObject)
{
if(riid==IID_IUnknown || riid==IID_IDB)
{
*ppObject=(IDB*)this;
}
else if(riid==IID_IDBAccess)
{
*ppObject=(IDBAccess*)this;
}
else if(riid==IID_IDBManage)
{
*ppObject=(IDBManage*)this;
}
else if(riid==IID_IDBInfo)
{
*ppObject=(IDBInfo*)this;
}
else
{
return E_NOINTERFACE;
}
AddRef();
return NO_ERROR;
}
d) AddRef 和 Release 函数
ULONG CDB::AddRef()
{
g_dwRefCount++;
m_dwRefCount++;
return m_dwRefCount;
}
ULONG CDB::Release()
{
g_dwRefCount--;
m_dwRefCount--;
if(m_dwRefCount==0)
{
delete this;
return 0;
}
return m_dwRefCount;
}
3、 创建类厂的实现文件 .CPP
a) 初始化 g_dwRefCount
b) 定义类厂的实现函数
c) 定义被引出的几个函数 DllGetClassObject 、 DllCanUnloadNow 、 DllRegisterServer 、 DllUnregisterServer
WINDOWS 系统提供的用于注册的实用工具 RegSvr32 调用 DLL 的 DllRegisterServer 或 DllUnregisterServer 进行对象的注册。
#include "stdafx.h"
#include "DBSrvImp.h"
ULONG g_dwRefCount=0;
HRESULT CDBSrvFactory::CreateInstance(IUnknown *pUnkOuter,REFIID riid,void **ppObject)
{
if(pUnkOuter!=NULL)
{
return CLASS_E_NOAGGREGATION;
}
CDB *pDB=new CDB;
if(FAILED(pDB->QueryInterface(riid,ppObject)))
{
delete pDB;
*ppObject=NULL;
return E_NOINTERFACE;
}
return NO_ERROR;
}
ULONG CDBSrvFactory::Release()
{
g_dwRefCount--;
m_dwRefCount--;
if(m_dwRefCount==0)
{
delete this;
return 0;
}
return m_dwRefCount;
}
STDAPI DllGetClassObject(REFCLSID rclsid,REFIID riid,void** ppObject)
{
if(rclsid==CLSID_DBSAMPLE)
{
CDBSrvFactory *pFactory= new CDBSrvFactory;
if(FAILED(pFactory->QueryInterface(riid,ppObject)))
{
delete pFactory;
*ppObject=NULL;
return E_INVALIDARG;
}
}
else
{
return CLASS_E_CLASSNOTAVAILABLE;
}
return NO_ERROR;
}
HRESULT CDBSrvFactory::LockServer(BOOL fLock)
{
if(fLock)
{
g_dwRefCount++;
}
else
{
g_dwRefCount--;
}
return NO_ERROR;
}
CDBSrvFactory::CDBSrvFactory()
{
m_dwRefCount=0;
}
HRESULT CDBSrvFactory::QueryInterface(REFIID riid,void **ppObject)
{
if(riid==IID_IUnknown || riid==IID_IClassFactory)
{
*ppObject=(IDB*)this;
}
else
{
return E_NOINTERFACE;
}
AddRef();
return NO_ERROR;
}
ULONG CDBSrvFactory::AddRef()
{
g_dwRefCount++;
m_dwRefCount++;
return m_dwRefCount;
}
HRESULT _stdcall DllCanUnloadNow()
{
if(g_dwRefCount)
{
return S_FALSE;
}
else
{
return S_OK;
}
}
STDAPI DllRegisterServer(void)
{
HKEY hKeyCLSID,hKeyInproc32;
DWORD dwDisposition;
if(RegCreateKeyEx(HKEY_CLASSES_ROOT,_T("CLSID\\{96ECE846 -90A 6-43e2-8060 -7A 6A 91C 3D900}"),NULL,_T(""),REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,
&hKeyCLSID,&dwDisposition)!=ERROR_SUCCESS)
{
return E_UNEXPECTED;
}
if(RegSetValueEx(hKeyCLSID,_T(""),NULL,REG_SZ,(BYTE*)_T("DB Sample Server"),
sizeof(_T("DB Sample Server")))!=ERROR_SUCCESS)
{
RegCloseKey(hKeyCLSID);
return E_UNEXPECTED;
}
if(RegCreateKeyEx(hKeyCLSID,_T("InprocServer32"),NULL,_T(""),REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS,NULL,&hKeyInproc32,&dwDisposition)!=ERROR_SUCCESS)
{
RegCloseKey(hKeyCLSID);
return E_UNEXPECTED;
}
HMODULE hModule=GetModuleHandle(_T("DB.DLL"));
if(!hModule)
{
RegCloseKey(hKeyInproc32);
RegCloseKey(hKeyCLSID);
return E_UNEXPECTED;
}
TCHAR szName[MAX_PATH+1];
if(GetModuleFileName(hModule,szName,sizeof(szName))==0)
{
RegCloseKey(hKeyCLSID);
return E_UNEXPECTED;
}
if(RegSetValueEx(hKeyInproc32,_T(""),NULL,REG_SZ,(BYTE*)szName,sizeof(TCHAR)*(lstrlen(szName)+1))!=ERROR_SUCCESS)
{
RegCloseKey(hKeyInproc32);
RegCloseKey(hKeyCLSID);
return E_UNEXPECTED;
}
RegCloseKey(hKeyInproc32);
RegCloseKey(hKeyCLSID);
return NO_ERROR;
}
STDAPI DllUnregisterServer(void)
{
if(RegDeleteKey(HKEY_CLASSES_ROOT,
_T("CLSID\\{96ECE846 -90A 6-43e2-8060 -7A 6A 91C 3D900}\\InprocServer32"))!=ERROR_SUCCESS)
{
return E_UNEXPECTED;
}
if (RegDeleteKey(HKEY_CLASSES_ROOT,
_T("CLSID\\{96ECE846 -90A 6-43e2-8060 -7A 6A 91C 3D900}"))!=ERROR_SUCCESS)
{
return E_UNEXPECTED;
}
return NO_ERROR;
}
4、 创建模块定义文件 .DEF
a) 引出在类厂的实现文件 .CPP 中定义的几个被引出函数
b) 文件内容
EXPORTS
;WEP @1 RESIDENTNAME
DllGetClassObject
DllCanUnloadNow
DllRegisterServer
DllUnregisterServer
c) 加入工程
5、 修改 StdAfx.h 文件
a) 在所有 include 前加入 #define _AFX_NO_BSTR_SUPPORT
b) #include <ole2.h>
6、 将定义 INITGUID 的 .CPP 文件加入工程,禁止其预编译头文件
7、 编译工程,并用 REGSVR32 注册
三、 客户端程序使用 COM
1、 Include 发布的接口文件 .H
2、 将伴随着接口文件 .H 的 GUIDS.CPP 文件添加入工程
3、 在头文件定义 IUnknown 类型的接口指针变量,用于访问对象
IUnknown* m_pDB;
4、 调用 COM 库函数 CoGetClassObject 装载 DLL ,返回一个类厂的指针 pDBFactory
5、 通过 pDBFactory 调用 CreateInstance 返回 IUnknown 的对象指针 m_pDB
6、 pDBFactory->Release()
7、 通过 m_pDB->QueryInterface 访问各个接口,返回该接口的指针,可以通过该指针使用该接口提高的功能。使用完毕后 Release 该指针
8、 初始化 COM ,在 App 的 InitInstance 函数开始处调用 CoInitialize ,在 ExitInstance 调用 CoUninitialize
9、 用 ClassWizard 为 App 重载 OnIdle 函数,调用 CoFreeUnusedLibraries ,卸载无用的对象
10、 编译运行
11、 部分程序
BOOL CDBDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
//create database object
IClassFactory *pDBFactory=NULL;
HRESULT hRes;
hRes=CoGetClassObject(CLSID_DBSAMPLE,CLSCTX_SERVER,NULL,IID_IClassFactory,(void**)&pDBFactory);
if(FAILED(hRes))
{
CString csError;
csError.Format(_T("Error %x obtaining class factory for DB Object!"),hRes);
AfxMessageBox(csError);
return FALSE;
}
hRes=pDBFactory->CreateInstance(NULL,IID_IUnknown,(void**)&m_pDB);
if(FAILED(hRes))
{
CString csError;
csError.Format(_T("Error %x creating DB Object!"),hRes);
AfxMessageBox(csError);
return FALSE;
}
pDBFactory->Release();
//initialization
m_csData="No data yet!";
m_nCount=0;
m_nTable=-1;
return TRUE;
}
void CDBDoc::OnDbCreate()
{
IDBManage* pManage=NULL;
if(FAILED(m_pDB->QueryInterface(IID_IDBManage,(void**)&pManage)))
{
AfxMessageBox(_T("Error in QueryInterface for IDBManage!"));
return;
}
pManage->Create(m_nTable,L"Testing");
m_nCount=0;//set number of writes to 0
pManage->Release();
}
BOOL CDBApp::InitInstance()
{
if(FAILED(CoInitialize(NULL)))
{
AfxMessageBox(_T("Could not initialize COM Libraries!"));
return FALSE;
}
……….
}
int CDBApp::ExitInstance()
{
CoUninitialize();
return CWinApp::ExitInstance();
}
BOOL CDBApp::OnIdle(LONG lCount)
{
return CWinApp::OnIdle(lCount);
CoFreeUnusedLibraries();
return FALSE;
}