如何声明 COM 接口以便可以强制转换

本文关键字:转换 接口 COM 何声明 声明 | 更新日期: 2023-09-27 18:34:54

首先,我必须说我是C#,.NET和COM Interop的新手。

当我尝试将COM对象转换为我编写的接口类型时,我收到以下错误消息:

错误消息:无法将类型为"System.__ComObject"的 COM 对象强制转换为接口类型"观察者.IObserver"。此操作失败,因为 COM 组件上的 IID '{13478219-8C3B-4849-99D9-27CEF1A49A55}' 的接口的 QueryInterface 调用失败,原因如下:不支持此类接口(HRESULT 异常:0x80004002 (E_NOINTERFACE((。

我在Windows 7上使用VS2010(.NET Framework 3.5(。

这是我的界面(观察器类库项目(:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace Observer
{
    [ComImport]
    [Guid("2B2D0BC7-A7C6-4924-A3DE-42F7075E5947")]
    public interface IObservable
    {
        void attach(IObserver observer);
        void detach(IObserver observer);
        void notify();
    }
    [ComImport]
    [Guid("13478219-8C3B-4849-99D9-27CEF1A49A55")]
    public interface IObserver
    {
        void update(IObservable observable);
    }
}

构建此dll时,我有以下警告:
Observer.dll 不包含任何可以为 COM 互操作注册的类型(当然是因为接口是在其他 dll 中实现的(。
IObserver 没有出现在我的注册表中(HKEY_CLASSES_ROOT''接口中不存在(。

这是失败的代码(合成类库项目(:

// Arguments de la méthode permettant de récupérer un objet selon son chemin
Object[] args;
// Objet représentant l'état technique de la synthèse
Observer.IObserver pEtatTechniqueSynthese;
args = new Object[] { m_sFullName + "/Etat_Technique" };
pEtatTechniqueSynthese = m_typeSite.InvokeMember("FindObject2",
                        BindingFlags.InvokeMethod,
                        null, m_pSite, args) as Observer.IObserver;
//Here pEtatTechniqueSynthese is null
//This call works fine without casting when pEtatTechniqueSynthese's type is Object
//but I need to cast it because my Observer.IObservable's attach method waits for an Observer.IObserver
//If I don't cast I get a "Exception has been thrown by the target of an invocation"
//=> InnerException : "La valeur n'est pas comprise dans la plage attendue"
//I don't know the exact translation, but it sort of means "Value not in expected range"
pEtatTechniqueSynthese = (Observer.IObserver)m_typeSite.InvokeMember("FindObject2",
                        BindingFlags.InvokeMethod,
                        null, m_pSite, args);
//Exception raised

pEtatTechniqueSynthese 真实类型,Etat_Technique(合成类库项目(,实现了 Observer.IObserver:

public class Etat_Technique : CODRA.SDK.DotNetUtils.COM.IObjectWithSite, Observer.IObservable, Observer.IObserver, ICalculateurEtatTechnique

我所有的程序集都是 COM 可见的。选中了我的观察器项目生成选项"注册 COM 互操作",并且我使用强名称密钥文件对程序集进行了签名。

我无法访问COM服务器代码(第三方组件(,但我确信问题来自我的代码。
有人知道我错过了什么吗?

====

================================================

有关第三方软件的更多信息:

该软件管理对象。
数据结构似乎是一个对象树,根节点称为站点。
当 dll 类想要访问对象时,它必须从站点调用"FindObject2"方法,并向其传递对象路径。此方法显然返回一个 COM 对象,因此我们可以调用方法、获取属性、...

我可以开发自己的对象类型并将它们添加到软件中,描述类(指定程序集、类、dll、属性等(。

在那里,我想获取我开发的对象并将其从 COM 对象转换回它实现的接口。
将对象声明为 Object 并调用其方法就可以了。
在那里,我需要一个观察者将其附加到可观察量,所以我必须投射。
一个解决方案肯定是使 attach 方法的参数成为对象,但如果我这样做,那么使用接口就没有兴趣了。

====

================================================

更多信息基于Michael Edenfield的回答:

[Com导入括号打开]

ComImport 属性是一个猜测。我使用它是因为我可以访问一个实用程序文件,以便与第三方代码进行交互。
下面是此文件中定义的接口。当我按照第三方软件文档所说的方式实现它时,我可以访问站点根对象。
我想如果他们使用它,我也应该使用它。

[ComVisible(true)]
[ComImport]
[Guid("FC4801A3-2BA9-11CF-A229-00AA003D7352") ]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
public interface IObjectWithSite
{
    void SetSite([MarshalAs(UnmanagedType.IUnknown)]
        [In] object pSite);
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")]
    void GetSite([In] ref Guid riid, [Out] out IntPtr pvSite);
}

我会删除这个属性,你比我更了解底层是什么。此外,问题是相同的,因此此属性毫无用处。

[Com导入括号关闭]

糟糕的是,FindObject2 有效地返回了一个 {System.__ComObject},所以强制转换不起作用,我一次又一次地被 InvalidCastException 困住了......
好的一点是我知道它应该是什么底层类。

我在"有关第三方软件的更多信息"部分中谈到的解决方法有效,即使附加方法采用对象参数,然后管理对象而不是 IObservers。这样做,我使用反射调用 IObserver 的更新方法:

foreach (Object observer in m_observers)
{
    Object[] args = new Object[] { this };
    observer.GetType().InvokeMember("update",
                            BindingFlags.InvokeMethod,
                            null, observer, args);
}

哎呀,不是吗?但是如果我找不到更干净的东西,这个蹩脚的东西就会完成工作。

如何声明 COM 接口以便可以强制转换

我不太清楚您要完成什么,但看起来您在这里混淆了几种不同的 COM 互操作概念。

仅当接口已在某处的外部类型库中定义时才有意义,使用 ComImportAttribute 导入该接口才有意义。如果这是你在 C# 中为自己类创建的新接口,则将其声明为 COM 导入属性将没有任何好处。这是因为没有实际的 COM 对象会实现您的接口,因此您将永远无法从 QueryInterface 获取指向一个的指针。从您对问题的描述来看,ComImport完全是错误的方式。

如果需要向 COM 客户端公开自己的接口,只需为它们提供 GUID。如果省略 ComImport 属性,则任何ComVisible类或接口都将注册为互操作(假设已启用该属性(。但是,COM 客户端需要了解接口并重新编译以实现它。我也不认为这会解决你的问题。

我不知道您的第三方库如何实例化您的自定义类;如果这是通过 COM 完成的,那么您将需要导出您的 CoClass 和接口定义才能正常工作。它要求提供程序集和类信息的事实使我怀疑这是不需要的。

在我看来,这听起来像是在某处创建一个托管 C# 对象,该对象实现了您的托管 C# 接口,您只需要获取它。如果 FindObject 方法实际上返回类型的实例,则应首先将返回值类型转换为具体的 C# 类型,然后尝试将其案例转换为接口。如果您尝试在ComObject上使用类型转换运算符来强制转换为接口,它将QueryInterface运行,并且几乎肯定会失败。

从未尝试过这个,但作为第一个猜测,我建议先对System.Object进行类型转换。从那里,C# 应使用托管类型元数据来确定接口是否可用并正确转换。

当然,如果FindObject返回一个实际的 COM 对象,该对象以某种方式包装在 C# 类周围,则需要首先弄清楚如何从该返回值中获取基础类。

请注意,第三方软件文档中包含的接口是 IObjectWithSite 。这是 Windows 定义的已知接口,因此如果您计划实现此接口,则需要使用 [ComImport]。在这种情况下,站点根对象将在自定义对象上调用 IObjectWithSite::SetSite() 并将自身传入,这就是嵌入对象"知道"其容器的方式。由于该接口是在外部定义的,因此您需要"导入"它,以便每个人都在实现相同的接口。