トップ » 技術記事 » 高速スクリプト言語「Lua」を始めよう!(7) - Delphi2009 から Lua を使う

高速スクリプト言語「Lua」を始めよう!(7) - Delphi2009 から Lua を使う はてなブックマーク数 このエントリーをブックマークに追加

今回は、Delphi2009 から、Lua を利用する方法を紹介します。これまで、C 言語から使う方法を紹介しましたが、今回は、Delphi 2009 です。生産性の高い Delphi と、柔軟で動作が高速な Lua を組み合わせれば鬼に金棒と言えます。

Delphi + Lua で可能姓無限大!

Delphi だけでも十分生産性は高いのですが、スクリプト言語のLuaと組み合わせることで、より手軽に高度なアプリケーションの開発が可能になります。

例えば、Delphi では、画面デザインと、システム周りのイベントの補足などを行って、Lua では、ロジックの記述をします。ユーザーがボタンを押したタイミングで、Lua で記述したバッチを実行したり、USBのフラッシュメモリが挿入されたタイミングで、Lua でメモリ内のファイルをアップロードするなど、さまざまな使い方ができるでしょう。また、本来のスクリプト言語としての使い方として、Delphiで作ったアプリケーションを拡張するために、Lua を使うこともできます。

環境の準備

本稿では、それぞれ原稿執筆時点での最新版、Delphi 2009、Lua 5.1 を使う方法を紹介します。

Delphi 2009 について

Delphi 2009 は有料ですが、以下より30日間利用可能なトライアル版をダウンロードすることができます。

Lua for Windows 5.1

Lua 5.1 については、Lua for Windows のページから開発用のエディタを含む完全版をダウンロードしました。Lua の基本パッケージだけだと、ライブラリを何も含んでいないので、今回は、多くの Lua 用ライブラリを含む、Lua for Windows を利用します。

※ちなみに筆者が動作検証に利用したのは、5.1.4.23 です。

DelphiのLua用のユニット

Delphi から手軽に Lua を呼び出すためには、Lua の DLL にある関数を定義したユニットが必要になります。これを探してみると、すでにメンテナンスが終了したものや、以前のバージョンのためのものがあるだけで、Delphi 2009 + Lua 5.1 に準拠したものは見つかりません。

ないなら作るしかないということで、割と新しい Delphi の Lua ユニットを含むLuaEditをベースとして、Delphi2009 でコンパイルが通るように書き換えてみました。以下よりダウンロードできます。

Delphi + Lua の連携プログラミング

Hello,WorldをDelphiから実行してみる

それでは、一番簡単な以下のようなプログラムをDelphiから実行してみようと思います。

-- hello.lua
print("Hello, World!")
print("Hello, World!")
print("Hello, World!")
(1) Delphi 2009でプロジェクトを作る

Delphi 2009 を起動したら、メニューから[ファイル > 新規作成] をクリックします。そして、新規作成ダイアログから [コンソールアプリケーション]を選択します。

http://aoikujira.com/demo/hakkaku/rc/20090414ahchQ-delphi2009-console.png

それから、一度、プロジェクトを保存します。[ファイル > 名前を付けて保存]をクリックして、「HelloWorld.dproj」という名前で保存します。

(2) 必要なファイルをコピーする

次に必要なファイルをプロジェクトのフォルダにコピーします。以下のものをコピーしてください。

  • Lua のプログラム「hello.lua」
  • Lua Unit for Delphi2009の中のユニット (*.pas)
  • Lua のエンジンDLL「lua5.1.dll」(Lua for Windows より)

それから、Lua Unit が、lua.dll に静的リンクするようになっているので、「lua.5.1.dll」を「lua.dll」にファイル名を変更します。(或いは、Lua.pas の21行目にある LuaDLL の値を 「Lua.5.1」に書き換えてください。)

(3) プログラムの記述

準備が整ったら、メインファイルの HelloWorld.dpr を以下のように記述します。これを実行すると、Luaのプログラムが実行され、「Hello,Wolrd!」と表示されます。

http://aoikujira.com/demo/hakkaku/rc/20090414Tl4lh-lua-hello.png
program HelloWorld;
{$APPTYPE CONSOLE}
uses
  SysUtils,
  Lua in 'Lua.pas';
var
  L: Plua_State;
  res: Integer;
begin
  L := luaL_newstate; // Lua が利用できるようにする
  try
    // 標準ライブラリを使える用に
    luaL_openlibs(L);
    // ソースを実行
    res := luaL_dofile(L, 'hello.lua');
    // 成功?
    if res <> 0 then WriteLn('実行に失敗:', res);
  finally
    lua_close(L); // Lua の利用を終了する
  end;
  ReadLn;
