1. 程式人生 > >Visual Studio環境變數使用例項:使用環境變數來組織工程

Visual Studio環境變數使用例項:使用環境變數來組織工程

前言

使用vs環境變數來組織工程

通常一個解決方案包含多個專案,這些專案相互之間可能存在依賴關係,以下面這個解決方案為例:

my_soulution.jpg

這個解決方案叫:CS.cpp, 包含了7個專案:

專案名 生成目標 描述
Algorithm .exe 演算法和資料結構實踐
c_language .exe c語言實踐
TotalSTL .exe STL實踐
TrainingGround .exe C++語法自由訓練場
UnderstandingCpp11 .exe 深入理解C++11程式碼實踐
gtest
.lib google c++單元測試框架,給其他幾個專案作為測試框架
util .lib 個人積累工具類,為其他幾個專案提供util函式

其中五個專案是生成.exe檔案的應用程式,另外兩個gtestutil是服務於其他五個專案的,它倆生成的是.lib庫檔案,來為其他五個專案連結使用。

下圖是這個解決方案檔案的物理路徑:

sln_tree.jpg

可以看到,每個專案名稱對應一個同名資料夾。(Algorithm專案對應CS.cpp資料夾,因為Algorithm這個專案名字是中途修改的。)

除了7個專案名對應的目錄,其他幾個資料夾的作用如下表所示:

資料夾 作用
include 專案中使用到的標頭檔案存放於此
libs 專案中使用到的庫檔案存放於此, gtest和util這種庫工程的輸出檔案也存放於此,如各種.lib檔案
intermediate 所有專案的”中間目錄”集中存放於此
output 所有應用程式專案的”輸出檔案”存放於此,如各種.exe檔案
res 專案中用到的資原始檔存放於此,比如.txt, .json等檔案
_build 與VC++專案無關,不需留意。

下面介紹下我是如何把這7個專案組織起來協同工作,並且做到沒有冗餘檔案。

其實,組織專案很簡單,僅需掌握C++程式構建的本質,關鍵的兩個階段:編譯和連結。

第一步,讓專案編譯通過

這一步的目標是:讓5個生成.exe的專案編譯通過。以其中任意一個為例講解,其他的與之類似。那麼我就以TotalSTL為例吧,

先保證TotalSTL其內部程式碼沒有語法錯誤。

其次,因為程式碼中使用了gtest和util兩個專案中的程式碼,因此需要確保TotalSTL專案能夠搜尋到gtest和util的標頭檔案。也就是說把gtest和util的標頭檔案所在目錄新增到TotalSTL專案的包含路徑裡即可。增加專案包含目錄的操作在上一篇文章中已經提到,這裡不再細說。

需要注意的一點是,由於gtest和util屬於公共使用的庫,所以最好是把它們的標頭檔案放在一個公共的路徑下,比如放在常見的以include命名的目錄。這正是前面表格中提到的include資料夾的作用,其物理結構如下圖所示:

include_tree.jpg

可以看到,在include目錄下包含了gtest和util等子目錄,他們是按照專案來分類,除了gtest和util這兩個專案,還有其他的包含檔案也集中放在此處。

我要做的是把include新增到TotalSTL包含目錄中,運用上一篇文章學到的環境變數$(SolutionDir),我可以這樣編寫這個包含目錄:

$(SolutionDir)include/

新增完包含目錄,在TotalSTL專案的main.cpp中,就可以這樣引用gtest和util的標頭檔案:

main.cpp

#include "gtest/gtest.h"            // gtest是include資料夾的子資料夾,gtest.h是在gtest資料夾下,因此要加上gtest/字首
#include "util/FileReader.h"        // 同理,util是include的子資料夾,FileReader.h是在util資料夾下,因此加上util/字首

void dummyExitFunction() 
{
    elloop::FileReader::getInstance()->purege();
    char c = getchar();
}

