4

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 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 では、Services プロパティを次のように実装します。

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 ではget__NewEnum(IUnknown **pUnkown)CMessengerServices クラスの関数が実行されないことです。代わりに、他の関数が呼び出されます。これはいい。

ただし、C# では、呼び出される MessengerServices クラスの最初のメンバーは GetEnumerator() メソッドです。そこにブレークポイントを設定すると、Outlook が起動を停止し、ログに 0x8000FFFF エラーを報告する前に実行される C# コード (私の制御下) の最後の行であることが明確にわかります。その後、.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

もう 1 つの興味深い点(少なくとも私にとって) は、Interface 宣言とクラス定義から Enumerator を完全に削除すると、Outlook は引き続き私のインターフェイスを呼び出しますが、代わりに別のメソッド (完全を期すために PrimaryService) を使用しますが、基本的には同じエラー。

正直なところ、このエラーをどうしたらいいのかまったくわかりません。どんな種類の助けにも感謝します!

4

2 に答える 2

4

このような問題の標準的な診断は、[ComVisible] インターフェイスのレイアウトが間違っていることです。これにより、クライアント コードが IDispatch::GetTypeInfoCount() などを呼び出したときに、間違ったメソッドが実行されます。これは IMessengerServices v-table の 4 番目のメソッド ポインターであり、GetEnumerator() メソッドはインターフェイスの v-table の 4 番目のメソッドです。処理方法がわからない奇妙な戻り値を受け取ると、Outlook は失敗します。

「CCW プロキシ」をどこから入手したのかわかりません。Tlbimp.exe によって生成されたようには見えません。欠落している重要な属性は[InterfaceType(ComInterfaceType.InterfaceIsDual)]. これが、IDispatch を実装する必要があることを CLR に伝えるものです。したがって、インターフェイスには 4 つの IDispatch メソッドがありません。

インターフェイス宣言を修正する以外に、この問題を解決する最良の方法は、タイプ ライブラリからインターフェイス定義をインポートして、不一致が発生しないようにすることです。私のマシンにはインストールされていませんHKCR\CLSID\{2E50547B-A8AA-4F60-B57E-1F414711007B}

于 2012-08-08T11:20:08.767 に答える
3

私は今問題の解決策を見つけました。Hans Passants の回答は正しい方向に私を導きました (ありがとう!)、しかし、問題は次の文が真実であるということでした:

Tlbimp.exe は、最初に IDL/TLB で定義されたように、vtable 内のメソッドの順序を保持しません。代わりに、メソッドとプロパティはアルファベット順に再ソートされます!

そのため、インターフェイスを実装し、Visual Studio を使用してタイプ ライブラリを参照すると、CCW によって作成された vtable が台無しになる可能性があります。解決策は、Tlbimp (相互運用アセンブリを生成する Visual Studio で使用されるツール) によって生成されたラッパーを独自のコードにコピーし、メソッドとプロパティの順序を変更して、タイプ ライブラリ内の順序と同じにすることです。

于 2012-08-09T08:08:03.453 に答える