レベル: 中級 Peter Seebach, Freelance writer, Wind River Systems
2009年 01月 27日 Lua プログラミング言語は他のプログラムに組み込むために専用に設計された簡単なスクリプト言語です。Lua の C 言語用 API を利用すると、C から Lua を呼び出すためのコードも Lua から C を呼び出すためのコードも、非常にすっきりした単純なコードになります。これにより、開発者は便利なランタイム・スクリプト言語が必要な場合に、その言語に必要な基本 API 要素を容易に実装し、アプリケーションから Lua コードを呼び出すことができます。この記事では、一般的な開発作業を単純化する手段として使用できる Lua 言語を紹介し、また、そもそもなぜスクリプト言語を埋め込むのかを説明します。
Lua は簡単なスクリプト言語です。どのくらい簡単なのでしょうか。Lua では POSIX の正規表現ではなくカスタムのパターン・マッチング機能を使いますが、これは正規表現を完全に実装しようとすると Lua の標準ライブラリーすべてを集めたものよりも大幅に大きくなってしまうためです。POSIX の正規表現に比べ、Lua に用意されているストリング・マッチ機能は、強力ではありませんが、はるかに単純であり、サイズは POSIX 正規表現の何分の一という程度です。
Lua の変数は強い型付けがされません。値の型をチェックすることはできますが、変数の型が時間と共に変化しても問題はありません。こうした点は、スクリプト言語にとって好都合です。Lua の型システムはかなり単純ですが、非常に柔軟です。配列と連想配列は 1 つの型にまとめられ、テーブルと呼ばれます。ストリング、数値 (浮動小数点のみ)、ブール値、そして特殊な nil などは基本型です。おそらくもっと興味深い点は、関数も基本型であることです。他の型とまったく同じように関数を変数に割り当てることができ、特別な構文は何もありません。その他にカスタムの userdata オブジェクトもサポートされています。このオブジェクトを定義すると、上記の基本的な型システム以外の型を扱うことができます。
他の言語を使用してきたプログラマーにとって最大の驚きの 1 つは、Lua では false と nil のみが偽と見なされ、非ブール型のオブジェクトはすべて、テストでは必ず真と見なされることです。この動作は、例えば真と偽には 1 と 0 を使うなどの C のイディオムに慣れている人達には驚きかもしれませんが、すぐに慣れることができます。
Lua は移植可能な C で作成されています。Lua を C++ で使うこともできますが、Lua のコアとなる言語は非常に移植性が高くなっているため、Lua は (ホスト機能を必要とする機能もいくつかあるものの) プラットフォームに依存することなく適切な動作をします。また、Lua には autoconf テスト用の巨大スイートはなく、標準的なテストを行うようになっています。Lua は MIT ライセンスの下で配布されており、商用を含め、どのような使い方をしても完全に無料です。(おそらくそのため、多くのプログラマーは自由に Lua をアプリケーションに組み込んでいます。)
なぜ言語を組み込むのか
スクリプト言語を組み込むと、いくつかのメリットがあります。ここでは私が Lua を使い始めるに至った、Blizzard による MMORPG (Massively-Multiplayer Online RPG: 多人数同時参加型オンライン・ロールプレイング・ゲーム)、WoW (World of Warcraft) を例として使いましょう。WoW のユーザー・インターフェースは完全に Lua で実装されています。このインターフェースには、実際にレンダリング・エンジンとやり取りして WoW に関するデータを要求できる基本的な API 呼び出しがいくつか提供されており、またユーザー・インターフェース・コードのコアには Lua が使われています。
そのため、ユーザー・インターフェース・コードをサンドボックスとしてゲームの中心部分からすっきりと分離することができ、セキュリティーや信頼性を改善することができます。つまり、Blizzard ではユーザー・インターフェースをプレイヤーに一任することができ、プレイヤーはカスタム・コードを作成してゲームとのやり取り方法を変更できるということです。
一般に、大半の作業ではスクリプト言語を使用して処理する方が低級言語を使用するよりも容易です。暗黙的な割り当てや連想配列によってガーベッジ・コレクションが行われる言語の方が単純なコードを作成できる場合が多く、それによって開発を迅速に進めることができます。そうしたコードの実行速度は速くないかもしれませんが、多くの場合、それは問題になりません。例えばユーザー・インターフェースはユーザーがキーボードやマウスを操作するよりも速く実行できさえすればよいのです。
スクリプト言語の使い方はいくつかあります。第 1 の、そして最も単純な使い方は、プログラムの動作の制御にスクリプト言語を使うもので、Lua で作成したプログラムの詳細の実装として C コードを利用するものです。第 2 は、基本的に C でプログラムを作成し、データや構成を保存したり、レポートしたりする手段として組み込みの Lua を使う方法です。第 3 の、そして最も柔軟な使い方は、そうした方法をさまざまに組み合わせ、一部の動作を Lua を使ってスクリプト化し、それ以外の動作の処理を C でコーディングするという使い方です。Lua と C との間のインターフェースは単純なため、こうした手法を手軽に使うことができます。
エンジンを作成する
CPU 時間の大半が個々の操作に使われており、最上位レベルの制御が比較的軽量な場合には、プログラム用のエンジンを基本的に Lua で作成するのが妥当です。そうすれば、実装の詳細を上位レベルの設計と分離することができます。プログラムのコア・ロジックを C ではなく Lua で実装すると、開発に要する時間を大幅に削減できる可能性があります。
このタイプの設計では、Lua から C へのインターフェース設計の大部分は Lua から呼び出される C 関数を定義する作業になるはずです。なぜなら、いったんスクリプトの実行を開始すると、その後 C コードが使われる場合は、すべてスクリプトから呼び出されることになるからです。
スクリプト化可能な構成ファイル
私の知るプログラマーの誰もが作成したことがあるコードとして、構成の値をファイルに保存し、後でそれらの値を読み出すだけのコードがあります (そうした類のコードで私が使用したものの中では、私は Apple のプロパティー・リストを気に入っています)。組み込みのスクリプト言語をそうしたファイルのフォーマットとして使用すると、膨大な種類の構成オプションをユーザーに提供することができます。Ion ウィンドウ・マネージャーは構成ファイルに Lua を使用しており、そのためユーザーは強力で柔軟な構成を作成することができます。
これがユーザーにとって素晴らしいのは、構成ファイルは単に値を割り当てるためのものではなくなり、構成ファイルを Lua で表現することによってコメントや条件などを構成ファイルに追加できるのです。また制限付きの API を提供し、構成の選択肢に影響する可能性のあるデータのみを取得することもできます。
さまざまなものを組み合わせる
Lua インタープリターはリエントラントなので、Lua と C との間で自由にやり取りを行うことができます。C プログラムがスクリプト上で Lua インタープリターを呼び出し、そのインタープリターが C 関数を呼び出し、そしてその関数が再度 Lua インタープリターを使う、といったこともできます。
World of Warcraft では、基本的にこのモデルをユーザー・インターフェースに使っています。つまりユーザー・インターフェースで Lua を呼び出すことによってエンジンへのコールバックを行い、そしてエンジンは、Lua で作成されたユーザー・インターフェース・コードに対してイベントを提供するのです。その結果インターフェースは適切な分離とセキュリティーが実現された柔軟なものになり、ユーザーは非常に多種多様なことを行える上、それによってアプリケーションの中でバッファー・オーバーランが起きたり、機能が停止したりする危険性がほとんどありません。C ベースの API では組み込みコードは必ずと言ってもよいほど機能の停止を引き起こしますが、Lua インターフェースを使用する場合には、ユーザー・インターフェース・コードによって機能の停止が起こったとしても、それはバグのためであり、そのバグは修正できるはずです。
Lua のビルド方法と使用方法
Lua のビルドは簡単であり、単純に make <platform> を実行するだけです。プラットフォーム特有の機能に依存したくない場合には、posix や、さらには ansi に準拠するようにもできます。ビルドすると、プログラムの中にリンクできる liblua.a というライブラリーが生成されます。おめでとうございます。これで Lua を組み込むことができました。もちろん、実際に Lua を使うためにはもう少し作業が必要です。
Lua では、インタープリターのすべての状態を 1 つのオブジェクトの中に保持することでリエントラント機能を実現しています。複数のインタープリターが存在する可能性がありますが、それらが変数を共有することはなく、またそれらの間でグローバル項目が共有されることもありません。Lua とやり取りするためには、まず以下のように Lua の状態を作成する必要があります。
lua_State *l;
l = lua_open();
もし lua_open() 呼び出しが失敗すると、ヌル・ポインターが返されます。それ以外の場合には、Lua インタープリターの有効な状態が得られます。もちろん状態だけあってもライブラリーがなければ何もできません。状態に標準ライブラリーを追加するためには luaL_openlibs() 関数を使います。
lua_State *l;
l = lua_open();
これで、Lua の状態からはいつでもコードを実行することができます。下記は、あるプログラム用のサンプル・ループであり、単純に引数を Lua コードとして実行します。
リスト 1. 引数を Lua コードとして実行する
for (i = 1; i < argc; ++i) {
if (luaL_loadbuffer(l, argv[i], strlen(argv[i]), "argument")) {
fprintf(stderr, "lua couldn't parse '%s': %s.\n",
argv[i], lua_tostring(l, -1));
lua_pop(l, 1);
} else {
if (lua_pcall(l, 0, 1, 0)) {
fprintf(stderr, "lua couldn't execute '%s': %s.\n",
argv[i], lua_tostring(l, -1));
lua_pop(l, 1);
} else {
lua_pop(l, lua_gettop(l));
}
}
}
|
luaL_loadbuffer() 関数はスクリプトを Lua コードにコンパイルします。構文エラーがあると、ここで失敗します。エラー・メッセージはスタックに返されます。それ以外の場合には、コンパイルされたコードを lua_pcall() 関数を使って実行することができます。この場合にも、エラーがある場合には、エラーはスタックに返されます。Lua に対する、あるいは Lua に関する C 言語による呼び出しはすべて Lua の状態を引数に取るため、デフォルトの状態はないことに注意してください。
Lua のスタックを理解する
Lua インタープリターはスタック・インターフェースを使って呼び出し側のコードとデータのやり取りをします。Lua コードに送信されるデータは C コードによってスタックにプッシュされます。Lua インタープリターからのレスポンスもスタックにプッシュされます。luaL_loadbuffer() に渡されたコードが無効な場合には、エラー・メッセージがスタックにプッシュされます。
スタック上の項目には型と値があります。lua_type() 関数はオブジェクトの型を照会し、また lua_to<type>() 関数 (例えば lua_tostring() など) を実行すると、強制的に C の特定の型にされた値が生成されます。Lua で作成されたコードは常に、厳密にスタック・モデルに従います。一方で C コードはスタックの他の部分を自由に操作することができ、スタックに値を挿入することすらできます。
このインターフェースは単純ですが、驚くほど強力です。実行対象のコードも他とまったく同じに扱われ、単純にスタックにプッシュされて lua_pcall() 関数によって実行されます。
lua_pcall() を使う
lua_pcall() 関数は動作対象である Lua の状態以外に 3 つの引数を取ります。実行対象のコードはこれらの引数には含まれず、luaL_loadbuffer() などの別の関数によってスタックにプッシュされ、そうした別の関数がコードを取得します。一方 lua_pcall() が引数として取るのは、lua_pcall() 関数の実行対象のコードに渡すスタック引数の数、返されるはずの結果の数、そしてオプションとしてのエラー・ハンドラーです。関数を呼び出すためには、まずその関数をプッシュし、次にその関数が取る引数を (関数が引数を取る順に) プッシュします。返される引数は同じ順序でプッシュされます。つまり最初に返された値がスタックの一番下にプッシュされ、最後に返された値が先頭にプッシュされます。
Lua は引数を渡す場合にも、戻り値を取得する場合にも、lua_pcall() に渡された数と一致するように値の数を暗黙的に修正します。十分な数だけ値が提供されていない場合には残りの部分に nil 値が入れられ、値が余分にある場合には暗黙のうちに廃棄されます。(これは複数の割り当て演算子がある場合の Lua の動作と同じです。)
エラー・ハンドラーが提供されている場合には、エラー・ハンドラーは Lua コードのスタックの指標であり、発生するすべてのエラーを処理します。この記事は概要の説明なのでエラー処理の詳細は省略しますが、知っておくべき重要なことは、第 1 にエラー処理があること、そして第 2 にエラー処理が Lua によって行われることです。これは非常に便利であるということがわかります。
Lua に C を組み込む
Lua で使用する関数を C で作成することは驚くほど簡単です。別のスクリプト言語に組み込むためのコードを作成したことがある人なら、こうした容易さは衝撃的かもしれません。下記は x という数値を与えると x+1 を返す C 関数です。
リスト 2. Lua で使用する C 関数
int
l_ink(lua_State *L) {
int x;
if (lua_gettop(L) >= 0) {
x = (int) lua_tonumber(L, -1);
lua_pushnumber(L, x + 1);
}
return 1;
}
|
この関数は Lua の状態引数を付けて呼び出されます。この場合も、C と Lua の間のすべてのやり取りは Lua の状態のスタックをとおして行われます。戻り値は、この関数がスタックにプッシュしたオブジェクトの数です。この関数を Lua で利用するためには 2 つのことを行う必要があります。その第 1 はこの関数を表現する Lua オブジェクトを作成することであり、第 2 はそのオブジェクトに名前を付けることです。
lua_pushcfunction(L, l_ink);
lua_setglobal(L, "ink");
lua_pushcfunction() を使って C 関数ポインターを Lua の内部オブジェクトに変換します。このオブジェクトはもちろんスタックにプッシュされます。次に lua_setglobal() 関数はスタック先頭の値を名前付きのグローバル変数に割り当てます。Lua では関数は単なる値にすぎないため、呼び出し可能な関数をこの方法で作成することができます。
この手順はスタック・インターフェースのおかげで大幅に単純化され、関数が取る引数の定義や宣言はまったく必要ありません。Lua ではコードの呼び出し方が非常に柔軟なので、そうした定義や宣言がなくても問題ありません。ただし必要な場合には、いくつかのチェックをもっと詳細に行うこともできます。引数のチェックや参考用のエラー・メッセージ出力、関数の実行の強制終了などには luaL_checknumber() 関数も使うことができます。
組み込みのスクリプトを活用する
Lua は他の言語の (特に C の) コードの中に簡単に組み込むことができるため、それを活用して他の言語におけるプログラムの機能を大幅に改善することができます。これは独自の構成言語を作成したり、あるいは式を解析するための独自パーサーを作成したりすることに代わる、現実的で有効な選択肢となります。
もっと大規模なスクリプト言語を扱うことに慣れた人達は、いくつかのことに驚くかもしれません。第 1 に、Lua インタープリターをセットアップするためのコストはごくわずかです。サンドボックスの中で何かを実行したい場合には、即座にそれを実現することができます。Lua には私が説明しなかったセキュリティー機能がたくさんありますが、Lua の 1 つの状態の中だけであってもサンドボックスを効果的に実現できることを理解する必要があります。
プログラムの状態に関するデータを単純に返す関数があると、スクリプトやデータを構成するための素晴らしいツールになります。しかし最上位レベルのロジックを Lua で作成しようとする場合にも、それを非常に容易かつ効率的に行えるのです。多くのスクリプト言語では、やむを得ず他の言語のコードと密接に連動しなければなりませんが、Lua は他の言語と密接に連動するため専用に設計された、おそらく最もわかりやすい言語の一例と言うことができます。
参考文献 学ぶために
製品や技術を入手するために
- developerWorks から直接ダウンロードできる IBM ソフトウェアの試用版を利用して皆さんの次期 Linux 開発プロジェクトを構築してください。
議論するために
著者について  | 
|  | Peter Seebach はLinux 上で実行されるちょっとしたデバイスを収集しています。彼はそれらの Beowulf クラスターを作成することに関する冗談を聞くのにうんざりしています。 |
記事の評価
|