diff --git a/.github/skills/classic-com-abi/SKILL.md b/.github/skills/classic-com-abi/SKILL.md index 5a33489a..cedb13a6 100644 --- a/.github/skills/classic-com-abi/SKILL.md +++ b/.github/skills/classic-com-abi/SKILL.md @@ -9,9 +9,13 @@ Use this skill for changes under: - `crates/dynwinrt/src/com.rs`, `signature.rs`, `native_call.rs`, or `call.rs`; - `bindings/js/src/com.rs`; +- `crates/dynwinrt/src/win32.rs`; +- `bindings/js/src/win32.rs`; - `tools/dynwinrt-codegen/src/com_metadata.rs`; - `tools/dynwinrt-codegen/src/codegen/com/`; or -- Classic COM runners in `tests/runners/com/`. +- `tools/dynwinrt-codegen/src/codegen/win32/`; +- Classic COM runners in `tests/runners/com/`; or +- flat Win32 runners in `tests/runners/flat/`. Read [`docs/classic-com-support.md`](../../../docs/classic-com-support.md) before changing supported types or claiming support for an interface. @@ -32,6 +36,20 @@ Windows.Win32.winmd facts `Buffer`, `bigint`, `string`, and generated wrappers are projection choices. They must not determine native semantics. +Flat Win32 is a third semantic frontend: + +```text +Windows.Win32.winmd [DllImport] + -> flat-local metadata and validated ABI plan + -> flat JavaScript projection + -> @microsoft/dynwinrt/win32 + -> System32 DLL export +``` + +Keep it out of `DynWinRt*`, the npm root, and `DynCom*`. Preserve P/Invoke +calling convention, architecture, pointer depth, native-array size +relationships, `SupportsLastError`, and return lifetime before rendering. + ## Required semantic model Preserve these facts before rendering: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5a0b9950..2dfe98ca 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -161,3 +161,5 @@ jobs: bindings/js/dist/winrt.d.ts bindings/js/dist/com.js bindings/js/dist/com.d.ts + bindings/js/dist/win32.js + bindings/js/dist/win32.d.ts diff --git a/.gitignore b/.gitignore index 04c6e8b5..1de4d092 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,8 @@ mono_crash.* x64/ x86/ [Ww][Ii][Nn]32/ +!tools/dynwinrt-codegen/src/codegen/win32/ +!tools/dynwinrt-codegen/src/codegen/win32/** [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ [Aa][Rr][Mm]64[Ee][Cc]/ diff --git a/README.md b/README.md index 516ee180..de10cfb4 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,13 @@ package root remains the WinRT-only API. See [Classic COM support](docs/classic-com-support.md) for the supported ABI, common-interface test matrix, unsupported native types, and ownership rules. +Flat Win32 bindings use the separate `@microsoft/dynwinrt/win32` runtime +entrypoint and are generated into namespace-specific modules. Generation is +fail-closed for native shapes whose pointer depth, size, architecture, calling +convention, lifetime, or ownership is not modeled. See +[Flat Win32 support](docs/flat-win32-support.md) for the supported subset and +limitations. + Generated bindings project unambiguous public WinRT activation metadata as JavaScript constructors, including overloads such as `new Uri(base, relative)`. Existing static factory methods remain available. Classes that can only be returned by diff --git a/bindings/js/README.md b/bindings/js/README.md index 59e22783..993aa1a2 100644 --- a/bindings/js/README.md +++ b/bindings/js/README.md @@ -55,6 +55,16 @@ native output that transfers an existing `+1` reference; numeric pointers and typed-array pointers are borrowed and cannot be adopted. Win32 handles are not COM references and require their own type-specific cleanup function. +Flat Win32 DLL exports use a third entrypoint: + +```js +const { DynWin32 } = require('@microsoft/dynwinrt/win32'); +``` + +Use generated flat Win32 wrappers rather than calling `DynWin32.invoke` +directly. Generated wrappers encode the validated native signature and capture +`GetLastError` when required. + Unambiguous public WinRT activation metadata is projected as JavaScript constructors. Parameterized and composable activations support idiomatic forms such as `new Uri(base, relative)` and `new StackPanel()`. The generated static factory diff --git a/bindings/js/__test__/index.spec.ts b/bindings/js/__test__/index.spec.ts index fbb7a73f..edb1aeac 100644 --- a/bindings/js/__test__/index.spec.ts +++ b/bindings/js/__test__/index.spec.ts @@ -20,6 +20,7 @@ import { } from '../dist/winrt.js' import * as winrtRuntime from '../dist/winrt.js' import { DynCom, DynComMethodSig } from '../dist/com.js' +import { DynWin32 } from '../dist/win32.js' test('Classic COM is isolated from the WinRT root entrypoint', (t) => { t.false(Object.prototype.hasOwnProperty.call(winrtRuntime, 'DynCom')) @@ -58,6 +59,55 @@ test('Classic COM is isolated from the WinRT root entrypoint', (t) => { t.regex(esm.stdout, /runtime-entrypoints-ok/) }) +test('flat Win32 is isolated from the WinRT root entrypoint', (t) => { + t.false(Object.prototype.hasOwnProperty.call(winrtRuntime, 'DynWin32')) + t.truthy(DynWin32) + + const assertion = + "const assert = require('node:assert/strict');" + + "const winrt = require('@microsoft/dynwinrt');" + + "const win32 = require('@microsoft/dynwinrt/win32');" + + "assert.equal(Object.prototype.hasOwnProperty.call(winrt, 'DynWin32'), false);" + + "assert.equal(typeof win32.DynWin32, 'function');" + + "console.log('win32-entrypoint-ok')" + const cjs = spawnSync(process.execPath, ['--eval', assertion], { + cwd: resolve(process.cwd()), + encoding: 'utf8', + windowsHide: true, + }) + t.is(cjs.status, 0, cjs.stderr) + t.regex(cjs.stdout, /win32-entrypoint-ok/) +}) + +test('DynWin32 validates scalar widths and retained pointer storage', (t) => { + t.notThrows(() => DynWin32.i64(-(2n ** 63n))) + t.notThrows(() => DynWin32.i64(Number.MAX_SAFE_INTEGER)) + t.throws(() => DynWin32.i64(2n ** 63n), { message: /signed 64-bit/ }) + t.throws(() => DynWin32.i64(1.5), { message: /safe integer/ }) + t.throws(() => DynWin32.i8(128), { message: /range/ }) + t.throws(() => DynWin32.i8(4_294_967_297), { message: /range/ }) + t.throws(() => DynWin32.i32(4_294_967_296), { message: /range/ }) + t.throws(() => DynWin32.u32(-1), { message: /range/ }) + t.throws(() => DynWin32.u16(1.5), { message: /integer/ }) + t.is(DynWin32.toPointerBigint(DynWin32.handle(-1n)), (2n ** 64n) - 1n) + t.is(DynWin32.toPointerBigint(DynWin32.handle(-2)), (2n ** 64n) - 2n) + + const bytes = new Uint8Array(8) + const pointer = DynWin32.pointer(bytes) + structuredClone(bytes.buffer, { transfer: [bytes.buffer] }) + const error = t.throws(() => DynWin32.toPointerBigint(pointer)) + t.regex(error.message, /backing ArrayBuffer is detached/) +}) + +test('DynWinRtValue accepts lossless UInt64 bigint inputs', (t) => { + const max = (2n ** 64n) - 1n + t.is(DynCom.toU64Bigint(DynWinRtValue.u64(max)), max) + t.throws(() => DynWinRtValue.u64(-1n), { message: /unsigned 64-bit/ }) + t.throws(() => DynWinRtValue.u64(Number.MAX_SAFE_INTEGER + 1), { + message: /safe integer/, + }) +}) + test('DynCom rejects pointers after their TypedArray backing store is detached', (t) => { const bytes = new Uint8Array(16) const pointer = DynCom.pointer(bytes) diff --git a/bindings/js/package.json b/bindings/js/package.json index 779bdace..c962d4bb 100644 --- a/bindings/js/package.json +++ b/bindings/js/package.json @@ -17,12 +17,21 @@ "require": "./dist/com.js", "default": "./dist/com.js" }, + "./win32": { + "types": "./dist/win32.d.ts", + "import": "./dist/win32.js", + "require": "./dist/win32.js", + "default": "./dist/win32.js" + }, "./package.json": "./package.json" }, "typesVersions": { "*": { "com": [ "dist/com.d.ts" + ], + "win32": [ + "dist/win32.d.ts" ] } }, diff --git a/bindings/js/scripts/generate-entrypoints.mjs b/bindings/js/scripts/generate-entrypoints.mjs index 2fa73d4a..8c3e0b7e 100644 --- a/bindings/js/scripts/generate-entrypoints.mjs +++ b/bindings/js/scripts/generate-entrypoints.mjs @@ -27,18 +27,24 @@ const comExports = new Set([ 'WinGuid', 'WinGUID', ]) +const win32Exports = new Set(['DynWin32', 'DynWin32CallResult', 'DynWin32Value']) writeFacade( 'winrt', - nativeExports.filter((name) => !name.startsWith('DynCom')), + nativeExports.filter((name) => !name.startsWith('DynCom') && !name.startsWith('DynWin32')), ) writeFacade( 'com', nativeExports.filter((name) => comExports.has(name)), ) +writeFacade( + 'win32', + nativeExports.filter((name) => win32Exports.has(name)), +) function writeFacade(name, exports) { - const missing = name === 'com' ? [...comExports].filter((value) => !exports.includes(value)) : [] + const required = name === 'com' ? comExports : name === 'win32' ? win32Exports : new Set() + const missing = [...required].filter((value) => !exports.includes(value)) if (missing.length > 0) { throw new Error(`Missing required ${name} exports: ${missing.join(', ')}`) } diff --git a/bindings/js/src/com.rs b/bindings/js/src/com.rs index 9f3882d9..19ff7aad 100644 --- a/bindings/js/src/com.rs +++ b/bindings/js/src/com.rs @@ -206,7 +206,7 @@ fn uint8_array_info( })) } -fn pointer(value: Unknown) -> napi::Result { +pub(super) fn pointer(value: Unknown) -> napi::Result { use napi::sys; let env = value.value().env; @@ -364,7 +364,7 @@ fn adopt_co_task_mem_pointer(value: &mut DynWinRTValue) -> napi::Result napi::Result { +pub(super) fn as_pointer_bigint(value: &DynWinRTValue) -> napi::Result { validate_pointer_owner(value)?; let bits = match &value.0 { dynwinrt::WinRTValue::Object(_) => { @@ -414,7 +414,7 @@ fn take_bstr(value: &mut DynWinRTValue) -> napi::Result { String::try_from(&value).map_err(|error| napi::Error::from_reason(error.to_string())) } -fn validate_pointer_owner(value: &DynWinRTValue) -> napi::Result<()> { +pub(super) fn validate_pointer_owner(value: &DynWinRTValue) -> napi::Result<()> { if let Some(owner) = &value.1 { owner.validate()?; } diff --git a/bindings/js/src/lib.rs b/bindings/js/src/lib.rs index bbdb7686..8b065afa 100644 --- a/bindings/js/src/lib.rs +++ b/bindings/js/src/lib.rs @@ -12,13 +12,15 @@ use std::{ use dynwinrt; use napi::Env; -use napi::bindgen_prelude::{BigInt, PromiseRaw}; +use napi::bindgen_prelude::{BigInt, Either, PromiseRaw}; use napi::threadsafe_function::ThreadsafeFunctionCallMode; use napi_derive::napi; use windows::core::{HSTRING, IUnknown, Interface}; mod com; pub use com::{DynCom, DynComInterface, DynComMethodHandle, DynComMethodSig, DynComType}; +mod win32; +pub use win32::{DynWin32, DynWin32CallResult, DynWin32Value}; mod async_promise; mod scheduled_start; @@ -693,8 +695,33 @@ impl DynWinRTValue { DynWinRTValue::new(dynwinrt::WinRTValue::I64(value)) } #[napi] - pub fn u64(value: i64) -> DynWinRTValue { - DynWinRTValue::new(dynwinrt::WinRTValue::U64(value as u64)) + pub fn u64( + #[napi(ts_arg_type = "number | bigint")] value: Either, + ) -> napi::Result { + let value = match value { + Either::A(value) => { + let (negative, value, lossless) = value.get_u64(); + if negative || !lossless { + return Err(napi::Error::from_reason( + "DynWinRtValue.u64(): value must fit in an unsigned 64-bit integer", + )); + } + value + } + Either::B(value) => { + if !value.is_finite() + || value < 0.0 + || value.fract() != 0.0 + || value > 9_007_199_254_740_991.0 + { + return Err(napi::Error::from_reason( + "DynWinRtValue.u64(): number must be a non-negative safe integer", + )); + } + value as u64 + } + }; + Ok(DynWinRTValue::new(dynwinrt::WinRTValue::U64(value))) } #[napi] pub fn f32(value: f64) -> DynWinRTValue { diff --git a/bindings/js/src/win32.rs b/bindings/js/src/win32.rs new file mode 100644 index 00000000..914a1aa6 --- /dev/null +++ b/bindings/js/src/win32.rs @@ -0,0 +1,325 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use napi::bindgen_prelude::{BigInt, Either, FromNapiValue, Unknown}; +use napi::JsValue; +use napi_derive::napi; + +use super::{DynWinRTValue, com}; + +#[napi] +pub struct DynWin32Value(DynWinRTValue); + +#[napi] +pub struct DynWin32CallResult { + value: Option, + last_error: Option, +} + +#[napi] +impl DynWin32CallResult { + #[napi(getter)] + pub fn value(&mut self) -> napi::Result { + self + .value + .take() + .ok_or_else(|| napi::Error::from_reason("Flat Win32 result value was already consumed")) + } + + #[napi(getter)] + pub fn last_error(&self) -> Option { + self.last_error + } +} + +#[napi] +pub struct DynWin32; + +#[napi] +impl DynWin32 { + #[napi] + pub fn pointer( + #[napi(ts_arg_type = "bigint | number | Buffer | Uint8Array | null | undefined")] + value: Unknown, + ) -> napi::Result { + com::pointer(value).map(DynWin32Value) + } + + #[napi] + pub fn handle( + #[napi(ts_arg_type = "bigint | number")] value: Unknown, + ) -> napi::Result { + let bits = handle_bits(value)?; + Ok(DynWin32Value(DynWinRTValue::with_borrowed_pointer( + dynwinrt::WinRTValue::RawPtr(bits as usize as *mut std::ffi::c_void), + ))) + } + + #[napi] + pub fn i8(value: f64) -> napi::Result { + let value = checked_integer(value, i8::MIN as f64, i8::MAX as f64, "i8")?; + Ok(DynWin32Value(DynWinRTValue::new(dynwinrt::WinRTValue::I8( + value as i8, + )))) + } + + #[napi] + pub fn u8(value: f64) -> napi::Result { + let value = checked_integer(value, u8::MIN as f64, u8::MAX as f64, "u8")?; + Ok(DynWin32Value(DynWinRTValue::new(dynwinrt::WinRTValue::U8( + value as u8, + )))) + } + + #[napi] + pub fn i16(value: f64) -> napi::Result { + let value = checked_integer(value, i16::MIN as f64, i16::MAX as f64, "i16")?; + Ok(DynWin32Value(DynWinRTValue::new( + dynwinrt::WinRTValue::I16(value as i16), + ))) + } + + #[napi] + pub fn u16(value: f64) -> napi::Result { + let value = checked_integer(value, u16::MIN as f64, u16::MAX as f64, "u16")?; + Ok(DynWin32Value(DynWinRTValue::new( + dynwinrt::WinRTValue::U16(value as u16), + ))) + } + + #[napi] + pub fn i32(value: f64) -> napi::Result { + let value = checked_integer(value, i32::MIN as f64, i32::MAX as f64, "i32")?; + Ok(DynWin32Value(DynWinRTValue::new( + dynwinrt::WinRTValue::I32(value as i32), + ))) + } + + #[napi] + pub fn u32(value: f64) -> napi::Result { + let value = checked_integer(value, u32::MIN as f64, u32::MAX as f64, "u32")?; + Ok(DynWin32Value(DynWinRTValue::new( + dynwinrt::WinRTValue::U32(value as u32), + ))) + } + + #[napi] + pub fn i64( + #[napi(ts_arg_type = "number | bigint")] value: Either, + ) -> napi::Result { + let value = match value { + Either::A(value) => { + let (value, lossless) = value.get_i64(); + if !lossless { + return Err(napi::Error::from_reason( + "DynWin32.i64(): value must fit in a signed 64-bit integer", + )); + } + value + } + Either::B(value) => { + if !value.is_finite() || value.fract() != 0.0 || value.abs() > 9_007_199_254_740_991.0 { + return Err(napi::Error::from_reason( + "DynWin32.i64(): number must be a safe integer", + )); + } + value as i64 + } + }; + Ok(DynWin32Value(DynWinRTValue::new( + dynwinrt::WinRTValue::I64(value), + ))) + } + + #[napi] + pub fn u64( + #[napi(ts_arg_type = "number | bigint")] value: Either, + ) -> napi::Result { + let value = match value { + Either::A(value) => { + let (negative, value, lossless) = value.get_u64(); + if negative || !lossless { + return Err(napi::Error::from_reason( + "DynWin32.u64(): value must fit in an unsigned 64-bit integer", + )); + } + value + } + Either::B(value) => { + if !value.is_finite() + || value < 0.0 + || value.fract() != 0.0 + || value > 9_007_199_254_740_991.0 + { + return Err(napi::Error::from_reason( + "DynWin32.u64(): number must be a non-negative safe integer", + )); + } + value as u64 + } + }; + Ok(DynWin32Value(DynWinRTValue::new( + dynwinrt::WinRTValue::U64(value), + ))) + } + + #[napi] + pub fn f32(value: f64) -> DynWin32Value { + DynWin32Value(DynWinRTValue::new(dynwinrt::WinRTValue::F32(value as f32))) + } + + #[napi] + pub fn f64(value: f64) -> DynWin32Value { + DynWin32Value(DynWinRTValue::new(dynwinrt::WinRTValue::F64(value))) + } + + #[napi] + pub fn invoke( + dll: String, + entry: String, + ret_kind: String, + args: Vec<&DynWin32Value>, + capture_last_error: bool, + ) -> napi::Result { + let ret = parse_return_kind(&ret_kind)?; + for arg in &args { + com::validate_pointer_owner(&arg.0)?; + } + let args = args.iter().map(|arg| arg.0.0.clone()).collect::>(); + let result = unsafe { + dynwinrt::win32::flat_invoke_with_options(&dll, &entry, ret, &args, capture_last_error) + } + .map_err(|error| { + napi::Error::from_reason(format!( + "DynWin32.invoke({dll}!{entry}): {}", + error.message() + )) + })?; + Ok(DynWin32CallResult { + value: Some(DynWin32Value(DynWinRTValue::new(result.value))), + last_error: result.last_error, + }) + } + + #[napi] + pub fn to_number(value: &DynWin32Value) -> napi::Result { + match &value.0.0 { + dynwinrt::WinRTValue::Bool(value) => Ok(u8::from(*value) as f64), + dynwinrt::WinRTValue::I8(value) => Ok(*value as f64), + dynwinrt::WinRTValue::U8(value) => Ok(*value as f64), + dynwinrt::WinRTValue::I16(value) => Ok(*value as f64), + dynwinrt::WinRTValue::U16(value) => Ok(*value as f64), + dynwinrt::WinRTValue::I32(value) => Ok(*value as f64), + dynwinrt::WinRTValue::U32(value) => Ok(*value as f64), + dynwinrt::WinRTValue::HResult(value) => Ok(value.0 as f64), + _ => Err(napi::Error::from_reason("Value is not a 32-bit scalar")), + } + } + + #[napi] + pub fn to_pointer_bigint(value: &DynWin32Value) -> napi::Result { + com::as_pointer_bigint(&value.0) + } + + #[napi] + pub fn to_i64_bigint(value: &DynWin32Value) -> napi::Result { + match &value.0.0 { + dynwinrt::WinRTValue::I64(value) => Ok(BigInt::from(*value)), + _ => Err(napi::Error::from_reason("Value is not an i64")), + } + } + + #[napi] + pub fn to_u64_bigint(value: &DynWin32Value) -> napi::Result { + match &value.0.0 { + dynwinrt::WinRTValue::U64(value) => Ok(BigInt::from(*value)), + _ => Err(napi::Error::from_reason("Value is not a u64")), + } + } + + #[napi] + pub fn to_f64(value: &DynWin32Value) -> napi::Result { + match &value.0.0 { + dynwinrt::WinRTValue::F32(value) => Ok(*value as f64), + dynwinrt::WinRTValue::F64(value) => Ok(*value), + _ => Err(napi::Error::from_reason( + "Value is not a floating-point scalar", + )), + } + } +} + +fn checked_integer(value: f64, min: f64, max: f64, kind: &str) -> napi::Result { + if !value.is_finite() || value.fract() != 0.0 || value < min || value > max { + return Err(napi::Error::from_reason(format!( + "DynWin32.{kind}(): value must be an integer in the range {min}..={max}" + ))); + } + Ok(value) +} + +fn handle_bits(value: Unknown) -> napi::Result { + use napi::sys; + + let env = value.value().env; + let raw = value.value().value; + let mut value_type = sys::ValueType::napi_undefined; + unsafe { sys::napi_typeof(env, raw, &mut value_type) }; + let bits = if value_type == sys::ValueType::napi_bigint { + let value = unsafe { BigInt::from_napi_value(env, raw) }?; + let (signed, signed_lossless) = value.get_i64(); + if signed_lossless { + signed as u64 + } else { + let (negative, unsigned, unsigned_lossless) = value.get_u64(); + if negative || !unsigned_lossless { + return Err(napi::Error::from_reason( + "DynWin32.handle(): bigint must fit in a signed or unsigned pointer-width value", + )); + } + unsigned + } + } else if value_type == sys::ValueType::napi_number { + let mut number = 0.0; + unsafe { sys::napi_get_value_double(env, raw, &mut number) }; + if !number.is_finite() || number.fract() != 0.0 || number.abs() > 9_007_199_254_740_991.0 { + return Err(napi::Error::from_reason( + "DynWin32.handle(): number must be a safe integer", + )); + } + (number as i64) as u64 + } else { + return Err(napi::Error::from_reason( + "DynWin32.handle(): expected bigint or number", + )); + }; + + if bits as usize as u64 != bits { + return Err(napi::Error::from_reason( + "DynWin32.handle(): value does not fit this target pointer width", + )); + } + Ok(bits) +} + +fn parse_return_kind(value: &str) -> napi::Result { + use dynwinrt::win32::FlatReturnKind; + + match value.to_ascii_lowercase().as_str() { + "void" => Ok(FlatReturnKind::Void), + "i8" => Ok(FlatReturnKind::I8), + "u8" => Ok(FlatReturnKind::U8), + "i16" => Ok(FlatReturnKind::I16), + "u16" => Ok(FlatReturnKind::U16), + "i32" => Ok(FlatReturnKind::I32), + "u32" => Ok(FlatReturnKind::U32), + "i64" => Ok(FlatReturnKind::I64), + "u64" => Ok(FlatReturnKind::U64), + "f32" => Ok(FlatReturnKind::F32), + "f64" => Ok(FlatReturnKind::F64), + "ptr" | "pointer" => Ok(FlatReturnKind::Ptr), + _ => Err(napi::Error::from_reason(format!( + "Unsupported flat Win32 return kind: {value}" + ))), + } +} diff --git a/crates/dynwinrt/src/lib.rs b/crates/dynwinrt/src/lib.rs index 0f900016..27361b39 100644 --- a/crates/dynwinrt/src/lib.rs +++ b/crates/dynwinrt/src/lib.rs @@ -12,6 +12,7 @@ mod result; mod roapi; mod signature; mod value; +pub mod win32; mod winapp; mod xaml_application; diff --git a/crates/dynwinrt/src/win32.rs b/crates/dynwinrt/src/win32.rs new file mode 100644 index 00000000..38dbcfec --- /dev/null +++ b/crates/dynwinrt/src/win32.rs @@ -0,0 +1,897 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use core::ffi::c_void; +#[cfg(all(windows, target_pointer_width = "64"))] +use std::ffi::CString; + +#[cfg(all(windows, target_pointer_width = "64"))] +use libffi::middle::{Arg, Cif, CodePtr, Type}; +use windows::Win32::Foundation::GetLastError; +#[cfg(all(windows, target_pointer_width = "64"))] +use windows::Win32::Foundation::HMODULE; +#[cfg(all(windows, target_pointer_width = "64"))] +use windows::Win32::System::LibraryLoader::{ + GetProcAddress, LOAD_LIBRARY_SEARCH_SYSTEM32, LoadLibraryExW, +}; +use windows_core::HRESULT; +#[cfg(all(windows, target_pointer_width = "64"))] +use windows_core::{HSTRING, PCSTR}; + +use crate::{ + result::{Error, Result}, + value::WinRTValue, +}; + +/// Wraps an `HMODULE` so it can live in a process-lifetime `static` cache across +/// threads. Safe because an `HMODULE` is an opaque handle and `GetProcAddress` +/// is thread-safe; the module is intentionally never unloaded. +#[cfg(all(windows, target_pointer_width = "64"))] +struct CachedModule(HMODULE); +#[cfg(all(windows, target_pointer_width = "64"))] +unsafe impl Send for CachedModule {} + +#[cfg(all(windows, target_pointer_width = "64"))] +fn module_cache() -> &'static std::sync::Mutex> { + static CACHE: std::sync::OnceLock< + std::sync::Mutex>, + > = std::sync::OnceLock::new(); + CACHE.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new())) +} + +/// Returns a process-lifetime `HMODULE` for `dll`, loading it once and caching +/// it. The module is intentionally **never** `FreeLibrary`'d: flat exports can +/// return pointers, strings, or function addresses that point *into* the loaded +/// module, and unloading it after each call would leave those returns dangling. +/// Holding a single reference for the life of the process matches how .NET +/// `[DllImport]` behaves and also avoids repeated load/unload overhead. +/// +/// Flat metadata imports are restricted to bare system DLL names and loaded +/// exclusively from System32 to prevent DLL preloading. +#[cfg(all(windows, target_pointer_width = "64"))] +fn get_cached_module(dll: &str) -> Result { + if !is_bare_system_module_name(dll) { + return Err(invalid_arg_error()); + } + // Windows DLL resolution is case-insensitive, so normalize the cache key: + // "ADVAPI32.dll" and "advapi32.dll" must share one cached module (and one + // LoadLibraryW reference) rather than creating duplicate entries. + let key = dll.to_ascii_lowercase(); + // Fast path: check the cache under a short-lived lock, then release it. + if let Some(module) = module_cache() + .lock() + .unwrap_or_else(|e| e.into_inner()) + .get(&key) + .map(|cached| cached.0) + { + return Ok(module); + } + // Load WITHOUT holding the cache lock. LoadLibraryW runs loader work and the + // DLL's DllMain, which can re-enter flat_invoke -> get_cached_module; holding + // the (non-reentrant) cache mutex across it would risk a deadlock and would + // serialize all flat calls during a load. + let module = unsafe { LoadLibraryExW(&HSTRING::from(dll), None, LOAD_LIBRARY_SEARCH_SYSTEM32) } + .map_err(Error::WindowsError)?; + // Re-acquire and insert. If another thread loaded the same DLL concurrently, + // keep the first entry; both HMODULEs refer to the same module and the extra + // reference is intentionally never released (process-lifetime residency). + let mut cache = module_cache().lock().unwrap_or_else(|e| e.into_inner()); + Ok(cache.entry(key).or_insert(CachedModule(module)).0) +} + +#[cfg(all(windows, target_pointer_width = "64"))] +fn is_bare_system_module_name(dll: &str) -> bool { + let lower = dll.to_ascii_lowercase(); + !dll.is_empty() + && (lower.ends_with(".dll") || lower.ends_with(".drv")) + && !dll.encode_utf16().any(|unit| unit == 0) + && !dll + .chars() + .any(|character| matches!(character, '/' | '\\' | ':')) + && dll != "." + && dll != ".." +} + +#[cfg(all(windows, target_pointer_width = "64"))] +fn proc_address(module: HMODULE, dll: &str, entry: &str) -> Result<*mut c_void> { + let proc_name = CString::new(entry).map_err(|_| invalid_arg_error())?; + let proc = unsafe { GetProcAddress(module, PCSTR::from_raw(proc_name.as_ptr().cast())) }; + match proc { + Some(proc) => Ok(unsafe { std::mem::transmute(proc) }), + None => Err(proc_not_found_error(dll, entry)), + } +} + +/// Owns a NUL-terminated UTF-16 string for passing as a stable `LPCWSTR` argument. +pub struct WideStringArg { + buffer: Vec, +} + +impl WideStringArg { + /// Returns a raw `LPCWSTR` pointer wrapped as a `WinRTValue`. + /// + /// The returned pointer is valid only while this `WideStringArg` is alive; + /// do not store or use the value after the owner is dropped. + pub fn as_winrt_value(&self) -> WinRTValue { + WinRTValue::RawPtr(self.buffer.as_ptr() as *mut c_void) + } +} + +pub fn wide_string_arg(value: &str) -> Result { + if value.encode_utf16().any(|unit| unit == 0) { + return Err(invalid_arg_error()); + } + + let mut buffer: Vec = value.encode_utf16().collect(); + buffer.push(0); + Ok(WideStringArg { buffer }) +} + +pub fn get_last_error() -> u32 { + unsafe { GetLastError().0 } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FlatReturnKind { + Void, + I8, + U8, + I16, + U16, + I32, + U32, + I64, + U64, + F32, + F64, + Ptr, +} + +pub struct FlatCallResult { + pub value: WinRTValue, + pub last_error: Option, +} + +/// Invokes a flat Win32 export through libffi. +/// +/// # Safety +/// +/// The caller must ensure that `dll`/`entry`, `ret`, and `args` exactly match +/// the target export's ABI signature, and that all pointer arguments remain +/// valid for the duration of the call. The DLL is loaded once and cached for +/// the lifetime of the process (never unloaded), so `FlatReturnKind::Ptr` +/// returns that point into the module stay valid after the call. +/// +/// `LoadLibraryW` uses the default DLL search order, so pass a trusted or +/// fully qualified DLL path to avoid DLL preloading/hijacking risks. +pub unsafe fn flat_invoke( + dll: &str, + entry: &str, + ret: FlatReturnKind, + args: &[WinRTValue], +) -> Result { + Ok(unsafe { flat_invoke_with_options(dll, entry, ret, args, false) }?.value) +} + +pub unsafe fn flat_invoke_with_options( + dll: &str, + entry: &str, + ret: FlatReturnKind, + args: &[WinRTValue], + capture_last_error: bool, +) -> Result { + #[cfg(not(all(windows, target_pointer_width = "64")))] + { + let _ = (dll, entry, ret, args, capture_last_error); + return Err(unsupported_platform_error()); + } + + #[cfg(all(windows, target_pointer_width = "64"))] + { + let module = get_cached_module(dll)?; + let proc = proc_address(module, dll, entry)?; + let arg_types = args + .iter() + .map(flat_arg_type) + .collect::>>()?; + let ffi_args = args.iter().map(flat_arg).collect::>>()?; + let ret_type = flat_return_type(ret)?; + let cif = Cif::new(arg_types, ret_type); + + // On x64 Windows there is a single native calling convention, so libffi's + // default ABI is correct for Winapi/stdcall and cdecl flat exports. + let value = unsafe { call_and_convert(&cif, proc, &ffi_args, ret) }?; + let last_error = capture_last_error.then(get_last_error); + Ok(FlatCallResult { value, last_error }) + } +} + +#[cfg(all(windows, target_pointer_width = "64"))] +fn flat_arg_type(value: &WinRTValue) -> Result { + match value { + WinRTValue::I8(_) => Ok(Type::i8()), + WinRTValue::U8(_) => Ok(Type::u8()), + WinRTValue::I16(_) => Ok(Type::i16()), + WinRTValue::U16(_) => Ok(Type::u16()), + WinRTValue::RawPtr(_) => Ok(Type::pointer()), + WinRTValue::I32(_) => Ok(Type::i32()), + WinRTValue::U32(_) => Ok(Type::u32()), + WinRTValue::I64(_) => Ok(Type::i64()), + WinRTValue::U64(_) => Ok(Type::u64()), + WinRTValue::F32(_) => Ok(Type::f32()), + WinRTValue::F64(_) => Ok(Type::f64()), + _ => Err(invalid_arg_error()), + } +} + +#[cfg(all(windows, target_pointer_width = "64"))] +fn flat_arg(value: &WinRTValue) -> Result> { + match value { + WinRTValue::I8(_) + | WinRTValue::U8(_) + | WinRTValue::I16(_) + | WinRTValue::U16(_) + | WinRTValue::I32(_) + | WinRTValue::U32(_) + | WinRTValue::I64(_) + | WinRTValue::U64(_) + | WinRTValue::F32(_) + | WinRTValue::F64(_) + | WinRTValue::RawPtr(_) => Ok(value.libffi_arg()), + _ => Err(invalid_arg_error()), + } +} + +#[cfg(all(windows, target_pointer_width = "64"))] +fn flat_return_type(kind: FlatReturnKind) -> Result { + match kind { + FlatReturnKind::Void => Ok(Type::void()), + FlatReturnKind::I8 => Ok(Type::i8()), + FlatReturnKind::U8 => Ok(Type::u8()), + FlatReturnKind::I16 => Ok(Type::i16()), + FlatReturnKind::U16 => Ok(Type::u16()), + FlatReturnKind::I32 => Ok(Type::i32()), + FlatReturnKind::U32 => Ok(Type::u32()), + FlatReturnKind::I64 => Ok(Type::i64()), + FlatReturnKind::U64 => Ok(Type::u64()), + FlatReturnKind::F32 => Ok(Type::f32()), + FlatReturnKind::F64 => Ok(Type::f64()), + FlatReturnKind::Ptr => Ok(Type::pointer()), + } +} + +#[cfg(all(windows, target_pointer_width = "64"))] +unsafe fn call_and_convert( + cif: &Cif, + proc: *mut c_void, + args: &[Arg<'_>], + ret: FlatReturnKind, +) -> Result { + match ret { + FlatReturnKind::Void => { + let _: () = unsafe { cif.call(CodePtr(proc), args) }; + Ok(WinRTValue::Null) + } + FlatReturnKind::I8 => Ok(WinRTValue::I8(unsafe { cif.call(CodePtr(proc), args) })), + FlatReturnKind::U8 => Ok(WinRTValue::U8(unsafe { cif.call(CodePtr(proc), args) })), + FlatReturnKind::I16 => Ok(WinRTValue::I16(unsafe { cif.call(CodePtr(proc), args) })), + FlatReturnKind::U16 => Ok(WinRTValue::U16(unsafe { cif.call(CodePtr(proc), args) })), + FlatReturnKind::I32 => Ok(WinRTValue::I32(unsafe { cif.call(CodePtr(proc), args) })), + FlatReturnKind::U32 => Ok(WinRTValue::U32(unsafe { cif.call(CodePtr(proc), args) })), + FlatReturnKind::I64 => Ok(WinRTValue::I64(unsafe { cif.call(CodePtr(proc), args) })), + FlatReturnKind::U64 => Ok(WinRTValue::U64(unsafe { cif.call(CodePtr(proc), args) })), + FlatReturnKind::F32 => Ok(WinRTValue::F32(unsafe { cif.call(CodePtr(proc), args) })), + FlatReturnKind::F64 => Ok(WinRTValue::F64(unsafe { cif.call(CodePtr(proc), args) })), + FlatReturnKind::Ptr => Ok(WinRTValue::RawPtr(unsafe { + cif.call::<*mut c_void>(CodePtr(proc), args) + })), + } +} + +fn invalid_arg_error() -> Error { + Error::WindowsError(windows_core::Error::from_hresult(HRESULT( + 0x80070057u32 as i32, + ))) +} + +#[cfg(all(windows, target_pointer_width = "64"))] +fn proc_not_found_error(dll: &str, entry: &str) -> Error { + Error::WindowsError(windows_core::Error::new( + HRESULT(0x8007007Fu32 as i32), + format!("Export '{entry}' not found in '{dll}'"), + )) +} + +#[cfg(not(all(windows, target_pointer_width = "64")))] +fn unsupported_platform_error() -> Error { + Error::WindowsError(windows_core::Error::from_hresult(HRESULT( + 0x80004001u32 as i32, + ))) +} + +#[cfg(all(test, windows, target_pointer_width = "64"))] +mod tests { + use super::*; + use std::sync::atomic::{AtomicU32, Ordering}; + use windows::Win32::Foundation::{SetLastError, WIN32_ERROR}; + + fn invoke( + dll: &str, + entry: &str, + ret: FlatReturnKind, + args: &[WinRTValue], + ) -> Result { + unsafe { flat_invoke(dll, entry, ret, args) } + } + + unsafe fn invoke_proc( + proc: *mut c_void, + ret: FlatReturnKind, + args: &[WinRTValue], + ) -> Result { + let arg_types = args + .iter() + .map(flat_arg_type) + .collect::>>()?; + let ffi_args = args.iter().map(flat_arg).collect::>>()?; + let ret_type = flat_return_type(ret)?; + let cif = Cif::new(arg_types, ret_type); + unsafe { call_and_convert(&cif, proc, &ffi_args, ret) } + } + + extern "C" fn test_returns_f64(x: f64) -> f64 { + x * 2.0 + } + + extern "C" fn test_returns_u64() -> u64 { + 0x1_0000_0001 + } + + extern "C" fn test_returns_i16() -> i16 { + i16::MIN + } + + extern "C" fn test_echo_i8(value: i8) -> i8 { + value + } + + static VOID_CALLED: AtomicU32 = AtomicU32::new(0); + + extern "C" fn test_returns_void(value: u32) { + VOID_CALLED.store(value, Ordering::SeqCst); + } + + #[test] + fn flat_call_invokes_test_f64_return_and_arg() -> Result<()> { + let result = unsafe { + invoke_proc( + test_returns_f64 as *mut c_void, + FlatReturnKind::F64, + &[WinRTValue::F64(2.25)], + ) + }?; + let WinRTValue::F64(v) = result else { + panic!("expected F64 return"); + }; + assert!((v - 4.5).abs() < f64::EPSILON); + Ok(()) + } + + #[test] + fn flat_call_invokes_test_u64_return_without_truncation() -> Result<()> { + let result = + unsafe { invoke_proc(test_returns_u64 as *mut c_void, FlatReturnKind::U64, &[]) }?; + let WinRTValue::U64(v) = result else { + panic!("expected U64 return"); + }; + assert_eq!(v, 0x1_0000_0001); + Ok(()) + } + + #[test] + fn flat_call_preserves_narrow_integer_args_and_returns() -> Result<()> { + let returned = + unsafe { invoke_proc(test_returns_i16 as *mut c_void, FlatReturnKind::I16, &[]) }?; + assert!(matches!(returned, WinRTValue::I16(i16::MIN))); + + let echoed = unsafe { + invoke_proc( + test_echo_i8 as *mut c_void, + FlatReturnKind::I8, + &[WinRTValue::I8(-7)], + ) + }?; + assert!(matches!(echoed, WinRTValue::I8(-7))); + Ok(()) + } + + #[test] + fn flat_call_invokes_test_void_return_as_null() -> Result<()> { + VOID_CALLED.store(0, Ordering::SeqCst); + let result = unsafe { + invoke_proc( + test_returns_void as *mut c_void, + FlatReturnKind::Void, + &[WinRTValue::U32(1234)], + ) + }?; + assert!(matches!(result, WinRTValue::Null)); + assert_eq!(VOID_CALLED.load(Ordering::SeqCst), 1234); + Ok(()) + } + + #[test] + fn flat_call_mul_div_multiplies_divides_and_rounds() -> Result<()> { + let result = invoke( + "kernel32.dll", + "MulDiv", + FlatReturnKind::I32, + &[WinRTValue::I32(100), WinRTValue::I32(3), WinRTValue::I32(2)], + )?; + assert_eq!(result.as_i32(), Some(150)); + + let rounded = invoke( + "kernel32.dll", + "MulDiv", + FlatReturnKind::I32, + &[WinRTValue::I32(7), WinRTValue::I32(1), WinRTValue::I32(2)], + )?; + assert_eq!(rounded.as_i32(), Some(4)); + Ok(()) + } + + #[test] + fn module_cache_returns_stable_handle_and_ptr_return_survives() -> Result<()> { + // Same DLL resolves to the same cached HMODULE across calls: loaded + // once and never freed (regression for the flat `Ptr`-return dangling + // hazard, where a per-call FreeLibrary could unload the module before + // the caller uses a pointer that points into it). + let a = get_cached_module("kernel32.dll")?; + let b = get_cached_module("kernel32.dll")?; + assert_eq!(a.0 as usize, b.0 as usize); + + // The cached module stays usable, and a Ptr-returning export's pointer + // is non-null after the call returns — nothing unloaded the module in + // between. + let proc = proc_address(a, "kernel32.dll", "GetCommandLineW")?; + assert!(!proc.is_null()); + let value = invoke("kernel32.dll", "GetCommandLineW", FlatReturnKind::Ptr, &[])?; + match value { + WinRTValue::RawPtr(ptr) => assert!(!ptr.is_null()), + _ => panic!("expected a RawPtr return from GetCommandLineW"), + } + Ok(()) + } + + #[test] + fn module_cache_recovers_from_poisoned_mutex() { + // Regression (commit 123d172): poison the module-cache mutex by + // panicking while holding it, then confirm get_cached_module still + // works — it recovers via `unwrap_or_else(|e| e.into_inner())` instead + // of propagating the panic and aborting the host process. + let _ = std::thread::spawn(|| { + let _guard = module_cache().lock().unwrap(); + panic!("intentionally poison the module cache"); + }) + .join(); + assert!(module_cache().is_poisoned()); + let module = get_cached_module("kernel32.dll") + .expect("get_cached_module must recover from a poisoned mutex"); + assert_ne!(module.0 as usize, 0); + } + + #[test] + fn concurrent_first_load_does_not_deadlock() { + // Regression (commit 98b3f99): get_cached_module must NOT hold the + // cache mutex while calling LoadLibraryW. Several threads loading the + // same not-yet-cached DLL concurrently must all complete (this test + // finishing at all proves there is no self-deadlock) and agree on a + // non-null handle. + let handles: Vec<_> = (0..8) + .map(|_| std::thread::spawn(|| get_cached_module("winmm.dll").map(|m| m.0 as usize))) + .collect(); + let results: Vec = handles + .into_iter() + .map(|h| h.join().unwrap().expect("winmm.dll must load")) + .collect(); + assert!(results.iter().all(|&h| h != 0)); + // After the race the cache is coherent: a subsequent lookup matches. + let again = get_cached_module("winmm.dll").unwrap().0 as usize; + assert_eq!(again, results[0]); + } + + #[test] + fn module_cache_key_is_case_insensitive() { + // Regression: Windows DLL resolution is case-insensitive, so case + // variants of the same DLL name must map to ONE cache entry (one load / + // one reference), not a duplicate. Pre-fix (raw-string key) the second + // case variant added a new entry. + let _ = get_cached_module("gdi32.dll").unwrap(); + let before = module_cache() + .lock() + .unwrap_or_else(|e| e.into_inner()) + .len(); + let _ = get_cached_module("GDI32.DLL").unwrap(); + let after = module_cache() + .lock() + .unwrap_or_else(|e| e.into_inner()) + .len(); + assert_eq!( + before, after, + "a case-variant DLL name must reuse the same cache entry, not add a new one" + ); + assert!( + module_cache() + .lock() + .unwrap_or_else(|e| e.into_inner()) + .contains_key("gdi32.dll") + ); + } + + #[test] + fn flat_call_get_current_process_id_matches_rust_process_id() -> Result<()> { + let result = invoke( + "kernel32.dll", + "GetCurrentProcessId", + FlatReturnKind::U32, + &[], + )?; + let WinRTValue::U32(pid) = result else { + panic!("expected U32 process id"); + }; + assert_eq!(pid, std::process::id()); + Ok(()) + } + + #[test] + fn flat_call_lstrlenw_accepts_wide_string_pointer() -> Result<()> { + let hello = wide_string_arg("hello")?; + let result = invoke( + "kernel32.dll", + "lstrlenW", + FlatReturnKind::I32, + &[hello.as_winrt_value()], + )?; + assert_eq!(result.as_i32(), Some(5)); + + let empty = wide_string_arg("")?; + let result = invoke( + "kernel32.dll", + "lstrlenW", + FlatReturnKind::I32, + &[empty.as_winrt_value()], + )?; + assert_eq!(result.as_i32(), Some(0)); + Ok(()) + } + + #[test] + fn flat_call_nonexistent_dll_returns_error() { + let result = invoke("no_such_dll_xyz.dll", "MulDiv", FlatReturnKind::I32, &[]); + let Err(Error::WindowsError(err)) = result else { + panic!("expected WindowsError for missing DLL"); + }; + assert_eq!(err.code(), HRESULT(0x8007007Eu32 as i32)); + } + + #[test] + fn flat_call_rejects_interior_nul_dll_name() { + let result = invoke( + "kernel32.dll\0ignored.dll", + "MulDiv", + FlatReturnKind::I32, + &[], + ); + let Err(Error::WindowsError(err)) = result else { + panic!("expected WindowsError for interior-NUL DLL name"); + }; + assert_eq!(err.code(), HRESULT(0x80070057u32 as i32)); + } + + #[test] + fn flat_call_rejects_dll_paths_outside_system32_policy() { + let result = invoke( + r"C:\Windows\System32\kernel32.dll", + "GetCurrentProcessId", + FlatReturnKind::U32, + &[], + ); + let Err(Error::WindowsError(error)) = result else { + panic!("expected invalid argument for a DLL path"); + }; + assert_eq!(error.code(), HRESULT(0x80070057u32 as i32)); + } + + #[test] + fn system_module_policy_accepts_dll_and_driver_names_only() { + assert!(is_bare_system_module_name("kernel32.dll")); + assert!(is_bare_system_module_name("winspool.drv")); + assert!(!is_bare_system_module_name("FORCEINLINE")); + assert!(!is_bare_system_module_name("kernel32.exe")); + } + + #[test] + fn flat_call_nonexistent_export_returns_error() { + let result = invoke( + "kernel32.dll", + "ThisExportDoesNotExist", + FlatReturnKind::I32, + &[], + ); + let Err(Error::WindowsError(err)) = result else { + panic!("expected WindowsError for missing export"); + }; + assert_eq!(err.code(), HRESULT(0x8007007Fu32 as i32)); + } + + #[test] + fn wide_string_arg_rejects_interior_nul() { + assert!(wide_string_arg("prefix\0suffix").is_err()); + } + + #[test] + fn flat_call_get_module_handlew_uses_get_last_error_model() -> Result<()> { + let bogus_module = wide_string_arg("no_such_module_xyz.dll")?; + unsafe { SetLastError(WIN32_ERROR(0)) }; + let result = invoke( + "kernel32.dll", + "GetModuleHandleW", + FlatReturnKind::Ptr, + &[bogus_module.as_winrt_value()], + )?; + let WinRTValue::RawPtr(module) = result else { + panic!("expected raw pointer return"); + }; + assert!(module.is_null()); + assert_eq!(get_last_error(), 126); + + unsafe { SetLastError(WIN32_ERROR(0)) }; + let captured = unsafe { + flat_invoke_with_options( + "kernel32.dll", + "GetModuleHandleW", + FlatReturnKind::Ptr, + &[bogus_module.as_winrt_value()], + true, + ) + }?; + assert_eq!(captured.last_error, Some(126)); + Ok(()) + } + + // ------------------------------------------------------------------ + // Registry marshalling primitives. + // + // These exercise the three flat-Win32 argument shapes needed by real + // Win32 APIs, using the advapi32 registry ABI: + // + // 1. Out handle via pointer-to-pointer + // (RegOpenKeyExW's `PHKEY phkResult` last arg) + // 2. Caller-allocated in/out byte buffer + in/out DWORD size + // (RegQueryValueExW's `LPBYTE lpData` + `LPDWORD lpcbData`) + // 3. Wide-string out buffer -> Rust String (UTF-16LE decode) + // + // Each buffer is a plain Vec/u32 slot owned by the test; we pass its + // address as a WinRTValue::RawPtr. That is exactly the same shape the + // napi layer uses when the JS caller passes a Node `Buffer` through + // `.pointer(buf)`. If these tests pass, the marshalling that the JS + // Registry wrapper depends on is proven at the Rust layer. + // ------------------------------------------------------------------ + + // HKEY_LOCAL_MACHINE — predefined pointer-sized HKEY constant. + // (The Win32 header defines this as (HKEY)(LONG_PTR)(LONG)0x80000002.) + const HKEY_LOCAL_MACHINE: usize = 0x80000002; + + // KEY_READ = STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS + // | KEY_NOTIFY + const KEY_READ: u32 = 0x20019; + + // Win32 registry error codes (LSTATUS = LONG). + const ERROR_SUCCESS: i32 = 0; + const ERROR_FILE_NOT_FOUND: i32 = 2; + const ERROR_MORE_DATA: i32 = 234; + + // REG_SZ registry value type. + const REG_SZ: u32 = 1; + + /// RegOpenKeyExW(HKEY hKey, LPCWSTR lpSubKey, DWORD ulOptions, + /// REGSAM samDesired, PHKEY phkResult) -> LSTATUS + fn reg_open_key(parent: usize, sub_key: &str) -> Result<(i32, usize)> { + let sub_key_arg = wide_string_arg(sub_key)?; + // Caller-allocated slot for the out HKEY. Pass its address as a raw + // pointer. The callee writes an HKEY (pointer-sized) into it. + let mut hkey_out: usize = 0; + let phkey = WinRTValue::RawPtr(&mut hkey_out as *mut usize as *mut c_void); + let status = invoke( + "advapi32.dll", + "RegOpenKeyExW", + FlatReturnKind::I32, + &[ + WinRTValue::RawPtr(parent as *mut c_void), + sub_key_arg.as_winrt_value(), + WinRTValue::U32(0), // ulOptions + WinRTValue::U32(KEY_READ), + phkey, + ], + )?; + let code = status.as_i32().expect("LSTATUS is a signed LONG"); + Ok((code, hkey_out)) + } + + /// RegCloseKey(HKEY) -> LSTATUS + fn reg_close_key(hkey: usize) -> Result { + let status = invoke( + "advapi32.dll", + "RegCloseKey", + FlatReturnKind::I32, + &[WinRTValue::RawPtr(hkey as *mut c_void)], + )?; + Ok(status.as_i32().unwrap()) + } + + /// RegQueryValueExW(HKEY, LPCWSTR lpValueName, LPDWORD lpReserved, + /// LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData) -> LSTATUS + /// + /// Returns `(status, type, bytes_written, buffer)` where `buffer` is the + /// caller-allocated data buffer (unchanged on error but with valid length + /// on ERROR_MORE_DATA). + fn reg_query_value( + hkey: usize, + value_name: &str, + mut buffer: Vec, + ) -> Result<(i32, u32, u32, Vec)> { + let name_arg = wide_string_arg(value_name)?; + let mut reg_type: u32 = 0; + let mut cb_data: u32 = buffer.len() as u32; // in: capacity; out: bytes written + let data_ptr = if buffer.is_empty() { + std::ptr::null_mut() + } else { + buffer.as_mut_ptr() as *mut c_void + }; + let status = invoke( + "advapi32.dll", + "RegQueryValueExW", + FlatReturnKind::I32, + &[ + WinRTValue::RawPtr(hkey as *mut c_void), + name_arg.as_winrt_value(), + WinRTValue::RawPtr(std::ptr::null_mut()), // lpReserved + WinRTValue::RawPtr(&mut reg_type as *mut u32 as *mut c_void), + WinRTValue::RawPtr(data_ptr), + WinRTValue::RawPtr(&mut cb_data as *mut u32 as *mut c_void), + ], + )?; + Ok((status.as_i32().unwrap(), reg_type, cb_data, buffer)) + } + + /// Decode a REG_SZ payload (UTF-16LE bytes, possibly NUL-terminated) into + /// a Rust String. `cb_bytes` is the count reported by RegQueryValueExW. + fn decode_reg_sz(buffer: &[u8], cb_bytes: u32) -> String { + let byte_len = cb_bytes as usize; + assert!(byte_len <= buffer.len(), "cb_bytes exceeds buffer"); + // REG_SZ values are wide-char aligned. Truncate a trailing NUL if any. + let mut u16s: Vec = buffer[..byte_len] + .chunks_exact(2) + .map(|c| u16::from_le_bytes([c[0], c[1]])) + .collect(); + if u16s.last() == Some(&0) { + u16s.pop(); + } + String::from_utf16_lossy(&u16s) + } + + /// Normal path: open HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion, + /// read the REG_SZ "ProductName" value, and verify it looks like Windows. + /// + /// Proves: (a) HKEY out via pointer-to-pointer, (b) caller-allocated + /// LPBYTE lpData + in/out LPDWORD lpcbData, (c) UTF-16LE decode. + #[test] + fn flat_call_reads_registry_product_name() -> Result<()> { + let (status, hkey) = reg_open_key( + HKEY_LOCAL_MACHINE, + r"SOFTWARE\Microsoft\Windows NT\CurrentVersion", + )?; + assert_eq!(status, ERROR_SUCCESS, "RegOpenKeyExW failed: {status}"); + assert_ne!(hkey, 0, "RegOpenKeyExW returned a null HKEY"); + + let buffer = vec![0u8; 512]; + let (status, reg_type, cb, buffer) = reg_query_value(hkey, "ProductName", buffer)?; + // Always close the key, even if the query failed. + let close_status = reg_close_key(hkey)?; + assert_eq!(close_status, ERROR_SUCCESS); + + assert_eq!(status, ERROR_SUCCESS, "RegQueryValueExW failed: {status}"); + assert_eq!(reg_type, REG_SZ, "ProductName should be REG_SZ"); + assert!(cb > 0, "cb_data should reflect bytes written"); + + let product_name = decode_reg_sz(&buffer, cb); + assert!(!product_name.is_empty(), "ProductName should not be empty"); + assert!( + product_name.to_lowercase().contains("windows"), + "ProductName should mention Windows, got {product_name:?}" + ); + Ok(()) + } + + /// Corner case: opening a non-existent subkey returns ERROR_FILE_NOT_FOUND + /// and the out HKEY slot stays null. + #[test] + fn flat_call_reg_open_key_missing_returns_file_not_found() -> Result<()> { + let (status, hkey) = reg_open_key(HKEY_LOCAL_MACHINE, r"SOFTWARE\DynWinrt\NoSuchKey\Nope")?; + assert_eq!( + status, ERROR_FILE_NOT_FOUND, + "expected ERROR_FILE_NOT_FOUND" + ); + assert_eq!(hkey, 0, "out HKEY should stay null on failure"); + Ok(()) + } + + /// Corner case: querying a value that doesn't exist returns + /// ERROR_FILE_NOT_FOUND (the same LSTATUS the flat wrapper must surface). + #[test] + fn flat_call_reg_query_missing_value_returns_file_not_found() -> Result<()> { + let (status, hkey) = reg_open_key( + HKEY_LOCAL_MACHINE, + r"SOFTWARE\Microsoft\Windows NT\CurrentVersion", + )?; + assert_eq!(status, ERROR_SUCCESS); + + let (query_status, _reg_type, _cb, _buf) = + reg_query_value(hkey, "ThisValueShouldNeverExist_DynWinrt", vec![0u8; 32])?; + let _ = reg_close_key(hkey)?; + assert_eq!(query_status, ERROR_FILE_NOT_FOUND); + Ok(()) + } + + /// Corner case: buffer-too-small returns ERROR_MORE_DATA and the in/out + /// `lpcbData` slot is rewritten with the required byte count. This + /// specifically proves the in/out DWORD marshalling: we pass 4 in and + /// read a >4 out from the same slot. + /// + /// NOTE: RegQueryValueExW treats `lpData == NULL` as a size-query and + /// returns SUCCESS, not ERROR_MORE_DATA. We therefore pass a real (too + /// small) buffer. + #[test] + fn flat_call_reg_query_buffer_too_small_reports_required_size() -> Result<()> { + let (status, hkey) = reg_open_key( + HKEY_LOCAL_MACHINE, + r"SOFTWARE\Microsoft\Windows NT\CurrentVersion", + )?; + assert_eq!(status, ERROR_SUCCESS); + + // 4 bytes is guaranteed to be smaller than any REG_SZ ProductName. + let (query_status, reg_type, required_bytes, _buf) = + reg_query_value(hkey, "ProductName", vec![0u8; 4])?; + let _ = reg_close_key(hkey)?; + assert_eq!(query_status, ERROR_MORE_DATA); + assert_eq!(reg_type, REG_SZ); + assert!( + required_bytes > 4, + "lpcbData in/out slot should be rewritten with the required byte count \ + (got {required_bytes})" + ); + Ok(()) + } + + /// Corner case (size-query idiom): passing `lpData == NULL` with + /// `cb == 0` is the documented way to query the required size. This + /// specifically proves the null-pointer marshalling path. + #[test] + fn flat_call_reg_query_null_data_returns_size_query() -> Result<()> { + let (status, hkey) = reg_open_key( + HKEY_LOCAL_MACHINE, + r"SOFTWARE\Microsoft\Windows NT\CurrentVersion", + )?; + assert_eq!(status, ERROR_SUCCESS); + + let (query_status, reg_type, required_bytes, _buf) = + reg_query_value(hkey, "ProductName", Vec::new())?; + let _ = reg_close_key(hkey)?; + // Win32 documents this "null data, 0 cb" path as returning + // ERROR_SUCCESS with the required size in cb_data. + assert_eq!(query_status, ERROR_SUCCESS); + assert_eq!(reg_type, REG_SZ); + assert!(required_bytes > 0); + Ok(()) + } +} diff --git a/docs/flat-win32-support.md b/docs/flat-win32-support.md new file mode 100644 index 00000000..d7149528 --- /dev/null +++ b/docs/flat-win32-support.md @@ -0,0 +1,76 @@ +# Flat Win32 support + +Flat Win32 APIs are DLL exports described by `[DllImport]` methods in +`Windows.Win32.winmd`. They do not use WinRT activation or COM vtables. + +```text +Windows.Win32.winmd + -> flat Win32 metadata model + -> validated ABI and projection plan + -> generated JavaScript and declarations + -> @microsoft/dynwinrt/win32 + -> System32 DLL export through libffi +``` + +## Generate bindings + +```powershell +dynwinrt-codegen generate ` + --winmd C:\path\to\Windows.Win32.winmd ` + --namespace Windows.Win32.System.Registry ` + --class-name Apis ` + --output .\generated +``` + +The namespace is isolated so multiple `Apis` containers can share one output: + +```text +generated/ + package.json + win32/ + Windows.Win32.System.Registry/ + Apis.js + Apis.d.ts + index.js + index.d.ts + package.json +``` + +Generated modules import the dedicated +`@microsoft/dynwinrt/win32` entrypoint. The npm package root remains WinRT-only, +and `@microsoft/dynwinrt/com` remains Classic COM-only. + +## Current supported subset + +- x64 and ARM64 system DLL exports; +- fixed-arity functions; +- signed and unsigned integers from 8 through 64 bits; +- `float`, `double`, `BOOL`, and 32-bit enums; +- explicitly classified Win32 handle values; +- UTF-16 input strings; +- caller-encoded ANSI byte strings; +- single-level scalar and handle out/in-out parameters; +- caller-owned buffers with explicit element-count or byte-count metadata; +- direct handle and function-pointer returns; and +- atomic `GetLastError` capture when metadata marks an export accordingly. + +Metadata DLL names are loaded from System32 with +`LOAD_LIBRARY_SEARCH_SYSTEM32`. Arbitrary DLL paths are rejected. + +## Fail-closed behavior + +An individual export is omitted with a diagnostic when its complete ABI cannot +be represented safely. This includes: + +- variadic functions; +- architecture-specific overloads that differ between x64 and ARM64; +- by-value structs or unions without a native layout model; +- unbounded writable pointers and string buffers; +- pointer returns without known lifetime or ownership; +- nested or unsized native arrays; +- JavaScript callbacks without a managed native thunk; +- BSTR, SAFEARRAY, VARIANT, and other allocator-sensitive values; and +- enums whose underlying ABI cannot be represented faithfully. + +Generated bindings are a safe subset of the requested `Apis` container, not a +claim that every function in a namespace is supported. diff --git a/tests/e2e_test.ps1 b/tests/e2e_test.ps1 index 271cac0b..cc91d510 100644 --- a/tests/e2e_test.ps1 +++ b/tests/e2e_test.ps1 @@ -3,7 +3,8 @@ # Licensed under the MIT License. # # E2E test orchestrator: build, generate, run language-specific runners, collect results. -# Test logic lives in runners/py_runner.py, runners/ts_runner.ts, and runners/com/*.mjs. +# Test logic lives in runners/py_runner.py, runners/ts_runner.ts, +# runners/com/*.mjs, and runners/flat/*.mjs. # # Usage: # .\tests\e2e_test.ps1 # Full (build + generate + test) @@ -11,11 +12,12 @@ # .\tests\e2e_test.ps1 -Lang py # Python only # .\tests\e2e_test.ps1 -Lang ts # TypeScript only # .\tests\e2e_test.ps1 -Lang com # Classic COM only +# .\tests\e2e_test.ps1 -Lang flat # Flat Win32 only param( [switch]$SkipBuild, - [ValidateSet("py", "ts", "com")] - [string[]]$Lang = @("py", "ts", "com") + [ValidateSet("py", "ts", "com", "flat")] + [string[]]$Lang = @("py", "ts", "com", "flat") ) $ErrorActionPreference = "Stop" @@ -30,6 +32,7 @@ $comShellDir = Join-Path $comBindingsDir "shell" $comInteropDir = Join-Path $comBindingsDir "interop" $comWicDir = Join-Path $comBindingsDir "wic" $comSmtcDir = Join-Path $comBindingsDir "smtc" +$flatBindingsDir = Join-Path $e2eDir "flat" $env:PATH = "$env:USERPROFILE\.cargo\bin;$env:PATH" @@ -45,9 +48,9 @@ if ("py" -in $Lang -and -not $hasPython) { Write-Host " SKIP Python (not installed)" -ForegroundColor DarkYellow $Lang = $Lang | Where-Object { $_ -ne "py" } } -if (("ts" -in $Lang -or "com" -in $Lang) -and -not $hasNode) { +if (("ts" -in $Lang -or "com" -in $Lang -or "flat" -in $Lang) -and -not $hasNode) { Write-Host " SKIP JavaScript E2E (Node.js not installed)" -ForegroundColor DarkYellow - $Lang = @($Lang | Where-Object { $_ -notin @("ts", "com") }) + $Lang = @($Lang | Where-Object { $_ -notin @("ts", "com", "flat") }) } function Find-Win32Winmd { @@ -69,15 +72,15 @@ function Find-Win32Winmd { } $win32Winmd = $null -if ("com" -in $Lang) { +if ("com" -in $Lang -or "flat" -in $Lang) { $win32Winmd = Find-Win32Winmd if (-not $win32Winmd) { if ($langWasExplicit -or $env:DYNWINRT_REQUIRE_WIN32_METADATA -eq "1") { - Write-Error "Classic COM E2E requires Windows.Win32.winmd. Set DYNWINRT_WIN32_WINMD or install Microsoft.Windows.SDK.Win32Metadata." + Write-Error "Classic COM and flat Win32 E2E require Windows.Win32.winmd. Set DYNWINRT_WIN32_WINMD or install Microsoft.Windows.SDK.Win32Metadata." exit 1 } - Write-Host " SKIP Classic COM (Windows.Win32.winmd not found)" -ForegroundColor DarkYellow - $Lang = @($Lang | Where-Object { $_ -ne "com" }) + Write-Host " SKIP Classic COM and flat Win32 (Windows.Win32.winmd not found)" -ForegroundColor DarkYellow + $Lang = @($Lang | Where-Object { $_ -notin @("com", "flat") }) } else { $env:DYNWINRT_WIN32_WINMD = $win32Winmd Write-Host " Win32 metadata: $win32Winmd" @@ -114,7 +117,7 @@ if (-not $SkipBuild) { Pop-Location } - if ("ts" -in $Lang -or "com" -in $Lang) { + if ("ts" -in $Lang -or "com" -in $Lang -or "flat" -in $Lang) { Push-Location (Join-Path $root "bindings\js") npm install --quiet 2>&1 | Out-Null if ($LASTEXITCODE -ne 0) { Write-Error "npm install failed"; exit 1 } @@ -219,6 +222,31 @@ if ("com" -in $Lang) { if ($LASTEXITCODE -ne 0) { Write-Error "SMTC WinRT generation failed"; exit 1 } } +if ("flat" -in $Lang) { + Write-Host "`n--- Generate (flat Win32) ---" -ForegroundColor Yellow + $flatRuntimeImport = "../../../../../../bindings/js/dist/win32.js" + $flatTargets = @( + @{ Namespace = "Windows.Win32.System.Registry"; Output = "registry" }, + @{ Namespace = "Windows.Win32.System.LibraryLoader"; Output = "library-loader" }, + @{ Namespace = "Windows.Win32.System.SystemInformation"; Output = "system-information" }, + @{ Namespace = "Windows.Win32.System.Threading"; Output = "threading" }, + @{ Namespace = "Windows.Win32.Graphics.Direct2D"; Output = "direct2d" } + ) + foreach ($target in $flatTargets) { + $flatOutput = Join-Path $flatBindingsDir $target.Output + & cargo run -p dynwinrt-codegen --release --quiet -- generate ` + --winmd $win32Winmd ` + --namespace $target.Namespace ` + --class-name Apis ` + --output $flatOutput ` + --import-name $flatRuntimeImport + if ($LASTEXITCODE -ne 0) { + Write-Error "Flat Win32 generation failed: $($target.Namespace)" + exit 1 + } + } +} + # -------------------------------------------------------------------------- # Run language-specific runners # -------------------------------------------------------------------------- @@ -296,6 +324,28 @@ if ("com" -in $Lang) { } } +if ("flat" -in $Lang) { + Write-Host "`n--- Flat Win32 E2E ---" -ForegroundColor Yellow + $flatRunners = @("registry.mjs", "returns.mjs") + $flatPassed = 0 + $flatFailed = 0 + foreach ($runner in $flatRunners) { + Write-Host " $runner" + & node (Join-Path $runnersDir "flat\$runner") + if ($LASTEXITCODE -eq 0) { + $flatPassed++ + } else { + $flatFailed++ + } + } + if ($flatFailed -eq 0) { $totalPass++ } else { $totalFail++ } + $allResults += [pscustomobject]@{ + language = "flat" + passed = $flatPassed + total = $flatRunners.Count + } +} + # -------------------------------------------------------------------------- # Summary # -------------------------------------------------------------------------- diff --git a/tests/runners/flat/registry.mjs b/tests/runners/flat/registry.mjs new file mode 100644 index 00000000..c35c0914 --- /dev/null +++ b/tests/runners/flat/registry.mjs @@ -0,0 +1,153 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// +// E2E test for the GENERATED flat-Win32 Registry wrapper. +// +// This test imports the generated Windows.Win32.System.Registry.Apis wrapper +// and reads a real +// registry value through it: HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion +// ProductName. On a normal Windows install this reads something like +// "Windows 10 Pro" or "Windows 11 Enterprise". +// +// Composes a `Registry.getString(hive, subKey, valueName)` helper on top of +// the generated `regOpenKeyExW` / `regQueryValueExW` / `regCloseKey` — the +// wrapper itself is codegen output; the composition (retry-on-more-data, +// REG_SZ decode) is a thin ergonomic layer. + +import { existsSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, resolve } from 'node:path'; + +// The generated flat-Win32 Registry wrapper under +// ./generated/flat_registry/ is a codegen fixture and is intentionally +// gitignored. On a clean checkout it must be regenerated before this test can +// run — otherwise a static import below would fail with an opaque +// module-not-found error. Fail early with a helpful message that spells out +// the exact regeneration command. +const __dirname_flat = dirname(fileURLToPath(import.meta.url)); +const FLAT_FIXTURE = resolve( + __dirname_flat, + '../../e2e_generated/flat/registry/win32/Windows.Win32.System.Registry/Apis.js' +); +if (!existsSync(FLAT_FIXTURE)) { + console.error(`[e2e] FAIL: flat_registry fixture not found: ${FLAT_FIXTURE}`); + console.error(`[e2e] This fixture is gitignored — regenerate it with:`); + console.error(` cargo run -p dynwinrt-codegen -- generate \\`); + console.error(` --winmd C:\\s\\win32metadata\\Windows.Win32.winmd \\`); + console.error(` --namespace Windows.Win32.System.Registry \\`); + console.error(` --class-name Apis \\`); + console.error(` --output tests/e2e_generated/flat/registry \\`); + console.error(` --import-name ../../../../../../bindings/js/dist/win32.js`); + process.exit(1); +} + +const { + regOpenKeyExW, + regQueryValueExW, + regCloseKey, +} = await import('../../e2e_generated/flat/registry/win32/Windows.Win32.System.Registry/Apis.js'); + +// Predefined HKEY hive constants. These are stable Win32 pseudo-handles that +// live in the same address slot on x86/x64 and are safe to pass as bigints. +const HKEY_LOCAL_MACHINE = 0x80000002n; + +// KEY_READ = STANDARD_RIGHTS_READ (0x00020000) | KEY_QUERY_VALUE (0x0001) +// | KEY_ENUMERATE_SUB_KEYS (0x0008) | KEY_NOTIFY (0x0010) +const KEY_READ = 0x20019; + +const ERROR_SUCCESS = 0; +const ERROR_MORE_DATA = 234; + +// REG_VALUE_TYPE constants we care about. +const REG_SZ = 1; +const REG_EXPAND_SZ = 2; + +function decodeWideNulTerminated(buf, byteLength) { + // REG_SZ / REG_EXPAND_SZ values are stored as UTF-16LE with (usually) a + // NUL terminator inside the reported byte length. Strip the trailing NUL + // if present so the surface string doesn't end in U+0000. + let end = byteLength; + if (end >= 2 && buf.readUInt16LE(end - 2) === 0) { + end -= 2; + } + return buf.toString('utf16le', 0, end); +} + +function getString(hive, subKey, valueName) { + // 1. Open the subkey via the generated wrapper. `regOpenKeyExW` returns + // { status, phkResult } — natural JS shape, no raw flatInvoke leaking. + const openRes = regOpenKeyExW(hive, subKey, 0, KEY_READ); + if (openRes.status !== ERROR_SUCCESS) { + throw new Error( + `RegOpenKeyExW('${subKey}') failed with LSTATUS=${openRes.status}`, + ); + } + const hKey = openRes.phkResult; + try { + // 2. Probe the required buffer size. Passing data=null and + // lpcbData=0 causes RegQueryValueExW to fill lpcbData with the + // needed byte count and return either ERROR_SUCCESS or + // ERROR_MORE_DATA depending on the OS / value size. + let probe = regQueryValueExW(hKey, valueName, null, null, 0); + if (probe.status !== ERROR_SUCCESS && probe.status !== ERROR_MORE_DATA) { + throw new Error( + `RegQueryValueExW('${valueName}') sizing failed with LSTATUS=${probe.status}`, + ); + } + const needed = probe.lpcbData; + if (needed === 0) { + return ''; + } + + // 3. Allocate a caller-owned Buffer and re-query. The generated + // wrapper accepts the Buffer as the opaque `data` param and the + // initial size as `lpcbData`; the returned object carries back + // both the type discriminator and the number of bytes actually + // written. + const buf = Buffer.alloc(needed); + const res = regQueryValueExW(hKey, valueName, null, buf, needed); + if (res.status !== ERROR_SUCCESS) { + throw new Error( + `RegQueryValueExW('${valueName}') read failed with LSTATUS=${res.status}`, + ); + } + if (res.type !== REG_SZ && res.type !== REG_EXPAND_SZ) { + throw new Error( + `Value '${valueName}' has type ${res.type}; expected REG_SZ or REG_EXPAND_SZ`, + ); + } + return decodeWideNulTerminated(buf, res.lpcbData); + } finally { + // 4. Always release the key handle — codegen exposes this as a + // natural single-arg call returning `{ status }`. + const closeRes = regCloseKey(hKey); + if (closeRes.status !== ERROR_SUCCESS) { + // Not fatal, but surface it so leaks are visible in CI logs. + console.warn( + `RegCloseKey failed with LSTATUS=${closeRes.status}`, + ); + } + } +} + +function main() { + const subKey = 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion'; + const valueName = 'ProductName'; + const productName = getString(HKEY_LOCAL_MACHINE, subKey, valueName); + + if (typeof productName !== 'string' || productName.length === 0) { + console.error(`FAIL: expected non-empty string, got ${JSON.stringify(productName)}`); + process.exit(1); + } + if (!productName.includes('Windows')) { + console.error( + `FAIL: expected ProductName to contain 'Windows', got ${JSON.stringify(productName)}`, + ); + process.exit(1); + } + + console.log(`ProductName = ${JSON.stringify(productName)}`); + console.log('PASS'); +} + +main(); diff --git a/tests/runners/flat/returns.mjs b/tests/runners/flat/returns.mjs new file mode 100644 index 00000000..2d1e9d6d --- /dev/null +++ b/tests/runners/flat/returns.mjs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// +// E2E coverage for flat-Win32 return kinds that require exact ABI handling: +// pointer/function-pointer, void, u64, and optional float returns. + +import assert from 'node:assert/strict'; +import { existsSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + +const __dirname_flat_returns = dirname(fileURLToPath(import.meta.url)); + +function fixture(path) { + return resolve(__dirname_flat_returns, path); +} + +function requireFixture(path, namespace) { + const full = fixture(path); + if (existsSync(full)) { + return full; + } + console.error(`[e2e] FAIL: required fixture not found: ${full}`); + console.error('[e2e] Regenerate it with:'); + console.error(' target\\release\\dynwinrt-codegen.exe generate \\'); + console.error(' --winmd C:\\s\\win32metadata\\Windows.Win32.winmd \\'); + console.error(` --namespace ${namespace} \\`); + console.error(' --class-name Apis \\'); + console.error(` --output ${dirname(full)} \\`); + console.error(' --import-name ../../../dist/index.js'); + process.exit(1); +} + +const libraryLoaderPath = requireFixture( + '../../e2e_generated/flat/library-loader/win32/Windows.Win32.System.LibraryLoader/Apis.js', + 'Windows.Win32.System.LibraryLoader', +); +const systemInformationPath = requireFixture( + '../../e2e_generated/flat/system-information/win32/Windows.Win32.System.SystemInformation/Apis.js', + 'Windows.Win32.System.SystemInformation', +); +const threadingPath = requireFixture( + '../../e2e_generated/flat/threading/win32/Windows.Win32.System.Threading/Apis.js', + 'Windows.Win32.System.Threading', +); + +const { + getModuleHandleW, + getProcAddress, +} = await import(pathToFileURL(libraryLoaderPath).href); +const { + getTickCount64, +} = await import(pathToFileURL(systemInformationPath).href); +const { sleep } = await import(pathToFileURL(threadingPath).href); + +function pass(msg) { + console.log(`[e2e] PASS: ${msg}`); +} + +// F4: FARPROC/function-pointer returns must be BigInt pointer values, not +// truncated I32/EAX numbers. +const k32 = getModuleHandleW('KERNEL32.dll').result; +assert.equal(typeof k32, 'bigint'); +assert.notEqual(k32, 0n, 'KERNEL32.dll should already be loaded'); + +const missingModule = getModuleHandleW('dynwinrt-module-that-does-not-exist.dll'); +assert.equal(missingModule.result, 0n); +assert.equal(missingModule.lastError, 126); +pass(`GetModuleHandleW captured LastError=${missingModule.lastError} atomically`); + +const procName = Buffer.from('GetProcAddress\0', 'ascii'); +const proc = getProcAddress(k32, procName).result; +assert.equal(typeof proc, 'bigint'); +assert.notEqual(proc, 0n, 'GetProcAddress export should resolve'); +assert(proc > 0xffffffffn, 'x64 function pointer should not be EAX-truncated'); +pass(`GetProcAddress returned full pointer ${proc}`); + +// U64 return: GetTickCount64 must surface as BigInt and be monotonic. +const firstTick = getTickCount64().result; +await new Promise((resolveDelay) => setTimeout(resolveDelay, 20)); +const secondTick = getTickCount64().result; +assert.equal(typeof firstTick, 'bigint'); +assert(firstTick > 0n); +assert(secondTick >= firstTick); +pass(`GetTickCount64 returned monotonic BigInts ${firstTick} -> ${secondTick}`); + +// Void return with a scalar input. +const voidRet = sleep(0); +assert.equal(voidRet, undefined); +pass('Sleep(0) returned undefined'); + +// Optional F32 return + F32 arg: Direct2D is present on normal Windows 10/11, +// but keep this resilient because the Rust Win32 runtime unit is the authoritative +// float ABI proof. +const direct2DPath = fixture('../../e2e_generated/flat/direct2d/win32/Windows.Win32.Graphics.Direct2D/Apis.js'); +let floatLiveCheckSkipped = undefined; +if (!existsSync(direct2DPath)) { + floatLiveCheckSkipped = 'Direct2D fixture not generated'; + console.log(`[e2e] SKIP: ${floatLiveCheckSkipped}`); +} else { + const direct2D = await import(pathToFileURL(direct2DPath).href); + if (typeof direct2D.d2D1Tan !== 'function') { + floatLiveCheckSkipped = 'Direct2D D2D1Tan export unavailable in generated fixture'; + console.log(`[e2e] SKIP: ${floatLiveCheckSkipped}`); + } else { + const zero = direct2D.d2D1Tan(0).result; + const one = direct2D.d2D1Tan(Math.PI / 4).result; + assert.equal(typeof zero, 'number'); + assert.equal(typeof one, 'number'); + assert(Math.abs(zero) < 1e-6, `D2D1Tan(0) = ${zero}`); + assert(Math.abs(one - 1) < 1e-5, `D2D1Tan(pi/4) = ${one}`); + pass(`D2D1Tan float return/arg works (${zero}, ${one})`); + } +} + +if (floatLiveCheckSkipped) { + console.log(`PASS (float live check SKIPPED — ${floatLiveCheckSkipped}; covered by Rust unit test)`); +} else { + console.log('PASS'); +} diff --git a/tools/dynwinrt-codegen/src/codegen/mod.rs b/tools/dynwinrt-codegen/src/codegen/mod.rs index 23ec598c..a581e291 100644 --- a/tools/dynwinrt-codegen/src/codegen/mod.rs +++ b/tools/dynwinrt-codegen/src/codegen/mod.rs @@ -4,6 +4,7 @@ pub mod com; pub mod common; pub mod package; +pub mod win32; pub mod winrt; // Preserve the existing public module paths while callers migrate to diff --git a/tools/dynwinrt-codegen/src/codegen/win32/mod.rs b/tools/dynwinrt-codegen/src/codegen/win32/mod.rs new file mode 100644 index 00000000..0ee39a3d --- /dev/null +++ b/tools/dynwinrt-codegen/src/codegen/win32/mod.rs @@ -0,0 +1,1609 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Flat-Win32 `[DllImport]` code generation. +//! +//! Reads a `FlatApisMeta` (a container of DllImport static methods on an +//! `Apis` class in `Windows.Win32.winmd`) and emits a natural JS/DTS wrapper +//! that calls into the dedicated `DynWin32` runtime under the hood. +//! +//! ## Emission model +//! +//! For each flat method we categorise every parameter into one of three shapes: +//! +//! * **Input scalar / handle / enum / string** — passed by value into the JS +//! function's argument list. +//! * **Pointer to a small scalar/handle/enum**, with direction `[out]` — the +//! generator allocates a caller-side `Buffer` internally and projects the +//! value into the JS return. +//! * **Pointer to a byte buffer / void / opaque struct** — remains in the +//! argument list as a `Buffer | null` slot so the caller controls allocation +//! (matches the natural Win32 idiom for `RegQueryValueExW`'s `lpData`). +//! +//! Non-zero LSTATUS/WIN32_ERROR/HRESULT returns are surfaced as a `.status` +//! field on the returned object (or as the sole `number` return when there +//! are no projected out-params). The emitted `.js` never throws on non-zero +//! LSTATUS — the caller decides what to do (mirroring the hand-written +//! `bindings/js/e2e/registry.js` design). + +use std::collections::{BTreeSet, HashSet}; + +use crate::meta::{ + FlatAbiType, FlatApisMeta, FlatBufferSize, FlatDirection, FlatMethodMeta, FlatParamMeta, +}; +use crate::types::TypeMeta; + +/// Rendered flat-Apis output: primary `.js` + `.d.ts` for the class, plus +/// zero or more sibling files (one `.js` + `.d.ts` per referenced enum). +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FlatGeneratedOutput { + pub js: String, + pub dts: String, + /// Additional files (filename → content), stable-sorted by filename. + pub extra_files: Vec<(String, String)>, +} + +// --------------------------------------------------------------------------- +// Public entry +// --------------------------------------------------------------------------- + +pub fn generate_flat_apis_files(meta: &FlatApisMeta) -> FlatGeneratedOutput { + generate_flat_apis_files_with_import(meta, "@microsoft/dynwinrt/win32") +} + +pub fn generate_flat_apis_files_with_import( + meta: &FlatApisMeta, + runtime_import: &str, +) -> FlatGeneratedOutput { + // Fail-loud filter: methods whose return type isn't representable by the + // current flat-call ABI MUST be skipped rather than silently emitted as + // a truncating I32 read. Print a per-skip warning so the operator sees + // what was omitted and why. + let (kept, skipped) = partition_supported_methods(&meta.methods); + for (name, reason) in &skipped { + eprintln!( + "warning: dynwinrt-codegen: skipping flat export `{}::{}` — {}", + meta.class_name, name, reason + ); + } + let kept_enum_keys = referenced_enum_keys_for_methods(&kept); + let referenced_enums = meta + .referenced_enums + .iter() + .filter(|en| match en { + TypeMeta::Enum { + namespace, name, .. + } => kept_enum_keys.contains(&(namespace.clone(), name.clone())), + _ => false, + }) + .cloned() + .collect(); + let filtered_meta = FlatApisMeta { + methods: kept, + referenced_enums, + ..meta.clone() + }; + + let js = render_js(&filtered_meta, runtime_import); + let dts = render_dts(&filtered_meta); + + // Sibling files: one per referenced enum. + // + // Enum sibling files (`Foo.js`, `Foo.d.ts`) key on the simple name only, + // so two distinct enums that share the same simple name from different + // namespaces would collide here and produce a wrong-shape enum file + // (only one variant survives). `parse_flat_apis_from_index` already + // deduplicates by `(namespace, name)` — but if the caller assembles a + // `FlatApisMeta` with a genuine simple-name collision across + // namespaces, we fail loud with a diagnostic rather than emit a + // corrupt Apis module. + let mut by_simple_name: std::collections::BTreeMap<&str, Vec<&str>> = + std::collections::BTreeMap::new(); + for en in &filtered_meta.referenced_enums { + if let TypeMeta::Enum { + namespace, name, .. + } = en + { + by_simple_name.entry(name).or_default().push(namespace); + } + } + for (name, namespaces) in &by_simple_name { + if namespaces.len() > 1 { + panic!( + "flat codegen: multiple distinct enums named `{name}` referenced by \ + `{}` from namespaces {:?}. Sibling-file emission would collide on the \ + `{name}` simple name. Split the export or add namespace-qualified \ + aliasing in the codegen before proceeding.", + filtered_meta.class_name, namespaces, + ); + } + } + + let mut extra_files: Vec<(String, String)> = Vec::new(); + for en in &filtered_meta.referenced_enums { + if let TypeMeta::Enum { name, .. } = en { + let (ejs, edts) = render_enum_files(en); + extra_files.push((format!("{}.js", name), ejs)); + extra_files.push((format!("{}.d.ts", name), edts)); + } + } + extra_files.sort_by(|a, b| a.0.cmp(&b.0)); + + FlatGeneratedOutput { + js, + dts, + extra_files, + } +} + +/// Split the methods into (kept, skipped). Skipped methods are those the +/// codegen cannot yet emit correctly — silently emitting them would produce +/// wrong-value wrappers (truncation, mis-marshalling), which violates the +/// fail-loud principle applied elsewhere. +fn partition_supported_methods( + methods: &[FlatMethodMeta], +) -> (Vec, Vec<(String, &'static str)>) { + let mut kept: Vec = Vec::new(); + let mut skipped: Vec<(String, &'static str)> = Vec::new(); + for m in methods { + if let Some(reason) = unsupported_return_reason(&m.return_type) { + skipped.push((m.name.clone(), reason)); + continue; + } + if let Some(reason) = m + .params + .iter() + .find_map(|p| unsupported_param_reason(&p.abi)) + { + skipped.push((m.name.clone(), reason)); + continue; + } + if let Some(reason) = unsupported_method_reason(m) { + skipped.push((m.name.clone(), reason)); + continue; + } + kept.push(m.clone()); + } + (kept, skipped) +} + +fn unsupported_method_reason(method: &FlatMethodMeta) -> Option<&'static str> { + for param in &method.params { + if matches!(param.direction, FlatDirection::Out | FlatDirection::InOut) + && match ¶m.abi { + FlatAbiType::Ptr => true, + FlatAbiType::PtrTo(inner) => !is_small_scalarish(inner), + FlatAbiType::PWStr | FlatAbiType::PStr => true, + _ => false, + } + { + return Some( + "writable pointer has no modeled scalar storage, size relationship, or ownership", + ); + } + let FlatAbiType::NativeArray { element, size } = ¶m.abi else { + continue; + }; + match size { + FlatBufferSize::Unknown => { + return Some("native array has no usable size contract"); + } + FlatBufferSize::ElementCountParam(index) | FlatBufferSize::ByteCountParam(index) => { + let Some(count_param) = method.params.get(*index) else { + return Some("native array references a missing count parameter"); + }; + if matches!(classify(count_param), ParamSurface::OutScalar) { + return Some("native array capacity is produced only after the call"); + } + } + FlatBufferSize::Constant(_) => {} + } + if !matches!(size, FlatBufferSize::ByteCountParam(_)) + && flat_element_size(element).is_none() + { + return Some("native array element size is not modeled"); + } + } + None +} + +fn flat_element_size(typ: &FlatAbiType) -> Option { + match typ { + FlatAbiType::I8 | FlatAbiType::U8 => Some(1), + FlatAbiType::I16 | FlatAbiType::U16 | FlatAbiType::Char16 => Some(2), + FlatAbiType::I32 + | FlatAbiType::U32 + | FlatAbiType::F32 + | FlatAbiType::Bool + | FlatAbiType::Bool32 => Some(4), + FlatAbiType::I64 + | FlatAbiType::U64 + | FlatAbiType::F64 + | FlatAbiType::Ptr + | FlatAbiType::PtrTo(_) + | FlatAbiType::Handle { .. } + | FlatAbiType::FunctionPointer + | FlatAbiType::PWStr + | FlatAbiType::PStr => Some(8), + FlatAbiType::Enum { underlying, .. } => flat_element_size(underlying), + FlatAbiType::NativeArray { .. } | FlatAbiType::Void | FlatAbiType::Unknown => None, + } +} + +fn referenced_enum_keys_for_methods(methods: &[FlatMethodMeta]) -> HashSet<(String, String)> { + let mut keys = HashSet::new(); + for m in methods { + collect_referenced_enum_keys(&m.return_type, &mut keys); + for p in &m.params { + collect_referenced_enum_keys(&p.abi, &mut keys); + } + } + keys +} + +fn collect_referenced_enum_keys(t: &FlatAbiType, keys: &mut HashSet<(String, String)>) { + match t { + FlatAbiType::PtrTo(inner) => collect_referenced_enum_keys(inner, keys), + FlatAbiType::Enum { + namespace, + name, + underlying, + .. + } => { + keys.insert((namespace.clone(), name.clone())); + collect_referenced_enum_keys(underlying, keys); + } + _ => {} + } +} + +/// True when an enum's underlying ABI type cannot be faithfully represented on +/// the current JS enum surface. Enum members are `i32`-backed and project as a +/// `number`-based union, so only 32-bit-or-smaller integer underlyings are +/// representable. A 64-bit (`I64`/`U64`) or float (`F32`/`F64`) underlying would +/// silently emit truncated/wrong member constants and an ABI-mismatched calling +/// convention, so such methods are skipped fail-loud instead. +fn enum_underlying_unrepresentable(t: &FlatAbiType) -> bool { + matches!( + t, + FlatAbiType::Enum { underlying, .. } + if !matches!( + **underlying, + FlatAbiType::I8 + | FlatAbiType::U8 + | FlatAbiType::I16 + | FlatAbiType::U16 + | FlatAbiType::I32 + | FlatAbiType::U32 + ) + ) +} + +/// Returns `Some(reason)` if the given return type has no faithful mapping +/// to the current flat-call return-kind ABI. `None` means the type is +/// representable and the method can be emitted. +fn unsupported_return_reason(t: &FlatAbiType) -> Option<&'static str> { + if enum_underlying_unrepresentable(t) { + return Some( + "enum return type has a 64-bit/float underlying ABI with no faithful JS \ + enum projection; refusing to emit an unsafe fallback.", + ); + } + match t { + FlatAbiType::Unknown => { + Some("return type could not be classified; refusing to emit an ABI-unsafe I32 fallback") + } + FlatAbiType::Ptr + | FlatAbiType::PtrTo(_) + | FlatAbiType::NativeArray { .. } + | FlatAbiType::PWStr + | FlatAbiType::PStr => Some( + "returned pointer ownership/lifetime is not modeled; refusing to emit an ownerless pointer", + ), + _ => None, + } +} + +fn unsupported_param_reason(t: &FlatAbiType) -> Option<&'static str> { + // Enum params (by value) OR enum out-params (PtrTo(Enum)) with a 64-bit/float + // underlying can't be faithfully represented (i32-backed members, number-typed + // surface), so skip rather than emit ABI-mismatched constants/calling convention. + if enum_underlying_unrepresentable(t) + || matches!(t, FlatAbiType::PtrTo(inner) if enum_underlying_unrepresentable(inner)) + { + return Some( + "enum parameter has a 64-bit/float underlying ABI that the JS enum surface \ + cannot faithfully represent; refusing to emit an ABI-mismatched wrapper.", + ); + } + match t { + FlatAbiType::Unknown => Some( + "parameter type could not be classified as a by-value ABI type; \ + refusing to emit a wrapper that would pass a pointer where the callee \ + expects an inline value", + ), + FlatAbiType::NativeArray { element, .. } => match element.as_ref() { + FlatAbiType::Unknown + | FlatAbiType::Ptr + | FlatAbiType::PtrTo(_) + | FlatAbiType::I8 + | FlatAbiType::U8 + | FlatAbiType::I16 + | FlatAbiType::U16 + | FlatAbiType::I32 + | FlatAbiType::U32 + | FlatAbiType::I64 + | FlatAbiType::U64 + | FlatAbiType::F32 + | FlatAbiType::F64 + | FlatAbiType::Char16 + | FlatAbiType::Bool + | FlatAbiType::Bool32 + | FlatAbiType::Handle { .. } + | FlatAbiType::Enum { .. } + | FlatAbiType::FunctionPointer + | FlatAbiType::PWStr + | FlatAbiType::PStr => None, + FlatAbiType::Void | FlatAbiType::NativeArray { .. } => { + Some("nested or void native arrays are unsupported") + } + }, + _ => None, + } +} + +// --------------------------------------------------------------------------- +// Per-param classification +// --------------------------------------------------------------------------- + +/// How a flat parameter surfaces in the generated JS wrapper. +#[derive(Debug, Clone, PartialEq, Eq)] +enum ParamSurface { + /// Value passed by the caller (scalar, handle, enum, string, or opaque pointer). + Input, + /// Caller passes an initial value (a scalar); the wrapper allocates a + /// slot, writes the caller's value, calls the API, and reads the final + /// value back. Both a param slot and a return-object field appear. + InOutScalar, + /// A pure `[out]` pointer to a small scalar. The wrapper allocates the + /// slot internally and projects the value into the return object. + OutScalar, + /// Opaque pointer — remains in the argument list as `Buffer|bigint|null`. + OpaquePointer, +} + +fn classify(p: &FlatParamMeta) -> ParamSurface { + match &p.abi { + FlatAbiType::NativeArray { .. } | FlatAbiType::PStr => ParamSurface::OpaquePointer, + FlatAbiType::PtrTo(inner) => { + let is_projectable = is_small_scalarish(inner); + match (p.direction, is_projectable) { + (FlatDirection::Out, true) => ParamSurface::OutScalar, + (FlatDirection::InOut, true) => ParamSurface::InOutScalar, + _ => ParamSurface::OpaquePointer, + } + } + FlatAbiType::Ptr => ParamSurface::OpaquePointer, + // A PWSTR/PSTR (LPWSTR/LPSTR) parameter marked `[out]` or + // `[in,out]` is a caller-allocated output buffer (e.g. + // `RegEnumKeyW(..., LPWSTR name, ...)`, `RegLoadMUIStringW`), NOT + // a read-only string input. Surfacing it as `string | null` and + // marshalling via `_wideStringBuffer` would make these APIs + // unusable (the caller can't observe what was written into the + // freshly-allocated internal buffer). Route them through + // `OpaquePointer` so the caller supplies a Buffer they own, + // matching the actual Win32 usage pattern. + FlatAbiType::PWStr if matches!(p.direction, FlatDirection::Out | FlatDirection::InOut) => { + ParamSurface::OpaquePointer + } + _ => ParamSurface::Input, + } +} + +fn is_small_scalarish(t: &FlatAbiType) -> bool { + // NOTE: U8/I8 are intentionally EXCLUDED. Byte-sized pointer params in + // Win32 are overwhelmingly caller-allocated buffers (e.g. + // `RegQueryValueExW`'s `lpData: LPBYTE` with a separate `lpcbData: DWORD` + // size slot). Projecting them as scalar returns would silently promote a + // 1-byte read to the return object AND hide the buffer semantics. + matches!( + t, + FlatAbiType::I16 + | FlatAbiType::U16 + | FlatAbiType::I32 + | FlatAbiType::U32 + | FlatAbiType::I64 + | FlatAbiType::U64 + | FlatAbiType::Bool32 + | FlatAbiType::Handle { .. } + | FlatAbiType::Enum { .. } + ) +} + +// --------------------------------------------------------------------------- +// Return / status classification +// --------------------------------------------------------------------------- + +/// Whether the method's return should project as a Win32 `.status` numeric +/// field. Backed by `FlatMethodMeta::return_is_status`, which is set at +/// parse time by inspecting the raw winmd Type (HRESULT/NTSTATUS/LSTATUS) +/// and the mapped enum name (WIN32_ERROR-family). Deliberately does NOT +/// treat every I32/U32 as a status code — plain integer returns like +/// `GetCurrentProcessId -> u32` or `MulDiv -> i32` are real values and +/// must project as `{ result: number }`, not `{ status: number }`. +fn is_status_return(m: &FlatMethodMeta) -> bool { + m.return_is_status +} + +fn flat_ret_kind_literal(t: &FlatAbiType) -> &'static str { + // Map return type to the string literal passed to DynWin32.invoke. + match t { + FlatAbiType::I8 => "I8", + FlatAbiType::U8 => "U8", + FlatAbiType::I16 => "I16", + FlatAbiType::U16 | FlatAbiType::Char16 => "U16", + FlatAbiType::I32 | FlatAbiType::Bool | FlatAbiType::Bool32 => "I32", + FlatAbiType::U32 => "U32", + FlatAbiType::I64 => "I64", + FlatAbiType::U64 => "U64", + FlatAbiType::Enum { underlying, .. } => match **underlying { + FlatAbiType::I32 => "I32", + FlatAbiType::I8 => "I8", + FlatAbiType::U8 => "U8", + FlatAbiType::I16 => "I16", + FlatAbiType::U16 => "U16", + FlatAbiType::U32 => "U32", + _ => unreachable!("unsupported enum backing was filtered"), + }, + FlatAbiType::Void => "Void", + FlatAbiType::Ptr + | FlatAbiType::PtrTo(_) + | FlatAbiType::PWStr + | FlatAbiType::PStr + | FlatAbiType::Handle { .. } + | FlatAbiType::FunctionPointer => "Ptr", + FlatAbiType::F32 => "F32", + FlatAbiType::F64 => "F64", + FlatAbiType::NativeArray { .. } | FlatAbiType::Unknown => { + debug_assert!( + false, + "flat_ret_kind_literal: Unknown return should have been filtered upstream" + ); + "I32" + } + } +} + +fn flat_ret_decode_expr(t: &FlatAbiType, ret_kind: &str) -> String { + match (t, ret_kind) { + (FlatAbiType::Enum { underlying, .. }, _) + if matches!(underlying.as_ref(), FlatAbiType::U32) => + { + "(DynWin32.toNumber(_ret) | 0)".to_string() + } + (FlatAbiType::Bool | FlatAbiType::Bool32, _) => { + "(DynWin32.toNumber(_ret) !== 0)".to_string() + } + (_, "Ptr") => "DynWin32.toPointerBigint(_ret)".to_string(), + (_, "I64") => "DynWin32.toI64Bigint(_ret)".to_string(), + (_, "U64") => "DynWin32.toU64Bigint(_ret)".to_string(), + (_, "F32" | "F64") => "DynWin32.toF64(_ret)".to_string(), + (_, "Void") => "undefined".to_string(), + _ => "DynWin32.toNumber(_ret)".to_string(), + } +} + +// --------------------------------------------------------------------------- +// Naming +// --------------------------------------------------------------------------- + +fn camel_case(s: &str) -> String { + if s.is_empty() { + return String::new(); + } + let chars: Vec = s.chars().collect(); + let mut i = 0; + while i < chars.len() && chars[i].is_ascii_uppercase() { + i += 1; + } + if i == 0 { + return s.to_string(); + } + if i == chars.len() { + return s.to_ascii_lowercase(); + } + if i == 1 { + let mut out = String::with_capacity(s.len()); + out.push(chars[0].to_ascii_lowercase()); + for c in &chars[1..] { + out.push(*c); + } + return out; + } + // Multi-char uppercase followed by lowercase: last uppercase begins the next word. + let mut out = String::with_capacity(s.len()); + for c in &chars[..i - 1] { + out.push(c.to_ascii_lowercase()); + } + for c in &chars[i - 1..] { + out.push(*c); + } + out +} + +fn js_param_name(raw: &str, idx: usize) -> String { + let base = if raw.is_empty() { + format!("arg{}", idx) + } else { + raw.to_string() + }; + let stripped = strip_hungarian(&base); + let mut out = String::with_capacity(stripped.len()); + let mut chars = stripped.chars(); + if let Some(first) = chars.next() { + out.push(first.to_ascii_lowercase()); + } + for c in chars { + out.push(c); + } + // Reserved-word guard. + match out.as_str() { + "class" | "return" | "function" | "default" | "this" | "new" | "delete" | "let" + | "const" | "var" | "if" | "else" | "for" | "while" | "do" | "switch" | "case" + | "break" | "continue" | "true" | "false" | "null" | "undefined" | "in" | "of" + | "typeof" | "instanceof" | "throw" | "try" | "catch" | "finally" | "yield" | "async" + | "await" | "with" | "void" | "public" | "private" | "protected" | "package" | "static" + | "import" | "export" | "extends" | "super" | "arguments" | "status" | "result" => { + format!("{}_", out) + } + _ => out, + } +} + +/// Compute per-method JS parameter names, deduplicating collisions. Two +/// different Win32 params can strip to the same identifier (e.g. +/// `RegLoadMUIStringA` has both `pOutBuf` and `OutBuf` which both reduce to +/// `outBuf`). Duplicate parameter names are a fatal SyntaxError in strict +/// mode, so we suffix collisions with `_2`, `_3`, ... in encounter order. +fn js_param_names_for_method(m: &FlatMethodMeta) -> Vec { + let mut seen: std::collections::HashMap = std::collections::HashMap::new(); + let mut out = Vec::with_capacity(m.params.len()); + for (i, p) in m.params.iter().enumerate() { + let base = js_param_name(&p.name, i); + let name = match seen.get(&base).copied() { + Some(n) => { + let renamed = format!("{}_{}", base, n + 1); + seen.insert(base.clone(), n + 1); + renamed + } + None => { + seen.insert(base.clone(), 1); + base + } + }; + out.push(name); + } + out +} + +fn strip_hungarian(s: &str) -> &str { + let prefixes = [ + "lpwsz", "pwsz", "lpsz", "psz", "pwstr", "pcwstr", "lp", "pp", "ppv", "hwnd", "dw", "sz", + "cb", "cx", "cy", "cw", "ch", "cn", "cc", "np", "ph", "pd", "pf", "pv", + ]; + for p in prefixes { + if let Some(rest) = s.strip_prefix(p) { + if rest + .chars() + .next() + .map(|c| c.is_ascii_uppercase()) + .unwrap_or(false) + { + return rest; + } + } + } + s +} + +// --------------------------------------------------------------------------- +// Type surface +// --------------------------------------------------------------------------- + +fn dts_type_of(t: &FlatAbiType) -> String { + match t { + FlatAbiType::Void => "void".into(), + FlatAbiType::Bool | FlatAbiType::Bool32 => "boolean".into(), + FlatAbiType::I8 + | FlatAbiType::U8 + | FlatAbiType::I16 + | FlatAbiType::U16 + | FlatAbiType::I32 + | FlatAbiType::U32 + | FlatAbiType::Char16 => "number".into(), + FlatAbiType::I64 | FlatAbiType::U64 => "bigint".into(), + FlatAbiType::F32 | FlatAbiType::F64 => "number".into(), + FlatAbiType::PWStr => "string | null".into(), + FlatAbiType::PStr | FlatAbiType::NativeArray { .. } => { + "bigint | Buffer | Uint8Array | null".into() + } + FlatAbiType::Handle { name, .. } => name.clone(), + FlatAbiType::Enum { name, .. } => name.clone(), + FlatAbiType::Ptr | FlatAbiType::PtrTo(_) => "bigint | Buffer | Uint8Array | null".into(), + FlatAbiType::FunctionPointer => "bigint".into(), + // Opaque type we couldn't classify from metadata. At runtime it is + // marshalled as `DynWin32.pointer(var)` (the same shape as + // `Ptr`), so the .d.ts input type must match the runtime contract: + // a pointer-like BigInt/Buffer, not a permissive `unknown`. Using + // `unknown` here silently accepts arbitrary JS values that would + // then fail inside `DynWin32.pointer(...)` with a type error. + FlatAbiType::Unknown => "bigint | Buffer | null".into(), + } +} + +/// Return-position type for the flat wrapper. +/// +/// Distinct from [`dts_type_of`] because the runtime read side +/// (`render_method_js` around `DynWin32.toPointerBigint` / `toNumber`) +/// produces different JS values than the input-side types [`dts_type_of`] +/// accepts. Concretely: any `retKind === "Ptr"` (per +/// [`flat_ret_kind_literal`] — `Ptr`, `PtrTo(_)`, `PWStr`, `PStr`, +/// `Handle{..}`) is unconditionally converted via `asPointerBigint()`, +/// which returns a plain `bigint` (`0n` for null). Typing the `.d.ts` +/// `result` as `bigint | Buffer | null` or `string | null` (as +/// [`dts_type_of`] does for input params) would misdescribe the runtime. +/// All other kinds match [`dts_type_of`]: booleans → `boolean`, small +/// integers → `number`, enums → their alias. +fn dts_return_type_of(t: &FlatAbiType) -> String { + match t { + FlatAbiType::Void => "void".into(), + FlatAbiType::Ptr + | FlatAbiType::PtrTo(_) + | FlatAbiType::PWStr + | FlatAbiType::PStr + | FlatAbiType::Handle { .. } + | FlatAbiType::FunctionPointer => "bigint".into(), + FlatAbiType::NativeArray { .. } => unreachable!("native array returns are filtered"), + _ => dts_type_of(t), + } +} + +// --------------------------------------------------------------------------- +// .js rendering +// --------------------------------------------------------------------------- + +fn render_js(meta: &FlatApisMeta, runtime_import: &str) -> String { + let mut out = String::new(); + out.push_str("// Generated by dynwinrt-codegen — do not edit\n"); + out.push_str("// Flat-Win32 [DllImport] wrappers for "); + out.push_str(&meta.namespace); + out.push_str("."); + out.push_str(&meta.class_name); + out.push_str("\n"); + out.push_str("//\n// Each exported function is a natural JS wrapper around\n"); + out.push_str("// DynWin32.invoke(dll, entry, retKind, args). Pointer-to-scalar\n"); + out.push_str("// [out]/[in,out] params are projected as return-object fields; opaque\n"); + out.push_str("// pointer params (Buffer|bigint|null) stay in the argument list.\n\n"); + out.push_str(&format!( + "import {{ DynWin32 }} from '{runtime_import}';\n\n" + )); + + // A small runtime helper for wide- and narrow-string marshalling. + // Emitted inline so the generated file has no cross-file runtime + // dependencies beyond `dynwinrt`. + let mut methods_js = String::new(); + for m in &meta.methods { + render_method_js(&mut methods_js, m); + methods_js.push('\n'); + } + + out.push_str(WIDE_STRING_HELPER); + // The handle-slot helper is only needed when a method writes a `bigint | + // number` handle into an in/out 64-bit slot; emit it only if referenced. + if methods_js.contains("_handleU64(") { + out.push_str(HANDLE_SLOT_HELPER); + } + out.push_str("\n"); + out.push_str(&methods_js); + + // Aggregate exports as a frozen object, mirroring the classic-COM + // `export class` shape but for a module-namespace of functions. + out.push_str("export const Apis = Object.freeze({\n"); + for m in &meta.methods { + let camel = camel_case(&m.name); + out.push_str(&format!(" {camel},\n")); + } + out.push_str("});\n"); + + // Also emit named DLL/entry constants for advanced callers. + out.push_str("\n// Raw metadata for each export (dll, entry point).\n"); + out.push_str("export const FLAT_EXPORTS = Object.freeze({\n"); + for m in &meta.methods { + let camel = camel_case(&m.name); + out.push_str(&format!( + " {camel}: {{ dll: '{}', entry: '{}' }},\n", + m.dll, m.entry_point + )); + } + out.push_str("});\n"); + + out +} + +const WIDE_STRING_HELPER: &str = "\ +// Build a NUL-terminated UTF-16LE Buffer for LPCWSTR args. Rejects embedded +// U+0000 up front — Win32 wide-string APIs would silently truncate at the +// first NUL, which is a source of validation-bypass bugs. +function _wideStringBuffer(str) { + if (str === null || str === undefined) return null; + if (typeof str !== 'string') { + throw new TypeError(`expected string, got ${typeof str}`); + } + if (str.indexOf('\\u0000') !== -1) { + throw new RangeError('string contains embedded NUL (U+0000)'); + } + const buf = Buffer.alloc((str.length + 1) * 2); + buf.write(str, 'utf16le'); + return buf; +} +"; + +const HANDLE_SLOT_HELPER: &str = "\ +// Coerce a handle (bigint | number) to unsigned pointer bits for a 64-bit +// in/out slot. Signed pseudo-handles are preserved through two's complement. +function _handleU64(x) { + if (typeof x === 'bigint') { + if (x < -(1n << 63n) || x > ((1n << 64n) - 1n)) { + throw new RangeError('handle bigint must fit in a signed or unsigned 64-bit value'); + } + return BigInt.asUintN(64, x); + } + if (typeof x === 'number') { + if (!Number.isSafeInteger(x)) { + throw new RangeError('handle number must be a safe integer (use a bigint for a full 64-bit handle)'); + } + return BigInt.asUintN(64, BigInt(x)); + } + throw new TypeError(`expected a bigint or number handle, got ${typeof x}`); +} +"; + +fn render_method_js(out: &mut String, m: &FlatMethodMeta) { + let camel = camel_case(&m.name); + let ret_kind = flat_ret_kind_literal(&m.return_type); + + // Classify params + let classified: Vec<(usize, ParamSurface)> = m + .params + .iter() + .enumerate() + .map(|(i, p)| (i, classify(p))) + .collect(); + + // Compute JS parameter names ONCE with collision-avoidance so downstream + // sites (argument list, JSDoc, slot names, arg wrappers, result object) + // all agree — a duplicate JS identifier would be a fatal SyntaxError. + let jnames: Vec = js_param_names_for_method(m); + + // Names for arg list (all except OutScalar). + let mut param_names: Vec = Vec::new(); + for (i, s) in &classified { + if *s != ParamSurface::OutScalar { + param_names.push(jnames[*i].clone()); + } + } + + // Emit function + out.push_str("/**\n"); + out.push_str(&format!(" * {} — {} export.\n", m.name, m.dll)); + out.push_str(" *\n"); + for (i, p) in m.params.iter().enumerate() { + let kind = match &classified[i].1 { + ParamSurface::Input => "in", + ParamSurface::InOutScalar => "in,out", + ParamSurface::OutScalar => "out", + ParamSurface::OpaquePointer => "in/out pointer", + }; + out.push_str(&format!( + " * @param {} [{}] {}\n", + jnames[i], + kind, + describe_abi(&p.abi) + )); + } + out.push_str(&format!( + " * @returns {}\n", + describe_return_shape(m, &classified, &jnames) + )); + out.push_str(" */\n"); + + out.push_str(&format!( + "export function {camel}({}) {{\n", + param_names.join(", ") + )); + + for (i, param) in m.params.iter().enumerate() { + let FlatAbiType::NativeArray { element, size } = ¶m.abi else { + continue; + }; + let jname = &jnames[i]; + let (count_expr, multiplier) = match size { + FlatBufferSize::ElementCountParam(index) => { + (jnames[*index].clone(), flat_element_size(element).unwrap()) + } + FlatBufferSize::ByteCountParam(index) => (jnames[*index].clone(), 1), + FlatBufferSize::Constant(count) => { + (count.to_string(), flat_element_size(element).unwrap()) + } + FlatBufferSize::Unknown => unreachable!("unsupported array was filtered"), + }; + let required = format!("_{jname}RequiredBytes"); + out.push_str(&format!( + " const {required} = Number({count_expr}) * {multiplier};\n\ + \x20 if (!Number.isSafeInteger({required}) || {required} < 0) {{\n\ + \x20 throw new RangeError('{jname} size is not a non-negative safe integer');\n\ + \x20 }}\n\ + \x20 if ({jname} != null && ArrayBuffer.isView({jname}) && {jname}.byteLength < {required}) {{\n\ + \x20 throw new RangeError('{jname} buffer is smaller than the native size contract');\n\ + \x20 }}\n" + )); + } + + // Emit slot allocations for OutScalar / InOutScalar params. + for (i, s) in &classified { + let p = &m.params[*i]; + let jname = &jnames[*i]; + let slot = format!("_{jname}Slot"); + match s { + ParamSurface::OutScalar => { + let (alloc, _read) = scalar_slot_alloc_and_read(&pointee(&p.abi)); + out.push_str(&format!(" const {slot} = {alloc};\n")); + } + ParamSurface::InOutScalar => { + let inner = pointee(&p.abi); + let (alloc, _read) = scalar_slot_alloc_and_read(&inner); + let writer = scalar_slot_write(&inner, jname); + out.push_str(&format!(" const {slot} = {alloc};\n")); + let write_line = writer.replace.replace("{slot}", &slot); + out.push_str(&format!(" {write_line};\n")); + } + _ => {} + } + } + + // Keep synthesized UTF-16 buffers reachable through the native call. + let mut string_keepalive: Vec<(usize, String)> = Vec::new(); + for (i, s) in &classified { + if *s != ParamSurface::Input { + continue; + } + let p = &m.params[*i]; + let jname = &jnames[*i]; + match &p.abi { + FlatAbiType::PWStr => { + let local = format!("_{jname}Buf"); + out.push_str(&format!( + " const {local} = _wideStringBuffer({jname});\n" + )); + string_keepalive.push((*i, local)); + } + _ => {} + } + } + + // Build the native-call args array. + let mut arg_exprs: Vec = Vec::with_capacity(m.params.len()); + for (i, s) in &classified { + let p = &m.params[*i]; + let jname = &jnames[*i]; + let expr = match s { + ParamSurface::OutScalar | ParamSurface::InOutScalar => { + let slot = format!("_{jname}Slot"); + format!("DynWin32.pointer({slot})") + } + ParamSurface::OpaquePointer => { + // Caller-supplied Buffer / bigint / null — pass through + // untouched. Skip `wrap_arg_js`, which would incorrectly + // apply the string-input transformation (`_wideStringBuffer` + // et al.) to a PWStr/PStr param that the caller wants to + // treat as a raw byte buffer. + format!("DynWin32.pointer({jname})") + } + ParamSurface::Input => { + // If this is a string param with a keep-alive local, + // pass the local directly to pointer() — do NOT recreate + // a fresh temp Buffer inline. + if let Some((_, local)) = string_keepalive.iter().find(|(idx, _)| idx == i) { + format!("DynWin32.pointer({local})") + } else { + wrap_arg_js(&p.abi, jname) + } + } + }; + arg_exprs.push(expr); + } + + let args_line = arg_exprs.join(", "); + out.push_str(&format!( + " const _call = DynWin32.invoke('{}', '{}', '{}', [{}], {});\n\ + \x20 const _ret = _call.value;\n", + m.dll, m.entry_point, ret_kind, args_line, m.supports_last_error, + )); + let ret_val = flat_ret_decode_expr(&m.return_type, ret_kind); + + // Compose the return. + let has_projected_out = classified + .iter() + .any(|(_, s)| matches!(s, ParamSurface::OutScalar | ParamSurface::InOutScalar)); + if !has_projected_out { + // Simple return: status/return value. + if matches!(m.return_type, FlatAbiType::Void) { + if m.supports_last_error { + out.push_str(" return { lastError: _call.lastError };\n"); + } else { + out.push_str(" return undefined;\n"); + } + } else if is_status_return(m) { + out.push_str(&format!(" return {{ status: {ret_val}")); + if m.supports_last_error { + out.push_str(", lastError: _call.lastError"); + } + out.push_str(" };\n"); + } else { + out.push_str(&format!(" return {{ result: {ret_val}")); + if m.supports_last_error { + out.push_str(", lastError: _call.lastError"); + } + out.push_str(" };\n"); + } + } else { + // Build result object. + out.push_str(" return {\n"); + if is_status_return(m) { + out.push_str(&format!(" status: {ret_val},\n")); + } else if !matches!(m.return_type, FlatAbiType::Void) { + out.push_str(&format!(" result: {ret_val},\n")); + } + if m.supports_last_error { + out.push_str(" lastError: _call.lastError,\n"); + } + for (i, s) in &classified { + if !matches!(s, ParamSurface::OutScalar | ParamSurface::InOutScalar) { + continue; + } + let p = &m.params[*i]; + let jname = &jnames[*i]; + let slot = format!("_{jname}Slot"); + let (_alloc, read) = scalar_slot_alloc_and_read(&pointee(&p.abi)); + let read_expr = read.replace("{slot}", &slot); + out.push_str(&format!(" {jname}: {read_expr},\n")); + } + out.push_str(" };\n"); + } + + out.push_str("}\n"); +} + +fn pointee(t: &FlatAbiType) -> FlatAbiType { + match t { + FlatAbiType::PtrTo(inner) => (**inner).clone(), + _ => FlatAbiType::U32, + } +} + +struct WriteExpr { + replace: String, +} + +impl WriteExpr { + fn new(s: &str) -> Self { + Self { + replace: s.to_string(), + } + } +} + +/// Returns (alloc-expression, read-expression) for a caller-side Buffer slot +/// backing a scalar out or inout parameter. The read expression contains the +/// literal placeholder `{slot}` to substitute with the slot variable name. +fn scalar_slot_alloc_and_read(t: &FlatAbiType) -> (String, String) { + match t { + FlatAbiType::I8 => ("Buffer.alloc(1)".into(), "{slot}.readInt8(0)".into()), + FlatAbiType::U8 => ("Buffer.alloc(1)".into(), "{slot}.readUInt8(0)".into()), + FlatAbiType::I16 => ("Buffer.alloc(2)".into(), "{slot}.readInt16LE(0)".into()), + FlatAbiType::U16 | FlatAbiType::Char16 => { + ("Buffer.alloc(2)".into(), "{slot}.readUInt16LE(0)".into()) + } + FlatAbiType::Bool | FlatAbiType::Bool32 => ( + "Buffer.alloc(4)".into(), + "({slot}.readInt32LE(0) !== 0)".into(), + ), + FlatAbiType::I32 => ("Buffer.alloc(4)".into(), "{slot}.readInt32LE(0)".into()), + FlatAbiType::U32 => ("Buffer.alloc(4)".into(), "{slot}.readUInt32LE(0)".into()), + FlatAbiType::I64 => ("Buffer.alloc(8)".into(), "{slot}.readBigInt64LE(0)".into()), + FlatAbiType::U64 | FlatAbiType::Handle { .. } => ( + // Handles are pointer-sized on x64; use 8-byte BigUInt64 for both + // storage and read-back. + "Buffer.alloc(8)".into(), + "{slot}.readBigUInt64LE(0)".into(), + ), + FlatAbiType::Enum { underlying, .. } => match **underlying { + FlatAbiType::U32 => ( + "Buffer.alloc(4)".into(), + "({slot}.readUInt32LE(0) | 0)".into(), + ), + _ => scalar_slot_alloc_and_read(underlying), + }, + _ => ( + // Fallback: 4-byte slot as an u32 (matches most Win32 DWORDs). + "Buffer.alloc(4)".into(), + "{slot}.readUInt32LE(0)".into(), + ), + } +} + +/// Write-expression for an inout scalar slot. Returns a `WriteExpr` where +/// `.replace` contains `{slot}` to substitute with the slot variable name. +fn scalar_slot_write(t: &FlatAbiType, value_var: &str) -> WriteExpr { + match t { + FlatAbiType::I8 => WriteExpr::new(&format!("{{slot}}.writeInt8({value_var}, 0)")), + FlatAbiType::U8 => WriteExpr::new(&format!("{{slot}}.writeUInt8({value_var}, 0)")), + FlatAbiType::I16 => WriteExpr::new(&format!("{{slot}}.writeInt16LE({value_var}, 0)")), + FlatAbiType::U16 | FlatAbiType::Char16 => { + WriteExpr::new(&format!("{{slot}}.writeUInt16LE({value_var}, 0)")) + } + FlatAbiType::I32 | FlatAbiType::Bool32 => { + WriteExpr::new(&format!("{{slot}}.writeInt32LE({value_var}, 0)")) + } + FlatAbiType::U32 => WriteExpr::new(&format!("{{slot}}.writeUInt32LE({value_var}, 0)")), + FlatAbiType::I64 => { + WriteExpr::new(&format!("{{slot}}.writeBigInt64LE(BigInt({value_var}), 0)")) + } + FlatAbiType::U64 => WriteExpr::new(&format!( + "{{slot}}.writeBigUInt64LE(BigInt({value_var}), 0)" + )), + // Handle in-out slots accept both bigint and number (Buffer is + // intentionally NOT a valid Handle input — see the handle typedef + // in the .d.ts — because `DynWin32.pointer(Buffer)` uses the + // buffer's own address, not the bytes it contains). Route through + // `_handleU64`, which preserves signed pseudo-handles and rejects a + // number that is not a safe integer. + FlatAbiType::Handle { .. } => WriteExpr::new(&format!( + "{{slot}}.writeBigUInt64LE(_handleU64({value_var}), 0)" + )), + FlatAbiType::Enum { underlying, .. } => match **underlying { + FlatAbiType::U32 => { + WriteExpr::new(&format!("{{slot}}.writeUInt32LE(({value_var}) >>> 0, 0)")) + } + _ => scalar_slot_write(underlying, value_var), + }, + _ => WriteExpr::new(&format!("{{slot}}.writeUInt32LE({value_var}, 0)")), + } +} + +fn wrap_arg_js(t: &FlatAbiType, var: &str) -> String { + match t { + FlatAbiType::Bool | FlatAbiType::Bool32 => format!("DynWin32.i32({var} ? 1 : 0)"), + FlatAbiType::I8 => format!("DynWin32.i8({var})"), + FlatAbiType::U8 => format!("DynWin32.u8({var})"), + FlatAbiType::I16 => format!("DynWin32.i16({var})"), + FlatAbiType::U16 | FlatAbiType::Char16 => format!("DynWin32.u16({var})"), + FlatAbiType::I32 => format!("DynWin32.i32({var})"), + FlatAbiType::U32 => format!("DynWin32.u32({var})"), + FlatAbiType::I64 => format!("DynWin32.i64({var})"), + FlatAbiType::U64 => format!("DynWin32.u64({var})"), + // Emit correctly-typed float wrappers so the value round-trips as + // an IEEE-754 float, not a mis-marshalled pointer. If the Rust + // `flat_invoke` path doesn't yet accept F32/F64 args, this will + // throw a clear "unsupported arg kind" — fail loud, not silently + // wrong. Never emit `pointer()` here. + FlatAbiType::F32 => format!("DynWin32.f32({var})"), + FlatAbiType::F64 => format!("DynWin32.f64({var})"), + FlatAbiType::PWStr => { + format!("DynWin32.pointer(_wideStringBuffer({var}))") + } + FlatAbiType::PStr => format!("DynWin32.pointer({var})"), + // Handles: type is `bigint | number` (see the handle typedef in + // the .d.ts). Pass the value straight through to `pointer`, which + // accepts `bigint | number`: a bigint carries full 64-bit handle + // bits losslessly, and a JS number is validated as a safe integer + // (unsafe values are rejected, not silently truncated). Do NOT wrap + // in `BigInt(x)` — for a number above Number.MAX_SAFE_INTEGER the + // bits are already lost before BigInt sees them, and wrapping also + // bypasses `pointer`'s safe-integer validation. + FlatAbiType::Handle { .. } => format!("DynWin32.handle({var})"), + FlatAbiType::FunctionPointer => format!("DynWin32.pointer({var})"), + FlatAbiType::Ptr | FlatAbiType::PtrTo(_) | FlatAbiType::NativeArray { .. } => { + format!("DynWin32.pointer({var})") + } + FlatAbiType::Enum { underlying, .. } => match **underlying { + FlatAbiType::U32 => format!("DynWin32.u32(({var}) >>> 0)"), + _ => wrap_arg_js(underlying, var), + }, + FlatAbiType::Void | FlatAbiType::Unknown => { + format!("DynWin32.pointer({var})") + } + } +} + +fn describe_abi(t: &FlatAbiType) -> String { + match t { + FlatAbiType::Handle { name, .. } => format!("{name} handle"), + FlatAbiType::PWStr => "LPCWSTR string".into(), + FlatAbiType::PStr => "LPCSTR string".into(), + FlatAbiType::Enum { name, .. } => format!("{name} enum"), + FlatAbiType::Ptr => "opaque pointer".into(), + FlatAbiType::PtrTo(inner) => format!("pointer to {}", describe_abi(inner)), + FlatAbiType::NativeArray { element, size } => { + format!("caller-owned {size:?} buffer of {}", describe_abi(element)) + } + FlatAbiType::FunctionPointer => "native function pointer".into(), + other => format!("{other:?}"), + } +} + +fn describe_return_shape( + m: &FlatMethodMeta, + classified: &[(usize, ParamSurface)], + jnames: &[String], +) -> String { + let outs: Vec<(usize, &FlatParamMeta)> = classified + .iter() + .filter(|(_, s)| matches!(s, ParamSurface::OutScalar | ParamSurface::InOutScalar)) + .map(|(i, _)| (*i, &m.params[*i])) + .collect(); + if outs.is_empty() { + if matches!(m.return_type, FlatAbiType::Void) { + if m.supports_last_error { + "{ lastError: number }".into() + } else { + "undefined".into() + } + } else if is_status_return(m) { + if m.supports_last_error { + "{ status: number, lastError: number }".into() + } else { + "{ status: number }".into() + } + } else { + if m.supports_last_error { + "{ result: , lastError: number }".into() + } else { + "{ result: }".into() + } + } + } else { + let mut parts: Vec = Vec::new(); + if is_status_return(m) { + parts.push("status: number".into()); + } else if !matches!(m.return_type, FlatAbiType::Void) { + parts.push("result: ".into()); + } + if m.supports_last_error { + parts.push("lastError: number".into()); + } + // Use the SANITIZED JS identifiers (jnames) — not raw winmd param + // names — because the emitter uses these same identifiers as the + // return-object field names (see the `return { : ... }` emit + // site). Documenting `p.name` would show Hungarian-prefixed / raw + // names that don't actually exist on the returned object. + for (i, _p) in outs { + parts.push(format!("{}: ", jnames[i])); + } + format!("{{ {} }}", parts.join(", ")) + } +} + +// --------------------------------------------------------------------------- +// .d.ts rendering +// --------------------------------------------------------------------------- + +fn render_dts(meta: &FlatApisMeta) -> String { + let mut out = String::new(); + out.push_str("// Generated by dynwinrt-codegen — do not edit\n"); + out.push_str("// Flat-Win32 [DllImport] wrappers for "); + out.push_str(&meta.namespace); + out.push_str("."); + out.push_str(&meta.class_name); + out.push_str("\n\n"); + + // Import referenced enums as type-only imports. + let mut enum_imports: BTreeSet = BTreeSet::new(); + for e in &meta.referenced_enums { + if let TypeMeta::Enum { name, .. } = e { + enum_imports.insert(name.clone()); + } + } + for name in &enum_imports { + out.push_str(&format!("import {{ {name} }} from './{name}.js';\n")); + } + if !enum_imports.is_empty() { + out.push('\n'); + } + + // Emit handle typedef aliases. + let handle_aliases = collect_handle_aliases(meta); + for h in &handle_aliases { + out.push_str(&format!( + "/** Opaque Win32 handle. Pass either a raw pointer value as a `bigint` (safe for full 64-bit handle values) or a `number` (only for handles that fit in a JS safe integer, e.g. HWND with small window IDs). Do NOT pass a `Buffer` — `DynWin32.pointer(Buffer)` uses the buffer's own address, not the bytes it contains, so a Buffer of pointer bits would be misinterpreted as a pointer TO a `{h}`. */\nexport type {h} = bigint | number;\n" + )); + } + if !handle_aliases.is_empty() { + out.push('\n'); + } + + for m in &meta.methods { + render_method_dts(&mut out, m); + out.push('\n'); + } + + // Aggregate object type. + out.push_str("export declare const Apis: {\n"); + for m in &meta.methods { + let camel = camel_case(&m.name); + out.push_str(&format!(" {camel}: typeof {camel};\n")); + } + out.push_str("};\n\n"); + out.push_str("export declare const FLAT_EXPORTS: Readonly>;\n"); + + out +} + +fn render_method_dts(out: &mut String, m: &FlatMethodMeta) { + let camel = camel_case(&m.name); + let classified: Vec<(usize, ParamSurface)> = m + .params + .iter() + .enumerate() + .map(|(i, p)| (i, classify(p))) + .collect(); + + // Match .js name-generation exactly (including collision suffixes). + let jnames: Vec = js_param_names_for_method(m); + + // Argument list (Input, InOutScalar, OpaquePointer). + let mut params: Vec = Vec::new(); + for (i, s) in &classified { + let p = &m.params[*i]; + let jname = &jnames[*i]; + let ts_ty = match s { + ParamSurface::Input => dts_type_of(&p.abi), + ParamSurface::InOutScalar => dts_type_of(&pointee(&p.abi)), + ParamSurface::OutScalar => continue, + ParamSurface::OpaquePointer => "bigint | Buffer | Uint8Array | null".into(), + }; + params.push(format!("{jname}: {ts_ty}")); + } + + // Return type. Collect (index, param) so we can look up the deduped name. + let out_indices: Vec = classified + .iter() + .filter(|(_, s)| matches!(s, ParamSurface::OutScalar | ParamSurface::InOutScalar)) + .map(|(i, _)| *i) + .collect(); + + let ret_ty = if out_indices.is_empty() { + if matches!(m.return_type, FlatAbiType::Void) { + if m.supports_last_error { + "{ readonly lastError: number }".to_string() + } else { + "void".to_string() + } + } else if is_status_return(m) { + let last_error = if m.supports_last_error { + "; readonly lastError: number" + } else { + "" + }; + format!("{{ readonly status: number{last_error} }}") + } else { + let last_error = if m.supports_last_error { + "; readonly lastError: number" + } else { + "" + }; + format!( + "{{ readonly result: {}{} }}", + dts_return_type_of(&m.return_type), + last_error + ) + } + } else { + let mut fields: Vec = Vec::new(); + if is_status_return(m) { + fields.push("readonly status: number".into()); + } else if !matches!(m.return_type, FlatAbiType::Void) { + fields.push(format!( + "readonly result: {}", + dts_return_type_of(&m.return_type) + )); + } + if m.supports_last_error { + fields.push("readonly lastError: number".into()); + } + for i in &out_indices { + let p = &m.params[*i]; + let jname = &jnames[*i]; + let ty = dts_return_type_of(&pointee(&p.abi)); + fields.push(format!("readonly {jname}: {ty}")); + } + format!("{{ {} }}", fields.join("; ")) + }; + + out.push_str(&format!( + "/** {name} — {dll} export. */\nexport declare function {camel}({params}): {ret_ty};\n", + name = m.name, + dll = m.dll, + camel = camel, + params = params.join(", "), + ret_ty = ret_ty, + )); +} + +// --------------------------------------------------------------------------- +// Handles + enums helpers +// --------------------------------------------------------------------------- + +fn collect_handle_aliases(meta: &FlatApisMeta) -> Vec { + let mut set: BTreeSet = BTreeSet::new(); + for m in &meta.methods { + for p in &m.params { + walk_abi_for_handles(&p.abi, &mut set); + } + walk_abi_for_handles(&m.return_type, &mut set); + } + set.into_iter().collect() +} + +fn walk_abi_for_handles(t: &FlatAbiType, set: &mut BTreeSet) { + match t { + FlatAbiType::Handle { name, .. } => { + set.insert(name.clone()); + } + FlatAbiType::PtrTo(inner) => walk_abi_for_handles(inner, set), + FlatAbiType::NativeArray { element, .. } => walk_abi_for_handles(element, set), + FlatAbiType::Enum { .. } + | FlatAbiType::Bool + | FlatAbiType::Bool32 + | FlatAbiType::I8 + | FlatAbiType::U8 + | FlatAbiType::I16 + | FlatAbiType::U16 + | FlatAbiType::I32 + | FlatAbiType::U32 + | FlatAbiType::I64 + | FlatAbiType::U64 + | FlatAbiType::F32 + | FlatAbiType::F64 + | FlatAbiType::Char16 + | FlatAbiType::PWStr + | FlatAbiType::PStr + | FlatAbiType::FunctionPointer + | FlatAbiType::Ptr + | FlatAbiType::Void + | FlatAbiType::Unknown => {} + } +} + +fn render_enum_files(en: &TypeMeta) -> (String, String) { + let (name, members) = match en { + TypeMeta::Enum { name, members, .. } => (name.as_str(), members), + _ => unreachable!(), + }; + let mut js = String::new(); + js.push_str("// Generated by dynwinrt-codegen — do not edit\n"); + js.push_str(&format!("export const {name} = Object.freeze({{\n")); + for m in members { + js.push_str(&format!(" {}: {},\n", m.name, m.value)); + } + js.push_str("});\n"); + + // Emit .d.ts as a const object + companion type, matching the JS + // `Object.freeze({...})` runtime shape and the convention used by + // the WinRT/classic-COM enum emitters + // (tools/dynwinrt-codegen/src/codegen/javascript/render/declarations.rs). + // Deliberately avoids `export declare const enum` so that consumers + // with TypeScript `isolatedModules` (Vite, esbuild, Next.js, etc.) + // don't hit the "const enums are not usable when isolatedModules is + // enabled" error, and so the emitted type mirrors what actually + // exists at runtime. + let mut dts = String::new(); + dts.push_str("// Generated by dynwinrt-codegen — do not edit\n"); + dts.push_str(&format!( + "export type {name} = (typeof {name})[keyof typeof {name}];\n" + )); + dts.push_str(&format!("export declare const {name}: {{\n")); + for m in members { + dts.push_str(&format!(" readonly {}: {};\n", m.name, m.value)); + } + dts.push_str("};\n"); + (js, dts) +} + +// --------------------------------------------------------------------------- +// Unit tests (no winmd — pure logic) +// --------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn camel_case_flat() { + assert_eq!(camel_case("RegOpenKeyExW"), "regOpenKeyExW"); + assert_eq!(camel_case("MulDiv"), "mulDiv"); + assert_eq!(camel_case("GetLastError"), "getLastError"); + assert_eq!(camel_case("URL"), "url"); + } + + #[test] + fn js_param_name_reserves_return_object_keys_and_js_keywords() { + // `status` and `result` are the return-object field names for a flat + // wrapper; a parameter/out-field that strips to either would collide + // with (and overwrite) the actual return value, so both are reserved. + assert_eq!(js_param_name("status", 0), "status_"); + assert_eq!(js_param_name("result", 0), "result_"); + // JS keywords are reserved too. + assert_eq!(js_param_name("class", 0), "class_"); + assert_eq!(js_param_name("return", 0), "return_"); + // Ordinary names are unchanged. + assert_eq!(js_param_name("hKey", 0), "hKey"); + } + + #[test] + fn handle_inout_slot_write_validates_via_helper() { + // A handle in/out slot (.d.ts type `bigint | number`) must route the + // value through `_handleU64`, which rejects lossy numbers above 2^53-1 + // rather than silently writing wrong handle bits via `BigInt(x)`. + let h = scalar_slot_write( + &FlatAbiType::Handle { + namespace: "Windows.Win32.Foundation".into(), + name: "HANDLE".into(), + }, + "hFile", + ); + assert_eq!(h.replace, "{slot}.writeBigUInt64LE(_handleU64(hFile), 0)"); + // A `bigint`-typed U64 slot has no number ambiguity, so it keeps the + // direct BigInt() coercion. + let u = scalar_slot_write(&FlatAbiType::U64, "count"); + assert_eq!(u.replace, "{slot}.writeBigUInt64LE(BigInt(count), 0)"); + } + + #[test] + fn handle_arg_passes_through_without_lossy_bigint_wrap() { + // Regression (commit 16d293f): a handle ARG must be passed straight to + // pointer() — which accepts bigint|number and validates safe integers — + // NOT wrapped in BigInt(x). BigInt(number) for a value above 2^53-1 has + // already lost bits and bypasses pointer()'s validation. + let arg = wrap_arg_js( + &FlatAbiType::Handle { + namespace: "Windows.Win32.System.Registry".into(), + name: "HKEY".into(), + }, + "hKey", + ); + assert_eq!(arg, "DynWin32.handle(hKey)"); + assert!( + !arg.contains("BigInt("), + "handle arg must not wrap in BigInt(): {arg}" + ); + } + + #[test] + fn dts_type_of_scalars_and_handles() { + assert_eq!(dts_type_of(&FlatAbiType::Bool), "boolean"); + assert_eq!(dts_type_of(&FlatAbiType::Bool32), "boolean"); + assert_eq!(dts_type_of(&FlatAbiType::U32), "number"); + assert_eq!(dts_type_of(&FlatAbiType::I64), "bigint"); + assert_eq!(dts_type_of(&FlatAbiType::PWStr), "string | null"); + assert_eq!( + dts_type_of(&FlatAbiType::Handle { + namespace: "Windows.Win32.System.Registry".into(), + name: "HKEY".into() + }), + "HKEY" + ); + } + + #[test] + fn classify_out_hkey_projects_as_return() { + let p = FlatParamMeta { + name: "phkResult".into(), + direction: FlatDirection::Out, + abi: FlatAbiType::PtrTo(Box::new(FlatAbiType::Handle { + namespace: "Windows.Win32.System.Registry".into(), + name: "HKEY".into(), + })), + }; + assert_eq!(classify(&p), ParamSurface::OutScalar); + } + + #[test] + fn classify_out_byte_buffer_stays_opaque() { + // Byte-sized pointer params in Win32 are almost always caller-allocated + // buffers with a separate size argument (e.g. RegQueryValueExW's + // lpData/lpcbData). We deliberately keep them as OpaquePointer so the + // caller passes a Buffer|null. The `is_small_scalarish` helper + // excludes U8/I8 for this reason. + let p = FlatParamMeta { + name: "lpData".into(), + direction: FlatDirection::Out, + abi: FlatAbiType::PtrTo(Box::new(FlatAbiType::U8)), + }; + assert_eq!(classify(&p), ParamSurface::OpaquePointer); + } + + #[test] + fn status_return_reads_flag_not_type() { + // Since the flag is populated at parse time from raw winmd type + // info, the unit test just verifies the accessor reads what's + // stored — the parse-time classification is covered by snapshot + // tests against real Win32 metadata (see registry_apis snapshot). + fn method(return_type: FlatAbiType, return_is_status: bool) -> FlatMethodMeta { + FlatMethodMeta { + name: "F".into(), + dll: "x.dll".into(), + entry_point: "F".into(), + return_type, + params: vec![], + return_is_status, + supports_last_error: false, + } + } + assert!(is_status_return(&method(FlatAbiType::I32, true))); + assert!(!is_status_return(&method(FlatAbiType::I32, false))); + assert!(is_status_return(&method( + FlatAbiType::Enum { + namespace: "Windows.Win32.Foundation".into(), + name: "WIN32_ERROR".into(), + underlying: Box::new(FlatAbiType::U32), + members: vec![], + }, + true, + ))); + } + + #[test] + fn generate_end_to_end_snapshot_shape_for_synthetic_method() { + // Synthesise a minimal Apis with one method to keep this fast and + // hermetic (no winmd required). + let m = FlatMethodMeta { + name: "MulDiv".into(), + dll: "kernel32.dll".into(), + entry_point: "MulDiv".into(), + return_type: FlatAbiType::I32, + params: vec![ + FlatParamMeta { + name: "nNumber".into(), + abi: FlatAbiType::I32, + direction: FlatDirection::In, + }, + FlatParamMeta { + name: "nNumerator".into(), + abi: FlatAbiType::I32, + direction: FlatDirection::In, + }, + FlatParamMeta { + name: "nDenominator".into(), + abi: FlatAbiType::I32, + direction: FlatDirection::In, + }, + ], + return_is_status: false, + supports_last_error: false, + }; + let apis = FlatApisMeta { + namespace: "Test".into(), + class_name: "Apis".into(), + methods: vec![m], + referenced_enums: vec![], + }; + let out = generate_flat_apis_files(&apis); + assert!(out.js.contains("export function mulDiv")); + assert!( + out.js + .contains("DynWin32.invoke('kernel32.dll', 'MulDiv', 'I32'") + ); + assert!(out.dts.contains("mulDiv")); + } +} diff --git a/tools/dynwinrt-codegen/src/main.rs b/tools/dynwinrt-codegen/src/main.rs index 6de1773b..5fccaaf1 100644 --- a/tools/dynwinrt-codegen/src/main.rs +++ b/tools/dynwinrt-codegen/src/main.rs @@ -12,6 +12,7 @@ use dynwinrt_codegen::codegen::package; use dynwinrt_codegen::codegen::python; use dynwinrt_codegen::codegen::typescript; use dynwinrt_codegen::codegen::winrt::extensions::winui; +use dynwinrt_codegen::codegen::win32; use dynwinrt_codegen::codegen::{project, render_dts, render_js}; use dynwinrt_codegen::com_metadata; use dynwinrt_codegen::meta; @@ -295,10 +296,16 @@ fn run() -> Result<(), String> { if let Some(ref cls_arg) = class_name { let class_requests = parse_class_requests(cls_arg, namespace.as_deref())?; - // First: partition into WinRT classes and classic-COM interfaces. + // First: partition into WinRT classes, classic-COM interfaces, + // and flat-Win32 [DllImport] modules. let mut classes = Vec::new(); let mut com_interfaces: Vec = Vec::new(); + let mut flat_apis: Vec = Vec::new(); for (ns, cls) in &class_requests { + if let Some(apis) = meta::parse_flat_apis(&winmd, ns, cls) { + flat_apis.push(apis); + continue; + } if let Some(com_iface) = com_metadata::parse_com_interface(&winmd, ns, cls) { // Route through classic-COM path when: // 1) The interface is IUnknown-rooted (base +3), OR @@ -337,15 +344,16 @@ fn run() -> Result<(), String> { } } - // Fail loud: classic-COM codegen only emits `.js` + `.d.ts` - // today. If the user asked for a different language - // (e.g. `--lang py`) but any of the requested `--class-name` - // inputs resolved to a classic-COM interface, silently writing - // JS files into a Python output directory would produce the - // wrong artifact types with no diagnostic. Reject the - // combination up front. - if lang != "js" && !com_interfaces.is_empty() { + // Flat-Win32 and classic-COM codegen only emit JavaScript and + // declarations today. + if lang != "js" && (!flat_apis.is_empty() || !com_interfaces.is_empty()) { let mut offenders: Vec = Vec::new(); + for apis in &flat_apis { + offenders.push(format!( + "{}.{} (flat-Win32 [DllImport])", + apis.namespace, apis.class_name + )); + } for ci in &com_interfaces { offenders.push(format!( "{}.{} (classic-COM interface)", @@ -353,17 +361,75 @@ fn run() -> Result<(), String> { )); } return Err(format!( - "`--lang {}` is not supported for classic-COM interfaces \ - (they emit only `.js` + `.d.ts` today). \ + "`--lang {}` is not supported for flat-Win32 [DllImport] modules or \ + classic-COM interfaces (they emit only `.js` + `.d.ts` today). \ Offending inputs: {}. Re-run with `--lang js`, or split the \ invocation so the WinRT classes are generated with `--lang {}` and \ - the COM classes with `--lang js`.", + the flat/COM classes with `--lang js`.", lang, offenders.join(", "), lang )); } + if !flat_apis.is_empty() && (!classes.is_empty() || !com_interfaces.is_empty()) { + return Err("Flat Win32 generation uses a dedicated output package. \ + Generate WinRT or Classic COM bindings in a separate invocation." + .into()); + } + + if !flat_apis.is_empty() { + ensure_flat_output_package(output_dir)?; + for apis in &flat_apis { + let runtime_import = if import_name == "@microsoft/dynwinrt" { + "@microsoft/dynwinrt/win32" + } else { + &import_name + }; + let out = win32::generate_flat_apis_files_with_import(apis, runtime_import); + let flat_output_dir = output_dir.join("win32").join(&apis.namespace); + if !dry_run { + fs::create_dir_all(&flat_output_dir).map_err(|error| { + format!( + "Failed to create flat Win32 output directory {}: {error}", + flat_output_dir.display() + ) + })?; + } + let js_name = format!("{}.js", apis.class_name); + let dts_name = format!("{}.d.ts", apis.class_name); + if !dry_run { + fs::write(flat_output_dir.join(&js_name), &out.js) + .map_err(|e| format!("Failed to write {}: {}", js_name, e))?; + fs::write(flat_output_dir.join(&dts_name), &out.dts) + .map_err(|e| format!("Failed to write {}: {}", dts_name, e))?; + for (name, content) in &out.extra_files { + fs::write(flat_output_dir.join(name), content) + .map_err(|e| format!("Failed to write {}: {}", name, e))?; + } + write_flat_namespace_package(&flat_output_dir)?; + println!( + "Generated flat-Win32 {}.{} ({} methods, {} extra files)", + apis.namespace, + apis.class_name, + apis.methods.len(), + out.extra_files.len() + ); + } else { + println!( + "[dry-run] Would generate flat-Win32 {}.{}", + apis.namespace, apis.class_name + ); + } + } + if classes.is_empty() && com_interfaces.is_empty() { + if !dry_run { + write_flat_root_manifest(output_dir)?; + } + return Ok(()); + } + } + // Classic COM occupies its own ESM subpackage so its symbols // cannot collide with or leak into the WinRT root barrel. if !com_interfaces.is_empty() { @@ -1089,6 +1155,92 @@ fn write_js_barrel_and_manifest(output_dir: &Path, index_content: &str) -> Resul Ok(()) } +fn ensure_flat_output_package(output_dir: &Path) -> Result<(), String> { + let package_path = output_dir.join("package.json"); + if !package_path.is_file() { + return Ok(()); + } + let package = fs::read_to_string(&package_path) + .map_err(|error| format!("Failed to read {}: {error}", package_path.display()))?; + if !package.contains("\"dynwinrtDomain\": \"win32\"") { + return Err(format!( + "Flat Win32 bindings cannot share {} with another generated package", + output_dir.display() + )); + } + Ok(()) +} + +fn write_flat_namespace_package(output_dir: &Path) -> Result<(), String> { + let mut modules = BTreeSet::new(); + for entry in fs::read_dir(output_dir) + .map_err(|error| format!("Failed to read {}: {error}", output_dir.display()))? + .flatten() + { + let path = entry.path(); + let Some(name) = path.file_name().and_then(|name| name.to_str()) else { + continue; + }; + let Some(module) = name.strip_suffix(".js") else { + continue; + }; + if module != "index" { + modules.insert(module.to_string()); + } + } + + let mut index = String::from("// Generated by dynwinrt-codegen - do not edit\n"); + for module in modules { + index.push_str(&format!("export * from './{module}.js';\n")); + } + fs::write(output_dir.join("index.js"), &index) + .map_err(|error| format!("Failed to write flat Win32 index.js: {error}"))?; + fs::write(output_dir.join("index.d.ts"), &index) + .map_err(|error| format!("Failed to write flat Win32 index.d.ts: {error}"))?; + fs::write( + output_dir.join("package.json"), + "{\n \"type\": \"module\",\n \"sideEffects\": false\n}\n", + ) + .map_err(|error| format!("Failed to write flat Win32 package.json: {error}")) +} + +fn write_flat_root_manifest(output_dir: &Path) -> Result<(), String> { + let flat_root = output_dir.join("win32"); + let mut namespaces = BTreeSet::new(); + for entry in fs::read_dir(&flat_root) + .map_err(|error| format!("Failed to read {}: {error}", flat_root.display()))? + .flatten() + { + if entry.path().join("index.js").is_file() { + namespaces.insert(entry.file_name().to_string_lossy().to_string()); + } + } + + let mut package = String::from( + "{\n \"name\": \"@winapp/bindings\",\n \"type\": \"module\",\n \ + \"sideEffects\": false,\n \"dynwinrtDomain\": \"win32\",\n \"exports\": {", + ); + for (index, namespace) in namespaces.iter().enumerate() { + if index > 0 { + package.push(','); + } + package.push_str(&format!( + "\n \"./win32/{namespace}\": {{\n \ + \"types\": \"./win32/{namespace}/index.d.ts\",\n \ + \"import\": \"./win32/{namespace}/index.js\"\n }}" + )); + package.push_str(&format!( + ",\n \"./win32/{namespace}/*\": {{\n \ + \"types\": \"./win32/{namespace}/*.d.ts\",\n \ + \"import\": \"./win32/{namespace}/*.js\"\n }}" + )); + } + package.push_str("\n }\n}\n"); + let package_path = output_dir.join("package.json"); + fs::write(&package_path, package) + .map_err(|error| format!("Failed to write {}: {error}", package_path.display())) +} + fn write_com_js_barrel(com_output_dir: &Path) -> Result<(), String> { let mut modules: BTreeMap> = BTreeMap::new(); let entries = fs::read_dir(com_output_dir).map_err(|error| { @@ -1790,6 +1942,7 @@ fn print_capabilities() { "generate", "lang.js", "lang.py", + "domain.win32", "input.winmd", "input.ref", "input.winmd-list", diff --git a/tools/dynwinrt-codegen/src/meta.rs b/tools/dynwinrt-codegen/src/meta.rs index d4417047..80105f86 100644 --- a/tools/dynwinrt-codegen/src/meta.rs +++ b/tools/dynwinrt-codegen/src/meta.rs @@ -950,6 +950,749 @@ fn parse_interface(index: &reader::Index, namespace: &str, name: &str) -> Option parse_interface_methods(index, &def, name, namespace, &iid, &[]) } +// --------------------------------------------------------------------------- +// Flat-Win32 [DllImport] method discovery +// --------------------------------------------------------------------------- + +/// A single flat-Win32 export parameter with its ABI shape preserved. +/// +/// Unlike WinRT `ParamMeta`, this keeps raw pointer types (`PtrMut`/`PtrConst`) +/// distinct from opaque handles so the flat emitter can project pointer-based +/// out-params (e.g. `PHKEY`) as JS return values. +#[derive(Debug, Clone)] +pub struct FlatParamMeta { + pub name: String, + pub abi: FlatAbiType, + pub direction: FlatDirection, +} + +/// Direction of a flat-Win32 parameter, computed from `ParamAttributes` +/// (`In=0x01`, `Out=0x02`; a pointer that's both is `InOut`). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FlatDirection { + In, + Out, + InOut, +} + +/// A restricted ABI type space for flat-Win32 exports. +/// +/// This is intentionally SEPARATE from `TypeMeta`: `map_winmd_type_with_generics` +/// collapses pointer types (`PtrMut`, `PtrConst`) to `TypeMeta::Object`, losing +/// the pointee direction we need to project out-params. Flat marshalling also +/// treats Win32 typedef wrappers (HKEY, PWSTR, LSTATUS, WIN32_ERROR) as first- +/// class shapes so the emitter can pick a natural JS surface (string, bigint, +/// enum-number) per shape. +#[derive(Debug, Clone, PartialEq)] +pub enum FlatAbiType { + Void, + Bool, + I8, + U8, + I16, + U16, + I32, + U32, + I64, + U64, + F32, + F64, + /// Wide-character UCS-2 code unit. + Char16, + /// Opaque pointer of any pointee type (raw `void*`). + Ptr, + /// A pointer with a KNOWN pointee ABI type. Used for out/inout scalar + /// slots we can project (e.g. `PtrMut(HKEY)` → out HKEY value; + /// `PtrMut(U32)` [InOut] → in-out DWORD). + PtrTo(Box), + /// Pointer with an explicit element-count or byte-count contract. The + /// caller owns the storage and the count parameter remains visible. + NativeArray { + element: Box, + size: FlatBufferSize, + }, + /// PWSTR / PCWSTR / LPCWSTR: pointer to a UTF-16 string. The flat + /// emitter models these as *read-only* string inputs: the wrapper + /// builds a NUL-terminated UTF-16 `Buffer` on demand from a + /// `string | null` argument. This is correct for `PCWSTR` / `LPCWSTR` + /// (Win32's const-form pointer-to-CH); for the mutable `PWSTR` form + /// used as an OUT/INOUT string buffer, this projection would be too + /// narrow (the caller would need a pre-sized `Buffer` — that case + /// falls through the ``[out]``/``[in,out]`` param classification in + /// `flat.rs` and is currently marshalled via ``pointer()`` + /// rather than via the string-input path). + PWStr, + /// PSTR / PCSTR / LPCSTR: pointer to native code-page bytes. Callers pass + /// encoded bytes explicitly; codegen must not assume UTF-8. + PStr, + /// Native callback or exported function address such as FARPROC. + FunctionPointer, + /// A Win32 opaque handle struct (single `Value` field with a pointer + /// or integer shape). Natural surface is `bigint | number` — see the + /// handle typedef doc in `codegen/win32`. `Buffer` is intentionally + /// NOT a valid input shape because `DynWin32.pointer(Buffer)` + /// uses the buffer's own base address rather than the pointer bits + /// contained in it, which would be misinterpreted as a pointer to + /// the handle (an address-of-address) instead of the handle itself. + Handle { + namespace: String, + name: String, + }, + /// Win32 BOOL — 32-bit integer at the ABI, `boolean` on the surface. + Bool32, + /// A named `[Flags]` or plain enum from the winmd. `underlying` is the + /// storage type (usually `U32`). The surface projects as `number`. + Enum { + namespace: String, + name: String, + underlying: Box, + members: Vec, + }, + /// Anything we cannot classify precisely. Flat codegen FAIL-LOUD SKIPS any + /// method with a bare `Unknown` by-value param or return (there is no safe + /// by-value ABI marshalling for it — see `unsupported_param_reason` / + /// `unsupported_return_reason`). Only `PtrTo(Unknown)` survives, as an opaque + /// pointer param where the caller supplies a `Buffer|bigint`. + Unknown, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FlatBufferSize { + ElementCountParam(usize), + ByteCountParam(usize), + Constant(usize), + Unknown, +} + +/// A single flat-Win32 export from an `Apis`-class static method. +#[derive(Debug, Clone)] +pub struct FlatMethodMeta { + /// PascalCase name of the method in the winmd (e.g. `RegOpenKeyExW`). + pub name: String, + /// DLL name from the `[DllImport]` module ref (e.g. `ADVAPI32.dll`). + pub dll: String, + /// Entry-point name from `ImplMap.import_name` — usually identical to + /// `name`, but can differ for aliased exports. + pub entry_point: String, + /// Return type at the ABI. + pub return_type: FlatAbiType, + /// Ordered parameters, with `[in]` / `[out]` / `[in,out]` direction + /// recovered from `ParamAttributes`. + pub params: Vec, + /// True when the return type is a known Win32 status typedef (HRESULT, + /// NTSTATUS, LSTATUS) or a WIN32_ERROR-family enum. Callers should + /// project the return as a numeric `.status` field so consumers can + /// branch on ERROR_SUCCESS / ERROR_FILE_NOT_FOUND / etc. FALSE for + /// plain I32/U32 returns (e.g. `GetCurrentProcessId -> u32`, + /// `MulDiv -> i32`) — those are real integer values and must be + /// projected as `.result` rather than mis-labelled as status codes. + pub return_is_status: bool, + /// Whether the P/Invoke metadata requires atomic GetLastError capture. + pub supports_last_error: bool, +} + +/// A container class whose static methods are all `[DllImport]` exports — +/// the `Apis` class pattern used throughout `Windows.Win32.winmd`. +#[derive(Debug, Clone)] +pub struct FlatApisMeta { + pub namespace: String, + pub class_name: String, + pub methods: Vec, + /// Distinct enum types referenced by any parameter or return type. The + /// generator emits a per-enum sibling `.js`/`.d.ts` for each one. + pub referenced_enums: Vec, +} + +/// Parse a flat-Win32 `Apis`-shaped class (a container of `[DllImport]` static +/// methods) from the winmd. Returns `None` when the class does not exist, +/// when it has no DllImport methods (i.e. it's actually a WinRT class), or +/// when it fails to parse. +pub fn parse_flat_apis( + winmd_paths: &str, + namespace: &str, + class_name: &str, +) -> Option { + let index = load_index(winmd_paths)?; + parse_flat_apis_from_index(&index, namespace, class_name) +} + +/// True when the RAW winmd return type is a known Win32 status typedef — +/// HRESULT / NTSTATUS / LSTATUS in `Windows.Win32.Foundation`. Preserves +/// typedef intent that would otherwise be lost by `map_flat_type` collapsing +/// them all to `FlatAbiType::I32`, so the emitter can distinguish real +/// status codes (project as `.status`) from integer-return APIs like +/// `MulDiv` or `GetCurrentProcessId` (project as `.result`). +fn is_status_return_type(ty: &windows_metadata::Type) -> bool { + use windows_metadata::Type; + match ty { + Type::Name(tn) => { + tn.namespace == "Windows.Win32.Foundation" + && matches!(tn.name.as_ref(), "HRESULT" | "NTSTATUS" | "LSTATUS") + } + _ => false, + } +} + +/// True when the mapped `FlatAbiType` is a WIN32_ERROR-family enum whose +/// underlying storage is a 32-bit integer. The Win32 winmd exposes many +/// error/status typedefs as `[Flags]`-style enums (e.g. `WIN32_ERROR`, +/// `NTSTATUS`-like enums whose name ends with `STATUS`) — those still count +/// as status codes for return-value projection. +fn is_status_return_enum(t: &FlatAbiType) -> bool { + if let FlatAbiType::Enum { + name, underlying, .. + } = t + { + (name == "WIN32_ERROR" || name.ends_with("STATUS")) + && matches!(**underlying, FlatAbiType::U32 | FlatAbiType::I32) + } else { + false + } +} + +/// Some Win32 metadata rows expose status-code returns as raw `I32` instead +/// of preserving their LSTATUS/WIN32_ERROR typedef name. Keep this allowlist +/// narrow so genuine scalar value returns (`MulDiv`, `GetCurrentProcessId`, +/// etc.) continue to project as `.result`. +fn is_known_raw_i32_status_return( + namespace: &str, + method_name: &str, + return_type: &FlatAbiType, +) -> bool { + namespace == "Windows.Win32.System.Registry" + && matches!( + method_name, + "RegConnectRegistryExA" | "RegConnectRegistryExW" + ) + && matches!(return_type, FlatAbiType::I32) +} + +fn parse_flat_apis_from_index( + index: &reader::Index, + namespace: &str, + class_name: &str, +) -> Option { + let def = index.get(namespace, class_name).next()?; + + // The published runtime targets x64 and ARM64. Architecture-specific + // signatures are filtered below so one generated package is valid on both. + + let mut methods: Vec = Vec::new(); + let mut referenced_enums: Vec = Vec::new(); + // Deduplicate referenced enums by (namespace, name) to avoid silently + // dropping a distinct type with the same simple name from a different + // namespace (e.g. `SomeNs.WIN32_ERROR` vs `Windows.Win32.Foundation + // .WIN32_ERROR`). Keying by `name` alone would keep only the first- + // seen variant and emit incorrect sibling files. + let mut seen_enum_keys: HashSet<(String, String)> = HashSet::new(); + + for m in def.methods() { + let Some(imap) = m.impl_map() else { + // Not a [DllImport] method — skip. (An Apis class may also have + // constructor stubs; we intentionally ignore those.) + continue; + }; + let sig = m.signature(&[]); + if sig + .flags + .contains(windows_metadata::MethodCallAttributes::VARARG) + { + eprintln!( + "warning: skipping {}.{}.{} — variadic flat exports are unsupported", + namespace, + class_name, + m.name() + ); + continue; + } + if !supports_all_runtime_architectures(&m) { + eprintln!( + "warning: skipping {}.{}.{} — export is not available on both x64 and ARM64", + namespace, + class_name, + m.name() + ); + continue; + } + let pinvoke_flags = imap.flags(); + // Skip .ctor (unlikely on Apis, but future-proof). + if m.name() == ".ctor" || m.name() == ".cctor" { + continue; + } + let dll = imap.import_scope().name().to_string(); + if !is_supported_system_module_name(&dll) { + eprintln!( + "warning: skipping {}.{}.{} — unsupported system module `{}`", + namespace, + class_name, + m.name(), + dll + ); + continue; + } + let entry_point = imap.import_name().to_string(); + + let return_type = map_flat_type(&sig.return_type, index, &mut |e| { + collect_enum(e, &mut seen_enum_keys, &mut referenced_enums) + }); + // Preserve typedef intent from the raw return Type: only project as + // a `.status` numeric field when the return is a known Win32 status + // typedef (HRESULT/NTSTATUS/LSTATUS) OR a WIN32_ERROR-family enum + // after mapping. A plain I32/U32 return (e.g. `GetCurrentProcessId`, + // `MulDiv`) is a real value, NOT a status code, and must project as + // `{ result: number }` — see `render_method_js`. + let return_is_status = is_status_return_type(&sig.return_type) + || is_status_return_enum(&return_type) + || is_known_raw_i32_status_return(namespace, m.name(), &return_type); + + let param_defs: Vec<_> = m.params().filter(|p| p.sequence() > 0).collect(); + // Fail-loud on parameter/signature divergence. Silently truncating + // to the shorter list would emit a wrapper with a fabricated + // argument list, and a mismatched flat call is UB. Skip the whole + // method (with a stderr warning) instead — the codegen surface then + // simply lacks this export, which is far safer than a wrapper that + // corrupts the callee's stack. + if param_defs.len() != sig.types.len() { + eprintln!( + "warning: skipping {}.{}.{} — param count ({}) differs from signature type count ({}); metadata is inconsistent", + namespace, + class_name, + m.name(), + param_defs.len(), + sig.types.len(), + ); + continue; + } + let mut params: Vec = Vec::with_capacity(param_defs.len()); + for (i, pd) in param_defs.iter().enumerate() { + let ty = &sig.types[i]; + let mut abi = map_flat_type(ty, index, &mut |e| { + collect_enum(e, &mut seen_enum_keys, &mut referenced_enums) + }); + if let Some(size) = flat_buffer_size(pd) { + let element = match abi { + FlatAbiType::PtrTo(element) => *element, + FlatAbiType::Ptr => FlatAbiType::U8, + FlatAbiType::PWStr => FlatAbiType::Char16, + FlatAbiType::PStr => FlatAbiType::U8, + other => other, + }; + abi = FlatAbiType::NativeArray { + element: Box::new(element), + size, + }; + } + let flags = pd.flags(); + let is_in = flags.contains(windows_metadata::ParamAttributes::In); + let is_out = flags.contains(windows_metadata::ParamAttributes::Out); + let direction = match (is_in, is_out) { + (_, true) if is_in => FlatDirection::InOut, + (_, true) => FlatDirection::Out, + _ => FlatDirection::In, + }; + params.push(FlatParamMeta { + name: pd.name().to_string(), + abi, + direction, + }); + } + + methods.push(FlatMethodMeta { + name: m.name().to_string(), + dll, + entry_point, + return_type, + params, + return_is_status, + supports_last_error: pinvoke_flags + .contains(windows_metadata::PInvokeAttributes::SupportsLastError), + }); + } + + if methods.is_empty() { + return None; + } + // Stable order: winmd row order is arbitrary. Sort by name so snapshots + // are deterministic across metadata rewrites. + methods.sort_by(|a, b| a.name.cmp(&b.name)); + let duplicate_names: HashSet = methods + .windows(2) + .filter(|pair| pair[0].name == pair[1].name) + .map(|pair| pair[0].name.clone()) + .collect(); + for name in &duplicate_names { + eprintln!( + "warning: skipping {}.{}.{} — unresolved architecture overload collision", + namespace, class_name, name + ); + } + methods.retain(|method| !duplicate_names.contains(&method.name)); + referenced_enums.sort_by(|a, b| match (a, b) { + (TypeMeta::Enum { name: an, .. }, TypeMeta::Enum { name: bn, .. }) => an.cmp(bn), + _ => std::cmp::Ordering::Equal, + }); + + Some(FlatApisMeta { + namespace: namespace.to_string(), + class_name: class_name.to_string(), + methods, + referenced_enums, + }) +} + +fn supports_all_runtime_architectures(method: &reader::MethodDef) -> bool { + const X64: i32 = 0x2; + const ARM64: i32 = 0x4; + + let Some(attribute) = method.find_attribute("SupportedArchitectureAttribute") else { + return true; + }; + let bits = match attribute.value().first() { + Some((_, windows_metadata::Value::I32(value))) => *value, + Some((_, windows_metadata::Value::U32(value))) => *value as i32, + Some((_, windows_metadata::Value::AttributeEnum(_, value))) => *value, + _ => return false, + }; + bits == 0 || bits & (X64 | ARM64) == (X64 | ARM64) +} + +fn is_supported_system_module_name(module: &str) -> bool { + let lower = module.to_ascii_lowercase(); + !module.is_empty() + && (lower.ends_with(".dll") || lower.ends_with(".drv")) + && !module + .chars() + .any(|character| matches!(character, '/' | '\\' | ':')) +} + +fn flat_buffer_size(param: &reader::MethodParam) -> Option { + if let Some(attribute) = param.find_attribute("NativeArrayInfoAttribute") { + let values = attribute.value(); + if let Some(index) = attribute_index(&values, "CountParamIndex") { + return Some(FlatBufferSize::ElementCountParam(index)); + } + if let Some(count) = attribute_index(&values, "CountConst") { + return Some(FlatBufferSize::Constant(count)); + } + return Some(FlatBufferSize::Unknown); + } + if let Some(attribute) = param.find_attribute("MemorySizeAttribute") { + let values = attribute.value(); + return Some( + attribute_index(&values, "BytesParamIndex") + .map(FlatBufferSize::ByteCountParam) + .unwrap_or(FlatBufferSize::Unknown), + ); + } + None +} + +fn attribute_index(values: &[(String, windows_metadata::Value)], name: &str) -> Option { + values + .iter() + .find(|(candidate, _)| candidate == name) + .and_then(|(_, value)| match value { + windows_metadata::Value::I16(value) if *value >= 0 => Some(*value as usize), + windows_metadata::Value::U16(value) => Some(*value as usize), + windows_metadata::Value::I32(value) if *value >= 0 => Some(*value as usize), + windows_metadata::Value::U32(value) => usize::try_from(*value).ok(), + _ => None, + }) +} + +fn collect_enum(en: TypeMeta, seen: &mut HashSet<(String, String)>, sink: &mut Vec) { + if let TypeMeta::Enum { + namespace, name, .. + } = &en + { + if seen.insert((namespace.clone(), name.clone())) { + sink.push(en); + } + } +} + +/// Map a `windows_metadata::Type` to a `FlatAbiType`, following `Windows.Win32` +/// typedef conventions (single-field structs with `NativeTypedefAttribute` +/// wrapping a primitive → the underlying primitive OR a Handle/String flavour +/// depending on the pointee). +fn map_flat_type( + ty: &windows_metadata::Type, + index: &reader::Index, + enum_sink: &mut dyn FnMut(TypeMeta), +) -> FlatAbiType { + use windows_metadata::Type; + match ty { + Type::Void => FlatAbiType::Void, + Type::Bool => FlatAbiType::Bool, + Type::Char => FlatAbiType::Char16, + Type::I8 => FlatAbiType::I8, + Type::U8 => FlatAbiType::U8, + Type::I16 => FlatAbiType::I16, + Type::U16 => FlatAbiType::U16, + Type::I32 => FlatAbiType::I32, + Type::U32 => FlatAbiType::U32, + Type::I64 => FlatAbiType::I64, + Type::U64 => FlatAbiType::U64, + Type::F32 => FlatAbiType::F32, + Type::F64 => FlatAbiType::F64, + Type::PtrMut(inner, depth) | Type::PtrConst(inner, depth) => { + map_flat_pointer(inner, *depth, index, enum_sink) + } + Type::Name(tn) => resolve_named_flat_type(&tn.namespace, &tn.name, index, enum_sink), + // Anything else (Array, ConstRef, generics, …) is not a valid flat + // ABI shape in practice — surface as unknown pointer. + _ => FlatAbiType::Unknown, + } +} + +fn resolve_named_flat_type( + namespace: &str, + name: &str, + index: &reader::Index, + enum_sink: &mut dyn FnMut(TypeMeta), +) -> FlatAbiType { + // Handle well-known Win32 typedef wrappers directly by name so we don't + // depend on TypeDef lookup succeeding for well-known types. + if namespace == "Windows.Win32.Foundation" { + match name { + "PWSTR" | "PCWSTR" => return FlatAbiType::PWStr, + "PSTR" | "PCSTR" => return FlatAbiType::PStr, + // BSTR is a length-prefixed, SysAllocString-owned COM string — + // NOT a NUL-terminated PWSTR/PCWSTR. Marshalling as PWStr would + // silently drop the 4-byte length prefix and can crash callees + // that use SysStringLen. Treat as an opaque pointer so callers + // must supply a properly-allocated BSTR (or generation fails + // loudly with an unsupported-arg error at call time) instead + // of silently mis-marshalling. + "BSTR" => return FlatAbiType::Unknown, + "BOOL" => return FlatAbiType::Bool32, + "BOOLEAN" => return FlatAbiType::U8, + "FARPROC" | "PROC" | "NEARPROC" => return FlatAbiType::FunctionPointer, + "HRESULT" => return FlatAbiType::I32, + "NTSTATUS" => return FlatAbiType::I32, + // LSTATUS is a plain Int32 typedef in the win32 metadata, but + // if a future metadata revision ever exposed it as a + // `struct { Value: I32 }` (like Handle typedefs) the TypeDef + // path below would classify it as a Handle — which routes + // returns through the `'Ptr'` retKind and would mis-marshal + // the status code as a pointer. Also route it through I32 + // explicitly so it stays consistent with is_status_return_type + // in this module (which treats LSTATUS as a status typedef). + "LSTATUS" => return FlatAbiType::I32, + _ => {} + } + } + if is_flat_data_pointer_alias(name) { + return FlatAbiType::Ptr; + } + if is_flat_handle_alias(name) { + return FlatAbiType::Handle { + namespace: namespace.to_string(), + name: name.to_string(), + }; + } + let Some(def) = index.get(namespace, name).next() else { + return FlatAbiType::Unknown; + }; + let Some(ext) = def.extends() else { + return FlatAbiType::Unknown; + }; + if ext.namespace() == "System" && matches!(ext.name(), "Delegate" | "MulticastDelegate") { + return FlatAbiType::FunctionPointer; + } + // Enum: extends System.Enum. + if ext.namespace() == "System" && ext.name() == "Enum" { + let en = parse_flat_enum_def(&def); + if let TypeMeta::Enum { + underlying, + members, + .. + } = &en + { + let underlying_flat = match underlying.as_ref() { + TypeMeta::U32 => FlatAbiType::U32, + TypeMeta::I32 => FlatAbiType::I32, + TypeMeta::U16 => FlatAbiType::U16, + TypeMeta::I16 => FlatAbiType::I16, + TypeMeta::U8 => FlatAbiType::U8, + TypeMeta::I8 => FlatAbiType::I8, + TypeMeta::U64 => FlatAbiType::U64, + TypeMeta::I64 => FlatAbiType::I64, + _ => FlatAbiType::I32, + }; + let result = FlatAbiType::Enum { + namespace: namespace.to_string(), + name: name.to_string(), + underlying: Box::new(underlying_flat), + members: members.clone(), + }; + enum_sink(en); + return result; + } + } + // Struct: extends System.ValueType. Handle-like typedefs are single-field + // wrappers named `{ Value: T }` — we treat these as opaque handles. + if ext.namespace() == "System" && ext.name() == "ValueType" { + if !def.has_attribute("NativeTypedefAttribute") { + return FlatAbiType::Unknown; + } + let fields: Vec<(String, windows_metadata::Type)> = def + .fields() + .map(|f| (f.name().to_string(), f.ty())) + .collect(); + if fields.len() == 1 && fields[0].0 == "Value" { + match &fields[0].1 { + windows_metadata::Type::PtrMut(inner, _) + | windows_metadata::Type::PtrConst(inner, _) => { + // Pointer typedefs are not handles unless explicitly + // classified above. + return match inner.as_ref() { + windows_metadata::Type::Char => FlatAbiType::PWStr, + windows_metadata::Type::U8 => FlatAbiType::PStr, + _ => FlatAbiType::Unknown, + }; + } + windows_metadata::Type::I32 => { + return FlatAbiType::I32; + } + windows_metadata::Type::U32 => { + return FlatAbiType::U32; + } + _ => {} + } + } + // Multi-field struct — fall through to unknown (opaque pointer at ABI). + return FlatAbiType::Unknown; + } + FlatAbiType::Unknown +} + +fn is_flat_data_pointer_alias(name: &str) -> bool { + matches!( + name, + "PSID" + | "PSECURITY_DESCRIPTOR" + | "MEMORY_MAPPED_VIEW_ADDRESS" + | "LPPROC_THREAD_ATTRIBUTE_LIST" + | "PVOID" + | "PCVOID" + | "LPVOID" + | "LPCVOID" + ) +} + +fn is_flat_handle_alias(name: &str) -> bool { + matches!( + name, + "HANDLE" + | "HWND" + | "HACCEL" + | "HBITMAP" + | "HBRUSH" + | "HCURSOR" + | "HDC" + | "HDESK" + | "HDWP" + | "HENHMETAFILE" + | "HFILE" + | "HFONT" + | "HGDIOBJ" + | "HGLOBAL" + | "HHOOK" + | "HICON" + | "HIMAGELIST" + | "HINSTANCE" + | "HKEY" + | "HKL" + | "HLOCAL" + | "HMENU" + | "HMETAFILE" + | "HMODULE" + | "HMONITOR" + | "HPALETTE" + | "HPEN" + | "HRAWINPUT" + | "HRGN" + | "HRSRC" + | "HTHEME" + | "HWINSTA" + | "SC_HANDLE" + | "SERVICE_STATUS_HANDLE" + | "DPI_AWARENESS_CONTEXT" + ) +} + +fn map_flat_pointer( + inner: &windows_metadata::Type, + depth: usize, + index: &reader::Index, + enum_sink: &mut dyn FnMut(TypeMeta), +) -> FlatAbiType { + if depth == 0 { + return FlatAbiType::Unknown; + } + let mut mapped = if matches!(inner, windows_metadata::Type::Void) { + FlatAbiType::Ptr + } else { + FlatAbiType::PtrTo(Box::new(map_flat_type(inner, index, enum_sink))) + }; + for _ in 1..depth { + mapped = FlatAbiType::PtrTo(Box::new(mapped)); + } + mapped +} + +fn parse_flat_enum_def(def: &reader::TypeDef) -> TypeMeta { + let mut members = Vec::new(); + let mut underlying = TypeMeta::I32; + for field in def.fields() { + let name = field.name().to_string(); + if name == "value__" { + underlying = flat_enum_underlying_type(&field.ty()); + continue; + } + if let Some(constant) = field.constant() { + let value = match constant.value() { + windows_metadata::Value::I8(value) => value as i32, + windows_metadata::Value::U8(value) => value as i32, + windows_metadata::Value::I16(value) => value as i32, + windows_metadata::Value::U16(value) => value as i32, + windows_metadata::Value::I32(value) => value, + windows_metadata::Value::U32(value) => value as i32, + _ => 0, + }; + members.push(EnumMember { + name, + value, + doc: None, + }); + } + } + TypeMeta::Enum { + namespace: def.namespace().to_string(), + name: def.name().to_string(), + underlying: Box::new(underlying), + members, + is_flags: def.has_attribute("FlagsAttribute"), + doc: None, + deprecated: None, + } +} + +fn flat_enum_underlying_type(ty: &windows_metadata::Type) -> TypeMeta { + match ty { + windows_metadata::Type::I8 => TypeMeta::I8, + windows_metadata::Type::U8 => TypeMeta::U8, + windows_metadata::Type::I16 => TypeMeta::I16, + windows_metadata::Type::U16 => TypeMeta::U16, + windows_metadata::Type::I32 => TypeMeta::I32, + windows_metadata::Type::U32 => TypeMeta::U32, + windows_metadata::Type::I64 => TypeMeta::I64, + windows_metadata::Type::U64 => TypeMeta::U64, + _ => TypeMeta::I32, + } +} + fn parse_interface_type( index: &reader::Index, interface_type: &windows_metadata::Type, diff --git a/tools/dynwinrt-codegen/tests/snapshots/registry_apis/Apis.d.ts b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/Apis.d.ts new file mode 100644 index 00000000..3b89ab56 --- /dev/null +++ b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/Apis.d.ts @@ -0,0 +1,330 @@ +// Generated by dynwinrt-codegen — do not edit +// Flat-Win32 [DllImport] wrappers for Windows.Win32.System.Registry.Apis + +import { OBJECT_SECURITY_INFORMATION } from './OBJECT_SECURITY_INFORMATION.js'; +import { REG_CREATE_KEY_DISPOSITION } from './REG_CREATE_KEY_DISPOSITION.js'; +import { REG_NOTIFY_FILTER } from './REG_NOTIFY_FILTER.js'; +import { REG_OPEN_CREATE_OPTIONS } from './REG_OPEN_CREATE_OPTIONS.js'; +import { REG_ROUTINE_FLAGS } from './REG_ROUTINE_FLAGS.js'; +import { REG_SAM_FLAGS } from './REG_SAM_FLAGS.js'; +import { REG_SAVE_FORMAT } from './REG_SAVE_FORMAT.js'; +import { REG_VALUE_TYPE } from './REG_VALUE_TYPE.js'; +import { WIN32_ERROR } from './WIN32_ERROR.js'; + +/** Opaque Win32 handle. Pass either a raw pointer value as a `bigint` (safe for full 64-bit handle values) or a `number` (only for handles that fit in a JS safe integer, e.g. HWND with small window IDs). Do NOT pass a `Buffer` — `DynWin32.pointer(Buffer)` uses the buffer's own address, not the bytes it contains, so a Buffer of pointer bits would be misinterpreted as a pointer TO a `HANDLE`. */ +export type HANDLE = bigint | number; +/** Opaque Win32 handle. Pass either a raw pointer value as a `bigint` (safe for full 64-bit handle values) or a `number` (only for handles that fit in a JS safe integer, e.g. HWND with small window IDs). Do NOT pass a `Buffer` — `DynWin32.pointer(Buffer)` uses the buffer's own address, not the bytes it contains, so a Buffer of pointer bits would be misinterpreted as a pointer TO a `HKEY`. */ +export type HKEY = bigint | number; + +/** GetRegistryValueWithFallbackW — api-ms-win-core-state-helpers-l1-1-0.dll export. */ +export declare function getRegistryValueWithFallbackW(hkeyPrimary: HKEY, primarySubKey: string | null, hkeyFallback: HKEY, fallbackSubKey: string | null, value: string | null, flags: number, data: bigint | Buffer | Uint8Array | null, dataIn: number): { readonly status: number; readonly pdwType: number; readonly pcbDataOut: number }; + +/** RegCloseKey — ADVAPI32.dll export. */ +export declare function regCloseKey(hKey: HKEY): { readonly status: number }; + +/** RegConnectRegistryA — ADVAPI32.dll export. */ +export declare function regConnectRegistryA(machineName: bigint | Buffer | Uint8Array | null, hKey: HKEY): { readonly status: number; readonly phkResult: bigint }; + +/** RegConnectRegistryExA — ADVAPI32.dll export. */ +export declare function regConnectRegistryExA(machineName: bigint | Buffer | Uint8Array | null, hKey: HKEY, flags: number): { readonly status: number; readonly phkResult: bigint }; + +/** RegConnectRegistryExW — ADVAPI32.dll export. */ +export declare function regConnectRegistryExW(machineName: string | null, hKey: HKEY, flags: number): { readonly status: number; readonly phkResult: bigint }; + +/** RegConnectRegistryW — ADVAPI32.dll export. */ +export declare function regConnectRegistryW(machineName: string | null, hKey: HKEY): { readonly status: number; readonly phkResult: bigint }; + +/** RegCopyTreeA — ADVAPI32.dll export. */ +export declare function regCopyTreeA(hKeySrc: HKEY, subKey: bigint | Buffer | Uint8Array | null, hKeyDest: HKEY): { readonly status: number }; + +/** RegCopyTreeW — ADVAPI32.dll export. */ +export declare function regCopyTreeW(hKeySrc: HKEY, subKey: string | null, hKeyDest: HKEY): { readonly status: number }; + +/** RegCreateKeyA — ADVAPI32.dll export. */ +export declare function regCreateKeyA(hKey: HKEY, subKey: bigint | Buffer | Uint8Array | null): { readonly status: number; readonly phkResult: bigint }; + +/** RegCreateKeyExA — ADVAPI32.dll export. */ +export declare function regCreateKeyExA(hKey: HKEY, subKey: bigint | Buffer | Uint8Array | null, reserved: number, class_: bigint | Buffer | Uint8Array | null, options: REG_OPEN_CREATE_OPTIONS, samDesired: REG_SAM_FLAGS, securityAttributes: bigint | Buffer | Uint8Array | null): { readonly status: number; readonly phkResult: bigint; readonly lpdwDisposition: REG_CREATE_KEY_DISPOSITION }; + +/** RegCreateKeyExW — ADVAPI32.dll export. */ +export declare function regCreateKeyExW(hKey: HKEY, subKey: string | null, reserved: number, class_: string | null, options: REG_OPEN_CREATE_OPTIONS, samDesired: REG_SAM_FLAGS, securityAttributes: bigint | Buffer | Uint8Array | null): { readonly status: number; readonly phkResult: bigint; readonly lpdwDisposition: REG_CREATE_KEY_DISPOSITION }; + +/** RegCreateKeyTransactedA — ADVAPI32.dll export. */ +export declare function regCreateKeyTransactedA(hKey: HKEY, subKey: bigint | Buffer | Uint8Array | null, reserved: number, class_: bigint | Buffer | Uint8Array | null, options: REG_OPEN_CREATE_OPTIONS, samDesired: REG_SAM_FLAGS, securityAttributes: bigint | Buffer | Uint8Array | null, hTransaction: HANDLE, pExtendedParemeter: bigint | Buffer | Uint8Array | null): { readonly status: number; readonly phkResult: bigint; readonly lpdwDisposition: REG_CREATE_KEY_DISPOSITION }; + +/** RegCreateKeyTransactedW — ADVAPI32.dll export. */ +export declare function regCreateKeyTransactedW(hKey: HKEY, subKey: string | null, reserved: number, class_: string | null, options: REG_OPEN_CREATE_OPTIONS, samDesired: REG_SAM_FLAGS, securityAttributes: bigint | Buffer | Uint8Array | null, hTransaction: HANDLE, pExtendedParemeter: bigint | Buffer | Uint8Array | null): { readonly status: number; readonly phkResult: bigint; readonly lpdwDisposition: REG_CREATE_KEY_DISPOSITION }; + +/** RegCreateKeyW — ADVAPI32.dll export. */ +export declare function regCreateKeyW(hKey: HKEY, subKey: string | null): { readonly status: number; readonly phkResult: bigint }; + +/** RegDeleteKeyA — ADVAPI32.dll export. */ +export declare function regDeleteKeyA(hKey: HKEY, subKey: bigint | Buffer | Uint8Array | null): { readonly status: number }; + +/** RegDeleteKeyExA — ADVAPI32.dll export. */ +export declare function regDeleteKeyExA(hKey: HKEY, subKey: bigint | Buffer | Uint8Array | null, samDesired: number, reserved: number): { readonly status: number }; + +/** RegDeleteKeyExW — ADVAPI32.dll export. */ +export declare function regDeleteKeyExW(hKey: HKEY, subKey: string | null, samDesired: number, reserved: number): { readonly status: number }; + +/** RegDeleteKeyTransactedA — ADVAPI32.dll export. */ +export declare function regDeleteKeyTransactedA(hKey: HKEY, subKey: bigint | Buffer | Uint8Array | null, samDesired: number, reserved: number, hTransaction: HANDLE, pExtendedParameter: bigint | Buffer | Uint8Array | null): { readonly status: number }; + +/** RegDeleteKeyTransactedW — ADVAPI32.dll export. */ +export declare function regDeleteKeyTransactedW(hKey: HKEY, subKey: string | null, samDesired: number, reserved: number, hTransaction: HANDLE, pExtendedParameter: bigint | Buffer | Uint8Array | null): { readonly status: number }; + +/** RegDeleteKeyValueA — ADVAPI32.dll export. */ +export declare function regDeleteKeyValueA(hKey: HKEY, subKey: bigint | Buffer | Uint8Array | null, valueName: bigint | Buffer | Uint8Array | null): { readonly status: number }; + +/** RegDeleteKeyValueW — ADVAPI32.dll export. */ +export declare function regDeleteKeyValueW(hKey: HKEY, subKey: string | null, valueName: string | null): { readonly status: number }; + +/** RegDeleteKeyW — ADVAPI32.dll export. */ +export declare function regDeleteKeyW(hKey: HKEY, subKey: string | null): { readonly status: number }; + +/** RegDeleteTreeA — ADVAPI32.dll export. */ +export declare function regDeleteTreeA(hKey: HKEY, subKey: bigint | Buffer | Uint8Array | null): { readonly status: number }; + +/** RegDeleteTreeW — ADVAPI32.dll export. */ +export declare function regDeleteTreeW(hKey: HKEY, subKey: string | null): { readonly status: number }; + +/** RegDeleteValueA — ADVAPI32.dll export. */ +export declare function regDeleteValueA(hKey: HKEY, valueName: bigint | Buffer | Uint8Array | null): { readonly status: number }; + +/** RegDeleteValueW — ADVAPI32.dll export. */ +export declare function regDeleteValueW(hKey: HKEY, valueName: string | null): { readonly status: number }; + +/** RegDisablePredefinedCache — ADVAPI32.dll export. */ +export declare function regDisablePredefinedCache(): { readonly status: number }; + +/** RegDisablePredefinedCacheEx — ADVAPI32.dll export. */ +export declare function regDisablePredefinedCacheEx(): { readonly status: number }; + +/** RegDisableReflectionKey — ADVAPI32.dll export. */ +export declare function regDisableReflectionKey(hBase: HKEY): { readonly status: number }; + +/** RegEnableReflectionKey — ADVAPI32.dll export. */ +export declare function regEnableReflectionKey(hBase: HKEY): { readonly status: number }; + +/** RegEnumKeyA — ADVAPI32.dll export. */ +export declare function regEnumKeyA(hKey: HKEY, index: number, name: bigint | Buffer | Uint8Array | null, cchName: number): { readonly status: number }; + +/** RegEnumKeyW — ADVAPI32.dll export. */ +export declare function regEnumKeyW(hKey: HKEY, index: number, name: bigint | Buffer | Uint8Array | null, cchName: number): { readonly status: number }; + +/** RegEnumValueA — ADVAPI32.dll export. */ +export declare function regEnumValueA(hKey: HKEY, index: number, valueName: bigint | Buffer | Uint8Array | null, lpcchValueName: number, reserved: bigint | Buffer | Uint8Array | null, data: bigint | Buffer | Uint8Array | null, lpcbData: number): { readonly status: number; readonly lpcchValueName: number; readonly type: number; readonly lpcbData: number }; + +/** RegEnumValueW — ADVAPI32.dll export. */ +export declare function regEnumValueW(hKey: HKEY, index: number, valueName: bigint | Buffer | Uint8Array | null, lpcchValueName: number, reserved: bigint | Buffer | Uint8Array | null, data: bigint | Buffer | Uint8Array | null, lpcbData: number): { readonly status: number; readonly lpcchValueName: number; readonly type: number; readonly lpcbData: number }; + +/** RegFlushKey — ADVAPI32.dll export. */ +export declare function regFlushKey(hKey: HKEY): { readonly status: number }; + +/** RegGetKeySecurity — ADVAPI32.dll export. */ +export declare function regGetKeySecurity(hKey: HKEY, securityInformation: OBJECT_SECURITY_INFORMATION, pSecurityDescriptor: bigint | Buffer | Uint8Array | null, lpcbSecurityDescriptor: number): { readonly status: number; readonly lpcbSecurityDescriptor: number }; + +/** RegGetValueA — ADVAPI32.dll export. */ +export declare function regGetValueA(hkey: HKEY, subKey: bigint | Buffer | Uint8Array | null, value: bigint | Buffer | Uint8Array | null, flags: REG_ROUTINE_FLAGS, data: bigint | Buffer | Uint8Array | null, pcbData: number): { readonly status: number; readonly pdwType: REG_VALUE_TYPE; readonly pcbData: number }; + +/** RegGetValueW — ADVAPI32.dll export. */ +export declare function regGetValueW(hkey: HKEY, subKey: string | null, value: string | null, flags: REG_ROUTINE_FLAGS, data: bigint | Buffer | Uint8Array | null, pcbData: number): { readonly status: number; readonly pdwType: REG_VALUE_TYPE; readonly pcbData: number }; + +/** RegLoadAppKeyA — ADVAPI32.dll export. */ +export declare function regLoadAppKeyA(file: bigint | Buffer | Uint8Array | null, samDesired: number, options: number, reserved: number): { readonly status: number; readonly phkResult: bigint }; + +/** RegLoadAppKeyW — ADVAPI32.dll export. */ +export declare function regLoadAppKeyW(file: string | null, samDesired: number, options: number, reserved: number): { readonly status: number; readonly phkResult: bigint }; + +/** RegLoadKeyA — ADVAPI32.dll export. */ +export declare function regLoadKeyA(hKey: HKEY, subKey: bigint | Buffer | Uint8Array | null, file: bigint | Buffer | Uint8Array | null): { readonly status: number }; + +/** RegLoadKeyW — ADVAPI32.dll export. */ +export declare function regLoadKeyW(hKey: HKEY, subKey: string | null, file: string | null): { readonly status: number }; + +/** RegLoadMUIStringA — ADVAPI32.dll export. */ +export declare function regLoadMUIStringA(hKey: HKEY, value: bigint | Buffer | Uint8Array | null, outBuf: bigint | Buffer | Uint8Array | null, outBuf_2: number, flags: number, directory: bigint | Buffer | Uint8Array | null): { readonly status: number; readonly pcbData: number }; + +/** RegLoadMUIStringW — ADVAPI32.dll export. */ +export declare function regLoadMUIStringW(hKey: HKEY, value: string | null, outBuf: bigint | Buffer | Uint8Array | null, outBuf_2: number, flags: number, directory: string | null): { readonly status: number; readonly pcbData: number }; + +/** RegNotifyChangeKeyValue — ADVAPI32.dll export. */ +export declare function regNotifyChangeKeyValue(hKey: HKEY, bWatchSubtree: boolean, notifyFilter: REG_NOTIFY_FILTER, hEvent: HANDLE, fAsynchronous: boolean): { readonly status: number }; + +/** RegOpenCurrentUser — ADVAPI32.dll export. */ +export declare function regOpenCurrentUser(samDesired: number): { readonly status: number; readonly phkResult: bigint }; + +/** RegOpenKeyA — ADVAPI32.dll export. */ +export declare function regOpenKeyA(hKey: HKEY, subKey: bigint | Buffer | Uint8Array | null): { readonly status: number; readonly phkResult: bigint }; + +/** RegOpenKeyExA — ADVAPI32.dll export. */ +export declare function regOpenKeyExA(hKey: HKEY, subKey: bigint | Buffer | Uint8Array | null, ulOptions: number, samDesired: REG_SAM_FLAGS): { readonly status: number; readonly phkResult: bigint }; + +/** RegOpenKeyExW — ADVAPI32.dll export. */ +export declare function regOpenKeyExW(hKey: HKEY, subKey: string | null, ulOptions: number, samDesired: REG_SAM_FLAGS): { readonly status: number; readonly phkResult: bigint }; + +/** RegOpenKeyTransactedA — ADVAPI32.dll export. */ +export declare function regOpenKeyTransactedA(hKey: HKEY, subKey: bigint | Buffer | Uint8Array | null, ulOptions: number, samDesired: REG_SAM_FLAGS, hTransaction: HANDLE, pExtendedParemeter: bigint | Buffer | Uint8Array | null): { readonly status: number; readonly phkResult: bigint }; + +/** RegOpenKeyTransactedW — ADVAPI32.dll export. */ +export declare function regOpenKeyTransactedW(hKey: HKEY, subKey: string | null, ulOptions: number, samDesired: REG_SAM_FLAGS, hTransaction: HANDLE, pExtendedParemeter: bigint | Buffer | Uint8Array | null): { readonly status: number; readonly phkResult: bigint }; + +/** RegOpenKeyW — ADVAPI32.dll export. */ +export declare function regOpenKeyW(hKey: HKEY, subKey: string | null): { readonly status: number; readonly phkResult: bigint }; + +/** RegOpenUserClassesRoot — ADVAPI32.dll export. */ +export declare function regOpenUserClassesRoot(hToken: HANDLE, options: number, samDesired: number): { readonly status: number; readonly phkResult: bigint }; + +/** RegOverridePredefKey — ADVAPI32.dll export. */ +export declare function regOverridePredefKey(hKey: HKEY, hNewHKey: HKEY): { readonly status: number }; + +/** RegQueryReflectionKey — ADVAPI32.dll export. */ +export declare function regQueryReflectionKey(hBase: HKEY): { readonly status: number; readonly bIsReflectionDisabled: boolean }; + +/** RegQueryValueA — ADVAPI32.dll export. */ +export declare function regQueryValueA(hKey: HKEY, subKey: bigint | Buffer | Uint8Array | null, data: bigint | Buffer | Uint8Array | null, lpcbData: number): { readonly status: number; readonly lpcbData: number }; + +/** RegQueryValueExA — ADVAPI32.dll export. */ +export declare function regQueryValueExA(hKey: HKEY, valueName: bigint | Buffer | Uint8Array | null, reserved: bigint | Buffer | Uint8Array | null, data: bigint | Buffer | Uint8Array | null, lpcbData: number): { readonly status: number; readonly type: REG_VALUE_TYPE; readonly lpcbData: number }; + +/** RegQueryValueExW — ADVAPI32.dll export. */ +export declare function regQueryValueExW(hKey: HKEY, valueName: string | null, reserved: bigint | Buffer | Uint8Array | null, data: bigint | Buffer | Uint8Array | null, lpcbData: number): { readonly status: number; readonly type: REG_VALUE_TYPE; readonly lpcbData: number }; + +/** RegQueryValueW — ADVAPI32.dll export. */ +export declare function regQueryValueW(hKey: HKEY, subKey: string | null, data: bigint | Buffer | Uint8Array | null, lpcbData: number): { readonly status: number; readonly lpcbData: number }; + +/** RegRenameKey — ADVAPI32.dll export. */ +export declare function regRenameKey(hKey: HKEY, subKeyName: string | null, newKeyName: string | null): { readonly status: number }; + +/** RegReplaceKeyA — ADVAPI32.dll export. */ +export declare function regReplaceKeyA(hKey: HKEY, subKey: bigint | Buffer | Uint8Array | null, newFile: bigint | Buffer | Uint8Array | null, oldFile: bigint | Buffer | Uint8Array | null): { readonly status: number }; + +/** RegReplaceKeyW — ADVAPI32.dll export. */ +export declare function regReplaceKeyW(hKey: HKEY, subKey: string | null, newFile: string | null, oldFile: string | null): { readonly status: number }; + +/** RegRestoreKeyA — ADVAPI32.dll export. */ +export declare function regRestoreKeyA(hKey: HKEY, file: bigint | Buffer | Uint8Array | null, flags: number): { readonly status: number }; + +/** RegRestoreKeyW — ADVAPI32.dll export. */ +export declare function regRestoreKeyW(hKey: HKEY, file: string | null, flags: number): { readonly status: number }; + +/** RegSaveKeyA — ADVAPI32.dll export. */ +export declare function regSaveKeyA(hKey: HKEY, file: bigint | Buffer | Uint8Array | null, securityAttributes: bigint | Buffer | Uint8Array | null): { readonly status: number }; + +/** RegSaveKeyExA — ADVAPI32.dll export. */ +export declare function regSaveKeyExA(hKey: HKEY, file: bigint | Buffer | Uint8Array | null, securityAttributes: bigint | Buffer | Uint8Array | null, flags: REG_SAVE_FORMAT): { readonly status: number }; + +/** RegSaveKeyExW — ADVAPI32.dll export. */ +export declare function regSaveKeyExW(hKey: HKEY, file: string | null, securityAttributes: bigint | Buffer | Uint8Array | null, flags: REG_SAVE_FORMAT): { readonly status: number }; + +/** RegSaveKeyW — ADVAPI32.dll export. */ +export declare function regSaveKeyW(hKey: HKEY, file: string | null, securityAttributes: bigint | Buffer | Uint8Array | null): { readonly status: number }; + +/** RegSetKeySecurity — ADVAPI32.dll export. */ +export declare function regSetKeySecurity(hKey: HKEY, securityInformation: OBJECT_SECURITY_INFORMATION, pSecurityDescriptor: bigint | Buffer | Uint8Array | null): { readonly status: number }; + +/** RegSetKeyValueA — ADVAPI32.dll export. */ +export declare function regSetKeyValueA(hKey: HKEY, subKey: bigint | Buffer | Uint8Array | null, valueName: bigint | Buffer | Uint8Array | null, type: number, data: bigint | Buffer | Uint8Array | null, data_2: number): { readonly status: number }; + +/** RegSetKeyValueW — ADVAPI32.dll export. */ +export declare function regSetKeyValueW(hKey: HKEY, subKey: string | null, valueName: string | null, type: number, data: bigint | Buffer | Uint8Array | null, data_2: number): { readonly status: number }; + +/** RegSetValueA — ADVAPI32.dll export. */ +export declare function regSetValueA(hKey: HKEY, subKey: bigint | Buffer | Uint8Array | null, type: REG_VALUE_TYPE, data: bigint | Buffer | Uint8Array | null, data_2: number): { readonly status: number }; + +/** RegSetValueExA — ADVAPI32.dll export. */ +export declare function regSetValueExA(hKey: HKEY, valueName: bigint | Buffer | Uint8Array | null, reserved: number, type: REG_VALUE_TYPE, data: bigint | Buffer | Uint8Array | null, data_2: number): { readonly status: number }; + +/** RegSetValueExW — ADVAPI32.dll export. */ +export declare function regSetValueExW(hKey: HKEY, valueName: string | null, reserved: number, type: REG_VALUE_TYPE, data: bigint | Buffer | Uint8Array | null, data_2: number): { readonly status: number }; + +/** RegSetValueW — ADVAPI32.dll export. */ +export declare function regSetValueW(hKey: HKEY, subKey: string | null, type: REG_VALUE_TYPE, data: bigint | Buffer | Uint8Array | null, data_2: number): { readonly status: number }; + +/** RegUnLoadKeyA — ADVAPI32.dll export. */ +export declare function regUnLoadKeyA(hKey: HKEY, subKey: bigint | Buffer | Uint8Array | null): { readonly status: number }; + +/** RegUnLoadKeyW — ADVAPI32.dll export. */ +export declare function regUnLoadKeyW(hKey: HKEY, subKey: string | null): { readonly status: number }; + +export declare const Apis: { + getRegistryValueWithFallbackW: typeof getRegistryValueWithFallbackW; + regCloseKey: typeof regCloseKey; + regConnectRegistryA: typeof regConnectRegistryA; + regConnectRegistryExA: typeof regConnectRegistryExA; + regConnectRegistryExW: typeof regConnectRegistryExW; + regConnectRegistryW: typeof regConnectRegistryW; + regCopyTreeA: typeof regCopyTreeA; + regCopyTreeW: typeof regCopyTreeW; + regCreateKeyA: typeof regCreateKeyA; + regCreateKeyExA: typeof regCreateKeyExA; + regCreateKeyExW: typeof regCreateKeyExW; + regCreateKeyTransactedA: typeof regCreateKeyTransactedA; + regCreateKeyTransactedW: typeof regCreateKeyTransactedW; + regCreateKeyW: typeof regCreateKeyW; + regDeleteKeyA: typeof regDeleteKeyA; + regDeleteKeyExA: typeof regDeleteKeyExA; + regDeleteKeyExW: typeof regDeleteKeyExW; + regDeleteKeyTransactedA: typeof regDeleteKeyTransactedA; + regDeleteKeyTransactedW: typeof regDeleteKeyTransactedW; + regDeleteKeyValueA: typeof regDeleteKeyValueA; + regDeleteKeyValueW: typeof regDeleteKeyValueW; + regDeleteKeyW: typeof regDeleteKeyW; + regDeleteTreeA: typeof regDeleteTreeA; + regDeleteTreeW: typeof regDeleteTreeW; + regDeleteValueA: typeof regDeleteValueA; + regDeleteValueW: typeof regDeleteValueW; + regDisablePredefinedCache: typeof regDisablePredefinedCache; + regDisablePredefinedCacheEx: typeof regDisablePredefinedCacheEx; + regDisableReflectionKey: typeof regDisableReflectionKey; + regEnableReflectionKey: typeof regEnableReflectionKey; + regEnumKeyA: typeof regEnumKeyA; + regEnumKeyW: typeof regEnumKeyW; + regEnumValueA: typeof regEnumValueA; + regEnumValueW: typeof regEnumValueW; + regFlushKey: typeof regFlushKey; + regGetKeySecurity: typeof regGetKeySecurity; + regGetValueA: typeof regGetValueA; + regGetValueW: typeof regGetValueW; + regLoadAppKeyA: typeof regLoadAppKeyA; + regLoadAppKeyW: typeof regLoadAppKeyW; + regLoadKeyA: typeof regLoadKeyA; + regLoadKeyW: typeof regLoadKeyW; + regLoadMUIStringA: typeof regLoadMUIStringA; + regLoadMUIStringW: typeof regLoadMUIStringW; + regNotifyChangeKeyValue: typeof regNotifyChangeKeyValue; + regOpenCurrentUser: typeof regOpenCurrentUser; + regOpenKeyA: typeof regOpenKeyA; + regOpenKeyExA: typeof regOpenKeyExA; + regOpenKeyExW: typeof regOpenKeyExW; + regOpenKeyTransactedA: typeof regOpenKeyTransactedA; + regOpenKeyTransactedW: typeof regOpenKeyTransactedW; + regOpenKeyW: typeof regOpenKeyW; + regOpenUserClassesRoot: typeof regOpenUserClassesRoot; + regOverridePredefKey: typeof regOverridePredefKey; + regQueryReflectionKey: typeof regQueryReflectionKey; + regQueryValueA: typeof regQueryValueA; + regQueryValueExA: typeof regQueryValueExA; + regQueryValueExW: typeof regQueryValueExW; + regQueryValueW: typeof regQueryValueW; + regRenameKey: typeof regRenameKey; + regReplaceKeyA: typeof regReplaceKeyA; + regReplaceKeyW: typeof regReplaceKeyW; + regRestoreKeyA: typeof regRestoreKeyA; + regRestoreKeyW: typeof regRestoreKeyW; + regSaveKeyA: typeof regSaveKeyA; + regSaveKeyExA: typeof regSaveKeyExA; + regSaveKeyExW: typeof regSaveKeyExW; + regSaveKeyW: typeof regSaveKeyW; + regSetKeySecurity: typeof regSetKeySecurity; + regSetKeyValueA: typeof regSetKeyValueA; + regSetKeyValueW: typeof regSetKeyValueW; + regSetValueA: typeof regSetValueA; + regSetValueExA: typeof regSetValueExA; + regSetValueExW: typeof regSetValueExW; + regSetValueW: typeof regSetValueW; + regUnLoadKeyA: typeof regUnLoadKeyA; + regUnLoadKeyW: typeof regUnLoadKeyW; +}; + +export declare const FLAT_EXPORTS: Readonly>; diff --git a/tools/dynwinrt-codegen/tests/snapshots/registry_apis/Apis.js b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/Apis.js new file mode 100644 index 00000000..eb0a8904 --- /dev/null +++ b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/Apis.js @@ -0,0 +1,1726 @@ +// Generated by dynwinrt-codegen — do not edit +// Flat-Win32 [DllImport] wrappers for Windows.Win32.System.Registry.Apis +// +// Each exported function is a natural JS wrapper around +// DynWin32.invoke(dll, entry, retKind, args). Pointer-to-scalar +// [out]/[in,out] params are projected as return-object fields; opaque +// pointer params (Buffer|bigint|null) stay in the argument list. + +import { DynWin32 } from '@microsoft/dynwinrt/win32'; + +// Build a NUL-terminated UTF-16LE Buffer for LPCWSTR args. Rejects embedded +// U+0000 up front — Win32 wide-string APIs would silently truncate at the +// first NUL, which is a source of validation-bypass bugs. +function _wideStringBuffer(str) { + if (str === null || str === undefined) return null; + if (typeof str !== 'string') { + throw new TypeError(`expected string, got ${typeof str}`); + } + if (str.indexOf('\u0000') !== -1) { + throw new RangeError('string contains embedded NUL (U+0000)'); + } + const buf = Buffer.alloc((str.length + 1) * 2); + buf.write(str, 'utf16le'); + return buf; +} + +/** + * GetRegistryValueWithFallbackW — api-ms-win-core-state-helpers-l1-1-0.dll export. + * + * @param hkeyPrimary [in] HKEY handle + * @param primarySubKey [in] LPCWSTR string + * @param hkeyFallback [in] HKEY handle + * @param fallbackSubKey [in] LPCWSTR string + * @param value [in] LPCWSTR string + * @param flags [in] U32 + * @param pdwType [out] pointer to U32 + * @param data [in/out pointer] caller-owned ByteCountParam(8) buffer of U8 + * @param dataIn [in] U32 + * @param pcbDataOut [out] pointer to U32 + * @returns { status: number, pdwType: , pcbDataOut: } + */ +export function getRegistryValueWithFallbackW(hkeyPrimary, primarySubKey, hkeyFallback, fallbackSubKey, value, flags, data, dataIn) { + const _dataRequiredBytes = Number(dataIn) * 1; + if (!Number.isSafeInteger(_dataRequiredBytes) || _dataRequiredBytes < 0) { + throw new RangeError('data size is not a non-negative safe integer'); + } + if (data != null && ArrayBuffer.isView(data) && data.byteLength < _dataRequiredBytes) { + throw new RangeError('data buffer is smaller than the native size contract'); + } + const _pdwTypeSlot = Buffer.alloc(4); + const _pcbDataOutSlot = Buffer.alloc(4); + const _primarySubKeyBuf = _wideStringBuffer(primarySubKey); + const _fallbackSubKeyBuf = _wideStringBuffer(fallbackSubKey); + const _valueBuf = _wideStringBuffer(value); + const _call = DynWin32.invoke('api-ms-win-core-state-helpers-l1-1-0.dll', 'GetRegistryValueWithFallbackW', 'U32', [DynWin32.handle(hkeyPrimary), DynWin32.pointer(_primarySubKeyBuf), DynWin32.handle(hkeyFallback), DynWin32.pointer(_fallbackSubKeyBuf), DynWin32.pointer(_valueBuf), DynWin32.u32(flags), DynWin32.pointer(_pdwTypeSlot), DynWin32.pointer(data), DynWin32.u32(dataIn), DynWin32.pointer(_pcbDataOutSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + pdwType: _pdwTypeSlot.readUInt32LE(0), + pcbDataOut: _pcbDataOutSlot.readUInt32LE(0), + }; +} + +/** + * RegCloseKey — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @returns { status: number } + */ +export function regCloseKey(hKey) { + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegCloseKey', 'U32', [DynWin32.handle(hKey)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegConnectRegistryA — ADVAPI32.dll export. + * + * @param machineName [in/out pointer] LPCSTR string + * @param hKey [in] HKEY handle + * @param phkResult [out] pointer to HKEY handle + * @returns { status: number, phkResult: } + */ +export function regConnectRegistryA(machineName, hKey) { + const _phkResultSlot = Buffer.alloc(8); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegConnectRegistryA', 'U32', [DynWin32.pointer(machineName), DynWin32.handle(hKey), DynWin32.pointer(_phkResultSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + phkResult: _phkResultSlot.readBigUInt64LE(0), + }; +} + +/** + * RegConnectRegistryExA — ADVAPI32.dll export. + * + * @param machineName [in/out pointer] LPCSTR string + * @param hKey [in] HKEY handle + * @param flags [in] U32 + * @param phkResult [out] pointer to HKEY handle + * @returns { status: number, phkResult: } + */ +export function regConnectRegistryExA(machineName, hKey, flags) { + const _phkResultSlot = Buffer.alloc(8); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegConnectRegistryExA', 'I32', [DynWin32.pointer(machineName), DynWin32.handle(hKey), DynWin32.u32(flags), DynWin32.pointer(_phkResultSlot)], false); + const _ret = _call.value; + return { + status: DynWin32.toNumber(_ret), + phkResult: _phkResultSlot.readBigUInt64LE(0), + }; +} + +/** + * RegConnectRegistryExW — ADVAPI32.dll export. + * + * @param machineName [in] LPCWSTR string + * @param hKey [in] HKEY handle + * @param flags [in] U32 + * @param phkResult [out] pointer to HKEY handle + * @returns { status: number, phkResult: } + */ +export function regConnectRegistryExW(machineName, hKey, flags) { + const _phkResultSlot = Buffer.alloc(8); + const _machineNameBuf = _wideStringBuffer(machineName); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegConnectRegistryExW', 'I32', [DynWin32.pointer(_machineNameBuf), DynWin32.handle(hKey), DynWin32.u32(flags), DynWin32.pointer(_phkResultSlot)], false); + const _ret = _call.value; + return { + status: DynWin32.toNumber(_ret), + phkResult: _phkResultSlot.readBigUInt64LE(0), + }; +} + +/** + * RegConnectRegistryW — ADVAPI32.dll export. + * + * @param machineName [in] LPCWSTR string + * @param hKey [in] HKEY handle + * @param phkResult [out] pointer to HKEY handle + * @returns { status: number, phkResult: } + */ +export function regConnectRegistryW(machineName, hKey) { + const _phkResultSlot = Buffer.alloc(8); + const _machineNameBuf = _wideStringBuffer(machineName); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegConnectRegistryW', 'U32', [DynWin32.pointer(_machineNameBuf), DynWin32.handle(hKey), DynWin32.pointer(_phkResultSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + phkResult: _phkResultSlot.readBigUInt64LE(0), + }; +} + +/** + * RegCopyTreeA — ADVAPI32.dll export. + * + * @param hKeySrc [in] HKEY handle + * @param subKey [in/out pointer] LPCSTR string + * @param hKeyDest [in] HKEY handle + * @returns { status: number } + */ +export function regCopyTreeA(hKeySrc, subKey, hKeyDest) { + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegCopyTreeA', 'U32', [DynWin32.handle(hKeySrc), DynWin32.pointer(subKey), DynWin32.handle(hKeyDest)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegCopyTreeW — ADVAPI32.dll export. + * + * @param hKeySrc [in] HKEY handle + * @param subKey [in] LPCWSTR string + * @param hKeyDest [in] HKEY handle + * @returns { status: number } + */ +export function regCopyTreeW(hKeySrc, subKey, hKeyDest) { + const _subKeyBuf = _wideStringBuffer(subKey); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegCopyTreeW', 'U32', [DynWin32.handle(hKeySrc), DynWin32.pointer(_subKeyBuf), DynWin32.handle(hKeyDest)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegCreateKeyA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in/out pointer] LPCSTR string + * @param phkResult [out] pointer to HKEY handle + * @returns { status: number, phkResult: } + */ +export function regCreateKeyA(hKey, subKey) { + const _phkResultSlot = Buffer.alloc(8); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegCreateKeyA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(subKey), DynWin32.pointer(_phkResultSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + phkResult: _phkResultSlot.readBigUInt64LE(0), + }; +} + +/** + * RegCreateKeyExA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in/out pointer] LPCSTR string + * @param reserved [in] U32 + * @param class_ [in/out pointer] LPCSTR string + * @param options [in] REG_OPEN_CREATE_OPTIONS enum + * @param samDesired [in] REG_SAM_FLAGS enum + * @param securityAttributes [in/out pointer] pointer to Unknown + * @param phkResult [out] pointer to HKEY handle + * @param lpdwDisposition [out] pointer to REG_CREATE_KEY_DISPOSITION enum + * @returns { status: number, phkResult: , lpdwDisposition: } + */ +export function regCreateKeyExA(hKey, subKey, reserved, class_, options, samDesired, securityAttributes) { + const _phkResultSlot = Buffer.alloc(8); + const _lpdwDispositionSlot = Buffer.alloc(4); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegCreateKeyExA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(subKey), DynWin32.u32(reserved), DynWin32.pointer(class_), DynWin32.u32((options) >>> 0), DynWin32.u32((samDesired) >>> 0), DynWin32.pointer(securityAttributes), DynWin32.pointer(_phkResultSlot), DynWin32.pointer(_lpdwDispositionSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + phkResult: _phkResultSlot.readBigUInt64LE(0), + lpdwDisposition: (_lpdwDispositionSlot.readUInt32LE(0) | 0), + }; +} + +/** + * RegCreateKeyExW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in] LPCWSTR string + * @param reserved [in] U32 + * @param class_ [in] LPCWSTR string + * @param options [in] REG_OPEN_CREATE_OPTIONS enum + * @param samDesired [in] REG_SAM_FLAGS enum + * @param securityAttributes [in/out pointer] pointer to Unknown + * @param phkResult [out] pointer to HKEY handle + * @param lpdwDisposition [out] pointer to REG_CREATE_KEY_DISPOSITION enum + * @returns { status: number, phkResult: , lpdwDisposition: } + */ +export function regCreateKeyExW(hKey, subKey, reserved, class_, options, samDesired, securityAttributes) { + const _phkResultSlot = Buffer.alloc(8); + const _lpdwDispositionSlot = Buffer.alloc(4); + const _subKeyBuf = _wideStringBuffer(subKey); + const _class_Buf = _wideStringBuffer(class_); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegCreateKeyExW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_subKeyBuf), DynWin32.u32(reserved), DynWin32.pointer(_class_Buf), DynWin32.u32((options) >>> 0), DynWin32.u32((samDesired) >>> 0), DynWin32.pointer(securityAttributes), DynWin32.pointer(_phkResultSlot), DynWin32.pointer(_lpdwDispositionSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + phkResult: _phkResultSlot.readBigUInt64LE(0), + lpdwDisposition: (_lpdwDispositionSlot.readUInt32LE(0) | 0), + }; +} + +/** + * RegCreateKeyTransactedA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in/out pointer] LPCSTR string + * @param reserved [in] U32 + * @param class_ [in/out pointer] LPCSTR string + * @param options [in] REG_OPEN_CREATE_OPTIONS enum + * @param samDesired [in] REG_SAM_FLAGS enum + * @param securityAttributes [in/out pointer] pointer to Unknown + * @param phkResult [out] pointer to HKEY handle + * @param lpdwDisposition [out] pointer to REG_CREATE_KEY_DISPOSITION enum + * @param hTransaction [in] HANDLE handle + * @param pExtendedParemeter [in/out pointer] opaque pointer + * @returns { status: number, phkResult: , lpdwDisposition: } + */ +export function regCreateKeyTransactedA(hKey, subKey, reserved, class_, options, samDesired, securityAttributes, hTransaction, pExtendedParemeter) { + const _phkResultSlot = Buffer.alloc(8); + const _lpdwDispositionSlot = Buffer.alloc(4); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegCreateKeyTransactedA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(subKey), DynWin32.u32(reserved), DynWin32.pointer(class_), DynWin32.u32((options) >>> 0), DynWin32.u32((samDesired) >>> 0), DynWin32.pointer(securityAttributes), DynWin32.pointer(_phkResultSlot), DynWin32.pointer(_lpdwDispositionSlot), DynWin32.handle(hTransaction), DynWin32.pointer(pExtendedParemeter)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + phkResult: _phkResultSlot.readBigUInt64LE(0), + lpdwDisposition: (_lpdwDispositionSlot.readUInt32LE(0) | 0), + }; +} + +/** + * RegCreateKeyTransactedW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in] LPCWSTR string + * @param reserved [in] U32 + * @param class_ [in] LPCWSTR string + * @param options [in] REG_OPEN_CREATE_OPTIONS enum + * @param samDesired [in] REG_SAM_FLAGS enum + * @param securityAttributes [in/out pointer] pointer to Unknown + * @param phkResult [out] pointer to HKEY handle + * @param lpdwDisposition [out] pointer to REG_CREATE_KEY_DISPOSITION enum + * @param hTransaction [in] HANDLE handle + * @param pExtendedParemeter [in/out pointer] opaque pointer + * @returns { status: number, phkResult: , lpdwDisposition: } + */ +export function regCreateKeyTransactedW(hKey, subKey, reserved, class_, options, samDesired, securityAttributes, hTransaction, pExtendedParemeter) { + const _phkResultSlot = Buffer.alloc(8); + const _lpdwDispositionSlot = Buffer.alloc(4); + const _subKeyBuf = _wideStringBuffer(subKey); + const _class_Buf = _wideStringBuffer(class_); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegCreateKeyTransactedW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_subKeyBuf), DynWin32.u32(reserved), DynWin32.pointer(_class_Buf), DynWin32.u32((options) >>> 0), DynWin32.u32((samDesired) >>> 0), DynWin32.pointer(securityAttributes), DynWin32.pointer(_phkResultSlot), DynWin32.pointer(_lpdwDispositionSlot), DynWin32.handle(hTransaction), DynWin32.pointer(pExtendedParemeter)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + phkResult: _phkResultSlot.readBigUInt64LE(0), + lpdwDisposition: (_lpdwDispositionSlot.readUInt32LE(0) | 0), + }; +} + +/** + * RegCreateKeyW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in] LPCWSTR string + * @param phkResult [out] pointer to HKEY handle + * @returns { status: number, phkResult: } + */ +export function regCreateKeyW(hKey, subKey) { + const _phkResultSlot = Buffer.alloc(8); + const _subKeyBuf = _wideStringBuffer(subKey); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegCreateKeyW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_subKeyBuf), DynWin32.pointer(_phkResultSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + phkResult: _phkResultSlot.readBigUInt64LE(0), + }; +} + +/** + * RegDeleteKeyA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in/out pointer] LPCSTR string + * @returns { status: number } + */ +export function regDeleteKeyA(hKey, subKey) { + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegDeleteKeyA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(subKey)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegDeleteKeyExA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in/out pointer] LPCSTR string + * @param samDesired [in] U32 + * @param reserved [in] U32 + * @returns { status: number } + */ +export function regDeleteKeyExA(hKey, subKey, samDesired, reserved) { + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegDeleteKeyExA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(subKey), DynWin32.u32(samDesired), DynWin32.u32(reserved)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegDeleteKeyExW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in] LPCWSTR string + * @param samDesired [in] U32 + * @param reserved [in] U32 + * @returns { status: number } + */ +export function regDeleteKeyExW(hKey, subKey, samDesired, reserved) { + const _subKeyBuf = _wideStringBuffer(subKey); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegDeleteKeyExW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_subKeyBuf), DynWin32.u32(samDesired), DynWin32.u32(reserved)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegDeleteKeyTransactedA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in/out pointer] LPCSTR string + * @param samDesired [in] U32 + * @param reserved [in] U32 + * @param hTransaction [in] HANDLE handle + * @param pExtendedParameter [in/out pointer] opaque pointer + * @returns { status: number } + */ +export function regDeleteKeyTransactedA(hKey, subKey, samDesired, reserved, hTransaction, pExtendedParameter) { + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegDeleteKeyTransactedA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(subKey), DynWin32.u32(samDesired), DynWin32.u32(reserved), DynWin32.handle(hTransaction), DynWin32.pointer(pExtendedParameter)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegDeleteKeyTransactedW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in] LPCWSTR string + * @param samDesired [in] U32 + * @param reserved [in] U32 + * @param hTransaction [in] HANDLE handle + * @param pExtendedParameter [in/out pointer] opaque pointer + * @returns { status: number } + */ +export function regDeleteKeyTransactedW(hKey, subKey, samDesired, reserved, hTransaction, pExtendedParameter) { + const _subKeyBuf = _wideStringBuffer(subKey); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegDeleteKeyTransactedW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_subKeyBuf), DynWin32.u32(samDesired), DynWin32.u32(reserved), DynWin32.handle(hTransaction), DynWin32.pointer(pExtendedParameter)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegDeleteKeyValueA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in/out pointer] LPCSTR string + * @param valueName [in/out pointer] LPCSTR string + * @returns { status: number } + */ +export function regDeleteKeyValueA(hKey, subKey, valueName) { + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegDeleteKeyValueA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(subKey), DynWin32.pointer(valueName)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegDeleteKeyValueW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in] LPCWSTR string + * @param valueName [in] LPCWSTR string + * @returns { status: number } + */ +export function regDeleteKeyValueW(hKey, subKey, valueName) { + const _subKeyBuf = _wideStringBuffer(subKey); + const _valueNameBuf = _wideStringBuffer(valueName); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegDeleteKeyValueW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_subKeyBuf), DynWin32.pointer(_valueNameBuf)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegDeleteKeyW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in] LPCWSTR string + * @returns { status: number } + */ +export function regDeleteKeyW(hKey, subKey) { + const _subKeyBuf = _wideStringBuffer(subKey); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegDeleteKeyW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_subKeyBuf)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegDeleteTreeA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in/out pointer] LPCSTR string + * @returns { status: number } + */ +export function regDeleteTreeA(hKey, subKey) { + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegDeleteTreeA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(subKey)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegDeleteTreeW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in] LPCWSTR string + * @returns { status: number } + */ +export function regDeleteTreeW(hKey, subKey) { + const _subKeyBuf = _wideStringBuffer(subKey); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegDeleteTreeW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_subKeyBuf)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegDeleteValueA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param valueName [in/out pointer] LPCSTR string + * @returns { status: number } + */ +export function regDeleteValueA(hKey, valueName) { + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegDeleteValueA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(valueName)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegDeleteValueW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param valueName [in] LPCWSTR string + * @returns { status: number } + */ +export function regDeleteValueW(hKey, valueName) { + const _valueNameBuf = _wideStringBuffer(valueName); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegDeleteValueW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_valueNameBuf)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegDisablePredefinedCache — ADVAPI32.dll export. + * + * @returns { status: number } + */ +export function regDisablePredefinedCache() { + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegDisablePredefinedCache', 'U32', [], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegDisablePredefinedCacheEx — ADVAPI32.dll export. + * + * @returns { status: number } + */ +export function regDisablePredefinedCacheEx() { + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegDisablePredefinedCacheEx', 'U32', [], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegDisableReflectionKey — ADVAPI32.dll export. + * + * @param hBase [in] HKEY handle + * @returns { status: number } + */ +export function regDisableReflectionKey(hBase) { + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegDisableReflectionKey', 'U32', [DynWin32.handle(hBase)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegEnableReflectionKey — ADVAPI32.dll export. + * + * @param hBase [in] HKEY handle + * @returns { status: number } + */ +export function regEnableReflectionKey(hBase) { + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegEnableReflectionKey', 'U32', [DynWin32.handle(hBase)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegEnumKeyA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param index [in] U32 + * @param name [in/out pointer] caller-owned ElementCountParam(3) buffer of U8 + * @param cchName [in] U32 + * @returns { status: number } + */ +export function regEnumKeyA(hKey, index, name, cchName) { + const _nameRequiredBytes = Number(cchName) * 1; + if (!Number.isSafeInteger(_nameRequiredBytes) || _nameRequiredBytes < 0) { + throw new RangeError('name size is not a non-negative safe integer'); + } + if (name != null && ArrayBuffer.isView(name) && name.byteLength < _nameRequiredBytes) { + throw new RangeError('name buffer is smaller than the native size contract'); + } + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegEnumKeyA', 'U32', [DynWin32.handle(hKey), DynWin32.u32(index), DynWin32.pointer(name), DynWin32.u32(cchName)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegEnumKeyW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param index [in] U32 + * @param name [in/out pointer] caller-owned ElementCountParam(3) buffer of Char16 + * @param cchName [in] U32 + * @returns { status: number } + */ +export function regEnumKeyW(hKey, index, name, cchName) { + const _nameRequiredBytes = Number(cchName) * 2; + if (!Number.isSafeInteger(_nameRequiredBytes) || _nameRequiredBytes < 0) { + throw new RangeError('name size is not a non-negative safe integer'); + } + if (name != null && ArrayBuffer.isView(name) && name.byteLength < _nameRequiredBytes) { + throw new RangeError('name buffer is smaller than the native size contract'); + } + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegEnumKeyW', 'U32', [DynWin32.handle(hKey), DynWin32.u32(index), DynWin32.pointer(name), DynWin32.u32(cchName)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegEnumValueA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param index [in] U32 + * @param valueName [in/out pointer] caller-owned ElementCountParam(3) buffer of U8 + * @param lpcchValueName [in,out] pointer to U32 + * @param reserved [in/out pointer] pointer to U32 + * @param type [out] pointer to U32 + * @param data [in/out pointer] caller-owned ByteCountParam(7) buffer of U8 + * @param lpcbData [in,out] pointer to U32 + * @returns { status: number, lpcchValueName: , type: , lpcbData: } + */ +export function regEnumValueA(hKey, index, valueName, lpcchValueName, reserved, data, lpcbData) { + const _valueNameRequiredBytes = Number(lpcchValueName) * 1; + if (!Number.isSafeInteger(_valueNameRequiredBytes) || _valueNameRequiredBytes < 0) { + throw new RangeError('valueName size is not a non-negative safe integer'); + } + if (valueName != null && ArrayBuffer.isView(valueName) && valueName.byteLength < _valueNameRequiredBytes) { + throw new RangeError('valueName buffer is smaller than the native size contract'); + } + const _dataRequiredBytes = Number(lpcbData) * 1; + if (!Number.isSafeInteger(_dataRequiredBytes) || _dataRequiredBytes < 0) { + throw new RangeError('data size is not a non-negative safe integer'); + } + if (data != null && ArrayBuffer.isView(data) && data.byteLength < _dataRequiredBytes) { + throw new RangeError('data buffer is smaller than the native size contract'); + } + const _lpcchValueNameSlot = Buffer.alloc(4); + _lpcchValueNameSlot.writeUInt32LE(lpcchValueName, 0); + const _typeSlot = Buffer.alloc(4); + const _lpcbDataSlot = Buffer.alloc(4); + _lpcbDataSlot.writeUInt32LE(lpcbData, 0); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegEnumValueA', 'U32', [DynWin32.handle(hKey), DynWin32.u32(index), DynWin32.pointer(valueName), DynWin32.pointer(_lpcchValueNameSlot), DynWin32.pointer(reserved), DynWin32.pointer(_typeSlot), DynWin32.pointer(data), DynWin32.pointer(_lpcbDataSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + lpcchValueName: _lpcchValueNameSlot.readUInt32LE(0), + type: _typeSlot.readUInt32LE(0), + lpcbData: _lpcbDataSlot.readUInt32LE(0), + }; +} + +/** + * RegEnumValueW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param index [in] U32 + * @param valueName [in/out pointer] caller-owned ElementCountParam(3) buffer of Char16 + * @param lpcchValueName [in,out] pointer to U32 + * @param reserved [in/out pointer] pointer to U32 + * @param type [out] pointer to U32 + * @param data [in/out pointer] caller-owned ByteCountParam(7) buffer of U8 + * @param lpcbData [in,out] pointer to U32 + * @returns { status: number, lpcchValueName: , type: , lpcbData: } + */ +export function regEnumValueW(hKey, index, valueName, lpcchValueName, reserved, data, lpcbData) { + const _valueNameRequiredBytes = Number(lpcchValueName) * 2; + if (!Number.isSafeInteger(_valueNameRequiredBytes) || _valueNameRequiredBytes < 0) { + throw new RangeError('valueName size is not a non-negative safe integer'); + } + if (valueName != null && ArrayBuffer.isView(valueName) && valueName.byteLength < _valueNameRequiredBytes) { + throw new RangeError('valueName buffer is smaller than the native size contract'); + } + const _dataRequiredBytes = Number(lpcbData) * 1; + if (!Number.isSafeInteger(_dataRequiredBytes) || _dataRequiredBytes < 0) { + throw new RangeError('data size is not a non-negative safe integer'); + } + if (data != null && ArrayBuffer.isView(data) && data.byteLength < _dataRequiredBytes) { + throw new RangeError('data buffer is smaller than the native size contract'); + } + const _lpcchValueNameSlot = Buffer.alloc(4); + _lpcchValueNameSlot.writeUInt32LE(lpcchValueName, 0); + const _typeSlot = Buffer.alloc(4); + const _lpcbDataSlot = Buffer.alloc(4); + _lpcbDataSlot.writeUInt32LE(lpcbData, 0); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegEnumValueW', 'U32', [DynWin32.handle(hKey), DynWin32.u32(index), DynWin32.pointer(valueName), DynWin32.pointer(_lpcchValueNameSlot), DynWin32.pointer(reserved), DynWin32.pointer(_typeSlot), DynWin32.pointer(data), DynWin32.pointer(_lpcbDataSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + lpcchValueName: _lpcchValueNameSlot.readUInt32LE(0), + type: _typeSlot.readUInt32LE(0), + lpcbData: _lpcbDataSlot.readUInt32LE(0), + }; +} + +/** + * RegFlushKey — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @returns { status: number } + */ +export function regFlushKey(hKey) { + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegFlushKey', 'U32', [DynWin32.handle(hKey)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegGetKeySecurity — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param securityInformation [in] OBJECT_SECURITY_INFORMATION enum + * @param pSecurityDescriptor [in/out pointer] caller-owned ByteCountParam(3) buffer of U8 + * @param lpcbSecurityDescriptor [in,out] pointer to U32 + * @returns { status: number, lpcbSecurityDescriptor: } + */ +export function regGetKeySecurity(hKey, securityInformation, pSecurityDescriptor, lpcbSecurityDescriptor) { + const _pSecurityDescriptorRequiredBytes = Number(lpcbSecurityDescriptor) * 1; + if (!Number.isSafeInteger(_pSecurityDescriptorRequiredBytes) || _pSecurityDescriptorRequiredBytes < 0) { + throw new RangeError('pSecurityDescriptor size is not a non-negative safe integer'); + } + if (pSecurityDescriptor != null && ArrayBuffer.isView(pSecurityDescriptor) && pSecurityDescriptor.byteLength < _pSecurityDescriptorRequiredBytes) { + throw new RangeError('pSecurityDescriptor buffer is smaller than the native size contract'); + } + const _lpcbSecurityDescriptorSlot = Buffer.alloc(4); + _lpcbSecurityDescriptorSlot.writeUInt32LE(lpcbSecurityDescriptor, 0); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegGetKeySecurity', 'U32', [DynWin32.handle(hKey), DynWin32.u32((securityInformation) >>> 0), DynWin32.pointer(pSecurityDescriptor), DynWin32.pointer(_lpcbSecurityDescriptorSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + lpcbSecurityDescriptor: _lpcbSecurityDescriptorSlot.readUInt32LE(0), + }; +} + +/** + * RegGetValueA — ADVAPI32.dll export. + * + * @param hkey [in] HKEY handle + * @param subKey [in/out pointer] LPCSTR string + * @param value [in/out pointer] LPCSTR string + * @param flags [in] REG_ROUTINE_FLAGS enum + * @param pdwType [out] pointer to REG_VALUE_TYPE enum + * @param data [in/out pointer] caller-owned ByteCountParam(6) buffer of U8 + * @param pcbData [in,out] pointer to U32 + * @returns { status: number, pdwType: , pcbData: } + */ +export function regGetValueA(hkey, subKey, value, flags, data, pcbData) { + const _dataRequiredBytes = Number(pcbData) * 1; + if (!Number.isSafeInteger(_dataRequiredBytes) || _dataRequiredBytes < 0) { + throw new RangeError('data size is not a non-negative safe integer'); + } + if (data != null && ArrayBuffer.isView(data) && data.byteLength < _dataRequiredBytes) { + throw new RangeError('data buffer is smaller than the native size contract'); + } + const _pdwTypeSlot = Buffer.alloc(4); + const _pcbDataSlot = Buffer.alloc(4); + _pcbDataSlot.writeUInt32LE(pcbData, 0); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegGetValueA', 'U32', [DynWin32.handle(hkey), DynWin32.pointer(subKey), DynWin32.pointer(value), DynWin32.u32((flags) >>> 0), DynWin32.pointer(_pdwTypeSlot), DynWin32.pointer(data), DynWin32.pointer(_pcbDataSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + pdwType: (_pdwTypeSlot.readUInt32LE(0) | 0), + pcbData: _pcbDataSlot.readUInt32LE(0), + }; +} + +/** + * RegGetValueW — ADVAPI32.dll export. + * + * @param hkey [in] HKEY handle + * @param subKey [in] LPCWSTR string + * @param value [in] LPCWSTR string + * @param flags [in] REG_ROUTINE_FLAGS enum + * @param pdwType [out] pointer to REG_VALUE_TYPE enum + * @param data [in/out pointer] caller-owned ByteCountParam(6) buffer of U8 + * @param pcbData [in,out] pointer to U32 + * @returns { status: number, pdwType: , pcbData: } + */ +export function regGetValueW(hkey, subKey, value, flags, data, pcbData) { + const _dataRequiredBytes = Number(pcbData) * 1; + if (!Number.isSafeInteger(_dataRequiredBytes) || _dataRequiredBytes < 0) { + throw new RangeError('data size is not a non-negative safe integer'); + } + if (data != null && ArrayBuffer.isView(data) && data.byteLength < _dataRequiredBytes) { + throw new RangeError('data buffer is smaller than the native size contract'); + } + const _pdwTypeSlot = Buffer.alloc(4); + const _pcbDataSlot = Buffer.alloc(4); + _pcbDataSlot.writeUInt32LE(pcbData, 0); + const _subKeyBuf = _wideStringBuffer(subKey); + const _valueBuf = _wideStringBuffer(value); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegGetValueW', 'U32', [DynWin32.handle(hkey), DynWin32.pointer(_subKeyBuf), DynWin32.pointer(_valueBuf), DynWin32.u32((flags) >>> 0), DynWin32.pointer(_pdwTypeSlot), DynWin32.pointer(data), DynWin32.pointer(_pcbDataSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + pdwType: (_pdwTypeSlot.readUInt32LE(0) | 0), + pcbData: _pcbDataSlot.readUInt32LE(0), + }; +} + +/** + * RegLoadAppKeyA — ADVAPI32.dll export. + * + * @param file [in/out pointer] LPCSTR string + * @param phkResult [out] pointer to HKEY handle + * @param samDesired [in] U32 + * @param options [in] U32 + * @param reserved [in] U32 + * @returns { status: number, phkResult: } + */ +export function regLoadAppKeyA(file, samDesired, options, reserved) { + const _phkResultSlot = Buffer.alloc(8); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegLoadAppKeyA', 'U32', [DynWin32.pointer(file), DynWin32.pointer(_phkResultSlot), DynWin32.u32(samDesired), DynWin32.u32(options), DynWin32.u32(reserved)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + phkResult: _phkResultSlot.readBigUInt64LE(0), + }; +} + +/** + * RegLoadAppKeyW — ADVAPI32.dll export. + * + * @param file [in] LPCWSTR string + * @param phkResult [out] pointer to HKEY handle + * @param samDesired [in] U32 + * @param options [in] U32 + * @param reserved [in] U32 + * @returns { status: number, phkResult: } + */ +export function regLoadAppKeyW(file, samDesired, options, reserved) { + const _phkResultSlot = Buffer.alloc(8); + const _fileBuf = _wideStringBuffer(file); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegLoadAppKeyW', 'U32', [DynWin32.pointer(_fileBuf), DynWin32.pointer(_phkResultSlot), DynWin32.u32(samDesired), DynWin32.u32(options), DynWin32.u32(reserved)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + phkResult: _phkResultSlot.readBigUInt64LE(0), + }; +} + +/** + * RegLoadKeyA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in/out pointer] LPCSTR string + * @param file [in/out pointer] LPCSTR string + * @returns { status: number } + */ +export function regLoadKeyA(hKey, subKey, file) { + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegLoadKeyA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(subKey), DynWin32.pointer(file)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegLoadKeyW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in] LPCWSTR string + * @param file [in] LPCWSTR string + * @returns { status: number } + */ +export function regLoadKeyW(hKey, subKey, file) { + const _subKeyBuf = _wideStringBuffer(subKey); + const _fileBuf = _wideStringBuffer(file); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegLoadKeyW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_subKeyBuf), DynWin32.pointer(_fileBuf)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegLoadMUIStringA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param value [in/out pointer] LPCSTR string + * @param outBuf [in/out pointer] caller-owned ByteCountParam(3) buffer of U8 + * @param outBuf_2 [in] U32 + * @param pcbData [out] pointer to U32 + * @param flags [in] U32 + * @param directory [in/out pointer] LPCSTR string + * @returns { status: number, pcbData: } + */ +export function regLoadMUIStringA(hKey, value, outBuf, outBuf_2, flags, directory) { + const _outBufRequiredBytes = Number(outBuf_2) * 1; + if (!Number.isSafeInteger(_outBufRequiredBytes) || _outBufRequiredBytes < 0) { + throw new RangeError('outBuf size is not a non-negative safe integer'); + } + if (outBuf != null && ArrayBuffer.isView(outBuf) && outBuf.byteLength < _outBufRequiredBytes) { + throw new RangeError('outBuf buffer is smaller than the native size contract'); + } + const _pcbDataSlot = Buffer.alloc(4); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegLoadMUIStringA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(value), DynWin32.pointer(outBuf), DynWin32.u32(outBuf_2), DynWin32.pointer(_pcbDataSlot), DynWin32.u32(flags), DynWin32.pointer(directory)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + pcbData: _pcbDataSlot.readUInt32LE(0), + }; +} + +/** + * RegLoadMUIStringW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param value [in] LPCWSTR string + * @param outBuf [in/out pointer] caller-owned ByteCountParam(3) buffer of Char16 + * @param outBuf_2 [in] U32 + * @param pcbData [out] pointer to U32 + * @param flags [in] U32 + * @param directory [in] LPCWSTR string + * @returns { status: number, pcbData: } + */ +export function regLoadMUIStringW(hKey, value, outBuf, outBuf_2, flags, directory) { + const _outBufRequiredBytes = Number(outBuf_2) * 1; + if (!Number.isSafeInteger(_outBufRequiredBytes) || _outBufRequiredBytes < 0) { + throw new RangeError('outBuf size is not a non-negative safe integer'); + } + if (outBuf != null && ArrayBuffer.isView(outBuf) && outBuf.byteLength < _outBufRequiredBytes) { + throw new RangeError('outBuf buffer is smaller than the native size contract'); + } + const _pcbDataSlot = Buffer.alloc(4); + const _valueBuf = _wideStringBuffer(value); + const _directoryBuf = _wideStringBuffer(directory); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegLoadMUIStringW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_valueBuf), DynWin32.pointer(outBuf), DynWin32.u32(outBuf_2), DynWin32.pointer(_pcbDataSlot), DynWin32.u32(flags), DynWin32.pointer(_directoryBuf)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + pcbData: _pcbDataSlot.readUInt32LE(0), + }; +} + +/** + * RegNotifyChangeKeyValue — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param bWatchSubtree [in] Bool32 + * @param notifyFilter [in] REG_NOTIFY_FILTER enum + * @param hEvent [in] HANDLE handle + * @param fAsynchronous [in] Bool32 + * @returns { status: number } + */ +export function regNotifyChangeKeyValue(hKey, bWatchSubtree, notifyFilter, hEvent, fAsynchronous) { + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegNotifyChangeKeyValue', 'U32', [DynWin32.handle(hKey), DynWin32.i32(bWatchSubtree ? 1 : 0), DynWin32.u32((notifyFilter) >>> 0), DynWin32.handle(hEvent), DynWin32.i32(fAsynchronous ? 1 : 0)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegOpenCurrentUser — ADVAPI32.dll export. + * + * @param samDesired [in] U32 + * @param phkResult [out] pointer to HKEY handle + * @returns { status: number, phkResult: } + */ +export function regOpenCurrentUser(samDesired) { + const _phkResultSlot = Buffer.alloc(8); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegOpenCurrentUser', 'U32', [DynWin32.u32(samDesired), DynWin32.pointer(_phkResultSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + phkResult: _phkResultSlot.readBigUInt64LE(0), + }; +} + +/** + * RegOpenKeyA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in/out pointer] LPCSTR string + * @param phkResult [out] pointer to HKEY handle + * @returns { status: number, phkResult: } + */ +export function regOpenKeyA(hKey, subKey) { + const _phkResultSlot = Buffer.alloc(8); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegOpenKeyA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(subKey), DynWin32.pointer(_phkResultSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + phkResult: _phkResultSlot.readBigUInt64LE(0), + }; +} + +/** + * RegOpenKeyExA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in/out pointer] LPCSTR string + * @param ulOptions [in] U32 + * @param samDesired [in] REG_SAM_FLAGS enum + * @param phkResult [out] pointer to HKEY handle + * @returns { status: number, phkResult: } + */ +export function regOpenKeyExA(hKey, subKey, ulOptions, samDesired) { + const _phkResultSlot = Buffer.alloc(8); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegOpenKeyExA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(subKey), DynWin32.u32(ulOptions), DynWin32.u32((samDesired) >>> 0), DynWin32.pointer(_phkResultSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + phkResult: _phkResultSlot.readBigUInt64LE(0), + }; +} + +/** + * RegOpenKeyExW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in] LPCWSTR string + * @param ulOptions [in] U32 + * @param samDesired [in] REG_SAM_FLAGS enum + * @param phkResult [out] pointer to HKEY handle + * @returns { status: number, phkResult: } + */ +export function regOpenKeyExW(hKey, subKey, ulOptions, samDesired) { + const _phkResultSlot = Buffer.alloc(8); + const _subKeyBuf = _wideStringBuffer(subKey); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegOpenKeyExW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_subKeyBuf), DynWin32.u32(ulOptions), DynWin32.u32((samDesired) >>> 0), DynWin32.pointer(_phkResultSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + phkResult: _phkResultSlot.readBigUInt64LE(0), + }; +} + +/** + * RegOpenKeyTransactedA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in/out pointer] LPCSTR string + * @param ulOptions [in] U32 + * @param samDesired [in] REG_SAM_FLAGS enum + * @param phkResult [out] pointer to HKEY handle + * @param hTransaction [in] HANDLE handle + * @param pExtendedParemeter [in/out pointer] opaque pointer + * @returns { status: number, phkResult: } + */ +export function regOpenKeyTransactedA(hKey, subKey, ulOptions, samDesired, hTransaction, pExtendedParemeter) { + const _phkResultSlot = Buffer.alloc(8); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegOpenKeyTransactedA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(subKey), DynWin32.u32(ulOptions), DynWin32.u32((samDesired) >>> 0), DynWin32.pointer(_phkResultSlot), DynWin32.handle(hTransaction), DynWin32.pointer(pExtendedParemeter)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + phkResult: _phkResultSlot.readBigUInt64LE(0), + }; +} + +/** + * RegOpenKeyTransactedW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in] LPCWSTR string + * @param ulOptions [in] U32 + * @param samDesired [in] REG_SAM_FLAGS enum + * @param phkResult [out] pointer to HKEY handle + * @param hTransaction [in] HANDLE handle + * @param pExtendedParemeter [in/out pointer] opaque pointer + * @returns { status: number, phkResult: } + */ +export function regOpenKeyTransactedW(hKey, subKey, ulOptions, samDesired, hTransaction, pExtendedParemeter) { + const _phkResultSlot = Buffer.alloc(8); + const _subKeyBuf = _wideStringBuffer(subKey); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegOpenKeyTransactedW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_subKeyBuf), DynWin32.u32(ulOptions), DynWin32.u32((samDesired) >>> 0), DynWin32.pointer(_phkResultSlot), DynWin32.handle(hTransaction), DynWin32.pointer(pExtendedParemeter)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + phkResult: _phkResultSlot.readBigUInt64LE(0), + }; +} + +/** + * RegOpenKeyW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in] LPCWSTR string + * @param phkResult [out] pointer to HKEY handle + * @returns { status: number, phkResult: } + */ +export function regOpenKeyW(hKey, subKey) { + const _phkResultSlot = Buffer.alloc(8); + const _subKeyBuf = _wideStringBuffer(subKey); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegOpenKeyW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_subKeyBuf), DynWin32.pointer(_phkResultSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + phkResult: _phkResultSlot.readBigUInt64LE(0), + }; +} + +/** + * RegOpenUserClassesRoot — ADVAPI32.dll export. + * + * @param hToken [in] HANDLE handle + * @param options [in] U32 + * @param samDesired [in] U32 + * @param phkResult [out] pointer to HKEY handle + * @returns { status: number, phkResult: } + */ +export function regOpenUserClassesRoot(hToken, options, samDesired) { + const _phkResultSlot = Buffer.alloc(8); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegOpenUserClassesRoot', 'U32', [DynWin32.handle(hToken), DynWin32.u32(options), DynWin32.u32(samDesired), DynWin32.pointer(_phkResultSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + phkResult: _phkResultSlot.readBigUInt64LE(0), + }; +} + +/** + * RegOverridePredefKey — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param hNewHKey [in] HKEY handle + * @returns { status: number } + */ +export function regOverridePredefKey(hKey, hNewHKey) { + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegOverridePredefKey', 'U32', [DynWin32.handle(hKey), DynWin32.handle(hNewHKey)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegQueryReflectionKey — ADVAPI32.dll export. + * + * @param hBase [in] HKEY handle + * @param bIsReflectionDisabled [out] pointer to Bool32 + * @returns { status: number, bIsReflectionDisabled: } + */ +export function regQueryReflectionKey(hBase) { + const _bIsReflectionDisabledSlot = Buffer.alloc(4); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegQueryReflectionKey', 'U32', [DynWin32.handle(hBase), DynWin32.pointer(_bIsReflectionDisabledSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + bIsReflectionDisabled: (_bIsReflectionDisabledSlot.readInt32LE(0) !== 0), + }; +} + +/** + * RegQueryValueA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in/out pointer] LPCSTR string + * @param data [in/out pointer] caller-owned ByteCountParam(3) buffer of U8 + * @param lpcbData [in,out] pointer to I32 + * @returns { status: number, lpcbData: } + */ +export function regQueryValueA(hKey, subKey, data, lpcbData) { + const _dataRequiredBytes = Number(lpcbData) * 1; + if (!Number.isSafeInteger(_dataRequiredBytes) || _dataRequiredBytes < 0) { + throw new RangeError('data size is not a non-negative safe integer'); + } + if (data != null && ArrayBuffer.isView(data) && data.byteLength < _dataRequiredBytes) { + throw new RangeError('data buffer is smaller than the native size contract'); + } + const _lpcbDataSlot = Buffer.alloc(4); + _lpcbDataSlot.writeInt32LE(lpcbData, 0); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegQueryValueA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(subKey), DynWin32.pointer(data), DynWin32.pointer(_lpcbDataSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + lpcbData: _lpcbDataSlot.readInt32LE(0), + }; +} + +/** + * RegQueryValueExA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param valueName [in/out pointer] LPCSTR string + * @param reserved [in/out pointer] pointer to U32 + * @param type [out] pointer to REG_VALUE_TYPE enum + * @param data [in/out pointer] caller-owned ByteCountParam(5) buffer of U8 + * @param lpcbData [in,out] pointer to U32 + * @returns { status: number, type: , lpcbData: } + */ +export function regQueryValueExA(hKey, valueName, reserved, data, lpcbData) { + const _dataRequiredBytes = Number(lpcbData) * 1; + if (!Number.isSafeInteger(_dataRequiredBytes) || _dataRequiredBytes < 0) { + throw new RangeError('data size is not a non-negative safe integer'); + } + if (data != null && ArrayBuffer.isView(data) && data.byteLength < _dataRequiredBytes) { + throw new RangeError('data buffer is smaller than the native size contract'); + } + const _typeSlot = Buffer.alloc(4); + const _lpcbDataSlot = Buffer.alloc(4); + _lpcbDataSlot.writeUInt32LE(lpcbData, 0); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegQueryValueExA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(valueName), DynWin32.pointer(reserved), DynWin32.pointer(_typeSlot), DynWin32.pointer(data), DynWin32.pointer(_lpcbDataSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + type: (_typeSlot.readUInt32LE(0) | 0), + lpcbData: _lpcbDataSlot.readUInt32LE(0), + }; +} + +/** + * RegQueryValueExW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param valueName [in] LPCWSTR string + * @param reserved [in/out pointer] pointer to U32 + * @param type [out] pointer to REG_VALUE_TYPE enum + * @param data [in/out pointer] caller-owned ByteCountParam(5) buffer of U8 + * @param lpcbData [in,out] pointer to U32 + * @returns { status: number, type: , lpcbData: } + */ +export function regQueryValueExW(hKey, valueName, reserved, data, lpcbData) { + const _dataRequiredBytes = Number(lpcbData) * 1; + if (!Number.isSafeInteger(_dataRequiredBytes) || _dataRequiredBytes < 0) { + throw new RangeError('data size is not a non-negative safe integer'); + } + if (data != null && ArrayBuffer.isView(data) && data.byteLength < _dataRequiredBytes) { + throw new RangeError('data buffer is smaller than the native size contract'); + } + const _typeSlot = Buffer.alloc(4); + const _lpcbDataSlot = Buffer.alloc(4); + _lpcbDataSlot.writeUInt32LE(lpcbData, 0); + const _valueNameBuf = _wideStringBuffer(valueName); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegQueryValueExW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_valueNameBuf), DynWin32.pointer(reserved), DynWin32.pointer(_typeSlot), DynWin32.pointer(data), DynWin32.pointer(_lpcbDataSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + type: (_typeSlot.readUInt32LE(0) | 0), + lpcbData: _lpcbDataSlot.readUInt32LE(0), + }; +} + +/** + * RegQueryValueW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in] LPCWSTR string + * @param data [in/out pointer] caller-owned ByteCountParam(3) buffer of Char16 + * @param lpcbData [in,out] pointer to I32 + * @returns { status: number, lpcbData: } + */ +export function regQueryValueW(hKey, subKey, data, lpcbData) { + const _dataRequiredBytes = Number(lpcbData) * 1; + if (!Number.isSafeInteger(_dataRequiredBytes) || _dataRequiredBytes < 0) { + throw new RangeError('data size is not a non-negative safe integer'); + } + if (data != null && ArrayBuffer.isView(data) && data.byteLength < _dataRequiredBytes) { + throw new RangeError('data buffer is smaller than the native size contract'); + } + const _lpcbDataSlot = Buffer.alloc(4); + _lpcbDataSlot.writeInt32LE(lpcbData, 0); + const _subKeyBuf = _wideStringBuffer(subKey); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegQueryValueW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_subKeyBuf), DynWin32.pointer(data), DynWin32.pointer(_lpcbDataSlot)], false); + const _ret = _call.value; + return { + status: (DynWin32.toNumber(_ret) | 0), + lpcbData: _lpcbDataSlot.readInt32LE(0), + }; +} + +/** + * RegRenameKey — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKeyName [in] LPCWSTR string + * @param newKeyName [in] LPCWSTR string + * @returns { status: number } + */ +export function regRenameKey(hKey, subKeyName, newKeyName) { + const _subKeyNameBuf = _wideStringBuffer(subKeyName); + const _newKeyNameBuf = _wideStringBuffer(newKeyName); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegRenameKey', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_subKeyNameBuf), DynWin32.pointer(_newKeyNameBuf)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegReplaceKeyA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in/out pointer] LPCSTR string + * @param newFile [in/out pointer] LPCSTR string + * @param oldFile [in/out pointer] LPCSTR string + * @returns { status: number } + */ +export function regReplaceKeyA(hKey, subKey, newFile, oldFile) { + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegReplaceKeyA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(subKey), DynWin32.pointer(newFile), DynWin32.pointer(oldFile)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegReplaceKeyW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in] LPCWSTR string + * @param newFile [in] LPCWSTR string + * @param oldFile [in] LPCWSTR string + * @returns { status: number } + */ +export function regReplaceKeyW(hKey, subKey, newFile, oldFile) { + const _subKeyBuf = _wideStringBuffer(subKey); + const _newFileBuf = _wideStringBuffer(newFile); + const _oldFileBuf = _wideStringBuffer(oldFile); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegReplaceKeyW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_subKeyBuf), DynWin32.pointer(_newFileBuf), DynWin32.pointer(_oldFileBuf)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegRestoreKeyA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param file [in/out pointer] LPCSTR string + * @param flags [in] U32 + * @returns { status: number } + */ +export function regRestoreKeyA(hKey, file, flags) { + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegRestoreKeyA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(file), DynWin32.u32(flags)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegRestoreKeyW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param file [in] LPCWSTR string + * @param flags [in] U32 + * @returns { status: number } + */ +export function regRestoreKeyW(hKey, file, flags) { + const _fileBuf = _wideStringBuffer(file); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegRestoreKeyW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_fileBuf), DynWin32.u32(flags)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegSaveKeyA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param file [in/out pointer] LPCSTR string + * @param securityAttributes [in/out pointer] pointer to Unknown + * @returns { status: number } + */ +export function regSaveKeyA(hKey, file, securityAttributes) { + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegSaveKeyA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(file), DynWin32.pointer(securityAttributes)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegSaveKeyExA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param file [in/out pointer] LPCSTR string + * @param securityAttributes [in/out pointer] pointer to Unknown + * @param flags [in] REG_SAVE_FORMAT enum + * @returns { status: number } + */ +export function regSaveKeyExA(hKey, file, securityAttributes, flags) { + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegSaveKeyExA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(file), DynWin32.pointer(securityAttributes), DynWin32.u32((flags) >>> 0)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegSaveKeyExW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param file [in] LPCWSTR string + * @param securityAttributes [in/out pointer] pointer to Unknown + * @param flags [in] REG_SAVE_FORMAT enum + * @returns { status: number } + */ +export function regSaveKeyExW(hKey, file, securityAttributes, flags) { + const _fileBuf = _wideStringBuffer(file); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegSaveKeyExW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_fileBuf), DynWin32.pointer(securityAttributes), DynWin32.u32((flags) >>> 0)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegSaveKeyW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param file [in] LPCWSTR string + * @param securityAttributes [in/out pointer] pointer to Unknown + * @returns { status: number } + */ +export function regSaveKeyW(hKey, file, securityAttributes) { + const _fileBuf = _wideStringBuffer(file); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegSaveKeyW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_fileBuf), DynWin32.pointer(securityAttributes)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegSetKeySecurity — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param securityInformation [in] OBJECT_SECURITY_INFORMATION enum + * @param pSecurityDescriptor [in/out pointer] opaque pointer + * @returns { status: number } + */ +export function regSetKeySecurity(hKey, securityInformation, pSecurityDescriptor) { + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegSetKeySecurity', 'U32', [DynWin32.handle(hKey), DynWin32.u32((securityInformation) >>> 0), DynWin32.pointer(pSecurityDescriptor)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegSetKeyValueA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in/out pointer] LPCSTR string + * @param valueName [in/out pointer] LPCSTR string + * @param type [in] U32 + * @param data [in/out pointer] caller-owned ByteCountParam(5) buffer of U8 + * @param data_2 [in] U32 + * @returns { status: number } + */ +export function regSetKeyValueA(hKey, subKey, valueName, type, data, data_2) { + const _dataRequiredBytes = Number(data_2) * 1; + if (!Number.isSafeInteger(_dataRequiredBytes) || _dataRequiredBytes < 0) { + throw new RangeError('data size is not a non-negative safe integer'); + } + if (data != null && ArrayBuffer.isView(data) && data.byteLength < _dataRequiredBytes) { + throw new RangeError('data buffer is smaller than the native size contract'); + } + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegSetKeyValueA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(subKey), DynWin32.pointer(valueName), DynWin32.u32(type), DynWin32.pointer(data), DynWin32.u32(data_2)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegSetKeyValueW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in] LPCWSTR string + * @param valueName [in] LPCWSTR string + * @param type [in] U32 + * @param data [in/out pointer] caller-owned ByteCountParam(5) buffer of U8 + * @param data_2 [in] U32 + * @returns { status: number } + */ +export function regSetKeyValueW(hKey, subKey, valueName, type, data, data_2) { + const _dataRequiredBytes = Number(data_2) * 1; + if (!Number.isSafeInteger(_dataRequiredBytes) || _dataRequiredBytes < 0) { + throw new RangeError('data size is not a non-negative safe integer'); + } + if (data != null && ArrayBuffer.isView(data) && data.byteLength < _dataRequiredBytes) { + throw new RangeError('data buffer is smaller than the native size contract'); + } + const _subKeyBuf = _wideStringBuffer(subKey); + const _valueNameBuf = _wideStringBuffer(valueName); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegSetKeyValueW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_subKeyBuf), DynWin32.pointer(_valueNameBuf), DynWin32.u32(type), DynWin32.pointer(data), DynWin32.u32(data_2)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegSetValueA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in/out pointer] LPCSTR string + * @param type [in] REG_VALUE_TYPE enum + * @param data [in/out pointer] caller-owned ByteCountParam(4) buffer of U8 + * @param data_2 [in] U32 + * @returns { status: number } + */ +export function regSetValueA(hKey, subKey, type, data, data_2) { + const _dataRequiredBytes = Number(data_2) * 1; + if (!Number.isSafeInteger(_dataRequiredBytes) || _dataRequiredBytes < 0) { + throw new RangeError('data size is not a non-negative safe integer'); + } + if (data != null && ArrayBuffer.isView(data) && data.byteLength < _dataRequiredBytes) { + throw new RangeError('data buffer is smaller than the native size contract'); + } + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegSetValueA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(subKey), DynWin32.u32((type) >>> 0), DynWin32.pointer(data), DynWin32.u32(data_2)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegSetValueExA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param valueName [in/out pointer] LPCSTR string + * @param reserved [in] U32 + * @param type [in] REG_VALUE_TYPE enum + * @param data [in/out pointer] caller-owned ByteCountParam(5) buffer of U8 + * @param data_2 [in] U32 + * @returns { status: number } + */ +export function regSetValueExA(hKey, valueName, reserved, type, data, data_2) { + const _dataRequiredBytes = Number(data_2) * 1; + if (!Number.isSafeInteger(_dataRequiredBytes) || _dataRequiredBytes < 0) { + throw new RangeError('data size is not a non-negative safe integer'); + } + if (data != null && ArrayBuffer.isView(data) && data.byteLength < _dataRequiredBytes) { + throw new RangeError('data buffer is smaller than the native size contract'); + } + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegSetValueExA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(valueName), DynWin32.u32(reserved), DynWin32.u32((type) >>> 0), DynWin32.pointer(data), DynWin32.u32(data_2)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegSetValueExW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param valueName [in] LPCWSTR string + * @param reserved [in] U32 + * @param type [in] REG_VALUE_TYPE enum + * @param data [in/out pointer] caller-owned ByteCountParam(5) buffer of U8 + * @param data_2 [in] U32 + * @returns { status: number } + */ +export function regSetValueExW(hKey, valueName, reserved, type, data, data_2) { + const _dataRequiredBytes = Number(data_2) * 1; + if (!Number.isSafeInteger(_dataRequiredBytes) || _dataRequiredBytes < 0) { + throw new RangeError('data size is not a non-negative safe integer'); + } + if (data != null && ArrayBuffer.isView(data) && data.byteLength < _dataRequiredBytes) { + throw new RangeError('data buffer is smaller than the native size contract'); + } + const _valueNameBuf = _wideStringBuffer(valueName); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegSetValueExW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_valueNameBuf), DynWin32.u32(reserved), DynWin32.u32((type) >>> 0), DynWin32.pointer(data), DynWin32.u32(data_2)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegSetValueW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in] LPCWSTR string + * @param type [in] REG_VALUE_TYPE enum + * @param data [in/out pointer] caller-owned ByteCountParam(4) buffer of Char16 + * @param data_2 [in] U32 + * @returns { status: number } + */ +export function regSetValueW(hKey, subKey, type, data, data_2) { + const _dataRequiredBytes = Number(data_2) * 1; + if (!Number.isSafeInteger(_dataRequiredBytes) || _dataRequiredBytes < 0) { + throw new RangeError('data size is not a non-negative safe integer'); + } + if (data != null && ArrayBuffer.isView(data) && data.byteLength < _dataRequiredBytes) { + throw new RangeError('data buffer is smaller than the native size contract'); + } + const _subKeyBuf = _wideStringBuffer(subKey); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegSetValueW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_subKeyBuf), DynWin32.u32((type) >>> 0), DynWin32.pointer(data), DynWin32.u32(data_2)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegUnLoadKeyA — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in/out pointer] LPCSTR string + * @returns { status: number } + */ +export function regUnLoadKeyA(hKey, subKey) { + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegUnLoadKeyA', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(subKey)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +/** + * RegUnLoadKeyW — ADVAPI32.dll export. + * + * @param hKey [in] HKEY handle + * @param subKey [in] LPCWSTR string + * @returns { status: number } + */ +export function regUnLoadKeyW(hKey, subKey) { + const _subKeyBuf = _wideStringBuffer(subKey); + const _call = DynWin32.invoke('ADVAPI32.dll', 'RegUnLoadKeyW', 'U32', [DynWin32.handle(hKey), DynWin32.pointer(_subKeyBuf)], false); + const _ret = _call.value; + return { status: (DynWin32.toNumber(_ret) | 0) }; +} + +export const Apis = Object.freeze({ + getRegistryValueWithFallbackW, + regCloseKey, + regConnectRegistryA, + regConnectRegistryExA, + regConnectRegistryExW, + regConnectRegistryW, + regCopyTreeA, + regCopyTreeW, + regCreateKeyA, + regCreateKeyExA, + regCreateKeyExW, + regCreateKeyTransactedA, + regCreateKeyTransactedW, + regCreateKeyW, + regDeleteKeyA, + regDeleteKeyExA, + regDeleteKeyExW, + regDeleteKeyTransactedA, + regDeleteKeyTransactedW, + regDeleteKeyValueA, + regDeleteKeyValueW, + regDeleteKeyW, + regDeleteTreeA, + regDeleteTreeW, + regDeleteValueA, + regDeleteValueW, + regDisablePredefinedCache, + regDisablePredefinedCacheEx, + regDisableReflectionKey, + regEnableReflectionKey, + regEnumKeyA, + regEnumKeyW, + regEnumValueA, + regEnumValueW, + regFlushKey, + regGetKeySecurity, + regGetValueA, + regGetValueW, + regLoadAppKeyA, + regLoadAppKeyW, + regLoadKeyA, + regLoadKeyW, + regLoadMUIStringA, + regLoadMUIStringW, + regNotifyChangeKeyValue, + regOpenCurrentUser, + regOpenKeyA, + regOpenKeyExA, + regOpenKeyExW, + regOpenKeyTransactedA, + regOpenKeyTransactedW, + regOpenKeyW, + regOpenUserClassesRoot, + regOverridePredefKey, + regQueryReflectionKey, + regQueryValueA, + regQueryValueExA, + regQueryValueExW, + regQueryValueW, + regRenameKey, + regReplaceKeyA, + regReplaceKeyW, + regRestoreKeyA, + regRestoreKeyW, + regSaveKeyA, + regSaveKeyExA, + regSaveKeyExW, + regSaveKeyW, + regSetKeySecurity, + regSetKeyValueA, + regSetKeyValueW, + regSetValueA, + regSetValueExA, + regSetValueExW, + regSetValueW, + regUnLoadKeyA, + regUnLoadKeyW, +}); + +// Raw metadata for each export (dll, entry point). +export const FLAT_EXPORTS = Object.freeze({ + getRegistryValueWithFallbackW: { dll: 'api-ms-win-core-state-helpers-l1-1-0.dll', entry: 'GetRegistryValueWithFallbackW' }, + regCloseKey: { dll: 'ADVAPI32.dll', entry: 'RegCloseKey' }, + regConnectRegistryA: { dll: 'ADVAPI32.dll', entry: 'RegConnectRegistryA' }, + regConnectRegistryExA: { dll: 'ADVAPI32.dll', entry: 'RegConnectRegistryExA' }, + regConnectRegistryExW: { dll: 'ADVAPI32.dll', entry: 'RegConnectRegistryExW' }, + regConnectRegistryW: { dll: 'ADVAPI32.dll', entry: 'RegConnectRegistryW' }, + regCopyTreeA: { dll: 'ADVAPI32.dll', entry: 'RegCopyTreeA' }, + regCopyTreeW: { dll: 'ADVAPI32.dll', entry: 'RegCopyTreeW' }, + regCreateKeyA: { dll: 'ADVAPI32.dll', entry: 'RegCreateKeyA' }, + regCreateKeyExA: { dll: 'ADVAPI32.dll', entry: 'RegCreateKeyExA' }, + regCreateKeyExW: { dll: 'ADVAPI32.dll', entry: 'RegCreateKeyExW' }, + regCreateKeyTransactedA: { dll: 'ADVAPI32.dll', entry: 'RegCreateKeyTransactedA' }, + regCreateKeyTransactedW: { dll: 'ADVAPI32.dll', entry: 'RegCreateKeyTransactedW' }, + regCreateKeyW: { dll: 'ADVAPI32.dll', entry: 'RegCreateKeyW' }, + regDeleteKeyA: { dll: 'ADVAPI32.dll', entry: 'RegDeleteKeyA' }, + regDeleteKeyExA: { dll: 'ADVAPI32.dll', entry: 'RegDeleteKeyExA' }, + regDeleteKeyExW: { dll: 'ADVAPI32.dll', entry: 'RegDeleteKeyExW' }, + regDeleteKeyTransactedA: { dll: 'ADVAPI32.dll', entry: 'RegDeleteKeyTransactedA' }, + regDeleteKeyTransactedW: { dll: 'ADVAPI32.dll', entry: 'RegDeleteKeyTransactedW' }, + regDeleteKeyValueA: { dll: 'ADVAPI32.dll', entry: 'RegDeleteKeyValueA' }, + regDeleteKeyValueW: { dll: 'ADVAPI32.dll', entry: 'RegDeleteKeyValueW' }, + regDeleteKeyW: { dll: 'ADVAPI32.dll', entry: 'RegDeleteKeyW' }, + regDeleteTreeA: { dll: 'ADVAPI32.dll', entry: 'RegDeleteTreeA' }, + regDeleteTreeW: { dll: 'ADVAPI32.dll', entry: 'RegDeleteTreeW' }, + regDeleteValueA: { dll: 'ADVAPI32.dll', entry: 'RegDeleteValueA' }, + regDeleteValueW: { dll: 'ADVAPI32.dll', entry: 'RegDeleteValueW' }, + regDisablePredefinedCache: { dll: 'ADVAPI32.dll', entry: 'RegDisablePredefinedCache' }, + regDisablePredefinedCacheEx: { dll: 'ADVAPI32.dll', entry: 'RegDisablePredefinedCacheEx' }, + regDisableReflectionKey: { dll: 'ADVAPI32.dll', entry: 'RegDisableReflectionKey' }, + regEnableReflectionKey: { dll: 'ADVAPI32.dll', entry: 'RegEnableReflectionKey' }, + regEnumKeyA: { dll: 'ADVAPI32.dll', entry: 'RegEnumKeyA' }, + regEnumKeyW: { dll: 'ADVAPI32.dll', entry: 'RegEnumKeyW' }, + regEnumValueA: { dll: 'ADVAPI32.dll', entry: 'RegEnumValueA' }, + regEnumValueW: { dll: 'ADVAPI32.dll', entry: 'RegEnumValueW' }, + regFlushKey: { dll: 'ADVAPI32.dll', entry: 'RegFlushKey' }, + regGetKeySecurity: { dll: 'ADVAPI32.dll', entry: 'RegGetKeySecurity' }, + regGetValueA: { dll: 'ADVAPI32.dll', entry: 'RegGetValueA' }, + regGetValueW: { dll: 'ADVAPI32.dll', entry: 'RegGetValueW' }, + regLoadAppKeyA: { dll: 'ADVAPI32.dll', entry: 'RegLoadAppKeyA' }, + regLoadAppKeyW: { dll: 'ADVAPI32.dll', entry: 'RegLoadAppKeyW' }, + regLoadKeyA: { dll: 'ADVAPI32.dll', entry: 'RegLoadKeyA' }, + regLoadKeyW: { dll: 'ADVAPI32.dll', entry: 'RegLoadKeyW' }, + regLoadMUIStringA: { dll: 'ADVAPI32.dll', entry: 'RegLoadMUIStringA' }, + regLoadMUIStringW: { dll: 'ADVAPI32.dll', entry: 'RegLoadMUIStringW' }, + regNotifyChangeKeyValue: { dll: 'ADVAPI32.dll', entry: 'RegNotifyChangeKeyValue' }, + regOpenCurrentUser: { dll: 'ADVAPI32.dll', entry: 'RegOpenCurrentUser' }, + regOpenKeyA: { dll: 'ADVAPI32.dll', entry: 'RegOpenKeyA' }, + regOpenKeyExA: { dll: 'ADVAPI32.dll', entry: 'RegOpenKeyExA' }, + regOpenKeyExW: { dll: 'ADVAPI32.dll', entry: 'RegOpenKeyExW' }, + regOpenKeyTransactedA: { dll: 'ADVAPI32.dll', entry: 'RegOpenKeyTransactedA' }, + regOpenKeyTransactedW: { dll: 'ADVAPI32.dll', entry: 'RegOpenKeyTransactedW' }, + regOpenKeyW: { dll: 'ADVAPI32.dll', entry: 'RegOpenKeyW' }, + regOpenUserClassesRoot: { dll: 'ADVAPI32.dll', entry: 'RegOpenUserClassesRoot' }, + regOverridePredefKey: { dll: 'ADVAPI32.dll', entry: 'RegOverridePredefKey' }, + regQueryReflectionKey: { dll: 'ADVAPI32.dll', entry: 'RegQueryReflectionKey' }, + regQueryValueA: { dll: 'ADVAPI32.dll', entry: 'RegQueryValueA' }, + regQueryValueExA: { dll: 'ADVAPI32.dll', entry: 'RegQueryValueExA' }, + regQueryValueExW: { dll: 'ADVAPI32.dll', entry: 'RegQueryValueExW' }, + regQueryValueW: { dll: 'ADVAPI32.dll', entry: 'RegQueryValueW' }, + regRenameKey: { dll: 'ADVAPI32.dll', entry: 'RegRenameKey' }, + regReplaceKeyA: { dll: 'ADVAPI32.dll', entry: 'RegReplaceKeyA' }, + regReplaceKeyW: { dll: 'ADVAPI32.dll', entry: 'RegReplaceKeyW' }, + regRestoreKeyA: { dll: 'ADVAPI32.dll', entry: 'RegRestoreKeyA' }, + regRestoreKeyW: { dll: 'ADVAPI32.dll', entry: 'RegRestoreKeyW' }, + regSaveKeyA: { dll: 'ADVAPI32.dll', entry: 'RegSaveKeyA' }, + regSaveKeyExA: { dll: 'ADVAPI32.dll', entry: 'RegSaveKeyExA' }, + regSaveKeyExW: { dll: 'ADVAPI32.dll', entry: 'RegSaveKeyExW' }, + regSaveKeyW: { dll: 'ADVAPI32.dll', entry: 'RegSaveKeyW' }, + regSetKeySecurity: { dll: 'ADVAPI32.dll', entry: 'RegSetKeySecurity' }, + regSetKeyValueA: { dll: 'ADVAPI32.dll', entry: 'RegSetKeyValueA' }, + regSetKeyValueW: { dll: 'ADVAPI32.dll', entry: 'RegSetKeyValueW' }, + regSetValueA: { dll: 'ADVAPI32.dll', entry: 'RegSetValueA' }, + regSetValueExA: { dll: 'ADVAPI32.dll', entry: 'RegSetValueExA' }, + regSetValueExW: { dll: 'ADVAPI32.dll', entry: 'RegSetValueExW' }, + regSetValueW: { dll: 'ADVAPI32.dll', entry: 'RegSetValueW' }, + regUnLoadKeyA: { dll: 'ADVAPI32.dll', entry: 'RegUnLoadKeyA' }, + regUnLoadKeyW: { dll: 'ADVAPI32.dll', entry: 'RegUnLoadKeyW' }, +}); diff --git a/tools/dynwinrt-codegen/tests/snapshots/registry_apis/OBJECT_SECURITY_INFORMATION.d.ts b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/OBJECT_SECURITY_INFORMATION.d.ts new file mode 100644 index 00000000..ab54f36c --- /dev/null +++ b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/OBJECT_SECURITY_INFORMATION.d.ts @@ -0,0 +1,16 @@ +// Generated by dynwinrt-codegen — do not edit +export type OBJECT_SECURITY_INFORMATION = (typeof OBJECT_SECURITY_INFORMATION)[keyof typeof OBJECT_SECURITY_INFORMATION]; +export declare const OBJECT_SECURITY_INFORMATION: { + readonly ATTRIBUTE_SECURITY_INFORMATION: 32; + readonly BACKUP_SECURITY_INFORMATION: 65536; + readonly DACL_SECURITY_INFORMATION: 4; + readonly GROUP_SECURITY_INFORMATION: 2; + readonly LABEL_SECURITY_INFORMATION: 16; + readonly OWNER_SECURITY_INFORMATION: 1; + readonly PROTECTED_DACL_SECURITY_INFORMATION: -2147483648; + readonly PROTECTED_SACL_SECURITY_INFORMATION: 1073741824; + readonly SACL_SECURITY_INFORMATION: 8; + readonly SCOPE_SECURITY_INFORMATION: 64; + readonly UNPROTECTED_DACL_SECURITY_INFORMATION: 536870912; + readonly UNPROTECTED_SACL_SECURITY_INFORMATION: 268435456; +}; diff --git a/tools/dynwinrt-codegen/tests/snapshots/registry_apis/OBJECT_SECURITY_INFORMATION.js b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/OBJECT_SECURITY_INFORMATION.js new file mode 100644 index 00000000..c3c3052e --- /dev/null +++ b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/OBJECT_SECURITY_INFORMATION.js @@ -0,0 +1,15 @@ +// Generated by dynwinrt-codegen — do not edit +export const OBJECT_SECURITY_INFORMATION = Object.freeze({ + ATTRIBUTE_SECURITY_INFORMATION: 32, + BACKUP_SECURITY_INFORMATION: 65536, + DACL_SECURITY_INFORMATION: 4, + GROUP_SECURITY_INFORMATION: 2, + LABEL_SECURITY_INFORMATION: 16, + OWNER_SECURITY_INFORMATION: 1, + PROTECTED_DACL_SECURITY_INFORMATION: -2147483648, + PROTECTED_SACL_SECURITY_INFORMATION: 1073741824, + SACL_SECURITY_INFORMATION: 8, + SCOPE_SECURITY_INFORMATION: 64, + UNPROTECTED_DACL_SECURITY_INFORMATION: 536870912, + UNPROTECTED_SACL_SECURITY_INFORMATION: 268435456, +}); diff --git a/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_CREATE_KEY_DISPOSITION.d.ts b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_CREATE_KEY_DISPOSITION.d.ts new file mode 100644 index 00000000..0a5074cd --- /dev/null +++ b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_CREATE_KEY_DISPOSITION.d.ts @@ -0,0 +1,6 @@ +// Generated by dynwinrt-codegen — do not edit +export type REG_CREATE_KEY_DISPOSITION = (typeof REG_CREATE_KEY_DISPOSITION)[keyof typeof REG_CREATE_KEY_DISPOSITION]; +export declare const REG_CREATE_KEY_DISPOSITION: { + readonly REG_CREATED_NEW_KEY: 1; + readonly REG_OPENED_EXISTING_KEY: 2; +}; diff --git a/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_CREATE_KEY_DISPOSITION.js b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_CREATE_KEY_DISPOSITION.js new file mode 100644 index 00000000..3897fd7b --- /dev/null +++ b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_CREATE_KEY_DISPOSITION.js @@ -0,0 +1,5 @@ +// Generated by dynwinrt-codegen — do not edit +export const REG_CREATE_KEY_DISPOSITION = Object.freeze({ + REG_CREATED_NEW_KEY: 1, + REG_OPENED_EXISTING_KEY: 2, +}); diff --git a/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_NOTIFY_FILTER.d.ts b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_NOTIFY_FILTER.d.ts new file mode 100644 index 00000000..8dd618e1 --- /dev/null +++ b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_NOTIFY_FILTER.d.ts @@ -0,0 +1,9 @@ +// Generated by dynwinrt-codegen — do not edit +export type REG_NOTIFY_FILTER = (typeof REG_NOTIFY_FILTER)[keyof typeof REG_NOTIFY_FILTER]; +export declare const REG_NOTIFY_FILTER: { + readonly REG_NOTIFY_CHANGE_NAME: 1; + readonly REG_NOTIFY_CHANGE_ATTRIBUTES: 2; + readonly REG_NOTIFY_CHANGE_LAST_SET: 4; + readonly REG_NOTIFY_CHANGE_SECURITY: 8; + readonly REG_NOTIFY_THREAD_AGNOSTIC: 268435456; +}; diff --git a/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_NOTIFY_FILTER.js b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_NOTIFY_FILTER.js new file mode 100644 index 00000000..0dc81e51 --- /dev/null +++ b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_NOTIFY_FILTER.js @@ -0,0 +1,8 @@ +// Generated by dynwinrt-codegen — do not edit +export const REG_NOTIFY_FILTER = Object.freeze({ + REG_NOTIFY_CHANGE_NAME: 1, + REG_NOTIFY_CHANGE_ATTRIBUTES: 2, + REG_NOTIFY_CHANGE_LAST_SET: 4, + REG_NOTIFY_CHANGE_SECURITY: 8, + REG_NOTIFY_THREAD_AGNOSTIC: 268435456, +}); diff --git a/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_OPEN_CREATE_OPTIONS.d.ts b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_OPEN_CREATE_OPTIONS.d.ts new file mode 100644 index 00000000..df5eea86 --- /dev/null +++ b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_OPEN_CREATE_OPTIONS.d.ts @@ -0,0 +1,11 @@ +// Generated by dynwinrt-codegen — do not edit +export type REG_OPEN_CREATE_OPTIONS = (typeof REG_OPEN_CREATE_OPTIONS)[keyof typeof REG_OPEN_CREATE_OPTIONS]; +export declare const REG_OPEN_CREATE_OPTIONS: { + readonly REG_OPTION_RESERVED: 0; + readonly REG_OPTION_NON_VOLATILE: 0; + readonly REG_OPTION_VOLATILE: 1; + readonly REG_OPTION_CREATE_LINK: 2; + readonly REG_OPTION_BACKUP_RESTORE: 4; + readonly REG_OPTION_OPEN_LINK: 8; + readonly REG_OPTION_DONT_VIRTUALIZE: 16; +}; diff --git a/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_OPEN_CREATE_OPTIONS.js b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_OPEN_CREATE_OPTIONS.js new file mode 100644 index 00000000..6424fec5 --- /dev/null +++ b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_OPEN_CREATE_OPTIONS.js @@ -0,0 +1,10 @@ +// Generated by dynwinrt-codegen — do not edit +export const REG_OPEN_CREATE_OPTIONS = Object.freeze({ + REG_OPTION_RESERVED: 0, + REG_OPTION_NON_VOLATILE: 0, + REG_OPTION_VOLATILE: 1, + REG_OPTION_CREATE_LINK: 2, + REG_OPTION_BACKUP_RESTORE: 4, + REG_OPTION_OPEN_LINK: 8, + REG_OPTION_DONT_VIRTUALIZE: 16, +}); diff --git a/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_ROUTINE_FLAGS.d.ts b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_ROUTINE_FLAGS.d.ts new file mode 100644 index 00000000..45962bfd --- /dev/null +++ b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_ROUTINE_FLAGS.d.ts @@ -0,0 +1,19 @@ +// Generated by dynwinrt-codegen — do not edit +export type REG_ROUTINE_FLAGS = (typeof REG_ROUTINE_FLAGS)[keyof typeof REG_ROUTINE_FLAGS]; +export declare const REG_ROUTINE_FLAGS: { + readonly RRF_RT_DWORD: 24; + readonly RRF_RT_QWORD: 72; + readonly RRF_RT_REG_NONE: 1; + readonly RRF_RT_REG_SZ: 2; + readonly RRF_RT_REG_EXPAND_SZ: 4; + readonly RRF_RT_REG_BINARY: 8; + readonly RRF_RT_REG_DWORD: 16; + readonly RRF_RT_REG_MULTI_SZ: 32; + readonly RRF_RT_REG_QWORD: 64; + readonly RRF_RT_ANY: 65535; + readonly RRF_SUBKEY_WOW6464KEY: 65536; + readonly RRF_SUBKEY_WOW6432KEY: 131072; + readonly RRF_WOW64_MASK: 196608; + readonly RRF_NOEXPAND: 268435456; + readonly RRF_ZEROONFAILURE: 536870912; +}; diff --git a/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_ROUTINE_FLAGS.js b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_ROUTINE_FLAGS.js new file mode 100644 index 00000000..d09499fd --- /dev/null +++ b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_ROUTINE_FLAGS.js @@ -0,0 +1,18 @@ +// Generated by dynwinrt-codegen — do not edit +export const REG_ROUTINE_FLAGS = Object.freeze({ + RRF_RT_DWORD: 24, + RRF_RT_QWORD: 72, + RRF_RT_REG_NONE: 1, + RRF_RT_REG_SZ: 2, + RRF_RT_REG_EXPAND_SZ: 4, + RRF_RT_REG_BINARY: 8, + RRF_RT_REG_DWORD: 16, + RRF_RT_REG_MULTI_SZ: 32, + RRF_RT_REG_QWORD: 64, + RRF_RT_ANY: 65535, + RRF_SUBKEY_WOW6464KEY: 65536, + RRF_SUBKEY_WOW6432KEY: 131072, + RRF_WOW64_MASK: 196608, + RRF_NOEXPAND: 268435456, + RRF_ZEROONFAILURE: 536870912, +}); diff --git a/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_SAM_FLAGS.d.ts b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_SAM_FLAGS.d.ts new file mode 100644 index 00000000..ffd1ccf6 --- /dev/null +++ b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_SAM_FLAGS.d.ts @@ -0,0 +1,17 @@ +// Generated by dynwinrt-codegen — do not edit +export type REG_SAM_FLAGS = (typeof REG_SAM_FLAGS)[keyof typeof REG_SAM_FLAGS]; +export declare const REG_SAM_FLAGS: { + readonly KEY_QUERY_VALUE: 1; + readonly KEY_SET_VALUE: 2; + readonly KEY_CREATE_SUB_KEY: 4; + readonly KEY_ENUMERATE_SUB_KEYS: 8; + readonly KEY_NOTIFY: 16; + readonly KEY_CREATE_LINK: 32; + readonly KEY_WOW64_32KEY: 512; + readonly KEY_WOW64_64KEY: 256; + readonly KEY_WOW64_RES: 768; + readonly KEY_READ: 131097; + readonly KEY_WRITE: 131078; + readonly KEY_EXECUTE: 131097; + readonly KEY_ALL_ACCESS: 983103; +}; diff --git a/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_SAM_FLAGS.js b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_SAM_FLAGS.js new file mode 100644 index 00000000..a739638b --- /dev/null +++ b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_SAM_FLAGS.js @@ -0,0 +1,16 @@ +// Generated by dynwinrt-codegen — do not edit +export const REG_SAM_FLAGS = Object.freeze({ + KEY_QUERY_VALUE: 1, + KEY_SET_VALUE: 2, + KEY_CREATE_SUB_KEY: 4, + KEY_ENUMERATE_SUB_KEYS: 8, + KEY_NOTIFY: 16, + KEY_CREATE_LINK: 32, + KEY_WOW64_32KEY: 512, + KEY_WOW64_64KEY: 256, + KEY_WOW64_RES: 768, + KEY_READ: 131097, + KEY_WRITE: 131078, + KEY_EXECUTE: 131097, + KEY_ALL_ACCESS: 983103, +}); diff --git a/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_SAVE_FORMAT.d.ts b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_SAVE_FORMAT.d.ts new file mode 100644 index 00000000..43b1e367 --- /dev/null +++ b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_SAVE_FORMAT.d.ts @@ -0,0 +1,7 @@ +// Generated by dynwinrt-codegen — do not edit +export type REG_SAVE_FORMAT = (typeof REG_SAVE_FORMAT)[keyof typeof REG_SAVE_FORMAT]; +export declare const REG_SAVE_FORMAT: { + readonly REG_STANDARD_FORMAT: 1; + readonly REG_LATEST_FORMAT: 2; + readonly REG_NO_COMPRESSION: 4; +}; diff --git a/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_SAVE_FORMAT.js b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_SAVE_FORMAT.js new file mode 100644 index 00000000..6536649d --- /dev/null +++ b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_SAVE_FORMAT.js @@ -0,0 +1,6 @@ +// Generated by dynwinrt-codegen — do not edit +export const REG_SAVE_FORMAT = Object.freeze({ + REG_STANDARD_FORMAT: 1, + REG_LATEST_FORMAT: 2, + REG_NO_COMPRESSION: 4, +}); diff --git a/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_VALUE_TYPE.d.ts b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_VALUE_TYPE.d.ts new file mode 100644 index 00000000..91f14fa4 --- /dev/null +++ b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_VALUE_TYPE.d.ts @@ -0,0 +1,18 @@ +// Generated by dynwinrt-codegen — do not edit +export type REG_VALUE_TYPE = (typeof REG_VALUE_TYPE)[keyof typeof REG_VALUE_TYPE]; +export declare const REG_VALUE_TYPE: { + readonly REG_NONE: 0; + readonly REG_SZ: 1; + readonly REG_EXPAND_SZ: 2; + readonly REG_BINARY: 3; + readonly REG_DWORD: 4; + readonly REG_DWORD_LITTLE_ENDIAN: 4; + readonly REG_DWORD_BIG_ENDIAN: 5; + readonly REG_LINK: 6; + readonly REG_MULTI_SZ: 7; + readonly REG_RESOURCE_LIST: 8; + readonly REG_FULL_RESOURCE_DESCRIPTOR: 9; + readonly REG_RESOURCE_REQUIREMENTS_LIST: 10; + readonly REG_QWORD: 11; + readonly REG_QWORD_LITTLE_ENDIAN: 11; +}; diff --git a/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_VALUE_TYPE.js b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_VALUE_TYPE.js new file mode 100644 index 00000000..23e5f6a9 --- /dev/null +++ b/tools/dynwinrt-codegen/tests/snapshots/registry_apis/REG_VALUE_TYPE.js @@ -0,0 +1,17 @@ +// Generated by dynwinrt-codegen — do not edit +export const REG_VALUE_TYPE = Object.freeze({ + REG_NONE: 0, + REG_SZ: 1, + REG_EXPAND_SZ: 2, + REG_BINARY: 3, + REG_DWORD: 4, + REG_DWORD_LITTLE_ENDIAN: 4, + REG_DWORD_BIG_ENDIAN: 5, + REG_LINK: 6, + REG_MULTI_SZ: 7, + REG_RESOURCE_LIST: 8, + REG_FULL_RESOURCE_DESCRIPTOR: 9, + REG_RESOURCE_REQUIREMENTS_LIST: 10, + REG_QWORD: 11, + REG_QWORD_LITTLE_ENDIAN: 11, +}); diff --git a/tools/dynwinrt-codegen/tests/win32_flat_test.rs b/tools/dynwinrt-codegen/tests/win32_flat_test.rs new file mode 100644 index 00000000..d0bf860e --- /dev/null +++ b/tools/dynwinrt-codegen/tests/win32_flat_test.rs @@ -0,0 +1,1749 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! TDD tests for flat-Win32 `[DllImport]` code generation from Windows.Win32.winmd. +//! +//! Covers: +//! - Metadata discovery of `Apis`-class static DllImport methods (dll, entry +//! point, params with direction, return type). +//! - Natural JS/DTS wrapper emission via `codegen::win32::generate_flat_apis_files`. +//! - Corner cases: out-param projection, void/no-arg exports, partial generation, +//! and non-regression of the classic-COM / WinRT paths. + +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; + +use dynwinrt_codegen::codegen::com; +use dynwinrt_codegen::codegen::win32 as flat; +use dynwinrt_codegen::com_metadata; +use dynwinrt_codegen::meta; +use dynwinrt_codegen::meta::{FlatAbiType, FlatBufferSize, FlatDirection}; +use dynwinrt_codegen::types::TypeMeta; + +/// Path to `Windows.Win32.winmd`. Overridable via the `DYNWINRT_WIN32_WINMD` +/// environment variable so this suite can run on CI and other machines without +/// editing the source; falls back to the common local checkout path. +fn win32_winmd() -> String { + std::env::var("DYNWINRT_WIN32_WINMD") + .unwrap_or_else(|_| r"C:\s\win32metadata\Windows.Win32.winmd".to_string()) +} +const REGISTRY_NS: &str = "Windows.Win32.System.Registry"; + +fn win32_available() -> bool { + Path::new(&win32_winmd()).exists() +} + +// --------------------------------------------------------------------------- +// NORMAL: metadata discovery +// --------------------------------------------------------------------------- + +/// 1. Discover flat `[DllImport]` static methods for a namespace's `Apis` +/// class. The `Apis` class must NOT be treated as a COM interface. +#[test] +fn discover_flat_apis_for_registry_namespace() { + if !win32_available() { + eprintln!("Skipping: Win32 winmd not available"); + return; + } + let apis = meta::parse_flat_apis(&win32_winmd(), REGISTRY_NS, "Apis") + .expect("Registry Apis class should parse as a flat-DllImport container"); + assert_eq!(apis.namespace, REGISTRY_NS); + assert_eq!(apis.class_name, "Apis"); + assert!( + !apis.methods.is_empty(), + "must discover at least one flat method" + ); + let names: Vec<&str> = apis.methods.iter().map(|m| m.name.as_str()).collect(); + for expected in &["RegOpenKeyExW", "RegQueryValueExW", "RegCloseKey"] { + assert!( + names.contains(expected), + "expected `{expected}` in Registry Apis, got: {names:?}" + ); + } + + // The `Apis` class is NOT a COM interface — parse_com_interface should + // return None (no interface with that name) OR a Some whose IID is empty. + let as_com = com_metadata::parse_com_interface(&win32_winmd(), REGISTRY_NS, "Apis"); + if let Some(ci) = as_com { + assert!( + ci.interface.iid.is_empty(), + "Apis is not a COM interface but parse_com_interface returned an IID" + ); + } +} + +/// 2. `RegOpenKeyExW` parses correctly: dll = advapi32.dll (any case), +/// entry point = "RegOpenKeyExW", params in order, LSTATUS (i32) return. +#[test] +fn parse_reg_open_key_ex_w() { + if !win32_available() { + eprintln!("Skipping: Win32 winmd not available"); + return; + } + + let apis = meta::parse_flat_apis(&win32_winmd(), REGISTRY_NS, "Apis").unwrap(); + let m = apis + .methods + .iter() + .find(|m| m.name == "RegOpenKeyExW") + .expect("RegOpenKeyExW must be discovered"); + assert!( + m.dll.to_ascii_lowercase().starts_with("advapi32"), + "expected advapi32.dll, got {}", + m.dll + ); + assert_eq!(m.entry_point, "RegOpenKeyExW"); + + // Return type: WIN32_ERROR is a U32 enum but at the ABI it's a 32-bit int + // (LSTATUS). The generator projects LSTATUS as a signed number. + match &m.return_type { + FlatAbiType::Enum { + name, underlying, .. + } => { + assert_eq!(name, "WIN32_ERROR"); + assert!(matches!(**underlying, FlatAbiType::U32 | FlatAbiType::I32)); + } + other => panic!("expected Enum return type for WIN32_ERROR, got {:?}", other), + } + + // Params: hKey (HKEY), lpSubKey (PWSTR), ulOptions (u32), samDesired (enum), + // phkResult (PtrTo(HKEY), out). + assert_eq!(m.params.len(), 5); + let by = |n: &str| m.params.iter().find(|p| p.name == n).unwrap(); + + let hkey = by("hKey"); + assert!( + matches!(&hkey.abi, FlatAbiType::Handle { name, .. } if name == "HKEY"), + "hKey must be Handle{{HKEY}}: {:?}", + hkey.abi + ); + assert_eq!(hkey.direction, FlatDirection::In); + + let sub = by("lpSubKey"); + assert_eq!(sub.abi, FlatAbiType::PWStr); + assert_eq!(sub.direction, FlatDirection::In); + + let opt = by("ulOptions"); + assert_eq!(opt.abi, FlatAbiType::U32); + assert_eq!(opt.direction, FlatDirection::In); + + let sam = by("samDesired"); + assert!( + matches!(&sam.abi, FlatAbiType::Enum { name, .. } if name == "REG_SAM_FLAGS"), + "samDesired must be REG_SAM_FLAGS enum: {:?}", + sam.abi + ); + if let FlatAbiType::Enum { underlying, .. } = &sam.abi { + assert!( + matches!(**underlying, FlatAbiType::U32), + "REG_SAM_FLAGS must preserve its unsigned U32 backing type: {:?}", + sam.abi + ); + } + + let phk = by("phkResult"); + match &phk.abi { + FlatAbiType::PtrTo(inner) => match inner.as_ref() { + FlatAbiType::Handle { name, .. } => assert_eq!(name, "HKEY"), + other => panic!("expected PtrTo(Handle{{HKEY}}), got PtrTo({:?})", other), + }, + other => panic!("phkResult must be PtrTo(HKEY): {:?}", other), + } + + assert_eq!(phk.direction, FlatDirection::Out, "phkResult must be [out]"); +} + +#[test] +fn parse_unsigned_win32_enum_preserves_u32_backing_and_codegen_coerces_high_bit() { + if !win32_available() { + eprintln!("Skipping: Win32 winmd not available"); + return; + } + + let apis = meta::parse_flat_apis(&win32_winmd(), REGISTRY_NS, "Apis").unwrap(); + let m = apis + .methods + .iter() + .find(|m| m.name == "RegSetKeySecurity") + .expect("RegSetKeySecurity must be discovered"); + let security_information = m + .params + .iter() + .find(|p| p.name == "SecurityInformation") + .expect("SecurityInformation param must be discovered"); + match &security_information.abi { + FlatAbiType::Enum { + name, underlying, .. + } => { + assert_eq!(name, "OBJECT_SECURITY_INFORMATION"); + assert!( + matches!(**underlying, FlatAbiType::U32), + "OBJECT_SECURITY_INFORMATION must preserve unsigned U32 backing: {:?}", + security_information.abi + ); + } + other => panic!("expected OBJECT_SECURITY_INFORMATION enum, got {:?}", other), + } + + let out = flat::generate_flat_apis_files(&apis); + assert!( + out.js.contains("DynWin32.u32((securityInformation) >>> 0)"), + "unsigned high-bit enum args must coerce through >>> 0 before napi u32 conversion:\n{}", + out.js + ); +} + +#[test] +fn parse_get_proc_address_return_is_pointer() { + if !win32_available() { + eprintln!("Skipping: Win32 winmd not available"); + return; + } + let apis = meta::parse_flat_apis(&win32_winmd(), "Windows.Win32.System.LibraryLoader", "Apis") + .expect("LibraryLoader Apis should parse"); + let m = apis + .methods + .iter() + .find(|m| m.name == "GetProcAddress") + .expect("GetProcAddress must be discovered"); + assert_eq!(m.return_type, FlatAbiType::FunctionPointer); + let out = flat::generate_flat_apis_files(&synth_apis(vec![m.clone()])); + assert!( + out.js.contains("'GetProcAddress', 'Ptr'"), + "GetProcAddress must use Ptr retKind:\n{}", + out.js + ); + assert!( + out.js.contains("DynWin32.toPointerBigint(_ret)"), + "GetProcAddress must decode pointer returns as BigInt:\n{}", + out.js + ); +} + +#[test] +fn real_narrow_returns_and_signed_i64_inputs_use_exact_runtime_types() { + if !win32_available() { + eprintln!("Skipping: Win32 winmd not available"); + return; + } + let keyboard = meta::parse_flat_apis( + &win32_winmd(), + "Windows.Win32.UI.Input.KeyboardAndMouse", + "Apis", + ) + .expect("KeyboardAndMouse Apis should parse"); + let key_state = keyboard + .methods + .iter() + .find(|method| method.name == "GetAsyncKeyState") + .expect("GetAsyncKeyState should parse"); + assert_eq!(key_state.return_type, FlatAbiType::I16); + let generated = flat::generate_flat_apis_files(&synth_apis(vec![key_state.clone()])); + assert!( + generated + .js + .contains("DynWin32.invoke('USER32.dll', 'GetAsyncKeyState', 'I16'") + ); + + let file_system = + meta::parse_flat_apis(&win32_winmd(), "Windows.Win32.Storage.FileSystem", "Apis") + .expect("FileSystem Apis should parse"); + let set_pointer = file_system + .methods + .iter() + .find(|method| method.name == "SetFilePointerEx") + .expect("SetFilePointerEx should parse"); + let generated = flat::generate_flat_apis_files(&synth_apis(vec![set_pointer.clone()])); + assert!(generated.js.contains("DynWin32.i64(")); +} + +#[test] +fn pointer_depth_is_preserved_for_double_pointer_outputs() { + if !win32_available() { + eprintln!("Skipping: Win32 winmd not available"); + return; + } + let apis = meta::parse_flat_apis( + &win32_winmd(), + "Windows.Win32.System.Com.StructuredStorage", + "Apis", + ) + .expect("StructuredStorage Apis should parse"); + let method = apis + .methods + .iter() + .find(|method| method.name == "PropVariantToUInt32VectorAlloc") + .expect("PropVariantToUInt32VectorAlloc should parse"); + let output = method + .params + .iter() + .find(|param| param.name == "pprgn") + .expect("pprgn should exist"); + assert!(matches!( + output.abi, + FlatAbiType::PtrTo(ref outer) + if matches!(outer.as_ref(), FlatAbiType::PtrTo(_)) + )); + + let generated = flat::generate_flat_apis_files(&synth_apis(vec![method.clone()])); + assert!( + !generated.js.contains("propVariantToUInt32VectorAlloc") + && !generated.dts.contains("propVariantToUInt32VectorAlloc"), + "unowned double-pointer outputs must fail closed" + ); +} + +#[test] +fn counted_native_arrays_remain_caller_owned_buffers() { + if !win32_available() { + eprintln!("Skipping: Win32 winmd not available"); + return; + } + let apis = meta::parse_flat_apis(&win32_winmd(), "Windows.Win32.System.Threading", "Apis") + .expect("Threading Apis should parse"); + let method = apis + .methods + .iter() + .find(|method| method.name == "GetProcessGroupAffinity") + .expect("GetProcessGroupAffinity should parse"); + let groups = method + .params + .iter() + .find(|param| param.name == "GroupArray") + .expect("GroupArray should exist"); + assert!(matches!( + groups.abi, + FlatAbiType::NativeArray { + size: FlatBufferSize::ElementCountParam(_), + .. + } + )); + + let generated = flat::generate_flat_apis_files(&synth_apis(vec![method.clone()])); + assert!( + generated + .dts + .contains("groupArray: bigint | Buffer | Uint8Array | null") + ); + assert!(!generated.js.contains("_groupArraySlot")); + assert!( + generated + .js + .contains("groupArray buffer is smaller than the native size contract") + ); + assert!(generated.js.contains("Number(groupCount) * 2")); +} + +#[test] +fn architecture_overloads_and_variadic_exports_fail_closed() { + if !win32_available() { + eprintln!("Skipping: Win32 winmd not available"); + return; + } + let search = meta::parse_flat_apis(&win32_winmd(), "Windows.Win32.System.Search", "Apis") + .expect("Search Apis should parse"); + assert!( + search + .methods + .iter() + .filter(|method| method.name == "SQLGetData") + .count() + <= 1, + "architecture overloads must never emit duplicate JS declarations" + ); + + let shell = meta::parse_flat_apis(&win32_winmd(), "Windows.Win32.UI.Shell", "Apis") + .expect("Shell Apis should parse"); + assert!( + shell + .methods + .iter() + .all(|method| method.name != "wnsprintfW"), + "variadic exports must be omitted until variadic ABI support exists" + ); +} + +#[test] +fn character_buffers_enum_returns_and_module_scopes_match_runtime_policy() { + if !win32_available() { + eprintln!("Skipping: Win32 winmd not available"); + return; + } + let console = meta::parse_flat_apis(&win32_winmd(), "Windows.Win32.System.Console", "Apis") + .expect("Console Apis should parse"); + let wide = console + .methods + .iter() + .find(|method| method.name == "WriteConsoleW") + .expect("WriteConsoleW should parse"); + let wide_buffer = wide + .params + .iter() + .find(|param| param.name == "lpBuffer") + .expect("WriteConsoleW lpBuffer should exist"); + assert!(matches!( + wide_buffer.abi, + FlatAbiType::NativeArray { + ref element, + .. + } if matches!(element.as_ref(), FlatAbiType::Char16) + )); + let generated = flat::generate_flat_apis_files(&synth_apis(vec![wide.clone()])); + assert!(generated.js.contains("* 2;")); + + let ansi = console + .methods + .iter() + .find(|method| method.name == "WriteConsoleA") + .expect("WriteConsoleA should parse"); + let ansi_buffer = ansi + .params + .iter() + .find(|param| param.name == "lpBuffer") + .expect("WriteConsoleA lpBuffer should exist"); + assert!(matches!( + ansi_buffer.abi, + FlatAbiType::NativeArray { + ref element, + .. + } if matches!(element.as_ref(), FlatAbiType::U8) + )); + let generated = flat::generate_flat_apis_files(&synth_apis(vec![ansi.clone()])); + assert!(generated.js.contains("* 1;")); + + let threading = meta::parse_flat_apis(&win32_winmd(), "Windows.Win32.System.Threading", "Apis") + .expect("Threading Apis should parse"); + assert!( + threading + .methods + .iter() + .all(|method| method.name != "GetCurrentProcessToken") + ); + let wait = threading + .methods + .iter() + .find(|method| method.name == "WaitForSingleObject") + .expect("WaitForSingleObject should parse"); + let generated = flat::generate_flat_apis_files(&synth_apis(vec![wait.clone()])); + assert!( + generated + .js + .contains("result: (DynWin32.toNumber(_ret) | 0)") + ); + + let image_machine = threading + .referenced_enums + .iter() + .find(|typ| { + matches!( + typ, + TypeMeta::Enum { name, .. } if name == "IMAGE_FILE_MACHINE" + ) + }) + .expect("IMAGE_FILE_MACHINE should be collected"); + let TypeMeta::Enum { members, .. } = image_machine else { + unreachable!() + }; + assert_eq!( + members + .iter() + .find(|member| member.name == "IMAGE_FILE_MACHINE_AMD64") + .expect("AMD64 should exist") + .value, + 34404 + ); + assert_eq!( + members + .iter() + .find(|member| member.name == "IMAGE_FILE_MACHINE_ARM64") + .expect("ARM64 should exist") + .value, + 43620 + ); +} + +#[test] +fn last_error_and_ansi_contracts_are_preserved() { + if !win32_available() { + eprintln!("Skipping: Win32 winmd not available"); + return; + } + let file_system = + meta::parse_flat_apis(&win32_winmd(), "Windows.Win32.Storage.FileSystem", "Apis") + .expect("FileSystem Apis should parse"); + let create_file = file_system + .methods + .iter() + .find(|method| method.name == "CreateFileW") + .expect("CreateFileW should parse"); + assert!(create_file.supports_last_error); + let generated = flat::generate_flat_apis_files(&synth_apis(vec![create_file.clone()])); + assert!(generated.js.contains("lastError: _call.lastError")); + assert!(generated.dts.contains("readonly lastError: number")); + + let registry = + meta::parse_flat_apis(&win32_winmd(), REGISTRY_NS, "Apis").expect("Registry should parse"); + let ansi = registry + .methods + .iter() + .find(|method| method.name == "RegOpenKeyExA") + .expect("RegOpenKeyExA should parse"); + let generated = flat::generate_flat_apis_files(&synth_apis(vec![ansi.clone()])); + assert!( + generated + .dts + .contains("subKey: bigint | Buffer | Uint8Array | null") + ); + assert!(!generated.js.contains("_narrowStringBuffer")); + + let windows = meta::parse_flat_apis( + &win32_winmd(), + "Windows.Win32.UI.WindowsAndMessaging", + "Apis", + ) + .expect("WindowsAndMessaging Apis should parse"); + let char_next = windows + .methods + .iter() + .find(|method| method.name == "CharNextW") + .expect("CharNextW should parse"); + let generated = flat::generate_flat_apis_files(&synth_apis(vec![char_next.clone()])); + assert!( + !generated.js.contains("charNextW"), + "pointer into a synthesized input string must not escape without an owner" + ); +} + +#[test] +fn data_pointers_and_scalar_typedefs_are_not_inferred_as_handles() { + if !win32_available() { + eprintln!("Skipping: Win32 winmd not available"); + return; + } + let authorization = meta::parse_flat_apis(&win32_winmd(), "Windows.Win32.Security", "Apis") + .expect("Authorization Apis should parse"); + let is_valid_sid = authorization + .methods + .iter() + .find(|method| method.name == "IsValidSid") + .expect("IsValidSid should parse"); + let sid = is_valid_sid + .params + .iter() + .find(|param| param.name == "pSid") + .expect("pSid should exist"); + assert_eq!(sid.abi, FlatAbiType::Ptr); + + let gdi = meta::parse_flat_apis(&win32_winmd(), "Windows.Win32.Graphics.Gdi", "Apis") + .expect("GDI Apis should parse"); + let get_pixel = gdi + .methods + .iter() + .find(|method| method.name == "GetPixel") + .expect("GetPixel should parse"); + assert_eq!(get_pixel.return_type, FlatAbiType::U32); +} + +#[test] +fn reg_connect_registry_ex_projects_status_like_non_ex_variant() { + if !win32_available() { + eprintln!("Skipping: Win32 winmd not available"); + return; + } + let out = generate_registry_apis(); + for name in ["regConnectRegistryW", "regConnectRegistryExW"] { + let idx = out + .js + .find(&format!("export function {name}")) + .unwrap_or_else(|| panic!("{name} must be generated")); + let body = &out.js[idx..out.js[idx..].find("\n}\n").map(|end| idx + end).unwrap()]; + assert!( + body.contains("status: DynWin32.toNumber(_ret)") + || body.contains("status: (DynWin32.toNumber(_ret) | 0)"), + "{name} must project LSTATUS/WIN32_ERROR-family return as status:\n{body}" + ); + assert!( + !body.contains("result: DynWin32.toNumber(_ret)") + && !body.contains("result: (DynWin32.toNumber(_ret) | 0)"), + "{name} must not project status-code return as result:\n{body}" + ); + } + + let numeric = flat::generate_flat_apis_files(&synth_apis(vec![synth_method( + "PlainI32Value", + FlatAbiType::I32, + )])); + assert!( + numeric + .js + .contains("return { result: DynWin32.toNumber(_ret) };"), + "plain I32 value returns must still project as result:\n{}", + numeric.js + ); +} + +// --------------------------------------------------------------------------- +// NORMAL: natural wrapper emission +// --------------------------------------------------------------------------- + +fn generate_registry_apis() -> flat::FlatGeneratedOutput { + let apis = meta::parse_flat_apis(&win32_winmd(), REGISTRY_NS, "Apis").unwrap(); + flat::generate_flat_apis_files(&apis) +} + +#[test] +fn opaque_pointer_param_dts_accepts_uint8array() { + if !win32_available() { + eprintln!("Skipping: Win32 winmd not available"); + return; + } + // Regression: opaque pointer params (e.g. Registry `data`) must accept + // Uint8Array in the .d.ts. The runtime `DynWin32.pointer()` accepts a + // Uint8Array, so typing only `bigint | Buffer` makes a valid Uint8Array + // argument a spurious TypeScript error. + let out = generate_registry_apis(); + assert!( + out.dts.contains("bigint | Buffer | Uint8Array | null"), + "opaque pointer .d.ts must accept Uint8Array:\n{}", + out.dts + ); + assert!( + !out.dts.contains("data: bigint | Buffer | null"), + "opaque pointer .d.ts must not omit Uint8Array:\n{}", + out.dts + ); +} + +/// 3. Emit a NATURAL wrapper whose `.js` calls +/// `DynWin32.invoke('advapi32.dll', 'RegOpenKeyExW', 'I32', [...])` +/// and whose `.d.ts` types params naturally — no raw invocation string +/// leaked at the typed surface. +#[test] +fn emit_natural_registry_wrapper() { + if !win32_available() { + eprintln!("Skipping: Win32 winmd not available"); + return; + } + let out = generate_registry_apis(); + + // The generated .js must call flatInvoke against advapi32 for each fn. + let js = &out.js; + assert!( + js.contains("DynWin32.invoke"), + ".js must invoke DynWin32.invoke: {}", + js + ); + assert!( + js.to_ascii_lowercase().contains("advapi32.dll"), + ".js must reference advapi32.dll" + ); + for fname in &["RegOpenKeyExW", "RegQueryValueExW", "RegCloseKey"] { + assert!( + js.contains(&format!("'{fname}'")) || js.contains(&format!("\"{fname}\"")), + ".js must reference entry point `{fname}`" + ); + } + // camelCase surface in .js + for camel in &["regOpenKeyExW", "regQueryValueExW", "regCloseKey"] { + assert!( + js.contains(&format!("{camel}(")), + ".js must expose `{camel}` as a natural function" + ); + } + + // .d.ts must NOT leak raw flatInvoke; params should be typed naturally. + let dts = &out.dts; + assert!( + !dts.contains("flatInvoke"), + ".d.ts must not leak raw flatInvoke" + ); + // Natural types for the primary shapes. + assert!( + dts.contains("HKEY") || dts.contains("hkey"), + ".d.ts should surface HKEY typedef" + ); + assert!( + dts.contains("string"), + ".d.ts should type LPCWSTR params as string" + ); +} + +/// 4. Partial generation: only the requested namespace/class is emitted; +/// the CLI reference to another namespace's flat container is not required +/// (this is a unit-level check on the meta layer). +#[test] +fn partial_generation_only_requested_namespace() { + if !win32_available() { + eprintln!("Skipping: Win32 winmd not available"); + return; + } + let registry = meta::parse_flat_apis(&win32_winmd(), REGISTRY_NS, "Apis").unwrap(); + for m in ®istry.methods { + // Every method belongs to the Registry namespace's advapi32 exports. + assert!( + m.dll.to_ascii_lowercase().contains("advapi32") + || m.dll.to_ascii_lowercase().contains("kernel32") + || m.dll.to_ascii_lowercase().contains("api-ms-"), + "Registry Apis unexpectedly refers to {}", + m.dll + ); + } +} + +/// 5. Determinism: two consecutive generations of the same class emit +/// byte-identical output. +#[test] +fn generation_is_deterministic() { + if !win32_available() { + eprintln!("Skipping: Win32 winmd not available"); + return; + } + let a = generate_registry_apis(); + let b = generate_registry_apis(); + assert_eq!(a.js, b.js, "generated .js must be deterministic"); + assert_eq!(a.dts, b.dts, "generated .d.ts must be deterministic"); + assert_eq!( + a.extra_files, b.extra_files, + "generated sibling files must be deterministic" + ); +} + +/// 5b. Snapshot: golden files under +/// `tests/snapshots/registry_apis/`. Update the snapshot by running: +/// +/// cargo run -p dynwinrt-codegen -- generate \ +/// --winmd C:\s\win32metadata\Windows.Win32.winmd \ +/// --namespace Windows.Win32.System.Registry \ +/// --class-name Apis \ +/// --output tools\dynwinrt-codegen\tests\snapshots\registry_apis +#[test] +fn snapshot_registry_apis() { + if !win32_available() { + eprintln!("Skipping snapshot: Win32 winmd not available"); + return; + } + let out = generate_registry_apis(); + + let snapshot_dir: PathBuf = + Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/snapshots/registry_apis"); + if !snapshot_dir.exists() { + panic!( + "Snapshot directory not found: {}\n\ + Create it and populate with the CLI shown above.", + snapshot_dir.display() + ); + } + + let mut generated: Vec<(String, String)> = Vec::new(); + generated.push(("Apis.js".into(), out.js.clone())); + generated.push(("Apis.d.ts".into(), out.dts.clone())); + for (name, content) in &out.extra_files { + // WIN32_ERROR has thousands of SDK-version-specific members. Its ABI, + // status projection, and enum rendering are covered by focused tests. + if name.starts_with("WIN32_ERROR.") { + continue; + } + generated.push((name.clone(), content.clone())); + } + + let mut mismatches: Vec = Vec::new(); + for (name, actual) in &generated { + let path = snapshot_dir.join(name); + if !path.exists() { + mismatches.push(format!(" missing snapshot: {}", name)); + continue; + } + let expected = fs::read_to_string(&path).unwrap(); + if actual.trim_end() != expected.trim_end() { + mismatches.push(format!(" differs: {}", name)); + } + } + if let Ok(entries) = fs::read_dir(&snapshot_dir) { + let names: std::collections::HashSet = + generated.iter().map(|(n, _)| n.clone()).collect(); + for entry in entries.flatten() { + let name = entry.file_name().to_string_lossy().to_string(); + if !names.contains(&name) { + mismatches.push(format!(" extra snapshot not generated: {}", name)); + } + } + } + if !mismatches.is_empty() { + panic!( + "Registry Apis snapshot mismatch!\n{}\n\n\ + To update, re-run the generator into the snapshot dir.", + mismatches.join("\n") + ); + } +} + +// --------------------------------------------------------------------------- +// CORNER: out-param projection +// --------------------------------------------------------------------------- + +/// 6. A flat method with an out-param (PHKEY on RegOpenKeyExW) projects the +/// out as a return value. The generator hoists pure-Out pointer-to-scalar +/// params into the return so the caller doesn't have to allocate a Buffer. +#[test] +fn out_param_projects_as_return() { + if !win32_available() { + eprintln!("Skipping: Win32 winmd not available"); + return; + } + let out = generate_registry_apis(); + let dts = &out.dts; + + // Locate the regOpenKeyExW declaration. + let sig_line = dts + .lines() + .find(|l| l.contains("regOpenKeyExW")) + .expect(".d.ts must declare regOpenKeyExW"); + + // The PHKEY out-slot must appear in the return type, NOT the parameter list. + // Extract just the parameter list (text between the FIRST `(` and its + // matching `)`) and assert `phkResult` is absent there. The return-type + // portion after the `:` is expected to contain it. + let open = sig_line + .find('(') + .expect("regOpenKeyExW signature must have a param list"); + let close = sig_line[open..] + .find(')') + .map(|i| open + i) + .expect("regOpenKeyExW signature must close its param list"); + let params = &sig_line[open + 1..close]; + assert!( + !params.contains("phkResult"), + "regOpenKeyExW must hoist phkResult out of the params list; \ + params were: `{params}` in full sig: {sig_line}" + ); + // Return shape must include HKEY (either as bare or a field). + assert!( + sig_line.to_lowercase().contains("hkey"), + "regOpenKeyExW return type must expose the HKEY: {sig_line}" + ); + // And the return type (text after the closing paren) MUST expose phkResult. + let ret = &sig_line[close..]; + assert!( + ret.contains("phkResult"), + "regOpenKeyExW return type must expose phkResult: {ret}" + ); +} + +/// 7. Status-only return, single input, no out-params: `RegCloseKey(HKEY) +/// -> LSTATUS` — exercise the "one [in] param + status return, no out +/// projection" shape so we don't regress it when the emitter changes. +#[test] +fn no_arg_and_void_returns_are_emitted() { + if !win32_available() { + eprintln!("Skipping: Win32 winmd not available"); + return; + } + let out = generate_registry_apis(); + let js = &out.js; + let dts = &out.dts; + assert!(js.contains("regCloseKey("), ".js must expose regCloseKey"); + let sig_line = dts + .lines() + .find(|l| l.contains("regCloseKey")) + .expect(".d.ts must declare regCloseKey"); + // RegCloseKey has one [in] HKEY and returns LSTATUS. No out-param projection. + assert!( + sig_line.contains("HKEY") || sig_line.contains("hkey"), + "regCloseKey must accept an HKEY: {sig_line}" + ); + assert!( + sig_line.contains("number") || sig_line.contains("void"), + "regCloseKey must have a numeric LSTATUS or void return: {sig_line}" + ); +} + +// --------------------------------------------------------------------------- +// CORNER: non-regression +// --------------------------------------------------------------------------- + +/// 8a. Generating a classic-COM interface (ITaskbarList3) still works. +#[test] +fn com_interface_generation_still_works() { + if !win32_available() { + eprintln!("Skipping: Win32 winmd not available"); + return; + } + let com_iface = com_metadata::parse_com_interface( + &win32_winmd(), + "Windows.Win32.UI.Shell", + "ITaskbarList3", + ) + .expect("ITaskbarList3 must exist"); + let out = com::generate_com_interface_files(&com_iface, &win32_winmd()) + .expect("COM codegen must succeed"); + assert!(out.js.contains("class ITaskbarList3")); + assert!(out.dts.contains("ITaskbarList3")); +} + +/// 8b. WinRT class generation isn't broken by the flat additions: try to +/// invoke the CLI on `Windows.Foundation.Uri` and verify it emits Uri.js +/// and Uri.d.ts. This exercises the full main.rs routing. +#[test] +fn winrt_generation_still_works() { + if !win32_available() { + eprintln!("Skipping: Win32 winmd not available"); + return; + } + if com_metadata::discover_newest_windows_winmd().is_none() { + eprintln!( + "Skipping: Windows SDK Windows.winmd not available (needed to generate Windows.Foundation.Uri)" + ); + return; + } + // Use a unique per-process directory under the OS temp dir to avoid + // cross-test interference when Rust runs tests in parallel and to prevent + // stale state from a previous interrupted run leaking in. + let out_dir = std::env::temp_dir().join(format!( + "dynwinrt_codegen_tmp_gen_uri_{}", + std::process::id() + )); + if out_dir.exists() { + let _ = fs::remove_dir_all(&out_dir); + } + fs::create_dir_all(&out_dir).unwrap(); + + // Invoke the CLI via `cargo run`. + let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); + let workspace_root = manifest_dir.ancestors().nth(2).expect("workspace root"); + let status = Command::new(env!("CARGO_BIN_EXE_dynwinrt-codegen")) + .args([ + "generate", + "--namespace", + "Windows.Foundation", + "--class-name", + "Uri", + "--output", + ]) + .arg(out_dir.to_str().unwrap()) + .current_dir(workspace_root) + .status() + .expect("run cargo"); + assert!(status.success(), "CLI Uri generation should succeed"); + assert!(out_dir.join("Uri.js").exists(), "expected Uri.js"); + assert!(out_dir.join("Uri.d.ts").exists(), "expected Uri.d.ts"); + // Clean up. + let _ = fs::remove_dir_all(&out_dir); +} + +// --------------------------------------------------------------------------- +// FAIL-LOUD: unsupported return kinds must be skipped, not silently truncated +// (Regression for the I64/U64/F32/F64 → I32 silent-degrade bug caught in code +// review.) +// --------------------------------------------------------------------------- + +use dynwinrt_codegen::meta::{FlatApisMeta, FlatMethodMeta, FlatParamMeta}; + +#[test] +fn flat_skips_methods_with_64bit_or_float_underlying_enum_params() { + use dynwinrt_codegen::types::EnumMember; + let enum_param = |ename: &str, underlying: FlatAbiType| FlatParamMeta { + name: "flags".into(), + abi: FlatAbiType::Enum { + namespace: "Fake.Ns".into(), + name: ename.into(), + underlying: Box::new(underlying), + members: vec![EnumMember { + name: "A".into(), + value: 0, + doc: None, + }], + }, + direction: FlatDirection::In, + }; + // A method whose enum param has a U64 underlying is NOT faithfully + // representable (i32-backed members, number-typed surface) -> must be + // skipped fail-loud. A U32-underlying enum param IS representable -> kept. + let mut bad = synth_method("BadEnumMethod", FlatAbiType::U32); + bad.params = vec![enum_param("BigEnum", FlatAbiType::U64)]; + let mut good = synth_method("GoodEnumMethod", FlatAbiType::U32); + good.params = vec![enum_param("SmallEnum", FlatAbiType::U32)]; + + let out = flat::generate_flat_apis_files(&synth_apis(vec![bad, good])); + assert!( + !out.js.contains("badEnumMethod") && !out.dts.contains("badEnumMethod"), + "method with a 64-bit-underlying enum param must be skipped:\n{}", + out.js + ); + assert!( + out.js.contains("goodEnumMethod"), + "method with a 32-bit-underlying enum param must be emitted:\n{}", + out.js + ); +} + +fn synth_method(name: &str, ret: FlatAbiType) -> FlatMethodMeta { + FlatMethodMeta { + name: name.into(), + dll: "FAKE.dll".into(), + entry_point: name.into(), + return_type: ret, + params: vec![FlatParamMeta { + name: "arg".into(), + abi: FlatAbiType::U32, + direction: FlatDirection::In, + }], + return_is_status: false, + supports_last_error: false, + } +} + +fn synth_apis(methods: Vec) -> FlatApisMeta { + FlatApisMeta { + namespace: "Fake.Ns".into(), + class_name: "Apis".into(), + methods, + referenced_enums: Vec::new(), + } +} + +fn synth_enum_meta(namespace: &str, name: &str, member: &str) -> TypeMeta { + synth_enum_meta_with_value(namespace, name, member, 0) +} + +fn synth_enum_meta_with_value(namespace: &str, name: &str, member: &str, value: i32) -> TypeMeta { + use dynwinrt_codegen::types::EnumMember; + + TypeMeta::Enum { + namespace: namespace.into(), + name: name.into(), + underlying: Box::new(TypeMeta::I32), + members: vec![EnumMember { + name: member.into(), + value, + doc: None, + }], + is_flags: false, + doc: None, + deprecated: None, + } +} + +fn synth_enum_abi(namespace: &str, name: &str, member: &str) -> FlatAbiType { + synth_enum_abi_with_value(namespace, name, member, 0) +} + +fn synth_enum_abi_with_value(namespace: &str, name: &str, member: &str, value: i32) -> FlatAbiType { + FlatAbiType::Enum { + namespace: namespace.into(), + name: name.into(), + underlying: Box::new(FlatAbiType::I32), + members: vec![dynwinrt_codegen::types::EnumMember { + name: member.into(), + value, + doc: None, + }], + } +} + +/// A flat export returning I64/U64 must be emitted with an explicit 64-bit +/// retKind and decoded as BigInt, never through the truncating number path. +#[test] +fn flat_emits_i64_u64_returns_with_bigint_decoders() { + let apis = synth_apis(vec![ + synth_method("GoodStatus", FlatAbiType::I32), + synth_method("GetTickCount64", FlatAbiType::U64), + synth_method("GetLargeCounter", FlatAbiType::I64), + ]); + let out = flat::generate_flat_apis_files(&apis); + assert!(out.js.contains("export function goodStatus")); + assert!( + out.js + .contains("DynWin32.invoke('FAKE.dll', 'GetTickCount64', 'U64'"), + ".js must invoke U64 returns with retKind U64:\n{}", + out.js + ); + assert!( + out.js.contains("DynWin32.toU64Bigint(_ret)"), + ".js must decode U64 returns with toU64BigInt():\n{}", + out.js + ); + assert!( + out.js + .contains("DynWin32.invoke('FAKE.dll', 'GetLargeCounter', 'I64'"), + ".js must invoke I64 returns with retKind I64:\n{}", + out.js + ); + assert!( + out.js.contains("DynWin32.toI64Bigint(_ret)"), + ".js must decode I64 returns with toI64BigInt():\n{}", + out.js + ); + assert!( + out.dts + .contains("getTickCount64(arg: number): { readonly result: bigint }") + && out + .dts + .contains("getLargeCounter(arg: number): { readonly result: bigint }"), + ".d.ts must declare I64/U64 returns as bigint:\n{}", + out.dts + ); +} + +/// A flat export returning F32/F64 must be emitted with explicit float +/// retKinds and decoded as JS numbers via toF64(). +#[test] +fn flat_emits_float_returns_with_number_decoder() { + let apis = synth_apis(vec![ + synth_method("Ok", FlatAbiType::I32), + synth_method("FloatFn", FlatAbiType::F32), + synth_method("DoubleFn", FlatAbiType::F64), + ]); + let out = flat::generate_flat_apis_files(&apis); + assert!(out.js.contains("export function ok")); + assert!( + out.js + .contains("DynWin32.invoke('FAKE.dll', 'FloatFn', 'F32'"), + ".js must invoke F32 returns with retKind F32:\n{}", + out.js + ); + assert!( + out.js + .contains("DynWin32.invoke('FAKE.dll', 'DoubleFn', 'F64'"), + ".js must invoke F64 returns with retKind F64:\n{}", + out.js + ); + assert!( + out.js.matches("DynWin32.toF64(_ret)").count() >= 2, + ".js must decode F32/F64 returns with toF64():\n{}", + out.js + ); + assert!( + out.dts + .contains("floatFn(arg: number): { readonly result: number }") + && out + .dts + .contains("doubleFn(arg: number): { readonly result: number }"), + ".d.ts must declare F32/F64 returns as number:\n{}", + out.dts + ); +} + +#[test] +fn flat_bool_return_decodes_boolean_not_number() { + let apis = synth_apis(vec![ + synth_method("ReturnsBool", FlatAbiType::Bool), + synth_method("ReturnsBool32", FlatAbiType::Bool32), + synth_method("ReturnsI32", FlatAbiType::I32), + ]); + let out = flat::generate_flat_apis_files(&apis); + assert!( + out.js + .contains("return { result: (DynWin32.toNumber(_ret) !== 0) };"), + ".js must decode BOOL returns to boolean:\n{}", + out.js + ); + assert!( + out.dts + .contains("returnsBool(arg: number): { readonly result: boolean }") + && out + .dts + .contains("returnsBool32(arg: number): { readonly result: boolean }"), + ".d.ts must declare BOOL returns as boolean:\n{}", + out.dts + ); + assert!( + out.js.contains("export function returnsI32") + && out + .js + .contains("return { result: DynWin32.toNumber(_ret) };"), + "non-bool I32 returns must remain numeric:\n{}", + out.js + ); +} + +#[test] +fn flat_bool32_out_slot_decodes_boolean_not_number() { + let m = FlatMethodMeta { + name: "GetFlag".into(), + dll: "FAKE.dll".into(), + entry_point: "GetFlag".into(), + return_type: FlatAbiType::Void, + params: vec![FlatParamMeta { + name: "enabled".into(), + abi: FlatAbiType::PtrTo(Box::new(FlatAbiType::Bool32)), + direction: FlatDirection::Out, + }], + return_is_status: false, + supports_last_error: false, + }; + let out = flat::generate_flat_apis_files(&synth_apis(vec![m])); + assert!( + out.js + .contains("enabled: (_enabledSlot.readInt32LE(0) !== 0)"), + ".js must decode BOOL out slots to boolean:\n{}", + out.js + ); + assert!( + out.dts.contains("getFlag(): { readonly enabled: boolean }"), + ".d.ts must declare BOOL out slots as boolean:\n{}", + out.dts + ); +} + +/// Unknown return types must be skipped instead of falling back to I32. +#[test] +fn flat_skips_unknown_return_instead_of_silently_truncating() { + let apis = synth_apis(vec![ + synth_method("Ok", FlatAbiType::I32), + synth_method("Mystery", FlatAbiType::Unknown), + ]); + let out = flat::generate_flat_apis_files(&apis); + assert!(out.js.contains("export function ok")); + assert!( + !out.js.contains("mystery") && !out.dts.contains("mystery"), + "Unknown-returning export must be skipped, not emitted with I32 fallback:\n{}\n{}", + out.js, + out.dts + ); +} + +#[test] +fn flat_skips_bare_unknown_param_but_keeps_opaque_pointer_param() { + let by_value_struct = FlatMethodMeta { + name: "ByValueStruct".into(), + dll: "FAKE.dll".into(), + entry_point: "ByValueStruct".into(), + return_type: FlatAbiType::I32, + params: vec![FlatParamMeta { + name: "value".into(), + abi: FlatAbiType::Unknown, + direction: FlatDirection::In, + }], + return_is_status: false, + supports_last_error: false, + }; + let struct_pointer = FlatMethodMeta { + name: "StructPointer".into(), + dll: "FAKE.dll".into(), + entry_point: "StructPointer".into(), + return_type: FlatAbiType::I32, + params: vec![FlatParamMeta { + name: "buffer".into(), + abi: FlatAbiType::PtrTo(Box::new(FlatAbiType::Unknown)), + direction: FlatDirection::In, + }], + return_is_status: false, + supports_last_error: false, + }; + + let out = flat::generate_flat_apis_files(&synth_apis(vec![by_value_struct, struct_pointer])); + assert!( + !out.js.contains("byValueStruct") && !out.dts.contains("byValueStruct"), + "bare Unknown by-value params must be skipped to avoid pointer-for-struct ABI mismatch:\n{}\n{}", + out.js, + out.dts + ); + assert!( + out.js.contains("export function structPointer(buffer)") + && out.js.contains("DynWin32.pointer(buffer)"), + "PtrTo(Unknown) struct pointer params remain valid opaque pointer inputs:\n{}", + out.js + ); + assert!( + out.dts + .contains("structPointer(buffer: bigint | Buffer | Uint8Array | null)"), + "PtrTo(Unknown) should stay in the typed surface as an opaque pointer:\n{}", + out.dts + ); +} + +#[test] +fn flat_emits_void_return_without_result_field() { + let apis = synth_apis(vec![ + synth_method("NoOuts", FlatAbiType::Void), + FlatMethodMeta { + name: "WithOut".into(), + dll: "FAKE.dll".into(), + entry_point: "WithOut".into(), + return_type: FlatAbiType::Void, + params: vec![FlatParamMeta { + name: "value".into(), + abi: FlatAbiType::PtrTo(Box::new(FlatAbiType::U32)), + direction: FlatDirection::Out, + }], + return_is_status: false, + supports_last_error: false, + }, + ]); + let out = flat::generate_flat_apis_files(&apis); + assert!( + out.js + .contains("DynWin32.invoke('FAKE.dll', 'NoOuts', 'Void'") + && out.js.contains("return undefined;"), + "void/no-out export must use Void retKind and return undefined:\n{}", + out.js + ); + assert!( + out.js + .contains("DynWin32.invoke('FAKE.dll', 'WithOut', 'Void'") + && out.js.contains("value: _valueSlot.readUInt32LE(0)"), + "void/out export must omit result and project out params:\n{}", + out.js + ); + assert!( + out.dts.contains("noOuts(arg: number): void") + && out.dts.contains("withOut(): { readonly value: number }"), + ".d.ts must model void returns without result fields:\n{}", + out.dts + ); +} + +/// Enum returns whose underlying type is I64/U64/F32/F64 must be skipped too: +/// the underlying-type widening in `flat_ret_kind_literal` would otherwise +/// silently pick the wrong return kind. +#[test] +fn flat_skips_enum_return_over_unsupported_underlying() { + let bad_enum = FlatAbiType::Enum { + namespace: "Fake.Ns".into(), + name: "LargeStatus".into(), + underlying: Box::new(FlatAbiType::U64), + members: Vec::new(), + }; + let apis = synth_apis(vec![ + synth_method("Ok", FlatAbiType::I32), + synth_method("BigStatus", bad_enum), + ]); + let out = flat::generate_flat_apis_files(&apis); + assert!(out.js.contains("export function ok")); + assert!( + !out.js.contains("bigStatus"), + ".js must NOT include enum export whose underlying is U64:\n{}", + out.js + ); +} + +/// Float PARAMS (not returns) must be wrapped with typed `f32()`/`f64()` — +/// NOT `pointer(...)`, which would silently mis-marshal an IEEE-754 float as +/// a raw pointer. Passing a proper typed value means the wrapper fails +/// loudly at runtime (if the ABI doesn't yet accept floats) rather than +/// producing wrong values. +#[test] +fn flat_float_params_use_typed_wrappers_not_pointer() { + let m = FlatMethodMeta { + name: "SetLevel".into(), + dll: "FAKE.dll".into(), + entry_point: "SetLevel".into(), + return_type: FlatAbiType::I32, + params: vec![ + FlatParamMeta { + name: "amount".into(), + abi: FlatAbiType::F32, + direction: FlatDirection::In, + }, + FlatParamMeta { + name: "precise".into(), + abi: FlatAbiType::F64, + direction: FlatDirection::In, + }, + ], + return_is_status: false, + supports_last_error: false, + }; + let out = flat::generate_flat_apis_files(&synth_apis(vec![m])); + assert!( + out.js.contains("DynWin32.f32(amount)"), + ".js must wrap F32 param with typed f32():\n{}", + out.js + ); + assert!( + out.js.contains("DynWin32.f64(precise)"), + ".js must wrap F64 param with typed f64():\n{}", + out.js + ); + // And crucially, must NOT be `pointer()`. + assert!( + !out.js.contains("DynWin32.pointer(amount)"), + ".js must NOT pointer-wrap F32 (silent mis-marshal):\n{}", + out.js + ); + assert!( + !out.js.contains("DynWin32.pointer(precise)"), + ".js must NOT pointer-wrap F64 (silent mis-marshal):\n{}", + out.js + ); +} + +#[test] +fn flat_unsigned_enum_high_bit_args_cross_u32_boundary_as_unsigned() { + let high_bit_enum = FlatAbiType::Enum { + namespace: "Fake.Ns".into(), + name: "UnsignedFlags".into(), + underlying: Box::new(FlatAbiType::U32), + members: vec![dynwinrt_codegen::types::EnumMember { + name: "HighBit".into(), + value: i32::MIN, + doc: None, + }], + }; + let method = FlatMethodMeta { + name: "UseFlags".into(), + dll: "FAKE.dll".into(), + entry_point: "UseFlags".into(), + return_type: high_bit_enum.clone(), + params: vec![ + FlatParamMeta { + name: "flags".into(), + abi: high_bit_enum.clone(), + direction: FlatDirection::In, + }, + FlatParamMeta { + name: "inoutFlags".into(), + abi: FlatAbiType::PtrTo(Box::new(high_bit_enum)), + direction: FlatDirection::InOut, + }, + ], + return_is_status: false, + supports_last_error: false, + }; + let apis = FlatApisMeta { + namespace: "Fake.Ns".into(), + class_name: "Apis".into(), + methods: vec![method], + referenced_enums: vec![TypeMeta::Enum { + namespace: "Fake.Ns".into(), + name: "UnsignedFlags".into(), + underlying: Box::new(TypeMeta::U32), + members: vec![dynwinrt_codegen::types::EnumMember { + name: "HighBit".into(), + value: i32::MIN, + doc: None, + }], + is_flags: true, + doc: None, + deprecated: None, + }], + }; + let out = flat::generate_flat_apis_files(&apis); + + assert!( + out.extra_files + .iter() + .any(|(name, content)| name == "UnsignedFlags.js" + && content.contains("HighBit: -2147483648")), + "high-bit enum constants should remain signed i32 values so === comparisons with toNumber() returns keep working: {:?}", + out.extra_files + ); + assert!( + out.js.contains("DynWin32.u32((flags) >>> 0)"), + "unsigned enum input args must coerce signed high-bit constants before napi u32 conversion:\n{}", + out.js + ); + assert!( + out.js + .contains("_inoutFlagsSlot.writeUInt32LE((inoutFlags) >>> 0, 0)"), + "unsigned enum inout args must coerce signed high-bit constants before writeUInt32LE:\n{}", + out.js + ); + assert!( + out.js.contains("result: (DynWin32.toNumber(_ret) | 0)") + && out + .js + .contains("inoutFlags: (_inoutFlagsSlot.readUInt32LE(0) | 0)"), + "unsigned enum returns/out slots should stay signed to match emitted constants:\n{}", + out.js + ); +} + +/// The `.d.ts` return type for pointer-like return kinds MUST match what +/// `.js` actually produces at runtime. Any `retKind === "Ptr"` (see +/// `flat_ret_kind_literal` — `Ptr`, `PtrTo(_)`, `PWStr`, `PStr`, +/// `Handle{..}`) is unconditionally converted through +#[test] +fn flat_pointer_returns_require_known_lifetime_semantics() { + let apis = synth_apis(vec![ + synth_method("ReturnsRawPtr", FlatAbiType::Ptr), + synth_method( + "ReturnsPtrToU32", + FlatAbiType::PtrTo(Box::new(FlatAbiType::U32)), + ), + synth_method("ReturnsPWStr", FlatAbiType::PWStr), + synth_method("ReturnsPStr", FlatAbiType::PStr), + synth_method("ReturnsFunctionPointer", FlatAbiType::FunctionPointer), + synth_method( + "ReturnsHandle", + FlatAbiType::Handle { + namespace: "Windows.Win32.Foundation".into(), + name: "HWND".into(), + }, + ), + // Non-pointer sanity check: I32 must still project as `number`. + synth_method("ReturnsI32", FlatAbiType::I32), + ]); + let out = flat::generate_flat_apis_files(&apis); + for rejected in [ + "returnsRawPtr", + "returnsPtrToU32", + "returnsPWStr", + "returnsPStr", + ] { + assert!( + !out.dts.contains(rejected) && !out.js.contains(rejected), + "ownerless pointer return must fail closed: {rejected}" + ); + } + for camel in ["returnsHandle", "returnsFunctionPointer"] { + let needle = format!("function {camel}("); + let idx = out + .dts + .find(&needle) + .unwrap_or_else(|| panic!(".d.ts missing declaration for {camel}:\n{}", out.dts)); + let sig = &out.dts[idx..]; + let end = sig.find(';').unwrap_or(sig.len()); + let sig = &sig[..end]; + assert!( + sig.contains("readonly result: bigint"), + ".d.ts for {camel} must type result as bigint (matches asPointerBigint at runtime), got: {sig}", + ); + assert!( + !sig.contains("Buffer") && !sig.contains("string"), + ".d.ts for {camel} must not surface input-only pointer shapes: {sig}", + ); + } + // Non-pointer sanity check. + assert!( + out.dts + .contains("function returnsI32(arg: number): { readonly result: number }"), + ".d.ts for returnsI32 must project result as number:\n{}", + out.dts + ); + // And the same signals in the .js confirm the contract we're describing. + assert!( + out.js.contains("DynWin32.toPointerBigint(_ret)"), + ".js must convert supported pointer-valued returns to bigint:\n{}", + out.js + ); +} + +#[test] +fn flat_filters_referenced_enums_to_kept_methods_only() { + let kept_enum = synth_enum_abi("Fake.Kept", "KeptStatus", "Ok"); + let skipped_a = synth_enum_abi("Fake.SkippedA", "Status", "A"); + let skipped_b = synth_enum_abi("Fake.SkippedB", "Status", "B"); + let kept = FlatMethodMeta { + name: "Kept".into(), + dll: "FAKE.dll".into(), + entry_point: "Kept".into(), + return_type: FlatAbiType::I32, + params: vec![FlatParamMeta { + name: "status".into(), + abi: kept_enum, + direction: FlatDirection::In, + }], + return_is_status: false, + supports_last_error: false, + }; + let skipped_one = FlatMethodMeta { + name: "SkippedOne".into(), + dll: "FAKE.dll".into(), + entry_point: "SkippedOne".into(), + return_type: FlatAbiType::Unknown, + params: vec![FlatParamMeta { + name: "status".into(), + abi: skipped_a, + direction: FlatDirection::In, + }], + return_is_status: false, + supports_last_error: false, + }; + let skipped_two = FlatMethodMeta { + name: "SkippedTwo".into(), + dll: "FAKE.dll".into(), + entry_point: "SkippedTwo".into(), + return_type: FlatAbiType::Unknown, + params: vec![FlatParamMeta { + name: "status".into(), + abi: skipped_b, + direction: FlatDirection::In, + }], + return_is_status: false, + supports_last_error: false, + }; + let apis = FlatApisMeta { + namespace: "Fake.Ns".into(), + class_name: "Apis".into(), + methods: vec![kept, skipped_one, skipped_two], + referenced_enums: vec![ + synth_enum_meta("Fake.Kept", "KeptStatus", "Ok"), + synth_enum_meta("Fake.SkippedA", "Status", "A"), + synth_enum_meta("Fake.SkippedB", "Status", "B"), + ], + }; + + let out = std::panic::catch_unwind(|| flat::generate_flat_apis_files(&apis)) + .expect("skipped-only enum simple-name collisions must not abort generation"); + let extra_names: Vec<&str> = out + .extra_files + .iter() + .map(|(name, _)| name.as_str()) + .collect(); + assert_eq!( + extra_names, + vec!["KeptStatus.d.ts", "KeptStatus.js"], + "only enums referenced by emitted methods should produce sibling files" + ); + assert!(out.js.contains("export function kept")); + assert!(!out.js.contains("skippedOne") && !out.js.contains("skippedTwo")); +} + +#[test] +fn flat_cli_emits_isolated_incremental_namespace_packages() { + if !win32_available() { + eprintln!("Skipping: Win32 winmd not available"); + return; + } + let out_dir = std::env::temp_dir().join(format!( + "dynwinrt_codegen_flat_package_{}", + std::process::id() + )); + let _ = fs::remove_dir_all(&out_dir); + + for namespace in [REGISTRY_NS, "Windows.Win32.System.LibraryLoader"] { + let output = Command::new(env!("CARGO_BIN_EXE_dynwinrt-codegen")) + .args([ + "generate", + "--winmd", + &win32_winmd(), + "--namespace", + namespace, + "--class-name", + "Apis", + "--output", + ]) + .arg(&out_dir) + .output() + .expect("run flat codegen"); + assert!( + output.status.success(), + "flat generation failed:\n{}", + String::from_utf8_lossy(&output.stderr) + ); + } + + for namespace in [REGISTRY_NS, "Windows.Win32.System.LibraryLoader"] { + let namespace_dir = out_dir.join("win32").join(namespace); + assert!(namespace_dir.join("Apis.js").is_file()); + assert!(namespace_dir.join("Apis.d.ts").is_file()); + assert!(namespace_dir.join("index.js").is_file()); + assert!(namespace_dir.join("package.json").is_file()); + } + let registry_js = + fs::read_to_string(out_dir.join("win32").join(REGISTRY_NS).join("Apis.js")).unwrap(); + assert!(registry_js.contains("from '@microsoft/dynwinrt/win32'")); + let package = fs::read_to_string(out_dir.join("package.json")).unwrap(); + assert!(package.contains("\"dynwinrtDomain\": \"win32\"")); + assert!(package.contains("\"./win32/Windows.Win32.System.Registry\"")); + assert!(package.contains("\"./win32/Windows.Win32.System.LibraryLoader\"")); + assert!(!out_dir.join("Apis.js").exists()); + + fs::remove_dir_all(out_dir).unwrap(); +} + +/// The CLI must fail loud when `--lang py` (or any non-`js` language) is +/// combined with a `--class-name` that resolves to a flat-Win32 `[DllImport]` +/// module — those emitters produce only `.js` + `.d.ts` and would otherwise +/// silently write the wrong artifact types into the output directory. +#[test] +fn cli_rejects_non_js_lang_for_flat_apis() { + if !win32_available() { + eprintln!("Skipping: Win32 winmd not available"); + return; + } + let out_dir = std::env::temp_dir().join(format!( + "dynwinrt_codegen_reject_flat_py_{}", + std::process::id() + )); + if out_dir.exists() { + let _ = fs::remove_dir_all(&out_dir); + } + fs::create_dir_all(&out_dir).unwrap(); + + let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); + let workspace_root = manifest_dir.ancestors().nth(2).expect("workspace root"); + let output = Command::new(env!("CARGO_BIN_EXE_dynwinrt-codegen")) + .args([ + "generate", + "--winmd", + &win32_winmd(), + "--namespace", + REGISTRY_NS, + "--class-name", + "Apis", + "--lang", + "py", + "--output", + ]) + .arg(out_dir.to_str().unwrap()) + .current_dir(workspace_root) + .output() + .expect("run cargo"); + + assert!( + !output.status.success(), + "CLI must reject --lang py for a flat-Apis class (got success)" + ); + let stderr = String::from_utf8_lossy(&output.stderr); + let combined = format!("{}{}", String::from_utf8_lossy(&output.stdout), stderr); + assert!( + combined.contains("--lang py") + && (combined.contains("flat-Win32") || combined.contains("[DllImport]")), + "error must explain the flat-Win32 language mismatch. output was:\n{}", + combined + ); + // And no artifacts should have been written. + assert!( + !out_dir.join("Apis.js").exists(), + "no .js should be written when the CLI rejects the invocation" + ); + let _ = fs::remove_dir_all(&out_dir); +} + +/// `parse_flat_apis_from_index` deduplicates referenced enums by +/// `(namespace, name)`, not `name` alone, so an `Apis` class that +/// references two enums that happen to share a simple name across +/// distinct namespaces keeps both entries. The emitter then fails +/// loud with a clear panic instead of silently emitting a +/// wrong-shape sibling file (only one variant would survive because +/// enum-file names use the simple name). +#[test] +#[should_panic(expected = "multiple distinct enums named `Status`")] +fn flat_fails_loud_on_simple_name_enum_collision() { + // Two distinct enums with the same simple name from different + // namespaces. Both must reach codegen (post-dedup) because the + // `(namespace, name)` key differs. + let apis = FlatApisMeta { + namespace: "Fake.Ns".into(), + class_name: "Apis".into(), + methods: vec![FlatMethodMeta { + name: "Noop".into(), + dll: "FAKE.dll".into(), + entry_point: "Noop".into(), + return_type: FlatAbiType::I32, + params: vec![ + FlatParamMeta { + name: "a".into(), + abi: synth_enum_abi("Fake.NsA", "Status", "AVariant"), + direction: FlatDirection::In, + }, + FlatParamMeta { + name: "b".into(), + abi: synth_enum_abi("Fake.NsB", "Status", "BVariant"), + direction: FlatDirection::In, + }, + ], + return_is_status: false, + supports_last_error: false, + }], + referenced_enums: vec![ + synth_enum_meta("Fake.NsA", "Status", "AVariant"), + synth_enum_meta("Fake.NsB", "Status", "BVariant"), + ], + }; + // Should panic before returning FlatGeneratedOutput. + let _ = flat::generate_flat_apis_files(&apis); +}