Skip to content

Commit

Permalink
Add macros for creating null-terminated string literals to `windows-s…
Browse files Browse the repository at this point in the history
…ys` (#2043)
  • Loading branch information
kennykerr committed Sep 20, 2022
1 parent ba4566c commit dd244b5
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 7 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Expand Up @@ -119,6 +119,7 @@ jobs:
cargo clippy -p sample_kernel_event &&
cargo clippy -p sample_memory_buffer &&
cargo clippy -p sample_message_box &&
cargo clippy -p sample_message_box_sys &&
cargo clippy -p sample_ocr &&
cargo clippy -p sample_overlapped &&
cargo clippy -p sample_rss &&
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/test.yml
Expand Up @@ -102,6 +102,7 @@ jobs:
cargo test --target ${{ matrix.target }} -p sample_kernel_event &&
cargo test --target ${{ matrix.target }} -p sample_memory_buffer &&
cargo test --target ${{ matrix.target }} -p sample_message_box &&
cargo test --target ${{ matrix.target }} -p sample_message_box_sys &&
cargo test --target ${{ matrix.target }} -p sample_ocr &&
cargo test --target ${{ matrix.target }} -p sample_overlapped &&
cargo test --target ${{ matrix.target }} -p sample_rss &&
Expand Down Expand Up @@ -129,8 +130,8 @@ jobs:
cargo test --target ${{ matrix.target }} -p test_class_factory &&
cargo test --target ${{ matrix.target }} -p test_component &&
cargo test --target ${{ matrix.target }} -p test_component_client &&
cargo test --target ${{ matrix.target }} -p test_const_fields &&
cargo clean &&
cargo test --target ${{ matrix.target }} -p test_const_fields &&
cargo test --target ${{ matrix.target }} -p test_core &&
cargo test --target ${{ matrix.target }} -p test_data_object &&
cargo test --target ${{ matrix.target }} -p test_debug &&
Expand Down
115 changes: 115 additions & 0 deletions crates/libs/sys/src/core/literals.rs
@@ -0,0 +1,115 @@
/// A literal UTF-8 string with a trailing null terminator.
#[macro_export]
macro_rules! s {
($s:literal) => {
::core::concat!($s, '\0').as_ptr()
};
}

/// A literal UTF-16 wide string with a trailing null terminator.
#[macro_export]
macro_rules! w {
($s:literal) => {{
const INPUT: &[u8] = $s.as_bytes();
const OUTPUT_LEN: usize = $crate::core::utf16_len(INPUT) + 1;
const OUTPUT: &[u16; OUTPUT_LEN] = {
let mut buffer = [0; OUTPUT_LEN];
let mut input_pos = 0;
let mut output_pos = 0;
while let Some((mut code_point, new_pos)) = $crate::core::decode_utf8_char(INPUT, input_pos) {
input_pos = new_pos;
if code_point <= 0xffff {
buffer[output_pos] = code_point as u16;
output_pos += 1;
} else {
code_point -= 0x10000;
buffer[output_pos] = 0xd800 + (code_point >> 10) as u16;
output_pos += 1;
buffer[output_pos] = 0xdc00 + (code_point & 0x3ff) as u16;
output_pos += 1;
}
}
&{ buffer }
};
OUTPUT.as_ptr()
}};
}

// Ensures that the macros are exported from the `windows::core` module.
pub use s;
pub use w;

#[doc(hidden)]
pub const fn decode_utf8_char(bytes: &[u8], mut pos: usize) -> Option<(u32, usize)> {
if bytes.len() == pos {
return None;
}
let ch = bytes[pos] as u32;
pos += 1;
if ch <= 0x7f {
return Some((ch, pos));
}
if (ch & 0xe0) == 0xc0 {
if bytes.len() - pos < 1 {
return None;
}
let ch2 = bytes[pos] as u32;
pos += 1;
if (ch2 & 0xc0) != 0x80 {
return None;
}
let result: u32 = ((ch & 0x1f) << 6) | (ch2 & 0x3f);
if result <= 0x7f {
return None;
}
return Some((result, pos));
}
if (ch & 0xf0) == 0xe0 {
if bytes.len() - pos < 2 {
return None;
}
let ch2 = bytes[pos] as u32;
pos += 1;
let ch3 = bytes[pos] as u32;
pos += 1;
if (ch2 & 0xc0) != 0x80 || (ch3 & 0xc0) != 0x80 {
return None;
}
let result = ((ch & 0x0f) << 12) | ((ch2 & 0x3f) << 6) | (ch3 & 0x3f);
if result <= 0x7ff || (0xd800 <= result && result <= 0xdfff) {
return None;
}
return Some((result, pos));
}
if (ch & 0xf8) == 0xf0 {
if bytes.len() - pos < 3 {
return None;
}
let ch2 = bytes[pos] as u32;
pos += 1;
let ch3 = bytes[pos] as u32;
pos += 1;
let ch4 = bytes[pos] as u32;
pos += 1;
if (ch2 & 0xc0) != 0x80 || (ch3 & 0xc0) != 0x80 || (ch4 & 0xc0) != 0x80 {
return None;
}
let result = ((ch & 0x07) << 18) | ((ch2 & 0x3f) << 12) | ((ch3 & 0x3f) << 6) | (ch4 & 0x3f);
if result <= 0xffff || 0x10ffff < result {
return None;
}
return Some((result, pos));
}
None
}

#[doc(hidden)]
pub const fn utf16_len(bytes: &[u8]) -> usize {
let mut pos = 0;
let mut len = 0;
while let Some((code_point, new_pos)) = decode_utf8_char(bytes, pos) {
pos = new_pos;
len += if code_point <= 0xffff { 1 } else { 2 };
}
len
}
5 changes: 5 additions & 0 deletions crates/libs/sys/src/core/mod.rs
@@ -1,3 +1,8 @@
mod literals;

#[doc(hidden)]
pub use literals::*;

#[repr(C)]
pub struct GUID {
pub data1: u32,
Expand Down
7 changes: 4 additions & 3 deletions crates/libs/windows/src/core/strings/literals.rs
@@ -1,16 +1,17 @@
/// Simply terminates the literal UTF-8 string.
/// A literal UTF-8 string with a trailing null terminator.
#[macro_export]
macro_rules! s {
($s:literal) => {
$crate::core::PCSTR::from_raw(::std::concat!($s, '\0').as_ptr())
};
}

/// A literal UTF-16 wide string.
/// A literal UTF-16 wide string with a trailing null terminator.
///
/// Converts the literal UTF-8 string into a UTF-16 string adding a terminator and then wrapping
/// that in an HSTRING reference so that it can be used for calling both WinRT APIs expecting an
/// HSTRING as well as Win32 APIs expecting a PCWSTR.
/// HSTRING as well as Win32 APIs expecting a PCWSTR. All of this is done at compile time so there's
/// no run time cost at all.
#[macro_export]
macro_rules! w {
($s:literal) => {{
Expand Down
6 changes: 3 additions & 3 deletions crates/samples/create_window_sys/src/main.rs
@@ -1,11 +1,11 @@
use windows_sys::{Win32::Foundation::*, Win32::Graphics::Gdi::ValidateRect, Win32::System::LibraryLoader::GetModuleHandleA, Win32::UI::WindowsAndMessaging::*};
use windows_sys::{core::*, Win32::Foundation::*, Win32::Graphics::Gdi::ValidateRect, Win32::System::LibraryLoader::GetModuleHandleA, Win32::UI::WindowsAndMessaging::*};

fn main() {
unsafe {
let instance = GetModuleHandleA(std::ptr::null());
debug_assert!(instance != 0);

let window_class = b"window\0".as_ptr();
let window_class = s!("window");

let wc = WNDCLASSA {
hCursor: LoadCursorW(0, IDC_ARROW),
Expand All @@ -23,7 +23,7 @@ fn main() {
let atom = RegisterClassA(&wc);
debug_assert!(atom != 0);

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());
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());

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

Expand Down
11 changes: 11 additions & 0 deletions crates/samples/message_box_sys/Cargo.toml
@@ -0,0 +1,11 @@
[package]
name = "sample_message_box_sys"
version = "0.0.0"
edition = "2018"

[dependencies.windows-sys]
path = "../../libs/sys"
features = [
"Win32_Foundation",
"Win32_UI_WindowsAndMessaging",
]
8 changes: 8 additions & 0 deletions crates/samples/message_box_sys/src/main.rs
@@ -0,0 +1,8 @@
use windows_sys::{core::*, Win32::UI::WindowsAndMessaging::*};

fn main() {
unsafe {
MessageBoxA(0, s!("Ansi"), s!("World"), MB_OK);
MessageBoxW(0, w!("Wide"), w!("World"), MB_OK);
}
}
9 changes: 9 additions & 0 deletions crates/tests/reserved/Cargo.toml
Expand Up @@ -12,3 +12,12 @@ features = [
"Win32_UI_WindowsAndMessaging",
"Win32_System_Threading",
]

[dependencies.windows-sys]
path = "../../libs/sys"
features = [
"Win32_Foundation",
"Win32_System_Registry",
"Win32_UI_WindowsAndMessaging",
"Win32_System_Threading",
]
19 changes: 19 additions & 0 deletions crates/tests/reserved/tests/sys.rs
@@ -0,0 +1,19 @@
use windows_sys::{core::*, Win32::System::Registry::*, Win32::System::Threading::*, Win32::UI::WindowsAndMessaging::*};

/// Tests a few APIs that have reserved parameters to ensure they can be called with `None`.
#[test]
fn test() {
unsafe {
assert_eq!(InSendMessageEx(std::ptr::null_mut()), ISMEX_NOSEND);
assert!(CreateThreadpool(std::ptr::null_mut()) != 0);
assert_eq!(TrackPopupMenu(0, TPM_LEFTBUTTON, 1, 2, 0, 0, std::ptr::null()), 0);

let mut key = 0;
RegOpenKeyExA(HKEY_CLASSES_ROOT, s!(r".txt"), 0, KEY_QUERY_VALUE, &mut key);
let mut len = 0;
RegQueryValueExA(key, s!("Content Type"), std::ptr::null_mut(), std::ptr::null_mut(), std::ptr::null_mut(), &mut len);
let mut buffer = vec![0u8; (len) as usize];
RegQueryValueExA(key, s!("Content Type"), std::ptr::null_mut(), std::ptr::null_mut(), buffer.as_mut_ptr() as _, &mut len);
assert_eq!(String::from_utf8_lossy(&buffer), "text/plain\0");
}
}
File renamed without changes.

0 comments on commit dd244b5

Please sign in to comment.