溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Rust中FFI編程知識(shí)點(diǎn)有哪些

發(fā)布時(shí)間:2022-09-15 10:22:58 來源:億速云 閱讀:198 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹了Rust中FFI編程知識(shí)點(diǎn)有哪些的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡(jiǎn)單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇Rust中FFI編程知識(shí)點(diǎn)有哪些文章都會(huì)有所收獲,下面我們一起來看看吧。

    Rust語(yǔ)言對(duì)FFI的支持

    Rust 語(yǔ)言主要在關(guān)鍵字和標(biāo)準(zhǔn)庫(kù)兩個(gè)方面對(duì) FFI 提供了支持,具體如下:
    關(guān)鍵字 extern
    屬性 #[no_mangle]
    外部塊 ExternBlock 及其屬性 link 和 link_name
    標(biāo)準(zhǔn)庫(kù)
    std:os:raw 模塊:例如c_char。
    std:ffi 模塊:傳遞 UTF-8 字符串時(shí),CString和CStr很有用。

    libc-crate庫(kù)

    你可以使用 libc::foo 這種形式訪問這個(gè)庫(kù)中的任何導(dǎo)出內(nèi)容。
    在Rust里,只能創(chuàng)建子線程,如果想創(chuàng)建子進(jìn)程,就需要用到libc庫(kù)

    fn main() {    
    unsafe {        
    let pid = libc::fork();                                                                                                               if pid > 0 {println!("Hello, I am parent thread: {}", libc::getpid());}   
    else if pid == 0 {println!("Hello, I am child thread: {}", libc::getpid());println!("My parent thread: {}", libc::getppid());        }        
    else {println!("Fork creation failed!");}}}

    1.libc 的所有函數(shù)調(diào)用,都必須放進(jìn) unsafe 塊中。因?yàn)樗乃姓{(diào)用都是 unsafe 的;
    2.std 的線程操作封裝,好用,形象。libc 的進(jìn)程操作,與 C 語(yǔ)言系統(tǒng)編程一樣,完全是另外一套思路和編程風(fēng)格;
    3.std 的線程操作雖然簡(jiǎn)潔,但是也缺少更細(xì)顆粒度的控制。而 libc 可以對(duì)進(jìn)程的操作(及后面對(duì)子進(jìn)程的功能擴(kuò)充,父進(jìn)程中的信號(hào)管理等),做到完全的控制,更加靈活,功能強(qiáng)大;
    4.std 本身無法實(shí)現(xiàn)進(jìn)程 fork 的功能。
    因?yàn)槲?Rust 的封裝是 zero cost (零成本)的。零成本抽象賦予了 Rust 系統(tǒng)編程的能力。

    libc 與 std::os:????::raw,這里面有的用法是一樣的,沒有任何問題。簡(jiǎn)單的和C交互可以用os:raw里面的,而一旦產(chǎn)生了系統(tǒng)調(diào)用或者 Unix 環(huán)境編程,那么就得引入 libc 庫(kù)來操作。

    cbindgen 工具的介紹和使用

    這個(gè)工具就是將寫好的Rust代碼配置一下,然后會(huì)自動(dòng)生成接口代碼頭文件等等。其實(shí),F(xiàn)FI封裝、轉(zhuǎn)換,熟悉了之后,知識(shí)點(diǎn)就那些,模式也比較固定,如果接口量很大,那就需要大量重復(fù)的 coding。量一大,人手動(dòng)綁定出錯(cuò)的機(jī)率也大。所以這種輔助工具的意義就顯露出來了?;谳o助工具生成的代碼,如不完美,再適當(dāng)手動(dòng)修一修,幾下就能搞定,大大提高生產(chǎn)效率。

    Rust指針

    在Rust中,存在三種類型的指針:

    1.Rust自帶的指針類型:

    引用—安全的指針

    &T:它是對(duì)類型T的不可變引用
    &mut T:它是對(duì)類型T的可變引用

    2. 原始指針

    眾所周知,Rust語(yǔ)言的指針是一種安全的指針,它會(huì)遵循一定的規(guī)則,比如ownership規(guī)則,會(huì)確保不出現(xiàn)懸掛指針。但是當(dāng)我們需要寫一些底層框架的時(shí)候,往往需要繞過這些規(guī)則,自由的控制指針,這時(shí)候我們就可以使用原始指針。
    *const T:表示指向類型T的不可變?cè)贾羔?。它是Copy類型。這類似于&T,只是它可以為空值。
    *mut T:一個(gè)指向T的可變?cè)贾羔槪恢С諧opy特征(non-Copy)。
    以下可以定義Rust的原始指針:

    fn main() {
        let mut num = 5;
        let r1 = &num as *const i32;
        let r2 = &mut num as *mut i32;
    }

    3.智能指針

    管理原始指針非常不安全,開發(fā)者在使用它們時(shí)需要注意很多細(xì)節(jié)。不恰當(dāng)?shù)厥褂盟鼈兛赡軙?huì)以非常隱蔽的方式導(dǎo)致諸如內(nèi)存泄漏、引用掛起,以及大型代碼庫(kù)中的雙重釋放等問題。為了解決這些問題,我們可以使用C++中廣泛采用的智能指針。
    智能指針的兩個(gè)特性:Drop和Deref
    Drop:這是多次提及的特征,它可以自動(dòng)釋放相關(guān)值超出作用域后占用的資源。Drop特征類似于你在其他語(yǔ)言中遇到的被稱為對(duì)象析構(gòu)函數(shù)的東西。它包含一個(gè)drop方法,當(dāng)對(duì)象超出作用域時(shí)就會(huì)被調(diào)用。該方法將&mut self作為參數(shù)。使用drop釋放值是以LIFO的方式進(jìn)行的。也就是說,無論最后構(gòu)建的是什么,都首先會(huì)被銷毀。drop方法是你為自己的結(jié)構(gòu)體放置清理代碼的理想場(chǎng)所。例如使用引用計(jì)數(shù)值或GC時(shí),它尤其方便。當(dāng)我們實(shí)例化任何Drop實(shí)現(xiàn)值時(shí)(任意堆分配類型),Rust編譯器會(huì)在編譯后的代碼中每個(gè)作用域結(jié)束的位置插入drop方法調(diào)用。因此,我們不需要在這些實(shí)例上手動(dòng)調(diào)用drop方法。
    Deref:為了提供與普通指針類似的行為,也就是說,為了能夠解引用被指向類型的調(diào)用方法,智能指針類型通常會(huì)實(shí)現(xiàn)Deref特征,這允許用戶對(duì)這些類型使用解引用運(yùn)算符*。雖然Deref只為你提供了只讀權(quán)限,但是還有DerefMut,它可以為你提供對(duì)底層類型的可變引用。

    智能指針的種類:
    標(biāo)準(zhǔn)庫(kù)中的智能指針有如下幾種。
    Box:它提供了最簡(jiǎn)單的堆資源分配方式。Box類型擁有其中的值,并且可用于保存結(jié)構(gòu)體中的值,或者從函數(shù)返回它們。
    Rc:它用于引用計(jì)數(shù)。每當(dāng)獲取新引用時(shí),計(jì)數(shù)器會(huì)執(zhí)行遞增操作,并在用戶釋放引用時(shí)對(duì)計(jì)數(shù)器執(zhí)行遞減操作。當(dāng)計(jì)數(shù)器的值為零時(shí),該值將被移除。
    Arc:它用于原子引用計(jì)數(shù)。這與之前的類型類似,但具有原子性以保證多線程的安全性。
    Cell:它為我們提供實(shí)現(xiàn)了Copy特征的類型的內(nèi)部可變性。換句話說,我們有可能獲得多個(gè)可變引用。
    RefCell:它為我們提供了類型的內(nèi)部可變性,并且不需要實(shí)現(xiàn)Copy特征。它用于運(yùn)行時(shí)的鎖定以確保安全性。

    引用計(jì)數(shù)指針:
    所有權(quán)規(guī)則只允許某個(gè)給定作用域中存在一個(gè)所有者。但是,在某些情況下你需要與多個(gè)變量共享類型。例如在GUI庫(kù)中,每個(gè)子窗體小部件都需要具有對(duì)其父容器窗口小部件的引用,以便基于用戶的resize事件來調(diào)整子窗口的布局。雖然有時(shí)生命周期允許你將父節(jié)點(diǎn)存儲(chǔ)為&'a Parent,但是它通常受到’a值生命周期的限制,一旦作用域結(jié)束,你的引用將失效。在這種情況下,我們需要更靈活的方法,并且需要使用引用計(jì)數(shù)類型。程序中的這些智能指針類型會(huì)提供值的共享所有權(quán)。

    引用計(jì)數(shù)類型支持某個(gè)粒度級(jí)別的垃圾回收。在這種方法中,智能指針類型允許用戶對(duì)包裝值進(jìn)行多次引用。在內(nèi)部,智能指針使用引用計(jì)數(shù)器(這里是refcount)來統(tǒng)計(jì)已發(fā)放的并且活動(dòng)的引用數(shù)量,不過它只是一個(gè)整數(shù)值。當(dāng)引用包裝的智能指針值的變量超出作用域時(shí),refcount的值就會(huì)遞減。一旦該對(duì)象的所有引用都消失,refcount的值也會(huì)變成0,之后該值會(huì)被銷毀。這就是引用計(jì)數(shù)指針的常見工作模式。

    Rust為我們提供了兩種引用計(jì)數(shù)指針類型。
    Rc:這主要用于單線程環(huán)境。
    Arc:這主要用于多線程環(huán)境。

    Rust和C交互時(shí)的各種指針變換

    1.pub extern “C” fn sum_of_array(array: *const u32, len: usize) -> u32
    slice::from_raw_parts(array,len)
    C端傳來的數(shù)組(指針類型),進(jìn)到Rust這邊進(jìn)行強(qiáng)制類型轉(zhuǎn)換,變成非可變?cè)贾羔橆愋汀:瘮?shù)slice::from_raw_parts(array,len)就是對(duì)原始指針進(jìn)行轉(zhuǎn)換為Rust切片類型,切片就是一個(gè)指針+一個(gè)長(zhǎng)度即可。

    2.CStr::from_ptr(raw_string):CStr就是C端產(chǎn)生數(shù)據(jù),Rust端使用,
    只是借用,常用于打印。raw_string是直接從C接過來的可變?cè)贾羔槨?br/>使用std::ffi::CStr提供的from_ptr方法包裝 C 的字符串指針,它基于空字符’\0’來計(jì)算字符串的長(zhǎng)度,并可以通過它將外部 C 字符串轉(zhuǎn)換為 Rust 的 &str和String

    use std::ffi::CStr;
    use libc::c_char;
    extern {
    fn char_func() -> *mut c_char;
    }
    
    fn get_string() -> String {
    unsafe {
    let raw_string: *mut c_char = char_func();
    let cstr = CStr::from_ptr(raw_string);
    cstr.to_string_lossy().into_owned()
    }
    }

    3.CStr::from_ptr(s).to_string_lossy().into_owned():注意to_string_lossy()的使用:因?yàn)樵趓ust中一切字符都是采用utf8表示的而c不是,
    因此如果要將c的字符串轉(zhuǎn)換到rust字符串的話,需要檢查是否都為有效utf-8字節(jié)。

    4.CString::new(“Hello, world!”).as_ptr():Cstring是Rust端產(chǎn)生數(shù)據(jù),C端進(jìn)行使用。
    as_ptr()就是將RustCString指針類型轉(zhuǎn)化為C的原始指針類型。

    5.CString::new(“Hello world!”).into_raw()
    使用std::ffi::CString提供的一對(duì)方法into_raw和from_raw可以進(jìn)行原始指針轉(zhuǎn)換,由于將字符串的所有權(quán)轉(zhuǎn)移給了調(diào)用者,所以調(diào)用者必須將字符串返回給 Rust,以便正確地釋放內(nèi)存。
    into_raw()和.as_ptr()的作用類似,都是變成原始指針傳給C端。
    6.CString::from_raw(s)
    一般在釋放內(nèi)存的時(shí)候使用,C端用完需要Rust端來釋放。

    7.Box::into_raw(Box::new(new_stu)):其實(shí)這里是智能指針和兩端堆棧申請(qǐng)有關(guān),into_raw()就是將Rust智能指針變成原始指針。
    8.Box::from_raw(p_stu):from_raw():就是將C端傳來的p_stu變成Rust智能指針。

    數(shù)組類型傳遞

    C代碼:

      uint32_t sum = sum_of_array(numbers, length);

    Rust代碼:

    pub extern "C" fn sum_of_array(array: *const u32, len: usize) -> u32 {
        let array = unsafe {
            assert!(!array.is_null());
            slice::from_raw_parts(array, len)
        };
       array.iter().sum()
    }

    這里的參數(shù)傳遞一目了然,array一開始是C過來的指針類型,通過slice::from_raw_parts(array,len)之后,變成一個(gè)Rust切片類型,后面用iter進(jìn)行求和。切片類型就是一個(gè)指針和一組數(shù)據(jù)合在一起組成。

    字符串類型

    對(duì)于C語(yǔ)言來說,字符串有兩種,一種是共享的只讀字符串 char * ,不能修改。另一種是動(dòng)態(tài)分配的可變字符串 char [],可以修改。
    而在Rust里面,字符串是由字符的 UTF-8 編碼組成的字節(jié)序列。表示的類型有很多種。
    字符串則比較復(fù)雜,Rust 中的字符串,是一組u8組成的 UTF-8 編碼的字節(jié)序列,字符串內(nèi)部允許NULL字節(jié);但在 C 中,字符串只是指向一個(gè)char的指針,用一個(gè)NULL字節(jié)作為終止。
    我們需要做一些特殊的轉(zhuǎn)換,在 Rust FFI 中使用std::ffi::CStr,它表示一個(gè)NULL字節(jié)作為終止的字節(jié)數(shù)組,可以通過 UTF-8 驗(yàn)證轉(zhuǎn)換成 Rust 中的&str。
    CStr:表示以空字符終止的 C 字符串或字節(jié)數(shù)組的借用,屬于引用類型。一般用于和 C 語(yǔ)言交互,由 C 分配并被 Rust 借用的字符串。
    CString:表示擁有所有權(quán)的,中間沒有空字節(jié),以空字符終止的字符串類型。一般用于和 C 語(yǔ)言交互時(shí),由 Rust 分配并傳遞給 C 的字符串。
    下面這段代碼,在這里get_string使用CStr::from_ptr從C的char*獲取一個(gè)字符串,并且轉(zhuǎn)化成了一個(gè)String。

    fn get_string() -> String {
    unsafe {
    let raw_string: *mut c_char = char_func();
    let cstr = CStr::from_ptr(raw_string);
    cstr.to_string_lossy().into_owned()
    }
    }

    和CStr表示從C中來,rust不擁有歸屬權(quán)的字符串相反,CString表示由rust分配,Rust擁有所有權(quán),可以進(jìn)行修改,用以傳給C程序的字符串。

    use std::ffi::CString;
    use std::os::raw::c_char;
    extern {
    fn my_printer(s: *const c_char);
    }
    
    let c_to_print = CString::new("Hello, world!").unwrap();
    unsafe {
    my_printer(c_to_print.as_ptr()); // 使用 as_ptr 將CString轉(zhuǎn)化成char指針傳給c函數(shù)
    }

    兩端分配堆棧,另一端填充打印

    關(guān)于“Rust中FFI編程知識(shí)點(diǎn)有哪些”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“Rust中FFI編程知識(shí)點(diǎn)有哪些”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI