我愛學(xué)習(xí)網(wǎng)-上傳
當(dāng)前位置: 主頁 > 文庫 > C語言 >

外部函數(shù)接口(FFI)

時(shí)間:2020-11-13 13:16來源:我愛學(xué)習(xí)網(wǎng) 作者:apple 點(diǎn)擊:

介紹

 

本教程會(huì)使用snappy壓縮/解壓縮庫來作為一個(gè) Rust 編寫外部語言代碼綁定的介紹。目前 Rust 還不能直接調(diào)用 C++ 庫,不過 snappy 庫包含一個(gè) C 接口(記錄在snappy-c.h中)。

 

一個(gè)關(guān)于 libc 的說明

 

很多這些例子使用libc crate,它提供了很多 C 類型的類型定義,還有很多其他東西。如果你正在自己嘗試這些例子,你會(huì)需要在你的Cargo.toml中添加libc

 

[dependencies]
libc = "0.2.0"

 

并在你的 crate 根文件添加extern crate libc;

 

調(diào)用外部函數(shù)

 

下面是一個(gè)最簡(jiǎn)單的調(diào)用其它語言函數(shù)的例子,如果你安裝了snappy的話它將能夠編譯:

 

# #![feature(libc)]
extern crate libc;
use libc::size_t;

#[link(name = "snappy")]
extern {
    fn snappy_max_compressed_length(source_length: size_t) -> size_t;
}

fn main() {
    let x = unsafe { snappy_max_compressed_length(100) };
    println!("max compressed length of a 100 byte buffer: {}", x);
}

 

extern塊是一個(gè)外部庫函數(shù)標(biāo)記的列表,在這里例子中是 C ABI。#[link(...)]屬性用來指示鏈接器鏈接snappy 庫來解析符號(hào)。

 

外部函數(shù)被假定為不安全的所以調(diào)用它們需要包裝在unsafe {}中,用來向編譯器保證大括號(hào)中代碼是安全的。C 庫經(jīng)常提供不是線程安全的接口,并且?guī)缀跛幸灾羔樧鳛閰?shù)的函數(shù)不是對(duì)所有輸入時(shí)有效的,因?yàn)橹羔樋梢允谴箲业模衣阒羔槼隽?Rust 安全內(nèi)存模型的范圍。

 

當(dāng)聲明外部語言的函數(shù)參數(shù)時(shí),Rust 編譯器不能檢查它是否正確,所以指定正確的類型是保證綁定運(yùn)行時(shí)正常工作的一部分。

 

extern塊可以擴(kuò)展以包括整個(gè) snappy API:

 

# #![feature(libc)]
extern crate libc;
use libc::{c_int, size_t};

#[link(name = "snappy")]
extern {
    fn snappy_compress(input: *const u8,
                       input_length: size_t,
                       compressed: *mut u8,
                       compressed_length: *mut size_t) -> c_int;
    fn snappy_uncompress(compressed: *const u8,
                         compressed_length: size_t,
                         uncompressed: *mut u8,
                         uncompressed_length: *mut size_t) -> c_int;
    fn snappy_max_compressed_length(source_length: size_t) -> size_t;
    fn snappy_uncompressed_length(compressed: *const u8,
                                  compressed_length: size_t,
                                  result: *mut size_t) -> c_int;
    fn snappy_validate_compressed_buffer(compressed: *const u8,
                                         compressed_length: size_t) -> c_int;
}
# fn main() {}

 

創(chuàng)建安全接口

 

原始 C API 需要需要封裝才能提供內(nèi)存安全性和利用像向量這樣的高級(jí)內(nèi)容。一個(gè)庫可以選擇只暴露出安全的,高級(jí)的接口并隱藏不安全的底層細(xì)節(jié)。

 

包裝用到了緩沖區(qū)的函數(shù)涉及使用slice::raw模塊來將Rust向量作為內(nèi)存指針來操作。Rust 的 vector 確保是一個(gè)連續(xù)的內(nèi)存塊。它的長(zhǎng)度是當(dāng)前包含的元素個(gè)數(shù),而容量則是分配內(nèi)存的大小。長(zhǎng)度小于或等于容量。

 

# #![feature(libc)]
# extern crate libc;
# use libc::{c_int, size_t};
# unsafe fn snappy_validate_compressed_buffer(_: *const u8, _: size_t) -> c_int { 0 }
# fn main() {}
pub fn validate_compressed_buffer(src: &[u8]) -> bool {
    unsafe {
        snappy_validate_compressed_buffer(src.as_ptr(), src.len() as size_t) == 0
    }
}

 

