Waylandで日本語入力への道: コンストラクタに届け編
Kernel/VM Advent Calendarの何日目かの記事です. このカレンダーは delayed allocationで, extent allocationです.
前回のあらすじ: fcitx::AddonFactoryのvtableを作ることに成功し, 無事にfcitxがクラッシュするようになった. 次はfcitx::InputMethodEngineV3を作りたいけど24個もエントリがあるなあ.
DISCLAIMER: waylandで日本語入力したい時にこの記事は役に立たないし, RustでC++のbindingをしたい時にもやめた方がいいと思う.
vtableをうめよう
とりあえず, build.rsをいじって"fcitx::InputMethodEngineV4"のbindingも生成します.
fcitx::InputMethodEngineV4 -> fcitx::InputMethodEngineV3 -> fcitx::InputMethodEngineV2 -> fcitx::InputMethodEngine -> fcitx::AddonInstance という継承関係になっています. これを生成されたとおりに, rustにすると以下のようになります.
let engine = fcitx5_sys::fcitx_InputMethodEngineV4 { _base: fcitx5_sys::fcitx_InputMethodEngineV3 { _base: fcitx5_sys::fcitx_InputMethodEngineV2 { _base: fcitx5_sys::fcitx_InputMethodEngine { _base: fcitx5_sys::fcitx_AddonInstance { vtable_: todo!(), d_ptr: todo!(), }, }, }, }, };
あまりにネストが深くてうるさい感じです. 結局はvtable_とd_ptrしか持っていないので, フラットにして構わないでしょう. ということで, vtableおよびcreate()から返すべきTCodeEngineを実装します. d_ptrはとりあえず0でやっておきます.
#[repr(C)] struct EngineVTable { pub complete_object_dtor: unsafe extern "C" fn(this: *mut TCodeEngine), pub deleting_dtor: unsafe extern "C" fn(this: *mut TCodeEngine), pub reload_config: unsafe extern "C" fn(this: *mut TCodeEngine), ... pub set_config_for_input_method: unsafe extern "C" fn( this: *mut TCodeEngine, entry: *const fcitx5_sys::fcitx_InputMethodEntry, config: *const fcitx5_sys::fcitx_RawConfig, ), } const TCODE_ENGINE_VTABLE: EngineVTable = EngineVTable { complete_object_dtor: dtor, deleting_dtor: dtor, reload_config: reload_config, ... set_config_for_input_method: set_config_for_input_method, }; unsafe extern "C" fn dtor(this: *mut TCodeEngine) { eprintln!("engine dtor called"); } unsafe extern "C" fn reload_config(this: *mut TCodeEngine) { eprint!("reload_config called"); } unsafe extern "C" fn save(this: *mut TCodeEngine) {} ... struct TCodeEngine { vtable: *const EngineVTable, d_ptr: u64, } 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"); let engine = Box::new(TCodeEngine { vtable: &mut TCODE_ENGINE_VTABLE, d_ptr: 0, }); Box::into_raw(engine) as *mut _ }
これで動かすと, 以下のようにfcitx5がクラッシュします.
Thread 1 "fcitx5" received signal SIGSEGV, Segmentation fault. 0x00007ffff7f566dd in fcitx::AddonManagerPrivate::realLoad (this=this@entry=0x5555555d5af0, q_ptr=q_ptr@entry=0x5555555d2f68, addon=...) at /dev/shm/portage/app-i18n/fcitx-5.1.5/work/fcitx5-5.1.5/src/lib/fcitx/addonmanager.cpp:192 192 addon.instance_->d_func()->addonInfo_ = &(addon.info()); (gdb) p *addon.instance_ $2 = {_vptr.AddonInstance = 0x7ffff49fc160, d_ptr = std::unique_ptr<fcitx::AddonInstancePrivate> = { get() = 0x0}}
ここで addon.instance_ == TCodeEngineのインスタンスです. d_ptrとd_func()がなにやら関係のありそうな感じです.
AddonInstance::d_ptrの定義はこれです.
std::unique_ptr<AddonInstancePrivate> d_ptr; FCITX_DECLARE_PRIVATE(AddonInstance);
d_ptrの一行下のFCITX_DECLARE_PRIVATE()が以下のようなマクロになります.
#define FCITX_DECLARE_PRIVATE(Class) \ inline Class##Private *d_func() { \ return static_cast<Class##Private *>(d_ptr.get()); \ } \ inline const Class##Private *d_func() const { \ return static_cast<Class##Private *>(d_ptr.get()); \ } \ friend class Class##Private;
これがSEGVしてたコードのd_func()の部分で, 結局はd_ptrを0でさぼってたからぬるぽをふんでたという話ですね. じゃあ, d_ptrを適切に初期化すればいいわけです. 適切な初期化方法がなにかというと…まあ AddonInstanceのコンストラクタが初期化しているので, これを呼びましょう.
AddonInstance::AddonInstance() : d_ptr(std::make_unique<AddonInstancePrivate>()) {}
コンストラクタを呼ぼう
コンストラクタを呼ぶのは簡単です. 生成されたbinding.rsを見ていると, fcitx5側の関数を呼びたかったらこんな感じでlinkすればいいんだなとわかってきます.
extern "C" { #[link_name = "\u{1}_ZN5fcitx13AddonInstanceC2Ev"] fn TCodeEngine_ctor(this: *const TCodeEngine); }
そして, 呼び出し側をこうやっとくと…コンストラクタが呼ばれ…
let engine = Box::new(TCodeEngine { vtable: &TCODE_ENGINE_VTABLE, d_ptr: 0, }); let ptr = Box::into_raw(engine) as *mut _; TCodeEngine_ctor(ptr); let engine = Box::from_raw(ptr); assert!(engine.vtable == &TCODE_ENGINE_VTABLE); Box::into_raw(engine) as *mut _
assertにひっかかってクラッシュします. ちゃんとこのへん知らなかったんですが, 親クラスのコンストラクタ呼んだ後は, vtableが親クラスのやつになってるみたいです. 学びですね〜
そんならコンストラクタの後からvtableをいれます.
let engine = Box::new(TCodeEngine { vtable: std::ptr::null_mut(), d_ptr: 0, }); let ptr = Box::into_raw(engine) as *mut _; TCodeEngine_ctor(ptr); let mut engine = Box::from_raw(ptr); engine.vtable = &TCODE_ENGINE_VTABLE; Box::into_raw(engine) as *mut _
ということで, ここまでやるとfcitx5がtcodeのアドオンを読んで動くようになります…. まあ実際は"OnDemand=True"にしてるのでちゃんと読んでるわけではなさそうですが…
次回こそ多分 Waylandで日本語入力への道: dynamic_castがならなくて編