如何声明 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 互操作概念。
仅当接口已在某处的外部类型库中定义时才有意义,使用 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()
并将自身传入,这就是嵌入对象"知道"其容器的方式。由于该接口是在外部定义的,因此您需要"导入"它,以便每个人都在实现相同的接口。