亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

如何使用bindgen將C語言頭文件轉(zhuǎn)換為Rust接口代碼

 更新時(shí)間:2023年01月29日 09:38:40   作者:塵觴葉  
這篇文章主要介紹了使用bindgen將C語言頭文件轉(zhuǎn)換為Rust接口代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

Rust語言調(diào)用C語言接口

嵌入式系統(tǒng)層及應(yīng)用層的軟件開發(fā),離不開C語言。筆者希望使用一種高效、穩(wěn)定的開發(fā)語言,在一定程度上替代C語言,從而提高開發(fā)效率、降低嵌入式軟件的擴(kuò)展、維護(hù)成本,同時(shí)縮小研發(fā)團(tuán)隊(duì)規(guī)模。Rust編程語言很好地滿足了高效、穩(wěn)定這兩個(gè)要求。不過需要在一定程度解決Rust調(diào)用外部C語言模塊的問題:Rust語言已提供了完善的解決方案,筆者希望通過本文做一個(gè)必要的記錄。

筆者在之前一篇文章中簡(jiǎn)要介紹了Rust語言調(diào)用C語言動(dòng)態(tài)庫提供的函數(shù)的一般方法。不過隨著Rust工程依賴的外部的C語言模塊越來越來復(fù)雜,手工將C語言頭文件定義的調(diào)用接口轉(zhuǎn)換為Rust接口代碼變得不具可操作性。幸運(yùn)的是,一個(gè)名為bindgen的開源項(xiàng)目很好地解決了這個(gè)問題,它通過clang編譯器庫對(duì)C語言的頭文件進(jìn)行預(yù)處理,并生成相應(yīng)的Rust接口代碼;本文參考了其官方文檔,結(jié)合筆者的開發(fā)需要作簡(jiǎn)要的使用說明。

Rust語言將字符串轉(zhuǎn)換為整型

筆者在實(shí)際開發(fā)過程,需要將Rust的一個(gè)字符串類型轉(zhuǎn)換為整型,Rust柡準(zhǔn)庫已經(jīng)提供了相應(yīng)的轉(zhuǎn)換函數(shù)parse

pub fn parse<F>(&self) -> Result<F, <F as FromStr>::Err>where
    F: FromStr,
...
let four: u32 = "4".parse().unwrap();
assert_eq!(4, four);

不過,parse函數(shù)的缺陷是,它要求輸入的字符串是十進(jìn)制的,對(duì)于"0x1234"之類的非十六進(jìn)制數(shù),則不能正確處理。然而,柡準(zhǔn)庫也提供了另一個(gè)函數(shù)from_str_radix,可指定任意仍意進(jìn)制的字符串到整型:

pub fn from_str_radix(src: &str, radix: u32) -> Result<i64, ParseIntError>
...
assert_eq!(i64::from_str_radix("A", 16), Ok(10));

結(jié)合這兩個(gè)柡準(zhǔn)庫提供的函數(shù),就可以編寫一個(gè)純粹的Rust函數(shù),根據(jù)字符串的前綴決定調(diào)用哪一個(gè)轉(zhuǎn)換函數(shù)了。不過筆者是懷舊的,希望繼續(xù)調(diào)用C語言柡準(zhǔn)庫提供的函數(shù)strtoll/strtoull,這兩個(gè)函數(shù)可以自動(dòng)判斷字符串的進(jìn)制(盡管僅限于幾個(gè)進(jìn)制)。

筆者為Rust工程編寫的代碼如下(完整代碼可參考此處):

/* extmodule/extmodule.h */
#ifndef RUST_EXTMODULE_H
#define RUST_EXTMODULE_H 1

int extm_strtol(const char * strp, long long * valp, int base);
int extm_strtoul(const char * strp, unsigned long long * valp, int base);

#endif

/* extmodule/extmodule.c */
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

int extm_strtol(const char * strp, long long * valp, int base)
{
    long long ret;
    int error = EINVAL;
    char * strend = NULL;

    if (strp == NULL)
        return error;

    errno = 0;
    ret = strtoll(strp, &strend, base);
    error = errno;
    if (error || strend == strp)
        return (error > 0) ? error : EINVAL;

    if (valp != NULL)
        *valp = ret;
    return 0;
}

int extm_strtoul(const char * strp, unsigned long long * valp, int base)
{
    int error = EINVAL;
    char * strend = NULL;
    unsigned long long ret;

    if (strp == NULL)
        return error;

    errno = 0;
    ret = strtoull(strp, &strend, base);
    error = errno;
    if (error || strend == strp)
        return (error > 0) ? error : EINVAL;

    if (valp != NULL)
        *valp = ret;
    return 0;
}

之后,筆者對(duì)這兩個(gè)函數(shù)extm_strtol/extm_strtoul進(jìn)一步封裝:

// src/lib.rs
#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]

use std::os::raw::c_int;
use std::os::raw::c_longlong;
use std::os::raw::c_ulonglong;

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

pub fn strtol(x: &str, base: i32) -> Result<i64, std::io::Error> {
    let mut res: c_longlong = 0;
    let y: Vec<u8> = x.as_bytes().iter().cloned().collect();
    let error = unsafe {
        let z = std::ffi::CString::from_vec_unchecked(y);
        extm_strtol(z.as_ptr(), &mut res as *mut c_longlong, base as c_int)
    };
    match error {
        0 => Ok(res as i64),
        _ => Err(std::io::Error::from_raw_os_error(error as i32)),
    }
}

pub fn strtoul(x: &str, base: i32) -> Result<u64, std::io::Error> {
    let mut res: c_ulonglong = 0;
    let y: Vec<u8> = x.as_bytes().iter().cloned().collect();
    let error = unsafe {
        let z = std::ffi::CString::from_vec_unchecked(y);
        extm_strtoul(z.as_ptr(), &mut res as *mut c_ulonglong, base as c_int)
    };
    match error {
        0 => Ok(res as u64),
        _ => Err(std::io::Error::from_raw_os_error(error as i32)),
    }
}

這樣,筆者就得到了兩個(gè)Rust語言版本的字符串到整型的轉(zhuǎn)換函數(shù),strtol/strtoul。接下來就要解決編譯的問題,即將extmodule/extmodule.h頭文件轉(zhuǎn)換為src/lib.rs包含的接口文件,bindings.rs

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

此外還要求在為嵌入式設(shè)備編譯構(gòu)建時(shí),也能夠交叉編譯extmodule模塊。

編寫build.rs自動(dòng)化編譯外部模塊

筆者參考了bindgen的相關(guān)文檔,調(diào)用相關(guān)的binding接口,將extmodule/extmodule.h轉(zhuǎn)換為$(OUT_DIR)目錄下的bindings.rs接口代碼;之后又調(diào)用了make命令行工具,實(shí)現(xiàn)extmodule的(交叉)編譯,生成libextm.so動(dòng)態(tài)庫:

extern crate bindgen;
use std::process::Command;

fn main() {
    // generate binding.rs for extmodule
    let bindings = bindgen::Builder::default()
        .header("extmodule/extmodule.h")
        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
        .generate()
        .expect("Unable to generate bindings");
    let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
    bindings.write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");

    // invoke make to build external C module
    let cc = format!("CC={}", std::env::var("TARGET_CC")
        .unwrap_or_else(|_| "cc".to_string()));
    let cflags = format!("CFLAGS={}", std::env::var("TARGET_CFLAGS")
        .unwrap_or_else(|_| "-Wall -fPIC -D_GNU_SOURCE -Os -ggdb".to_string()));
    let okay = Command::new("make")
        .arg(AsRef::<std::ffi::OsStr>::as_ref(&cc))
        .arg(AsRef::<std::ffi::OsStr>::as_ref(&cflags))
        .args(&["-C", "./extmodule", "-j1", "clean", "all"])
        .spawn()
        .expect("Failed to invoke make utility")
        .wait()
        .expect("Failed to wait make utility")
        .success();
    if !okay {
        eprintln!("Error, make for external C module has failed!");
        std::process::exit(1);
    }

    println!("cargo:rustc-link-lib=extm");
    println!("cargo:rustc-link-search=./extmodule");
    println!("cargo:rerun-if-changed=./extmodule/extmodule.h");
    println!("cargo:rustc-link-arg-bins=-Wl,-rpath=$ORIGIN");
}

在編譯之前,需要為系統(tǒng)安裝clang相關(guān)的依賴,這是bindgen需要的:

sudo apt install clang-14 libclang-14-dev # for ubuntu-22.04

筆者編譯、運(yùn)行bindings工程的輸出結(jié)果如下:

yejq@ubuntu:~/program/bindings$ cargo build --release
   Compiling bindings v0.1.0 (/home/yejq/program/bindings)
    Finished release [optimized] target(s) in 0.73s
