簡體   English   中英

通過 IDispatch 將接口數組從 C# COM 服務器傳遞到 C++

[英]Pass Interface Array from C# COM Server to C++ Via IDispatch

我正在嘗試開發一個 C# COM 服務器,客戶端只能通過 IDispatch 訪問該服務器。 我的 COM 對象的一些方法返回到其他 COM 對象的接口數組。

當我嘗試使用早期綁定來執行此操作時,例如通過此答案中提供的代碼,它可以正常工作。 但是,一旦我嘗試裝飾我的類型以通過 IDispatch 使用后期綁定,請求就會失敗,並顯示 0x80020005:類型不匹配。 其他非接口類型可以通過 IDispatch 毫無問題地返回,只是 COM 接口無法正確傳輸。

給定以下 C++ 客戶端

CoInitialize(NULL);
IMyRootClassPtr ptr(__uuidof(MyRootClass));

try
{
    auto entities = ptr->GetEntities();
}
catch (_com_error &err)
{
    wprintf(L"The server throws the error: %s\n", err.ErrorMessage());

    wprintf(L"Description: %s\n", (PCWSTR)err.Description());
}

以下代碼適用於早期綁定

C#:

[ComVisible(true)]
public interface IMyRootClass
{
    IEmailEntity[] GetEntities();
}

[ComVisible(true)]
public class MyRootClass : IMyRootClass // some class to start with
{
    public IEmailEntity[] GetEntities()
    {
        List<IEmailEntity> list = new List<IEmailEntity>();
        for (int i = 0; i < 10; i++)
        {
            EmailEntity entity = new EmailEntity();
            entity.Body = "hello world " + i;
            list.Add(entity);
        }
        return list.ToArray();
    }
}

[ComVisible(true)]
public interface IEmailEntity
{
    string Body { get; set; }
}

public class EmailEntity : IEmailEntity
{
    public string Body { get; set; }
}

通過進行以下更改

  • [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]添加到IMyRootClass
  • [ClassInterface(ClassInterfaceType.None)]添加到MyRootClass

我認為這現在應該通過 IDispatch 工作,但是我得到了上面提到的類型不匹配錯誤。 我花了一個下午的時間來調試錯誤的確切來源。 對 .NET 框架的 Dispatch 調用似乎成功,返回 VT_ARRAY 類型的VARIANT VT_ARRAY | VT_DISPATCH VT_ARRAY | VT_DISPATCH (2009),這是預期的,但最終結果的驗證似乎落入了

main
_com_dispatch_method
_com_invoke_helper
VariantChangeType
VariantChangeTypeEx

我已經查看了它在 oleaut32.dll 中掉下來的位置的反匯編,在這個階段我只是得出結論,這一定是 Windows 中的錯誤。 有沒有人可能有其他建議?

請記住, IDispatch / 后期綁定是為特殊客戶端創建的(例如:VB/VBA/VBScript/JScript),從純 C/C++ 客戶端使用它總是很痛苦。

使用原始定義,這里是IMyRootClass的定義方式(您可以使用 RegAsm 生成的 .tlb 文件上的 Windows SDK 中的OleView 工具閱讀):

interface IMyRootClass : IDispatch {
    [id(0x60020000)]
    HRESULT GetEntities([out, retval] SAFEARRAY(IEmailEntity*)* pRetVal);
};

這將在#import之后結束,在 C/C++ header 級別:

IMyRootClass : IDispatch
{
    //
    // Wrapper methods for error-handling
    //

    SAFEARRAY * GetEntities ( );

    //
    // Raw methods provided by interface
    //

      virtual HRESULT __stdcall raw_GetEntities (
        /*[out,retval]*/ SAFEARRAY * * pRetVal ) = 0;
};

其中GetEntities實際上只是圍繞IUnknown / early-binding 接口的一個小包裝代碼:

inline SAFEARRAY * IMyRootClass::GetEntities ( ) {
    SAFEARRAY * _result = 0;
    HRESULT _hr = raw_GetEntities(&_result);
    if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
    return _result;
}

這就是為什么“雙重”界面很好的原因(不知道為什么你只想要IDispatch ),因為它們允許兩個世界輕松訪問。

現在,如果您更改問題中的定義,則不再定義IMyRootClass COM 接口。 相反,您只會獲得 IDL 級別的dispinterface

dispinterface IMyRootClass {
    properties:
    methods:
        [id(0x60020000)]
        SAFEARRAY(IEmailEntity*) GetEntities();
};

這將在#import之后結束,在 C/C++ header 級別:

IMyRootClass : IDispatch
{
    //
    // Wrapper methods for error-handling
    //

    // Methods:
    SAFEARRAY * GetEntities ( );
};

其中GetEntities實際上是一個完全不同的包裝代碼:

inline SAFEARRAY * IMyRootClass::GetEntities ( ) {
    SAFEARRAY * _result = 0;
    _com_dispatch_method(this, 0x60020000, DISPATCH_METHOD, VT_ARRAY|VT_DISPATCH, (void*)&_result, NULL);
    return _result;
}

正如您在此處看到的,由於我們使用的是IDispatch ,因此一切都或多或少接近於 VARIANT 類型(它是同時為這些客戶端發明的),按原樣使用或與包裝器一起使用。

這就是您看到預期返回類型為VT_ARRAY|VT_DISPATCH的原因。 也可以使用VT_ARRAY|VT_UNKNOWNVT_ARRAY|VT_VARIANT或簡單的VT_VARIANT (最終包裝器類型),但沒有辦法說VT_ARRAY of IEmailEntity*

所以,這個問題至少有兩種解決方案:

1 - 你可以這樣定義你的界面:

[ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IMyRootClass
{
    object[] GetEntities();
}

[ComVisible(true), ClassInterface(ClassInterfaceType.None)]
public class MyRootClass : IMyRootClass
{
    public object[] GetEntities()
    {
        ...
        // the Cast forces the creation of an array of object => VT_ARRAY | VT_DISPATCH
        return list.Cast<object>().ToArray();
    }
}   

2 - 或者您可以像這樣“手動”使用IDispatch接口(不要使用包裝器):

IMyRootClassPtr ptr(__uuidof(MyRootClass));
CComVariant result;
DISPPARAMS p = {};
ptr->Invoke(0x60020000, IID_NULL, 0, DISPATCH_METHOD, &p, &result, nullptr, nullptr);

在這種情況下,結果將是VT_ARRAY | VT_UNKNOWN VT_ARRAY | VT_UNKNOWN (與第一種情況一樣),這就是包裝器拋出異常的原因。 comdef.h 的包裝器在其自動化類型支持方面比 VB 等客戶端更受限制。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM