【问题标题】:COM Interop Catastrophic Failure (0x8000FFFF) when marshaling IDispatch封送 IDispatch 时出现 COM 互操作灾难性故障 (0x8000FFFF)
【发布时间】:2012-08-05 05:18:30
【问题描述】:

我正在为 Outlook 2010 开发一个 IM 提供程序。为此,我必须实现一些 Outlook 在启动时加载的 IMessenger* COM 接口。我想使用 C# 和我遵循 MSDN 示例 here 的进程外服务器来执行此操作。

这是我已经实现的:

  • 正确注册所有内容(CLSID、类型库等)并配置 Outlook,以便它在启动时尝试加载我的服务器
  • 将 COM 服务器实现为原生 ATL/C++ 服务器并确保其正常工作
  • 在 C#/.NET 中实现基本接口并确保已加载

现在,我基本上可以启动我的 .EXE 服务器,然后启动 Outlook 并观察我服务器的控制台窗口,同时它会显示来自 COM 子系统的所有方法调用(我已经为每个方法添加了日志记录)。问题是,在某些时候(仅在 .NET 实现中!)我在 Outlook 日志中遇到如下错误:

CMsoIMProviderOC20::HrEnsureServiceData !failed!  Line: 402  hr = 0x8000FFFF

我确切地知道这在我的程序中发生的位置,但我对此无能为力(我已经尝试为此找到解决方案好几天了)。请记住,如果我使用 C++/ATL 实现它,它实际上可以工作,所以我想它一定与 .NET 互操作封送处理的工作方式有关。

以下是详细信息:

与这个问题相关的接口基本上有3个:IMessenger、IMessengerServices和IMessengerService:

[
    uuid(D50C3186-0F89-48f8-B204-3604629DEE10), // IID_IMessenger
    helpstring("Messenger Interface"),
    helpcontext(0x0000),
    dual,
    oleautomation
]
interface IMessenger : IDispatch
{
    ...
    [id(DISPID_MUAM_SERVICES), propget, helpstring("Returns services list."), helpcontext(0x0000)]
    HRESULT Services([out, retval] IDispatch ** ppdispServices);
    ...
}

而我知道这个 Services 属性实际上应该返回这个接口:

[
 uuid(2E50547B-A8AA-4f60-B57E-1F414711007B), // IID_IMessengerServices
 helpstring("Messenger Services Interface"),
 helpcontext(0x0000),
 dual,
 oleautomation
]
interface IMessengerServices : IDispatch
{
    ...
    [id(DISPID_NEWENUM), propget, restricted, helpstring("Enumerates the services."), helpcontext(0x0000)]
    HRESULT _NewEnum([out, retval] IUnknown **ppUnknown);
    ...
}

这个接口又应该返回 IMesengerService 接口。但是,这对于这个问题的目的并不重要。

在 C++/ATL 中,我像这样实现属性服务:

STDMETHOD(get_Services)(IDispatch ** ppdispServices)
{
    (*ppdispServices) = (IDispatch*)new CComObject<CMessengerServices>();
    (*ppdispServices)->AddRef();
    return S_OK;
}

这里是 C# 实现:

public object Services
{
    get { return new MessengerServices(); }
}

直到这里,两种实现都一切正常!现在问题开始了……

首先奇怪的是,在 C++/ATL 中,我的 CMessengerServices 类的函数 get__NewEnum(IUnknown **pUnkown) 永远不会被执行。而是调用其他函数。 这很好。

然而,在 C# 中,调用的 MessengerServices 类的第一个成员是 GetEnumerator() 方法。我可以在那里设置一个断点,并清楚地看到它是 C# 代码中的最后一行(在我的控制下),它在 Outlook 停止启动并在其日志中报告 0x8000FFFF 错误之前执行。之后,我的 .EXE 服务器中没有其他代码行被调用。

MessengerServices 类实现有最重要的部分:

[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true), Guid("DF394E2C-38E2-4B70-B707-9749A4F857B0")]
public class MessengerServices : IMessengerServices
{
    IEnumerator IMessengerServices.GetEnumerator()
    {
        return messengerServices.GetEnumerator();
    }

    private readonly ArrayList messengerServices;

    public MessengerServices()
    {
        messengerServices = new ArrayList { new MessengerService() };
    }
    ...
}

