Visual Studio 2026 討厭我們程式碼的那個週末
「跨平台」這個承諾,要等到每個平台的建置都亮綠燈才算數。這是 RakuAI 在 Windows 上兌現承諾的那個週末——也是一份應對六類錯誤的攻略,這些錯誤同樣會撞上你的 C++ 程式碼庫。
有個老笑話說,資深工程師和菜鳥工程師的差別在於,資深工程師動手之前就知道這會是一場和編譯器的搏鬥。這個週末就是一場和編譯器的搏鬥。編譯器是 Visual Studio 2026 Insiders 內附的 Microsoft Visual C++。程式碼庫是 Raku。這場搏鬥橫跨了整整兩天,外加平日的幾次深夜建置。程式碼庫贏了,但只是險勝。
我想把這一段記下來,因為公開的工程日誌應該包含那些事情不順利的日子,而且代理和我一起解決這件事的方式,正是這套工作流程裡我最想聽聽其他人意見的部分。
事情的開端
在這個週末之前,執行環境在 Linux 上用 GCC、在 macOS 上用 Clang 都能乾淨地建置。那兩個一直是我週末開發的主要環境。自從十月初次啟動以來,我其實沒再試過 Windows 的 MSVC 建置。執行環境理應是跨平台的。跨平台就意味著 Windows。所以週六早上,我在一台裝著 Visual Studio 2026 Insiders 的 Windows 機器上簽出了儲存庫,跑了建置。
它沒有建置成功。連接近成功都談不上。第一輪編譯產出了兩百多個錯誤和數量相當的警告。這些錯誤大致落在六個類別,每一類後來都成了各自的 PR。
這些類別是什麼
每一類都值得描述,因為每一類都是任何 C++ 程式碼庫第一次遇到新版 MSVC 時會撞上的東西。其他團隊也會撞上這些。有些團隊可能現在就正在撞。
第一類:Windows 巨集污染我們的命名空間。 Windows 標頭檔會 #define 一堆通用名稱(min、max、DOMAIN、HULL、ERROR、OK、NEAR、FAR),和合理的列舉名稱、函式名稱、模板特化撞在一起。我們的日誌 C API 有一個叫 ERROR 的列舉值。Windows 也把 ERROR 定義成了巨集。編譯器完全按照規格行事,也就是展開巨集然後產出一堆亂碼。解法是在我們的標頭檔之前 #undef ERROR,作用範圍收得很緊。其他幾個名稱也一樣處理。
第二類:MSVC 拒絕編譯帶 BOM 標記的原始檔。 我們有些原始檔開頭帶著 UTF-8 位元組順序標記,多數編譯器都容忍它,而 MSVC 2026 不容忍。代理落地了一次掃蕩,把儲存庫裡每個 C/C++ 原始檔的 BOM 都移除了。
第三類:已棄用的 CRT API。 一堆 C 標準認為沒問題的標準 C 函式,被 MSVC 標記為「已棄用,請使用安全版本」。sscanf 變成 sscanf_s。strncpy 變成 strncpy_s。代理逐一處理,要嘛把呼叫換成安全版本,要嘛在安全版本會以我們不想要的方式改變語意的地方,用適當的 _CRT_SECURE_NO_WARNINGS pragma 包起來。
第四類:DLL 匯出巨集。 最大的一類。每個 DLL 的每個公開符號,在建置該 DLL 時必須標上 __declspec(dllexport),在其他 DLL 取用它時必須標上 __declspec(dllimport)。Linux 的 GCC 和 macOS 的 Clang 不需要這個。MSVC 需要,而我們的程式碼庫有很多地方巨集缺失、套用不一致,或者不小心套用在編譯器根本不想匯出的模板特化上。解法是一次掃蕩,把匯出巨集在每個公開 API 標頭檔中正規化,並在缺少的地方補上。
第五類:RAKU_API 與合併後的 DLL。 比較詭異的一類。我們內部的某些 CMake 設定會在編譯時注入 -DRAKU_API=__declspec(dllexport),連本來就不該匯出的靜態函式庫也被注入。到處都是 MSVC C2491 錯誤。解法是一個 CMake 消毒器,對靜態目標明確取消定義 RAKU_API,只對動態目標設定它。
第六類:OpenXR 標頭檔與 <array>。 少數幾個 MSVC 編譯失敗,是因為用了 <array> 卻沒有明確 include 它。GCC 和 Clang 傾向透過其他 STL 標頭檔間接把它包進來。MSVC 不會,而且無論如何「include what you use」都是正確答案。代理補上了缺少的 include。
這個週末是怎麼過的
週六早上大部分時間,是我在重現每一類錯誤並撰寫診斷筆記。代理面前並沒有一台 Windows 虛擬機。我必須擷取建置輸出、整理乾淨,再連同明確的要求餵回給代理:「這是這一類錯誤,這是一個典型範例,這是它所在的檔案,請提出一個能修掉這一類所有實例的掃蕩方案。」
代理把這些掃蕩處理得很乾淨。PR #408 修了 logging.cpp 裡的 Windows 巨集衝突。PR #406 修了 MSVC 的列舉重複定義。PR #404 加入了 RAKU_API 巨集的 CMake 消毒器。PR #408(arvr_demo_telemetry 裡另一個同號的)修了 Windows DLL 匯出錯誤。PR #411 修了 MSVC 2026 的 DLL 連結錯誤與棄用警告。PR #413 移除了 BOM 並替換了已棄用的 API。PR #415 透過為靜態函式庫加上 RAKU_RUNTIME_EXPORTS 修了 C2491 錯誤。PR #416 用僅標頭檔的 OpenXR 抓取修了 CMake 建置,並補上缺少的執行環境原始檔。PR #401 修了 Windows DLL 匯出巨集,並移除了一個如今已過時的 MSVC 權宜之計。
兩天十二個 PR,上下不差多少。每一個都記錄在執行環境儲存庫的 #404–#416 之下。
週六下午是最漫長的一段。DLL 匯出正規化需要讀遍引擎裡每一個公開 API 標頭檔,並判斷哪些符號真正屬於公開表面。結果有些並不屬於。有幾個在這次工作中被降級為內部符號,即使增加了工作範圍,整體仍是淨收益。
週日投入在一條帶 vcpkg 與 OpenXR SDK 整合的 Windows MSVC CI 工作流程上,讓這種事再也不會無聲無息地發生。現在每一次推送都會觸發 MSVC 建置。建置不可能在沒人於 CI 上看到的情況下倒退。
週日的尾聲是慶祝時刻。當晚闔上筆電之前,Windows 上的建置亮起了綠燈。執行環境史上第一次成功的 x64 建置。提交訊息一字不差地寫著:build: first successful x64 build with OpenXR handle fixes。提交雜湊是 f35f78bd,它是這個專案至今我最喜歡的提交之一。
什麼有效,什麼下次會換個做法
幾條誠實的筆記。
太晚讓新平台上線的代價很高。 我早該在幾個週末之前、程式碼庫還比較小的時候就跑一次 MSVC 建置。那個規模下的錯誤會是二十個,而不是兩百個。修二十個錯誤的成本,明顯小於修兩百個。教訓是:任何目標平台都不要放著超過兩三個週末不建置。
當修法是機械性的,代理驅動的掃蕩就是正確答案。 DLL 匯出掃蕩、BOM 移除、棄用 CRT 替換:這些全都正是代理做得比人類更快、更一致的那種工作。我把每一項都框成「找出模式 X 的所有實例,套用轉換 Y,其餘一律不動」,代理就照做無誤。
當修法需要判斷力,代理驅動的掃蕩就是錯誤答案。 RAKU_API 巨集問題需要判斷哪些 CMake 目標真的想要匯出、哪些不想要。那不是掃蕩。那是逐目標的仔細審視。那一項是我親手做的,代理在每個決定上扮演第二雙眼睛,而不是執行者。
一個模型抓到了另一個模型寫出的錯誤。 在棄用 CRT 替換掃蕩期間,某個代理在一處把 sprintf 換成了 sprintf_s,而那裡的緩衝區大小語意和呼叫端預期的有著微妙差異。PR 落地了。另一個模型的審查在出貨前抓到了這個不一致。多供應商審查工作流程這個週末實實在在地證明了自己的價值。
合作夥伴與開發者能從中帶走什麼
如果你是合作夥伴,正在考慮這個引擎能不能在 Windows 上出貨,截至週日晚上的答案是:能。MSVC 建置是綠的。CI 會讓它一直綠下去。
如果你是開發者,手上有自己的跨平台 C++ 程式碼庫,而且正準備第一次嘗試 Visual Studio 2026 Insiders,上面那六個類別就是即將撞上你的東西。其中大多數你都可以預先防範。現在你知道了。
如果你是 MSVC 團隊的人而且正在讀這篇文章,你們在 2026 Insiders 上做得很好。那些警告真的有用,新工具鏈也比 2022 更好。相容性的破壞大多有充分的理由。要是我時間多一點,我會回報幾個錯誤。它們會來的。
週日晚上,引擎在三個平台上都能乾淨建置。帶著這個走進星期一,再合適不過。
一個執行環境,涵蓋你的眼鏡出貨的每一個平台
RakuAI 在 Linux、macOS 與 Windows MSVC 2026 上都能乾淨建置——這是智慧眼鏡製造商出貨真正空間體驗所需要的跨平台基礎。看看引擎如何鎖定你的硬體。