1. 为什么今天还有人在装 Visual Studio 2019
Visual Studio 2019 这个"老伙计",在 Visual Studio 2022 已经铺开好几年的今天,依然活跃在很多团队的生产环境里。我做过的几个项目里,有的产线设备驱动工具链只认它,有的老 .NET Framework 项目升级一次要动几百个文件,最后大家一致决定:留着 2019,别折腾。它内部版本号是 16.x,C++ 工具集是 MSVC v142,支持到 .NET Core 3.1、.NET 5(16.8 之后)以及 .NET Framework 4.8,对 C++14/C++17 是完整支持的,C++20 也塞进去了一部分(概念、协程在 16.3 之后陆续可用,模块是预览状态)。
如果你是新入行的开发者,可能会疑惑:不是有更新的版本吗?道理很简单——工具的价值不在于版本号,而在于生态匹配度。Visual Studio 2019 的 SDK 版本矩阵、第三方插件兼容性、构建服务器(CI)上的镜像可用性,都已经非常稳定了。很多企业的内网构建机镜像,早在 2020 年前后就固化了 2019 的版本,改一次要过一轮安全审计和回归测试,成本不低。
这篇内容我想聊的是实操层面的东西。适合谁看?三类人:一是接手老项目需要立刻跑起来的;二是被编译编码问题折磨过的 C++ 或 C# 开发者;三是在服务器上装 MySQL、跑 CMake、跑 node-gyp 时被"找不到 Visual Studio"报错拦住的人。我会把安装规划、UTF-8 编码设置、命令行构建、报错排查这几个高频场景拆开讲清楚,包含我自己踩过的坑和当时没找到答案、后来才想明白的细节。
1.1 Community、Professional、Enterprise 三个版本到底怎么选
大多数人直接装 Community 就行,它是免费的,个人开发者、学术研究、小型团队(我记得是五人以内的企业组织)都可以合法使用。功能上它和 Professional 的差距主要在协作和高级调试方面,日常写代码、调试、做单元测试完全够用。
Professional 和 Enterprise 走的是订阅制。Professional 主要多了 CodeLens 的一些协作特性、更完整的架构验证工具。Enterprise 才是真正的"全家桶"——Live Unit Testing、IntelliTrace 快照调试、代码克隆检测、Fakes 隔离框架这些都在这一档。我个人的判断标准很粗暴:如果你需要在不重启调试会话的情况下回看历史执行快照,或者团队在做大型遗留系统的重构,那 Enterprise 才有意义;否则那笔订阅费花得不值。
有一个容易被忽略的点:三个版本可以共存安装在同一台机器上,安装器会给它们分配不同的目录(Community、Professional、Enterprise),DevEnv 的实例 ID 也是分开的。但我不建议这么做,除非你确实在做跨版本兼容测试。原因是共享组件目录(Shared)会被多个版本争抢更新,曾经出现过装完 Enterprise 之后 Community 的某个 SDK 被悄悄降级的情况,排查起来很头疼。
1.2 与 VS2017、VS2022 之间的取舍逻辑
VS2017 和 VS2019 的 C++ 工具集是二进制兼容的(v141 和 v142 在同一个 "MSVC 兼容集" 里),链接的时候可以混用,这是微软当年一个很聪明的设计。所以如果某个第三方静态库只有 2017 编译出来的版本,用 2019 链接通常不会出问题。但反过来,VS2022 的 v143 工具集和 v142 就不保证这种兼容了,混链接大概率会撞到 CRT 版本冲突。
VS2022 是 64 位进程,这个变化带来的实际好处是:大型解决方案(几千个 C++ 源文件)的 IntelliSense 不再动不动就内存爆掉。而 VS2019 的 IDE 主体是 32 位程序,物理上装在Program Files (x86)目录下,处理超大工程时确实会喘。但它换来了兼容性——VS2019 能跑在 Windows 7 SP1 及以上系统,VS2022 的官方最低要求是 Windows 10 1909。如果你的构建机还挂着 Windows Server 2012 R2,那基本没有选择余地。
还有一类场景是 SDK 锁定。比如某些厂商的板级支持包、仿真器插件、老版本的 CUDA 工具链,它们的安装脚本会去注册表里找VisualStudio\2019这个键。这种时候装新版本没用,脚本找不到就是找不到。所以我的建议是:新项目直接上 2022,老项目原地不动,需要跨版本时用vcvarsall.bat切换环境,而不是反复卸载重装。
2. 安装规划:装什么、不装什么,这步错了后面全是坑
Visual Studio 2019 的安装体积可以从 5GB 到 60GB 不等,差距全在你勾了哪些工作负载。我见过最夸张的一台机器,工程师把"移动开发""游戏开发""数据存储和处理"全勾上,装完 C 盘直接少了 80 个 G,结果项目只用到 C++ 桌面开发。所以安装这一步值得花二十分钟认真规划。
另外一个常见误区是"先全装,用不到再说"。Visual Studio 的工作负载不是简单堆文件,它会注册大量的项目模板、SDK 探测路径、MSBuild 目标文件。装得越多,项目属性的下拉框越乱,IntelliSense 解析的时候要扫描的目录也越多,首次打开解决方案的等待时间会明显变长。所以精确勾选不只是省磁盘,也是省时间。
2.1 工作负载勾选策略:按项目类型反推
我的习惯是先列清楚项目需要什么,再倒推勾选。判断方法很简单:打开现有解决方案的.vcxproj或.csproj,看它引用了哪些 SDK 和工具集。
- 纯 C/C++ 桌面程序或库:只需要"使用 C++ 的桌面开发"这一个工作负载。右侧细节里会自动带上 MSVC v142 生成工具和 Windows 10 SDK,这两样是关键。
- .NET Framework 或 .NET Core 应用:勾".NET 桌面开发",里面包含 WPF、WinForms 和 .NET Framework 4.8 的目标包。如果是 Web API 或 ASP.NET Core,再加"ASP.NET 和 Web 开发"。
- 需要 MFC/ATL 的老项目:必须在单个组件里额外勾"C++ MFC 最新版 v142"和"C++ ATL 最新版 v142"。这两个不在默认推荐里,漏勾的典型症状是打开项目时提示"找不到 afxwin.h"。
- 要编译 Python 的 C 扩展或跑 node-gyp:勾"使用 C++ 的桌面开发"就够了,但必须确认 MSBuild 和 Windows SDK 都被勾上,这是后面那类"找不到 Visual Studio 实例"报错的根源。
- Python 开发本身:可以勾"Python 开发"工作负载,但说实话,我更倾向于只装 Python 官方发行版 + VS Code,让 VS2019 专注做它擅长的编译和调试。
注意:勾选工作负载后,右侧的"安装详细信息"面板会出现"包括推荐的组件"和"可选组件"两栏。很多人只看左边的大类,忽略了右边,结果装完发现缺东西。养成习惯,装之前把右侧列表从上到下扫一遍。
2.2 单个组件粒度微调与磁盘占用控制
工作负载之上,还可以通过"单个组件"标签页做精细加减。这里我说几个实际会用到但容易被忽视的组件。
C++ CMake 工具(组件 ID 里带Microsoft.VisualStudio.Component.VC.CMake.Project)——如果你用 CMake 管理工程,这个东西一定要勾。它不只是让 VS 能打开CMakeLists.txt,更重要的是提供了一套 CMake 与 MSBuild 之间的桥接逻辑。缺了它,VS 只会把 CMake 文件当普通文本,调试配置也不会自动生成。
适用于 Windows 的 C++ Clang 工具——这个看情况。想用 Clang 做静态检查或者交叉编译到其他平台的,勾上;纯粹 MSVC 技术栈的,可以不勾,能省两三个 G。
Git for Windows 和 NuGet 包管理器——默认自带,别取消。特别是 NuGet,C# 项目九十以上的第三方依赖都靠它。
关于磁盘位置,安装器允许分离三个路径:IDE 安装目录、下载缓存目录、共享组件目录。我的经验是:
# 启动安装引导程序时指定路径,避免全塞进 C 盘 vs_community.exe ^ --installPath "D:\VS2019\Community" ^ --sharedInstallPath "D:\VS2019\Shared" ^ --cache "D:\VS2019\Cache"加--cache false还能直接禁用本地缓存,但我不建议——离线修复、重装、加组件的时候,没有缓存就得重新下载。缓存在 SSD 上留 5 到 10GB 是划算的。
2.3 离线布局制作与内网部署
企业内网、无外网权限的构建机,装 VS 唯一可行的路子是离线布局。做法是用一台能上网的机器下载完整包,再拷贝进去。命令行大概是这样:
vs_community.exe --layout D:\vslayout ^ --lang zh-CN en-US ^ --add Microsoft.VisualStudio.Workload.NativeDesktop ^ --add Microsoft.VisualStudio.Workload.ManagedDesktop ^ --includeRecommended--add后面跟工作负载或组件 ID,--includeRecommended表示把推荐组件一起拉下来。不写这一条,装出来的环境会缺一堆东西,然后在项目加载时才暴露出来。语言包按需加,--lang zh-CN en-US两个都下,中文界面加英语报错信息翻译,这个组合用起来最舒服。
下载完之后,目标机器上执行:
D:\vslayout\vs_community.exe --noweb --add Microsoft.VisualStudio.Workload.NativeDesktop--noweb是强制离线,一旦有缺失会明确报错而不是偷偷去联网。整包体积通常在 15GB 到 40GB 之间,用移动硬盘拷之前记得确认文件系统——FAT32 单文件上限 4GB,某些 SDK 的 cab 包会超,务必用 NTFS 或 exFAT。
提示:离线布局下载过程中如果网络中断,重新执行同样的命令会断点续传,不会从头开始。这个特性救过我好几次。
3. 把 VS2019 的编码彻底改成 UTF-8,别只改一半
编码这件事,是中文开发者踩坑最多的地方,没有之一。典型症状我都遇到过:源码里中文注释在别人机器上变成一堆问号;字符串常量里的中文编译出来是乱码;编译时报C4819警告说文件包含当前代码页无法表示的字符;更诡异的直接报C2001: 常量中有换行符,明明那行代码好端端地待着。
问题的根源在于,MSVC 编译器判断源文件编码的逻辑是:先看有没有 BOM,有 BOM 就按 BOM 指定的编码解析;没有 BOM 就按系统当前的 ANSI 代码页解析。简体中文 Windows 的默认代码页是 936(GBK),所以一个 UTF-8 无 BOM 的源文件,编译器会当 GBK 读,一个汉字三个字节被强行拆成 GBK 的双字节序列,读出来的自然是乱码,还可能把某个字节误判成引号或换行符。
3.1 编辑器设置和文件保存编码,两步都要做
很多人只知道改一处,实际上要分两层:一是让编辑器正确识别和保存文件,二是让编译器按 UTF-8 处理源文件。
编辑器层面,打开"工具 → 选项 → 文本编辑器 → 常规",把"自动检测不带签名的 UTF-8 编码"勾上。这个选项的意思是:读取文件时,如果内容看起来像合法的 UTF-8,就按 UTF-8 处理,而不是死按 GBK。它对读取有奇效,但对保存没有约束力。
保存层面,VS 默认的保存编码是"简体中文 (GB2312) - 代码页 936",这个必须改。改的地方藏得比较深——"文件 → 高级保存选项"。如果菜单里没有这一项,需要在"工具 → 自定义 → 命令"里,从"文件"类别中把它拖出来。在对话框里把编码改成"UTF-8 带签名 (BOM)",然后保存。
对已经有几百个源文件的老项目,一个个点不现实。有两个更高效的办法。
一是用.editorconfig统一约定。在解决方案根目录放一个文件,内容:
root = true [*] charset = utf-8-bom end_of_line = crlf insert_final_newline = true indent_style = space indent_size = 4VS2019 原生支持读取.editorconfig(16.x 早期版本需要装 EditorConfig Language Service 扩展,后期已经内置)。新创建的文件会遵循这个约定。注意utf-8-bom和utf-8的区别——前者带 BOM,后者不带。
二是批量转换已有文件。用一个 PowerShell 脚本扫一遍,把无 BOM 的 UTF-8 和 GBK 文件统一成 UTF-8 with BOM:
# 把指定目录下的 .cpp/.h 文件统一转成 UTF-8 with BOM $utf8Bom = New-Object System.Text.UTF8Encoding($true) Get-ChildItem -Path "D:\Project\src" -Recurse -Include *.cpp,*.h,*.hpp | ForEach-Object { $content = [System.IO.File]::ReadAllText($_.FullName, [System.Text.Encoding]::Default) [System.IO.File]::WriteAllText($_.FullName, $content, $utf8Bom) }这段脚本的前提是这些文件当前确实是 GBK 编码(用Encoding::Default读)。执行前务必备份,否则一旦猜错编码,原文会被破坏得无法恢复。
3.2 编译期加 /utf-8 才是治本手段
就算文件都带了 BOM,我仍然建议在项目级别显式指定编码,因为你要防的是以后有人用别的编辑器创建了无 BOM 文件,或者 CI 上用命令行工具生成了源文件。
做法是在项目属性里:C/C++ → 命令行 → 其他选项,填入:
/utf-8这一个参数等价于同时指定/source-charset:utf-8 /execution-charset:utf-8。它告诉编译器:源文件按 UTF-8 读,字符串常量按 UTF-8 写进目标文件。如果你想更精细控制,也可以分别写:
/source-charset:utf-8 /execution-charset:utf-8第一次看到"执行字符集"这个概念容易懵。类比一下:源字符集是"你怎么把字念出来",执行字符集是"你写进最终产物里的是什么形态"。两者都设成 UTF-8,才能保证从源码到二进制全程一致。
如果是 CMake 工程,加在CMakeLists.txt里:
if (MSVC) add_compile_options(/utf-8) endif()如果代码里用了L"中文"这种宽字符串,还需要注意/execution-charset对宽字符的影响。宽字符走的是 UTF-16,只要源字符集对了,宽字符串基本不会出错,这一点比窄字符串省心。
3.3 几个容易漏掉的编码细节
控制台输出乱码。程序里printf的中文在控制台显示成问号,往往不是编译问题,而是控制台代码页。运行前执行chcp 65001切到 UTF-8 代码页,或者在代码里调用SetConsoleOutputCP(CP_UTF8)。不过要注意,切了 65001 之后,某些老的控制台字体渲染会有问题,实际交付给客户时我更倾向于在程序启动时判断并设置,而不是依赖外部环境。
JSON、XML 配置文件的 BOM。带 BOM 的 JSON 在某些解析库(尤其是用 C 写的轻量库,比如 cJSON、RapidJSON 的部分用法)里会解析失败,因为 BOM 被当成非法字符。所以源文件用 UTF-8 with BOM,但配置文件不要加 BOM。这是个反直觉的约定,我曾经因为统一脚本把 JSON 也加了 BOM,导致生产环境一个配置读取模块挂了两小时。
Git 的换行和编码。.gitattributes里加上*.cs text eol=crlf之类的规则会更稳妥。真正的编码问题 Git 管不了,它只存字节。但换行符如果混了 LF 和 CRLF,某些对行尾敏感的代码(比如 C# 的逐行字符串)会表现不一致。
数据库和日志文件的编码。程序内部是 UTF-8,但写进 MySQL 的时候,如果连接串没指定characterEncoding=utf8,驱动可能按系统代码页转一次,中文就成乱码了。这是跨系统协作时最常见的"我本地是好的",排查的时候永远先看连接层。