min32-proc 0.1.1

Procedural macros for the min32 crate
Documentation
//! Defines procedural macros for the `min32` crate.
//!
//! You do not need to depend on this directly; you can use `min32` where these macros are imported.

#![warn(missing_docs)]

use proc_macro::TokenStream;
use proc_macro2::TokenTree;
use quote::{quote, ToTokens};

/// Defines the entry point for a Win32 executable.
///
/// The function can be named anything and use any Rust-callable ABI. It can also take either no
/// parameters or these four parameters:
/// * `hInstance: windows_sys::Win32::Foundation::HANDLE`
/// * `hPrevInstance: windows_sys::Win32::Foundation::HANDLE`
/// * `commandLine: *const core::ffi::c_char`
/// * `nShowCmd: core::ffi::c_int`
///
/// It can optionally return an integer of `core::ffi::c_int` to indicate an exit code, but if there
/// is no return value, then it will be treated as always returning `0`.
#[proc_macro_attribute]
pub fn winmain(a: TokenStream, b: TokenStream) -> TokenStream {
    if !a.is_empty() {
        return quote! {
            compile_error!("winmain attribute does not take any parameters");
        }.into()
    };

    let Ok(tokens) = syn::parse::<syn::ItemFn>(b.clone()) else {
        return b;
    };

    let rval = tokens.sig.output.into_token_stream();
    let function_name = &tokens.sig.ident;

    let handle_rval = if !rval.is_empty() && rval.to_string() != "()" {
        TokenTree::Ident(proc_macro2::Ident::new("_rval", proc_macro2::Span::call_site()))
    }
    else {
        TokenTree::Literal(proc_macro2::Literal::i32_suffixed(0))
    };

    let inputs = tokens.sig.inputs.to_token_stream();
    let function_call = if inputs.is_empty() {
        quote! { super::#function_name () }
    }
    else {
        quote! {
            super::#function_name (
                _hInstance as windows_sys::Win32::Foundation::HANDLE,
                _hPrevInstance as windows_sys::Win32::Foundation::HANDLE,
                _commandLine,
                _nShowCmd
            )
        }
    };

    let winmain: TokenStream = quote! {
        mod __private_winmain_impl {
            #[unsafe(no_mangle)]
            extern "system" fn WinMain(
                _hInstance: usize,
                _hPrevInstance: usize,
                _commandLine: *const core::ffi::c_char,
                _nShowCmd: core::ffi::c_int
            ) -> core::ffi::c_int {
                let _rval = unsafe { #function_call };
                #handle_rval as core::ffi::c_int
            }
        }
    }.into();

    TokenStream::from_iter([winmain, b].into_iter())
}


/// Defines the entry point for a Win32 dynamic library.
///
/// The function can be named anything and use any Rust-callable ABI, but it must take these three
/// parameters:
/// * `hInstance: windows_sys::Win32::Foundation::HANDLE`
/// * `fdwReason: u32`
/// * `lpvReserved: *const core::ffi::c_void`
///
/// It can optionally return a `bool`, but if it does not return anything, then it will be treated
/// as always returning `true`.
#[proc_macro_attribute]
pub fn dllmain(a: TokenStream, b: TokenStream) -> TokenStream {
    if !a.is_empty() {
        return quote! {
            compile_error!("dllmain attribute does not take any parameters")
        }.into()
    };

    let Ok(tokens) = syn::parse::<syn::ItemFn>(b.clone()) else {
        return b;
    };

    let rval = tokens.sig.output.into_token_stream();
    let function_name = &tokens.sig.ident;

    let inputs = tokens.sig.inputs.to_token_stream();
    if inputs.is_empty() {
        return quote! {
            compile_error!("DllMain functions must take three parameters");
        }.into()
    };

    let handle_rval = if !rval.is_empty() && rval.to_string() != "()" {
        quote! { _rval as i32 }
    }
    else {
        TokenTree::Literal(proc_macro2::Literal::i32_suffixed(1)).to_token_stream()
    };

    let function_call = quote! {
        super::#function_name (
            _hInstance as windows_sys::Win32::Foundation::HANDLE,
            _fdwReason,
            _lpvReserved
        )
    };

    let dllmain: TokenStream = quote! {
        mod __private_dllmain_impl {
            #[unsafe(no_mangle)]
            extern "system" fn DllMain(
                _hInstance: usize,
                _fdwReason: u32,
                _lpvReserved: *mut core::ffi::c_void
            ) -> core::ffi::c_int {
                let _rval = unsafe { #function_call };
                #handle_rval
            }
        }
    }.into();

    TokenStream::from_iter([dllmain, b].into_iter())
}