Для текущего проекта мы думали о соответствующих мерах, которые могли быть приняты основным программным циклом. Основная программа принимает сообщения XML и сохраняет информацию в базу данных (с изрядным количеством обработки промежуточного).
первый объект является контролируемой исключительной ситуацией, так как мы полагали, что проверка данных была частью интерфейса метода. Другие неконтролируемы, так как основной цикл не может знать реализации субкомпонентов, например, реализация может использовать базу данных SQL или может просто сохранить данные в памяти - вызывающая сторона не должна знать.
У вас есть четыре варианта, которые я могу придумать: два с использованием только «безопасного» кода и два с использованием небезопасного кода. Небезопасные варианты, вероятно, будут значительно быстрее.
Выделите свой массив в управляемой памяти, и объявите, что ваша функция P / Invoke принимает массив. т.е. вместо:
[DllImport (...)]
статический extern bool Foo (int count, IntPtr arrayPtr);
сделать это
[DllImport (...)]
static extern bool Foo (int count, NativeType [] массив);
(Я использовал NativeType
для вашего имени структуры вместо T
, поскольку T
часто используется в общем контексте.)
Проблема с этим подходом состоит в том, что, насколько я понимаю, массив NativeType []
будет маршалироваться дважды при каждом вызове Foo
. Он будет скопирован из управляемой памяти в неуправляемую.
память перед вызовом и копирование из неуправляемой памяти в управляемую память впоследствии. Однако это можно улучшить, если Foo
будет только читать или записывать в массив. В этом случае украсьте параметр tarray
атрибутом [In]
(только для чтения) или [Out]
(только для записи). Это позволяет среде выполнения пропустить один из этапов копирования.
Как и сейчас, выделите массив в неуправляемой памяти и используйте несколько вызовов Marshal.PtrToStructure
и Marshal .StructureToPtr
. Это, вероятно, будет работать даже хуже, чем первый вариант, поскольку вам все равно нужно копировать элементы массива туда и обратно, и вы делаете это поэтапно, поэтому у вас больше накладных расходов. С другой стороны, если у вас много элементов в массиве, но вы обращаетесь к небольшому их количеству между вызовами Foo
, тогда это может работать лучше. Вам может понадобиться пара небольших вспомогательных функций, например:
static T ReadFromArray (IntPtr arrayPtr, int index) {
// ниже, если вы ** знаете **, что будете на 32-битной платформе,
// вы можете изменить ToInt64 () на ToInt32 ().
return (T) Marshal.PtrToStructure ((IntPtr) (arrayPtr.ToInt64 () +
index * Marshal.SizeOf (typeof (T)));
}
// вы можете изменить `T value` ниже на` ref T value`, чтобы избежать еще одной копии
static void WriteToArray (IntPtr arrayPtr, int index, T value) {
// ниже, если вы ** знаете **, что будете на 32-битной платформе,
// вы можете изменить ToInt64 () на ToInt32 ().
Marshal.StructureToPtr (значение, (IntPtr) (arrayPtr.ToInt64 () +
index * Marshal.SizeOf (typeof (T)), ложь);
}
Разместите свой массив в неуправляемой памяти и используйте указатели для доступа к элементам. Это означает, что весь код, использующий массив, должен находиться в пределах блока unsafe
.
IntPtr arrayPtr = Marhsal.AllocHGlobal (count * sizeof (typeof (NativeType)));
unsafe {
NativeType * ptr = (NativeType *) arrayPtr.ToPointer ();
ptr [0] .Member1 = foo;
ptr [1] .Member2 = bar;
/* и так далее */
}
Foo (количество, arrayPtr);
Разместите свой массив в управляемой памяти и закрепите его, когда вам нужно вызвать собственную процедуру:
NativeType [] array = new NativeType [count];
массив [0] .Member1 = foo;
array [1] .Member2 = bar;
/* и так далее */
unsafe {
фиксированный (NativeType * ptr = array)
Foo (количество, (IntPtr) ptr);
// или просто Foo (count, ptr), если Foo объявлен как таковой:
// статический небезопасный тип bool Foo (int count, NativeType * arrayPtr);
}
Этот последний вариант, вероятно, самый чистый, если вы можете использовать небезопасный код и беспокоиться о производительности, потому что ваш единственный небезопасный код - это то, где вы вызываете собственную процедуру. Если производительность не является проблемой (возможно, если размер массива относительно мал) или если вы не можете использовать небезопасный код (возможно, у вас нет полного доверия), то первый вариант, вероятно, будет самым чистым, хотя как я уже упоминал, если количество элементов, к которым вы будете обращаться между вызовами собственной подпрограммы, составляет небольшой процент от количества элементов в массиве, тогда второй вариант будет быстрее.
Небезопасный операции предполагают, что ваша структура непреобразуемая . Если нет, то безопасные процедуры - ваш единственный выход.
Действительно, тип 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]
}
}
EDIT:
Я думаю, что IntPtr
позволяет вы обрабатываете указатели на данные без явного манипулирования адресами указателей . Это позволяет взаимодействовать с COM и собственным кодом без объявления небезопасных контекстов. Единственное требование, которое предъявляет среда выполнения, - это разрешение на неуправляемый код. Для этих целей кажется, что большинство методов маршалинга принимают только целые IntPtr
данные, а не чистые целочисленные
или длинные
типы, поскольку он обеспечивает тонкий слой, защищающий от манипулирования содержимым структуры. Вы можете напрямую манипулировать внутренними компонентами IntPtr
, но для этого либо требуются небезопасные указатели (опять же небезопасные контексты), либо отражение. Наконец, IntPtr автоматически адаптируется к размеру указателя платформы.
«Почему
IntPtr
не хватает арифметики?»
IntPtr
хранит только адрес памяти. У него нет никакой информации о содержимом этой ячейки памяти. Таким образом, он похож на void *
. Чтобы включить арифметику указателей, вы должны знать размер объекта, на который указывает.
По сути, IntPtr
в первую очередь предназначен для использования в управляемых контекстах в качестве непрозрачного дескриптора (т. Е. Такого дескриптора, который вы не разыменовываете напрямую в управляемом коде и просто оставляете его для перехода к неуправляемому коду). небезопасный
контекст предоставляет указатели, которыми вы можете управлять напрямую.