研究性代码
#[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 };