.NET 6 IDispatch 客户端实现崩溃

Posted

技术标签:

【中文标题】.NET 6 IDispatch 客户端实现崩溃【英文标题】:.NET 6 IDispatch client implementation crash 【发布时间】:2022-01-14 19:44:41 【问题描述】:

.NET 6 无法调用自己的 IDispatch 对象(如果已编组)。

复制:

if (Array.IndexOf(Environment.GetCommandLineArgs(), "/s")>=0) //Server

    Thread t = new Thread(new ThreadStart(() =>
    
        Hello h = new Hello();
        new RunningObjectTable().Register("HelloTest", h);
        Thread.Sleep(1000 * 3600);

    ));
    t.SetApartmentState(ApartmentState.MTA);
    t.Start();
    Thread.Sleep(1000 * 3600);

else //Client

    object o = new RunningObjectTable().Get("HelloTest");
    IHello h = o as IHello;
    int f = h.Foo();
    Console.WriteLine(h);


[ComImport]
[Guid("00020400-0000-0000-C000-000000000046")] //IID_IDispatch
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[TypeLibType(TypeLibTypeFlags.FDispatchable)]
public interface IHello

    [MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall)]
    [DispId(1)]
    public int Foo();


public class Hello: IHello

    public int Foo()
    
        Debug.WriteLine("Hello from server");
        return 19;
    

使用 /s 运行程序,即对象服务器。然后运行另一个副本成为客户端。

客户端中的方法调用行因“未找到成员”而崩溃 - HRESULT 0x80020003,DISP_E_MEMBERNOTFOUND。通常这意味着一个虚假的 DISPID,但差异可能来自哪里?

该代码中的一个小违规行为是该接口使用 IDispatch 的 IID 进行修饰。按照 COM 的规定,使用自定义 IID,它甚至不会解组为分发接口(@98​​7654323@ 行返回 null);在内部,使用所述自定义 IID 跨进程调用 QueryInterface,dispinterface 未在 HKCR\Interfaces 下注册,因此进程间 COM 机器不知道如何编组它。至少这是我的理论。

如果服务器是本机 (C++) 服务器,则类似的逻辑可以正常工作。如果针对 .NET 框架 4.72 重新编译相同的 C# 部分,它甚至没有达到那么远,o as IHello; 行返回 null。


RunningObjectTable 是一个围绕 ROT 的辅助类。为了完整起见,这里:

internal class RunningObjectTable

    #region API
    [DllImport("ole32.dll")]
    private static extern int CreateItemMoniker([MarshalAs(UnmanagedType.LPWStr)] string
               lpszDelim, [MarshalAs(UnmanagedType.LPWStr)] string lpszItem,
               out IMoniker ppmk);

    [DllImport("ole32.dll")]
    private static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable prot);
    #endregion

    private IRunningObjectTable m_rot;

    public RunningObjectTable()
    
        GetRunningObjectTable(0, out m_rot);
    

    private IMoniker CreateItemMoniker(string s)
    
        IMoniker mon;
        CreateItemMoniker("", s, out mon);
        return mon;
    

    public int Register(string ItemName, object o)
    
        return m_rot.Register(0, o, CreateItemMoniker(ItemName));
    

    public void Unregister(int ROTCookie)
    
        m_rot.Revoke(ROTCookie);
    

    public object Get(string ItemName)
    
        object o;
        m_rot.GetObject(CreateItemMoniker(ItemName), out o);
        return o;
    

能否请有 .NET Core 内部访问权限的人告诉我发生了什么?

【问题讨论】:

我在 .NET 6 和 .NET Framework 之间有完全相同的行为,即 DISP_E_MEMBERNOTFOUND。如果您不注册类型库,我看不出这将如何工作。如果你用这个替换你的调用代码:dynamic o = new RunningObjectTable().Get("HelloTest");Console.WriteLine(o.Foo); 那么它在 .NET 6 和 .NET Framework 中都可以工作。 原来可以:) 你的回答与你的问题无关 我的问题是跨进程方法调用期间 DISP_E_MEMBERNOTFOUND 的异常。解决了。另外,我已经弄清楚了错误的 DISPID 是从哪里来的。 【参考方案1】:

这就是正在发生的事情。 .NET 对象支持两个调度接口——一个对应IHello,另一个对应Hello 本身,后者被视为默认接口。我已经通过向Hello 添加一个额外的方法Bar() 并检查本机C++ 客户端中的类型来确认:

IUnknownPtr pUnk;
ROTGet(L"HelloTest", &pUnk);
IDispatchPtr pDisp(pUnk);
LPOLESTR Name = (LPOLESTR)L"Bar";
DISPID dispid;
hr = pDisp->GetIDsOfNames(IID_NULL, &Name, 1, 0, &dispid);

我得到了 S_OK 和一个有效的(如果是假的)DISPID。然后我以同样的方式为Foo 请求了一个 DISPID,并得到了另一个虚假的 DISPID。

我找到了两种解决方法。


第一,告诉框架它不应该为Hello隐式生成一个调度接口。用[ClassInterface(ClassInterfaceType.None)] 注释类,它不会。对Foo 的 DISPID 的显式检查证实了这一点。客户端中未编组的dispinterface对应IHello,方法按预期工作。


另一种方法,给IHello 一个身份(一个IID)并使其可编组。如果我在该界面上添加一个自定义 GUID,并将具有该 GUID 的密钥添加到 HKCR\Interfaces,提供等于 00020420-0000-0000-C000-000000000046ProxyStubClsid32(即 PSDispatch,IDispatch 的代理/存根 CLSID),该片段也可以工作.

告诉 COM 一个 IID 对应于一个调度接口并且应该被封送的另一种方法是通过实现IStdMarshalInfo。这个我没试过。


另一个注意事项:Hello 类必须被声明为 public 才能工作。如果不是,由于某种原因,as IHello 返回 null。

【讨论】:

以上是关于.NET 6 IDispatch 客户端实现崩溃的主要内容,如果未能解决你的问题,请参考以下文章

IDispatch 从 COM 服务器返回 NULL 到客户端(客户端和服务器都在 C++ 中)

如何在没有 IDispatch 的情况下创建 VB.NET COM 可见接口?

LPDISPATCH是啥数据类型,他和IDispatch有啥关系

枚举机器上所有 IDispatch 实现对象

IDispatch接口介绍

响应所有属性的 IDispatch 对象?