Next: , Previous: , Up: Writing Dynamic Modules   [Contents][Index]


E.8.2 モジュール関数の記述

Emacsモジュールを記述する主な理由は、そのモジュールをロードしたLispプログラムが追加の関数を利用できるようにするためです。このサブセクションでは、そのようなモジュール関数(module functions)の記述方法を説明します。

モジュール関数は以下のような一般的なフォームとシグネチャをもっています:

Function: emacs_value module_func (emacs_env *env, ptrdiff_t nargs, emacs_value *args, void *data)

引数envはEmacsのオブジェクトや関数へのアクセスに必要となるAPI環境へのポインターを提供する。引数nargsは要求される引数の個数であり0もあり得る(引数の個数にたいするより柔軟な仕様については以下のmake_functionを参照)。argsは関数の引数へのポインター。引数dataは関数により要求される追加データへのポインターであり、module_funcからEmacs関数を作成するためにmodule_func (以下参照)が呼び出される際にアレンジされる。

モジュール関数はEmacsとモジュール間でLispオブジェクトをやり取りするためにタイプemacs_valueを使用する(Module Valuesを参照)。以下で説明するAPIと以降のサブセクションではCの基本データ型と、それらに対応するemacs_valueオブジェクトの慣習にたいする機能を提供する。

モジュール関数は常に値をリターンする。関数が正常にリターンすると、それに呼び出されたLispコードは関数がリターンしたemacs_value値に対応するLispオブジェクトを目にすることになる。しかしユーザーがC-gをタイプしたり、モジュール関数やその呼び出し先がエラーをシグナルしたり非ローカルなexit (Module Nonlocalを参照)を行なった場合には、Emacsはリターン値を無視してLispコードが同じ状況に遭遇した際のようにquitやthrrowを行う。

モジュール関数用のCコード記述後には、そこからmake_functionを使用してLisp関数オブジェクトを作成する必要があります。make_function関数へのポインターは環境内で提供されます(環境へのポインターはget_environmentがリターンすることを思い出してほしい)。これは通常はAPIのモジュール初期化関数(module initialization functionを参照)の内部で互換性検証後に行われます。

Function: emacs_value make_function (emacs_env *env, ptrdiff_t min_arity, ptrdiff_t max_arity, subr func, const char *docstring, void *data)

これはC関数funcが作成した上記module_funcで説明したシグネチャをもつEmacs関数をリターンする(ここではsubrtypedefされると仮定)。引数min_aritymax_arityは、funcが受け付ける引数の最大個数と最小個数を指定する。引数max_arityは特別な値emacs_variadic_functionをもつことができる。これはLispの&restキーワードのように、関数の受け付ける引数の個数を無制限にする(Argument Listを参照)。

引数datafuncの呼び出し時に任意の追加データを渡すための手段である。make_functionに渡されたポインターが何であれ、それは変更されずにfuncに渡される。

引数docstringはその関数用のドキュメント文字列を指定する。これはASCII文字列かUTF-8にエンコードされた非ASCII文字列、またはNULLポインターのいずれかであること。後者の場合には関数がドキュメントをもたないことを意味する。ドキュメント文字列はadvertised-calling-conventionを指定する行で終端できる。Function Documentationを参照のこと。

すべてのモジュール関数は1つ目の引数として環境へのポインターを受け取らなければならないので、make_functionは任意のモジュール関数から呼び出され得るが、モジュールが一度ロードされればすべてのモジュール関数がEmacsに既知となるように、通常はこれをモジュール初期化関数で行うことを望むだろう。

最後にLispコードが関数を名前で呼び出せるようにLisp関数をシンボルにバインドする必要があります。これを行うにはモジュールAPI関数intern (internを参照)を使用します。この関数のポインターもモジュール関数がアクセス可能な環境内で提供されています。

上述のステップを組み合わせて、モジュール初期化関数の中で以下のようにC用にアレンジしたコードmodule_funcが、Lispからmodule-funcとして呼び出し可能になります:

 emacs_env *env = ert->get_environment (ert);
 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の呼び出しによりシンボルmodule-funcはEmacsが知ることとなり、それから関数をこのシンボルにバインドするためにEmacsからdefaliasを呼び出します。defaliasのかわりにfsetの使用も可能なことに注意してください。両者の違いはdefaliasに説明があります。

emacs_module_init関数(module initialization functionを参照)を含むモジュール関数は、直接間接を問わずEmacsから呼び出されていれば、何らかの生きたemacs_envポインターによる環境関数呼び出しにより、Emacsだけと対話する可能性があります。言い換えると、モジュール関数によるLisp関数やEmacsプリミティブを呼び出し、emacs_valueとCデータタイプ(Module Valuesを参照)の間の変換、あるいは別の方法によるEmacsとの対話を行いたい場合には、Emacsからのemacs_module_initやモジュール関数への呼び出しのいくつかがコールスタック上になければなりません。モジュール関数はガーベージコレクションの間はEmacsと対話しないかもしれません。Garbage Collectionを参照してください。モジュール関数はEmacsが作成したLispインタープリタースレッド(メインスレッドを含む)とだけ対話するかもしれません。Threadsを参照してください。コマンドラインオプション--module-assertionsは、上記の要件にたいする違反のいくつかを検知できます。Initial Options in The GNU Emacs Manualを参照してください。

モジュールAPIの使用により、より複雑な関数やデータ型(インタラクティブ関数、インライン関数、マクロ等)の定義が可能になります。ただしCの結果コードは扱いにくく、可読性も低下します。したがって関数やデータ構造を作成するモジュールコードを可能なかぎり最小限に留めるとともに、残りの部分をモジュールに付属するLispパッケージに付託することを推奨します。なぜならこれらの追加タスクをLispで行うのはより用意であり、より可読性の高いコードが生成されるでしょう。たとえば上記のように定義されたモジュール関数module-funcがある場合には、以下のようなシンプルなLispラッパーにもとづくインタラクティブコマンドmodule-macroを作成するのも1つの方法です:

(defun module-cmd (&rest args)
  "Documentation string for the command."
  (interactive spec)
  (apply 'module-func args))

モジュールに同梱されるLispパッケージは、Emacsにパッケージがロードされる際にloadプリミティブ(Dynamic Modulesを参照)を使用してモジュールをロードできます。