2021年10月04日

LuaAppMaker の AppImage を作る (wxWebView オプショナル版)

 「LuaAppMaker の AppImage を作る」の続きです。wxWebView を立ち上げ後にロードして、エラーが出れば wxWebView なしで実行を継続できるようにします。

 まず、LuaAppMaker を wxWebView 抜きでビルドしないといけない。wxLua のソース (wxluasetup.h) を見ると、一見 wxLUA_USE_wxWebView0#define すれば外せそうに見えるんだけど、実はそうではない。このマクロは定義されているだけで、どこにも使われていない。wxWebView のバインディングを実装している wxwebview_bind.cpp の中では、wxUSE_WEBVIEW だけしかチェックされていない。このマクロは wxWidgets のビルドの時に値が固定されているので、後から変更するとおかしなことになりそう。

 正解は、wxLUA_USEBINDING_WXWEBVIEW0#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 になった。まずまずです。

20211004-1.jpg

Posted at 2021年10月04日 20:22:43
email.png