除极少数情况外,大多数模块都需要与调用它们的 Lisp 程序交换数据:接收模块函数的参数,并从模块函数返回值。为此,模块 API 提供了 emacs_value 类型,用于表示通过 API 传递的 Emacs Lisp 对象;它在功能上等同于 Emacs C 原语中使用的 Lisp_Object 类型(see 编写 Emacs 原语)。本节介绍模块 API 中用于创建与基础 Lisp 数据类型对应的 emacs_value 对象的部分,以及如何从 C 代码中访问 emacs_value 对象中对应 Lisp 对象的数据。
下文描述的所有函数实际上都是 函数指针,通过每个模块函数接收的环境指针提供。因此,模块代码应通过环境指针调用这些函数,示例如下:
emacs_env *env; /* the environment pointer */ env->some_function (arguments...);
emacs_env 指针通常来自模块函数的第一个参数;如果需要在模块初始化函数中使用环境,则通过调用 get_environment 获取。
下文描述的大多数函数从 Emacs 25 版本开始可用,该版本是首个支持动态模块的 Emacs 版本。对于后续 Emacs 版本新增的少数函数,我们会标注其首次支持的版本号。
以下 API 函数从 emacs_value 对象中提取各种 C 数据类型的值。如果参数 emacs_value 对象的类型与函数预期不符,所有这些函数都会触发 wrong-type-argument 错误条件(see 类型谓词)。see 模块中的非本地退出 章节详细介绍了 Emacs 模块中错误信号的触发机制,以及如何在错误上报给 Emacs 之前在模块内部捕获错误。API 函数 type_of(see type_of)可用于获取 emacs_value 对象的类型。
intmax_t extract_integer (emacs_env *env, emacs_value arg) ¶该函数返回 arg 指定的 Lisp 整数的值。返回值的 C 数据类型 intmax_t 是 C 编译器支持的最宽整数类型,通常为 long long。如果 arg 的值无法存入 intmax_t,函数会使用错误符号 overflow-error 触发错误。
bool extract_big_integer (emacs_env *env, emacs_value arg, int *sign, ptrdiff_t *count, emacs_limb_t *magnitude) ¶该函数从 Emacs 27 版本开始可用,用于提取 arg 的整数值。arg 的值必须是整数(定长整数或大数)。如果 sign 不为 NULL,函数会将 arg 的符号(-1、0 或 +1)存入 *sign。绝对值的存储规则如下:如果 count 和 magnitude 均不为 NULL,则 magnitude 必须指向一个至少包含 *count 个 unsigned long 元素的数组。如果 magnitude 数组足够容纳 arg 的绝对值,函数会以小端格式将绝对值写入 magnitude 数组,将实际写入的数组元素数量存入 *count,并返回 true。如果 magnitude 数组空间不足,函数会将所需的数组大小存入 *count,触发错误,并返回 false。如果 count 不为 NULL 且 magnitude 为 NULL,函数会将所需的数组大小存入 *count 并返回 true。
Emacs 保证 *count 的最大所需值永远不会超过 min (PTRDIFF_MAX, SIZE_MAX) / sizeof (emacs_limb_t),因此你可以使用 malloc (*count * sizeof *magnitude) 分配 magnitude 数组,无需担心大小计算时发生整数溢出。
这是一个无符号整数类型,用作大数转换函数的绝对值数组的元素类型。该类型保证具有唯一的对象表示,即无填充位。
该宏展开为一个常量表达式,表示 emacs_limb_t 对象的最大可能值。
该表达式适用于 #if 条件判断。
double extract_float (emacs_env *env, emacs_value arg) ¶该函数以 C double 类型返回 arg 指定的 Lisp 浮点数的值。
struct timespec extract_time (emacs_env *env, emacs_value arg) ¶该函数从 Emacs 27 版本开始可用,将 arg 解析为 Emacs Lisp 时间值,并返回对应的 struct timespec。See 时刻。struct timespec 表示纳秒精度的时间戳,包含以下成员:
time_t tv_sec整数秒数。
long tv_nsec纳秒级的小数秒。
对于 extract_time 返回的时间戳,
该值始终非负且小于十亿。
(尽管 POSIX 要求 tv_nsec 的类型为 long,
但在部分非标准平台上该类型为 long long。)
See (libc)Elapsed Time。
如果 time 的精度高于纳秒,函数会向负无穷方向截断至纳秒精度。如果 time(截断至纳秒后)无法用 struct timespec 表示,函数会触发错误。例如,如果 time_t 是 32 位整数类型,值为一百亿秒的 time 会触发错误,而值为 600 皮秒的 time 会被截断为零。
如果需要处理无法用 struct timespec 表示的时间值,或需要更高精度,请调用 Lisp 函数 encode-time 并处理其返回值。See 时间转换。
bool copy_string_contents (emacs_env *env, emacs_value arg, char *buf, ptrdiff_t *len) ¶该函数将 arg 指定的 Lisp 字符串的 UTF-8 编码文本存入 buf 指向的 char 数组,数组空间需至少容纳 *len 字节(包含结束的空字节)。参数 len 不能为 NULL 指针;调用函数时,它需指向一个表示 buf 字节大小的值。
如果 *len 指定的缓冲区大小足够容纳字符串文本,函数会将实际复制到 buf 的字节数(包含结束的空字节)存入 *len,并返回 true。如果缓冲区空间不足,函数会触发 args-out-of-range 错误条件,将所需字节数存入 *len,并返回 false。See 模块中的非本地退出 介绍了如何处理待处理的错误条件。
参数 buf 可以是 NULL 指针,此时函数会将存储 arg 内容所需的字节数存入 *len 并返回 true。你可以通过这种方式确定存储特定字符串所需的 buf 大小:首先以 NULL 作为 buf 调用 copy_string_contents,然后根据函数存入 *len 的字节数分配足够的内存,最后以非 NULL 的 buf 再次调用函数完成实际的文本复制。
emacs_value vec_get (emacs_env *env, emacs_value vector, ptrdiff_t index) ¶该函数返回 vector 中索引为 index 的元素。向量第一个元素的索引为 0。如果 index 的值无效,函数会触发 args-out-of-range 错误条件。要从函数返回的值中提取 C 数据,请根据向量元素存储的 Lisp 数据类型,使用本文描述的其他提取函数。
ptrdiff_t vec_size (emacs_env *env, emacs_value vector) ¶该函数返回 vector 中的元素数量。
void vec_set (emacs_env *env, emacs_value vector, ptrdiff_t index, emacs_value value) ¶该函数将 value 存入 vector 中索引为 index 的元素。如果 index 的值无效,函数会触发 args-out-of-range 错误条件。
以下 API 函数从基础 C 数据类型创建 emacs_value 对象,所有函数均返回创建的 emacs_value 对象。
emacs_value make_integer (emacs_env *env, intmax_t n) ¶该函数接收整数参数 n,返回对应的 emacs_value 对象。根据 n 的值是否在 most-negative-fixnum 和 most-positive-fixnum 设定的范围内(see 整数基础),返回定长整数或大数。
emacs_value make_big_integer (emacs_env *env, int sign, ptrdiff_t count, const emacs_limb_t *magnitude) ¶该函数从 Emacs 27 版本开始可用,接收任意大小的整数参数,返回对应的 emacs_value 对象。参数 sign 指定返回值的符号。如果 sign 非零,则 magnitude 必须指向一个至少包含 count 个元素的数组,以小端格式指定返回值的绝对值。
以下示例使用 GNU 高精度算术库(GMP)计算给定整数之后的下一个可能素数。See (gmp)Top 是 GMP 的概述,see (gmp)Integer Import and Export 介绍了如何在 magnitude 数组与 GMP 的 mpz_t 值之间进行转换。
#include <emacs-module.h>
int plugin_is_GPL_compatible;
#include <assert.h>
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <gmp.h>
static void
memory_full (emacs_env *env)
{
static const char message[] = "Memory exhausted";
emacs_value data = env->make_string (env, message,
strlen (message));
env->non_local_exit_signal
(env, env->intern (env, "error"),
env->funcall (env, env->intern (env, "list"), 1, &data));
}
enum
{
order = -1, endian = 0, nails = 0,
limb_size = sizeof (emacs_limb_t),
max_nlimbs = ((SIZE_MAX < PTRDIFF_MAX ? SIZE_MAX : PTRDIFF_MAX)
/ limb_size)
};
static bool
extract_big_integer (emacs_env *env, emacs_value arg, mpz_t result)
{
ptrdiff_t nlimbs;
bool ok = env->extract_big_integer (env, arg, NULL, &nlimbs, NULL);
if (!ok)
return false;
assert (0 < nlimbs && nlimbs <= max_nlimbs);
emacs_limb_t *magnitude = malloc (nlimbs * limb_size);
if (magnitude == NULL)
{
memory_full (env);
return false;
}
int sign;
ok = env->extract_big_integer (env, arg, &sign, &nlimbs, magnitude);
assert (ok);
mpz_import (result, nlimbs, order, limb_size, endian, nails, magnitude);
free (magnitude);
if (sign < 0)
mpz_neg (result, result);
return true;
}
static emacs_value
make_big_integer (emacs_env *env, const mpz_t value)
{
size_t nbits = mpz_sizeinbase (value, 2);
int bitsperlimb = CHAR_BIT * limb_size - nails;
size_t nlimbs = nbits / bitsperlimb + (nbits % bitsperlimb != 0);
emacs_limb_t *magnitude
= nlimbs <= max_nlimbs ? malloc (nlimbs * limb_size) : NULL;
if (magnitude == NULL)
{
memory_full (env);
return NULL;
}
size_t written;
mpz_export (magnitude, &written, order, limb_size, endian, nails, value);
assert (written == nlimbs);
assert (nlimbs <= PTRDIFF_MAX);
emacs_value result = env->make_big_integer (env, mpz_sgn (value),
nlimbs, magnitude);
free (magnitude);
return result;
}
static emacs_value
next_prime (emacs_env *env, ptrdiff_t nargs, emacs_value *args,
void *data)
{
assert (nargs == 1);
mpz_t p;
mpz_init (p);
extract_big_integer (env, args[0], p);
mpz_nextprime (p, p);
emacs_value result = make_big_integer (env, p);
mpz_clear (p);
return result;
}
int
emacs_module_init (struct emacs_runtime *runtime)
{
emacs_env *env = runtime->get_environment (runtime);
emacs_value symbol = env->intern (env, "next-prime");
emacs_value func
= env->make_function (env, 1, 1, next_prime, NULL, NULL);
emacs_value args[] = {symbol, func};
env->funcall (env, env->intern (env, "defalias"), 2, args);
return 0;
}
emacs_value make_float (emacs_env *env, double d) ¶该函数接收 double 类型参数 d,返回对应的 Emacs 浮点数值。
emacs_value make_time (emacs_env *env, struct timespec time) ¶该函数从 Emacs 27 版本开始可用,接收 struct timespec 类型参数 time,以 (ticks . hz) 对的形式返回对应的 Emacs 时间戳。See 时刻。返回值与 time 表示完全相同的时间戳:所有输入值均可表示,且无精度损失。time.tv_sec 和 time.tv_nsec 可以是任意值,特别地,不要求 time 是标准化格式。这意味着 time.tv_nsec 可以为负数或大于 999,999,999。
emacs_value make_string (emacs_env *env, const char *str, ptrdiff_t len) ¶该函数从 str 指向的 C 文本字符串创建 Emacs 字符串,len 是不包含结束空字节的字节长度。str 中的原始字符串可以是 ASCII 字符串或 UTF-8 编码的非 ASCII 字符串;可以包含嵌入的空字节,且无需在 str[len] 位置以结束空字节结尾。如果 len 为负数或超过 Emacs 字符串的最大长度,函数会触发 overflow-error 错误条件。如果 len 为 0,则 str 可以是 NULL,否则必须指向有效的内存。对于非零的 len,make_string 返回唯一的可变字符串对象。
emacs_value make_unibyte_string (emacs_env *env, const char *str, ptrdiff_t len) ¶该函数与 make_string 类似,但对 C 字符串中的字节值无限制,可用于以单字节字符串的形式向 Emacs 传递二进制数据。
API 未提供操作 Lisp 数据结构的函数,例如使用 cons 和 list 创建列表(see 构建 cons 单元与列表)、使用 car 和 cdr 提取列表成员(see 访问列表元素)、使用 vector 创建向量(see 向量相关函数)等。对于这些操作,请使用下一小节介绍的 intern 和 funcall 调用对应的 Lisp 函数。
通常情况下,emacs_value 对象的生命周期非常短:当创建它们所用的 emacs_env 指针超出作用域时,生命周期即结束。偶尔你可能需要创建 全局引用(global references):生命周期可自定义的 emacs_value 对象。使用以下两个函数管理此类对象。
emacs_value make_global_ref (emacs_env *env, emacs_value value) ¶该函数返回 value 的全局引用。
void free_global_ref (emacs_env *env, emacs_value global_value) ¶该函数释放之前由 make_global_ref 创建的 global_value。调用后 global_value 不再有效。你的模块代码应将每一次 make_global_ref 调用与对应的 free_global_ref 调用配对使用。
保存后续需要传递给模块函数的 C 数据结构的一种替代方案是创建 用户指针(user pointer) 对象。用户指针(user-ptr)对象是封装了 C 指针的 Lisp 对象,可以关联一个终结器函数,该函数会在对象被垃圾回收时调用(see 垃圾回收)。模块 API 提供了创建和访问 user-ptr 对象的函数。如果这些函数作用于不表示 user-ptr 对象的 emacs_value,会触发 wrong-type-argument 错误条件。
emacs_value make_user_ptr (emacs_env *env, emacs_finalizer fin, void *ptr) ¶该函数创建并返回一个封装了 C 指针 ptr 的 user-ptr 对象。终结器函数 fin 可以是 NULL 指针(表示无终结器),也可以是具有以下签名的函数:
typedef void (*emacs_finalizer) (void *ptr);
如果 fin 不为 NULL 指针,当 user-ptr 对象被垃圾回收时,会以 ptr 为参数调用该函数。不要在终结器中运行耗时较长的代码,因为垃圾回收必须快速完成以保证 Emacs 的响应性。
void *get_user_ptr (emacs_env *env, emacs_value arg) ¶该函数从 arg 表示的 Lisp 对象中提取 C 指针。
void set_user_ptr (emacs_env *env, emacs_value arg, void *ptr) ¶该函数将 arg 表示的 user-ptr 对象中嵌入的 C 指针设置为 ptr。
emacs_finalizer get_user_finalizer (emacs_env *env, emacs_value arg) ¶该函数返回 arg 表示的 user-ptr 对象的终结器;如果没有终结器,则返回 NULL。
void set_user_finalizer (emacs_env *env, emacs_value arg, emacs_finalizer fin) ¶该函数将 arg 表示的 user-ptr 对象的终结器修改为 fin。如果 fin 是 NULL 指针,user-ptr 对象将不再有终结器。
注意,emacs_finalizer 类型同时适用于用户指针和模块函数终结器。See Module Function Finalizers。