C#向COM公开 - 接口继承

16

假设有一个实现了IBaseClass接口的类BaseClass。

然后有一个继承自IBaseClass的接口IClass。

接着有一个实现了IClass接口的类class。

例如:

[ComVisible(true), InterfaceType(ComInterfaceType.IsDual), Guid("XXXXXXX")]
public interface IBaseClass
{
  [PreserveSig]
  string GetA()
}

[ComVisible(true), InterfaceType(ComInterfaceType.IsDual), Guid("XXXXXXX")]  
public interface IClass : IBaseClass
{
  [PreserveSig]
  string GetB()
}

[ComVisible(true), ClassInterface(ClassInterfaceType.None), Guid("XXXXXXX")]
public class BaseClass : IBaseClass
{
  public string GetA() { return "A"; }
}

[ComVisible(true), ClassInterface(ClassInterfaceType.None), Guid("XXXXXXX")]
public class Class : BaseClass, IClass
{
  public string GetB() { return "B"; }
}
当将程序暴露给COM接口后,如果我创建一个名为“Class”的实例,它将不允许我调用GetA()方法。 在.tlb文件中查看我的IDL时,IClass接口如下所示:
[
  odl,
  uuid(XXXXXXXXXXXXXXXXXXXXX),
  version(1.0),
  dual,
  oleautomation,

]
interface IClass : IDispatch {
    [id(0x60020000)]
    BSTR GetB();
}

看起来IClass根本没有继承IBaseClass!

如果我去掉IClass继承IBaseClass的部分,只是将方法添加到接口中,那么就可以正常工作。

如何让C#在COM中启用这种继承呢? 我不想在可以继承它们时重新实现接口。

请查看此链接.Net COM Limitation

如果有人知道为什么会这样,或者有比复制粘贴到我的“派生”接口更好的解决方法,请告诉我。如果没有,我会在几天内标记一个答案。


您需要查询其他接口以访问它们中的方法。根据您使用的对象所用的语言,这可能不方便。为了解决这个问题,您可以为每个类创建一个“平面”界面,或者使用“ClassInterface”让编译器为您完成(但这也会带来其他问题)。 - adrianm
1
如果您查看我的链接,回答者提到在导出时,.Net不会将基接口应用于COM接口。换句话说,您必须复制粘贴基接口定义以模拟基接口。 - jonathanpeppers
1
我认为你应该将自己的 .Net COM 限制链接作为答案发布——这是正确的解决方案,如果你自己找到了解决方法,推荐发布自己的答案。 - bacar
4个回答

14

这不是.NET的问题,而是COM工作方式的结果。它不支持继承。您需要在客户端上修复此问题,它需要使用IBaseClass的IID调用QueryInterface()来获取IBaseClass接口的接口指针,以便可以调用GetA()。.NET互操作性自动提供了一个QI实现,使其正常工作。然而,在这种情况下它并不是非常用户友好,请设计您的C#端代码,使得客户端更容易使用您的类而不是相反。通常需要一个一行代码的覆盖方法,将基础C#类实现委托给它。

请注意,您的方法签名和[PreserveSig]属性的使用存在问题。它们无法通过IDispatch进行调用,也不能自动封送。这需要一个返回值类型为HRESULT的方法。当您删除该属性时,这将会自动完成。


我最初设置 [PreserveSig] 是为了便于在 VBScript 中进行测试(谁想要与输出参数纠缠呢),但是即使没有它也可以正常工作(并且具有适当的 IDL 定义)。我将在基类中复制方法,以防止客户端需要调用 QueryInterface。 - jonathanpeppers
2
在我看来,这是一个 .net 问题,COM 完全支持接口(而不是类)继承,regasm 可能已经生成了一个 TLB,其中 IClass 派生自 ComVisible 的 IBaseClass。我注意到当发生这种情况时,Class 的 IDispatch 实现也不知道 IBaseClass 方法 - 非常令人失望。你的建议(QI)对于一个 coclass 实现多个接口的情况是好的;根据 Jonathan 链接中的 MVP,这种技术在继承层次结构中不起作用,因为它根本不被支持。 - bacar
@bacar:聚合可能是更好的术语。无论如何,实现接口需要一个具体的类。为了使继承的COM接口工作,编译器需要生成多个v表,每个接口一个。这仅受支持多重继承的运行时环境支持。.NET不是其中之一,这是一个非常基本的限制。是的,你可以称它为.NET问题,但这并不能让你有所收获。 - Hans Passant
@Hans:肯定只需要一个(深层)虚函数表,对应于最派生的接口?只有在存在多重继承时才需要多个虚函数表,这不是OP想要的。即使存在多重继承,.net也支持从多个接口进行继承/聚合。 - bacar
2
@bacar - 一个深度的虚函数表很麻烦。它需要执行多个不兼容的任务,支持接口方法指针以及类本身的虚拟方法。你无法控制指针的位置。而且.NET类总是有虚拟方法,因为它继承了System.Object。我并不反对CLR在为COM客户端合成一组单独的虚函数表方面做得更好。但这没有发生,他们的目标是自动化。修改IUnknown接口是可能的,只是不太美观。总之,没有类型库是没有什么美观的。 - Hans Passant
@Hans:是的,很好的观点(尽管在自动化方面,这个例子也无法通过IDispatch工作!)。我想他们不太可能在未来修复它。也许现在是时候让我起身学习PowerShell了;-) - bacar

6

这似乎与此问题非常相似:C#中ComVisible类的接口继承

正如您在编辑中指出的那样,这是.NET到COM转换的限制,即COM接口不包括任何继承的接口。

在某些语言中,您可以通过在所有相关接口中包含一块代码来解决此问题,但就我所能看到的而言,C#中不可能实现。


结果发现,这只是一些烦人的额外工作,以使我们的汇编代码可见。 - jonathanpeppers
使用C# 8.0特性编程,现在可以在接口中包含一段代码块了! - schwarz

0

在扩展其他接口的接口上使用[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]作为属性。 这是一个解决方法。


0

这里有一种不同的方法,可以最小化工作量,并使接口保持清洁。
只需提供一个额外的属性,返回BaseInterface:

public IBaseClass BaseProperties {
    get { return this; }
}

当然,在你的IBaseClass接口中,你必须添加这一部分:

public ISingleShot BaseProperties { get; }



以下是相应的VB.Net代码:

ReadOnly Property BaseProperties As ISingleShot
Public ReadOnly Property BaseProperties As IBaseClass Implements IClass.BaseProperties
  Get
    Return Me
  End Get
End Property

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接