Rust:开发 DLL 动态链接库时如何处理 C 字符串

在 Rust 开发 DLL 时,正确处理 *mut c_char*const c_charString 的转换非常重要。以下是几种常见场景的安全转换方法:


1️⃣ C 字符串 → Rust String

将传入的 *const c_char (C 风格以 null 结尾的字符串) 转换为 Rust 的 String

rust 复制代码
use std::ffi::CStr;
use std::os::raw::c_char;

#[no_mangle]
pub extern "C" fn process_c_string(c_string_ptr: *const c_char) {
    // 安全转换
    let rust_str = unsafe {
        // 注意:要求传入有效的 C 字符串指针 (null-terminated)
        CStr::from_ptr(c_string_ptr)
            .to_str()         // 转换为 &str
            .expect("Invalid UTF-8") // 处理错误
            .to_string()      // &str → String
    };

    println!("Rust received: {}", rust_str);
}

安全要点

  • 使用 CStr::from_ptr 包装 C 字符串
  • to_str() 处理 UTF-8 转换(可能会失败)
  • 必须进行错误处理(这里用 expect(),生产环境建议更健壮的方案)

2️⃣ Rust String → C 字符串

将 Rust String 转换为可返回给 C 的 *mut c_char

rust 复制代码
use std::ffi::CString;

#[no_mangle]
pub extern "C" fn generate_c_string() -> *mut c_char {
    let rust_string = "从 Rust 生成的字符串".to_string();
    
    // 创建 CString (可能失败)
    let c_string = CString::new(rust_string)
        .expect("Failed to create CString");
    
    // 泄漏所有权,转为原始指针
    c_string.into_raw()
}

// ⚠️ 必须提供对应的内存释放函数!
#[no_mangle]
pub extern "C" fn free_c_string(ptr: *mut c_char) {
    unsafe {
        if !ptr.is_null() {
            // 回收所有权并自动释放内存
            let _ = CString::from_raw(ptr);
        }
    }
}

关键点

  • into_raw() 让 Rust 放弃所有权,C 代码需负责管理内存
  • 必须提供对应的 free_xxx 函数
  • 注意字符串中的 null 字符会导致 CString::new() 失败

3️⃣ 完整 DLL 示例

结合转换的综合使用:

rust 复制代码
// src/lib.rs
use std::ffi::{CStr, CString};
use std::os::raw::c_char;

#[no_mangle]
pub extern "C" fn modify_string(input: *const c_char) -> *mut c_char {
    // Step 1: C字符串 → Rust String
    let rust_str = unsafe {
        CStr::from_ptr(input)
            .to_str()
            .unwrap_or_default()  // 更安全的错误处理
            .to_uppercase()       // 处理字符串
    };

    // Step 2: Rust String → C字符串
    CString::new(rust_str)
        .map(|s| s.into_raw())
        .unwrap_or(std::ptr::null_mut())
}

#[no_mangle]
pub extern "C" fn free_modified_string(ptr: *mut c_char) {
    if !ptr.is_null() {
        unsafe { 
            let _ = CString::from_raw(ptr); 
        }
    }
}

4️⃣ C/C++ 调用示例

如何在 C 中正确使用 DLL:

c 复制代码
#include <stdio.h>
#include <windows.h>

typedef char* (*fn_modify_string)(const char*);
typedef void (*fn_free_string)(char*);

int main() {
    HINSTANCE dll = LoadLibrary("rust_dll.dll");
    if (!dll) return 1;
    
    fn_modify_string modify = (fn_modify_string)GetProcAddress(dll, "modify_string");
    fn_free_string free_str = (fn_free_string)GetProcAddress(dll, "free_modified_string");
    
    // 调用函数
    char* result = modify("hello from c");
    printf("Modified: %s\n", result);
    
    // 必须释放内存!
    free_str(result);
    
    FreeLibrary(dll);
    return 0;
}

🧠 高级处理技巧

