1. ホーム
  2. c

[解決済み] FFIを使用してCの文字列をRustの文字列に変換する方法は?

2023-04-22 23:24:26

質問

C言語ライブラリから返されたC言語の文字列を、FFIを介してRustの文字列に変換しようとしています。

mylib.c

const char* hello(){
    return "Hello World!";
}

main.rs

#![feature(link_args)]

extern crate libc;
use libc::c_char;

#[link_args = "-L . -I . -lmylib"]
extern {
    fn hello() -> *c_char;
}

fn main() {
    //how do I get a str representation of hello() here?
}

どのように解決するのですか?

RustでC言語の文字列を扱う最良の方法は、C言語の文字列を扱うための構造体である std::ffi モジュール、すなわち CStr CString .

CStr は動的な大きさの型なので、ポインタを通してのみ使用することができます。このため、通常の str 型と非常によく似ています。を構築することができます。 &CStr から *const c_char を使用して、安全でない CStr::from_ptr 静的メソッドを使用しています。このメソッドは安全ではありません。なぜなら、このメソッドに渡される生のポインタが有効であること、それが本当に有効なC文字列を指していること、そして文字列の寿命が正しいことは保証されないからです。

を取得することができます。 &str から &CStr を使って、その to_str() メソッドを使用します。

以下はその例です。

extern crate libc;

use libc::c_char;
use std::ffi::CStr;
use std::str;

extern {
    fn hello() -> *const c_char;
}

fn main() {
    let c_buf: *const c_char = unsafe { hello() };
    let c_str: &CStr = unsafe { CStr::from_ptr(c_buf) };
    let str_slice: &str = c_str.to_str().unwrap();
    let str_buf: String = str_slice.to_owned();  // if necessary
}

の寿命を考慮する必要があります。 *const c_char のポインタの寿命とその所有者を考慮する必要があります。C API によっては、文字列に対して特別なデアロケーション関数を呼び出す必要があるかもしれません。スライスがポインターより長持ちしないように、変換を注意深く手配する必要があります。という事実は CStr::from_ptr を返すということは &CStr を任意の寿命で返すことは、(それ自体は危険ですが)ここで役に立ちます。例えば、Cの文字列を構造体にカプセル化して Deref 変換を提供することで、構造体を文字列スライスのように使用することができます。

extern crate libc;

use libc::c_char;
use std::ops::Deref;
use std::ffi::CStr;

extern "C" {
    fn hello() -> *const c_char;
    fn goodbye(s: *const c_char);
}

struct Greeting {
    message: *const c_char,
}

impl Drop for Greeting {
    fn drop(&mut self) {
        unsafe {
            goodbye(self.message);
        }
    }
}

impl Greeting {
    fn new() -> Greeting {
        Greeting { message: unsafe { hello() } }
    }
}

impl Deref for Greeting {
    type Target = str;

    fn deref<'a>(&'a self) -> &'a str {
        let c_str = unsafe { CStr::from_ptr(self.message) };
        c_str.to_str().unwrap()
    }
}

また、このモジュールにはもう一つ CString . と同じ関係にあり CStr と同じ関係にあります。 Stringstr - CString の所有バージョンです。 CStr . これは、バイトデータの割り当てのハンドルを保持していることを意味します。 CString を落とすと、それが提供するメモリが解放されることになります(基本的に。 CStringVec<u8> をラップし、ドロップされるのは後者です)。その結果、Rustで確保したデータをCの文字列として公開したい場合に有効です。

残念ながら、C言語の文字列は常に0バイトで終わり、その中に1バイトを含むことはできませんが、Rustの &[u8] / Vec<u8> は全く逆で、0バイトで終わらず、内部に任意の数の0バイトを含むことができます。これはつまり Vec<u8> から CString はエラーフリーでもアロケーションフリーでもありません。 CString コンストラクタは、提供されたデータ内にゼロがあるかどうかをチェックし、見つかった場合はエラーを返し、バイトベクタの末尾にゼロバイトを追加します。

例えば String を実装している Deref<Target = str> , CString 実装 Deref<Target = CStr> で定義されたメソッドを呼び出すことができます。 CStr で定義されたメソッドを直接 CString . これは重要です。 as_ptr() を返すメソッドは *const c_char は、C言語の相互運用に必要な CStr . このメソッドを直接 CString の値に対して直接呼び出すことができ、便利です。

CString に変換できるもの全てから作成できます。 Vec<u8> . String , &str , Vec<u8>&[u8] はコンストラクタ関数の有効な引数です。 CString::new() . 当然ながら、バイトスライスや文字列スライスを渡すと、新しいアロケーションが作成されます。 Vec<u8> または String が消費されます。

extern crate libc;

use libc::c_char;
use std::ffi::CString;

fn main() {
    let c_str_1 = CString::new("hello").unwrap(); // from a &str, creates a new allocation
    let c_str_2 = CString::new(b"world" as &[u8]).unwrap(); // from a &[u8], creates a new allocation
    let data: Vec<u8> = b"12345678".to_vec(); // from a Vec<u8>, consumes it
    let c_str_3 = CString::new(data).unwrap();

    // and now you can obtain a pointer to a valid zero-terminated string
    // make sure you don't use it after c_str_2 is dropped
    let c_ptr: *const c_char = c_str_2.as_ptr();

    // the following will print an error message because the source data
    // contains zero bytes
    let data: Vec<u8> = vec![1, 2, 3, 0, 4, 5, 0, 6];
    match CString::new(data) {
        Ok(c_str_4) => println!("Got a C string: {:p}", c_str_4.as_ptr()),
        Err(e) => println!("Error getting a C string: {}", e),
    }  
}

の所有権を移す必要がある場合は CString の所有権を C のコードに移す必要がある場合は CString::into_raw . で使われるアロケータと Rust のアロケータが同じであることはまずありません。 mallocfree . 必要なのは CString::from_raw と入力し、文字列が普通に落とせるようにすればよいのです。