エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

LuaとGLUTをShared Libraryでつなげてみる

わけあって、Lua再起動中。

おさらい

前回の様子。もう2年も経ってる…。


「つなげる」ということで。GLUTのライブラリをLuaから扱えるように、つなぎのコードをC++で書いてみていました。このときはLuaそのものよりも、LuaのライブラリをC++で書くことに目が向いていて、ライブラリを組み込んだ実行ファイルまで作ってしまっています。でも、ふつう、動的に拡張する(ライブラリを読み込む)方法があるよね、ということで。Shared Libraryをつくってそれを読み込ませてみることに今回は挑戦です。


ライブラリ作成には前回の成果を利用します。

先に結果をリンクしておきます。このあとで、ここに至る道のりを解説します。

Mac OS XでShared Libraryを作る方法

Shared LibraryをGCCでつくるには。検索すると「-sharedオプションを付ければいい」とすぐにヒットします。ふつうはこれでいいみたいです。Mac OS Xはこれではダメでした。

どうも期待したとおりにならないと、「man gcc」で-sharedオプションの説明を読んでみると。

       -shared
           (説明省略)

           This option is not supported on Mac OS X.

…orz。


気を取り直して。Mac OS XでShared Libraryのオプションについて検索すると。

オプションが -shared ではなくて -dynamiclib 。


OSX(gcc) で共有ライブラリを作る - Higepon’s blog


一刀両断。


これで解決しました。

LuaでShared Libraryを使う方法

Luaでは動的にライブラリや他のスクリプトファイルを読み込む方法のひとつに、[http://www.lua.org/manual/5.1/manual.html#pdf-require:title=require]関数を使う方法があります。

require "foo"

ためしに、読み込むファイルがないところで上の行を実行してみると、わたしの環境のばあいこんなエラーが表示されます。

$ lua
Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio
> require "foo"
stdin:1: module 'foo' not found:
	no field package.preload['foo']
	no file './foo.lua'
	no file '/opt/local/share/lua/5.1/foo.lua'
	no file '/opt/local/share/lua/5.1/foo/init.lua'
	no file '/opt/local/lib/lua/5.1/foo.lua'
	no file '/opt/local/lib/lua/5.1/foo/init.lua'
	no file './foo.so'
	no file '/opt/local/lib/lua/5.1/foo.so'
	no file '/opt/local/lib/lua/5.1/loadall.so'
stack traceback:
	[C]: in function 'require'
	stdin:1: in main chunk
	[C]: ?


foo.luaあるいはfooフォルダ下のinit.luaというファイルをライブラリパスの順に検索し、見つからないばあい次にShared Libraryのfoo.soあるいはfooフォルダ下のloadall.soを検索しています。

ここで検索されるフォルダは、Luaのファルであれば[http://www.lua.org/manual/5.1/manual.html#pdf-package.path:title=package.path](対応する環境変数LUA_PATH)、Shared Libraryであれば
[http://www.lua.org/manual/5.1/manual.html#pdf-package.cpath:title=package.cpath](対応する環境変数LUA_CPATH)で指定されています。

またrequire関数はフォルダの階層構造にも対応しています。
階層をつけて次のように書くと、

> require "foo.bar"
stdin:1: module 'foo.bar' not found:
	no field package.preload['foo.bar']
	no file './foo/bar.lua'
	no file '/opt/local/share/lua/5.1/foo/bar.lua'
	no file '/opt/local/share/lua/5.1/foo/bar/init.lua'
	no file '/opt/local/lib/lua/5.1/foo/bar.lua'
	no file '/opt/local/lib/lua/5.1/foo/bar/init.lua'
	no file './foo/bar.so'
	no file '/opt/local/lib/lua/5.1/foo/bar.so'
	no file '/opt/local/lib/lua/5.1/loadall.so'
	no file './foo.so'
	no file '/opt/local/lib/lua/5.1/foo.so'
	no file '/opt/local/lib/lua/5.1/loadall.so'
stack traceback:
	[C]: in function 'require'
	stdin:1: in main chunk
	[C]: ?

このようにサブフォルダを検索します。


ライブラリファイルの存在はわかったので、次はライブラリの呼び出し方。

require "foo"を実行するとfoo.so(あるいはfoo/init.so)というライブラリが読み込まれますが、読み込まれたあとにライブラリの中のluaopen_fooという関数が呼び出されます。
引数戻り値も書くとこうなります。

LUALIB_API int luaopen_foo(lua_State* L)

ここで使われているマクロや型はlua.hで定義されています。


階層化している場合、たとえばrequire "foo.bar"を実行するとfoo/bar.so(あるいはfoo/bar/init.so)というライブラリが読み込まれますが、読み込まれたあとにライブラリの中のluaopen_foo_barという関数が呼び出されます。

これらの詳細についてはLuaのマニュアルのここを読んでみてください。


ちなみに。ここではバージョン5.1のマニュアルにリンクしてあるのは、わたしが使っているバージョンが5.1だからというだけという理由。5.2を使われている方は5.2のマニュアルを参照してみてください。

LuaGLUTをつなげてみる、ふたたび

で。ようやく準備が整いました。

じつは。前回書いたコードですが、C++のコードの部分はエントリポイントまでほぼ条件を満たしていて、Shared Libraryとしてビルドするだけでライブラリができる状態になっていました。偶然にも。既存のコードをまねて書いた結果、偶然に条件を満たしていたようです。

一カ所。C++でコードを書いたわけですが、エントリポイントになる関数の名前はC++の名前の修飾があると呼び出せないのでextern "C"をつける必要があります。

extern "C" LUALIB_API int luaopen_glut(lua_State* L)

あるいは

extern "C"
{
LUALIB_API int luaopen_glut(lua_State* L)
{
    ...
}
}


あとはフレームワークやライブラリを指定してビルド。

g++ -ansi -Wall -framework OpenGL -framework GLUT -framework Foundation -dynamiclib -o glut.so glut.cpp -llua


これでLuaGLUT Shared Libraryができてしまいました。


なんで今になってこんなことを始めたかについては、また後日。

いつか読むはずっと読まない:弩級

“The Lost Fleet: Beyond the Frontier Dreadnaught”の邦訳が刊行されました。
これは“The Lost Fleet”シリーズ、邦訳「彷徨える艦隊」シリーズの続編になるのですが、以前予想したとおり「彷徨える艦隊7」と通巻になりました。
いきなり冒頭で政治に巻き込まれるのですけど、どこでも「ダメな政治」に巻き込まれるのは嫌なものです、はい。

彷徨える艦隊〈7〉戦艦ドレッドノート (ハヤカワ文庫SF)

彷徨える艦隊〈7〉戦艦ドレッドノート (ハヤカワ文庫SF)