1. 处理无效指针和错误
rust 复制代码
pub extern "C" fn safe_convert(ptr: *const c_char) -> Option<String> {
    if ptr.is_null() {
        return None;
    }
    
    unsafe {
        CStr::from_ptr(ptr)
            .to_str()
            .ok()
            .map(|s| s.to_string())
    }
}
2. 多字节编码转换 (Windows)
rust 复制代码
use encoding_rs::WINDOWS_1252;

pub fn to_wide_string(s: &str) -> Vec<u16> {
    let (cow, _) = WINDOWS_1252.encode(s);
    cow.iter()
        .map(|b| *b as u16) // 单字节转宽字符
        .chain(std::iter::once(0)) // 添加 null 终止符
        .collect()
}
3. 返回复杂数据结构
rust 复制代码
#[repr(C)]
pub struct StringResult {
    data: *mut c_char,
    success: bool
}

#[no_mangle]
pub extern "C" fn complex_operation() -> StringResult {
    // ... 操作 ...
    match result {
        Ok(s) => StringResult {
            data: CString::new(s).unwrap().into_raw(),
            success: true
        },
        Err(_) => StringResult {
            data: std::ptr::null_mut(),
            success: false
        }
    }
}

⚠️ 关键安全警告

  1. 线程安全

    rust 复制代码
    use std::sync::Mutex;
    
    static BUFFER: Mutex<String> = Mutex::new(String::new());
  2. 空指针检查

    • 所有指针参数必须 is_null() 检查
    • 返回指针要考虑无效情况
  3. 禁止的字符串转换

    rust 复制代码
    // 绝对禁止这种转换!
    unsafe { std::str::from_utf8_unchecked(slice) }
  4. 内存生命周期

    rust 复制代码
    // 危险:局部变量内存被释放!
    pub extern "C" fn invalid_example() -> *const c_char {
        let s = String::from("temporary");
        CString::new(s).unwrap().as_ptr() // 返回即将销毁的指针
    }

💡 最佳实践总结

  1. 使用 CStr / CString 进行安全的转换
  2. 明确所有权
    • into_raw() 转让所有权到 C
    • from_raw() 取回所有权释放内存
  3. 配套释放函数
    • 每个 into_raw() 必须有对应的 free 函数
  4. 错误处理
    • 返回 Option 或带错误标识的结构体
  5. 测试边界情况
    • 空指针
    • 无效 UTF-8
    • 含 null 的字符串

完成开发后,建议使用 cbindgen 自动生成头文件:

bash 复制代码
cargo install cbindgen
cbindgen --lang c --output mydll.h
相关推荐
YLCHUP几秒前
题解:P4447 [AHOI2018初中组] 分组
开发语言·数据结构·c++·经验分享·算法·贪心算法·抽象代数
R-G-B8 分钟前
【05】大恒相机SDK C#开发 —— Winform中采集图像并显示
开发语言·c#·大恒相机sdk·winform中采集图像·winform中采集图像并显示
gnawkhhkwang27 分钟前
io_setup系统调用及示例
linux·c语言
John.Lewis33 分钟前
C语言数据结构(7)贪吃蛇项目2.贪吃蛇项目实现
c语言·数据结构·算法
三小尛1 小时前
C++友元
开发语言·c++·算法
Blossom.1181 小时前
基于深度学习的医学图像分析:使用PixelRNN实现医学图像超分辨率
c语言·人工智能·python·深度学习·yolo·目标检测·机器学习
小小洋洋1 小时前
笔记:C语言中指向指针的指针作用
c语言·开发语言·笔记
许野平2 小时前
Rust 同步方式访问 REST API 的完整指南
java·网络·rust·restful
wjs20242 小时前
正则表达式 - 示例
开发语言
懷淰メ2 小时前
日常--详细介绍qt Designer常用快捷键(详细图文)
开发语言·qt·pyqt·快捷键·qtdesigner·ui设计·qt设计师