我正在尝试从托管程序集调用本机函数。我已经在预编译的库上完成了这一切,一切都进展顺利。此刻我正在建立自己的图书馆,我无法完成这项工作。
本机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 (当它没有退出,没有例外)。
实际问题是函数实现:它包含带有SSE指令的 asm 块;如果我删除 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 之后,堆栈会更改结果值(存储在堆栈中),并存储 xmm0 的值存储在结果中的修改(和错误)地址。
从* Matrix4x4_Multiply_SSE *退出后,原始内存未被修改。
我缺少什么?
答案 0 :(得分:2)
对齐校正错误。您需要添加alignment-misalignment
来更正对齐方式。所以代码应该是:
mAlignedBuffer =
new IntPtr(mUnmanagedBuffer.ToInt64() + alignment - misalignment);
但是,我建议您先在本机设置中测试该功能。一旦你知道它在那里工作,你就可以转移到托管设置,并知道任何问题都是由托管代码引起的。
答案 1 :(得分:1)
你的装配是有缺陷的。
之间存在差异void DoSomething(int *x)
{
__asm
{
mov x[0], 10 // wrong
mov [x], 10 // also wrong
mov esi,x // first get address
mov [esi],500 // then assign - correct
}
}
前两个示例没有写入指向指针的内存位置,而是写入指针本身的存储位置。由于参数来自堆栈,因此您使用movups指令覆盖了堆栈。调用时,您可以在调试器窗口中看到此信息。
int x=0;
DoSomething(&x);
使用mov [x],10你不会将x设置为10但是你会写入你的堆栈。
答案 2 :(得分:0)
我找到了解决方案。在CPU寄存器上加载指针值,并使用寄存器重定向到内存:
mov esi, result;
movups [esi][ 0], xmm0;
使用这些说明可使代码按预期工作。
但问题仍然没有完全解决,因为 movups 指令可以作为内存地址的第一个参数;所以,如果有人知道发生了什么,我很高兴看到最好的答案。