99re热这里只有精品视频,7777色鬼xxxx欧美色妇,国产成人精品一区二三区在线观看,内射爽无广熟女亚洲,精品人妻av一区二区三区

外部函數(shù)接口

2018-08-12 22:03 更新

外部函數(shù)接口

引言

本指南將使用 snappy 壓縮/解壓庫作為引言來介紹編寫綁定外部代碼。Rust 目前無法直接調(diào)用 c++ 庫,但是 snappy 包括 C 的接口(記錄在 snappy-c.h)。

下面是調(diào)用外部函數(shù)的一個例子,如果你的機(jī)器安裝了 snappy 它將能夠編譯通過:

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 語句塊中包含的是外部庫中函數(shù)簽名列表,在這個例子中調(diào)用的是平臺的 C ABI。#[link(...)] 屬性用于指示鏈接器對 snappy 庫進(jìn)行連接,從而保證庫中的符號能夠被解析。

外部函數(shù)被假定為不安全的,所以當(dāng)調(diào)用他們時,需要利用 unsafe{ } 進(jìn)行封裝,進(jìn)而告訴編譯器被調(diào)用的函數(shù)中包含的代碼是安全的。C 庫經(jīng)常暴露不是線程安全的接口給外部調(diào)用,而且?guī)缀跞魏螖y帶指針參數(shù)的函數(shù)對于所有的輸入都不是有效的,因為這些指可能懸空,并且未經(jīng)處理的指針可能指向 Rust 內(nèi)存安全模型之外的區(qū)域。

當(dāng)聲明外部函數(shù)的參數(shù)類型時,Rust 編譯器不會檢查聲明是正確的,所以正確地指定它是在運行時能夠正確的綁定的一部分。

extern 塊可以擴(kuò)展到覆蓋整個 snappy API:

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;
}

創(chuàng)建一個安全接口

原始 C API 需要經(jīng)過封裝之后提供內(nèi)存安全性,并且才可以使用更高級的概念類似向量。庫可以選擇只暴露安全、高級接口而隱藏不安全的內(nèi)部細(xì)節(jié)。

封裝那些使用 slice::raw 模塊來訪問緩沖區(qū)的函數(shù),從而將其當(dāng)作指針來操作 Rust 的向量。Rust 的向量是內(nèi)存中的一塊連續(xù)的區(qū)域。向量的長度指的是其中包含元素個數(shù)的長度,向量的容量指的是在分配的內(nèi)存中元素的總大小。長度小于或等于容量。

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 封裝器使用了不安全的語句塊,但它保證對于所有的輸入在離開那個 unsafe 函數(shù)簽名的時候是安全的。

snappy_compress 和 snappy_uncompress 函數(shù)更復(fù)雜,因為必須要分配一個緩沖區(qū)來保存輸出數(shù)據(jù)。

snappy_max_compressed_length 函數(shù)可以通過指定最大所需容量用來分配向量空間,接著用該向量來保存輸出。向量接著可以被傳遞到 snappy_compress 函數(shù)作為輸出參數(shù)。輸出參數(shù)也會被傳遞,這樣通過設(shè)置長度之后被壓縮的數(shù)據(jù)的實際長度也可以得到。

pub fn compress(src: &[u8]) -> Vec<u8> {
    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
    }
}

解壓是相似的,因為 snappy 保存未壓縮的大小作為壓縮格式的一部分,snappy_uncompressed_length 能夠返回所需的確切緩沖區(qū)大小。

pub fn uncompress(src: &[u8]) -> Option<Vec<u8>> {
    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
        }
    }
}

供參考,這里使用的例子也可以在 GitHub 庫 里面查看。

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

外部庫經(jīng)常更換被調(diào)用代碼資源的所有權(quán)。當(dāng)這種情況發(fā)生時,我們必須使用 Rust 提供的析構(gòu)函數(shù)來提供安全保證的釋放這些資源(特別是在恐慌的情況下)。

想要了解更多的析構(gòu)函數(shù),請查看 Drop trait

Rust 函數(shù)調(diào)用 C 代碼進(jìn)行回調(diào)

一些外部庫需要使用回調(diào)函數(shù)來報告給調(diào)用者他們的當(dāng)前狀態(tài)或中間數(shù)據(jù)。可以通過 Rust 中定義的傳遞函數(shù)與外部庫進(jìn)行通信。當(dāng)調(diào)用 C 代碼時,要求回調(diào)函數(shù)必須使用 extern 標(biāo)記。

回調(diào)函數(shù)可以通過注冊器發(fā)送給調(diào)用的 C 庫,之后就可以被調(diào)用。

