From f6fc2e4e2efe2eb9dd9a439daeacd73a3da3eaf5 Mon Sep 17 00:00:00 2001 From: Stephan Schreiber Date: Thu, 21 Aug 2025 07:23:13 +0200 Subject: [PATCH] refactor(user32): rework WNDCLASS[EX] registration --- source/win32/structs.ts | 97 +++++++++++++---------------------- source/win32/user32/class.ts | 98 +++++++++++++----------------------- 2 files changed, 69 insertions(+), 126 deletions(-) diff --git a/source/win32/structs.ts b/source/win32/structs.ts index b328a15..dd0d687 100644 --- a/source/win32/structs.ts +++ b/source/win32/structs.ts @@ -26,12 +26,8 @@ import type { */ export class ACL { readonly AclRevision = Internals.ACL_REVISION - readonly Sbsz1 = 0 // Used for alignment - readonly Sbsz2 = 0 // Used for alignment - constructor( - public AceCount: number = 0, - public AclSize: number = 0 - ) {} + public AceCount!: number + public AclSize!: number } /** @internal */ @@ -63,15 +59,9 @@ export const cSID_IDENTIFIER_AUTHORITY = koffi.array(cBYTE, 6, 'Array') */ export class SID { readonly Revision = Internals.SID_REVISION - public SubAuthorityCount: number - public SubAuthority: number[] - constructor( - public IdentifierAuthority: SID_IDENTIFIER_AUTHORITY, - ...SubAuthority: number[] - ) { - this.SubAuthorityCount = SubAuthority.length - this.SubAuthority = SubAuthority.slice() - } + public SubAuthorityCount!: number + public SubAuthority!: number[] + public IdentifierAuthority!: SID_IDENTIFIER_AUTHORITY } /** @internal */ @@ -156,32 +146,17 @@ export const cCLAIM_SECURITY_ATTRIBUTE_OCTET_STRING_VALUE = koffi.struct({ * https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-claim_security_attribute_v1 */ export class CLAIM_SECURITY_ATTRIBUTE_V1 { - readonly Reserved = 0 - public Values = new koffi.Union('CLAIM_SECURITY_ATTRIBUTE_V1_Values') as { + public Name!: string + public ValueType!: CLAIM_SECURITY_ATTRIBUTE_TYPE_ + public Flags!: CLAIM_SECURITY_ATTRIBUTE_ + public ValueCount!: number + public Values = new koffi.Union('CSA_V1_Values') as { pInt64?: BigInt[] pUint64?: BigInt[] ppString?: string[] pFqbn?: CLAIM_SECURITY_ATTRIBUTE_FQBN_VALUE[] pOctetString?: CLAIM_SECURITY_ATTRIBUTE_OCTET_STRING_VALUE[] } - constructor( - public Name: string = null!, - public Flags: CLAIM_SECURITY_ATTRIBUTE_ = 0 as CLAIM_SECURITY_ATTRIBUTE_, - public ValueType: CLAIM_SECURITY_ATTRIBUTE_TYPE_ = 0, - public ValueCount = 0, - { pInt64, pUint64, ppString, pFqbn, pOctetString }: CLAIM_SECURITY_ATTRIBUTE_V1['Values'] = {} - ) { - if (pInt64) - this.Values.pInt64 = pInt64 - else if (pUint64) - this.Values.pInt64 = pUint64 - else if (ppString) - this.Values.ppString = ppString - else if (pFqbn) - this.Values.pFqbn = pFqbn - else if (pOctetString) - this.Values.pOctetString = pOctetString - } } /** @internal */ @@ -191,7 +166,7 @@ export const cCLAIM_SECURITY_ATTRIBUTE_V1 = koffi.struct({ Reserved: cWORD, Flags: cDWORD, ValueCount: cDWORD, - Values: koffi.union('CLAIM_SECURITY_ATTRIBUTE_V1_Values', { + Values: koffi.union('CSA_V1_Values', { pInt64: koffi.pointer(cLONG64, Internals.ANYSIZE_ARRAY), pUint64: koffi.pointer(cDWORD64, Internals.ANYSIZE_ARRAY), ppString: koffi.pointer(cSTR, Internals.ANYSIZE_ARRAY), @@ -217,7 +192,7 @@ export const cCLAIM_SECURITY_ATTRIBUTES_INFORMATION = koffi.struct({ Version: cWORD, Reserved: cWORD, AttributeCount: cDWORD, - Attribute: koffi.struct({ // Should be a union, but there is only one member... + Attribute: koffi.struct({ // Should be a union, but since there is only one member... pAttributeV1: koffi.pointer(cCLAIM_SECURITY_ATTRIBUTE_V1, Internals.ANYSIZE_ARRAY) }) }) @@ -645,19 +620,17 @@ export const cDLGPROC = koffi.pointer(koffi.proto('DLG', cLRESULT, [ cHANDLE, cU * * https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassw */ -export class WNDCLASS { - constructor( - public hInstance: HINSTANCE | null = null, - public lpszClassName: string = '', - public style: CS_ = 0, - public lpfnWndProc: WNDPROC | null = null, - public hCursor: HCURSOR | null = null, - public hIcon: HICON | null = null, - public hbrBackground: HBRUSH | null = null, - public lpszMenuName: string | null = null, - public cbClsExtra: number = 0, - public cwWndExtra: number = 0 - ) {} +export interface WNDCLASS { + hInstance: HINSTANCE | null + lpszClassName: string | null + style: CS_ + lpfnWndProc: WNDPROC | null + hCursor: HCURSOR | null + hIcon: HICON | null + hbrBackground: HBRUSH | null + lpszMenuName: string | null + cbClsExtra: number + cwWndExtra: number } /** @internal */ @@ -683,16 +656,16 @@ export class WNDCLASSEX { readonly cbSize = SIZEOF_WNDCLASSEX constructor( public hInstance: HINSTANCE | null = null, - public lpszClassName: string = '', - public style: CS_ = 0, - public lpfnWndProc: WNDPROC | null = null, - public hCursor: HCURSOR | null = null, - public hIcon: HICON | null = null, - public hIconSm: HICON | null = null, - public hbrBackground: HBRUSH | null = null, - public lpszMenuName: string | null = null, - public cbClsExtra: number = 0, - public cwWndExtra: number = 0 + public lpszClassName: string | null = null, + public style: CS_ = 0, + public lpfnWndProc: WNDPROC | null = null, + public hCursor: HCURSOR | null = null, + public hIcon: HICON | null = null, + public hIconSm: HICON | null = null, + public hbrBackground: HBRUSH | null = null, + public lpszMenuName: string | null = null, + public cbClsExtra: number = 0, + public cwWndExtra: number = 0 ) {} } @@ -1108,7 +1081,7 @@ export const cPOLICY_ACCOUNT_DOMAIN_INFO = koffi.struct({ /** * Used to set and query the system's auditing rules. * - * learn.microsoft.com/en-us/windows/win32/api/ntsecapi/ns-ntsecapi-policy_audit_events_info + * https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/ns-ntsecapi-policy_audit_events_info */ export interface POLICY_AUDIT_EVENTS_INFO { AuditingMode: boolean @@ -1119,7 +1092,7 @@ export interface POLICY_AUDIT_EVENTS_INFO { /** @internal */ export const cPOLICY_AUDIT_EVENTS_INFO = koffi.struct({ AuditingMode: cBOOL, - EventAuditingOptions: koffi.array(cULONG, 'MaximumAuditEventCount', Internals.POLICY_AUDIT_EVENTS_INFO_MAX_OPTIONS), + EventAuditingOptions: koffi.pointer(null, cULONG, 'MaximumAuditEventCount'), MaximumAuditEventCount: cULONG }) diff --git a/source/win32/user32/class.ts b/source/win32/user32/class.ts index 811a657..2443abe 100644 --- a/source/win32/user32/class.ts +++ b/source/win32/user32/class.ts @@ -5,56 +5,40 @@ import { cATOM, type ATOM, cHANDLE, type HINSTANCE, type HWND } from '../ctypes.js' import { - cWNDCLASS, WNDCLASS, + cWNDCLASS, type WNDCLASS, cWNDCLASSEX, WNDCLASSEX, cWNDPROC } from '../structs.js' import { user32 } from './lib.js' -const wndProcs = new Map() +const wndProcs = new Map() /** * Retrieves information about a window class. * - * FIXME: at the moment, libwin32 does not support passing an ATOM for `className`. + * Note: libwin32 only supports querying by class name (string). * * https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclassinfow */ export function GetClassInfo(hInstance: HINSTANCE | null, className: string): WNDCLASS | null { - const wndClass = new WNDCLASS() - return typeof className === 'number' ? info_by_atom() : info_by_string() + GetClassInfo.native ??= user32.func('GetClassInfoW', cBOOL, [ cHANDLE, cSTR, koffi.out(koffi.pointer(cWNDCLASS)) ]) - function info_by_atom() { - info_by_atom.native ??= user32.func('GetClassInfoW', cBOOL, [ cHANDLE, cATOM, koffi.out(koffi.pointer(cWNDCLASS)) ]) - return info_by_atom.native(hInstance, className, wndClass) !== 0 ? wndClass : null - } - - function info_by_string() { - info_by_string.native ??= user32.func('GetClassInfoW', cBOOL, [ cHANDLE, cSTR, koffi.out(koffi.pointer(cWNDCLASS)) ]) - return info_by_string.native(hInstance, className, wndClass) !== 0 ? wndClass : null - } + const wndClass = {} as WNDCLASS + return GetClassInfo.native(hInstance, className, wndClass) !== 0 ? wndClass : null } /** * Retrieves information about a window class, including a handle to the small icon associated with the window class. * - * FIXME: at the moment, libwin32 does not support passing an ATOM for `className`. + * Note: libwin32 only supports querying by class name (string). * * https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclassinfoexw */ export function GetClassInfoEx(hInstance: HINSTANCE | null, className: string): WNDCLASSEX | null { + GetClassInfoEx.native ??= user32.func('GetClassInfoExW', cBOOL, [ cHANDLE, cSTR, koffi.out(koffi.pointer(cWNDCLASSEX)) ]) + const wndClassEx = new WNDCLASSEX() - return typeof className === 'number' ? info_by_atom() : info_by_string() - - function info_by_atom() { - info_by_atom.native ??= user32.func('GetClassInfoExW', cBOOL, [ cHANDLE, cATOM, koffi.out(koffi.pointer(cWNDCLASSEX)) ]) - return info_by_atom.native(hInstance, className, wndClassEx) !== 0 ? wndClassEx : null - } - - function info_by_string() { - info_by_string.native ??= user32.func('GetClassInfoExW', cBOOL, [ cHANDLE, cSTR, koffi.out(koffi.pointer(cWNDCLASSEX)) ]) - return info_by_string.native(hInstance, className, wndClassEx) !== 0 ? wndClassEx : null - } + return GetClassInfoEx.native(hInstance, className, wndClassEx) !== 0 ? wndClassEx : null } /** @@ -62,12 +46,12 @@ export function GetClassInfoEx(hInstance: HINSTANCE | null, className: string): * * https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclassnamew */ -export function GetClassName(hWnd: HWND): string { +export function GetClassName(hWnd: HWND): string | null { GetClassName.native ??= user32.func('GetClassNameW', cINT, [ cHANDLE, koffi.out(cPVOID), cINT ]) const className = new StringOutputBuffer(256) const len = GetClassName.native(hWnd, className.buffer, className.length) - return className.decode(len) + return len ? className.decode(len) : null } /** @@ -75,10 +59,10 @@ export function GetClassName(hWnd: HWND): string { * * https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerclassw */ -export function RegisterClass(wndClass: WNDCLASS): ATOM { +export function RegisterClass(wndClass: WNDCLASS): ATOM | null { RegisterClass.native ??= user32.func('RegisterClassW', cATOM, [ koffi.pointer(cWNDCLASS) ]) - let wndProc: koffi.IKoffiRegisteredCallback | undefined = undefined + let wndProc: koffi.IKoffiRegisteredCallback | undefined if (typeof wndClass.lpfnWndProc === 'function') { wndProc = koffi.register(wndClass.lpfnWndProc, cWNDPROC) wndClass = { @@ -88,15 +72,9 @@ export function RegisterClass(wndClass: WNDCLASS): ATOM { } const atom = RegisterClass.native(wndClass) - if (atom && wndProc) { - wndProcs.set(atom, wndProc) - if (wndClass.lpszClassName) - wndProcs.set(wndClass.lpszClassName, wndProc) - } - else if (wndProc) - koffi.unregister(wndProc) - return atom + ? (wndProcs.set(atom, wndProc), atom) + : (wndProc && koffi.unregister(wndProc), null) } /** @@ -104,10 +82,10 @@ export function RegisterClass(wndClass: WNDCLASS): ATOM { * * https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerclassexw */ -export function RegisterClassEx(wndClassEx: WNDCLASSEX): ATOM { +export function RegisterClassEx(wndClassEx: WNDCLASSEX): ATOM | null { RegisterClassEx.native ??= user32.func('RegisterClassExW', cATOM, [ koffi.pointer(cWNDCLASSEX) ]) - let wndProc: koffi.IKoffiRegisteredCallback | undefined = undefined + let wndProc: koffi.IKoffiRegisteredCallback | undefined if (typeof wndClassEx.lpfnWndProc === 'function') { wndProc = koffi.register(wndClassEx.lpfnWndProc, cWNDPROC) wndClassEx = { @@ -117,38 +95,30 @@ export function RegisterClassEx(wndClassEx: WNDCLASSEX): ATOM { } const atom = RegisterClassEx.native(wndClassEx) - if (atom && wndProc) { - wndProcs.set(atom, wndProc) - if (wndClassEx.lpszClassName) - wndProcs.set(wndClassEx.lpszClassName, wndProc) - } - else if (wndProc) - koffi.unregister(wndProc) - return atom + ? (wndProcs.set(atom, wndProc), atom) + : (wndProc && koffi.unregister(wndProc), null) } /** * Unregisters a window class, freeing the memory required for the class. * + * Note: despite the parameter being named `className`, as in the native API, + * libwin32 only supports unregistering by ATOM -- the one you received + * from RegisterClass/RegisterClassEx. + * * https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-unregisterclassw */ -export function UnregisterClass(className: ATOM | string, hInstance: HINSTANCE | null = null): boolean { - const wndProc = wndProcs.get(className) - const ret = typeof className === 'number' ? unregister_by_atom() : unregister_by_string() - if (wndProc) { - koffi.unregister(wndProc) - wndProcs.forEach((v, k) => v === wndProc && wndProcs.delete(k)) +export function UnregisterClass(className: ATOM, hInstance: HINSTANCE | null = null): boolean { + UnregisterClass.native ??= user32.func('UnregisterClassW', cBOOL, [ cATOM, cHANDLE ]) + + const ret = UnregisterClass.native(className, hInstance) !== 0 + if (ret) { + const wndProc = wndProcs.get(className) + if (wndProc) { + koffi.unregister(wndProc) + wndProcs.delete(className) + } } return ret - - function unregister_by_atom() { - unregister_by_atom.native ??= user32.func('UnregisterClassW', cBOOL, [ cATOM, cHANDLE ]) - return unregister_by_atom.native(className, hInstance) !== 0 - } - - function unregister_by_string() { - unregister_by_string.native ??= user32.func('UnregisterClassW', cBOOL, [ cSTR, cHANDLE ]) - return unregister_by_string.native(className, hInstance) !== 0 - } }