yejq@ubuntu:~/program/bindings$ cp -v ./extmodule/libextm.so ./target/release/
'./extmodule/libextm.so' -> './target/release/libextm.so'
yejq@ubuntu:~/program/bindings$ ./target/release/bindings 2099 0x2030
arg0: 2099, arg1: 0x2030
System uptime: 86910
total 0
lrwx------ 1 yejq yejq 64  1月 25 11:35 0 -> /dev/pts/11
lrwx------ 1 yejq yejq 64  1月 25 11:35 1 -> /dev/pts/11
lrwx------ 1 yejq yejq 64  1月 25 11:35 2 -> /dev/pts/11
lr-x------ 1 yejq yejq 64  1月 25 11:35 3 -> /dev/null
lr-x------ 1 yejq yejq 64  1月 25 11:35 4 -> /proc/17644/fd
total 0
lrwx------ 1 yejq yejq 64  1月 25 11:35 0 -> /dev/pts/11
lrwx------ 1 yejq yejq 64  1月 25 11:35 1 -> /dev/pts/11
lrwx------ 1 yejq yejq 64  1月 25 11:35 2 -> /dev/pts/11
lr-x------ 1 yejq yejq 64  1月 25 11:35 3 -> /proc/17645/fd

可以看到,使用extmodule外部模塊,可以很好地解決十六進(jìn)制字符串0x2030轉(zhuǎn)換為整型的問題。

使用bindgen命令行工具轉(zhuǎn)換接口文件

Rust語言、工具鏈開發(fā)者選擇Rust作為自定義編譯構(gòu)建的語言,相應(yīng)的代碼為工作根目錄下的build.rs。該代碼依賴了bindgen庫,將extmodule/extmodule.h轉(zhuǎn)化為Rust編程語言的接口文件,這一依賴在Cargo.toml需要指明:

[dependencies]

[build-dependencies]
bindgen = "0.62.0"

有人可能會(huì)提議,使用build.rs作為自定義編譯構(gòu)建代碼,可能不太方便,因?yàn)槟承┕こ滩划a(chǎn)生以上依賴,而是使用bindgen命令行工具實(shí)現(xiàn)以上C語言頭文件到bindings.rs接口的轉(zhuǎn)換,那么使用shell腳本就更合理。例如build.rs.sh腳本實(shí)現(xiàn)了目前的build.rs所有功能:

#!/bin/bash

# Created by yejq.jiaqiang@gmail.com
# Simple build script for bindtest
# 2023/01/24

# generate bindings.rs source file in `$(OUT_DIR) directory
generate_bindings() {
    if [ ! -d "${OUT_DIR}" ] ; then
        echo "Error, \`\${OUT_DIR} not found." 1>&2
        return 1
    fi
    bindgen -o "${OUT_DIR}/bindings.rs" 'extmodule/extmodule.h'
    return $?
}

compile_extmodule() {
    local COMPILER="${TARGET_CC:-gcc}"
    local C_FLAGS="${TARGET_CFLAGS:--Wall -fPIC -Os -D_GNU_SOURCE -ggdb}"
    make "CC=${COMPILER}" "CFLAGS=${C_FLAGS}" -C extmodule -j1 clean all
    return $?
}

define_rustc_flags() {
    echo "cargo:rustc-link-lib=extm"
    echo "cargo:rustc-link-search=./extmodule"
    echo "cargo:rerun-if-changed=./build.rs.sh"
    echo "cargo:rerun-if-changed=./extmodule/extmodule.h"
    echo "cargo:rustc-link-arg-bins=-Wl,-rpath=\$ORIGIN"
    return 0
}

generate_bindings || exit $?
compile_extmodule || exit $?
define_rustc_flags ; exit 0

該腳本,即簡(jiǎn)潔,又具備很強(qiáng)的擴(kuò)展性,修改起來又比build.rs方便很多;確實(shí)是這樣。那么可以修改build.rs腳本,實(shí)現(xiàn)對(duì)該腳本的一勞永逸的調(diào)用:

diff --git a/Cargo.toml b/Cargo.toml
index a57c279..3b947ae 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,4 +8,4 @@ edition = "2021"
 [dependencies]
 
 [build-dependencies]
-bindgen = "0.62.0"
+libc = { version = "0.2.139" }
diff --git a/build.rs b/build.rs
index 6a2fff1..dddf1f8 100644
--- a/build.rs
+++ b/build.rs
@@ -1,38 +1,17 @@
-extern crate bindgen;
-use std::process::Command;
+use std::ffi::CString;
+use libc::{c_char, execv};
+use std::collections::VecDeque;
 
 fn main() {
-    // generate binding.rs for extmodule
-    let bindings = bindgen::Builder::default()
-        .header("extmodule/extmodule.h")
-        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
-        .generate()
-        .expect("Unable to generate bindings");
-    let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
-    bindings.write_to_file(out_path.join("bindings.rs"))
-        .expect("Couldn't write bindings!");
-
-    // invoke make to build external C module
-    let cc = format!("CC={}", std::env::var("TARGET_CC")
-        .unwrap_or_else(|_| "cc".to_string()));
-    let cflags = format!("CFLAGS={}", std::env::var("TARGET_CFLAGS")
-        .unwrap_or_else(|_| "-Wall -fPIC -D_GNU_SOURCE -Os -ggdb".to_string()));
-    let okay = Command::new("make")
-        .arg(AsRef::<std::ffi::OsStr>::as_ref(&cc))
-        .arg(AsRef::<std::ffi::OsStr>::as_ref(&cflags))
-        .args(&["-C", "./extmodule", "-j1", "clean", "all"])
-        .spawn()
-        .expect("Failed to invoke make utility")
-        .wait()
-        .expect("Failed to wait make utility")
-        .success();
-    if !okay {
-        eprintln!("Error, make for external C module has failed!");
-        std::process::exit(1);
-    }
-
-    println!("cargo:rustc-link-lib=extm");
-    println!("cargo:rustc-link-search=./extmodule");
-    println!("cargo:rerun-if-changed=./extmodule/extmodule.h");
-    println!("cargo:rustc-link-arg-bins=-Wl,-rpath=$ORIGIN");
+    // invoke build.rs.sh script instead
+    let argv: Vec<String> = std::env::args().skip(1).collect();
+    let mut argw: VecDeque<CString> = argv.iter()
+        .map(|x| CString::new(x.as_bytes()).unwrap()).collect();
+    argw.push_front(CString::new("./build.rs.sh").unwrap());
+    let mut argx: Vec<*const c_char> = argw.iter().map(|y| y.as_ptr()).collect();
+    argx.push(std::ptr::null());
+    unsafe { execv(argx[0], argx.as_mut_ptr()) };
+    eprintln!("Error, failed to invoke ./build.rs.sh: {:?}",
+        std::io::Error::last_os_error());
+    std::process::exit(1);
 }

簡(jiǎn)單的C語言頭文件

以上的編譯構(gòu)建,考慮到了對(duì)嵌入式設(shè)備支持。主要是在build.rs(或build.rs.sh)訪問TARGET_CC/TARGET_CFLAGS兩個(gè)與交叉編譯相關(guān)的環(huán)境變量。不過,值得說明的是,對(duì)于簡(jiǎn)單的C語言頭文件(例如筆者編寫的extmodule/extmodule.h)可以這樣轉(zhuǎn)換,但對(duì)于復(fù)雜的開源庫,交叉編譯時(shí),因其頭文件比較復(fù)雜,這種基于bindgen的接口轉(zhuǎn)換常常是不可用的。舉個(gè)例子,對(duì)于開源的paho.mqtt.rust軟件,因其依賴了paho.mqtt.c庫,在交叉編譯時(shí),就會(huì)使用該工程自己維護(hù)的bindings接口代碼,而不是使用bindgen來轉(zhuǎn)換:

yejq@ubuntu:~/program/paho.mqtt.rust/paho-mqtt-sys/bindings$ ls -lh
total 1.7M
-rw-rw-r-- 1 ubuntu ubuntu 267K Jan 25 11:36 bindings_paho_mqtt_c_1.3.12-aarch64-unknown-linux-gnu.rs
-rw-rw-r-- 1 ubuntu ubuntu 216K Jan 25 11:36 bindings_paho_mqtt_c_1.3.12-armv7-unknown-linux-gnueabihf.rs
-rw-rw-r-- 1 ubuntu ubuntu 216K Jan 25 11:36 bindings_paho_mqtt_c_1.3.12-default-32.rs
-rw-rw-r-- 1 ubuntu ubuntu 265K Jan 25 11:36 bindings_paho_mqtt_c_1.3.12-default-64.rs
-rw-rw-r-- 1 ubuntu ubuntu 281K Jan 25 11:36 bindings_paho_mqtt_c_1.3.12-x86_64-apple-darwin.rs
-rw-rw-r-- 1 ubuntu ubuntu 211K Jan 25 11:36 bindings_paho_mqtt_c_1.3.12-x86_64-pc-windows-msvc.rs
-rw-rw-r-- 1 ubuntu ubuntu 265K Jan 25 11:36 bindings_paho_mqtt_c_1.3.12-x86_64-unknown-linux-gnu.rs
drwxrwxr-x 2 ubuntu ubuntu 4.0K Jan 25 11:36 old

