在之前的一篇文章 《曾经我认为C语言就是个弟弟》 中,我们通过使用 Windows 系统自带的 EDIT 控件,创建了一个简单的文本编辑器。而且在文章的最后,还承诺要不使用 EDIT 控件,自己制作一个十六进制编辑器。 本篇文章,将会逐步实现我们的编辑器。

让我们写一个 Win32 文本编辑器吧 - 1. 简介


在之前的一篇文章 《​​曾经我认为C语言就是个弟弟​​​》 中,我们通过使用 ​​Windows​​​ 系统自带的 ​​EDIT​​​ 控件,创建了一个简单的文本编辑器。而且在文章的最后,还承诺要不使用 ​​EDIT​​ 控件,自己制作一个十六进制编辑器。

后来想到,既然十六进制编辑器都做了,作为一个程序员,不如写一个文本编辑器吧,既可以编辑二进制,又可以编辑文本,岂不美哉。


由于实现一个编辑器的复杂性相对比较大,一篇内容肯定完不成。所以,这里决定将整个过程作为一个系列来编辑。

本篇是系列的第一篇。在本篇文章中,将会对我们期望获得的结果进行简单的描述,并进行基础代码框架进行构建。

本文主要包含两个部分,如下:


  1. 项目简介


在项目简介部分,将会对我们要实现的目标编辑器的样子,以及使用方法进行介绍。同时,也会对本系列接下来的文章要讨论的主题进行大致说明。


  1. 基础代码框架搭建


在代码框架搭建部分,将会对项目的创建,设置进行说明。并编辑我们的基础代码。

1. 项目简介

a. 目标


在 ​​Windows11​​ 中,已经将记事本的编辑控件由 ​​EDIT​​ 替换为 ​​RichEditD2DPT​​,详情参考​​Windows 11 Notepad​​。但是由于关于 ​​RichEditD2DPT​​ 控件的描述太少,不知道其具体的用方法。
所以,这里我们以 ​​EDIT​​ 控件的接口为准,并实现附加功能。


在此,我们的目标是:通过 ​​C​​​ 语言,调用 ​​Win32​​​ 接口,生成一个文本编辑器。目标编辑器除了实现和 ​​Windows​​​ 提供的默认文本编辑器 ​​EDIT​​​ 的​​所有消息​​处理,还提供如下功能:

  1. 可以设置字体颜色
  2. 对于 ​​EDIT​​ 控件,虽然可以设置其文本字体,但是没有设置颜色的方法。

  3. 可以编辑比较大的文件

  4. 对于平时的文件编辑器来说,编辑小文件基本上都差不多,但是当遇到比较大的文件时(比如1G),很可能无能为力,甚至卡死。
    所以,既然我们要做一个新的编辑器,自然要考虑大文件的编辑问题。


  5. 采取​​Direct Write​​方式实现,而不是和普通的编辑器一样,通过 ​​GDI​​。
  6. 做此选择的原因,除了因为 ​​Direct Write​​ 支持颜色之外,还有一些其它优点,详情可以点击​​Direct Write​​进行参考。

  7. 支持 ​​EDIT控件​​ 的所有消息。
  8. 为了使得旧 ​​Win32​​ 代码更好的使用本编辑器,所有 ​​EDIT​​ 控件支持的操作,本项目都应该支持。

  9. 处理​​\r\n​
  10. 此选择和​​Windows 11​​中的选择具有相同的理由,为了更好的处理换行。

  11. 支持撤销操作/恢复上一步操作
  12. 在编辑文本时,难免会想恢复到不久之前的版本,撤销操作允许你做到这个。而当你后悔撤销的时候,也应该能够恢复到最新版本,恢复上一步允许你做到重新执行你之前的操作。

  13. 支持 ​​Unicode​​ 编码
  14. 可以设置注解
  15. 在编辑时,尤其是要编辑二进制文件时,我们有时候可能要对某个字节,或某段文本进行注解。我们的编辑器允许提供一个注解结构,以在显示文档时,可以进行注解显示。

  16. 可以进行二进制文件编辑。
  17. 二进制编辑虽然不常用,但是,不能在需要用的时候找不到。所以,这里提供了二进制编辑功能。此功能和注解相结合,就可以进行辅助二进制文件的分析。

