我试图在 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 位有符号整数),但是当我返回时,我得到一个错误。