上面的validate_compressed_buffer封裝使用了一個(gè)unsafe塊,不過它通過從函數(shù)標(biāo)記匯總?cè)サ?code>unsafe從而保證了對(duì)于所有輸入調(diào)用都是安全的。

 

snappy_compresssnappy_uncompress函數(shù)更復(fù)雜,因?yàn)檩敵鲆彩褂昧吮环峙涞木彌_區(qū)。

 

snappy_max_compressed_length函數(shù)可以用來分配一個(gè)所需最大容量的向量來存放壓縮的輸出。接著這個(gè)向量可以作為一個(gè)輸出參數(shù)傳遞給snappy_compress。另一個(gè)輸出參數(shù)也被傳遞進(jìn)去并設(shè)置了長(zhǎng)度,可以用它來獲取壓縮后的真實(shí)長(zhǎng)度。

 

# #![feature(libc)]
# extern crate libc;
# use libc::{size_t, c_int};
# unsafe fn snappy_compress(a: *const u8, b: size_t, c: *mut u8,
#                           d: *mut size_t) -> c_int { 0 }
# unsafe fn snappy_max_compressed_length(a: size_t) -> size_t { a }
# fn main() {}
pub fn compress(src: &[u8]) -> Vec {
    unsafe {
        let srclen = src.len() as size_t;
        let psrc = src.as_ptr();

        let mut dstlen = snappy_max_compressed_length(srclen);
        let mut dst = Vec::with_capacity(dstlen as usize);
        let pdst = dst.as_mut_ptr();

        snappy_compress(psrc, srclen, pdst, &mut dstlen);
        dst.set_len(dstlen as usize);
        dst
    }
}

 

解壓是相似的,因?yàn)?snappy 儲(chǔ)存了未壓縮的大小作為壓縮格式的一部分并且snappy_uncompressed_length可以取得所需緩沖區(qū)的實(shí)際大小。

 

# #![feature(libc)]
# extern crate libc;
# use libc::{size_t, c_int};
# unsafe fn snappy_uncompress(compressed: *const u8,
#                             compressed_length: size_t,
#                             uncompressed: *mut u8,
#                             uncompressed_length: *mut size_t) -> c_int { 0 }
# unsafe fn snappy_uncompressed_length(compressed: *const u8,
#                                      compressed_length: size_t,
#                                      result: *mut size_t) -> c_int { 0 }
# fn main() {}
pub fn uncompress(src: &[u8]) -> Option> {
    unsafe {
        let srclen = src.len() as size_t;
        let psrc = src.as_ptr();

        let mut dstlen: size_t = 0;
        snappy_uncompressed_length(psrc, srclen, &mut dstlen);

        let mut dst = Vec::with_capacity(dstlen as usize);
        let pdst = dst.as_mut_ptr();

        if snappy_uncompress(psrc, srclen, pdst, &mut dstlen) == 0 {
            dst.set_len(dstlen as usize);
            Some(dst)
        } else {
            None // SNAPPY_INVALID_INPUT
        }
    }
}

 

接下來,我們可以添加一些測(cè)試來展示如何使用他們:

 

# #![feature(libc)]
# extern crate libc;
# use libc::{c_int, size_t};
# unsafe fn snappy_compress(input: *const u8,
#                           input_length: size_t,
#                           compressed: *mut u8,
#                           compressed_length: *mut size_t)
#                           -> c_int { 0 }
# unsafe fn snappy_uncompress(compressed: *const u8,
#                             compressed_length: size_t,
#                             uncompressed: *mut u8,
#                             uncompressed_length: *mut size_t)
#                             -> c_int { 0 }
# unsafe fn snappy_max_compressed_length(source_length: size_t) -> size_t { 0 }
# unsafe fn snappy_uncompressed_length(compressed: *const u8,
#                                      compressed_length: size_t,
#                                      result: *mut size_t)
#                                      -> c_int { 0 }
# unsafe fn snappy_validate_compressed_buffer(compressed: *const u8,
#                                             compressed_length: size_t)
#                                             -> c_int { 0 }
# fn main() { }

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn valid() {
        let d = vec![0xde, 0xad, 0xd0, 0x0d];
        let c: &[u8] = &compress(&d);
        assert!(validate_compressed_buffer(c));
        assert!(uncompress(c) == Some(d));
    }

    #[test]
    fn invalid() {
        let d = vec![0, 0, 0, 0];
        assert!(!validate_compressed_buffer(&d));
        assert!(uncompress(&d).is_none());
    }

    #[test]
    fn empty() {
        let d = vec![];
        assert!(!validate_compressed_buffer(&d));
        assert!(uncompress(&d).is_none());
        let c = compress(&d);
        assert!(validate_compressed_buffer(&c));
        assert!(uncompress(&c) == Some(d));
    }
}

 

