emacs-modules简介
Table of Contents
1 dynamic-module的工作原理
Emacs25新增了一个新功能就是允许你加载动态模块. 也就是说,除了Emacs Lisp,你可以使用原生编译的代码来扩展Emacs, 使用原生代码扩展Emacs的两个优势在于:能够更好与Emacs进行整合,而且速度也更快.
所谓模块,类似于其他程序中的插件. 每个模块都被编译成共享库的形式(根据操作系统的不同,它的文件后缀可能为.so, .dylib 或 .dll). 这种共享库提供了一些函数可以供Emacs主程序调用. 插件API或者说模块的接口描述了主程序的入口点是怎样的,以及模块可以用哪些东西来与主程序进行交互.
具体来说,该API原型定义在 emacs-module.h
头文件中. 要创建一个Emacs模块无需链接任何Emacs的库或者执行程序,只需要引用该头文件然后实现该API即可.
emacs-module.h
文件定义了很多东西,其中最重要的是以下几点:
- 一种名叫
emacs_value
的类型,用于表示与Emacs传递Lisp类型的值. - 一个名为
emacs_runtime
的结构体,用于获取一个指向环境的指针. - 一个名为
emacs_env
的结构体,该结构体包含了各种指向API的函数指针. 模块主入口点的函数原型.
extern int emacs_module_init (struct emacs_runtime *ert);
当加载动态模块时,Emacs会调用模块主入口点的函数,并传递給该函数一个指向"emacs运行期"的指针作为参数. 通过该指针,你可以获取到指向"emacs环境"的指针:
emacs_env *env = ert->get_environment(ert);
"emacs环境"中提供了许多函数来让你与lisp解析器进行交互. 所有的这些函数的第一个参数都需要是指向"emacs环境"的指针.
2 一个module的简单例子
下面我们会定义一个名为 mymode
的模块,该模块中定义了一个函数 mymod-test
, 该函数返回整数42.
首先我们需要得到一份Emacs源码的拷贝. 因为你需要以支持module的方式来编译Emacs(默认是不支持module的), 同时你也需要 emacs_module.h
头文件.
Emacs源代码可以从这里下载: http://alpha.gnu.org/gnu/emacs/pretest/emacs-25.0.92.tar.xz
编译的命令为 ./configure --with-modules && make
. 你可以通过运行 ./src/emacs -Q
来测试刚编译出来的emacs. 这里 -Q
表示Emacs启动时不要加载初始化文件.
2.1 Makefile
# 设置emacs源代码路径 # (你可以在makefile中直接设置,也可以make时在命令行中提供) #ROOT = CC = gcc LD = gcc CFLAGS = -ggdb3 -Wall LDFLAGS = all: mymod.so # make shared library out of the object file %.so: %.o $(LD) -shared $(LDFLAGS) -o $@ $< # compile source file to object file %.o: %.c $(CC) $(CFLAGS) -I$(ROOT)/src -fPIC -c $<
2.2 mymod.c
Emacs模块必须遵循GPL协议. 为了保证这一点,Emacs在加载模块之前,会监测该模块是否包含有名为 plugin_is_GPL_compatible
的变量.
#include <emacs-module.h> /* 强制申明GPL符号 */ int plugin_is_GPL_compatible; /* 定义新emacs lisp函数. 所有暴露給Emacs使用的函数都必须符合该原型 */ static emacs_value Fmymod_test (emacs_env *env, ptrdiff_t nargs, emacs_value args[], void *data) { return env->make_integer (env, 42); } /* 将函数Sfun以name为名称,暴露給emacs使用 */ static void bind_function (emacs_env *env, const char *name, emacs_value Sfun) { /* 使用fset函数来将SFUN函数绑定到名为NAME的符号 */ /* 通过intern将字符串转换为symbol */ emacs_value Qfset = env->intern (env, "fset"); emacs_value Qsym = env->intern (env, name); /* 准备好参数数组*/ emacs_value args[] = { Qsym, Sfun }; /* 调用fset函数,这里的2为参数的个数*/ env->funcall (env, Qfset, 2, args); } /* Provide FEATURE to Emacs. */ static void provide (emacs_env *env, const char *feature) { /* call 'provide' with FEATURE converted to a symbol */ emacs_value Qfeat = env->intern (env, feature); emacs_value Qprovide = env->intern (env, "provide"); emacs_value args[] = { Qfeat }; env->funcall (env, Qprovide, 1, args); } int emacs_module_init (struct emacs_runtime *ert) { emacs_env *env = ert->get_environment (ert); /* 创建匿名函数,该匿名函数被封装成一个emacs_value*/ emacs_value fun = env->make_function (env, 0, /* 最少需要的参数个数*/ 0, /* 支持的最大参数个数*/ Fmymod_test, /* 实际函数的指针*/ "doc", /* docstring */ NULL /* user pointer of your choice (data param in Fmymod_test) */ ); bind_function (env, "mymod-test", fun); provide (env, "mymod"); /* loaded successfully */ return 0; }
2.3 测试
编译该模块,编译时需要将ROOT变量指向Emacs源代码目录(可以直接修改makefile,或者用类似下面的命令编译)
make ROOT=$HOME/prog/emacs-25.0.92 # gcc -ggdb3 -Wall -I/home/aaptel/prog/emacs-25.0.92/src -fPIC -c mymod.c # gcc -shared -o mymod.so mymod.o # rm mymod.o
然后可以在Emacs加载并测试该模块了. 使用 -L
来将模块所在目录添加到load-path变量后.
~/prog/emacs-25.0.92/src/emacs -Q -L $PWD
在*scratch* buffer中加载该模块,然后调用 mymod-test
方法. 若一切顺利的话,应该有以下结果(一种,C-j为快捷键表示执行前面的S-Form并插入运算结果):
(require 'mymod) <C-j> ;; mymod (mymod-test) <C-j> ;; 42