windows_helpers/
res_guard.rs

1use crate::{windows, Null};
2use std::ops::Deref;
3
4/// Holds a resource and a free-function (like a non-capturing closure) that is called when the guard is dropped.
5///
6/// Allows to couple resource acquisition and freeing, while treating the guard as the contained resource and ensuring freeing will happen. When writing the code, it's also nice to transfer the documentation into everything that has to happen in one go without having to split it into upper and lower or here- and there-code. In a function, Rust's drop order should ensure that later aquired resources are freed first.
7///
8/// For functions ending in Windows API function names (differently cased, like `..._destroy_icon()`), you have to activate crate features. First, see the repository's read-me. Then, derive the needed features from the Windows API function and the handle type the instance manages.
9pub struct ResGuard<R: Copy> {
10    resource: R,
11    free_fn: fn(R),
12}
13
14impl<R: Copy> ResGuard<R> {
15    pub fn new(resource: R, free: fn(R)) -> Self {
16        //! Should normally not be needed.
17
18        Self {
19            resource: resource,
20            free_fn: free,
21        }
22    }
23
24    pub fn with_acquisition<A, E>(acquire: A, free: fn(R)) -> Result<Self, E>
25    where
26        A: FnOnce() -> Result<R, E>,
27    {
28        //! For use with functions that return the resource.
29
30        Ok(Self {
31            resource: acquire()?,
32            free_fn: free,
33        })
34    }
35
36    pub fn with_mut_acquisition<A, T, E>(acquire: A, free: fn(R)) -> Result<Self, E>
37    where
38        R: Null,
39        A: FnOnce(&mut R) -> Result<T, E>,
40    {
41        //! For use with functions that provide the resource by means of an out-parameter.
42
43        let mut resource = R::NULL;
44        acquire(&mut resource)?;
45
46        Ok(Self {
47            resource,
48            free_fn: free,
49        })
50    }
51
52    pub fn two_with_mut_acquisition<A, T, E>(
53        acquire_both: A,
54        free_first: fn(R),
55        free_second: fn(R),
56    ) -> Result<(Self, Self), E>
57    where
58        R: Null,
59        A: FnOnce(&mut R, &mut R) -> Result<T, E>,
60    {
61        //! For purpose, see [`Self::two_with_mut_acq_and_close_handle()`].
62
63        let mut first_resource = R::NULL;
64        let mut second_resource = R::NULL;
65        acquire_both(&mut first_resource, &mut second_resource)?;
66
67        Ok((
68            Self {
69                resource: first_resource,
70                free_fn: free_first,
71            },
72            Self {
73                resource: second_resource,
74                free_fn: free_second,
75            },
76        ))
77    }
78}
79
80macro_rules! impl_with_acq_and_free_fn {
81    ($type:ty, $with_res:ident, $with_acq:ident, $with_acq_mut:ident, $free_fn:expr) => {
82        impl ResGuard<$type> {
83            const FREE_FN: fn($type) = $free_fn;
84
85            pub fn $with_res(resource: $type) -> Self {
86                Self::new(resource, Self::FREE_FN)
87            }
88
89            pub fn $with_acq<A, E>(acquire: A) -> Result<Self, E>
90            where
91                A: FnOnce() -> Result<$type, E>,
92            {
93                Self::with_acquisition(acquire, Self::FREE_FN)
94            }
95
96            pub fn $with_acq_mut<A, T, E>(acquire: A) -> Result<Self, E>
97            where
98                A: FnOnce(&mut $type) -> Result<T, E>,
99            {
100                Self::with_mut_acquisition(acquire, Self::FREE_FN)
101            }
102        }
103    };
104}
105
106#[cfg(all(feature = "f_Win32_Foundation"))]
107impl_with_acq_and_free_fn!(
108    windows::Win32::Foundation::HANDLE,
109    with_res_and_close_handle,
110    with_acq_and_close_handle,
111    with_mut_acq_and_close_handle,
112    |handle| {
113        let _ = unsafe { windows::Win32::Foundation::CloseHandle(handle) };
114    }
115);
116
117#[cfg(all(feature = "f_Win32_Foundation", feature = "f_Win32_Graphics_Gdi"))]
118impl_with_acq_and_free_fn!(
119    windows::Win32::Graphics::Gdi::HBITMAP,
120    with_res_and_delete_object,
121    with_acq_and_delete_object,
122    with_mut_acq_and_delete_object,
123    |h_bitmap| {
124        unsafe { windows::Win32::Graphics::Gdi::DeleteObject(h_bitmap) };
125    }
126);
127
128#[cfg(all(feature = "f_Win32_Foundation", feature = "f_Win32_Graphics_Gdi"))]
129impl_with_acq_and_free_fn!(
130    windows::Win32::Graphics::Gdi::HBRUSH,
131    with_res_and_delete_object,
132    with_acq_and_delete_object,
133    with_mut_acq_and_delete_object,
134    |h_brush| {
135        unsafe { windows::Win32::Graphics::Gdi::DeleteObject(h_brush) };
136    }
137);
138
139#[cfg(feature = "windows_v0_48")]
140#[cfg(all(feature = "f_Win32_Foundation", feature = "f_Win32_Graphics_Gdi"))]
141impl_with_acq_and_free_fn!(
142    windows::Win32::Graphics::Gdi::CreatedHDC,
143    with_res_and_delete_dc,
144    with_acq_and_delete_dc,
145    with_mut_acq_and_delete_dc,
146    |h_dc| {
147        unsafe { windows::Win32::Graphics::Gdi::DeleteDC(h_dc) };
148    }
149);
150
151#[cfg(not(feature = "windows_v0_48"))]
152#[cfg(all(feature = "f_Win32_Foundation", feature = "f_Win32_Graphics_Gdi"))]
153impl_with_acq_and_free_fn!(
154    windows::Win32::Graphics::Gdi::HDC,
155    with_res_and_delete_dc,
156    with_acq_and_delete_dc,
157    with_mut_acq_and_delete_dc,
158    |h_dc| {
159        unsafe { windows::Win32::Graphics::Gdi::DeleteDC(h_dc) };
160    }
161);
162
163#[cfg(all(feature = "f_Win32_Foundation", feature = "f_Win32_Graphics_Gdi"))]
164impl_with_acq_and_free_fn!(
165    windows::Win32::Graphics::Gdi::HFONT,
166    with_res_and_delete_object,
167    with_acq_and_delete_object,
168    with_mut_acq_and_delete_object,
169    |h_font| {
170        unsafe { windows::Win32::Graphics::Gdi::DeleteObject(h_font) };
171    }
172);
173
174#[cfg(all(feature = "f_Win32_Foundation", feature = "f_Win32_Graphics_Gdi"))]
175impl_with_acq_and_free_fn!(
176    windows::Win32::Graphics::Gdi::HGDIOBJ,
177    with_res_and_delete_object,
178    with_acq_and_delete_object,
179    with_mut_acq_and_delete_object,
180    |h_gdi_obj| {
181        unsafe { windows::Win32::Graphics::Gdi::DeleteObject(h_gdi_obj) };
182    }
183);
184
185#[cfg(feature = "windows_v0_48")]
186#[cfg(all(feature = "f_Win32_Foundation", feature = "f_Win32_System_Memory"))]
187impl_with_acq_and_free_fn!(
188    windows::Win32::Foundation::HGLOBAL,
189    with_res_and_global_free,
190    with_acq_and_global_free,
191    with_mut_acq_and_global_free,
192    |h_global| {
193        let _ = unsafe { windows::Win32::System::Memory::GlobalFree(h_global) };
194    }
195);
196
197#[cfg(not(feature = "windows_v0_48"))]
198#[cfg(all(feature = "f_Win32_Foundation"))]
199impl_with_acq_and_free_fn!(
200    windows::Win32::Foundation::HGLOBAL,
201    with_res_and_global_free,
202    with_acq_and_global_free,
203    with_mut_acq_and_global_free,
204    |h_global| {
205        let _ = unsafe { windows::Win32::Foundation::GlobalFree(h_global) };
206    }
207);
208
209#[cfg(all(
210    feature = "f_Win32_Foundation",
211    feature = "f_Win32_UI_WindowsAndMessaging"
212))]
213impl_with_acq_and_free_fn!(
214    windows::Win32::UI::WindowsAndMessaging::HICON,
215    with_res_and_destroy_icon,
216    with_acq_and_destroy_icon,
217    with_mut_acq_and_destroy_icon,
218    |h_icon| {
219        let _ = unsafe { windows::Win32::UI::WindowsAndMessaging::DestroyIcon(h_icon) };
220    }
221);
222
223#[cfg(feature = "windows_v0_48")]
224#[cfg(all(feature = "f_Win32_Foundation", feature = "f_Win32_System_Memory"))]
225impl_with_acq_and_free_fn!(
226    windows::Win32::Foundation::HLOCAL,
227    with_res_and_local_free,
228    with_acq_and_local_free,
229    with_mut_acq_and_local_free,
230    |h_local| {
231        let _ = unsafe { windows::Win32::System::Memory::LocalFree(h_local) };
232    }
233);
234
235#[cfg(not(feature = "windows_v0_48"))]
236#[cfg(all(feature = "f_Win32_Foundation"))]
237impl_with_acq_and_free_fn!(
238    windows::Win32::Foundation::HLOCAL,
239    with_res_and_local_free,
240    with_acq_and_local_free,
241    with_mut_acq_and_local_free,
242    |h_local| {
243        let _ = unsafe { windows::Win32::Foundation::LocalFree(h_local) };
244    }
245);
246
247#[cfg(all(
248    feature = "f_Win32_Foundation",
249    feature = "f_Win32_UI_WindowsAndMessaging"
250))]
251impl_with_acq_and_free_fn!(
252    windows::Win32::UI::WindowsAndMessaging::HMENU,
253    with_res_and_destroy_menu,
254    with_acq_and_destroy_menu,
255    with_mut_acq_and_destroy_menu,
256    |h_menu| {
257        let _ = unsafe { windows::Win32::UI::WindowsAndMessaging::DestroyMenu(h_menu) };
258    }
259);
260
261#[cfg(feature = "windows_v0_48")]
262#[cfg(all(
263    feature = "f_Win32_Foundation",
264    feature = "f_Win32_System_LibraryLoader"
265))]
266impl_with_acq_and_free_fn!(
267    windows::Win32::Foundation::HMODULE,
268    with_res_and_free_library,
269    with_acq_and_free_library,
270    with_mut_acq_and_free_library,
271    |h_module| {
272        let _ = unsafe { windows::Win32::System::LibraryLoader::FreeLibrary(h_module) };
273    }
274);
275
276#[cfg(not(feature = "windows_v0_48"))]
277#[cfg(all(feature = "f_Win32_Foundation"))]
278impl_with_acq_and_free_fn!(
279    windows::Win32::Foundation::HMODULE,
280    with_res_and_free_library,
281    with_acq_and_free_library,
282    with_mut_acq_and_free_library,
283    |h_module| {
284        let _ = unsafe { windows::Win32::Foundation::FreeLibrary(h_module) };
285    }
286);
287
288#[cfg(all(feature = "f_Win32_Foundation", feature = "f_Win32_Graphics_Gdi"))]
289impl_with_acq_and_free_fn!(
290    windows::Win32::Graphics::Gdi::HPALETTE,
291    with_res_and_delete_object,
292    with_acq_and_delete_object,
293    with_mut_acq_and_delete_object,
294    |h_palette| {
295        unsafe { windows::Win32::Graphics::Gdi::DeleteObject(h_palette) };
296    }
297);
298
299#[cfg(all(feature = "f_Win32_Foundation", feature = "f_Win32_Graphics_Gdi"))]
300impl_with_acq_and_free_fn!(
301    windows::Win32::Graphics::Gdi::HPEN,
302    with_res_and_delete_object,
303    with_acq_and_delete_object,
304    with_mut_acq_and_delete_object,
305    |h_pen| {
306        unsafe { windows::Win32::Graphics::Gdi::DeleteObject(h_pen) };
307    }
308);
309
310#[cfg(all(feature = "f_Win32_Foundation", feature = "f_Win32_System_Power"))]
311impl_with_acq_and_free_fn!(
312    windows::Win32::System::Power::HPOWERNOTIFY,
313    with_res_and_unregister_power_setting_notification,
314    with_acq_and_unregister_power_setting_notification,
315    with_mut_acq_and_unregister_power_setting_notification,
316    |h_power_notify| {
317        let _ = unsafe {
318            windows::Win32::System::Power::UnregisterPowerSettingNotification(h_power_notify)
319        };
320    }
321);
322
323#[cfg(all(feature = "f_Win32_Foundation", feature = "f_Win32_Graphics_Gdi"))]
324impl_with_acq_and_free_fn!(
325    windows::Win32::Graphics::Gdi::HRGN,
326    with_res_and_delete_object,
327    with_acq_and_delete_object,
328    with_mut_acq_and_delete_object,
329    |h_rgn| {
330        unsafe { windows::Win32::Graphics::Gdi::DeleteObject(h_rgn) };
331    }
332);
333
334#[cfg(feature = "windows_v0_48")]
335#[cfg(all(feature = "f_Win32_Foundation", feature = "f_Win32_System_Memory"))]
336impl_with_acq_and_free_fn!(
337    windows::core::PWSTR,
338    with_res_and_local_free,
339    with_acq_and_local_free,
340    with_mut_acq_and_local_free,
341    |pwstr| {
342        let _ = unsafe {
343            windows::Win32::System::Memory::LocalFree(windows::Win32::Foundation::HLOCAL(
344                pwstr.0 as _,
345            ))
346        };
347    }
348);
349
350//. Useful for functions like `ConvertSidToStringSidW()` and `FormatMessageW()`, which allocate for you and are documented to require a call to `LocalFree()`.
351#[cfg(not(feature = "windows_v0_48"))]
352#[cfg(all(feature = "f_Win32_Foundation"))]
353impl_with_acq_and_free_fn!(
354    windows::core::PWSTR,
355    with_res_and_local_free,
356    with_acq_and_local_free,
357    with_mut_acq_and_local_free,
358    |pwstr| {
359        let _ = unsafe {
360            windows::Win32::Foundation::LocalFree(windows::Win32::Foundation::HLOCAL(
361                pwstr.0.cast(),
362            ))
363        };
364    }
365);
366
367#[cfg(feature = "f_Win32_Foundation")]
368impl ResGuard<windows::Win32::Foundation::HANDLE> {
369    // (`FREE_FN` was already defined in previous impl with this type parameter.)
370
371    pub fn two_with_mut_acq_and_close_handle<A, T, E>(acquire_both: A) -> Result<(Self, Self), E>
372    where
373        A: FnOnce(
374            &mut windows::Win32::Foundation::HANDLE,
375            &mut windows::Win32::Foundation::HANDLE,
376        ) -> Result<T, E>,
377    {
378        //! For a function like [`CreatePipe()`][1] that returns two resources at once.
379        //!
380        //! [1]: https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe
381
382        Self::two_with_mut_acquisition(acquire_both, Self::FREE_FN, Self::FREE_FN)
383    }
384}
385
386impl<R: Copy> Deref for ResGuard<R> {
387    type Target = R;
388
389    fn deref(&self) -> &Self::Target {
390        &self.resource
391    }
392}
393
394impl<R: Copy> Drop for ResGuard<R> {
395    fn drop(&mut self) {
396        (self.free_fn)(self.resource);
397    }
398}
399
400#[cfg(all(test, feature = "windows_latest_compatible_all"))]
401mod tests {
402    use super::ResGuard;
403    use crate::{
404        core::{CheckNullError, CheckNumberError},
405        windows, Null,
406    };
407    use std::{mem, ptr};
408    use windows::{
409        core::PCWSTR,
410        Win32::{
411            Foundation::{CloseHandle, COLORREF},
412            Graphics::Gdi::{CreateSolidBrush, GetObjectW, HBRUSH, LOGBRUSH},
413            Storage::FileSystem::{ReadFile, WriteFile},
414            System::{
415                Pipes::CreatePipe,
416                Threading::{CreateEventW, SetEvent},
417            },
418        },
419    };
420
421    #[test]
422    fn new() {
423        let event_handle = unsafe { CreateEventW(None, true, false, PCWSTR::NULL) }
424            .expect("should be able to create event handle");
425        let event_handle = ResGuard::new(event_handle, |handle| {
426            let _ = unsafe { CloseHandle(handle) };
427        });
428
429        assert_eq!(unsafe { SetEvent(*event_handle) }, Ok(()));
430    }
431
432    #[test]
433    fn with_acq_and_close_handle() {
434        let event_handle = ResGuard::with_acq_and_close_handle(|| unsafe {
435            CreateEventW(None, true, false, PCWSTR::NULL)
436        })
437        .expect("should be able to create event handle");
438
439        assert_eq!(unsafe { SetEvent(*event_handle) }, Ok(()));
440    }
441
442    #[test]
443    fn two_with_mut_acq_and_close_handle() {
444        // Acquire pipe handles.
445        let (read_handle, write_handle) =
446            ResGuard::two_with_mut_acq_and_close_handle(|read_handle, write_handle| unsafe {
447                CreatePipe(read_handle, write_handle, None, 0)
448            })
449            .expect("should be able to create pipe handles");
450
451        // Write.
452        let bytes = [123, 45, 67];
453        let mut bytes_written = 0;
454        assert_eq!(
455            unsafe { WriteFile(*write_handle, Some(&bytes), Some(&mut bytes_written), None,) },
456            Ok(())
457        );
458        assert_eq!(bytes_written as usize, bytes.len());
459
460        // Read.
461        let mut buffer = Vec::new();
462        buffer.resize(bytes.len(), 0);
463        let mut bytes_read = 0;
464        assert_eq!(
465            unsafe { ReadFile(*read_handle, Some(&mut buffer), Some(&mut bytes_read), None) },
466            Ok(())
467        );
468        assert_eq!(bytes_read as usize, buffer.len());
469        assert_eq!(buffer, bytes);
470    }
471
472    #[test]
473    fn with_acq_and_delete_object() -> windows::core::Result<()> {
474        //! Tests handle type conversion: `HBRUSH` to `HGDIOBJ`.
475
476        const BGR: u32 = 0x123456;
477
478        let h_brush = ResGuard::<HBRUSH>::with_acq_and_delete_object(|| {
479            unsafe { CreateSolidBrush(COLORREF(BGR)) }.nonnull_or_e_handle()
480        })?;
481
482        let mut log_brush = LOGBRUSH::default();
483        unsafe {
484            GetObjectW(
485                *h_brush,
486                mem::size_of::<LOGBRUSH>() as _,
487                Some(ptr::addr_of_mut!(log_brush).cast()),
488            )
489        }
490        .nonzero_or_e_fail()?;
491
492        assert_eq!(log_brush.lbColor.0, BGR);
493
494        Ok(())
495    }
496}