1#![cfg(windows)]
2#![no_std]
3
4use ::windows::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM};
7use ::windows::Win32::System::Memory::AtlThunkData_t;
8use ::windows::Win32::UI::WindowsAndMessaging::WNDPROC;
9use core::ffi::c_void;
10use core::mem;
11use core::ptr::NonNull;
12
13pub mod windows {
14 pub use ::windows::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM};
15}
16
17#[cfg_attr(
18 target_arch = "x86",
19 link(
20 name = "atlthunk.dll",
21 kind = "raw-dylib",
22 modifiers = "+verbatim",
23 import_name_type = "undecorated"
24 )
25)]
26#[cfg_attr(
27 not(target_arch = "x86"),
28 link(name = "atlthunk.dll", kind = "raw-dylib", modifiers = "+verbatim")
29)]
30extern "system" {
31 fn AtlThunk_AllocateData() -> *mut AtlThunkData_t;
33
34 fn AtlThunk_DataToCode(thunk: *mut AtlThunkData_t) -> WNDPROC;
36
37 fn AtlThunk_FreeData(thunk: *mut AtlThunkData_t);
39
40 fn AtlThunk_InitData(thunk: *mut AtlThunkData_t, proc: *mut c_void, first_parameter: usize);
42}
43
44type WindowProcedure = unsafe extern "system" fn(HWND, u32, WPARAM, LPARAM) -> LRESULT;
45
46pub struct AtlThunk {
50 raw_thunk_ptr: NonNull<AtlThunkData_t>,
51}
52
53impl AtlThunk {
54 pub fn try_new() -> ::windows::core::Result<Self> {
57 match NonNull::new(unsafe { AtlThunk_AllocateData() }) {
58 None => Err(::windows::core::Error::from_win32()),
59 Some(raw_thunk_ptr) => Ok(Self { raw_thunk_ptr }),
60 }
61 }
62
63 pub fn try_new_with(window_procedure: WindowProcedure, first_parameter: HWND) -> ::windows::core::Result<Self> {
69 let mut result = Self::try_new();
70
71 if let Ok(thunk) = &mut result {
72 thunk.set_data(window_procedure, first_parameter);
73 }
74
75 result
76 }
77
78 #[inline(always)]
88 pub fn as_window_procedure(&self) -> WindowProcedure {
89 unsafe { AtlThunk_DataToCode(self.raw_thunk_ptr.as_ptr()).unwrap_unchecked() }
90 }
91
92 #[inline(always)]
95 pub fn set_data(&mut self, window_procedure: WindowProcedure, first_parameter: HWND) {
96 unsafe {
97 #[expect(clippy::transmutes_expressible_as_ptr_casts, reason = "by-design")]
98 let procedure = mem::transmute::<WindowProcedure, *mut c_void>(window_procedure);
99
100 let first_parameter = mem::transmute::<HWND, usize>(first_parameter);
101
102 AtlThunk_InitData(self.raw_thunk_ptr.as_mut(), procedure, first_parameter);
103 }
104 }
105}
106
107impl Drop for AtlThunk {
108 #[inline(always)]
111 fn drop(&mut self) {
112 unsafe { AtlThunk_FreeData(self.raw_thunk_ptr.as_ptr()) };
113 }
114}
115
116unsafe impl Send for AtlThunk {}
117unsafe impl Sync for AtlThunk {}
118
119#[cfg(test)]
120mod tests {
121 use super::AtlThunk;
122 use windows::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM};
123
124 #[test]
125 fn test_thunk_try_new_with() {
126 unsafe extern "system" fn callback_1(
127 first_parameter: HWND,
128 message: u32,
129 w_param: WPARAM,
130 l_param: LPARAM,
131 ) -> LRESULT {
132 assert_eq!(first_parameter.0 as usize, 2);
133 assert_eq!(message, 3);
134 assert_eq!(w_param.0, 5);
135 assert_eq!(l_param.0, 7);
136
137 LRESULT(11)
138 }
139
140 unsafe extern "system" fn callback_2(
141 first_parameter: HWND,
142 message: u32,
143 w_param: WPARAM,
144 l_param: LPARAM,
145 ) -> LRESULT {
146 assert_eq!(first_parameter.0 as usize, 13);
147 assert_eq!(message, 17);
148 assert_eq!(w_param.0, 19);
149 assert_eq!(l_param.0, 23);
150
151 LRESULT(29)
152 }
153
154 let mut thunk = AtlThunk::try_new_with(callback_1, HWND(2 as _)).unwrap();
155
156 assert_eq!(
157 unsafe { thunk.as_window_procedure()(HWND::default(), 3, WPARAM(5), LPARAM(7)) }.0,
158 11,
159 );
160
161 thunk.set_data(callback_2, HWND(13 as _));
162
163 assert_eq!(
164 unsafe { thunk.as_window_procedure()(HWND::default(), 17, WPARAM(19), LPARAM(23)) }.0,
165 29,
166 );
167 }
168}