Native に Managed なオブジェクトを渡したい時

今まで、Native を叩くコードしか書いてなかったのだけど、
コールバックの中で Managed なオブジェクトを触りたくて、
Native に Managed なオブジェクトを渡す必要が生じた。


なんか、ちょこちょこ見たんだけどイマイチわからん。
とりあえず書いてみようということで実験

// Native に渡したいのは System::IO::Stream^ の stream です。
// まずは GCHandle の割り当て
GCHandle normalGCHandle = GCHandle::Alloc(stream, GCHandleType::Normal);
// GCHandle pinnedGCHandle = GCHandle::Alloc(stream, GCHandleType::Pinned); // POD な Managed しかピン止めできないらしい
GCHandle weakGCHandle   = GCHandle::Alloc(stream, GCHandleType::Weak);
GCHandle weakTRGCHandle = GCHandle::Alloc(stream, GCHandleType::WeakTrackResurrection);

// これは全滅。ピン止めしてないとできないらしい
// System::IntPtr normalGCPointer = (System::IntPtr)normalGCHandle.AddrOfPinnedObject();
// System::IntPtr pinnedGCPointer = (System::IntPtr)pinnedGCHandle.AddrOfPinnedObject();
// System::IntPtr weakGCPointer   = (System::IntPtr)weakGCHandle.AddrOfPinnedObject();
// System::IntPtr weakTRGCPointer = (System::IntPtr)weakTRGCHandle.AddrOfPinnedObject();

// 代わりにこっち
System::IntPtr normalGCPointer = GCHandle::ToIntPtr(normalGCHandle);
// System::IntPtr pinnedGCPointer = GCHandle::ToIntPtr(pinnedGCHandle)
System::IntPtr weakGCPointer   = GCHandle::ToIntPtr(weakGCHandle);
System::IntPtr weakTRGCPointer = GCHandle::ToIntPtr(weakTRGCHandle);

// 生 void* を取る
void* pNormalRawPtr = normalGCPointer.ToPointer();
// void* pPinnedRawPtr = pinnedGCPointer.ToPointer();
void* pWeakRawPtr   = weakGCPointer.ToPointer();
void* pWeakTRRawPtr = weakTRGCPointer.ToPointer();

// 生 void* を確認
fprintf(stdout, "normal: %p\n", pNormalRawPtr);
// fprintf(stdout, "pinned: %p\n", pPinnedRawPtr);
fprintf(stdout, "  weak: %p\n", pWeakRawPtr);
fprintf(stdout, "weakTR: %p\n", pWeakTRRawPtr);

// 生 void* から IntPtr に戻す
System::IntPtr restoredNormalIntPtr = IntPtr(pNormalRawPtr);
System::IntPtr restoredWeakIntPtr   = IntPtr(pWeakRawPtr);
System::IntPtr restoredWeakTRIntPtr = IntPtr(pWeakTRRawPtr);

// ちゃんと IntPtr ができてるか確認
System::Diagnostics::Debug::Print("restoredNormalIntPtr:" + restoredNormalIntPtr);
System::Diagnostics::Debug::Print("restoredWeakIntPtr:" + restoredWeakIntPtr);
System::Diagnostics::Debug::Print("restoredWeakTRIntPtr:" + restoredWeakTRIntPtr);

// GCHandle を復元してみる
GCHandle restoredNormalGCHandle = GCHandle::FromIntPtr(restoredNormalIntPtr);
GCHandle restoredWeakGCHandle   = GCHandle::FromIntPtr(restoredWeakIntPtr);
GCHandle restoredWeakTRGCHandle = GCHandle::FromIntPtr(restoredWeakTRIntPtr);

// GCHandle を復元できてるか確認
System::Diagnostics::Debug::Print("restoredNormalGCHandle:" + restoredNormalGCHandle);
System::Diagnostics::Debug::Print("restoredWeakGCHandle:" + restoredWeakGCHandle);
System::Diagnostics::Debug::Print("restoredWeakTRGCHandle:" + restoredWeakTRGCHandle);

// GCHandle から元のオブジェクトに戻す
Stream^ normalStream = (Stream^)restoredNormalGCHandle.Target;
Stream^ weakStream   = (Stream^)restoredWeakGCHandle.Target;
Stream^ weakTRStream = (Stream^)restoredWeakTRGCHandle.Target;

// 元の System::IO::Stream^ になってるか確認
System::Diagnostics::Debug::Print("restoredNormalGCHandle:" + normalStream);
System::Diagnostics::Debug::Print("weakStream:" + weakStream);
System::Diagnostics::Debug::Print("weakTRStream:" + weakTRStream);

// GCHandle は Free() する(FromIntPtr で取得した分は Free() する必要があるのか?)
normalGCHandle.Free();
// pinnedGCHandle.Free();
weakGCHandle.Free();
weakTRGCHandle.Free();

GCHandleType::Normal, GCHandleType::Weak, GCHandleType::WeakTrackResurrection を指定した分は
なんとなく出来てるっぽい気がするが、MSDN

Normal ハンドルは非透過です。つまり、このハンドルによって、その中に格納されているオブジェクトのアドレスを解決することはできません。


って書いてあるのが気になる。デリファレンスできてる気がするんですが…?
まあ Weak でも、処理が終わるまで Managed 側で元オブジェクトを保持しておけばいいんだと思うんだけど…。
なんかやっぱり色々小難しいなぁ。


っていうか、IntPtr って "InternalPointer" の略語だと思ってきたんだけど、
もしかして "InteriorPointer" のことだったのかな?
cli::interior_ptr は generic 版 IntPTr ってことでいいのかな?
cli::interior_ptrcli::pin_ptr の存在に気づいて初めて意識した。
対象がコンパクションで移動しても大丈夫ってことなのかな?