编写模块时,首先需要包含头文件 emacs-module.h 并定义 GPL 兼容符号:
#include <emacs-module.h> int plugin_is_GPL_compatible;
emacs-module.h 会随 Emacs 安装被放置到系统的头文件目录中。你也可以在 Emacs 源码树中找到该文件。
接下来,为模块编写初始化函数。
int emacs_module_init (struct emacs_runtime *runtime) ¶Emacs 在加载模块时会调用此函数。如果模块未导出名为 emacs_module_init 的函数,尝试加载该模块将会触发错误。初始化函数初始化成功时应返回 0,失败则返回非 0 值。在后一种情况下,Emacs 会抛出错误,模块加载将失败。如果用户在初始化过程中按下 C-g,Emacs 会忽略初始化函数的返回值并退出(see 退出)。(如有需要,你可以在初始化函数内部捕获用户退出操作,see should_quit。)
参数 runtime 是指向 C 结构体 struct 的指针,该结构体包含 2 个公共字段:size,表示该结构体的字节大小;以及 get_environment,指向一个函数,该函数允许模块初始化函数访问 Emacs 环境对象及其接口。
初始化函数应执行模块所需的一切初始化操作。此外,它还可以完成以下工作:
模块可以通过比较 runtime 结构体的 size 成员与编译进模块中的对应值,校验加载该模块的 Emacs 可执行文件是否与模块兼容:
int
emacs_module_init (struct emacs_runtime *runtime)
{
if (runtime->size < sizeof (*runtime))
return 1;
}
如果传递给模块的运行时对象大小小于其预期值,说明该模块是针对更新版本的 Emacs 编译的,即模块可能与当前运行的 Emacs 二进制程序不兼容。
此外,模块还可以校验模块 API 与自身预期是否兼容。以下示例代码假定位于上述 emacs_module_init 函数内部:
emacs_env *env = runtime->get_environment (runtime);
if (env->size < sizeof (*env))
return 2;
这段代码通过 runtime 结构体中的指针调用 get_environment 函数,获取指向 API 环境(environment)的指针。该环境是一个 C 结构体 struct,同样包含 size 字段以表示自身字节大小。
最后,你可以通过比较 Emacs 传递的环境大小与已知版本的大小,编写能够兼容旧版 Emacs 的模块,示例如下:
emacs_env *env = runtime->get_environment (runtime);
if (env->size >= sizeof (struct emacs_env_26))
emacs_version = 26; /* Emacs 26 or later. */
else if (env->size >= sizeof (struct emacs_env_25))
emacs_version = 25;
else
return 2; /* Unknown or unsupported version. */
该方法可行的原因是,新版 Emacs 只会 增加环境结构体的成员,而不会 移除任何成员,因此结构体大小只会随 Emacs 新版本发布而增长。得知 Emacs 版本后,模块便可仅使用该版本中存在的模块 API 部分,这些部分在后续版本中保持不变。
emacs-module.h 定义了预处理宏 EMACS_MAJOR_VERSION。它会展开为整数字面量,表示该头文件支持的最新 Emacs 主版本。See 版本信息。注意,EMACS_MAJOR_VERSION 的值是编译期常量,并不代表当前运行并加载你模块的 Emacs 版本。如果你希望模块兼容不同版本的 emacs-module.h 以及不同版本的 Emacs,可以基于 EMACS_MAJOR_VERSION 使用条件编译。
我们建议模块始终执行兼容性校验,除非模块仅在初始化函数中完成全部工作,且不访问任何 Lisp 对象,也不使用任何可通过环境结构体访问的 Emacs 函数。
这一步为模块函数赋予名称,使 Lisp 代码可以通过该名称调用函数。我们将在下文 编写模块函数 中介绍具体做法。