ferrous_opencc/
ffi.rs

1//! `Ferrous OpenCC` 的 FFI 接口。
2
3use crate::OpenCC;
4use crate::config::BuiltinConfig;
5use std::ffi::{CStr, CString, c_char};
6use std::panic::{AssertUnwindSafe, catch_unwind};
7use std::sync::atomic::{AtomicBool, Ordering};
8
9/// FFI 函数的通用返回码。
10#[repr(i32)]
11#[derive(Debug, PartialEq, Eq)]
12pub enum OpenCCResult {
13    /// 操作成功。
14    Success = 0,
15
16    /// 传入的句柄无效。
17    InvalidHandle = 1,
18
19    /// 传入的参数无效。
20    InvalidArgument = 2,
21
22    /// `OpenCC` 实例创建失败(找不到配置文件之类的)。
23    CreationFailed = 3,
24
25    /// 发生了一个未预料的错误(通常是 `panic`)。
26    InternalError = 4,
27}
28
29/// `OpenCC` 的不透明句柄。
30pub struct OpenCCHandle {
31    /// 核心的 `OpenCC` 实例。
32    instance: OpenCC,
33
34    /// 一个原子标志,用于防止双重释放。
35    is_destroyed: AtomicBool,
36}
37
38/// 从嵌入的资源创建 `OpenCC` 实例。
39///
40/// # 参数
41/// - `config`: 代表内置配置的枚举值,例如 `S2t`。
42/// - `out_handle`: 一个指向 `*mut OpenCCHandle` 的指针,用于接收成功创建的句柄。
43///
44/// # 返回
45/// - `OpenCCResult::Success` 表示成功,`out_handle` 将被设置为有效的句柄。
46/// - 其他 `OpenCCResult` 枚举值表示失败,`out_handle` 将被设置为 `NULL`。
47///
48/// # Safety
49/// - `out_handle` 必须指向一个有效的 `*mut OpenCCHandle` 内存位置。
50/// - 返回的句柄必须在不再需要时通过 `opencc_destroy` 释放,以避免资源泄漏。
51#[unsafe(no_mangle)]
52pub unsafe extern "C" fn opencc_create(
53    config: BuiltinConfig,
54    out_handle: *mut *mut OpenCCHandle,
55) -> OpenCCResult {
56    let result = catch_unwind(AssertUnwindSafe(|| {
57        if out_handle.is_null() {
58            return OpenCCResult::InvalidArgument;
59        }
60        unsafe { *out_handle = std::ptr::null_mut() };
61
62        OpenCC::from_config(config).map_or(OpenCCResult::CreationFailed, |instance| {
63            let handle = Box::new(OpenCCHandle {
64                instance,
65                is_destroyed: AtomicBool::new(false),
66            });
67            unsafe { *out_handle = Box::into_raw(handle) };
68            OpenCCResult::Success
69        })
70    }));
71    result.unwrap_or(OpenCCResult::InternalError)
72}
73
74/// 销毁 `OpenCC` 实例,并释放所有资源。
75///
76/// # Safety
77/// - `handle_ptr` 必须是一个有效指针。
78/// - 在调用此函数后,`handle_ptr` 将变为无效指针,不应再次使用。
79#[unsafe(no_mangle)]
80pub unsafe extern "C" fn opencc_destroy(handle_ptr: *mut OpenCCHandle) {
81    if handle_ptr.is_null() {
82        return;
83    }
84
85    let result = catch_unwind(AssertUnwindSafe(|| {
86        let handle = unsafe { &*handle_ptr };
87
88        if handle
89            .is_destroyed
90            .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
91            .is_ok()
92        {
93            unsafe { drop(Box::from_raw(handle_ptr)) };
94        }
95    }));
96
97    if result.is_err() {
98        // 没有日志库,只能直接打印了
99        eprintln!("opencc_destroy 内部发生 Panic!");
100    }
101}
102
103/// 根据加载的配置转换字符串。
104///
105/// # 参数
106/// - `handle_ptr`: 指向有效 `OpenCCHandle` 实例的指针。
107/// - `text`: 一个指向需要转换的字符串的指针。
108///
109/// # 返回
110/// - 成功时,返回一个指向新的、转换后的 UTF-8 字符串的指针。
111/// - 如果句柄无效、输入文本为 `NULL` 或发生内部错误,则返回 `NULL`。
112///
113/// # 注意
114/// 返回的字符串在堆上分配,你需要在使用完毕后调用 `opencc_free_string`
115/// 来释放它,否则将导致内存泄漏。
116///
117/// # Safety
118/// - `handle_ptr` 必须指向一个有效的、尚未被销毁的 `OpenCCHandle`。
119/// - `text` 必须指向一个有效的、以空字符结尾的 C 字符串。
120#[unsafe(no_mangle)]
121pub unsafe extern "C" fn opencc_convert(
122    handle_ptr: *const OpenCCHandle,
123    text: *const c_char,
124) -> *mut c_char {
125    let result = catch_unwind(AssertUnwindSafe(|| {
126        if handle_ptr.is_null() {
127            return std::ptr::null_mut();
128        }
129
130        let handle = unsafe { &*handle_ptr };
131
132        if handle.is_destroyed.load(Ordering::SeqCst) {
133            return std::ptr::null_mut();
134        }
135
136        if text.is_null() {
137            return std::ptr::null_mut();
138        }
139        let c_str = unsafe { CStr::from_ptr(text) };
140        let Ok(r_str) = c_str.to_str() else {
141            return std::ptr::null_mut();
142        };
143
144        let converted_string = handle.instance.convert(r_str);
145
146        CString::new(converted_string).map_or(std::ptr::null_mut(), CString::into_raw)
147    }));
148
149    result.unwrap_or_else(|_| {
150        // 没有日志库,只能直接打印了
151        eprintln!("opencc_convert 内部发生 Panic!");
152        std::ptr::null_mut()
153    })
154}
155
156/// 释放返回的字符串内存。
157///
158/// # Safety
159/// - `s_ptr` 必须是通过 `opencc_convert` 返回的有效指针,或者是 `NULL`。
160/// - `s_ptr` 只能被释放一次,重复释放会导致未定义行为。
161/// - 在调用此函数后,`s_ptr` 将变为无效指针,不应再次使用。
162/// - 传入不是由 `opencc_convert` 分配的指针会导致未定义行为。
163#[unsafe(no_mangle)]
164pub unsafe extern "C" fn opencc_free_string(s_ptr: *mut c_char) {
165    if s_ptr.is_null() {
166        return;
167    }
168    let result = catch_unwind(AssertUnwindSafe(|| {
169        unsafe { drop(CString::from_raw(s_ptr)) };
170    }));
171
172    if result.is_err() {
173        // 没有日志库,只能直接打印了
174        eprintln!("opencc_free_string 内部发生 Panic!");
175    }
176}