一個基本的例子如下:

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
}

在這個例子中 Rust 的 main() 函數(shù)將調(diào)用 C 語言中的 trigger_callback() 函數(shù),接著會在 C 語言中反過來調(diào)用 Rust 中的 callback() 函數(shù)。

針對 Rust 對象的回調(diào)

前面的例子顯示了如何在 C 代碼中如何回調(diào)一個全局函數(shù)。然而通常這個回調(diào)是針對于 Rust 中某個特定的對象。這個對象可能相應(yīng)的由 C 對象封裝之后的對象。

這個可以通過利用傳遞一個不安全的指針給 C 庫來實現(xiàn)。接著 C 庫能夠在通知中包含 Rust 對象的指針。此時,允許不安全的訪問 Rust 索引對象。

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)用外部 C 庫中提供的函數(shù)進(jìn)行回調(diào)。當(dāng)前線程的控制會從 Rust 轉(zhuǎn)向 C 接著轉(zhuǎn)向 Rust,接著執(zhí)行回調(diào),最后,觸發(fā)回調(diào)的被調(diào)用的函數(shù)會在同一線程中執(zhí)行。

當(dāng)外部庫生成自己的線程,并調(diào)用回調(diào)時情況就變得更加的復(fù)雜。在這些情況下,在回調(diào)函數(shù)內(nèi)使用 Rust 中的數(shù)據(jù)結(jié)構(gòu)是特別不安全的,而且必須使用適當(dāng)?shù)耐綑C(jī)制。除了經(jīng)典的同步機(jī)制,例如互斥,Rust 中提供了一種可行的方式是使用管道(std::comm),它會將數(shù)據(jù)從調(diào)用回調(diào)的 C 線程中轉(zhuǎn)發(fā)到 Rust 中的線程。

如果異步回調(diào)的目標(biāo)是 Rust 地址空間中的一個特殊對象,那么在對象的 Rust 對象被銷毀之前在 C 庫中肯定不會有更多回調(diào)會執(zhí)行。這個可以通過在對象的析構(gòu)函數(shù)中解除回調(diào)關(guān)系,并且設(shè)計該庫確保在正確執(zhí)行完解除注冊之前不會有回調(diào)執(zhí)行。

鏈接

extern 塊中的 link 屬性提供給 rustc 基本構(gòu)建塊,告訴它如何鏈接到本地庫。有兩種可接受的 link 編寫形式:

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

在這兩種情況下,foo 是它要連接到本地庫的名稱,而在第二種情況中 bar 是編譯期連接到本地庫的類型。目前有三個已知的本地庫類型:

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

注意,框架類型僅僅對 OSX 目標(biāo)平臺可用。

不同的 kind 值是為了區(qū)分本地庫如何不同的進(jìn)行連接。從連接的角度來看,Rust 編譯器創(chuàng)建兩種構(gòu)件:部分(rlib/staticlib)和最終(dylib/binary)。本地動態(tài)庫和框架屬于最終構(gòu)件范圍,而靜態(tài)庫不屬于。

如下是幾個例子關(guān)于如何使用這個模型的:

  • 本地構(gòu)建依賴。有時候在編寫 Rust 代碼是需要使用一些 C/C++ 代碼,但是分布的 C / C++ 代碼庫格式只是一個負(fù)擔(dān)。在這種情況下,代碼將被歸檔到libfoo。然后銹箱將聲明一個依賴通過 #[link(name = "foo", kind = "static")]。

不管輸出箱的味道,本機(jī)靜態(tài)庫將被包含在輸出中,這意味著分配本機(jī)靜態(tài)庫是不必要的。

  • 一個正常的動態(tài)依賴關(guān)系。常見的系統(tǒng)庫(比如readline)都可以在大量的系統(tǒng),而且經(jīng)常無法找到這些庫的靜態(tài)副本。當(dāng)這種依賴是包含在鐵銹箱,部分目標(biāo)(如rlibs)不會鏈接到庫,但當(dāng)rlib包含在最終的目標(biāo)(如二進(jìn)制),本地庫將聯(lián)系在一起。

在 OSX,框架的行為作為一個動態(tài)庫相同的語義。

不安去的語句塊

一些操作,比如引用不安全指針或調(diào)用已經(jīng)被標(biāo)明為不安全的函數(shù)時只允許在不安全的區(qū)域內(nèi)進(jìn)行。不安全的區(qū)域隔離危險,并且向編譯期保證不會溢出不安全區(qū)域。

不安全的函數(shù),另一方面,必須要顯式的表明出。不安全的函數(shù)如下所示:

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

