Что такое Reg-Free COM: механизм использования COM без регистрации
· Обновлено: · Го Комура · COM, Reg-Free COM, Registration-Free COM, Разработка Windows, Устаревшие технологии
История изменений (1 обновлений, последнее 30 Aug 2026)
Журнал изменений этой статьи. Там, где версия до правки была заархивирована, она остаётся доступной для чтения по постоянной ссылке с DOI.
- Русский текст переписан как полноценный технический перевод, а не калька с японского. Утверждения статьи не менялись.
- Первая публикация
Цитирование статьи(DOI: 10.5281/zenodo.21619728)
Статья заархивирована на Zenodo. Ниже приведены DOI, который всегда ведёт к последней версии, и DOI, закреплённый за версией, которую вы читаете.
Го Комура (2026). Что такое Reg-Free COM: механизм использования COM без регистрации. KomuraSoft LLC. https://doi.org/10.5281/zenodo.21619728 https://comcomponent.com/ru/blog/2026/03/16/011-what-is-reg-free-com/
- DOI (последняя версия)
- 10.5281/zenodo.21619728
- DOI (эта версия)
- 10.5281/zenodo.21619729
В проектах с COM / ActiveX / OCX при каждом развёртывании и обновлении всплывает одна и та же грязь.
- нужен
regsvr32 - почти всегда требуются права администратора
- возникает конфликт с другой версией, которую поставило другое приложение
- удаление приложения задевает и другие продукты
- работает на машине разработчика, но не работает в чистом окружении
Эту трясину заметно уменьшает Reg-Free COM. Правда, вопреки названию, это не «волшебство, которое снимает все хлопоты с COM». Исчезает в первую очередь то, что тянет за собой глобальная регистрация. Сложности с разрядностью (bitness), зависимыми DLL, библиотеками типов и моделью потоков никуда не деваются.
flowchart TB
accTitle: Что исчезает и что остаётся в Reg-Free COM
accDescr: Рисунок показывает, что Reg-Free COM в первую очередь уменьшает хлопоты глобальной регистрации, а разрядность, зависимые DLL, библиотеки типов и сложность модели потоков не исчезают.
rf1["Reg-Free COM"] --> rf2["Хлопот глобальной регистрации меньше"]
rf1 --> rf3["Остаются и свои сложности"]
rf3 -.-> rf4["bitness / зависимые DLL / библиотеки типов"]
Рис. 1: Это не волшебство: отдельно ждут, что исчезнет, и что останется.
В этой статье Reg-Free COM разбирается прежде всего в контексте использования COM DLL / OCX в Windows-десктоп-приложении с хранением локально у приложения.
Для кого эта статья и какие допущения
Текст рассчитан на разработчиков, которые распространяют Windows-десктоп-приложение с существующими COM DLL / OCX и хотят уйти от regsvr32 и прав администратора. Предполагается, что основы COM (CLSID, ProgID, CoCreateInstance, in-proc сервер) уже встречались. Если это ещё расплывчато, быстрее сначала прочитать «Что такое COM / ActiveX / OCX - объясняем различия и связь между ними».
В процедурной части используются mt.exe и sxstrace из Windows SDK, поэтому предполагается среда с Visual Studio или Windows SDK.
1. Сначала вывод (коротко)
Сначала грубая, но полезная формулировка.
- Reg-Free COM — это способ хранить регистрационные сведения COM не в реестре, а в манифесте
- Во время выполнения при разрешении
CoCreateInstanceилиCLSIDFromProgIDсначала смотрят контекст активации (activation context) - Благодаря этому COM DLL / OCX можно держать private для каждого приложения
- Основные преимущества — удобное XCOPY-распространение, легче избегать конфликтов версий и меньше риск сломать удаление
- Однако проблема 32-бит / 64-бит никуда не исчезает. Записью манифеста её не обойти
- Кроме того, отдельно нужно продумывать зависимые DLL, библиотеки типов, ссылки на этапе проектирования и зависимость от нестандартной регистрации
- На практике это хорошо подходит, когда рядом с приложением хотят положить COM-компоненты только для него
Иными словами, Reg-Free COM — это механизм, который возвращает активацию COM на уровень отдельного приложения.
flowchart TB
accTitle: Меняется место регистрационных сведений
accDescr: Рисунок показывает, что Reg-Free COM хранит регистрационные сведения COM в манифесте, а не в реестре, и поскольку при разрешении CoCreateInstance сначала смотрят контекст активации, COM-компоненты можно держать private для каждого приложения.
mg1["Сведения хранят в манифесте"] --> mg2["При разрешении сначала контекст активации"]
mg2 --> mg3["COM можно держать private для приложения"]
mg3 -.-> mg4["Проще XCOPY и меньше конфликтов"]
Рис. 2: Суть в том, что вход разрешения смещается с реестра на манифест.
Карта знаний этой статьи
Reg-Free COM держит сведения о регистрации COM не в реестре, а в манифесте и при выполнении сначала смотрит контекст активации, чтобы разрешить DLL. Манифест приложения объявляет зависимую side-by-side assembly, манифест компонента держит сведения вроде CLSID и ProgID, которые раньше жили в реестре, — такое разделение ролей; встраивают через mt.exe, сбои разрешения отслеживают через sxstrace. Требование совпадения 32-bit и 64-bit при этом не снимается, а обращение с зависимыми DLL и библиотекой типов остаётся отдельным вопросом, поэтому схема подходит, чтобы сделать app-local существующие наработки рабочего стола вроде VB6, MFC и WinForms, но не подходит, когда нужно совместное использование COM на всю машину. В .NET 5+ / .NET 8 comhost создают через EnableComHosting, а включение EnableRegFreeCom позволяет вывести side-by-side манифест для Reg-Free COM.
flowchart LR
accTitle: Карта знаний Reg-Free COM
accDescr: Схема, которая показывает, что Reg-Free COM через контекст активации и механизм side-by-side assembly держит сведения о регистрации COM на уровне приложения; разделение ролей манифеста приложения и манифеста компонента; сборку через mt.exe и разбор сбоев через sxstrace; и связь с comhost в .NET 5+
reg_free_com["Reg-Free COM (без регистрации)"]
activation_context["контекст активации (activation context)"]
win32_application_manifest["манифест приложения (Win32 side-by-side)"]
component_manifest["манифест компонента (assembly manifest)"]
side_by_side_assembly["side-by-side assembly"]
bitness_match_requirement["требование совпадения разрядности"]
regsvr32["regsvr32"]
type_library["библиотека типов (TLB)"]
mt_exe["mt.exe (Manifest Tool)"]
sxstrace["sxstrace"]
assembly_searching_sequence["порядок поиска private assembly"]
dotnet[".NET (начиная с Core)"]
comhost["COM host (*.comhost.dll)"]
enable_com_hosting["EnableComHosting"]
enable_regfreecom["EnableRegFreeCom"]
vb6["Visual Basic 6.0 (VB6)"]
clsid["CLSID (Class ID)"]
progid["ProgID (Programmatic Identifier)"]
com["COM (Component Object Model)"]
activex["ActiveX"]
ocx["OCX"]
mfc["MFC (Microsoft Foundation Classes)"]
windows_forms["Windows Forms"]
machine_wide_com_sharing["общее использование COM на всю машину"]
reg_free_com -->|"использует"| activation_context
reg_free_com -->|"использует"| win32_application_manifest
reg_free_com -->|"использует"| component_manifest
win32_application_manifest -->|"требует"| side_by_side_assembly
component_manifest -->|"реализует"| side_by_side_assembly
reg_free_com -->|"требует"| bitness_match_requirement
reg_free_com -.->|"преемник"| regsvr32
component_manifest -.->|"использует"| type_library
reg_free_com -->|"настраивается"| mt_exe
reg_free_com -->|"проверяется"| sxstrace
component_manifest -->|"настраивается"| mt_exe
reg_free_com -->|"требует"| assembly_searching_sequence
dotnet -.->|"использует"| comhost
comhost -->|"требует"| enable_com_hosting
comhost -->|"настраивается"| enable_regfreecom
enable_regfreecom -->|"реализует"| reg_free_com
vb6 -.->|"использует"| type_library
component_manifest -->|"использует"| clsid
component_manifest -.->|"использует"| progid
reg_free_com -->|"использует"| com
reg_free_com -->|"использует"| activex
reg_free_com -->|"использует"| ocx
reg_free_com -->|"рекомендуется для"| vb6
reg_free_com -->|"рекомендуется для"| mfc
reg_free_com -->|"рекомендуется для"| windows_forms
reg_free_com -->|"не рекомендуется"| machine_wide_com_sharing
На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 26, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle
2. Что эта статья понимает под Reg-Free COM
Reg-Free COM — сокращение от Registration-Free COM. По-японски это иногда описывают как «COM без регистрации».
«Без регистрации» здесь значит: чтобы пользоваться COM, не нужно полностью опираться на глобальную регистрацию в реестре — HKCR / CLSID / InprocServer32 и подобное.
Это не значит, что сам COM исчезает, и не значит, что GUID становится не нужен.
Основные предметы рассмотрения в этой статье такие:
- нативные COM DLL
- COM-серверы на базе ATL
- ActiveX / OCX
- COM-взаимодействие на базе .NET Framework
- публикация через COM host в .NET 5+ / .NET 8
И наоборот, в этой статье хочется особо подчеркнуть два момента.
- Reg-Free COM — это разговор об «активации»
- Распространение типовой информации и настройка ссылок на этапе проектирования могут оставаться отдельным вопросом
Если смешать эти темы, разговор становится куда мутнее.
flowchart TB
accTitle: Две темы, которые нельзя смешивать
accDescr: Рисунок показывает, что Reg-Free COM — разговор об активации, а распространение типовой информации и ссылки на этапе проектирования могут оставаться отдельным вопросом, поэтому смешение этих двух тем мутит разговор.
pt1["Reg-Free COM"] --> pt2["Разговор об активации"]
pt3["Типовая информация и ссылки на этапе проектирования"] --> pt4["Остаётся отдельным вопросом"]
pt2 -.-> pt5["Если смешать, разговор мутнеет"]
pt4 -.-> pt5
Рис. 3: Разговор о времени выполнения и разговор о времени проектирования с самого начала держат отдельно.
3. Сначала общая картина на одной схеме
До схемы заранее определим четыре термина, которые в статье встречаются снова и снова.
| Термин | Смысл |
|---|---|
| Контекст активации (activation context) | Структура данных времени выполнения, которая держит «этот поток сейчас использует какую сборку какой версии». CoCreateInstance смотрит сюда раньше, чем в реестр |
| side-by-side assembly | Механизм Windows, который позволяет сосуществовать на одной машине компонентам с одним именем, но разными версиями. Их идентифицирует манифест; assemblyIdentity здесь играет роль ярлыка |
| Манифест приложения | XML на стороне EXE: «от каких side-by-side assembly я завишу» |
| Манифест компонента (assembly manifest) | XML на стороне компонента: «какие файлы входит в эту сборку и какие COM-классы она публикует». Сюда приходят сведения, которые раньше жили в реестре |
Контекст активации держат по потокам. COM передаёт контекст активации создающего потока на поток-хозяин и только потом вызывает LoadLibrary и DllGetClassObject, поэтому на вызывающей стороне отдельная подготовка не нужна.
После этого быстрее один раз посмотреть на общую картину.
flowchart LR
APP["MyApp.exe"] --> AM["Application Manifest"]
AM --> DEP["dependentAssembly"]
DEP --> CM["Component / Assembly Manifest"]
CM --> META["file / comClass / typelib"]
META --> DLL["VendorControl.dll / .ocx"]
APP --> ACTX["Activation Context"]
ACTX --> COM["CLSIDFromProgID / CoCreateInstance"]
COM --> DLL
Рис. 4: От манифеста приложения идут к манифесту компонента и доходят до DLL, не пользуясь реестром.
В обычном COM при вызове CoCreateInstance реестр обходят, чтобы решить, какую DLL загружать.
В Reg-Free COM перед этим сначала смотрят сейчас активный контекст активации и разрешают по информации манифеста, которая в нём записана.
Из-за этого приложениям A и B на одной машине становится проще работать, держа разные версии COM-компонентов одного семейства. Культуру совместного использования COM это чуть возвращает в сторону локальности для конкретного приложения.
4. Почему обычное распространение COM обычно оказывается тяжёлым
Обычное распространение COM тяжело не столько из-за самого COM, сколько из-за допущения о глобальной регистрации.
Чтобы использовать класс COM, нужна примерно такая информация.
| Сведения | Роль |
|---|---|
| CLSID | GUID, однозначно идентифицирующий класс |
| ProgID | Имя, удобное для человека |
| InprocServer32 | Какую DLL загружать |
| ThreadingModel | Допущения вроде Apartment / Both |
| TypeLib | Типовая информация |
Когда всё это попадает в реестр, это удобно для машины в целом: нескольким приложениям проще пользоваться сообща.
Однако на практике это совместное использование бьёт в обратную сторону.
- установка одного продукта перезаписывает COM-регистрацию другого продукта
- деинсталлятор «думает, что удаляет только своё», а на деле ломает общий COM
- регистрация, которая случайно есть на машине разработчика, отсутствует на боевой машине
- регистрации для 32-бит и 64-бит не сходятся, и только симптомы жутко расходятся
Иными словами, людей куда чаще беспокоит не сам COM, а модель распространения. Reg-Free COM — механизм, который снижает боль именно этой модели распространения.
flowchart TB
accTitle: Как глобальное совместное использование бьёт в обратную сторону
accDescr: Рисунок показывает, что регистрационные сведения COM в реестре удобны для совместного использования всей машиной, но на практике оборачиваются перезаписью другим продуктом, задеванием при удалении и расхождением среды разработки и боя, так что людей чаще беспокоит модель распространения, а не сам COM.
gs1["Сведения делят на всю машину"] --> gs2["Удобно: несколько приложений могут пользоваться"]
gs1 --> gs3["На практике совместное использование бьёт в обратную сторону"]
gs3 -.-> gs4["Перезапись / задевание / расхождение сред"]
gs3 --> gs5["Людей беспокоит модель распространения"]
Рис. 5: Источник тяжести — не сам COM, а допущение глобальной регистрации.
5. Как работает Reg-Free COM
5.1 Зависимости указывают в манифесте приложения
Сначала приложение в манифесте приложения указывает, от каких side-by-side assembly оно зависит.
Этот манифест можно оформить любым из двух способов:
- положить рядом с EXE, например как
MyApp.exe.manifest - встроить в EXE как ресурс
На практике часто выбирают так: внешний файл — если важно, чтобы распространение и замена были наглядными; встраивание — если в приоритете устойчивость и простота распространения.
Если существуют одновременно внешняя и встроенная версии, приоритет имеет манифест на файловой системе.
5.2 Сведения о COM описывают в манифесте компонента
Далее COM-сторона переносит сведения, которые раньше жили в реестре, в манифест компонента.
Сюда входит, например, такая информация:
comClassclsidprogidthreadingModeltypelib- при необходимости — proxy / stub, классы окон и т. п.
То есть идея в том, чтобы вместо реестра описывать «облик» COM на XML.
Этот манифест также можно оформить любым из двух способов:
- положить отдельным файлом рядом с DLL
- встроить в DLL как ресурс
На практике встраивание в DLL как private assembly обычно даёт меньше сбоев. Отдельный файл нагляднее, но легче споткнуться о соответствие между именем файла и assemblyIdentity, о место размещения и о забытые копирования.
flowchart TB
accTitle: Как держат манифест компонента
accDescr: Рисунок показывает, что сведения вроде comClass, clsid и typelib, которые раньше жили в реестре, манифест компонента держит в XML, и можно положить их отдельным файлом рядом с DLL или встроить в DLL как ресурс; на практике встраивание обычно даёт меньше сбоев.
cm1["Сведения из реестра держат в XML"] --> cm2["Положить отдельным файлом"]
cm1 --> cm3["Встроить в DLL"]
cm2 -.-> cm4["Легче споткнуться о забытое копирование"]
cm3 -.-> cm5["На практике обычно меньше сбоев"]
Рис. 6: Содержание одно и то же, но выбор места влияет на частоту сбоев.
5.3 Во время выполнения сначала смотрят контекст активации
Здесь заключена суть Reg-Free COM.
Когда приложение вызывает CLSIDFromProgID или CoCreateInstance, среда выполнения COM смотрит активный контекст активации.
Если там есть нужные сведения ProgID → CLSID и CLSID → DLL, разрешение выполняется без обращения к реестру.
И наоборот, если в манифесте не хватает нужных сведений, происходит откат к обычному разрешению по регистрации. Из-за этого поведения возникает ловушка: на машине разработчика приложение случайно работает. Кажется, что переход на Reg-Free состоялся, а на самом деле помогает локальная регистрация.
Это самая коварная ловушка Reg-Free COM.
flowchart TB
accTitle: Ловушка отката к разрешению по регистрации
accDescr: Рисунок показывает, что при разрешении CoCreateInstance сначала смотрят контекст активации, но если в манифесте не хватает сведений, происходит откат к обычному разрешению по регистрации, и на машине разработчика приложение может случайно работать за счёт локальной регистрации.
ac1["Сначала смотрят контекст активации"] --> ac2{"В манифесте хватает сведений?"}
ac2 -->|"Хватает"| ac3["Разрешают без реестра"]
ac2 -->|"Не хватает"| ac4["Откат к разрешению по регистрации"]
ac4 -.-> ac5["Ловушка: на машине разработчика случайно работает"]
Рис. 7: Тихий откат — главная ловушка этого механизма.
6. В чём польза
На практике преимущества Reg-Free COM видны вполне отчётливо.
6.1 Удобное XCOPY-распространение
Все нужные файлы можно собрать в папке приложения, поэтому установщик и процедура регистрации становятся легче.
Разумеется, если запись идёт в Program Files, вопрос прав остаётся отдельной темой, но по крайней мере административную работу, связанную с регистрацией COM, обычно удаётся сократить.
6.2 Легче сократить конфликты версий
Даже если на одной машине несколько версий COM-компонента, используемую версию проще развести по приложениям. Заметно легче избежать неприятности вроде «поведение внезапно изменилось из-за установки другого продукта».
6.3 Часто не требует крупных изменений в существующем коде
Reg-Free COM — механизм, который меняет способ разрешения, а не в корне переделывает то, как существующий код делает вызовы.
Поэтому при удачном сценарии его можно внедрить, почти не трогая код на стороне CoCreateInstance.
6.4 Удаление и откат становятся проще
Поскольку всё замкнуто на уровне приложения, обновление и откат становятся заметно прямее. Утрируя, легче принять подход «просто заменить всю папку целиком».
flowchart TB
accTitle: Польза от замыкания на уровне приложения
accDescr: Рисунок показывает, что замыкание COM-компонентов на уровне приложения даёт удобное XCOPY-распространение, более лёгкое избегание конфликтов версий, внедрение почти без правок существующего кода и более простое удаление и откат.
cl1["Замыкают на уровне приложения"] --> cl2["Удобное XCOPY-распространение"]
cl1 --> cl3["Меньше конфликтов версий"]
cl2 -.-> cl5["Можно заменить папку целиком"]
cl3 -.-> cl4["Код вызовов почти не трогают"]
Рис. 8: Все преимущества растут из одного свойства: компонент замкнули.
7. Где это подходит, а где — нет
7.1 Ситуации, где это подходит
В следующих случаях Reg-Free COM оказывается весьма сильным решением.
| Ситуация | Насколько подходит |
|---|---|
| Нужно поставлять COM DLL / OCX, предназначенные только для конкретного приложения | Очень хорошо |
| Нужно, чтобы на одном ПК сосуществовало несколько версий | Очень хорошо |
| Нужно избежать проблем с регистрацией компонентов от поставщика | Хорошо |
| Нужно использовать ActiveX / OCX приватно в существующем десктопном приложении | Хорошо |
| Нужно облегчить распространение, не меняя сильно существующие вызовы | Хорошо |
Как правило, это хорошо сочетается с бизнес-десктоп-приложениями, инструментами интеграции с оборудованием и существующими наработками на VB6 / MFC / WinForms.
7.2 Ситуации, где это не подходит или требует осторожности
С другой стороны, есть случаи, которые стоит рассматривать осторожнее.
| Ситуация | Комментарий |
|---|---|
| Нужно, чтобы COM был общим для всей машины | Польза Reg-Free невелика |
| Разрядность (bitness) не совпадает | Reg-Free это не решает |
| Сильная зависимость от нестандартных регистрационных сведений или собственного установщика | Трудно выразить в манифесте |
| Не продумано распространение зависимых DLL или среды выполнения VC++ | В итоге споткнётесь в другом месте |
| Проектные инструменты или настройка ссылок в IDE предполагают наличие реестра | Нужен отдельный порядок эксплуатации |
Особенно важен последний пункт. Reg-Free COM помогает с активацией во время выполнения, но не меняет одним махом то, на что рассчитан UI настройки ссылок на этапе проектирования.
flowchart TB
accTitle: Время выполнения выигрывает, время проектирования остаётся
accDescr: Рисунок показывает, что Reg-Free COM помогает с активацией во время выполнения, но если инструменты проектирования и настройка ссылок в IDE предполагают реестр, это само по себе не меняется и нужен отдельный порядок эксплуатации.
rt1["Активация во время выполнения"] --> rt2["Reg-Free помогает"]
dt1["UI ссылок на этапе проектирования"] --> dt2["Иногда предполагает реестр"]
dt2 -.-> dt3["Нужен отдельный порядок эксплуатации"]
Рис. 9: Даже если механизм запуска уже собран, механизм разработки собирают отдельно.
8. Распространённые заблуждения
8.1 «С Reg-Free COM проблема разрядности исчезает»
Не исчезает. 32-битный процесс может загружать только 32-битные in-proc COM DLL, а в 64-битный попадают только 64-битные DLL. В этом отношении Reg-Free ничего не меняет по сравнению с прежним подходом.
8.2 «С Reg-Free COM реестр вообще не используется»
Это тоже неверно. Если в манифесте не хватает нужных сведений, происходит откат к обычному разрешению по регистрации. Поэтому успешный запуск на машине разработчика ещё не означает, что конфигурация Reg-Free корректна.
8.3 «С Reg-Free COM вопрос библиотек типов тоже решается автоматически»
Здесь верно лишь наполовину.
В манифесте действительно можно указать сведения typelib, но настройка ссылок в VBA, #import в C++, генерация ссылок на этапе проектирования на стороне .NET — обращение с типовой информацией обычно требует отдельного проектирования.
Reg-Free COM в первую очередь про то, чтобы приложение вообще запускалось. Как вести типизированную разработку — следующий, отдельный вопрос.
flowchart TB
accTitle: Порядок: сначала запуск, потом типы
accDescr: Рисунок показывает, что в манифесте можно указать typelib, но настройка ссылок VBA, import в C++ и генерация ссылок .NET на этапе проектирования требуют отдельного проектирования; Reg-Free COM — сначала про запуск, типизированная разработка — следующий вопрос.
st1["Собирают Reg-Free COM"] --> st2["Сначала добиваются запуска"]
st2 --> st3["Затем — как разрабатывать с типами"]
st3 -.-> st4["Ссылки VBA / import / генерация interop"]
Рис. 10: То, что typelib можно записать, и то, что типовая информация уже в эксплуатации, — разные вещи.
8.4 «С Reg-Free COM любой ActiveX / OCX подойдёт как есть»
Это тоже опасное заблуждение. Если компонент опирается на стандартные регистрационные сведения COM, продвигаться легко, но если он сильно зависит от собственных настроек реестра, дополнительной установки, обработки лицензий или набора других модулей, переход на Reg-Free резко усложняется.
8.5 «Reg-Free COM в .NET Framework и .NET 8 — примерно одно и то же»
Сходство есть, но набор инструментов заметно различается.
Контекст .NET Framework + RegAsm и контекст .NET 5+ / .NET 8 + comhost — разные площадки, даже если речь об одном и том же COM.
9. Различия между нативным COM, .NET Framework, .NET 5+ и .NET 8
Здесь легко всё перепутать, поэтому разберём отдельно.
| Направление | Кратко |
|---|---|
| Нативные COM DLL / OCX | В основе — связка манифеста приложения и манифеста компонента |
| COM-взаимодействие на базе .NET Framework | Помимо манифеста приложения в стиле Win32 нужен ещё манифест на стороне управляемого компонента |
| Публикация COM в .NET 5+ / .NET 8 | EnableComHosting создаёт COM host, EnableRegFreeCom позволяет сгенерировать манифест для Reg-Free |
9.1 COM на базе .NET Framework
В COM на базе .NET Framework образуется двухуровневая структура: манифест приложения в стиле Win32 на стороне COM-приложения и манифест компонента на стороне управляемого компонента.
То есть манифестов на один больше, чем у нативного COM. Ограничения на имя и идентификатор ресурса из раздела 10.4 точно так же действуют и на этот манифест компонента.
9.2 Публикация COM в .NET 5+ / .NET 8
В .NET 5+ / .NET 8 точкой входа для публикации COM становится *.comhost.dll.
Если дополнительно указать EnableRegFreeCom=true, будет сгенерирован side-by-side манифест для Reg-Free COM.
Что типовая информация — отдельный вопрос, сказано в разделе 8.3, но в .NET Core / .NET 5+ это меняется ещё сильнее. Это уже не тот мир, где, как во времена .NET Framework, TLB естественным образом появляется из сборки, поэтому если нужно типизированное использование, генерацию, встраивание и регистрацию TLB нужно собирать отдельно. Конкретные шаги разобраны в «Как использовать DLL на .NET 8 из VBA с типизацией — публикация COM и TLB через dscom».
flowchart TB
accTitle: Reg-Free COM начиная с .NET 5
accDescr: Рисунок показывает, что начиная с .NET 5 EnableComHosting создаёт comhost.dll как точку входа для публикации COM, включение EnableRegFreeCom выводит side-by-side манифест для Reg-Free COM, а генерацию и регистрацию TLB собирают отдельно.
dn1["Сборка с EnableComHosting"] --> dn2["Точкой входа становится comhost.dll"]
dn2 --> dn3["Включают EnableRegFreeCom"]
dn3 --> dn4["Выводится манифест для Reg-Free"]
dn4 -.-> dn5["Обращение с TLB собирают отдельно"]
Рис. 11: В .NET 8 два свойства дают площадку, но типовая информация — отдельная работа.
10. Набросок минимальной конфигурации
Здесь показан минимальный набросок того, как MyApp.exe использует Vendor.CameraControl.dll через Reg-Free COM.
10.1 Набросок структуры файлов
MyApp.exe
MyApp.exe.manifest
Vendor.CameraControl.Asm.manifest
Vendor.CameraControl.dll
Vendor.Helper.dll
В примере выше предполагается, что манифест компонента лежит отдельным файлом. Его можно встроить в DLL, но тогда на имя сборки и идентификатор ресурса накладываются фиксированные ограничения. Шаги — в разделе 10.4.
10.2 Набросок манифеста приложения
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
type="win32"
name="KomuraSoft.MyApp"
version="1.0.0.0"
processorArchitecture="amd64" />
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Vendor.CameraControl.Asm"
version="1.0.0.0"
processorArchitecture="amd64" />
</dependentAssembly>
</dependency>
</assembly>
10.3 Набросок манифеста компонента
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
type="win32"
name="Vendor.CameraControl.Asm"
version="1.0.0.0"
processorArchitecture="amd64" />
<file name="Vendor.CameraControl.dll">
<comClass
clsid="{01234567-89AB-CDEF-0123-456789ABCDEF}"
progid="Vendor.CameraControl.1"
threadingModel="Apartment"
tlbid="{89ABCDEF-0123-4567-89AB-CDEF01234567}" />
<typelib
tlbid="{89ABCDEF-0123-4567-89AB-CDEF01234567}"
version="1.0"
helpdir="" />
</file>
</assembly>
В этом примере по-настоящему важны не детали XML, а то, что dependentAssembly на стороне приложения и assemblyIdentity на стороне компонента совпадают.
Если здесь возникает рассогласование, получается сбой запуска, причину которого по тексту ошибки не видно.
GUID и имена выше — лишь пример для иллюстрации. На практике их нужно правильно указывать в соответствии с CLSID / TLBID / ProgID и моделью потоков, которые реально предоставляет компонент.
flowchart TB
accTitle: Требование совпадения двух манифестов
accDescr: Рисунок показывает, что dependentAssembly на стороне приложения и assemblyIdentity на стороне компонента должны совпадать по имени и версии, и если они расходятся, получается сбой запуска, причину которого по тексту ошибки не видно.
mm1["dependentAssembly на стороне приложения"] --> mm3{"Имя и версия совпадают?"}
mm2["assemblyIdentity на стороне компонента"] --> mm3
mm3 -->|"Совпадают"| mm4["Разрешение проходит"]
mm3 -->|"Расходятся"| mm5["Сбой запуска без видимой причины"]
Рис. 12: Важнее деталей XML то, совпадают ли ярлыки с обеих сторон.
10.4 Куда класть манифест и как встраивать
Именно здесь на шагах чаще всего спотыкаются. От того, кладёте ли вы отдельный файл или встраиваете в двоичный файл, зависит, какое имя можно поставить.
side-by-side ищет private assembly относительно папки приложения в таком порядке.
- Папка WinSxS
<appdir>\<assemblyname>.DLL<appdir>\<assemblyname>.manifest<appdir>\<assemblyname>\<assemblyname>.DLL<appdir>\<assemblyname>\<assemblyname>.manifest
Если раньше находится DLL с тем же именем, что у сборки, поиск на этом останавливается. Отсюда следует, что работают только два варианта.
| Способ размещения | Имя сборки | Файл | Идентификатор ресурса при встраивании |
|---|---|---|---|
| Отдельный файл | Имя другое, чем у DLL. Пример: Vendor.CameraControl.Asm |
Vendor.CameraControl.Asm.manifest рядом с DLL |
Не встраивают |
| Встраивание в DLL | Имя может совпадать с DLL. Пример: Vendor.CameraControl |
Только Vendor.CameraControl.dll |
1 |
То есть если, как в примерах 10.2 и 10.3, используется имя Vendor.CameraControl.Asm, это приём отдельного файла. При переходе на встраивание name в assemblyIdentity меняют на Vendor.CameraControl и то же имя выравнивают на стороне dependentAssembly.
flowchart TB
accTitle: Как поиск останавливается
accDescr: Рисунок показывает, что side-by-side ищет private assembly от WinSxS к папке приложения и останавливается, как только находит DLL с тем же именем, что у сборки, поэтому при отдельном файле имя сборки нужно делать отличным от имени DLL.
se1["Начинают поиск private assembly"] --> se2["Ищут DLL с тем же именем, что у сборки"]
se2 -->|"Нашли"| se3["Поиск на этом останавливается"]
se2 -->|"Не нашли"| se4["Ищут одноимённый файл manifest"]
se3 -.-> se5["При отдельном файле имена делают разными"]
Рис. 13: Ограничение на имена следует из того, что поиск останавливается на середине.
Есть ещё одно ограничение, на котором легко ошибиться. Манифест компонента нельзя положить в ресурсы EXE. В EXE можно положить только манифест приложения.
Для встраивания используют mt.exe (Manifest Tool) из Windows SDK. Запускайте из командной строки разработчика Visual Studio.
rem 1. Перед встраиванием сначала прогоняем проверку синтаксиса
mt.exe -manifest MyApp.exe.manifest -validate_manifest
mt.exe -manifest Vendor.CameraControl.manifest -validate_manifest
rem 2. Встраиваем манифест приложения в EXE
mt.exe -manifest MyApp.exe.manifest -outputresource:MyApp.exe;#1
rem 3. Встраиваем манифест компонента в DLL (идентификатор ресурса — 1)
mt.exe -manifest Vendor.CameraControl.manifest -outputresource:Vendor.CameraControl.dll;#1
rem 4. Проверяем, что встроилось: извлекаем и сравниваем
mt.exe -inputresource:Vendor.CameraControl.dll;#1 -out:extracted.manifest
Если извлечённый четвёртой командой extracted.manifest сравнить с исходным XML, можно отсечь случай «думали, что встроили, а внутри ничего нет».
flowchart TB
accTitle: Шаги встраивания через mt.exe
accDescr: Рисунок показывает поток mt.exe: сначала прогоняют проверку синтаксиса validate_manifest, затем встраивают манифест приложения в EXE, манифест компонента в DLL с идентификатором ресурса 1 и в конце извлекают и сравнивают с исходным XML.
mt1["Прогоняют проверку синтаксиса"] --> mt2["Встраивают сторону приложения в EXE"]
mt2 --> mt3["Встраивают сторону компонента в DLL (ID 1)"]
mt3 --> mt4["Извлекают и сравнивают с исходным XML"]
Рис. 14: Встраивание гоняют как одну процедуру, включая проверку.
У mt.exe есть два важных ограничения.
- Файлы, на которые ссылается манифест, должны лежать в том же каталоге, что и манифест. Если написали
<file name="Vendor.CameraControl.dll">, эту DLL кладут рядом с манифестом и только потом запускают. Если место сборки и место хранения манифеста разъехались, здесь процесс останавливается - Если в
-outputresourceопустить идентификатор ресурса, используетсяCREATEPROCESS_MANIFEST_RESOURCE(= 1). Чтобы случайный переход в 1 не путал,;#1лучше указывать явно — так читать проще
Если нужно только заменить уже встроенное, можно -updateresource:<файл>;#1. Это то же самое, что передать одинаковые аргументы в -inputresource и -outputresource.
10.5 Порядок проверки, включая чистое окружение
Для Reg-Free COM «заработало на машине разработчика» почти ничего не значит. Всегда остаётся возможность, что просто помогала локальная регистрация в реестре. Проверяют в таком порядке.
- Собрать и сложить комплект поставки в одну папку. EXE, манифесты, COM DLL, зависимые DLL, среда выполнения VC++, proxy / stub DLL
- Подготовить проверочную среду. Идеально — среда, где этот COM ни разу не регистрировали. С Windows Sandbox каждый раз можно начинать с чистого состояния
- Сначала убедиться, что в этой среде целевой COM не зарегистрирован. Командами ниже ошибка «ключ не найден» как раз и значит, что регистрации нет
- Скопировать папку и запустить как есть. Ни установщик, ни
regsvr32не запускают - Дойти до создания COM-объекта. Одного запуска мало: компоненты с отложенным созданием так не проверяются. Нужно пройти экран или функцию, где реально вызывается
CoCreateInstance - Если не получилось — снять журнал по шагам из раздела 11.2
Команды для шага 3 такие.
reg query "HKCR\CLSID\{01234567-89AB-CDEF-0123-456789ABCDEF}" /reg:64
reg query "HKCR\CLSID\{01234567-89AB-CDEF-0123-456789ABCDEF}" /reg:32
reg query "HKCR\Vendor.CameraControl.1"
Представления реестра 32-бит и 64-бит — разные вещи, поэтому обязательно смотрят сторону, которая совпадает с разрядностью приложения. Надёжнее глянуть и /reg:64, и /reg:32.
Если хочется проверить на машине разработчика, сначала снимают регистрацию командой regsvr32 /u Vendor.CameraControl.dll. Но в среде, где тот же COM используют другие продукты, это даёт побочные эффекты, поэтому безопаснее готовить чистое окружение.
flowchart TB
accTitle: Поток проверки в чистом окружении
accDescr: Рисунок показывает поток: комплект поставки складывают в одну папку, готовят проверочную среду, где целевой COM никогда не регистрировали, сначала подтверждают отсутствие регистрации, копируют папку и запускают как есть, затем проходят функцию, где вызывается CoCreateInstance.
cv1["Складывают комплект поставки"] --> cv2["Готовят чистое проверочное окружение"]
cv2 --> cv3["Сначала подтверждают, что не зарегистрирован"]
cv3 --> cv4["Копируют и запускают как есть"]
cv4 --> cv5["Доходят до функции, где создаётся COM"]
cv5 -.->|"Если сбой"| cv6["Снимают журнал и разбирают"]
Рис. 15: Проверка «не зарегистрирован» вычёркивает из проверки случайное везение.
11. Типичные ловушки
11.1 Работает на машине разработчика, но не работает на целевом компьютере
В первую очередь стоит заподозрить сценарий, при котором на самом деле помогала регистрация в реестре. Проверку Reg-Free COM по возможности безопаснее проводить в чистом окружении.
11.2 Приложение не запускается с ошибкой «side-by-side configuration is incorrect»
Эта группа ошибок возникает из-за рассогласования манифеста, нехватки зависимых DLL, отсутствия среды выполнения VC++, несовпадения архитектуры и тому подобного.
Одного лишь текста ошибки на поверхности недостаточно, поэтому стандартный способ разобраться — журнал событий и sxstrace.
Журнал событий: в «Просмотре событий» открывают «Журналы Windows» > «Приложение» и ищут ошибки с источником SideBySide. Там видно, на разрешении какой сборки произошёл сбой.
sxstrace снимают, воспроизводя сбой. Командную строку надёжнее открыть от имени администратора.
rem 1. Начинаем трассировку. Это окно оставляем открытым
sxstrace trace -logfile:sxstrace.etl
rem 2. В другом окне запускаем приложение и воспроизводим сбой
rem 3. Останавливаем трассировку. В окне из шага 1 нажимаем Enter либо из другого окна выполняем следующее
sxstrace stoptrace
rem 4. Сырой .etl переводим в читаемый вид
sxstrace parse -logfile:sxstrace.etl -outfile:sxstrace.txt
Если не хотите видеть запрос на остановку, к шагу 1 добавляют -nostop. Если вывод слишком длинный, к шагу 4 можно добавить -filter:MyApp.exe и оставить только долю целевого приложения.
В преобразованном sxstrace.txt по порядку видно, какой манифест искали и где не совпало. Если в разделе 10.4 ошиблись с именами, здесь по «имени файла, который искали» это будет видно.
flowchart TB
accTitle: Как разбирать ошибку side-by-side
accDescr: Рисунок показывает поток при отказе запуска с «side-by-side configuration is incorrect»: в журнале событий смотрят ошибки источника SideBySide, запускают трассировку sxstrace, воспроизводят сбой, после остановки делают parse в читаемый вид и идут по местам поиска и расхождения.
sx1["В журнале событий смотрят SideBySide"] --> sx2["Запускают трассировку sxstrace"]
sx2 --> sx3["Запускают приложение и воспроизводят сбой"]
sx3 --> sx4["Останавливают и делают parse"]
sx4 --> sx5["Читают места поиска и расхождения"]
Рис. 16: Не застревают на тексте ошибки на поверхности, а идут по журналу и трассировке пути разрешения.
11.3 Рассогласование манифеста компонента и манифеста приложения
- отличается
name - отличается
version - отличается
processorArchitecture - манифест, который вы думали, что скопировали, на самом деле устарел
На вид это совсем небольшие расхождения, но при запуске они сказываются очень сильно.
11.4 Забытые зависимые DLL
Если ограничиться только Vendor.CameraControl.dll и на этом успокоиться, легко упустить Vendor.Helper.dll, загружаемый следом, среду выполнения VC++ и proxy / stub DLL.
Reg-Free COM сокращает проблемы регистрации COM, но не устраняет заодно и проблемы разрешения нативных зависимостей.
11.5 Откладывание вопросов библиотеки типов и настройки ссылок
Даже если активация во время выполнения проходит успешно, как только появляется потребность
- в раннем связывании из VBA,
- в
#importиз C++, - в создании проектного interop на стороне .NET,
требуется способ распространения типовой информации. Reg-Free COM не настраивает всё это автоматически, поэтому важно рассматривать runtime и design-time отдельно друг от друга.
12. Итоги
Если сформулировать Reg-Free COM одной фразой, это механизм, который переносит регистрационные сведения COM с уровня всей машины на уровень отдельного приложения.
Благодаря этому появляются такие преимущества:
- легче хранить COM DLL / OCX локально для приложения
- легче сокращать конфликты версий
- легче упрощать распространение и откат
В то же время по-прежнему важны:
- разрядность 32-бит / 64-бит
- зависимые DLL
- TLB / настройка ссылок
- зависимость от нестандартной регистрации
- проверка в чистом окружении
Поэтому базовый подход при внедрении Reg-Free COM такой:
- Чётко принять, что это вопрос активации
- Разделять вопросы runtime и design-time
- Проверять в чистом окружении
- Сначала согласовать разрядность и зависимые DLL
Если смотреть на всё в таком порядке, риск проблем заметно снижается.
flowchart TB
accTitle: Базовый подход при внедрении
accDescr: Рисунок показывает, что при внедрении Reg-Free COM меньше сбоев, если принять, что это разговор об активации, разделить вопросы времени выполнения и проектирования, проверять в чистом окружении и сначала согласовать разрядность и зависимые DLL.
bs1["Принять, что это разговор об activation"] --> bs2["Разделить runtime и design-time"]
bs2 --> bs3["Проверять в чистом окружении"]
bs3 --> bs4["Сначала согласовать bitness и зависимые DLL"]
Рис. 17: Если держать четыре установки по порядку, сбоев при внедрении заметно меньше.
13. Похожие статьи
- Что такое COM / ActiveX / OCX - объясняем различия и связь между ними
- Как сегодня поступать с ActiveX / OCX: оставить, обернуть или заменить
- Как использовать DLL на .NET 8 из VBA с типизацией — публикация COM и TLB через dscom
14. Справочные материалы
- Microsoft Learn — Создание объектов Registration-Free COM
- Microsoft Learn — Манифесты приложений
- Microsoft Learn — Assembly Manifests (идентификатор ресурса и ограничения на имена при встраивании)
- Microsoft Learn — Assembly Searching Sequence (порядок поиска private assembly)
- Microsoft Learn — Manifest File Schema
- Microsoft Learn — Mt.exe
- Microsoft Learn — Взаимодействие с COM без регистрации (.NET Framework)
- Microsoft Learn — Публикация компонентов .NET Core для COM
- Microsoft Learn — sxstrace
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
Ловушки регистрации и bitness при разработке COM/OCX/ActiveX
Практический разбор типичных ловушек COM, OCX и ActiveX: разрядность 32/64-бит, Visual Studio 2022, regsvr32 и Regasm, права администрато...
Как построить вывод отчётов Excel: COM / Open XML / шаблоны
Проектирование вывода отчётов Excel сильно меняется в зависимости от того, автоматизируете ли вы сам Excel, собираете ли xlsx напрямую ил...
Что такое COM / ActiveX / OCX — различия и связь
Что такое COM, ActiveX и OCX: чем они отличаются, как связаны с OLE, где встречаются на практике и как к ним относиться сегодня.
Введение в Media Foundation: как понять API через COM
Разбираем, что такое Media Foundation, вместе с базовой терминологией Windows-медиа-API — COM, HRESULT, IMFSourceReader, MFT — в том поря...
STA и MTA в COM: модель потоков и как не получить зависание
STA и MTA в COM — модель апартаментов, которая задаёт, с какого потока можно вызывать компонент. Разбираем UI-поток, цикл сообщений, марш...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы Windows
Раздел о разработке Windows, расследовании сбоев и использовании существующих активов.
Миграция ActiveX
Решения о сохранении, обёртке или замене компонентов COM / ActiveX / OCX.
Услуги по этой теме
Статья напрямую связана со следующими услугами.
Разработка приложений для Windows
Тема напрямую связана с реализацией Windows-десктоп-приложений: распространение COM DLL / OCX, сборка манифестов, разрядность (bitness) и зависимые DLL.
Технические консультации и ревью дизайна
Также подходит, когда нужно решить, переходить ли на Reg-Free COM или оставить эксплуатацию на регистрации, и как разделить работу с библиотеками типов и настройкой ссылок на этапе проектирования.
Частые вопросы
Вопросы, которые часто возникают при консультациях по теме статьи.
- Что такое Reg-Free COM?
- Это механизм, при котором сведения о регистрации COM хранятся не в реестре, а в манифесте. Сокращение от Registration-Free COM. Во время выполнения при разрешении CoCreateInstance или CLSIDFromProgID сначала смотрят контекст активации (activation context) и по записанной в нём информации манифеста разрешают DLL. Благодаря этому COM DLL / OCX можно держать private для каждого приложения: проще XCOPY-распространение, легче избегать конфликтов версий, меньше риск сломать удаление.
- Решает ли переход на Reg-Free COM проблему 32-бит / 64-бит?
- Нет. 32-битный процесс может загружать только 32-битные in-proc COM DLL, 64-битный — только 64-битные DLL; в этом отношении Reg-Free COM ничего не меняет. Отдельно нужно продумывать распространение зависимых DLL и среды выполнения VC++, библиотеки типов, настройку ссылок на этапе проектирования и зависимость от нестандартных регистрационных сведений. Reg-Free COM в основном снимает только хлопоты, которые тянет за собой глобальная регистрация.
- Почему конфигурация Reg-Free COM работает на машине разработчика, но не работает на целевом компьютере?
- Сначала стоит заподозрить, что на самом деле помогала регистрация в реестре. Если в манифесте не хватает нужных сведений, среда выполнения COM откатывается к обычному разрешению по регистрации, поэтому на машине разработчика приложение может случайно работать за счёт локальной регистрации. Поэтому Reg-Free COM безопаснее проверять в чистом окружении. Если приложение не запускается с ошибкой «side-by-side configuration is incorrect», причина обычно в рассогласовании манифеста или нехватке зависимых DLL; стандартный способ разобраться — журнал событий и sxstrace.
- Можно ли сделать Reg-Free COM для COM-компонента, созданного на .NET 8?
- Да. В .NET 5+ / .NET 8 параметр EnableComHosting создаёт *.comhost.dll — точку входа для публикации COM, а EnableRegFreeCom=true выводит side-by-side манифест для Reg-Free COM. Однако Reg-Free COM и стратегия работы с TLB — разные вопросы. В .NET Core / .NET 5+ TLB не появляется из сборки естественным образом, как во времена .NET Framework, поэтому если нужно типизированное использование — например, раннее связывание из VBA, — генерацию, встраивание и регистрацию TLB безопаснее продумывать отдельно.
Об авторе
Страница с профилем автора статьи.
Го Комура
Представитель KomuraSoft LLC
Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.