LibraryImportとソース生成P/Invoke
[LibraryImport](C# 11以降)を使い、DllImportを上回る、AOT対応のソース生成マーシャリングを利用します。
「LibraryImportとソース生成P/Invoke」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC# Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C# Academyコースには全4レッスンが含まれています。
LibraryImportを使う理由
[LibraryImport]は、[DllImport]に代わる、ソース生成に対応したAOT互換の仕組みとして.NET 7(C# 11)で導入されました。従来のDllImportはリフレクションを介した実行時マーシャリングに依存するため、Native AOTでは問題になることがあります。LibraryImportはマーシャリングに必要なすべてのコードをコンパイル時に生成します。
LibraryImportメソッドの宣言
static partialメソッドに[LibraryImport]を付けます。ソースジェネレーターが実装を補完します。さらに、メソッドを含むクラスにもpartialを付ける必要があります。
using System.Runtime.InteropServices;
internal static partial class NativeMethods
{
// Source generator creates the P/Invoke body at compile time
[LibraryImport("mylib", EntryPoint = "add_integers")]
internal static partial int AddIntegers(int a, int b);
// String marshaling must be explicit in LibraryImport
[LibraryImport("mylib", StringMarshalling = StringMarshalling.Utf8)]
internal static partial int ProcessString(string text);
}LibraryImportでの文字列マーシャリング
DllImportとは異なり、LibraryImportでは文字列のマーシャリング方法を明示的に指定する必要があります。StringMarshalling列挙型またはMarshalAs属性を使用できるため、マーシャリングのコストを把握して制御できます。
// Option 1: StringMarshalling enum (all strings in method)
[LibraryImport("kernel32", StringMarshalling = StringMarshalling.Utf16)]
static partial bool CreateDirectory(string lpPathName, nint lpSecurityAttributes);
// Option 2: Per-parameter MarshalAs
[LibraryImport("libc")]
static partial int Open(
[MarshalAs(UnmanagedType.LPUTF8Str)] string path,
int flags);
// Option 3: MarshalUsing for custom marshalers
[LibraryImport("mylib")]
static partial void Process(
[MarshalUsing(typeof(Utf8StringMarshaller))] string name);構造体のマーシャリング
構造体では、[NativeMarshalling]を追加して、マネージド型をネイティブ表現にどのように対応付けるかを定義します。ソースジェネレーターはマーシャラー型を使用して、安全でアロケーションを最小限に抑えたコードを生成します。
[NativeMarshalling(typeof(PointMarshaller))]
public struct Point
{
public int X;
public int Y;
}
[CustomMarshaller(typeof(Point), MarshalMode.Default, typeof(PointMarshaller))]
public static class PointMarshaller
{
public static Point ConvertToManaged(NativePoint native)
=> new Point { X = native.X, Y = native.Y };
public static NativePoint ConvertToUnmanaged(Point managed)
=> new NativePoint { X = managed.X, Y = managed.Y };
[StructLayout(LayoutKind.Sequential)]
public struct NativePoint { public int X, Y; }
}DllImportとLibraryImportの比較
DllImportは実行時に解釈されるため、起動が遅く、リフレクションベースで、AOTのトリミングと互換性がありません。LibraryImportはビルド時に最適化されたC#コードを生成するため、実行時のリフレクションが不要で、トリミングに安全であり、ベンチマークでも測定可能な高速化が確認できます。
// Old — DllImport (still works, but avoid for new AOT code)
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool MoveFile(string src, string dst);
// New — LibraryImport (AOT-safe, source-generated)
[LibraryImport("kernel32", StringMarshalling = StringMarshalling.Utf16,
SetLastError = true)]
static partial bool MoveFile(string src, string dst);
// The compiler generates the actual P/Invoke wrapper bodySetLastErrorとエラー処理
[LibraryImport]でSetLastError = trueを設定し、OSのエラーコードを取得できるようにします。呼び出し後にMarshal.GetLastPInvokeError()(推奨)またはMarshal.GetLastWin32Error()を使用してエラーコードを取得します。
[LibraryImport("kernel32", StringMarshalling = StringMarshalling.Utf16,
SetLastError = true)]
static partial bool CreateDirectory(string path, nint secAttr);
bool ok = CreateDirectory(@"C:\Temp\NewDir", 0);
if (!ok)
{
int err = Marshal.GetLastPInvokeError();
// err is ERROR_ALREADY_EXISTS (183) if folder exists
throw new Win32Exception(err);
}Span<T>とメモリのマーシャリング
ソース生成されるP/Invokeの利点の1つは、Span<T>を第一級にサポートしていることです。ReadOnlySpan<byte>を渡すと、配列を使うDllImportで必要になるピン留めやアロケーションを回避できます。
[LibraryImport("mylib")]
static partial int ProcessBuffer(
ReadOnlySpan<byte> data,
int length);
// Usage — no fixed or GCHandle needed
byte[] buffer = Encoding.UTF8.GetBytes("hello");
int result = ProcessBuffer(buffer, buffer.Length);
// For output buffers use Span<byte>
[LibraryImport("mylib")]
static partial int FillBuffer(Span<byte> output, int maxLen);ソース生成の有効化
.NET 7以降を対象とするプロジェクトでSystem.Runtime.InteropServices名前空間を参照すると、ソース生成は自動的に有効になります。古いターゲットでは、Microsoft.Interop.SourceGenerationアナライザーパッケージが必要です。
<!-- In your .csproj — no extra package needed on .NET 7+ -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<!-- Enable Roslyn analyzers and source generators -->
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!-- AllowUnsafeBlocks may be needed for some marshalers -->
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>生成されたコードの確認
csprojに<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>を追加すると、生成されたファイルがobj/に書き込まれます。これにより、ソースジェネレーターが実際に何を生成するのかを詳しく確認できます。学習に最適な方法です。
// The generator emits something like this for a LibraryImport method:
// (simplified view of generated stub)
internal static partial int AddIntegers(int a, int b)
{
// Direct call — no reflection, no boxing, fully inlineable
return __PInvoke(a, b);
[System.Runtime.InteropServices.DllImportAttribute(
"mylib",
EntryPoint = "add_integers",
ExactSpelling = true)]
static extern int __PInvoke(int a, int b);
}実践:Cライブラリのラッパー作成
よく使われるパターンは、すべてのLibraryImport宣言を含む静的ラッパークラスを定義し、その上に高レベルで安全なAPIを公開する方法です。partial宣言はinternalまたはprivateにして、公開するのは安全なラッパーだけにします。
internal static partial class LibSodiumNative
{
private const string LibName = "libsodium";
[LibraryImport(LibName, EntryPoint = "crypto_secretbox_keybytes")]
internal static partial nuint KeyBytes();
[LibraryImport(LibName, EntryPoint = "crypto_secretbox_easy")]
internal static partial int SecretBoxEasy(
Span<byte> ciphertext,
ReadOnlySpan<byte> message,
ulong mlen,
ReadOnlySpan<byte> nonce,
ReadOnlySpan<byte> key);
}
// Public safe wrapper hides the native signature
public static byte[] Encrypt(byte[] message, byte[] key, byte[] nonce)
{
var cipher = new byte[message.Length + 16];
LibSodiumNative.SecretBoxEasy(cipher, message, (ulong)message.Length, nonce, key);
return cipher;
}理解度チェック
[LibraryImport]が[DllImport]より優れている主な点は何ですか?
振り返り:LibraryImportとソース生成P/Invoke
主なポイント:
[LibraryImport]は、AOTに安全なP/Invokeのために[DllImport]に代わるものです- マーシャリングはコンパイル時に生成されるため、実行時のリフレクションが不要です
static partialメソッドとpartialクラスが必要です- 文字列のマーシャリングは
StringMarshallingまたはMarshalAsを使って明示的に指定する必要があります - ピン留めのオーバーヘッドなしで
Span<T>を第一級にサポートします EmitCompilerGeneratedFilesを使って生成されたコードを確認できます
よくある質問
「LibraryImportとソース生成P/Invoke」レッスンは無料ですか?
はい。「LibraryImportとソース生成P/Invoke」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全4レッスンが含まれています。
「LibraryImportとソース生成P/Invoke」で何を学びますか?
[LibraryImport](C# 11以降)を使い、DllImportを上回る、AOT対応のソース生成マーシャリングを利用します。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
C# Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。
「LibraryImportとソース生成P/Invoke」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このC# Academyレッスンでコードを書いて実行できますか?
はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- P/Invokeの基礎
- LibraryImportとソース生成P/Invoke
- 安全でないコード、ポインター、固定バッファー
- COM 相互運用とランタイム呼び出し可能ラッパー