Как я могу перенести COM-объект в собственный класс.NET?

Я использую обширный существующий API COM (мог быть Outlook, но это не) в.NET (C#). Я сделал это путем добавления "Ссылки COM" в Visual Studio, таким образом, все "волшебство" сделано негласно (т.е. я не должен вручную выполнять tlbimp).

В то время как API COM может теперь "легко" использоваться от.NET, это не очень дружественная.NET. Например, нет никаких дженериков, события являются странными, причуды как IPicture, и т.д. Так, я хотел бы создать собственную.NET API, который реализован с помощью существующего API COM.

Простая первичная обработка могла бы быть

namespace Company.Product {
   class ComObject {
       public readonly global::Product.ComObject Handle; // the "native" COM object
       public ComObject(global::Product.ComObject handle) {
          if (handle == null) throw new ArgumentNullException("handle");
          Handle = handle;
       }

       // EDIT: suggestions from nobugz
       public override int GetHashCode() {
          return Handle.GetHashCode();
       }
       public override bool Equals(object obj) {
          return Handle.Equals(obj);
       }
   }
}

Одна непосредственная проблема с этим подходом состоит в том, что можно легко закончить с несколькими экземплярами ComObject для того же базового "собственного COM" объект. Например, при выполнении перечисления:

IEnumerable Items {
   get {
      foreach (global::Item item in Handle.Items)
         yield return new Company.Product.Item(item);
   }
}

Это, вероятно, было бы неожиданно в большинстве ситуаций. Решение этой проблемы могло бы быть похожим

namespace Company.Product {
   class ComObject {
       public readonly global::Product.ComObject Handle; // the "native" COM object
       static Dictionary m_handleMap = new Dictionary();
       private ComObject(global::Product.ComObject handle) {
          Handle = handle;
          handleMap[Handle] = this;
       }
       public ComObject Create(global::Product.ComObject handle) {
          if (handle == null) throw new ArgumentNullException("handle");

          ComObject retval;
          if (!handleMap.TryGetValue(handle, out retval))
              retval = new ComObject(handle);
          return retval;             
       }
   }
}

Это выглядит лучше. Перечислитель изменяется на вызов Company.Product.Item.Create(item). Но теперь проблемой является Словарь <>, поддержит оба объекта, таким образом, они никогда не будут собираться "мусор"; это, вероятно, плохо для COM-объекта. И вещи начинают становиться грязными теперь...

Похоже, что часть решения использует WeakReference в некотором роде. Существуют также предложения об использовании IDisposable, но это не кажется очень благоприятным для.NET вообще для контакта с, Располагают () на каждом объекте. И затем существуют различные обсуждения того, когда/если ReleaseComObject нужно назвать. Существует также код, законченный на http://codeproject.com, который использует позднее связывание, но я доволен зависимым от версии API.

Так, в этой точке я не действительно уверен, что состоит в том, чтобы продолжиться лучший способ. Я хотел бы, чтобы моя собственная.NET API была максимально "подобна.NET" (возможно, даже встраивающий блок Interop с.NET 4.0) и w/o, имеющий необходимость использовать эвристику как "две точки" правило.

Одна вещь я думал о попытке, состоит в том, чтобы создать проект ATL, скомпилировать со сбросом/, отмечают и используют компилятор C++ поддержка COM (продукт:: ComObjectPtr, созданный #import), а не.NET RCWs. Конечно, я обычно кодировал бы в C#, чем C++ / CLI...

16
задан Community 23 May 2017 в 12:32
поделиться

4 ответа

Самая большая проблема, которую я обнаружил при переносе COM-объектов в .NET - это тот факт, что сборщик мусора работает в другом потоке, и финальное освобождение COM-объекта часто (всегда?) будет вызываться из этого потока.

Microsoft намеренно нарушила здесь правила модели потоков COM, которые гласят, что в многопоточных объектах все методы должны вызываться из одного потока.

Для некоторых библиотек COM это не является большой проблемой, но для других это огромная проблема - особенно для библиотек, которым необходимо освобождать ресурсы в своих деструкторах.

Кое-что, о чем следует знать...

6
ответ дан 30 November 2019 в 22:17
поделиться

Вы сами не имеете дело с COM-объектами. Вы уже имеете дело с фасадом, который был создан в тот момент, когда вы добавили ссылку на двоичный файл COM в свой проект. (.NET) сгенерирует для вас фасад, что упростит задачу использования COM-объектов до простого использования обычных классов .NET. Если вам не нравится созданный для вас интерфейс, вам, вероятно, следует создать фасад существующего фасада. Вам не нужно беспокоиться о хитросплетениях COM, потому что это уже было сделано за вас (могут быть некоторые вещи, о которых вам действительно нужно беспокоиться, но я думаю, что их немного и они редки). Просто используйте класс как обычный.net, потому что это именно то, что есть, и решает любые проблемы по мере их возникновения.

РЕДАКТИРОВАТЬ: Одна из проблем, с которыми вы можете столкнуться, - это недетерминированное разрушение COM-объекта. Подсчет ссылок, который происходит за кулисами, основан на сборке мусора, поэтому вы не можете быть уверены, когда ваши объекты будут уничтожены. В зависимости от вашего приложения вам может потребоваться более детерминированное уничтожение ваших COM-объектов. Для этого вы должны использовать Marshal.ReleaseComObject . Если это так, то вы должны знать об этой проблеме.

Извините, я бы разместил больше ссылок, но очевидно, что я не могу опубликовать больше 1, не получив предварительно 10 репутации.

12
ответ дан 30 November 2019 в 22:17
поделиться

Похоже, что вы хотите создать более простой .NET API вокруг вашего сложного COM API. Как сказал nobugz, ваши классы ComObject - это настоящие, родные объекты .NET, которые содержат неуправляемые ссылки на ваши реальные объекты com. Вам не нужно делать ничего сложного для управления ими... просто используйте их как обычные объекты .NET.

Теперь, что касается представления "более красивого лица" вашим потребителям .NET. Для этого существует шаблон проектирования, который называется Facade. Я собираюсь предположить, что вам действительно нужна только часть функциональности, которую предоставляют эти COM-объекты. Если это так, то создайте слой фасада вокруг ваших объектов COM. Этот слой должен содержать необходимые классы, методы и типы поддержки, которые обеспечивают необходимую функциональность для всех клиентов .NET с более дружественным API, чем у самих объектов com. Фасад также будет отвечать за упрощение таких странностей, как преобразование событий, которые делают объекты com, в обычные события .NET, маршалинг данных, упрощение создания, настройки и разрушения объектов com и т.д.

Хотя в целом абстракций следует избегать, поскольку они добавляют работы и сложности. Однако иногда они необходимы, а в некоторых случаях могут значительно упростить работу. Если более простой API может повысить производительность других членов команды, которым необходимо использовать некоторые функциональные возможности, предоставляемые очень сложной системой com-объектов, то абстракция приносит ощутимую пользу.

0
ответ дан 30 November 2019 в 22:17
поделиться

Вы излишне усложняете задачу. Это не «дескриптор», нет необходимости в подсчете ссылок. __ComObject - это обычный класс .NET, на который распространяются обычные правила сборки мусора.

2
ответ дан 30 November 2019 в 22:17
поделиться
Другие вопросы по тегам:

Похожие вопросы: