2013-07-23 19 views
10

私は一見些細な作業に立ち往生しており、あなたの助けが必要です。IntPtrの配列へのポインタの配列を変換する

Iは、次のシグネチャを持つメソッドを記述する必要があり:

System.Array ToIntPtrArray(System.Array a) 
実引数は任意 pointer type(例えば int*[]long**[]void*[,])のアレイとすることができる

と同じ形状の配列を返しますタイプ配列System.IntPtrの要素は、入力配列の要素と同じ数値を持ちます。問題は、あらかじめその型を知らないとポインタの数値を抽出する方法がわかりません。私は私の引数は、常にタイプvoid*[]のあることを事前に知っていた場合


は例えば、私は次のようにメソッドを書くことができます:

unsafe IntPtr[] ToIntPtrArray(void*[] a) 
{ 
    var result = new IntPtr[a.Length]; 
    for (int i = 0; i < a.Length; i++) 
     result[i] = (IntPtr) a[i]; 

    return result; 
} 

をしかし、問題はそれがないvoid*[]可能性があるが、void**[]または何か他の方法を使用することができ、この方法はすべてのケースを処理できる必要があります。

+1

'void *'の数値は? –

+0

明示的な型変換を試してください。私はあなたの関数には未定義の動作がたくさんあると確信しています。 –

+4

@CédricBignon私はあなたの質問をよく理解していません。 'void *'型の変数は、プラットフォームに依存するアドレス範囲内に任意の数値を持つことができ、いずれの場合でもそのプラットフォーム上の 'IntPtr'に収まるでしょう。この場合、どのように動作するかを示す例を追加しました。 –

答えて

3

短い答えは、これは直接行うことはできません。その理由は、変換機能を渡すと、従来のインデックス対応コンテナ(System.ArrayCollections.IListArrayListなど)は、インデックス操作を実行すると結果がSystem.Objectにキャストされます。 C#のポインタはObjectから派生しないので、SystemNotSupportedまたは同様の例外が発生します。

2つの合理的な回避策があります。

  1. は メソッドを呼び出す前にポインタ配列をvoidへのポインタ配列に変換します。
  2. メソッドを呼び出す前に、ポインタ配列をvoidポインタポインタに変換します。

最初のものは、配列の内容全体をforループで複製する必要があるため、やや面倒です。 2番目のオプションは、管理されたSystem.Arrayオブジェクトでラップされていないため、配列の長さを渡す必要があります。

サンプルコード

方法:

unsafe Array ToIntPtrArray(void** a, int count) 
    { 
     IntPtr[] intPtrArray = new IntPtr[count]; 

     for (int n = 0; n < count; n++) 
      intPtrArray[n] = new IntPtr(a[n]); 

     return intPtrArray; 
    } 

使用例(整数ポインタ配列):

int*[] intPtrArray; 

// Code that initializes the values of intPtrArray 

fixed(int** ptr = &intPtrArray[0]) 
{ 
    Array result = ToIntPtrArray((void**)ptr, intPtrArray.Length); 
} 

使用例(ボイドポインタポインタ配列):

void**[] voidPtrPtrArray; 

// Code that initializes the values of voidPtrPtrArray 

fixed(void*** ptr = &voidPtrPtrArray[0]) 
{ 
    Array result = ToIntPtrArray((void**)ptr, voidPtrPtrArray.Length); 
} 

使用例(多次元INTポインタ配列):

int*[,] int2dArray; 

// Code that initializes the values of int2dArray 

fixed(int** ptr = &int2dArray[0,0]) 
{ 
    Array result = ToIntPtrArray((void**)ptr, TotalSize(int2dArray)); 
    Array reshaped = ReshapeArray(result,int2dArray); 
} 

TotalSizeReshapeArrayは、多次元配列に対処するために書かれているヘルパー関数です。これを達成するためのヒントについては、Programatically Declare Array of Arbitrary Rankを参照してください。

+0

いいですが、OPの' void * [、] 'と同じ形のOPの場合は扱えませんe正しく。それは配列を一次元に平らにしますが、反射を使わなければ避けられないものです。 – Xcelled194

+0

@ Xcelled194では、問題のポインタの制約がなくても、元の配列と同じランクとディメンションを持つ任意の配列を作成することは難しい問題です。ギザギザの配列の場合、これは再帰を使って行うことができます。長方形の多次元配列の場合は、まったく実行できないと思います。 – nicholas

+0

それは反射とSetValue()で行う必要があります、私はほとんど確信しています。しかし、私の腸はそれができたと私に伝えます。 – Xcelled194

2

これはかなり難しい問題です。適切な形状の配列を作成することはそれほど悪くはありません。

