我试图以这种方式分配结构的数组:
struct T {
int a; int b;
}
data = Marshal.AllocHGlobal(count*Marshal.SizeOf(typeof(T));
...
我想访问分配数据“绑定”一个struct每个元素与AllocHGlobal分配的数组......这样的事情
T v;
v = (T)Marshal.PtrToStructure(data+1, typeof(T));
但我没有找到任何方便的方式...... 为什么IntPtr的缺乏算术吗? 我怎样才能在一个“安全”的方式解决这个?
有人可以证实,累得PtrToStructure功能将数据复制到结构体变量? 换言之,对矫正该结构体现在结构阵列数据的修改,或不?
当然,我想在使用结构由一个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
将仅读取或写入到该阵列。 在这种情况下,装饰tarray
与参数[In]
只读)或[Out]
只写)属性。 这使得运行时跳过的复制步骤之一。
正如你现在正在做的,在分配非托管内存数组,并使用一堆调用的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); }
最后一个选项可能是最干净的,如果你可以使用不安全的代码并关注性能,因为你唯一的不安全的代码是你调用本机程序。 如果性能是不是一个问题(也许如果数组的大小比较小),或者如果您不能使用不安全的代码(也许你没有完全信任),那么第一个选项可能是干净的,但是,正如我所说,如果你调用之间访问本机程序的元素数量在数组中的元素数量的一小部分,那么第二个选项是快。
注意:
不安全的操作假定你的结构是blittable 。 如果不是,那么安全程序是你唯一的选择。
“为什么IntPtr
缺乏算术吗?”
IntPtr
商店只是一个内存地址。 它没有任何形式的有关存储位置的内容的信息。 通过这种方式,它类似于void*
。 为了使指针运算,你必须知道对象的大小指出。
从根本上说, IntPtr
主要设计在托管环境中使用作为不透明句柄(即一个你不直接取消引用托管代码,您只需保持周围传递给非托管代码。) unsafe
情况下提供了可以直接处理指针。
事实上, IntPtr
类型没有自己的算术运算符。 适当的(不安全)的指针运算是用C#支持,但IntPtr
和Marshal
类指针“更安全的”的使用存在。
我想你想的东西像下面这样:
int index = 1; // 2nd element of array
var v = (T)Marshal.PtrToStructure(new IntPtr(data.ToInt32() +
index * Marshal.SizeOf(typeof(T)), typeof(T));
另外,还要注意IntPtr
有之间没有隐式转换int
和IntPtr
,所以没有运气。
一般来说,如果你打算做什么用的指针远程复杂,它可能是最好的选择不安全的代码。
您可以使用使用指针结构的整体内存地址IntPtr.ToInt32()
但平台“位数”的提防(32/64)。
对于典型的指针算术,使用指针(查找fixed
和unsafe
的文档中):
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
数据,而不是纯integer
或long
的类型,因为它提供保护免受操纵结构的含量的薄层。 你可以操纵的内部IntPtr
直接,但要么需要不安全的指针(再一次不安全的环境中)或反射。 最后,IntPtr的将自动采用该平台的指针大小。
你可以使用Marshal.UnsafeAddrOfPinnedArrayElement
中使用的阵列来获得特定元素的地址IntPtr
从固定阵列。
这里是一个周边的固定阵列的包装样品类 ,这样我可以用的IntPtr和编组代码中使用它们:
/// <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);
}
}
恕我直言用的PInvoke和IntPtr的工作是一样危险标记您的程序集不安全并在不安全的环境中使用指针(如果不是更多)
如果你不介意的不安全块,你可以写上操作的扩展功能IntPtr
强制转换为byte*
如下所示:
public static long Distance(this IntPtr a, IntPtr b)
{
return Math.Abs(((byte*)b) - ((byte*)a));
}
但是,像往常一样,你必须强制转换为不同的指针类型时,要注意可能的对齐问题。