b. 目标编辑器的样子

作为一个现代的编辑器,我们希望它有一般编辑器都应该有的能力,下面是一个编辑器的例子:

不难看到,作为一个编辑器,应该支持行号,高亮,多字体,滚动条等内容。这在我们的编辑器中,都将一一实现,并详细描述实现过程。

c. 项目结构

对于本项目来说,一共包含两个子项目,如下:

  • vicapp:
    用于对编辑器控件进行调用的样例程序
  • vitality-controls:
    编辑器控件的实现项目,将作为一个​​​DLL​​ 文件提供给调用者。

d. 参考链接

代码地址:​​https://github.com/vitalitylee/vitality-controls​

2. 基础代码框架搭建

接下来,我们详细说明整个项目的构建过程。

a. 打开 ​​Visual Studio​​​ ,并点击​​创建新项目​​如下:

让我们写一个 Win32 文本编辑器吧 - 1. 简介_文本编辑器

b. 在​​创建新项目​​​对话框中,选择空项目,并点击​​下一步​​,如下:

让我们写一个 Win32 文本编辑器吧 - 1. 简介_文本编辑器_02

c. 在​​配置新项目​​对话框中,设置项目内容,如下:

让我们写一个 Win32 文本编辑器吧 - 1. 简介_右键_03

让我们写一个 Win32 文本编辑器吧 - 1. 简介_控件_04

d. 右键项目​​vitality-conrols​​​,并点击属性,弹出​​属性​​对话框,如下:

让我们写一个 Win32 文本编辑器吧 - 1. 简介_右键_05

e. 在​​常规​​​选项卡中,设置配置类型为 ​​动态库​​,如下:

让我们写一个 Win32 文本编辑器吧 - 1. 简介_右键_06

让我们写一个 Win32 文本编辑器吧 - 1. 简介_文本编辑器_07

f. 一次点击​​配置属性​​​->​​链接器​​​->​​系统​​​,并设置​​子系统​​​为​​窗口​​,如下:

让我们写一个 Win32 文本编辑器吧 - 1. 简介_文本编辑器_08

让我们写一个 Win32 文本编辑器吧 - 1. 简介_右键_09

g. 鼠标右键​​源文件​​​文件夹,选择​​添加​​​->​​新建项​​​,弹出​​添加新项​​对话框,如下:


注意,这里添加 ​​.c​​​文件,而不是 ​​.cpp​​。


让我们写一个 Win32 文本编辑器吧 - 1. 简介_文本编辑器_10

让我们写一个 Win32 文本编辑器吧 - 1. 简介_文本编辑器_11

h. 输入 ​​DLL​​ 的入口代码,如下:

#include <Windows.h>

BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

让我们写一个 Win32 文本编辑器吧 - 1. 简介_右键_12

i. 添加预处理声明

再次打开项目 ​​vitality-controls​​​ 的属性页面,不难发现,比添加源文件之前,左侧多了一个​​C/C++​​​节点,左侧依次选择​​配置属性​​​->​​C/C++​​​->​​预处理器​​​选项,在右侧的​​预处理器定义​​​中,添加 ​​VITALITY_CONTROLS_EXPORTS​​​声明,最终的值应为​​_DEBUG;VITALITY_CONTROLS_EXPORTS;_CONSOLE;%(PreprocessorDefinitions)​​,如下图所示:

让我们写一个 Win32 文本编辑器吧 - 1. 简介_文本编辑器_13

j. 添加接口声明文件

为了在两个项目中公用一套代码,新建的​​.h​​​文件,放置在了解决方案根目录下的​​shared-include​​目录下,如下:

让我们写一个 Win32 文本编辑器吧 - 1. 简介_文本编辑器_14

k. 修改接口代码

向新建的​​vitality-controls.h​​文件中,输入如下代码:

#pragma once

#ifdef VITALITY_CONTROLS_EXPORTS
#define VIC_API __declspec(dllexport)
#else
#define VIC_API __declspec(dllimport)
#endif // VITALITY_CONTROLS_EXPORTS

