вторник, 22 апреля 2014 г.

А как вы работаете с COM?

  Самый известный и простой способ для работы с COM в .NET, это попросить VS сгенерировать сборку-обертку для импортируемых типов. Он подкупает своей простотой, но у него, на мой взгляд, есть ряд недостатков, о которых, я и хотел бы рассказать.



  Итак, сборка для доступа к COM объектам подключена. Что будем делать дальше? Давайте мыслить логически. Что бы ни пришлось делать нам с получаемыми объектами, мы все равно обязаны высвобождать полученные ресурсы. Нам не избежать Marshal.ReleaseComObject. Лично я в своих задачах ни разу не сталкивался со случаем, когда мне не было бы удобно организовать этот вызов через Dispose. А это уже новый тип. Так появляются типы-обертки для уже обернутых типов:

  1. public class DisposableWrapper: IDisposable  
  2. {  
  3.   private COMTypeWrapper _object;  
  4.   
  5.   internal DisposableWrapper(COMTypeWrapper object_)  
  6.   {  
  7.     _object = object_;  
  8.   }  
  9.   
  10.   public void Dispose()  
  11.   {  
  12.     if (_object != null)  
  13.       Marshal.ReleaseComObject(_object);  
  14.   }  
  15.   
  16.   // Все методы ComTypeWrapper придется обернуть   
  17. }  

  Это в свою очередь потянет оборачивание всех нужных методов исходного типа (COMTypeWrapper). Почему Dispose нельзя сразу разместить в исходной обертке? Если кто-то делал по-другому, прошу делиться.

  Во-вторых, меня беспокоят перечисления, которые предлагает OLE Automation. Например, IEnumVARIANT. Для классов COM, которые поддерживают подобные интерфейсы, в автосгенерированной обертке будут предусмотрены вызовы для перечисления. В обертке от VS пользователю будет предоставлен GetEnumerator().
Тут пользователя и подстерегают еще одни грабли. Если просто воспользоваться foreach, то вам будет гарантирована утечка памяти. Дело в том, что IEnumerator, получаемый для перечисления, инкапсулирует в себе COM-объект, реализацию IEnumVARIANT. foreach скрывает получение IEnumerator, а обертка не вызовет ReleaseComObject. Отсюда следует, что в исходном типе нужно освобождать и IEnumerator. Вот, что получается:

  1. public class DisposableWrapper: IDisposable  
  2. {  
  3.   private COMTypeWrapper _object;  
  4.   private IEnumerator _enum;  
  5.   
  6.   internal DisposableWrapper(COMTypeWrapper object_)  
  7.   {  
  8.     _object = object_;  
  9.   }  
  10.   
  11.   public void Dispose()  
  12.   {  
  13.     if (_enum != null)  
  14.       Marshal.ReleaseComObject(_enum);  
  15.   
  16.     if (_object != null)  
  17.       Marshal.ReleaseComObject(_object);  
  18.   }  
  19.   
  20.   public IEnumerator GetEnumerator()  
  21.   {  
  22.     if (_enum == null)  
  23.       _enum = _object.GetEnumerator();  
  24.     else  
  25.       _enum.Reset();  
  26.     return _enum;  
  27.   }  
  28. }  

  И так для каждого типа. А это уже нехилая такая доработка после сгенерированной обертки. В примере я пользуюсь одним IEnumerator, но это мелочи. Можно переделать, чтобы COM-enumerator каждый раз был новый.

  К сожалению ничего удобней, чем использовать IDisposable, я не нашел. Если вам нужно очень много объектов сразу, но кратковременно, то код превращается в какую-то кашу из using. Такой вот минус моей реализации. А вы как работаете с COM в .NET?

2 комментария:

  1. Конечно, DisposableWrapper - название не ахти, надо подобрать что то более подходящее. Ну и ещё про энумератор. Я думаю, что эта штука одноразовая. Я бы при повторном вызове GetEnumerator() удалял бы старый энумератор и создавал новый. Мало ли, может, в новом энумераторе набор элементов изменится.

    ОтветитьУдалить
    Ответы
    1. DisposableWrapper - ну я это название с потолка взял. В реальном приложении, конечно, по-другому будет называться.
      Про GetEnumerator у меня это уже написано.

      Удалить