Rust 獲取函數地址 裸指針


研究性代碼

alert('doo');

#[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
}

END


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM