转自:https://blog.csdn.net/shejiannan/article/details/26386271
最近在学习COM,自己实现了一个小Demo,拿出来和大家分享一下。求各种批评。
我实现的这个组件向外提供了一个接口ICompTest,里面只有一个函数helloworld(),功能为返回一个整数89。
实现了自注册功能。下面贴出代码,希望对刚开始学习COM的朋友有所帮助。
首先看一下工程结构,编译环境为vs 2008
CompTest工程是服务端工程,是一个dll,CtrlTest是客户端工程,是一个控制台工程。
下面通过客户端的运行逻辑来讲述整个运行流程,先看一下CtrlTest.cpp文件
/** * @file CtrlTest.cpp * @author LiWang112358 * @date 2012/3/17 * @version 1.0 * @brief COM客户端代码 */ #include <iostream> #include "ICompTest.h" using namespace std; int main() { CoInitialize(NULL); //初始化COM库,使用默认的内存分配器 IUnknown* pUnknown = NULL; GUID CLSID_CompTestClass; HRESULT hResult = CLSIDFromProgID(L"COMCTL.CompTest", &CLSID_CompTestClass); //获取ProgID为COMCTL.CompTest组建的CLSID if (S_OK != hResult){ printf("Can't find CLSID! "); return -1; } else{ LPOLESTR szCLSID; StringFromCLSID(CLSID_CompTestClass, &szCLSID); //将其转化为字符串形式用来输出 wprintf(L"find CLSID "%s" ",szCLSID); CoTaskMemFree(szCLSID); //调用COM库的内存释放 } //用此CLSID创建一个COM对象并获取IUnknown接口 hResult = CoCreateInstance(CLSID_CompTestClass, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown,(void **)&pUnknown); if (S_OK != hResult || NULL == pUnknown){ printf("Create Object Failed! "); return -1; } ICompTest* pCompTest = NULL; hResult = pUnknown->QueryInterface(IID_ICompTest, (void**)&pCompTest);//通过此结构查询我们自己的ICompTest接口 cout << pCompTest->HelloWorld() << endl;//调用我们自己接口中的函数 pCompTest->Release(); //释放自己的接口 pUnknown->Release(); //释放IUnknown接口 CoUninitialize(); //COM库反初始化 return 0; }
这是客户程序的主逻辑,主要就是通过COM库创建CompTestClass对象,这个对象在这里是不可见的,这里只能拿到ICompTest接口,通过该接口调用函数HelloWorld。
下面看一下接口的声明ICompTest.h文件,这个文件是客户端和服务端都要有的。
/** * @file ICompTest.h * @author LiWang112358 * @date 2012/3/17 * @version 1.0 * @brief ICompTest接口的声明这个文件是服务端和客户端共有的 */ #ifndef ICOMPTEST_H #define ICOMPTEST_H #include <unknwn.h> // {81A80687-6CC4-4996-8DD2-F058907FDCA8} static const GUID IID_ICompTest = { 0x81a80687, 0x6cc4, 0x4996, { 0x8d, 0xd2, 0xf0, 0x58, 0x90, 0x7f, 0xdc, 0xa8 } }; class ICompTest : public IUnknown { public: virtual int _stdcall HelloWorld() = 0; }; #endif
这个文件中有一个GUID IID_ICompTest ,用于查询接口ICompTest 。
我们可以在 CtrlTest.cpp文件中看到CLSIDFromProgID和CoCreateInstance这两个函数,
第一个函数是要通过一个名字"COMCTL.CompTest"拿到一个CLSID,这个过程需要CLSID信息。
第二个函数是要通过这个CLSID找到我们的组件(dll),并加载这个dll,然后创建COM对象,这个过程需要dll的路径信息。
这些信息都被放在注册表中,是这个组件自注册的时候由DllRegisterServer函数写入的。
我们可以先看一下注册之后注册表中的内容
其中COMCTL.CompTest是在键HKEY_CLASSES_ROOT下,{9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}是在键HKEY_CLASSES_ROOTCLSID下。
下面我们看一下这个dll的注册过程。
用“regsvr32.exe dll路径”对dll进行注册,实际上regsvr32只是调用了dll中的DllRegisterServer引出函数。
下面我们看一下DllRegisterServer函数的实现,这个函数在CompTest.cpp中。
int myReg(LPCWSTR lpPath) //将本组件的信息写入注册表,包括CLSID、所在路径lpPath、ProgID { HKEY thk, tclsidk; //打开键HKEY_CLASSES_ROOTCLSID,创建新键为CompTestClass的CLSID, //在该键下创建键InprocServer32,并将本组件(dll)所在路径lpPath写为该键的默认值 if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"CLSID", &thk)){ if (ERROR_SUCCESS == RegCreateKey(thk, L"{9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}", &tclsidk)){ HKEY tinps32k, tprogidk; if (ERROR_SUCCESS == RegCreateKey(tclsidk, L"InprocServer32", &tinps32k)){ if (ERROR_SUCCESS == RegSetValue(tinps32k, NULL, REG_SZ, lpPath, wcslen(lpPath) * 2)){ } RegCloseKey(tinps32k); } RegCloseKey(tclsidk); } RegCloseKey(thk); } //在键HKEY_CLASSES_ROOT下创建新键为COMCTL.CompTest, //在该键下创建子键,并将CompTestClass的CLSID写为该键的默认值 if (ERROR_SUCCESS == RegCreateKey(HKEY_CLASSES_ROOT, L"COMCTL.CompTest", &thk)){ if (ERROR_SUCCESS == RegCreateKey(thk, L"CLSID", &tclsidk)){ if (ERROR_SUCCESS == RegSetValue(tclsidk, NULL, REG_SZ, L"{9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}", wcslen(L"{9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}") * 2)){ } } } //这样的话一个客户端程序如果想要使用本组件,首先可以以COMCTL.CompTest为参数调用CLSIDFromProgID函数 //来获取CompTestClass的CLSID,再以这个CLSID为参数调用CoCreateInstance创建COM对象 return 0; } extern "C" HRESULT _stdcall DllRegisterServer() { WCHAR szModule[1024]; DWORD dwResult = GetModuleFileName(g_hModule, szModule, 1024); //获取本组件(dll)所在路径 if (0 == dwResult){ return -1; } MessageBox(NULL, szModule, L"", MB_OK); myReg(szModule);//将路径等信息写入注册表 return 0; }
用“regsvr32.exe dll路径 -u”对dll进行反注册,同样,实际上regsvr32只是调用了dll中的DllUnregisterServer引出函数。
下面我们来看一下DllUnregisterServer函数的实现,这个函数在CompTest.cpp中。
int myDelKey(HKEY hk, LPCWSTR lp) { if (ERROR_SUCCESS == RegDeleteKey(hk, lp)){ } return 0; } int myDel() //删除注册时写入注册表的信息 { HKEY thk; if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"CLSID", &thk)){ myDelKey(thk, L"{9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}\InprocServer32"); myDelKey(thk, L"{9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}"); RegCloseKey(thk); } if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"COMCTL.CompTest", &thk)){ myDelKey(thk, L"CLSID"); } myDelKey(HKEY_CLASSES_ROOT, L"COMCTL.CompTest"); return 0; } extern "C" HRESULT _stdcall DllUnregisterServer() { myDel();//删除注册时写入注册表的信息 return 0; }
我们继续分析客户端的代码CoCreateInstance(CLSID_CompTestClass, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown,(void **)&pUnknown);
这个函数是要调用CoGetClassObject函数,来获取CompTestClass的类厂,以此创建CompTestClass对象并获取IUnknown接口。其中,CoGetClassObject函数
实际上是调用了CompTest.cpp中的又一个引出函数DllGetClassObject来获取IClassFactory接口的。最终CoCreateInstance会调用IClassFactory接口的CreateInstance
函数去创建COM对象。
下面我们看一下DllGetClassObject函数的实现
extern "C" HRESULT _stdcall DllGetClassObject(__in REFCLSID rclsid, __in REFIID riid, LPVOID FAR* ppv)//用于创建类厂并返回所需接口,由CoGetClassObject函数调用 { if (CLSID_CompTestClass == rclsid){ CompTestFactory* pFactory = new CompTestFactory();//创建类厂对象 if (NULL == pFactory){ return E_OUTOFMEMORY; } HRESULT result = pFactory->QueryInterface(riid, ppv);//获取所需接口 return result; } else{ return CLASS_E_CLASSNOTAVAILABLE; } }
接下来我们看一下组件中的最后一个引出函数DllCanUnloadNow,这样dll中的所有引出函数就都出现了
extern "C" HRESULT _stdcall DllCanUnloadNow()//用于判断是否可以卸载本组建, 由CoFreeUnusedLibraries函数调用 { if (0 == g_num){//如果对象个数为0,则可以卸载 return S_OK; } else{ return S_FALSE; } }
其中ULONG g_num表示组件中CompTestClass对象的个数,用于判断是否可以卸载本组建,如值为0则可以卸载
下面我们看一下CompTest.cpp的全貌
/** * @file CompTest.cpp * @author LiWang112358 * @date 2012/3/17 * @version 1.0 * @brief 四个重要的引出函数的实现, * @ 分别是DllRegisterServer,用于注册本dll,由regsvr32.exe调用 * @ DllUnregisterServer, 用于反注册本dll,由regsvr32.exe -u 调用 * @ DllCanUnloadNow,用于判断是否可以卸载本组建, 由CoFreeUnusedLibraries函数调用 * @ DllGetClassObject,用于创建类厂并返回所需接口,由CoGetClassObject函数调用 */ #include <iostream> #include <windows.h> #include "factory.h" #include "CompTestClass.h" using namespace std; HMODULE g_hModule; //dll进程实例句柄 ULONG g_num; //组件中CompTestClass对象的个数,用于判断是否可以卸载本组建,如值为0则可以卸载 int myReg(LPCWSTR lpPath) //将本组件的信息写入注册表,包括CLSID、所在路径lpPath、ProgID { HKEY thk, tclsidk; //打开键HKEY_CLASSES_ROOTCLSID,创建新键为CompTestClass的CLSID, //在该键下创建键InprocServer32,并将本组件(dll)所在路径lpPath写为该键的默认值 if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"CLSID", &thk)){ if (ERROR_SUCCESS == RegCreateKey(thk, L"{9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}", &tclsidk)){ HKEY tinps32k, tprogidk; if (ERROR_SUCCESS == RegCreateKey(tclsidk, L"InprocServer32", &tinps32k)){ if (ERROR_SUCCESS == RegSetValue(tinps32k, NULL, REG_SZ, lpPath, wcslen(lpPath) * 2)){ } RegCloseKey(tinps32k); } RegCloseKey(tclsidk); } RegCloseKey(thk); } //在键HKEY_CLASSES_ROOT下创建新键为COMCTL.CompTest, //在该键下创建子键,并将CompTestClass的CLSID写为该键的默认值 if (ERROR_SUCCESS == RegCreateKey(HKEY_CLASSES_ROOT, L"COMCTL.CompTest", &thk)){ if (ERROR_SUCCESS == RegCreateKey(thk, L"CLSID", &tclsidk)){ if (ERROR_SUCCESS == RegSetValue(tclsidk, NULL, REG_SZ, L"{9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}", wcslen(L"{9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}") * 2)){ } } } //这样的话一个客户端程序如果想要使用本组件,首先可以以COMCTL.CompTest为参数调用CLSIDFromProgID函数 //来获取CompTestClass的CLSID,再以这个CLSID为参数调用CoCreateInstance创建COM对象 return 0; } extern "C" HRESULT _stdcall DllRegisterServer() { WCHAR szModule[1024]; DWORD dwResult = GetModuleFileName(g_hModule, szModule, 1024); //获取本组件(dll)所在路径 if (0 == dwResult){ return -1; } MessageBox(NULL, szModule, L"", MB_OK); myReg(szModule);//将路径等信息写入注册表 return 0; } int myDelKey(HKEY hk, LPCWSTR lp) { if (ERROR_SUCCESS == RegDeleteKey(hk, lp)){ } return 0; } int myDel() //删除注册时写入注册表的信息 { HKEY thk; if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"CLSID", &thk)){ myDelKey(thk, L"{9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}\InprocServer32"); myDelKey(thk, L"{9CA9DBE8-C0B1-42c9-B6C7-856BE5756855}"); RegCloseKey(thk); } if (ERROR_SUCCESS == RegOpenKey(HKEY_CLASSES_ROOT, L"COMCTL.CompTest", &thk)){ myDelKey(thk, L"CLSID"); } myDelKey(HKEY_CLASSES_ROOT, L"COMCTL.CompTest"); return 0; } extern "C" HRESULT _stdcall DllUnregisterServer() { myDel();//删除注册时写入注册表的信息 return 0; } extern "C" HRESULT _stdcall DllCanUnloadNow()//用于判断是否可以卸载本组建, 由CoFreeUnusedLibraries函数调用 { if (0 == g_num){//如果对象个数为0,则可以卸载 return S_OK; } else{ return S_FALSE; } } extern "C" HRESULT _stdcall DllGetClassObject(__in REFCLSID rclsid, __in REFIID riid, LPVOID FAR* ppv)//用于创建类厂并返回所需接口,由CoGetClassObject函数调用 { if (CLSID_CompTestClass == rclsid){ CompTestFactory* pFactory = new CompTestFactory();//创建类厂对象 if (NULL == pFactory){ return E_OUTOFMEMORY; } HRESULT result = pFactory->QueryInterface(riid, ppv);//获取所需接口 return result; } else{ return CLASS_E_CLASSNOTAVAILABLE; } } BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { g_hModule = hModule;//获取进程实例句柄,用于获取本组件(dll)路径 switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
下面是.def文件mydef.def用于声明dll的引出函数
LIBRARY "CompTest" EXPORTS DllCanUnloadNow DllGetClassObject DllUnregisterServer DllRegisterServer
下面是剩下的文件,有些许注释,语焉不详之处,望海涵。
CompTestClass.h
/** * @file CompTestClass.h * @author LiWang112358 * @date 2012/3/17 * @version 1.0 * @brief CompTestClass对象的声明,这个类要实现ICompTest接口 */ #ifndef COMPTESTCLASS_H #define COMPTESTCLASS_H #include "ICompTest.h" // {9CA9DBE8-C0B1-42c9-B6C7-856BE5756855} static const GUID CLSID_CompTestClass = { 0x9ca9dbe8, 0xc0b1, 0x42c9, { 0xb6, 0xc7, 0x85, 0x6b, 0xe5, 0x75, 0x68, 0x55 } }; class CompTestClass : public ICompTest { public: CompTestClass(); ~CompTestClass(); //要实现IUnknown接口 virtual HRESULT _stdcall QueryInterface(const IID& riid, void** ppvObject); virtual ULONG _stdcall AddRef(); virtual ULONG _stdcall Release(); //要实现ICompTest接口 virtual int _stdcall HelloWorld(); protected: ULONG m_Ref; }; #endif
CompTestClass.cpp
/** * @file CompTestClass.cpp * @author LiWang112358 * @date 2012/3/17 * @version 1.0 * @brief CompTestClass类的实现 */ #include "CompTestClass.h" ULONG CompTestClass::m_objNum = 0;//组件中CompTestClass对象的个数,用于判断是否可以卸载本组建,如值为0则可以卸载 CRITICAL_SECTION CompTestClass::m_cs;//为了多线程调用对m_objNum加的锁 CompTestClass::CompTestClass() { m_Ref = 0; autoLock tlock(m_cs); m_objNum ++; //构造了一个对象 } CompTestClass::~CompTestClass() { autoLock tlock(m_cs); m_objNum --; //释放了一个对象 } HRESULT _stdcall CompTestClass::QueryInterface(const IID &riid, void **ppvObject) { if (IID_IUnknown == riid){ *ppvObject = (IUnknown*)this; ((IUnknown*)(*ppvObject))->AddRef(); } else if (IID_ICompTest == riid){ *ppvObject = (ICompTest*)this; ((ICompTest*)(*ppvObject))->AddRef(); } else{ *ppvObject = NULL; return E_NOINTERFACE; } return S_OK; } ULONG _stdcall CompTestClass::AddRef() { m_Ref ++; return m_Ref; } ULONG _stdcall CompTestClass::Release() { m_Ref --; if (0 == m_Ref){ delete this; return 0; } return m_Ref; } int _stdcall CompTestClass::HelloWorld()//ICompTest接口的实现,返回一个整数89 { return 89; } int CompTestClass::Init() { m_objNum = 0; InitializeCriticalSection(&m_cs); return 0; } ULONG CompTestClass::ObjNum() { return m_objNum; }
factory.h
/** * @file factory.h * @author LiWang112358 * @date 2012/3/17 * @version 1.0 * @brief CompTestClass对象的类厂的声明 */ #ifndef FACTORY_H #define FACTORY_H #include <unknwn.h> class CompTestFactory : public IClassFactory { public: CompTestFactory(); ~CompTestFactory(); //要实现IUnknown接口 virtual HRESULT _stdcall QueryInterface(const IID& riid, void** ppvObject); virtual ULONG _stdcall AddRef(); virtual ULONG _stdcall Release(); //要实现IClassFactory接口 virtual HRESULT _stdcall CreateInstance(IUnknown *pUnkOuter, const IID& riid, void **ppvObject); virtual HRESULT _stdcall LockServer(BOOL fLock); protected: ULONG m_Ref; }; #endif
factory.cpp
/** * @file factory.cpp * @author LiWang112358 * @date 2012/3/17 * @version 1.0 * @brief CompTestClass对象的类厂的实现 */ #include "factory.h" #include "CompTestClass.h" CompTestFactory::CompTestFactory() { m_Ref = 0; } CompTestFactory::~CompTestFactory() { } HRESULT _stdcall CompTestFactory::QueryInterface(const IID &riid, void **ppvObject) { if (IID_IUnknown == riid){ *ppvObject = (IUnknown*)this; ((IUnknown*)(*ppvObject))->AddRef(); } else if (IID_IClassFactory == riid){ *ppvObject = (IClassFactory*)this; ((IClassFactory*)(*ppvObject))->AddRef(); } else{ *ppvObject = NULL; return E_NOINTERFACE; } return S_OK; } ULONG _stdcall CompTestFactory::AddRef() { m_Ref ++; return m_Ref; } ULONG _stdcall CompTestFactory::Release() { m_Ref --; if (0 == m_Ref){ delete this; return 0; } return m_Ref; } HRESULT _stdcall CompTestFactory::CreateInstance(IUnknown *pUnkOuter, const IID &riid, void **ppvObject)//最重要的函数,这个函数创建CompTestClass对象,并返回所需接口 { if (NULL != pUnkOuter){ return CLASS_E_NOAGGREGATION; } HRESULT hr = E_OUTOFMEMORY; CompTestClass::Init(); CompTestClass* pObj = new CompTestClass(); if (NULL == pObj){ return hr; } hr = pObj->QueryInterface(riid, ppvObject); if (S_OK != hr){ delete pObj; } return hr; } HRESULT _stdcall CompTestFactory::LockServer(BOOL fLock) { return NOERROR; }
以下是客户程序的运行结果