C++/CLI基本语法和最佳实践
详细剖析C+ +/CLI中的六大核心类型,总结C+ +/CLI实现跨语言调用既有库的最佳实践。
定位
C+ +/CLI是C+ +语言的一个变种,不仅支持原生C+ +的语法特性,也支持托管代码特性,这样就达到了在C+ +中调用C#库的目的。
结合原生C+ +的性能和C#的强大生态,C+ +/CLI可以高效开发独立运行的程序(控制台程序、窗体程序等),可惜的是,这并没有成为主流(大概是因为C+ +和C#两者的应用场景过于分明,实际并不需要大规模混合使用)!C+ +/CLI作为连接原生C+ +和C#的桥梁,通常被用来实现原生C+ +和C#既有程序库的跨语言调用。
所以,本文聚焦在C+ +/CLI的核心语法特性上,不会对其侧重于独立软件开发的语法特性进行过多介绍。
基本语法
“隙中窥月”
1 | // 使用原生C+ +标准库 |
以上代码中:
(1)通过命名空间可引入C#库。
(2)定义托管类CLIPointClass,使用关键字ref class;实例化时使用gcnew表示创建托管类型,类型带上符号^。
(3)在主函数中可混合使用C+ +和C#的数据类型。
六种类和结构体类型详解
在C+ +/CLI中可以使用6种基本类和结构体类型:
- struct / class
- value struct / value class
- ref struct / ref class
C#中struct / class的区别
(1)struct是值类型,class是引用类型。
(2)默认访问权限不一样,struct是public,class是private。
(3)struct不能有空参数的构造函数。
C+ +/CLI中struct / class的区别
与原生C+ +的结构体和类对应。
(1)默认访问权限不一样,struct是public,class是private。
(2)在C+ +11之后,struct也支持定义成员函数和继承。
(3)两者使用界限逐渐模糊,但是还是倾向于将struct视为一个数据容器。
C+ +/CLI中value struct / value class的区别
两者都是托管类型,基本等同;两者都不能有空参数的构造函数(与C#的struct特性对应);等同于C#中的struct。
区别:默认访问权限不一样,struct是public,class是private。
C+ +/CLI中ref struct / ref class的区别
两者都是托管类型,基本等同;等同于C#中的class。
区别:默认访问权限不一样,struct是public,class是private。
六种类型的实例化、作为函数参数传递的方式
(1)struct / class
struct和class的表现一样。同原生C+ +的struct / class,可声明到栈或堆上:
1 | // 声明到栈上 |
作为参数时,可以值传参、引用传参、指针传参,后两者可以对实例进行修改:
1 | // 值传参 |
如果想修改指针本身的值,需要采用指针的引用或指针的指针来传参:
1 | // 指针的引用传参 |
(2)value struct / value class
value struct和value class的表现一样。可以声明为值类型,也可以声明为托管类型:
1 | // 声明为值类型 |
同struct / class一样,可以使用值传参、引用传参、指针传参,引用传参时符号&可以写成%:
1 | // 值传参 |
若接收托管类型参数,可以采用托管指针传参、托管指针引用传参,后者可以修改指针本身的值(类似C#中的ref参数):
1 | // 托管指针传参 |
(3)ref struct / ref class
ref struct和ref class的表现一样。只能声明为托管类型:
1 | CLIRefPointClass^ p1 = gcnew CLIRefPointClass(11, 12); |
可以采用托管指针传参、托管指针引用传参,后者可以修改指针本身的值(类似C#中的ref参数):
1 | // 托管指针传参 |
六种类型中允许使用的数据类型
(1)struct / class
- 无法定义托管类型的字段。
- 方法的参数和返回值可以为托管类型。
(2)托管类型(value struct / value class 和 ref struct / ref class)
- 不能定义非托管类型的字段。
- 可以定义非托管类型的C+ +普通指针类型。
- 可以使用C+ +的基本数据类型,因为在这些类型可以和C#数据类型自动转换。
- 托管类型的方法参数和返回值可以为非托管类型,但是无法传参。
C+ +/CLI的其他托管特性
C+ +/CLI的其他诸多特性,例如enum class、interface class、property、委托、事件等,都是为扩展C+ +语言以支持更多的托管特性,如果采用C+ +/CLI来开发独立程序,使用的价值会比较大。
最佳实践
六种类型在原生C+ +或C#项目中呈现什么样子?
当使用C+ +/CLI开发独立应用程序时,可以在程序逻辑中混用C+ +和C#的数据类型。
但是如果使用C+ +/CLI封装成DLL供原生C+ +或C#项目使用,这六种类型会呈现什么样子呢?
原生C+ +项目的角度
站在原生C+ +项目的角度看六种类型,可以通过简单分析得到结论:原生C+ +项目要想引用C+ +/CLI的DLL,必须包含其头文件(指明了DLL导出的数据类型),如果这个头文件中包含任何托管代码特性,那么在原生C+ +项目中就会报错(理由很明晰:原生C+ +项目不支持托管代码特性)。
所以,使用C+ +/CLI封装程序库给原生C+ +项目使用时,头文件中不应该包含任何托管代码特性;但是在实现导出类成员方法或导出函数的具体逻辑时,可以使用托管代码。
C#项目的角度
站在C#项目的角度看六种类型,可通过代码实验得到结论:
- C+ +/CLI中的struct / class不可见,需在前面加上关键字public。在C#项目中,变成struct类型,其中的字段和方法不可访问;
- C+ +/CLI中的value struct / value class在C#项目中是struct类型。
- C+ +/CLI中的ref struct / ref class在C#项目中是class类型。
- C+ +/CLI中的四种托管类型中,可以声明非托管类型的指针成员变量,也可以定义参数和返回值均为非托管类型的方法,但是这些变量和方法无法在C#项目中直接被使用。
小结
六种类型 | 原生C+ +项目里 | C#项目里 |
---|---|---|
struct | struct | — |
class | class | — |
value struct | — | struct |
value class | — | struct |
ref struct | — | class |
ref class | — | class |
C+ +/CLI实现跨语言调用既有库的最佳实践
基于以上各种结论,在使用C+ +/CLI封装程序库给原生C+ +或C#项目使用时,注意如下:
(1)封装程序库供C+ +使用:
- 在C+ +/CLI的头文件中不能包含任何托管代码特性。
- 封装C#代码给原生C+ +项目使用:在C+ +/CLI的导出类方法成员或导出函数内部使用C#的静态方法,或实例化C#类,从而使用既有C#的代码。
(2)封装程序库供C#使用:
- 凡是要暴露给C#项目的接口,统一使用托管类型。
- 托管类型的指针成员变量、参数或返回值为非托管类型的方法,只能充当函数逻辑实现的辅助,无法暴露给C#项目(避免出错,最好不要使用!)
- 封装原生C+ +代码给C#项目使用:可以定义一个C+ +类指针成员变量,作为原生C+ +代码的统一访问入口;(推荐方法)
- 封装原生C+ +代码给C#项目使用:可在C+ +/CLI的方法内部使用原生C+ +代码中的静态方法,或实例化原生C+ +代码里面的类,从而使用既有原生C+ +的代码。
以上就是使用C+ +/CLI实现程序库跨语言调用的最佳实践,后面两篇文章将以示例代码来展开说明。