int main(int argc, char** argv) {

#if defined(_MSC_VER) && defined(_DEBUG)
    // make program stop when debug.
    atexit(dummyExitFunction);
    turnOnMemroyCheck();
#endif

    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

其中,#include "gtest/gtest.h", gtest是include資料夾的子資料夾,gtest.h是在gtest資料夾下,因此要加上gtest/字首
同理,#include "util/FileReader.h", util是include的子資料夾,FileReader.h是在util資料夾下,因此加上util/字首

包含目錄配置完畢,專案就能夠順利通過編譯了。其他四個專案的配置與TotalSTL的配置一樣,也把include加入到包含目錄即可。

第二步:讓專案連結通過

在配置完包含目錄,編譯通過之後,我如果點“生成”專案,在連結階段會報錯的,是因為五個.exe專案在連結時,沒有找到它們依賴的gtest和util庫檔案。

這一步就是配置庫搜尋路徑:

思路是,先確定gtest和util兩個專案生成的庫檔案存放在何處,然後把庫檔案所在路徑加入到其他五個專案的庫搜尋路徑即可。

1. 確定庫檔案位置

跟前面講的把公共標頭檔案統一放在include目錄類似,公共的庫檔案一般是放到名為lib或者library的資料夾下,正如前文的目錄結構圖所示,我把它們統一放到了$(SolutionDir)libs目錄下,如圖,gtest.lib和util.lib就是gtest和util兩個專案生成的庫檔案:

libs_tree.jpg

要想做到讓gtest和util這倆專案”把蛋下到libs這裡”是需要設定的。以gtest專案為例,進行如下的設定:專案屬性 - 庫管理器 - 常規 - 輸出檔案 :

gtest_output.jpg

注意到其中對環境變數的使用,$(SolutionDir)libs/ 就是我的目的地,$(TargetFileName) == gtest.lib

2. 把庫檔案所在目錄加入到庫搜尋路徑

現在確定了庫檔案路徑為:

$(SolutionDir)libs/

下面把它加入到專案的庫搜尋路徑,還是以TotalSTL專案為例,進行如下操作:專案屬性 - 配置屬性 - 連結器 - 常規 - 附加庫目錄

lib_dir.jpg

經過這兩個小步驟,就完成了庫檔案搜尋路徑的設定。其他的4個專案也按照TotalSTL這樣設定一下庫搜尋目錄也就完成了第二步,至此即可保證專案連結通過了。

調整專案生成順序

在設定完檔案包含目錄和庫檔案搜尋目錄之後,當我點選“生成解決方案”的時候,還是可能發生有些專案生成失敗的情況。在生成失敗之後,我什麼也不改,再點一次“生成解決方案”,第二次就生成成功了。這是為什麼呢?

這是因為專案生成順序問題造成的。我們知道,5個.exe專案依賴gtest和util這倆專案,如果在生成gtest和util之前,就開始生成其他專案,比如TotalSTL, 那麼當TotalSTL連結時,發現gtest.lib和util.lib還沒有生成,此時就生成失敗了。

而第二次點選生成的時候,此時,gtest和util在第一次生成時已經成功產生gtest.lib和util.lib,第二次生成時,TotalSTL等其他失敗的專案重新重試連結,這次找到了兩個.lib檔案,於是生成成功了。

怎麼能讓解決方案一次就生成成功呢?

這就需要調整專案的生成順序,很簡單,還是以TotalSTL為例,進行如下操作:專案屬性 - 通用屬性 - 引用 - 新增新引用

add_ref.jpg

在彈出的列表中,選擇其依賴的專案。選擇gtest和util,確定即可。

其他四個.exe專案也做相應處理。設定完畢即可一次生成成功了。

管理專案的中間目錄和輸出目錄

在上文的解決方案物理路徑圖中,還有兩個資料夾:intermediate和output 值得介紹一下。

  • intermediate: 專案的中間目錄,生成過程中產生的一些中間檔案存放於此

  • output: 專案的輸出目錄,生成的結果檔案存放於此,比如TotalSTL.exe, TotalSTL.pdb, TotalSTL.ilk這些型別的檔案

設定這兩個目錄是為了方便所有專案統一管理,避免混亂。

下面是這兩個目錄的設定過程:專案屬性 - 配置屬性 - 常規 - 輸出目錄/中間目錄

outdir.jpg

輸出目錄的值為: $(SolutionDir)output/$(Configuration)/$(ProjectName)

中間目錄的值為:$(SolutionDir)intermediate/$(Configuration)/$(ProjectName)

注意其中環境變數的使用:$(SolutionDir)/intermediate$(SolutionDir)output 分別定為到上面提到的兩個資料夾,然後按照編譯配置, 即$(Configuration)(通常為Debug或者Release)來分目錄,最後以專案名稱來分目錄。

生成之後的目錄結構如下圖所示, 可以看到圖中路徑正是把$(Configuration)(值為Debug), $(ProjectName)(專案名字)代入之後的結果:

inter_tree.jpg

管理可執行檔案生成位置

上面在講到gtest和util這兩個專案的生成.lib的位置時,提到了改變專案的生成檔案位置。與之類似,其他5個生成.exe的專案,也可以做設定,使生成的.exe按照統一的目錄存放,方便查詢和管理。

以TotalSTL專案為例,具體操作如下:專案屬性 - 配置屬性 - 連結器 - 常規 - 輸出檔案

output_exe.jpg

其值設定為:$(OutDir)$(TargetFileName)

注意到其中環境變數的使用,其中的$(OutDir)就是上一小節提到的輸出目錄,其值剛才被設定為$(SolutionDir)output/$(Configuration)/$(ProjectName),把它代入到上面,展開為:

$(SolutionDir)output/$(Configuration)/$(ProjectName)$(TargetFileName)

生成之後的物理路徑結構為:

output.jpg

可以看到輸出的TotalSTL.exe的路徑正是”輸出目錄”,檔名TotalSTL.exe即$(TargetFileName)

管理工作目錄

工作目錄是程式執行時,搜尋資原始檔的路徑,具體設定在:專案屬性 - 配置屬性 - 除錯 - 工作目錄:

以TrainingGround專案為例:

workdir.jpg

其值為:$(SolutionDir)res/, 即對應開篇解決方案圖中的res資料夾。

總結

本文展示瞭如何藉助Visual Studio的環境變數來組織一個VC++解決方案的工程目錄結構。提到了如何使用環境變數來編寫標頭檔案包含路徑、庫檔案搜尋路徑、中間目錄、輸出目錄、輸出檔案位置、工作目錄等。

解決方案程式碼地址:CS.cpp 1.0(visual-studio分支)

作者水平有限,對相關知識的理解和總結難免有錯誤,還望給予指正,非常感謝!