二,初步逼近COM--使用COM库加载C++对象
上面的代码中声明了DLL的一个入口点DllGetClassFactoryObject,而客户程序就是通过调用这个函数,从而获取到相应的类工厂对象,再使用类工厂对象创建真正的对象的。我们这一步要尝试让客户程序不去直接调用对象的入口函数,而是让COM库为我们服务,并且要让对象使用标准的入口函数。
1,修改接口定义文件
首先在接口定义文件中增加类ID和接口ID的声明,这两个ID在对象程序和客户程序中都会有定义,在这里只是对这两个外部变量进行说明。然后将引出函数DllGetClassFactoryObject删除,因为接下来我们将会使用标准入口函数DllGetObject,因此不需要再自己定义入口函数了。

Code
typedef long HRESULT;
#define DEF_EXPORT __declspec(dllexport)
// {30DF3430-0266-11cf-BAA6-00AA003E0EED}
extern const GUID CLSID_DBSAMPLE;
//{ 0x30df3430, 0x266, 0x11cf, { 0xba, 0xa6, 0x0, 0xaa, 0x0, 0x3e, 0xe, 0xed } };
// {30DF3431-0266-11cf-BAA6-00AA003E0EED}
extern const GUID IID_IDBSrvFactory;
//{ 0x30df3431, 0x266, 0x11cf, { 0xba, 0xa6, 0x0, 0xaa, 0x0, 0x3e, 0xe, 0xed } };
class IDB
{
// Interfaces
public:
// Interface for data access
virtual HRESULT Read(short nTable, short nRow, LPWSTR lpszData) =0;
virtual HRESULT Write(short nTable, short nRow, LPCWSTR lpszData) =0;
// Interface for database management
virtual HRESULT Create(short &nTable, LPCWSTR lpszName) =0;
virtual HRESULT Delete(short nTable) =0;
// Interfase para obtenber informacion sobre la base de datos
virtual HRESULT GetNumTables(short &nNumTables) =0;
virtual HRESULT GetTableName(short nTable, LPWSTR lpszName) =0;
virtual HRESULT GetNumRows(short nTable, short &nRows) =0;
virtual ULONG Release() =0;
};
class IDBSrvFactory
{
// Interface
public:
virtual HRESULT CreateDB(IDB** ppObject) =0;
virtual ULONG Release() =0;
};
//HRESULT DEF_EXPORT DllGetClassFactoryObject(IDBSrvFactory ** ppObject);
2,修改对象程序
修改如下:1)为类ID和接口ID定义GUID。2)将DllGetClassFactoryObject改成标准入口函数DllGetClassObject。具体代码如下:

Code
#include "stdafx.h"
#include "DBsrvImp.h"
// {30DF3430-0266-11cf-BAA6-00AA003E0EED}
static const GUID CLSID_DBSAMPLE =
{ 0x30df3430, 0x266, 0x11cf, { 0xba, 0xa6, 0x0, 0xaa, 0x0, 0x3e, 0xe, 0xed } };
// {30DF3431-0266-11cf-BAA6-00AA003E0EED}
static const GUID IID_IDBSrvFactory =
{ 0x30df3431, 0x266, 0x11cf, { 0xba, 0xa6, 0x0, 0xaa, 0x0, 0x3e, 0xe, 0xed } };
// Create a new database object and return a pointer to it
HRESULT CDBSrvFactory::CreateDB(IDB** ppvDBObject)
{
*ppvDBObject=(IDB*) new CDB;
return NO_ERROR;
}
ULONG CDBSrvFactory::Release()
{
delete this;
return 0;
}
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppObject)
{
if (rclsid!=CLSID_DBSAMPLE)
{
return CLASS_E_CLASSNOTAVAILABLE;
}
if (riid!=IID_IDBSrvFactory)
{
return E_INVALIDARG;
}
*ppObject=(IDBSrvFactory*) new CDBSrvFactory;
return NO_ERROR;
}
注:1)由于标准入口函数DllGetClassObject在 objbase.h文件中已经声明了,因此在我们的代码中不必再声明它。2)在stdafx.h中加入了#include <ole2.h>,并且在所有的include前加入了:#define _AFX_NO_BSTR_SUPPORT,这是因为MFC头文件中的一些定义和ole2.h中不一样。
然后,要让客户程序访问到入口函数,我们要为创建一个模块定义DEF文件,在其中引出DllGetClassObject函数,DB.def代码如下:

Code
EXPORTS
;WEP @1 RESIDENTNAME
DllGetClassObject
注:不能用__declspec(dllexport)来引出DllGetClassObject函数,因为在objbase.h中它的定义处已经使用了其他修饰词。
最后,这个对象要想通过COM库创建,就必须在注册表中进行注册,但目前还没有加入自我注册部分,因此我们先对其进行手动注册吧。其实要做的事情很简单,就是把我们的DLL的路径告诉给COM库就行了。步骤如下:
1)HKEY_CLASSES_ROOT"CLSID下添加一个子键,名字就是上面定义的类ID:{ 30DF3430-0266-11cf-BAA6-00AA003E0EED}。
2)为这个子键再添加一个子键InprocServer32,为它添加一个未命名的字符串值:类型为REG_SZ,数据为<path>"db.dll,也就是保存你的DLL的路径。
3,修改客户程序
1)在DBDoc.cpp中也加入类ID和接口ID的定义

Code
// {30DF3430-0266-11cf-BAA6-00AA003E0EED}
static const GUID CLSID_DBSAMPLE =
{ 0x30df3430, 0x266, 0x11cf, { 0xba, 0xa6, 0x0, 0xaa, 0x0, 0x3e, 0xe, 0xed } };
// {30DF3431-0266-11cf-BAA6-00AA003E0EED}
static const GUID IID_IDBSrvFactory =
{ 0x30df3431, 0x266, 0x11cf, { 0xba, 0xa6, 0x0, 0xaa, 0x0, 0x3e, 0xe, 0xed } };
2)初始化COM库。在我们使用COM库之前先对其进行初始化。

Code
if (FAILED(CoInitialize(NULL)))
{
AfxMessageBox(_T("Could not initialize COM Libraries!"));
return FALSE;
}
并且在程序退出时调用CoUninitialize()

Code
int CDBApp::ExitInstance()
{
CoUninitialize();
return CWinApp::ExitInstance();
}
2)改为使用COM库函数来创建对象。在CDBDoc::OnNewDocument()函数中,使用COM库函数CoGetClassObject代替原来直接装载DLL的方式。

Code
//新建数据库对象
//m_pDB=new CDB;
IDBSrvFactory *pDBFactory=NULL;
HRESULT hRes;
hRes=CoGetClassObject(CLSID_DBSAMPLE, CLSCTX_SERVER, NULL, IID_IDBSrvFactory, (void**) &pDBFactory);
if (FAILED(hRes))
{
CString csError;
csError.Format(_T("Error %x creating DB Object!"), hRes);
AfxMessageBox(csError);
return FALSE;
}
pDBFactory->CreateDB(&m_pDB);
pDBFactory->Release(); // do not need the factory anymore
注:以前在客户程序中需要对应的DLL在路径下,但现在并不需要了,因为COM库会根据注册表中的信息找到DLL所在路径的。