首頁 > 軟體

使用Visual Studio 2019進行Linux遠端開發

2020-06-16 16:31:25

通常,當我們開發Linux程式時有兩種方案:

  1. 在Linux上直接編寫程式並進行執行測試和偵錯
  2. 在Windows或Mac OS X上藉助工具進行遠端開發

雖然我自己是在Linux環境上直接進行開發的,但也有許多的人是在Windows環境上從事開發工作的,如果離開自己熟悉的系統到陌生的環境上也許會影響到工作效率。

因此今天我們就來看下如何在Windows上使用Visual Studio 2019進行Linux遠端開發以及如何避免常見的陷阱。

本文索引

  • Visual Studio的跨平台開發功能簡介
  • 使用vs2019進行Linux遠端開發
    • 建立專案
    • 設定遠端專案
    • 新增遠端環境
    • 本地編寫和遠端偵錯
  • 避免踩坑
    • 中文亂碼
    • 使用數學函數和第三方庫

Visual Studio的跨平台開發功能簡介

從Visual Studio 2017開始微軟推出了vs的跨平台開發功能,你可以在vs中編輯程式碼,隨後進行跨平台編譯和遠端偵錯,將原先我們需要手動完成的工作進行了自動化,大幅減輕了我們的負擔。其中支援的平台包括Android和Linux,也就是我們今天要重點介紹的主角。

也許你會好奇,vs究竟是怎樣進行遠端開發的,雖然你不用了解這些知識也可以進行開發,但我還是希望能用兩分鐘做個簡短的解釋。

vs進行遠端開發分為兩步:

  1. 建立遠端環境的連線,隨後讓vs將遠端環境中的系統標頭檔案同步到本地(也可以指定其他地方的標頭檔案,後面會講解),c++的程式碼補全只需要標頭檔案即可。
  2. 當程式碼寫好後,選擇合適的遠端環境,vs將目標檔案和程式碼複製到遠端環境的指定位置,接著根據你的設定進行編譯。
  3. 隨後vs將會在console的gdb或gdbserver中執行你的程式,在此期間你可以充分享受vs debugger帶來的高效和便利。

經過上述步驟之後你就可以在vs裡偵錯自己編寫的跨平台程式了。

使用Visual Studio 2019進行Linux遠端開發

簡介到此結束了,下面我們來看看在vs2019進行Linux開發的圖文教學。在我們開始之前,首先要做點準備工作:

  1. 安裝好vs2019,且勾選了c++ for Linux功能;
  2. 準備一個可用的Linux遠端環境,例如設定了靜態IP的Linux虛擬機器,並且已經安裝好了GCC工具鏈以及openssh。

做好準備後我們就該進入正題了。

建立專案

安裝好c++ for Linux功能後我們會在建立新專案的面板中看到Linux的選項,如圖:

這裡我們選擇了使用傳統的vs專案解決方案構建的空白控制台程式,後續的文章中你還可以看到如何建立cmake專案,這裡暫且不提。

下面沒什麼要說的,選擇專案的儲存位置,注意是原生的位置,遠端機器的位置在後面會進行設定:

點選建立,我們的遠端開發專案就建立成功了。

設定遠端專案

vs不能編輯空專案的設定,所以我們先在專案中建立一個main.cpp,然後點選頂部選單:專案->屬性,你就能看到專案的設定介面了:

遠端計算機是在偵錯中的遠端連線管理器中新增的。這裡一般不需要改動,除非你需要改變專案的型別或編譯結果的存放位置。如果有多個遠端環境時,也可以在這裡進行選擇。

偵錯部分提供了gdbgdbserver,前者是讓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會有如下幾個情況:

  1. 使用cpp時,libstdc++依賴於libm,所以使用g++編譯你的程式時會自動連結數學函數庫;
  2. 使用c時,如果是sqrt(4)這樣的形式,較新的gcc提供了替換措施,不需要顯示連結libm;
  3. 接上一條,如果你的引數是個變數,那麼編譯器可能會選擇需要你連結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() << "msn";
}

點選執行按鈕,程式就能正常偵錯了,否則會報錯:


IT145.com E-mail:sddin#qq.com