История изменений (5 обновлений, последнее 30 Aug 2026)
Журнал изменений этой статьи. Там, где версия до правки была заархивирована, она остаётся доступной для чтения по постоянной ссылке с DOI.
- Восстановлена подпись второй диаграммы COM. Утверждения статьи не менялись.
- Переведены названия источников в списке литературы. Утверждения статьи не менялись.
- После слияния с main уточнён текст про DllSurrogate. Утверждения статьи не менялись.
- Русский текст переписан как полноценный технический перевод, а не калька с японского. Утверждения статьи не менялись.
- Добавлено объяснение DllSurrogate: почему успешный regsvr32 всё равно не позволяет 64-битному процессу загрузить 32-битный InprocServer32, как AppID у CLSID вместе с пустым DllSurrogate под этим AppID размещает DLL в dllhost.exe без написания нового EXE, почему разрядность запускаемого dllhost.exe определяется DLL, а не клиентом, почему зарегистрированный LocalServer32 имеет приоритет над суррогатом и как проверить результат. Также прямо сказано, что суррогат не устраняет разницу в разрядности: исчезает только требование одного процесса, а вызовы становятся out-of-process со стоимостью маршалинга и IPC. Открыть версию до этого обновления (DOI: 10.5281/zenodo.21619843)
- Первая публикация
Цитирование статьи(DOI (зарегистрированный архив): 10.5281/zenodo.21619842)
Приведённые ниже DOI относятся к ранее зарегистрированным архивным версиям, которые могут отличаться от текущего текста. Для ссылки на текущий текст используйте URL этой страницы.
Го Комура (2026). Ловушки регистрации и bitness при разработке COM/OCX/ActiveX. KomuraSoft LLC. https://comcomponent.com/ru/blog/2026/04/15/001-com-ocx-activex-pitfalls-visual-studio-bitness-admin-rights/
- DOI (зарегистрированный архив)
- 10.5281/zenodo.21619842
- DOI (последняя зарегистрированная версия)
- 10.5281/zenodo.22170293
Проекты с COM-компонентами и OCX / ActiveX чаще ломаются не на самом коде, а на границе среды выполнения, регистрации, хоста и прав.
Типичные симптомы такие.
- Сборка проходит, но при запуске —
0x80040154. - На своей машине разработки работает, на другом ПК — нет.
- В рантайме всё живо, а падает только Designer в Visual Studio.
- От администратора работает, с обычными правами ломается.
regsvr32запускаете, а лучше не становится.
Это не столько отдельные баги, сколько состояние, когда где-то не сходятся предпосылки COM.
Если сначала нужна сама терминология COM / ActiveX / OCX, общую картину быстрее даёт статья Что такое COM / ActiveX / OCX — различия и связь. Эта статья — следующий шаг: где именно на практике чаще всего застревают, включая разрядность Visual Studio и права администратора.
1. Сначала вывод
Если говорить так, как это работает на практике, получится следующее.
- Проблемы COM / OCX / ActiveX чаще возникают не из-за логики кода, а из-за несовпадения bitness (32-бит / 64-бит), места регистрации, хоста и прав.
- Visual Studio 2022 — 64-битный процесс, поэтому старое взаимодействие на этапе дизайна, рассчитанное на 32-бит, «как было» больше не работает.12
regsvr32— не волшебная команда, которая регистрирует что угодно. Она для нативных in-proc COM-серверов (DLL / OCX). Сборку .NET Framework для COM регистрируют черезRegasm.exe; для .NET 5+ / .NET 6+ / .NET 8+ регистрируют сгенерированный.comhost.dll.3456- «Работает от администратора — значит всё в порядке» — опасная мысль. Нередко компонент виден лишь потому, что его зарегистрировали per-user, или регистрацию, которую должен делать инсталлятор, руками прогнали только на машине разработчика.378
Поэтому при работе с COM / OCX / ActiveX безопаснее сразу смотреть по этим четырём осям.
- Какой процесс хостит компонент
- Этот процесс 32-бит или 64-бит
- Куда зарегистрировали (HKCU / HKLM, 32-битный view / 64-битный view)
- Нужны ли для этой операции или запуска права администратора
На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 34, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle
2. От симптома к причине картина обычно такая
| Симптом | Что подозревают сначала | Частая настоящая причина |
|---|---|---|
0x80040154 Class not registered |
Не зарегистрировано | На деле часто «зарегистрировано только в другой разрядности» или «зарегистрировано только для этого пользователя» |
DllRegisterServer failed: 0x80070005 |
Не хватает прав | Регистрируют от обычного пользователя или в post-build без повышения прав |
| В VS2022 падает только Designer | Ограничения Designer | 64-битная Visual Studio не может напрямую загрузить 32-битный COM / ActiveX |
| Работает только от администратора | Проблема прав | По сути чаще не права как таковые, а несовпадение области регистрации или схемы установки |
| 64-битное приложение не вызывает 32-битный OCX | Ограничение механизма COM | in-proc сервер загружается только в хост той же разрядности |
| В UI-потоке работает, в фоновом зависает | Модель потоков | Нарушены предпосылки STA / MTA, CoInitializeEx, цикла сообщений |
Официально 0x80040154 — это REGDB_E_CLASSNOTREG, дословно Class not registered.9
А 0x80070005 у regsvr32 официально описывается как случай, когда нет прав администратора, чтобы писать в реестр или в System32.10
Важно не принимать имя ошибки за полный диагноз.
Например, Class not registered вовсе не всегда значит «совсем не зарегистрировано»: так бывает и когда запись лежит в другом view реестра или в месте, видимом только этому пользователю.117
3. Где ломает разрядность Visual Studio
3.1 Visual Studio 2022 стала 64-битной
Сейчас это самое частое место, где спотыкаются в разработке COM / ActiveX.
В Visual Studio 2022 devenv.exe — только 64-бит.1
Поэтому на этапе дизайна WinForms сама Visual Studio не может напрямую загрузить 32-битный компонент. Microsoft прямо пишет: Visual Studio 2022 — 64-битный процесс и не загружает 32-битные .NET / COM / ActiveX компоненты.2
Раньше это как-то держалось на предпосылках вроде:
- проект — x86
- подключаемый ActiveX тоже x86
- сама Visual Studio — 32-бит
В VS2022 из этого получается перекос:
- приложение в рантайме по-прежнему может работать как x86
- а Designer крутится на стороне 64-битной Visual Studio
В итоге получается плохо диагностируемое состояние: рантайм жив, падает только Designer. Хост в рантайме — процесс приложения, хост на этапе дизайна — процесс Visual Studio, и хосты разные, значит и bitness разный.2
3.2 Переход на AnyCPU не всегда решает проблему
Это ещё одно частое заблуждение.
AnyCPU — не магия, которая делает нейтральными и зависимости.
Microsoft поясняет: даже компонент, который выглядит как AnyCPU, на этапе дизайна в Visual Studio 2022 ломается, если дальше по цепочке он ссылается на COM / ActiveX, жёстко привязанный к 32-бит.2
Поэтому если после перехода на AnyCPU ошибка не ушла, быстрее проверить:
- нет ли дальше по цепочке этой сборки 32-битной нативной зависимости
- не зафиксирован ли ActiveX / OCX как x86
- нет ли кода, который подгружается только в Designer
3.3 Ловушка System32 и SysWOW64
На Windows x64 имена папок и их реальное назначение расходятся, и это регулярно путает.
Microsoft Learn пишет: на x64 Windows %windir%\System32 предназначен для 64-битных приложений. 32-битную сторону файловый редиректор WOW64 уводит в другое место.12
С реестром то же самое: редиректор реестра WOW64 показывает 32-битному и 64-битному коду разные логические view.11
Поэтому при локальной диагностике безопаснее явно фиксировать, какой именно regsvr32 вы вызываете.
# Чтобы зарегистрировать 64-битный DLL / OCX
C:\Windows\System32\regsvr32.exe vendor.ocx
# Чтобы зарегистрировать 32-битный DLL / OCX на x64 Windows
C:\Windows\SysWOW64\regsvr32.exe vendor.ocx
Неприятный случай — регистрация «не той» стороной. Сама регистрация проходит успешно, но целевой процесс её не видит. В итоге:
regsvr32отработал успешно- приложение всё равно даёт
0x80040154 - в реестре запись как будто есть
- но вы смотрите в другой view
3.4 regsvr32 регистрирует не всё подряд
И здесь заблуждений много.
Нативные in-proc COM-серверы обычно поддерживают саморегистрацию и экспортируют DllRegisterServer / DllUnregisterServer.3
regsvr32 — инструмент именно для таких DLL / OCX.4
Если же сборку .NET Framework нужно вызывать из COM, базовый инструмент — Regasm.exe. Microsoft Learn тоже пишет: сборки, которые используют из COM, регистрируют через Regasm.exe.514
Для .NET 5+ / .NET 6+ / .NET 8+ схема чуть другая: при сборке с <EnableComHosting>true</EnableComHosting> появляется *.comhost.dll, и уже его регистрируют через regsvr32.6
Грубо разделить можно так.
| Что публикуете | Типичный способ регистрации |
|---|---|
| Нативный C++ DLL / OCX | regsvr32 |
| Сборка .NET Framework, публикуемая в COM | Regasm.exe |
| Публикация в COM для .NET 5+ / 6+ / 8+ | сгенерированный .comhost.dll через regsvr32 |
Regasm.exe запускают из Developer Command Prompt / Developer PowerShell. На практике чаще всего три варианта.14
:: 1) Зарегистрировать публичные классы сборки
regasm myTest.dll
:: 2) Сгенерировать и зарегистрировать библиотеку типов (для VBA / VB6 и других клиентов, которым нужен TLB)
regasm myTest.dll /tlb:myTest.tlb
:: 3) Не трогать реестр, а выгрузить содержимое в .reg и посмотреть
regasm myTest.dll /regfile:myTest.reg
Снятие — /unregister. Библиотеку типов, зарегистрированную через /tlb, снимают, указывая и /tlb, и /unregister.
regasm myTest.dll /tlb:myTest.tlb /unregister
Здесь легко наступить на два ограничения.14
- Сборке, которую не кладут в GAC, нужен
/codebase./codebaseзаписывает в реестр путь к файлу на момент регистрации, поэтому если потом сборку перенести, запуск ломается. Microsoft ещё и настоятельно рекомендует давать таким сборкам строгое имя (strong name). /regfileнельзя сочетать с/unregisterили/tlb. Плюс/regfileпишет только записи управляемых классов:TypeLibIDиInterfaceIDв файл не попадают. Если думать «раздам.reg— и регистрация готова», этого не хватит.
Если эти способы смешать, типичный сценарий такой:
- на управляемую DLL вешают
regsvr32 DllRegisterServer, естественно, не находится- отсюда неверный вывод: «DLL повреждена»
В мире, где нужна библиотека типов (VBA / VB6 / часть клиентов с ранним связыванием), отдельно встаёт ещё и генерация и регистрация TLB. В .NET Framework библиотеку типов можно сгенерировать и зарегистрировать через Regasm.exe /tlb; Microsoft отдельно подчёркивает, что «регистрация типов» и «регистрация библиотеки типов» — разные действия.15
3.5 Когда 32-битный in-proc нужно вынести из 64-битного процесса — DllSurrogate
Разделы 3.3 и 3.4 были о том, совпадают ли место и способ регистрации. Здесь ещё один выход — на случай, когда регистрация правильная, а разрядность не совпадает.
Сначала вывод.
- Даже если
regsvr32отработал успешно, 64-битный процесс не может загрузить 32-битныйInprocServer32. Это не дефект регистрации, а сама предпосылка in-proc. - Если этот DLL нужно использовать с 64-битной стороны, не написав новый EXE, минимальное решение — добавить к CLSID значение
AppIDи записать в соответствующий ключAppIDпустую строкуDllSurrogate.16 - Запускается при этом
dllhost.exeтой разрядности, что и DLL, на который указываетInprocServer32, а не разрядности клиента.
Почему это не работает, пока всё остаётся in-proc
Пункт «64-битное приложение не вызывает 32-битный OCX» из таблицы симптомов в главе 2 выглядит так же, как 0x80040154, но по сути это другое.
InprocServer32 буквально означает «сервер, работающий внутри вызывающего процесса». 64-битный процесс не загрузит записанный там 32-битный DLL, как бы вы ни правили реестр. То, что view реестра разделены, о чём говорится в 3.3, в конечном счёте восходит к этому же ограничению.
Поэтому дальше речь не про «исправить регистрацию», а про «вынести в отдельный процесс».
Что делает суррогат
DllSurrogate — указание поместить in-proc DLL-сервер в суррогатный процесс и выставить его как Local Server.17
Если значение сделать пустой строкой, используется суррогат по умолчанию, который поставляет Windows (dllhost.exe).18
Для 32-битного DLL запускается 32-битный суррогат, и DLL загружается внутри него in-proc, как и раньше. 64-битный клиент обращается к объекту извне этого процесса, через proxy.
Это легко понять неправильно, поэтому скажу прямо.
Суррогат не устраняет разницу в разрядности. Исчезает только ограничение «это должно быть в одном процессе». Вызовы становятся out-of-proc, и сверху добавляется стоимость маршалинга и межпроцессного взаимодействия. Как предпосылка нужно ещё, чтобы типы, которыми вы обмениваетесь, поддавались маршалингу (IDispatch, зарегистрированный proxy / stub либо тип, с которым справляется стандартный маршалер).
Ещё одно. Суррогат хорошо справляется с объектом автоматизации, который полностью обходится вызовами методов и событиями. Визуальный OCX, который кладут на форму, чтобы он рисовал, рассчитан на то, что его окно живёт внутри процесса хоста, поэтому одним этим решением дело не закрыть.
flowchart TB
accTitle: На что попадает активация — на in-proc или на суррогат
accDescr: Диаграмма, показывающая, что при запросе с CLSCTX, включающим in-proc, объект сначала загружается in-proc, если у CLSID в том же view есть InprocServer32; что если его нет, а регистрация EXE-сервера есть, то EXE запускается раньше суррогата; что если регистрации EXE нет, а пустой DllSurrogate есть, то запускается dllhost той разрядности, что и DLL; и что если нет ничего из этого, активация невозможна.
req["Клиент запрашивает активацию"] --> ctx["Запрос с CLSCTX, включающим in-proc"]
ctx --> q1{"Есть ли InprocServer32 у CLSID в том же view"}
q1 -->|"Есть"| inproc["Загружается in-proc, как и раньше"]
q1 -->|"Нет"| q2{"Есть ли регистрация EXE-сервера"}
q2 -->|"Есть"| exe["EXE запускается раньше суррогата"]
q2 -->|"Нет"| q3{"Есть ли пустой DllSurrogate"}
q3 -->|"Есть"| host["Запускается dllhost той разрядности, что и DLL"]
q3 -->|"Нет"| err["Активация невозможна, 0x80040154"]
Рис. 1: Активация сначала ветвится по тому, виден ли InprocServer32 той же разрядности, и только если нет — переходит к EXE-серверу, а затем к пустому DllSurrogate.
Минимальный набор регистрационных записей
Microsoft Learn перечисляет условия, при которых DLL-сервер попадает в суррогат, так.16
- У ключа CLSID есть значение
AppID, и соответствующий ключAppIDсуществует - В вызове активации установлен
CLSCTX_LOCAL_SERVER, а у ключа CLSID нетLocalServer32/LocalServer/LocalService - У ключа CLSID есть
InprocServer32 - DLL, на который указывает
InprocServer32, реально существует - Под ключом
AppIDесть значениеDllSurrogate
Если этот DLL нужно запускать в одиночку в одном суррогате, Microsoft рекомендует делать AppID тем же GUID, что и CLSID.16
Для регистрации хватает трёх строк. Ниже — случай использования 32-битного COM DLL из 64-битного хоста, GUID здесь — заглушки. Предполагается, что InprocServer32 уже внесён через regsvr32 со стороны SysWOW64 (см. 3.3).
:: Выполнять в командной строке с правами администратора
set CLSID={11111111-2222-3333-4444-555555555555}
set APPID={11111111-2222-3333-4444-555555555555}
:: 1) Добавляем AppID к CLSID. Всё под CLSID разделено для 32/64, поэтому view указываем явно
:: Для обратного направления, когда 64-битный COM DLL используется из 32-битного хоста, читайте здесь /reg:64
reg add "HKLM\SOFTWARE\Classes\CLSID\%CLSID%" /v AppID /t REG_SZ /d "%APPID%" /f /reg:32
:: 2) Создаём соответствующий ключ AppID. Classes\AppID общий для 32/64, поэтому указывать view не нужно
reg add "HKLM\SOFTWARE\Classes\AppID\%APPID%" /f
:: 3) Делаем DllSurrogate пустым REG_SZ. Если опустить /d, значение создаётся пустой строкой
reg add "HKLM\SOFTWARE\Classes\AppID\%APPID%" /v DllSurrogate /t REG_SZ /f
Почему именно пустая строка
DllSurrogate имеет тип REG_SZ, и само значение — это путь к суррогату. Пустая строка (или NULL) означает, что используется системный суррогат по умолчанию, а если записать путь, будет предпринята попытка запустить кастомный суррогат по этому пути.1718
Иначе говоря, записать туда из лучших побуждений путь к dllhost.exe — контрпродуктивно: сама пустота значения и есть указание «использовать вариант по умолчанию».
Заметьте, что HKLM\SOFTWARE\Classes\AppID — ключ, общий для 32-бит и 64-бит начиная с Windows 7 / Windows Server 2008 R2, без разделения на view.19
А вот всё, что находится под CLSID, разделено по view, поэтому для 32-битного сервера значение AppID нужно добавлять на стороне CLSID в 32-битном view. Если не срабатывает, обязательно проверяйте эти два места вместе.
Это выглядит так, будто противоречит утверждению из 3.3 и 6.3 «если зарегистрировано только на 32-битной стороне, из 64-битного процесса будет 0x80040154», но там речь про in-proc. При out-of-proc активации, когда ни клиент, ни сервер не выражают предпочтения по разрядности, COM ищет сервер той разрядности, которая подходит клиенту, а если такого нет — запускает сервер другой разрядности.20
Именно поэтому эта процедура работает, оставляя сторону CLSID в 32-битном view. Копировать InprocServer32 в 64-битный view не нужно.
Разрядность запускаемого dllhost.exe
Здесь чаще всего возникает недопонимание.
Разрядность суррогата определяется не стороной клиента, а стороной DLL, на который указывает InprocServer32. Раз DLL читается внутри суррогата in-proc, иначе и быть не может.
| COM DLL, читаемый внутри | Запускаемый dllhost.exe |
|---|---|
| 32-бит | %SystemRoot%\SysWOW64\dllhost.exe |
| 64-бит | %SystemRoot%\System32\dllhost.exe |
Если посмотреть командную строку в диспетчере задач или Process Explorer, процесс стоит там в виде dllhost.exe /Processid:{...}. GUID здесь — это AppID, а не CLSID. Если искать его, считая его CLSID, вы его не найдёте, так что будьте внимательны.
Если есть LocalServer32, побеждает EXE
Microsoft Learn прямо указывает: когда присутствует LocalServer / LocalServer32 / LocalService, запуск EXE-сервера или службы всегда имеет приоритет над загрузкой DLL в суррогат.16
Иначе говоря, суррогат — это выход на случай, когда EXE-сервера нет. Если добавить DllSurrogate к CLSID, для которого уже зарегистрирован EXE-сервер, этот путь использоваться не будет. И приоритет здесь действует строго по отношению к суррогату, а не по отношению к in-proc.
Полезно зафиксировать и влияние на существующих вызывающих.
Даже если добавить DllSurrogate, клиент, который создаёт объект с CLSCTX, включающим in-proc, остаётся in-proc ровно как раньше, пока разрядность совпадает. Дело в том, что при передаче нескольких значений CLSCTX, объединённых по OR, они пробуются в порядке перечисления (in-proc → local → remote), и на стадии in-proc, если ключ InprocServer32 есть, используется именно он.20
Под это подпадают вызовы, передающие и in-proc, и local, такие как CLSCTX_ALL или CLSCTX_SERVER. И наоборот, места, вызывающие только с CLSCTX_INPROC_SERVER, не будут направлены на суррогат даже после добавления DllSurrogate.
А с точки зрения 64-битного клиента InprocServer32, положенный в 32-битный view, в 64-битном view не существует. Стадия in-proc проходит насквозь как «нет такого ключа», поэтому дело сразу переходит к активации на стороне local (суррогат). Это не значит, что попытка загрузить DLL другой разрядности заканчивается неудачей.2019
Как проверить
Вошло ли всё как надо, надёжнее всего подтвердить, прочитав записи обратно с явным указанием view.
reg query "HKLM\SOFTWARE\Classes\CLSID\{11111111-2222-3333-4444-555555555555}\InprocServer32" /ve /reg:32
reg query "HKLM\SOFTWARE\Classes\CLSID\{11111111-2222-3333-4444-555555555555}" /v AppID /reg:32
reg query "HKLM\SOFTWARE\Classes\AppID\{11111111-2222-3333-4444-555555555555}" /v DllSurrogate
Не пропускайте первую строку. reg add создаёт указанный ключ, если его нет, поэтому при опечатке в GUID или регистрации в другой view вы получите пустой ключ CLSID, несущий только значение AppID, а вторая строка всё равно пройдёт. Только убедившись, что InprocServer32 находится в том же view, вы действительно что-то подтвердили.
Для DllSurrogate правильный результат — когда он выводится со всё ещё пустым значением. После этого создайте объект из 64-битного клиента и посмотрите, запускается ли dllhost.exe со стороны SysWOW64.
- По-прежнему
0x80040154→ перепроверьте, добавлено ли значениеAppIDна стороне CLSID в 32-битном view и существует ли реально DLL, на который указываетInprocServer32 dllhost.exeзапускается, но вызов падает → проверьте предпосылки маршалинга (IDispatch/ proxy-stub / стандартный маршалер)- Запускается другой EXE → у этого CLSID остался
LocalServer32или что-то подобное
На этом «регистрация» заканчивается
Достаточно ли суррогата или всё же стоит написать собственный вспомогательный EXE на 32-битной стороне — это вопрос не регистрации, а выбора архитектуры. Эта сторона разобрана в разделе 5.2 «Хотите перенести 32-битный OCX на 64-битную сторону» статьи Как сегодня поступать с ActiveX / OCX — таблица решений «оставить, обернуть или заменить».
4. Где ломают права администратора
4.1 Регистрация в post-build, которая проходит только от администратора
В C++-сборках Visual Studio вызов regsvr32.exe из build event или custom build step — обычная практика. Microsoft Learn даже приводит пример post-build события с регистрацией через regsvr32.exe.21
Но уметь так сделать и делать так безопасно — разные вещи.
Если запустить regsvr32 от обычного пользователя, запись в реестр или в System32 может быть недоступна, и получится 0x80070005. В KB Microsoft причина тоже сведена к отсутствию прав администратора.10
Типичный инцидент выглядит так:
- Visual Studio запущена с обычными правами, сборка проходит
- регистрация в post-build падает
- ошибку в логе пропускают
- на месте случайно живёт старая регистрация
- в чистом окружении, разумеется, ничего не работает
Для таких проектов базово разделяют сборку и регистрацию.
- Сборка только производит бинарники
- Регистрация — отдельный явный install step / скрипт / инсталлятор
- В CI шаги, которым нужна регистрация, выносят в отдельную задачу от сборки
Одного этого разделения уже хватает, чтобы заметно снизить число таких аварий.
4.2 Смешение per-user и per-machine регистрации ломает работу
Если помнить только «COM смотрит в HKCR», здесь как раз и застрянете.
На самом деле, как пишет Microsoft Learn, COM сначала смотрит HKEY_CURRENT_USER\Software\Classes, а уже потом — сведения на уровне компьютера.3
А HKEY_CLASSES_ROOT — это объединённое представление HKLM\Software\Classes и HKCU\Software\Classes.722
Поэтому вполне обычна картина:
- разработчик A зарегистрировал компонент вручную под своей учётной записью
- под учёткой A всё работает
- у разработчика B не работает
- под сервисной учётной записью тоже не работает
- при запуске от администратора поведение меняется
Дальше Microsoft указывает: приложениям, которым нужны права администратора, следует регистрировать зависимые COM-объекты при установке в per-machine хранилище конфигурации COM.87
Поэтому в команде разработки это лучше различать явно.
- это dev-регистрация только для вашей учётной записи
- это боевая регистрация для всех пользователей машины
- это регистрация, к которой ходят службы или приложения с повышенными правами
Если оставить это размытым, получаются истории вроде «почему-то работает только от администратора» или «в расширении Explorer работает, в службе — нет».
4.3 Постоянный запуск Visual Studio «от имени администратора» — не решение
Ситуации, когда это нужно, бывают. Но если держать Visual Studio постоянно elevated, можно спрятать за повышением прав IDE проблему, которую должен закрывать инсталлятор или скрипт регистрации.
Плюс сама Visual Studio в elevated-режиме иначе обращается с per-user расширениями. Microsoft Learn описывает настройку, при которой запуск Visual Studio elevated отключает per-user расширения.23
Поэтому на практике удобнее так:
- повседневная разработка — с обычными правами
- шаги, которым нужна регистрация, выполняют только в явно повышенном Developer Command Prompt / PowerShell / инсталляторе
- если что-то «воспроизводится только от администратора», само это условие стоит зафиксировать как часть спецификации
Так меньше сюрпризов потом.
5. Ловушки, специфичные для ActiveX / OCX
5.1 Лицензия на этапе дизайна и лицензия времени выполнения — разные вещи
Неочевидная, но неприятная сторона OCX / ActiveX — лицензирование.
У старых элементов управления ActiveX design-time license и run-time license нередко разделены. В документации MFC ActiveX тоже описан механизм, который разделяет design-time / run-time через файлы лицензий и ключи.2425
В этом мире случается следующее:
- в рантайме компонент работает
- а при попытке положить его на форму — «нет лицензии»
- на машине разработчика A положить можно
- на машине разработчика B — нельзя
Ошибки вроде «лицензия для этого компонента не найдена» или «нет подходящей лицензии» для старого ActiveX не редкость.26
5.2 Как только ActiveX оказывается на WinForms, обёртка уже в деле
Когда ActiveX используют в WinForms, Windows Forms не хостит ActiveX «как есть».
Документация Microsoft Learn по Aximp.exe прямо говорит: ActiveX Control Importer строит обёртку для WinForms из COM-библиотеки типов и дальше работает с ней как с элементом управления на базе AxHost.2728
То есть проблема не в одном слое, а сразу в нескольких:
- исходный OCX / ActiveX
- библиотека типов
- сгенерированный interop / wrapper
- WinForms Designer / runtime
Отсюда типичные сюрпризы:
- замена вендорского OCX сменила сигнатуры событий
- повторное добавление ссылки перегенерировало обёртку и выдало огромный diff
- результат генерации interop чуть отличается от машины к машине разработчика
Появление в Choose Toolbox Items ещё ничего не гарантирует.
Можно ли положить компонент в Designer, приходят ли события в рантайме, и встаёт ли обёртка целиком на машине развёртывания — это три разных проверки.
5.3 Если недооценить STA / MTA и цикл сообщений, всё зависает
Сначала три термина.
| Термин | Смысл |
|---|---|
| Апартамент (apartment) | Логическая группа COM-объекта и потоков с одними правилами параллельного выполнения. Один объект принадлежит только одному апартаменту |
| STA (single-threaded apartment) | Апартамент, которому принадлежит ровно один поток. Вызовы этого объекта всегда исполняются на этом потоке |
| Маршалинг (marshaling) | Механизм, которым COM передаёт вызов, когда указатель интерфейса идёт через границу апартамента. Идёт через прокси и стаб |
COM нужно инициализировать через CoInitializeEx на каждом потоке, который им пользуется. Microsoft Learn прямо пишет: каждый поток, использующий COM, должен отдельно вызвать CoInitializeEx.29
В STA (single-threaded apartment) ещё и нужен цикл сообщений.2930
Почему без цикла сообщений всё зависает
Если запомнить только вывод, применить это к другому случаю уже сложно, поэтому откроем механизм на один уровень.
Для каждого STA COM создаёт скрытое окно класса OleMainThreadWndClass. Когда объект вызывают из другого апартамента, это уже не прямой вызов функции: вызов приходит как оконное сообщение этому скрытому окну. Поток STA достаёт сообщение и диспетчеризует его, оконная процедура вызывает соответствующий метод интерфейса.30
То есть цикл сообщений — не «хороший тон», а сам механизм, которым вызовы доходят до объекта.
sequenceDiagram
accTitle: Как вызов STA доходит через цикл сообщений
accDescr: Вызов из другого апартамента попадает в очередь сообщений скрытого окна; поток STA извлекает его циклом сообщений, оконная процедура вызывает метод объекта. Если цикл остановлен, вызывающая сторона продолжает ждать
participant Caller as вызывающая сторона в другом потоке
participant Queue as очередь сообщений скрытого окна
participant STA as поток STA
participant Obj as OCX / ActiveX
Caller->>Queue: вызов метода кладётся как сообщение
STA->>Queue: цикл сообщений извлекает сообщение
Queue->>Obj: оконная процедура вызывает соответствующий метод
Obj-->>Caller: возвращается результат
Note over STA,Obj: Если цикл сообщений остановлен,<br/>извлечения нет и вызывающая сторона продолжает ждать
Рис. 2: Вызов из другого потока приходит как сообщение скрытому окну и доходит до объекта только после того, как цикл сообщений его извлечёт.
Поэтому если заблокировать поток STA, всё зависает. Task.Wait() или Task.Result на UI-потоке, ожидание через WaitOne останавливают выборку сообщений: колбэки COM и вызовы между апартаментами перестают доходить, получается взаимная блокировка.30
По той же причине указатель интерфейса объекта STA нельзя просто скопировать в другой поток. Иначе вызов, который должен был дойти до потока STA, исполнится напрямую на другом потоке, и объект получит параллельность, на которую не рассчитывал. Через границу апартамента указатель передают маршалингом: CoMarshalInterThreadInterfaceInStream и CoGetInterfaceAndReleaseStream.30
UI-ориентированные OCX / ActiveX чаще всего как раз рассчитаны на STA, поэтому получается неприятная картина:
- в UI-потоке всё работает
- бросили в
Task.Runили в пул потоков — зависает - события не возвращаются
- воспроизводится лишь иногда
В STA действует ещё одно правило: указатель интерфейса нельзя просто скопировать в другой поток; если нужно — маршалить.2930
Такие дефекты гораздо менее дружелюбны, чем 0x80040154: видно только зависание, отсутствие ответа, редкие падения, поэтому времени они съедают не меньше, чем проблемы с реестром.
6. Порядок диагностики, который работает на практике
На практике быстрее не углубляться сразу, а сужать область поиска в таком порядке.
6.1 Сначала зафиксировать, какой процесс какой разрядности
Это первое, на что смотрят.
- Что является хостом (Visual Studio Designer / своё приложение / Office / Access / Explorer / среда совместимости браузера)
- Этот хост 32-бит или 64-бит
- Целевой DLL / OCX — 32-бит или 64-бит
- Это in-proc или out-of-proc
Пока эти четыре пункта не зафиксированы, смотреть реестр рано: непонятно, какой view вообще читать, и расследование крутится вхолостую. Сначала зафиксируйте это.
6.2 Затем проверить вид регистрации
Следующий вопрос — чем правильно регистрировать что.
- Нативный DLL / OCX →
regsvr32 - Публикация .NET Framework в COM →
Regasm.exe - Публикация .NET 5+ / 6+ / 8+ в COM →
.comhost.dll - DLL без саморегистрации → это вообще не задача для
regsvr32
Одной этой классификации уже хватает, чтобы отсечь значительную часть ложных шагов.
6.3 После этого смотреть, куда попала регистрация
Одного HKCR недостаточно. Нужно смотреть:
HKCU\Software\ClassesHKLM\Software\Classes- при необходимости 32-битный / 64-битный view реестра
- целевой ProgID / CLSID / TypeLib
InprocServer32/LocalServer32- ThreadingModel
- реальный путь к целевой DLL
Факта «есть в HKCR» мало: смысл появляется только когда известно ещё и кто смотрит, с какой разрядностью и через какой view.711
Какие ключи смотреть
Если открыть HKEY_CLASSES_ROOT\CLSID\{...}, видны только те записи, которые доступны разрядности текущего инструмента и текущему пользователю. Для диагностики смотрят четыре физических места до слияния. Подразделы CLSID попадают под перенаправление WOW64; физическое место 32-битной стороны — Wow6432Node под Classes.19
| Что хотите увидеть | Путь в реестре |
|---|---|
| Вся машина / 64-битный view | HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{CLSID} |
| Вся машина / 32-битный view | HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Wow6432Node\CLSID\{CLSID} |
| Только этот пользователь / 64-битный view | HKEY_CURRENT_USER\SOFTWARE\Classes\CLSID\{CLSID} |
| Только этот пользователь / 32-битный view | HKEY_CURRENT_USER\SOFTWARE\Classes\Wow6432Node\CLSID\{CLSID} |
| CLSID по ProgID | значение по умолчанию HKEY_CLASSES_ROOT\{ProgID}\CLSID |
regedit.exe в Windows 10 / 11 — 64-битный процесс, поэтому эти пути можно вводить как есть и открывать оба view напрямую. Вставьте путь в адресную строку — редактор перейдёт на это место.
Путь с Wow6432Node — это физическое расположение; Microsoft просит приложения не трогать его из кода напрямую. Для ручного разбора это нормально, а из скрипта или приложения безопаснее задавать view так, как ниже.11
Проверка из командной строки
У reg.exe view задают явно: /reg:32 и /reg:64.
reg query "HKLM\SOFTWARE\Classes\CLSID\{00000000-0000-0000-0000-000000000000}\InprocServer32" /reg:32
reg query "HKLM\SOFTWARE\Classes\CLSID\{00000000-0000-0000-0000-000000000000}\InprocServer32" /reg:64
В PowerShell, чтобы сразу пройти все четыре места, view передают в RegistryKey.OpenBaseKey. Тогда разрядность самого PowerShell не тянет результат за собой — для диагностики это надёжнее.
# ProgID, который проверяем, задаём в одном месте
$progId = 'Vendor.Control.1'
# 1) Достаём CLSID по ProgID (HKCR — объединённый view HKLM и HKCU)
$clsid = (Get-ItemProperty -Path "Registry::HKEY_CLASSES_ROOT\$progId\CLSID" -Name '(default)' -ErrorAction Stop).'(default)'
"ProgID $progId -> CLSID $clsid"
# 2) Перебираем 2 куста × 2 view = 4 места
foreach ($hive in [Microsoft.Win32.RegistryHive]::LocalMachine, [Microsoft.Win32.RegistryHive]::CurrentUser) {
foreach ($view in [Microsoft.Win32.RegistryView]::Registry64, [Microsoft.Win32.RegistryView]::Registry32) {
$base = [Microsoft.Win32.RegistryKey]::OpenBaseKey($hive, $view)
$key = $base.OpenSubKey("SOFTWARE\Classes\CLSID\$clsid\InprocServer32")
if ($null -eq $key) {
'{0,-12} {1,-11} : нет регистрации' -f $hive, $view
}
else {
'{0,-12} {1,-11} : {2} (ThreadingModel={3})' -f $hive, $view, $key.GetValue(''), $key.GetValue('ThreadingModel')
$key.Dispose()
}
$base.Dispose()
}
}
Эти четыре строки как раз и есть ответ диагностики.
- Во всех четырёх «нет регистрации» → действительно не зарегистрировано. Сверьте способ регистрации с классификацией из 6.2.
- Есть только в
Registry32→ зарегистрировано лишь на 32-битной стороне. Из 64-битного процесса будет0x80040154. - Есть только в
CurrentUser→ видно лишь этому пользователю. Другой пользователь, сервисная учётка, elevated-процесс этого не увидят. - Путь есть, а работать не хочет → проверьте, что файл из значения по умолчанию
InprocServer32существует и что его разрядность совпадает с хостом.
6.4 В конце проверить, не прячутся ли права за другой регистрацией
Последний шаг — понять, проблема ли это в правах, или права просто показывают другую регистрацию.
- Меняется ли поведение у обычного пользователя и у администратора
- Что меняется, если Visual Studio запустить elevated
- Воспроизводится ли под сервисной учётной записью или другим пользователем
- Встаёт ли всё в чистом окружении, развёрнутом через инсталлятор
Если смотреть только на одну машину разработки, именно здесь выводы чаще всего ошибочны.
7. Правила, которые лучше зафиксировать заранее
В разработке и сопровождении COM / ActiveX / OCX иногда сильнее работает не приём реализации, а то, как заранее устроена эксплуатация.
7.1 Сначала решить политику разрядности
В первую очередь фиксируют вот что.
- Идти жёстко в x86
- Считать каноном x64
- Поддерживать обе стороны
- Обязан ли этот компонент быть in-proc
Особенно если вендорский OCX зафиксирован как x86: игнорировать это и переводить на x64 только приложение — потом упереться. Тема связана с более широким архитектурным вопросом; см. также Пример COM-моста: вызов 64-битной DLL из 32-битного приложения.
7.2 Решить стратегию регистрации
Регистрацию тоже лучше не делать по случаю.
- Нужно всей машине → per-machine регистрация через инсталлятор
- Нужно только этому пользователю → per-user осознанно
- Замыкается только своим приложением → рассмотреть registration-free COM
- Нужно только для разработки → закрыть явным dev setup script
Registration-free COM держит сведения активации не в реестре, а в манифесте, и это рабочий способ уменьшить ад регистрации. Официально описаны и registration-free COM на стороне Win32, и RegFree COM на стороне .NET.31632
Механизм такой: когда COM обрабатывает CoCreateInstance и подобные вызовы, он сначала ищет контекст активации и только если там пусто, смотрит реестр. Манифест как раз объявляет содержимое этого контекста активации.31
Нужны два манифеста, оба кладут в ту же папку, что и exe.
Первый — манифест сборки компонента (VendorCtl.manifest). В нём написано, какой файл предоставляет какой CLSID.33
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32"
name="MyCompany.VendorCtl"
version="1.0.0.0"
processorArchitecture="x86" />
<file name="vendor.ocx">
<comClass description="Vendor Control"
clsid="{00000000-0000-0000-0000-000000000000}"
threadingModel="Apartment"
progid="Vendor.Control.1" />
</file>
</assembly>
Второй — манифест приложения (MyApp.exe.manifest). В нём только зависимость от сборки выше.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32"
name="MyCompany.MyApp"
version="1.0.0.0"
processorArchitecture="x86" />
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32"
name="MyCompany.VendorCtl"
version="1.0.0.0"
processorArchitecture="x86" />
</dependentAssembly>
</dependency>
</assembly>
При написании чаще всего ошибаются в следующем.33
assemblyIdentityна стороне зависимости должен в точности совпадать сassemblyIdentityкомпонента. Достаточно разъехатьсяname,versionилиprocessorArchitecture— разрешение не произойдёт.- Имя сборки и имя DLL — разные вещи. Если манифест лежит отдельным файлом, имя сборки и имя манифеста должны отличаться от имени DLL.
- Имена элементов и атрибутов чувствительны к регистру.
ComClassвместоcomClassпросто не прочитается. processorArchitecture— это и есть bitness. x86 OCX из приложенияamd64не разрешится. Это нужно выровнять с политикой разрядности из 7.1.- Если OCX встраивают, могут понадобиться атрибуты семейства
miscStatus. Сведения, которые в реестре живут в ключеMiscStatus, в манифесте нужно переписать какmiscStatus/miscStatusContentи подобные.
7.3 Не держать слишком много артефактов вне контроля версий
Классическая беда проектов OCX / ActiveX — разъезд зависимостей.
- сам OCX
- зависимые DLL
- TLB
.lic- interop DLL
- обёртка AxHost
- скрипты регистрации
- тестовый хост
Если всё это живёт только на локальной машине конкретного человека, через несколько месяцев инцидент почти гарантирован.
Как минимум рядом с кодом стоит оставить:
- какая версия предполагается
- что и в каком порядке ставить
- какой командой регистрировать
- это для x86 или для x64
8. С какими запросами эта тема хорошо сочетается
По этой теме ценность часто даёт уже сам разбор причин и прояснение политики, ещё до полной переработки.
Например, тема особенно хорошо ложится на такие запросы:
- разложить причины
0x80040154или0x80070005по bitness / регистрации / правам - после перехода на Visual Studio 2022 сломался Designer, и нужно понять, что ещё можно спасти
- оставить вендорский OCX, а окружение вокруг него сдвинуть к .NET и C#
- решить, насколько продлевать жизнь активов, жёстко привязанных к x86, и где начинать bridge / wrap / replace
- уйти от ручной зависимости от
regsvr32и заново спроектировать install / deploy
По выбору «оставить / обернуть / заменить» полезна также статья Как сегодня поступать с ActiveX / OCX — таблица решений «оставить, обернуть или заменить».
9. Итог
Когда разработка COM-компонентов или OCX / ActiveX упирается в проблему, причина почти всегда сводится к одной из этих четырёх.
- Не совпадает bitness
- Неверно выбран способ регистрации
- Область регистрации (HKCU / HKLM, 32-битный / 64-битный view) съехала
- Состояние, которое видно лишь случайно из-за прав, принимают за норму
После перехода Visual Studio 2022 на 64-бит старые схемы, которые раньше «как-то работали», стали проявляться гораздо заметнее.12 Поэтому при работе с COM / OCX / ActiveX кратчайший путь — сначала выровнять предпосылки окружения и только потом писать код.
Вместо того чтобы считать, сколько раз запущен regsvr32, полезнее разобрать:
- какой процесс хостит компонент
- какой разрядности этот процесс
- куда должна попасть регистрация
- действительно ли эта регистрация предполагает права администратора
- смотрите ли вы Designer и runtime раздельно
Так задача решается намного быстрее.
Справочные материалы
-
Microsoft Learn, Visual Studio 2022 version 17.0 Release Notes —
devenv.exe is now 64-bit only. ↩ ↩2 ↩3 -
Microsoft Learn, Troubleshoot 32-bit problems - Windows Forms — Visual Studio 2022 является 64-битным процессом и не может напрямую загружать 32-битные .NET / COM / ActiveX; ограничения out-of-process designer. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Classes and Servers — регистрация COM, HKCU / HKCR, саморегистрация и
DllRegisterServer. ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, Регистрация сборок с помощью COM — регистрация сборок .NET Framework для COM через
Regasm.exe. ↩ ↩2 -
Microsoft Learn, Предоставление доступа к компонентам .NET Core для COM —
EnableComHosting, генерируемый.comhost.dll,regsvr32,EnableRegFreeCom. ↩ ↩2 ↩3 -
Microsoft Learn, Merged View of HKEY_CLASSES_ROOT — HKCR как объединённое представление HKLM и HKCU. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, HKEY_CLASSES_ROOT Key — приложениям, которым нужны права администратора, рекомендуется регистрация в per-machine конфигурации COM. ↩ ↩2
-
Microsoft Learn, COM Error Codes (Generic) (Winerror.h) —
REGDB_E_CLASSNOTREG (0x80040154)и другие. ↩ -
Microsoft Learn, You receive 0x80070005 error when you try to register a DLL by using Regsvr32.exe — типичный случай сбоя регистрации DLL из-за нехватки прав. ↩ ↩2
-
Microsoft Learn, Registry Redirector — 32-битный / 64-битный view реестра в WOW64, физическое расположение
HKLM\SoftwareвWow6432Node, запрет трогать физический путь из кода приложения. ↩ ↩2 ↩3 ↩4 ↩5 -
Microsoft Learn, File System Redirector —
%windir%\System32на x64 Windows и перенаправление файловой системы WOW64. ↩ -
Microsoft Learn, Общие сведения о совместимости для 32-разрядных программ в 64-разрядных версиях Windows — перенаправление файлов / реестра через WOW64. ↩
-
Microsoft Learn, Regasm.exe (Assembly Registration Tool) — роль
Regasm.exeи параметры вроде/tlb. ↩ ↩2 ↩3 -
Microsoft Learn, Packaging a .NET Framework Assembly for COM — библиотеки типов и
Regasm.exe /tlb. ↩ -
Microsoft Learn, Registering the DLL Server for Surrogate Activation — условия попадания в суррогат, приоритет запуска EXE-сервера или службы при наличии
LocalServer/LocalServer32/LocalService, конфигурация, в которойAppIDсовпадает по GUID с CLSID. ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, DllSurrogate —
DllSurrogateподAppIDимеет типREG_SZ: пустая строка означает системный суррогат по умолчанию, путь — кастомный суррогат по этому пути. ↩ ↩2 -
Microsoft Learn, Using the system-supplied surrogate — пустая строка или
NULLзапускает системный суррогат по умолчанию; модель потоков и время жизни процесса внутри суррогата. ↩ ↩2 -
Microsoft Learn, CLSCTX enumeration — несколько значений
CLSCTX, переданных объединёнными по OR, пробуются в порядке перечисления; на стадии in-proc используется ключInprocServer32, если он есть; когда ни клиент, ни сервер не выражают предпочтения по разрядности, выбирается сервер, подходящий клиенту, а если такого нет — запускается сервер другой разрядности. ↩ ↩2 ↩3 -
Microsoft Learn, Understanding Custom Build Steps and Build Events — пример
regsvr32.exeв post-build событии. ↩ -
Microsoft Learn, Реестр Windows для опытных пользователей —
HKCU\Software\ClassesиHKLM\Software\Classes, поведение HKCR. ↩ -
Microsoft Learn, Find, install, and manage extensions for Visual Studio — обращение с per-user расширениями при elevated-запуске. ↩
-
Microsoft Learn, MFC ActiveX Controls: Licensing an ActiveX Control — лицензии design-time / run-time,
.LIC. ↩ -
Microsoft Learn, Application Settings, MFC ActiveX Control Wizard — генерация run-time лицензии и файл
.lic. ↩ -
Microsoft Learn, License information for this component not found. You don’t have an appropriate license to use this functionality in the design environment. ↩
-
Microsoft Learn, Aximp.exe (Windows Forms ActiveX Control Importer) — преобразование ActiveX в обёртку для WinForms. ↩
-
Microsoft Learn, AxHost Class — обёртка на базе AxHost, которую генерирует ActiveX Control Importer. ↩
-
Microsoft Learn, Инициализация COM-библиотеки —
CoInitializeEx, инициализация на каждом потоке, цикл сообщений STA. ↩ ↩2 ↩3 -
Microsoft Learn, Апартаменты Single-Threaded — цикл сообщений STA, маршалинг,
ThreadingModel. ↩ ↩2 ↩3 ↩4 ↩5 -
Microsoft Learn, Создание Registration-Free COM-объектов — COM без регистрации через activation context. ↩ ↩2
-
Microsoft Learn, Registration-Free COM-взаимодействие — registration-free COM interop в .NET Framework. ↩
-
Microsoft Learn, Assembly Manifests — атрибуты
assembly/assemblyIdentity/dependency/file/comClass, требование совпаденияassemblyIdentityна стороне REF и DEF, регистр имён, атрибуты семействаmiscStatus. ↩ ↩2
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
Office 2024/Microsoft 365: почему не работает ActiveX и как это диагностировать
Когда ActiveX не работает в Office 2024/Microsoft 365, разбираем порядок диагностики: отключение по умолчанию, 32-бит/64-бит, регистрация...
Что такое COM / ActiveX / OCX — различия и связь
Что такое COM, ActiveX и OCX: чем они отличаются, как связаны с OLE, где встречаются на практике и как к ним относиться сегодня.
Как сегодня поступать с ActiveX / OCX: оставить, обернуть или заменить
Как выбрать, оставить, обернуть или заменить ActiveX / OCX, с учётом 32-бит/64-бит, регистрации, зависимости от браузера и поддержки венд...
Что такое COM — почему дизайн Windows COM до сих пор красив
Разбираем, что такое COM: интерфейсы Windows COM, IUnknown, GUID и бинарная совместимость — и почему эта модель работает до сих пор.
Что продумать перед заказом разработки Windows-приложения
Перед заказом разработки Windows-приложения разберём, что стоит прояснить: доработка существующего ПО, интеграция с оборудованием, COM/Ac...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы Windows
Раздел о разработке Windows, расследовании сбоев и использовании существующих активов.
Миграция ActiveX
Решения о сохранении, обёртке или замене компонентов COM / ActiveX / OCX.
Услуги по этой теме
Статья напрямую связана со следующими услугами.
Использование и перенос существующих активов
Помогаем использовать и переносить активы COM / ActiveX / OCX и зависимости 32/64 бит.
Частые вопросы
Вопросы, которые часто возникают при консультациях по теме статьи.
- Как разбирать ошибку 0x80040154 (Class not registered)?
- Официально это REGDB_E_CLASSNOTREG — дословно «класс не зарегистрирован», но полная незарегистрированность тут не обязательна. Ошибка бывает и тогда, когда класс есть только в другом bitness-view реестра или только в HKCU, видимом одному пользователю. Быстрее всего сначала зафиксировать, 32-бит или 64-бит хост-процесс, затем проверить, тем ли способом регистрировали, и по порядку посмотреть, куда попала запись: HKCU или HKLM, 32-битный или 64-битный view.
- Почему после regsvr32 компонент всё равно не работает?
- regsvr32 рассчитан на нативные in-proc COM-серверы (DLL / OCX), которые экспортируют DllRegisterServer, и это не волшебная команда «зарегистрировать что угодно». Сборку .NET Framework для COM регистрируют через Regasm.exe; начиная с .NET 5 берут сгенерированный .comhost.dll (его даёт EnableComHosting) и уже его регистрируют через regsvr32. На x64 Windows 64-битный вариант — System32\regsvr32, 32-битный — SysWOW64\regsvr32: если попасть не в ту сторону, регистрация «успешна», но целевой процесс её не видит.
- Почему в Visual Studio 2022 ломается только Designer?
- Потому что devenv.exe в Visual Studio 2022 — 64-битный процесс и не может напрямую загрузить 32-битный COM / ActiveX. Приложение при выполнении может работать как x86, а Designer крутится в 64-битном процессе самой Visual Studio: отсюда перекос «рантайм жив, падает только Designer». AnyCPU сам по себе это не снимает, если дальше по цепочке остаётся COM / ActiveX, жёстко привязанный к 32-бит.
- Почему от администратора работает, а с обычными правами — нет?
- Чаще дело не в самих правах, а в том, что не совпала область регистрации или схема установки. COM сначала смотрит HKCU\Software\Classes, а HKEY_CLASSES_ROOT — это объединённое представление HKLM и HKCU. Если разработчик A зарегистрировал компонент вручную под своей учётной записью, у A всё работает, а у другого пользователя или сервисной учётки — нет; это обычная картина. Базово разделяют dev-регистрацию per-user и боевую per-machine через инсталлятор и не смешивают сборку с регистрацией.
Об авторе
Страница с профилем автора статьи.
Го Комура
Представитель KomuraSoft LLC
Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.