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 ::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    /// <https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/nf-atlthunk-atlthunk_allocatedata>.
32    fn AtlThunk_AllocateData() -> *mut AtlThunkData_t;
33
34    /// <https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/nf-atlthunk-atlthunk_datatocode>.
35    fn AtlThunk_DataToCode(thunk: *mut AtlThunkData_t) -> WNDPROC;
36
37    /// <https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/nf-atlthunk-atlthunk_freedata>.
38    fn AtlThunk_FreeData(thunk: *mut AtlThunkData_t);
39
40    /// <https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/nf-atlthunk-atlthunk_initdata>.
41    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
46/// Rust wrapper of [ATL thunk](https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/) type. It is used as
47/// a [window procedure](https://learn.microsoft.com/en-us/windows/win32/winmsg/about-window-procedures) with associated
48/// data.
49pub struct AtlThunk {
50    raw_thunk_ptr: NonNull<AtlThunkData_t>,
51}
52
53impl AtlThunk {
54    /// Creates a new [`AtlThunk`] object. For more information, see document for
55    /// [`AtlThunk_AllocateData`](<https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/nf-atlthunk-atlthunk_allocatedata>).
56    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    /// Creates a new [`AtlThunk`] object from specified window procedure and associated first parameter value. For more
64    /// information, see document for
65    /// [`AtlThunk_AllocateData`](<https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/nf-atlthunk-atlthunk_allocatedata>)
66    /// and
67    /// [`AtlThunk_InitData`](<https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/nf-atlthunk-atlthunk_initdata>).
68    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    /// Returns a wrapped window procedure. The returned function pointer is only valid if the following conditions are
79    /// met:
80    ///
81    /// - Associated data has been set through either [`AtlThunk::try_new_with`] or [`AtlThunk::set_data`].
82    /// - The originating [`AtlThunk`] object has not been dropped.
83    /// - There is no concurrent [`AtlThunk::set_data`] operating on the originating [`AtlThunk`] object.
84    ///
85    /// For more information, see document for
86    /// [`AtlThunk_DataToCode`](<https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/nf-atlthunk-atlthunk_datatocode>).
87    #[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    /// Updates the associated window procedure and data. For more information, see document for
93    /// [`AtlThunk_InitData`](<https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/nf-atlthunk-atlthunk_initdata>).
94    #[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    /// For more information, see document for
109    /// [`AtlThunk_FreeData`](<https://learn.microsoft.com/en-us/windows/win32/api/atlthunk/nf-atlthunk-atlthunk_freedata>).
110    #[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}