「LuaAppMaker の AppImage を作る」の続きです。wxWebView
を立ち上げ後にロードして、エラーが出れば wxWebView
なしで実行を継続できるようにします。
まず、LuaAppMaker を wxWebView
抜きでビルドしないといけない。wxLua のソース (wxluasetup.h
) を見ると、一見 wxLUA_USE_wxWebView
を 0
に #define
すれば外せそうに見えるんだけど、実はそうではない。このマクロは定義されているだけで、どこにも使われていない。wxWebView
のバインディングを実装している wxwebview_bind.cpp
の中では、wxUSE_WEBVIEW
だけしかチェックされていない。このマクロは wxWidgets のビルドの時に値が固定されているので、後から変更するとおかしなことになりそう。
正解は、wxLUA_USEBINDING_WXWEBVIEW
を 0
に #define
すること。こうすると、バインディングの初期化ルーチンを呼ばなくなる。その上で、LuaAppMaker本体をビルドする時に wxwebview_bind.o
をリンク対象から外しておけば、wxWebView
関連のコードは LuaAppMaker に入らなくなるはず。
前回と同じように AppImage を作って、同梱されるライブラリを調べてみた。
$ cd LuaAppMaker.AppDir/usr/lib
$ ls -lS
-rw-r--r-- 1 nagata staff 8561644 9 22 23:52 libgtk-3.so.0
-rw-r--r-- 1 nagata staff 1390740 9 22 23:52 libcairo.so.2
-rw-r--r-- 1 nagata staff 1214772 9 22 23:52 libepoxy.so.0
-rw-r--r-- 1 nagata staff 1137548 9 22 23:52 libgdk-3.so.0
...
WebKit 関連のでかいライブラリが消えて、AppImage のサイズは 16.9 MBになりました。よしよし。
次は、いったん外した wxWebView
関連の初期化ルーチンを、dlopen()
経由で呼べるようにしたい。理屈の上では wxwebview_bind.o
を共有ライブラリにして dlopen()
でロードすればいいんだけど、うまくいくんかいな。
wxwebview_bind.o
を共有ライブラリにしてみる。(下記は Makefile
からの引用なので、単独では動作しませんが、まあ雰囲気はわかるでしょう。)
$(CPP) -shared -fPIC -o $(DESTPREFIX)/wxwebview.so $(DESTPREFIX)/wxlua/wxLua/modules/wxbind/src/wxwebview_bind.o $(CFLAGS) $(LDFLAGS) -lwebkit2gtk-4.0 -ljavascriptcoregtk-4.0 $(LUAJIT_LDFLAGS)
一応通った。LuaAppMaker 本体の方で、dlopen()
で共有ライブラリを読み込み、dlsym()
で関数のエントリーアドレスを得て呼び出してみる。
void *handle = dlopen(rpath + wxT("/lib/wxwebview.so"), RTLD_NOW);
if (handle != NULL) {
wxLuaBinding * (*wxwebview_init)() =
(typeof(wxwebview_init))dlsym(handle, "_Z27wxLuaBinding_wxwebview_initv");
// Mangled name of wxLuaBinding_wxwebview_init()
if (wxwebview_init != NULL) {
(*wxwebview_init)();
webviewAvailable = true;
} else {
fprintf(stderr, "dlsym() failed: %s\n", dlerror());
}
} else {
fprintf(stderr, "dlopen() failed: %s\n", dlerror());
}
実行してみると、wxWidgets から苦情を言われた。
../src/common/object.cpp(239): assert "classTable->Get(m_className) == __null" failed in Register(): Class "wxObject" already in RTTI table - have you used wxIMPLEMENT_DYNAMIC_CLASS() multiple times or linked some object file twice)?
うわ、これは面倒だな。LuaAppMaker 本体と wxwebview.so
で、オブジェクトの初期化コードがダブってるんだ。そりゃそうだ、同じ wxWidgets 静的ライブラリに独立にリンクしているんだから、同じコードにアクセスしていれば当然ダブってしまう。LuaAppMaker 本体がコードや変数をすでに持っている場合は、それを参照しないといけないんだ。そんなことできるんかな……
似たようなことをやろうとしている人がいた。"Selective static linking of library functions in shared library"
ポイントが3つある。
- 実行ファイルをリンクする時に
-export-dynamics
を指定すると、すべてのシンボルがダイナミックテーブルにエキスポートされる。このオプションをつけないと、グローバルシンボルであってもダイナミックテーブルにはエキスポートされないので、共有ライブラリ側から実行ファイルのシンボルを参照することはできない。セキュリティ的にその方が安全なのだが、この際仕方がない。 - 実行ファイルをリンクする時に
-shared -pie
を指定しておけば、実行ファイルを共有ライブラリと同じようにリンカに渡すことができる。 - 共有ライブラリから実行ファイルを参照できるように、
RPATH
の指定が必要。
やってみた。(このコードもこのままでは動きませんが、やったことの雰囲気だけ。)
# LuaAppMaker 本体のビルド
$(CPP) -o $(EXECUTABLE) $(DESTOBJECTS) $(DESTPREFIX)/buildInfo.o $(WIN_FOPEN_O) $(CFLAGS) -shared -pie -Wl,-export-dynamic $(LDFLAGS) $(LUAJIT_LDFLAGS)
# wxwebview.so のビルド: LuaAppMaker から見て lib/wxwebview.so に置くものとする
$(CPP) -shared -fPIC -o $(DESTPREFIX)/wxwebview.so $(DESTPREFIX)/wxlua/wxLua/modules/wxbind/src/wxwebview_bind.o $(CFLAGS) $(EXECUTABLE) -Wl,-rpath='\$ORIGIN/..' $(LDFLAGS) -lwebkit2gtk-4.0 -ljavascriptcoregtk-4.0 $(LUAJIT_LDFLAGS)
通りました。AppImage のサイズは 17.5 MB になった。まずまずです。