研究性代碼
#[no_mangle]
// extern "C" fn using(f: extern "C" fn()) {
extern "C" fn using(f: *const u8) {
let a = (&f) as *const _ as *const fn();
let b = unsafe { *a };
println!("DLL開始");
b();
println!("DLL結束");
}
extern "C" fn a() {
println!("a()...");
}
#[no_mangle]
extern "C" fn DllMain(hinstDLL: *mut u8, reason: u32, reserved: *mut u8) -> u32 {
match reason {
1 => {
// DLL_PROCESS_ATTACH
println!("連接到進程!");
intro();
}
0 => {
// DLL_PROCESS_DETACH
println!("檢測到進程退出");
}
_ => (),
}
return 1;
}
fn intro() {
let p = a as extern "C" fn ();
println!("p -> {:p}.", p); // 該地址正確,不過類型是fn()
let p = &a as *const _ as *const u8;
println!("p -> {:p}.", p); // 似乎是臨時變量的地址
let p = &(a as extern "C" fn ()) as *const _ as *const *mut u8;
println!("p -> {:p}.", p); // 這個肯定是臨時變量的地址,因為解引用之后正是a函數的地址
println!("p -> {:p}.", unsafe { *p }); // a函數正確的地址
unsafe {
using(*p); // 該函數接收的是一個C函數的地址
}
}
一般結論
函數其實是一個結構體,除了代碼地址以外還附帶其它一些屬性。(更新:這句話說錯了)
一般不建議直接使用裸指針,如果要傳遞一個函數指針給C,可以改寫:
#[link(name = "my_c_program", kind = "dylib")]
extern "C" {
fn SetCallback(callback: *const u8);
}
// 改寫為:
#[link(name = "my_c_program", kind = "dylib")]
extern "C" {
fn SetCallback(callback: fn(input: i32) -> i32);
}
// 就可以直接使用函數名或者閉包傳遞參數給C了:
SetCallback(main);
SetCallback(| input | -> i64 {
return 0;
});
當然,如果非要使用裸指針,那也是可以的:
雖然fn()對象不可以直接強轉為裸指針,不過我們可以使用一個雙重指針,unsafe解引用就可以了:
type HookProc = fn(nCode: i32, wParam: u64, lParam: *const u8) -> i64; // 函數類型
// 首先將函數指針保存在另一個指針變量中:
// 至於這兩種的區別,我們稍后研究
let ppfn = &callback as *const _ as *const *const u8; // 這種做法通常是錯誤的,只有閉包可以這么使用
let ppfn = &(callback as HookProc) as *const _ as *const *const u8;
// 然后解引用:
let pfn: *const u8 = unsafe { *ppfn };
SetCallback(pfn);
callback、&callback、&(callback as HookProc) 的區別
// 由於Rust函數具有可重載等特性,我們用一個靜態閉包代替函數
static callback: fn() = || {
println!("函數運行...\n");
};
#[inline(always)]
fn a() {
let task = std::thread::spawn(|| unsafe {
let ppfn = &callback as *const _ as *const fn();
println!("&callback -> {:p}", ppfn);
println!("解引用&callback -> {:p}", *ppfn);
(*ppfn)();
});
task.join().unwrap();
}
#[inline(always)]
fn b() {
let task = std::thread::spawn(|| unsafe {
let ppfn = &(callback as fn()) as *const _ as *const fn();
println!("&(callback as fn()) -> {:p}", ppfn);
println!("解引用&(callback as fn()) -> {:p}", *ppfn);
(*ppfn)();
});
task.join().unwrap();
}
#[test]
fn it_works() {
println!("我們所需要的裸指針 -> {:p} \n", callback); // 如果callback不是閉包,這里將編譯失敗
a();
a();
b();
b();
}
程序輸出:
我們所需要的裸指針 -> 0x7ff6c3d124f0
&callback -> 0x7ff6c3d96998
解引用&callback -> 0x7ff6c3d124f0
函數運行...
&callback -> 0x7ff6c3d96998 <-----此值保持不變,可能是靜態區數據,或者是堆上的數據
解引用&callback -> 0x7ff6c3d124f0
函數運行...
&(callback as fn()) -> 0x162a8ff468
解引用&(callback as fn()) -> 0x7ff6c3d124f0
函數運行...
&(callback as fn()) -> 0x162a8ffa18 <-----此值發生了變化,即使去掉多線程,與上一次的值相差也是很大,不只一個指針,說明這個結構體不小
解引用&(callback as fn()) -> 0x7ff6c3d124f0
函數運行...
我暫時將&(callback as fn())
理解為等價於以下形式:
#[inline(always)]
fn c() {
let task = std::thread::spawn(|| unsafe {
let inner_var = callback as fn(); <-----fn()對象克隆給了一個中間變量
let ppfn = &(inner_var) as *const _ as *const fn();
println!("&(callback as fn()) -> {:p}", ppfn);
println!("解引用&(callback as fn()) -> {:p}", *ppfn);
(*ppfn)();
});
task.join().unwrap();
}
好了,現在把callback改回函數,完蛋了,a()
運行失敗:
&callback -> 0x7ff64fee7598
解引用&callback -> 0x6361626c6c616326
process didn't exit successfully: `xx.exe it_works --exact --nocapture` (exit code: 0xc0000005, STATUS_ACCESS_VIOLATION)
而且發現b和c也不完全等價,因為指針差的有點遠:
&(callback as fn()) -> 0x7ff79b0b6598
解引用&(callback as fn()) -> 0x7ff79b0372a0
函數運行...
&(callback as fn()) -> 0x7ff79b0b6598
解引用&(callback as fn()) -> 0x7ff79b0372a0
函數運行...
&(inner_var) -> 0x296d4ff760
解引用&(inner_var) -> 0x7ff79b0372a0
函數運行...
&(inner_var) -> 0x296d4ff400
解引用&(inner_var) -> 0x7ff79b0372a0
函數運行...
源碼在這里:
fn callback() {
println!("函數運行...\n");
}
#[inline(always)]
fn b() {
let task = std::thread::spawn(|| unsafe {
let ppfn = &(callback as fn()) as *const _ as *const fn();
println!("&(callback as fn()) -> {:p}", ppfn);
println!("解引用&(callback as fn()) -> {:p}", *ppfn);
(*ppfn)();
});
task.join().unwrap();
}
#[inline(always)]
fn c() {
let task = std::thread::spawn(|| unsafe {
let inner_var = callback as fn();
let ppfn = &(inner_var) as *const _ as *const fn();
println!("&(inner_var) -> {:p}", ppfn);
println!("解引用&(inner_var) -> {:p}", *ppfn);
(*ppfn)();
});
task.join().unwrap();
}
#[test]
fn it_works() {
// println!("我們所需要的裸指針 -> {:p} \n", callback); // 編譯失敗
// a(); // 運行失敗
b();
b();
c();
c();
}
結論
使用以下方式獲取Rust函數原始指針:
let ppfn = &(callback as fn()) as *const _ as *const fn();
let pfn = unsafe { *ppfn };
更新
let pfn: *const u8 = callback as *const _; // 函數可以轉為任意類型的指針
// 指針轉為函數則比較麻煩,雖然它們其實具有相同的值,不過編譯器認為類型完全不一樣,所以需要臨時變量
let f: fn() = *(&pfn as *const _ as *const fn());
// 下面這種寫法看起來帥,但嚴格意義上來說是不正確的,你不能對pfn解引用去得到一個fn變量,因為*callback就是代碼區了,callback就是fn
let pfn = callback as *const fn();
不過最好還是不要使用指針:
use std::os::raw::c_int;
#[link(name = "libtest", kind = "dylib")]
extern {
fn call_raw_fn(proc: extern fn(c_int, c_int) -> c_int);
}
fn main() {
unsafe {
call_raw_fn(foo);
}
}
extern fn foo(a: c_int, b: c_int) -> c_int {
a + b
}