14

dll にこのインターフェイスがあります (このコードはメタデータから Visual Studio に表示されます)。

#region Assembly XCapture.dll, v2.0.50727
// d:\svn\dashboard\trunk\Source\MockDiagnosticsServer\lib\XCapture.dll
#endregion

using System;
using System.Runtime.InteropServices;

namespace XCapture
{
    [TypeLibType(4160)]
    [Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")]
    public interface IDiagnostics
    {
        [DispId(1)]
        void GetStatusInfo(int index, ref object data);
    }
}

だから私はそのようなクラスでCOMサーバーを作成しました:

[ComVisible(true)]
[Guid(SimpleDiagnosticsMock.CLSID)]
[ComDefaultInterface(typeof(IDiagnostics))]
[ClassInterface(ClassInterfaceType.None)]
public class SimpleDiagnosticsMock : ReferenceCountedObject, IDiagnostics
{
    public const string CLSID = "281C897B-A81F-4C61-8472-79B61B99A6BC";

    // These routines perform the additional COM registration needed by 
    // the service. ---- stripped from example

    void IDiagnostics.GetStatusInfo(int index, ref object data)
    {
        Log.Info("GetStatusInfo called with index={0}, data={1}", index, data);

        data = index.ToString();
    }
}

サーバーは正常に動作しているようで、VBScript からオブジェクトを使用できます。しかし、別の C# クライアントから使用しようとしました。

    [STAThread]
    static void Main(string[] args)
    {
        Guid mockClsId = new Guid("281C897B-A81F-4C61-8472-79B61B99A6BC");
        Type mockType = Type.GetTypeFromCLSID(mockClsId, true);
        IDiagnostics mock = (IDiagnostics)Activator.CreateInstance(mockType);

        //var diag = mock as IDiagnostics;

        object s = null;
        mock.GetStatusInfo(3, ref s);

        Console.WriteLine(s);
        Console.ReadKey();
    }

そして、それは失敗します

タイプ 'System.__ComObject' の COM オブジェクトをインターフェイス タイプ 'XCapture.IDiagnostics' にキャストできません。IID '{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}' を持つインターフェイスの COM コンポーネントでの QueryInterface 呼び出しが次のエラーのために失敗したため、この操作は失敗しました: サポートされているそのようなインターフェイスはありません (HRESULT からの例外: 0x80004002 (E_NOINTERFACE)) .

私は何を間違っていますか?

私も InvokeMember を使用しようとしましたが、ref-returned dataパラメータを取得できなかったことを除いて、それはちょっとうまくいきました。

編集:メイン プロシージャに STAThread 属性を追加しました。これで問題が解決するわけではありませんが、STAThreadが不要であるという確信がない限り、COM で STAThread を使用する必要があります。以下のHans Passantの回答を参照してください。

4

2 に答える 2

31

この例外は、DLL 地獄の問題である可能性があります。しかし、最も簡単な説明は、スニペットに何が欠けているかということです。Main() メソッドに [STAThread] 属性がありません。

これは、コードで COM オブジェクトを使用するときに重要な属性です。それらのほとんどはスレッドセーフではなく、スレッド化をサポートできないコードのためのもてなしのホームであるスレッドを必要とします。この属性は、Thread.SetApartmentState() で明示的に設定できるスレッドの状態を強制します。Windows がアプリを起動して以来、アプリのメイン スレッドに対してこれを行うことはできないため、属性を使用して構成します。

これを省略すると、メイン スレッドがマルチスレッド アパートメントである MTA に参加します。次に、COM は新しいスレッドを作成して、コンポーネントに安全なホームを提供するよう強制されます。これには、すべての呼び出しをメイン スレッドからそのヘルパー スレッドにマーシャリングする必要があります。E_NOINTERFACE エラーは、COM がそれを行う方法を見つけられない場合に発生します。メソッドの引数をシリアル化する方法を知っているヘルパーが必要です。これは、COM 開発者が対処する必要があることですが、彼はそれを行いませんでした。ずさんですが、珍しいことではありません。

STA スレッドの要件は、メッセージ ループもポンピングすることです。Application.Run() から Winforms または WPF アプリで取得する種類。コードにそれがありません。実際にはワーカー スレッドから呼び出しを行わないため、問題を回避できる可能性があります。ただし、COM コンポーネントは、メッセージ ループを利用できるかどうかに依存する傾向があります。これは、イベントやデッドロックを発生させるのではなく、動作がおかしいことに気付くでしょう。

したがって、最初に属性を適用してこれを修正し始めます。

[STAThread]
static void Main(string[] args)
{
    // etc..
}

この例外を解決します。上記のイベント発生またはデッドロックの問題がある場合は、アプリケーションの種類を変更する必要があります。通常、Winforms は簡単に使用できます。

そうでなければ、モックの失敗を突き刺すことはできません。COM には重要な展開の詳細が含まれます。COM がコンポーネントを検出できるようにするには、レジストリ キーを書き込む必要があります。GUID を正しく取得する必要があり、インターフェイスは完全に一致する必要があります。[ComVisible] の .NET コンポーネントを登録するには Regasm.exe が必要です。既存の COM コンポーネントをモックしようとして、それが正しく行われた場合、実際のコンポーネントの登録が破棄されます。追求する価値があるかどうかはよくわかりません;) そして、[ComVisible] アセンブリへの参照を追加する際に重大な問題が発生します。IDE は、.NET プログラムが COM を介して .NET アセンブリを使用することを拒否します。遅延バインディングだけがマシンをだますことができます。COM 例外から判断すると、まだモックに近づいていません。COM コンポーネントをそのまま、実際のテストとしても使用するのが最善です。

于 2013-06-05T15:02:25.300 に答える
5

したがって、問題は、IDiagnostics インターフェイスを備えた私の DLL が TLB から生成され、その TLB が登録されなかったことです。

DLL は TLB からインポートされたため、RegAsm.exe はライブラリの登録を拒否します。そこで、regtlibv12.exeツールを使用して TLB 自体を登録しました。

C:\Windows\Microsoft.NET\Framework\v4.0.30319\regtlibv12.exe "$(ProjectDir)\lib\Diagnostics.tlb"

その後、すべてが魔法のように機能し始めました。

regtlibv12 はサポートされているツールではないため、これを適切に行う方法がまだわかりません。

于 2013-06-06T10:43:41.623 に答える