IntPtr арифметика - PullRequest
       27

IntPtr арифметика

7 голосов
/ 23 августа 2009

Я попытался выделить массив структур следующим образом:

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? Другими словами, изменение структуры отражает изменения в данных массива структуры или нет?

Определенно, я хочу работать с данными, указанными IntPtr, используя struct, не копируя каждый раз данные, избегая небезопасного кода.

Спасибо всем!

Ответы [ 5 ]

11 голосов
/ 23 августа 2009

У меня есть четыре варианта: два с использованием только «безопасного» кода и два с использованием небезопасного кода. Небезопасные варианты, вероятно, будут значительно быстрее.

Safe:

  • Распределите ваш массив в управляемой памяти и объявите вашу функцию 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 . Если нет, то безопасные процедуры - ваш единственный выбор.

8 голосов
/ 23 августа 2009

«Почему IntPtr не хватает арифметики?»

IntPtr хранит только адрес памяти. Он не имеет никакой информации о содержимом этого места в памяти. Таким образом, это похоже на void*. Чтобы включить арифметику указателей, вы должны знать размер объекта, на который указывает.


По сути, IntPtr в первую очередь предназначен для использования в управляемых контекстах как непрозрачный дескриптор (т. Е. Тот, который вы не разыменовываете напрямую в управляемом коде, и вы просто сохраняете время для передачи в неуправляемый код.) предоставляет указатели, которыми вы можете манипулировать напрямую.

3 голосов
/ 23 августа 2009

Действительно, тип 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, поэтому не повезло.

Как правило, если вы собираетесь делать что-то сложное с указателями, вероятно, лучше выбрать небезопасный код.

2 голосов
/ 23 августа 2009

Вы можете использовать интегральный адрес памяти структуры указателя, используя 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]
    }
}

EDIT:

Я размышляю, что IntPtr позволяет вам обрабатывать указатели на данные без явного манипулирования адресами указателей. Это позволяет вам взаимодействовать с COM и собственным кодом, не объявляя небезопасные контексты. Единственное требование, предъявляемое средой выполнения, - это разрешение неуправляемого кода. В этих целях кажется, что большинство методов маршаллинга принимают только целые данные IntPtr, а не чистые типы integer или long, поскольку он обеспечивает тонкий слой, который защищает от манипулирования содержимым структуры. Вы могли бы напрямую манипулировать внутренними объектами IntPtr, но для этого нужны либо небезопасные указатели (опять-таки небезопасные контексты), либо рефлексия. Наконец, IntPtr автоматически адаптируется к размеру указателя платформы.

0 голосов
/ 31 января 2019

Вы можете использовать Marshal.UnsafeAddrOfPinnedArrayElement, чтобы получить адрес определенных элементов в массиве, используя IntPtr из закрепленного массива.

Вот пример класса для обёртки вокруг закрепленных массивов , чтобы я мог использовать их с кодом 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 Работа с PInvoke и IntPtr так же опасна, как и пометка вашей сборки как небезопасной и использование указателей в небезопасном контексте (если не больше)

Если вы не возражаете против небезопасных блоков, вы можете написать функции расширения, которые работают на приведении IntPtr к byte*, например:

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

Однако, как всегда, вы должны знать о возможных проблемах выравнивания при приведении к различным типам указателей.

...