OOTA's page

EMACS dynamic library

ダウンロード

  • Emacs23.4に対するパッチ+サンプルemacs23-dynload.zip
  • UNIX/Linux版でautoconfに対応しました。

概要

GNU emacsにダイナミックロード機能を実装しました。emacs本体の再コンパイルなしにC言語インターフェースで機能を拡張できます。Meadowのmw32dl.[ch]を参考にしました。

emacs-23.4-x64-r883-98には組み込み済みです。

機能

  • ダイナミックライブラリのロード、アンロード
  • ダイナミックライブラリ固有のデータタイプをサポートするGENERICタイプ疑似ベクタの新規サポート
  • Windows, UNIX(linux)サポート

    UNIX(linux)はemacs-23.4へのパッチとして提供します。パッチ適用後autoconf, configure, makeしてください。
  • セルフドキュメンテーションサポート

    DOCファイルをダイナミックライブラリに埋め込みます

以下のファンクションが利用できます。

  • load-dynamic-library

    (load-dynamic-library FILENAME)

    FILENAMEで指定されるダイナミックライブラリをロードしライブラリIDを返します。FILENAMEが相対パスの場合はexec-directoryからの相対パスとしてロードします。

    既にロード済みの場合は新たにロードはせず、ライブラリIDを返します。
  • unload-dynamic-library

    (unload-dynamic-library LIBRARY-ID)

    LIBRARY-IDで指定されたダイナミックライブラリをアンロードします。
  • dynamic-library-list

    (dynamic-library-list)

    ロードされているライブラリのリストを返します。

DLLの作成方法

ソースの記述

ヘッダー部
  • emacsでは共通に使われる構造体,関数プロトタイプ,グローバル変数がlisp.hにまとめられています(完璧ではありません、抜けているものも多々あります)。Windowsで必要になるインポート宣言はexternを__declspec(dllimport) externとマクロ定義することでemacsからインポートする事を宣言します。
  • dynload.hがダイナミックロードをサポートするヘッダーです。
  • 64bitメモリモデル(LLP64, LP64)ではプロトタイプ宣言が非常に重要です。プロトタイプ宣言がない関数の返り値/引数はintと判断され、ポインタを返している/渡しているつもりでも上位32ビットが削られる場合があります。#pragma warning(error : 4013)はプロトタイプ宣言なしの関数呼び出しをエラーにします。念のため入れておきます。
#include <config.h>
#include <setjmp.h>
#ifdef _WIN32
#define extern __declspec(dllimport) extern
#endif
#include <lisp.h>
#undef extern

#include <dynload.h>
#pragma warning(error : 4013)
DLL本体部

基本的にはemacs本体へのスタティックな組み込みと同様のコードを記述します。 intern_c_stringなどスタティックリンクを前提としている関数はemacs.exeからエクスポートしていませんのでリンク時に未定義になります(Windows版のみ)。staticpro, defsubr,defvar_xxxはダイナミックリンクサポート版を呼び出すマクロになっているので、そのまま使用可能です。lispの名前空間に関数、変数をリンクするsyms_of_xxxもemacs本体と同様に記述します。

GENERICタイプ疑似ベクタ

emacsではCレベルの固有のデータタイプは疑似ベクタとして表現しますが、データタイプを表すタグをサイズフィールド(EMACS_INT型)中にエンコードします。データタイプを表すタグはハードコードされているので、ダイナミックに定義する事はできません。ダイナミックロードのサポートに当たりGENERIC疑似ベクタを定義しました。

(疑似でない)Lispベクタとは以下の構造体として定義されています。contents配列は可変サイズで実際にアロケートするときは指定個数の配列が確保されます。contents配列はLisp_ObjectでGCの対象になります。
struct {
    EMACS_UINT size;
    union {
      struct buffer *buffer;
      struct Lisp_Vector *vector;
    } next;
    Lisp_Object contents[1];
]
疑似ベクタとはcontents配列の後に更にメモリ領域が追加された構造になっています。sizeフィールドはcontents配列の長さを保持します。ここでは便宜上dataをchar配列としましたが、任意の型が使用可能です。data部はGCの対象外です。
struct {
    EMACS_UINT size;
    union {
      struct buffer *buffer;
      struct Lisp_Vector *vector;
    } next;
    Lisp_Object contents[m];
    /* ここまでLispベクタ */
    char data[n];
]
GENERIC疑似ベクタはGENERICタグを持つ疑似ベクタでcontentsの0番めの要素はサブタイプを表すシンボル。dataの最初はGCで解放される時に呼ばれる終了処理関数と定義してあります。finalizer以降に任意のフィールドを配置して使用する事ができます。
struct {
    EMACS_UINT size;
    union {
      struct buffer *buffer;
      struct Lisp_Vector *vector;
    } next;
    Lisp_Object contents[m];
    /* ここまでLispベクタ */
    void (*finalizer)(void *);
]
emacsとのリンク

