一般,當咱們開發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"; }
點擊運行按鈕,程序就能正常調試了,不然會報錯: