32bitアプリから64bit DLLを呼ぶCOMブリッジ実例

· 更新日: · · COM, Windows開発, 32bit, 64bit

更新履歴(5件・最終更新 2026年08月02日)

この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。

記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
`LocalServer32`に実行ファイルのパスを引用符なしで格納していたのを直しました。`/d "%SERVER%"`と書くと引用符は`reg.exe`の引数解析で消えるため、`C:\Program Files\...`は「`C:\Program`に`Files\...`を渡す」とも解釈でき、`C:\Program.exe`を置ける環境ではそちらが起動され得ます。引用符ごと格納する形にし、確認用の`reg query`も添えました。
冒頭に対象読者と前提環境を追加し、レジストリ登録の手順を新設しました(必要な3キーの表、`reg add /reg:64`と`/reg:32`による登録と解除、EXE側でのクラスファクトリ登録、管理者権限なしでHKCUに登録する代替)。ProgIDが何のためにあるかの説明、用語ミニ辞書、.NET Frameworkと.NET 5以降の差の対比表、まとめの章を追加しました。
サンプルコードのクライアントが ProgID でサーバーを取得しているのに、サーバー側に ProgID を指定する属性が書かれておらず、そのままでは再現できませんでした。属性を補いました。
初版公開
この記事を引用する(DOI: 10.5281/zenodo.21589580)

この記事はZenodoにアーカイブされています。常に最新版へ解決されるDOIと、いま表示している版に固定されたDOIの両方を下に示します。

小村 豪(2026)「32bitアプリから64bit DLLを呼ぶCOMブリッジ実例」合同会社小村ソフト. https://doi.org/10.5281/zenodo.21589580 https://staging.comcomponent.com/blog/2026/01/25/002-com-case-study-32bit-to-64bit/

DOI(最新版)
10.5281/zenodo.21589580
DOI(この版)
10.5281/zenodo.21732574

32bit アプリから 64bit DLL を呼び出したい、という要件は Windows ではかなり典型的です。 特に既存資産を残したまま 64bit 側の機能だけ使いたいとき、COM ブリッジの構成が現実的な解になりやすいです。

対象読者: 32bit の既存 Windows アプリを保守していて、64bit 側の DLL やライブラリを使いたい方。COM を「聞いたことはあるが自分で組んだことはない」程度の前提で読めるように書いています。

前提環境: 64bit 版の Windows(x64)と、C# を書ける開発環境(Visual Studio など)。COM サーバーをマシン全体(HKEY_LOCAL_MACHINE 配下)に登録する作業には管理者権限が必要です。COM の基本的な考え方は「COM とは何か - Windows COM の設計が今でも美しい理由」で整理しています。

目次

  1. 想定状況
  2. 解決方法
  3. 処理の流れ(シーケンス図)
  4. サンプルコード(イメージ)
  5. 完全なサンプルコード
  6. まとめ
  7. 参考資料

この記事の知識マップ

32bitアプリはビット構成が一致しないin-proc COMとして64bit DLLを直接ロードできないため、この記事では64bit側の処理をCOM LocalServer(EXEサーバー)として別プロセスに分離し、IDLとTypeLibで共有したCOMインターフェース越しに型付きで呼び出します。マーシャリングとProxy/Stubがプロセス境界をまたぐ呼び出しを橋渡しし、CLSIDとProgIDの登録、そしてCLSIDサブキーが32bit viewと64bit viewで別になるWOW64レジストリリダイレクターへの両面登録が前提になります。マシン全体への登録には管理者権限が必要で、.NET(5以降)は標準のEnableComHostingがin-proc専用のためOut-of-procサーバーの登録処理は自前で書く必要があります。

32bit/64bit COMブリッジの知識マップ32bitアプリが64bit DLLをCOM LocalServerのEXEサーバーとして呼び出す構成が、型ライブラリ・マーシャリング・CLSID/ProgIDの登録・WOW64レジストリリダイレクターとどう関係するかを示す図利用する前提とする利用する利用する前提とする軽減する前提とする前提とする前提とするに保存されるで構成できる前提とする前提とする用いるのは非推奨前提とする利用する用いるのは非推奨前提とする利用する32bit/64bit COMブリッジCOM LocalServer(別プロセスCOMサーバー)型ライブラリ(TLB)マーシャリングProxy/StubIn-proc COM(DLLサーバー)bitness一致要件CLSID(Class ID)ProgID(Programmatic Identifier)WOW64レジストリリダイレクターWOW6432Nodereg.exe(/reg:32 /reg:64)クラスファクトリの登録管理者権限Regasm.exe.NET(Core以降).NET Framework.NET(5以降)のEnableComHosting/.comhost.dllCOM(コンポーネントオブジェクトモデル)

