ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Windows下用VS2017与Meson编译libnice:依赖配置与集成实战

2026/9/18 14:16:52 拓冰建站 浏览量
Windows下用VS2017与Meson编译libnice:依赖配置与集成实战 1. 项目背景与整体思路1.1 libnice到底是干什么的libnice是一个实现ICEInteractive Connectivity Establishment、STUN和TURN协议的C语言库主要服务于实时音视频和P2P通信场景。如果你做过WebRTC网关、SIP客户端或者自研的P2P传输模块多半会和它打交道。简单说它解决的是“两个处于不同网络环境下的设备怎么找到一条可以互通的链路”这个问题NAT穿透和中继转发都能处理。这个库在Linux平台上的构建非常顺滑autotools一套下来基本没毛病。但在Windows上尤其还要配合Visual Studio 2017的MSVC工具链使用就没有那么愉快了。libnice依赖glib而glib在Windows上的构建本身就有一堆ABI、pkg-config和头文件兼容性的讲究。更麻烦的是libnice官方文档几乎没有针对Windows MSVC给出完整步骤很多东西要靠自己试。1.2 为什么是Meson Ninja VS2017这套组合先说VS2017。很多企业项目至今还锁在VS2017上原因可能是历史工程依赖、团队统一工具链也可能是第三方库只提供了v141版本的预编译产物。VS2017的编译器版本是MSVC 19.14-19.16和VS2019、VS2022的ABI不完全兼容所以如果项目里已经有一堆v141的静态库新接入的库也最好用同一套工具链编译否则链接期各种符号冲突和运行时崩溃会让人怀疑人生。再说Meson。libnice从0.1.x版本开始使用Meson作为官方构建系统这是最大的便利。Meson的语法比CMake干净生成Ninja构建文件后编译速度快而且对Windows和MSVC的支持做得相当到位。Ninja负责并行编译和增量构建配合VS2017的命令行环境整个构建流程可以做到全程不打开Visual Studio界面。整体思路是这样的先装好Python、Meson和Ninja再把VS2017的C工具链和Windows SDK准备好然后用Meson生成构建目录设置好依赖glib的查找路径执行编译和安装。下面我把每个环节的细节和坑都拆开讲。2. 编译环境准备2.1 安装Python并配置PATHMeson是Python写的所以Python是必须的。建议安装Python 3.7到3.10之间的版本太老的版本对Meson新版本支持不好太新的版本有时在Windows上会有一些莫名其妙的编码问题。我实测用的是Python 3.8.10稳定没毛病。安装时记得勾选“Add Python to PATH”这样后面在命令行里直接敲python和pip就能用。如果安装时忘了勾选安装完成后手动把C:\Python38和C:\Python38\Scripts加到系统环境变量里然后在命令行执行python --version pip --version确认输出正常再往下走。2.2 安装Meson和NinjaMeson和Ninja都用pip安装最简单一条命令搞定pip install meson ninja这个操作会同时安装最新版本的Meson和Ninja。Meson的当前版本迭代非常快如果你是从老项目的文档里看到某个特定版本号不要盲目升级。有些老项目用的Meson版本过低生成的构建文件和最新Ninja不兼容但这种情况在libnice上没遇到直接装最新版即可。装完之后验证一下meson --version ninja --version如果powershell或者cmd提示无法识别命令多半是Scripts目录没有在PATH里回上一节检查。提示Ninja在Windows上是通过pip下载预编译exe的不需要额外配置。如果pip安装失败也可以直接从Ninja的GitHub Release页面下载ninja-win.zip解压后把ninja.exe放到任意已经在PATH里的目录比如C:\Python38\Scripts。2.3 VS2017 C工具链配置VS2017的安装方式有两种一种是Visual Studio Installer的图形界面安装一种是离线包安装。无论哪种关键是要确保工作负载里勾选了“使用C的桌面开发”里面包含MSVC v141工具集、Windows 10 SDK和C CMake工具。很多人只安装了.NET开发负载装完发现根本没有cl.exe这就是工具链缺失。确认方法是在开始菜单找到“Developer Command Prompt for VS 2017”打开后在命令行里执行cl如果输出一长串用法说明说明编译器可用。如果提示“不是内部或外部命令”说明环境不对要么重装负载要么确认你打开的是VS2017的开发者命令行而不是普通cmd。这里我想强调一个Windows构建C/C项目的基本规律编译器的include路径和库路径不是靠PATH环境变量实现的而是通过INCLUDE和LIB环境变量或者靠vsdevcmd批处理脚本临时设置。所以后续所有Meson和Ninja命令都要在这个开发者命令行里执行除非你手动把环境变量都配好。2.4 获取libnice源码libnice的源码托管在GitLab上项目地址是https://gitlab.freedesktop.org/libnice/libnice。clone的时候建议带--depth1只拉最新代码git clone --depth1 https://gitlab.freedesktop.org/libnice/libnice.git cd libnice为什么不建议用release tarball因为某些release包里的meson.build文件存在小瑕疵新版本Meson会亮warning但能继续编译旧版本直接报错。直接拉最新源码最省心反正libnice的API很稳定master分支不会三天两头变。如果你有版本洁癖想固定某个tag可以这样git clone --depth1 --branch 0.1.21 https://gitlab.freedesktop.org/libnice/libnice.git0.1.21是我印象中比较稳定的版本功能完备配VS2017编译没有问题。3. 依赖准备Windows上最难的一环3.1 libnice需要哪些依赖libnice最核心的依赖是glib-2.0这个绕不开。STUN、ICE的实现都依赖glib提供的事件循环、数据结构GList、GHashTable和内存管理宏。此外它还需要libm、ws2_32、iphlpapi之类的系统库这些在Windows SDK里都有不用额外折腾。问题全出在glib上。glib在Windows MSVC环境下没有官方的二进制发布包你需要自己想办法弄到一份用MSVC编译的glib而且版本不能太老否则libnice代码里用的g_autoptr、g_autofree这些新特性会编译不过。3.2 用vcpkg准备MSVC版的glibvcpkg是微软官方维护的C/C包管理器用一条命令就能编译并安装glib而且生成的是MSVC ABI兼容的库和VS2017搭配完美。具体操作首先把vcpkg clone到本地git clone https://github.com/microsoft/vcpkg.git cd vcpkg bootstrap-vcpkg.batbootstrap完成后安装glib。注意如果想编译64位的libnice就安装x64版本的glib如果想用32位则安装x86版本。绝大多数情况下建议直接做64位vcpkg install glib:x64-windows这一步会花很长时间因为vcpkg会把glib以及它的依赖链全部从源码编译一遍包括libffi、gettext、zlib等。机器性能好的话大概十几分钟差的话可能半小时以上。提示vcpkg默认编译动态链接库你会在installed\x64-windows\bin下看到glib-2.0.dlllib目录下是导入库glib-2.0.lib。如果希望 libnice 和 glib 都使用静态库可以用vcpkg install glib:x64-windows-static这样生成的DLL数量会少很多部署更干净。但静态编译时 glib 需要开启-Dglib-asserts之类的选项可能会碰到一些配置问题第一次建议先用动态库把流程跑通。3.3 pkg-config的查找路径配置Meson在配置阶段通过pkg-config来查找glib所以必须让pkg-config能发现vcpkg安装的glib的.pc文件。vcpkg的glib包在installed\x64-windows\lib\pkgconfig目录下有三个文件glib-2.0.pc、gobject-2.0.pc、gio-2.0.pc。我们在执行Meson配置命令之前先设置环境变量set PKG_CONFIG_PATHC:\path\to\vcpkg\installed\x64-windows\lib\pkgconfig同时还要设置PATH让pkg-config能找到glib的DLL。因为pkg-config工具本身是通过pkg-config --cflags --libs glib-2.0这种命令来查询编译参数的而Meson会自己在内部调用pkg-config。不设置PATH的话即使能找到.pc文件也会因为给出的-prefix路径下的bin目录找不到DLL而在运行阶段报错set PATH%PATH%;C:\path\to\vcpkg\installed\x64-windows\binvcpkg的include目录不需要手动加到INCLUDE环境变量里Meson会从pkg-config返回的参数中自动提取头文件路径和库文件路径。3.4 pkg-config工具的来源Windows系统默认没有pkg-config命令。vcpkg也提供了pkg-config的port可以直接安装vcpkg install pkgconf:x64-windows或者你不想装pkg-config也没关系Meson自带了对pkg-config的兼容处理只要你设置了PKG_CONFIG_PATH并且系统里存在一个可用的pkg-config或pkgconf程序即可。vcpkg安装的pkgconf二进制在installed\x64-windows\tools\pkgconf目录下记得把这个目录也加到PATH里。如果实在不想用vcpkg的pkgconf用MSYS2自带的pkg-config也可以但要注意MSYS2的pkg-config在解析路径时会把MSYS风格的路径转换成Windows风格有时候会给出/mingw64/include这种路径MSVC编译器不认。所以还是用vcpkg的pkgconf最省事。注意绝对不要用MSYS2的MinGW版glib直接给MSVC工程链接。MSYS2的glib是用GCC编译的ABI和MSVC不兼容即使程序能链接成功运行时内存布局和异常处理方式不一致会出现非常难排查的随机崩溃。4. 实操编译libnice全过程4.1 打开VS2017开发者命令行并激活环境所有编译步骤都要在“Developer Command Prompt for VS 2017”里执行。这一步很关键因为只有这个命令行才能让cl.exe、link.exe以及vcvarsall.bat设置的环境变量生效。打开命令行后先确认当前环境是x64还是x86。VS2017的开发者命令行默认是x86编译器环境也就是编译出的程序是32位。如果你要编译64位libnice最好直接执行以下命令切换到x64编译器环境vcvarsall.bat x64这条命令在VS2017的安装目录下C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Auxiliary\Build\vcvarsall.bat具体路径取决于你安装的是Professional版还是Community版。执行成功后再用cl命令验证一下。4.2 配置Meson构建目录进入libnice源码目录后开始Meson配置meson setup build --backendninja -Dtestsdisabled -Dexamplesdisabled -Dgstreamerdisabled我来解释这几个参数的含义build是构建目录名可以随便取但建议和源码目录区分开这样源码目录始终保持干净。--backendninja显式指定使用Ninja后端。-Dtestsdisabled关闭测试用例的编译。测试代码依赖的gtest和check框架在Windows上很难搞不关的话十有八九会编译失败。-Dexamplesdisabled关闭示例程序的编译示例代码里用到的一些GStreamer和GObject特性与MSVC兼容性一般。-Dgstreamerdisabled关闭GStreamer插件支持。libnice可以编译成GStreamer的插件但那需要先编译GStreamer依赖链太长最初建议先关掉。执行完配置命令Meson会扫码当前系统的编译器和依赖。如果一切顺利终端里会显示类似这样的信息Build targets in project: 17 Subprojects: none如果配置失败大概率卡在“Run-time dependency glib-2.0 found: NO”这一步。这个问题怎么解决我在下一章单独讲。4.3 执行Ninja编译配置成功后就简单了ninja -C buildNinja会调用MSVC的cl.exe执行并行编译。libnice这个库本身源码量不算大大约一两分钟就能完成。看到类似下面的输出就说明编译成功[17/17] Linking target nice.dll编译成功后在build目录下会生成这些关键产物nice/libnice-0.1.dll或类似名字的动态库nice/libnice.dll.a这个是MinGW格式的导入库MSVC不用它nice/nice.libMSVC格式的导入库nice/nice.pdb调试符号文件nice/agent.h、nice/nice.h等头文件会被自动复制到构建目录下等等这里有个细节要确认MSVC编译出的动态库导入库命名到底是nice.lib还是libnice.lib取决于meson的library()函数设置。libnice的meson.build里对Windows有特殊处理生成的具体名字你在构建输出里能看到以实际生成的为准。4.4 执行安装编译完成后执行安装命令把头文件和库文件统一放到一个干净的目录里ninja -C build install默认安装到C:\Program Files\libnice。如果你想自定义安装目录在Meson配置时加上前缀参数meson setup build --prefixC:/local/libnice--prefix指定的路径不要太复杂建议不要带空格否则后续在VS工程里引用头文件目录和库目录时会因为路径解析问题踩坑。安装完成后检查一下C:\local\libnice下面是否有include\nice和lib目录include\nice下应该有agent.h、address.h等头文件。lib目录下应该有nice.lib和libnice-0.1.dll。5. 在VS2017工程里集成libnice5.1 配置包含目录和库目录在VS2017中新建或打开一个工程进入项目属性页“C/C” - “常规” - “附加包含目录”添加C:\local\libnice\include。“链接器” - “常规” - “附加库目录”添加C:\local\libnice\lib。“链接器” - “输入” - “附加依赖项”添加nice.lib。如果libnice是动态编译的还需要把libnice-0.1.dll复制到exe输出目录或者放到系统PATH里。另外libnice依赖glib所以glib的include目录和glib-2.0.lib也需要一并配置。glib的头文件比较特殊它分两层glib-2.0/glib.h是主头文件但实际实现头文件在glib-2.0/include目录下所以vcpkg的glib需要添加两个包含路径C:\path\to\vcpkg\installed\x64-windows\include\glib-2.0 C:\path\to\vcpkg\installed\x64-windows\lib\glib-2.0\include第二个目录用来放glibconfig.h这个文件是构建时自动生成的缺了它会报一堆“找不到g_type”之类的诡异错误。5.2 运行时依赖处理当你在VS2017里启动程序发现运行时报“程序无法启动因为缺少glib-2.0-0.dll”不要慌。把vcpkg的DLL目录、libnice的DLL目录都加到可执行文件所在目录。为了省心我通常建一个third_party_bin目录把libnice-0.1.dll、glib-2.0-0.dll、gio-2.0-0.dll、gobject-2.0-0.dll、intl-8.dll等所有运行期DLL统一拷贝一份然后把这个目录加到PATH里。5.3 写一个最小验证程序集成后建议先写一段最小的代码验证库可用不要上来就接完整业务逻辑。我这里用一个最常见的初始化流程#include nice/nice.h #include glib.h int main(void) { NiceAgent *agent; gchar *stream_id_str; agent nice_agent_new(g_main_context_default(), NICE_COMPATIBILITY_RFC5245); if (agent NULL) { g_print(Failed to create agent\n); return 1; } guint stream_id nice_agent_add_stream(agent, 1); stream_id_str g_strdup_printf(%u, stream_id); g_print(Stream ID: %s\n, stream_id_str); g_free(stream_id_str); g_object_unref(agent); return 0; }编译并运行如果能看到Stream ID: 1或者某个递增的数字输出说明库的链接和运行基本没问题。6. 常见问题与排查技巧6.1 Meson配置阶段报“glib-2.0 not found”这个是最常见的问题。Meson输出类似Run-time dependency glib-2.0 found: NO (tried pkgconfig)排查步骤第一确认PKG_CONFIG_PATH是否指向了真正的.pc文件所在目录。注意vcpkg安装的不同架构库在不同路径下x64-windows的是installed\x64-windows\lib\pkgconfig静态库版本是installed\x64-windows-static\lib\pkgconfig用错了体系就找不到。第二在命令行执行pkg-config --modversion glib-2.0如果pkg-config命令都找不到说明vcpkg的pkgconf没加到PATH或者系统里根本没有pkg-config。如果命令能执行但没有输出说明.pc文件的路径配置不对。第三如果.pc文件能读取但版本太老libnice要求glib-2.0的版本通常要在2.38以上vcpkg默认安装的版本肯定满足但如果手头用的一个很老的自编译glib就要更新了。6.2 编译时报“g_autoptr was not declared”这个错误说明glib的版本太旧或者include路径没有正确指向新版本glib的头文件。重点检查glibconfig.h是否被找到。这个文件放在lib\glib-2.0\include目录下也就是上面提到的第二个include路径。一个常见的坑是VS工程里不小心把MSYS2的/mingw64/include路径加到附加包含目录和vcpkg的glib头文件混在一起。由于两者的glibconfig.h内容不同编译器可能会先找到MinGW版的头文件导致后续一堆宏定义冲突。解决方案是确保附加包含目录里只有一个glib版本。6.3 链接阶段报“unresolved external symbol g_xxx”根本原因是glib的库没有按正确顺序链接。MSVC的工具链不像GCC那样可以自动做符号段的无序合并链接顺序是严格的。把glib-2.0.lib、gobject-2.0.lib、gio-2.0.lib都加到“附加依赖项”并按glib在前、gobject在后的顺序排列。另外如果编译32位版本要检查vcpkg安装的是x86架构的glib同时VS2017的开发者命令行也要切换到x86环境。两者架构不一致链接同样报错而且错误信息不容易看出来经常显示一堆重复符号或无法解析的外部符号。6.4 Ninja构建时报“fatal error C1083: Cannot open include file: ws2tcpip.h”这说明Windows SDK的include路径没有在环境变量里。在VS2017开发者命令行里通常不会这个问题但如果你设置了INCLUDE环境变量来覆盖默认值就可能把SDK的路径弄丢。解决办法是重置环境变量set INCLUDE set LIB然后重新执行vcvarsall。更好的做法是彻底清理当前命令行窗口重新打开一个新的开发者命令行再执行Meson配置。6.5 运行时报缺少DLL这个分两种情况。一种是缺少libnice的DLL直接从构建目录或者安装目录拷贝到exe目录即可。另一种是缺少glib或它的间接依赖DLL比如libffi-8.dll、intl-8.dll、zlib1.dll。这些DLL通常在vcpkg的installed\x64-windows\bin目录下。可以把整个bin目录拷贝到项目输出目录或者写一个脚本每次编译后自动同步。我这里提供一个简单的bat脚本echo off set VCPKG_BINC:\path\to\vcpkg\installed\x64-windows\bin set OUTPUT_DIR%1 if %OUTPUT_DIR% set OUTPUT_DIR.\x64\Debug copy /Y %VCPKG_BIN%\libffi-8.dll %OUTPUT_DIR% copy /Y %VCPKG_BIN%\glib-2.0-0.dll %OUTPUT_DIR% copy /Y %VCPKG_BIN%\shortcuts-1.dll %OUTPUT_DIR% copy /Y %VCPKG_BIN%\intl-8.dll %OUTPUT_DIR% copy /Y %VCPKG_BIN%\zlib1.dll %OUTPUT_DIR% copy /Y C:\local\libnice\bin\libnice-0.1.dll %OUTPUT_DIR% echo Done.把路径改成你本机的实际路径在VS的后期生成事件里调用这个脚本每次编译完自动拷贝省心很多。6.6 增量构建不生效每次从头编译Ninja本身有增量构建能力但如果你直接把源码目录拖到另一个路径下或者把构建目录删除重建Ninja会认为是全新构建这是正常的。还有一种常见情况Meson的构建目录和源码目录在同一个文件夹的嵌套关系被破坏Ninja的depfile找不到依赖信息会强制全量编译。确保你执行的命令是ninja -C build而不是在源码目录下裸执行ninja。另外修改了meson.build文件后重新执行meson setup build --reconfigure再编译。如果你改了编译选项比如开关不同功能一定要重新跑Meson配置不要手动去改Ninja生成的build.ninja文件否则很容易搞坏依赖关系。7. 一些补充建议和扩展方向7.1 如果必须静态链接vcpkg安装静态版glib的命令是vcpkg install glib:x64-windows-static然后在Meson配置时通过PKG_CONFIG_PATH指向静态版的pkgconfig目录。静态链接时VS工程的“运行库”设置要和glib的编译设置保持一致建议统一用/MTRelease和/MTdDebug。但这里有个小坑libnice在静态编译时如果你调用nice_agent_new后程序退出控件会报“GLib-GIO:ERROR:gdbusconnection.c:XXXX:g_dbus_connection_real_closed: assertion failed”之类的错误。这通常是因为静态链接时GIO的初始化函数没有被正确引入需要在编译命令里通过-DG_STATIC_COMPILE宏定义来规避。Meson的add_project_arguments通常会处理这个问题但如果自己手工改构建配置容易漏掉。7.2 如果项目必须依赖GStreamerlibnice的GStreamer插件功能在某些项目里是刚需但GStreamer在Windows MSVC下的构建难度比glib还高一个量级。我的建议是如果业务不强制要求通过GStreamer管线直接调用libnice就放弃这部分功能。libnice的核心ICE、STUN、TURN能力完全不依赖GStreamer你可以用C API直接在应用层调用。如果确实需要GStreamer的nicebin元素可以考虑用GStreamer官方提供的MSVC预编译运行时再配合libnice的插件源码做二次开发但这条路比较曲折不太适合快速落地。7.3 调试segment异常的小技巧Windows上调试ICE相关的P2P程序经常遇到程序莫名崩溃。很多情况下不是libnice本身的bug而是glib的断言机制在MSVC下的表现和GCC不一样。遇到的问题可以先开一个环境变量set G_DEBUGfatal-warnings这个环境变量会让glib在警告时直接abort方便你抓堆栈。如果开了之后问题定位到g_log相关函数多半是某个对象没有初始化就传入libnice的API检查一下GMainLoop和GMainContext是否都初始化完整。7.4 后续可以扩展的方向编译通过只是第一步libnice真正要落地还需要考虑ICE候选收集策略、TURN服务器的接入、连接检查超时设置等。在Windows这种多网卡的平台上尤其要注意nice_agent_set_software可以用来设置信令中的软件版本方便排查时区分不同的客户端。如果你后续要在项目里做多实例并发建议把每个NiceAgent绑定到独立的GMainContext上这样可以做到多线程隔离避免全局锁带来的性能瓶颈。libnice的线程安全性本身做得不错但MSVC编译版本下默认的glib线程模型是老式的g_thread_init在创建线程前需要确保glib库版本支持GPrivate和GThread的静态初始化。我个人在实际操作中的一个心得是第一次编译这类跨平台库最好全程开着Process Monitor观察编译进程的CreateFile调用。很多时候一个头文件找不到或者link阶段说无法打开文件“xxx.lib”根本不是真的缺失只是路径拼写多了一个反斜杠或者目录层级差了一层。Windows下路径分隔符的反斜杠在字符串里经常被转义掉在设置环境变量时尽量用正斜杠或者双反斜杠能省掉一大批莫名其妙的路径问题。这个流程跑通之后以后在Windows上编译任何基于glib的项目都会轻松很多因为关键的依赖获取、pkg-config配置、MSVC工具链对接这三个环节你已经有了一套可复用的模板。就算以后换VS2022也只是换一条vcvarsall的路径而已整体思路完全一致。