【问题标题】:How can I "replace" the interface for an object returned from COM to managed code?如何将 COM 返回的对象的接口“替换”为托管代码?
【发布时间】:2021-01-17 02:26:04
【问题描述】:

我面对some weird performance issues,我怀疑它们可能源于将dynamic 指定为COM 接口方法的返回类型。具体来说,这就是 IHTMLDOMChildrenCollection 接口在互操作中的结束方式:

[DefaultMember("item")]
[Guid("3050F5AB-98B5-11CF-BB82-00AA00BDCE0B")]
[TypeLibType(4160)]
public interface IHTMLDOMChildrenCollection : IEnumerable
{
    [DispId(-4)]
    [TypeLibFunc(65)]
    IEnumerator GetEnumerator();
    [DispId(0)]
    dynamic item(int index); // HERE is the dynamic

    [DispId(1500)]
    int length { get; }
}

我怀疑一旦我的代码调用了item() 方法,就会创建一些额外的布线,这会降低其他代码的性能。

代码是这样组织的:

var document = (IHTMLDocument)documentBrowser.Document;
var selector = (IDocumentSelector)document;
IHTMLDOMChildrenCollection allElements = selector.querySelectorAll("*");
int length = allElements.length;
for (int index = 0; index < length; index++)
{
    var item = allElements.item(index);
}

This answer 指出,如果 IHTMLDOMChildrenCollection 接口声明为 item() 返回 object,而不是 dynamic,则性能可能会提高。

是否可以声明一个新接口并以某种方式说服 CLR 给我一个实现该接口的 RCW?

我尝试了以下方法:

[DefaultMember("item")]
[Guid("3050F5AB-98B5-11CF-BB82-00AA00BDCE0B")]
[TypeLibType(4160)]
public interface IHTMLDOMChildrenCollectionCopy : IEnumerable
{
    [DispId(-4)]
    [TypeLibFunc(65)]
    IEnumerator GetEnumerator();
    [DispId(0)]
    [return: MarshalAs(UnmanagedType.IDispatch)]
    object item(int index); // HERE it's object, not dynamic

    [DispId(1500)]
    int length { get; }
}

在代码中:

IHTMLDOMChildrenCollection allElementsOriginal = selector.querySelectorAll("*");
var unknown = Marshal.GetIUnknownForObject(allElements);
var newElements = (IHTMLDOMChildrenCollectionCopy)Marshal.GetTypedObjectForIUnknown(
   unknown , typeof(IHTMLDOMChildrenCollectionCopy));
int length = newElements.length; // this fails

它一直有效,直到读取.length 的行被执行。后者失败了

System.AccessViolationException: '试图读取或写入受保护的内存。这通常表明其他内存已损坏。'

这应该有效吗?我做错了什么?

【问题讨论】:

  • dynamic 肯定会影响性能,因为它会在 C# 端创建大量包装器。试试这个:[PreserveSig] int Item(int index, [MarshalAs(UnmanagedType.IDispatch)] out object ppItem);
  • [PreserveSig] 由于某些原因无法正常工作。但是我确实“替换”了接口 - 它必须用 [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] 声明
  • 我以为接口是完全生成的(TypeLibType、TypeLibFunc、DefautlMember很少用到),所以没有仔细看它的定义。它应该是 InterfaceIsIDual,而不是纯调度,否则您将再次遇到性能问题(.NET 将始终使用 Invoke 而不是直接调用方法)。 [PreserveSig] 应该可以工作。
  • 使用 InterfaceIsIDual 会导致 AccessViolationException,因为它没有属性。
  • 在这种情况下,.NET 直接调用方法(这是您想要的性能),因此这意味着定义不正确。其实是这样的,界面二进制布局是先get_length,然后是get__newEnumitem这样:pastebin.com/raw/4cnbrCrQ

标签: c# .net com rcw


【解决方案1】:

是的,这应该可以。缺少的部分是界面没有用[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]装饰

这是完整的声明:

[DefaultMember("item")]
[Guid("3050F5AB-98B5-11CF-BB82-00AA00BDCE0B")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[TypeLibType(4160)]
public interface IHTMLDOMChildrenCollectionCopy : IEnumerable
{
   // same members as in the question
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-23
    • 1970-01-01
    • 2011-01-10
    • 1970-01-01
    • 1970-01-01
    • 2011-01-21
    • 1970-01-01
    相关资源
    最近更新 更多