Next: , Previous: , Up: 動的にロードされるモジュールの記述   [Contents][Index]


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

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

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

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

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

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

モジュール関数のbodyにおいては、インデックスnargs-1を超えた配列argsの要素へのアクセスを試みてはならない。配列argsにたいするメモリーはnargsの値を正確に収納できるように割り当てられるため、それを超えてアクセスを行うとあなたのモジュールはほとんどの場合はクラッシュするだろう。特に実行時に関数に渡されたnargsの値が0の場合には、メモリーは何も割り当てられないのでargsにアクセスしてはならない。

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

ヘッダーemacs-module.hは関数ポインターからモジュール関数へのエイリアス型としてemacs_function型をを提供する。

モジュール関数用の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, emacs_function func, const char *docstring, void *data)

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

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

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

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

最後にLispコードが関数を名前で呼び出せるようにLisp関数をシンボルにバインドする必要があります。これを行うにはモジュールAPI関数intern (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の呼び出しによりシンボルmodule-funcはEmacsが知ることとなり、それから関数をこのシンボルにバインドするためにEmacsからdefaliasを呼び出します。defaliasのかわりにfsetの使用も可能なことに注意してください。両者の違いはdefaliasに説明があります。

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

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

(defmacro module-macro (&rest args)
  "Documentation string for the macro."
  (module-func args))

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

デフォルトではmake_functionにより作成したモジュール関数は非インタラクティブです。これらをインタラクティブにするために、以下の関数を使用できます。

Function: void make_interactive (emacs_env *env, emacs_value function, emacs_value spec)

このEmacs 28以降で利用可能になった関数はインタラクティブ仕様specを使用して、関数functionをインタラクティブにする。Emacsはspecinteractiveフォームの引数のように解釈する。interactiveの使用interactiveにたいするコード文字を参照のこと。functionmake_functionがリターンするEmacsモジュール関数であること。

モジュール関数のインタラクティブ仕様を取得するためのネイティブなモジュールサポートは存在しないことに注意。これを行うためにはinteractive-formを使用する。interactiveの使用を参照のこと。一度make_interactiveを使用してモジュール関数をインタラクティブにした後に、非インタラクティブにすることはできない。

モジュール関数オブジェクト(例: make_functionがリターンしたオブジェクト)がガーベージコレクトされた際に何らかのコードを実行したい場合には、関数ファイナライザー(function finalizer)をインストールできます。関数ファイナライザーはEmacs 28以降で利用できます。たとえばmake_functiondata引数にヒープ割り当てした構造体を渡した場合には、構造体の割り当て解放にファイナライザーを使用できます。(libc)Basic Allocation(libc)Freeing after Mallocを参照してください。ファイナライザー関数は以下のsignatureをもちます:

void finalizer (void *data)

ここでdatamake_function呼び出し時にdataに渡された値です。ファイナライザーがEmacsと相互作用する手段は何もないことに注意してください。

make_function呼びの直後には、新たに作成された関数はファイナライザーをもちません。ファイナライザーを望む場合には、ファイナライザーを追加するためにset_function_finalizerを使用します。

Function: void emacs_finalizer (void *ptr)

ヘッダーemacs-module.hはEmacsファイナライザー関数にたいするエイリアスタイプとしてタイプemacs_finalizerを提供する。

Function: emacs_finalizer get_function_finalizer (emacs_env *env, emacs_value arg)

このEmacs 28以降で利用可能になった関数は、argにより示されるモジュール関数に関連付けられた関数ファイナライザーをリターンする。argはモジュール関数、すなわちmake_functionがリターンするオブジェクトを参照しなければならない。その関数に関連付けられたファイナライザーがなければ、NULLをリターンする。

Function: void set_function_finalizer (emacs_env *env, emacs_value arg, emacs_finalizer fin)

このEmacs 28以降で利用可能になった関数は、argで示されるモジュール関数に関連付けられる関数ファイナライザーにfinをセットする。argはモジュール関数、すなわちmake_functionがリターンするオブジェクトを参照しなければならない。finにはargの関数ファイナライザーをクリアーするNULL、あるいはargにより示されるオブジェクトのガーベージコレクト時に呼び出される関数へのポインターを指定できる。関数ごとに最大で1つの関数ファイナライザーをセットできる。argがすでにファイナライザーを所有する場合にはfinで置き換えられる。


Next: Lisp・モジュール間の値変換, Previous: モジュールの初期化コード, Up: 動的にロードされるモジュールの記述   [Contents][Index]