COM/OCX/ActiveXの登録トラブル
bitness・登録先・ホスト・権限の不一致によって起きる、COMやOCX/ActiveXの典型的な登録トラブル群。
Visual Studio 2022
devenv.exeが64bit onlyになったバージョンのVisual Studio。
WOW64レジストリリダイレクター
64bit Windows上で32bitアプリと64bitアプリに別々のレジストリviewを見せる仕組み。HKLM\SOFTWARE\Classes直下は共有される一方、CLSIDサブキー配下は32bit側と64bit側で別になる。
レジストリリダイレクタ
ActiveX
実務ではCOMベースのコントロールやその周辺資産をまとめて指す言葉として使われる技術群。特に.ocx形式のUIコントロールやIE/コンテナへ埋め込む部品を含むことが多い。
ActiveXコントロール
OCX
ActiveXコントロールの実体を持つ.ocx形式のファイル。in-procの形態でホストプロセスに読み込まれる。
.ocxファイル
OLEカスタムコントロール
bitness一致要件
ネイティブモジュール(in-procのCOMコンポーネント、P/Invokeで呼ぶDLL、ベンダー提供SDKのDLLなど)を読み込むホストプロセスと、そのモジュールのビット構成(32bit/64bit)が一致していなければならないという制約。
32bit/64bitの一致
COM(コンポーネントオブジェクトモデル)
公開インターフェース、レジストリ登録、Apartment Modelなどを土台にした、Windowsのバイナリ互換コンポーネントモデル。
HKEY_CURRENT_USER\Software\Classes
COMがコンピューター全体の情報を見る前に先に参照する、そのユーザーだけに見えるCOMクラス登録の場所。
CLSID(Class ID)
COMコンポーネントの実装(クラス)を一意に指すGUID。レジストリ上の登録の鍵になる識別子。
WOW6432Node
64bit Windows上で32bitアプリ向けのレジストリ内容が物理的に置かれる場所。
regsvr32
DLLやOCXなどのCOMコンポーネントをレジストリへ登録・登録解除するコマンドラインツール。
In-proc COM(DLLサーバー)
COMコンポーネントを呼び出し側と同じプロセスに読み込んで使う形態。
DLLサーバー
Regasm.exe
.NET FrameworkのアセンブリをCOMへ登録するための登録ツール。
.NET Framework
Windowsのコンポーネントとして扱われ、インストールされているOSのライフサイクルに沿ってサポートされるランタイム。System.Messagingの対象範囲。
.NET(5以降)のEnableComHosting/.comhost.dll
.NET 5以降でCOM公開を行うための仕組み。ビルド時に.comhost.dllを生成しregsvr32で登録する(既定でin-procのみ)。
.NET(Core以降)
.NET Frameworkの後継となる、クロスプラットフォーム対応の.NETランタイム(.NET 5以降)。
モダンな.NET
WOW64ファイルシステムリダイレクター
x64 Windows上で、64bitアプリ用の%windir%\System32と32bitアプリ向けの別の場所(SysWOW64)を振り分ける仕組み。
管理者権限
コンピューター全体の設定を変更できる権限。
昇格
0x80040154(Class not registered)
公式にはREGDB_E_CLASSNOTREGと呼ばれるCOMのエラーで、完全な未登録だけでなく、別ビット数側のレジストリviewにだけ登録されている場合や、そのユーザーにしか見えないHKCU側にだけ登録されている場合にも起きる。
per-user登録
そのユーザーのアカウントだけで有効な、開発機での手動登録などに使われるCOM登録のスコープ。
ユーザー単位の登録
HKEY_CLASSES_ROOT(HKCR)
HKLM\Software\ClassesとHKCU\Software\Classesのmerged view。
HKEY_LOCAL_MACHINE\Software\Classes
そのマシンの全ユーザーに見える、コンピューター全体のCOMクラス登録の場所。
per-machine登録
そのマシンの全ユーザーで有効な、インストーラーで行うべきCOM登録のスコープ。
マシン単位の登録
ActiveXのデザイン時/実行時ライセンス
古いActiveXコントロールで、フォームへの貼り付け(デザイン時)と実行時とで別々に必要になることがあるライセンスの仕組み。
AxImp(ActiveX Control Importer)
.NET Framework付属のツールで、OCXからCOM型のランタイム呼び出し可能ラッパーと、AxHost派生のWindows Forms用ラッパーアセンブリを生成する。生成ファイル名はProgIDから決まる。
型ライブラリ(TLB)
インターフェース、メソッド、引数の型といった型情報をバイナリで持つファイル。VB6や.NETから型付きでCOMを呼び出す際の情報源になる。
AxHost
Windows Forms上でActiveXコントロールをホストするための基底クラス。
COMアパートメントモデル(STA/MTA)
どのスレッドからCOMコンポーネントを呼んでよいかを決める、COMのスレッドモデルの約束。
メッセージループ
スレッドがGetMessage/DispatchMessageなどで自分のウィンドウ宛てのメッセージを順に取り出し処理し続ける仕組み。入力・描画・イベント処理を担うと同時に、STAが持つOleMainThreadWndClassという隠しウィンドウ宛てに届く、別アパートメントからの呼び出しメッセージもここでディスパッチされる。
CoInitializeEx
COMを使う各スレッドが個別に呼んで初期化する必要があるWin32 API。
STAスレッドのブロックによるデッドロック
UIスレッドでTask.Wait()やWaitOneなどの同期待ちを行うと、メッセージの取り出しが止まりCOMのコールバックとアパートメント間の呼び出しが配達されなくなり、デッドロックになる状態。
マーシャリング
プロセス境界だけでなく、同一プロセス内のCOMアパートメント境界やマネージド/ネイティブ境界のような呼び出し境界を越えて、引数や戻り値をやり取りできる形に詰め直すこと。
Reg-Free COM(登録不要COM)
レジストリ登録なしに、アプリ側のマニフェストに書いたCLSIDやProgIDなどの情報でCOMコンポーネントを解決する仕組み。
アセンブリマニフェスト
どのファイルがどのCLSIDを提供するかをcomClass要素で宣言する、Reg-Free COMで使うマニフェストファイル。依存側のassemblyIdentityはコンポーネント側と完全に一致していなければならない。
COM LocalServer(別プロセスCOMサーバー)
COMサーバーをEXEとして別プロセスで動かす形態。bitnessの壁を越えたり、クラッシュを隔離したりできる。レジストリのCLSID配下のLocalServer32キーにEXEパスを登録する。
out-of-proc COMサーバー
ProgID(Programmatic Identifier)
CLSIDに付けた、人が読める文字列の名前。`Excel.Application`のような形でCLSIDに対応付けられる。
COM/ActiveXの登録トラブルの多くは、CLSIDサブキーが32bit viewと64bit viewで別になるWOW64レジストリリダイレクターの理解不足から生じる。
Visual Studio 2022がdevenv.exeとして64bitプロセスになったことで、32bit前提のデザイン時連携が壊れる問題が表面化しやすくなった。
Visual Studio 2022は64bitプロセスのため、x86固定のCOM/ActiveXコンポーネントをデザイン時に直接ロードできない。AnyCPUにしても、依存先が32bit固定のActiveXなら問題は残る。
in-procのOCXは読み込むホストプロセスとビット構成(32bit/64bit)を揃える必要があり、64bitアプリから32bit OCXを直接呼ぶことはできない。
OCXはActiveXコントロールの実体を持つ.ocx形式のファイルである。
COMはHKEY_CURRENT_USER\Software\Classesを先に見てから、コンピューター全体の情報を扱う。
64bit Windows上で32bitのCOMクラスを登録した場合、CLSIDの配下はWOW64のリダイレクト対象となり、物理的な置き場所はWow6432Nodeになる。64bit登録はネイティブビューに置かれ、32bit WindowsにはWow6432Nodeの分岐自体がない
regsvr32はDllRegisterServer/DllUnregisterServerをエクスポートするネイティブのin-proc COMサーバー(DLL/OCX)向けの登録コマンドである。
COMで使用する.NET Frameworkアセンブリの登録にはRegasm.exeを使う。
regsvr32は何でも登録できる汎用コマンドではなく、.NET Frameworkアセンブリの登録には向かない。
.NET 5+/6+/8+のCOM公開ではEnableComHostingを設定してビルドし、生成された.comhost.dllをregsvr32で登録する。
x64 WindowsではSystem32用とSysWOW64用のregsvr32を使い分ける必要があり、これはWOW64のファイルシステムリダイレクターによる32bit側の別配置に対応する。
標準ユーザーでregsvr32を実行するとレジストリやSystem32へ書き込めず0x80070005になることがある。
対象が別のレジストリviewにだけ登録されている場合、正しいCLSIDが見えず0x80040154(Class not registered)になり得る。
そのユーザーにしか見えないHKCU側にだけ登録されている場合も、他のユーザーやサービスアカウントからは0x80040154になり得る。
HKEY_CLASSES_ROOTは、HKLM\Software\Classesの内容を継承して見せるmerged viewの一方の入力である。
HKEY_CLASSES_ROOTは、HKCU\Software\Classesの内容を継承して見せるmerged viewのもう一方の入力である。
管理者権限を必要とするアプリケーションは、依存COMオブジェクトをインストール時にper-machineのCOM構成ストアへ登録するべきとされる。
古いActiveXコントロールではデザイン時ライセンスと実行時ライセンスが分かれていることがあり、開発機にライセンスファイルがないとフォームに貼れないことがある。
AximpはOCXが持つ型情報(型ライブラリ)をもとに、型付きのラッパーアセンブリを生成する。
Aximpが生成するWindows Forms用ラッパーは、AxHostから派生したクラスとして作られる。
AxHostはWindows Forms上でActiveXコントロールをホストするための基底クラスの実装を担う。
STA(single-threaded apartment)ではメッセージループが必要で、これは作法ではなく、隠しウィンドウ宛てのメッセージとして呼び出しを配達する仕組みそのものである。
COMを使う各スレッドはCoInitializeExで個別に初期化する必要がある。
メッセージループが動き続けることで別アパートメントからの呼び出しメッセージが取り出され続け、UIスレッドをブロックして取り出しを止めることによるデッドロックを避けられる。
STAのオブジェクトのインターフェイスポインターを別スレッドへそのままコピーしてはならず、アパートメントをまたぐにはマーシャリングする必要がある。
Reg-Free COMは、レジストリの代わりにアセンブリマニフェストへCLSIDと実体ファイルの対応を書くことでアクティブ化情報を持たせる。
activation contextにアクティブ化情報を持たせることで、レジストリ登録に伴う環境差の事故を減らせる。
LocalServer32はCLSIDサブキーの配下にあり、out-of-procサーバーのEXEパスを保持する。
ProgIDから実際のCLSIDを引く対応が登録されていて初めて、ProgID経由でサーバーを見つけられる。
HKLM\SOFTWARE\Classes\CLSID配下はWOW64のリダイレクト対象で、32bit側の物理的な置き場所はClassesの下のWow6432Nodeになる。
CLSIDはHKEY_CLASSES_ROOT\CLSID\{CLSID}のようにレジストリのCLSIDキー配下に登録され、そのマージ元はHKLMとHKCUのClasses配下である。
特にUI系のOCX/ActiveXはSTA前提であることが多く、STAスレッドをブロックするとコールバックの配達が止まって固まる。
.NET 5+/6+/8+のCOM公開で生成された.comhost.dllも、規約通りregsvr32で登録する。