编写 Emacs 模块的主要目的,是为加载该模块的 Lisp 程序提供额外的函数。本小节介绍如何编写此类 模块函数(module functions)。
模块函数具有以下通用形式与签名:
emacs_value emacs_function (emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data) ¶参数 env 提供指向 API 环境的指针,用于访问 Emacs 对象与函数。参数 nargs 是所需的参数个数,可以为 0(更灵活的参数个数指定方式参见下方 make_function),args 是指向函数参数数组的指针。参数 data 指向函数所需的额外数据,该数据在通过 make_function(见下文)从 emacs_function 创建 Emacs 函数时指定。
模块函数使用 emacs_value 类型在 Emacs 与模块之间传递 Lisp 对象(see Lisp 与模块值之间的转换)。下文及后续子小节介绍的 API 提供了在基础 C 数据类型与对应 emacs_value 对象之间转换的工具。
在模块函数体内,切勿尝试访问 args 数组中索引超出 nargs-1 的元素:args 数组的内存恰好分配为容纳 nargs 个值,越界访问极有可能导致模块崩溃。特别地,如果运行时传递给函数的 nargs 值为 0,则绝不能访问 args,因为此时不会为其分配内存。
模块函数总会返回一个值。如果函数正常返回,调用它的 Lisp 代码将看到与函数返回的 emacs_value 对应的 Lisp 对象。但如果用户按下 C-g,或模块函数及其被调用函数抛出错误或发生非局部退出(see 模块中的非本地退出),Emacs 会忽略返回值,并按照 Lisp 代码遇到相同情况时的行为执行退出或跳转。
头文件 emacs-module.h 提供 emacs_function 类型,作为指向模块函数的函数指针别名。
为模块函数编写好 C 代码后,应使用环境中提供的 make_function 函数指针(环境指针由 get_environment 返回)将其创建为 Lisp 函数对象。这一操作通常在模块初始化函数中完成(see module initialization function),且需在校验 API 兼容性之后进行。
emacs_value make_function (emacs_env *env, ptrdiff_t min_arity, ptrdiff_t max_arity, emacs_function func, const char *docstring, void *data) ¶该函数从 C 函数 func 创建并返回一个 Emacs 函数,其签名与 emacs_function 所述一致。
参数 min_arity 与 max_arity 指定 func 可接受的参数最小与最大个数。max_arity 可以使用特殊值 emacs_variadic_function,使函数接受任意数量的参数,类似于 Lisp 中的 &rest 关键字(see 参数列表的特性)。
参数 data 用于在 func 被调用时传递任意额外数据。传递给 make_function 的指针会原封不动地传递给 func。
参数 docstring 指定函数的文档字符串。它可以是 ASCII 字符串、UTF-8 编码的非 ASCII 字符串,或 NULL 指针;在后一种情况下函数将没有文档。文档字符串末尾可以使用一行说明函数的调用约定,参见 函数的文档字符串。
由于所有模块函数都必须将环境指针作为第一个参数,make_function 调用可以在任意模块函数中执行,但通常建议在模块初始化函数中完成,以便模块加载后所有函数即可被 Emacs 识别。
最后,你需要将 Lisp 函数绑定到一个符号,使 Lisp 代码可以按名称调用你的函数。为此可使用模块 API 中的 intern 函数(see intern),其指针同样存在于模块函数可访问的环境中。
结合以上步骤,在模块初始化函数中实现让 C 函数 module_func 可从 Lisp 以 module-func 调用的代码如下:
emacs_env *env = runtime->get_environment (runtime);
emacs_value func = env->make_function (env, min_arity, max_arity,
module_func, docstring, data);
emacs_value symbol = env->intern (env, "module-func");
emacs_value args[] = {symbol, func};
env->funcall (env, env->intern (env, "defalias"), 2, args);
代码通过调用 env->intern 让 Emacs 识别符号 module-func,再调用 Emacs 的 defalias 将函数绑定到该符号。注意也可以使用 fset 代替 defalias,二者区别参见 defalias。
包括 emacs_module_init 函数在内的模块函数(see module initialization function),仅可在被 Emacs 直接或间接调用期间,通过有效的 emacs_env 指针调用环境函数与 Emacs 交互。换言之,如果模块函数想要调用 Lisp 函数或 Emacs 原语、在 emacs_value 对象与 C 数据类型之间转换(see Lisp 与模块值之间的转换),或以其他方式与 Emacs 交互,调用栈中必须存在从 Emacs 到 emacs_module_init 或某个模块函数的调用。模块函数不得在垃圾回收运行时与 Emacs 交互;see 垃圾回收。它们仅可在 Emacs 创建的 Lisp 解释器线程(包括主线程)中与 Emacs 交互;see 线程。命令行选项 --module-assertions 可检测部分上述规则的违反情况。See Initial Options in The GNU Emacs Manual。
使用模块 API 可以定义更复杂的函数与数据类型:内联函数、宏等。但生成的 C 代码会变得冗长且难以阅读。因此我们建议,将创建函数与数据结构的模块代码控制在绝对最少的程度,其余工作交由随模块一同发布的 Lisp 包完成,因为在 Lisp 中完成这些额外任务要简单得多,且代码可读性更高。例如,在上述定义好 module-func 模块函数后,可以通过如下简单的 Lisp 包装基于它创建宏 module-macro:
(defmacro module-macro (&rest args) "Documentation string for the macro." (module-func args))
与模块配套的 Lisp 包可在自身加载到 Emacs 时,通过 load 原语加载该模块(see Emacs 动态模块)。
默认情况下,由 make_function 创建的模块函数是非交互式的。若要使其支持交互,可使用以下函数。
void make_interactive (emacs_env *env, emacs_value function, emacs_value spec) ¶该函数从 Emacs 28 开始可用,它使用交互规范 spec 将 function 设置为交互式函数。Emacs 对 spec 的解析与 interactive 形式的参数一致。使用 interactive,以及 see interactive 的代码字符。function 必须是由 make_function 返回的 Emacs 模块函数。
注意,模块原生不支持获取模块函数的交互规范。可使用函数 interactive-form 完成该操作。使用 interactive。一旦使用 make_interactive 将模块函数设为交互式,便无法再将其改回非交互式。
如果你希望在模块函数对象(即 make_function 返回的对象)被垃圾回收时执行某些代码,可以安装一个 函数终结器(function finalizer)。函数终结器从 Emacs 28 开始可用。例如,如果你将某个堆分配的结构传递给了 make_function 的 data 参数,可以使用终结器释放该结构。See (libc)Basic Allocation,以及 see (libc)Freeing after Malloc。终结器函数具有如下签名:
void finalizer (void *data)
此处 data 接收调用 make_function 时传递给 data 的值。注意终结器不能以任何方式与 Emacs 交互。
刚调用 make_function 创建的新函数没有终结器。如有需要,可使用 set_function_finalizer 进行设置。
void emacs_finalizer (void *ptr) ¶头文件 emacs-module.h 提供 emacs_finalizer 类型,作为 Emacs 终结器函数的类型别名。
emacs_finalizer get_function_finalizer (emacs_env *env, emacs_value arg) ¶该函数从 Emacs 28 开始可用,返回与 arg 所代表的模块函数关联的函数终结器。arg 必须指向一个模块函数,即由 make_function 返回的对象。如果该函数未关联终结器,则返回 NULL。
void set_function_finalizer (emacs_env *env, emacs_value arg, emacs_finalizer fin) ¶该函数从 Emacs 28 开始可用,将与 arg 所代表的模块函数关联的函数终结器设置为 fin。arg 必须指向一个模块函数,即由 make_function 返回的对象。fin 可以是 NULL 以清除 arg 的函数终结器,也可以是指向一个函数的指针,该函数会在 arg 代表的对象被垃圾回收时调用。每个函数最多只能设置一个终结器;如果 arg 已有终结器,将会被 fin 替换。