尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Notepad++ v8.6.6源码深度解析:Win32 C++桌面应用架构与实现

Notepad++ v8.6.6源码深度解析:Win32 C++桌面应用架构与实现 简介Notepad v8.6.6源代码是一份可直接查阅的Windows C工程覆盖从窗口消息调度、Scintilla编辑器封装到多语言语法高亮、插件系统的完整实现适合希望研究成熟编辑器实现细节并开展二次定制的开发者。源码包共2000个文件总大小11.48MB核心为C/C代码包括261个h、182个hpp、132个cpp和103个cxx并配合147个styled、147个folded语法样式定义、198个XML语言配置、大量ico/bmp界面资源以及py、properties、sln等辅助脚本与构建入口整体目录层次清晰适合按模块研读。借助源码可以具体理解Notepad如何使用Windows API构建原生界面如何通过Scintilla接口实现代码折叠、自动完成、查找替换也能学习插件管理器加载和消息通信的机制同时语法高亮相关的styled/folded和XML配置也能为自定义语言支持提供直接参考。对研究大文件打开优化、MDI标签页管理和自定义编辑器功能都有直接帮助也能对照源码熟悉Windows窗口类注册、消息循环和子窗口控件调度等经典实现。目前已有877人学习下载值得作为源码分析、教学案例或二次开发的基础。 Notepad v8.6.6源代码这套东西说实话很多写C写了五六年的朋友都不一定认真翻过。第一反应往往都是不就是个Win32的文本框套壳吗有什么好看的但等我真的把整个仓库拉下来、编译通过、然后顺着启动流程把核心代码走了一遍之后我得承认这个结论站不住脚。它表面上是记事本Plus实际上是一套非常完整的Win32桌面应用架构里面关于窗口消息分发、编辑组件封装、插件ABI兼容的设计比很多商业软件都干净。这篇文章不会去罗列每个文件的作用而是按照我自己读这套源码的路径来写先讲为什么值得读再说怎么把环境跑通然后拆解它的骨架最后挑几个核心机制深入读了一遍外加编译和阅读过程中踩过的坑。适合三类人看想把手头C桌面项目做扎实的动了念头想自己写编辑器的以及纯粹想找一份不大不小、能一口气啃完的开源Win32工程来练手的。1. 为什么这个版本值得读而不是只看官方文档Notepad的历史很长代码量也一直在膨胀但v8.6.6这个版本处于一个很有意思的位置它已经全面使用了现代Visual Studio工具链来构建底层依赖Scintilla和Lexilla的版本也比较新同时插件接口的兼容层仍然保留着老版本那套ABI设计。换句话说它既有老项目沉淀多年的架构经验又有新工具链搭起来的工程实践两者能在同一个代码库里共存这本身就很有看头。1.1 一套可以拆着学的Win32 C工程很多人学Win32编程接触的示例都是单文件里写一个窗口过程、处理几条消息。那样的代码到真正做产品时是完全不够用的。Notepad v8.6.6的源代码给了一个非常标准的进阶样本怎么把应用拆成主窗口类、编辑视图类、命令管理器、插件管理器、各个子控件模块怎么把这些模块组织到不同的目录和名字空间里怎么在消息循环里保持界面流畅怎么做多标签、多视图下的文档对象管理。这些不是靠读一两本Windows程序设计能直接获得的需要真的在代码里看一遍别人怎么落地的。这个项目还有一个优点它的体量对个人阅读者来说是可消化的。不像Chromium那种千万行级别的庞然大物Notepad的源码去掉第三方库之后核心代码量在十几万行级别。配合Visual Studio的调用层次结构和速览定义功能一个周末通读主链路不是痴人说梦。我在读的过程里最大的感受是它没有用特别抽象的设计模式就是老老实实的C类加Win32消息机制但边界划得很清楚这恰恰是工程化的精髓。1.2 从能用到好用编辑器的产品化细节官方文档只会告诉你Notepad支持什么功能但源码会告诉你这些功能是怎么实现的。举个例子当你打开一个很大的文件时编辑器不会卡死这里涉及到延迟加载、增量显示、取消耗时操作等一系列机制。又如自动补全的弹窗在什么情况下触发多文档切换时undo/redo栈是怎么隔离的折叠状态的存储到底放在哪一层这些问题在源码里都有明确的答案。我最初是想找一个语法高亮到底怎么实现的答案才去读源码的结果读着读着发现真正的收获远远超出预期。你会发现一个成熟的编辑器产品需要处理的细节非常恐怖从换行符风格检测到编码自动识别再到标签页的关闭按钮交互每一块都有专门的代码在伺候。这些产品化细节才是v8.6.6源代码最值钱的部分。2. 源码获取与本地编译先把环境跑通读源码最忌讳的是一直停在纸面上。我强烈建议第一件事就是把代码编译跑起来哪怕你暂时不打算改任何东西。因为只有当你能在调试器里下断点、看调用栈的时候那些静态阅读时想不通的逻辑才会瞬间通顺。而且编译过程本身就能逼你去搞懂项目的依赖关系。2.1 获取源码的两种方式与版本一致性获取源码无非两种方式从GitHub直接下载v8.6.6对应的zip包或者用git clone拉取仓库再checkout到对应tag。我个人的建议是用git方式因为后面如果参考社区提交历史或者想在旧版本和最新版之间对比代码差异本地有完整历史会方便很多。git clone --recursive https://github.com/notepad-plus-plus/notepad-plus-plus.git cd notepad-plus-plus git checkout v8.6.6这里有个关键点--recursive参数一定不要漏。Notepad仓库里有一部分逻辑依赖子模块尤其是Scintilla和Lexilla这两个核心组件。如果你用zip下载务必确认压缩包里确实包含了完整的scintilla目录和lexilla目录而不是只有占位文件。我自己第一次犯的错就是从某个镜像站下的zip包解压后发现scintilla目录是空的编译到中途报一堆头文件找不到折腾了半天才发现是源码本身不完整。2.2 编译环境准备Visual Studio 2022的安装细节构建Notepad v8.6.6需要Visual Studio 2022安装时选择使用C的桌面开发工作负载就够用了额外组件里建议勾选Windows 11 SDK和MSVC v143生成工具。这里有一个容易被忽略的点如果你机器上装了多个版本的VS或者之前装过预览版工具链打开解决方案时一定要留意右上角选中的是哪个工具集。Notepad的工程文件对平台工具集是有要求的用太老或者太新的预览版工具集可能编译出奇怪的错误。我自己的经验是全新安装的VS2022社区版加默认配置就能直接编译过几乎不需要额外装第三方库。这是因为仓库里已经随源码带好了必要的头文件依赖比如Boost等第三方库的副本都放在了仓库相应目录里。不需要联网拉包这对国内网络环境下的构建来说是很友好的。2.3 编译、产物与运行验证解决方案文件在PowerEditor/visualization目录下文件名类似NotepadPlusPlus.2022.sln。打开后把配置切换为Release平台选择x64如果你需要32位版本就选Win32。编译整个解决方案成功后所有产物会输出到PowerEditor/bin目录下。这里要特别强调一下运行目录的概念。Notepad并不是单个exe文件它运行起来要依赖旁边的SciLexer.dll和Lexilla.dll插件也放在同级的plugins目录下。如果你只拷贝notepad.exe到别处去运行会提示找不到组件。调试运行的时候直接把PowerEditor/bin设为工作目录就行。编译完成后在bin目录下启动notepad.exe能正常打开文件、切换语法高亮、加载插件就说明环境和源码都对了。到了这一步你才算拿到了后续阅读的入场券。3. 源码目录拆解认识这个项目的骨架拿到源码后第一次打开目录结构的人大概率会有点懵因为顶层的文件夹既有PowerEditor又有scintilla和lexilla还有一堆第三方库目录。我的建议是不要着急进文件先建立一张地图。3.1 顶层目录主程序、编辑核心、词法库整个项目的顶层职责可以分成三块。第一块是PowerEditor它里面装着Notepad自己的全部代码包括界面逻辑、命令管理、插件调度、配置读写这些src子目录是主程序源码visualization子目录放解决方案和工程文件。第二块是scintilla这是一个独立维护的通用编辑组件负责文本存储、显示、选区、滚动、撤销重做这些最底层的编辑能力。第三块是lexilla它承担语法高亮和代码折叠所需的词法分析把语言定义和词法状态机都放在这里。三块的关系我打一个比方lexilla是翻译官把源代码文本翻译成带语义标记的token流scintilla是排版工人负责把token流渲染成屏幕上的彩色文字并处理交互Notepad自己则是总调度决定打开哪个文件、在哪个标签页展示、按什么编码读取、插件在什么时候介入。分清楚这三层之后你再看具体文件就不会迷路。3.2 主程序内的模块边界PowerEditor/src目录下面是Notepad自己的代码它内部又按职责分成了好几块。有跟编辑器视图直接相关的组件封装了Scintilla的窗口句柄和消息交互有自定义Win32控件模块比如标签栏、自动补全列表、查找替换对话框有核心管理类负责主窗口生命周期和文档管理还有命令管理模块把菜单项、工具栏按钮、快捷键统一映射成内部命令ID。这些模块之间是单向依赖的上层界面代码调用核心管理类核心管理类再调用编辑器视图和底层组件很少出现底层反过来依赖上层的情况。这一点在做大项目时尤其重要一旦模块边界模糊了改一个功能可能牵扯出三个模块的问题。Notepad在保持这种清晰边界的同时还维持住了非常高的功能密度这是值得反复琢磨的。3.3 插件系统版本号与函数表插件目录相关代码单独拿出来讲是因为它代表了一种非常实用的扩展设计。Notepad的插件并不是编译进主程序的而是以独立DLL的形式放在plugins目录下。主程序加载插件时会先调用一个导出函数来获取版本信息和函数表指针然后通过函数表里约定的回调来建立通信。这种设计的好处是插件和主程序可以分别更新只要接口版本匹配就能协同工作。源码里能看到对插件接口版本号的严格校验逻辑主程序会读插件导出的版本跟当前主程序支持的版本做比对不一致就拒绝加载并提示用户。这个思路做产品扩展时可以直接借走比让插件直接链接主程序导出函数要安全得多。4. 核心机制的三处精读地图有了编译也过了接下来就是挑重点读源码。我挑选了三个自己觉得最值得精读的机制启动流程、命令分发、文档生命周期。这三个点覆盖了程序怎么跑起来、用户的每个操作怎么被响应、文件数据在内存里怎么被管理三个最基本的问题。4.1 启动流程从入口函数到主窗口出现在调试器里从入口函数开始按F11跟进你会看到一个很典型的Win32应用启动序列初始化全局状态和公共控件库解析命令行参数包括是否要打开指定文件、是否以多实例模式启动然后创建主窗口类并注册再创建主窗口实例窗口创建过程中会进一步创建标签栏、状态栏、编辑视图等子窗口最后进入消息循环。值得留意的是启动顺序的取舍Notepad是先创建了主框架窗口再在窗口初始化的流程里去加载配置、恢复会话、加载插件。这样做的原因很实际——如果先把插件和配置全部加载完再创建窗口用户会感觉启动时间很长而先把空窗口画出来让用户先看到界面再后台做这些重活体感上会快很多。很多桌面应用在启动优化上忽略了视觉反馈优先这一点源码里却处理得很自然。4.2 命令分发链路菜单、快捷键、工具栏的统一如果你在Notepad里按一个快捷键或者点一个菜单项或者点一个工具栏按钮最终都会走到同一个命令处理入口。实现这套统一响应的核心是一个命令管理器它内部维护了一个命令ID到处理函数/处理对象的映射表。菜单项、快捷键、工具栏按钮在初始化时都会绑定到同一个命令ID上于是不管用户通过哪个途径触发最终都执行同一份逻辑。这是一个很小的设计但效果非常好。新增功能时只需要注册一个新的命令ID然后把菜单项和快捷键指向它即可不需要为每一种触发方式单独写处理分支。读这段代码时我建议关注两点一是命令ID如何避免冲突二是涉及UI更新的命令如当前文档是否允许保存是怎么做置灰/可用的状态同步的。这两处细节在工作区里非常实用。4.3 多标签与文档生命周期Notepad的多标签体验一直被认为是同类软件里做得顺手的源码里的核心在于文档对象和视图对象的分离。一个文档对象对应一份打开的文件数据维护着内容、语言类型、编码、修改标记、撤销栈而一个视图对象对应屏幕上看到的编辑器窗口。多个视图可以指向同一个文档对象所以你能开两个窗口看同一份文件编辑其中一个另一个实时同步。这种分离也简化了文件被外部修改这类问题的处理。主程序可以检测文件变化然后在文档对象上标记状态再通过UI刷新通知所有关联的视图更新标题栏和标签样式。如果你想给自己的编辑器项目加多标签能力这部分的源码值得一个字一个字地读它把生命周期、事件通知、界面刷新拆得非常清楚。5. 编译与阅读过程中踩过的坑最后这部分是实操中遇到的坑和经验算是我个人最想分享的内容。毕竟纯讲架构容易飘落到坑里才知道自己的理解哪里不对。5.1 最容易翻车的缺文件问题前面提过zip包缺scintilla目录这是第一坑。第二个坑是部分第三方库里头文件用到了相对路径包含如果你把仓库移动了位置或者用某些工具做了路径转换会导致编译时偶尔报无法打开包含文件的错误。遇到这种情况别急着怀疑代码先看项目属性里的附加包含目录是不是还指向原位置。仓库自带的第三方库并不需要你主动配置但前提是你别把目录结构弄乱。第三个坑是插件依赖。编译出来主程序没问题但启动时如果提示找不到插件或插件加载失败先去bin/plugins目录看放没放插件DLL。有些插件DLL还依赖额外的运行库缺了会在插件管理器里静默失败。排查顺序永远是先看exe所在目录的核心DLL再看插件目录的依赖。5.2 字符集与编码的认知刷新Notepad是一个Unicode程序但它要处理的文件却什么编码都有这是它内部代码比较绕的一个来源。源码里你能看到大量编码转换相关的逻辑有UTF-8的、UTF-16的、各种代码页的以及文件开头BOM的处理。读这段代码之前我一直以为文件读进来转成宽字符就完事了但Noetepad的代码告诉我编码识别、存储标记、显示转换、保存时回写每一步都有独立的状态管理不能混在一起。如果你要改它的编码相关代码或者想模仿这套设计建议在Debug模式下打开几种不同编码的文件观察内存里的字节分布和调试器里的字符串对象内容比靠猜靠谱得多。这里也踩过一个坑在中文Windows系统上如果你的工作目录路径里有特殊字符或超长路径某些调试场景会打不开文件看起来像是编码问题实际是路径转换的问题换一个简单的目录就好。5.3 高效的源码阅读方法最后给一点阅读方法上的建议。第一不要试图从第一行顺序读到最后一行的方式去理解一个大型项目那样效率极低。正确姿势是先跑起来然后通过调试器设断点跟着实际交互路径读代码。第二善用速览定义和调用层次结构这两个功能查一个消息怎么从控件一路走到处理函数两个功能就够用。第三对长函数不要有恐惧心理很多Win32通知处理入口是很长的switch分支你只需要找到跟你关注的消息相关的case即可。另外一个非常实用的小技巧是全局搜索。如果你想知道某个功能入口在哪里直接去搜用户界面上的字符串比如菜单项的名称基本几秒就能定位到资源文件和命令ID再通过命令ID跳到处理器函数。这套界面字符串→命令ID→处理函数的链路检索法我后来用在了很多开源项目上都非常好使。我最后想说的是Notepad v8.6.6这套源码最大的价值不是让你抄它某个具体的编辑算法而是让你看到一个被上亿次下载的桌面软件是怎么把一个简单的文本框逐步演化成完整产品的全过程。编译只是开始真正有价值的是你在代码里看到的那些取舍和边界。如果你能把这篇文章里提到的启动链路、命令分发、文档生命周期这三块都读透再回头看自己的项目大概率会有一种原来这里应该这么拆的启发感。本文还有配套的精品资源点击获取
返回列表