0Pricing
C# Academy · レッスン

P/Invokeの基礎

DllImportでネイティブ関数を宣言・呼び出しし、プリミティブ型、文字列、構造体のマーシャリングを理解します。

「P/Invokeの基礎」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC# Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C# Academyコースには全4レッスンが含まれています。

P/Invokeとは

Platform Invocation Services(P/Invoke)を使用すると、C#からネイティブ共有ライブラリ(Windowsでは.dll、Linuxでは.so、macOSでは.dylib)の関数を呼び出せます。.NETからWin32 APIやC ABIのライブラリを呼び出すための標準的な仕組みです。

最初のP/Invoke呼び出し

[DllImport]を使用してネイティブ関数を宣言し、ライブラリ名を指定します。CLRがライブラリの検索と読み込み、引数のマーシャリングを処理します。

using System.Runtime.InteropServices;

// Calling MessageBoxW from user32.dll (Windows)
internal static partial class NativeMethods
{
    [DllImport("user32.dll",
        EntryPoint = "MessageBoxW",
        CharSet = CharSet.Unicode,
        SetLastError = true)]
    internal static extern int MessageBox(
        IntPtr hwnd,
        string text,
        string caption,
        uint type);
}

// Call it:
NativeMethods.MessageBox(IntPtr.Zero, "Hello!", "P/Invoke", 0);

// Windows-only — wrap in RuntimeInformation.IsOSPlatform check
// for cross-platform code

カスタムネイティブライブラリの呼び出し

P/InvokeはOS APIだけでなく、C形式でエクスポートされたあらゆる関数で機能します。ネイティブライブラリをビルドし、Cリンケージで関数をエクスポートして、C#から呼び出します。

// mymath.h / mymath.c:
// extern "C" double AddNumbers(double a, double b) { return a + b; }
// Compile: gcc -shared -o libmymath.so mymath.c

// C# declaration:
[DllImport("mymath",              // libmymath.so / mymath.dll
    EntryPoint = "AddNumbers",
    CallingConvention = CallingConvention.Cdecl)]
private static extern double AddNumbers(double a, double b);

// Call:
double result = AddNumbers(3.14, 2.72); // 5.86

// .NET resolves the library via:
// 1. Absolute path if provided
// 2. App directory
// 3. OS library path (PATH / LD_LIBRARY_PATH / DYLD_LIBRARY_PATH)

基本型のマーシャリング

CLRは、ほとんどのプリミティブ型について、マネージド表現とネイティブ表現の間のマーシャリングを自動的に行います。対応関係を理解しておくと、見つけにくいバグを防げます。

// C Type       → C# Type
// int (32-bit)  → int  or  System.Int32
// long (64-bit) → long or  System.Int64
// float         → float
// double        → double
// bool          → [MarshalAs(UnmanagedType.Bool)] bool
// char*  (ANSI) → string  (CharSet.Ansi)
// wchar_t*      → string  (CharSet.Unicode)
// void*         → IntPtr
// size_t        → UIntPtr

// Example with explicit marshalling:
[DllImport("libc", EntryPoint = "strlen", CharSet = CharSet.Ansi)]
private static extern UIntPtr StrLen(
    [MarshalAs(UnmanagedType.LPStr)] string s);

int len = (int)StrLen("hello"); // 5

ネイティブコードへの構造体の受け渡し

[StructLayout(LayoutKind.Sequential)]を使用して、ネイティブコードが想定する通りに構造体がメモリ上に正確に配置されるようにします。

// C struct:
// struct Point { int x; int y; };
// void DrawPoint(struct Point p);

[StructLayout(LayoutKind.Sequential)]
public struct Point
{
    public int X;
    public int Y;
}

[DllImport("graphics", EntryPoint = "DrawPoint",
    CallingConvention = CallingConvention.Cdecl)]
private static extern void DrawPoint(Point p);

// Pass by value:
DrawPoint(new Point { X = 10, Y = 20 });

// Pass by pointer (ref or out):
[DllImport("graphics", EntryPoint = "GetCenter",
    CallingConvention = CallingConvention.Cdecl)]
private static extern void GetCenter(out Point center);
GetCenter(out var pt);

エラー処理:GetLastWin32Error

Windows API関数は、GetLastError()を介してエラーを通知します。[DllImport]でSetLastError = trueを設定し、呼び出し後にMarshal.GetLastWin32Error()を呼び出してください。

[DllImport("kernel32.dll",
    EntryPoint = "CreateFileW",
    CharSet = CharSet.Unicode,
    SetLastError = true)]
private static extern IntPtr CreateFile(
    string fileName,
    uint desiredAccess,
    uint shareMode,
    IntPtr securityAttributes,
    uint creationDisposition,
    uint flagsAndAttributes,
    IntPtr templateFile);

const uint GENERIC_READ = 0x80000000;
const uint OPEN_EXISTING  = 3;

var handle = CreateFile("test.txt", GENERIC_READ, 0,
    IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);

if (handle == (IntPtr)(-1))
{
    int error = Marshal.GetLastWin32Error();
    throw new System.ComponentModel.Win32Exception(error);
}

文字列とバッファーのマーシャリング

