我正在为Outlook 2010开发一个IM提供程序。为了做到这一点,我必须实现一些IMessenger * COM接口,在启动时加载Outlook。我想使用C#和一个out-of-proc服务器,我遵循MSDN示例here。
这是我已经取得的成就:
现在,我基本上可以启动我的.EXE服务器,然后启动outlook并观察我的服务器的控制台窗口,同时它显示来自COM子系统的所有方法调用(我已经为每个方法添加了日志记录)。问题是,在某些时候(仅在.NET实现中!)我在Outlook日志中遇到了这样的错误:
CMsoIMProviderOC20::HrEnsureServiceData !failed! Line: 402 hr = 0x8000FFFF
我确切地知道在我的程序中发生了这种情况,但我无法做任何事情(我已经尝试了几天找到解决方案)。请记住,如果我使用C ++ / ATL实现它实际上是有效的,所以我想它必须与.NET Interop封送工作方式有关。
以下是详细信息:
基本上有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
另一个有趣的事情(至少对我而言)是如果我从Interface声明和我的类定义中完全删除了Enumerator,Outlook仍会调用我的接口,但使用另一种方法(PrimaryService for相反,但基本上有相同的错误。
老实说,我完全不知道如何处理这个错误?提前感谢您的任何帮助!
答案 0 :(得分:4)
此类问题的标准诊断是您的[ComVisible]界面布局错误。这导致在客户端代码调用时执行错误的方法,比如IDispatch :: GetTypeInfoCount()。这是IMessengerServices v表中的第4个方法指针,您的GetEnumerator()方法是接口v表中的第4个方法。当它获得一个奇怪的返回值时,Outlook会失败,它不知道如何处理。
我不知道你从哪里获得了“CCW代理”,它看起来并不像Tlbimp.exe那样生成它。缺少的重要属性是[InterfaceType(ComInterfaceType.InterfaceIsDual)]
。这就是告诉CLR它需要实现IDispatch的原因。所以你的界面缺少四种IDispatch方法。
除了修复接口声明之外,到目前为止,解决此问题的最佳方法是从类型库导入接口定义,以便永远不会出现不匹配。我没有在我的机器上安装它,你可以通过查看HKCR\CLSID\{2E50547B-A8AA-4F60-B57E-1F414711007B}
的注册表找到它。
答案 1 :(得分:3)
我现在找到了解决问题的方法。 Hans Passants的回答让我走向了正确的方向(谢谢!),然而,问题在于以下句子是真的:
Tlbimp.exe不像最初在IDL / TLB中定义的那样保留vtable中方法的顺序。相反,方法和属性按字母顺序重新排序!
因此,当您使用Visual Studio实现接口并引用类型库时,可能会破坏CCW创建的vtable。解决方案是将Tlbimp(由Visual Studio生成的工具生成interop程序集)生成的包装器复制到您自己的代码中,并将方法和属性重新排序为与类型库中的顺序相同。