这里是 tlbimp.exe 生成的 CCW 代理:

[ComVisible(true), Guid("2E50547B-A8AA-4F60-B57E-1F414711007B")]
public interface IMessengerServices : IEnumerable
{
    [TypeLibFunc(TypeLibFuncFlags.FRestricted)]
    [DispId(-4)]
    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
    [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalType = "System.Runtime.InteropServices.CustomMarshalers.EnumeratorToEnumVariantMarshaler, CustomMarshalers, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
    new IEnumerator GetEnumerator();
    ...
}

如果可能有帮助,最后一个提示:当我在 GetEnumerator() 实现中设置断点并按 F10 进行单步执行时,这是 Visual Studio 输出窗口中的最后一行:

跳过非用户代码'System.Runtime.InteropServices.CustomMarshalers.EnumeratorToEnumVariantMarshaler.MarshalManagedToNative

另一个有趣的事情(至少对我而言)是,如果我从接口声明和类定义中完全删除 Enumerator 事物,Outlook 仍会调用我的接口,但使用另一种方法(PrimaryService for为了完整起见),但基本上有相同的错误。

说实话,我完全不知道如何处理这个错误?提前感谢您的任何帮助!

【问题讨论】:

    标签: c# .net com exception-handling marshalling


    【解决方案1】:

    此类故障的标准诊断是您的 [ComVisible] 界面布局错误。当客户端代码调用 IDispatch::GetTypeInfoCount() 时,这会导致执行错误的方法。这是 IMessengerServices v-table 中的第 4 个方法指针,您的 GetEnumerator() 方法是您接口的 v-table 中的第 4 个方法。当 Outlook 收到一个不知道如何处理的奇怪返回值时,它就会崩溃。

    我不知道你从哪里得到“CCW 代理”,它看起来不像是 Tlbimp.exe 生成的。缺少的重要属性是[InterfaceType(ComInterfaceType.InterfaceIsDual)]。这就是告诉 CLR 它需要实现 IDispatch 的原因。所以你的界面缺少四个 IDispatch 方法。

    除了修复接口声明,目前解决这个问题的最好方法是从类型库中导入接口定义,这样就不会出现不匹配的情况。我的机器上没有安装它,你可以通过在HKCR\CLSID\{2E50547B-A8AA-4F60-B57E-1F414711007B} 的注册表中找到它。

    【讨论】:

    • 感谢您的帮助。不幸的是,这并没有解决问题。代理接口缺少 InterfaceIsDual 属性是对的,但这只是因为我测试了所有内容......我已经从我的 C# 项目中删除了所有代码,注册了“Office 2007 Communicator API”附带的 tlb,并从 Visual 中引用了它工作室 - 同样的错误。我还实现了 ICustomQueryInterface 并进行了一些登录,因此我知道 COM 在我的对象上调用 QueryInterface,我可以看到 IDispatch 实际上是由 CCW 实现的。
    • 对此进行扩展:我将 tlbimp.exe 生成的接口复制到我的程序集中,然后使用 tlbexp.exe 再次将其导出。然后,我使用 regtlibv12 注册了新的 .tlb,并比较了 OLE-COM 对象查看器中的“原始”和“新”接口。它们在 _NewEnum(..) 和 GetEnumerator() 方面有所不同。这可能是问题吗?
    • 没关系,我找到了答案并将其发布在下面(现在无法标记)。非常感谢您提供 vtable 顺序的提示!
    【解决方案2】:

    我现在找到了解决问题的方法。 Hans Passants 的回答将我引向了正确的方向(谢谢!),但是,问题在于以下句子是正确的:

    Tlbimp.exe 不会像最初在 IDL/TLB 中定义的那样保留 vtable 中方法的顺序。相反,方法和属性会按字母顺序重新排序!

    因此,当您使用 Visual Studio 实现接口并引用类型库时,CCW 创建的 vtable 很可能会被搞砸。解决方案是将 Tlbimp(Visual Studio 生成互操作程序集使用的工具)生成的包装器复制到您自己的代码中,并将方法和属性重新排序以与类型库中的顺序相同。

    【讨论】:

      猜你喜欢
      • 2014-07-18
      • 2016-07-06
      • 1970-01-01
      • 2010-12-04
      • 2010-10-14
      • 2016-03-21
      • 2023-03-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多