标识来自互操作库的对象的唯一引用(Doument.Paragraphs等) [英] Identify unique references to objects from Interop Libraries (Doument.Paragraphs, etc)

查看:67
本文介绍了标识来自互操作库的对象的唯一引用(Doument.Paragraphs等)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我希望能够确定两个互操作变量对象何时引用相同实际"对象. 实际"是指例如 Microsoft Word 文档中的给定段落或脚注.

I would like to be able to identify when two interop variable objects refer to the same "actual" object. By "actual", I mean for example a given paragraph or footnote in a Microsoft Word document.

的问题: (请注意的问题也可以,问题与与语言无关无关)

Example in vb: (note c# answers are ok too, the question is not language related)

Imports Microsoft.Office.Interop

Sub Tests()

    Dim WordApp as Word.Application = Globals.ThisAddIn.Application         
    Dim ThisDoc as Word.Document = WordApp.ActiveDocument
    Dim ThisSelection As Word.Selection = ThisDoc .Application.Selection
    If ThisSelection.Range Is Nothing Then Exit Sub

    Dim SelectedPara As Word.Paragraph = ThisSelection.Range.Paragraphs.First


    For Each MyPara As Word.Paragraph In ThisDoc.Paragraphs

        'Reference equality: Never finds a match
        If MyPara.Equals(SelectedPara) Then MsgBox("Paragraph Found by ref") 

        'Property equality: Seems to works ok with .ParaID
        If MyPara.ParaID = SelectedPara.ParaID Then MsgBox("Paragraph Found by Id")

    Next

End Sub

如您所见,按引用比较对象变量不起作用.尽管这有点令人沮丧,但如果

As you can see, comparing the object variables by reference does not work. Whilst this is a bit frustrating, I would be ok running a comparer over the .ParaID property if the documentation did not say just as little as:

保留供内部使用.

Reserved for internal use.

欢迎就(1)如何避免使用.ParaID和(2)使用.ParaID作为唯一标识符的可靠性提出任何评论(也欢迎使用此属性的任何信息,因为Microsoft和Google保持沉默主题)

Any comments are welcome on (1) how to avoid using .ParaID, and (2) the reliability of using .ParaID as a unique identifier (any info on this property is welcome too as Microsoft and Google remain quite silent on the topic)

该问题也可以归类为其他集合,例如Word.FootnotesWord.Bookmarks.我想Excel.Worksheets等也会发生同样的情况.

The question can be generelised for other collections as well, such as Word.Footnotes, Word.Bookmarks. I suppose the same would happen with Excel.Worksheets, etc.

推荐答案


我的第二个答案-好的,所以我走了正确的路,但是由于.NET的Runtime Callable Wrappers(RCW),尤其是当COM对象表示 collection 时,我的先前解决方案失败了.


My second answer - OK so I was on the right track, however my prior solution failed due to .NET's Runtime Callable Wrappers (RCW), specifically when the COM object represents a collection.

TL; DR:您可以通过.NET比较任何 COM对象,只需通过IntPtr比较指针即可测试是否相等.即使对象没有IdParaId属性,您也可以对其进行比较.

TL;DR: You can compare any COM object via .NET and test for equality simply by comparing the pointers via IntPtr. You can compare objects even if they don’t have Id or ParaId properties.

首先从COM的IUnknown上的MSDN中获取一个词:

First a word from MSDN on IUnknown in COM:

对于任何给定的COM对象(也称为COM组件),任何对象接口上的IUnknown接口的特定查询必须始终返回相同的指针值.这样,客户端就可以通过调用QueryInterfaceIID_IUnknown并比较结果来确定是否两个指针指向相同组件.特别不是查询IUnknown以外的接口(甚至通过同一指针的同一接口)必须返回相同的指针值的情况 [1]

For any given COM object (also known as a COM component), a specific query for the IUnknown interface on any of the object's interfaces must always return the same pointer value. This enables a client to determine whether two pointers point to the same component by calling QueryInterface with IID_IUnknown and comparing the results. It is specifically not the case that queries for interfaces other than IUnknown (even the same interface through the same pointer) must return the same pointer value[1]

RCW

现在来看RCW如何成为COM和.NET之间的中间人:

RCW

Now to see how RCW are a middleman between COM and .NET:

公共语言运行库通过称为运行库可调用包装器(RCW)的代理公开COM对象.尽管RCW似乎是.NET客户端的普通对象,但其主要功能是封送.NET客户端和COM对象之间的调用.

The common language runtime exposes COM objects through a proxy called the runtime callable wrapper (RCW). Although the RCW appears to be an ordinary object to .NET clients, its primary function is to marshal calls between a .NET client and a COM object.

运行时为每个COM对象创建一个RCW ,无论该对象上存在多少引用.运行时为每个对象的每个进程维护一个RCW [3]

The runtime creates exactly one RCW for each COM object, regardless of the number of references that exist on that object. The runtime maintains a single RCW per process for each object[3]

请注意它怎么说"正好一个" ,它可能应该有一个星号(*),就像我们很快就会看到的一样.

Note how it said "exactly one", it probably should have had an asterisk (*) as we shall soon see.

RCW.图片由MSDN [3] 提供,未经允许使用.

RCW. Image courtesy of MSDN[3], used without permission.

OP:

我希望能够识别两个互操作变量对象何时引用相同的实际"对象

I would like to be able to identify when two interop variable objects refer to the same "actual" object

在下面的使用Word互操作的示例中,我们故意检索指向同一子COM对象的指针两次,以证明COM IUnknown指针是唯一标识COM对象的方式,如下所示:在上面提到的SDK中进行了概述. IntPtr.Equals使我们可以很好地比较COM指针.

In the following example of using Word interop, we deliberately retrieve a pointer to the same child COM object twice in order to demonstrate that COM IUnknown pointers are a means to uniquely identiy COM objects as outlined in the SDK mentioned above. IntPtr.Equals allows us to compare COM pointers quite nicely.

Document document =                                   // a Word document 
Paragraphs paragraphs = document.Paragraphs;          // grab the collection
var punk = Marshal.GetIUnknownForObject(paragraphs);  // get IUnknown
Paragraphs p2 = document.Paragraphs;                  // get the collection again
var punk2 = Marshal.GetIUnknownForObject(p2);         // get its IUnknown
Debug.Assert(punk.Equals(punk2));                     // This is TRUE!

在上面的示例中,我们通过Paragraphs属性检索了Paragraphs COM对象.然后,我们检索一个表示对象IUnkown接口的IntPtr(所有COM对象都必须实现,以与所有.NET类最终从Object派生的方式相同的方式).

In the above example, we retrieve the Paragraphs COM object via the Paragraphs property. We then retrieve a IntPtr that represents the objects IUnkown interface (that all COM objects must implement, sort of in the same way all .NET classes derive ultimately from Object).

尽管上面的示例适用于大多数COM对象,但当与COM集合一起使用时,每次从集合中获取项目时,都会为该集合中的项目创建一个新的RCW!在以下示例中对此进行演示:

Though the above example works well with most COM objects, when used with a COM collection, a new RCW is created for an item in the collection each time you fetch it from the collection! We can demonstrate this in the following example:

const string Id = "Miss Piggy";
var x = paragraphs[1];                   // get first paragraph
Debug.Assert(x.ID == null);              // make sure it is empty first 
x.ID = Id;                               // assign an ID 
punk = Marshal.GetIUnknownForObject(x);  // get IUnknown
// get it again
var y = paragraphs[1];                   // get first paragraph AGAIN
Debug.Assert(x.ID == Id);                // true
punk2 = Marshal.GetIUnknownForObject(y); // get IUnknown
Debug.Assert(punk.Equals(punk2));        // FALSE!!! Therefore different RCW

幸运的是,有一个解决方案,经过大量研究最终偶然发现有人遇到相同问题的另一篇文章.长话短说,为了比较RCW时比较COM集合中的项目,最好的方法是存储本地副本 [2] ,以避免像这样创建其他RCW:

Luckily there is a solution and after much researching eventually stumbled across another post where someone was encountering the same issue. Long story short, in order to compare items in a COM collection when RCW is in the way, the best way is to store a local copy[2] so as to avoid additonal RCWs being created like so:

var paragraphsCopy = paragraphs.Cast<Paragraph>().ToList();

现在集合中的对象是静止 RCW,因此对COM对象的任何更改将反映在COM客户端中 本地集合不是,因此,如果您需要添加/删除项目以最好地参考COM集合,在本例中为Word的Paragraphs集合.

Now the objects in the collection are still RCW so any changes to the COM objects will reflect in COM clients however the local collection isn't so if you need to add/remove items best to refer to the COM collection proper - in this case Word's Paragraphs collection.

这是最终代码:

Document document = // ...
Paragraphs paragraphs = document.Paragraphs;
var paragraphsCopy = paragraphs.Cast<Paragraph>().ToList();
Paragraph firstParagraph = paragraphsCopy.First();

// here I explicitly select a paragraph but you might have one already
// select first paragraph
var firstRange = firstParagraph.Range;
firstRange.Select();

var selectedPunk = Marshal.GetIUnknownForObject(firstParagraph);
var i = 1;
foreach (var paragraph in paragraphsCopy)
{
    var otherPunk = Marshal.GetIUnknownForObject(paragraph);
    if (selectedPunk.Equals(otherPunk))
    {
        Console.WriteLine($"Paragraph {i} is the selected paragraph");
    }

    i++;
}

另请参见

[1]

See also

[1] IUnknown::QueryInterface, MSDN

[2] https://stackoverflow.com/a/9048685/585968

[3]

[3] Runtime Callable Wrapper, MSDN

这篇关于标识来自互操作库的对象的唯一引用(Doument.Paragraphs等)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