Emacsは古くから使われているテキストエディタであり、様々な歴史を経て主流となったGNU Emacsは今も根強い人気がある。単にEmacsという場合、大抵はGNU Emacsの事を表している(以降、Emacsという時はGNU Emacsの事とする)。Emacsにはダイナミックモジュールという機構がある。これにより他の言語で実装されたライブラリをEmacsから利用しやすくなる。今回はこのダイナミックモジュールを作る時の勘所について考える。
2016年頃リリースされた Emacs 25
から、Emacsにはダイナミックモジュールが組み込まれた。この仕組みを使うと、Emacs LispでインタプリタにLispの load
や require
を使って、 ddl
や so
のような動的リンク可能な共有ライブラリを読み込む事ができる。
ここでは空のダイナミックモジュールを作る。正しい作り方は公式ドキュメントで解説されている。ここではその内容をかいつまんでいく。
ダイナミックモジュールの基本的な作り方は、CやC++で実装し、Cコンパイラでコンパイルする。ここではCで実装しgccでコンパイルする。
ダイナミックモジュールを作るにはコンパイラ(今回は gcc
を使用)と、ダイナミックモジュール用のEmacsのヘッダーファイル emacs-module.h
が必要となる。
今回はmacOS上でビルドするため、gccはHomebrewでインストールする。
brew install gcc
ヘッダーファイル emacs-module.h
はEmacsに同梱されている。これは環境によって配置されている場所が異なるため、いくつか例を示す。
macOSで /Applications
配下にインストールするようなアプリケーションバンドルの場合、 /Applications/Emacs.app/Contents/Resources/include/emacs-module.h
にある。 /usr/local
配下にEmacsをインストールしている場合、 /usr/local/include
配下を探してみて欲しい。 Homebrew
でAppleシリコン系のmacにEmacsをインストールしている場合、 /opt/homebrew/include
や /opt/homebrew/opt/emacs/include
配下にあるかもしれない。
僕の環境の場合、Emacs自体を自分でビルドして使っているため、ソースツリーがローカルにあり、その中にヘッダファイル emacs-module.h
もあった。Emacsのソースコードをリポジトリからダウンロードし、 ./cofigure
を行う事でテンプレートファイルから生成される仕組みとなっている。
それでは空のダイナミックモジュールをCで書いていく。必要な事は実はかなり少ない。「ヘッダーファイルをインクルードする」、「plugin_is_GPL_compatible変数をグローバルに宣言する」、「emacs_module_init関数を定義する」、この3つだ。
emacs-module.h
をインクルードする
ダイナミックモジュールで使われる emacs_runtime
や、後ほど使う emacs_value
といった型の宣言がある。そのためヘッダーファイル emacs-module.h
は必須となる。#include <emacs-module.h>
int plugin_is_GPL_compatible;
int emacs_module_init (struct emacs_runtime *ert) {
return 0;
}
このファイルをコンパイルして共有ライブラリを作成する。
EMACS_INCLUDE_DIR="emacs-module.hのあるディレクトリへのパス"
gcc --shared \
-I${EMACS_INCLUDE_DIR} \
-o mylibempty.dylib \
mylibempty.c
処理が正常に終了すると mylibempty.dylib
という名前で、共有ライブラリが生成される。拡張子が .dylib
なのは、環境がmacOSだからであって、Linuxの場合は .so
、Windowsの場合は .dll
にすると良いだろう。
共有ライブラリができたら、それをロードしてみよう。Emacsにロードする。ライブラリをロードする時はLispであれ、共有ライブラリであれ、通常は require
を使だろう。しかし、これはまだ初期化処理で何もしていないから使えない。
他の方法として、 load
を使ってファイルパスを指定して直接読み込むという原始的な方法があるため、ここではそれを使う。
(load "共有ライブラリのファイルへのパス")
空のダイナミックモジュールのため何も起きないが、このLispが正常にロードを完了すると t
を返す。
ダイナミックモジュールなんてマニアックな手段を知りたいと考えているのだろうから、諸兄諸姉はEmacsについてある程度知識があるハッカーなのだろう。当然コードはEmacsで書くだろうし、シェル等の操作もEmacs上で行っている事だろう。
その今使用しているEmacsで不適切な実装のダイナミックモジュールをロードしたり、その関数を呼び出したりすると、使用しているEmacsがOSによって強制終了させられる事がある。
ダイナミックモジュールはC等で実装する事が多いため、領域外参照などのメモリ保護の違反を犯してしまいやすい。そうするとOSからの例外処理により、Emacsのプロセスごと終了させられてしまう。これでは開発を円滑に進めにくい。
これを回避する方法はいくつかあるけれど、僕が好んで使っている方法は、サブプロセスとしてEmacsをバッチモードで起動し、そこでダイナミックモジュールを読み込むというものだ。たとえOSにEmacsを強制終了させられたとしても、サブプロセスとして起動したEmacsを強制終了させられただけだから、親プロセスのEmacsには影響はない。
load
を使って読み込みだけを確認する例を示す。
emacs -nw \
--batch \
--eval '(load "共有ライブラリのフィアルへのパス")'
-nw
は No Window
、 --batch
はEmacsでインタラクティブな操作を行わないためのオプションの詰め合せセット、 --eval
は実行するLispのコードを文字列として渡す事ができる。
--eval
を使わず --script
を使い、実行するLispを test.el
などのファイルに記述し、そのLispを実行するといった事もできる。
emacs -nw \
--batch \
--script test.el
これらを使う事で、今使用しているEmacsに影響を与えず、ダイナミックモジュールを円滑に確認できる。
空のダイナミックモジュールを作って、作成の流れを確認した。次はEmacsのライブラリとしてちゃんとしている最小のライブラリを作る。これにより require
でこのライブラリをロードできるようになる。
#include <emacs-module.h>
int plugin_is_GPL_compatible;
int emacs_version;
int emacs_module_init (struct emacs_runtime *ert) {
/* verify compatibitiy */
if (ert->size < sizeof (*ert)) {
return 1;
}
emacs_env *env = ert->get_environment(ert);
if (env->size >= sizeof (struct emacs_env_26)) {
emacs_version = 26;
} else if (env->size >= sizeof (struct emacs_env_25)) {
emacs_version = 25;
} else {
return 2;
}
/* packaging */
emacs_value QSym_provide = env->intern(env, "provide");
emacs_value QSym_feat = env->intern(env, "mylibmini");
emacs_value args_feat[] = { QSym_feat };
env->funcall(env, QSym_provide, 1, args_feat);
return 0;
}
このコードでは emacs_module_init
の記述が結構増えた。この関数の前半部分はEmacsやダイナミックモジュールのAPIに関する互換性のチェックを行っている。
/* verify compatibitiy */
if (ert->size < sizeof (*ert)) {
return 1;
}
emacs_env *env = ert->get_environment(ert);
if (env->size >= sizeof (struct emacs_env_26)) {
emacs_version = 26;
} else if (env->size >= sizeof (struct emacs_env_25)) {
emacs_version = 25;
} else {
return 2;
}
後半部分はパッケージとしての宣言を行っている。
emacs_value QSym_provide = env->intern(env, "provide");
emacs_value QSym_feat = env->intern(env, "mylibmini");
emacs_value args_feat[] = { QSym_feat };
env->funcall(env, QSym_provide, 1, args_feat);
このコードはEmacs Lispの次のコードと同じ意味となる。
(provide 'mylibmini)
それではこのコードを先程の要領でビルドしよう。今回は mylibmini.dylib
という名前で出力する事にする。
gcc --shared \
-I${EMACS_INCLUDE_DIR} \
-o mylibmini.dylib \
mylibmini.c
生成された共有ライブラリをロードする。今回は provide
で宣言を行っているため、共有ライブラリのあるディレクトリへのパスが load-path
に追加されていれば、 require
を使ってロードできる。
実行するLispのスクリプトファイルを作る。
(add-to-list 'load-path (expand-file-name "."))
(require 'mylibmini)
test_mini.el
emacs -nw \
--batch \
--script test_mini.el
正常にロードできると、エラーが発生せず終了する。
今回はEmacsのダイナミックモジュールを作りながら、開発時の勘所について考えた。世の中にはCやC++で実装されている素晴しいツールがたくさんあるので、今後はそれらをEmacsに気軽に飲み込んでいきたい。ただし、EMacsはどちらかというと、ダイナミックモジュールを使うよりも、サブプロセスで処理する方が好まれるだろうし向いていると思うので、使い所を見極めながら活用していきたい。