這個函數(shù)只能從一個不安全的區(qū)域中調(diào)用或被其他不安全的函數(shù)調(diào)用。

訪問外部全局變量

外部 API 經(jīng)常導(dǎo)出全局變量,這樣可以做一些類似于跟蹤全局狀態(tài)的事情。為了訪問這些變量,你在 extern 語句塊中聲明他們時要使用關(guān)鍵字 static:

extern crate libc;

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

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

或者,您可能需要使用外部接口來改變?nèi)譅顟B(tài)。為了做到這一點,在聲明他們時使用 mut,這樣就可以修改他們了。

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 類型的變量交互是不安全的,包括讀和寫。為了處理全局可變狀態(tài)你需要多花點心思。

外部調(diào)用約定

大多數(shù)外部代碼暴露了 C ABI,并且 Rust 默認(rèn)情況下調(diào)用外部函數(shù)時使用的是 C 平臺調(diào)用約束。一些外部函數(shù),尤其是 Windows API,使用的是其他調(diào)用約定。Rust 提供了一種方法來告訴編譯器它使用的是哪個約定:

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;
}

下面的適用于整個 extern 塊。Rust 中支持的ABI 約束列表如下:

  • stdcall
  • aapcs
  • cdecl
  • fastcall
  • Rust
  • rust-intrinsic
  • system
  • C
  • win64

上面列表中的大部分 abis 是不需要解釋的,但 system 這個 abi 可能看起來有點奇怪。這個約束的意思是選擇與任何與目標(biāo)庫合適的 ABI 進(jìn)行交互。例如,在 win32 x86 體系結(jié)構(gòu)中,這意味著 abi 將會選擇 stdcall。然而在 x86_64 中,windows 使用 C 調(diào)用協(xié)定,因此將會使用 C 的標(biāo)準(zhǔn)。也就是說,在前面的例子中,我們可以在 extern 中使用 “system”{...} 來定義 所有 windows 系統(tǒng)中的塊,而不僅僅是 x86 的。

與外部代碼的交互

只要 #[repr(C)] 這個屬性應(yīng)用在代碼中,Rust 保證的 struct 的結(jié)構(gòu)與平臺的表示形式是兼容的。#[repr(C、包裝)] 可以用來布局 sturct 的成員沒而不需要有填充元素。#[repr(C)] 也適用于枚舉類型。

Rust 中的 boxes(Box<T>) 使用非空指針作為句柄指向其中所包含的對象。然而,他們不應(yīng)該手動創(chuàng)建的,因為它們是由內(nèi)部分配器管理。引用可以安全地假定指針非空指向該類型。然而,破壞 borrow 的檢查或易改變的改規(guī)則不能保證是安全的,所以如果有必要請使用原始指針(*),因為編譯器不能對他們呢進(jìn)行過多的假設(shè)。

向量和字符串共享相同的基本的內(nèi)存布局,并且可以通過 vec 和 str 模塊與 C APIs 進(jìn)行交流。然而,字符串不是以 \0 作為它的結(jié)束符。如果你想要使用一個空終結(jié)符字符串與 C 語言的交互,此時你應(yīng)該使用 std::ffi 模塊中的 CString 類型。

標(biāo)準(zhǔn)庫包括類型別名和函數(shù)的定義,對于 C 標(biāo)準(zhǔn)庫位于 libc 模塊中,而且 Rust 默認(rèn)情況下已經(jīng)鏈接了 libc 和 libm 庫。

可空指針優(yōu)化

某些類型的定義不為空。這包括引用類型(&T、&mut T),boxes(Box<T>),和函數(shù)指針(extern "abi" fn())。當(dāng)與 C 交互時,經(jīng)常使用的指針可能為空。特殊的情況下,泛型枚舉中僅僅包含兩個亮亮,其中一個不包含數(shù)據(jù),另一個包含單個字段,這個能夠進(jìn)行空指針優(yōu)化。當(dāng)這個枚舉類型被一個非空類型初始化時,它就表示一個指針,并且那個沒有數(shù)值的變量就成為空指針。因此,Option<extern "C" fn(c_int) -> c_int> 展示了一個表示空函數(shù)指針是如何使用 C ABI。

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

你可能想要在 C 中調(diào)用 Rust 代碼,并且編譯。這也好似相當(dāng)容易,但是需要幾件事:

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

extern 讓這個函數(shù)符合 C 調(diào)用函數(shù)的約束,就如上面說的“外部函數(shù)調(diào)用約束”。no_mangle 屬性關(guān)閉了 Rust 的名稱糾正,因此這里是很容易的進(jìn)行連接的。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號