Waylandで日本語入力への道: vtableを作ろう編

adventar.org


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 "とだけ書いています. 大量に出てきますがその中に fcitx::AddonFactory のvtableのレイアウトも書かれています.

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)に以下のように書かれています.

refspecs.linuxbase.org

> 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を参考にします.

fcitx5-anthy/src/engine.cpp at 1172f034313fb085e5b1e79382ad4f8fd03704cc · fcitx/fcitx5-anthy · GitHub

    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の作り方もわかったし, なんもこわいことがないで……す…ね…

fcitx5/src/lib/fcitx/inputmethodengine.h at 827561460baf2baba2e16207c6355a2268f12e1e · fcitx/fcitx5 · GitHub

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で日本語入力への道: コンストラクタに届け編

gentoo.hatenablog.com