一般,当咱们开发Linux程序时有两种方案:html
虽然我本身是在Linux环境上直接进行开发的,但也有许多的人是在Windows环境上从事开发工做的,若是离开本身熟悉的系统到陌生的环境上也许会影响到工做效率。linux
所以今天咱们就来看下如何在Windows上使用Visual Studio 2019进行Linux远程开发以及如何避免常见的陷阱。ios
本文索引
从visual studio 2017开始微软推出了vs的跨平台开发功能,你能够在vs中编辑代码,随后进行跨平台编译和远程调试,将原先咱们须要手动完成的工做进行了自动化,大幅减轻了咱们的负担。其中支持的平台包括Android和Linux,也就是咱们今天要重点介绍的主角。c++
也许你会好奇,vs到底是怎样进行远程开发的,虽然你不用了解这些知识也能够进行开发,但我仍是但愿能用两分钟作个简短的解释。缓存
vs进行远程开发分为两步:安全
通过上述步骤以后你就能够在vs里调试本身编写的跨平台程序了。架构
简介到此结束了,下面咱们来看看在vs2019进行Linux开发的图文教程。在咱们开始以前,首先要作点准备工做:ssh
作好准备后咱们就该进入正题了。函数
安装好c++ for Linux功能后咱们会在建立新项目的面板中看到Linux的选项,如图:工具
这里咱们选择了使用传统的vs项目解决方案构建的空白控制台程序,后续的文章中你还能够看到如何建立cmake项目,这里暂且不提。
下面没什么要说的,选择项目的存储位置,注意是本地的位置,远程机器的位置在后面会进行配置:
点击建立,咱们的远程开发项目就建立成功了。
vs不能编辑空项目的配置,因此咱们先在项目中建立一个main.cpp
,而后点击顶部菜单:项目->属性,你就能看到项目的配置界面了:
远程计算机是在调试中的远程链接管理器中添加的。这里通常不须要改动,除非你须要改变项目的类型或编译结果的存放位置。若是有多个远程环境时,也能够在这里进行选择。
调试部分提供了gdb
和gdbserver
,前者是让vs在Linux上启动一个console,而后在其中运行gdb并返回输出,若是你的Linux上的终端配置了彩色输出,那么和遗憾vs并不认识他们,会显示成原始的字符串;使用gdbserver时会在远程启用gdbserver,本地vs解析回传的数据不会出现杂音。这里咱们选择了gdbserver,若是你发现没法打断点,那么参考微软的建议,换回gdb方案:
接着是配置的重点,首先是配置须要同步的远程环境的头文件,有了这些文件vs才能对你的代码进行自动补全和提示:
默认复制的路径一般已经包含了Linux上大部分的头文件,一般咱们也不须要作更改。头文件的同步发生在第一次构建项目成功后或添加远程链接后手动同步。
接着是c/c++编译器的选择,也就是对gcc和g++编译参数的配置,讲解这些参数超出了咱们的讨论范围,咱们这里只须要选择合适的c++标准版本:
这里咱们选择了c++17。其余设置与在Windows上进行开发时同样,vs能够自动转换成g++的参数,这里就再也不赘述。
有了远程环境咱们才能同步头文件或者进行调试运行。
在第一次编译或调试你的项目时vs会自动让你链接远程环境,固然,咱们推荐在调试->选项->跨平台->链接管理器中进行设置:
填入你的远程ip/域名,端口ssh默认为22,安全起见你须要修改为其余端口,这里方便演示使用了默认配置,密码同上,你应该考虑使用更安全的ssh私钥登陆。
登陆成功后这个链接就添加完成了,咱们看到管理器下面还有一个远程标头管理器的设置项,这就是用来同步头文件的:
点击更新按钮就会开始同步头文件,这些文件会被缓存在本地,由于要从远程一次性复制大量文件,因此可能会花费较长的时间。
这样远程环境就添加好了,能够开始写代码了。
至此你已经能够在vs中编写面向Linux平台的代码了,自动补全能够正常工做:
能够看到Linux中的头文件和结构体都已经能够识别了。若是你发现没法自动补全(一般发生在刚添加远程链接或是项目设置发生了变化后),先试试关闭vs从新打开,若是没用请尝试刷新intellisense或从新同步头文件。
在编辑结束后咱们就能点击调试按钮运行咱们的程序了:
注意,构建的体系架构必须是和远程环境一致的,好比远程环境是x64,这里能够选择x64或x86,可是不能选择arm,不然会报错。
这是测试代码,它将输出当前Linux系统内核的版本:
#include <sys/utsname.h> #include <iostream> #include <cstdio> int main() { auto start = chrono::high_resolution_clock::now(); utsname names; if (uname(&names) != 0) { std::perror("cannot get unames"); } std::cout << "Linux kernel version: " << names.release << std::endl; }
点击调试->Linux 控制台,会显示一个能够交互的console,你能够在其中输入内容或是看到程序的输出:
程序运行成功。
远程编译顺利完成后,咱们就能够接着利用vs debugger设置断点,在断点处查看变量,甚至对运行中的Linux进行动态性能分析了。
不过在此以前,还有一些坑须要提早踩掉。
编码问题带来的麻烦永远会被放在第一位,毕竟当人们看到预想的输出其实是一堆乱码时总会不可避省得紧张起来。
众所周知,编码问题一直是老大难,特别是Windows上中文环境一般是GB18030或GBK,而Linux上统一为utf8时。
下面看个实际例子,一般咱们的程序里只包含ASCII字符的话不容易产生问题,因此咱们加上一点中文字符:
#include <sys/utsname.h> #include <iostream> #include <cstdio> #include <string> int main() { utsname names; if (uname(&names) != 0) { std::perror("cannot get unames"); } std::cout << "Linux kernel version: " << names.release << std::endl; std::cout << "输入内容:"; std::string input; std::cin >> input; std::cout << "你输入了:" << input << std::endl; }
对于上面的测试程序,咱们添加了一点中文输出信息,如今打开控制台进行调试:
能够看到中文输出变成了乱码,咱们输入一些信息进去,这是运行结果:
能够看到,程序内写入的中文发生了乱码,而咱们的输入没有。缘由很简单,输入时实在linux的控制台环境下,编码默认是utf8的,因此咱们的输入被正确编码,而源文件中的内容是GB18030的,因此在Linux控制台(默认以utf8解码数据并显示)中会发生乱码。
错误的缘由知道了解决起来也就很简单了,把源文件的编码改为utf8就行,咱们选择最简单的方法,在高级保存选项
中修改编码(这个菜单选项默认被隐藏,网上有不少介绍如何显示它的方法的资料):
设置好后保存文件,如今文件的编码已经被改成了utf8了。
如今运行修改后的程序:
运行结果也是正常的:
在Linux上使用标准库提供的数学函数也是一个老生常谈的问题,根据你使用cpp仍是c会有以下几个状况:
sqrt(4)
这样的形式,较新的gcc提供了替换措施,不须要显示连接libm;一般在Windows上咱们无需操心这点,但在Linux上使用c语言时就很难忽略这个问题了。
所以保险起见,若是你正在编写一个使用了数学函数的c程序,那么老是指定链接libm是没错的。(具体能够参考这里)
另外当你使用例如boost这类第三方库时,也须要注意。在Windows上咱们一般指定好附加包含目录和附加库目录便可正常编译,可是Linux上必须明确指定连接库的名字,所以咱们在项目属性中进行设置。
在Linux上咱们可使用pkg-config来减轻上述的重复劳动,而在vs中咱们不能直接利用这一工具,当你的项目使用了大量第三方库时就会成为不小的麻烦,若是想要解决这一问题,能够参考后续文章里我会介绍的vs+cmake构建项目。
下面咱们给例子加上一点boost chrono的功能测试,在Linux上须要指定-lboost_chrono
,这是设置:
下面是完整的代码:
#include <sys/utsname.h> #include <iostream> #include <cstdio> #include <string> #include <boost/chrono.hpp> int main() { namespace chrono = boost::chrono; auto start = chrono::high_resolution_clock::now(); utsname names; if (uname(&names) != 0) { std::perror("cannot get unames"); } std::cout << "Linux kernel version: " << names.release << std::endl; std::cout << "输入内容:"; std::string input; std::cin >> input; std::cout << "你输入了:" << input << std::endl; auto counter = chrono::duration_cast<chrono::milliseconds>(chrono::high_resolution_clock::now() - start); std::cout << "程序运行了:" << counter.count() << "ms\n"; }
点击运行按钮,程序就能正常调试了,不然会报错: