本站先前有介紹過在Rust程式語言中使用C/C++的函式庫的方式,而在這篇文章中會來談談反過來的作法,也就是在C/C++程式語言中使用Rust的函式庫。
底下先舉一個最簡單的例子,來說明如何使用C/C++程式語言呼叫Rust的函式庫。
C + Rust語言的Hello World
首先,使用以下指令建立出名為hello-world-lib
的Cargo函式庫專案:
在src
目錄下的lib.rs
中,撰寫以下程式:
pub fn print_hello_world() {
println!("Hello world!");
}
就這樣,我們的Rust函式庫就設計完成了!接下來就是決定要讓哪些函數開放出來,使外部有引用到此Rust函式庫的C/C++程式可以呼叫。當然,在這個例子中,我們只有撰寫一個print_hello_world
函數,所以當然要將它公開出去。
雖然我們已經有對print_hello_world
函數使用pub
關鍵字,但pub
關鍵字只是定義這個函數能夠被外部的Rust crate來呼叫。所以我們必須在pub
關鍵字後面再加上extern
關鍵字,來讓這個函數可以透過函式庫來被呼叫。
程式修改如下:
#[no_mangle]
pub extern fn print_hello_world() {
println!("Hello world!");
}
替使用extern
關鍵字來定義的函數加上#[no_mangle]
屬性是為了要保留函數名稱,確保它可以被C/C++程式呼叫到。
由於Rust的函式庫預設的格式為.rlib
,並非C/C++使用的.a
、.so
、.dll
、.dylib
等。為了要編譯出C/C++語言能用的函式庫,必須要去修改Cargo的設定檔,在[lib]
區塊中,加上crate-type
項目,將其的值設定為["cdylib"]
。如下:
[lib]
crate-type = ["cdylib"]
如果不設定crate-type
,Cargo會根據src
目錄中有無lib.rs
檔案來決定要不要加入rlib
,也就是crate-type = ["rlib"]
。
在crate-type
項目加入cdylib
,可以使Cargo在編譯程式專案時,將能用在C/C++程式的動態函式庫也跟著編譯出來。
將設定檔修改好後,執行cargo build --release
指令,就可以在target/release
目錄中找到動態函式庫檔案啦!目標平台如果是Linux的話,就會看到libhello_world_lib.so
檔案。
如果想要編譯出靜態函式庫,只要在crate-type
項目加入staticlib
,就可以使Cargo在編譯程式專案時,將靜態函式庫也跟著編譯出來。如下:
[lib]
crate-type = ["cdylib", "staticlib"]
目標平台如果是Linux的話,在執行cargo release
指令後,在target/release
目錄中就會看到libhello_world_lib.a
檔案。
撰寫標頭檔
我們可以在src
目錄下加上一個C/C++的hello_world_lib.h
標頭檔,來對應函式庫中可被使用的函數。
寫法如下:
void print_hello_world();
撰寫C/C++程式
接著撰寫C/C++程式碼,檔名為hello_world.c
,存放在Cargo程式專案的根目錄中。
#include "src/hello_world_lib.h"
int main() {
print_hello_world();
return 0;
}
編譯與執行C/C++程式
動態連結
在Cargo程式專案的根目錄中,執行以下指令:
以上指令會使用gcc
來編譯hello_world.c
,並且會去連結target/release
中的hello_world_lib
函式庫,如果動態函式庫和靜態函式庫同時存在(.a
和.so
檔案同時存在),就會使用動態函式庫。編譯完成後,執行檔會被輸出為hello_world
檔案。
在執行使用動態連結的C/C++執行檔時,動態函式庫必須要被登錄到系統環境中。在Linux環境下可以直接使用LD_LIBRARY_PATH
來設定要尋找動態函式庫檔案的目錄,如下圖:
而Windows環境下可以直接將.dll
和執行檔放置在同一個目錄下。
靜態連結
在Cargo程式專案的根目錄中,執行以下指令:
以上指令會使用gcc
來編譯hello_world.c
,並且會去連結靜態函式庫target/release/libhello_world_lib.a
。編譯完成後,執行檔會被輸出為hello_world
檔案。靜態連結另外還要加上-ldl
和-pthread
這兩個參數,因為這些是Rust應用程式基本會使用到的動態函式庫。
Rust型別和C/C++型別的轉換
如果我們需要在Rust程式和C/C++程式間共享記憶體中的變數資料,就必須要透過FFI(Foreign Function Interface,外部函數介面)來完成。
稍微改寫一下剛才寫的Hello World程式來舉例吧!
use std::ffi::CStr;
#[no_mangle]
pub extern fn greet(s: *const std::os::raw::c_char, a: i32, b: i32) {
let c_str: &CStr = unsafe {
CStr::from_ptr(s)
};
println!("Hello {}! {} + {} = {}", c_str.to_str().unwrap(), a, b, a + b);
}
#include <stdint.h>
void greet(char* s, int32_t a, int32_t b);
#include <stdio.h>
#include "src/hello_world_lib.h"
int main() {
char* name = "Rust";
int32_t a = 7, b = 11;
greet(name, a, b);
return 0;
}
重新編譯並執行C/C++程式,程式會輸出: