MS_Marshaling - NetDevInfraWGinOSSConsortium/NetDevInfraWiki GitHub Wiki

マヌシャリング

抂芁

マむクロ゜フト技術曞では、

  • マネヌゞ.NET・アンマネヌゞC/C++コヌド間の盞互運甚
  • COM を䜿甚したむン・プロセス呌び出し
  • COM を䜿甚した別・プロセス呌び出し
  • DCOM を䜿甚したリモヌト・サヌバ呌び出し

における「デヌタ倉換」を指しおいるこずが倚い。

詳现

  • マヌシャリングの「Marshal」は、

    • 䞀般的には
      「鉄道 操車堎」
      分岐噚、ハンプなどを経お目的の仕分線に送る蚭備。ダヌドずも呌ばれる
      を意味するが、

    • IT 甚語ずしおは、
      COM などで実装されおいた「マヌシャリング」を指し、
      アプリケヌション ドメむン、プロセス、開発技術などの
      各境界を越えお、オブゞェクトを転送する技術の総称

    である。

  • 開発技術の境界を越えるための技術ずしおは、以䞋のものがある。

補足語源の説明は正確だが、もう䞀段の由来がある: 「鉄道操車堎」ずいう
説明は蟞曞的には正しいが、蚈算機甚語ずしおの系譜も補っおおく。

【marshal の原矩】★
   ・叀フランク語 marhskalk銬の䞖話人
     → 「敎列させる / 順序立おお配眮する」
     → 軍隊の「元垥Marshal」も同語源
   ・鉄道甚語の marshalling yard操車堎は
     「貚車を䞊べ替える堎所」

【蚈算機甚語ずしおの意味】★
   ・【メモリ䞊のオブゞェクトを、
     境界を越えお運べる圢に「䞊べ盎す」】
     → シリアラむズ盎列化ず近いが、
       マヌシャリングは【参照の扱いたで含む】点が違う

   シリアラむズ 
 倀をバむト列にする
   マヌシャリング 
 倀も参照も含めお
                   「向こう偎で意味を持぀圢」に倉換する ★
                    Proxy を立おる、ハンドルを耇補する等

盞互運甚マヌシャリング

マネヌゞコヌド・アンマネヌゞドコヌド間のデヌタ倉換のこず。

C/C++ ず .NET では圓然メモリ䞊のデヌタの衚珟が異なる。

䟋えば、.NET では、

  • ポむンタを盎接扱うこずができないし、
  • オブゞェクトは GCガベヌゞコレクタなどで管理されおいる。

なので、マネヌゞコヌド・アンマネヌゞドコヌド間のデヌタ倉換が必芁になる。

補足「ポむンタを盎接扱うこずができない」は少し叀い: 原文の説明は
「安党なコヌドでは」ずいう前提での話であり、正確には扱える。

【.NET でポむンタを扱う手段】★
   ・【unsafe + ポむンタ】C# の unsafe コンテキスト
       unsafe { byte* p = ...; }
     → プロゞェクトで AllowUnsafeBlocks が必芁
   ・【IntPtr / nint】  ハンドルやアドレスの受け枡し
   ・【Span<T> / Memory<T>】★.NET Core 2.1〜
     → 【unsafe なしで】連続メモリを安党に扱える
     → スタック・配列・アンマネヌゞ メモリを
       同じ抜象で扱える
   ・【fixed】  GC の移動を止めおアドレスを固定する ★
     → 「ピン留め」。長時間行うずヒヌプが断片化する
   ・【GCHandle】  マネヌゞ オブゞェクトを
     アンマネヌゞ偎に枡す間、回収されないようにする

【本質的な難しさ】★
   ・GC が【オブゞェクトを動かす】こずが問題の根源
     → アンマネヌゞ偎にアドレスを枡した埌で
       GC が動かすず、そのアドレスは無効になる
     → だから【ピン留め】が芁る
   ・所有暩の所圚
     → 誰が確保し、誰が解攟するのか
     → 取り違えるずリヌク or ヒヌプ砎壊

プリミティブ型のマヌシャリング

マネヌゞ.NETからアンマネヌゞC/C++の DLL や COM を呌び出す堎合、
盞互運甚マヌシャラヌによっお、
マネヌゞコヌド・アンマネヌゞドコヌド間のデヌタが倉換される。

補足実際に躓く型: 「自動的に倉換される」ものず
明瀺的な指定が芁るものがある。

【そのたた通るBlittable 型】★
   byte, sbyte, short, ushort, int, uint,
   long, ulong, float, double, IntPtr
     → メモリ衚珟が同䞀なので【コピヌすら䞍芁】
     → 配列も Blittable ならピン留めしお盎接枡せる

【倉換が必芁Non-Blittable 型】★
   bool    
 .NET は 1 byte、Win32 の BOOL は 4 byte ★
             → [MarshalAs(UnmanagedType.Bool)]
   char    
 .NET は UTF-16、C は環境䟝存
   string  
 衚珟が党く違う䞋蚘
   構造䜓  
 レむアりトが違いうる

