別 vcxproj:Visual Studio 項(xiàng)目系統(tǒng)與 MSBuild 內(nèi)部機(jī)制解析)
在 VS2017 的使用周期里應(yīng)該有不少人做過這樣的事打開命令行敲一句devenv MyProject.vcxproj然后看著 Visual Studio 的界面刷一下蹦出來項(xiàng)目被掛到了解決方案資源管理器里。如果你再狠一點(diǎn)輸入devenv MyProject.vcxproj /Build構(gòu)建居然也能正常跑。這時(shí)很多人就會(huì)冒出那個(gè)經(jīng)典疑問devenv 明明只是一個(gè) IDE 的啟動(dòng)入口它憑什么能認(rèn)一個(gè) .vcxproj 項(xiàng)目文件這背后到底是文件關(guān)聯(lián)的魔法還是 Visual Studio 內(nèi)部藏著一套更深的識(shí)別機(jī)制我當(dāng)年第一次認(rèn)真面對(duì)這個(gè)問題是在幫同事排查 CI 構(gòu)建腳本的時(shí)候。腳本里混用了 msbuild 和 devenv兩邊對(duì)同一個(gè) vcxproj 的處理結(jié)果居然不一樣于是我只能一層層往下翻源碼文檔和注冊(cè)表。翻完之后發(fā)現(xiàn)答案并不神秘.vcxproj 本身就是 MSBuild 項(xiàng)目文件而 devenv 的進(jìn)程里就嵌著 MSBuild 引擎再加上 Visual Studio 項(xiàng)目系統(tǒng)的注冊(cè)項(xiàng)這一整套鏈路才讓 devenv 能順理成章地接住vcxproj。這篇文章就把這條鏈路從頭到尾拆開講順便帶上我實(shí)際驗(yàn)證過的命令和踩坑記錄。不管你是剛接觸 VS2017 的 C 新手還是天天跟構(gòu)建腳本打交道的工程效能同學(xué)應(yīng)該都能拿到點(diǎn)能直接用的東西。1. 先搞清楚 devenv 的真實(shí)身份1.1 devenv 不是編譯器它是個(gè)IDE 宿主進(jìn)程很多人會(huì)把devenv.exe當(dāng)成一個(gè)能編譯的編譯器命令行工具這個(gè)理解其實(shí)跑偏了。devenv.exe是 Visual Studio IDE 的進(jìn)程宿主它的職責(zé)是拉起一整套開發(fā)環(huán)境加載擴(kuò)展包Package、初始化菜單和工具欄、恢復(fù)上次的窗口布局、創(chuàng)建解決方案模型再把編輯器、調(diào)試器、項(xiàng)目系統(tǒng)這些組件串起來。真正干編譯活的是項(xiàng)目系統(tǒng)拿到 MSBuild 引擎之后調(diào)用的cl.exe、link.exe、rc.exe這些底層工具。這里可以打一個(gè)不夠嚴(yán)謹(jǐn)?shù)芎枚谋确絛evenv 就像一個(gè)酒店的大堂經(jīng)理它負(fù)責(zé)接待、分配房間、協(xié)調(diào)服務(wù)但真正做飯的是后廚編譯器。你丟給大堂經(jīng)理一張菜單vcxproj他知道該把這張菜單轉(zhuǎn)到哪個(gè)后廚但他自己是不下鍋的。這個(gè)稱呼也能解釋很多歷史devenv 全稱是 Developer Environment從 Visual Studio .NET 時(shí)代一直用到現(xiàn)在。也就是說無論你從開始菜單圖標(biāo)進(jìn) VS還是雙擊 .vcxproj又或者在命令行里手動(dòng)敲最終進(jìn)入的都是這個(gè)主進(jìn)程只是入口參數(shù)不一樣。1.2 雙擊文件與命令行傳參走的是兩條路先區(qū)分兩個(gè)場(chǎng)景不然下面講機(jī)制時(shí)容易混。場(chǎng)景 A你在資源管理器里雙擊一個(gè) .vcxproj。這個(gè)時(shí)候是 Windows Shell 在起作用。系統(tǒng)去注冊(cè)表查 .vcxproj 擴(kuò)展名關(guān)聯(lián)的 ProgID找到對(duì)應(yīng)的 Visual Studio 版本然后拼出一條類似devenv.exe %1的現(xiàn)代命令并執(zhí)行。所以雙擊的本質(zhì)還是 devenv只是 Shell 幫你套了一層打開方式的包裝。場(chǎng)景 B你在命令行或構(gòu)建腳本里顯式輸入devenv xxx.vcxproj。這個(gè)時(shí)候沒有 Shell 參與是你直接把參數(shù)交給了 devenv.exe。兩條路最終會(huì)匯到同一個(gè)地方devenv 進(jìn)程啟動(dòng)后對(duì)傳入的命令行參數(shù)做解析區(qū)分出這到底是文件路徑、解決方案路徑、構(gòu)建開關(guān)還是調(diào)試開關(guān)。關(guān)鍵點(diǎn)在于場(chǎng)景 A 依賴的是系統(tǒng)注冊(cè)表里的文件關(guān)聯(lián)而場(chǎng)景 B 不依賴文件關(guān)聯(lián)它靠的是 devenv 進(jìn)程內(nèi)部的輸入分類能力。那內(nèi)部是怎么分類的這就要說到 .vcxproj 的真正身份了。2. 核心答案.vcxproj 和 MSBuild 本來就是一家2.1 脫掉外殼.vcxproj 就是一份 XML 構(gòu)建腳本.vcxproj 文件雖然叫工程文件看起來也像一份私有格式的配置但打開一看你就明白了它就是一個(gè) XML 文檔。對(duì) MSBuild 引擎來說這個(gè) XML 就是一份完整的構(gòu)建腳本。根節(jié)點(diǎn)是Project里面定義了若干個(gè)PropertyGroup屬性組和ItemGroup項(xiàng)組還有可能出現(xiàn)的Target、Task定義。MSBuild 引擎讀取這個(gè)文件后會(huì)按照節(jié)點(diǎn)結(jié)構(gòu)生成一個(gè)執(zhí)行計(jì)劃然后一步一步調(diào)用具體的任務(wù)執(zhí)行器去完成編譯、鏈接、復(fù)制文件這些操作。我寫一個(gè)最小的 vcxproj 核心結(jié)構(gòu)給你感受一下Project DefaultTargetsBuild ToolsVersion15.0 xmlnshttp://schemas.microsoft.com/developer/msbuild/2003 ItemGroup LabelProjectConfigurations ProjectConfiguration IncludeDebug|Win32 ConfigurationDebug/Configuration PlatformWin32/Platform /ProjectConfiguration /ItemGroup ItemGroup ClCompile Includemain.cpp / /ItemGroup Import Project$(VCTargetsPath)\Microsoft.Cpp.Default.props / Import Project$(VCTargetsPath)\Microsoft.Cpp.props / Import Project$(VCTargetsPath)\Microsoft.Cpp.targets / /Project這三個(gè)Import特別關(guān)鍵它們把 Microsoft.Cpp 整套屬性、目標(biāo)文件都導(dǎo)了進(jìn)來而真正決定如何調(diào)用 cl.exe、怎么傳 /I、/D、/EHsc的邏輯就藏在這些 .props 和 .targets 文件里。所以 vcxproj 不是某種 Visual Studio 私有格式它是開放的 MSBuild 項(xiàng)目文件格式任何實(shí)現(xiàn)了 MSBuild 語義的程序都能消費(fèi)它。這也是為什么獨(dú)立的 msbuild.exe 命令行工具也能直接編譯 .vcxproj。2.2 從 vcproj 到 vcxproj一次歷史性的自洽老一點(diǎn)的開發(fā)者應(yīng)該記得VC6 和 VS2008 時(shí)代的 C 項(xiàng)目文件叫 .vcproj它是 VC 項(xiàng)目系統(tǒng)自己的私有格式只有 Visual Studio 的 VC 項(xiàng)目系統(tǒng)能完整讀取。那個(gè)時(shí)代的命令行構(gòu)建要么靠 vcbuild 命令要么只能在 IDE 里點(diǎn)鼠標(biāo)做自動(dòng)化構(gòu)建非常痛苦。從 VS2010 開始微軟把整個(gè)項(xiàng)目系統(tǒng)整合到 MSBuild 上C 項(xiàng)目格式也隨之升級(jí)成了 .vcxproj。這一改的意義特別大IDE 里看到的項(xiàng)目文件、命令行 msbuild 用的構(gòu)建腳本、CI 服務(wù)器上的自動(dòng)化流程第一次統(tǒng)一成了同一種格式?,F(xiàn)在你再看為什么 devenv 能接受 .vcxproj這個(gè)問題第一層答案已經(jīng)呼之欲出因?yàn)?.vcxproj 就是 MSBuild 項(xiàng)目文件而 deve nv 進(jìn)程內(nèi)嵌了 MSBuild 引擎。兩者本來就是一家的東西不存在跨格式識(shí)別的障礙。舊格式 .vcproj 到了 VS2017 雖然也能被打開但會(huì)彈升級(jí)向?qū)П举|(zhì)上就是要把舊項(xiàng)目轉(zhuǎn)換成新的 MSBuild 項(xiàng)目格式反而從側(cè)面印證了這條路。2.3 devenv 進(jìn)程里的 MSBuild 與獨(dú)立的 msbuild.exeVS2017 里devenv 在啟動(dòng)時(shí)會(huì)加載 Microsoft.Build.dll 等程序集并在進(jìn)程內(nèi)部創(chuàng)建 BuildManager、ProjectCollection 這些對(duì)象。也就是說你在 IDE 里按 F7 構(gòu)建和你在命令行里手動(dòng)敲 msbuild核心構(gòu)建邏輯使用的是同一套引擎。你甚至可以在 VS2017 的安裝目錄里找到獨(dú)立的 MSBuild 程序集和 msbuild.exe例如C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\MSBuild.exedevenv 的調(diào)度主路徑是 devenv.exemsbuild.exe 是獨(dú)立分發(fā)的最小構(gòu)建宿主。二者共享 .targets、.props、工具集定義只是在宿主環(huán)境上有所差異。這一點(diǎn)極其重要后面講為什么 devenv /Build 和 msbuild 結(jié)果不一樣的時(shí)候你會(huì)再看到它。3. 接住 .vcxproj 的完整鏈路從參數(shù)到項(xiàng)目系統(tǒng)3.1 命令行解析devenv 怎么判斷你給的是什么文件當(dāng)用戶在命令行輸入devenv MyProject.vcxproj時(shí)devenv 主進(jìn)程啟動(dòng)后會(huì)有專門的命令行解析邏輯處理參數(shù)。它會(huì)遍歷每一個(gè)參數(shù)判斷如果以/或-開頭就當(dāng)作開關(guān)項(xiàng)處理比如/Build、/Clean否則當(dāng)成文件路徑。拿到文件路徑后devenv 會(huì)先確認(rèn)這個(gè)文件是否存在、是不是目錄再看擴(kuò)展名。對(duì)擴(kuò)展名的判斷不是靠 exe 里寫死的字符串而是查系統(tǒng)注冊(cè)表里 Visual Studio 各個(gè)項(xiàng)目系統(tǒng)注冊(cè)過的項(xiàng)目文件類型映射。每家項(xiàng)目系統(tǒng)VC、C#、VB、F#在安裝時(shí)都會(huì)把支持的擴(kuò)展名和對(duì)應(yīng)的項(xiàng)目類型 GUID 寫進(jìn)注冊(cè)表。devenv 拿到 .vcxproj 后會(huì)在這些映射里找到一條類似vcxproj - VC 項(xiàng)目工廠的記錄然后一切就從這里開始了。3.2 項(xiàng)目類型 GUIDVC 項(xiàng)目靠它認(rèn)親很多搞了多年 VS 開發(fā)的人可能都不知道每個(gè)項(xiàng)目類型在 Visual Studio 里都有一個(gè) GUID。VC 項(xiàng)目的類型 GUID 是個(gè)老資歷{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}這個(gè) GUID 最早可以追溯到 VC6 時(shí)代一直沿用到 VS2017。你在解決方案 .sln 文件里也能看到它格式大致是這樣Project({8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}) MyProject, MyProject.vcxproj, {項(xiàng)目自己的GUID}需要注意的是在 .vcxproj 文件內(nèi)部項(xiàng)目類型 GUID 通常不會(huì)顯式寫在根節(jié)點(diǎn)上它存在于擴(kuò)展名關(guān)聯(lián)、項(xiàng)目模板注冊(cè)信息和 .sln 的項(xiàng)目聲明中。devenv 根據(jù)擴(kuò)展名找到對(duì)應(yīng)的項(xiàng)目工廠后再由項(xiàng)目工廠實(shí)例化具體的項(xiàng)目對(duì)象。所以嚴(yán)格說起來devenv 并不是在解析一個(gè)文件而是在按注冊(cè)表映射找對(duì)項(xiàng)目系統(tǒng)然后讓項(xiàng)目系統(tǒng)去加載文件。這里還有個(gè)值得補(bǔ)充的點(diǎn)VS2017 的 C 項(xiàng)目系統(tǒng)并沒有遷移到 Common Project SystemCPS它仍然使用經(jīng)典的 VCProject 項(xiàng)目系統(tǒng)核心類叫 VCProjectEngine。CPS 主要用于 C#、VB 這類托管語言項(xiàng)目后續(xù)才逐步擴(kuò)展。這也意味著 VC 項(xiàng)目在 devenv 中的加載路徑和 C# 項(xiàng)目的加載路徑是完全不同的。你把 .vcxproj 丟給 devenv它絕對(duì)不會(huì)按 C# 的工廠去處理映射表指向哪個(gè)工廠就走哪條路。3.3 臨時(shí)解決方案項(xiàng)目如何被掛進(jìn)解決方案模型一個(gè)很多人沒注意到的細(xì)節(jié)你只傳了一個(gè) .vcxproj但打開 Visual Studio 后看到的還是解決方案資源管理器里面有一個(gè)解決方案節(jié)點(diǎn)下面掛著你的項(xiàng)目。這其實(shí)是 devenv 的單一項(xiàng)目打開機(jī)制在起作用。如果傳入的不是 .sln 而是某個(gè)項(xiàng)目文件devenv 會(huì)在內(nèi)部生成一個(gè)內(nèi)存中的解決方案對(duì)象再把項(xiàng)目掛進(jìn)去。這樣 IDE 后續(xù)的構(gòu)建、調(diào)試、NuGet 管理都還是按照解決方案維度來運(yùn)作只不過這個(gè)方案是臨時(shí)的等你在 IDE 里另存為 .sln 文件時(shí)才會(huì)真正落盤。所以devenv 不是破例接受了一個(gè)項(xiàng)目文件它只是把你給的項(xiàng)目文件納入了它熟悉的解決方案模型。這也是為什么你可以在命令行直接傳 .sln 文件devenv 按正常方案打開處理邏輯是一樣的。3.4 能被 devenv 接受的遠(yuǎn)不止 .vcxproj把視野放寬一點(diǎn)devenv 能接受的輸入其實(shí)是一個(gè)集合.sln、.slnx、.csproj、.vbproj、.fsproj、.vcxproj、.vcproj舊格式需要通過升級(jí)向?qū)А?suo 都屬于它懂的格式。遇到這些擴(kuò)展名devenv 會(huì)走項(xiàng)目系統(tǒng)加載遇到不在集合里的普通文件比如單獨(dú)的 main.cppdevenv 不會(huì)拒絕打開而是把它當(dāng)成雜項(xiàng)文件顯示在解決方案里的 Misc 區(qū)域或者直接拉到編輯器里讓你看代碼。理解了這一點(diǎn)你就明白了能不能被 devenv 接受的判斷標(biāo)準(zhǔn)不是文件名本身而是這個(gè)擴(kuò)展名是否被某個(gè)已安裝的項(xiàng)目系統(tǒng)在注冊(cè)表里登記過。如果你裝的 VS2017 缺了 C 桌面開發(fā)工作負(fù)載就算 vcxproj 文件放在面前devenv 也只能當(dāng)普通文件打開或者報(bào)項(xiàng)目系統(tǒng)缺失。4. 實(shí)操驗(yàn)證在 VS2017 里跑通這幾條命令4.1 環(huán)境準(zhǔn)備先確保工具鏈和命令行入口正常要復(fù)現(xiàn)下面的內(nèi)容你需要一臺(tái)裝了 VS2017 的 Windows 機(jī)器并且確保已經(jīng)安裝了C 桌面開發(fā)這個(gè)工作負(fù)載。如果你所在的是隔離內(nèi)網(wǎng)環(huán)境通常的做法是提前用 VS2017 離線安裝包生成布局目錄把 VC 工具集、MSBuild、Windows SDK 這些組件都勾選上再拿布局目錄到目標(biāo)機(jī)器上安裝。很多構(gòu)建機(jī)沒有外網(wǎng)離線安裝包在這里幾乎成了標(biāo)配省去聯(lián)網(wǎng)下載的麻煩。打開命令行也有講究。建議不要直接用默認(rèn)的 cmd而是用開始菜單里的Developer Command Prompt for VS 2017開發(fā)者命令提示符。這個(gè)入口已經(jīng)幫你初始化好了 INCLUDE、LIB、PATH 等環(huán)境變量能直接找到 devenv.exe 和 msbuild.exe。如果你只有普通 PowerShell那也可以先手動(dòng)把 vs2017 的 Common7\IDE 和 MSBuild\15.0\Bin 目錄加進(jìn) PATH 即可。4.2 準(zhǔn)備一個(gè)最小的 vcxproj 與 main.cpp為了不引入其他干擾我用最原始的方式準(zhǔn)備一個(gè)最小 C 項(xiàng)目一個(gè) main.cpp一個(gè) MyDemo.vcxproj。main.cpp 內(nèi)容很簡(jiǎn)單#include iostream int main() { std::cout hello devenv std::endl; return 0; }.vcxproj 參考前面第 2.1 節(jié)的最小結(jié)構(gòu)是夠的但真正要讓 MSBuild 的 VC 目標(biāo)完整跑起來里面的 PropertyGroup、Import 順序都不能亂。所以我更建議你直接用 VS2017 新建一個(gè)Windows 桌面應(yīng)用程序模板項(xiàng)目然后把生成的 vcxproj 拿出來做實(shí)驗(yàn)。模板生成的 vcxproj 包含完整的工具集、平臺(tái)、預(yù)編譯頭、鏈接器配置拿來做實(shí)驗(yàn)最可靠也不會(huì)因?yàn)樯倭四硞€(gè)節(jié)點(diǎn)而踩坑。4.3 三條命令把打開、構(gòu)建、清理串起來下面三條命令是我個(gè)人最常用的組合建議按順序試一遍devenv MyDemo.vcxproj devenv MyDemo.vcxproj /Build Debug devenv MyDemo.vcxproj /Clean Debug第一條會(huì)啟動(dòng) IDE 并顯示 MyDemo 項(xiàng)目第二條不會(huì)打開完整界面而是直接以命令行構(gòu)建模式執(zhí)行 Debug 配置的構(gòu)建第三條清理 Debug 配置下的中間產(chǎn)物。/Build、/Rebuild、/Clean是 devenv 命令行最常用的三個(gè)構(gòu)建開關(guān)區(qū)別是Build 做增量編譯Rebuild 全量重編Clean 刪除中間輸出。如果你在解決方案場(chǎng)景下想指定構(gòu)建某個(gè)子項(xiàng)目可以用/Project參數(shù)devenv MySolution.sln /Build Debug /Project MyDemo.vcxproj /ProjectConfig Debug|Win32單個(gè) vcxproj 直接傳進(jìn)去的時(shí)候也可以帶上/ProjectConfig指定平臺(tái)和配置避免 devenv 用默認(rèn)平臺(tái)去匹配導(dǎo)致構(gòu)建被跳過。4.4 devenv /Build 和 msbuild 的差異誰更快誰更全很多做 CI 的人都會(huì)糾結(jié)既然 vcxproj 能被 devenv 接受也能被 msbuild 接受那構(gòu)建腳本里到底用哪個(gè)我把差異直接擺出來。devenv /Build 是帶著 IDE 宿主一起構(gòu)建進(jìn)程初始化更重可能會(huì)加載擴(kuò)展包、創(chuàng)建 ActivityLog所以啟動(dòng)明顯更慢。但它有個(gè)特點(diǎn)能復(fù)現(xiàn) IDE 環(huán)境下的完整行為。比如某些屬性在 IDE 屬性頁里被可視化地修改過或者有些擴(kuò)展只在 IDE 宿主下才注入構(gòu)建邏輯。當(dāng)你懷疑IDE 能編、命令行不能編時(shí)先試 devenv /Build 作為對(duì)照組是最合理的。msbuild.exe 是輕量級(jí)宿主不啟動(dòng) IDE只執(zhí)行構(gòu)建引擎。它需要正確的環(huán)境變量INCLUDE、LIB、PATH才能找到工具集所以 CI 里通常先調(diào)用 vcvarsall.bat 再跑 msbuild。優(yōu)點(diǎn)是速度快適合純構(gòu)建場(chǎng)景。對(duì)比項(xiàng)devenv /Buildmsbuild.exe引擎內(nèi)嵌 MSBuild先啟動(dòng) IDE 宿主獨(dú)立 MSBuild 宿主速度較慢啟動(dòng)開銷大較快環(huán)境依賴自動(dòng)定位 VS 內(nèi)部工具依賴 vcvarsall 初始化環(huán)境行為一致性更接近 IDE 手工操作更接近純腳本日志通常生成 ActivityLog.xml輸出可選診斷日志哪種更好沒有絕對(duì)答案。我的建議是本地排查用 devenv /Build自動(dòng)化構(gòu)建優(yōu)先 msbuild.exe。但如果你的構(gòu)建邏輯里有 IDE 擴(kuò)展參與那統(tǒng)一用 devenv /Build 更穩(wěn)。提示不要把 devenv /Build 當(dāng)成輕量級(jí)工具。它啟動(dòng)的是整個(gè) IDE 宿主進(jìn)程在 CI 節(jié)點(diǎn)上可能因?yàn)樵S可證、擴(kuò)展加載等問題出幺蛾子如果純編譯msbuild 更可控。4.5 順帶一提配置 PCL 這種重型庫(kù)時(shí)命令行驗(yàn)證的價(jià)值搜過Windows 下 VS2017 配置 PCL的人應(yīng)該對(duì)這個(gè)場(chǎng)景很熟PCL 點(diǎn)云庫(kù)配置要編譯一堆依賴中間大量 .vcxproj 項(xiàng)目需要逐個(gè)構(gòu)建。如果全在 IDE 里點(diǎn)很容易因?yàn)榕渲庙?xiàng)遺漏而返工但如果把構(gòu)建過程腳本化用 devenv 或 msbuild 在命令行里跑就能快速定位是哪個(gè)項(xiàng)目、哪個(gè)配置出了問題。很多 PCL 教程教你把 vcxproj 拖進(jìn) VS 就開始 build實(shí)際上你先用devenv xxx.vcxproj /Build Debug跑一遍輸出更干凈問題更好定位。這個(gè)習(xí)慣對(duì)后續(xù)排查非常有幫助。5. 常見問題與排查技巧實(shí)錄5.1 多個(gè) VS 版本并存時(shí).vcxproj 被搶走了這個(gè)問題在裝了多個(gè) VS 版本的機(jī)器上特別常見。資源管理器里雙擊 vcxproj系統(tǒng)按注冊(cè)表關(guān)聯(lián)彈出的可能是 VS2019 或 VS2022而不是你想要的 VS2017。解決思路有兩個(gè)一是右鍵選擇打開方式手動(dòng)指向 VS2017 的 devenv.exe二是在命令行里直接指定完整路徑C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\devenv.exe MyDemo.vcxproj這種寫法繞過了文件關(guān)聯(lián)也繞過了Open With的彈窗選擇適合在腳本里固定版本。5.2 打開 vcxproj 報(bào)未能加載項(xiàng)目怎么排查可能的坑不少。第一類是文件本身的問題vcxproj 損壞、XML 語法錯(cuò)誤、被非法編碼破壞。第二類是版本問題項(xiàng)目是用更高版本 VS 創(chuàng)建的PlatformToolset 在當(dāng)前 VS2017 里不存在比如 v143 對(duì)應(yīng) VS2022VS2017 里默認(rèn)只有 v141。第三類是組件缺失機(jī)器上沒裝 C 桌面開發(fā)工作負(fù)載。第四類是權(quán)限問題文件在只讀目錄或被進(jìn)程占用。我的排查習(xí)慣分三層先用文本編輯器打開 vcxproj檢查 XML 是否正常重點(diǎn)看Project根節(jié)點(diǎn)有沒有完整的命名空間Import路徑是不是指向了$(VCTargetsPath)。再看PlatformToolset節(jié)點(diǎn)確認(rèn)工具集版本和當(dāng)前 VS 匹配。最后打開 Visual Studio Installer 的修改頁確認(rèn) C 相關(guān)組件確實(shí)裝上了。如果是離線環(huán)境用離線安裝包補(bǔ)裝對(duì)應(yīng)組件即可。5.3 命令行構(gòu)建失敗但 IDE 構(gòu)建成功差在哪這種陰陽差異通常不是 devenv 本身的問題而是你的命令行環(huán)境少了 IDE 自動(dòng)注入的變量。比如你是從普通 cmd 里直接敲 devenv 構(gòu)建沒有調(diào)用 vcvarsall.bat那 cl.exe 可能找得到但 INCLUDE 和 LIB 不全編譯器報(bào)找不到頭文件。VS2017 的 IDE 會(huì)自己定位到 VC 工具集但命令行宿主進(jìn)程啟動(dòng)時(shí)還是依賴一組完整環(huán)境。最穩(wěn)妥的做法是用 Developer Command Prompt 來跑 devenv 命令或者在腳本里先調(diào)用 vcvarsall.bat 再執(zhí)行構(gòu)建。還有一個(gè)隱蔽點(diǎn)如果 vcxproj 里的配置名寫的是Debug|x64而你只寫了devenv /Build Debug沒有指定平臺(tái)devenv 可能因?yàn)檎也坏酵耆ヅ涞钠脚_(tái)配置而靜默跳過構(gòu)建。建議把/ProjectConfig Debug|x64完整寫出來減少歧義。5.4 devenv 構(gòu)建產(chǎn)物和 msbuild 不一致的經(jīng)典問題我在實(shí)際項(xiàng)目里遇到過好幾次用 devenv /Build 編出來的 exe 運(yùn)行正常用 msbuild 編出來的卻崩潰或找不到 DLL。對(duì)比之后發(fā)現(xiàn)原因大多是兩種構(gòu)建方式執(zhí)行時(shí)的環(huán)境變量、屬性繼承順序不同或者 msbuild 調(diào)用前沒有初始化 vcvarsall。舉個(gè)例子某些第三方庫(kù)的頭文件搜索路徑是在系統(tǒng)環(huán)境變量里追加的如果 msbuild 在干凈環(huán)境里跑這些路徑丟失編譯產(chǎn)物自然就不對(duì)。處理辦法是讓兩種構(gòu)建盡量走同一套環(huán)境先在 Developer Command Prompt 里初始化 vcvarsall再執(zhí)行 msbuild。如果用的還是同一份 vcxproj沒有額外屬性覆蓋產(chǎn)物應(yīng)該是一致的。如果仍有差異那就該懷疑是不是有 IDE 擴(kuò)展在 devenv 宿主里偷偷改了構(gòu)建參數(shù)這時(shí)候再考慮換用 devenv /Build 保持一致性。5.5 隱藏技巧用 /Log 抓取 devenv 完整日志VS2017 的 devenv 支持/Log參數(shù)可以把整個(gè)加載和構(gòu)建過程輸出到一個(gè) XML 文件。遇到devenv 接住 vcxproj 之后行為詭異的問題比如某個(gè)屬性沒生效、擴(kuò)展包加載失敗直接加一句devenv MyDemo.vcxproj /Build Debug /Log D:\logs\devenv.log然后用文本編輯器打開日志搜索Error、Warning、Load關(guān)鍵詞定位速度會(huì)快很多。這個(gè)技巧在很多官方文檔里只是一句帶過但實(shí)際用起來非常管用。有一次我排查一個(gè)第三方擴(kuò)展在構(gòu)建時(shí)注入錯(cuò)誤配置的問題就是靠這條日志找到的。我個(gè)人在這些年的實(shí)際使用中最大的體會(huì)是不要把 devenv 和 msbuild 當(dāng)成兩個(gè)互相不認(rèn)識(shí)的工具。devenv 能接受 .vcxproj靠的是 Visual Studio 2010 之后項(xiàng)目系統(tǒng)整體遷移到 MSBuild 之上devenv 本身就帶著 MSBuild 引擎。你把項(xiàng)目文件交給它它背后做的第一件事就是按注冊(cè)表映射找匹配的項(xiàng)目工廠再讓 MSBuild 引擎去消化 XML 里的構(gòu)建意圖。理解了這條鏈路以后再遇到文件關(guān)聯(lián)變了命令行項(xiàng)目打不開IDE 和命令行構(gòu)建結(jié)果不一樣這類問題你就不是瞎猜而是知道該往哪個(gè)方向查了。順便說一句VS2017 里如果遇到 vcxproj 加載異常第一反應(yīng)不應(yīng)該是重裝而是先看 ActivityLog 和平臺(tái)工具集版本大部分問題都在這里。