析構(gòu)函數(shù)

 

外部庫經(jīng)常把資源的所有權(quán)傳遞給調(diào)用函數(shù)。當(dāng)這發(fā)生時(shí),我們必須使用 Rust 析構(gòu)函數(shù)來提供安全性和確保釋放了這些資源(特別是在恐慌的時(shí)候)。

 

關(guān)于析構(gòu)函數(shù)的更多細(xì)節(jié),請(qǐng)看Droptrait

 

在 Rust 函數(shù)中處理 C 回調(diào)(Callbacks from C code to Rust functions)

 

一些外部庫要求使用回調(diào)來向調(diào)用者反饋它們的當(dāng)前狀態(tài)或者即時(shí)數(shù)據(jù)。可以傳遞在 Rust 中定義的函數(shù)到外部庫中。要求是這個(gè)回調(diào)函數(shù)被標(biāo)記為extern并使用正確的調(diào)用約定來確保它可以在 C 代碼中被調(diào)用。

 

接著回調(diào)函數(shù)可以通過一個(gè)C庫的注冊(cè)調(diào)用傳遞并在后面被執(zhí)行。

 

一個(gè)基礎(chǔ)的例子:

 

Rust代碼:

 

extern fn callback(a: i32) {
    println!("I'm called from C with value {0}", a);
}

#[link(name = "extlib")]
extern {
   fn register_callback(cb: extern fn(i32)) -> i32;
   fn trigger_callback();
}

fn main() {
    unsafe {
        register_callback(callback);
        trigger_callback(); // Triggers the callback.
    }
}

 

C 代碼:

 

typedef void (*rust_callback)(int32_t);
rust_callback cb;

int32_t register_callback(rust_callback callback) {
    cb = callback;
    return 1;
}

void trigger_callback() {
  cb(7); // Will call callback(7) in Rust.
}

 

這個(gè)例子中 Rust 的main()會(huì)調(diào)用 C 中的trigger_callback(),它會(huì)反過來調(diào)用 Rust 中的callback()

 

在 Rust 對(duì)象上使用回調(diào)(Targeting callbacks to Rust objects)

 

之前的例子展示了一個(gè)全局函數(shù)是如何在C代碼中被調(diào)用的。然而我們經(jīng)常希望回調(diào)是針對(duì)一個(gè)特殊 Rust 對(duì)象的。這個(gè)對(duì)象可能代表對(duì)應(yīng) C 語言中的封裝。

 

這可以通過向 C 庫傳遞這個(gè)對(duì)象的不安全指針來做到。C 庫則可以根據(jù)這個(gè)這個(gè)通知中的指針來取得 Rust 對(duì)象。這允許回調(diào)不安全的訪問被引用的 Rust 對(duì)象。

 

Rust 代碼:

 

#[repr(C)]
struct RustObject {
    a: i32,
    // Other members...
}

extern "C" fn callback(target: *mut RustObject, a: i32) {
    println!("I'm called from C with value {0}", a);
    unsafe {
        // Update the value in RustObject with the value received from the callback:
        (*target).a = a;
    }
}

#[link(name = "extlib")]
extern {
   fn register_callback(target: *mut RustObject,
                        cb: extern fn(*mut RustObject, i32)) -> i32;
   fn trigger_callback();
}

fn main() {
    // Create the object that will be referenced in the callback:
    let mut rust_object = Box::new(RustObject { a: 5 });

    unsafe {
        register_callback(&mut *rust_object, callback);
        trigger_callback();
    }
}

 

C 代碼:

 

typedef void (*rust_callback)(void*, int32_t);
void* cb_target;
rust_callback cb;

int32_t register_callback(void* callback_target, rust_callback callback) {
    cb_target = callback_target;
    cb = callback;
    return 1;
}

