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 属性添加到我的 Main 过程中。这并不能解决问题,但是除非您绝对确定不需要它,否则您确实应该将 STAThread 与 COM 一起使用。请参阅下面的 Hans Passant 的回答。

4

2 回答 2

31

此异常可能是 DLL Hell 问题。但最简单的解释是您的代码段中缺少的内容。您的 Main() 方法缺少 [STAThread] 属性。

当您在代码中使用 COM 对象时,这是一个重要的属性。它们中的大多数都不是线程安全的,并且它们需要一个线程,该线程是不支持线程的代码的好客之家。该属性强制线程的状态,您可以使用 Thread.SetApartmentState() 显式设置该状态。自 Windows 启动应用程序的主线程以来,您无法为应用程序的主线程执行此操作,因此该属性用于配置它。

如果您省略它,那么您的主线程将加入 MTA,即多线程单元。然后 COM 被迫创建一个新线程,为组件提供一个安全的家。这需要将所有调用从主线程编组到该辅助线程。当 COM 找不到方法时会引发 E_NOINTERFACE 错误,它需要一个知道如何序列化方法参数的助手。这是 COM 开发人员需要注意的事情,他没有这样做。邋遢但并不罕见。

STA 线程的一个要求是它还泵送消息循环。您从 Application.Run() 在 Winforms 或 WPF 应用程序中获得的那种。您的代码中没有一个。您可能会侥幸逃脱,因为您实际上并没有从工作线程进行任何调用。但是 COM 组件倾向于依赖消息循环来供自己使用。你会注意到它行为不端,而不是引发事件或死锁。

所以首先通过应用属性来解决这个问题:

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

这将解决这个异常。如果您有描述的事件引发或死锁问题,那么您需要更改您的应用程序类型。Winforms 通常很容易上手。

我不能以其他方式对嘲笑失败采取行动。COM 涉及重要的部署细节,必须编写注册表项以允许 COM 发现组件。你必须得到正确的指导,界面必须完全匹配。Regasm.exe 是注册 [ComVisible] 的 .NET 组件所必需的。如果您尝试模拟现有的 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 回答