文字列のマーシャリングでは、エンコーディングと所有権に細心の注意が必要です。出力バッファーにはStringBuilderを使用し、エンコーディングの指定にはMarshalAs属性を使用します。

// Read into a buffer:
[DllImport("kernel32.dll",
    EntryPoint = "GetComputerNameW",
    CharSet = CharSet.Unicode,
    SetLastError = true)]
private static extern bool GetComputerName(
    System.Text.StringBuilder lpBuffer,
    ref uint nSize);

uint size = 256;
var buffer = new System.Text.StringBuilder((int)size);
if (GetComputerName(buffer, ref size))
    Console.WriteLine(buffer.ToString());

// Return a string owned by native code (don't free it):
[DllImport("mylib", CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.LPStr)]
private static extern string GetVersion();

関数ポインターとコールバック

マネージドデリゲートをCの関数ポインターとして渡します。CLRはサンクを作成しますが、デリゲートを存続させるために参照を保持する必要があります。そうしないとGCがデリゲートを回収し、ネイティブコードがクラッシュします。

// C callback signature: typedef int (*Comparer)(const void*, const void*);
// void qsort(void* base, size_t nitems, size_t size, Comparer compar);

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int CompareCallback(IntPtr a, IntPtr b);

[DllImport("libc", CallingConvention = CallingConvention.Cdecl)]
private static extern void QSort(
    int[] data, UIntPtr count, UIntPtr size, CompareCallback compare);

// Keep the delegate ALIVE for the duration of the call:
private static readonly CompareCallback _compare =
    (a, b) => Marshal.ReadInt32(a).CompareTo(Marshal.ReadInt32(b));

var data = new[] { 5, 2, 8, 1, 3 };
QSort(data, (UIntPtr)data.Length, (UIntPtr)sizeof(int), _compare);

NativeLibrary API

NativeLibraryクラスは、明示的なライブラリの読み込み、関数ポインターの解決、クロスプラットフォームのパスのカスタマイズを提供します。これは、暗黙的なライブラリ名解決に代わる最新の方法です。

using System.Runtime.InteropServices;

// Explicit load:
var handle = NativeLibrary.Load("/usr/lib/libssl.so.3");

// Resolve a function pointer:
var addPtr = NativeLibrary.GetExport(handle, "AddNumbers");
var addFn = Marshal.GetDelegateForFunctionPointer<Func<double,double,double>>(addPtr);
double result = addFn(1.0, 2.0);

// Unload:
NativeLibrary.Free(handle);

// Custom resolver (called when DllImport can't find a library):
NativeLibrary.SetDllImportResolver(typeof(MyNative).Assembly,
    (libName, assembly, searchPath) =>
    {
        if (libName == "mymath")
            return NativeLibrary.Load("/opt/mymath/libmymath.so");
        return IntPtr.Zero;
    });

実践例:OpenSSLの呼び出し

実践例として、P/Invokeを使用してC#からOpenSSLのSHA-256ダイジェスト関数を呼び出します。

internal static class OpenSslInterop
{
    [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
    private static extern IntPtr EVP_MD_CTX_new();

    [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
    private static extern void EVP_MD_CTX_free(IntPtr ctx);

    [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
    private static extern IntPtr EVP_sha256();

    [DllImport("libssl", CallingConvention = CallingConvention.Cdecl)]
    private static extern int EVP_DigestInit_ex(IntPtr ctx, IntPtr type, IntPtr engine);

    // In practice, use System.Security.Cryptography.SHA256 instead:
    // var hash = SHA256.HashData(data);
    // P/Invoke to OpenSSL is only needed when you require
    // non-managed cryptographic operations or specific OpenSSL features
}

確認問題

ネイティブコールバックとしてデリゲートを渡すとき、なぜデリゲートを存続させる必要があるのでしょうか。

振り返り:P/Invokeの基礎

主なポイント:

  • P/Invoke:[DllImport]宣言を介してネイティブのC ABI関数を呼び出します
  • CLRはプリミティブ型を自動的にマーシャリングします。文字列やカスタム型には[MarshalAs]を付けます
  • [StructLayout(LayoutKind.Sequential)]:構造体のメモリレイアウトがネイティブ側の想定と一致するようにします
  • Win32のエラー処理では、SetLastError = trueを設定してからMarshal.GetLastWin32Error()を呼び出します
  • ネイティブコールバックとして渡すデリゲートへの参照を保持します
  • NativeLibrary:クロスプラットフォームでパスを制御するための明示的なロード、解決、カスタムリゾルバーを提供します

よくある質問

「P/Invokeの基礎」レッスンは無料ですか?

はい。「P/Invokeの基礎」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全4レッスンが含まれています。

「P/Invokeの基礎」で何を学びますか?

DllImportでネイティブ関数を宣言・呼び出しし、プリミティブ型、文字列、構造体のマーシャリングを理解します。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

C# Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。

「P/Invokeの基礎」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このC# Academyレッスンでコードを書いて実行できますか?

はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. P/Invokeの基礎
  2. LibraryImportとソース生成P/Invoke
  3. 安全でないコード、ポインター、固定バッファー
  4. COM 相互運用とランタイム呼び出し可能ラッパー
← C# Academyに戻る