雖然如此,我們?cè)谇度胧杰浖_發(fā)時(shí),可以編寫易于轉(zhuǎn)換的C語言頭文件,這就需要我們?cè)趯?shí)際開發(fā)中不斷調(diào)整頭文件的編寫。

到此這篇關(guān)于使用bindgen將C語言頭文件轉(zhuǎn)換為Rust接口代碼的文章就介紹到這了,更多相關(guān)C語言頭文件轉(zhuǎn)換為Rust接口內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • rust語言基礎(chǔ)pub關(guān)鍵字及Some語法示例

    rust語言基礎(chǔ)pub關(guān)鍵字及Some語法示例

    這篇文章主要為大家介紹了rust語言基礎(chǔ)pub關(guān)鍵字及Some語法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • Rust?中?Deref?Coercion講解

    Rust?中?Deref?Coercion講解

    Rust 的設(shè)計(jì)理念一向是顯式比隱式好,也就是說所有的行為盡量在代碼中表現(xiàn)出來,這篇文章主要介紹了Rust?中?Deref?Coercion?介紹,需要的朋友可以參考下
    2022-10-10
  • Rust突破編譯器限制構(gòu)造可修改的全局變量

    Rust突破編譯器限制構(gòu)造可修改的全局變量

    這篇文章主要為大家介紹了Rust突破編譯器限制構(gòu)造可修改的全局變量示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • Rust Option類型基本使用詳解

    Rust Option類型基本使用詳解

    Rust的Option是一種強(qiáng)大的類型,用于處理可能為空的情況,避免了許多空值引起的運(yùn)行時(shí)錯(cuò)誤,本文介紹Rust Option類型詳解,感興趣的朋友一起看看吧
    2024-02-02
  • rust中trait的使用方法詳解

    rust中trait的使用方法詳解

    trait用中文來講就是特征,它就是一個(gè)標(biāo)記,只不過這個(gè)標(biāo)記被用在特定的地方,也就是類型參數(shù)的后面,下面我們就來學(xué)習(xí)一下trait的具體使用方法吧
    2023-12-12
  • Rust Atomics and Locks并發(fā)基礎(chǔ)理解

    Rust Atomics and Locks并發(fā)基礎(chǔ)理解

    這篇文章主要為大家介紹了Rust Atomics and Locks并發(fā)基礎(chǔ)理解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-02-02
  • Rust 連接 SQLite 數(shù)據(jù)庫的過程解析

    Rust 連接 SQLite 數(shù)據(jù)庫的過程解析

    本文通過一個(gè)例子給大家介紹了Rust 連接 SQLite 數(shù)據(jù)庫的詳細(xì)過程,我使用rusqlite這個(gè)crate,對(duì)Rust 連接 SQLite 數(shù)據(jù)庫相關(guān)知識(shí)感興趣的朋友跟隨小編一起看看吧
    2022-01-01
  • 聊聊Rust 運(yùn)算符

    聊聊Rust 運(yùn)算符

    運(yùn)算符 用于對(duì)數(shù)據(jù)執(zhí)行一些操作。被運(yùn)算符執(zhí)行操作的數(shù)據(jù)我們稱之為操作數(shù)。下面通過本文給大家介紹Rust 運(yùn)算符的相關(guān)知識(shí),感興趣的朋友一起看看吧
    2021-11-11
  • 在Rust?web服務(wù)中使用Redis的方法

    在Rust?web服務(wù)中使用Redis的方法

    這篇文章主要介紹了在Rust?web服務(wù)中使用Redis,在這篇文章中,我們將演示如何在一個(gè)Rust?web應(yīng)用程序中使用Redis,需要的朋友可以參考下
    2022-08-08
  • 一文弄懂rust生命周期

    一文弄懂rust生命周期

    生命周期是Rust語言中的一個(gè)概念,用于決內(nèi)存安全問題,本文主要介紹了一文弄懂rust生命周期,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-12-12

最新評(píng)論