本文主要记录一下COM编程的简单实现。关于COM的简单介绍可以参考文章 COM的简单介绍。
目录
1、代码的主要结构
2、ComTest
2.1、接口类的定义
2.2、对象类的定义
2.3、类厂的定义
2.4、自注册模块
2.5、ComTest.cpp
2.6、dllmain.cpp
2.7、ComTest.def
3、ComCtrl
4、代码实现
1、代码的主要结构
代码结构主要分为两部分,其中 ComTest 为组件程序部分,ComCtrl 为客户部分(即组件的使用者),具体如下所示:
2、ComTest
ComTest 为 VS2012所创建的 Win32项目->DLL 类型的工程。代码的组成结构如下所示:

- IUnknown 是所有接口的基类;
- IComTest 为组件的接口类,在这个接口类中定义能提供的服务(即函数),所以用户只需要知道这个类就可以了,;
- CComTest 是对象类,继承自接口IComTest,用于实现服务;
- IClassFactory 为类厂类的接口类,不需要开发人员实现;
- CComTestFactory 为类厂类,用于生产CComTest对象;
- 还有一个自注册模块,用于将组件程序注册进注册表中;
具体实现可以参考以下进行开发。
2.1、接口类的定义
IComTest.h
#pragma once #include <Unknwn.h> // 接口 IComTest 的GUID标识,可以通过VS自带的工具自动生成 // {24EC0D3E-2C69-4C39-8AAB-59AFF} static const GUID IID_IComTest = { 0x24ec0d3e, 0x2c69, 0x4c39, { 0x8a, 0xab, 0x59, 0xaf, 0xf1, 0x11, 0x19, 0x82 } }; class IComTest : public IUnknown{ public: virtual BOOL __stdcall SayHello() = 0; //声明一个服务函数,用于测试 };
讯享网
在这个文件中,主要是声明了接口类的GUID, 即IID_IComTest;同时声明了组件程序的接口函数。接口类没有实现,所以没有对应的 IComTest.cpp 文件。另外这个 IComTest.h 头文件必须包含在用户程序中。

