web-dev-qa-db-ja.com

C文字列をRust文字列に変換し、FFI経由で戻すにはどうすればよいですか?

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?
}
42
Dirk

RustでC文字列を操作する最良の方法は、 _std::ffi_ モジュールの構造を使用することです。すなわち、 CStr および CString

CStrは動的なサイズの型であるため、ポインターを介してのみ使用できます。これにより、通常のstr型と非常によく似ています。安全でない _&CStr_ 静的メソッドを使用して、_*const c_char_から_CStr::from_ptr_を構築できます。このメソッドは、渡される生のポインターが有効であるという保証がなく、有効なC文字列を実際に指し示し、文字列の有効期間が正しいという保証がないため、安全ではありません。

_&str_は、その to_str() メソッドを使用して_&CStr_から取得できます。

以下に例を示します。

_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をドロップすると、提供するメモリが解放されます(基本的に、CStringは_Vec<u8>_をラップし、後者がドロップされます)。したがって、Rustに割り当てられたデータをC文字列として公開する場合に役立ちます。

残念ながら、Cの文字列は常にゼロバイトで終わり、その中に1つを含めることはできませんが、Rust _&[u8]_/_Vec<u8>_は正反対です-それらは含まれませんこれは、_Vec<u8>_からCStringへの移行はエラーフリーでも割り当てフリーでもないことを意味します-CStringコンストラクターは、指定されたデータ内のゼロをチェックし、エラーが見つかった場合、エラーが発生し、バイトベクトルの最後にゼロバイトを追加します。

_Deref<Target = str>_を実装するStringと同様に、CStringは_Deref<Target = CStr>_を実装するため、CStrで定義されたメソッドをCStringで直接呼び出すことができます。 Cの相互運用に必要な_*const c_char_を返す as_ptr() メソッドが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で解放する必要があります。 Rustアロケーターは、mallocおよびfreeが使用するアロケーターと同じである可能性は低いです。必要なのは、 _CString::from_raw_ その後、文字列を通常どおりドロップします。

76