2015-05-28 49 views
15

我對Rust很新。我如何從可以在Python中使用的Rust函數返回String從Rust函數返回一個字符串到Python

這裏是我的防鏽實現:

use std::ffi::CString; 

#[no_mangle] 
pub extern fn query() -> CString { 
    let s = CString::new("Hello!").unwrap(); 
    return s; 
} 

並調用它的Python代碼:

from ctypes import cdll, c_char_p 

lib = cdll.LoadLibrary("target/release/libtest.so") 
result = lib.query() 

print(c_char_p(result).value) 

我得到一個分段錯誤時,它的運行。

編輯:使用弗拉基米爾Matveev的鏽代碼下面我能夠得到它與修改了自己的Python代碼工作:

from ctypes import * 

lib = cdll.LoadLibrary("target/release/libtest.so") 
lib.query.restype = c_char_p 
result = lib.query() 
print cast(result, c_char_p).value 
lib.free_query(result) 
+0

請檢閱http:// stackoverflow。com/questions/30440068/segmentation-fault-when-calling-a-rust-lib-with-ruby-ffi and http://stackoverflow.com/questions/30312885/pass-python-list-to-embedded-rust-功能/ 30313295#30313295並讓我們知道您的問題如何不同。 – Shepmaster

+0

我已經回顧了這兩個問題,他們確實有所不同。在第一個中,調用來自Ruby,我的問題來自Python。 在第二個問題中,返回值是一個整數,這是一個簡單的例子。這裏,返回值具體是一個字符串值。 – LeeMobile

+1

生鏽的一面絕對沒有什麼不同,它應該根據你所說的語言而改變。就Rust代碼而言,C正在調用它。其他任何語言都在調用看起來像C代碼的東西。 – Shepmaster

回答

11

最直接的版本是這樣的:

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

#[no_mangle] 
pub extern fn query() -> *mut c_char { 
    let s = CString::new("Hello!").unwrap(); 
    s.into_raw() 
} 

在這裏,我們返回一個指向零結尾序列號爲char,可以傳遞給Python的c_char_p。你不能僅僅返回CString,因爲它是Rust結構,它不應該直接在C代碼中使用 - 它包裝Vec<u8>,實際上包含三個指針大小的整數。它直接與C的char*不兼容。我們需要從中獲取一個原始指針。 CString::into_raw()方法執行此操作 - 它通過值消耗CString,「忘記」它使其分配不會被銷燬,並返回一個指向數組開頭的指針。

但是,這樣字符串就會被泄漏,因爲我們忘記了它在Rust方面的分配,並且它永遠不會被釋放。我不太瞭解Python的FFI,但解決這個問題最直接的方法是創建兩個函數,一個用於生成數據,另一個用於釋放它。然後需要通過調用此釋放函數來釋放在Python側的數據:

// above function 
#[no_mangle] 
pub extern fn query() -> *mut c_char { ... } 

#[no_mangle] 
pub extern fn free_query(c: *mut c_char) { 
    // convert the pointer back to `CString` 
    // it will be automatically dropped immediately 
    unsafe { CString::from_raw(c); } 
} 

CString::from_raw()方法接受*mut c_char指針和出它創建一個CString實例中,計算基本零終止的字符串的在長度處理。此操作意味着所有權轉移,因此得到的CString值將擁有分配,並且在分配時,分配將被釋放。這正是我們想要的。

+0

我需要從[here](https://github.com/rust-lang/libc#usage)開始執行前兩個步驟,以使其工作:(1)在'Cargo.toml'中爲rustc添加依賴關係, 2)將其導入使用的防鏽文件中。 (不知道這是否永遠是必要的,但我是全新的生鏽。) – ArneHugo

1

這裏的問題是,你是直接返回一個CString,這不對應於C中的字符串表示(您可以看到here的源代碼CString)。

您應該返回一個指向字符串的指針,使用s.as_ptr()。但是,您需要確保該字符串不會在函數結尾釋放,因爲這會導致懸掛指針。

我能想到的唯一解決方案是使用forget讓鏽蝕忘記變量而不是釋放它。當然,你需要找到一種方法來釋放字符串以避免內存泄漏(參見弗拉基米爾的回答)。

隨着我提到的變化,你的防鏽代碼應該是以下幾點:

use std::ffi::CString; 
use std::mem; 

#[no_mangle] 
pub extern fn query() -> *const i8 { 
    let s = CString::new("Hello!").unwrap(); 
    let ptr = s.as_ptr(); 
    mem::forget(s); 
    return ptr; 
}