2.2、对象类的定义
CComTest.h
讯享网#pragma once #include <stdio.h> #include "icomtest.h" // CComTest 对象的GUID标识,名称为 CLSID // {CEF56010-936E-4F71-BC51-32F7ED3B3F73} static const GUID CLSID_CComTest = { 0xcef56010, 0x936e, 0x4f71, { 0xbc, 0x51, 0x32, 0xf7, 0xed, 0x3b, 0x3f, 0x73 } }; class CComTest : public IComTest { public: CComTest(void); ~CComTest(void); public: //IUnknown member functions virtual HRESULT __stdcall QueryInterface(const IID& iid, void ppv); virtual ULONG __stdcall AddRef(); virtual ULONG __stdcall Release(); //IComTest member functions virtual BOOL __stdcall SayHello(); private: ULONG m_Ref; //引用计数 };
在这个文件中,主要是声明了对象类的GUID(也成为CLSID), 即 CLSID_CComTest;同时声明了对象类的成员函数,这些成员函数是必须要实现的,所以有对应的 CComTest.cpp文件。
CComTest.cpp
#include "stdafx.h" #include "CComTest.h" extern ULONG g_ObjNumber; //在ComTest.cpp中定义 CComTest::CComTest(void){ this->m_Ref = 0; //引用计数初始化为0 g_ObjNumber ++; //对象计数 +1 } CComTest::~CComTest(void){ } //IUnknown member functions HRESULT CComTest::QueryInterface(const IID& iid, void ppv){ if( iid == IID_IUnknown ){ *ppv = (IUnknown *) this ; ((IUnknown *)(*ppv))->AddRef(); //返回的是 IUnknown 接口 }else if ( iid == IID_IComTest ){ //IID_IComTest 已经在 IComTest.h 中定义 *ppv = (IComTest *) this ; ((IComTest *)(*ppv))->AddRef(); } else{ *ppv = NULL; return E_NOINTERFACE ; } return S_OK; } ULONG CComTest::AddRef(){ this->m_Ref ++; //引用计数 +1 return this->m_Ref; } ULONG CComTest::Release(){ this->m_Ref --; //引用计数 -1 if(this->m_Ref == 0){ //当引用计数为0时,则删除自身对象 g_ObjNumber --; //同时对象个数 -1 delete this; return 0; } return this->m_Ref; } //IComTest member functions BOOL CComTest::SayHello(){ printf("Hello COM!\n"); return TRUE; }
2.3、类厂的定义
CComTestFactory.h
讯享网#pragma once #include <Unknwn.h> //包含头文件 class CComTestFactory : public IClassFactory { public: CComTestFactory(void); ~CComTestFactory(void); public: //IUnknown member functions virtual HRESULT __stdcall QueryInterface(const IID& iid, void ppv); virtual ULONG __stdcall AddRef(); virtual ULONG __stdcall Release(); //IClassFactory member functions virtual HRESULT __stdcall CreateInstance(IUnknown *pUnknownOuter, const IID& iid, void ppv); virtual HRESULT __stdcall LockServer(BOOL bLock); private: ULONG m_Ref; //引用计数 };
CComTestFactory.cpp
#include "stdafx.h" #include "CComTest.h" #include "CComTestFactory.h" extern ULONG g_LockNumber; //类厂对象 的锁计数器,在ComTest.cpp中定义,下同 extern ULONG g_ObjNumber; //组件中 CComTest 对象的个数,用于判断是否可以卸载本组件,如果值为0则可以卸载 CComTestFactory::CComTestFactory(void){ this->m_Ref = 0; //引用计数初始化为0 } CComTestFactory::~CComTestFactory(void){} //IUnknown member functions HRESULT CComTestFactory::QueryInterface(const IID& iid, void ppv){ if( iid == IID_IUnknown ){ *ppv = (IUnknown *) this ; ((IUnknown *)(*ppv))->AddRef(); }else if ( iid == IID_IClassFactory ){ //IID_IClassFactory 已经在 Unknwn.h 中定义 *ppv = (IClassFactory *) this ; ((IClassFactory *)(*ppv))->AddRef(); } else{ *ppv = NULL; return E_NOINTERFACE ; } return S_OK; } ULONG CComTestFactory::AddRef(){ this->m_Ref ++; //引用计数 +1 return this->m_Ref; } ULONG CComTestFactory::Release(){ this->m_Ref --; //引用计数 -1 //当引用计数为0时,则删除自身对象 if(this->m_Ref == 0){ delete this; return 0; } return this->m_Ref; } //IClassFactory member functions //创建 CComTest 对象,并返回 接口指针 HRESULT CComTestFactory::CreateInstance(IUnknown *pUnknownOuter, const IID& iid, void ppv){ if (NULL != pUnknownOuter) return CLASS_E_NOAGGREGATION; HRESULT hr = E_OUTOFMEMORY; //Create the object passing function to notify on destruction. CComTest* pObj = new CComTest(); if (NULL == pObj) return hr; //Obtain the first interface pointer (which does an AddRef) *ppv = NULL; hr=pObj->QueryInterface(iid, ppv); if (hr != S_OK) { //Kill the object if initial creation or FInit failed. g_ObjNumber --; // Reference count g_ObjNumber be added in CComTest constructor delete pObj; } return hr; } HRESULT CComTestFactory::LockServer(BOOL bLock){ if(bLock){ g_LockNumber++; }else{ g_LockNumber--; } return NOERROR; }
其中最主要的函数是 CreateInstance ,该函数用于创建对应对象类的接口指针。因为每个类厂能创建的对象类是已知的,所以返回的接口指针就是对应对象类的接口指针。CreateInstance函数 是当用户调用 CoCreateInstance函数创建类厂接口指针时,会自行调用,这个与 《COM的简单介绍》中介绍的不是很一致,请注意区分。
2.4、自注册模块
组件的自注册就是要在组件的引出函数中定义 DllRegisterServer 和 DllUnregisterServer 两个函数。其中 DllRegisterServer 函数是往注册表写入注册信息,DllUnregisterServer 函数是从注册表中删除注册信息。
DllRegisterServer
讯享网extern "C" HRESULT __stdcall DllRegisterServer() { char szModule[1024]; //For char wchar_t w_szModule[1024]; //For wchar_t DWORD dwResult = ::GetModuleFileName((HMODULE)g_hModule, w_szModule, 1024); if (dwResult == 0) return SELFREG_E_CLASS; Wchar2Char(szModule, w_szModule); //在这里进行转换,防止对 Registry.cpp进行过多的修改 return RegisterServer(CLSID_CComTest, //对象类 CComTest 的GUID szModule, //组件程序的完整路径 "ComTest.Object", //ProgID "ComTest Component", //Description NULL); }
DllUnregisterServer
extern "C" HRESULT __stdcall DllUnregisterServer() { return UnregisterServer(CLSID_CComTest, "ComTest.Object", NULL); }
其中,函数 RegisterServer 和 UnregisterServer 全都定义在 Registry.cpp文件中,在该文件中实现了一个比较通用的注册成员。由于该文件中代码比较多,可前往文末最后的下载链接中自行下载。
2.5、ComTest.cpp
在该文件中定义了组件程序必须提供的四个引出函数 DllGetClassObject、DllCanUnloadNow、DllRegisterServer 、DllUnregisterServer ,详细代码如下所示:
讯享网// ComTest.cpp : 定义 DLL 应用程序的导出函数。 // #include "stdafx.h" #include "olectl.h" #include "IComTest.h" #include "CComTest.h" #include "CComTestFactory.h" #include "Registry.h" ULONG g_LockNumber = 0; //类厂对象 的锁计数器 ULONG g_ObjNumber = 0; //组件中 CComTest 对象的个数,用于判断是否可以卸载本组件,如果值为0则可以卸载 HANDLE g_hModule; //DLL句柄 extern "C" HRESULT __stdcall DllGetClassObject(const CLSID& clsid, const IID& iid, void ppv) { if(clsid == CLSID_CComTest ){ CComTestFactory *pFactory = new CComTestFactory; 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) { //双重检查,只有当对象计数和锁计数都为0时,才可以卸载组件程序 if ((g_ObjNumber == 0) && (g_LockNumber == 0)) return S_OK; else return S_FALSE; } // Server registration extern "C" HRESULT __stdcall DllRegisterServer() { char szModule[1024]; //For char wchar_t w_szModule[1024]; //For wchar_t DWORD dwResult = ::GetModuleFileName((HMODULE)g_hModule, w_szModule, 1024); if (dwResult == 0) return SELFREG_E_CLASS; Wchar2Char(szModule, w_szModule); //在这里进行转换,防止对 Registry.cpp进行过多的修改 return RegisterServer(CLSID_CComTest, //对象类 CComTest 的GUID szModule, //组件程序的完整路径 "ComTest.Object", //ProgID "ComTest Component", //Description NULL); } // Server unregistration extern "C" HRESULT __stdcall DllUnregisterServer() { return UnregisterServer(CLSID_CComTest, "ComTest.Object", NULL); }
2.6、dllmain.cpp
dllmain.cpp定义了组件程序的入口函数,如下所示:
// dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "stdafx.h" extern HANDLE g_hModule; //DLL句柄,在ComTest.cpp中定义 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; }
2.7、ComTest.def
这个.def文件声明了组件需要引出的几个函数,如下所示:
讯享网; ComTest.def : Declares the module parameters for the DLL. LIBRARY "ComTest" EXPORTS ; Explicit exports can go here DllGetClassObject PRIVATE DllRegisterServer PRIVATE DllUnregisterServer PRIVATE DllCanUnloadNow PRIVATE
3、ComCtrl
ComCtrl 为 VS2012所创建的 Win32控制台应用程序 类型的工程,主要用于模拟对组件的调用。在ComCtrl工程中,一定要包含ComTest工程中的 IComTest.h头文件,因为在该文件中声明了组件程序的接口。需要注意的是,在使用组件对象之前一定要使用 RegSvr32 命令进行组件的注册:
,当注册成功之后会有以下提示,否则会报错:

此时就会在注册表中看到的注册信息

用户详细代码如下所示:
#include "windows.h" #include <stdio.h> #include <comutil.h> int TestCom(){ //初始化COM库,使用默认的内存分配器 if (CoInitialize(NULL) != S_OK) { printf("Initialize COM library failed!\n"); return -1; } HRESULT hResult; GUID comTestCLSID; //获取 ProgID为 ComTest.Object 组件的CLISD,其中 ComTest.Object 是在注册注册表时确定的ID hResult = ::CLSIDFromProgID(L"ComTest.Object", &comTestCLSID); if (hResult != S_OK) { printf(">>> Can't find the ComTest CLSID!\n"); return -2; }else{ LPOLESTR szCLSID; StringFromCLSID(comTestCLSID, &szCLSID); //将其转化为字符串形式用来输出 wprintf(L">>> Find ComTest CLSID: \"%s\"\n",szCLSID); CoTaskMemFree(szCLSID); //调用COM库的内存释放 } IUnknown *pUnknown; //用此 CLSID 创建一个COM对象,并获取 IUnknown 接口,指向的是类厂对象 hResult = CoCreateInstance( comTestCLSID, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void )&pUnknown); if (hResult != S_OK || NULL == pUnknown){ printf("Create object failed!\n"); return -2; } IComTest* pComTest; //通过此 IUnknown 接口查询 IComTest 接口 hResult = pUnknown->QueryInterface(IID_IComTest, (void )&pComTest); if (hResult != S_OK) { pUnknown->Release(); printf("QueryInterface IComTest failed!\n"); return -3; } BOOL re = FALSE; printf(">>> 调用组件接口函数:\n"); re = pComTest->SayHello(); //调用 IComTest 接口中的函数 if(re){ printf(">>> 成功调用 COM 组件!\n"); }else{ printf(">>> ERROR!\n"); } pComTest->Release(); //释放 IComTest 接口 if (pUnknown->Release()== 0) //释放 IUnknown 接口 printf("The reference count of ComTest object is zero.\n"); CoUninitialize(); //COM库反初始化 return 0; } int main(int argc, char* argv[]) { int re = TestCom(); system("pause"); return 0; }
以上代码中,当用户调用 CoCreateInstance函数创建类厂接口指针时,会自行调用类厂的CreateInstance函数创建对象,所以不用再进行显示的调用了,这个与 前文《COM的简单介绍》中的最后介绍的协作流程的不是很一致,请注意区分。
4、代码实现
工程采用VS2012编写,全部代码的下载请访问:ComTest.zip下载

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/121743.html