IntPtr算术

Luc*_*uca 7 c# marshalling intptr

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

struct T {
    int a; int b;
}

data = Marshal.AllocHGlobal(count*Marshal.SizeOf(typeof(T));
...
Run Code Online (Sandbox Code Playgroud)

我想访问分配的数据"绑定"一个结构到分配给AllocHGlobal的数组中的每个元素...像这样的东西

T v;
v = (T)Marshal.PtrToStructure(data+1, typeof(T));
Run Code Online (Sandbox Code Playgroud)

但我没有找到任何方便的方法... 为什么IntPtr缺乏算术?我该如何以"安全"的方式解决这个问题?

有人可以确认PtrToStructure函数将数据复制到struct变量中吗?换句话说,修改结构体是否反映了结构数组数据中的修改?

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

谢谢大家!

P D*_*ddy 11

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

安全:

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

    [DllImport(...)]
    static extern bool Foo(int count, IntPtr arrayPtr);
    
    Run Code Online (Sandbox Code Playgroud)

    做了

    [DllImport(...)]
    static extern bool Foo(int count, NativeType[] array);
    
    Run Code Online (Sandbox Code Playgroud)

    (我已经使用NativeType了你的结构名而不是T,因为T它通常用在通用上下文中.)

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

  • 正如您现在所做的那样,在非托管内存中分配数组,并使用一堆调用Marshal.PtrToStructureMarshal.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);
    }
    
    Run Code Online (Sandbox Code Playgroud)

不安全:

  • 在非托管内存中分配数组,并使用指针访问元素.这意味着使用该数组的所有代码都必须在一个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);
    
    Run Code Online (Sandbox Code Playgroud)
  • 在托管内存中分配数组,并在需要调用本机例程时将其固定:

    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);
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

注意:

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


Meh*_*ari 8

"为什么IntPtr缺少算术?"

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


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