用C++/CLI搭建C++和C#之间的桥梁
一、简单用法
C#和C++是非常相似的两种语言,然而我们却常常将其用于两种不同的地方,C#得益于其简洁的语法和丰富的类库,常用来构建业务系统。C++则具有底层API的访问能力和拔尖的执行效率,往往用于访问底层模块和构建有性能要求的算法。
这两种场景看起来有较大的差异,大多数的时候可以各行其道。但还是有很多时候会出现融合的情况。当我们构建分布式系统的时候,由于RPC机制一般都是语言无关的,我们大可以将其各尽所长,按需划分在最能发挥其长处的位置。然而,一旦我们需要构建融合两者需求的集中式系统的时候,就会头痛无比。
此时,我们可以使用C++/CLI搭建C++和.Net之间的桥梁,C++/CLI是一个比较有意思的两栖模块,它具有如下特点
- 既可以访问.Net类库,也可以访问C++原生类库
- 既可以被.Net程序引用,也可以被C++原生程序引用
使用C++/CLI,我们可以使用C++编写算法,用C#编写界面,也可以使用.Net Framework类库增强C++程序功能,各取所长。关于的优点,园子里有篇文章介绍的比较详细,值得一读:从C++到C++/CLI。
下面我们就以一个简单的例子来演示一下它的用法:
Calculator.h:
#pragma once
namespace CppCliTest
{
public ref class Calculator
{
public:
int Add(int a, int b);
};
}
Calculator.cpp
#include "stdafx.h"
#include "Calculator.h" namespace CppCliTest
{
int Calculator::Add(int a, int b)
{
return a + b;
}
}
main.cpp
#include "stdafx.h"
#include "Calculator.h" using namespace System;
using namespace CppCliTest; int main(array<System::String ^> ^args)
{
Calculator^ calculator = gcnew Calculator();
int result = calculator->Add(, ); Console::WriteLine(L"Result is {0}", result);
return ;
}
从这个例子中,我们可以简单的管中窥豹的看看C++/CLI是在C++的基础上扩充了一套语法,使其具有访问.Net原始的功能,这里用到的有:
- 使用ref class声明CLI引用类型(C#中的class)
- 使用^(例如如这里的String ^)来定义CLI引用类型
- 使用gcnew创建CLI的引用类型
具体的功能我将在后面的文章中再做介绍,MSDN中也有文档详细的介绍了这些语法:https://msdn.microsoft.com/zh-cn/library/ms235289.aspx
虽然C++/CLI同时具有两者的功能,但它使得本就比较复杂的C++语法变得更加复杂了(特别是初期的版本,非常复杂,现在已经简化了不少了),并且长期没有得到VisualStudio这宇宙第一IDE的较好支持(在VS2010的时候还不支持智能提示),是无法与拥有大量语法糖的C#比开发效率的。加上大多数需求场景可以通过分布式系统解决,这些都导致了它一直没有得到太多的关注。但是,微软还是在积极的改进它的,加上C++11的支持,现在已经比之前好用多了,如果用在合适的位置,是绝对能让你的开发如鱼得水的。
二、复杂用法
对于如下C#代码:
System.Object x = new System.Object();
其在C++/CLI中的等价代码如下:
System::Object^ x = gcnew System::Object();
和传统的C++创建的语法比较下,
P* x = new P();
我们不难发现,对于托管对象,主要引入了如下两个语法:
- 用gcnew代替new实现托管对象的创建
- 用^代替*实现托管对象的指针
这种方式创建的对象是可以直接被CLR支持的,可以在C#中使用。托管对象指针使用的方式和传统的对象指针还是比较类似的,直接使用->即可:
System::Object^ x = gcnew System::Object();
auto str = x->ToString();
另外,C++/CLI也有一种类似于C++的对托管对象的引用的语法:
System::Object^ x = gcnew System::Object();
System::Object% y = *x;
auto str = y.ToString();
由于这种方式在C#里没有对应的语法,用起来感觉怪怪的,也不方便于其它.net语言集成。
托管类型的定义
我们也可以自定义托管类型,在CLR中,托管类型是分为引用类型(class)和值类型(struct)的,在C++/CLI中的分别定义方式如下:
引用类型:
public ref class MyClass
{
};
值类型:
public value class MyClass
{
};
在ISO C++中类定义中加上了ref或value标记为托管类型,还算比较容易使用。
枚举
枚举的定义和C++11的enum class一样,它像数字那样可以同时应用于托管类型和非托管类型。
public enum class SomeColors { Red, Yellow, Blue };
或者更精确的表示:
public enum class SomeColors : char { Red, Yellow, Blue };
数组
C++/CLI中新增了array<T> ^的方式定义数组。
array<int> ^a = gcnew array<int>() { , , };
或者使用它的完整版:
cli::array<int> ^a = gcnew cli::array<int> {, , };
不定参数
对于C#中的不定参数的语法:
void foo(params string[] args)
在C++/CLI中对应的版本为:
void foo(... array<String^>^ args)
三、基本类型
数值类型
对于基本的数值类型,在C++/CLI中是可以直接映射为托管类型的数值的,可以同时应用于托管类型和非托管类型,编译器会将其自动转换。
|
基本类型 |
System命名空间中对应的类 |
注释/用法 |
|
bool |
System::Boolean |
bool dirty = false; |
|
char |
System::SByte |
char sp = ' '; |
|
signed char |
System::SByte |
signed char ch = -1; |
|
unsigned char |
System::Byte |
unsigned char ch = '\0'; |
|
wchar_t |
System::Char |
wchar_t wch = ch; |
|
short |
System::Int16 |
short s = ch; |
|
unsigned short |
System::UInt16 |
unsigned short s = 0xffff; |
|
int |
System::Int32 |
int ival = s; |
|
unsigned int |
System::UInt32 |
unsigned int ui = 0xffffffff; |
|
long |
System::Int32 |
long lval = ival; |
|
unsigned long |
System::UInt32 |
unsigned long ul = ui; |
|
long long |
System::Int64 |
long long etime = ui; |
|
unsigned long long |
System::UInt64 |
unsigned long long mtime = etime; |
|
float |
System::Single |
float f = 3.14f; |
|
double |
System::Double |
double d = 3.14159; |
|
long double |
System::Double |
long double d = 3.14159L; |
字符串
字符串CLI已经内置了:System::String,但C++的常用字符串有char*、wchar_t*、std::string等好多种,编译器提供了char*、wchar_t*到System::String的自动转换:
System::String^ s = "hello worold";
System::String^ s2 = L"hello worold";
另外,也可以使用gcnew创建托管字符串:
System::String^ s = gcnew String("hello worold");
但是,对于System::String转char*,系统没有直接的语法支持。方法有很多种,我通常使用如下方式来转换:
IntPtr ip = Marshal::StringToHGlobalAnsi(str);
const char* ch = static_cast<const char*>(ip.ToPointer());
//do something with ch
Marshal::FreeHGlobal(ip);
这里有个需要注意的地方是在使用完转换出来的const char*后需要释放掉转换过程中的Intptr,如果没有太多需要考虑性能的地方,大可以使用一个std::string将其拷贝走,写成如下函数形式:
#include <string>
using namespace std;
using namespace System;
using namespace System::Runtime::InteropServices;
string cast_to_string(String^ str)
{
IntPtr ip = Marshal::StringToHGlobalAnsi(str);
const char* ch = static_cast<const char*>(ip.ToPointer());
string stdStr = ch;
Marshal::FreeHGlobal(ip);
return stdStr;
}
四、网络资源
关于C++/CLI的基础,我前面已经写过了几篇文章介绍过一些了,不过这些基本上都是管中窥豹,如果要详细了解C++/CLI,MSDN无疑是最好的教程。
如果需要在MFC中使用.net控件的话,可以参考如下三篇文章:
此外,MSDN文章如何实现 C++ 互操作 上有更加详细的文章索引。后面有空的话,我会继续继写一些相关的介绍文章的。
用C++/CLI搭建C++和C#之间的桥梁的更多相关文章
- 用C++/CLI搭建C++和C#之间的桥梁(一)—— 简介
C#和C++是非常相似的两种语言,然而我们却常常将其用于两种不同的地方,C#得益于其简洁的语法和丰富的类库,常用来构建业务系统.C++则具有底层API的访问能力和拔尖的执行效率,往往用于访问底层模块和 ...
- 用C++/CLI搭建C++和C#之间的桥梁(四)—— 网络资源
关于C++/CLI的基础,我前面已经写过了几篇文章介绍过一些了,不过这些基本上都是管中窥豹,如果要详细了解C++/CLI,MSDN无疑是最好的教程. 使用 C++ 互操作(隐式 PInvoke) Vi ...
- 用C++/CLI搭建C++和C#之间的桥梁(三)—— 基本类型
数值类型 对于基本的数值类型,在C++/CLI中是可以直接映射为托管类型的数值的,可以同时应用于托管类型和非托管类型,编译器会将其自动转换. 基本类型 System命名空间中对应的类 注释/用法 bo ...
- 用C++/CLI搭建C++和C#之间的桥梁(二)—— 基本语法
托管对象的创建和引用 在前文中我们已经演示过创建一个托管对象,对于如下C#代码: System.Object x = new System.Object(); 其在C++/CLI中的等价代码如下: S ...
- vue cli搭建项目及文件引入
cli搭建方法:需安装nodejs先 1.npm install -g cnpm --registry=https://registry.npm.taobao.org //安装cnpm,用cnpm下载 ...
- [原创].NET 分布式架构开发实战之四 构建从理想和实现之间的桥梁(前篇)
原文:[原创].NET 分布式架构开发实战之四 构建从理想和实现之间的桥梁(前篇) .NET 分布式架构开发实战之四 构建从理想和实现之间的桥梁(前篇) 前言:上一篇文章讲述了一些实现DAL的理论,本 ...
- gcc 头文件是用户应用程序和函数库之间的桥梁和纽带 功能的真正逻辑实现是以硬件层为基础
gcc GCC, the GNU Compiler Collection - GNU Project - Free Software Foundation (FSF) http://gcc.gnu.o ...
- ROS Learning-016 Arduino-For-ROS-001 搭建 Arduino 和 ROS 之间相连接的开发环境
Arduino For ROS-001 - 搭建 ROS 和 Arduino 相连接的开发环境 我的Ubuntu系统:Ubuntu 14.04.10 TLS 32位 Arduino的版本:Arduin ...
- 使用@vue/cli搭建vue项目开发环境
当前系统版本 mac OS 10.14.2 1.安装node.js开发环境 前端开发框架和环境都是需要 Node.js vue的运行是要依赖于node的npm的管理工具来实现 <mac OS ...
随机推荐
- 13、Actor
1.介绍 Scala的Actor类似于Java中的多线程编程.但是不同的是,Scala的Actor提供的模型与多线程有所不同. Scala的Actor尽可能地避免锁和共享状态,从而避免多线程并发时出现 ...
- 【NLP】中文分词:原理及分词算法
一.中文分词 词是最小的能够独立活动的有意义的语言成分,英文单词之间是以空格作为自然分界符的,而汉语是以字为基本的书写单位,词语之间没有明显的区分标记,因此,中文词语分析是中文信息处理的基础与关键. ...
- hdu1053
#include<iostream> #include<algorithm> using namespace std; bool cmp(int a,int b) { retu ...
- 【原创】谈谈redis的热key问题如何解决
引言 讲了几天的数据库系列的文章,大家一定看烦了,其实还没讲完...(以下省略一万字). 今天我们换换口味,来写redis方面的内容,谈谈热key问题如何解决. 其实热key问题说来也很简单,就是瞬间 ...
- 【Java】NIO中Selector的创建源码分析
在使用Selector时首先需要通过静态方法open创建Selector对象 public static Selector open() throws IOException { return Sel ...
- Elasticsearch+Logstash+Kibana搭建分布式日志平台
一.前言 编译安装 1.ELK简介 下载相关安装包地址:https://www.elastic.co/cn/downloads ELK是Elasticsearch+Logstash+Kibana的简称 ...
- Java SE自学阶段的笔记整理
其他知识点 1.String和Char的区别: (1)String是字符串类型,Char是字符类型: (2)Char要用单引号,String要用双引号: (3)String是一个类,具有面向对象的特性 ...
- Solr 6.7学习笔记(02)-- 配置文件 managed-schema (schema.xml)(1)
刚学Solr(版本6.7.0),新建一个core时,提示要求schema.xml文件,我找了半天也没在源码包中找到名为schema.xml的文件.这个版本其实用的是managed-schema文件,没 ...
- jmeter - 函数:Random 随机函数的使用
场景:在做接口测试时,比如说要求用户的手机号码不允许重复,那此时可以通过Random 随机函数来解决此问题: 1.在JMeter 工具中,选择{选项-函数助手对话框-} 函数助手中选择 Random ...
- Error:依赖版本不一致
在编译unity时发现一个问题:依赖版本不一致 解决方案: 通过nugut管理在已安装项里选中要更新的dll文件