none
書式指定されたBlittableな構造体の一次元配列に関して RRS feed

  • 質問

  • .NET Framework 4.0上でなのですが、

    http://msdn.microsoft.com/ja-jp/library/75dwhxf7(VS.80).aspx

    に記載されている複合型のBlittable構造体(System.Valuetypeの派生型)を作成し、その一次元配列をマーシャリングした場合、上記ページ、及び、

    http://msdn.microsoft.com/ja-jp/library/23acw07k.aspx

    こちらのコピーと固定の挙動に従った場合、本来固定された上で該当エンティティ(マネージヒープに存在する該当配列の実体)のポインタがUnmanagedに渡されると理解しておりました。

    しかしながら、CLR側(実際にはC♯にて記述しております)にて、パラメータに渡すべき配列を明示的に、GCHandleでPinned指定した上アロケートを行い、AddrOfPinnedObjectメソッドにて取得されたIntPtrと、Unmanaged側に実際に渡されたポインタを比較すると不一致になり、実際にはコピーによってUnmanaged側にマーシャリングされているように思えます。

    何故このような挙動になるのか、ご存じの方がいらっしゃいましたら、ご教授頂ければと思います。

    以上よろしくお願い申し上げます。


    NetSeed
    2010年9月13日 12:57

回答

  • GCHandle で Pinned できるのは Blittable 型に限られるため、成功すると言うことは Blittable の構造体の 1 次元配列も Blittable なのでしょう、おそらく。

    私は、どちらかというと、固定とコピーのルール側を疑っています。1 次元配列で固定されるのは、狭義の Blittable だけとみなせば、Blittable の構造体の配列はコピーされてもおかしくないかなと、想像しています。
    ただし、これを裏付ける何らかの資料を見つけられていないため、特にレスをつけていませんでした。

    # 英語圏も検索してみても、さっぱり良い資料に当たらないので。。。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    • 回答としてマーク NetSeed 2010年9月17日 15:29
    2010年9月17日 14:10
    モデレータ

すべての返信

  • AddrOfPinnedObjectメソッドは1次元配列を格納しているArrayクラスインスタンスの先頭アドレスで、比較したいのはMarshal.UnsafeAddrOfPinnedArrayElement( array, 0 )のアドレスなのでは?
    前者にはLengthプロパティやRank等の情報が入っています。
    2010年9月13日 13:24
  • ご返信ありがとうございます。

    教えて頂いたメソッドを利用し(事前にPinnedしておきました)AddrOfPinnedObjectメソッドにて得たアドレス及び、UnsafeAddrOfPinnedArrayElementメソッドにて得たアドレスを比較したところ不一致でしたので、これか?と思いました。

    が、残念ながら、Unmanaged側に渡されるアドレス(ポインタ)とはどちらのアドレス(ポインタ)とも不一致という結果でした。

    ただ、1つわかったことがありまして、Blittable型かつプリミティブ(System.Int32等)な一次元配列の場合、AddrOfPinnedObjectメソッドで取得できるポインタと、UnsafeAddrOfPinnedArrayElementで得られるアドレスが一致しておりました。

    実際この違いが、固定で渡されるのと、コピーで渡される違いになってくるのではないかと思います。


    NetSeed
    2010年9月13日 14:32
  • サンプルコードに不備があり、その訂正をします。

     

    昨日の投稿で、該当両メソッドにて取得できるアドレスが不一致だったと記載しましたが、複合Blittable型の一次配列に対して、AddrOfPinnedObjectメソッドを適用して取得したアドレスと、UnsafeAddrOfPinnedArrayElementメソッドを適用して取得したアドレスは一致しているようです。

    (昨日作成したテストコードでは、UnsafeAddrOfPinnedArrayElementメソッドのindexパラメータに本来0であるべきなのに、1を入れておりました)


    NetSeed
    2010年9月14日 14:52
  • すみません、結局どのような型を作られているのでしょうか? 見せてもらった方が早いかもしれません。

    2点、参考になる情報を。

    unsafeコンテキストで sizeof( 型名 ) をすることができます。コンパイラーはBlittableかどうかを事前に把握していて、ダメな場合はコンパイルエラーをはいてくれます。

    sizeof(bool) = 1、Marshal.SizeOf( typeof(bool) ) = 4だったりします。レイアウトが異なるのでコピーが必要になります。昨日ハマって、今朝このことに気づきました。

    2010年9月17日 2:56
  • 再度のご返信ありがとうございます。

    実行環境ですが、以下の通りとなります。

     

    • .NET Framework 4.0
    • Windows 7 Professional/64bit
    • Native側、CLR側ともDebug ビルド、かつx86/Win32にてコンパイルを実行。

    呼び出し元(CLR)はC♯にて記述しており、型は以下のようになっております。
    [StructLayout(LayoutKind.Sequential)]
    public struct ssPoint
    {
    	public int x;
    	public int y;
    }
    
    [StructLayout(LayoutKind.Sequential)]
    public class csPoint
    {
    	public int x;
    	public int y;
    }
    
    また、P/Invokeの定義は以下のように行いました。
    [DllImport(DLL, EntryPoint = "ReportAdressPoint")]
    private static extern IntPtr ReportAdress(ssPoint[] target);
    
    [DllImport(DLL, EntryPoint = "ReportAdressPoint")]
    private static extern IntPtr ReportAdress(ref ssPoint target);
    
    [DllImport(DLL, EntryPoint = "ReportAdressPoint")]
    private static extern IntPtr ReportAdress(csPoint target);
    

    呼び出し先に関しては、以下のように定義しております。
    #include "stdafx.h"
    
    
    typedef struct{
    	int x;
    	int y;
    } point;
    
    
    __declspec(dllexport) const point* __stdcall ReportAdressPoint(const point *target)
    {
    	return target;
    }
    
    
    上記コードを以下のようなサンプルコードにて呼び出しました。
    static void Main(string[] args)
    {
    //書式指定された参照型(ヒープに置くための代案その1)
    csPoint csp = new csPoint();
    
    //今回の主題となる複合Blittable型の1次元配列
    ssPoint[] pArray = new ssPoint[10];
    
    
    //明示的にPinするためのハンドル
    GCHandle gch;
    IntPtr clrAddr;
    IntPtr nativeAddr;
    
    
    //-------------------------------------------------------------------------
    //Case1:ヒープに存在する複合Blittable型
    //-------------------------------------------------------------------------
    Console.WriteLine("------Test case1------");
    
    gch = GCHandle.Alloc(csp, GCHandleType.Pinned);
    clrAddr = gch.AddrOfPinnedObject();
    nativeAddr = ReportAdress(csp);
    
    
    //Trueが戻ってくる。(アドレスは一致している)
    Console.WriteLine("CLR/Native アドレスチェック結果:{0}", clrAddr == nativeAddr);
    
    //後始末
    gch.Free();
    Console.WriteLine();
    
    //-------------------------------------------------------------------------
    //Case2:ヒープに存在する複合Blittable型(配列の要素を参照させる)
    //-------------------------------------------------------------------------
    Console.WriteLine("------Test case2------");
    gch = GCHandle.Alloc(pArray, GCHandleType.Pinned);
    clrAddr = gch.AddrOfPinnedObject();
    
    //事前検査:clrAddrの内容と、配列要素の1番目の要素のアドレスの一致状況(Trueが返ってきます。)
    Console.WriteLine("ピンオブジェクトとエレメントのアドレスの一致状況:{0}",clrAddr == Marshal.UnsafeAddrOfPinnedArrayElement(pArray, 0));
    
    nativeAddr = ReportAdress(ref pArray[0]);
    
    //呼び出し元と呼び出し先の一致確認(Trueが返ってきます)
    Console.WriteLine("CLR/Native アドレスチェック結果:{0}",clrAddr == nativeAddr);
    
    //後始末
    gch.Free();
    Console.WriteLine();
    
    
    //-------------------------------------------------------------------------
    //Case3:複合Blittable型の1次元配列をパラメータにする
    //-------------------------------------------------------------------------
    Console.WriteLine("------Test case3------");
    gch = GCHandle.Alloc(pArray, GCHandleType.Pinned);
    clrAddr = gch.AddrOfPinnedObject();
    
    //事前検査:clrAddrの内容と、配列要素の1番目の要素のアドレスの一致状況(Trueが返ってきます。)
    Console.WriteLine("ピンオブジェクトとエレメントのアドレスの一致状況:{0}",clrAddr == Marshal.UnsafeAddrOfPinnedArrayElement(pArray, 0));
    
    nativeAddr = ReportAdress(pArray);
    
    //呼び出し先と呼び出し元のアドレス一致確認(falseが返ってきます。)
    Console.WriteLine("CLR/Native アドレスチェック結果:{0}",clrAddr == nativeAddr);
    
    //後始末
    gch.Free();
    Console.WriteLine();
    
    Console.ReadLine();
    }
    

    このようにした場合、以下のように、csPoint参照型(Case1)、又は、ssPoint値型をパラメータとした場合(Case2)は、呼び出し元のアドレスと、呼び出し先のアドレスは一致しておりましたので、
    これらの場合は、固定されていると判断しました。
    しかしながら、ssPoint値型の1次元配列の場合(Case3)のみ、アドレスが不一致となっており、この場合はコピーが行われていると判断しました。

    先の質問に戻るのですが、このCase3の場合も、Blittableの1次元配列と理解しておりましたので、MSDNの記載内容から自分が理解した結果と、実行結果が異なっており、
    その理由を調べたのですが、参考になるような情報が無く質問させて頂いた次第となります。

    以上、もしご存じでしたらご教授頂ければと思います。

    NetSeed
    • 編集済み NetSeed 2010年9月17日 13:18 誤記訂正
    2010年9月17日 13:17
  • GCHandle で Pinned できるのは Blittable 型に限られるため、成功すると言うことは Blittable の構造体の 1 次元配列も Blittable なのでしょう、おそらく。

    私は、どちらかというと、固定とコピーのルール側を疑っています。1 次元配列で固定されるのは、狭義の Blittable だけとみなせば、Blittable の構造体の配列はコピーされてもおかしくないかなと、想像しています。
    ただし、これを裏付ける何らかの資料を見つけられていないため、特にレスをつけていませんでした。

    # 英語圏も検索してみても、さっぱり良い資料に当たらないので。。。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    • 回答としてマーク NetSeed 2010年9月17日 15:29
    2010年9月17日 14:10
    モデレータ
  • ご回答ありがとうございます。

    色々と今回試してみた限り、Azuleanさんのおっしゃるとおりで、ルール側に誤りがあるのではないか?と思いました。
    理由を見つけることは出来ませんでしたが、個人的には以下のような結論で現状は良いのではないかなと考えております。

     

    • 固定されるのは狭義のBlittable型の1次元配列とした場合に限られる
    • 広義のBlittable型を1次元配列とした場合はコピーによってパラメータは渡される

    このたびは、佐祐理さん、Azuleanさん
    ご回答下さいまして本当にありがとうございました。

     


    NetSeed
    2010年9月17日 15:28
  • そういうものでしたか。私はマネージメモリが直接公開されるようなコードはあまり書かず、大抵はアンマネージメモリにコピーされるような書き方をしています。なので、回答の投稿はしていましたが、過去に経験もなく実際の確認もしていませんでした。
    2010年9月18日 7:00