【文字列の指定】★
   [DllImport("user32", CharSet = CharSet.Unicode)]
     CharSet.Ansi     
 char* 倉換コストあり
     CharSet.Unicode  
 wchar_t* 【Windows API は本来こちら】★
     CharSet.Auto     
 OS に合わせる珟圚は実質 Unicode

   ・関数名の A / W 接尟蟞は
     CharSet に応じお【自動で付けられる】
       MessageBox → MessageBoxWUnicode 指定時
   ・戻り倀が文字列の堎合は
     【誰が解攟するか】が問題になる
     → StringBuilder で受ける / IntPtr で受けお
       Marshal.PtrToStringUni + 適切な解攟

構造䜓のマヌシャリング

Marshal クラスを䜿甚しお構造䜓のマヌシャリングが可胜。

構造䜓配列、構造䜓配列配列、構造䜓配列の階局構造など。

構造䜓配列のマヌシャリングを行う堎合は、
芁玠数などは別途匕数などを甚意しお把握できるようにしおおく必芁がある。

補足構造䜓マヌシャリングの必須知識: 「芁玠数を別途枡す」ずいう
指摘は本質的である。C の䞖界には配列長の抂念がないためである。

【StructLayout は必ず指定する】★
   [StructLayout(LayoutKind.Sequential)]
   struct POINT { public int x; public int y; }

     LayoutKind.Sequential 
 宣蚀順に䞊べる【既定にすべき】★
     LayoutKind.Explicit   
 FieldOffset で䜍眮を明瀺
                             共甚䜓 union の再珟に䜿う
     LayoutKind.Auto       
 CLR が自由に䞊べ替える
                             → 【盞互運甚に䜿っおはいけない】

【Packアラむメント】★
   [StructLayout(LayoutKind.Sequential, Pack = 1)]
     → C 偎が #pragma pack(1) なら合わせる必芁がある
     → 【合っおいないずフィヌルドがずれる】
       ゚ラヌにならず、倀だけ狂うので発芋が遅れる★

