You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无注册C++ ATL COM实现32/64位进程通信报错求助

32位客户端调用64位COM服务器:IDispatch调用仍需类型库?

问题背景

用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;
}

问题原因

  1. 双接口的自动化封送依赖类型库
    你的服务器接口IZebraServer是双接口(dual interface),同时支持早期绑定和IDispatch晚期绑定。ATL的IDispatchImpl实现处理跨进程(尤其是32/64位跨位)调用时,默认使用自动化封送,而自动化封送必须依赖类型库解析方法的DISPID、参数类型和返回值类型。

  2. 32位客户端无法加载64位EXE中的类型库
    客户端用#import指向64位服务器EXE,但32位进程无法加载64位模块中的资源(包括类型库),直接导致类型库加载失败。

  3. 跨进程封送必须有代理/存根
    即便使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 15:00:57