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

cat*_*ert 13 c# com

我在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);
    }
}
Run Code Online (Sandbox Code Playgroud)

所以我用这样的类创建了一个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();
    }
}
Run Code Online (Sandbox Code Playgroud)

服务器似乎工作正常,我能够使用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();
    }
Run Code Online (Sandbox Code Playgroud)

它失败了

无法将"System .__ ComObject"类型的COM对象强制转换为接口类型"XCapture.IDiagnostics".此操作失败,因为IID为"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXX"的接口的COM组件上的QueryInterface调用由于以下错误而失败:不支持此类接口(HRESULT异常:0x80004002(E_NOINTERFACE)) .

我究竟做错了什么?

我也尝试过使用InvokeMember,除了我无法获得ref-returned 数据参数之外,还有一些工作.

编辑:将STAThread属性添加到我的主过程.这并没有解决问题,但你真的应该使用STAThread与COM,除非你绝对确定你不需要它.请参阅下面的Hans Passant的回答.

Han*_*ant 27

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

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

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

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

因此,首先应用属性开始修复此问题:

[STAThread]
static void Main(string[] args)
{
    // etc..
}
Run Code Online (Sandbox Code Playgroud)

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

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


cat*_*ert 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"
Run Code Online (Sandbox Code Playgroud)

然后一切都神奇地开始起作用.

由于regtlibv12不是受支持的工具,我仍然不知道如何正确地执行此操作.

  • 嗯,不,你找到了缺少[STAThread]属性的解决方法.通过注册类型库(通常由Regasm.exe/tlb选项完成),标准编组器现在可以调用从一个线程到另一个线程的调用.哪个可能会让你继续前进,但核心问题仍然是代码在错误的线程上运行.注意其他副作用,比如调用中的大量开销.Regtlibv12不是受支持的工具,因为它始终是错误的工具.它存在的唯一原因是因为.NET框架安装程序需要它. (5认同)
  • 如果你没有tlb怎么办?例如在客户的机器上? (2认同)