如果你在做的是一个真正的代码编辑器、日志分析工具、配置查看器,或者任何需要给用户提供“带语法高亮、代码折叠、自动补全”的文本编辑能力的功能,那么QScintilla基本上是你绕不开的一个组件。它是Scintilla编辑器控件在Qt环境下的封装版,把Scintilla强大的文本编辑内核和Qt的控件体系结合在一起,开箱就能获得语法高亮、代码折叠、行号、断点标记、自动补全提示这些能力。平时用QPlainTextEdit写个简单的文本编辑器没问题,但一旦涉及语言级的着色和折叠,原生控件真的帮不上忙,QScintilla几乎是Qt生态里唯一好用且靠谱的选择。这篇文章记录的是我在Windows下编译QScintilla的完整过程,包括环境准备、qmake/nmake编译、安装到Qt目录、再把它接进自己的Qt项目,以及几个能救命的踩坑排查经验。
如果你之前只用过Linux下编译QScintilla,切换到Windows会有不少细节上的差别,比如编译器的选择、DLL的处理、调试库和发布库的区分,这些坑网上资料都比较零散。我会把这些年在Windows下编译这个库的实际经验整理成一套可以直接复用的流程,适合正在做编辑器类工具、想引入语法高亮功能的Qt开发者,也为第一次接触QScintilla编译的朋友兜个底。
1. 项目是怎么想起来的:QScintilla的定位与触发场景
1.1 QScintilla是什么,它能给项目带来什么
QScintilla可以简单理解成一个“编辑器内核”的Qt化包装。底层的Scintilla是一个用C++写成的文本编辑控件,功能非常硬核,但它跟具体的GUI框架没有绑定关系,你很难把它直接塞进Qt窗口里。QScintilla做的就是适配层的工作,它把Scintilla封装成Qt的控件类,让你可以用QSciScintilla这个类来操作编辑器,同时通过Lexer机制实现各种语言的语法高亮。
在实际项目里,我使用QScintilla最常见的场景是做一个内部用的脚本编辑器,或者日志分析工具,日志文件几百兆的情况下,QPlainTextEdit直接加载会非常卡,而QScintilla因为内核本身设计为高性能文本编辑,滚动和定位都要流畅很多。它自带的能力包括:
- 多种语言的语法高亮,Python、C++、XML/HTML、JSON等都有现成的Lexer
- 代码折叠,可以像IDE那样折叠函数和块
- 行号、断点标记、书签标记,编辑器的基本排版控件都齐全
- 自动补全、调用提示,支持自定义词条列表
- 查找替换、括号匹配、字符编码切换等编辑器通用功能
所以如果你的项目里需要“一块真正能编辑代码的控件”,而不是简单展示文本,QScintilla几乎是必备的。
1.2 为什么偏要自己编译,不直接用预编译包
很多Qt开发者在官网安装Qt时,其实已经能通过Qt的在线安装器看到QScintilla的预编译模块,勾选一下就能用。但实际工作中我更推荐自己编译一次,原因有三点。
第一,版本匹配问题。QScintilla的预编译包跟你本地Qt的版本、编译器的类型、目标架构(x86/x64)必须完全对应,否则链接的时候一大堆奇怪的符号错误。Qt在线安装器提供的预编译包更新节奏往往滞后,而且不一定覆盖你需要的编译器组合,我自己就遇到过Qt 5.15搭配老版本QScintilla库时,符号找不到的问题,折腾到最后还是自己动手编译。
第二,定制需求。默认编译的QScintilla会包含所有Lexer支持,如果你的项目是嵌入式设备或者对安装包体积敏感,你可能只想要其中一部分语言的高亮。通过自己编译,你可以修改src源码里的配置,把不需要的Lexer去掉,让库体积更小。虽然现在不少人觉得体积无所谓,但几百K的差距在某些场景下还是有意义的。
第三,理解产物结构。自己编译一次,你会清楚头文件在哪、库文件叫什么、debug和release版本的区别是什么,之后不管怎么集成都不会抓瞎。我一直觉得,QScintilla这种纯C++库自己编译根本不是难事,难的是搞清楚编译之后的“全家桶”里每一份文件该放哪里。
1.3 这篇文章用的环境与版本组合
为了能边看边复现,我先说明我这次的编译环境,你用自己的版本组合时逻辑是一样的:
| 项目 | 版本/配置 |
|---|---|
| 操作系统 | Windows 10/11 64位 |
| Qt版本 | Qt 5.15.2 MSVC2019 64bit |
| 编译器 | MSVC2019(对应Visual Studio 2019工具链) |
| QScintilla源码 | 2.13.x或2.14.x版本 |
| 构建工具 | jom(Qt Creator自带)或nmake |
| Shell环境 | x64 Native Tools Command Prompt for VS 2019 |
QScintilla的新版本已经支持Qt 6,如果你在用Qt 6,编译流程完全一样,只是库文件名后缀会从qt5变成qt6,后面我会专门讲到这个差异。
2. 开工前的准备:工具选型与源码结构
2.1 需要准备的软件清单
正式开始之前,先确认你本机上的几个基础软件是齐的:
- Qt:建议安装时带上对应的编译套件。比如你用MSVC编译,就要在Qt安装器里勾选MSVC 2019/2022套件,而不是只装MinGW套件。
- QScintilla源码包:从官网下载源码压缩包,解压即可。注意区分源码包和“针对某Python版本的二进制包”,我们做C++集成一定选源码包。
- 解压工具:Windows自带资源管理器就能解压tar.gz,但保险起见我习惯用解压软件,避免中文路径或深层目录名导致解析异常。
- 编译工具:如果安装了Qt Creator,一般会自带jom(并行nmake),也可以直接用Visual Studio自带的nmake。用MinGW的话还需确保MinGW的make工具在PATH里。
我实际用下来的经验是:Windows上不要把源码解压到带有中文、空格、特殊字符的路径下,比如D:\源码\QScintilla_src这种目录名后面容易出事,qmake在解析路径时遇到中文偶尔会抽风,报一些让你摸不着头脑的错误。
2.2 MSVC与MinGW工具链怎么选
Windows下编译QScintilla,首先要明确你项目里Qt用的是哪套编译器,这决定了后面所有步骤。如果搞混了,你编译出来的QScintilla库和你的Qt库会互相“打架”,链接期报一堆错误。
| 对比维度 | MSVC | MinGW |
|---|---|---|
| 编译速度 | 较慢,但配合jom可并行加速 | 中规中矩 |
| 与Qt官方二进制兼容性 | 官方预编译包默认基于MSVC | 需要单独下载MinGW版Qt |
| 调试器 | 配合Visual Studio调试体验最佳 | 在Qt Creator里用GDB调试 |
| 生成库文件 | .lib+.dll | .a+.dll,命名风格不同 |
如果你平时就是在Qt Creator里用MSVC套件开发,那你编译QScintilla也应该用MSVC。如果你用的是MinGW版Qt,那就必须用MinGW工具链编译QScintilla,不能用MSVC的nmake去编译,否则链接时库格式都不一样,整个项目直接编译失败。
我最推荐的方式是:命令行工具选择“x64 Native Tools Command Prompt for VS”,然后在这个环境里调用Qt的qtenv2.bat脚本把Qt的环境变量加载进来。这样编译器环境是VS的,而Qt路径也是同一个套件的,环环对得上。
2.3 源码包里藏着什么,编译入口在哪个目录
QScintilla源码包解压后,你可能会觉得目录结构有点乱,但其实逻辑很清晰:
- 根目录下有
src,里面是QScintilla和Scintilla的源码文件,不需要动 Qt4Qt5目录是Qt 4和Qt 5时代的编译入口,我们编译就用这个目录里的qscintilla.proQt6Qt6目录是Qt 6时代的新入口,如果使用Qt 6,编译入口要换到这个目录example目录有一个演示工程,可以帮你快速确认库能不能正常工作Python目录是给Python绑定用的,做C++集成完全不用管
很多人一开始会误去编译根目录或者src目录,结果找不到可用的工程文件。记住:C++版本的编译入口永远在Qt4Qt5或Qt6Qt6。打开这个目录,里面会有一个qscintilla.pro,这个才是qmake要处理的工程文件。
3. 一步一步在Windows下编译QScintilla
3.1 第一步:把Qt编译环境激活
这一步是Windows下最微妙的地方。打开一个普通的cmd窗口然后直接执行qmake,大概率会提示“qmake不是内部或外部命令”。因为Qt的qmake路径并没有加到系统全局PATH里面,就算加了,你也不知道当前激活的到底是不是自己用的那个版本。
我的做法是:从开始菜单打开“x64 Native Tools Command Prompt for VS 2019”(不同VS版本名称略有差异,大意一致),然后在命令行里执行Qt自带的qtenv2.bat脚本:
cd /d C:\Qt\Qt5.15.2\5.15.2\msvc2019_64\bin qtenv2.bat执行完这个脚本后,当前命令行窗口才会真正拥有Qt和MSVC两套环境变量。验证环境是否OK,直接执行:
qmake --version如果你能看到QMake version 3.1以及Using Qt version 5.15.2这类输出,说明环境已经通了。如果这一步看到的是Qt 6版本或者某个MinGW版本的路径,那你后面编译出来的库自然也是对应版本,整套流程同理,但跨版本混合使用是不行的。
这一步我建议你养成一个习惯:编译任何Qt项目之前,先在一个干净的编译环境里验证qmake版本。踩过太多系统PATH被各种软件改乱的例子,直接在cmd里乱敲qmake,指不定使用的是哪个残留在PATH里的老版本。
3.2 第二步:用qmake生成Makefile
环境准备好后,进入源码的Qt4Qt5目录:
cd /d D:\dev\QScintilla_src-2.14.1\Qt4Qt5 qmake "CONFIG+=release" qscintilla.pro这行命令的作用是让qmake读取qscintilla.pro这个工程定义文件,根据当前Qt环境的信息生成一份Makefile。加上CONFIG+=release是明确指定只生成release版本的构建规则,如果不加,默认可能同时编译debug和release两套,耗时会翻倍。如果调试阶段需要debug版本的库,可以把release换成debug。
命令执行完毕后,你会在当前目录下看到生成的Makefile文件,以及一些.qmake.stash之类的临时文件。如果qmake报“Unable to find file”或者“Project ERROR”,第一件事检查当前目录是否真的在Qt4Qt5下面,第二件事检查源码路径里有没有中文或空格字符。
3.3 第三步:用jom或nmake开始编译
生成Makefile之后,这个项目的构建行为已经确定了。下一步就是实际的编译动作。
如果你装了Qt Creator,强烈建议用jom,因为jom是nmake的并行版本,可以同时启用多核编译,速度提升非常明显。QScintilla加上底层Scintilla的源码文件量不小,单线程nmake可能要踩几分钟的油门,多核jom会快很多。命令如下:
jom -j8其中-j8表示8个并行任务,具体数字根据你CPU核数调整。如果没有jom,也可以直接使用nmake:
nmake release如果是MinGW环境,对应命令是:
mingw32-make -j8编译过程中的日志会出现大量的g++或cl.exe输出,正常现象。编译结束后,你会在Qt4Qt5目录下看到release子目录(如果编译的是debug则是debug子目录),里面会生成:
qscintilla2_qt5.dll:动态运行库,程序运行时需要它qscintilla2_qt5.lib:Windows下的导入库,链接时使用- 其他中间文件如
.obj和.o
如果你是在Qt Creator里直接打开qscintilla.pro文件构建,生成的目录结构会在你的build文件夹下,但产物类型是一样的,不影响后续集成。
3.4 第四步:把头文件和库文件安装到Qt目录
编译完成后有两种收尾方式:一种是运行安装命令,让QScintilla自动把头文件和库文件拷贝到Qt安装目录对应的位置;另一种是手动拷贝,适合不想污染Qt目录的情况。
推荐先试自动安装,命令很简单:
jom install或
nmake install这个命令会根据qscintilla.pro里配置的路径,把头文件拷贝到Qt的include目录下,把.lib导入库拷贝到lib目录,把.dll拷到bin目录。安装完成后,Qt开发环境就已经“感知”到QScintilla了,新项目可直接引用。
如果不想用install命令,可以手动把编译产物复制过去:
release\qscintilla2_qt5.dll-> Qt安装目录的bin目录release\qscintilla2_qt5.lib-> Qt安装目录的lib目录- 源码
src目录下的Qsci头文件夹 -> Qt安装目录的include目录,并改名为QScintilla(或者保持Qsci目录名,看你项目里怎么写include)
安装完之后,可以打开Qt安装目录确认一下:include\QScintilla\Qsci\qsciscintilla.h这个文件是否存在。存在就说明头文件有了,链接时不会找不到声明。
4. 编译完怎么用:把QScintilla接进自己的Qt项目
4.1 在.pro文件里正确引用库和头文件
编译只是第一步,日常开发中更重要的是在项目里正确使用它。在你自己的Qt工程xxx.pro里加入以下几行:
QT += core gui widgets INCLUDEPATH += $$[QT_INSTALL_HEADERS] LIBS += -L$$[QT_INSTALL_LIBS] LIBS += -lqscintilla2_qt5$$[QT_INSTALL_HEADERS]和$$[QT_INSTALL_LIBS]是qmake内置的变量,会指向当前qtkit的include和lib目录。这样写的好处是项目换到另一台机器,只要Qt环境一致,路径就不用改。如果你没有执行install命令而是手动拷贝,那INCLUDEPATH和LIBS就要改成你自己的实际路径,例如:
INCLUDEPATH += D:/dev/QScintilla_src-2.14.1/src LIBS += -L"D:/dev/QScintilla_src-2.14.1/Qt4Qt5/release" -lqscintilla2_qt5如果你用的是Qt 6,库名通常是qscintilla2_qt6,这一行末尾的库名要跟着改,否则链接时会提示找不到qscintilla2_qt5.lib。
4.2 写一个最简单的编辑器Demo
引用配置完毕,写一个最小可运行的编辑器示例。创建一个Qt Widgets工程,在主窗口代码里加入:
#include <Qsci/qsciscintilla.h> #include <Qsci/qscilexerpython.h> QScintilla *editor = new QScintilla(this); editor->setLexer(new QsciLexerPython(this)); editor->setUtf8(true); editor->setMarginLineNumbers(1, true); editor->setMarginWidth(1, "000000"); setCentralWidget(editor);这段代码做了什么?我拆开解释一下:
QScintilla *editor = new QScintilla(this)创建编辑器控件,它是一个完整的QWidget子类,可以直接嵌入布局setLexer(new QsciLexerPython(this))给编辑器绑定一个Python语言的词法分析器,语法高亮、缩进相关功能由它驱动setUtf8(true)设置编辑器按UTF-8处理文本,这个在很多项目里非常关键,尤其是处理中文注释时,不设置的话容易出现乱码setMarginLineNumbers(1, true)在第1个margin区域显示行号setMarginWidth(1, "000000")给出一个固定宽度占位,让行号区域宽度与6位字符宽度一致,避免数字位数变化时区域宽度跳动
编译运行后,窗口里就会出现一个支持Python语法高亮和行号显示的编辑器。如果你把内容改成QsciLexerCPP,则直接切换成C++语法高亮,非常方便。
4.3 运行时DLL的处理:别让程序一启动就崩
很多新手在项目里引用QScintilla后,编译能过,但一运行就弹窗提示“找不到qscintilla2_qt5.dll”。原因很简单:你的exe启动时需要加载这个动态库,但Windows的可执行文件搜索路径里没有它。
解决办法有三种:
- 直接把
qscintilla2_qt5.dll拷贝到exe同目录下,最直接 - 把
qscintilla2_qt5.dll所在的Qt bin目录加入系统PATH环境变量 - 使用Qt自带的
windeployqt工具,它会自动把Qt运行时和QScintilla DLL一起拷贝到exe所在目录
发布时建议用windeployqt,因为它还会处理Qt自身的其他DLL依赖,比如Qt5Widgets.dll、Qt5Gui.dll等等。手动一个个拷容易漏东西,在别的机器上跑起来还是缺DLL,体验很难受。
5. 常见问题速查与避坑心得
5.1 编译阶段的典型报错
QScintilla编译过程中最常见的几个报错,我专门整理成一张表,你可以直接对照排查:
| 报错现象 | 大概率原因 | 解决办法 |
|---|---|---|
qmake: command not found或'qmake' 不是内部或外部命令 | Qt环境未激活 | 先执行qtenv2.bat,再验证qmake --version |
Project ERROR: Unknown module(s) in QT: core5之类 | 用旧源码配新Qt或反之 | 检查QScintilla版本与Qt版本兼容性,Qt 6用Qt6Qt6目录 |
| 路径含中文导致qmake解析失败 | 源码路径中存在中文、空格 | 把源码目录换到纯英文路径 |
jom: pipe.cpp(313) : fatal error | 并行编译时临时文件冲突 | 改用nmake单线程编译,或降低-j并发数 |
cannot find -lqscintilla2_qt5 | 链接时库路径不对 | 检查LIBS里的-L指向目录,并确认库文件名正确 |
其中“Unknown module(s)”这条在Windows下最容易误导新手。它不一定只出现在Qt版本对不上的时候,如果你用qmake打开了一个不存在的工程文件,或者工程文件里引用了其它未安装的模块,都会报类似错误。我的建议是先看完整报错上下文,不要盯着模块名纠结。
5.2 链接阶段的疑难杂症:LNK2019、debug/release不匹配
链接阶段最典型的报错是LNK2019 unresolved external symbol,这基本上暴露了库和项目配置之间的不匹配。
第一类是调试版本和发布版本混用。你给项目配置的是Debug模式,却链接了release版的qscintilla2_qt5.lib,这在一些老版本MSVC下会报符号找不到或者内存分配函数冲突。解决办法是配置两种库的路径:Debug项目链接debug版本的库(文件名可能带d后缀),Release项目链接release版本的库。
如果你编译QScintilla时明确执行了qmake CONFIG+=debug qscintilla.pro并且使用nmake debug,得到的库文件通常是qscintilla2_qt5d.lib和qscintilla2_qt5d.dll,在.pro文件里就需要写成-lqscintilla2_qt5d,同时LIBS += -L.../debug。
第二类是库的编译器版本不一致。比如你在MinGW版Qt环境下编译出来的.a库,却被一个MSVC工程引用,链接器直接就会懵掉。这种情况下不管怎么调整路径都没用,唯一正确的做法是重新用与目标Qt相匹配的编译器工具链去编译QScintilla。
第三类是Qt库本身版本不一致。比如你本机同时装了Qt 5.12和Qt 5.15,两套环境混淆使用,QScintilla编译时用的是5.15的Qt,而你项目切到5.12的Qt去链接,可能就会出现函数签名对不上的问题。这再次印证了那一句:先检查环境,再怀疑代码。
5.3 运行阶段的DLL与崩溃问题
编译和链接都正常,但程序运行就崩,这种情况多半不是QScintilla的问题,而是运行环境的问题。
最常见的是DLL缺失,如前文所述,解决办法是拷贝DLL或使用windeployqt。还有一种诡异的情况是程序不报DLL缺失,但启动时弹窗提示“应用程序无法正常启动0xc000007b”。这个错误在Windows下非常经典,本质上是DLL的位数或编译器类型不匹配。比如你的exe是64位的,但路径里被放进去一个32位版本的qscintilla2_qt5.dll,或者Qt库是MinGW版本而QScintilla的DLL是MSVC版本。
遇到0xc000007b,我的排查顺序是:先确认exe位数,再确认Qt位数,再确认QScintilla DLL位数,三者必须一致。也可以用Dependency Walker或Process Explorer看DLL加载到哪一步断开,但说实话这类工具对新手不太友好,多数情况下把DLL版本统一一下就好了。
5.4 我个人踩过的三个坑
第一个坑是用普通cmd编译。当初贪图方便,没有打开VS的编译环境,直接在普通cmd里调qmake,结果编译出来的库在链接阶段各种缺符号。后来排查半天才发现qmake调用的MSVC编译器环境没初始化,导致编译选项CPPFLAGS里缺少MSVC的预处理器定义,生成的库和Qt库不兼容。从此我养成了一个习惯:编译任何Qt库,先打开正确的编译环境,再执行qmake。
第二个坑是用了预编译包偷懒。有段时间为了省时间,我从网上找了一个别人编译好的QScintilla库,版本号看起来也对,但集成进项目后,链接时一直报cannot open input file “qscintilla2_qt5.lib”。后来才发现对方发的压缩包里根本没有.lib,只有.dll,而且DLL位数不对。那次之后我彻底放弃了找预编译包的念头,老老实实自己编译,其实整套流程也就五分钟的事。
第三个坑是忘记处理DLL依赖,发布程序时只拷了exe,结果终端用户运行时报缺失DLL。当时我以为是程序代码的问题,排查了半天才发现exe依赖的qscintilla2_qt5.dll压根不在发布目录里。从那以后我发布Qt项目前都会跑一遍windeployqt,旧脚本里也固定加上这一条,再没出过这类低级问题。
如果你正打算在Windows下编译QScintilla,我个人的建议是:不管你是用命令行还是Qt Creator,先把编译环境理顺,然后完整走一遍编译、安装、集成、发布四个环节。第一次编译可能会遇到几个坑,但只要你对照着这篇文章的思路,把Qt版本、编译器类型、debug/release模式这三件事对齐,后面的路就会顺畅很多。哪怕你不用QScintilla做编辑器类应用,只是想在Qt里实现一个带语法高亮的小控件,这个编译经验也完全通用。毕竟Qt音绕不开的很多第三方库,编译思路都是相通的。