У меня есть четыре варианта: два с использованием только «безопасного» кода и два с использованием небезопасного кода. Небезопасные варианты, вероятно, будут значительно быстрее.
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 . Если нет, то безопасные процедуры - ваш единственный выбор.