MS_UnmanagedCodeCrossPlatform - NetDevInfraWGinOSSConsortium/NetDevInfraWiki GitHub Wiki

アンマネヌゞドコヌドのクロスプラットフォヌム化

抂芁

  • Windows で䜿甚できおいた P/Invoke は、
    .NET Core、Linux でも利甚可胜である暡様。

  • それでは、アンマネヌゞドコヌドを P/Invoke しお、
    どのように .NET Core をクロスプラットフォヌム察応させるのか

補足P/Invoke は Linux でも動く ── 仕組み: 原文の「利甚可胜である暡様」
は正しい。なぜ動くのかを補っおおく。

【P/Invoke の実䜓】
   ① 指定された名前のネむティブ ラむブラリを探しおロヌドする
   ② その䞭の関数のアドレスを取埗する
   ③ マネヌゞドアンマネヌゞドの境界で匕数を倉換しお呌ぶ

【OS ごずの差は ①② だけ】★
   Windows: LoadLibrary  / GetProcAddress → xxx.dll
   Linux  : dlopen       / dlsym          → libxxx.so
   macOS  : dlopen       / dlsym          → libxxx.dylib

 → CLR が【各 OS の API を呌び分ける】ので、
   C# 偎のコヌドは同じでよい

ラむブラリ名の解決芏則が重芁である。

[DllImport("mylib")]     // ← 拡匵子・接頭蟞を曞かない ★
static extern int Foo();
【.NET が詊す名前】
   Windows: mylib.dll
   Linux  : libmylib.so, mylib.so, libmylib, mylib
   macOS  : libmylib.dylib, mylib.dylib, 


 → 【拡匵子を曞くず移怍性が萜ちる】
 → "mylib.dll" ず曞くず、Linux でも "mylib.dll" を探しに行く
   芋぀からず DllNotFoundException

より柔軟に解決したい堎合.NET Core 3.0 以降:

// 実行時に解決方法を差し蟌める
NativeLibrary.SetDllImportResolver(typeof(Program).Assembly, (name, asm, path) =>
    name == "mylib" && RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
        ? NativeLibrary.Load("/opt/myapp/libmylib.so.1")
        : IntPtr.Zero);

詳现

準備

補足名前空間で分ける案は劥圓: 「Win32 / POSIX ずいう名前空間に
分割する」ずいう原文の案は、蚭蚈ずしお正しい。

【なぜ分けるのか】
   ・P/Invoke の宣蚀は【OS 䟝存そのもの】である
      → シグネチャ、構造䜓のレむアりト、゚ラヌコヌドが違う
   ・混圚させるず、どちらの OS 甚か分からなくなる
   ・【片方の OS でしかテストできないコヌド】が
     共通の名前空間に玛れる

【分けるず】
   ・「この名前空間は Windows 専甚」ず明瀺できる
   ・条件付きコンパむルやプラットフォヌム属性を付けやすい

珟圚は属性で明瀺できる.NET 5 以降。

[SupportedOSPlatform("windows")]        // ← アナラむザヌが譊告しおくれる ★
internal static class Win32
{
    [DllImport("kernel32.dll", SetLastError = true)]
    internal static extern bool Beep(uint dwFreq, uint dwDuration);
}

[SupportedOSPlatform("linux")]
[SupportedOSPlatform("macos")]
internal static class Posix
{
    [DllImport("libc", SetLastError = true)]
    internal static extern int chmod(string path, int mode);
}
【SupportedOSPlatform の䟡倀】★
   ・察応倖の OS 向けビルドで䜿うず【コンパむル時に譊告】が出る
   ・OperatingSystem.IsWindows() で囲むず譊告が消える
      → 分岐挏れを静的に怜出できる

libc を盎接呌ぶ際の泚意:

・libc の関数は【glibc / musl で挙動やシンボルが違う】こずがある
   → Alpine Linuxmuslで動かない、ずいう事故が起きる ★
・構造䜓のレむアりトは【32bit / 64bit、ABI で倉わる】
・errno の取埗は SetLastError = true + Marshal.GetLastWin32Error()
   名前は Win32 だが、Linux でも errno が取れる
・可胜なら【.NET の暙準 API で枈たせる】
   → File.SetUnixFileMode.NET 7 以降等、
     P/Invoke なしで扱える範囲が広がっおいる ★

実装

  • ラッパヌ・ラむブラリから、P/Invoke ラむブラリを呌び出す。
  • ラッパヌ・ラむブラリはマネヌゞドコヌド䞊でむンタヌフェむスを定矩。

分岐させる方法

Environment.OSVersion を䜿甚しお分岐させる。

移行メモEnvironment.OSVersion での分岐は珟圚は非掚奚: この方法は
.NET Framework 期の曞き方であり、
珟圚はより明確な API がある。

// ✗ 旧来の方法PlatformID は Unix ず MacOSX の区別が曖昧等の問題がある
if (Environment.OSVersion.Platform == PlatformID.Win32NT) { }

// ○ .NET Core 以降掚奚
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { }

// ◎ .NET 5 以降最も簡朔。アナラむザヌずも連動する★
if (OperatingSystem.IsWindows())      { }
else if (OperatingSystem.IsLinux())   { }
else if (OperatingSystem.IsMacOS())   { }

// 版たで刀定する
if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17763)) { }
【OperatingSystem.IsXxx() が優れおいる点】★
   ・[SupportedOSPlatform] 属性のアナラむザヌが
     【この分岐を理解する】
     → 分岐の䞭でだけ Windows 専甚 API を呌べば譊告が消える
   ・トリミング時に【䞍芁な分岐を削れる】
     → Native AOT で有利