図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全19件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle

1. 想定状況

32bitの既存アプリはそのままに、64bit DLLの処理を使いたいというケースです。 ところが、32bitプロセスは64bit DLLを読み込めません。これはOSレベルの制約で、工夫すればどうにかなるという話ではありません。

よくあるのは、こんな状況です。

  • 既存の32bitアプリは資産として大きく、すぐには移行できない
  • 64bit DLL側に新機能がある、または依存ライブラリが64bitのみ
  • 32bit側から「型付きで」呼び出したい

この組み合わせでは、同一プロセス内で呼び出す道は最初から閉ざされています

2. 解決方法

この章から専門用語が続くので、先に最小限の言葉を並べておきます。

用語 意味
In-proc COM(DLLサーバー) COMコンポーネントを呼び出し側と同じプロセスに読み込んで使う形。速いがビット数が一致していないと読み込めない
Out-of-proc COM(EXEサーバー) COMコンポーネントを別プロセスとして起動して使う形。ビット数が違っても連携できる
LocalServer 同じPC内の別プロセスとして動くCOMサーバーのこと。レジストリの LocalServer32 キーにEXEのパスを登録する
IDL / TypeLib インターフェースの形(メソッド名・引数の型)を書いた定義(IDL)と、それをバイナリ化したもの(TypeLib)。両側が同じ「契約」を見るために使う
マーシャリング プロセス境界を越えるために、引数や戻り値を送れる形に詰め直すこと。逆はアンマーシャリング
Proxy / Stub マーシャリングを実際に行う代理コード。呼び出し側にProxy、サーバー側にStubが立つ
WOW6432Node 64bit Windows 上で、32bit アプリ向けのレジストリ内容が物理的に置かれる場所。同じキー名でも32bit側と64bit側で中身が別になる

解決の基本は、Out-of-proc COM(EXEサーバー)で分離することです。 64bit DLLは64bitのCOMサーバー(EXE)から呼び出し、32bitアプリはCOM経由で使います。

流れは次の通りです。

  1. 64bitのCOM LocalServer(EXE)を用意し、内部で64bit DLLを呼び出す
  2. COMインターフェース(IDL/TypeLib)を共有し、型を公開する
  3. 32bitアプリはCOMを「型付き」で呼び出す(Proxy/Marshalでやり取り)

ただし注意点もあります。

  • 32bit/64bitの登録は別(WOW6432Node含む)
  • 独自構造体はマーシャリング設計が必要
  • IPCのオーバーヘッドがあるため、高頻度呼び出しは注意

つまり、「64bitの処理を別プロセスに逃がして、COMで橋渡しする」のが王道です。

3. 処理の流れ(シーケンス図)

以下は、32bitアプリが64bit DLLの処理を呼び出すときの流れです。

登録済みのCOMマーシャリング基盤が処理64bit DLL64bit COM Server(EXE)COM Stub(64bit側)RPC/IPC(プロセス間通信)COM Proxy(32bit側)32bit クライアントアプリ64bit DLL64bit COM Server(EXE)COM Stub(64bit側)RPC/IPC(プロセス間通信)COM Proxy(32bit側)32bit クライアントアプリパラメータをマーシャリングパラメータをアンマーシャリング戻り値をマーシャリング戻り値をアンマーシャリングICalcService.Add(1, 2)シリアライズされたデータプロセス境界を越えて転送Add(1, 2)ネイティブ関数呼び出し結果: 3結果: 3シリアライズされた結果プロセス境界を越えて転送結果: 3

ポイント:

  • 32bitアプリは ICalcService インターフェースを通じて型安全に呼び出せる
  • COMランタイムは、登録済みの Proxy/Stub DLL、TypeLib マーシャラー、標準マーシャラーなどを使ってプロセス境界を越える
  • プロセス間通信のオーバーヘッドがあるため、細かい呼び出しより一括処理が望ましい

4. サンプルコード(イメージ)

