1

我试图在 C# 中创建一个 COM 服务器来公开一个 OLE 自动化对象。我想手动实现 IDispatch 接口,因为我需要对成员进行一些动态查找。当我调用我的自定义GetIDsOfNames时,C# 方法运行,然后客户端错误E_POINTER。但让我们从头开始

经过数小时的阅读和大量的反复试验,我设法拼凑出一些我认为应该可行的东西。我有一个非常简单的界面

[Guid("7B95C174-9320-4400-88AB-6960B019CEDE")]
[ComVisible(true)]
public interface ISimpleObject {
    EchoObject testNest();
}

这可以返回另一个 COM 对象。第一个 COM 对象使用默认的 C# IDispatch 实现并且工作正常。这EchoObject是具有自定义 IDispatch 实现的那个,因为它不是 C# SDK 的一部分,让我们看看我是如何定义它的

[ComImport]
[Guid("00020400-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IDispatch {
    int GetTypeInfoCount(out uint pctinfo);
    int GetTypeInfo(uint iTInfo, int lcid, out IntPtr info);

    int GetIDsOfNames(
        ref Guid iid,
        [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 2)] string[] names,
        int cNames,
        int lcid,
        [Out][MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.I4, SizeParamIndex = 2)] int[] rgDispId);

    int Invoke(
        int dispId,
        ref Guid riid,
        int lcid,
        System.Runtime.InteropServices.ComTypes.INVOKEKIND wFlags,
        ref System.Runtime.InteropServices.ComTypes.DISPPARAMS pDispParams,
        [Out][MarshalAs(UnmanagedType.LPArray)] object[] result,
        IntPtr pExcepInfo,
        IntPtr puArgErr);
}

我在 IEchoObject 接口的定义中使用了定义的 IDispatch 接口。

[Guid("D3E9B530-DFD5-4B54-88B9-4FBC8B0926E2")]
[ComVisible(true)]
public interface IEchoObject: IDispatch {
    string testint();
}

然后,此接口的实现必须实现 ICustomQueryInterface 以允许 IDispatch 强制转换发生。

[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IDispatch))]
[Guid("F625A833-3B4E-455D-90A9-89B1953147CD"), ComVisible(true)]
public class EchoObject : ReferenceCountedObject, IEchoObject, ICustomQueryInterface {
    public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv) {
        Console.WriteLine("Are we a: " + iid + "?");
        ppv = IntPtr.Zero;
        if (typeof(IDispatch).GUID == iid)
        {
            Console.WriteLine("Yes");
            ppv = Marshal.GetComInterfaceForObject(this, typeof(IDispatch), CustomQueryInterfaceMode.Ignore);
            return CustomQueryInterfaceResult.Handled;
        }
        return CustomQueryInterfaceResult.NotHandled;
    }

    public int GetIDsOfNames(
        ref Guid iid,
        [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 2)] string[] names,
        int cNames,
        int lcid,
        [Out][MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.I4, SizeParamIndex = 2)] int[] rgDispId
    ) {
        bool unknown = false;

        if(cNames == 1) {
            Console.WriteLine("Getting id of " + names[0]);
            if (names[0] == "testman") {
                Console.WriteLine("Member found");
                rgDispId[0] = 1;
            } else {
                rgDispId[0] = -1;
            }
        }

        return unknown ? HRESULT.DISP_E_UNKNOWNNAME : HRESULT.S_OK;
    }
}

我当然已经实现了 IDispatch 的其他方法,但我不相信它们对我遇到的问题很重要。请注意,GetIDsOfNames 打印到控制台。

我可以创建初始SimpleObject就好了。我也可以调用testNest它的方法并获取EchoObject实例。但是,在查找该testman方法的 ID 时EchoObject,客户端从 win32 获取E_POINTER(无效指针)而不是结果。

我已经使用 Excel、VBScript 和自定义 C++ COM 客户端对其进行了测试。都得到一个Invalid Pointer错误,C++ 客户端显示它来自GetIDsOfNames. 奇怪的是,C# 方法运行,它愉快地将行打印到控制台,返回,然后 C++ COM 客户端得到一个错误。

如果禁用我所有的自定义 IDispatch 而是使用默认的 C# 实现(调用 testint 方法而不是 testman)它可以正常工作。我怀疑我的编组可能在某个地方出错,但我已经盯着它看了好几个小时,我看不出问题所在。

编辑:我做了另一个测试。如果我将 GetIDsOfNames 的返回类型从 int 更改为 void 它可以工作。那里发生了什么事?根据MSDN,返回类型是 HRESULT(一个 32 位有符号整数),但是当我返回时,我得到一个错误。

4

0 回答 0