【VS开发】这就是COM组件
[实例]这就是COM组件
[实例]这就是COM组件
Author: ume Date:2012-02-21
自从微软推出.NET以来,COM技术就渐渐淡出人们的视野,然而这并不意味COM不再发挥作用,相反,COM非常重要。可以说.NET的实现离不开COM的支撑。COM是好东西,但是它太难了,不利于产品推广,于是微软只能在之上增加一层封装从而提高产品的易用性。对COM有所了解是很有必要的,希望这篇文章给你带来一点启发。
1. COM的思想
开篇就讲COM的思想肯定让人泄气,因为它极有可能抽象空洞、晦涩难懂。换个角度来说,我觉得COM的思想仍然是需要自己去体会的,这里给出的不过是一个思考的线索而已,你大可不求甚解。
软件的开发是永无止境的,只要软件还在存活期,它就应当不断被完善和更新。一款软件可能十分庞大,但真正可变的部分却是很有限的。我们当然希望在更新软件的时候,只更新变化的部分,而不要笨手笨脚把整个软件都换掉。只更新变化的部分?这就要求模块支持动态链接。所谓动态链接就是模块只在被使用的时候才被加载,程序调用模块中的某个函数是通过指针来完成的。动态链接允许模块分离出去,而不像静态链接那样须经过编译才能整合到程序中来。dll是实现动态链接的一种方法,它使更新软件的工作浓缩成了更新dll,用户无需重新安装软件,只需替换相应的dll文件就能实现软件的升级。
动态链接是针对普通用户而言的,现在换一个对象:模块的用户。模块的用户是应用程序开发人员,对于模块的提供商来说也算得上同行了,只不过术业有专攻,各自工作的重点不同而已。显然采用dll的形式,模块的提供商可以很方便的发布自己的产品。其中不可忽视的另一点即信息的封装(或称隐藏),即将模块的实现细节隐蔽起来,用户无法知道模块的提供商采用何种语言、何种算法,简而言之就是用户看不到模块的源代码。dll是二进制级别上的代码复用,它实现了信息的封装。
综上所述,软件开发要求模块支持“动态链接”和“信息封装”,更直白地说就是要求模块和客户代码之间更低的耦合度。把模块制作成组件是必然的选择,而COM本质上是一种创建组件的方法和规范。
注:dll并不等同于组件,它只是组件的一种形式。由于dll的易用性,它的应用很广泛。
2. 实例说明
我们创建一个COM组件,它将实现接口ICouplet,用户可通过该接口调用what()方法输出一副对联。what()方法不值一提,不过你可以将它当作程序可变的部分。我们创建的COM组件也要实现接口IClassFactory,它是创建组件的简单组件。之所以这么设计是为了让组件与客户代码彻底脱耦,尽可能少的联系。
除了实现接口ICouplet和IClassFactory外, COM组件还要能实现自注册,因此它必须导出函数DllRegister/DllUnregister。另外两个导出函数DllCanUnloadNow和DllGetClassObject也非常重要,前者用来询问当前dll能否被卸载,它被CoFreeUnusedLibraries调用;后者用来创建类厂组件,它被CoCreateInstance调用。名称形如Coxxx的函数是COM库函数,它是实现COM组件的公共操作,由微软提供,类似于Win32 API。我们常见的客户代码中CoInitialize/CoUninitialize函数就起到初始化和卸载COM库的作用。要导出上述4个函数就必须编写一个.def文件,具体写法见代码清单。
最后要说明的是COM组件的自注册。我们知道注册表是Windows的公共系统数据库,其中记录了软件、硬件、用户配置等信息。而COM组件是用一个128比特的GUID标识的,为了使得COM组件的安装目录更灵活,我们可以在注册表中对它进行注册,注册的主要信息即COM组件的GUID标识与其存储路径的对应关系,在使用该组件时就到注册表中去查找。注册一个COM组件一般使用regsvr32.exe程序来完成,当然你也可以自己写一个类似于regsvr32.exe的小程序来完成COM组件的注册,regsvr32.exe本质上调用了组件的导出函数DllRegister/DllUnregister。
生成Couplet.dll文件后,首先在控制台注册它。具体方法:切换到Couplet.dll所在目录,输入指令regsvr32 Couplet.dll。然后运行客户程序Reader.exe,其结果如下所示:
Create Couplet object
Succeeded in getting the pointer to ICouplet
1st Scroll: Study Hard, Work Hard, Make Money More and More
2nd Scroll: Eat Well, Sleep Well, Have Fun Day by Day
Top Scroll: Gelievable
Couplet object deleted
请按任意键继续. . .
然后修改Couplet::what()方法,让它输出中文,重新生成Couplet.dll。这一步不用重新注册Couplet.dll,因为Couplet.dll的路径没变,CLSID_Couplet也没变。运行客户程序Reader.exe,其结果如下所示:
CreateCouplet object
Succeededin getting the pointer to ICouplet
上联:我爱的人名花有主
下联:爱我的人惨不忍睹
横批:命苦
Coupletobject deleted
请按任意键继续. . .
这个例子证明了COM组件的更新不会对客户端造成影响,使用COM组件可以实现模块与客户代码彻底脱耦。实验结束后,在控制台输入指令regsvr32 /u Couplet.dll,从注册表中将dll模块信息清除。
3. 代码清单
/* File List: (COM) IFace.h Register.h Register.cpp Couplet.cpp Couple.def * (Client) IFace.h Reader.cpp * date: 2012-02-21 * author: ume */ ///////////////////////////////////////////////////////////////// // IFace.h 接口的声明,组件ID、接口ID的定义 // #include <ObjBase.h> // interface interface ICouplet : IUnknown { virtual void what() = 0; }; // GUIDs // {03844548-B0B9-4B12-869D-061AAE2E4B7F} static const GUID IID_ICouplet = { 0x3844548, 0xb0b9, 0x4b12, { 0x86, 0x9d, 0x6, 0x1a, 0xae, 0x2e, 0x4b, 0x7f } }; // {26615B48-1D2E-4A40-9C07-AD5B1B48368C} static const GUID CLSID_Couplet = { 0x26615b48, 0x1d2e, 0x4a40, { 0x9c, 0x7, 0xad, 0x5b, 0x1b, 0x48, 0x36, 0x8c } }; ///////////////////////////////////////////////////////////////// // Register.h 注册函数的声明 // HRESULT RegisterServer(HMODULE hModule, const CLSID& clsid, const char* szFriendlyName, const char* szVerIndProgID, const char* szProgID); HRESULT UnRegisterServer(const CLSID& clsid, const char* szVerIndProgID, const char* szProgID); ///////////////////////////////////////////////////////////////// // Register.cpp 注册函数的定义 // 这些函数可重复使用,非本文重点 // #include <objbase.h> #include "Register.h" //set the given key and its value; BOOL setKeyAndValue(const char* pszPath, const char* szSubkey, const char* szValue); //Convert a CLSID into a char string void CLSIDtochar(const CLSID& clsid, char* szCLSID, int length); //Delete szKeyChild and all of its descendents LONG recursiveDeleteKey(HKEY hKeyParent,const char* szKeyChild); //size of a CLSID as a string const int CLSID_STRING_SIZE = 39; //Register the component in the registry HRESULT RegisterServer(HMODULE hModule, const CLSID& clsid, const char* szFriendlyName, const char* szVerIndProgID, const char* szProgID) { //Get the Server location char szModule[512]; DWORD dwResult = ::GetModuleFileName(hModule,szModule,sizeof(szModule)/sizeof(char)); assert(dwResult!=0); //Convert the CLSID into a char char szCLSID[CLSID_STRING_SIZE]; CLSIDtochar(clsid,szCLSID,sizeof(szCLSID)); //Build the key CLSID\\{} char szKey[64]; strcpy(szKey,"CLSID\\"); strcat(szKey,szCLSID); //Add the CLSID to the registry setKeyAndValue(szKey,NULL,szFriendlyName); //Add the Server filename subkey under the CLSID key setKeyAndValue(szKey,"InprocServer32",szModule); setKeyAndValue(szKey,"ProgID",szProgID); setKeyAndValue(szKey,"VersionIndependentProgID",szVerIndProgID); //Add the version-independent ProgID subkey under HKEY_CLASSES_ROOT setKeyAndValue(szVerIndProgID,NULL,szFriendlyName); setKeyAndValue(szVerIndProgID,"CLSID",szCLSID); setKeyAndValue(szVerIndProgID,"CurVer",szProgID); //Add the versioned ProgID subkey under HKEY_CLASSES_ROOT setKeyAndValue(szProgID,NULL,szFriendlyName); setKeyAndValue(szProgID,"CLSID",szCLSID); return S_OK; } // //Remove the component from the register // HRESULT UnRegisterServer(const CLSID& clsid, // Class ID const char* szVerIndProgID, // Programmatic const char* szProgID) // IDs { //Convert the CLSID into a char. char szCLSID[CLSID_STRING_SIZE]; CLSIDtochar(clsid,szCLSID,sizeof(szCLSID)); //Build the key CLSID\\{} char szKey[64]; strcpy(szKey,"CLSID\\"); strcat(szKey,szCLSID); //Delete the CLSID key - CLSID\{} LONG lResult = recursiveDeleteKey(HKEY_CLASSES_ROOT,szKey); assert((lResult == ERROR_SUCCESS) || (lResult == ERROR_FILE_NOT_FOUND)); //Delete the version-independent ProgID Key lResult = recursiveDeleteKey(HKEY_CLASSES_ROOT,szVerIndProgID); assert((lResult == ERROR_SUCCESS) || (lResult == ERROR_FILE_NOT_FOUND)); //Delete the ProgID key. lResult = recursiveDeleteKey(HKEY_CLASSES_ROOT,szProgID); assert((lResult == ERROR_SUCCESS) || (lResult == ERROR_FILE_NOT_FOUND)); return S_OK; } //Convert a CLSID to a char string void CLSIDtochar(const CLSID& clsid, char* szCLSID, int length) { assert(length>=CLSID_STRING_SIZE); //Get CLSID LPOLESTR wszCLSID = NULL; HRESULT hr = StringFromCLSID(clsid,&wszCLSID); assert(SUCCEEDED(hr)); //Convert from wide characters to non_wide wcstombs(szCLSID,wszCLSID,length); //Free memory CoTaskMemFree(wszCLSID); } // Delete a Key and all of its descendents LONG recursiveDeleteKey(HKEY hKeyParent,const char* lpszKeyChild) { //Open the child. HKEY hKeyChild; LONG lRes = RegOpenKeyEx(hKeyParent,lpszKeyChild,0,KEY_ALL_ACCESS,&hKeyChild); if(lRes != ERROR_SUCCESS) return lRes; //Enumerate all of the decendents of this child FILETIME time; char szBuffer[256]; DWORD dwSize = 256 ; while(RegEnumKeyEx(hKeyChild,0,szBuffer,&dwSize,NULL, NULL,NULL,&time) == S_OK) { //Delete the decendents of this child. lRes = recursiveDeleteKey(hKeyChild,szBuffer); if(lRes != ERROR_SUCCESS) { RegCloseKey(hKeyChild); return lRes; } dwSize = 256; } RegCloseKey(hKeyChild); return RegDeleteKey(hKeyParent,lpszKeyChild); } BOOL setKeyAndValue(const char* szKey, const char* szSubkey, const char* szValue) { HKEY hKey; char szKeyBuf[1024]; //Copy keyname into buffer. strcpy(szKeyBuf,szKey); //Add subkey name to buffer. if(szSubkey!=NULL) { strcat(szKeyBuf,"\\"); strcat(szKeyBuf,szSubkey); } // Create and open key and subkey. long lResult = RegCreateKeyEx(HKEY_CLASSES_ROOT , szKeyBuf, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, NULL) ; if (lResult != ERROR_SUCCESS) { return FALSE ; } // Set the Value. if (szValue != NULL) { RegSetValueEx(hKey, NULL, 0, REG_SZ, (BYTE *)szValue, strlen(szValue)+1) ; } RegCloseKey(hKey) ; return TRUE ; } ///////////////////////////////////////////////////////////////// // Couplet.cpp 接口的实现 // 本文的重点,尤其是Couplet和CFactory的实现 // #include "IFace.h" #include "Register.h" #include <iostream> using namespace std; // trace void trace(const char* msg) { cout<<msg<<endl; } // global variables HMODULE g_hModule; static long g_cComponents = 0; static long g_cLocks = 0; // Friendly name of component const char g_szFriendlyName[] = "A Couplet"; // Version independent ProgID const char g_szVerIndProgID[] = "Couplet.Test"; // ProgID const char g_szProgID[] = "Couplet.Test.1"; // implementation class Couplet : public ICouplet { public: virtual LRESULT __stdcall QueryInterface(const IID& iid, void** ppv); virtual ULONG __stdcall AddRef() { return ::InterlockedIncrement(&m_cRef); } virtual ULONG __stdcall Release() { if(::InterlockedDecrement(&m_cRef) == 0) { delete this; return 0; } return m_cRef; } virtual void what() { //cout<<"\n上联:我爱的人名花有主\n下联:爱我的人惨不忍睹\n横批:命苦\n\n"; cout<<"\n1st Scroll: Study Hard, Work Hard, Make Money More and More\n\ 2nd Scroll: Eat Well, Sleep Well, Have Fun Day by Day\nTop Scroll: Gelievable\n\n"; } // constructor Couplet() : m_cRef(1) { ::InterlockedIncrement(&g_cComponents); trace("Create Couplet object"); } // destructor ~Couplet() { ::InterlockedDecrement(&g_cComponents); trace("Couplet object deleted"); } private: long m_cRef; }; // definition of QueryInterface LRESULT __stdcall Couplet::QueryInterface(const IID& iid, void** ppv) { if((iid == IID_IUnknown) || (iid == IID_ICouplet)) { *ppv = static_cast<ICouplet*>(this); } else { *ppv = NULL; return E_NOINTERFACE; } static_cast<IUnknown*>(*ppv)->AddRef(); return S_OK; } // class CFactory class CFactory : public IClassFactory { public: virtual LRESULT __stdcall QueryInterface(const IID& iid, void** ppv); virtual ULONG __stdcall AddRef() { return ::InterlockedIncrement(&m_cRef); } virtual ULONG __stdcall Release() { if(::InterlockedDecrement(&m_cRef) == 0) { delete this; return 0; } return m_cRef; } virtual LRESULT __stdcall CreateInstance(IUnknown* pCmpntOuter, const IID& iid, void** ppv); virtual LRESULT __stdcall LockServer(BOOL bLock); private: long m_cRef; }; // definition of QueryInterface LRESULT __stdcall CFactory::QueryInterface(const IID& iid, void** ppv) { if((iid == IID_IUnknown) || (iid == IID_IClassFactory)) { *ppv = static_cast<IClassFactory*>(this); } else { *ppv = NULL; return E_NOINTERFACE; } static_cast<IUnknown*>(*ppv)->AddRef(); return S_OK; } // definition of CreateInstance LRESULT __stdcall CFactory::CreateInstance(IUnknown* pCmpntOuter, const IID& iid, void** ppv) { if(pCmpntOuter != NULL) { cout<<"No Aggregate in this Class Factory"<<endl; return CLASS_E_NOAGGREGATION; } Couplet* pCouplet = new Couplet; if(pCouplet == NULL) return E_OUTOFMEMORY; HRESULT hr = pCouplet->QueryInterface(iid, ppv); pCouplet->Release(); return hr; } // definition of LockServer LRESULT __stdcall CFactory::LockServer(BOOL bLock) { if(bLock) { ::InterlockedIncrement(&g_cLocks); } else { ::InterlockedDecrement(&g_cLocks); } return S_OK; } STDAPI DllCanUnloadNow() { if((g_cComponents == 0) && (g_cLocks == 0)) { return S_OK; } else { return S_FALSE; } } // Get class factory STDAPI DllGetClassObject(const CLSID& clsid, const IID& iid, void** ppv) { // Can we create this component? if(clsid != CLSID_Couplet) { return CLASS_E_CLASSNOTAVAILABLE; } // Create class factory CFactory* pFactory = new CFactory; if(pFactory == NULL) { return E_OUTOFMEMORY; } // Get requested interface HRESULT hr = pFactory->QueryInterface(iid, ppv); pFactory->Release(); return hr; } // register and unregister component STDAPI DllRegisterServer() { return RegisterServer(g_hModule, CLSID_Couplet, g_szFriendlyName, g_szVerIndProgID, g_szProgID); } STDAPI DllUnregisterServer() { return UnRegisterServer(CLSID_Couplet, g_szVerIndProgID, g_szProgID); } // dll main BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved) { if(dwReason == DLL_PROCESS_ATTACH) { g_hModule = (HMODULE)hModule; } return TRUE; } ///////////////////////////////////////////////////////////////// // Couplet.def 模块定义文件 // LIBRARY Couplet.dll EXPORTS DllCanUnloadNow @1 PRIVATE DllGetClassObject @2 PRIVATE DllRegisterServer @3 PRIVATE DllUnregisterServer @4 PRIVATE ///////////////////////////////////////////////////////////////// // Reader.cpp 通过ICouplet接口调用what()方法读取对联内容 // 注意: 客户端的IFace.h与COM组件中的IFace.h完全一样 // #include <iostream> #include <ObjBase.h> #include "IFace.h" using namespace std; // global function void trace(const char* pMsg){ cout<<pMsg<<endl; } // main function int main() { ::CoInitialize(NULL); ICouplet* pICouplet = NULL; HRESULT hr = ::CoCreateInstance(CLSID_Couplet, NULL, CLSCTX_INPROC_SERVER, IID_ICouplet, (void**)&pICouplet); if(SUCCEEDED(hr)) { trace("Succeeded in getting the pointer to ICouplet"); pICouplet->what(); pICouplet->Release(); } else { trace("Failed to get the pointer to ICouplet"); } ::CoUninitialize(); system("pause"); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】