我试图从托管程序集中调用本地函数。在预编译库上我做到了这一点,一切都很顺利。但目前我正在构建自己的库,却无法让它正常工作。
本地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之后,堆栈会更改result值(存储在堆栈上),并将xmm0的值存储在result中修改的(错误的)地址上。
从 *Matrix4x4_Multiply_SSE* 中退出后,原始内存不会被修改。
我错过了什么?