Определите уникальные ссылки на объекты из Interop Libraries (Doument.Paragraphs и т. Д.) - PullRequest
2 голосов
/ 21 июня 2019

Я хотел бы иметь возможность определить, когда два объекта переменных взаимодействия ссылаются на один и тот же «фактический» объект.Под «фактическим» я имею в виду, например, данный абзац или сноску в документе Microsoft Word .

Пример в : (примечание ответы тоже в порядке, вопрос не , связанный с языком)

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

Как видите, сравнение переменных объекта по ссылке не работает.Хотя это немного расстраивает, но я бы хорошо запустил компаратор для свойства .ParaID, если бы документация 1018 * не говорила так просто:

Зарезервировано для внутреннего использования..

Любые комментарии приветствуются (1) как избежать использования .ParaID и (2) надежность использования .ParaID в качестве уникального идентификатора (любая информация об этом свойстве приветствуется такжепоскольку Microsoft и Google хранят молчание по этому вопросу)

Этот вопрос можно сгенерировать и для других коллекций, таких как Word.Footnotes, Word.Bookmarks.Я полагаю, то же самое произошло бы с Excel.Worksheets и т. Д.

Ответы [ 2 ]

3 голосов
/ 22 июня 2019

Мой второй ответ - ОК, так что я был на правильном пути, однако мое предыдущее решение не удалось из-за .NET Runtime Callable Wrappers (RCW), особенно когда объект COM представляет коллекцию .


TL; DR: Вы можете сравнить любой COM-объект через .NET и проверить на равенство, просто сравнив указатели с помощью IntPtr.Вы можете сравнивать объекты, даже если у них нет свойств Id или ParaId.

IUnknown

Первое слово из MSDN на IUnknown в COM:

Для любого данного объекта COM (также известного как компонент COM), a специальный запрос для интерфейса IUnknown на любой из интерфейсов объекта должен всегда возвращать одно и то же значение указателя .Это позволяет клиенту определить , указывают ли два указателя на один и тот же компонент , вызвав QueryInterface с IID_IUnknown и сравнив результаты.В частности, это не тот случай, когда запросы на интерфейсы, отличные от IUnknown (даже тот же интерфейс через тот же указатель), должны возвращать одно и то же значение указателя [1]

RCW

Теперь посмотрим, как RCW является посредником между COM и .NET:

Общеязыковая среда выполнения предоставляет COM-объекты через прокси-сервер, называемый оболочкой, вызываемой во время выполнения (RCW).Хотя RCW кажется обычным объектом для клиентов .NET, его основная функция заключается в распределении вызовов между клиентом .NET и объектом COM.

Среда выполнения создает ровно одно RCW для каждого объекта COM независимо от количества ссылок на этот объект.Среда выполнения поддерживает одно RCW на процесс для каждого объекта [3]

Обратите внимание, что "ровно один" , вероятно, должно было иметьзвездочка (*), как мы скоро увидим.

RCW.Изображение предоставлено MSDN [3] , используется без разрешения.

enter image description here

Проверка на равенство

OP:

Я хотел бы иметь возможность определить, когда два переменных объекта взаимодействия ссылаются на один и тот же «фактический» объект

В следующем примере использования взаимодействия Word мы намереннополучить указатель на один и тот же дочерний COM-объект дважды , чтобы продемонстрировать, что указатели COM IUnknown являются средством уникальной идентификации COM-объектов, как описано в SDK, упомянутом выше.IntPtr.Equals позволяет нам очень хорошо сравнивать COM-указатели.

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!

В приведенном выше примере мы извлекаем COM-объект Paragraphs через свойство Paragraphs.Затем мы получаем IntPtr, представляющий интерфейс объектов IUnkown (который должны реализовывать все COM-объекты, в некотором роде все классы .NET в конечном итоге наследуются от Object).

ПроблемаRCW и COM-коллекции

Хотя приведенный выше пример хорошо работает с большинством COM-объектов, при использовании с COM-коллекцией новый RCW создается для элемента в коллекции каждый раз, когда вы выбираете его из коллекции.! Мы можем продемонстрировать это в следующем примере:

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

К счастью, есть решение, и после долгих исследований в конечном итоге наткнулся на другой пост, где кто-то сталкивался с той же проблемой.Короче говоря, для сравнения элементов в коллекции COM, когда RCW находится в пути, лучше всего хранить локальную копию [2] , чтобы избежать создания дополнительных RCW следующим образом:

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

Теперь объекты в коллекции все еще RCW, поэтому любые изменения COM-объектов будут отражать в COM-клиентах однако локальная коллекция не , поэтому, если вам нужно добавить / удалить элементы, лучше всего обратиться к собственно коллекции COM - в данном случае Paragraphs коллекция Word.

Окончательный пример

Вот окончательный код:

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] IUnknown :: QueryInterface ,MSDN

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

[3] Оболочка, вызываемая во время выполнения , MSDN

0 голосов
/ 21 июня 2019

Существуют различные способы, которыми это может быть достигнуто в Word.Довольно простой способ - сравнить свойства Range, используя метод InRange.Например:

Sub Tests()

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

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

    For Each MyPara As Word.Paragraph In ThisDoc.Paragraphs
        Dim rng as Word.Range = myPara.Range
        If rng.InRange(SelectedPara) And SelectedPara.InRange(rng) Then
          'They're the same
        Else
          'They're not the same
        End If
        rng = Nothing
    Next

End Sub
...