diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 619dd3745a..5bc8f261c4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 && diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 501c6faf02..1c6566076e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 && @@ -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 && diff --git a/crates/libs/sys/src/core/literals.rs b/crates/libs/sys/src/core/literals.rs new file mode 100644 index 0000000000..a8b3a2f6f4 --- /dev/null +++ b/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 +} diff --git a/crates/libs/sys/src/core/mod.rs b/crates/libs/sys/src/core/mod.rs index 92fb13673b..ec66a2bb60 100644 --- a/crates/libs/sys/src/core/mod.rs +++ b/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, diff --git a/crates/libs/windows/src/core/strings/literals.rs b/crates/libs/windows/src/core/strings/literals.rs index 08ef211a83..0a43d53147 100644 --- a/crates/libs/windows/src/core/strings/literals.rs +++ b/crates/libs/windows/src/core/strings/literals.rs @@ -1,4 +1,4 @@ -/// Simply terminates the literal UTF-8 string. +/// A literal UTF-8 string with a trailing null terminator. #[macro_export] macro_rules! s { ($s:literal) => { @@ -6,11 +6,12 @@ macro_rules! s { }; } -/// 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) => {{ diff --git a/crates/samples/create_window_sys/src/main.rs b/crates/samples/create_window_sys/src/main.rs index e11c799f3e..7681e7504c 100644 --- a/crates/samples/create_window_sys/src/main.rs +++ b/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), @@ -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(); diff --git a/crates/samples/message_box_sys/Cargo.toml b/crates/samples/message_box_sys/Cargo.toml new file mode 100644 index 0000000000..7ac15fc0be --- /dev/null +++ b/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", +] diff --git a/crates/samples/message_box_sys/src/main.rs b/crates/samples/message_box_sys/src/main.rs new file mode 100644 index 0000000000..69955c0a92 --- /dev/null +++ b/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); + } +} diff --git a/crates/tests/reserved/Cargo.toml b/crates/tests/reserved/Cargo.toml index c22ceb5a07..4f3edb0d9d 100644 --- a/crates/tests/reserved/Cargo.toml +++ b/crates/tests/reserved/Cargo.toml @@ -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", +] diff --git a/crates/tests/reserved/tests/sys.rs b/crates/tests/reserved/tests/sys.rs new file mode 100644 index 0000000000..034ac3d082 --- /dev/null +++ b/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"); + } +} diff --git a/crates/tests/reserved/tests/tests.rs b/crates/tests/reserved/tests/win.rs similarity index 100% rename from crates/tests/reserved/tests/tests.rs rename to crates/tests/reserved/tests/win.rs