4.1. 共有インターフェースとサーバー・クライアント

以下は概念のイメージです。動かすには、このあと 4.2 の登録が必要です。

// 共有インターフェース(IDL相当)
[ComVisible(true)]
[Guid("7A4B5B23-0A2F-4D2B-9D4D-8A2A92B8B001")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ICalcService
{
    int Add(int a, int b);
}

// 64bit COM LocalServer(EXE側)
[ComVisible(true)]
[Guid("1C9B6F4D-1E9A-4E61-9A4F-6A0F1D2D9A11")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("KomuraSoft.CalcService")]
public class CalcService : ICalcService
{
    public int Add(int a, int b)
    {
        // ここで64bit DLLを呼び出す
        return a + b;
    }
}

// 32bitアプリ側(クライアント)
Type t = Type.GetTypeFromProgID("KomuraSoft.CalcService");
var calc = (ICalcService)Activator.CreateInstance(t);
int result = calc.Add(1, 2);

この形にすると、32bit側は「型付きで」扱えます。 COMが内部でプロキシ/スタブを使い、IPC経由で呼び出してくれます。

[ProgId("KomuraSoft.CalcService")] を付けているのは、クライアント側が Type.GetTypeFromProgID("KomuraSoft.CalcService") で探せるようにするためです。ProgID は「人間が読める別名」にすぎず、実際にサーバーを見つけるのは次に説明する CLSID の登録です。

4.2. 最低限の登録手順

COMは「レジストリに登録されているCLSIDを、COMランタイムが引いて起動する」仕組みなので、登録していないコードは絶対に動きませんType.GetTypeFromProgIDnull を返すか、CreateInstanceREGDB_E_CLASSNOTREG になります)。EXEサーバー(LocalServer)に必要な登録は、突き詰めると次の3つのキーだけです。

登録するもの キー
ProgID → CLSID の対応 HKEY_CLASSES_ROOT\KomuraSoft.CalcService\CLSID {1C9B6F4D-1E9A-4E61-9A4F-6A0F1D2D9A11}
CLSID → EXEのパス HKEY_CLASSES_ROOT\CLSID\{1C9B6F4D-…}\LocalServer32 64bit COMサーバーEXEのフルパス
CLSID → ProgID の逆引き HKEY_CLASSES_ROOT\CLSID\{1C9B6F4D-…}\ProgID KomuraSoft.CalcService

ここでこの記事の主題そのものの落とし穴があります。HKEY_LOCAL_MACHINE\SOFTWARE\Classes は32bitアプリと64bitアプリで共有されるのに対し、その配下の CLSID サブキー(および Interface など)は32bit側と64bit側で別々(32bit側の実体が WOW6432Node)と、Microsoft のドキュメントに明記されています。つまり ProgID のキーは一度書けば両方から見えますが、CLSID の登録は32bitビューと64bitビューの両方に書かないと、32bitクライアントからサーバーが見つかりません

管理者権限のコマンドプロンプトで、reg コマンドの /reg:32 /reg:64 を使うのが確実です(Wow6432Node を自分でパスに書くのは、Microsoft が非推奨としています)。

:: 管理者権限のコマンドプロンプトで実行する
set CLSID={1C9B6F4D-1E9A-4E61-9A4F-6A0F1D2D9A11}
set PROGID=KomuraSoft.CalcService
set SERVER=C:\Program Files\KomuraSoft\CalcServer.exe

:: 1) ProgID → CLSID(HKLM\SOFTWARE\Classes 直下は32/64で共有)
reg add "HKLM\SOFTWARE\Classes\%PROGID%\CLSID" /ve /d "%CLSID%" /f

:: 2) CLSID → LocalServer32 と ProgID(CLSID配下は32/64で別なので両方に書く)
::    LocalServer32 の値には、実行ファイルのパスを引用符ごと入れる。
::    /d "%SERVER%" と書くと引用符は reg.exe の引数解析で消え、値は
::    C:\Program Files\... のまま格納される。COM はこれをコマンドラインとして
::    解釈するため、空白の手前で切れた C:\Program.exe を先に探しに行く
reg add "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\LocalServer32" /ve /d "\"%SERVER%\"" /f /reg:64
reg add "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\ProgID"        /ve /d "%PROGID%"     /f /reg:64
reg add "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\LocalServer32" /ve /d "\"%SERVER%\"" /f /reg:32
reg add "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\ProgID"        /ve /d "%PROGID%"     /f /reg:32

