C#でポインタを使用する方法

ポインタ演算子
演算子 種類 説明
& address-of演算子 他のアドレスへのポインタを返す
* dereference演算子 ポインタのアドレスにある値を返す
-> pointer-to-member演算子 構文上のショートカット。x->yは(*x).yに相当する

アンマネージド オブジェクトのポインタ

C#でC言語形式のポインタを使用するためには、次の2点が必要です。

  • /unsafeコンパイラオプションを指定して、アンセーフ コードの使用を許可する
  • unsafeキーワードを指定して、アンセーフ コードが使用されるブロックを明示する

/unsafeコンパイラオプション

プロジェクトのプロパティで、ビルドにある「アンセーフ コードの許可」にチェックを入れます。これによりコンパイル時に/unsafeオプションが指定され、unsafeキーワードを使用できるようになります。

unsafeキーワード

C言語形式のポインタを使用するためには、unsafeキーワードを使用してアンセーフなコードであることを宣言しなくてはなりません。この宣言はステートメントのブロックに対して行い、そのスコープ内で有効となります。

void Method()
{
    int x;

    unsafe
    {
        int* y = &x;
    }
}

メソッド全体に適用するには、次のようにします。

unsafe void Method()
{
    char* p;
}

クラス全体では次のようにします。

unsafe class MyClass
{
    void Method()
    {
        char* p;
    }
}

マネージド オブジェクトのポインタ

C#ではガベージコレクションによってメモリが自動で管理されています。そこにはメモリの断片化を防ぐためにオブジェクトを移動する機能がありますが、これによってメモリのアドレスが変更されると、そのオブジェクトのポインタは使用できなくなってしまいます。

fixedステートメント

fixedステートメントを使用すると、マネージド オブジェクトのポインタを固定できます。fixed ステートメント (C# リファレンス) | MSDN

int[] arr = { 1, 2, 3 };
unsafe
{
    fixed (int* p = arr)
    {
        Console.Write(*p);       // 1
        Console.Write(*(p + 1)); // 2
    }
}

C++/CLIでは、pin_ptrでこれと同様に処理できます。

GCHandle構造体

GCHandleを使用することで、ガベージコレクションを妨げる、アドレスが変更されない固定されたオブジェクトを生成できます。

[SecurityCriticalAttribute]
public static GCHandle Alloc(
    object value,
    GCHandleType type
)
GCHandle.Alloc メソッド (Object, GCHandleType) (System.Runtime.InteropServices) | MSDN

typeを省略した書式では、GCHandleType.Normalを指定したものと見なされます。

[SecurityCriticalAttribute]
public static GCHandle Alloc(
    object value
)
GCHandleType列挙型
列挙子 目的 機能
Weak オブジェクトの追跡 オブジェクトがコードから到達不能であることを検出。そのときのオブジェクトの状態は不明
WeakTrackResurrection オブジェクトの追跡 オブジェクトがコードから到達不能であることを検出。そのときオブジェクトは解放済み。
Normal オブジェクトの寿命の制御 メモリに残しておくことを指示。ガベージコレクションによって移動される。
Pinned オブジェクトの寿命の制御 メモリに残しておくことを指示。ガベージコレクションによって移動されない。不要となったときはFree()で解放しなければ、メモリリークを引き起こす。
GCHandleType 列挙型 (System.Runtime.InteropServices) | MSDN

void*との相互変換

IntPtrを介することで、void*と変換できます。

MyClass myClass = new MyClass();

GCHandle handle = GCHandle.Alloc(myClass);
IntPtr ptr = GCHandle.ToIntPtr(handle);
// IntPtr ptr = (IntPtr)handle; // キャストしても同じ

unsafe
{
    void* p = (void*)ptr;

    // 1行にまとめると、
    // void* p = (void*)(IntPtr)GCHandle.Alloc(myClass);
}
handle.Free();

void*からマネージド オブジェクトへ戻すには、次のようにします。

void* p = (void*)(IntPtr)GCHandle.Alloc(myClass);

IntPtr ptr = (IntPtr)p;
GCHandle handle = GCHandle.FromIntPtr(ptr);
MyClass target = (MyClass)handle.Target;

// 1行にまとめると、
// MyClass target = (MyClass)GCHandle.FromIntPtr((IntPtr)p).Target;
マーシャリングによる変換

マーシャリングを利用することでもvoid*へ変換できます。Void のサンプル | MSDN

[DllImport( "sample.dll" )]
public static extern void SetData(
    DataType t,
    [MarshalAs(UnmanagedType.AsAny)] object o);

IntPtr構造体

C#でポインタを処理するための構造体です。

可能ならばSafeHandleに置き換えます。CA2006: Use SafeHandle to encapsulate native resources - Visual Studio 2015 | Microsoft Learn

コンストラクタ

public IntPtr(
    int value // 32ビットのポインターまたはハンドル
)
public IntPtr(
    long value // 64ビットのポインター
)
[SecurityCriticalAttribute]
[CLSCompliantAttribute(false)]
public unsafe IntPtr(
    void* value // 不特定のポインター
)
IntPtr 構造体 (System) | Microsoft Learn
IntPtr ptr = new IntPtr(10);
int a = ptr.ToInt32();

HandleRef構造体

リソースへのハンドルをラップできます。この構造体は.NET Framework 2.0以降、SafeHandleやCriticalHandleに置き換えられています。Remarks - HandleRef Struct (System.Runtime.InteropServices) | Microsoft Learn

SafeHandleクラス

SafeHandleはIDisposableを実装するため、IntPtrに対する終了処理を記述できます。

CriticalHandleクラス

アンマネージドとマネージドとのデータ変換

配列とのデータのコピー

MarshalクラスのCopy()メソッドによって、アンマネージド ポインタとマネージド配列で相互にデータをコピーできます。

参考

参考書

Microsoft Learnから検索