简体   繁体   English

IntPtr算术

[英]IntPtr arithmetics

I tried to allocate an array of structs in this way: 我试图以这种方式分配一个结构数组:

struct T {
    int a; int b;
}

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

I'd like to access to allocated data "binding" a struct to each element in array allocated with AllocHGlobal... something like this 我想访问分配的数据“绑定”一个结构到分配给AllocHGlobal的数组中的每个元素...像这样的东西

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

but i don't find any convenient way... why IntPtr lack of arithmetics ? 但我没有找到任何方便的方法... 为什么IntPtr缺乏算术 How can I workaround this in a "safe" way? 我该如何以“安全”的方式解决这个问题?

Someone could confirm that PtrToStructure function copy data into the struct variable? 有人可以确认PtrToStructure函数将数据复制到struct变量中吗? In other words, modifing the struct reflect modifications in the structure array data, or not? 换句话说,修改结构体是否反映了结构数组数据中的修改?

Definitely, I want to operate on data pointed by an IntPtr using struct, without copying data each time, avoiding unsafe code. 当然,我想对使用struct的IntPtr指向的数据进行操作,而不是每次都复制数据,避免使用不安全的代码。

Thank all! 谢谢大家!

You have four options that I can think of, two using only "safe" code, and two using unsafe code. 你有四个我能想到的选择,两个只使用“安全”代码,两个使用不安全代码。 The unsafe options are likely to be significantly faster. 不安全的选项可能会明显加快。

Safe: 安全:

  • Allocate your array in managed memory, and declare your P/Invoke function to take the array. 在托管内存中分配您的数组,并声明您的P / Invoke函数以获取该数组。 ie, instead of: 即,而不是:

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

    make it 做了

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

    (I've used NativeType for your struct name instead of T , since T is often used in a generic context.) (我使用NativeType作为结构名称而不是T ,因为T通常用在通用上下文中。)

    The problem with this approach is that, as I understand it, the NativeType[] array will be marshaled twice for every call to Foo . 这种方法的问题在于,据我所知, NativeType[]数组将在每次调用Foo被封送两次。 It will be copied from managed memory to unmanaged memory before the call, and copied from unmanaged memory to managed memory afterward. 它将在调用之前从托管内存复制到非托管内存,然后从非托管内存复制到托管内存。 It can be improved, though, if Foo will only read from or write to the array. 但是,如果Foo只读取或写入数组,则可以进行改进。 In this case, decorate the tarray parameter with an [In] (read only) or [Out] (write only) attribute. 在这种情况下,使用[In] (只读)或[Out] (只写)属性修饰tarray参数。 This allows the runtime to skip one of the copying steps. 这允许运行时跳过其中一个复制步骤。

  • As you're doing now, allocate the array in unmanaged memory, and use a bunch of calls to Marshal.PtrToStructure and Marshal.StructureToPtr . 正如您现在所做的那样,在非托管内存中分配数组,并使用一堆对Marshal.PtrToStructureMarshal.StructureToPtr的调用。 This will likely perform even worse than the first option, as you still need to copy elements of the array back and forth, and you're doing it in steps, so you have more overhead. 这可能比第一个选项执行得更糟,因为您仍然需要来回复制数组元素,并且您正在逐步执行此操作,因此您需要更多开销。 On the other hand, if you have many elements in the array, but you only access a small number of them in between calls to Foo , then this may perform better. 另一方面,如果数组中有许多元素,但是在调用Foo之间只访问少量元素,那么这可能会表现得更好。 You might want a couple of little helper functions, like so: 您可能需要一些小辅助函数,如下所示:

     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: 不安全:

  • Allocate your array in unmanaged memory, and use pointers to access the elements. 在非托管内存中分配数组,并使用指针访问元素。 This means that all the code that uses the array must be within an unsafe block. 这意味着使用该数组的所有代码必须位于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); 
  • Allocate your array in managed memory, and pin it when you need to call the native routine: 在托管内存中分配数组,并在需要调用本机例程时将其固定:

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