:: 格納された値を確認する。"C:\Program Files\..." と引用符付きで出れば正しい
reg query "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\LocalServer32" /ve /reg:64

LocalServer32 の引用符は、単に行儀の問題ではありません。引用符が無いと、C:\Program Files\... は「C:\ProgramFiles\... を引数として渡す」という解釈が成り立ってしまいます。その結果、C:\Program.exe を作れる権限を持つ相手がいる環境では、そちらが先に起動され得ます。空白を含むパスへ入れるなら必ず引用符を付けてください。

解除は同じキーを消すだけです。

set CLSID={1C9B6F4D-1E9A-4E61-9A4F-6A0F1D2D9A11}
set PROGID=KomuraSoft.CalcService

reg delete "HKLM\SOFTWARE\Classes\CLSID\%CLSID%" /f /reg:64
reg delete "HKLM\SOFTWARE\Classes\CLSID\%CLSID%" /f /reg:32
reg delete "HKLM\SOFTWARE\Classes\%PROGID%" /f

加えて、EXE側は起動したときに「自分がこのCLSIDの担当だ」とCOMに名乗り出る必要があります。C/C++なら CoRegisterClassObject、.NET Framework なら RegistrationServices.RegisterTypeForComClients がこれにあたります。レジストリ登録はCOMがEXEを起動するところまでしか面倒を見てくれないので、この名乗りが抜けているとEXEは起動するのにオブジェクトが作れない、という分かりにくい失敗になります。

なお、開発中に試すだけなら HKLM の代わりに HKCU\SOFTWARE\Classes へ同じ構造で書けば管理者権限なしでも登録できます(HKEY_CLASSES_ROOT は HKLM と HKCU の合成ビューです)。ただし HKCU\SOFTWARE\Classes\CLSID も同じく32bit/64bitで別扱いなので、両方のビューに書く点は変わりません。

4.3. .NET Framework と .NET(5以降)で作り方が違う

上のコードはC#ですが、どちらの.NETを使うかで手順が大きく変わります。ここを混ぜると詰まります。

  .NET Framework .NET(Core 3.0 / 5以降)
