Комментарии 8
Для нашего ком прокси (который нечто отнаследованное от MarshalByRefObject), при приходе горбатого коллектора позовут Dispose()
wat?
(подмигивает) ну, формально там нет IDisposable, и я знаю что вы это знаете ;-) Но нечто похожее таки там есть. Честно скажу — я после таких сюрпризов просто побоялся посмотреть в ildasm — там Dispose(), Finalize(), или ~__ComObject(). Для гипотезы это несущественно, а перебить себе сон я был неготов ;-)
Ибо при написании ком сервера (который вызывается откуда угодно) ожидать что мастер-коллекции скаждут Release() и продолжат использовать child elements, часть из которых останется uninitialized… Странный паттерн.
однако КОМ-объекты виноваты. Это вполне нормальный случай использования, когда мастер коллекции говорят Release(), а детей продолжают использовать. Если при этом мастер-коллекция грохнула дочерние элементы не глядя на их счетчики — оборвать руки писателю мастер-коллекции.
Или я неправильно понял суть проблемы?
Есть некий COM от внешнего сервера, не InProc (то есть с маршалингом и автопроксями). У него есть проперть возвращающая коллекцию в смысле COM — в данном примере стандартный Count()/_Item() в наличии, судя по всему, раз по индексу работает.
Дотнет генерит оберток интеропом, то есть никаких динамик биндингов как бы нет.
В нагенереном дотнетом (не в коме!) есть стандартный GetEnumerator().
Хождение по этому дотнетовскому енумератору что foreach что while(!foo.MoveNext()) приводит к дисконнекту. То есть в процессе хождения / энумерации, собственно энумератор жив, а проперть по которой ходит (то есть вся коллекция) — уже нет. Те ком объекты которые я успеваю получить до COMException — - работают дальше.
Что смешно, точно такая же работа без GetEnumerator() то есть по индексу — к дисконнекту не приводит, так что очень вряд ли виноват сам ком объект.
Ну и совсем крышесносяще, если эту проперть просто присвоить локальной переменной — то снова все работает.
Дотнет генерит оберток интеропом, то есть никаких динамик биндингов как бы нет.
В нагенереном дотнетом (не в коме!) есть стандартный GetEnumerator().
Хождение по этому дотнетовскому енумератору что foreach что while(!foo.MoveNext()) приводит к дисконнекту. То есть в процессе хождения / энумерации, собственно энумератор жив, а проперть по которой ходит (то есть вся коллекция) — уже нет. Те ком объекты которые я успеваю получить до COMException — - работают дальше.
Что смешно, точно такая же работа без GetEnumerator() то есть по индексу — к дисконнекту не приводит, так что очень вряд ли виноват сам ком объект.
Ну и совсем крышесносяще, если эту проперть просто присвоить локальной переменной — то снова все работает.
Действительно, проблема в COM. Я несколько лет разрабатывал COM-серверы на C++. Всякий разработчик COM знает, что енумератор, запрашиваемый у COM-коллекции через get__NewEnum, пока жив сам, должен держать ссылку на итерируемую коллекцию с поднятым счетчиком (за исключением копирующих енумераторов, которые копируют элементы коллекции в себя). В майкрософтовском ATL, на базе которого часто реализуются COM и коллекции в том числе, это сделано автоматом:
Судя по симптомам, в Вашем COM-сервере об этом скорее всего забыли. Без исходников на 100% гарантировать конечно нельзя.
Как раз, потому что в этом варианте не участвует кривонаписанный COM-енумератор.
var asset = lib.Assets[i]; получаем коллекцию и напрямую (без промежуточного енумератора) из нее берем элемент.
Кстати, в COM можно реализовать проперти так, что Assets возвращает коллекцию, а Assets(i) сразу нужный элемент без создания и заполнения коллекции. Не знаю, поддерживает ли такое клиент на C#. Но это уже другая песня.
… holds a COM reference on the collection object to ensure that the collection remains alive while there are outstanding enumerators
Судя по симптомам, в Вашем COM-сервере об этом скорее всего забыли. Без исходников на 100% гарантировать конечно нельзя.
точно такая же работа без GetEnumerator() то есть по индексу — к дисконнекту не приводит
Как раз, потому что в этом варианте не участвует кривонаписанный COM-енумератор.
var asset = lib.Assets[i]; получаем коллекцию и напрямую (без промежуточного енумератора) из нее берем элемент.
Кстати, в COM можно реализовать проперти так, что Assets возвращает коллекцию, а Assets(i) сразу нужный элемент без создания и заполнения коллекции. Не знаю, поддерживает ли такое клиент на C#. Но это уже другая песня.
Вероятно вы правы.
Осталось только понять, почему
;-)
Осталось только понять, почему
если эту проперть просто присвоить локальной переменной — то снова все работает.
;-)
Следует дополнить, если кто-то вдруг не уловил:
.NET-овский енумератор в данном случае — это обертка над COM-енумератором.
foreach перед выполнением получает lib.Assets и запрашивает у него COM-енумератор через скрытый get__NewEnum и оборачивает в .NET-енуменатор, ссылка на саму коллекцию lib.Assets нигде в оригинальном коде не сохраняется. Енумератор живет до конца foreach, а ссылка на саму коллекцию пропадает сразу же перед входом в цикл. Если бы коллекция жила внутри lib, то все было бы ок, но, судя по всему (нужно убедиться в исходниках), проперти lib.Assets генерит новую коллекцию всякий раз, когда к нему обращаются.
P.S. COM-енумератор — полноценный COM-объект.
.NET-овский енумератор в данном случае — это обертка над COM-енумератором.
foreach перед выполнением получает lib.Assets и запрашивает у него COM-енумератор через скрытый get__NewEnum и оборачивает в .NET-енуменатор, ссылка на саму коллекцию lib.Assets нигде в оригинальном коде не сохраняется. Енумератор живет до конца foreach, а ссылка на саму коллекцию пропадает сразу же перед входом в цикл. Если бы коллекция жила внутри lib, то все было бы ок, но, судя по всему (нужно убедиться в исходниках), проперти lib.Assets генерит новую коллекцию всякий раз, когда к нему обращаются.
P.S. COM-енумератор — полноценный COM-объект.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Сага о E_RPC_DISCONNECT