C#/C++混编的情形经常会碰到,下面就来讲一讲一些需要注意的点。废话不多说,Let's get started. (时间有限,暂时没有写完,后续会持续更细。如果有写的不严谨甚至错误的地方欢迎大家指正)

一、C++导出函数的声明

在导出函数的头文件,经常见到如下的模板

#pragma once

下面就来给出解释。

  • #ifdef __cplusplus这条预编译指令。如果项目是C++的项目,则用extern "C" {} 将所有的代码包起来。这样的好处是导出的函数名仍和代码中定义的一致,否则函数名前后会加上一些看起来很奇怪的符号。这是因为C++的函数有重载机制,同一个函数名,可以有多种参数形式,不加extern "C"包起来的话,编译器就会在函数名前后加上一些符号,来具体到这个函数的具体的某一种形式。
  • #ifdef YOURPROJECT_EXPORTS这条预编译指令。这段话的意思就是如果定义了YOURPROJECT_EXPORTS这个宏,则把YOURPROJECT_API这个宏定义成__declspec(dllexport),否则把YOURPROJECT_API定义成__declspec(dllimport)。这个设计是为了开发者和调用者的便利性设计的。因为这个头文件,后来也会拷贝给调用者用,开发者的导出函数的这个项目中定义一个宏YOURPROJECT_EXPORTS,而调用者那边没有这个宏。所以YOURPROJECT_API对于开发者就是__declspec(dllexport),对于调用者就是__declspec(dllimport)。__declspec(dllexport)告诉编译器这是一个需要导出的函数,__declspec(dllimport)告诉编译器这是一个从外部的库文件中导入的函数。
  • #define CALLINGCONVENTION _cdecl 这个宏,就是调用约定,可以根据需要把它定义成__cdecl或者__stdcall或者其他。调用约定关系到函数参数入栈和出栈的清理方式。常见的有__cdecl、_stdcall、_fastcall. 最常用的就是_cdecl、_stdcall. 如果调用方用C#,则推荐用_cdecl. 因为_stdcall也会像没加extern "C" 那样导致导出的函数名和头文件中定义的不一致。(__cdecl许多人记不住,其实它就是C Declaration的缩写)

二、C++导出函数的实现

声明写好了,实现就简单了。参考如下,不解释。

#include 

三、C#导入函数的声明

C#调用之前,先写一个类,里面用来声明从C++导入的函数

class 

说明:

  • 记得添加命名空间using System.Runtime.InteropServices;
  • 函数声明前写上DllImport属性,包含导入的库文件名,调用约定,字符集,入口点。
  • 库文件名可以写相对路径,也可以写绝对路径。
  • 调用约定和C++保持一致,推荐用Cdecl方式,如果强行用Stdcall会直接报错。
  • 字符集可写可不写,一般不会用上。但是如果C++参数中有char*或者wchart*时,就需要根据实际情况填写字符集这个属性。如果参数为wchar_t*时,指定字符集为Unicode, C#的参数使用string即可(当然也可以使用StringBuilder,具体不讨论)。当然也可以不指定字符集,在参数前加上如下声明[MarshalAs(UnmanagedType.LPWStr)]也可。
  • 入口点,就是dll中导出的函数的符号,当且仅当调用约定为__cdecl、并且加了extern "C"包装的情况下,入口点才和函数名相同。

四、参数的传递

  • 基本类型,比如int、float、double这些直接传就好,不再赘述。
  • 字符串类型。举个例子:
//C++:

  • 一般指针,比如void*、自定义类的指针YourClass*、句柄如HANDLE、HWND等。举个例子
//C++:
typedef void* IDicomSCU;
YOURPROJECT_API void CALLING_CONVENTION IDicomSCU_Close(IDicomSCU handle);//C#:
[DllImport(strDllName, CallingConvention = ccDef, CharSet = csDef, EntryPoint = "IDicomSCU_Close")]
public static extern void IDicomSCU_Close(IntPtr handle);
//说明:既然这么多种东西都可以用C#的IntPtr来传递,那在编码时一定需要注意IntPtr具体指向的一个什么东西,切不可混用。

  • 函数指针。这种情形,C++需要一个函数指针,C#传委托即可。举个例子
//C++
typedef bool(*IDriverCommandEvent)(int command, wchar_t *val);
YOURPROJECT_API bool CALLING_CONVENTION IDriver_Open(HWND hWnd, IDriverCommandEvent event);//C#
//先声明一个委托,注意委托的参数中有string类型,我就在前方加上了[MarshalAs(UnmanagedType.LPWStr)]以和C++的wchar_t*对应
//并且声明这个委托的调用约定是Cdecl还是Stdcall,这个很重要,必须和C++端的调用约定保持一致。
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
public delegate bool DriverCommandEventHandler(int command, [MarshalAs(UnmanagedType.LPWStr)] string val);
//导出函数声明
[DllImport(strDllName, CallingConvention = ccDef, CharSet = csDef, EntryPoint = "IDriver_Open")]
[return: MarshalAs(UnmanagedType.I1)]
public static extern bool IDriver_Open(HWND hWnd, DriverCommandEventHandler driverCommandEvent);
//说明:注意C#在传递形参的时候,形参不要是一个临时对象,因为临时对象的引用计数为0后会被GC回收,
//导致C++调用这个函数指针的时候找不到而报错

  • 结构体指针。
  • 参照C++端定义的结构体也在C#端定义同样的结构体,具体参见第六节。然后C#的形参用ref YourStruct类型,C++端用YourStruct*接收。

