Я пытаюсь вызвать нативную функцию из управляемой сборки. Я сделал это на предварительно скомпилированных библиотеках, и все прошло хорошо. В данный момент я строю свою собственную библиотеку, и я не могу получить эту работу.
Исходный источник DLL следующий:
#define DERM_SIMD_EXPORT __declspec(dllexport)
#define DERM_SIMD_API __cdecl
extern "C" {
DERM_SIMD_EXPORT void DERM_SIMD_API Matrix4x4_Multiply_SSE(float *result, float *left, float *right);
}
void DERM_SIMD_API Matrix4x4_Multiply_SSE(float *result, float *left, float *right) {
__asm {
....
}
}
Здесь и далее мы имеем управляемый код, который загружает библиотеку и создает делегата из указателя функции.
public unsafe class Simd
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void MatrixMultiplyDelegate(float* result, float* left, float* right);
public static MatrixMultiplyDelegate MatrixMultiply;
public static void LoadSimdExtensions()
{
string assemblyPath = "Derm.Simd.dll";
IntPtr address = GetProcAddress.GetAddress(assemblyPath, "Matrix4x4_Multiply_SSE");
if (address != IntPtr.Zero) {
MatrixMultiply = (MatrixMultiplyDelegate)Marshal.GetDelegateForFunctionPointer(address, typeof(MatrixMultiplyDelegate));
}
}
}
Используя приведенные выше источники, код работает без ошибок (указатель на функцию получен, а делегат фактически создан.
Проблема возникает, когда я вызываю делегата: он выполняется (и я также могу отладить его!), Но при выходе из функции управляемое приложение вызывает System.ExecutionEngineException (когда оно не выходит без исключений).
Фактическая проблема заключается в реализации функции: она содержит asm блок с инструкциями SSE; если я удаляю блок asm , код работает отлично.
Я подозреваю, что мне не хватает некоторой сборки для сохранения / восстановления реестра, но я совершенно не осведомлен с этой стороны.
Странно то, что если я изменю соглашение о вызовах на __stdcall, отладочная версия, похоже, будет работать, а выпускная версия будет вести себя так, как если бы использовалась __cdecl.
(И только потому, что мы здесь, вы можете уточнить, имеет ли значение конвекция вызова?)
Хорошо, спасибо за комментарий David Heffernan Я обнаружил, что плохие инструкции, вызывающие проблему, следующие:
movups result[ 0], xmm4;
movups result[16], xmm5;
movups инструкции перемещают 16 байтов в (не выровненную) память.
Функция вызывается с помощью следующего кода:
unsafe {
float* prodFix = (float*)prod.MatrixBuffer.AlignedBuffer.ToPointer();
float* m1Fix = (float*)m2.MatrixBuffer.AlignedBuffer.ToPointer();
float* m2Fix = (float*)m1.MatrixBuffer.AlignedBuffer.ToPointer();
if (Simd.Simd.MatrixMultiply == null) {
// ... unsafe C# code
} else {
Simd.Simd.MatrixMultiply(prodFix, m1Fix, m2Fix);
}
}
Где MatrixBuffer - мой класс; его член AlignedBuffer распределяется следующим образом:
// Allocate unmanaged buffer
mUnmanagedBuffer = Marshal.AllocHGlobal(new IntPtr((long)(size + alignment - 1)));
// Align buffer pointer
long misalignment = mUnmanagedBuffer.ToInt64() % alignment;
if (misalignment != 0)
mAlignedBuffer = new IntPtr(mUnmanagedBuffer.ToInt64() + misalignment);
else
mAlignedBuffer = mUnmanagedBuffer;
Может быть, ошибка вызвана Marshal.AllocHGlobal или IntPtr черная магия?
Это минимальный источник для определения ошибки:
void Matrix4x4_Multiply_SSE(float *result, float *left, float *right)
{
__asm {
movups xmm0, right[ 0];
movups result, xmm0;
}
}
int main(int argc, char *argv[])
{
float r0[16];
float m1[16], m2[16];
m1[ 0] = 1.0f; m1[ 4] = 0.0f; m1[ 8] = 0.0f; m1[12] = 0.0f;
m1[ 1] = 0.0f; m1[ 5] = 1.0f; m1[ 9] = 0.0f; m1[13] = 0.0f;
m1[ 2] = 0.0f; m1[ 6] = 0.0f; m1[10] = 1.0f; m1[14] = 0.0f;
m1[ 3] = 0.0f; m1[ 7] = 0.0f; m1[11] = 0.0f; m1[15] = 1.0f;
m2[ 0] = 1.0f; m2[ 4] = 0.0f; m2[ 8] = 0.0f; m2[12] = 0.0f;
m2[ 1] = 0.0f; m2[ 5] = 1.0f; m2[ 9] = 0.0f; m2[13] = 0.0f;
m2[ 2] = 0.0f; m2[ 6] = 0.0f; m2[10] = 1.0f; m2[14] = 0.0f;
m2[ 3] = 0.0f; m2[ 7] = 0.0f; m2[11] = 0.0f; m2[15] = 1.0f;
r0[ 0] = 0.0f; r0[ 4] = 0.0f; r0[ 8] = 0.0f; r0[12] = 0.0f;
r0[ 1] = 0.0f; r0[ 5] = 0.0f; r0[ 9] = 0.0f; r0[13] = 0.0f;
r0[ 2] = 0.0f; r0[ 6] = 0.0f; r0[10] = 0.0f; r0[14] = 0.0f;
r0[ 3] = 0.0f; r0[ 7] = 0.0f; r0[11] = 0.0f; r0[15] = 0.0f;
Matrix4x4_Multiply_SSE(r0, m1, m2);
Matrix4x4_Multiply_SSE(r0, m1, m2);
return (0);
}
Практически после второго movups стек изменяет значение result (хранится в стеке) и сохраняет значения xmm0 в измененном ( и неверно) адрес хранится в результате .
После выхода из * Matrix4x4_Multiply_SSE * исходная память не изменяется.
Чего мне не хватает?