atl_thunk/
lib.rs

1#![cfg(windows)]
2#![no_std]
3
4//! Rust wrapper of [ATL thunk](https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/) type.
5
6use ::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    // <https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/nf-atlthunk-atlthunk_allocatedata>.
22    ::windows::core::link!("atlthunk.dll" "system" fn AtlThunk_AllocateData() -> *mut AtlThunkData_t);
23
24    // <https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/nf-atlthunk-atlthunk_datatocode>.
25    ::windows::core::link!("atlthunk.dll" "system" fn AtlThunk_DataToCode(thunk: *mut AtlThunkData_t) -> WNDPROC);
26
27    // <https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/nf-atlthunk-atlthunk_freedata>.
28    ::windows::core::link!("atlthunk.dll" "system" fn AtlThunk_FreeData(thunk: *mut AtlThunkData_t));
29
30    // <https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/nf-atlthunk-atlthunk_initdata>.
31    ::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
36/// Rust wrapper of [ATL thunk](https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/) type. It is used as
37/// a [window procedure](https://learn.microsoft.com/en-us/windows/win32/winmsg/about-window-procedures) with associated
38/// data.
39pub struct AtlThunk {
40    raw_thunk_ptr: NonNull<AtlThunkData_t>,
41}
42
43impl AtlThunk {
44    /// Creates a new [`AtlThunk`] object. For more information, see document for
45    /// [`AtlThunk_AllocateData`](<https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/nf-atlthunk-atlthunk_allocatedata>).
46    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    /// Creates a new [`AtlThunk`] object from specified window procedure and associated first parameter value. For more
54    /// information, see document for
55    /// [`AtlThunk_AllocateData`](<https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/nf-atlthunk-atlthunk_allocatedata>)
56    /// and
57    /// [`AtlThunk_InitData`](<https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/nf-atlthunk-atlthunk_initdata>).
58    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    /// Returns a wrapped window procedure. The returned function pointer is only valid if the following conditions are
69    /// met:
70    ///
71    /// - Associated data has been set through either [`AtlThunk::try_new_with`] or [`AtlThunk::set_data`].
72    /// - The originating [`AtlThunk`] object has not been dropped.
73    /// - There is no concurrent [`AtlThunk::set_data`] operating on the originating [`AtlThunk`] object.
74    ///
75    /// For more information, see document for
76    /// [`AtlThunk_DataToCode`](<https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/nf-atlthunk-atlthunk_datatocode>).
77    #[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    /// Updates the associated window procedure and data. For more information, see document for
83    /// [`AtlThunk_InitData`](<https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/nf-atlthunk-atlthunk_initdata>).
84    #[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    /// For more information, see document for
99    /// [`AtlThunk_FreeData`](<https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/nf-atlthunk-atlthunk_freedata>).
100    #[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}