Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add macros for creating null-terminated string literals to windows-sys #2043

Merged
merged 3 commits into from Sep 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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");
}
}