为什么我不能将COM对象转换为它在c#中实现的接口?

本文关键字:实现 接口 不能 COM 转换 对象 为什么 | 更新日期: 2023-09-27 18:16:56

我在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 '类型的COM对象。__ComObject'到接口"XCapture.IDiagnostics"类型。操作失败,原因是在COM组件上调用具有IID的接口的QueryInterface'{xxxxxxxx - xxxx - xxxx - xxxx - xxxx - xxxxxxxxxxxx}'失败,原因如下error: No such interface supported (Exception from HRESULT: 0x80004002)(E_NOINTERFACE)) .

我做错了什么?

我也尝试过使用InvokeMember,除了我无法获得ref返回的data参数之外,这有点工作。

EDIT:添加STAThread属性到我的Main过程。这并不能解决问题,但是你真的应该在COM中使用STAThread,除非你绝对确定不需要它。

为什么我不能将COM对象转换为它在c#中实现的接口?

此异常可能是DLL Hell问题。但最简单的解释是,为什么在代码片段中缺少。你的Main()方法缺少[STAThread]属性。

当你在代码中使用COM对象时,这是一个很重要的属性。它们中的大多数都不是线程安全的,它们需要一个适合不支持线程的代码的线程。该属性强制线程的状态,您可以使用thread . setapartmentstate()显式设置该状态。你不能在应用程序的主线程中这样做,因为是Windows启动的,所以这个属性是用来配置它的。

如果省略它,主线程就会加入MTA,即多线程公寓。然后,COM被迫创建一个新线程来给组件一个安全的家。这要求将所有调用从主线程封送到该辅助线程。当COM无法找到这样做的方法时,会引发E_NOINTERFACE错误,它需要一个知道如何序列化方法参数的助手。这是COM开发者需要处理的事情,他没有这样做。邋遢但不寻常

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

那么首先应用属性来修复这个问题:

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

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

否则我不能尝试嘲讽失败。COM涉及到很多重要的部署细节,必须编写注册表项以允许COM发现组件。你必须获得正确的指南,并且接口必须完全匹配。Regasm.exe需要注册一个. net组件[ComVisible]。如果您试图模拟一个现有的COM组件,并且做对了,那么您将破坏真实组件的注册。我不确定这是否值得追求。而且你在添加一个引用到[ComVisible]程序集时会遇到一个大问题,IDE拒绝允许。net程序通过COM使用。net程序集。只有后期绑定才能骗过机器。从COM的例外来看,你还没有接近嘲讽。最好按原样使用COM组件,这也是一个真实的测试。

所以,问题是我的带有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不是一个支持的工具,我仍然不知道如何正确地做到这一点