Всем добрый день. Хотел бы получить краткие и понятные для новичка ответы на следующие вопросы:
Что такое COM объект?
Как происходит разработка COM объекта?
Какие особенности реализации COM Microsoft?
Прошу воздержаться от скидывания многостраничной литературы. Спасибо.
Мне попался на глаза такой вопрос, предлагаю свой развернутый ответ. Интересно получить оценку сообщества профессионалов. Надеюсь и новички смогут найти что то полезное.
COM объект – это объект, созданный-описанный-реализованный по правилам COM. Одним из основных правил COM является способ идентификации COM объектов по функциональности, которую они реализуют. Чтобы все-таки понять, что такое COM объект нам бы пришлось изучить не только достаточно обширную документацию по разработке COM объектов, но и разобрать хотя бы один практический пример, для которого применяется эта технология. Таким образом ответ на второй вопрос фактически является ответом и на первый вопрос.
Кстати в документации Майкрософта по DirectX, практически в заголовке мы также, как и 20+ лет назад, видим заявление о необходимости использования СОМ:
The Microsoft Component Object Model (COM) is an object-oriented programming model used by several technologies, including the bulk of the DirectX API surface. For that reason, you (as a DirectX developer) inevitably use COM when you program DirectX.
Давайте я попробую рассказать, исходя из своего опыта, на всем известном примере что такое COM объекты и зачем вообще нужна концепция моделирования объектов в виде компонент, по аналогии с миром физических устройств. Идея проста, так же как разные смартфоны (например) собираются из набора микросхем, резисторов, конденсатором, так же можно и разные большие приложения собирать (казалось бы-хотелось бы) из локализованных компонент, изолированно созданных и сохраненных в разных файлах (ДЛЛ-ках) и поэтому совершенно не зависимых, а значит не способных нарушить как минимум компиляцию друг друга.
Для примера можно рассмотреть задачу создания универсального проигрывателя видео файлов (видеоплеер).
Проблема сложности реализации (не путать со сложностью вычислений в ран-тайме)
Мало кто задумывается о том насколько много существует форматов кодирования изображения (для видео), форматов кодирования аудио, форматов упаковки кодированных потоков аудио и видео в одном файле.
Для осознания масштаба той программы, которая будет претендовать на универсальность, при проигрывании видео, допустим у нас есть по 10 форматов каждого типа. Таким образом наш универсальный проигрыватель видео должен уметь обрабатывать 10 * 10 * 10 = 1000 вариантов конфигурации проигрываемого файла, представьте себе switch на тысячу кейсов! Это конечно очень грубая абстракция для оценки масштаба программы для проигрывания видео, но, как ни странно, она дает очень наглядную и совершенно адекватную оценку масштаба проблемы, которую надо решить. Очевидно что в одном случае сложность определяется произведением целых чисел больших единицы, а в другом суммой плюс некоторая константа сравнимая с единицей. Кстати, по поводу оценки 10 «десять» вариантов – для тех, кто не в курсе – это очень умеренная оценка количества, в реальности это число оценивается в несколько десятков и, типов кодеков на самом деле, гораздо больше, чем три.
Решение как обычно будет очевидным после того, как оно кем-то сформулировано. Вместо того что бы писать 1000 вариантов алгоритма на каждый из случаев жизни, надо:
Написать отдельную компоненту (библиотеку‑DLL‑ку) для каждого декодера, распаковщика, рендерера, …, то есть в рассмотренном случае всего 30 ДЛЛ‑к вместо 1000 кейсов в свиче!
Написать некоторую логику, которая находит нужные компоненты для проигрывания любого файла с произвольной конфигурацией типов данных внутри, инстанциирует эти компоненты, соединяет в цепочку для обработки входных потоков данных из файла
Для тех, кто любит считать сложность по формулам, исходная была:
O (произведение всех ni для всех m типов кодеков) где
ni – число кодеков одного типа i,
m – число типов кодеков.
Во втором случае сложность равна:
O(суммы Ni для m типов + L) где
Ni – число кодеков одного типа i оформленных в виде компонент,
m – число типов кодеков и
L – сложность логики из пункта 2. Кажется, что N будет несколько выше, чем n так как нужно какое-то дополнительное оформление, на самом деле это оформление зачастую упрощает разработку так как заставляет сосредоточиться на действительно значимых аспектах реализации компоненты и обеспечивает существенный уровень унификации кода.
Есть отдельный огромный плюс компонентного подхода. У нас появляется возможность разрабатывать декодеры (например, для примера Mpeg4, MP4) совершенно изолированно друг от друга, в разных проектах, разными людьми, параллельно, … как угодно! Единственное условие соответствие разработанной компоненты определенным правилам оформления компонент.
И теперь можно остановиться на этих правилах оформления компонент поподробнее, их все равно надо понимать в каком-то виде для понимания всей технологии. Чтобы понять эти правила надо попробовать сформулировать логику по которой будет строиться и работать приложение видео плеера, основанное на использовании кодеков, каждый из которых может загружаться из отдельной ДЛЛ-ки.
Когда вы открываете видео плеер он понятия не имеет какого типа файл вы решите проиграть. Поэтому нет смысла загружать кодеки даже если их только 30 штук. Вполне очевидно, что тип кодеков, которые надо загрузить, можно понять только по результатам анализа файла, открытого для проигрывания. Видео файлы естественным образом сохраняют информацию о том какого формата стримы в них записаны и как они упакованы, для этого сформирована определенная система идентификации, которая также естественно позволяет определять какого типа – какие кодеки надо загрузить чтобы декодировать эти потоки и вывести в соответствующие устройства рендеринга (проигрывания) которые, кстати, тоже представлены соответствующими компонентами.
Как это выглядит на каком-то подобии схемы
На картинке ниже можно видеть обычный результат работы видеоплеера при открытии файла. Результатом является граф построенный из найденных, загруженных и состыкованных для передачи-приема данных компонент разного типа необходимых для парсинга, декодирования и отображения данных после нажатия кнопки «плей».
Как это ни странно в мире COM-объектов видео плеер НЕ декодирует, НЕ проигрывает видео сам, он строит связанный граф из компонент, которые будут проигрывать видео, а ПОТОМ транслирует пользовательские команды (такие как нажатие кнопок Стоп-Плей) в команды для этого графа. Уходя немного в сторону, можно отметить что граф тоже представлен некоторым объектом внутри видеоплеера и трансляция пользовательских команд к этому объекту тоже во многом регламентирована, не уверен что в рамках СОМ технологии.
Я уже отметил, что метод оценки сложности реализации функциональности, который я использовал очень грубый (хотя и адекватный по полученным результатам). Если мы даже немного погрузимся в детали того как выбирается нужный кодек для очередного этапа обработки данных, например декодирования, мы увидим что даже отдельный кодек зависит не только от типа данных в стриме который ему надо обрабатывать но и от того, например, каким образом эти данные ему скармливает предыдущий кодек (например МР4 декодер должен получить данные от файл-реадера который соответствующим образом парсит файл-екстрактит видео и аудио потоки). По поводу всего этого растущего с глубиной реализации многообразия можно отметить, что все это многообразие успешно управляется в рамках концепции COM объектов. COM технологии позволяют поддерживать управляемость кода, избегать древней, но все такой же актуальной проблемы известной под названием DLL HELL.
Я думаю, теперь вполне понятно, что смысл СОМ технологии или концепции состоит в том чтобы разбить решение одной очень объемной задачи, такой как разработка универсального видео плеера на множество подзадач которые можно решить независимо друг от друга. Нетрудно понять, что решение, которое делит огромную монолитную программу на множество независимых компонент и некоторое легковесное ядро для связывания этих компонент в законченное приложение требует поддержки со стороны операционной системы. Операционная система должна обеспечить поиск нужной компоненты для обработки выявленного формата данных, по сути ОС должна поддерживать базу данных исполняемых компонент и их описаний для того чтобы компоненты (обычно в виде ДЛЛ-к) можно было добавлять/регистрировать в этой базе данных, а потом находить и активировать-использовать эти компоненты по системе идентификации определенной в этой базе данных. Система идентификации компонент также является одной из глобальных инфраструктурных функций ОС, компоненты получают идентификаторы не зависимо от того добавлены они уже в базу данных компонент операционной системы или нет.
Фабрики классов и другие паттерны проектирования
Интересно отметить, что, например, применение паттерна проектирования с «фабриками классов» является неотъемлемой частью СОМ технологии. Чтобы иметь возможность создавать объекты классов, реализованных в ДЛЛ-ке по идентификатору (или по некоторой структуре с описанием функциональности требуемого объекта) каждая ДЛЛ-ка реализует один или несколько классов-фабрик для компонент, эти же классы-фабрики вызываются для получения информации о содержимом ДЛЛ-ки при регистрации в базе данных компонент.
Насколько я это понимаю большинство паттернов проектирования были, как минимум, практически отработаны, если не изначально сформулированы именно в рамках разработки концепции СОМ-объектов, поэтому это очень полезная тема для изучения, и навсегда останется таковой, как мне кажется.
Третий вопрос: Какие особенности реализации COM Microsoft?
Честно говоря, для меня вопрос выглядит не корректно. На сколько я знаю СОМ технология реализована только Мелко-Мягкими :). А анализ особенностей предполагает сравнение с какой-то другой реализацией, а другой реализации просто не существует, по крайней мере я таких не знаю.
Интересно в качестве ответа на 3-й вопрос попытаться сравнить КОМ технологию с какой-то в чем то сравнимой и/или чем-то похожей технологией. Возьмем для примера (неожиданно) технологию инстанциирования (создания, внедрения, …) объектов-бинов из Java Spring.
MS COM по сути тоже является технологией инстанциирования COM объектов
В какой-то степени Dependency Injection является интерпретацией идей создания объектов класса по его идентификатору, помноженной на идею создания некоторого универсального алгоритма для инстанциирования объектов на основе анализа кода.
Таким образом Dependency Injection для бинов из Java Spring мне представляется в каком то смысле развитием и переносом в новый контекст идей, изначально сформулированных в рамках СОМ технологии. При этом я совершенно не исключаю что СОМ технология также в свое время была развитием и переносом в новый контекст идей, изначально сформулированных где-то еще раньше. Я не историк, я разработчик, историческая точность для меня не стоит на первом месте.
Комментарии (44)
B13nerd
29.05.2023 16:34+1COM объект – это C++ класс
Поперхнулся и дальше не читал. Ничего, что по мнению создателей "COM is independent of implementation language"?
rukhi7 Автор
29.05.2023 16:34-2Действительно я не сталкивался с разработкой СОМ объектов на чем нибудь кроме С++. Я писал DirectShow фильтры. Чесно говоря несколько лет прошло прежде чем я обратил внимание что там используется СОМ в каком то виде.
Я прошу вас прочитать все таки дальше, я надеюсь что то интересное все таки получится найти.
Извините что заставил поперхнуться :), но дальше читайте все таки с осторожностью, я надеюсь найдется причина поперхнуться не по поводу ошибки.
aegoroff
29.05.2023 16:34+5Ух ты! Давненько не слышал чтобы это кому-либо было нужно, разве что для поддержки легаси. А действительно на этом еще кто-то серьезно разрабатывает? мне казалось что разработка под виндоус уже почти уехала на C#, а что-то низкоуровневое можно и на Win32 API заделать.
rukhi7 Автор
29.05.2023 16:34-1Как известно, все новое это хорошо забытое старое. Это не разработка уехала, это разработчики улетели в облака.
aegoroff
29.05.2023 16:34+3Т.е. вы на полном серьезе предлагаете вернуться к этому ужасу регистрации COM объектов в реестре, полуавтоматическому управлению памятью, вместо удобного автоматического, предлагаемого .NET, ко всей этой возне с заголовочными файлами и файлами реализации, к более сложному синтаксису C++, по сравнению с C# ну и так далее. Очень напоминает луддитов, которые боролись с неизбежным :)
rukhi7 Автор
29.05.2023 16:34+2Вам не предлогаю, а тем кому надо работать с DirectX, например, без этого не обойтись.
Если вы не в курсе не вся работа сосредоточена в облаках, некоторым все также приходится работать на разных уровнях приближения к земле, а земля как известно больше чем на треть состоит из железа.
aegoroff
29.05.2023 16:34Я вроде ни слова не сказал про облака - вы сами придумали это. Я лично имел ввиду классические Windows приложения. Вы их очень легко можете писать с использованием того же WinForms (весьма тонкая обертка над Win32 API) ну или WPF (немного более толстая), разницу в скорости, на более, менее нормальном железе вы не увидите, зато увидите разницу в скорости разработки. Это раз.
Далее, если уж очень хочется низкоуровщины, - можно и без COM, используя Win32 API, ну а совсем если хотите bleeding edge - можно использовать тот же Rust (за который Microsoft в целом и Марк Руссинович в частности очень сильно топят), тем более что есть вполне сносный крейт для этого (https://crates.io/crates/windows). Вы получите и современный язык, без кучи тараканов, и отсутствие сборки мусора, за что вы сильно переживаете. Это два
rukhi7 Автор
29.05.2023 16:34+1про облака - это мне так показалось - это да.
WPF вроде как обертка не над Win32 API, а над DirectX-м.
По поводу: хочется низкоуровщины, это вроде как вы немного передергиваете, ее никогда не хочется, с ней приходится иметь дело. Если вы нашли способ работать не вспоминая про низкоуровневые проблемы, ну хорошо. Но ведь кто то эту низкоуровщину вам обеспечивает, а у вас это звучит так что это какая то грязная работа недостойная светлых разработчиков.
Сборка мусора меня ни в каком виде не беспокоит, меня беспокоит что обсуждение сводится только к обсуждению ее наличия.
И тараконов можно развести на любом языке, это не от языка зависит, а от того кто на нем пишет, я так думаю.
KanuTaH
29.05.2023 16:34+2удобного автоматического, предлагаемого .NET
Это там, где момент уничтожения объекта в общем случае предсказать невозможно, а вместо полноценного RAII костыль в виде
IDisposable
? Спасибо, конечно, но... Мягко говоря, это не всем подходит.nronnie
29.05.2023 16:34IDisposable.Dispose()
это, по сути, и есть "уничтожение объекта". А GC это всего лишь уплотнение кучи. Можно сказать, что такого как "освобождение памяти" в .NET просто нет - куча просто уплотняется и все.KanuTaH
29.05.2023 16:34+1Ага, только вот это именно что нисколько не удобный и отнюдь не автоматический костыль, сделанный с расчётом на то, что "конечный пользователь" класса, реализующего
IDisposable
, не забудет обернуть его вusing
.
aegoroff
29.05.2023 16:34А GC это всего лишь уплотнение кучи
Это далеко не всегда так. Куча уплотняется лишь при сборке мусора второго поколения, при этом останавливаются все рабочие потоки. В нулевом и первом поколении никакого уплотнения нет. Далее, куча больших объектов (более 85000 байт) или LOH вообще не трогается (это слишком накладно двигать такие объекты), а учитывая что наверно 99% в ней это массивы и другие коллекции (мне сложно представить себе обычный объект такого размера), со временем, при длительно работающем процессе, который выделяет много подобных объектов, становящихся затем мусором - легко можно получить неслабую такую фрагментацию этой кучи и как следствие постоянный рост рабочего набора процесса. В общем, не так все радужно и с автоматическим мусоровозом.
aegoroff
29.05.2023 16:34Это там, где момент уничтожения объекта в общем случае предсказать невозможно
А это и не нужно для чисто managed объектов
а вместо полноценного RAII костыль в виде
IDisposable
этот паттерн придуман не для этого, а для освобождения особо ценных ресурсов, количество которых ограничено (открытые сокеты, файлы, соединения с БД и т.д.), которые неплохо бы освобождать сразу после использования. Для всего остального есть GC.
Мягко говоря, это не всем подходит.
Да, это совсем не нужно использовать для освобождения классических managed ресурсов, да и невозможно это с этим паттерном в принципе.
Детальнее о работе с памятью стоит прочитать книгу К. Кокосы, она вроде так и называется, - управление памятью в .NET, и есть очень неплохой русский перевод.
Прочтение откроет вам глаза на то как ДЕЙСТВИТЕЛЬНО все работает
KanuTaH
29.05.2023 16:34+1этот паттерн придуман не для этого
Для чего "этого"? Этот паттерн придуман, чтобы имитировать в C# RAII из нормальных языков (так же как и try-with-resources в Java), но, как водится, не без колхоза, причем до появления
using
нужно было вообще писатьtry
/finally
блок дляIDisposable
вручную (собственно,using
и есть синтаксических сахар для такого блока).Прочтение откроет вам глаза на то как ДЕЙСТВИТЕЛЬНО все работает
Спасибо, я в курсе, как ДЕЙСТВИТЕЛЬНО все работает.
aegoroff
29.05.2023 16:34Откуда такая агрессия?
ричем до появления
using
нужно было вообще писатьtry
/finally
блок дляIDisposable
вручнуюusing был с самого начала https://www.ecma-international.org/wp-content/uploads/ECMA-334_1st_edition_december_2001.pdf страница 200 (в pdf файле страница 214). Потрудились бы для начала стандарт почитать, прежде чем писать такую ересь - перехожу на ваши методы разговора, уж простите
KanuTaH
29.05.2023 16:34Откуда такая агрессия?
Какая агрессия?
using был с самого начала
Да, действительно, using statement был доступен в C# с самой первой его версии, это у меня видимо история его появления как-то пересеклась в голове с историей
try-with-resources
из Java, которыйслужит аналогичным целямявляется аналогичным костылем, но появился лишь в Java 7.
nronnie
29.05.2023 16:34причем до появления using нужно было вообще писать try/finally блок для IDisposable вручную
Ого... Это когда такое было-то??? Оно там с самой первой версии - погуглите ECMA-334 от 2001 года. Эксперты дотнета, блин. Фейспалм.
verh010m
29.05.2023 16:34+2.NET много где использует COM - оно никуда не делось. Нам дали толстоватую обертку над многими вещами и кое кто теперь думает, что COM отмер
megasuperlexa
29.05.2023 16:34"много где" - а можно перечислить хотя бы 3-5 "где"?
rukhi7 Автор
29.05.2023 16:34это даже я вам скажу: вроде как и Excel и Word никуда не делись, и DirectShow вроде никто не отменил, все это все также связано с СОМ спецификацией, все это доступно из .NET
так же как какой нибудь 17, 22, 25 С++ стандарт не отменяет самый исходный С++ стандарт, так же и СОМ спецификацию никто не отменяет, она все также лежит в основе практически всех технологий на Windows.
megasuperlexa
29.05.2023 16:34офис и директшоу - это и было "много где"?
ок
rukhi7 Автор
29.05.2023 16:34Причем DirectX аж с неизбежностью (inevitably):
COM is an object-oriented programming model used by several technologies, including the bulk of the DirectX API surface. For that reason, you (as a DirectX developer) inevitably use COM when you program DirectX.
NeoCode
29.05.2023 16:34Идея то была хорошая, а вот реализация уж больно запутанная. Наверное следствие того, что тогда все делалось на весьма низкоуровневом С++.
nronnie
29.05.2023 16:34-1В СОМ была, к сожалению, такая изначально порочная и, как показало дальнейшее развитие, тупиковая вещь, как управление памятью с помощью подсчета ссылок.
rukhi7 Автор
29.05.2023 16:34+2Интересно чего же в ней тупикового? То что она работает уже 25 лет и всегда будет работать? То что она позволяет собственную, абсолютно контролируемую, стратегию управления памятью реализовать?
nronnie
29.05.2023 16:34-1Подсчет ссылок хорош когда у тебя простая ситуация. Когда же у тебя целый граф связанных объектов с циклами, то это превращается в настоящий ад (а особый ад это когда все это еще распределено под DCOM).
rukhi7 Автор
29.05.2023 16:34+1А не надо в циклах объекты создавать, это очень плохо для производительности в том числе. Поэтому когда что-то вот так превращается в ад, это не ссылки виноваты, а какая-то прокладка между креслом и монитором. Не обязательно та прокладка которая это разбирает, пытается смягчить :))) .
nronnie
29.05.2023 16:34Так ведь не объекты в циклах, а циклические связи между объектами:
A -> B -> C -> A
aegoroff
29.05.2023 16:34+3и, как показало дальнейшее развитие, тупиковая вещь, как управление памятью с помощью подсчета ссылок
Хм, а как же Rust с его Rc (и потокобезопасным Arc)? Это ведь тоже умный указатель со счетчиком ссылок. Все таки не такая и тупиковая вещь, как может показаться
Chaos_Optima
29.05.2023 16:34С чего это тупиковая вещь? На С++ это чуть ли не обязаловка, управление памятью напрямую организуется обычно в исключительных целях, и считается плохим тоном в реалиях современной разработки на С++.
nronnie
29.05.2023 16:34-1В С++ просто альтернативы нет. Из-за его низкоуровневой природы указателей. А, вот, в JavaScript (точнее в его распространенных реализациях), как раз, от подсчета ссылок в свое время отказались и переделали все на GC.
rukhi7 Автор
29.05.2023 16:34применение сборщиков мусора это, конечно не та тема, которую я хотел бы видеть в рамках обсуждения этой статьи.
Хотелось бы что то про сложность реализации, критерии ее расчета, сложность реализации кода в соответствии с СОМ спецификацией, ... про dependency injection наконец.
Вот, сдается мне, это облачные технологии плохо влияют на способности программистов, все сводится к тому, как бы найти то, что сделает всю работу за нас.
Chaos_Optima
29.05.2023 16:34Потому что там и нет в этом особой нужды, JS язык не для производительности кода. И в С++ были альтернативы да и сейчас есть всякие версии сборщиков мусора, только они не приживаются, потому что сборка мусора отнимает производительность (ну или ручное управление памятью). А если например использовать только unique_ptr то вообще можно получить подобие rust.
rukhi7 Автор
29.05.2023 16:34Интересно а для какой идеи такого уровня вы видели не запутанную реализацию?
Потом почему идея "была"? Если кто-то решил на какую то идею забить, это не значит что она для всех пропала.
Как говорится:
что один человек придумал, другой завсегда разобрать сможет!
nronnie
29.05.2023 16:34+3Статья припозднилась лет, этак, на 25, а в остальном все более-менее ок :))
Tinkz
29.05.2023 16:34+2Когда-то давно разрабатывал под COM на паскале (дельфи). До сих пор осталось несколько книжек. В одной, как сейчас помню, написано что-то вроде: "Вот-вот COM заработает на макос". Сейчас это, разве что, в экселе можно встретить.
itHauntsMe
29.05.2023 16:34Ещё читал про XPCOM от Mozilla, но ни разу не видел в использовании. Жаль, что технология не получила развития.
sshikov
Вот с этой ошибки все и начинается. С нежелания и возможно неумения просто открыть и почитать родную документацию. Хотя бы главную страницу, где написано, что такое данная технология, и зачем она.
COM — это Component Object Model, и если открыть первую же страницу в поиске по этому сочетанию, то там написано:
То есть, это модель компонентов софта, платформо независимая (не то чтобы у них это получилось, но попытки реализации под линукс вроде бы были), распределенная (DCOM), и тут нет ни слова про C++, потому что компоненты эти предполагалось сразу (и можно до сих пор) писать почти на чем угодно. То есть скажем на Perl хотите? Можно и на нем.
И ровно из всего этого уже логично вытекает все остальное — что есть правила, которые общие для всех языков реализации, и которые можно реализовать на разных языках, поэтому есть отдельный язык описания API, поэтому есть реестр классов, чтобы скриптовые языки типа VB Script могли делать как-то так:
Set objXL = CreateObject("Excel.Application")
и работать с функциональностью Excel, который внутри состоит из множества COM компонентов.
Ну и другие реализации конечно были. Больше всего пожалуй COM похожа на SOM от IBM, которая лежала в основе OS/2, и благополучно почила вместе с ней. А IDL например — вполне в духе CORBA.
rukhi7 Автор
Спасибо за замечание - исправил!
Кросплатформенность мне в работе совершенно не пригодилась, видимо поэтому я про нее и не в курсе был. Я пишу про то что я использовал и что реально работает.