无注册C++ ATL COM实现32/64位进程通信报错求助
问题背景
用C++编写32位客户端,调用64位COM服务器。为避免注册,通过运行对象表(ROT)发布类工厂;为规避封送问题,客户端采用IDispatch接口。但首次调用服务器SetValue方法时,出现Error loading type library/DLL错误,疑惑仅使用IDispatch为何还需要类型库或代理?
IDL代码
import "unknwn.idl"; [ object, uuid(C3498C72-22F8-477A-B8BF-A6BCE9DE0596), dual, nonextensible, helpstring("IZebraServer Interface"), pointer_default(unique) ] interface IZebraServer : IDispatch { [id(1), helpstring("method GetValue")] HRESULT GetValue([out, retval] int* pValue); [id(2), helpstring("method SetValue")] HRESULT SetValue(int value); }; [ uuid(1632C43C-7C08-451F-A341-A6486F0CE517), version(1.0), ] library ZebraServer { importlib("stdole32.tlb"); importlib("stdole2.tlb"); [ uuid(C2E745B1-6DA1-4DD4-AF10-5860182B66EF), helpstring("CZebraServer Class"), ] coclass CZebraServer { [default] interface IZebraServer; }; };
服务端代码
#include <SDKDDKVer.h> #include <atlbase.h> #include <atlcom.h> #include <atlctl.h> #include <comdef.h> #include <sstream> #include <iostream> using namespace ATL; #include "ZebraServer_i.h" inline void CheckHR(HRESULT hr, const char* msg) { if (SUCCEEDED(hr)) return; USES_CONVERSION; _com_error error(hr); std::ostringstream ss; ss << msg << "::" << W2A(error.ErrorMessage()); throw std::exception(ss.str().c_str()); } DWORD RegisterObjectInROT( IUnknown* pUnknown) { // create binding context object CComPtr<IBindCtx> pBindCtx; CheckHR(CreateBindCtx(0, &pBindCtx), "CreateBindCtx"); // Get a pointer to the ROT CComPtr<IRunningObjectTable> pRot; CheckHR(pBindCtx->GetRunningObjectTable(&pRot), "Getting ROT"); // Create a moniker for the object CComPtr<IMoniker> pMoniker; CheckHR(CreateClassMoniker(CLSID_CZebraServer, &pMoniker), "CreateClassMoniker"); // Register the object in the ROT with the specified display name DWORD dwRegisterCookie{}; CheckHR(pRot->Register( ROTFLAGS_REGISTRATIONKEEPSALIVE, pUnknown, pMoniker, &dwRegisterCookie), "Register object"); return dwRegisterCookie; } class ATL_NO_VTABLE CZebraServer : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CZebraServer, &CLSID_CZebraServer>, public IDispatchImpl<IZebraServer, &IID_IZebraServer, &LIBID_ZebraServer, /*wMajor =*/ 1, /*wMinor =*/ 0> { public: CZebraServer() {} DECLARE_NO_REGISTRY() BEGIN_COM_MAP(CZebraServer) COM_INTERFACE_ENTRY(IZebraServer) COM_INTERFACE_ENTRY(IDispatch) END_COM_MAP() // IZebraServer methods STDMETHOD(GetValue)(int* pVal) { *pVal = m_value; return S_OK; } STDMETHOD(SetValue)(int newVal) { m_value = newVal; return S_OK; } private: int m_value = 0; }; OBJECT_ENTRY_AUTO(__uuidof(CZebraServer), CZebraServer) class ATL_NO_VTABLE CZebraServerFactory : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CZebraServerFactory, &CLSID_CZebraServer>, public IClassFactory { public: CZebraServerFactory() {} DECLARE_NO_REGISTRY() BEGIN_COM_MAP(CZebraServerFactory) COM_INTERFACE_ENTRY(IClassFactory) END_COM_MAP() // IClassFactory methods STDMETHOD(CreateInstance)(IUnknown* pUnkOuter, REFIID riid, void** ppv) { return CComCreator<CComObject<CZebraServer>>::CreateInstance(pUnkOuter, riid, ppv); } STDMETHOD(LockServer)(BOOL fLock); }; OBJECT_ENTRY_AUTO(CLSID_CZebraServer, CZebraServerFactory) CComObject<CZebraServerFactory>* g_pFactoryObject; class CZebraServerModule : public ATL::CAtlExeModuleT<CZebraServerModule> { public: DECLARE_NO_REGISTRY() DECLARE_LIBID(LIBID_ZebraServer) HRESULT PreMessageLoop(int nShowCmd) { try { CheckHR(ATL::CAtlExeModuleT<CZebraServerModule>::PreMessageLoop(nShowCmd), "CAtlExeModuleT<CZebraServerModule>::PreMessageLoop"); std::cout << "Server started" << std::endl; // Create an instance of the factory class CheckHR(CComObject<CZebraServerFactory>::CreateInstance(&g_pFactoryObject), "Create Class Factory Instance"); // Query for the IUnknown interface DWORD dwRegisterCookie = RegisterObjectInROT(g_pFactoryObject); } catch (std::exception& ex) { std::cout << ex.what(); return S_FALSE; } return S_OK; } }; CZebraServerModule g_module; HRESULT CZebraServerFactory::LockServer(BOOL fLock) { if (fLock) g_module.Lock(); else g_module.Unlock(); return S_OK; } extern "C" int WINAPI _tWinMain( HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPTSTR /*lpCmdLine*/, int nShowCmd) { g_module.WinMain(nShowCmd); }
客户端代码
#include <iostream> #include <sstream> #include <atlbase.h> #include <atlcom.h> #import "C:\Sources\misc\Zebra\x64\Debug\AAA_ZebraServer.exe" using namespace std; using namespace ATL; inline void CheckHR(HRESULT hr, const char* msg) { if (SUCCEEDED(hr)) return; USES_CONVERSION; _com_error error(hr); std::ostringstream ss; ss << msg << "::" << W2A(error.ErrorMessage()); const auto& str = ss.str(); throw std::exception(str.c_str()); } class CComInitializer { public: CComInitializer() { CheckHR(CoInitialize(nullptr), "CoInitialize"); } ~CComInitializer() { CoUninitialize(); } }; int main() { try { CComInitializer coinit; // create binding context object CComPtr<IBindCtx> pBindCtx; CheckHR(CreateBindCtx(0, &pBindCtx), "CreateBindCtx"); // Get a pointer to the ROT CComPtr<IRunningObjectTable> pRot; CheckHR(pBindCtx->GetRunningObjectTable(&pRot), "Get ROT"); CComPtr<IMoniker> pMoniker; CheckHR(CreateClassMoniker(__uuidof(ZebraServer::CZebraServer), &pMoniker), "CreateClassMoniker"); CComPtr<IUnknown> pUnknown; CheckHR(pRot->GetObject(pMoniker, &pUnknown), "pROT->GetObject"); CComQIPtr<IClassFactory> pClassFactory(pUnknown); if (!pClassFactory) return 0; CComPtr<IDispatch> pDispatch; CheckHR(pClassFactory->CreateInstance( nullptr,IID_IDispatch,(void**)&pDispatch), "pClassFactory->CreateInstance"); CComDispatchDriver driver(pDispatch); CComVariant ret, arg(100); CheckHR(driver.Invoke1(L"SetValue", &ret, &arg), "SetValue"); CheckHR(driver.Invoke0(L"GetValue", &ret), "GetValue"); } catch(...) {} return 0; }
问题原因
双接口的自动化封送依赖类型库
你的服务器接口IZebraServer是双接口(dual interface),同时支持早期绑定和IDispatch晚期绑定。ATL的IDispatchImpl实现处理跨进程(尤其是32/64位跨位)调用时,默认使用自动化封送,而自动化封送必须依赖类型库解析方法的DISPID、参数类型和返回值类型。32位客户端无法加载64位EXE中的类型库
客户端用#import指向64位服务器EXE,但32位进程无法加载64位模块中的资源(包括类型库),直接导致类型库加载失败。跨进程封送必须有代理/存根
即便使用IDispatch的Invoke方法,跨进程调用仍需要COM代理/存根传递数据。对于自动化接口,COM会自动生成代理/存根,但前提是能找到对应的类型库。
解决方案
方案1:使用独立的类型库文件
- 编译IDL时生成独立的
.tlb文件(IDL编译工具通常会自动生成)。 - 修改客户端的
#import语句,指向.tlb文件而非64位EXE:#import "C:\Sources\misc\Zebra\ZebraServer.tlb" no_namespace named_guids - 确保
.tlb文件能被32位客户端访问(比如放在客户端目录或系统路径)。
方案2:实现纯IDispatch接口(避免双接口)
修改IDL,让接口不继承自IDispatch,而是让服务器类手动实现IDispatch接口,跳过ATL的IDispatchImpl对类型库的依赖。但这种方式需要手动处理GetIDsOfNames和Invoke方法的DISPID映射,实现成本较高。
方案3:手动指定封送器
为IDispatch接口使用标准的自动化封送器,确保客户端能找到stdole32.dll中的封送器,但这仍需要类型库支持,本质和方案1一致。
内容的提问来源于stack exchange,提问作者SparcU

