IntPtr算术

我试图以这种方式分配一个结构数组:

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函数将数据复制到struct变量中吗? 换句话说,修改结构体是否反映了结构数组数据中的修改?

当然,我想对使用struct的IntPtr指向的数据进行操作,而不是每次都复制数据,避免不安全的代码。

谢谢大家!

你有四个我能想到的选择,两个只使用“安全”代码,两个使用不安全代码。 不安全的选项可能会明显加快。

安全:

  • 在托管内存中分配您的数组,并声明您的P / Invoke函数以获取该数组。 即,而不是:

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

    做了

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

    (我使用NativeType作为结构名而不是T ,因为T通常用在通用上下文中。)

    这种方法的问题在于,据我所知, NativeType[]数组将在每次调用Foo被封送两次。 它将在调用之前从托管内存复制到非托管内存,然后从非托管内存复制到托管内存。 但是,如果Foo只读取或写入数组,则可以进行改进。 在这种情况下,使用[In] (只读)或[Out] (只写)属性装饰tarray参数。 这允许运行时跳过其中一个复制步骤。

  • 正如您现在所做的那样,在非托管内存中分配数组,并使用一堆对Marshal.PtrToStructureMarshal.StructureToPtr的调用。 这可能比第一个选项表现得更差,因为您仍然需要来回复制数组元素,并且您正在逐步执行此操作,因此您需要更多开销。 另一方面,如果数组中有许多元素,但是在调用Foo之间只访问少量元素,那么这可能会表现得更好。 您可能需要一些小辅助函数,如下所示:

     static T ReadFromArray(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(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); } 

如果您可以使用不安全的代码并关注性能,那么最后一个选项可能是最干净的,因为您唯一不安全的代码是您调用本机例程的地方。 如果性能不是问题(也许如果数组的大小相对较小),或者如果你不能使用不安全的代码(也许你没有完全信任),那么第一个选项可能是最干净的,但是,正如我所提到的,如果在调用本机例程之间访问的元素数量只是数组中元素数量的一小部分,那么第二个选项会更快。

注意:

不安全的操作假设你的结构是blittable 。 如果没有,那么安全例程是您唯一的选择。

“为什么IntPtr缺乏算术?”

IntPtr只存储一个内存地址。 它没有关于该内存位置内容的任何信息。 以这种方式,它类似于void* 。 要启用指针运算,您必须知道指向的对象的大小。


从根本上说, IntPtr主要设计为在托管上下文中用作不透明句柄(即,您不会在托管代码中直接取消引用而只是保持传递给非托管代码。) unsafe context提供了可以直接操作的指针。

实际上, 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)); 

另外,请注意IntPtrintIntPtr之间没有隐式转换,所以没有运气。

通常,如果您要使用指针进行任何远程复杂操作,最好选择不安全的代码。

您可以使用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数据,而不是纯integerlong类型,因为它提供了一个薄层,可以防止操纵结构的内容。 您可以直接操作IntPtr的内部,但要么需要不安全的指针(再次是不安全的上下文)或reflection。 最后,IntPtr会自动采用平台的指针大小。