#declarative-macro #constructor #destructor #ctor #dtor

no-std bin+lib hictor

declarative macro for __attribute__((constructor))/__attribute__((destructor))

7 releases

0.1.6 Nov 17, 2023
0.1.5 Nov 17, 2023
0.1.4 Oct 15, 2023

#962 in Rust patterns


Used in 3 crates

MIT/Apache

13KB
159 lines

hictor

0.1.5

提供三个辅助函数获取命令行参数:

fn args() -> &'static [*const u8];
fn program_invocation_name() -> &'static str;
fn program_invocation_short_name() -> &'static str;

功能介绍

基于声明宏实现gcc支持的__attribute__((constructor))和__attribute__((destructor))的功能.

和ctor crate的差异在于:

  1. 声明宏; ctor是过程宏实现.
  2. 只支持函数; ctor还支持静态变量.
  3. ctor_args, ctor_custom支持获取命令行输入参数; ctor未支持.
  4. dtor完全等同__attribute__((destructor)),windows等平台不支持; ctor封装了atexit.

无论如何,业务代码不能依赖多个初始化函数的调用顺序,其无法保证.

注:仅在linux平台下测试通过.

attribute((constructor))

不关心命令行输入参数,则可以采用无参数的函数实现. 类型必须是unsafe fn().

unsafe fn init() {
    println!("init before main");
}

hictor::ctor!(init);

如果需要获取命令行输入,函数类型必须是unsafe extern "C" fn(i32, *const *const i8)

unsafe extern "C" fn init(argc: i32, argv: *const *const i8) {
    println!("argc = {argc}, program = {:?}", std::ffi::CStr::from_ptr(*argv));
}

hictor::ctor_args!(init);

有的平台下支持更多的参数,比如linux平台下还可以获取环境变量,其函数类型为unsafe extern "C" fn(u32, *const *const u8, *const *const u8).

#[cfg(target_os = "linux")]
unsafe extern "C" fn init(argc: i32, argv: *const *const i8, envs: *const *const i8) {
    println!("argc = {argc}, program = {:?} env.0 = {:?}", std::ffi::CStr::from_ptr(*argv), std::ffi::CStr::from_ptr(*envs));
}

#[cfg(target_os = "linux")]
hictor::ctor_custom!(init);

attribute((destructor))

函数类型必须是unsafe fn(). 注意业务不应该依赖这个机制,因为应用程序可能因多种未知因素异常退出,这些函数都不会被调用到. 一个高可用性的软件应该为这种无法绝对避免的场景做准备,比如启动时做恢复检查或者额外的监控和容错处理程序等.

unsafe fn fini() {
    println!("fini after main");
}

hictor::dtor!(fini);

获取命令行输入

如果不在main函数中,可以获取当前应用程序的命令行输入参数.

fn foo() {
    let args: &'static [*const i8] = hictor::args();
}

说明

其实现原理是定义一个全局变量,初始化为指定的函数指针,并指定在ELF的特殊段(linux下是.init_array)中. 需要定义一个静态变量实现这个功能. 但声明宏中无法同C的宏,组合行号等其他信息来生成一个变量名(concat_idents宏仅在nightly版本可用), 为了避免静态变量的重复定义,宏的实现中 利用函数名的唯一性定义了一个子模块,如果刚好有一个和函数同名的模块存在,则会存在冲突,这种情况下需要你指定一个唯一的模块名.

mod init {
    pub(super) unsafe fn init() {
        println!("init before main");
    }
}

use init::init;
hictor::ctor!(init_mod, init);

No runtime deps