二段階で行われます。

  1. ダイナミックライブラリの登録/解除

    register_dynamic_library, unregister_dynamic_libraryにて行います。後述のDEFINE_DLLMAINにて定義されるDllMainから呼び出されます。
  2. 初期化/終了処理(optional)

    dllのロード後/アンロード前にダイナミックローダーから呼び出されます。この関数中には以下の処理を記述します。
    • dll固有の初期化、終了処理を呼び出します。終了処理はオプショナルです。
    • 初期化処理ではsyms_of_xxxを呼び出しlispの名前空間にリンクします。

DEFINE_DLLMAIN(init, final)で記述します。initは初期化処理、finalは終了処理を行う関数です

void *init(void *reserved);
初期化処理を行います。syms_of_xxxを呼び出しlispの名前空間にリンクします。引数reservedは現在未使用です。初期化に失敗した場合はNULLを、成功した場合はそれ以外を返します。この値はfinalが呼ばれる時の引数として与えられます。初期化処理はcondition-caseのコンテキストで呼ばれるのでlispエラーをシグナルすることも可能です。
void final(void *data);
終了処理を行います。dataにはinitで返した値が渡されます。終了処置が不要な場合はDEFINE_DLLMAINの第2引数にNULLを渡します。
記述例
static void * initialize_sqlite3 (void *reserved)
{
    if (sqlite3_initialize() != SQLITE_OK ||
	sqlite3_os_init() != SQLITE_OK)
      return NULL;

    syms_of_sqlite3();
    return (void *)1;
}

static void finalize_sqlite3 (void *data)
{
    sqlite3_os_end();
    sqlite3_shutdown();
}

DEFINE_DLLMAIN(initialize_sqlite3, finalize_sqlite3)
リソースファイル

emacsの特徴の一つであるセルフドキュメントは、emacsに付属のmake-docfile.exeで作成したDOCファイルを以下のリソーススクリプトを用いて、リソースとしてdllに埋め込みます。

UNIX, Linux版ではGNU ldの-bオプションを用いてDOCファイルをオブジェクトファイル(.o)に変換し、dllにリンクします。

#include <dynload.h>
1  RT_EMACSDOC DOC

ビルド(Windows)

GNU emacs(x64)はモジュール間でCライブラリ中のグローバル変数を共有するために/MD(MSVCRT使用)でコンパイルされています。同一のVCランタイムを使用するためGNU emacs(x64)と同一のツール(Microsoft SDK for 6.1とそれに付属のMSVC+ 2008)が必要です。Microsoft download centerから入手できます。

Miscrosoft SDK for windows 6.1(Windows SDK for Windows Server 2008 and .NET Framework 3.5)

C:\Promgram Files\GNU\emacs23\bin\dl-nmake.bat利用してビルドしてください。defs.makというファイルに以下の内容を記述し、dl-make.batでビルドします。DOCファイル、リソーススクリプトファイルの生成も行います。

引数はそのままnmakeに渡すことができますが、バッチファイルですので"=", ":"区切り文字をそのまま渡すことができません。-DMACRO=valueを渡す場合は全体をダブルクォートでくくり"-DMACRO=value"として渡してください。

OBJS=		file.obj #オブジェクトファイルを記述します。
NAME=		file #.dllのベース名です

INCLUDE=	some_other;$(INCLUDE) #必要ならば追加のインクルードパスを記述します。

DEFS=		-Dxxx #必要ならば追加のマクロ定義を記述します。

現時点ではIDE向けのビルドサポートは作成していません。IDEでのビルドはカスタムビルドプロセスとしてプロジェクトに定義を追加することで可能だと思います。定義追加のヒントとしてdl-make.batが行っている事を説明します。

  1. インクルードパス、ライブラリパスの追加

    config.h, lisp.h, dynload.hはc:/Program Files/GNU/emacs23/includeにemacs.libはc:/Program Files/GNU/emacs23/libにインストールされています。
  2. オブジェクトファイル生成

    clにてコンパイルします。コンパイルは-MDにて行います。
  3. DOCファイル生成

    emacsに付属のmake-docfile.exeを用いてDOCファイルを生成します。実行されるコマンドは以下のとおりです。(.oを渡しているのは間違いではありません)make-docfile.exe" -o $@ $(OBJS:.obj=.o)
  4. リソースファイル生成

    前述のリソーススクリプトファイルを生成し、rc.exeにてDOCファイルを埋め込んだリソースファイルを生成します。
  5. リンク

    link.exeにてオブジェクトファイル、リソースファイル,emacs.libをリンクし、dllを生成します。
  6. マニフェストの埋め込み

    mt.exeでマニフェストを埋め込みます。mt -nologo -manifest $@.manifest -outputresource:$@;#2

ビルド(UNIX, Linux)

dll生成のステップは基本的にはWindowsと同一です。

  1. cmigemo, sqlite3ディレクトリにあるautogen.shを実行してください。
  2. configure, makeを実行してください
  3. make installでインストールします
Design downloaded from free website templates.