void trigger_callback() {
  cb(cb_target, 7); // Will call callback(&rustObject, 7) in Rust.
}

 

異步回調(diào)

 

在之前給出的例子中回調(diào)在一個(gè)外部 C 庫的函數(shù)調(diào)用后直接就執(zhí)行了。在回調(diào)的執(zhí)行過程中當(dāng)前線程控制權(quán)從 Rust 傳到了 C 又傳到了 Rust,不過最終回調(diào)和和觸發(fā)它的函數(shù)都在一個(gè)線程中執(zhí)行。

 

當(dāng)外部庫生成了自己的線程并觸發(fā)回調(diào)時(shí)情況就變得復(fù)雜了。在這種情況下回調(diào)中對(duì) Rust 數(shù)據(jù)結(jié)構(gòu)的訪問時(shí)特別不安全的并必須有合適的同步機(jī)制。除了像互斥量這種經(jīng)典同步機(jī)制外,另一種可能就是使用通道(std::sync::mpsc)來從觸發(fā)回調(diào)的 C 線程轉(zhuǎn)發(fā)數(shù)據(jù)到 Rust 線程。

 

如果一個(gè)異步回調(diào)指定了一個(gè)在 Rust 地址空間的特殊 Rust 對(duì)象,那么在確保在對(duì)應(yīng) Rust 對(duì)象被銷毀后不會(huì)再有回調(diào)被 C 庫觸發(fā)就格外重要了。這一點(diǎn)可以通過在對(duì)象的析構(gòu)函數(shù)中注銷回調(diào)和設(shè)計(jì)庫使其確保在回調(diào)被注銷后不會(huì)再被觸發(fā)來取得。

 

鏈接

 

extern上的link屬性提供了基本的構(gòu)建塊來指示rustc如何連接到原生庫。現(xiàn)在有兩種被接受的鏈接屬性形式:

 

  • #[link(name = "foo")]
  • #[link(name = "foo", kind = "bar")]

 

在這兩種形式中,foo是我們鏈接的原生庫的名字,而在第二個(gè)形式中bar是編譯器要鏈接的原生庫的類型。目前有 3 種已知的原生庫類型:

 

  • 動(dòng)態(tài) - #[link(name = "readline")]
  • 靜態(tài) - #[link(name = "my_build_dependency", kind = "static")]
  • Frameworks - #[link(name = "CoreFoundation", kind = "framework")]

 

注意 Frameworks 只支持 OSX 平臺(tái)。

 

不同kind的值意味著鏈接過程中不同原生庫的參與方式。從鏈接的角度看,rus t編譯器創(chuàng)建了兩種組件:部分的(rlib/staticlib)和最終的(dylib/binary)。原生動(dòng)態(tài)庫和框架會(huì)從擴(kuò)展到最終組件部分,而靜態(tài)庫則完全不會(huì)擴(kuò)展。

 

一些關(guān)于這些模型如何使用的例子:

 

  • 一個(gè)原生構(gòu)建依賴。有時(shí)編寫部分Rust代碼時(shí)需要一些C/C代碼,另外使用發(fā)行為庫格式的C/C代碼只是一個(gè)負(fù)擔(dān)。在這種情況下,代碼會(huì)被歸檔為libfoo.a然后rust包裝箱可以通過#[link(name = "foo", kind = "static")]聲明一個(gè)依賴。
    不管包裝箱輸出為何種形式,原生靜態(tài)庫將會(huì)包含在輸出中,這意味著分配一個(gè)原生靜態(tài)庫是沒有必要的。
  • 一個(gè)正常動(dòng)態(tài)庫依賴。通用系統(tǒng)庫(像readline)在大量系統(tǒng)上可用,通常你找不到這類庫的靜態(tài)拷貝。當(dāng)這種依賴被添加到包裝箱里時(shí),部分目標(biāo)(比如rlibs)將不會(huì)鏈接這些庫,但是當(dāng)rlib被包含進(jìn)最終目標(biāo)(比如二進(jìn)制文件)時(shí),原生庫將被鏈接。

 

在OSX上,框架與動(dòng)態(tài)庫有相同的語義。

 

不安全塊(Unsafe blocks)

 

一些操作,像解引用不安全的指針或者被標(biāo)記為不安全的函數(shù)只允許在 unsafe 塊中使用。unsafe 塊隔離的不安全性并向編譯器保證不安全代碼不會(huì)泄露到塊之外。

 

