性能提升:使用c#调用c++(核心代码优化)
一、情况描述
现在我接触到的生产环境中,使用C#做前端界面,开发效率很高,和负责的界面,使用很短的时间就完成了,程序中的核心算法,使用C++写,运算快,这样能够使整个开发周期缩短,界面上比较好看,运行效率问题也能解决。
在使用C#和C++结合的过程中就要涉及到C#调用C++dll的问题、向C++函数中传入参数、向C++传入C#的函数、C++返回参数的问题,下面我记录下我使用C#调用C++dll并将C#的函数传入C++中,遇到的主要问题以及几条注意事项;
二、开发环境
VS版本:vs2015
操作系统版本:win10
三、主要问题
typedef void (__stdcall *CallFunc)(char* info);
CallFunc csharpCallFunc;
extern "C" CALLCTEST_API void RegisterFunc(CallFunc callback);
extern "C" CALLCTEST_API void Run();
C++的cpp文件:
CALLCTEST_API void RegisterFunc(CallFunc callback) {
csharpCallFunc = callback;
};
CALLCTEST_API void Run()
{
while (true)
{
SYSTEMTIME localtime;
GetLocalTime(&localtime);
char *time = new char[20]();
sprintf_s(time, 20, "%02d-%02d-%02d %02d:%02d:%02d", localtime.wYear, localtime.wMonth, localtime.wDay, localtime.wHour, localtime.wMinute, localtime.wSecond);
csharpCallFunc(time);
Sleep(1000);
delete time;
}
};
C#通过DllImport导入C++函数:
public delegate void CallHandler(string info);
public static class CallCFunc
{
[ ]
public static extern void RegisterFunc(CallHandler call);
[ ]
public static extern void Run();
}
上面中C#委托CallHandler的参数类型是string,这个在C++中可以使用C++中的char *进行对应;
C#调用C++函数传入C#委托:
CallCFunc.RegisterFunc(WriteLog);
四、主要注意事项:
1.在C#向C++中传入参数时(CallCFunc.RegisterFunc(callwritelog);)一定要注意,使用属性,将函数保存到一个委托类型的属性中,否则(就像我上面初始化代码中的一种写法CallCFunc.RegisterFunc(WriteLog);)会出现以下错误:
0x00000000 处(位于 CalltestForm.exe 中)引发的异常: 0xC0000005: 执行位置 0x00000000 时发生访问冲突。
如有适用于此异常的处理程序,该程序便可安全地继续运行。
或者:
对“MotionCapture!MotionCapture.EKFRenderCallback::Invoke”类型的已垃圾回收委托进行了回调。
这可能会导致应用程序崩溃、损坏和数据丢失。向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们。
2.在C++的函数指针中使用__stdcall标记,如果不使用这个标记会出现:
Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call.
This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.
下面是我从网络上找到的__cdecl和__stdcal的解释,觉得很有用:
(1)__cdecl
即所谓的C调用规则,按从右至左的顺序压参数入栈,由调用者把参数弹出栈。切记:对于传送参数的内存栈是由调用者来维护的。返回值在EAX中。因此,对于象printf这样变参数的函数必须用这种规则。编译器在编译的时候对这种调用规则的函数生成修饰名的时候,仅在输出函数名前加上一个下划线前缀,格式为_functionname。
(2)__stdcall
按从右至左的顺序压参数入栈,由被调用者把参数弹出栈。_stdcall是Pascal程序的缺省调用方式,通常用于Win32 Api中,切记:函数自己在退出时清空堆栈,返回值在EAX中。 __stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个“@”符号和其参数的字节数,格式为_functionname@number。如函数int func(int a, double b)的修饰名是_func@12
所以,从C++ dll中回调函数给C#传递数据,必须由C#函数在使用完数据后(退出函数时)自己清空堆栈!
3.如果是数组,必须用 [MarshalAs(UnmanagedType.LPArray, SizeConst = 23)]标记参数,指定为数组且标记数组长度
上面测试代码我已经放到网盘中了,有兴趣的同学可以自己测试一下!
代码:https://pan.baidu.com/s/1Xwvh8OSg_q240mS1vC9X3w 提取密码:otfy