Waylandで日本語入力への道: vtableを作ろう編
Kernel/VM Advent Calendarの何日目かの記事です. このカレンダーは delayed allocationなのでサイコーです.
前回のあらすじ: Linuxデスクトップ元年の終わりが近付き, waylandで日本語入力したくなってきた. fcitx5で自作addonをrustで作るために下調べをして, bindgenでbindingを生成するも, 求めていたvtableはそこにはなかった.
DISCLAIMER: waylandで日本語入力したい時にこの記事は役に立たないし, RustでC++のbindingをしたい時にもやめた方がいいと思う.
vtableを作るぞ
ないものは作るしかないですね. vtableを作ります. まずはbuild.rsを調整して, 必要な他のクラスは生成しつつじゃまな fcitx::AddonFactory の分は消します. こんな感じ.
let bindings = builder .header("wrapper.hpp") .allowlist_item("fcitx::AddonManager") .allowlist_item("fcitx::AddonInstance") .blocklist_item("fcitx::AddonFactory") .opaque_type("std::.*") .vtable_generation(true) .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .generate() .expect("Unable to generate bindings");
vtableのレイアウトを調べるために, "g++ -fdump-lang-class=/dev/stdout factory.hpp $(pkgconf --cflags Fcitx5Core)"を実行します. factory.hppには"#include
Vtable for fcitx::AddonFactory fcitx::AddonFactory::_ZTVN5fcitx12AddonFactoryE: 5 entries 0 (int (*)(...))0 8 (int (*)(...))(& _ZTIN5fcitx12AddonFactoryE) 16 0 24 0 32 (int (*)(...))__cxa_pure_virtual
最初のエントリはtop_offsetというもので, 2つ目のエントリは型情報へのポインタが入っています. 実際にvirtual関数のテーブルになっているのは3つ目からです.
__cxa_pure_virtualなのが純粋仮想関数であるcreate()なのはOKとして…上2つはなんでしょう.
Itanium C++ ABI (Revision: 1.83)に以下のように書かれています.
> The entries for virtual destructors are actually pairs of entries. The first destructor, called the complete object destructor, performs the destruction without calling delete() on the object. The second destructor, called the deleting destructor, calls delete() after destroying the object.
つまり, これは2つはどちらもデストラクタで, 1つ目は complete object destructorでdeleteしないもの. 2つ目は deleting destructorでdeleteするものとのことです.
今回はfcitx5のaddon factoryなのでどうせデストラクタが呼ばれる時はプログラム自体終わるでしょうし, いまは気にしないことにします.
structの中のvtable_は, 最初の2つをとばして関数の並びの先頭を指します. ということで, 以下のようにstruct fcitx_AddonFactory__bindgen_vtableの定義を書きます. create()の部分はvirtual destructorを消して, bindgenを走らせれば作ってもらえます. そこからコピペしてよいでしょう.
ここで本当のvtableの最初の2つのエントリはいらないの?という話になります. dynamic_castなどをしなければ, 必要ないようです. bindgenもこれらなしでvtableを生成してきます.
#[repr(C)] pub struct fcitx_AddonFactory__bindgen_vtable { pub fcitx_AddonFactory_complete_object_destructor: unsafe extern "C" fn(this: *mut fcitx_AddonFactory), pub fcitx_AddonFactory_deleting_destructor: unsafe extern "C" fn(this: *mut fcitx_AddonFactory), pub fcitx_AddonFactory_create: unsafe extern "C" fn( this: *mut fcitx_AddonFactory, manager: *mut fcitx_AddonManager, ) -> *mut fcitx_AddonInstance, } #[doc = " Base class for addon factory."] #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct fcitx_AddonFactory { pub vtable_: *const fcitx_AddonFactory__bindgen_vtable, }
これにあわせてvtableと AddonFactoryに相当するstructを作ります.
const FACTORY_VTABLE: fcitx5_sys::fcitx_AddonFactory__bindgen_vtable = fcitx5_sys::fcitx_AddonFactory__bindgen_vtable { fcitx_AddonFactory_complete_object_destructor: tcode_complete_obj_dtor, fcitx_AddonFactory_deleting_destructor: tcode_deleting_dtor, fcitx_AddonFactory_create: tcode_factory_create, }; const FACTORY: fcitx5_sys::fcitx_AddonFactory = fcitx5_sys::fcitx_AddonFactory { vtable_: &FACTORY_VTABLE, };
関数はとりあえずこんな感じで…todo!()にぶつかってpanicしたら成功です.
unsafe extern "C" fn tcode_factory_create( _this: *mut fcitx5_sys::fcitx_AddonFactory, _manager: *mut fcitx5_sys::fcitx_AddonManager, ) -> *mut fcitx5_sys::fcitx_AddonInstance { eprintln!("create called"); todo!() } unsafe extern "C" fn tcode_complete_obj_dtor(_this: *mut fcitx5_sys::fcitx_AddonFactory) { eprintln!("dtor called"); todo!() } unsafe extern "C" fn tcode_deleting_dtor(_this: *mut fcitx5_sys::fcitx_AddonFactory) { eprintln!("dtor called"); todo!() }
addonのエントリポイント
AddonFactoryのデータができてしまえば, エントリポイントを書くのは簡単です. extern "C"とno_mangleだけやっときゃ大丈夫.
#[no_mangle] pub extern "C" fn fcitx_addon_factory_instance() -> *const fcitx5_sys::fcitx_AddonFactory { &FACTORY }
addonを読ませる
addon・inputmethodは設定ファイルでfcitx5に認識されます. $HOME下でやろうと思うと以下の2つのファイルでやるといいです. iconとしてはfcitx-anthyを使いまわしときます… また, Libraryでshared objectの名前を指定します.
$ cat .local/share/fcitx5/addon/tcode.conf [Addon] Name[ja]=T-Code Name=T-Code Category=InputMethod Version=5.1.2 Library=libtcode Type=SharedLibrary OnDemand=True Configurable=True [Dependencies] 0=core/5.0.6 $ cat .local/share/fcitx5/inputmethod/tcode.conf [InputMethod] Name[ja]=T-Code Name=T-Code Icon=fcitx-anthy LangCode=ja Addon=tcode Configurable=True Label=あ
Libraryで指定されたshared objectは, /usr/lib64/fcitx5の下から探されます. ビルドされたファイルをここにおくといいです. もしくは FCITX_ADDON_DIRS 環境変数でこのサーチパスを変更できます.
ということで, fcitx5を動かすとlibtcode.soが読まれてtodo!()に当たってクラッシュするようになってうれしいですね.
これだけじゃなんにもならないので, create()の実装をまともにしましょう. また, fcitx5-anthyを参考にします.
fcitx::AddonInstance *create(fcitx::AddonManager *manager) override { fcitx::registerDomain("fcitx5-anthy", FCITX_INSTALL_LOCALEDIR); return new AnthyEngine(manager->instance()); }
fcitx::AddonInstanceを返せばいいわけですが, 実際のところは AnthyEngineは, そこからさらに継承した fcitx::InputMethodEngineV3 になっているので, そうしたいところです.
https://github.com/fcitx/fcitx5-anthy/blob/1172f034313fb085e5b1e79382ad4f8fd03704cc/src/engine.h#L31
もうvtableの作り方もわかったし, なんもこわいことがないで……す…ね…
Vtable for fcitx::InputMethodEngineV3 fcitx::InputMethodEngineV3::_ZTVN5fcitx19InputMethodEngineV3E: 24 entries 0 (int (*)(...))0 8 (int (*)(...))(& _ZTIN5fcitx19InputMethodEngineV3E) 16 0 24 0 32 (int (*)(...))fcitx::AddonInstance::reloadConfig 40 (int (*)(...))fcitx::AddonInstance::save 48 (int (*)(...))fcitx::AddonInstance::getConfig 56 (int (*)(...))fcitx::AddonInstance::setConfig 64 (int (*)(...))fcitx::AddonInstance::getSubConfig 72 (int (*)(...))fcitx::AddonInstance::setSubConfig 80 (int (*)(...))fcitx::InputMethodEngine::listInputMethods 88 (int (*)(...))__cxa_pure_virtual 96 (int (*)(...))fcitx::InputMethodEngine::activate 104 (int (*)(...))fcitx::InputMethodEngine::deactivate 112 (int (*)(...))fcitx::InputMethodEngine::reset 120 (int (*)(...))fcitx::InputMethodEngine::filterKey 128 (int (*)(...))fcitx::InputMethodEngine::updateSurroundingText 136 (int (*)(...))fcitx::InputMethodEngine::subMode 144 (int (*)(...))fcitx::InputMethodEngine::overrideIcon 152 (int (*)(...))fcitx::InputMethodEngine::getConfigForInputMethod 160 (int (*)(...))fcitx::InputMethodEngine::setConfigForInputMethod 168 (int (*)(...))fcitx::InputMethodEngineV2::subModeIconImpl 176 (int (*)(...))fcitx::InputMethodEngineV2::subModeLabelImpl 184 (int (*)(...))fcitx::InputMethodEngineV3::invokeActionImpl
アー…結構でかいvtableでだるそー……ということで
次回: Waylandで日本語入力への道: コンストラクタに届け編