結論から言うと、.NET にある System.Runtime.InteropServices.ComTypes.STGMEDIUM は使ってはいけません。特定の状況下でアンマネージメモリからマネージオブジェクトへのマーシャリングに失敗し、NotImplementedException がスローされます。

Windows Server 2008 x86 上で Directory Object Picker を .NET で使った時、返ってきた IDataObject の GetData を呼ぶと NotImplementedException がスローされる、という現象に遭遇しました。IDataObject はデータを運ぶのが仕事ですから、他のメソッドならともかく GetData が E_NOTIMPL で失敗するのはあり得ないことです。

しばらくググっていたら、同じようなことをやっている人の記事が見つかり、ここの “STGMEDIUM nightmares” の節に原因が書いてありました。Directory Object Picker で追加で取得するアトリビュートを指定した場合、STGMEDIUM の pUnkForRelease フィールドが object 型として定義されているせいで、アンマネージメモリ上で NULL でない値を持つ pUnkForRelease のマネージオブジェクトへのマーシャリングに失敗し、そこで何故か NotImplementedException がスローされる、としています(MarshalingException とかじゃないんだな…)。ただ、手元の Windows 7 x64 (使われる .NET Framework のバージョンは同じ)の場合は NULL ではない値であっても正しく(というか例外にならずに)マーシャリングされているようなので、インターフェースポインタが指しているオブジェクトによって成功したり失敗したりするものと思われます。

というわけで、試しに STGMEDIUM を自前で以下のように定義しなおしてみました。

[StructLayout(LayoutKind.Sequential)]
public struct STGMEDIUM
{
    public uint tymed;
    public IntPtr unionmember;
    public IntPtr pUnkForRelease; // ここが違う
}

実際にはこれだけではなく、巻き添えを食って IDataObject やら FORMATETC やら、いろいろと自前で定義しなおすことになります。

で、この自前の STGMEDIUM 等を使ってみると、上記の環境でも期待したとおりにデータを取得することができます。pUnkForRelease を IntPtr ではなく object にすると、それだけで NotImplementedException がスローされるようになるので、マーシャリングの問題であることが分かります。

pUnkForRelease は STGMEDIUM を解放するために使うインターフェースポインタなのですが、実際に STGMEDIUM を解放するときは ReleaseStgMedium API を呼ぶので、pUnkForRelease のマネージ型が何であるかは問題になりません。むしろ、余計なマーシャリング動作が発生しない分、IntPtr になっていた方が安全かもしれません。


最初に .NET の System.Runtime.InteropServices.ComTypes.STGMEDIUM は使ってはいけない、と書きましたが、これはネイティブコードが提供する STGMEDIUM をマネージコードから触る時の話です。マネージコード側が STGMEDIUM を提供する場合にどういうことになるのかは分かりません。そもそも興味ないし…

Trackback

no comment untill now

Add your comment now