登録ツール RegAsm.exe がある(ただし作られるのは in-proc 用の InprocServer32 登録なので、LocalServer32 は結局自分で書く) RegAsm 相当のツールはない
標準的なCOM公開 アセンブリに属性を付けて RegAsm <EnableComHosting>true</EnableComHosting>*.comhost.dll を生成し regsvr32 で登録(in-proc のみ
TypeLib(.tlb)の生成 TlbExp / RegAsm /tlb で生成できる サポートされない。IDLを手で書いて MIDL でコンパイルする(.NET 6以降は、できあがった .tlb をcomhostに埋め込むことは可能)
CLSIDの指定 省略可 COMから生成させるクラスには CLSID の明示が必須
AnyCPU の扱い 32bit/64bit 両方のクライアントから使える 付随する *.comhost.dll が既定で64bitなので、64bitクライアントからしか使えない

本記事の構成(EXEサーバー)は、.NET(5以降)では標準の EnableComHosting の範囲外なので、登録処理を自分で書くことになります。Microsoft公式のサンプルとして OutOfProcCOM が用意されているので、.NET 側で組む場合はそちらが出発点になります。

5. 完全なサンプルコード

上記の概念を実際に動作する形で実装したサンプルを GitHub で公開しています。

Call64bitDLLFrom32bitProc - GitHub

このリポジトリには以下が含まれています:

  • Call64bitDLLFrom32bitProc/ - 64bit COM LocalServer (EXE)
  • X64DLL/ - 64bit DLL(実処理)
  • X86App/ - 32bit クライアント (WinForms)
  • scripts/ - COM サーバー登録・解除スクリプト

README に記載の手順に従ってビルド・登録すれば、実際に 32bit プロセスから 64bit DLL を呼び出す動作を確認できます。

6. まとめ

COMブリッジは万能ではなく、向く仕事と向かない仕事がはっきり分かれる構成です。採用を決める前に、次の表で自分のケースを当てはめてみてください。

向いているケース 向かないケース
32bitアプリ本体を作り直せない(改修コストが見合わない) そもそも32bit側を64bitに再ビルドできる(それが最短)
呼び出しが粗い粒度(1回で画像1枚、1回でファイル1本など) 1要素ずつ数万回など、細かい呼び出しを高頻度で回す(IPCのオーバーヘッドが支配的になる)
やり取りするのが数値・文字列・配列など、マーシャリングしやすい型 生ポインターや複雑な独自構造体を大量に往復させる
64bit側の処理が落ちても、アプリ本体は生かしておきたい(プロセス分離が利点になる) サーバー側のクラッシュや再起動を扱う復旧処理を書きたくない
型付きの呼び出し(IntelliSenseやコンパイル時チェック)を残したい 単発のバッチ処理で済み、標準入出力やファイル経由の受け渡しで十分

最後の行の裏返しとして、「64bit側の処理を単なるコンソールEXEにして、引数とファイルでやり取りする」という素朴な代替案も常に検討する価値があります。COMブリッジが効くのは、型付きの呼び出しを維持したい場合と、状態を持ったサーバーを何度も呼びたい場合です。

次のアクションとしては、この順番をおすすめします。

  1. まず 5章のサンプルリポジトリを clone して、README のとおりにビルド・登録し、動く状態を1つ手元に作る
  2. 自分の 64bit DLL の関数を1つだけ選び、サンプルの ICalcService に相当するインターフェースにメソッドを1本足して通す。
  3. 通ったら、呼び出し回数と1回あたりのデータ量を測る。ここで「粗い粒度に寄せる」設計判断(複数回の呼び出しを1回にまとめる)を先に済ませておくと、後戻りが減ります。

7. 参考資料

  • Component Object Model (COM) の概要 https://learn.microsoft.com/en-us/windows/win32/com/component-object-model–com–portal
  • COM LocalServer32 の登録 https://learn.microsoft.com/en-us/windows/win32/com/localserver32
  • COM インターフェースの基本 https://learn.microsoft.com/en-us/windows/win32/com/the-component-object-model
  • COM Interop(.NETからの利用) https://learn.microsoft.com/en-us/dotnet/standard/native-interop/cominterop
  • WOW64 のレジストリリダイレクター(HKLM\SOFTWARE\Classes は共有、CLSID 配下は32/64で別) https://learn.microsoft.com/en-us/windows/win32/winprog64/shared-registry-keys
  • .NET(Core / 5以降)のコンポーネントをCOMに公開する https://learn.microsoft.com/en-us/dotnet/core/native-interop/expose-components-to-com

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

この記事は次のサービスページにつながります。近い入口からご覧ください。

よくある質問

この記事のテーマについて、相談時によくある質問をまとめています。

32bitアプリから64bit DLLを直接呼び出せますか?
できません。32bitプロセスは64bit DLLを読み込めず、これはOSレベルの制約で、工夫すれば回避できるという話ではありません。同一プロセス内で呼び出す道は最初から閉ざされているため、64bit側の処理を別プロセスへ分離する構成が必要になります。
32bitアプリから64bit DLLの機能を使うにはどうすればよいですか?
Out-of-proc COM(EXEサーバー)で分離するのが王道です。64bit DLLは64bitのCOM LocalServer(EXE)から呼び出し、32bitアプリはCOMインターフェース経由で型付きで利用します。COMインターフェース(IDL/TypeLib)を共有して型を公開し、COMランタイムがプロキシ/スタブとプロセス間通信で境界を越えてくれます。
COMブリッジ構成の注意点は何ですか?
主に3点あります。32bit/64bitの登録は別である(WOW6432Node含む)こと、独自構造体はマーシャリング設計が必要なこと、そしてプロセス間通信のオーバーヘッドがあるため高頻度の細かい呼び出しには注意が必要なことです。細かい呼び出しを大量に流すより、一括処理に寄せるのが望ましいです。
実際に動作するサンプルコードはありますか?
あります。GitHubのCall64bitDLLFrom32bitProcリポジトリに、64bit COM LocalServer(EXE)、64bit DLL、32bitクライアント(WinForms)、COMサーバー登録・解除スクリプトを含むサンプル一式が公開されています。READMEの手順に従ってビルド・登録すれば、32bitプロセスから64bit DLLを呼び出す動作を確認できます。

著者プロフィール

記事の著者プロフィールページです。

小村 豪

合同会社小村ソフト 代表

Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。

ブログ一覧に戻る