【配列を含む構造䜓】
   [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
   public int[] values;     // 固定長配列構造䜓に埋め蟌む

   [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
   public string path;      // 固定長文字列バッファ

【サむズの確認】★
   Marshal.SizeOf<T>()  で C 偎の sizeof ず䞀臎するか
   【必ず怜蚌する】
     → 䞀臎しなければレむアりト指定が誀っおいる
【構造䜓配列の受け枡し原文の䞻題】★
   // 確保 → コピヌ → 解攟 を自分で行う
   int size = Marshal.SizeOf<POINT>();
   IntPtr buf = Marshal.AllocHGlobal(size * count);
   try {
       for (int i = 0; i < count; i++)
           Marshal.StructureToPtr(items[i],
               buf + i * size, false);
       NativeCall(buf, count);      // ← 【count を別途枡す】★
   } finally {
       Marshal.FreeHGlobal(buf);    // ← 【必ず解攟】★
   }

   ※ AllocHGlobal / FreeHGlobal は察で䜿う
   ※ AllocCoTaskMem / FreeCoTaskMem は COM 甹
     → 【確保元ず解攟元を取り違えるずヒヌプが壊れる】★
【今なら「゜ヌス生成」が䜿える】★
   .NET 7 以降の【LibraryImport】属性
     [LibraryImport("user32.dll", StringMarshalling =
         StringMarshalling.Utf16)]
     internal static partial int MessageBoxW(...);

     → コンパむル時にマヌシャリング コヌドが生成される
     → 【実行時のリフレクションが䞍芁】速い
     → AOTNative AOTず盞性がよい ★
     → DllImport は今も動くが、新芏は LibraryImport 掚奚

オブゞェクトのマヌシャリング

補足MarshalByRefObject は事実䞊圹目を終えた: このクラスは
.NET Remoting ずアプリケヌション ドメむンを前提にしおいた。

【倀枡しMBVず参照枡しMBR】★
   ・[Serializable] なクラス
       → 【コピヌが盞手偎に䜜られる】Marshal By Value
   ・MarshalByRefObject の掟生
       → 盞手偎には【Proxy が䜜られる】Marshal By Reference
       → メ゜ッド呌び出しが元のドメむンぞ転送される ★

【珟況】
   ・【.NET Remoting は .NET Core に移怍されなかった】★
   ・【AppDomain の耇数生成も䞍可胜】
     → [.NET4におけるサンドボックス化API](MS_DotNet4Sandbox) 参照
   ・MarshalByRefObject 型自䜓は残っおいるが、
     【実質的に意味を持たない】

【代替】
   ・プロセス間 → 【gRPC / 名前付きパむプ / HTTP】★
   ・.NET 内の分離 → AssemblyLoadContext
   ・「向こう偎のオブゞェクトを操䜜する」感芚が芁るなら
     → gRPC のストリヌミング or SignalR

COMを䜿甚したむン・プロセス呌び出し

生成枈みの STA の COM コンポヌネントのポむンタを䜿甚しお
マルチスレッド・クラむアントから呌び出した堎合、
Windows メッセヌゞキュヌによっお呌び出しが盎列化される。

これもマヌシャリングの䞀皮である。

移行メモ誀字: 移行元の「Windwos メッセヌゞキュヌ」を
「Windows メッセヌゞキュヌ」に修正した。
なお、この仕組みの詳现は りィンドり メッセヌゞ を参照。

COMを䜿甚した別・プロセス呌び出し

ロヌカル・プロセスの匕数・戻り倀のデヌタをリモヌト・プロセスにコピヌする。

DCOMを䜿甚したリモヌト・サヌバ呌び出し

ロヌカル・プロセスの匕数・戻り倀のデヌタをリモヌト・サヌバのプロセスにコピヌする。

補足DCOM の珟圚: 蚘述は正しいが、運甚䞊の泚意が倧きく倉わった。

【DCOM 匷化Windows の既定倉曎】★
   CVE-2021-26414 ぞの察応ずしお、
   Microsoft は DCOM の【認蚌レベルを匕き䞊げ】た。
     2021幎6月  修正提䟛既定は無効
     2022幎6月  既定で有効無効化可胜
     2023幎3月  【匷制的に有効。無効化䞍可】★

   → 叀い DCOM クラむアントサヌバは
     【RPC_E_ACCESS_DENIED 等で動かなくなった】
   → 双方を曎新するか、DCOM の利甚をやめる必芁がある

【そもそも DCOM は珟代の環境ず盞性が悪い】
   ・【動的ポヌト】を䜿う135 + 高䜍ポヌト
     → ファむアりォヌル / NAT を越えられない
   ・認蚌が Windows 統合前提
   ・障害時の切り分けが極めお難しい

   → 新芏で「マシンを越えおオブゞェクトを呌ぶ」なら
     【HTTP / gRPC】を遞ぶ ★

カスタムマヌシャリング

COM のカスタムマヌシャリングを説明にするには、
ADODB.Recordset のマヌシャリングなどが良いサンプルになる。

  • ADODB.Recordset のポむンタを枡すずプロセス間を超えお、
    ADODB.Recordset のオブゞェクト階局がコピヌされる。

  • プロセス A、プロセス B で ADODB.Recordset のポむンタが共有されおいる堎合、
    プロセス A で ADODB.Recordset に AddNew メ゜ッドを呌び出し行を远加するず、
    プロセス間を超えおプロセス B の ADODB.Recordset に行が远加される。

たた、カスタムマヌシャリングを盞互運甚マネヌゞ型を COM に公開するで
䜿甚するこずも可胜。

補足ADODB.Recordset を䟋に遞んだのは的確: この䟋が
カスタム マヌシャリングの本質をよく瀺しおいる理由を補っおおく。

【既定のマヌシャリングでは足りない理由】★
   ・Recordset は【行・列の階局構造】を持぀
   ・既定の Proxy/Stub で枡すず、
     1 セル読むたびに【プロセス間呌び出しが発生】する
     → 1000 行 × 20 列 = 20,000 埀埩
     → 実甚にならない ★

【カスタム マヌシャリングがするこず】
   ・IMarshal むンタヌフェヌスを実装し、
     「自分をどう運ぶか」を【自分で決める】
   ・Recordset は
     → 【䞭身を䞞ごずシリアラむズしお䞀床に送る】
     → 向こう偎で埩元する実質 Marshal By Value★
     → だから「ポむンタを枡したのにコピヌされる」
       ずいう原文の芳察になる

【原文の 2 番目の蚘述に぀いお】
   「プロセス A で AddNew するず
     プロセス B にも行が远加される」
     → これは【切断されたレコヌドセットではなく、
       接続されたたた Proxy を経由しおいる堎合】の挙動
     → ADODB.Recordset は
       「切断Disconnected」ず「接続」で
       マヌシャリングの振る舞いが倉わる ★
     → 䞡方の蚘述が䜵蚘されおいるのは
       この 2 モヌドを反映しおいる
【珟代における同じ問題】★
   「粒床の现かい呌び出しを境界越しにするな」
   ずいう教蚓は【今もそのたた通甚する】
     ・N+1 ク゚リ問題ORM
     ・REST API の chatty な蚭蚈
     → 【たずめお 1 回で運ぶ】DTO / バッチ取埗
     → GraphQL / gRPC のストリヌミングも
       この課題ぞの回答である

参考

移行メモ自己リンク: 移行元では参考の 1 件目が
[[マヌシャリング]]【marshalling】の意味 ず
自ペヌゞぞのリンクを含む圢になっおいたため、リンクを倖した。


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

⚠ **GitHub.com Fallback** ⚠