标签:com组件 工厂方法模式 多工厂类 大话设计模式 c++
上篇《大话设计模式C++版——抽象工厂模式》中,我们拯救世界未遂,留下小小的遗憾,本篇中我们将给出一个解决方案——COM组件技术,同时也顺便扯扯工厂模式在COM组件技术中的应用。工厂模式违背开放—封闭原则的根本原因在于对象的产生无法通过客户模块外的数据进行控制,如果我们能从xml、注册表、配置文件中写入一个类的名字,然后模块从中读出类名,并根据读出的类名创建对象,那不就和C#高大上的反射技术一样牛B哄哄了。非常幸运,微软的COM组件技术就提供了这么一个平台。
1、COM组件是神马为了节约篇幅,这个请自行百度
2、COM组件的实现为了揭开COM组件的本质,我们从0开始打造一个进程内COM组件,不使用ATL已自动化的一套。
2.1 COM组件的基础知识(已了解的自行跳过)class IUnknown { public: virtual ULONG AddRef() = 0; virtual ULONG Release() = 0; virtual HRESULT QueryInterface(REFIID riid, void **ppvObject) = 0; };IUnkown接口中 AddRef() 和 Release()是用来增加或减少接口对象的引用,当引用计数为0时,就删除对象自身,所以使用完COM接口后要记得 Release(),这样就会自动释放了,而不需要delete。QueryInterface()函数是返回对象接口的函数,参数 riid 是对象接口的IID,可以理解为请求的类名,参数 ppvObject 则为返回接口对象指针的指针。
class IClassFactory : public IUnknown { public: virtual HRESULT CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv) = 0; virtual HRESULT LockServer(BOOL bLock) = 0; };
IClassFactory 接口中最重要的是 CreateInstance() 函数,由它返回最终的用户使用的COM对象,第一个参数 pUnkOuter 表示是否聚合,我们这里都为NULL,表示不聚合,riid 是返回用户的COM对象的IID,ppv为返回用户的COM对象指针的指针。
2.2 新建一个Dll工程,导出如下4个函数STDAPI DllGetClassObject ( REFCLSID rclsid, REFIID riid, void** ppv ) STDAPI DllCanUnloadNow() STDAPI DllRegisterServer() STDAPI DllUnregisterServer()
最关键的是DllGetClassObject函数,其用来返回工厂对象,如何实现稍后再讲。
2.3 仍以上篇《大话设计模式C++版——抽象工厂模式》的数据库操作为例,改用COM组件技术实现class IEmployee : public IUnknown { public: virtual bool InserttoDB(Employee& stEmployee) = 0; virtual Employee GetEmployee(int nID) = 0; }; class IDepartment : public IUnknown { public: virtual bool InserttoDB(Department& stDepartment) = 0; virtual Department GetDepartment(int nID) = 0; };2.3.2 COM对象接口实现,CDataBasefromMysql实现类似(新增 IUnknown 接口的实现)
class CDataBasefromAccess : public IEmployee, public IDepartment { public: CDataBasefromAccess() : m_ulRefCount(0) {} ULONG STDMETHODCALLTYPE AddRef( void) { return ++m_ulRefCount; } ULONG STDMETHODCALLTYPE Release( void) { m_ulRefCount--; //如果对象引用计数为0,则删除自身 if ( 0 == m_ulRefCount ) { delete this; } return m_ulRefCount; } HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) { HRESULT hr = S_OK; //如果请求的是默认接口或者IID_IEmployee接口都予以返回IEmployee接口 if (IsEqualIID(riid, IID_IEmployee)) { *ppvObject = (IEmployee*)this; } else if (IsEqualIID(riid, IID_IDepartment)) { *ppvObject = (IDepartment*)this; } else { hr = E_NOINTERFACE; } if (SUCCEEDED(hr)) { AddRef(); } return hr; } //IEmployee接口实现函数 bool InserttoDB(Employee& stEmployee) { _tprintf(_T("Insert employee %s into access\n"), stEmployee.tstrName.c_str()); return true; } Employee GetEmployee(int nID) { Employee stEmployee; printf("Get an employee from access by id %d\n", nID); return stEmployee; } //IDepartment接口实现函数 bool InserttoDB(Department& stDepartment) { _tprintf(_T("Insert Department %s into access\n"), stDepartment.tstrDepartmentName.c_str()); return true; } Department GetDepartment(int nID) { Department stDepartment; printf("Get an Department from access by id %d\n", nID); return stDepartment; } private: ULONG m_ulRefCount; };由于COM组件技术中要求从QueryInterface()中返回1个或多个COM接口对象,并返回的任意一个接口能够查询到其他返回COM接口对象,故新增一个类继承并实现 IEmployee 和 IDepartment 接口,然后在QueryInterface()中通过类型强制转换的方式返回riid对应的COM接口对象,if else if 的结构返回接口对象的方式有点像简单工厂模式,但手法不一样了。
class CFactoryfromMysql : public IClassFactory { public: CFactoryfromMysql(void) : m_ulRefCount(0) {} ULONG STDMETHODCALLTYPE AddRef( void) { return ++m_ulRefCount; } ULONG STDMETHODCALLTYPE Release( void) { m_ulRefCount--; //如果对象引用计数为0,则删除自身 if ( 0 == m_ulRefCount ) { delete this; } return m_ulRefCount; } HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) { HRESULT hr = S_OK; //如果请求的是默认接口或者IID_IEmployee接口都予以返回接口指针,并增加引用 if (IsEqualIID(riid, IID_IClassFactory) || IsEqualIID(riid, IID_IFactoryfromMysql)) { *ppvObject = (IClassFactory*)this; } else if (IsEqualIID(riid, IID_IUnknown)) { *ppvObject = (IUnknown*)this; } else { hr = E_NOINTERFACE; } if (SUCCEEDED(hr)) { AddRef(); } return hr; } HRESULT STDMETHODCALLTYPE CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv) { if (pUnkOuter) { return CLASS_E_NOAGGREGATION; //不支持聚合 } CDataBasefromMysql* poCDataBasefromMysql = new CDataBasefromMysql(); if (NULL == poCDataBasefromMysql) { return E_OUTOFMEMORY; } HRESULT hr = poCDataBasefromMysql->QueryInterface(riid, ppv); if (FAILED(hr)) { delete poCDataBasefromMysql; return hr; } return S_OK; } HRESULT STDMETHODCALLTYPE LockServer(BOOL bLock) { bLock ? g_ulDllRefCount++ : g_ulDllRefCount--; return S_OK; } private: ULONG m_ulRefCount; };COM组件技术要求一个COM对象需要一个对应的工厂生产,即要满足工厂方法模式,其生产对象是在CreateInstance()中 new 出一个生产对象后用riid去请求对象的QueryInterface函数最终返回用户需要的COM对象。那么工厂对象又是从那来的呢?
STDAPI DllGetClassObject ( REFCLSID rclsid, REFIID riid, void** ppv ) { if(!IsEqualGUID(rclsid, CLSID_IDataBase)) { return CLASS_E_CLASSNOTAVAILABLE; } HRESULT hr = S_OK; IClassFactory* poCClassFactory = NULL; do { //遍历所有的工厂类,判断返回请求成功的riid *ppv = NULL; poCClassFactory = new CFactoryfromMysql(); if (poCClassFactory) { if (S_OK == poCClassFactory->QueryInterface(riid, ppv)) { break; } else { poCClassFactory->Release(); } } poCClassFactory = new CFactoryfromAccess(); if (poCClassFactory) { if (S_OK == poCClassFactory->QueryInterface(riid, ppv)) { break; } else { poCClassFactory->Release(); } } hr = CLASS_E_CLASSNOTAVAILABLE; } while (FALSE); return hr; }前面说过COM组件技术中要求从QueryInterface()中返回接口对象,IClassFactory 自然也一样,所以要挨个调用现有工厂的 QueryInterface 函数,取到riid对应的工厂对象,这里为了能让大家看清楚,就未作优化了。可能有同学会问,为什么不写成简单工厂的方式,根据riid返回不同的工厂对象,这是由于将判断放到工厂类内部,可以给工厂类更大的灵活性,允许工厂内部做一些检查或其他工作,决定是否返回工厂对象,同时,也保持了和COM接口对象请求的一致性。
// {377C4A5C-52CB-4557-A9E5-A57018B34197} static const GUID IID_IEmployee = { 0x377c4a5c, 0x52cb, 0x4557, { 0xa9, 0xe5, 0xa5, 0x70, 0x18, 0xb3, 0x41, 0x97 } }; // {940E7C36-8472-46E9-BE93-6C1F53699A1D} static const GUID IID_IDepartment = { 0x940e7c36, 0x8472, 0x46e9, { 0xbe, 0x93, 0x6c, 0x1f, 0x53, 0x69, 0x9a, 0x1d } }; // {094EF4B6-406F-4CDC-A036-DBFC9E163196} static const GUID IID_IFactoryfromMysql = { 0x94ef4b6, 0x406f, 0x4cdc, { 0xa0, 0x36, 0xdb, 0xfc, 0x9e, 0x16, 0x31, 0x96 } }; // {208C8F65-4F26-499D-A075-C0A6DFC312B1} static const GUID IID_IFactoryfromAccess = { 0x208c8f65, 0x4f26, 0x499d, { 0xa0, 0x75, 0xc0, 0xa6, 0xdf, 0xc3, 0x12, 0xb1 } }; // {712CE48E-F1B4-4C5D-8BA8-6E367C4D6422} static const GUID CLSID_IDataBase = { 0x712ce48e, 0xf1b4, 0x4c5d, { 0x8b, 0xa8, 0x6e, 0x36, 0x7c, 0x4d, 0x64, 0x22 } };2.4.2 注册组件
void Test() { CoInitialize(NULL); HRESULT hr = S_OK; IID stFactoryIID = IID_IFactoryfromAccess; //更换此处即可 IClassFactory* poIClassFactory = NULL; //取工厂类对象 hr = CoGetClassObject(CLSID_IDataBase, CLSCTX_INPROC_SERVER, NULL, stFactoryIID, (void**)&poIClassFactory); if (SUCCEEDED(hr)) { IEmployee* poIEmployee = NULL; if (SUCCEEDED(poIClassFactory->CreateInstance(NULL, IID_IEmployee, (void**)&poIEmployee))) { Employee stEmployee; stEmployee.nID = 1; stEmployee.tstrName = _T("Jim"); poIEmployee->InserttoDB(stEmployee); poIEmployee->Release(); } else { printf("Get IEmployee fail!!!\n"); } IDepartment* poIDepartment = NULL; if (SUCCEEDED(poIClassFactory->CreateInstance(NULL, IID_IDepartment, (void**)&poIDepartment))) { Department stDepartment; stDepartment.nID = 2; stDepartment.tstrDepartmentName = _T("Marketing"); stDepartment.tstrManager = _T("Jim"); poIDepartment->InserttoDB(stDepartment); poIDepartment->Release(); } else { printf("Get IDepartment fail!!!\n"); } poIClassFactory->Release(); } else { printf("Get IClassFactory fail!!!\n"); } CoUninitialize(); }
组件调用先通过CoGetClassObject()取工厂对象,组件注册后会将自身的CLSID和路径写入注册表中CoGetClassObject()实际通过 CLSID_IDataBase 找到Dll的路径并加载,然后调用DllGetClassObject()传入工厂类的IID取得工厂类对象,然后调用工厂类对象的CreateInstance()函数传入COM接口对象的IID取得用户需要的COM接口对象,改用COM组件技术实现后,如果需要更换数据库,只需要改变“IID stFactoryIID = IID_IFactoryfromAccess;”的IID即可,IID实际上是一串数字,我们可以放到配置文件、XML、注册表等其他位置,让客户端程序去加载。如果有新的数据类型要加入,比如SQL Server,我们在组件中加入后,客户端只要改变工厂类IID的配置文件即可,无需改动客户端代码,完美体现开放—封闭原则。
COM组件在注册表的CLSID和路径
由于代码较多,已将整个解决方案打包上传,代码在VS2010下编译通过,测试前在cmd中输入“Regsvr32 DataBaseCom.dll”即可完成注册,然后运行Console.exe即可看到运行结果标签:com组件 工厂方法模式 多工厂类 大话设计模式 c++
原文地址:http://blog.csdn.net/gufeng99/article/details/46500153