Skip to main content

msime_engine_capi/
abi.rs

1//! 跨边界的值类型和几条共用的规则:借用字符串、按 `struct_size` 收发结构体、线程局部的错误说明、把 panic 挡在边界内。
2
3use std::borrow::Cow;
4use std::cell::RefCell;
5use std::ffi::{c_char, CString};
6use std::mem::{size_of, MaybeUninit};
7
8use msime_engine::EngineError;
9
10use crate::{
11    MSIME_ENGINE_INVALID_ARGUMENT, MSIME_ENGINE_NULL, MSIME_ENGINE_OK, MSIME_ENGINE_PANIC,
12};
13
14/// 库借给调用方的一段 UTF-8 文本:`len` 个字节,不以 NUL 结尾,按长度读。空串时 `ptr` 也不是 NULL。指向会话里缓存的数据,有效期见各个函数:热路径上的都到同一会话下一次改变状态的调用为止。
15#[repr(C)]
16#[derive(Debug, Clone, Copy)]
17pub struct msime_engine_str {
18    pub ptr: *const c_char,
19    pub len: usize,
20}
21
22impl msime_engine_str {
23    pub(crate) const EMPTY: Self = Self {
24        ptr: c"".as_ptr(),
25        len: 0,
26    };
27
28    pub(crate) fn borrow(text: &str) -> Self {
29        if text.is_empty() {
30            return Self::EMPTY;
31        }
32        Self {
33            ptr: text.as_ptr().cast(),
34            len: text.len(),
35        }
36    }
37}
38
39/// 一次按键、命令或选择的结果(ABI 1.0)。调用前把 `struct_size` 设为 `sizeof(msime_engine_result)`;库写入 min(调用方大小, 库内大小) 个字节,把 `struct_size` 改成写入的大小,多出的部分清零。`commit` 和 `diagnostic` 借自会话,到同一会话下一次改变状态的调用为止有效。
40#[repr(C)]
41#[derive(Debug, Clone, Copy)]
42pub struct msime_engine_result {
43    pub struct_size: u32,
44    /// 引擎接管了这一键(非零)。为零时宿主按自己的规则处理,例如把字符原样交给应用。
45    pub handled: u32,
46    /// 有上屏文字(非零),文字在 `commit`;上屏的也可能是空串。
47    pub has_commit: u32,
48    /// 诊断的稳定数字码(引擎的 `DiagnosticCode`);0 是没有诊断,0xFFFF 是表外的文字。诊断不是失败:键照样处理了,只是学习没能存下之类。
49    pub diagnostic_code: u32,
50    /// 调用之后的候选修订号,与 `msime_engine_state::revision` 相同。
51    pub revision: u64,
52    pub commit: msime_engine_str,
53    /// 诊断的说明文字(英文),没有时为空串。
54    pub diagnostic: msime_engine_str,
55}
56
57/// 组字状态(ABI 1.0),`msime_engine_state_get` 填写,收发规则同 `msime_engine_result`。字符串借自会话按修订号缓存的快照,`msime_engine_state_get`、`msime_engine_candidate_get` 这类只读调用不让它们失效。
58#[repr(C)]
59#[derive(Debug, Clone, Copy)]
60pub struct msime_engine_state {
61    pub struct_size: u32,
62    /// 当前方案,`MSIME_ENGINE_SCHEME_*`。
63    pub scheme: u32,
64    /// 候选修订号:每次可能改变候选的调用都让它变,从 0 起只增不减。按下标选择和取候选时原样交回。
65    pub revision: u64,
66    pub candidate_count: u32,
67    /// `MSIME_ENGINE_STATE_*` 的组合。
68    pub flags: u32,
69    /// `MSIME_ENGINE_LOCAL_MODE_*`。
70    pub local_mode: u32,
71    /// 组字光标在 `editing_text` 里的字节偏移。
72    pub caret_byte: u32,
73    /// 整句改字的光标,按 `conversion` 的字(Unicode 标量)计:光标在第 `conversion_focus_start_scalar` 个字之前,`[start, end)` 是候选替换的那一段;不在改字时两者都是 0。
74    pub conversion_focus_start_scalar: u32,
75    pub conversion_focus_end_scalar: u32,
76    /// 给用户看的组字:全拼按音节分开(`ni'hao`),双拼默认是按键原文,本地模式与专用英文是按键原文。
77    pub preedit: msime_engine_str,
78    /// 按键原文(ASCII),`caret_byte` 指向它;为空就是没有在组字。
79    pub editing_text: msime_engine_str,
80    /// 整句改字时改好的整句,宿主在它非空时把它画在行内代替拼音。
81    pub conversion: msime_engine_str,
82    /// 此刻要按字符(`msime_engine_key_char`)而不是按标点交来的非字母键,见 README 的按键路由。
83    pub spelling_symbols: msime_engine_str,
84}
85
86/// 一行候选(ABI 1.0),`msime_engine_candidate_get` 填写,收发规则同 `msime_engine_result`,字符串的有效期同 `msime_engine_state`。
87#[repr(C)]
88#[derive(Debug, Clone, Copy)]
89pub struct msime_engine_candidate {
90    pub struct_size: u32,
91    /// 这一行的下标(就是传进来的那个)。
92    pub index: u32,
93    /// `MSIME_ENGINE_SOURCE_*`。
94    pub source: u32,
95    /// 给出这一行的方案,`MSIME_ENGINE_SCHEME_*`(五笔混拼里的拼音行是全拼)。
96    pub scheme: u32,
97    /// `MSIME_ENGINE_CANDIDATE_*` 的组合。
98    pub flags: u32,
99    /// 用户固定的位置 1..=5,没固定时 0。
100    pub fixed_position: i32,
101    pub word: msime_engine_str,
102    /// 这一行匹配的编码,按输入的写法(全拼是带 `'` 的音节)。
103    pub code: msime_engine_str,
104    /// 注释:辅助码或纠错提示,没有时为空串。
105    pub annotation: msime_engine_str,
106    /// 纠错读出的行:用户实际打的字母;其余为空串。
107    pub corrected_from: msime_engine_str,
108}
109
110/// 运行时的四个根目录(ABI 1.0),都要是绝对路径,UTF-8。库只读 min(调用方大小, 库内大小) 个字节,读不到的字段按全零处理。
111#[repr(C)]
112#[derive(Debug, Clone, Copy)]
113pub struct msime_engine_paths {
114    pub struct_size: u32,
115    /// 只读的随包资源:词库、辅助码表、模型。
116    pub resources: msime_engine_str,
117    /// 用户数据:学习日志 `msime_user.db` 和代次目录 `dictionaries/`。
118    pub user_data: msime_engine_str,
119    /// 可以随时清空的缓存。
120    pub cache: msime_engine_str,
121    /// 代次目录,`msime_engine_prepare_runtime` 准备并交回;`msime_engine_prepare_runtime` 不读这一项,调用方可以不填。
122    pub dictionaries: msime_engine_str,
123}
124
125/// 按 `struct_size` 收发的结构体。
126pub(crate) trait Sized1: Copy {
127    /// ABI 1.0 布局的大小;调用方给的 `struct_size` 不能比它小。在 1.0 之后追加字段时把它固定成 1.0 最后一个字段的结束偏移,不要跟着 `size_of` 变。
128    const V1_SIZE: usize;
129}
130
131macro_rules! sized1 {
132    ($($name:ident),*) => {$(
133        impl Sized1 for $name {
134            const V1_SIZE: usize = size_of::<$name>();
135        }
136    )*};
137}
138sized1!(
139    msime_engine_result,
140    msime_engine_state,
141    msime_engine_candidate,
142    msime_engine_paths
143);
144
145// 输出结构体按字节整块复制,所以不能有填充:填充字节的值是未定义的。每个断言都是字段大小之和。
146const _: () = {
147    let word = size_of::<msime_engine_str>();
148    assert!(size_of::<msime_engine_result>() == 4 * 4 + 8 + 2 * word);
149    assert!(size_of::<msime_engine_state>() == 4 * 2 + 8 + 4 * 6 + 4 * word);
150    assert!(size_of::<msime_engine_candidate>() == 4 * 6 + 4 * word);
151};
152
153/// 失败的状态码和说明文字。
154#[derive(Debug)]
155pub(crate) struct Failure {
156    pub code: i32,
157    pub message: Cow<'static, str>,
158}
159
160impl Failure {
161    pub(crate) fn new(code: i32, message: impl Into<Cow<'static, str>>) -> Self {
162        Self {
163            code,
164            message: message.into(),
165        }
166    }
167
168    pub(crate) fn invalid(message: impl Into<Cow<'static, str>>) -> Self {
169        Self::new(MSIME_ENGINE_INVALID_ARGUMENT, message)
170    }
171
172    pub(crate) fn null(what: &'static str) -> Self {
173        Self::new(MSIME_ENGINE_NULL, what)
174    }
175}
176
177impl From<EngineError> for Failure {
178    fn from(error: EngineError) -> Self {
179        Self::new(error.code().value() as i32, error.to_string())
180    }
181}
182
183pub(crate) type Outcome<T = ()> = Result<T, Failure>;
184
185thread_local! {
186    /// 本线程上一次失败的说明;成功的调用把它清掉。`None` 时 `msime_engine_last_error` 给空串,按键路径上因此不分配。
187    static LAST_ERROR: RefCell<Option<CString>> = const { RefCell::new(None) };
188}
189
190/// 本线程的说明文字;有效到本线程下一次调用这个库(`msime_engine_last_error` 本身除外)。
191pub(crate) fn last_error() -> *const c_char {
192    LAST_ERROR.with(|slot| {
193        slot.borrow()
194            .as_ref()
195            .map_or(c"".as_ptr(), |message| message.as_ptr())
196    })
197}
198
199fn set_last_error(message: Option<&str>) {
200    let message = message.map(|text| {
201        // 说明文字里不会有 NUL;万一有,换掉而不是丢掉整条。
202        CString::new(text.replace('\0', "\u{fffd}")).unwrap_or_default()
203    });
204    LAST_ERROR.with(|slot| *slot.borrow_mut() = message);
205}
206
207/// 进入库的每个入口都经这里:清掉上一条说明,把 panic 挡在边界内,失败时记下说明并交回状态码。会话级的 panic 另由 `session::with_session` 作废会话。
208pub(crate) fn guard(body: impl FnOnce() -> Outcome) -> i32 {
209    set_last_error(None);
210    let outcome = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(body)) {
211        Ok(outcome) => outcome,
212        Err(payload) => Err(Failure::new(
213            MSIME_ENGINE_PANIC,
214            panic_message(payload.as_ref()),
215        )),
216    };
217    match outcome {
218        Ok(()) => MSIME_ENGINE_OK,
219        Err(failure) => {
220            set_last_error(Some(&failure.message));
221            failure.code
222        }
223    }
224}
225
226pub(crate) fn panic_message(payload: &(dyn std::any::Any + Send)) -> String {
227    let detail = payload
228        .downcast_ref::<&str>()
229        .copied()
230        .or_else(|| payload.downcast_ref::<String>().map(String::as_str))
231        .unwrap_or("non-string payload");
232    format!("internal panic: {detail}")
233}
234
235/// 借用的输入文本:`(ptr, len)`,UTF-8,不要求 NUL 结尾。`ptr` 为 NULL 时只接受 `len == 0`,当作空串。
236///
237/// # Safety
238/// `ptr` 非空时指向至少 `len` 个可读字节,调用期间不变。
239pub(crate) unsafe fn input_str<'a>(
240    ptr: *const c_char,
241    len: usize,
242    what: &'static str,
243) -> Outcome<&'a str> {
244    if ptr.is_null() {
245        return if len == 0 {
246            Ok("")
247        } else {
248            Err(Failure::null(what))
249        };
250    }
251    // SAFETY: 调用方保证 `ptr` 指向 `len` 个可读字节。
252    let bytes = unsafe { std::slice::from_raw_parts(ptr.cast::<u8>(), len) };
253    std::str::from_utf8(bytes).map_err(|_| Failure::invalid(format!("{what} is not UTF-8")))
254}
255
256/// 结构体里的一个输入字符串字段。
257///
258/// # Safety
259/// 同 [`input_str`]。
260pub(crate) unsafe fn field_str<'a>(
261    value: msime_engine_str,
262    what: &'static str,
263) -> Outcome<&'a str> {
264    // SAFETY: 转交给 `input_str`,前提相同。
265    unsafe { input_str(value.ptr, value.len, what) }
266}
267
268/// 读调用方交来的输入结构体:只读它声称的大小与库内大小中较小的那部分,其余按全零。
269///
270/// # Safety
271/// `input` 非空时按 `T` 对齐,至少可读它开头的 `struct_size` 指出的字节数。
272pub(crate) unsafe fn read_sized<T: Sized1>(input: *const T, what: &'static str) -> Outcome<T> {
273    if input.is_null() {
274        return Err(Failure::null(what));
275    }
276    // SAFETY: 非空且对齐,开头四个字节是 `struct_size`。
277    let caller = unsafe { input.cast::<u32>().read() } as usize;
278    if caller < T::V1_SIZE {
279        return Err(Failure::invalid(format!(
280            "{what}: struct_size is smaller than the ABI 1.0 layout"
281        )));
282    }
283    let mut value = MaybeUninit::<T>::zeroed();
284    // SAFETY: 两边都至少有 `min(caller, size_of::<T>())` 个字节;全零对这些 `repr(C)` 类型(整数和裸指针)是合法值。
285    unsafe {
286        std::ptr::copy_nonoverlapping(
287            input.cast::<u8>(),
288            value.as_mut_ptr().cast::<u8>(),
289            caller.min(size_of::<T>()),
290        );
291        Ok(value.assume_init())
292    }
293}
294
295/// 调用方给的输出结构体声明的大小;比 1.0 的布局还小时失败。
296///
297/// # Safety
298/// `output` 非空,按 `T` 对齐,开头四个字节可读。
299unsafe fn output_size<T: Sized1>(output: *const T, what: &'static str) -> Outcome<usize> {
300    // SAFETY: 调用方的前提,开头四个字节是调用方填的 `struct_size`。
301    let caller = unsafe { output.cast::<u32>().read() } as usize;
302    if caller < T::V1_SIZE {
303        return Err(Failure::invalid(format!(
304            "{what}: struct_size is smaller than the ABI 1.0 layout"
305        )));
306    }
307    Ok(caller)
308}
309
310/// 改变会话之前核对可选的输出结构体(NULL 表示调用方不要结果):`struct_size` 太小的调用在这里就被拒绝,会话原样不动,不会出现键已经处理、上屏文字却交不回去的情况。
311///
312/// # Safety
313/// `output` 是 NULL,或按 `T` 对齐、开头四个字节可读。
314pub(crate) unsafe fn check_output<T: Sized1>(output: *const T, what: &'static str) -> Outcome {
315    if output.is_null() {
316        return Ok(());
317    }
318    // SAFETY: 调用方的前提。
319    unsafe { output_size(output, what) }.map(|_| ())
320}
321
322/// 把输出结构体写给调用方:写 min(调用方大小, 库内大小) 个字节,`struct_size` 改成写入的大小,调用方多出的部分清零。
323///
324/// # Safety
325/// `output` 非空时按 `T` 对齐,开头的 `struct_size` 是调用方这块内存的真实大小,整块可写。
326pub(crate) unsafe fn write_sized<T: Sized1>(
327    output: *mut T,
328    mut value: T,
329    what: &'static str,
330) -> Outcome {
331    if output.is_null() {
332        return Err(Failure::null(what));
333    }
334    // SAFETY: 非空且对齐,调用方的前提。
335    let caller = unsafe { output_size(output, what) }?;
336    let written = caller.min(size_of::<T>());
337    // SAFETY: 每个输出类型都以 `u32 struct_size` 开头(`repr(C)`),这里改写的就是它。
338    unsafe {
339        std::ptr::addr_of_mut!(value)
340            .cast::<u32>()
341            .write(written as u32)
342    };
343    // SAFETY: 输出类型没有填充(见上面的断言),按字节读 `value` 不会读到未初始化的内存;调用方保证 `caller` 个字节可写。
344    unsafe {
345        std::ptr::copy_nonoverlapping(
346            std::ptr::addr_of!(value).cast::<u8>(),
347            output.cast::<u8>(),
348            written,
349        );
350        if caller > written {
351            std::ptr::write_bytes(output.cast::<u8>().add(written), 0, caller - written);
352        }
353    }
354    Ok(())
355}
356
357/// 交给调用方、由 `msime_engine_string_free` 释放的字符串。
358pub(crate) fn owned_string(text: String) -> *mut c_char {
359    CString::new(text.replace('\0', "\u{fffd}"))
360        .unwrap_or_default()
361        .into_raw()
362}
363
364/// 写给调用方的 `char**` 输出参数。
365///
366/// # Safety
367/// `output` 非空时可写一个指针。
368pub(crate) unsafe fn write_owned(output: *mut *mut c_char, text: String) {
369    if !output.is_null() {
370        // SAFETY: 调用方保证可写。
371        unsafe { output.write(owned_string(text)) };
372    }
373}