同一进程中两个C++ COM DLL插件如何共享已加载COM DLL并通信?
实现两个COM DLL间共享已加载的COM模块句柄
首先,咱们得明确核心目标:让DLL1全权负责目标COM DLL的加载,DLL2直接复用DLL1已经加载好的模块句柄来调用函数,彻底避免重复加载的问题。下面是一套可落地的实现方案:
1. 在DLL1中封装加载逻辑并暴露句柄获取接口
DLL1要做的是:自己加载目标COM DLL,把句柄存起来,然后给DLL2留一个“获取句柄”的入口。
关键代码实现
在DLL1的某个源文件中添加如下逻辑:
#include <windows.h> #include <objbase.h> // 全局变量:保存目标COM DLL的模块句柄(进程内唯一) static HMODULE g_hTargetComDll = nullptr; // 引用计数:确保模块在被使用时不会被误卸载 static LONG g_nTargetDllRefCount = 0; // 初始化目标COM DLL(首次加载或增加引用时调用) HRESULT InitTargetComDll() { if (g_hTargetComDll) { // 已加载,直接增加引用计数 InterlockedIncrement(&g_nTargetDllRefCount); return S_OK; } // 加载目标COM DLL,这里替换成你的实际路径 g_hTargetComDll = LoadLibraryEx(L"YourTargetCom.dll", nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); if (!g_hTargetComDll) { return HRESULT_FROM_WIN32(GetLastError()); } InterlockedIncrement(&g_nTargetDllRefCount); return S_OK; } // 释放目标COM DLL(减少引用计数,计数为0时卸载) void ReleaseTargetComDll() { if (g_hTargetComDll && InterlockedDecrement(&g_nTargetDllRefCount) == 0) { FreeLibrary(g_hTargetComDll); g_hTargetComDll = nullptr; } } // 导出函数:供DLL2获取目标COM DLL的句柄 extern "C" __declspec(dllexport) HMODULE GetTargetComDllHandle() { // 确保模块已加载 if (SUCCEEDED(InitTargetComDll())) { return g_hTargetComDll; } return nullptr; } // 导出函数:供DLL2释放句柄引用 extern "C" __declspec(dllexport) void ReleaseTargetComDllHandle() { ReleaseTargetComDll(); }
注意事项
- 别在
DllMain的PROCESS_ATTACH里直接调用InitTargetComDll,DllMain中做复杂操作容易出问题。最好在DLL1首次创建目标COM实例的时候调用InitTargetComDll。 - 用
extern "C"导出函数是为了避免C++的名称修饰,否则DLL2用GetProcAddress会找不到函数。
2. 在DLL2中调用DLL1的接口获取句柄并调用函数
DLL2需要先拿到DLL1的模块句柄,然后调用DLL1导出的函数获取目标COM DLL的句柄,最后用GetProcAddress调用目标函数。
关键代码实现
#include <windows.h> #include <objbase.h> // 函数指针定义:匹配DLL1导出的GetTargetComDllHandle typedef HMODULE(*PFN_GET_TARGET_DLL_HANDLE)(); // 函数指针定义:匹配DLL1导出的ReleaseTargetComDllHandle typedef void(*PFN_RELEASE_TARGET_DLL_HANDLE)(); // 获取共享的目标COM DLL句柄 HMODULE GetSharedTargetComDllHandle() { // 获取DLL1的模块句柄(因为是同一应用的插件,DLL1肯定已加载) HMODULE hDll1 = GetModuleHandle(L"DLL1.dll"); if (!hDll1) { return nullptr; } // 获取DLL1导出的GetTargetComDllHandle函数地址 PFN_GET_TARGET_DLL_HANDLE pfnGetHandle = reinterpret_cast<PFN_GET_TARGET_DLL_HANDLE>( GetProcAddress(hDll1, "GetTargetComDllHandle") ); if (!pfnGetHandle) { return nullptr; } // 调用函数获取句柄 return pfnGetHandle(); } // 释放共享句柄的引用 void ReleaseSharedTargetComDllHandle() { HMODULE hDll1 = GetModuleHandle(L"DLL1.dll"); if (!hDll1) { return; } PFN_RELEASE_TARGET_DLL_HANDLE pfnReleaseHandle = reinterpret_cast<PFN_RELEASE_TARGET_DLL_HANDLE>( GetProcAddress(hDll1, "ReleaseTargetComDllHandle") ); if (pfnReleaseHandle) { pfnReleaseHandle(); } } // 示例:调用目标COM DLL中的函数 HRESULT CallTargetComFunction() { HMODULE hTargetDll = GetSharedTargetComDllHandle(); if (!hTargetDll) { return E_FAIL; } // 替换成你要调用的目标函数的签名和名称 typedef HRESULT(*PFN_YOUR_TARGET_FUNCTION)(/* 这里填函数参数 */); PFN_YOUR_TARGET_FUNCTION pfnTargetFunc = reinterpret_cast<PFN_YOUR_TARGET_FUNCTION>( GetProcAddress(hTargetDll, "YourTargetFunctionName") ); if (!pfnTargetFunc) { HRESULT hr = HRESULT_FROM_WIN32(GetLastError()); ReleaseSharedTargetComDllHandle(); return hr; } // 调用目标函数 HRESULT hr = pfnTargetFunc(/* 传递参数 */); // 释放句柄引用 ReleaseSharedTargetComDllHandle(); return hr; }
3. 额外的COM场景注意
如果你是通过标准CoCreateInstance激活目标COM类,其实COM运行时本身就会在进程级别维护已加载的模块,默认不会重复加载同一CLSID的DLL到同一个进程中(除非用了隔离COM比如Side-by-Side部署)。这种情况下,你完全可以直接在DLL2中调用CoCreateInstance,COM会自动复用已加载的模块,不需要手动共享句柄。
但如果你的场景是手动加载目标COM DLL并直接调用导出函数(不通过COM激活),那上面的手动共享句柄方案就是必需的了。
内容的提问来源于stack exchange,提问作者bhushanblaster
相关产品推荐
相关产品推荐

