vs的環境變量
https://zhuanlan.zhihu.com/p/34203415
https://wiki.jikexueyuan.com/project/visual-studio
https://blog.csdn.net/elloop/article/details/51010151
https://blog.csdn.net/elloop/article/details/51019110
http://elloop.github.io/tools/2016-03-28/visual-studio-predefined-macros
http://elloop.github.io/tools/2016-03-29/using-vs-environmental-variable
本文總結了Visual Studio中常見的環境變量及其在組織解決方案、工程中的作用。
注:本文使用的是Visual Studio 2013,由於作者主要從事C/C++開發,所以是以Visual C++的工作環境配置來描述。
什么是vs的環境變量?
先看圖吧,圖中以美元符號$開頭 + 一對括號,這樣進行引用的就是我所謂的環境變量,
圖中出現的幾個環境變量含義如下:
環境變量名 | 含義 |
---|---|
$(SolutionDir) | 解決方案目錄:即.sln文件所在路徑 |
$(Configuration) | 當前的編譯配置名稱,比如Debug,或Release |
$(ProjectName) | 當前項目名稱,圖中即為Game |
在中文版的VS中,環境變量翻譯為“宏”,為了避免與C/C++語言中的宏(Macro)搞混,我在本文中把它叫做“vs環境變量”,簡稱環境變量。
環境變量有什么用?
使用環境變量來組織工程目錄
vs作為一個IDE,其天職在於幫開發者組織好工程,主要包括對工程中源文件、庫文件的組織。(本質上是提供一個可視化的操作界面,讓開發者方便的定義編譯器和鏈接器的參數。)在使用vs來組織工程目錄時候最常用到的兩個目錄是:
- 頭文件包含目錄 (對應於編譯器命令的:-I 參數)
- 庫文件搜索路徑 (對應於編譯器的:-l 參數)
vs中默認的頭文件搜索路徑是: 工程路徑
– 即,.vcxproj
(不同版本的VC++后綴名稱不同,如vs2010中后綴為.vcproj) 文件所在路徑。比如如下的目錄結構:
–Root/
—-Test.vcxproj
—-hello.cpp
—-hello.h
—-world.cpp
—-world.h
—-main.cpp
—-/subdir
——sub.h
在Root目錄包含了.vcxproj文件,所以Root就是工程路徑
,在vs中,這個目錄下面的.h文件可以直接使用include包含進來, 比如在main.cpp中我可以寫:
#include "hello.h"
#include "world.h"
但是對於sub.h,我們就不能直接寫#include "sub.h"
, 因為工程路徑
下面不能搜索到這個文件,我要告訴編譯器這個文件在哪里,通常有以下兩種方法:
- 寫成
#include "subdir/sub.h"
- 把subdir目錄加入到頭文件搜索路徑
Google的C++編程風格鼓勵第一種做法,好處是可以看到文件相對完整的路徑,如果頭文件搜索路徑只有一個根目錄,那么這個路徑就是文件的相對於根目錄的物理路徑,方便定位文件。
如果你覺得這樣寫很麻煩,並且路徑深度可能有多層,不同深度的路徑下又通常包含大量的文件,那么就可以選擇第二種做法,把每個子目錄統統加入到搜索路徑中,這樣,就可以不用帶着路徑,直接#include "filename.h"
就可以了。具體在VS中要怎么合理的添加文件包含目錄呢?由此,便引出了本節問題的答案:環境變量有什么用?用途之一就是用來編寫頭文件的搜索路徑。
相信大家都知道如何在vs中添加一個頭文件搜索路徑這個常識,在此還是為初學者嘮叨一下具體做法:工程屬性 - 配置屬性 - C/C++ - 常規 - 編輯右側的”附加包含目錄”取值即可。
具體如下圖所示:
點擊編輯之后,彈出如下圖所示的編輯窗口:
在這里可以新建、刪除包含路徑、調整包含順序。
點擊新建按鈕或者雙擊列表空白處即可添加一條包含路徑,在編輯新添加的路徑時,可以看到列表條目右側有一個瀏覽按鈕,
點擊按鈕可以從打開文件對話框里選擇路徑,點擊確定后,會看到新添加的路徑名。雖然通過瀏覽來定位文件夾比較容易,免去了自己編寫,但是你會發現,通過瀏覽添加的路徑是絕對路徑。
如果你是項目的唯一開發者,並且僅僅使用這一台電腦來開發的話,那么使用絕對路徑也沒什么大問題。但是如果這個項目是個團隊協作項目,或者你需要在好幾台電腦之間切換,那么這個包含路徑如果寫絕對路徑就不夠靈活了,如果別人的路徑配置或別的電腦的路徑配置不同,那么要重新修改包含路徑。
一個比較合理的編寫包含路徑的方法是: 使用相對路徑。
相對誰呢? 相對項目根目錄或者解決方案根目錄。
為什么呢?因為不管別人的電腦有什么盤符、不管別人的項目放在何處,要包含的文件都可以通過項目所在位置來計算出來。
當然前提是,項目開發者們事先約定好被包含文件相對於項目根目錄的位置。通常是放在項目根目錄(或者解決方案根目錄)的某個子目錄里。
具體怎么做呢?這就需要用到本文的主題:環境變量了。剛才提到的兩個相對目錄所對應的環境變量如下表所示:
目錄 | 對應的環境變量名稱 |
---|---|
項目根目錄 | $(ProjectDir) |
解決方案根目錄 | $(SolutionDir) |
要解決剛才小例子中的問題,
–Root/
—-Test.vcxproj
—-hello.cpp
—-hello.h
—-world.cpp
—-world.h
—-main.cpp
—-/subdir
——sub.h
注意到.vcxproj所在目錄即項目根目錄,也就是$(ProjectDir)的取值等於Root/。所以要把subdir放在包含目錄里,可以新建這樣一條包含路徑:
$(ProjectDir)subdir
這樣,在main.cpp里就可以直接寫#include "sub.h"
了。不管項目被拷貝到哪里,都不用修改包含路徑。
上面就是環境變量使用的一個小例子。使用環境變量來編寫文件包含路徑的好處是: 包含路徑獨立於工程所在的路徑,無論工程被移動到哪里,都不需要重新修改包含路徑,因為使用環境變量來編寫的文件包含路徑是一種相對路徑。
其它vs環境變量
如何查看所有的環境變量值呢?
有好多個地方都可以查看,比如剛才在添加包含目錄時候,彈出的窗口,注意其右下方,有個“宏”按鈕
點擊它就能看到所有的“宏” (即vs環境變量的值):
在上方的輸入框可以進行過濾。
下面的表格給出了常用的環境變量的含義:
環境變量名 | 含義 |
---|---|
$(SolutionDir) |
解決方案目錄:即.sln文件所在路徑 |
$(ProjectDir) |
項目根目錄:, 即.vcxproj文件所在路徑 |
$(Configuration) |
當前的編譯配置名稱,比如Debug,或Release |
$(ProjectName) |
當前項目名稱 |
$(SolutionName) |
解決方案名稱 |
$(OutDir) |
項目輸出文件目錄 |
$(TargetDir) |
項目輸出文件目錄 |
$(TargetName) |
項目生成目標文件, 通常和$(ProjectName) 同名, 如Game |
$(TargetExt) |
項目生成文件后綴名,如.exe, .lib具體取決於工程設置 |
$(TargetFileName) |
項目輸出文件名字。比如Game.exe, 等於 $(TargetName) + $(TargetExt) |
$(ProjectExt) |
工程文件后綴名,如.vcxproj |
本文將以實際例子說明如何合理使用這些環境變量來組織VC++工程。
使用vs環境變量來組織工程
通常一個解決方案包含多個項目,這些項目相互之間可能存在依賴關系,以下面這個解決方案為例:
這個解決方案叫: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文件的應用程序,另外兩個gtest
和util
是服務於其他五個項目的,它倆生成的是.lib庫文件,來為其他五個項目鏈接使用。
下圖是這個解決方案文件的物理路徑:
可以看到,每個項目名稱對應一個同名文件夾。(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目錄下包含了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兩個項目生成的庫文件:
要想做到讓gtest和util這倆項目”把蛋下到libs這里”是需要設置的。以gtest項目為例,進行如下的設置:項目屬性 - 庫管理器 - 常規 - 輸出文件 :
注意到其中對環境變量的使用,$(SolutionDir)libs/
就是我的目的地,$(TargetFileName) == gtest.lib
2. 把庫文件所在目錄加入到庫搜索路徑
現在確定了庫文件路徑為:
$(SolutionDir)libs/
下面把它加入到項目的庫搜索路徑,還是以TotalSTL項目為例,進行如下操作:項目屬性 - 配置屬性 - 鏈接器 - 常規 - 附加庫目錄
經過這兩個小步驟,就完成了庫文件搜索路徑的設置。其他的4個項目也按照TotalSTL這樣設置一下庫搜索目錄也就完成了第二步,至此即可保證項目鏈接通過了。
調整項目生成順序
在設置完文件包含目錄和庫文件搜索目錄之后,當我點擊“生成解決方案”的時候,還是可能發生有些項目生成失敗的情況。在生成失敗之后,我什么也不改,再點一次“生成解決方案”,第二次就生成成功了。這是為什么呢?
這是因為項目生成順序問題造成的。我們知道,5個.exe項目依賴gtest和util這倆項目,如果在生成gtest和util之前,就開始生成其他項目,比如TotalSTL, 那么當TotalSTL鏈接時,發現gtest.lib和util.lib還沒有生成,此時就生成失敗了。
而第二次點擊生成的時候,此時,gtest和util在第一次生成時已經成功產生gtest.lib和util.lib,第二次生成時,TotalSTL等其他失敗的項目重新重試鏈接,這次找到了兩個.lib文件,於是生成成功了。
怎么能讓解決方案一次就生成成功呢?
這就需要調整項目的生成順序,很簡單,還是以TotalSTL為例,進行如下操作:項目屬性 - 通用屬性 - 引用 - 添加新引用
在彈出的列表中,選擇其依賴的項目。選擇gtest和util,確定即可。
其他四個.exe項目也做相應處理。設置完畢即可一次生成成功了。
管理項目的中間目錄和輸出目錄
在上文的解決方案物理路徑圖中,還有兩個文件夾:intermediate和output 值得介紹一下。
- intermediate: 項目的中間目錄,生成過程中產生的一些中間文件存放於此
- output: 項目的輸出目錄,生成的結果文件存放於此,比如TotalSTL.exe, TotalSTL.pdb, TotalSTL.ilk這些類型的文件
設置這兩個目錄是為了方便所有項目統一管理,避免混亂。
下面是這兩個目錄的設置過程:項目屬性 - 配置屬性 - 常規 - 輸出目錄/中間目錄
輸出目錄的值為: $(SolutionDir)output/$(Configuration)/$(ProjectName)
中間目錄的值為:$(SolutionDir)intermediate/$(Configuration)/$(ProjectName)
注意其中環境變量的使用:$(SolutionDir)/intermediate
和 $(SolutionDir)output
分別定為到上面提到的兩個文件夾,然后按照編譯配置, 即$(Configuration)
(通常為Debug或者Release)來分目錄,最后以項目名稱來分目錄。
生成之后的目錄結構如下圖所示, 可以看到圖中路徑正是把$(Configuration)
(值為Debug), $(ProjectName)
(項目名字)代入之后的結果:
管理可執行文件生成位置
上面在講到gtest和util這兩個項目的生成.lib的位置時,提到了改變項目的生成文件位置。與之類似,其他5個生成.exe的項目,也可以做設置,使生成的.exe按照統一的目錄存放,方便查找和管理。
以TotalSTL項目為例,具體操作如下:項目屬性 - 配置屬性 - 鏈接器 - 常規 - 輸出文件
其值設置為:$(OutDir)$(TargetFileName)
注意到其中環境變量的使用,其中的$(OutDir)
就是上一小節提到的輸出目錄,其值剛才被設置為$(SolutionDir)output/$(Configuration)/$(ProjectName)
,把它代入到上面,展開為:
$(SolutionDir)output/$(Configuration)/$(ProjectName)$(TargetFileName)
生成之后的物理路徑結構為:
可以看到輸出的TotalSTL.exe的路徑正是”輸出目錄”,文件名TotalSTL.exe即$(TargetFileName)
。
管理工作目錄
工作目錄是程序運行時,搜索資源文件的路徑,具體設置在:項目屬性 - 配置屬性 - 調試 - 工作目錄:
以TrainingGround項目為例:
其值為:$(SolutionDir)res/
, 即對應開篇解決方案圖中的res文件夾。
總結
本文展示了如何借助Visual Studio的環境變量來組織一個VC++解決方案的工程目錄結構。提到了如何使用環境變量來編寫頭文件包含路徑、庫文件搜索路徑、中間目錄、輸出目錄、輸出文件位置、工作目錄等。
解決方案代碼地址:CS.cpp ( https://github.com/elloop/CS.cpp )1.0(visual-studio分支)
============= End