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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16938467/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 08:10:27  来源:igfitidea点击:

Why cannot I cast my COM object to the interface it implements in C#?

c#com

提问by catbert

I have this interface in the dll (this code is shown in Visual Studio from metadata):

我在 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);
    }
}

So I created a COM server with such class:

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

Server seems to work fine, and I am able to use the object from a VBScript. But then I try to use it from another C# client:

服务器似乎工作正常,我可以使用 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();
    }

And it fails with

它失败了

Unable to cast COM object of type 'System.__ComObject' to interface type 'XCapture.IDiagnostics'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

无法将“System.__ComObject”类型的 COM 对象转换为接口类型“XCapture.IDiagnostics”。此操作失败,因为 IID 为“{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}”的接口的 COM 组件上的 QueryInterface 调用因以下错误而失败:不支持此类接口(来自 HRESULT 的异常:0x80004002 (E_NOINTERFACE)) .

What am I doing wrong?

我究竟做错了什么?

I have also tried to use InvokeMember, and that kinda worked except that I wasn't able to get the ref-returned dataparameter.

我也尝试过使用 InvokeMember,除了我无法获得 ref 返回的数据参数之外,这还挺有效的。

EDIT:added STAThread attribute to my Main procedure. This does not solve the issue, but you really shoulduse STAThread with COM unless you're absolutely sure you don't need it. See Hans Passant's answer below.

编辑:在我的 Main 过程中添加了 STAThread 属性。这并不能解决问题,但您确实应该将 STAThread 与 COM 一起使用,除非您绝对确定不需要它。请参阅下面的汉斯·帕桑特 (Hans Passant) 的回答。

采纳答案by catbert

So, the problem was that my DLL with IDiagnostics interface was generated from a TLB, and that TLB never got registered.

所以,问题是我的带有 IDiagnostics 接口的 DLL 是从 TLB 生成的,而 TLB 从未注册过。

Since the DLL was imported from the TLB, RegAsm.exe refuses to register the library. So I used the regtlibv12.exetool to register the TLB itself:

由于 DLL 是从 TLB 导入的,RegAsm.exe 拒绝注册该库。所以我用regtlibv12.exe工具注册了TLB本身:

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

Then everything magically started to work.

然后一切都神奇地开始工作。

Since regtlibv12 is not a supported tool, I still don't know how to do this properly.

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

回答by Hans Passant

This exception can be a DLL Hell problem. But the simplest explanation is for what's missingfrom your snippet. Your Main() method is missing the [STAThread] attribute.

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

That's an important attribute that matters when you use COM objects in your code. Most of them are not thread-safe and they require a thread that's a hospitable home for code that cannot support threading. The attribute forces the state of the thread, the one you can set explicitly with Thread.SetApartmentState(). Which you can't do for the main thread of an app since Windows starts it, so the attribute is used to configure it.

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

If you omit it then you the main thread joins the MTA, the multi-threaded apartment. COM is then forced to create a new thread to give the component a safe home. Which requires all calls to be marshaled from your main thread to that helper thread. The E_NOINTERFACE error is raised when COM cannot find a way to do that, it requires a helper that knows how to serialize the method arguments. That's something that needs to be taken care of by the COM developer, he didn't do that. Sloppy but not unusual.

如果省略它,则主线程将加入多线程单元 MTA。COM 然后被迫创建一个新线程来为组件提供一个安全的家。这需要将所有调用从主线程编组到该辅助线程。E_NOINTERFACE 错误在 COM 找不到方法来执行时引发,它需要知道如何序列化方法参数的帮助程序。这是 COM 开发人员需要处理的事情,他没有这样做。马虎但并不罕见。

A requirement of an STA thread is that it also pumps a message loop. The kind you get in a Winforms or WPF app from Application.Run(). You don't have one in your code. You mightget away with it since you don't actually make any calls from a worker thread. But COM components tend to rely on the message loop to be available for their own use. You'll notice this by it misbehaving, not raising an event or deadlocking.

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

So start fixing this by applying the attribute first:

因此,首先通过应用属性开始解决此问题:

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

Which will solve this exception. If you have the described event raising or deadlock problems then you'll need to change your application type. Winforms is usually easy to get going.

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

I cannot otherwise take a stab at the mocking failure. There are significant deployment details involved with COM, registry keys have to be written to allow COM to discover components. You have to get the guids right and the interfaces have to be an exact match. Regasm.exe is required to register a .NET component that's [ComVisible]. If you try to mock an existing COM component, and got it right, then you'll destroy the registration for the real component. Not so sure that's worth pursuing ;) And you'll have a significant problem adding a reference to the [ComVisible] assembly, the IDE refuses to allow a .NET program to use a .NET assembly through COM. Only late binding can fool the machine. Judging from the COM exception, you haven't gotten close to mocking yet. Best to use the COM component as-is, also a real test.

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