不安全函數(shù),另一方面,將它公布于眾。一個(gè)不安全的函數(shù)這樣寫:

 

unsafe fn kaboom(ptr: *const i32) -> i32 { *ptr }

 

這個(gè)函數(shù)只能被從unsafe塊中或者unsafe函數(shù)調(diào)用。

 

訪問外部全局變量(Accessing foreign globals)

 

外部 API 經(jīng)常導(dǎo)出一個(gè)全局變量來進(jìn)行像記錄全局狀態(tài)這樣的工作。為了訪問這些變量,你可以在extern塊中用static關(guān)鍵字聲明它們:

 

# #![feature(libc)]
extern crate libc;

#[link(name = "readline")]
extern {
    static rl_readline_version: libc::c_int;
}

fn main() {
    println!("You have readline version {} installed.",
             unsafe { rl_readline_version as i32 });
}

 

另外,你可能想修改外部結(jié)接口提供的全局狀態(tài)。為了做到這一點(diǎn),聲明為mut這樣我們就可以改變它了。

 

# #![feature(libc)]
extern crate libc;

use std::ffi::CString;
use std::ptr;

#[link(name = "readline")]
extern {
    static mut rl_prompt: *const libc::c_char;
}

fn main() {
    let prompt = CString::new("[my-awesome-shell] $").unwrap();
    unsafe {
        rl_prompt = prompt.as_ptr();

        println!("{:?}", rl_prompt);

        rl_prompt = ptr::null();
    }
}

 

注意與static mut變量的所有交互都是不安全的,包括讀或?qū)憽Ec全局可變量打交道需要足夠的注意。

 

外部調(diào)用約定(Foreign calling conventions)

 

大部分外部代碼導(dǎo)出為一個(gè) C 的 ABI,并且 Rust 默認(rèn)使用平臺(tái) C 的調(diào)用約定來調(diào)用外部函數(shù)。一些外部函數(shù),尤其是大部分 Windows API,使用其它的調(diào)用約定。Rust 提供了一個(gè)告訴編譯器應(yīng)該用哪種調(diào)用約定的方法:

 

# #![feature(libc)]
extern crate libc;

#[cfg(all(target_os = "win32", target_arch = "x86"))]
#[link(name = "kernel32")]
#[allow(non_snake_case)]
extern "stdcall" {
    fn SetEnvironmentVariableA(n: *const u8, v: *const u8) -> libc::c_int;
}
# fn main() { }

 

這適用于整個(gè)extern塊。被支持的ABI約束的列表為:

 

  • stdcall
  • aapcs
  • cdecl
  • fastcall
  • vectorcall 目前隱藏于abi_vectorcall gate 之后并傾向于改變。
  • Rust
  • rust-intrinsic
  • system
  • C
  • win64
  • sysv64

 

列表中大部分 ABI 都是自解釋的,不過systemABI 可能看起來有點(diǎn)奇怪。這個(gè)約束會(huì)選擇任何能和目標(biāo)庫正確交互的 ABI。例如,在x86架構(gòu)上,這意味著會(huì)使用stdcallABI。然而,在 x86_64 上 windows 使用C調(diào)用約定,所以C會(huì)被使用。這意味在我們之前的例子中,我們可以使用extern "system" { ... }定義一個(gè)適用于所有 windows 系統(tǒng)的塊,而不僅僅是 x86 系統(tǒng)。

 

外部代碼交互性(Interoperability with foreign code)

 

只有當(dāng)#[repr(C)]屬性被用于結(jié)構(gòu)體時(shí)Rust能確保struct的布局兼容平臺(tái)的 C 的表現(xiàn)。#[repr(C, packed)]可以用來不對(duì)齊的排列結(jié)構(gòu)體成員。#[repr(C)]也可以被用于一個(gè)枚舉。

 

Rust 擁有的裝箱(Box<T>)使用非空指針作為指向他包含的對(duì)象的句柄。然而,它們不應(yīng)該手動(dòng)創(chuàng)建因?yàn)樗鼈冇蓛?nèi)部分分配器托管。引用可以被安全的假設(shè)為直接指向數(shù)據(jù)的非空指針。然而,打破借用檢查和可變性規(guī)則并不能保證安全,所以傾向于只在需要時(shí)使用裸指針(*)因?yàn)榫幾g器不能為它們做更多假設(shè)。

 

