Самый известный и простой способ для работы с COM в .NET, это попросить VS сгенерировать сборку-обертку для импортируемых типов. Он подкупает своей простотой, но у него, на мой взгляд, есть ряд недостатков, о которых, я и хотел бы рассказать.
Итак, сборка для доступа к COM объектам подключена. Что будем делать дальше? Давайте мыслить логически. Что бы ни пришлось делать нам с получаемыми объектами, мы все равно обязаны высвобождать полученные ресурсы. Нам не избежать Marshal.ReleaseComObject. Лично я в своих задачах ни разу не сталкивался со случаем, когда мне не было бы удобно организовать этот вызов через Dispose. А это уже новый тип. Так появляются типы-обертки для уже обернутых типов:
Это в свою очередь потянет оборачивание всех нужных методов исходного типа (COMTypeWrapper). Почему Dispose нельзя сразу разместить в исходной обертке? Если кто-то делал по-другому, прошу делиться.
Во-вторых, меня беспокоят перечисления, которые предлагает OLE Automation. Например, IEnumVARIANT. Для классов COM, которые поддерживают подобные интерфейсы, в автосгенерированной обертке будут предусмотрены вызовы для перечисления. В обертке от VS пользователю будет предоставлен GetEnumerator().
Тут пользователя и подстерегают еще одни грабли. Если просто воспользоваться foreach, то вам будет гарантирована утечка памяти. Дело в том, что IEnumerator, получаемый для перечисления, инкапсулирует в себе COM-объект, реализацию IEnumVARIANT. foreach скрывает получение IEnumerator, а обертка не вызовет ReleaseComObject. Отсюда следует, что в исходном типе нужно освобождать и IEnumerator. Вот, что получается:
И так для каждого типа. А это уже нехилая такая доработка после сгенерированной обертки. В примере я пользуюсь одним IEnumerator, но это мелочи. Можно переделать, чтобы COM-enumerator каждый раз был новый.
К сожалению ничего удобней, чем использовать IDisposable, я не нашел. Если вам нужно очень много объектов сразу, но кратковременно, то код превращается в какую-то кашу из using. Такой вот минус моей реализации. А вы как работаете с COM в .NET?
Итак, сборка для доступа к COM объектам подключена. Что будем делать дальше? Давайте мыслить логически. Что бы ни пришлось делать нам с получаемыми объектами, мы все равно обязаны высвобождать полученные ресурсы. Нам не избежать Marshal.ReleaseComObject. Лично я в своих задачах ни разу не сталкивался со случаем, когда мне не было бы удобно организовать этот вызов через Dispose. А это уже новый тип. Так появляются типы-обертки для уже обернутых типов:
- public class DisposableWrapper: IDisposable
- {
- private COMTypeWrapper _object;
- internal DisposableWrapper(COMTypeWrapper object_)
- {
- _object = object_;
- }
- public void Dispose()
- {
- if (_object != null)
- Marshal.ReleaseComObject(_object);
- }
- // Все методы ComTypeWrapper придется обернуть
- }
Это в свою очередь потянет оборачивание всех нужных методов исходного типа (COMTypeWrapper). Почему Dispose нельзя сразу разместить в исходной обертке? Если кто-то делал по-другому, прошу делиться.
Во-вторых, меня беспокоят перечисления, которые предлагает OLE Automation. Например, IEnumVARIANT. Для классов COM, которые поддерживают подобные интерфейсы, в автосгенерированной обертке будут предусмотрены вызовы для перечисления. В обертке от VS пользователю будет предоставлен GetEnumerator().
Тут пользователя и подстерегают еще одни грабли. Если просто воспользоваться foreach, то вам будет гарантирована утечка памяти. Дело в том, что IEnumerator, получаемый для перечисления, инкапсулирует в себе COM-объект, реализацию IEnumVARIANT. foreach скрывает получение IEnumerator, а обертка не вызовет ReleaseComObject. Отсюда следует, что в исходном типе нужно освобождать и IEnumerator. Вот, что получается:
- public class DisposableWrapper: IDisposable
- {
- private COMTypeWrapper _object;
- private IEnumerator _enum;
- internal DisposableWrapper(COMTypeWrapper object_)
- {
- _object = object_;
- }
- public void Dispose()
- {
- if (_enum != null)
- Marshal.ReleaseComObject(_enum);
- if (_object != null)
- Marshal.ReleaseComObject(_object);
- }
- public IEnumerator GetEnumerator()
- {
- if (_enum == null)
- _enum = _object.GetEnumerator();
- else
- _enum.Reset();
- return _enum;
- }
- }
И так для каждого типа. А это уже нехилая такая доработка после сгенерированной обертки. В примере я пользуюсь одним IEnumerator, но это мелочи. Можно переделать, чтобы COM-enumerator каждый раз был новый.
К сожалению ничего удобней, чем использовать IDisposable, я не нашел. Если вам нужно очень много объектов сразу, но кратковременно, то код превращается в какую-то кашу из using. Такой вот минус моей реализации. А вы как работаете с COM в .NET?
Конечно, DisposableWrapper - название не ахти, надо подобрать что то более подходящее. Ну и ещё про энумератор. Я думаю, что эта штука одноразовая. Я бы при повторном вызове GetEnumerator() удалял бы старый энумератор и создавал новый. Мало ли, может, в новом энумераторе набор элементов изменится.
ОтветитьУдалитьDisposableWrapper - ну я это название с потолка взял. В реальном приложении, конечно, по-другому будет называться.
УдалитьПро GetEnumerator у меня это уже написано.