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        match OpenCC::from_config(config) {
63            Ok(instance) => {
64                let handle = Box::new(OpenCCHandle {
65                    instance,
66                    is_destroyed: AtomicBool::new(false),
67                });
68                unsafe { *out_handle = Box::into_raw(handle) };
69                OpenCCResult::Success
70            }
71            Err(_) => OpenCCResult::CreationFailed,
72        }
73    }));
74    result.unwrap_or(OpenCCResult::InternalError)
75}
76
77/// 销毁 OpenCC 实例,并释放所有资源。
78///
79/// # Safety
80/// - `handle_ptr` 必须是一个有效指针。
81/// - 在调用此函数后,`handle_ptr` 将变为无效指针,不应再次使用。
82#[unsafe(no_mangle)]
83pub unsafe extern "C" fn opencc_destroy(handle_ptr: *mut OpenCCHandle) {
84    if handle_ptr.is_null() {
85        return;
86    }
87
88    let result = catch_unwind(AssertUnwindSafe(|| {
89        let handle = unsafe { &*handle_ptr };
90
91        if handle
92            .is_destroyed
93            .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
94            .is_ok()
95        {
96            unsafe { drop(Box::from_raw(handle_ptr)) };
97        }
98    }));
99
100    if result.is_err() {
101        // 没有日志库,只能直接打印了
102        eprintln!("opencc_destroy 内部发生 Panic!");
103    }
104}
105
106/// 根据加载的配置转换字符串。
107///
108/// # 参数
109/// - `handle_ptr`: 指向有效 `OpenCCHandle` 实例的指针。
110/// - `text`: 一个指向需要转换的字符串的指针。
111///
112/// # 返回
113/// - 成功时,返回一个指向新的、转换后的 UTF-8 字符串的指针。
114/// - 如果句柄无效、输入文本为 `NULL` 或发生内部错误,则返回 `NULL`。
115///
116/// # 注意
117/// 返回的字符串在堆上分配,你需要在使用完毕后调用 `opencc_free_string`
118/// 来释放它,否则将导致内存泄漏。
119///
120/// # Safety
121/// - `handle_ptr` 必须指向一个有效的、尚未被销毁的 `OpenCCHandle`。
122/// - `text` 必须指向一个有效的、以空字符结尾的 C 字符串。
123#[unsafe(no_mangle)]
124pub unsafe extern "C" fn opencc_convert(
125    handle_ptr: *const OpenCCHandle,
126    text: *const c_char,
127) -> *mut c_char {
128    let result = catch_unwind(AssertUnwindSafe(|| {
129        if handle_ptr.is_null() {
130            return std::ptr::null_mut();
131        }
132
133        let handle = unsafe { &*handle_ptr };
134
135        if handle.is_destroyed.load(Ordering::SeqCst) {
136            return std::ptr::null_mut();
137        }
138
139        if text.is_null() {
140            return std::ptr::null_mut();
141        }
142        let c_str = unsafe { CStr::from_ptr(text) };
143        let r_str = match c_str.to_str() {
144            Ok(s) => s,
145            Err(_) => return std::ptr::null_mut(),
146        };
147
148        let converted_string = handle.instance.convert(r_str);
149
150        match CString::new(converted_string) {
151            Ok(c_string) => c_string.into_raw(),
152            Err(_) => std::ptr::null_mut(),
153        }
154    }));
155
156    result.unwrap_or_else(|_| {
157        // 没有日志库,只能直接打印了
158        eprintln!("opencc_convert 内部发生 Panic!");
159        std::ptr::null_mut()
160    })
161}
162
163/// 释放返回的字符串内存。
164///
165/// # Safety
166/// - `s_ptr` 必须是通过 `opencc_convert` 返回的有效指针,或者是 `NULL`。
167/// - `s_ptr` 只能被释放一次,重复释放会导致未定义行为。
168/// - 在调用此函数后,`s_ptr` 将变为无效指针,不应再次使用。
169/// - 传入不是由 `opencc_convert` 分配的指针会导致未定义行为。
170#[unsafe(no_mangle)]
171pub unsafe extern "C" fn opencc_free_string(s_ptr: *mut c_char) {
172    if s_ptr.is_null() {
173        return;
174    }
175    let result = catch_unwind(AssertUnwindSafe(|| {
176        unsafe { drop(CString::from_raw(s_ptr)) };
177    }));
178
179    if result.is_err() {
180        // 没有日志库,只能直接打印了
181        eprintln!("opencc_free_string 内部发生 Panic!");
182    }
183}