向量和字符串共享同樣基礎(chǔ)的內(nèi)存布局,vecstr模塊中可用的功能可以操作 C API。然而,字符串不是\0結(jié)尾的。如果你需要一個(gè)NUL結(jié)尾的字符串來與 C 交互,你需要使用std::ffi模塊中的CString類型。

 

標(biāo)準(zhǔn)庫中的libc模塊包含類型別名和 C 標(biāo)準(zhǔn)庫中的函數(shù)定義,Rust 默認(rèn)鏈接libclibm

 

可變參數(shù)函數(shù)

 

在 C 語言中,函數(shù)是“可變參數(shù)的”,這意味著它可以接受可變數(shù)量的參數(shù)。在 Rust 中可以通過在外部函數(shù)聲明中指定...來實(shí)現(xiàn):

 

extern {
    fn foo(x: i32, ...);
}

fn main() {
    unsafe {
        foo(10, 20, 30, 40, 50);
    }
}

 

普通的 Rust 不能是可變參數(shù)的:

 

// This will not compile

fn foo(x: i32, ...) { }

 

“可空指針優(yōu)化”(The "nullable pointer optimization")

 

特定的 Rust 類型被定義為永遠(yuǎn)不能為null。這包括引用(&T&mut T),裝箱(Box<T>),和函數(shù)指針(extern "abi" fn())。當(dāng)調(diào)用 C 接口時(shí),可以為null的指針被廣泛使用,這好像會(huì)需要使用到一些混亂的transmutes和/或不安全代碼來處理與 Rust 類型間的相互轉(zhuǎn)換。不過,Rust 語言提供了一個(gè)變通方案。

 

作為一個(gè)特殊的例子,一類只包含兩個(gè) variant 的enum適合用來進(jìn)行“可空指針優(yōu)化”,其中一個(gè) variant 不包含數(shù)據(jù),另一個(gè)包含一個(gè)上述不可空類型的字段。這意味著不需要額外的空間來進(jìn)行判別,相反,空的 variant 表現(xiàn)為將一個(gè)null值放入不可空的字段。這也被稱為一種優(yōu)化,不過不同于別讀優(yōu)化它保證適用于合適的類型。

 

得益于這種可空指針優(yōu)化的最常見的例子是Option<T>,這里None對(duì)應(yīng)null。所以Option<extern "C" fn(c_int) -> c_int>是一個(gè)用于 C ABI 的可空函數(shù)指針的正確方式(對(duì)應(yīng) C 類型int (*)(int))。

 

這是一個(gè)不太自然的例子。假如一些 C 庫擁有一個(gè)注冊(cè)回調(diào)的功能,它在特定情況下被調(diào)用。回調(diào)傳遞了一個(gè)函數(shù)指針和一個(gè)整型并應(yīng)該以這個(gè)整型作為參數(shù)執(zhí)行這個(gè)函數(shù)。所以我們有一些雙向穿越 FFI boundary 的函數(shù)指針。

 

# #![feature(libc)]
extern crate libc;
use libc::c_int;

# #[cfg(hidden)]
extern "C" {
    /// Registers the callback.
    fn register(cb: Option c_int>, c_int) -> c_int>);
}
# unsafe fn register(_: Option c_int>,
#                                            c_int) -> c_int>)
# {}

/// This fairly useless function receives a function pointer and an integer
/// from C, and returns the result of calling the function with the integer.
/// In case no function is provided, it squares the integer by default.
extern "C" fn apply(process: Option c_int>, int: c_int) -> c_int {
    match process {
        Some(f) => f(int),
        None    => int * int
    }
}

fn main() {
    unsafe {
        register(Some(apply));
    }
}

 

而 C 代碼看起來像這樣:

 

void register(void (*f)(void (*)(int), int)) {
    ...
}

 

并不需要transmute

 

在 C 中調(diào)用 Rust 代碼

 

你可能會(huì)希望這么編譯 Rust 代碼以便可以在 C 中調(diào)用。這是很簡(jiǎn)單的,不過需要一些東西:

 

#[no_mangle]
pub extern fn hello_rust() -> *const u8 {
    "Hello, world!\0".as_ptr()
}
# fn main() {}

 

extern使這個(gè)函數(shù)遵循 C 調(diào)用約定,就像之前討論外部調(diào)用約定時(shí)一樣。no_mangle屬性關(guān)閉Rust的命名改編,這樣它更容易鏈接。

 

FFI 和 panic

 

當(dāng)使用 FFI 時(shí)留意panic!是很重要的。一個(gè)跨越FFI邊界的panic!是未定義行為。如果你在編寫可能 panic 的代碼,你應(yīng)該使用 catch_unwind() 在一個(gè)閉包中運(yùn)行它:

 

use std::panic::catch_unwind;

#[no_mangle]
pub extern fn oh_no() -> i32 {
    let result = catch_unwind(|| {
        panic!("Oops!");
    });

    match result {
        Ok(_) => 0,
        Err(_) => 1,
    }
}
fn main() {}

 

注意 catch_unwind() 只捕獲那些不嚴(yán)重的(unwinding) panic,不是那些會(huì)終止進(jìn)程的。查看 catch_unwind() 的文檔來獲取更多信息。

 

 

表示 opaque 結(jié)構(gòu)體

 

有時(shí)一個(gè) C 庫想要提供某種指針,不過并不想讓你知道它需要的內(nèi)部細(xì)節(jié)。最簡(jiǎn)單的方法是使用一個(gè)void *參數(shù):

 

void foo(void *arg);
void bar(void *arg);

 

我們可以使用c_void在 Rust 中表示它:

 

# #![feature(libc)]
extern crate libc;

extern "C" {
    pub fn foo(arg: *mut libc::c_void);
    pub fn bar(arg: *mut libc::c_void);
}
# fn main() {}

 

這是處理這種情形完美有效的方式。然而,我們可以做的更好一點(diǎn)。為此,一些 C 庫會(huì)創(chuàng)建一個(gè)struct,結(jié)構(gòu)體的細(xì)節(jié)和內(nèi)存布局是私有的。這提供了一些類型安全性。這種結(jié)構(gòu)體叫做opaque。這是一個(gè) C 的例子:

 

struct Foo; /* Foo is a structure, but its contents are not part of the public interface */
struct Bar;
void foo(struct Foo *arg);
void bar(struct Bar *arg);

 

在 Rust 中,讓我們用enum創(chuàng)建自己的 opaque 類型:

 

pub enum Foo {}
pub enum Bar {}

extern "C" {
    pub fn foo(arg: *mut Foo);
    pub fn bar(arg: *mut Bar);
}
# fn main() {}

 

通過一個(gè)沒有變量的enum,我們創(chuàng)建了一個(gè)不能實(shí)例化的 opaque 類型,因?yàn)樗鼪]有變量。不過因?yàn)槲覀兊?code>Foo和Bar是不同類型,我們可以安全的獲取這兩個(gè)類型,所以我們不可能不小心向bar()傳遞一個(gè)Foo的指針。

------分隔線----------------------------
    ?分享到??
看看啦
主站蜘蛛池模板: 一区二区三区四区精品| 亚洲乱色熟女一区二区三区蜜臀 | 亚洲AV本道一区二区三区四区| 久久久久成人精品一区二区| 一区二区三区美女视频| 国产AV一区二区三区无码野战| 亚洲av区一区二区三| 性色av无码免费一区二区三区 | 人成精品视频三区二区一区| 国产SUV精品一区二区88| 日韩一区二区超清视频| 久久久久成人精品一区二区| 精品国产一区二区三区免费| 国产91精品一区二区麻豆亚洲 | 日韩精品无码一区二区三区AV| 精品3d动漫视频一区在线观看| 精品视频一区二区三区四区 | 蜜桃传媒一区二区亚洲AV| 日韩人妻不卡一区二区三区| 国产精品无码一区二区三级| 国偷自产av一区二区三区| 国产一区二区四区在线观看| 久久无码精品一区二区三区| 久久国产午夜精品一区二区三区| 亚洲成av人片一区二区三区 | 国产成人午夜精品一区二区三区| 亚洲一区综合在线播放| 日本成人一区二区三区| 国产在线精品一区二区中文| 无码人妻精品一区二区三区东京热 | 日韩av无码一区二区三区| 亚洲AV综合色区无码一区爱AV | 日韩人妻无码一区二区三区综合部| 久久精品国产亚洲一区二区三区| 人妻无码一区二区视频| 国语精品一区二区三区| 成人免费观看一区二区| 无码少妇一区二区浪潮免费| 国产福利一区视频| 超清无码一区二区三区| 国产亚洲一区二区三区在线|