Waylandで日本語入力への道: コンストラクタに届け編

adventar.org

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の定義はこれです.

https://github.com/fcitx/fcitx5/blob/827561460baf2baba2e16207c6355a2268f12e1e/src/lib/fcitx/addoninstance.h#L126

    std::unique_ptr<AddonInstancePrivate> d_ptr;
    FCITX_DECLARE_PRIVATE(AddonInstance);

d_ptrの一行下のFCITX_DECLARE_PRIVATE()が以下のようなマクロになります.

https://github.com/fcitx/fcitx5/blob/827561460baf2baba2e16207c6355a2268f12e1e/src/lib/fcitx-utils/macros.h#L14

#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がならなくて編