Skip to content

MS_ManagedUnmanagedBridge

nishi_74322014 edited this page Aug 21, 2026 · 2 revisions

マネージドコードとアンマネージドコードのブリッジ

概要

移行メモ(最新化:COM は後にサポートされた): 「COM は
サポートされていない模様」は .NET Core 2.x 時点の記述であり、
現在は状況が変わっている。

現状
P/Invoke 全プラットフォームでサポート(Windows / Linux / macOS)
COM 相互運用(.NET → COM) .NET Core 3.0 以降でサポート(Windows 限定)
COM 公開(.NET → COM サーバー) .NET 5 以降でサポート(ComVisible + EnableComHosting
C++/CLI .NET Core 3.1 以降でサポート(Windows 限定

ただし、いずれも Windows 専用機能であり、
.NETのクロスプラットフォーム対応の観点では
「Linux では使えない」という制約は変わっていない。
TFM も net8.0-windows が必要になる。

方法

アンマネージドコードのインターフェイスをマネージドコード(.NET)から使用する方法

以下の 2 つの方法があります。

対象 方法
DLL P/Invoke[DllImport])で関数を宣言して呼ぶ
COM RCW(Runtime Callable Wrapper)を生成して参照する(tlbimp.exe / VS の参照追加)

マネージドコード(.NET)のインターフェイスをアンマネージドコードに公開する方法

以下の 2 つの方法があります。

対象 方法
DLL インターフェイス .NET のアンマネージド拡張(C++/CLI)でブリッジ DLL を作り、DLL/COMユーザに公開する
COMインターフェイス .NET アセンブリに ComVisible を付け、CCW(COM Callable Wrapper)として公開する

補足(4 象限で整理する): 上記 4 つを 1 表にすると全体像が掴みやすい。

→ アンマネージド → マネージド
マネージドから呼ぶ P/Invoke(DLL)
RCW(COM)
アンマネージドから呼ぶ C++/CLI ブリッジ(DLL)
CCW(COM)

「どちらが呼ぶ側か」と「DLL か COM か」の 2 軸で決まる。

サンプルコード

.NET(manage)→ VC、VB6(unmanage)の相互運用

アンマネージコードからマネージコードを呼び出す

上記の逆(VC、VB6(unmanage)→ .NET(manage))を行う相互運用。

  • Other-DotNETBridge

    Win32 DLL と C++/CLI のブリッジ DLL を使用して、
    アンマネージコードからマネージコードを呼び出す。

コールバック

.NET側のコールバック関数をC/C++から呼び出したい

  • P/Invoke(相互運用マーシャラー)に標準実装されている模様。
  • 恐らく、自動的にマーシャリングされるのだと思います。
    • 行きはマーシャリング(デリゲートからポインタ)
    • 帰りもマーシャリング(多分 C にラッパのポインタが渡る)

補足(デリゲートの寿命に注意): この方向(.NET のデリゲートを
関数ポインタとしてネイティブに渡す)は動作するが、
最も事故が多い箇所でもある。

  • マーシャラーはデリゲートに対する**ネイティブのサンク(thunk)**を生成する。
  • しかし、デリゲート本体を GC が回収するとサンクも無効になる。
  • ネイティブ側があとから呼ぶと AccessViolationException で落ちる。
// NG: 呼び出しから抜けた瞬間、GC の対象になる
NativeApi.Register(new MyCallback(OnEvent));

// OK: フィールドで保持して寿命を延ばす
private static MyCallback _cb = OnEvent;
NativeApi.Register(_cb);

「デバッグ ビルドでは動くが、本番でしばらく動かすと落ちる」という
典型的な症状は、ほぼこれである。

C/C++側のコールバック関数を.NETから呼び出したい

  • 上記の逆
  • マーシャリングの動作のイメージができていないと、
    正しい処理を実装できない可能性があります。

質問

  • C++ の EXE から C#のメソッドを呼び出します。
  • このときの引数で C++ 側のコールバック関数のアドレスを渡します。
  • このあと、外的要因で C# の DLL から上記のコールバックを
    呼び出そうとしていますが、コールバック呼び出しで例外エラーとなります。

回答

  • C++ から C# は直接呼べないので、アンマネージド拡張(C++/CLI)で
    DLL のエクスポート関数経由で .NET に接続する必要があると思います。
  • また、.NET に C の関数ポインタを直接渡しても .NET からの呼び出しは
    できないので、C++/CLI 経由にする必要があると思います。

そこで思いついたのは、

  1. C++/CLI 内部でコールバックするメソッドを持ったオブジェクトを作成し、
    C# のメソッド経由でそのオブジェクトを渡す。
    C# からこのオブジェクトのメソッドを経由して C++ にコールバックする。
  2. C++/CLI で C# から呼ぶための DLL エクスポート関数を作る。
    このエクスポート関数に C の関数ポインタを渡してコールバックする。

という 2 つの方法です。

結果

上記の 1. で解決しました。

補足(現在なら別解もある): 「.NET に C の関数ポインタを直接渡しても
呼び出せない」という当時の前提は、現在では緩和されている。

// .NET 5 以降:関数ポインタを直接扱える
delegate* unmanaged<int, int> fp = (delegate* unmanaged<int, int>)ptr;
int r = fp(42);

C# 9 の 関数ポインタ(delegate*
[UnmanagedCallersOnly] 属性により、
C++/CLI を挟まずに双方向のコールバックを実装できるようになった。

従来 現在(.NET 5 以降)
ネイティブ → .NET C++/CLI ブリッジが必要 [UnmanagedCallersOnly] で直接エクスポート可
.NET → ネイティブの関数ポインタ Marshal.GetDelegateForFunctionPointer delegate*(unsafe だが高速)

ただし unsafe が必要で、GC やスレッドの扱いを誤ると
容易にプロセスごと落ちる。C++/CLI 経由の方が安全である点は変わらない。

クロスプラットフォーム化

補足(Linux 側の現在の解): C++/CLI が Linux 非対応であることは
現在も変わらない。Linux でネイティブとの相互運用が必要な場合は、
次のいずれかを使う。

方向 手段
.NET → ネイティブ P/Invoke.soDllImport)。Linux でも動作する
ネイティブ → .NET [UnmanagedCallersOnly] + NativeAOT / nethost でエクスポート関数を公開

後者は、.NET のクラス ライブラリを NativeAOT
共有ライブラリ(.so / .dll)としてビルドし、
C/C++ から普通の関数として呼ぶ、という方式である。
C++/CLI の代替として、現在はこれが標準的な答えになっている。

参考

microsoft.com

@IT

P/Invoke


Tags: 移行, Windows, プログラミング, .NET開発

NetDevInfraWiki

マイクロソフト系技術情報 Wiki
Open 棟梁 Wiki

(未着手)

開発基盤部会 Wiki

移行管理: DONETODO

Clone this wiki locally