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

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

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

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

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

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

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

unsafeキーワード

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

void Foo()
{
    int x;

    unsafe
    {
        int* y = &x;
    }
}

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

unsafe void Foo()
{
    char* p;
}

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

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

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

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

fixedステートメント

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

int[] arr = { 1, 2, 3 };
unsafe
{
    fixed (int* p = arr)
    {
        Console.WriteLine(*p);       // 1
        Console.WriteLine(*(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#でポインタを処理するための構造体です。

コンストラクタ
public IntPtr(
    int value // 32ビットのポインターまたはハンドル
)
public IntPtr(
    long value // 64ビットのポインター
)
[SecurityCriticalAttribute]
[CLSCompliantAttribute(false)]
public unsafe IntPtr(
    void* value // 不特定のポインター
)
IntPtr 構造体 (System) | MSDN

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

配列とのデータのコピー

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

参考

参考書

MSDN (Microsoft Developer Network) から検索