This last option is probably the cleanest if you can use unsafe code and are concerned about performance, because your only unsafe code is where you call the native routine. 如果您可以使用不安全的代码并关注性能,那么最后一个选项可能是最干净的,因为您唯一不安全的代码是您调用本机例程的地方。 If performance isn't an issue (perhaps if the size of the array is relatively small), or if you can't use unsafe code (perhaps you don't have full trust), then the first option is likely cleanest, although, as I mentioned, if the number of elements you'll access in between calls to the native routine are a small percentage of the number of elements within the array, then the second option is faster. 如果性能不是问题(可能是因为数组的大小相对较小),或者如果你不能使用不安全的代码(也许你没有完全信任),那么第一个选项可能是最干净的,但是,正如我所提到的,如果在调用本机例程之间访问的元素数量只是数组中元素数量的一小部分,那么第二个选项会更快。

Note: 注意:

The unsafe operations assume that your struct is blittable . 不安全的操作假设您的结构是blittable If not, then the safe routines are your only option. 如果没有,那么安全例程是您唯一的选择。

"Why IntPtr lack of arithmetics?" “为什么IntPtr缺乏算术?”

IntPtr stores just a memory address. IntPtr只存储一个内存地址。 It doesn't have any kind of information about the contents of that memory location. 它没有关于该内存位置内容的任何信息。 In this manner, it's similar to void* . 以这种方式,它类似于void* To enable pointer arithmetic you have to know the size of the object pointed to. 要启用指针运算,您必须知道指向的对象的大小。


Fundamentally, IntPtr is primarily designed to be used in managed contexts as an opaque handle (ie one that you don't directly dereference in managed code and you just keep around to pass to unmanaged code.) unsafe context provides pointers you can manipulate directly. 从根本上说, IntPtr主要设计为在托管上下文中用作不透明句柄(即,您不会在托管代码中直接取消引用,而只是保持传递给非托管代码。) unsafe context提供了可以直接操作的指针。

Indeed, the IntPtr type does not have its own arithmetic operators. 实际上, IntPtr类型没有自己的算术运算符。 Proper (unsafe) pointer arithmetic is supported in C#, but IntPtr and the Marshal class exist for 'safer' usage of pointers. 适当的(不安全)的指针运算用C#支持,但IntPtrMarshal类指针“更安全的”的使用存在。

I think you want something like the following: 我想你想要的东西如下:

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

Also, note that IntPtr has no implicit conversion between int and IntPtr , so no luck there. 另外,请注意IntPtrintIntPtr之间没有隐式转换,所以没有运气。

Generally, if you're going to be doing anything remotely complex with pointers, it's probably best to opt for unsafe code. 通常,如果您要使用指针进行任何远程复杂操作,最好选择不安全的代码。

You can use the integral memory address of the pointer structure using IntPtr.ToInt32() but beware of platform "bitness" (32/64). 您可以使用IntPtr.ToInt32()来使用指针结构的整数存储器地址,但要注意平台“位”(32/64)。

For typical pointer arithmetics, use pointers (look up fixed and unsafe in the documentation): 对于典型的指针算术,使用指针(在文档中查找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]
    }
}

EDIT: 编辑:

I'm pondering that IntPtr allows you to handle pointers to data without explicitly manipulating pointer addresses. 我在思考IntPtr允许您处理指向数据的指针而无需显式操作指针地址。 This allows you to interop with COM and native code without having to declare unsafe contexts. 这允许您与COM和本机代码互操作,而无需声明不安全的上下文。 The only requirement that the runtime imposes is the unmanaged code permission. 运行时强加的唯一要求是非托管代码权限。 For those purposes, it seems like most marshalling methods only accept whole IntPtr data, and not pure integer or long types, as it provides a thin layer that protects against manipulating the content of the structure. 出于这些目的,似乎大多数编组方法只接受整个IntPtr数据,而不是纯integerlong类型,因为它提供了一个薄层,可以防止操纵结构的内容。 You could manipulate the internals of an IntPtr directly, but that either requires unsafe pointers (again unsafe contexts) or reflection. 可以直接操作IntPtr的内部,但要么需要不安全的指针(再次是不安全的上下文)或反射。 Finally, IntPtr is automatically adopted to the platform's pointer size. 最后,IntPtr会自动采用平台的指针大小。

You could use Marshal.UnsafeAddrOfPinnedArrayElement to get address of specific elements in an array using an IntPtr from a pinned array. 您可以使用Marshal.UnsafeAddrOfPinnedArrayElement使用固定数组中的IntPtr获取数组中特定元素的地址。

Here is a sample class for a wrapper around pinned arrays so that I can use them with IntPtr and Marshaling code: 这是针对固定数组的包装器示例类,以便我可以将它们与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 Working with PInvoke and IntPtr is as dangerous as marking your assembly as unsafe and using pointers in an unsafe context (if not more) 恕我直言使用PInvoke和IntPtr与将程序集标记为不安全并在不安全的上下文中使用指针一样危险(如果不是更多)

If you don't mind unsafe blocks you can write extension functions that operate on the IntPtr cast to byte* like the following: 如果您不介意不安全的块,您可以编写对IntPtrbyte*扩展函数,如下所示:

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

However, like always you have to be aware of possible alignment issues when casting to different pointer types. 但是,在转换为不同的指针类型时,您必须始终注意可能的对齐问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM