Skip to main content

win32_round_trip/
win32_round_trip.rs

1// Copyright (c) 2026 Mike Grier
2//! A wide (`*W`) Win32 round trip with no string conversion in either
3//! direction.
4//!
5//! Run it with:
6//!
7//! ```text
8//! cargo run --example win32_round_trip
9//! ```
10//!
11//! It exercises all three halves of the FFI surface against real kernel32
12//! entry points:
13//!
14//! * **terminated input** -- `as_terminated_ptr()` into `GetFullPathNameW`'s
15//!   `LPCWSTR` parameter;
16//! * **buffer-fill output** -- `with_capacity` / `as_mut_ptr` /
17//!   `set_len_from_ffi` to receive that call's result;
18//! * **counted input** -- `as_ptr()` + `len()` into `CompareStringOrdinal`,
19//!   which takes explicit lengths and so works on borrowed slices that carry
20//!   no terminator at all.
21//!
22//! The entry points are declared inline rather than pulled from `windows-sys`,
23//! purely to keep the crate dependency-free.
24
25#[cfg(windows)]
26fn main() {
27    windows::run();
28}
29
30#[cfg(not(windows))]
31fn main() {
32    eprintln!("win32_round_trip is Windows-only; nothing to demonstrate here.");
33}
34
35#[cfg(windows)]
36mod windows {
37    use wtf_string::{Wtf16Str, Wtf16String};
38
39    #[link(name = "kernel32")]
40    unsafe extern "system" {
41        /// Expands `lpfilename` to a full path. With `nbufferlength == 0` it
42        /// reports the required size *including* the terminator; on success it
43        /// returns the count written *excluding* it. Zero means failure.
44        fn GetFullPathNameW(
45            lpfilename: *const u16,
46            nbufferlength: u32,
47            lpbuffer: *mut u16,
48            lpfilepart: *mut *mut u16,
49        ) -> u32;
50
51        /// Ordinal comparison of two *counted* wide strings: neither pointer
52        /// needs to be NUL-terminated.
53        fn CompareStringOrdinal(
54            lpstring1: *const u16,
55            cchcount1: i32,
56            lpstring2: *const u16,
57            cchcount2: i32,
58            bignorecase: i32,
59        ) -> i32;
60    }
61
62    /// `CompareStringOrdinal` returns these rather than the usual -1/0/1.
63    const CSTR_LESS_THAN: i32 = 1;
64    const CSTR_EQUAL: i32 = 2;
65    const CSTR_GREATER_THAN: i32 = 3;
66
67    pub fn run() {
68        let input = Wtf16String::from(r"C:\Windows\System32\..\Temp");
69        println!("input : {input}");
70
71        match full_path(&input) {
72            Some(expanded) => println!("expanded: {expanded}"),
73            None => println!("expanded: <GetFullPathNameW failed>"),
74        }
75
76        compare(&Wtf16String::from("alpha"), &Wtf16String::from("beta"));
77        compare(&Wtf16String::from("beta"), &Wtf16String::from("alpha"));
78        compare(&Wtf16String::from("same"), &Wtf16String::from("same"));
79
80        // The counted pair works on a borrowed slice, which has no terminator
81        // of its own -- the length is what makes it well-defined.
82        let units: Vec<u16> = "borrowed".encode_utf16().collect();
83        let borrowed = Wtf16Str::from_units(&units);
84        println!(
85            "borrowed slice of {} units compares equal to itself: {}",
86            borrowed.len(),
87            ordinal(borrowed, borrowed) == CSTR_EQUAL
88        );
89    }
90
91    /// Terminated input, then buffer-fill output -- both without converting.
92    fn full_path(input: &Wtf16String) -> Option<Wtf16String> {
93        // Pass 1: ask for the size. The terminator is already in the buffer, so
94        // handing over an `LPCWSTR` costs nothing.
95        // SAFETY: `as_terminated_ptr` is NUL-terminated and valid while
96        // `input` is borrowed; a zero length asks for the required size only.
97        let needed = unsafe {
98            GetFullPathNameW(
99                input.as_terminated_ptr(),
100                0,
101                core::ptr::null_mut(),
102                core::ptr::null_mut(),
103            )
104        };
105        if needed == 0 {
106            return None;
107        }
108
109        // `needed` counts the terminator; our capacity is a *content* length,
110        // and `with_capacity` reserves the terminator slot itself.
111        let mut out = Wtf16String::with_capacity(needed as usize - 1);
112
113        // Pass 2: let the API write straight into our buffer.
114        // SAFETY: the buffer has room for `needed` units (content + the
115        // reserved terminator slot), which is exactly what pass 1 asked for.
116        let written = unsafe {
117            GetFullPathNameW(
118                input.as_terminated_ptr(),
119                needed,
120                out.as_mut_ptr(),
121                core::ptr::null_mut(),
122            )
123        };
124        if written == 0 || written >= needed {
125            // Failed, or raced a directory change and now wants more room.
126            // `out`'s invariant is still broken here, so republish an empty
127            // string before dropping it (see `as_mut_ptr`'s contract).
128            // SAFETY: publishing zero content units is always in bounds.
129            unsafe { out.set_len_from_ffi(0) };
130            return None;
131        }
132
133        // `written` excludes the terminator, which is precisely the content
134        // length `set_len_from_ffi` wants -- no guessing about conventions.
135        // SAFETY: the API initialized `written` units and `written < needed`,
136        // so the appended terminator still fits.
137        unsafe { out.set_len_from_ffi(written as usize) };
138        Some(out)
139    }
140
141    /// Counted input: pointer + length, no terminator required.
142    fn ordinal(a: &Wtf16Str, b: &Wtf16Str) -> i32 {
143        // SAFETY: each pointer is valid for exactly its own `len()` units while
144        // borrowed, which is the contract `CompareStringOrdinal` expects.
145        unsafe {
146            CompareStringOrdinal(
147                a.as_ptr(),
148                a.len() as i32,
149                b.as_ptr(),
150                b.len() as i32,
151                0, // case-sensitive
152            )
153        }
154    }
155
156    fn compare(a: &Wtf16String, b: &Wtf16String) {
157        let verdict = match ordinal(a, b) {
158            CSTR_LESS_THAN => "<",
159            CSTR_EQUAL => "==",
160            CSTR_GREATER_THAN => ">",
161            _ => "?(failed)",
162        };
163        println!("compare: {a} {verdict} {b}");
164    }
165}