
简介Notepad v8.6.6 源代码包完整呈现这款经典编辑器在 Windows 平台上的核心实现对于需要分析编辑器底层机制或在此之上做扩展的开发者具有很强的参考价值。资源面向希望深入理解 Windows API 桌面程序、Scintilla 编辑控件、语法高亮与插件机制的中高级开发者也适合想通过实战源码学习大型开源软件架构的初学者。整个包共 2000 个文件包含 261 个 h 头文件、182 个 hpp、132 个 cpp 与 103 个 cxx 源文件并附有 XML 配置、Python 辅助脚本、properties 资源配置及 RC 界面资源压缩包仅 11.48MB。目前已有 877 人学习下载。读者可从源码层次理清消息处理循环、Scintilla 接口调用、多语言语法高亮定义、插件加载与通信、内存管理及多文档界面MDI优化等关键实现同时资源内还保留了 .vcxproj/sln 工程文件、官方构建脚本和示例配置便于直接在 Windows 下编译调试、二次定制或迁移学习。1. 从工具到源码为什么值得深挖 Notepad v8.6.6聊到 Windows 平台上的文本编辑器Notepad 几乎是绕不开的一个名字。很多人天天用它打开配置文件、写脚本草稿、看日志但真正打开过它源代码的人其实不多。这次我拿到 Notepad v8.6.6 的源代码认认真真看了一遍发现这个项目比我想象中更有意思它不只是“一个高级记事本”更是一个把 C 底层能力、Win32 原生交互、插件扩展机制和开源协作模式全部串起来的活教材。v8.6.6 这个版本处于 8.6 系列的中后段整体上延续了这代主版本对 UTF-8 编码策略和现代接口的靠拢同时修复了一批编辑器核心操作层面的老问题。对普通用户来说它可能只是“设置里多了一个编码选项”或者“某个插件兼容性好了”但在源代码层面这些变化往往牵动着一大片逻辑链。如果你写 C、做桌面软件、搞工具链开发或者单纯好奇“编辑器到底是怎么把字符渲染到屏幕上的”啃这份源码的收获会非常大。这篇文章我把整个 v8.6.6 源码的解剖过程整理了出来包括项目整体怎么组织、核心模块之间怎么协作、怎么编译出一个能跑的版本、以及基于这份代码写插件和改功能时最常踩的坑。面向的读者不要求是资深 Windows 开发但如果你知道指针、消息循环、回调函数这些基础概念读起来会顺畅很多。我会尽量用做软件的思路来讲而不是像教科书那样铺概念。2. 源码整体架构代码藏在哪、怎么组织的2.1 第一眼印象这不是一个“大而全”的工程打开 Notepad 源码根目录你首先会注意到它不是一个巨大的、带成百上千个子目录的工程。整个项目核心代码集中在PowerEditor/src下面MFC 相关的东西在PowerEditor/src/MFC核心编辑逻辑在PowerEditor/src/ScintillaComponent和PowerEditor/src/win32插件相关接口则散落在PowerEditor/src和Scintilla目录中。这种结构对一个 Windows 桌面应用来说属于相当克制的布局你能很清晰地看出“编辑器核心”和“外壳应用程序”是两套东西。这种拆分其实是有历史原因的。Notepad 的编辑内核并非自己从头写的而是基于 Scintilla——一个老牌的开源代码编辑组件。Scintilla 处理行号、语法高亮、自动换行、选区、撤销重做这些最底层的文本编辑行为Notepad 自己则负责窗口框架、菜单、工具栏、设置存储、编码转换、插件加载这些“外围”工程。理解了这个边界看代码时就不会迷路看到Scintilla目录下的东西你要清楚那是借来的轮子看到PowerEditor/src里的东西那才是 Notepad 自己的大脑。2.2 构建系统与依赖管理没有包管理器的“原生”项目这一点对现代开发者来说可能有点“复古”Notepad v8.6.6 的源码不依赖 vcpkg、Conan 这类包管理工具也不涉及 npm 或者 pip。它的依赖项——Scintilla、Lexilla词法解析器库、以及一堆 Windows SDK 头文件——都是直接以源码形式放在工程里的。换句话说你拿到这份源码只要装了对应的 Visual Studio 版本和 Windows SDK理论上就能直接编译出完整程序不需要联网拉任何东西。这里分享一个实操细节v8.6.6 的官方构建流程是基于 Visual Studio 2019 或 2022 的解决方案文件具体是PowerEditor/visual.net/NotepadPlusPlus.sln。打开这个解决方案后你会看到里面有多个项目其中最核心的就是NotepadPlusPlus主项目以及Scintilla、Lexilla这两个依赖项目。构建顺序不用操心VS 会自动处理好依赖关系。我第一次编译时踩了个小坑没有安装“单个组件”里的 MFC 相关依赖结果链接阶段报了一堆找不到afx开头的符号。这个后面我会在问题排查部分专门说。还有一点必须提因为整个项目是 Win32 原生应用它的字符处理大量使用了 TCHAR 宏和多字节/宽字符兼容逻辑。在 v8.6.6 里UTF-8 已经是默认编码了但你要在代码里搜索WideCharToMultiByte或MultiByteToWideChar依然能看到成片成片的编码转换调用。这些代码不是没用的历史包袱而是支撑“打开任意编码文件不乱码”这一核心体验的基础设施。2.3 插件机制核心扩展性的设计样板Notepad 的插件系统是我认为整个源码里最值得花时间研究的部分。它是典型的“消息回调”模型没有引入 COM 或 .NET 那种重型的组件模型而是靠一个NppData结构体和一组带编号的消息NPPM_*宏来完成插件与主程序之间的通信。插件本质上就是一个导出了几个固定符号的 DLL。主程序在启动时扫描plugins目录加载每个 DLL调用其导出的getFuncsArray等函数获取插件命令列表然后把命令挂到菜单上。用户在菜单里点击某个插件项时主程序通过函数指针回调插件代码。整套流程环环相扣但代码量不大读起来非常舒服。这种轻量插件模型的好处是显而易见的插件不需要继承某个 C 类不需要额外运行时只要能导出 C 风格接口的 DLL 就能被识别。这也解释了为什么 Notepad 的插件生态里有大量用 C、C、Delphi 甚至 Rust 写成的插件——门槛足够低格式足够开放。对想自己写插件的人来说官方提供了PluginTemplate示例复制一份改改就能跑起来。3. 核心代码细节从入口到消息循环再到编辑内核3.1 入口与生命周期wWinMain和它的“重活”很多 C 桌面项目的入口函数被藏得很深但 Notepad 的入口很直白就在PowerEditor/src/winmain.cpp里函数名是wWinMain。这里有一个值得注意的细节它做的第一件事不是去创建窗口而是先处理“单实例”逻辑——通过命名互斥体Mutex判断是否已有另一个 Notepad 实例在运行。如果检测到已有实例新进程会把启动参数通过自定义消息传给旧进程然后自杀退出。这就是为什么你双击多个文件时它们通常会在同一个 Notepad 窗口里以新标签页打开而不是启动多个进程。这段逻辑看起来简单但背后的设计考量很实际如果每次打开文件都起一个新进程内存占用和启动延迟会高得离谱而通过消息转发实现单实例复用既保证了响应速度又保留了“同一套配置、同一份会话”的连续性。从源码学习的角度看这是一段非常经典的 Win32 单实例实现样板比网上很多简化版示例完整得多。进入主流程后wWinMain会初始化全局状态、加载配置、创建主窗口然后进入标准 Win32 消息循环。消息循环本身没什么魔法就是GetMessage、TranslateMessage、DispatchMessage三件套。不过真正让编辑器“活”起来的是主窗口过程函数里那个硕大的switch语句——从WM_OPEN到WM_COMMAND从拖放文件到标签切换几乎所有用户操作都会在这里被分派到对应的处理函数。3.2 文档与视图Scintilla 是怎样嵌入进来的主界面上那个让你输入文字的编辑区域本质上是ScintillaEditView这个类在管理。它内部持有一个 Scintilla 窗口句柄所有文本编辑行为都通过向这个窗口发送SCI_*消息来完成。比如你要获取当前光标所在的整行内容就会发送SCI_GETCURLINE要设置语法高亮语言就会发送SCI_SETLEXER。这种“命令消息”式的 API 设计贯穿整个 Scintilla配合ScintillaCall之类的封装用起来非常顺手。源码里展示了一个极其重要的机制文档Document和视图View的分离。同一个文件可以在拆分窗口里同时显示两份视图它们对应的底层文档对象是同一个你在左侧窗口输入文字右侧窗口会同步刷新。这个机制核心在 Scintilla 里实现Notepad 这边通过getCurrentBuffer拿到当前活动文档的指针再根据文档指针来同步各种 UI 状态比如标题栏的文件路径、状态栏的字符数、当前语言等。理解这个模型后你就明白为什么 Notepad 能轻松支持多标签、多视图、文档对比这些功能了——因为每个标签页本质上是“一个文档对象加一个视图对象”的组合而标签切换只是切换了当前激活的文档指针。这比很多从零硬写编辑器的项目要优雅得多。3.3 编码识别与 UTF-8 默认化v8.6 系列的重头戏v8.6 系列一个广受关注的变化就是默认编码从 ANSI 迁移到了 UTF-8。源码里对应的逻辑分散在Buffer、FileManager和文档加载路径中。加载一个文件时Notepad 会先尝试通过 BOM字节序标记判断编码如果没有 BOM再用启发式规则猜测最终把文件内容转成统一的内部表示。这个“内部表示”是另一个值得说的设计文件在内存里并不以 UTF-8 存储而是以 UTF-16宽字符形式保存。这样做的好处是 Scintilla 内部处理字符位置、选区范围时逻辑简单不用频繁处理变长编码的偏移问题。但坏处也很明显——文件大时内存占用会翻倍。源码里对“大文件”有单独的策略超过阈值时会提示是否以只读模式打开避免内存被撑爆。如果你自己在写文本处理工具这个“外部编码→内部统一编码→编辑→导出编码”的模型很值得抄作业。它把“展示层”和“存储层”彻底解耦让高亮、查找、字数统计这些功能不用关心文件原始的编码是什么。3.4 语法高亮与词法分析Lexilla 的魔法说到语法高亮就要提到 Lexilla 这个库。它和 Scintilla 是同一个生态里的产物专门负责“把一段文本切成不同类型的 token”。Notepad 里支持的上百种语言对应的 lexer 大多在lexilla/src目录下。每种语言一个.cxx文件里面就是用 C 写的一个状态机逐字符扫描给字符流打上“关键词”“字符串”“注释”“数字”之类的标记。比如你在源码里看到LexCPP.cxx不要以为那是整个 C 语法分析器它是一个极度简化的“高亮专用”扫描器它不构建抽象语法树不检查语法错误只做词法切分。这种设计的妙处在于高亮的目的只是让用户看得舒服不需要真正理解代码语义所以可以用最粗暴的状态机加上正则辅助来做到接近实时的性能。如果你对编译原理里的词法分析一章似懂非懂强烈建议挑LexCPP.cxx读一读。里面充斥着switch (ch)和“当前状态下一个字符决定新状态”的转移逻辑完全可以当作一个能跑的真实 lexer 入门示例。看完之后你对“状态机”这个概念的认知绝对会上一个台阶。4. 实操过程用 v8.6.6 源码编译出一个自己的 Notepad4.1 环境准备工具链和依赖项在动手编译之前先把环境配齐。我用的组合是 Windows 11 Visual Studio 2022安装时记得勾选下面几个组件“使用 C 的桌面开发”工作负载针对最新 v143 生成工具的 C ATL可选某些插件可能需要适用于最新 v143 生成工具的 C MFCx86 和 x64Windows 10 SDK 或 11 SDK选择较新的稳定版本即可为什么要强调 MFC因为 Notepad 主程序大量使用了 MFC 的框架类比如CWinApp、CFrameWnd、CMenu等。如果缺少 MFC 库编译到一半就会出现无数fatal error C1083: 无法打开包括文件: afxwin.h的报错。第一次编译时我漏装了 MFC卡了很久才意识到问题出在 VS 组件而不是源码本身。另外如果企业环境或安全软件限制了文件路径访问建议把源码放到一个路径比较短、不含空格的目录下比如D:\npp-src。Win32 项目对超长路径的兼容性普遍一般路径太深偶尔会触发编译器的路径长度限制。4.2 编译流程从打开解决方案到产出 exe打开PowerEditor/visual.net/NotepadPlusPlus.sln后先在工具栏把配置切成Release和x64或x86取决于你自己的习惯。目前 Notepad 官方主要分发 x64 版本如果你的插件或系统环境没有特殊限制直接选 x64。然后右键解决方案选择“生成解决方案”。这里解释一下生成顺序解决方案里有三个项目Scintilla和Lexilla会先被编译成静态库然后NotepadPlusPlus项目链接它们生成最终的 exe。这种“核心库独立成项目”的结构可以让你只改 Notepad 外壳代码而不必重新编译整个 Scintilla大幅缩短增量编译时间。整个 Release 构建在我的机器上大约花了 3 到 5 分钟。期间你会看到大量的.cpp文件被编译偶尔有警告飞过但只要没有 error最后就会在PowerEditor\bin目录下生成一个绿色的notepad.exe。这里有个小知识点PowerEditor\bin不只是一个输出文件夹它同时也是程序运行时的“工作目录”plugins、themes、localization这些子目录都在那里。所以你编译完后直接运行PowerEditor\bin\notepad.exe就能看到一个和官方发布版几乎一样的界面。4.3 改点什么试试从源码层面验证你的理解编译通过只是第一步真正有意思的是改造代码。我建议你从几个低风险高反馈的地方入手改标题栏字符串在PowerEditor/src/winmain.cpp或资源文件里找到窗口标题相关定义改成你自己的名字重新编译。这能确认你改的代码确实生效了。修改默认启动语言找到默认语言初始化逻辑把默认的L_TEXT改成L_CPP启动后你会发现所有无扩展名的文件默认按 C 高亮显示。这会让你对“默认值从哪里来”有一个直观感受。加一条自定义菜单项在菜单资源里添加一个 ID然后在消息处理函数里增加一个分支弹出一个MessageBox。这个实验能帮你快速建立“资源 ID→消息→处理函数”的映射感。很多人觉得“看源码”就是逐行读但实际上最有效的学习路径是“改一行代码编译跑起来看效果”。只要你能改对、能解释为什么改那一行会带来那个效果就说明你真正理解了代码。4.4 插件开发的第一个示例从模板开始如果你想更进一步可以基于官方插件模板写一个最简单的插件。步骤大概是复制PowerEditor/src/PluginTemplate整个文件夹重命名成你自己的项目名然后在 Visual Studio 里新建一个 DLL 项目把模板代码加进去再设置好NPP_PLUGIN这个预处理器宏。插件入口逻辑集中在PluginDefinition.cpp里核心函数是setInfo(NppData notpadPlusPlusData)和getFuncsArray()。setInfo在插件加载时被调用这时插件会拿到NppData里面包括 Notepad 主窗口句柄和 Scintilla 编辑区句柄。getFuncsArray返回一个FuncItem数组每个元素描述一个菜单项名称和对应的回调函数。编译生成 DLL 后把它丢到PowerEditor\bin\plugins\你的插件名\目录下重启 Notepad就能在插件菜单里看到它。这一步做完你对“主程序如何加载插件”的理解就不再停留在概念层面了。5. 常见问题与排查技巧实录5.1 编译报错找不到 MFC / ATL 头文件这是我遇到概率最高的问题也不只是新手会踩。解决方案是回到 Visual Studio Installer勾选“适用于最新 v143 生成工具的 C MFC (x86 和 x64)”等它装完再重新打开工程。同时注意整个解决方案里如果有多个项目Notepad 主项目应该是设置了“使用 MFC”的这个配置在项目属性→常规→使用 MFC 里能看到默认是“在共享 DLL 中使用 MFC”。5.2 链接阶段报大量未解析的外部符号如果你只编译 NotepadPlusPlus 项目而没有先生成 Scintilla/Lexilla就会遇到一堆LNK2019未解析外部符号。解决办法是把解决方案里的所有项目都选中右键“生成”让依赖项目先构建。如果你改了 Scintilla 的配置比如从动态库切到静态库还要检查项目属性里的链接器输入是否匹配否则 Scintilla 的.lib名称对不上也会出现类似的链接错误。5.3 修改了界面资源但界面没变化这种问题通常是因为资源文件.rc没有正确参与编译或者你改的是themes目录下的 XML 主题文件而不是资源文件。新版 Notepad 的界面配色大量依赖主题系统单纯的对话框资源改动会被主题覆盖。建议先测一个最明显的控件比如直接改对话框模板里某个按钮的文字编译后看效果如果没变再去查主题设置是否开启了“覆盖对话框背景”之类的选项。5.4 插件不显示或加载失败加载失败的排查顺序我一般是这样确认插件 DLL 是否在正确路径plugins\插件名\插件名.dll版本不同稍有差异但 v8.6.6 基本遵循这个结构。确认 DLL 位数是否匹配64 位主程序只能加载 64 位插件把 32 位插件硬塞进去会被静默忽略。去%APPDATA%\Notepad\或程序目录下的Sessions文件里查找加载错误日志。用 Dependency Walker 或dumpbin /exports查看 DLL 是否导出了isUnicode、getFuncsArray这几个必要符号。5.5 真机调试断点为什么没触发如果你用 Visual Studio 直接 F5 调试 Notepad会发现启动参数和调试符号必须对齐。调试前建议将解决方案配置切到 Debug同时在项目属性里确认“调试信息格式”是“用于编辑并继续的程序数据库”。还有一点Notepad 启动时会试图复用已存在的实例如果你已经开了一个 Notepad调试时那个新实例很可能会把启动消息转发给旧实例然后退出导致断点根本不进。处理方法是先关掉所有 Notepad 窗口再开始调试。6. 这份源码还能带给你什么把整个 v8.6.6 源码读透之后我对 Notepad 的“值得研究程度”有了新判断。它没有用花哨的架构没有引入复杂的框架很多设计“土”但极其有效。正是这种务实风格让它能够用相对精简的代码量实现一个功能丰富、扩展性极强、性能不差的编辑器。对我个人来说收获最大的不是学会用某个 Win32 API而是看到了一个中型开源项目如何平衡功能需求、历史包袱和代码整洁度。比如那个庞大的主窗口消息处理函数里面有无数的 if-else 和 switch 分支看起来毫无美感但它能保证 20 多年积攒下来的各种功能稳定共存。这种“在复杂中维持可用”的代码组织方式比教科书的“优雅设计”更贴近真实世界。如果你未来想参与 Notepad 的贡献或者打算开发自己的编辑器、终端、IDE 插件建议从PowerEditor/src下的Notepad_plus.cpp和ScintillaEditView.cpp这两个文件读起。它们是整个项目的“心脏”绝大多数核心行为都能在这里找到线索。还有一个非常实用的扩展方向结合你在 v8.6.6 源码里学到的插件接口给自己的日常 workflow 写一些小插件。比如批量格式化日志、自动插入时间戳、一键生成代码片段。这些插件不需要写得多精致只要能用你就会发现“给自己做工具”的效率和成就感远高于到处找现成的。最后提醒一句拿到源码后先自己编译一遍别急着改。这是你验证工具链、熟悉工程结构、建立信心的第一步。在这个基础上再一点点啃代码、加功能你会发现开源项目的乐趣远不止于“用”而已。本文还有配套的精品资源点击获取