1#![cfg(windows)]
2#![no_std]
3
4use ::windows::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM};
7use ::windows::Win32::System::Memory::AtlThunkData_t;
8use core::ffi::c_void;
9use core::mem;
10use core::ptr::NonNull;
11
12pub mod windows {
13 pub use ::windows::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM};
14}
15
16mod ffi {
17 use ::windows::Win32::System::Memory::AtlThunkData_t;
18 use ::windows::Win32::UI::WindowsAndMessaging::WNDPROC;
19 use core::ffi::c_void;
20
21 ::windows::core::link!("atlthunk.dll" "system" fn AtlThunk_AllocateData() -> *mut AtlThunkData_t);
23
24 ::windows::core::link!("atlthunk.dll" "system" fn AtlThunk_DataToCode(thunk: *mut AtlThunkData_t) -> WNDPROC);
26
27 ::windows::core::link!("atlthunk.dll" "system" fn AtlThunk_FreeData(thunk: *mut AtlThunkData_t));
29
30 ::windows::core::link!("atlthunk.dll" "system" fn AtlThunk_InitData(thunk: *mut AtlThunkData_t, proc: *mut c_void, first_parameter: usize));
32}
33
34type WindowProcedure = unsafe extern "system" fn(HWND, u32, WPARAM, LPARAM) -> LRESULT;
35
36pub struct AtlThunk {
40 raw_thunk_ptr: NonNull<AtlThunkData_t>,
41}
42
43impl AtlThunk {
44 pub fn try_new() -> ::windows::core::Result<Self> {
47 match NonNull::new(unsafe { ffi::AtlThunk_AllocateData() }) {
48 None => Err(::windows::core::Error::from_thread()),
49 Some(raw_thunk_ptr) => Ok(Self { raw_thunk_ptr }),
50 }
51 }
52
53 pub fn try_new_with(window_procedure: WindowProcedure, first_parameter: HWND) -> ::windows::core::Result<Self> {
59 let mut result = Self::try_new();
60
61 if let Ok(thunk) = &mut result {
62 thunk.set_data(window_procedure, first_parameter);
63 }
64
65 result
66 }
67
68 #[inline(always)]
78 pub fn as_window_procedure(&self) -> WindowProcedure {
79 unsafe { ffi::AtlThunk_DataToCode(self.raw_thunk_ptr.as_ptr()).unwrap_unchecked() }
80 }
81
82 #[inline(always)]
85 pub fn set_data(&mut self, window_procedure: WindowProcedure, first_parameter: HWND) {
86 unsafe {
87 #[expect(clippy::transmutes_expressible_as_ptr_casts, reason = "by-design")]
88 let procedure = mem::transmute::<WindowProcedure, *mut c_void>(window_procedure);
89
90 let first_parameter = mem::transmute::<HWND, usize>(first_parameter);
91
92 ffi::AtlThunk_InitData(self.raw_thunk_ptr.as_mut(), procedure, first_parameter);
93 }
94 }
95}
96
97impl Drop for AtlThunk {
98 #[inline(always)]
101 fn drop(&mut self) {
102 unsafe { ffi::AtlThunk_FreeData(self.raw_thunk_ptr.as_ptr()) };
103 }
104}
105
106unsafe impl Send for AtlThunk {}
107unsafe impl Sync for AtlThunk {}
108
109#[cfg(test)]
110mod tests {
111 use super::AtlThunk;
112 use windows::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM};
113
114 #[test]
115 fn test_thunk_try_new_with() {
116 unsafe extern "system" fn callback_1(
117 first_parameter: HWND,
118 message: u32,
119 w_param: WPARAM,
120 l_param: LPARAM,
121 ) -> LRESULT {
122 assert_eq!(first_parameter.0 as usize, 2);
123 assert_eq!(message, 3);
124 assert_eq!(w_param.0, 5);
125 assert_eq!(l_param.0, 7);
126
127 LRESULT(11)
128 }
129
130 unsafe extern "system" fn callback_2(
131 first_parameter: HWND,
132 message: u32,
133 w_param: WPARAM,
134 l_param: LPARAM,
135 ) -> LRESULT {
136 assert_eq!(first_parameter.0 as usize, 13);
137 assert_eq!(message, 17);
138 assert_eq!(w_param.0, 19);
139 assert_eq!(l_param.0, 23);
140
141 LRESULT(29)
142 }
143
144 let mut thunk = AtlThunk::try_new_with(callback_1, HWND(2 as _)).unwrap();
145
146 assert_eq!(
147 unsafe { thunk.as_window_procedure()(HWND::default(), 3, WPARAM(5), LPARAM(7)) }.0,
148 11,
149 );
150
151 thunk.set_data(callback_2, HWND(13 as _));
152
153 assert_eq!(
154 unsafe { thunk.as_window_procedure()(HWND::default(), 17, WPARAM(19), LPARAM(23)) }.0,
155 29,
156 );
157 }
158}