Skip to main content

ctp2rs/
ffi.rs

1use std::{borrow::Cow, ptr};
2
3use encoding_rs::GB18030;
4
5/// 创建目录
6pub fn check_make_dir(dir: &str) {
7    match std::fs::create_dir_all(dir) {
8        Ok(_) => (),
9        Err(e) => {
10            if e.kind() == std::io::ErrorKind::AlreadyExists {
11            } else {
12                panic!("create dir {} failed: {}", dir, e);
13            }
14        }
15    }
16}
17pub fn copy_str_to_i8_array_with_truncation<const N: usize>(
18    buffer: &mut [i8; N],
19    text: &str,
20) -> Result<(), &'static str> {
21    if N == 0 {
22        return Err("Buffer size is zero, cannot copy string");
23    }
24
25    // 获取字符串的字节切片
26    let text_bytes = text.as_bytes();
27
28    // 计算要拷贝的长度(保留 1 字节给 `\0`)
29    let copy_len = std::cmp::min(text_bytes.len(), N - 1);
30
31    // 使用 unsafe 进行拷贝
32    unsafe {
33        // 将 buffer 转换为 `*mut u8`,然后通过偏移访问每个元素
34        ptr::copy_nonoverlapping(
35            text_bytes.as_ptr() as *const i8, // 源指针
36            buffer.as_mut_ptr(),              // 目标指针
37            copy_len,                         // 拷贝长度
38        );
39    }
40
41    // 添加终止符
42    buffer[copy_len] = 0;
43
44    Ok(())
45}
46
47pub fn copy_str_to_i8_array(dst: &mut [i8], src: &str) {
48    if dst.is_empty() {
49        return;
50    }
51    let bytes = src.as_bytes();
52    let len = usize::min(bytes.len(), dst.len() - 1);
53
54    unsafe {
55        ptr::copy_nonoverlapping(bytes.as_ptr(), dst.as_mut_ptr() as *mut u8, len);
56    }
57    dst[len] = 0;
58}
59
60#[macro_export]
61macro_rules! print_rsp_info {
62    ($p:expr) => {
63        if let Some(p) = $p {
64            println!(
65                "ErrorID[{}] Message[{}]",
66                p.ErrorID,
67                gb18030_cstr_i8_to_str(&p.ErrorMsg).unwrap().to_string()
68            );
69        }
70    };
71}
72
73/// 将 `&[std::os::raw::c_char]` 转换为 `Cow<str>`
74/// - 如果输入为 ASCII,返回 `Cow::Borrowed`。
75/// - 如果输入为 GB18030,解码后返回 `Cow::Owned`。
76/// - 如果解码失败,返回 `Err`。
77pub fn gb18030_cstr_i8_to_str<'a>(
78    c_chars: &'a [std::os::raw::c_char],
79) -> Result<Cow<'a, str>, String> {
80    let len = c_chars
81        .iter()
82        .position(|&c| c == 0)
83        .unwrap_or(c_chars.len());
84    let bytes = &c_chars[..len];
85
86    let bytes = unsafe { &*(bytes as *const [std::os::raw::c_char] as *const [u8]) };
87
88    if bytes.is_ascii() {
89        return std::str::from_utf8(bytes)
90            .map(Cow::Borrowed)
91            .map_err(|e| format!("Invalid UTF-8: {}", e));
92    }
93
94    // 非 ASCII 的情况:使用 GB18030 解码
95    let (decoded, _, had_errors) = GB18030.decode(bytes);
96    if had_errors {
97        return Err("Failed to decode GB18030 string".to_string());
98    }
99    Ok(Cow::Owned(decoded.into_owned()))
100}
101
102pub trait AssignFromString {
103    /// 将 `&str` 的内容写入到 `[i8; N]` 数组中
104    /// 超出数组大小的部分将被截断,未被覆盖的部分保留原值或清零。
105    fn assign_from_str(&mut self, s: &str);
106}
107
108impl<const N: usize> AssignFromString for [i8; N] {
109    fn assign_from_str(&mut self, s: &str) {
110        copy_str_to_i8_array(self, s);
111    }
112}
113
114impl<const N: usize> AssignFromString for &mut [i8; N] {
115    fn assign_from_str(&mut self, s: &str) {
116        copy_str_to_i8_array(*self, s);
117    }
118}
119
120pub trait SetString {
121    fn set_str(&mut self, s: &str);
122}
123
124impl<const N: usize> SetString for [i8; N] {
125    fn set_str(&mut self, s: &str) {
126        copy_str_to_i8_array(self, s);
127    }
128}
129
130impl<const N: usize> SetString for &mut [i8; N] {
131    fn set_str(&mut self, s: &str) {
132        copy_str_to_i8_array(*self, s);
133    }
134}
135
136pub trait WrapToString {
137    fn to_string(&self) -> String;
138    fn try_to_string(&self) -> Result<String, String>;
139}
140
141impl<const N: usize> WrapToString for [i8; N] {
142    fn to_string(&self) -> String {
143        let str_ = gb18030_cstr_i8_to_str(self);
144        str_.unwrap().to_string()
145    }
146
147    fn try_to_string(&self) -> Result<String, String> {
148        gb18030_cstr_i8_to_str(self).map(|cow| cow.to_string())
149    }
150}
151
152impl<const N: usize> WrapToString for &[i8; N] {
153    fn to_string(&self) -> String {
154        let str_ = gb18030_cstr_i8_to_str(*self);
155        str_.unwrap().to_string()
156    }
157
158    fn try_to_string(&self) -> Result<String, String> {
159        gb18030_cstr_i8_to_str(*self).map(|cow| cow.to_string())
160    }
161}
162
163/// 将 CTP 的 `[i8; N]` 字段(GB18030 编码)解码为 Rust UTF-8 字符串。
164///
165/// 与 `WrapToString` 功能相同,但方法名不与 `std::string::ToString` 冲突。
166pub trait DecodeString {
167    /// 解码为 UTF-8 字符串,解码失败时 panic
168    fn decode(&self) -> String;
169    /// 尝试解码为 UTF-8 字符串,解码失败时返回 Err
170    fn try_decode(&self) -> Result<String, String>;
171}
172
173impl<const N: usize> DecodeString for [i8; N] {
174    fn decode(&self) -> String {
175        gb18030_cstr_i8_to_str(self).unwrap().into_owned()
176    }
177
178    fn try_decode(&self) -> Result<String, String> {
179        gb18030_cstr_i8_to_str(self).map(|cow| cow.into_owned())
180    }
181}
182
183impl<const N: usize> DecodeString for &[i8; N] {
184    fn decode(&self) -> String {
185        gb18030_cstr_i8_to_str(*self).unwrap().into_owned()
186    }
187
188    fn try_decode(&self) -> Result<String, String> {
189        gb18030_cstr_i8_to_str(*self).map(|cow| cow.into_owned())
190    }
191}
192
193#[derive(Debug, Clone)]
194pub struct WrapString(pub String); // 包装 String
195
196impl<const N: usize> From<[i8; N]> for WrapString {
197    fn from(value: [i8; N]) -> Self {
198        let str_ = gb18030_cstr_i8_to_str(&value).expect("failed to decode");
199        WrapString(str_.into())
200    }
201}
202
203impl<const N: usize> From<&[i8; N]> for WrapString {
204    fn from(value: &[i8; N]) -> Self {
205        let str_ = gb18030_cstr_i8_to_str(value).expect("failed to decode");
206        WrapString(str_.into())
207    }
208}
209
210pub trait WrapFrom<T> {
211    fn wrap_from(value: T) -> Self;
212}
213
214// 自定义 MyInto trait
215pub trait WrapInto<T>: Sized {
216    fn wrap_into(self) -> T;
217}
218
219// 为 MyFrom 创建对应的 MyInto 实现
220impl<T, U> WrapInto<U> for T
221where
222    U: WrapFrom<T>,
223{
224    fn wrap_into(self) -> U {
225        U::wrap_from(self)
226    }
227}
228
229impl WrapFrom<&[i8]> for String {
230    fn wrap_from(bytes: &[i8]) -> Self {
231        let u8_bytes: Vec<u8> = bytes.iter().map(|&b| b as u8).collect();
232        String::from_utf8(u8_bytes).expect("Invalid UTF-8 sequence")
233    }
234}
235
236impl<const N: usize> WrapFrom<[i8; N]> for String {
237    fn wrap_from(value: [i8; N]) -> Self {
238        let str_ = gb18030_cstr_i8_to_str(&value).expect("failed to decode");
239        str_.into()
240    }
241}
242
243impl<const N: usize> WrapFrom<&[i8; N]> for String {
244    fn wrap_from(value: &[i8; N]) -> Self {
245        let str_ = gb18030_cstr_i8_to_str(value).expect("failed to decode");
246        str_.into()
247    }
248}
249
250/// CTP 动态库类型
251#[derive(Debug, Clone, Copy)]
252pub enum DynLibKind {
253    /// 行情 API (thostmduserapi_se)
254    MdApi,
255    /// 交易 API (thosttraderapi_se)
256    TraderApi,
257}
258
259/// 根据平台和库类型,在指定目录下解析 CTP 动态库的完整路径。
260///
261/// # 平台规则
262/// - **Linux**: `<dir>/thostmduserapi_se.so` 或 `thosttraderapi_se.so`
263/// - **macOS**: 优先查找 `.framework/<name>` 结构,其次 `.dylib`
264/// - **Windows**: `<dir>/thostmduserapi_se.dll` 或 `thosttraderapi_se.dll`
265///
266/// # 示例
267/// ```no_run
268/// use ctp2rs::ffi::{resolve_dynlib_path, DynLibKind};
269/// let path = resolve_dynlib_path("./api/ctp/v6.7.2/v6.7.2_linux64", DynLibKind::MdApi);
270/// ```
271pub fn resolve_dynlib_path<P: AsRef<std::path::Path>>(
272    dir: P,
273    kind: DynLibKind,
274) -> std::path::PathBuf {
275    let dir = dir.as_ref();
276    let lib_name = match kind {
277        DynLibKind::MdApi => "thostmduserapi_se",
278        DynLibKind::TraderApi => "thosttraderapi_se",
279    };
280
281    #[cfg(target_os = "linux")]
282    {
283        dir.join(format!("{}.so", lib_name))
284    }
285
286    #[cfg(target_os = "windows")]
287    {
288        dir.join(format!("{}.dll", lib_name))
289    }
290
291    #[cfg(target_os = "macos")]
292    {
293        // macOS: 优先检查 .framework 结构
294        let framework_path = dir.join(format!(
295            "{}.framework/{}",
296            lib_name, lib_name
297        ));
298        if framework_path.exists() {
299            return framework_path;
300        }
301        // 其次 .dylib
302        dir.join(format!("{}.dylib", lib_name))
303    }
304}