end.

これだけです!非常に手軽に利用できそうな気がしますね。

「luaL_newstate()」でLuaのエンジンを利用できるように初期化し、「lua_close()」でエンジンの利用を終了します。

また、Lua ファイルを読み込んで実行するのが、「luaL_dofile()」です。戻り値が0ならば成功で、それ以外なら失敗です。「lua.pas」で次のようにエラーメッセージが定義されていますので、もし、エラーが出たなら、この値をチェックしてみてください。

  LUA_ERRRUN    = 2;  // 実行に失敗
  LUA_ERRSYNTAX = 3;  // 文法エラー
  LUA_ERRMEM    = 4;  // メモリエラー
  LUA_ERRERR    = 5;  // エラー
  LUA_ERRFILE = LUA_ERRERR + 1; // ファイルエラー

また、「luaL_dostring()」関数を利用すると、Luaのファイルでなく、文字列を直接実行することもできます。

res := luaL_dostring(L, 'print("Hello, World!")');
Lua側の変数の値を書き換える

次に、Lua 側の変数を設定してみたいと思います。変数を書き換える場合、擬似なスタック操作用のAPIを利用します。例えば、「a1 = "TEST!!"」のような操作をしたい場合には、一度、スタックに "TEST!!" を乗せておいて、スタックの値を変数に設定するという手順になります。

lua_pushstring(L, 'TEST!!');   // (*1) スタックに値を乗せる
lua_setglobal(L, 'a1');        // (*2) スタックの値を変数 a1 に設定する
luaL_dostring(L, 'print(a1)'); // (*3) 画面に a1 の値を表示

どうでしょうか。完全なソースコードは以下のようになります。先ほどのプロジェクトを「SetLuaVar.dproj」という名前で保存し、次のように書き換えて実行します。

program SetLuaVar;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Lua in 'Lua.pas';

var
  L: Plua_State;
  res: Integer;
begin
  L := luaL_newstate;
  try
    // 標準ライブラリを使える用に
    luaL_openlibs(L);
    // Lua側の変数を書き換え
    //   a1 = "TEST!!"
    lua_pushstring(L, 'TEST!!');
    lua_setglobal(L, 'a1');
    // ソースを実行
    res := luaL_dostring(L, 'print(a1)');
    // 成功?
    if res <> 0 then WriteLn('実行に失敗:', res);
  finally
    lua_close(L);
  end;
  ReadLn;
end.

これを実行すると、「TEST!!」と画面に表示されることと思います。

Lua側の変数を取得する

次に Lua側の変数を取得してみます。変数の取得も、設定と同じようにスタックを利用します。まず、スタックに変数の値を乗せます。それから、スタックの一番上の値(-1)を取得します。取得が完了したら、スタックから値を取り除きます。

// 変数を設定する
luaL_dostring(L, 'a2 = "HOGE!"');
// Lua側の変数を取得する
lua_getglobal(L, 'a2');    // (*1) 変数の値をスタックに乗せる
str := LuaToString(L, -1); // (*2) スタックの一番上を取得
lua_pop(L, 1);             // (*3) スタックにある値を取り除く
// 結果の表示
WriteLn('a2 = ', str);

ここで、LuaToString() という関数が出てきました。これは、"LuaUtils.pas"で定義されているDelphiの関数となっています。そのため、uses 節を以下のように記述します。

uses
  SysUtils,
  Lua in 'Lua.pas',
  LuaUtils in 'LuaUtils.pas';

「LuaToString()」の実装は以下のようになっています。lua_strlen() で文字列の長さを取得しておいて、その後、lua_tostring() で実際に文字列をDelphiのメモリにコピーするというものです。

function LuaToString(L: PLua_State; Index: Integer): AnsiString;
var
  Size: Integer;
begin
  Size := lua_strlen(L, Index);
  SetLength(Result, Size);
  if (Size > 0) then
    Move(lua_tostring(L, Index)^, Result[1], Size);
end;
Lua側の関数を呼び出す

次に、Lua側の関数を呼び出してみます。以下のソースでは、Lua側に定義した lua_add() という関数を Delphi から呼び出します。(「lua_add()」関数自体の定義も、luaL_dostring() で実行しています。)

program CallLuaFunc;
{$APPTYPE CONSOLE}
uses
  SysUtils,
  Lua in 'Lua.pas';
var
  L: Plua_State;
  n: Integer;
