0

首先,我必须说我是 C#、.NET 和 COM 互操作的新手。

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

错误消息:无法将“System.__ComObject”类型的 COM 对象转换为接口类型“Observer.IObserver”。此操作失败,因为 IID 为“{13478219-8C3B-4849-99D9-27CEF1A49A55}”的接口的 COM 组件上的 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\Interface 中没有)。

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

// 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(Syntheses 类库项目)实现了 Observer.IObserver :

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

我所有的程序集都是 COM 可见的。我的 Observer 项目构建选项“注册 COM 互操作”被选中,我使用强名称密钥文件签署了我的程序集。

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

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

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

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

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

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

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

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

[ComImport 括号打开]

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

[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);
}

我会删除这个属性,你比我更了解底层是什么。此外,问题是一样的,所以这个属性是没有用的。

[ComImport 括号关闭]

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

我在“有关第三方软件的更多信息”部分中谈到的解决方法有效,即使 attach 方法采用 Object 参数,然后管理 Objects 而不是 IObservers。为此,我使用 Reflection 调用 IObserver 的更新方法:

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

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

4

1 回答 1

1

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

ComImportAttribute仅当接口已在某个外部类型库中定义时,使用导入 COM 接口才有意义。如果这是您在 C# 中为您自己的类创建的新接口,那么将其声明为 COM 导入属性不会有任何好处。那是因为没有真正的 COM 对象会实现你的接口,所以你永远无法从QueryInterface. 从你对问题的描述来看,ComImport是完全错误的做法。

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

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

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

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

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

请注意,第三方软件文档中包含的接口是IObjectWithSite. 这是Windows定义的一个众所周知的接口,所以如果你打算实现这个接口,你确实需要使用。[ComImport]在这种情况下,站点根对象将调用IObjectWithSite::SetSite()您的自定义对象并将其自身传递进来,这就是嵌入式对象“了解”其容器的方式。因为该接口是在外部定义的,所以您需要“导入”它,以便每个人都实现相同的接口。

于 2012-05-21T20:51:05.073 回答