10

私は一見些細な仕事に行き詰まっており、あなたの助けが必要です.

次のシグネチャを持つメソッドを記述する必要があります。

System.Array ToIntPtrArray(System.Array a)

ここで、実引数は、任意のポインター型( int*[]long**[]、 など) の配列にすることができ、入力配列の要素と同じ数値を持つvoid*[,]型の要素を持つ同じ形状の配列を返します。System.IntPtr問題は、ポインターの型が事前にわからないと、ポインターの数値を抽出する方法がわからないことです。


たとえば、引数が常に typevoid*[]であることを事前に知っていれば、次のようにメソッドを記述できます。

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**[]または他の何かである可能性があり、メソッドがすべてのケースを処理できる必要があることです。

4

3 に答える 3

3

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

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

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

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

サンプルコード

方法:

    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 ポインター ポインター配列):

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);
}

TotalSizeとは、多次元ReshapeArray配列を処理するために作成されたヘルパー関数です。これを達成する方法のヒントについては、次を参照してください: Programatically Declare Array of Arbitrary Rank .

于 2013-07-23T19:33:10.717 に答える
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。まさか、まさか。したがって、配列から値を取得することはできません! GetValueたとえば、 の配列を呼び出すとlong*、「型がサポートされていません」というメッセージが表示されます。

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

これは興味深いニワトリが先か卵が先かという問題です。として渡すと、to (またはその他のポインター型)System.Arrayからの変換パスがないため、そこからアイテムを取得できません。しかし、ポインタ型 (つまり) として渡すと、オブジェクトの形状を取得できません。objectint*int**

次のように書けると思います。

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

これで、System.Arrayメタデータと実際のデータが、使用できる形式で取得されます。

于 2013-07-23T21:10:11.527 に答える
0

質問には十分な回答がありましたが、将来の読者にメモ/警告を残す必要があると感じています。

CLR は、ユーザーを安全に保つ、または少なくとも可能な限り安全に保つように設計されています。これは、(とりわけ)型の安全性とメモリ操作の抽象化によって実現されます。これらの保護の一部は unsafe ブロックでオフにすることができますが、一部の保護はコンパイラ/ランタイムにハードコードされています。この追加のハードルを回避するには、ハッキーな、場合によっては遅いコードに頼る必要があります。これらの方法は機能しますが、経験から、そのようなことを行うと、ランタイムの変更、将来のプログラマーがコードのそのセグメントを変更する必要があるか、または自分でそれらを使用して何か他のことを行う必要があるかどうかにかかわらず、将来的に問題が発生することがわかりました。コードの後半のポインター。

この時点で、Managed C++ で記述されたヘルパー DLL を使用して、「生のポインター」側を処理することを真剣に検討します。私の推論は、Unsafe を使用することで、CLR が提供する多くの保護を既に破棄していることです。追加の焼き付けられた保護によって邪魔されずに作業する方が簡単だと感じるかもしれません. 言うまでもなく、ポインターを最大限に使用して、完了したら intptr にキャストし直すことができます。他に何もないとしても、C++ での ToIntPtrArray の実装を検討することをお勧めします。C# 側でポインターを渡しますが、CLR の見落としを回避します。

「安全でない」を使用するたびに C++ を破棄する必要があると言っているわけではないことに注意してください。まったく逆です - unsafe はかなりのことを可能にします - しかし、この場合、C++ ヘルパーはおそらく考慮すべきものです。

免責事項: 私はマネージ C++ で多くのことを行ったわけではありません。私が完全に間違っていて、CLR がまだポインターを監視している可能性があります。もっと経験豊富な魂がコメントして、どちらかの方法で私に教えてくれたら、それは大歓迎です.

于 2013-07-23T23:01:54.737 に答える