#include <stdio.h>

VIC_API void vic_prints(const char* str);

让我们写一个 Win32 文本编辑器吧 - 1. 简介_控件_15

并向​​main.c​​​中添加新建的​​vitality-controls.h​​​文件引用,并添加​​vic_prints​​函数实现,修改后代码如下:

#include <Windows.h>

#include "../../shared-include/vitality-controls.h"

BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

VIC_API void vic_prints(const char* str) {
puts(str);
}

让我们写一个 Win32 文本编辑器吧 - 1. 简介_文本编辑器_16

l. 生成接口

鼠标右键解决方案,并点击​​重新生成解决方案​​快捷菜单,如下:

让我们写一个 Win32 文本编辑器吧 - 1. 简介_右键_17

最终,你将得到一个编译好的​​vitality-controls.dll​​文件,如下:

让我们写一个 Win32 文本编辑器吧 - 1. 简介_控件_18

m. 查看导出函数是否正常导出

点击操作系统的​​开始​​​菜单,并点击​​Developer Command Prompt for VS XXXX​​​,其中​​XXXX​​​随着你使用的​​Visual Studio​​​ 版本不同而不同,本文中使用的版本为​​Visual Studio 2022​​。

所以,显示如下:

让我们写一个 Win32 文本编辑器吧 - 1. 简介_控件_19

点击菜单后,会出现命令行窗口,在窗口中输入命令​​cd/d [path]​​切换到目标文件所在目录,其中​​[path]​​为你生成的目标 ​​DLL​​ 的所在目录,如:

让我们写一个 Win32 文本编辑器吧 - 1. 简介_控件_20

切换到目标目录,就可以使用​​dumpbin​​​查看导出函数。在命令行中输入​​dumprin /exports vitality-controls.dll​​,看到如下内容,说明你生成成功了:

让我们写一个 Win32 文本编辑器吧 - 1. 简介_文本编辑器_21

n. 新建测试项目

右键​​解决方案​​​, 点击菜单​​添加​​​->​​新建项目​​​,根据之前的步骤,添加一个新建项目 ​​vicapp​​​,添加主文件​​vicapp-main.c​​,并输入如下代码:

#include "../../shared-include/vitality-controls.h"

int main(int argc, char** argv) {
vic_prints("hello vic.");
return 0;
}

如下所示:

让我们写一个 Win32 文本编辑器吧 - 1. 简介_控件_22

o. 设置启动项目

右键 ​​vicapp​​​ 项目,并点击​​设为启动项目​​菜单,如下:

让我们写一个 Win32 文本编辑器吧 - 1. 简介_文本编辑器_23

设置完成后,点击启动按钮,将默认启动​​启动项目​​。

p. 添加项目引用

为了可以使得 ​​vicapp​​​ 程序能够引用到 ​​vitality-controls.dll​​ 目标文件,需要设置两个项目之间的引用关系。

右键点击 ​​vicapp​​​ 项目,点击快捷菜单​​添加​​​->​​引用​​​,弹出​​添加引用​​对话框,如下:

让我们写一个 Win32 文本编辑器吧 - 1. 简介_控件_24

让我们写一个 Win32 文本编辑器吧 - 1. 简介_控件_25

p. 运行程序

点击​​Visual Studio​​​的​​本地 Windows 调试器​​​按钮,程序将启动,并输出 ​​hello vic.​​,如下:

让我们写一个 Win32 文本编辑器吧 - 1. 简介_右键_26

至此,我们项目的基础结构已经搭建完成。

下篇文章,我们将首先实现控件的初始化,以及控件展示功能,并讨论一下我们之后的项目计划,敬请期待。

​让我们写一个Win32文本编辑器吧​​​ 系列文章,其代码对应项目​​vitality-controls​​,主要对一个文本编辑器的实现过程进行说明。

如果要获取到实时更新,欢迎微信扫描下方二维码,关注微信公众号​​编程之路漫漫​​,码途求知己,天涯觅一心。

让我们写一个 Win32 文本编辑器吧 - 1. 简介_右键_27