Skip to content

Commit dd244b5

Browse files
authored
Add macros for creating null-terminated string literals to windows-sys (#2043)
1 parent ba4566c commit dd244b5

File tree

11 files changed

+177
-7
lines changed

11 files changed

+177
-7
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ jobs:
119119
cargo clippy -p sample_kernel_event &&
120120
cargo clippy -p sample_memory_buffer &&
121121
cargo clippy -p sample_message_box &&
122+
cargo clippy -p sample_message_box_sys &&
122123
cargo clippy -p sample_ocr &&
123124
cargo clippy -p sample_overlapped &&
124125
cargo clippy -p sample_rss &&

.github/workflows/test.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ jobs:
102102
cargo test --target ${{ matrix.target }} -p sample_kernel_event &&
103103
cargo test --target ${{ matrix.target }} -p sample_memory_buffer &&
104104
cargo test --target ${{ matrix.target }} -p sample_message_box &&
105+
cargo test --target ${{ matrix.target }} -p sample_message_box_sys &&
105106
cargo test --target ${{ matrix.target }} -p sample_ocr &&
106107
cargo test --target ${{ matrix.target }} -p sample_overlapped &&
107108
cargo test --target ${{ matrix.target }} -p sample_rss &&
@@ -129,8 +130,8 @@ jobs:
129130
cargo test --target ${{ matrix.target }} -p test_class_factory &&
130131
cargo test --target ${{ matrix.target }} -p test_component &&
131132
cargo test --target ${{ matrix.target }} -p test_component_client &&
132-
cargo test --target ${{ matrix.target }} -p test_const_fields &&
133133
cargo clean &&
134+
cargo test --target ${{ matrix.target }} -p test_const_fields &&
134135
cargo test --target ${{ matrix.target }} -p test_core &&
135136
cargo test --target ${{ matrix.target }} -p test_data_object &&
136137
cargo test --target ${{ matrix.target }} -p test_debug &&

crates/libs/sys/src/core/literals.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/// A literal UTF-8 string with a trailing null terminator.
2+
#[macro_export]
3+
macro_rules! s {
4+
($s:literal) => {
5+
::core::concat!($s, '\0').as_ptr()
6+
};
7+
}
8+
9+
/// A literal UTF-16 wide string with a trailing null terminator.
10+
#[macro_export]
11+
macro_rules! w {
12+
($s:literal) => {{
13+
const INPUT: &[u8] = $s.as_bytes();
14+
const OUTPUT_LEN: usize = $crate::core::utf16_len(INPUT) + 1;
15+
const OUTPUT: &[u16; OUTPUT_LEN] = {
16+
let mut buffer = [0; OUTPUT_LEN];
17+
let mut input_pos = 0;
18+
let mut output_pos = 0;
19+
while let Some((mut code_point, new_pos)) = $crate::core::decode_utf8_char(INPUT, input_pos) {
20+
input_pos = new_pos;
21+
if code_point <= 0xffff {
22+
buffer[output_pos] = code_point as u16;
23+
output_pos += 1;
24+
} else {
25+
code_point -= 0x10000;
26+
buffer[output_pos] = 0xd800 + (code_point >> 10) as u16;
27+
output_pos += 1;
28+
buffer[output_pos] = 0xdc00 + (code_point & 0x3ff) as u16;
29+
output_pos += 1;
30+
}
31+
}
32+
&{ buffer }
33+
};
34+
OUTPUT.as_ptr()
35+
}};
36+
}
37+
38+
// Ensures that the macros are exported from the `windows::core` module.
39+
pub use s;
40+
pub use w;
41+
42+
#[doc(hidden)]
43+
pub const fn decode_utf8_char(bytes: &[u8], mut pos: usize) -> Option<(u32, usize)> {
44+
if bytes.len() == pos {
45+
return None;
46+
}
47+
let ch = bytes[pos] as u32;
48+
pos += 1;
49+
if ch <= 0x7f {
50+
return Some((ch, pos));
51+
}
52+
if (ch & 0xe0) == 0xc0 {
53+
if bytes.len() - pos < 1 {
54+
return None;
55+
}
56+
let ch2 = bytes[pos] as u32;
57+
pos += 1;
58+
if (ch2 & 0xc0) != 0x80 {
59+
return None;
60+
}
61+
let result: u32 = ((ch & 0x1f) << 6) | (ch2 & 0x3f);
62+
if result <= 0x7f {
63+
return None;
64+
}
65+
return Some((result, pos));
66+
}
67+
if (ch & 0xf0) == 0xe0 {
68+
if bytes.len() - pos < 2 {
69+
return None;
70+
}
71+
let ch2 = bytes[pos] as u32;
72+
pos += 1;
73+
let ch3 = bytes[pos] as u32;
74+
pos += 1;
75+
if (ch2 & 0xc0) != 0x80 || (ch3 & 0xc0) != 0x80 {
76+
return None;
77+
}
78+
let result = ((ch & 0x0f) << 12) | ((ch2 & 0x3f) << 6) | (ch3 & 0x3f);
79+
if result <= 0x7ff || (0xd800 <= result && result <= 0xdfff) {
80+
return None;
81+
}
82+
return Some((result, pos));
83+
}
84+
if (ch & 0xf8) == 0xf0 {
85+
if bytes.len() - pos < 3 {
86+
return None;
87+
}
88+
let ch2 = bytes[pos] as u32;
89+
pos += 1;
90+
let ch3 = bytes[pos] as u32;
91+
pos += 1;
92+
let ch4 = bytes[pos] as u32;
93+
pos += 1;
94+
if (ch2 & 0xc0) != 0x80 || (ch3 & 0xc0) != 0x80 || (ch4 & 0xc0) != 0x80 {
95+
return None;
96+
}
97+
let result = ((ch & 0x07) << 18) | ((ch2 & 0x3f) << 12) | ((ch3 & 0x3f) << 6) | (ch4 & 0x3f);
98+
if result <= 0xffff || 0x10ffff < result {
99+
return None;
100+
}
101+
return Some((result, pos));
102+
}
103+
None
104+
}
105+
106+
#[doc(hidden)]
107+
pub const fn utf16_len(bytes: &[u8]) -> usize {
108+
let mut pos = 0;
109+
let mut len = 0;
110+
while let Some((code_point, new_pos)) = decode_utf8_char(bytes, pos) {
111+
pos = new_pos;
112+
len += if code_point <= 0xffff { 1 } else { 2 };
113+
}
114+
len
115+
}

crates/libs/sys/src/core/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
mod literals;
2+
3+
#[doc(hidden)]
4+
pub use literals::*;
5+
16
#[repr(C)]
27
pub struct GUID {
38
pub data1: u32,

crates/libs/windows/src/core/strings/literals.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
/// Simply terminates the literal UTF-8 string.
1+
/// A literal UTF-8 string with a trailing null terminator.
22
#[macro_export]
33
macro_rules! s {
44
($s:literal) => {
55
$crate::core::PCSTR::from_raw(::std::concat!($s, '\0').as_ptr())
66
};
77
}
88

9-
/// A literal UTF-16 wide string.
9+
/// A literal UTF-16 wide string with a trailing null terminator.
1010
///
1111
/// Converts the literal UTF-8 string into a UTF-16 string adding a terminator and then wrapping
1212
/// that in an HSTRING reference so that it can be used for calling both WinRT APIs expecting an
13-
/// HSTRING as well as Win32 APIs expecting a PCWSTR.
13+
/// HSTRING as well as Win32 APIs expecting a PCWSTR. All of this is done at compile time so there's
14+
/// no run time cost at all.
1415
#[macro_export]
1516
macro_rules! w {
1617
($s:literal) => {{

crates/samples/create_window_sys/src/main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
use windows_sys::{Win32::Foundation::*, Win32::Graphics::Gdi::ValidateRect, Win32::System::LibraryLoader::GetModuleHandleA, Win32::UI::WindowsAndMessaging::*};
1+
use windows_sys::{core::*, Win32::Foundation::*, Win32::Graphics::Gdi::ValidateRect, Win32::System::LibraryLoader::GetModuleHandleA, Win32::UI::WindowsAndMessaging::*};
22

33
fn main() {
44
unsafe {
55
let instance = GetModuleHandleA(std::ptr::null());
66
debug_assert!(instance != 0);
77

8-
let window_class = b"window\0".as_ptr();
8+
let window_class = s!("window");
99

1010
let wc = WNDCLASSA {
1111
hCursor: LoadCursorW(0, IDC_ARROW),
@@ -23,7 +23,7 @@ fn main() {
2323
let atom = RegisterClassA(&wc);
2424
debug_assert!(atom != 0);
2525

26-
CreateWindowExA(0, window_class, b"This is a sample window\0".as_ptr(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, instance, std::ptr::null());
26+
CreateWindowExA(0, window_class, s!("This is a sample window"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, instance, std::ptr::null());
2727

2828
let mut message = std::mem::zeroed();
2929

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "sample_message_box_sys"
3+
version = "0.0.0"
4+
edition = "2018"
5+
6+
[dependencies.windows-sys]
7+
path = "../../libs/sys"
8+
features = [
9+
"Win32_Foundation",
10+
"Win32_UI_WindowsAndMessaging",
11+
]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use windows_sys::{core::*, Win32::UI::WindowsAndMessaging::*};
2+
3+
fn main() {
4+
unsafe {
5+
MessageBoxA(0, s!("Ansi"), s!("World"), MB_OK);
6+
MessageBoxW(0, w!("Wide"), w!("World"), MB_OK);
7+
}
8+
}

crates/tests/reserved/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,12 @@ features = [
1212
"Win32_UI_WindowsAndMessaging",
1313
"Win32_System_Threading",
1414
]
15+
16+
[dependencies.windows-sys]
17+
path = "../../libs/sys"
18+
features = [
19+
"Win32_Foundation",
20+
"Win32_System_Registry",
21+
"Win32_UI_WindowsAndMessaging",
22+
"Win32_System_Threading",
23+
]

crates/tests/reserved/tests/sys.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use windows_sys::{core::*, Win32::System::Registry::*, Win32::System::Threading::*, Win32::UI::WindowsAndMessaging::*};
2+
3+
/// Tests a few APIs that have reserved parameters to ensure they can be called with `None`.
4+
#[test]
5+
fn test() {
6+
unsafe {
7+
assert_eq!(InSendMessageEx(std::ptr::null_mut()), ISMEX_NOSEND);
8+
assert!(CreateThreadpool(std::ptr::null_mut()) != 0);
9+
assert_eq!(TrackPopupMenu(0, TPM_LEFTBUTTON, 1, 2, 0, 0, std::ptr::null()), 0);
10+
11+
let mut key = 0;
12+
RegOpenKeyExA(HKEY_CLASSES_ROOT, s!(r".txt"), 0, KEY_QUERY_VALUE, &mut key);
13+
let mut len = 0;
14+
RegQueryValueExA(key, s!("Content Type"), std::ptr::null_mut(), std::ptr::null_mut(), std::ptr::null_mut(), &mut len);
15+
let mut buffer = vec![0u8; (len) as usize];
16+
RegQueryValueExA(key, s!("Content Type"), std::ptr::null_mut(), std::ptr::null_mut(), buffer.as_mut_ptr() as _, &mut len);
17+
assert_eq!(String::from_utf8_lossy(&buffer), "text/plain\0");
18+
}
19+
}

0 commit comments

Comments
 (0)