IntPtr 算术

发布于 2024-08-02 05:18:38 字数 513 浏览 5 评论 0原文

我尝试以这种方式分配结构数组:

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 指向的数据进行操作,而不是每次都复制数据,从而避免不安全的代码。

谢谢大家!

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

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

but i don't find any convenient way... why IntPtr lack of arithmetics? How can I workaround this in a "safe" way?

Someone could confirm that PtrToStructure function copy data into the struct variable? 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.

Thank all!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

咿呀咿呀哟 2024-08-09 05:18:39

“为什么IntPtr缺少算术?”

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


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

"Why IntPtr lack of arithmetics?"

IntPtr stores just a memory address. It doesn't have any kind of information about the contents of that memory location. In this manner, it's similar to 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 (i.e. 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.

你又不是我 2024-08-09 05:18:39

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

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

一般来说,如果您要使用指针执行任何复杂的操作,最好选择不安全的代码。

Indeed, the IntPtr type does not have its own arithmetic operators. Proper (unsafe) pointer arithmetic is supported in C#, but IntPtr and the Marshal class exist for 'safer' usage of pointers.

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.

Generally, if you're going to be doing anything remotely complex with pointers, it's probably best to opt for unsafe code.

や莫失莫忘 2024-08-09 05:18:39

您可以通过 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的内部,但这要么需要不安全的指针(同样是不安全的上下文)或反射。最后,IntPtr 自动采用平台的指针大小。

You can use the integral memory address of the pointer structure using IntPtr.ToInt32() but beware of platform "bitness" (32/64).

For typical pointer arithmetics, use pointers (look up fixed and unsafe in the documentation):

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. This allows you to interop with COM and native code without having to declare unsafe contexts. 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. You could manipulate the internals of an IntPtr directly, but that either requires unsafe pointers (again unsafe contexts) or reflection. Finally, IntPtr is automatically adopted to the platform's pointer size.

百合的盛世恋 2024-08-09 05:18:39

您可以使用 Marshal.UnsafeAddrOfPinnedArrayElement 使用固定数组中的 IntPtr 获取数组中特定元素的地址。

这是固定数组包装器的示例类< /a> 这样我就可以将它们与 IntPtr 和封送代码一起使用:

    /// <summary>
    /// Pins an array of Blittable structs so that we can access the data as bytes. Manages a GCHandle around the array.
    /// https://learn.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));
    }

但是,像往常一样,您必须注意在转换为不同指针时可能出现的对齐问题类型。

You could use Marshal.UnsafeAddrOfPinnedArrayElement to get address of specific elements in an array using an IntPtr from a pinned array.

Here is a sample class for a wrapper around pinned arrays so that I can use them with IntPtr and Marshaling code:

    /// <summary>
    /// Pins an array of Blittable structs so that we can access the data as bytes. Manages a GCHandle around the array.
    /// https://learn.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)

If you don't mind unsafe blocks you can write extension functions that operate on the IntPtr cast to byte* like the following:

    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.

仲春光 2024-08-09 05:18:38

我能想到的有四种选择,两种仅使用“安全”代码,两种使用不安全代码。不安全的选项可能会快得多。

安全:

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

    <前><代码>[DllImport(...)]
    静态 extern bool Foo(int count, IntPtr arrayPtr);

    成功

    <前><代码>[DllImport(...)]
    静态 extern bool Foo(int count, NativeType[] array);

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

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

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

    static T ReadFromArray(IntPtr arrayPtr, int index){
        // 下面,如果您 **知道** 您将使用 32 位平台,
        // 您可以将 ToInt64() 更改为 ToInt32()。
        返回 (T)Marshal.PtrToStructure((IntPtr)(arrayPtr.ToInt64() +
            索引 * Marshal.SizeOf(typeof(T)));
    }
    // 您可以将下面的“T value”更改为“ref T value”以避免再次复制
    static void WriteToArray(IntPtr arrayPtr, int 索引, T 值){
        // 下面,如果您 **知道** 您将使用 32 位平台,
        // 您可以将 ToInt64() 更改为 ToInt32()。
        Marshal.StructureToPtr(value, (IntPtr)(arrayPtr.ToInt64() +
            索引 * Marshal.SizeOf(typeof(T)), false);
    }
    

不安全:

  • 在非托管内存中分配数组,并使用指针访问元素。这意味着使用该数组的所有代码都必须位于 unsafe 块内。

    IntPtr arrayPtr = Marhsal.AllocHGlobal(count * sizeof(typeof(NativeType)));
    不安全{
        NativeType* ptr = (NativeType*)arrayPtr.ToPointer();
    
        ptr[0].Member1 = foo;
        ptr[1].Member2 = 栏;
        /* 等等 */
    }
    Foo(count, arrayPtr);
    
  • 在托管内存中分配数组,并在需要调用本机例程时固定它:

    NativeType[] array = new NativeType[count];
    数组[0].Member1 = foo;
    数组[1].Member2 = 条;
    /* 等等 */
    
    不安全{
        固定(NativeType* ptr = 数组)
            Foo(计数, (IntPtr)ptr);
            // 或者只是 Foo(count, ptr),如果 Foo 是这样声明的:
            // static unsafe bool Foo(int count, NativeType* arrayPtr);
    }
    

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

注意:

不安全操作假定您的结构是 blittable。如果没有,那么安全的日常活动是您唯一的选择。

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. i.e., 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.)

    The problem with this approach is that, as I understand it, the NativeType[] array will be marshaled twice for every call to 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. In this case, decorate the tarray parameter with an [In] (read only) or [Out] (write only) attribute. 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. 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. 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.

    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. If not, then the safe routines are your only option.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文