Skip to main content

nsis_plugin/
macros.rs

1//! The three macros that make up the plug-in surface.
2
3/// Declares the crate to be an NSIS plug-in DLL. Invoke exactly once.
4///
5/// Generates the `DllMain` that stashes the module handle, a `#[panic_handler]`
6/// that terminates rather than unwinding into the exehead, and a
7/// `#[global_allocator]` on the process heap so the DLL links without a C
8/// runtime.
9///
10/// Everything it emits is gated on `target_os = "windows"`, so a plug-in crate
11/// still `cargo check`s and `cargo clippy`s on macOS and Linux. Pair it with
12/// `#![cfg_attr(target_os = "windows", no_std)]`.
13///
14/// ```ignore
15/// nsis_plugin!();       // no_std: panic handler and allocator included
16/// nsis_plugin!(std);    // std build: only DllMain
17/// ```
18///
19/// A `std` build links the C runtime, which owns the PE entry point
20/// (`DllMainCRTStartup`), initialises itself and then calls `DllMain`. So the
21/// `std` form defines `DllMain` instead; defining the entry point too is a
22/// duplicate symbol.
23#[macro_export]
24macro_rules! nsis_plugin {
25	() => {
26		$crate::__nsis_dllmain!(DllMainCRTStartup);
27
28		#[cfg(target_os = "windows")]
29		#[panic_handler]
30		fn __nsis_panic(_info: &::core::panic::PanicInfo) -> ! {
31			$crate::rt::abort()
32		}
33
34		#[cfg(target_os = "windows")]
35		#[global_allocator]
36		static __NSIS_ALLOCATOR: $crate::rt::NsisAllocator = $crate::rt::NsisAllocator;
37
38		$crate::__nsis_unwind_stubs!();
39	};
40	(std) => {
41		$crate::__nsis_dllmain!(DllMain);
42
43		/// Named by the landing pads in the precompiled `std`. Rust's i686
44		/// target expects a DWARF-unwinding mingw, but many i686 mingw builds
45		/// (Homebrew's among them) unwind with SJLJ, and their `libgcc_eh`
46		/// only has `_Unwind_SjLj_Resume`. With `panic = "abort"` nothing
47		/// unwinds, so the landing pads are dead code. On a DWARF toolchain
48		/// this definition keeps the archive member out, and nothing else in a
49		/// `panic = "abort"` DLL asks for it.
50		#[cfg(all(target_os = "windows", target_env = "gnu", target_arch = "x86"))]
51		#[unsafe(no_mangle)]
52		extern "C" fn _Unwind_Resume() -> ! {
53			$crate::rt::unwind_unreachable()
54		}
55	};
56}
57
58/// The symbols a `no_std` plug-in needs in place of `std` and the CRT.
59///
60/// These are expanded into the plug-in crate, not defined in `nsis-plugin`,
61/// and that placement is load-bearing — see the note in `rt.rs`. Nothing
62/// references them in the IR, so as upstream symbols fat LTO would internalise
63/// and drop them, and the DLL would fail to link on exactly these names.
64#[doc(hidden)]
65#[macro_export]
66macro_rules! __nsis_unwind_stubs {
67	() => {
68		/// Named by the unwind tables in the precompiled `core` and `alloc`.
69		#[cfg(target_os = "windows")]
70		#[unsafe(no_mangle)]
71		extern "C" fn rust_eh_personality() {}
72
73		/// The MSVC personality, named by the abort-on-unwind guard the
74		/// compiler attaches to every `extern "C"` export. `vcruntime` defines
75		/// it; `/NODEFAULTLIB` removes `vcruntime`.
76		#[cfg(all(target_os = "windows", target_env = "msvc"))]
77		#[unsafe(no_mangle)]
78		extern "C" fn __CxxFrameHandler3() -> ! {
79			$crate::rt::unwind_unreachable()
80		}
81
82		/// Named by mingw's unwinder on the GNU targets.
83		#[cfg(all(target_os = "windows", target_env = "gnu"))]
84		#[unsafe(no_mangle)]
85		extern "C" fn _Unwind_Resume() -> ! {
86			$crate::rt::unwind_unreachable()
87		}
88	};
89}
90
91#[doc(hidden)]
92#[macro_export]
93macro_rules! __nsis_dllmain {
94	($entry:ident) => {
95		/// PE entry point, or the `DllMain` the CRT's entry point calls.
96		/// Returning 0 would make `LoadLibrary` fail.
97		#[cfg(target_os = "windows")]
98		#[unsafe(no_mangle)]
99		pub extern "system" fn $entry(
100			hinst: *mut ::core::ffi::c_void,
101			reason: u32,
102			_reserved: *mut ::core::ffi::c_void,
103		) -> i32 {
104			const DLL_PROCESS_ATTACH: u32 = 1;
105			if reason == DLL_PROCESS_ATTACH {
106				$crate::rt::set_hinstance(hinst);
107			}
108			1
109		}
110	};
111}
112
113/// Defines one or more plug-in exports.
114///
115/// Each body gets a `&mut Nsis` and returns [`Result<()>`](crate::Result).
116/// Returning `Err` sets `exec_flags->exec_error`, so `IfErrors` works in the
117/// calling script; `Ok` leaves the flag untouched.
118///
119/// The export name is the function name, verbatim and undecorated — that is
120/// what `plugin::Name` resolves to in an `.nsi` script.
121///
122/// ```ignore
123/// nsis_fn! {
124///     fn Add(nsis: &mut Nsis) -> Result<()> {
125///         let b = nsis.stack.pop_int()?;
126///         let a = nsis.stack.pop_int()?;
127///         nsis.stack.push_int(a + b)?;
128///         Ok(())
129///     }
130///
131///     fn Reverse(nsis: &mut Nsis) -> Result<()> {
132///         let s = nsis.stack.pop()?;
133///         nsis.stack.push(&s.chars().rev().collect::<alloc::string::String>())
134///     }
135/// }
136/// ```
137///
138/// Arguments arrive on the stack, not in the signature: the five-pointer
139/// boundary is fixed by NSIS and the same for every export.
140#[macro_export]
141macro_rules! nsis_fn {
142	($(
143		$(#[$attr:meta])*
144		fn $name:ident($nsis:ident: &mut Nsis) -> Result<()> $body:block
145	)*) => {$(
146		$(#[$attr])*
147		#[unsafe(no_mangle)]
148		pub unsafe extern "C" fn $name(
149			hwnd: $crate::Hwnd,
150			string_size: ::core::ffi::c_int,
151			variables: *mut $crate::Tchar,
152			stacktop: *mut *mut $crate::StackNode,
153			extra: *mut $crate::ExtraParameters,
154		) {
155			// A plain nested fn, not a closure: nothing from the environment
156			// can leak in, and `?` cannot escape past `__nsis_body`. The
157			// signature is written with the caller's own `Nsis` and `Result`,
158			// so the source reads as the real thing rather than as a pattern.
159			fn __nsis_body($nsis: &mut Nsis) -> Result<()> $body
160
161			// SAFETY: these are the installer's own arguments, by construction.
162			let mut nsis = unsafe {
163				$crate::Nsis::from_raw(hwnd, string_size, variables, stacktop, extra)
164			};
165			let outcome = __nsis_body(&mut nsis);
166			if outcome.is_err() {
167				nsis.set_error(true);
168			}
169		}
170	)*};
171}
172
173/// Defines an `NSPIM_UNLOAD` callback, to be handed to
174/// [`Nsis::register_callback`](crate::Nsis::register_callback).
175///
176/// This is where cleanup belongs: `/NOUNLOAD` and `SetPluginsUnload` were
177/// deprecated in NSIS 3, and plug-ins now stay loaded for the life of the
178/// installer. `api.h` documents `NSPIM_UNLOAD` as the last message a plug-in
179/// receives.
180///
181/// ```ignore
182/// nsis_unload! {
183///     fn cleanup() {
184///         // release anything held across calls
185///     }
186/// }
187///
188/// nsis_fn! {
189///     fn Init(nsis: &mut Nsis) -> Result<()> {
190///         nsis.register_callback(cleanup)?;
191///         Ok(())
192///     }
193/// }
194/// ```
195#[macro_export]
196macro_rules! nsis_unload {
197	($(
198		$(#[$attr:meta])*
199		fn $name:ident() $body:block
200	)*) => {$(
201		$(#[$attr])*
202		///
203		/// Registered with `RegisterPluginCallback`; not a DLL export.
204		pub unsafe extern "C" fn $name(message: ::core::ffi::c_int) -> usize {
205			if message == $crate::NSPIM_UNLOAD {
206				fn __nsis_unload_body() $body
207				__nsis_unload_body();
208			}
209			// Unknown messages must return 0.
210			0
211		}
212	)*};
213}