unsafe System.Array ToIntPtrArray(System.Array a) 
{ 
    int[] lengths = new int[a.Rank]; 
    int[] lowerBounds = new int[a.Rank]; 
    for (int i = 0; i < a.Rank; ++i) 
    { 
     lengths[i] = a.GetLength(i); 
     lowerBounds[i] = a.GetLowerBound(i); 
    } 
    Array newArray = Array.CreateInstance(typeof (IntPtr), lengths, lowerBounds); 

    // The hard part is iterating over the array. 
    // Multiplying the lengths will give you the total number of items. 
    // Then we go from 0 to n-1, and create the indexes 
    // This loop could be combined with the loop above. 
    int numItems = 1; 
    for (int i = 0; i < a.Rank; ++i) 
    { 
     numItems *= lengths[i]; 
    } 

    int[] indexes = new int[a.Rank]; 
    for (int i = 0; i < numItems; ++i) 
    { 
     int work = i; 
     int inc = 1; 
     for (int r = a.Rank-1; r >= 0; --r) 
     { 
      int ix = work%lengths[r]; 
      indexes[r] = lowerBounds[r] + ix; 
      work -= (ix*inc); 
      inc *= lengths[r]; 
     } 

     object obj = a.GetValue(indexes); 
     // somehow create an IntPtr from a boxed pointer 
     var myPtr = new IntPtr((long) obj); 
     newArray.SetValue(myPtr, indexes); 
    } 
    return newArray; 
} 

これは正しいタイプと形状(寸法と長さ)の配列を作成しますが、問題があります。配列からアイテムを取得するために使用するGetValueメソッドは、objectを返します。そして、ポインタ型をobjectにキャストすることはできません。いいえ、方法はありません。だからあなたは配列から値を得ることができません!たとえば、long*という配列のGetValueを呼び出すと、「タイプがサポートされていません」と表示されます。

あなたは、その奇妙な形の配列をint*(または他のポインタ型)の1次元配列にコピーする方法が必要だと思います。次に、一時配列を直接索引付けして、IntPtr配列を移入するための値を取得できます。

これは興味深い鶏卵問題です。 System.Arrayとして渡すと、objectからint*(または他のポインタ型)への変換パスがないため、項目を取得できません。しかし、ポインタ型(例:int**)として渡すと、そのオブジェクトの形状を取得できません。あなたはその後、System.Arrayメタデータとは、使用できる形で実際のデータを持っている

unsafe System.Array ToIntPtrArray(System.Array a, void** aAsPtr) 

は、私はあなたがとしてそれを書くことができたとします。

+0

あなたの次の最後の提案は本当にうまくいくものです。配列へのポインタを渡すことの最終的な提案は、あなたがマーシャリングをしたいのでない限り、 'System.Array'オブジェクトのアドレスを取ることが許されていないからではありません。 'fixed'キーワードを使って配列を' void ** 'として固定し、元の配列をメタデータに渡すことは、すべてのOPのリクエストを1つの石で殺すためだけの方法です。 – nicholas

+0

@nicholas:ありがとう。あなたが正しい。私はもう少し探検をした。私の更新を参照してください。 –

0

質問はうまく答えられましたが、私は将来の読者に注意/警告を残す必要があると感じています。

CLRは、の安全な、または少なくともできるだけ安全なように設計されています。これは、(とりわけ)型安全性と抽象的なメモリ操作でこれを実現します。これらの保護の一部を安全でないブロックで無効にすることはできますが、保護機能の一部はコンパイラ/ランタイムにハードコードされています。この追加のハードルを回避するためには、ハッキーに、そしておそらく遅いコードに頼らなければなりません。これらのメソッドは機能しますが、そのようなことをすることは、ランタイムの変更、そのコードセグメントの変更が必要な将来のプログラマ、またはそれらを使用して別のことをする必要があるかどうかにかかわらず、後でコード内のポインタ。

この時点で、私は真剣にManaged C++で書かれたヘルパーdllを事の「生ポインタ」側を扱うと考えます。私の推論は、Unsafeを使用することで、CLRが提供する多くの保護を既に捨てていることです。保護されている追加のものによって妨害されずに作業するほうが簡単かもしれません。言い換えれば、ポインタを最大限に使用して、完了したらintptrにキャストすることができます。それ以外の場合は、C++でToIntPtrArrayを実装することができます。それでもC#側でポインタを渡しますが、CLRの監督から脱出してください。

「安全でない」を使用するたびにC++を破棄する必要があるとは言いません。まったく逆のことに、安全ではないでしょうが、この例ではC++のヘルパーがおそらく何かを考えています。

免責事項:私は管理されたC++でずっとやっていません。私は完全に間違っている可能性があり、CLRは依然としてポインタを監視している可能性があります。もう少し経験豊富な魂がコメントして私にどちらかの方法で教えてもらえれば大いに感謝します。