效率提升:C#向C++传递函数问题

一、情况描述

        现在我接触到的生产环境中,使用C#做前端界面,开发效率很高,和负责的界面,使用很短的时间就完成了,程序中的核心算法,使用C++写,运算快,这样能够使整个开发周期缩短,界面上比较好看,运行效率问题也能解决。

        在使用C#和C++结合的过程中就要涉及到C#调用C++dll的问题、向C++函数中传入参数、向C++传入C#的函数、C++返回参数的问题,下面我记录下我使用C#调用C++dll并将C#的函数传入C++中,遇到的主要问题以及几条注意事项;

二、开发环境

        VS版本:vs2015

        操作系统版本:win10

三、主要问题

       下面我通过一个小demo记录主要遇到的问题,这个demo使用C#的winform做了一个时钟,界面如下:

界面中的时间值是通过C++调用C#函数,传入到winform界面中;

 

        C#向C++传递函数主要是将C#中的委托传递到C++中,在C++中通过函数指针进行接收;

        C++头文件代码:

#ifdef CALLCTEST_EXPORTS
#define CALLCTEST_API __declspec(dllexport)
#else
#define CALLCTEST_API __declspec(dllimport)
#endif

typedef void (__stdcall *CallFunc)(char* info);

CallFunc csharpCallFunc;

extern "C" CALLCTEST_API void RegisterFunc(CallFunc callback);

extern "C" CALLCTEST_API void Run();

 C++的cpp文件:

#include "stdafx.h"
#include "callcTest.h"
#include "windows.h"
#include "stdio.h"


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
    {
        [DllImport("callcTest.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern void RegisterFunc(CallHandler call);
        [DllImport("callcTest.dll", CallingConvention = CallingConvention.Cdecl)]
        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”类型的已垃圾回收委托进行了回调。这可能会导致应用程序崩溃、损坏和数据丢失。向非托管代码传递委托时,托管应用程序必须让这些委托保持活动状态,直到确信不会再次调用它们。

 这个问题出现的主要原因是因为,在使用C#向C++传入函数时,C++记录了函数地址,但是,当C#的GC开始工作时,认为WriteLog函数并没有被引用,因此GC将回收函数地址,造成了C++调用C#传入的函数时出现地址冲突,或者调用异常;解决办法就是通过属性将函数地址记录下来,因为属性为主窗体属性(在form类中),所以只有当窗体关闭时,才会被GC回收;

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C#中,可以使用extern关键字来声明调用C++的void函数。具体步骤如下: 1. 首先,需要在C#代码中引入System.Runtime.InteropServices命名空间,以便使用DllImport特性。 2. 在C#中声明extern函数,使用DllImport特性指定C++的dll文件名和函数名。例如,如果C++的dll文件名为"TEST_DLL",函数名为"init",则可以使用以下代码声明extern函数: \[System.Runtime.InteropServices.DllImport("TEST_DLL")\] public static extern void init(int a, float b, bool c); 3. 在C#代码中调用extern函数即可。例如,可以使用以下代码调用init函数: init(10, 3.14f, true); 这样,C#就可以调用C++的void函数了。需要注意的是,确保C++的dll文件与C#代码在同一目录下,或者将其路径添加到系统环境变量中。另外,确保函数的参数类型和顺序与C++中的函数定义一致。 #### 引用[.reference_title] - *1* [效率提升C#C++传递函数问题](https://blog.csdn.net/xiazhipeng1000/article/details/89293311)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [C#调用C++ dll函数传参及参数类型转换](https://blog.csdn.net/qq_27278957/article/details/120016750)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值