7

私はこの方法で構造体の配列を割り当てようとしました:

struct T {
    int a; int b;
}

data = Marshal.AllocHGlobal(count*Marshal.SizeOf(typeof(T));
...

AllocHGlobalで割り当てられた配列内の各要素に構造体を「バインド」する割り当てられたデータにアクセスしたい...このようなもの

T v;
v = (T)Marshal.PtrToStructure(data+1, typeof(T));

しかし、便利な方法が見つかりません...なぜIntPtrに算術がないのですか?これを「安全な」方法で回避するにはどうすればよいですか?

PtrToStructure関数がデータを構造体変数にコピーすることを誰かが確認できますか?言い換えれば、構造体を変更すると、構造体配列データの変更が反映されるかどうか。

間違いなく、構造体を使用してIntPtrが指すデータを操作し、毎回データをコピーせずに、安全でないコードを回避したいと思います。

みんなありがとう!

4

5 に答える 5

11

私が考えることができる4つのオプションがあります。2つは「安全な」コードのみを使用し、2つは安全でないコードを使用します。安全でないオプションは、大幅に高速になる可能性があります。

安全な:

  • 管理対象メモリに配列を割り当て、配列を取得するようにP/Invoke関数を宣言します。つまり、代わりに:

    [DllImport(...)]
    static extern bool Foo(int count, IntPtr arrayPtr);
    

    成功する

    [DllImport(...)]
    static extern bool Foo(int count, NativeType[] array);
    

    (一般的なコンテキストでよく使用されるため、NativeTypeの代わりに構造体名を使用しました。)TT

    このアプローチの問題は、私が理解しているように、へのNativeType[]呼び出しごとに配列が2回マーシャリングされることFooです。呼び出し前にマネージメモリからアンマネージメモリにコピーされ、後でアンマネージメモリからマネージメモリにコピーされます。Fooただし、アレイからの読み取りまたはアレイへの書き込みのみを行う場合は、改善できます。この場合、tarrayパラメーターを[In](読み取り専用)または[Out](書き込み専用)属性で装飾します。これにより、ランタイムはコピー手順の1つをスキップできます。

  • 現在行っているように、アンマネージメモリに配列を割り当て、とへの一連の呼び出しを使用しMarshal.PtrToStructureますMarshal.StructureToPtr。配列の要素を前後にコピーする必要があり、段階的に実行するため、オーバーヘッドが増えるため、これは最初のオプションよりもさらにパフォーマンスが低下する可能性があります。一方、配列に多くの要素があり、への呼び出しの間に少数の要素にしかアクセスしないFoo場合は、パフォーマンスが向上する可能性があります。次のような、いくつかの小さなヘルパー関数が必要になる場合があります。

    static T ReadFromArray<T>(IntPtr arrayPtr, int index){
        // below, if you **know** you'll be on a 32-bit platform,
        // you can change ToInt64() to ToInt32().
        return (T)Marshal.PtrToStructure((IntPtr)(arrayPtr.ToInt64() +
            index * Marshal.SizeOf(typeof(T)));
    }
    // you might change `T value` below to `ref T value` to avoid one more copy
    static void WriteToArray<T>(IntPtr arrayPtr, int index, T value){
        // below, if you **know** you'll be on a 32-bit platform,
        // you can change ToInt64() to ToInt32().
        Marshal.StructureToPtr(value, (IntPtr)(arrayPtr.ToInt64() +
            index * Marshal.SizeOf(typeof(T)), false);
    }
    

安全ではない:

  • アンマネージメモリに配列を割り当て、ポインタを使用して要素にアクセスします。unsafeこれは、配列を使用するすべてのコードがブロック内にある必要があることを意味します。

    IntPtr arrayPtr = Marhsal.AllocHGlobal(count * sizeof(typeof(NativeType)));
    unsafe{
        NativeType* ptr = (NativeType*)arrayPtr.ToPointer();
    
        ptr[0].Member1 = foo;
        ptr[1].Member2 = bar;
        /* and so on */
    }
    Foo(count, arrayPtr);
    
  • 管理対象メモリに配列を割り当て、ネイティブルーチンを呼び出す必要がある場合はそれを固定します。

    NativeType[] array = new NativeType[count];
    array[0].Member1 = foo;
    array[1].Member2 = bar;
    /* and so on */
    
    unsafe{
        fixed(NativeType* ptr = array)
            Foo(count, (IntPtr)ptr);
            // or just Foo(count, ptr), if Foo is declare as such:
            //     static unsafe bool Foo(int count, NativeType* arrayPtr);
    }
    

安全でないコードを使用でき、パフォーマンスが心配な場合は、この最後のオプションがおそらく最もクリーンです。安全でないコードは、ネイティブルーチンを呼び出す場所だけだからです。パフォーマンスが問題にならない場合(おそらく配列のサイズが比較的小さい場合)、または安全でないコードを使用できない場合(おそらく完全な信頼がない場合)、最初のオプションが最もクリーンである可能性がありますが、前述したように、ネイティブルーチンの呼び出しの間にアクセスする要素の数が、配列内の要素の数のわずかな割合である場合、2番目のオプションの方が高速です。

ノート:

安全でない操作は、構造体がblittableであることを前提としています。そうでない場合は、安全なルーチンが唯一の選択肢です。

于 2009-08-23T16:18:44.197 に答える
8

「なぜIntPtr算術が足りないのですか?」

IntPtrメモリアドレスのみを格納します。そのメモリ位置の内容に関する情報はありません。このように、それはに似ていvoid*ます。ポインタ演算を有効にするには、ポイントされているオブジェクトのサイズを知っている必要があります。


基本的に、IntPtr主にマネージドコンテキストで不透明なハンドル(つまり、マネージコードで直接逆参照せず、アンマネージコードに渡すためにそのまま使用するハンドル)として使用するように設計されています。unsafeコンテキストは、直接操作できるポインターを提供します。

于 2009-08-23T14:45:54.650 に答える
3

実際、このIntPtr型には独自の算術演算子がありません。適切な(安全でない)ポインター演算C#でサポートされていますが、ポインターを「より安全に」使用するためのクラスが存在しますIntPtrMarshal

私はあなたが次のようなものが欲しいと思います:

int index = 1; // 2nd element of array
var v = (T)Marshal.PtrToStructure(new IntPtr(data.ToInt32() + 
    index * Marshal.SizeOf(typeof(T)), typeof(T));

また、とIntPtrの間には暗黙の変換がないため、運がないことに注意してください。intIntPtr

一般に、ポインターを使用してリモートで複雑なことを行う場合は、安全でないコードを選択するのがおそらく最善です。

于 2009-08-23T14:24:13.233 に答える
2

IntPtr.ToInt32()プラットフォームの「ビットネス」(32/64)を使用して、ポインタ構造の整数メモリアドレスを使用できますが、注意が必要です。

一般的なポインタ演算には、ポインタを使用します(ドキュメントを参照してください)fixedunsafe

T data = new T[count];
fixed (T* ptr = &data)
{
    for (int i = 0; i < count; i++)
    {
        // now you can use *ptr + i or ptr[i]
    }
}

編集:

ポインタアドレスを明示的に操作IntPtrしなくても、データへのポインタを処理できるようになることを考えています。これにより、安全でないコンテキストを宣言することなく、COMおよびネイティブコードと相互運用できます。ランタイムが課す唯一の要件は、アンマネージコードのアクセス許可です。これらの目的のために、ほとんどのマーシャリングメソッドは、構造のコンテンツの操作から保護する薄いレイヤーを提供するため、純粋なデータや型ではなく、データ全体のみを受け入れるようです。内部を直接操作することもできますが、それには安全でないポインター(これも安全でないコンテキスト)またはリフレクションが必要です。最後に、IntPtrはプラットフォームのポインターサイズに自動的に採用されます。IntPtrintegerlongIntPtr

于 2009-08-23T14:23:09.320 に答える
0

を使用して、固定された配列からMarshal.UnsafeAddrOfPinnedArrayElementを使用して、配列内の特定の要素のアドレスを取得できます。IntPtr

IntPtrおよびMarshalingコードで使用できるように、固定された配列のラッパーのサンプルクラスを次に示します。

    /// <summary>
    /// Pins an array of Blittable structs so that we can access the data as bytes. Manages a GCHandle around the array.
    /// https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.unsafeaddrofpinnedarrayelement?view=netframework-4.7.2
    /// </summary>
    public sealed class PinnedArray<T> : IDisposable
    {
        public GCHandle Handle { get; }
        public T[] Array { get; }

        public int ByteCount { get; private set; }
        public IntPtr Ptr { get; private set; }

        public IntPtr ElementPointer(int n)
        {
            return Marshal.UnsafeAddrOfPinnedArrayElement(Array, n);
        }

        public PinnedArray(T[] xs)
        {
            Array = xs;
            // This will fail if the underlying type is not Blittable (e.g. not contiguous in memory)
            Handle = GCHandle.Alloc(xs, GCHandleType.Pinned);
            if (xs.Length != 0)
            {
                Ptr = ElementPointer(0);
                ByteCount = (int) Ptr.Distance(ElementPointer(Array.Length));
            }
            else
            {
                Ptr = IntPtr.Zero;
                ByteCount = 0;
            }
        }

        void DisposeImplementation()
        {
            if (Ptr != IntPtr.Zero)
            {
                Handle.Free();
                Ptr = IntPtr.Zero;
                ByteCount = 0;
            }
        }

        ~PinnedArray()
        {
            DisposeImplementation();
        }

        public void Dispose()
        {
            DisposeImplementation();
            GC.SuppressFinalize(this);
        }
    }

IMHO PInvokeおよびIntPtrの操作は、アセンブリを安全でないものとしてマークし、安全でないコンテキストでポインターを使用するのと同じくらい危険です(それ以上ではない場合)

安全でないブロックを気にしない場合は、IntPtrキャストを操作する拡張関数をbyte*次のように記述できます。

    public static long Distance(this IntPtr a, IntPtr b)
    {
         return Math.Abs(((byte*)b) - ((byte*)a));
    }

ただし、いつものように、さまざまなポインタタイプにキャストする場合は、配置の問題が発生する可能性があることに注意する必要があります。

于 2019-01-31T14:39:10.623 に答える