1. 项目概述:为什么要在Deepin上折腾wxWidgets
如果你用的是Deepin系统,又恰好需要开发跨平台桌面软件,那 wxWidgets 这个名字你一定不会陌生。它是一个老牌的C++ GUI框架,和Qt齐名,但走的完全是另一条路——它不搞自己的信号槽机制,不强制你使用特定的编译器,而是直接封装各平台的原生控件。换句话说,你在Windows上写好的界面逻辑,拿到Deepin上重新编译一遍,出来的就是深度系统原生风格的窗口,观感和手感都跟系统自带应用几乎一致。
我这次折腾的目标很简单:在Deepin 20.x(基于Debian 10)上,从源码编译一套wxWidgets 3.1.x,用来配合Code::Blocks做日常的桌面工具开发。之所以不直接apt install libwxgtk3.0了事,是因为Deepin软件仓库里的版本偏老,而且有些我自己写的程序依赖了3.1.5之后才有的接口——比如wxImage::ResampleNearest的行为修正和wxBitmapBundle的一些特性。直接用系统包,一方面是接口不全,另一方面是想顺便把库编译成-std=c++17,方便跟现代代码混用。
这篇文章不打算做成那种“照着敲命令就能过”的流水账,而是想把我编译过程中踩过的坑、查过的源码、对比过的配置都写清楚。尤其是Deepin这种基于Debian又自己改了一堆依赖的系统,经常会出现“包管理器说有,但编译时头文件找不到”的鬼畜情况。我会把每一步的原因和排查思路一并列出来,希望对同样在国产Linux发行版上做C++开发的朋友有点参考价值。
适合谁看:想在Deepin、UOS及其他Debian系发行版上自行编译wxWidgets的开发者;被系统包版本困扰、需要定制编译参数的老手;以及刚从Windows转过来、想搞明白Linux下C++ GUI项目怎么构建的新手。
2. 编译之前的准备:环境检查与依赖梳理
2.1 先确认你到底需要哪个版本的wxWidgets
wxWidgets的版本选择其实挺讲究。目前主线稳定版是3.1.x系列,3.2.x还在开发中,2.8.x和3.0.x属于老维护分支。如果你不是为了折腾新接口,直接用Deepin仓库里的3.0.x完全够用——那个版本稳定性极高,几乎所有的入门教程和示例代码都是基于3.0写的。
但如果你跟我一样,想用wxBitmapBundle来做多分辨率图标自适应,或者需要使用wxWebRequest这类网络组件,那至少要3.1.4以上。我这里选的是3.1.5,不选3.2.0的原因很简单:3.2.0当时还在RC阶段,API变动频繁,我跟的一个第三方库还没适配。做实际项目时,我习惯选“发布超过半年、社区反馈稳定”的版本,而不是盲目追新。
确认版本之后,去官网下载对应源码包就行。我不太推荐用git clone主干,因为主干经常处于“能编译但行为不一定稳定”的状态。下载完记得校验一下SHA256,虽然开源社区整体可信,但这种被广泛引用的基础库,谨慎一点总没错。我遇到过下载的源码包解压失败的情况,就是因为传输中断导致文件损坏,后来老老实实加了校验步骤。
2.2 Deepin的依赖坑:看似齐全,实则缺头文件
Deepin基于Debian 10,但深度团队自己维护了一批软件包版本,这导致一个很微妙的局面:你用apt search libgtk能看到一堆包,但里面有不少是深度改造过的,版本号跟Debian官方源不一样。编译wxWidgets需要 GTK 的开发头文件,很多人的第一反应是装libgtk-3-dev,这个没错,但在Deepin上,你往往还得额外检查libglib2.0-dev和libcairo2-dev是否安装。这是因为wxWidgets的GTK后端大量使用了GLib的事件循环和Cairo的绘图接口,缺了哪个都会在编译中途报出一堆莫名其妙的fatal error: glib.h: No such file or directory。
我在一台干净安装的Deepin 20.5上做过测试,直接跑配置脚本,报错顺序大致是这样的:
第一步:找不到 gtk/gtk.h —— 这是libgtk-3-dev没装。 第二步:找不到 glib-object.h —— 这是libglib2.0-dev没装。 第三步:找不到 pango/pango.h —— 这是libpango1.0-dev没装。 第四步:找不到 cairo.h —— 这是libcairo2-dev没装。你要是第一次编译,很容易被这四个错误来回折腾。其实不需要一个个试,一次性把基础依赖装齐就行:
sudo apt update sudo apt install build-essential libgtk-3-dev libglib2.0-dev libpango1.0-dev libcairo2-dev这里有个小提示:build-essential在Deepin上可能已经装过了,但有些精简安装的Deepin没有,直接装上也不会有副作用。如果不想装一堆用不到的包,也可以先用dpkg -l | grep libgtk-3-dev检查一下已有的情况,不过对于大多数开发者,我建议直接整组安装,省时间。
2.3 其他可能要装的组件
wxWidgets的GUI部分主要依赖GTK,但如果你打算用到一些扩展功能,还需要提前装好对应的开发库。我整理了一个清单,对应我这次编译时的选择:
| 功能需求 | 依赖包 | 说明 |
|---|---|---|
| 基础GUI(必装) | libgtk-3-dev, libglib2.0-dev | 不够,见上方说明 |
| PDF渲染(选装) | libcairo2-dev | 实际测试中wxPdfDocument依赖于它 |
| OpenGL支持(选装) | libgl1-mesa-dev, libglew-dev | 如果不需要3D视图可以跳过 |
| 图像格式(推荐) | libjpeg-dev, libpng-dev, libtiff-dev | wxImage处理这些格式需要对应头文件 |
| 网络组件(推荐) | libcurl4-openssl-dev | wxWebRequest的Linux实现基于libcurl |
| 压缩支持(可选) | zlib1g-dev | 通常系统自带,但保险起见装一下 |
这个表格里的包并不是全都要装。如果你只是写个简单的窗体程序,装上第一行就够了。但如果你想用wxImage读写PNG/JPG图片,而不想看到“无法处理该格式”的提示,那libjpeg-dev和libpng-dev最好还是装齐。
我后来在编译一个演示图像处理的小工具时,就是因为没装libtiff-dev,编译倒是通过了,但运行起来wxImage::LoadFile对TIFF格式直接返回false,查了半天源码才发现是库在编译时为了节省依赖自动禁用了TIFF支持。这类“编译通过但功能缺失”的问题其实比编译失败更隐蔽,所以如果你明确知道自己要用哪些功能,最好提前把对应的依赖都声明好。
3. 配置与编译:详解每一步的关键参数
3.1 解压与源码目录规划
我不会直接把源码解压到用户目录然后就地编译,因为那样会污染源码树,后续想重新配置很不方便。推荐的做法是建一个独立的构建目录,把编译产物和源代码分离。
mkdir -p ~/dev/wx && cd ~/dev/wx tar -xf wxWidgets-3.1.5.tar.bz2 mkdir build-deepin cd build-deepin可能在很多教程里你会看到直接在源码目录下执行./configure && make,这也能用,但我个人强烈不建议。wxWidgets的构建系统支持VPATH构建,你完全可以在外部目录运行源码里的配置脚本。这么做的好处很多,最实际的一条是:如果你想同时编译静态库和动态库,或者想针对不同编译选项搞两套产物,两份独立的构建目录互不干扰,省得反复清理。
提示:如果你解压之后发现目录名带版本号,比如
wxWidgets-3.1.5,建议保留这个目录名不动,因为配置脚本有时会根据目录名做一些检测。改目录名偶尔会触发一些诡异的路径问题,没必要自找麻烦。
3.2 configure 参数详解
wxWidgets的configure脚本是整个编译流程的核心,参数非常多,但常用的其实就那么几个。我这次用的配置是:
../wxWidgets-3.1.5/configure \ --prefix=/opt/wxwidgets/3.1.5 \ --enable-unicode \ --enable-shared \ --disable-static \ --with-gtk=3 \ --with-opengl \ --enable-mediactrl \ --enable-webview \ --enable-cxx17我挨个解释一下这些参数背后的逻辑:
--prefix=/opt/wxwidgets/3.1.5:指定安装路径。之所以不用默认的/usr/local,是因为我经常在/opt/wxwidgets/下放多个版本的库,比如3.0.4、3.1.5,方便随时切换。编译时通过CPPFLAGS和LDFLAGS指定对应版本的头文件和库文件即可。
--enable-unicode:这个必开。wxWidgets 3.0之后Unicode是默认的,但显式写上没坏处,也能提醒自己这是现代配置。
--enable-shared --disable-static:生成动态库。如果你做的是企业内部工具,不打算到处分发,动态库就够了,还省编译时间。但如果你想发布一个免安装的绿色版程序,那可能需要静态库——不过这里需要注意,wxWidgets的静态链接其实挺折腾的,涉及GTK库的传递依赖,后面我会细说。
--with-gtk=3:使用GTK3后端。Deepin整个桌面都是基于GTK3的,选这个能让程序在Deepin上获得最原生的观感。如果选GTK2,界面风格会停留在老版本,和Deepin桌面明显不搭。
--with-opengl:启用OpenGL支持。我有个工具用到了OpenGL预览3D模型,所以需要打开。如果你完全不需要3D,关掉能减少依赖和编译时间。
--enable-mediactrl:媒体控制组件。默认在某些平台是关闭的,但Deepin的GTK后端有GStreamer支持,打开之后可以用wxMediaCtrl播放视频。实际测试下来,Deepin上需要额外安装libgstreamer1.0-dev和libgstreamer-plugins-base1.0-dev,否则配置阶段会直接提示“mediactrl support not available”。
--enable-webview:网页视图组件。用wxWebView渲染HTML页面,官方默认在GTK3后端会尝试使用WebKit2。需要提前装libwebkit2gtk-4.0-dev。这个包体积比较大,如果你不搞HTML渲染,可以不开启,能省下不少编译时间。
--enable-cxx17:让wxWidgets自身代码以C++17标准编译。注意,这只是库的编译标准,不影响你调用库时的标准。你的代码依然可以用C++11编译,只要接口没用到C++17特性就行。
配置脚本跑完之后,会输出一个Summary,仔细看一下几项:GTK version是否显示3,wxWidgets libraries是否包含了你需要的组件,Use WebKit是否是yes。我见过很多人 configure 阶段不仔细看输出,结果make到一半才发现某个库被跳过了,这时候回头排查反而更费劲。
3.3 编译过程与并行度选择
configure 完之后就是常规的编译:
make -j$(nproc)这里有一个很常见的误区:nproc返回的是逻辑处理器数量,在配置较高的机器上可能是16甚至32,但直接用这么多线程编译有时反而会出问题。wxWidgets的编译过程中有少量步骤会使用临时文件,并行度过高可能导致磁盘I/O成为瓶颈,甚至偶发“file not found”错误。我在这台8核16线程的机器上实测,-j8和-j16的时间相差很小,但-j16偶尔会冒出一个找不到头文件的警告,重新跑一次又好了。后来我就固定用-j$(($(nproc)/2)),虽然少用了点核心,但全程稳定无异常。
编译时间取决于机器性能,我这边大概花了15分钟左右。如果你用的是老旧的笔记本,可能需要30分钟以上,这段时间可以去喝杯水,不用盯着终端。
3.4 安装与环境变量配置
sudo make install sudo ldconfig安装完之后,头文件在/opt/wxwidgets/3.1.5/include/wx-3.1,库文件在/opt/wxwidgets/3.1.5/lib。为了让它能正常被编译器和动态链接器找到,需要配置环境变量:
export WX_CONFIG=/opt/wxwidgets/3.1.5/bin/wx-config export PATH=/opt/wxwidgets/3.1.5/bin:$PATH export LD_LIBRARY_PATH=/opt/wxwidgets/3.1.5/lib:$LD_LIBRARY_PATH这里最重要的一点:wxWidgets官方提供wx-config工具,你在自己项目的 Makefile 里用它来获取编译参数,而不是手动拼-I和-L。手动拼很容易漏掉一些隐藏依赖。
验证安装是否成功,最简单的办法是在任意目录下运行:
wx-config --version wx-config --cxxflags wx-config --libs如果输出正常,说明安装基本没问题。我见过一些朋友安装了多版本wxWidgets之后,调用which wx-config发现路径不对,结果编译时用了旧版本的头文件,导致各种API不认识。解决办法是在项目的编译命令里明确用绝对路径调用/opt/wxwidgets/3.1.5/bin/wx-config,而不是依赖PATH搜索。
4. 编译完之后的第一个测试程序
4.1 最小可运行示例
库装好了,总得写个程序确认一把。这个示例不需要很复杂,能弹出一个原生窗口就行了:
#include <wx/wx.h> class MyApp : public wxApp { public: virtual bool OnInit() override { wxFrame* frame = new wxFrame(nullptr, wxID_ANY, "Deepin wxWidgets Test"); frame->Show(true); return true; } }; wxIMPLEMENT_APP(MyApp);编译命令很简单:
/opt/wxwidgets/3.1.5/bin/wx-config --cxxflags --libs但不要直接把它复制到命令行里。正确做法是:
g++ test.cpp -o test $(/opt/wxwidgets/3.1.5/bin/wx-config --cxxflags --libs) -std=c++11这里有个细节:wx-config --libs返回的是动态库链接参数,默认可能包含-lwx_baseu-3.1和-lwx_gtk3u_core-3.1等。如果你的程序只用到了核心控件,这些就够了。但如果用了网络、XML、AUI等模块,需要再加--libs=webview、--libs=xml这样的后缀。我建议养成习惯,用到哪个模块就在编译命令里明确声明哪个模块:
g++ test.cpp -o test $(/opt/wxwidgets/3.1.5/bin/wx-config --cxxflags --libs webview,xml)如果你不确定自己的程序需要哪些模块,先不加后缀编译,遇到“未定义的引用”错误时,再根据提示补充模块名就好。
4.2 运行时的动态库路径思考
编译出来的test程序,运行时会通过LD_LIBRARY_PATH找到动态库。但如果你设置了LD_LIBRARY_PATH但版本搞错了,会连接到一个不兼容的旧版本。这种情况下,程序可能能启动,但某些新接口一调用就崩溃,而且这种崩溃往往毫无提示。
为了彻底避免这个坑,我一般在自己项目的Makefile里把rpath写死:
g++ test.cpp -o test \ $(/opt/wxwidgets/3.1.5/bin/wx-config --cxxflags --libs) \ -Wl,-rpath,/opt/wxwidgets/3.1.5/lib这样一来,程序会优先在指定路径找动态库,不受系统环境变量影响。在需要把编译好的程序复制到别的机器上跑的场景下,这个做法尤其重要。
5. Deepin特有的坑:与Qt和GTK的纠缠
5.1 为什么用GTK而不是Qt
Deepin桌面本身是基于Qt写的,这一点可能让不少人困惑:既然Deepin用Qt,为什么wxWidgets还去选GTK?答案是:wxWidgets的Linux实现,GTK后端的成熟度和完整性远高于Qt后端。官方对于GTK的维护是主线级别的,而Qt后端虽然能编译通过,但部分控件的原生封装并不完善,尤其是文件对话框、剪贴板、拖拽这类和系统深度交互的功能,GTK后端明显更稳。
我自己也试过--with-qt编译了一次,界面确实能显示,但打开文件对话框时,弹出的竟然是Qt风格而不是Deepin原生的文件对话框,这让我感觉非常割裂。后来回到GTK3后端,一切又恢复正常。所以说,不要因为系统用Qt就试图让wxWidgets也用Qt,这不是一个明智的匹配。
5.2 字体渲染的细微差异
Deepin系统的默认字体渲染开启了一定的微调,这在Qt上表现良好,但GTK程序如果走wxWidgets的默认逻辑,可能会看到部分控件字体稍大或者间距不一致。这个问题我没有完全解决,但它不影响主要功能,所以只记录一下:如果你发现wxWidgets程序的界面和系统原生程序字体观感不同,可以在/etc/fonts/conf.d/里调整渲染规则,但这不是wxWidgets本身能控制的,属于GTK和桌面环境的适配问题。
6. 常见问题与排查技巧实录
6.1 configure阶段报错:找不到GTK头文件
这个基本就是2.2节说的情况。解决办法:执行sudo apt install libgtk-3-dev之后,再重新运行confige。但是要注意Deepin的包管理有一个特点:如果你同时启用了多个软件源,包版本可能冲突。我遇到过libgtk-3-dev安装之后,gtk/gtk.h仍然找不到的情况,排查发现是pkg-config的搜索路径里有一个失效的.pc文件,导致pkg-config --cflags gtk+-3.0返回了空值。
遇到这种问题,可以跑一下:
pkg-config --cflags gtk+-3.0如果输出为空,说明.pc文件没找对。检查PKG_CONFIG_PATH环境变量是否被设置过,或者是否把多个前缀混在一起了。我修复的方式是unset PKG_CONFIG_PATH之后重新安装包,让pkg-config用默认搜索路径,问题就消失了。
6.2 make阶段报错:unable to find string literal operator
这个报错看起来挺唬人,但一般不是源码问题,多半是你用了-std=c++17但编译器版本太老。Deepin 20默认的GCC是8.3,理论上支持C++17的绝大部分特性,但如果你在CXXFLAGS里加了些奇怪的宏,恰好触发了老版本GCC的bug,就会出现这个错误。
解决办法有两个方向:一是升级编译器,比如用apt install g++-10并在configure时指定CXX=g++-10;二是检查自己的额外编译参数。我实际遇到的情况是configure脚本自动检测到了C++17支持,但在一个单独的编译单元里出现了该错误,最后通过关掉-fno-exceptions绕过了。这个坑比较冷门,但出现频率并不算低。
6.3 链接阶段报错:libwx_gtk3u_core-3.1.so: undefined reference
链接时报undefined reference通常不是因为代码问题,而是因为wxWidgets库在编译时某些功能没启用。比如你用到了wxMediaCtrl,但configure时没开--enable-mediactrl,那么链接时就会因为wxMediaEvtHandler相关符号找不到而失败。
我的排查思路是先用nm -C /opt/wxwidgets/3.1.5/lib/libwx_gtk3u_media-3.1.so | grep MediaEvt看看库里有没有对应符号,如果没有,就回头检查configure参数,重新编译那个模块。还有一个常见的可能性:你链接了核心库,但忘了链接对应的扩展库。比如用wxBitmapBundle其实不需要额外库,但用wxAuiNotebook需要--libs=aui。
6.4 程序启动后窗口一片空白
这个坑在Deepin上我遇到过两次。第一次是因为GTK主题的问题,wxWidgets程序用了默认的GTK主题适配,但Deepin的主题定制比较激进,某些控件在深色主题下显示不出颜色差异,导致看起来像是空白。解决办法是给程序强制指定一个浅色主题:
wxSystemOptions::SetOption("gtk.toolbar.style", "both"); wxSystemOptions::SetOption("gtk.window.dark", "0");第二次是真正的渲染问题:我在编译时用--enable-graphics-ctx开启了wxGraphicsContext,但某个GL扩展在当前驱动下不可用,导致wxGCDC初始化失败。这个比较难查,最后是通过wxLogDebug输出日志才定位到的。解决办法是卸载掉自定义显卡驱动,改用系统自带驱动,问题消失。
6.5 常见问题速查表
| 问题 | 可能原因 | 解决办法 |
|---|---|---|
| configure找不到gtk/gtk.h | libgtk-3-dev未安装 | sudo apt install libgtk-3-dev |
| configure找不到glib-object.h | libglib2.0-dev未安装 | sudo apt install libglib2.0-dev |
| configure提示mediactrl不可用 | 缺GStreamer依赖 | sudo apt install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev |
| 编译时头文件找不到 | 并行编译偶发I/O问题 | 降低并行度,用make -j4 |
| 链接时undefined reference | 未开启对应模块或未链接对应库 | 检查configure参数或wx-config --libs=xxx |
| 运行空白窗口 | 主题问题或图形上下文初始化失败 | 强制浅色主题,更换显卡驱动 |
| 动态库版本混乱 | LD_LIBRARY_PATH指向错误 | 用绝对路径rpath链接 |
7. 实测心得与补充建议
7.1 如果为了减小体积,可以删掉哪些模块
如果你只需要最基础的窗口、按钮、文本框,可以考虑在configure时加这些参数:
--disable-html --disable-webkit --disable-mediactrl --disable-webview --disable-opengl这样能砍掉不少编译时间,我在一台虚拟机里测试,完整编译需要16分钟,省略这几个模块之后只要11分钟。而且最终的动态库体积从200MB降到130MB左右。不过提醒一句,删了之后,以后如果想用这些功能,就得重新编译整个库,所以我建议要不是特别介意体积,就先全开着。
7.2 多版本共存的管理方式
我目前机器上装了3.0.4和3.1.5两套,分别是:
/opt/wxwidgets/3.0.4/opt/wxwidgets/3.1.5
每个版本目录下都有bin/wx-config。切换版本时,只需要改一下环境变量,或者干脆在Makefile里手动指定编译器的编译参数。对于C++项目来说,头文件不匹配是最大的灾难,所以我坚持用绝对路径。
如果哪天系统重装了,只需要保留/opt/wxwidgets目录,不用重编译。但有个前提:deepin的GTK、GLib等系统库版本不能变化太大,否则动态库的二进制兼容性可能出问题。
7.3 为什么我不建议直接用Deepin的软件源里那个3.0
Deepin源里的wxWidgets 3.0.4其实很稳定,我自己第一版工具就是在它上面跑的。但后来遇到一个多线程刷新的问题,查了半天发现是旧版wxGrid在列宽调整时存在一个已知bug。这个bug在3.1.5里已经修了,所以我不得不升级。如果你没有这种“新接口刚需”,请老老实实留在稳定版。
7.4 最后一点点个人建议
用了这么久的wxWidgets,我的体会是:这个框架的上限其实很高,但它把很多“隐藏开关”放在了编译参数里。不同于Qt可以直接下载离线安装包一键配好,wxWidgets的魅力恰恰在于你每次编译都是一次对目标平台的深度适配。你得搞清楚GTK、GLib、Cairo、Pango这些底层组件各自扮演什么角色,才能在遇到问题时快速定位。
所以如果你也是刚开始在Deepin上折腾wxWidgets,千万别把编译成功当成终点。试试改几个configure参数,比如关掉一些模块、换个编译标准,再看看运行状态有什么变化。这个过程能帮你建立对框架构成更成体系的认知。我在实际使用中最大的收获,并不是终于跑通了编译,而是通过这几次编译,把Linux桌面程序底层的依赖关系理了一遍,之后再遇到其他库的编译问题,心里就有谱多了。