begin
  L := luaL_newstate;
  try
    luaL_openlibs(L);
    // Lua側で関数を定義する
    luaL_dostring(L, 'function lua_add(a,b) return a+b end');
    // Delphi から Lua の関数を呼ぶ
    lua_getglobal(L, 'lua_add'); // 関数を取得
    lua_pushinteger(L, 3);       // 第一引数をスタックに
    lua_pushinteger(L, 4);       // 第二引数をスタックに
    lua_pcall(L, 2, 1, 0);       // 関数を 引数2個、戻り値1個で呼び出す
    // 結果を取得する
    n := lua_tointeger(L, -1);
    lua_pop(L, 1);
    // 結果の表示
    WriteLn('Result = ', n);
  finally
    lua_close(L);
  end;
  ReadLn;
end.

Lua の API を利用する場合、スタック操作が多用されます。関数の呼び出しにおいても、関数自身、引数1、引数2と順番にスタックに積んでおき、lua_pcall() を呼び出して関数を実行します。

「lua_pcall()」では、引数の数と、戻り値の数を指定します。Lua では、戻り値を複数持つことができる仕様になっているため、このように戻り値の数も指定することになっています。

LuaからDelphi側の関数を呼ぶ

次に、Lua から Delphi の関数を呼んでみます。lua.dll は、VC++でコンパイルされており、API は、呼び出し規約が、cdecl になっているので注意が必要です。

以下は、Delphi側で定義した割り算の関数を、Lua 側から呼ぶ例です。

program CallDelphiFunc;
{$APPTYPE CONSOLE}
uses
  SysUtils,
  Lua in 'Lua.pas',
  LuaUtils in 'LuaUtils.pas';

// Delphiで簡単な割り算の関数を定義
function delphi_div(L: Plua_State): Integer; cdecl;
var
  a, b, res: Integer;
begin
  a := lua_tointeger(L, 1); // 第一引数を取得
  b := lua_tointeger(L, 2); // 第二引数を取得
  res := a div b;
  lua_pushinteger(L, res);  // 結果をスタックに積む
  Result := 1;              // 戻り値は1つ
end;

var
  L: Plua_State;
  n: Integer;
begin
  L := luaL_newstate;
  try
    luaL_openlibs(L);
    // Delphiの関数を登録する
    lua_register(L, 'delphi_div', delphi_div);
    // Lua を実行
    luaL_dostring(L, 'print(delphi_div(10,2))');
  finally
    lua_close(L);
  end;
  ReadLn;
end.

こちらも、実に簡単で、lua_register() でDelphiの関数を登録するだけです。ただし、この引数に指定できる Delphiの関数は、lua_CFunction 型の関数だけです。これは、以下のように定義されています。

type lua_CFunction = function(L : Plua_State) : Integer; cdecl;

実際の関数の定義では、スタックより関数の引数を取得し、関数の戻り値をスタックに積むという手順を踏みます。

function delphi_div(L: Plua_State): Integer; cdecl;
var
  a, b, res: Integer;
begin
  a := lua_tointeger(L, 1); // 第一引数を取得
  b := lua_tointeger(L, 2); // 第二引数を取得
  res := a div b;
  lua_pushinteger(L, res);  // 結果をスタックに積む
  Result := 1;              // 戻り値は1つ
end;

関数を呼ぶ時のスタック操作さえ覚えてしまえば、なんということはありません。

まとめ

以上、Delphi + Lua の連携方法について簡単にまとめてみました。Lua の柔軟性と、Delphi の生産性を組み合わせることで、ぐっとプログラミングが楽しくなることでしょう。Lua は高速で動作するだけでなく、ランタイム自体もとてもサイズが小さいので、組み込み用のスクリプトとして最適です。

最近では、Delphiはそれほど人気がないようですが、実際のところ、クライアントアプリの制作においての生産性の高さは群を抜いています。Lua と組み合わせることで、Delphi をより快適に使うことができるでしょう。

Series Navigation«高速スクリプト言語「Lua」を始めよう!(6)

このサイトについて

八角研究所
株式会社八角研究所のWEBサイトですよー。 いろんなものを創り出すことのできる環境をコツコツ構築中。 いったい、いつになったらできるのか。 この技術情報サイトもそのための活動の一環のつもり。

執筆者紹介

クジラ飛行机

クジラ飛行机

くじらはんど(http://kujirahand/)にて、日本語プログラミング言語「なでしこ」(IPA未踏ユース採択)、テキスト音楽「サクラ」(OSPオンラインソフト大賞入賞)など多くのオンラインソフトを開発。著書に「Flexプロフェッショナルガイド」「なでしこ公式バイブル」、「一週間でマスターするActionScript3.0」など。

TrackBack URL :