Мой второй ответ - ОК, так что я был на правильном пути, однако мое предыдущее решение не удалось из-за .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] , используется без разрешения.
Проверка на равенство
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