五、返回值的传递

基本类型基本可以直接声明,这里就把特殊的返回值拿出来提一下

  • bool类型:如果按照常规的写法,返回值总是错误的。我搜索了stackoverflow,发现了这个问题的解决方案,参考链接如下:

C# DllImport with C++ boolean function not returning correctly​stackoverflow.com

因此,声明就应该这样写:

YOURPROJECT_API 

[DllImport(strDllName, CallingConvention = ccDef, CharSet = csDef, EntryPoint ="DoSomething")]

六、复杂的数据类型的定义(结构体)

c# Task.Factory.StartNew 传参数_C#/C++混合编程一二事相关推荐

  1. C# Task.Run 和 Task.Factory.StartNew 区别

    有小伙伴问我,为什么不推荐他使用 Task.Factory.StartNew ,因为 Task.Run 是比较新的方法. 本文告诉大家 Task.Run 和 Task.Factory.StartNew ...

  2. Task.Factory.StartNew 和 Task.Run 到底有什么区别?

    前言 Task.Factory.StartNew 和 Task.Run 都可以创建 Task: Task.Factory.StartNew(() => { Console.WriteLine(& ...

  3. Task.Run vs Task.Factory.StartNew

    Task.Run 和 Task.Factory.StartNew 都可以把一段要执行的代码放到ThreadPool thread中去执行.Task.Factory.StartNew是.Net 4.0中 ...

  4. Task.Run Vs Task.Factory.StartNew z

    在.Net 4中,Task.Factory.StartNew是启动一个新Task的首选方法.它有很多重载方法,使它在具体使用当中可以非常灵活,通过设置可选参数,可以传递任意状态,取消任务继续执行,甚至 ...

  5. 单线程任务 Task.Factory.StartNew 封装

    代码: using log4net; using SunCreate.CombatPlatform.Security; using System; using System.Collections.G ...

  6. Task.Factory.StartNew 和 Task.Factory.FromAsync 有什么区别?

    咨询区 soleiljy 假设我们有一个涉及IO操作的方法 (读取数据库),这个方法支持以同步或者异步的方式执行. 同步方式 IOMethod() 异步方式 BeginIOMethod()  EndI ...

  7. Task.Factory.StartNewTResult 和 Task.RunTResult 到底有什么区别?

    前言 这不是和<Task.Factory.StartNew 和 Task.Run 到底有什么区别?>一样吗,怎么又写一篇? 起先我也是这么觉得的,但实际发现并非如此. 实现代码 查看这 2 ...

  8. pytest接口测试之fixture传参数request

    本文主要介绍了pytest接口测试之fixture传参数request的使用,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 前言 有的测试用例,需要依赖于某些特定的 ...

  9. Java中传参数--值传递和引用传递

    ** Java中传参数–值传递和引用传递 ** 在Java中,传参数分为值传递和引用传递. 在Java中的数据类型分为两大类:一类是引用类型,也叫类类型(除了String以外的所有复合数据类型,包括数 ...

最新文章

  1. 12种主要的Dropout方法:用于DNNs,CNNs,RNNs中的数学和可视化解释
  2. ResNeSt 登顶COCO数据集(目标检测,实例分割,全景分割)
  3. 用Python分析了1w四六级数据,教你如何通过四六级!
  4. Soldier and Badges
  5. 网民关注iPhone、Google、微软和安全
  6. 数据库模型设计PowerDesigner
  7. git传代码到github
  8. 方法参数泛型_无参数泛型方法反模式
  9. 快速学习R语言的经验分享
  10. Volcano plot | 别再问我这为什么是火山图
  11. 解决mac管理员变成普通成员
  12. 第三方框架-纯代码布局:Masonry的简单使用
  13. sublime text3 之 ctags
  14. 多线程之join用法
  15. 如何安装mysql 8.0.12_基于Windows安装MySQL 8.0.12图文教程
  16. 识别和非识别关系之间有什么区别?
  17. 啦啦外卖41.4全开源版 修复版(小程序+后台)
  18. 单片机蓝牙模块与手机蓝牙通信(7)
  19. 自动安装L2tp的脚本
  20. matlab中的kron函数

热门文章

  1. RabbitMQ队列持久化
  2. 关于DubboMain启动的真相
  3. Quartz框架中的Scheduler
  4. (常用API)正则表达式练习和相关的String类方法
  5. spring基于XML的声明式事务控制-配置步骤
  6. 制作模块-制作模块压缩包
  7. 字典-字典的循环遍历
  8. Spring WebApplicationContext
  9. [NOI2017]泳池
  10. 朴素贝叶斯法---朴素贝叶斯法的参数估计