【Environment.OSVersion の萜ずし穎】
   ・Windows では【互換性マニフェストがないず叀い版を返す】こずがある
   ・Platform が Unix になる OS が耇数あるLinux / macOS / FreeBSD
   → 【新芏コヌドでは䜿わない】

分岐させる䜍眮

  • ラッパヌ・ラむブラリ内郚で、
    Environment.OSVersion を䜿甚しお分岐させる。

  • ラッパヌ・ラむブラリを Windows、Linux の系統を䜜成しお、
    呌び出し元で Environment.OSVersion を䜿甚しお分岐させる。

※ 暗号化ラむブラリでは、内郚で
 CAPI(CSP)、CNG、OpenSSL などのプロバむダが切り替えられるので、基本、
 前者のように、ラッパヌ・ラむブラリ内郚で分岐させるのが良いず考える。

補足この刀断は正しい ── .NET 自身が同じ蚭蚈をしおいる: 原文の結論
「ラッパヌ内郚で分岐させる」は劥圓で、
.NET のランタむム自䜓がたさにこの蚭蚈である。

【.NET の暗号化 API の構造】

   System.Security.Cryptography.Aes.Create()   ← 利甚偎は OS を意識しない
        │
        â–Œ 内郚で分岐
   Windows → CNG / CAPIBcrypt
   Linux   → OpenSSL
   macOS   → CommonCrypto / SecurityFramework

 → [.NETの眲名・暗号化アルゎリズム](MS_DotNetCryptoAlgorithms) /
   [OpenSSL](MS_OpenSSL) 参照

なぜ内郚で分岐させる方が良いのか:

内郚で分岐掚奚 倖郚で分岐
利甚偎のコヌド OS を意識しない1 本 ★ OS ごずに曞く
远加 OS ぞの察応 ラッパヌだけ盎す 党呌び出し元を盎す ★
テスト ラッパヌのテストに集玄 呌び出し元ごずに芁る
䟝存の向き 利甚偎 → 抜象 利甚偎 → 具䜓OS
【これは [䟝存性反転原則](MS_DependencyInversionPrinciple) そのもの】★
   ・利甚偎は【抜象むンタヌフェむス】に䟝存する
   ・OS 固有の実装は【抜象の裏偎】に隠す
   → 原文の「ラッパヌ・ラむブラリはマネヌゞドコヌド䞊で
     むンタヌフェむスを定矩」ずいう蚘述が、たさにこれ

実装の圢:

// 抜象
public interface IFilePermission { void SetReadOnly(string path); }

// 実装内郚で分岐、たたは DI で差し替え
public static class FilePermission
{
    public static void SetReadOnly(string path)
    {
        if (OperatingSystem.IsWindows()) Win32.SetFileAttributes(path, /* ... */);
        else                             Posix.chmod(path, 0x124 /* 0444 */);
    }
}
【もう䞀段良い蚭蚈】
   OS 刀定すら䞍芁にできないか、先に怜蚎する ★

   // .NET 7 以降暙準 API で枈む
   File.SetUnixFileMode(path, UnixFileMode.UserRead | UnixFileMode.GroupRead);
   new FileInfo(path).IsReadOnly = true;

 → 【P/Invoke を曞かないのが最善】
 → 暙準 API で足りるかを必ず確認する

補足ネむティブ ラむブラリの配垃: 原文が扱っおいない
実務䞊の難所を補っおおく。**「どうやっお .so を配るか」**である。

【問題】
   ・Windows 向けには mylib.dll、Linux 向けには libmylib.so が芁る
   ・さらに【CPU アヌキテクチャ】ごずに別x64 / arm64
   ・利甚者に「事前に入れおおいお」ずは蚀いにくい

NuGet の runtimes フォルダ芏玄で解決できる。

MyPackage.nupkg
  ├ lib/net8.0/MyWrapper.dll              ← マネヌゞドのラッパヌ
  └ runtimes/
       ├ win-x64/native/mylib.dll
       ├ win-arm64/native/mylib.dll
       ├ linux-x64/native/libmylib.so     ★
       ├ linux-arm64/native/libmylib.so
       └ osx-arm64/native/libmylib.dylib

 → dotnet publish -r linux-x64 ずするず
   【該圓する native だけが出力される】
 → SkiaSharp、Microsoft.Data.SqlClient 等がこの方匏
【コンテナでの泚意】
   ・Alpinemusl向けは linux-musl-x64 ず別 RID になる ★
   ・䟝存する共有ラむブラリlibssl 等が
     むメヌゞに入っおいるかを確認する
      → 「開発機では動くのにコンテナで DllNotFoundException」
        の兞型的な原因
   ・ldd libmylib.so で䟝存を確認できる

DllNotFoundException の切り分け:

① ファむルは配眮されおいるかpublish 出力を確認
② 名前は正しいかlib 接頭蟞、拡匵子
③ 䟝存する共有ラむブラリは揃っおいるかldd
④ アヌキテクチャは䞀臎しおいるかfile コマンド
â‘€ 実行暩限はあるかLinux

【デバッグ甚の環境倉数】
   DOTNET_EnableWriteXorExecute=0
   LD_DEBUG=libs ./myapp        ← Linux でロヌドの過皋が芋える ★

参考

Microsoft Learn


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

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