(Follow up from !6788)
The goal is to use the device object API as the backend for Windows.Devices.Enumeration, as most concepts from the latter have a one-to-one mapping to the former:
* The device object API is centred around the [`DEV_OBJECT`](https://learn.microsoft.com/en-us/windows/win32/api/devquerydef/ns-devqueryd...) type, which provides a unified representation for devnodes, device interfaces/containers, device interface classes, and association endpoints (networking protocols and service instances, like UPnP and Bluetooth), together with the properties associated with it. The [`DevObjectType`](https://learn.microsoft.com/en-us/windows/win32/api/devquerydef/ne-devqueryd...) enum maps to [`Windows.Devices.Enumeration.DeviceInformationKind`](https://learn.microsoft.com/en-us/uwp/api/windows.devices.enumeration.device...). This would be used to implement the [`DeviceInformation`](https://learn.microsoft.com/en-us/uwp/api/windows.devices.enumeration.device...) class. * [`DEVPROP_FILTER_EXPRESSION`](https://learn.microsoft.com/en-us/windows/win32/api/devfiltertypes/ns-devfil...) allows filtering device queries by their properties. AQS filter strings would be parsed into an array of filters, which are then passed to the query object methods. * `DevCreateObjectQuery` would be the backend for `DeviceWatcher`, as it provides asynchronous callbacks for initial device enumeration, device addition, removal and updates. * [`DevGetObjects`](https://learn.microsoft.com/en-us/windows/win32/api/devquery/nf-devquery-dev...) would be used to implement the `FindAllAsync*` methods.
This MR adds a basic implementation and tests for `DevGetObjects`.
-- v3: cfgmgr32: Add a basic implementation for DevGetObjects(Ex) for device interface objects.
From: Vibhav Pant vibhavp@gmail.com
--- include/Makefile.in | 1 + include/devpropdef.h | 22 +++++++++++ include/devquerydef.h | 89 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 include/devquerydef.h
diff --git a/include/Makefile.in b/include/Makefile.in index 650d69815f8..cdc9bf29784 100644 --- a/include/Makefile.in +++ b/include/Makefile.in @@ -234,6 +234,7 @@ SOURCES = \ devicetopology.idl \ devpkey.h \ devpropdef.h \ + devquerydef.h \ dhcpcsdk.h \ dhtmldid.h \ dhtmled.idl \ diff --git a/include/devpropdef.h b/include/devpropdef.h index 94046f5bce0..7c0db0777d2 100644 --- a/include/devpropdef.h +++ b/include/devpropdef.h @@ -100,4 +100,26 @@ typedef struct _DEVPROPKEY { #else #define IsEqualDevPropKey(a,b) (((a).pid == (b).pid) && IsEqualIID(&(a).fmtid,&(b).fmtid)) #endif + +typedef enum _DEVPROPSTORE +{ + DEVPROP_STORE_SYSTEM, + DEVPROP_STORE_USER, +} DEVPROPSTORE, *PDEVPROPSTORE; + +typedef struct _DEVPROPCOMPKEY +{ + DEVPROPKEY Key; + DEVPROPSTORE Store; + PCWSTR LocaleName; +} DEVPROPCOMPKEY, *PDEVPROPCOMPKEY; + +typedef struct _DEVPROPERTY +{ + DEVPROPCOMPKEY CompKey; + DEVPROPTYPE type; + ULONG BufferSize; + void *Buffer; +} DEVPROPERTY, *PDEVPROPERTY; + #endif diff --git a/include/devquerydef.h b/include/devquerydef.h new file mode 100644 index 00000000000..21940ca9fd3 --- /dev/null +++ b/include/devquerydef.h @@ -0,0 +1,89 @@ +/* + * Copyright 2025 Vibhav Pant + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef __DEVQUERYDEF_H__ +#define __DEVQUERYDEF_H__ + +typedef enum _DEV_OBJECT_TYPE +{ + DevObjectTypeUnknown, + DevObjectTypeDeviceInterface, + DevObjectTypeDeviceContainer, + DevObjectTypeDevice, + DevObjectTypeDeviceInterfaceClass, + DevObjectTypeAEP, + DevObjectTypeAEPContainer, + DevObjectTypeDeviceInstallerClass, + DevObjectTypeDeviceInterfaceDisplay, + DevObjectTypeDeviceContainerDisplay, + DevObjectTypeAEPService, + DevObjectTypeDevicePanel, +} DEV_OBJECT_TYPE, *PDEV_OBJECT_TYPE; + +typedef enum _DEV_QUERY_FLAGS +{ + DevQueryFlagNone = 0x0, + DevQueryFlagUpdateResults = 0x1, + DevQueryFlagAllProperties = 0x2, + DevQueryFlagLocalize = 0x4, + DevQueryFlagAsyncClose = 0x8, +} DEV_QUERY_FLAGS, *PDEV_QUERY_FLAGS; + +typedef enum _DEV_QUERY_STATE +{ + DevQueryStateInitialized, + DevQueryStateEnumCompleted, + DevQueryStateAborted, + DevQueryStateClosed, +} DEV_QUERY_STATE, *PDEV_QUERY_STATE; + +typedef enum _DEV_QUERY_RESULT_ACTION +{ + DevQueryResultStateChange, + DevQueryResultAdd, + DevQueryResultUpdate, + DevQueryResultRemove, +} DEV_QUERY_RESULT_ACTION, *PDEV_QUERY_RESULT_ACTION; + +typedef struct _DEV_OBJECT +{ + DEV_OBJECT_TYPE ObjectType; + PCWSTR pszObjectId; + ULONG cPropertyCount; + const DEVPROPERTY *pProperties; +} DEV_OBJECT, *PDEV_OBJECT; + +typedef struct _DEV_QUERY_RESULT_ACTION_DATA +{ + DEV_QUERY_RESULT_ACTION Action; + union + { + DEV_QUERY_STATE State; + DEV_OBJECT DeviceObject; + } Data; +} DEV_QUERY_RESULT_ACTION_DATA, *PDEV_QUERY_RESULT_ACTION_DATA; + +typedef struct _DEV_QUERY_PARAMETER +{ + DEVPROPKEY Key; + DEVPROPTYPE Type; + ULONG BufferSize; + void *Buffer; +} DEV_QUERY_PARAMETER, *PDEV_QUERY_PARAMETER; + +#endif /* __DEVQUERYDEF_H__ */
From: Vibhav Pant vibhavp@gmail.com
--- include/Makefile.in | 1 + include/devfiltertypes.h | 74 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 include/devfiltertypes.h
diff --git a/include/Makefile.in b/include/Makefile.in index cdc9bf29784..8b85d6d680a 100644 --- a/include/Makefile.in +++ b/include/Makefile.in @@ -230,6 +230,7 @@ SOURCES = \ ddstream.idl \ delayloadhandler.h \ devenum.idl \ + devfiltertypes.h \ devguid.h \ devicetopology.idl \ devpkey.h \ diff --git a/include/devfiltertypes.h b/include/devfiltertypes.h new file mode 100644 index 00000000000..2c6a6ee95bf --- /dev/null +++ b/include/devfiltertypes.h @@ -0,0 +1,74 @@ +/* + * Copyright 2025 Vibhav Pant + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef __DEVFILTERTYPES_H__ +#define __DEVFILTERTYPES_H__ + +typedef enum _DEVPROP_OPERATOR +{ + DEVPROP_OPERATOR_MODIFIER_NOT = 0x00010000, + DEVPROP_OPERATOR_MODIFIER_IGNORE_CASE = 0x00020000, + DEVPROP_OPERATOR_NONE = 0x00000000, + DEVPROP_OPERATOR_EXISTS = 0x00000001, + DEVPROP_OPERATOR_NOT_EXISTS = DEVPROP_OPERATOR_MODIFIER_NOT | DEVPROP_OPERATOR_EXISTS , + DEVPROP_OPERATOR_EQUALS = 0x00000002, + DEVPROP_OPERATOR_NOT_EQUALS = DEVPROP_OPERATOR_MODIFIER_NOT | DEVPROP_OPERATOR_EQUALS, + DEVPROP_OPERATOR_GREATER_THAN = 0x00000003, + DEVPROP_OPERATOR_LESS_THAN = 0x00000004, + DEVPROP_OPERATOR_GREATER_THAN_EQUALS = 0x00000005, + DEVPROP_OPERATOR_LESS_THAN_EQUALS = 0x00000006, + DEVPROP_OPERATOR_EQUALS_IGNORE_CASE = DEVPROP_OPERATOR_MODIFIER_IGNORE_CASE | DEVPROP_OPERATOR_EQUALS, + DEVPROP_OPERATOR_NOT_EQUALS_IGNORE_CASE = DEVPROP_OPERATOR_MODIFIER_NOT | DEVPROP_OPERATOR_MODIFIER_IGNORE_CASE | DEVPROP_OPERATOR_EQUALS, + DEVPROP_OPERATOR_BITWISE_AND = 0x00000007, + DEVPROP_OPERATOR_BITWISE_OR = 0x00000008, + DEVPROP_OPERATOR_BEGINS_WITH = 0x00000009, + DEVPROP_OPERATOR_ENDS_WITH = 0x0000000a, + DEVPROP_OPERATOR_CONTAINS = 0x0000000b, + DEVPROP_OPERATOR_BEGINS_WITH_IGNORE_CASE = DEVPROP_OPERATOR_MODIFIER_IGNORE_CASE | DEVPROP_OPERATOR_BEGINS_WITH, + DEVPROP_OPERATOR_ENDS_WITH_IGNORE_CASE = DEVPROP_OPERATOR_MODIFIER_IGNORE_CASE | DEVPROP_OPERATOR_ENDS_WITH, + DEVPROP_OPERATOR_CONTAINS_IGNORE_CASE = DEVPROP_OPERATOR_MODIFIER_IGNORE_CASE | DEVPROP_OPERATOR_CONTAINS, + DEVPROP_OPERATOR_LIST_CONTAINS = 0x00001000, + DEVPROP_OPERATOR_LIST_ELEMENT_BEGINS_WITH = 0x00002000, + DEVPROP_OPERATOR_LIST_ELEMENT_ENDS_WITH = 0x00003000, + DEVPROP_OPERATOR_LIST_ELEMENT_CONTAINS = 0x00004000, + DEVPROP_OPERATOR_LIST_CONTAINS_IGNORE_CASE = DEVPROP_OPERATOR_MODIFIER_IGNORE_CASE | DEVPROP_OPERATOR_LIST_CONTAINS, + DEVPROP_OPERATOR_LIST_ELEMENT_BEGINS_WITH_IGNORE_CASE = DEVPROP_OPERATOR_MODIFIER_IGNORE_CASE | DEVPROP_OPERATOR_LIST_ELEMENT_BEGINS_WITH, + DEVPROP_OPERATOR_LIST_ELEMENT_ENDS_WITH_IGNORE_CASE = DEVPROP_OPERATOR_MODIFIER_IGNORE_CASE | DEVPROP_OPERATOR_LIST_ELEMENT_ENDS_WITH, + DEVPROP_OPERATOR_LIST_ELEMENT_CONTAINS_IGNORE_CASE = DEVPROP_OPERATOR_MODIFIER_IGNORE_CASE | DEVPROP_OPERATOR_LIST_ELEMENT_CONTAINS, + DEVPROP_OPERATOR_AND_OPEN = 0x00100000, + DEVPROP_OPERATOR_AND_CLOSE = 0x00200000, + DEVPROP_OPERATOR_OR_OPEN = 0x00300000, + DEVPROP_OPERATOR_OR_CLOSE = 0x00400000, + DEVPROP_OPERATOR_NOT_OPEN = 0x00500000, + DEVPROP_OPERATOR_NOT_CLOSE = 0x00600000, + DEVPROP_OPERATOR_ARRAY_CONTAINS = 0x10000000, + DEVPROP_OPERATOR_MASK_EVAL = 0x00000fff, + DEVPROP_OPERATOR_MASK_LIST = 0x0000f000, + DEVPROP_OPERATOR_MASK_MODIFIER = 0x000f0000, + DEVPROP_OPERATOR_MASK_NOT_LOGICAL = 0xf00fffff, + DEVPROP_OPERATOR_MASK_LOGICAL = 0x0ff00000, + DEVPROP_OPERATOR_MASK_ARRAY = 0xf0000000, +} DEVPROP_OPERATOR, *PDEVPROP_OPERATOR; + +typedef struct _DEVPROP_FILTER_EXPRESSION +{ + DEVPROP_OPERATOR Operator; + DEVPROPERTY Property; +} DEVPROP_FILTER_EXPRESSION, *PDEVPROP_FILTER_EXPRESSION; + +#endif /* __DEVFILTERTYPES_H__ */
From: Vibhav Pant vibhavp@gmail.com
--- include/Makefile.in | 1 + include/devquery.h | 68 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 include/devquery.h
diff --git a/include/Makefile.in b/include/Makefile.in index 8b85d6d680a..e8bf1aa7de2 100644 --- a/include/Makefile.in +++ b/include/Makefile.in @@ -235,6 +235,7 @@ SOURCES = \ devicetopology.idl \ devpkey.h \ devpropdef.h \ + devquery.h \ devquerydef.h \ dhcpcsdk.h \ dhtmldid.h \ diff --git a/include/devquery.h b/include/devquery.h new file mode 100644 index 00000000000..3ea9c7b2fed --- /dev/null +++ b/include/devquery.h @@ -0,0 +1,68 @@ +/* + * Copyright 2025 Vibhav Pant + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef __DEVQUERY_H__ +#define __DEVQUERY_H__ + +#include <devquerydef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef HANDLE HDEVQUERY, *PHDEVQUERY; + +typedef void (WINAPI *PDEV_QUERY_RESULT_CALLBACK)( HDEVQUERY devquery, void *user_data, const DEV_QUERY_RESULT_ACTION_DATA *action_data ); + +HRESULT WINAPI DevCreateObjectQuery( DEV_OBJECT_TYPE type, ULONG flags, ULONG props_len, const DEVPROPCOMPKEY *props, ULONG filters_len, + const DEVPROP_FILTER_EXPRESSION *filters, PDEV_QUERY_RESULT_CALLBACK callback, void *user_data, HDEVQUERY *devquery ); +HRESULT WINAPI DevCreateObjectQueryEx( DEV_OBJECT_TYPE type, ULONG flags, ULONG props_len, const DEVPROPCOMPKEY *props, ULONG filters_len, + const DEVPROP_FILTER_EXPRESSION *filters, ULONG params_len, const DEV_QUERY_PARAMETER *params, + PDEV_QUERY_RESULT_CALLBACK callback, void *user_data, HDEVQUERY *devquery ); + +HRESULT WINAPI DevCreateObjectQueryFromId( DEV_OBJECT_TYPE type, const WCHAR *id, ULONG flags, ULONG props_len, const DEVPROPCOMPKEY *props, ULONG filters_len, + const DEVPROP_FILTER_EXPRESSION *filters, PDEV_QUERY_RESULT_CALLBACK callback, void *user_data, HDEVQUERY *devquery ); +HRESULT WINAPI DevCreateObjectQueryFromIdEx( DEV_OBJECT_TYPE type, const WCHAR *id, ULONG flags, ULONG props_len, const DEVPROPCOMPKEY *props, + ULONG filters_len, const DEVPROP_FILTER_EXPRESSION *filters, ULONG params_len, const DEV_QUERY_PARAMETER *params, + PDEV_QUERY_RESULT_CALLBACK callback, void *user_data, HDEVQUERY *devquery ); +HRESULT WINAPI DevCreateObjectQueryFromIds( DEV_OBJECT_TYPE type, const WCHAR *id_sz, ULONG flags, ULONG props_len, const DEVPROPCOMPKEY *props, ULONG filters_len, + const DEVPROP_FILTER_EXPRESSION *filters, PDEV_QUERY_RESULT_CALLBACK callback, void *user_data, HDEVQUERY *devquery ); +HRESULT WINAPI DevCreateObjectQueryFromIdsEx( DEV_OBJECT_TYPE type, const WCHAR *id_sz, ULONG flags, ULONG props_len, const DEVPROPCOMPKEY *props, + ULONG filters_len, const DEVPROP_FILTER_EXPRESSION *filters, ULONG params_len, const DEV_QUERY_PARAMETER *params, + PDEV_QUERY_RESULT_CALLBACK callback, void *user_data, HDEVQUERY *devquery ); +void WINAPI DevCloseObjectQuery( HDEVQUERY devquery ); + +HRESULT WINAPI DevGetObjects( DEV_OBJECT_TYPE type, ULONG flags, ULONG props_len, const DEVPROPCOMPKEY *props, ULONG filters_len, + const DEVPROP_FILTER_EXPRESSION *filters, ULONG *objs_len, const DEV_OBJECT **objs ); +HRESULT WINAPI DevGetObjectsEx( DEV_OBJECT_TYPE type, ULONG flags, ULONG props_len, const DEVPROPCOMPKEY *props, ULONG filters_len, + const DEVPROP_FILTER_EXPRESSION *filters, ULONG params_len, const DEV_QUERY_PARAMETER *params, ULONG *objs_len, + const DEV_OBJECT **objs ); +void WINAPI DevFreeObjects( ULONG len, const DEV_OBJECT *objs ); + +HRESULT WINAPI DevGetObjectProperties( DEV_OBJECT_TYPE type, const WCHAR *id, ULONG flags, ULONG props_len, const DEVPROPCOMPKEY *props, ULONG *buf_len, + const DEVPROPERTY **buf ); +HRESULT WINAPI DevGetObjectPropertiesEx( DEV_OBJECT_TYPE type, const WCHAR *id, ULONG flags, ULONG props_len, const DEVPROPCOMPKEY *props, ULONG params_len, + const DEV_QUERY_PARAMETER *params, ULONG *buf_len, const DEVPROPERTY **buf ); +const DEVPROPERTY *WINAPI DevFindProperty( const DEVPROPKEY *key, DEVPROPSTORE store, const WCHAR *locale, ULONG props_len, const DEVPROPERTY *props ); +HRESULT WINAPI DevFreeObjectProperties( ULONG len, const DEVPROPERTY *props ); + +#ifdef __cplusplus +} +#endif + +#endif /* __DEVQUERY_H__ */
From: Vibhav Pant vibhavp@gmail.com
--- dlls/cfgmgr32/cfgmgr32.spec | 2 + dlls/cfgmgr32/main.c | 15 +++ dlls/cfgmgr32/tests/cfgmgr32.c | 163 +++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+)
diff --git a/dlls/cfgmgr32/cfgmgr32.spec b/dlls/cfgmgr32/cfgmgr32.spec index 6d493144455..3fd0d8bd6f3 100644 --- a/dlls/cfgmgr32/cfgmgr32.spec +++ b/dlls/cfgmgr32/cfgmgr32.spec @@ -188,3 +188,5 @@ @ stub CM_Unregister_Device_Interface_ExA @ stub CM_Unregister_Device_Interface_ExW @ stdcall CM_Unregister_Notification(ptr) +@ stdcall DevFreeObjects(long ptr) +@ stdcall DevGetObjects(long long long ptr long ptr ptr ptr) diff --git a/dlls/cfgmgr32/main.c b/dlls/cfgmgr32/main.c index fbff2c2fbf9..3e7cfc1c767 100644 --- a/dlls/cfgmgr32/main.c +++ b/dlls/cfgmgr32/main.c @@ -24,6 +24,8 @@ #include "dbt.h" #include "wine/plugplay.h" #include "setupapi.h" +#include "devfiltertypes.h" +#include "devquery.h"
#include "initguid.h" #include "devpkey.h" @@ -296,3 +298,16 @@ CONFIGRET WINAPI CM_Get_Device_Interface_PropertyW( LPCWSTR device_interface, co if (!err) return CR_SUCCESS; return err == ERROR_INSUFFICIENT_BUFFER ? CR_BUFFER_SMALL : CR_FAILURE; } + +HRESULT WINAPI DevGetObjects( DEV_OBJECT_TYPE type, ULONG flags, ULONG props_len, const DEVPROPCOMPKEY *props, ULONG filters_len, + const DEVPROP_FILTER_EXPRESSION *filters, ULONG *objs_len, const DEV_OBJECT **objs ) +{ + FIXME( "(%d, %#lx, %lu, %p, %lu, %p, %p, %p): stub!\n", type, flags, props_len, props, filters_len, filters, objs_len, objs ); + return E_NOTIMPL; +} + +void WINAPI DevFreeObjects( ULONG objs_len, const DEV_OBJECT *objs ) +{ + FIXME( "(%lu, %p): stub!\n", objs_len, objs ); + return; +} diff --git a/dlls/cfgmgr32/tests/cfgmgr32.c b/dlls/cfgmgr32/tests/cfgmgr32.c index 95f64698733..b5bdf4f4023 100644 --- a/dlls/cfgmgr32/tests/cfgmgr32.c +++ b/dlls/cfgmgr32/tests/cfgmgr32.c @@ -17,6 +17,7 @@ */
#include "wine/test.h" +#include "winerror.h" #include "winreg.h" #include "windef.h" #include "winbase.h" @@ -28,6 +29,8 @@ #include "setupapi.h" #include "cfgmgr32.h" #include "ntddvdeo.h" +#include "devfiltertypes.h" +#include "devquery.h"
static void test_CM_MapCrToWin32Err(void) { @@ -483,10 +486,170 @@ static void test_CM_Get_Device_Interface_List(void) ok(ret == CR_NO_SUCH_DEVICE_INTERFACE || broken(ret == CR_INVALID_DATA) /* w7 */, "got %#lx.\n", ret); }
+static void test_DevGetObjects( void ) +{ + struct { + DEV_OBJECT_TYPE object_type; + struct { + DEVPROPKEY key; + DEVPROPTYPE type; + } exp_props[3]; + ULONG props_len; + } test_cases[] = { + { + DevObjectTypeDeviceInterface, + { + { DEVPKEY_DeviceInterface_ClassGuid, DEVPROP_TYPE_GUID }, + { DEVPKEY_DeviceInterface_Enabled, DEVPROP_TYPE_BOOLEAN }, + { DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING } + }, + 3, + }, + { + DevObjectTypeDeviceInterfaceDisplay, + { + { DEVPKEY_DeviceInterface_ClassGuid, DEVPROP_TYPE_GUID }, + { DEVPKEY_DeviceInterface_Enabled, DEVPROP_TYPE_BOOLEAN }, + { DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING } + }, + 3, + }, + }; + const DEV_OBJECT *objects = NULL; + HDEVINFO set; + HRESULT hr; + ULONG i, len = 0; + + hr = DevGetObjects( DevObjectTypeDeviceInterface, DevQueryFlagNone, 1, NULL, 0, NULL, &len, &objects ); + todo_wine ok( hr == HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ), "got hr %#lx\n", hr ); + todo_wine ok( len == 0, "got len %lu\n", len ); + todo_wine ok( !objects, "got objects %p\n", objects ); + + hr = DevGetObjects( DevObjectTypeDeviceInterface, DevQueryFlagNone, 0, NULL, 1, NULL, &len, &objects ); + todo_wine ok( hr == HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ), "got hr %#lx\n", hr ); + todo_wine ok( len == 0, "got len %lu\n", len ); + todo_wine ok( !objects, "got objects %p\n", objects ); + + hr = DevGetObjects( DevObjectTypeDeviceInterface, DevQueryFlagNone, 0, (void *)0xdeadbeef, 0, NULL, &len, &objects ); + todo_wine ok( hr == HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ), "got hr %#lx\n", hr ); + todo_wine ok( len == 0, "got len %lu\n", len ); + todo_wine ok( !objects, "got objects %p\n", objects ); + + hr = DevGetObjects( DevObjectTypeDeviceInterface, DevQueryFlagNone, 0, NULL, 0, (void *)0xdeadbeef, &len, &objects ); + todo_wine ok( hr == HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ), "got hr %#lx\n", hr ); + todo_wine ok( len == 0, "got len %lu\n", len ); + todo_wine ok( !objects, "got objects %p\n", objects ); + + hr = DevGetObjects( DevObjectTypeDeviceInterface, DevQueryFlagUpdateResults, 0, NULL, 0, (void *)0xdeadbeef, &len, &objects ); + todo_wine ok( hr == HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ), "got hr %#lx\n", hr ); + todo_wine ok( len == 0, "got len %lu\n", len ); + todo_wine ok( !objects, "got objects %p\n", objects ); + + hr = DevGetObjects( DevObjectTypeDeviceInterface, DevQueryFlagAsyncClose, 0, NULL, 0, (void *)0xdeadbeef, &len, &objects ); + todo_wine ok( hr == HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ), "got hr %#lx\n", hr ); + todo_wine ok( len == 0, "got len %lu\n", len ); + todo_wine ok( !objects, "got objects %p\n", objects ); + + hr = DevGetObjects( DevObjectTypeDeviceInterface, 0xdeadbeef, 0, NULL, 0, (void *)0xdeadbeef, &len, &objects ); + todo_wine ok( hr == HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ), "got hr %#lx\n", hr ); + todo_wine ok( len == 0, "got len %lu\n", len ); + todo_wine ok( !objects, "got objects %p\n", objects ); + + hr = DevGetObjects( DevObjectTypeUnknown, DevQueryFlagNone, 0, NULL, 0, NULL, &len, &objects ); + todo_wine ok( hr == S_OK, "got hr %#lx\n", hr ); + todo_wine ok( len == 0, "got len %lu\n", len ); + todo_wine ok( !objects, "got objects %p\n", objects ); + + hr = DevGetObjects( 0xdeadbeef, DevQueryFlagNone, 0, NULL, 0, NULL, &len, &objects ); + todo_wine ok( hr == S_OK, "got hr %#lx\n", hr ); + todo_wine ok( len == 0, "got len %lu\n", len ); + todo_wine ok( !objects, "got objects %p\n", objects ); + + set = SetupDiCreateDeviceInfoListExW( NULL, NULL, NULL, NULL ); + ok( set != INVALID_HANDLE_VALUE, "SetupDiCreateDeviceInfoListExW failed: %lu\n", GetLastError() ); + + for (i = 0; i < ARRAY_SIZE( test_cases ); i++) + { + const DEV_OBJECT *objects = NULL; + ULONG j, len = 0; + + winetest_push_context( "test_cases[%lu]", i ); + hr = DevGetObjects( test_cases[i].object_type, DevQueryFlagAllProperties, 0, NULL, 0, NULL, &len, &objects ); + todo_wine ok( hr == S_OK, "got hr %#lx\n", hr ); + for (j = 0; j < len; j++) + { + ULONG rem_props = test_cases[i].props_len, k; + const DEV_OBJECT *obj = &objects[j]; + + winetest_push_context( "device %s", debugstr_w( obj->pszObjectId ) ); + ok( obj->ObjectType == test_cases[i].object_type, "got ObjectType %d\n", obj->ObjectType ); + todo_wine ok( obj->cPropertyCount >= test_cases[i].props_len, "got cPropertyCount %lu, should be >= %lu\n", + obj->cPropertyCount, test_cases[i].props_len ); + for (k = 0; k < obj->cPropertyCount && rem_props; k++) + { + const DEVPROPERTY *property = &obj->pProperties[k]; + ULONG l; + + for (l = 0; l < test_cases[i].props_len; l++) + { + if (IsEqualDevPropKey( property->CompKey.Key, test_cases[i].exp_props[l].key )) + { + SP_INTERFACE_DEVICE_DATA iface_data = {0}; + DEVPROPTYPE type = DEVPROP_TYPE_EMPTY; + ULONG size = 0; + CONFIGRET ret; + BYTE *buf; + + winetest_push_context( "exp_props[%lu]", l ); + rem_props--; + ok( property->type == test_cases[i].exp_props[l].type, "got type %#lx\n", property->type ); + + /* Ensure the value matches the value retrieved via SetupDiGetDeviceInterfacePropertyW */ + buf = calloc( property->BufferSize, 1 ); + iface_data.cbSize = sizeof( iface_data ); + ret = SetupDiOpenDeviceInterfaceW( set, obj->pszObjectId, 0, &iface_data ); + ok( ret, "SetupDiOpenDeviceInterfaceW failed: %lu\n", GetLastError() ); + ret = SetupDiGetDeviceInterfacePropertyW( set, &iface_data, &property->CompKey.Key, &type, buf, + property->BufferSize, &size, 0 ); + ok( ret, "SetupDiGetDeviceInterfacePropertyW failed: %lu\n", GetLastError() ); + SetupDiDeleteDeviceInterfaceData( set, &iface_data ); + + ok( size == property->BufferSize, "got size %lu\n", size ); + ok( type == property->type, "got type %#lx\n", type ); + if (size == property->BufferSize) + { + switch (type) + { + case DEVPROP_TYPE_STRING: + ok( !wcsicmp( (WCHAR *)buf, (WCHAR *)property->Buffer ), "got instance id %s != %s\n", + debugstr_w( (WCHAR *)buf ), debugstr_w( (WCHAR *)property->Buffer ) ); + break; + default: + ok( !memcmp( buf, property->Buffer, size ), "got mistmatching property values\n" ); + break; + } + } + free( buf ); + winetest_pop_context(); + break; + } + } + } + todo_wine ok( rem_props == 0, "got rem %lu != 0\n", rem_props ); + winetest_pop_context(); + } + winetest_pop_context(); + DevFreeObjects( len, objects ); + } + + SetupDiDestroyDeviceInfoList( set ); +} + START_TEST(cfgmgr32) { test_CM_MapCrToWin32Err(); test_CM_Get_Device_ID_List(); test_CM_Register_Notification(); test_CM_Get_Device_Interface_List(); + test_DevGetObjects(); }
From: Vibhav Pant vibhavp@gmail.com
--- dlls/cfgmgr32/Makefile.in | 2 +- dlls/cfgmgr32/cfgmgr32.spec | 1 + dlls/cfgmgr32/main.c | 141 +++++++++++++++++++++++++++++++-- dlls/cfgmgr32/tests/cfgmgr32.c | 56 ++++++------- 4 files changed, 166 insertions(+), 34 deletions(-)
diff --git a/dlls/cfgmgr32/Makefile.in b/dlls/cfgmgr32/Makefile.in index e8d9fecdcdd..fd819760e6f 100644 --- a/dlls/cfgmgr32/Makefile.in +++ b/dlls/cfgmgr32/Makefile.in @@ -1,6 +1,6 @@ MODULE = cfgmgr32.dll IMPORTLIB = cfgmgr32 -IMPORTS = setupapi sechost +IMPORTS = advapi32 rpcrt4 sechost setupapi
SOURCES = \ main.c diff --git a/dlls/cfgmgr32/cfgmgr32.spec b/dlls/cfgmgr32/cfgmgr32.spec index 3fd0d8bd6f3..e4afeb0a461 100644 --- a/dlls/cfgmgr32/cfgmgr32.spec +++ b/dlls/cfgmgr32/cfgmgr32.spec @@ -190,3 +190,4 @@ @ stdcall CM_Unregister_Notification(ptr) @ stdcall DevFreeObjects(long ptr) @ stdcall DevGetObjects(long long long ptr long ptr ptr ptr) +@ stdcall DevGetObjectsEx(long long long ptr long ptr long ptr ptr ptr) diff --git a/dlls/cfgmgr32/main.c b/dlls/cfgmgr32/main.c index 3e7cfc1c767..11953755d28 100644 --- a/dlls/cfgmgr32/main.c +++ b/dlls/cfgmgr32/main.c @@ -299,15 +299,146 @@ CONFIGRET WINAPI CM_Get_Device_Interface_PropertyW( LPCWSTR device_interface, co return err == ERROR_INSUFFICIENT_BUFFER ? CR_BUFFER_SMALL : CR_FAILURE; }
-HRESULT WINAPI DevGetObjects( DEV_OBJECT_TYPE type, ULONG flags, ULONG props_len, const DEVPROPCOMPKEY *props, ULONG filters_len, - const DEVPROP_FILTER_EXPRESSION *filters, ULONG *objs_len, const DEV_OBJECT **objs ) +BOOL dev_objects_append_iface( DEV_OBJECT **objects, ULONG *len, const WCHAR *path, DEV_OBJECT_TYPE type ) { - FIXME( "(%d, %#lx, %lu, %p, %lu, %p, %p, %p): stub!\n", type, flags, props_len, props, filters_len, filters, objs_len, objs ); - return E_NOTIMPL; + DEV_OBJECT *tmp; + WCHAR *id; + + if (!(id = wcsdup( path ))) + return FALSE; + if (!(tmp = realloc( *objects, (*len + 1) * sizeof( **objects ) ))) + { + free( id ); + return FALSE; + } + *objects = tmp; + + tmp = &tmp[*len]; + tmp->ObjectType = type; + tmp->pszObjectId = id; + tmp->cPropertyCount = 0; + tmp->pProperties = NULL; + + *len += 1; + return TRUE; +} + +HRESULT WINAPI DevGetObjects( DEV_OBJECT_TYPE type, ULONG flags, ULONG props_len, const DEVPROPCOMPKEY *props, + ULONG filters_len, const DEVPROP_FILTER_EXPRESSION *filters, ULONG *objs_len, + const DEV_OBJECT **objs ) +{ + TRACE( "(%d, %#lx, %lu, %p, %lu, %p, %p, %p)\n", type, flags, props_len, props, filters_len, filters, objs_len, objs ); + return DevGetObjectsEx( type, flags, props_len, props, filters_len, filters, 0, NULL, objs_len, objs ); +} + +HRESULT WINAPI DevGetObjectsEx( DEV_OBJECT_TYPE type, ULONG flags, ULONG props_len, const DEVPROPCOMPKEY *props, + ULONG filters_len, const DEVPROP_FILTER_EXPRESSION *filters, ULONG params_len, + const DEV_QUERY_PARAMETER *params, ULONG *objs_len, const DEV_OBJECT **objs ) +{ + ULONG objects_len = 0, valid_flags = DevQueryFlagAllProperties | DevQueryFlagLocalize; + DEV_OBJECT *objects = NULL; + HRESULT hr = S_OK; + HKEY iface_key; + + TRACE( "(%d, %#lx, %lu, %p, %lu, %p, %lu, %p, %p, %p)\n", type, flags, props_len, props, filters_len, filters, + params_len, params, objs_len, objs ); + + if (!!props_len != !!props || !!filters_len != !!filters || !!params_len != !!params || (flags & ~valid_flags)) + return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); + if (props || flags & DevQueryFlagAllProperties) + FIXME( "Object properties are not supported!\n" ); + if (filters) + FIXME( "Query filters are not supported!\n" ); + if (params) + FIXME( "Query parameters are not supported!\n" ); + + *objs = NULL; + *objs_len = 0; + + switch (type) + { + case DevObjectTypeDeviceInterface: + case DevObjectTypeDeviceInterfaceDisplay: + { + DWORD i; + + if (!(iface_key = SetupDiOpenClassRegKeyExW( NULL, KEY_ENUMERATE_SUB_KEYS, DIOCR_INTERFACE, NULL, NULL ))) + return HRESULT_FROM_WIN32( GetLastError() ); + + for (i = 0; SUCCEEDED( hr ); i++) + { + char buffer[sizeof( SP_DEVICE_INTERFACE_DETAIL_DATA_W ) + MAX_PATH * sizeof( WCHAR )]; + SP_DEVICE_INTERFACE_DATA iface = {.cbSize = sizeof( iface )}; + SP_DEVICE_INTERFACE_DETAIL_DATA_W *detail = (void *)buffer; + HDEVINFO set = INVALID_HANDLE_VALUE; + WCHAR iface_guid_str[40]; + DWORD ret, len, j; + GUID iface_class; + + len = ARRAY_SIZE( iface_guid_str ); + ret = RegEnumKeyExW( iface_key, i, iface_guid_str, &len, NULL, NULL, NULL, NULL ); + if (ret) + { + hr = (ret == ERROR_NO_MORE_ITEMS) ? S_OK : HRESULT_FROM_WIN32( ret ); + break; + } + + iface_guid_str[37] = '\0'; + if (!UuidFromStringW( &iface_guid_str[1], &iface_class )) + { + set = SetupDiGetClassDevsW( &iface_class, NULL, NULL, DIGCF_DEVICEINTERFACE ); + if (set == INVALID_HANDLE_VALUE) hr = HRESULT_FROM_WIN32( GetLastError() ); + } + else + ERR( "Could not parse device interface GUID %s\n", debugstr_w( iface_guid_str ) ); + + for (j = 0; SUCCEEDED( hr ) && SetupDiEnumDeviceInterfaces( set, NULL, &iface_class, j, &iface ); j++) + { + detail->cbSize = sizeof( *detail ); + if (!SetupDiGetDeviceInterfaceDetailW( set, &iface, detail, sizeof( buffer ), NULL, NULL )) continue; + if (!dev_objects_append_iface( &objects, &objects_len, detail->DevicePath, type )) hr = E_OUTOFMEMORY; + } + + if (set != INVALID_HANDLE_VALUE) + SetupDiDestroyDeviceInfoList( set ); + } + RegCloseKey( iface_key ); + break; + } + case DevObjectTypeDeviceContainer: + case DevObjectTypeDevice: + case DevObjectTypeDeviceInterfaceClass: + case DevObjectTypeAEP: + case DevObjectTypeAEPContainer: + case DevObjectTypeDeviceInstallerClass: + case DevObjectTypeDeviceContainerDisplay: + case DevObjectTypeAEPService: + case DevObjectTypeDevicePanel: + FIXME( "Unsupported DEV_OBJECT_TYPE: %d\n", type ); + default: + break; + } + + if (hr == S_OK) + { + *objs = objects; + *objs_len = objects_len; + } + else + DevFreeObjects( objects_len, objects ); + + return hr; }
void WINAPI DevFreeObjects( ULONG objs_len, const DEV_OBJECT *objs ) { - FIXME( "(%lu, %p): stub!\n", objs_len, objs ); + DEV_OBJECT *objects = (DEV_OBJECT *)objs; + ULONG i; + + TRACE( "(%lu, %p)\n", objs_len, objs ); + + for (i = 0; i < objs_len; i++) + free( (void *)objects[i].pszObjectId ); + free( objects ); return; } diff --git a/dlls/cfgmgr32/tests/cfgmgr32.c b/dlls/cfgmgr32/tests/cfgmgr32.c index b5bdf4f4023..308ee5012f4 100644 --- a/dlls/cfgmgr32/tests/cfgmgr32.c +++ b/dlls/cfgmgr32/tests/cfgmgr32.c @@ -521,49 +521,49 @@ static void test_DevGetObjects( void ) ULONG i, len = 0;
hr = DevGetObjects( DevObjectTypeDeviceInterface, DevQueryFlagNone, 1, NULL, 0, NULL, &len, &objects ); - todo_wine ok( hr == HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ), "got hr %#lx\n", hr ); - todo_wine ok( len == 0, "got len %lu\n", len ); - todo_wine ok( !objects, "got objects %p\n", objects ); + ok( hr == HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ), "got hr %#lx\n", hr ); + ok( len == 0, "got len %lu\n", len ); + ok( !objects, "got objects %p\n", objects );
hr = DevGetObjects( DevObjectTypeDeviceInterface, DevQueryFlagNone, 0, NULL, 1, NULL, &len, &objects ); - todo_wine ok( hr == HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ), "got hr %#lx\n", hr ); - todo_wine ok( len == 0, "got len %lu\n", len ); - todo_wine ok( !objects, "got objects %p\n", objects ); + ok( hr == HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ), "got hr %#lx\n", hr ); + ok( len == 0, "got len %lu\n", len ); + ok( !objects, "got objects %p\n", objects );
hr = DevGetObjects( DevObjectTypeDeviceInterface, DevQueryFlagNone, 0, (void *)0xdeadbeef, 0, NULL, &len, &objects ); - todo_wine ok( hr == HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ), "got hr %#lx\n", hr ); - todo_wine ok( len == 0, "got len %lu\n", len ); - todo_wine ok( !objects, "got objects %p\n", objects ); + ok( hr == HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ), "got hr %#lx\n", hr ); + ok( len == 0, "got len %lu\n", len ); + ok( !objects, "got objects %p\n", objects );
hr = DevGetObjects( DevObjectTypeDeviceInterface, DevQueryFlagNone, 0, NULL, 0, (void *)0xdeadbeef, &len, &objects ); - todo_wine ok( hr == HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ), "got hr %#lx\n", hr ); - todo_wine ok( len == 0, "got len %lu\n", len ); - todo_wine ok( !objects, "got objects %p\n", objects ); + ok( hr == HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ), "got hr %#lx\n", hr ); + ok( len == 0, "got len %lu\n", len ); + ok( !objects, "got objects %p\n", objects );
hr = DevGetObjects( DevObjectTypeDeviceInterface, DevQueryFlagUpdateResults, 0, NULL, 0, (void *)0xdeadbeef, &len, &objects ); - todo_wine ok( hr == HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ), "got hr %#lx\n", hr ); - todo_wine ok( len == 0, "got len %lu\n", len ); - todo_wine ok( !objects, "got objects %p\n", objects ); + ok( hr == HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ), "got hr %#lx\n", hr ); + ok( len == 0, "got len %lu\n", len ); + ok( !objects, "got objects %p\n", objects );
hr = DevGetObjects( DevObjectTypeDeviceInterface, DevQueryFlagAsyncClose, 0, NULL, 0, (void *)0xdeadbeef, &len, &objects ); - todo_wine ok( hr == HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ), "got hr %#lx\n", hr ); - todo_wine ok( len == 0, "got len %lu\n", len ); - todo_wine ok( !objects, "got objects %p\n", objects ); + ok( hr == HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ), "got hr %#lx\n", hr ); + ok( len == 0, "got len %lu\n", len ); + ok( !objects, "got objects %p\n", objects );
hr = DevGetObjects( DevObjectTypeDeviceInterface, 0xdeadbeef, 0, NULL, 0, (void *)0xdeadbeef, &len, &objects ); - todo_wine ok( hr == HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ), "got hr %#lx\n", hr ); - todo_wine ok( len == 0, "got len %lu\n", len ); - todo_wine ok( !objects, "got objects %p\n", objects ); + ok( hr == HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ), "got hr %#lx\n", hr ); + ok( len == 0, "got len %lu\n", len ); + ok( !objects, "got objects %p\n", objects );
hr = DevGetObjects( DevObjectTypeUnknown, DevQueryFlagNone, 0, NULL, 0, NULL, &len, &objects ); - todo_wine ok( hr == S_OK, "got hr %#lx\n", hr ); - todo_wine ok( len == 0, "got len %lu\n", len ); - todo_wine ok( !objects, "got objects %p\n", objects ); + ok( hr == S_OK, "got hr %#lx\n", hr ); + ok( len == 0, "got len %lu\n", len ); + ok( !objects, "got objects %p\n", objects );
hr = DevGetObjects( 0xdeadbeef, DevQueryFlagNone, 0, NULL, 0, NULL, &len, &objects ); - todo_wine ok( hr == S_OK, "got hr %#lx\n", hr ); - todo_wine ok( len == 0, "got len %lu\n", len ); - todo_wine ok( !objects, "got objects %p\n", objects ); + ok( hr == S_OK, "got hr %#lx\n", hr ); + ok( len == 0, "got len %lu\n", len ); + ok( !objects, "got objects %p\n", objects );
set = SetupDiCreateDeviceInfoListExW( NULL, NULL, NULL, NULL ); ok( set != INVALID_HANDLE_VALUE, "SetupDiCreateDeviceInfoListExW failed: %lu\n", GetLastError() ); @@ -575,7 +575,7 @@ static void test_DevGetObjects( void )
winetest_push_context( "test_cases[%lu]", i ); hr = DevGetObjects( test_cases[i].object_type, DevQueryFlagAllProperties, 0, NULL, 0, NULL, &len, &objects ); - todo_wine ok( hr == S_OK, "got hr %#lx\n", hr ); + ok( hr == S_OK, "got hr %#lx\n", hr ); for (j = 0; j < len; j++) { ULONG rem_props = test_cases[i].props_len, k;
I have an additional `DevObjectTypeAEPProtocol` enum after this one.
```suggestion:-0+0 DEVPROPTYPE Type; ```
```suggestion:-0+0 DEVPROP_OPERATOR_NOT_EXISTS = DEVPROP_OPERATOR_MODIFIER_NOT | DEVPROP_OPERATOR_EXISTS, ```
```suggestion:-1+0 DEVPROP_OPERATOR_EQUALS_IGNORE_CASE = DEVPROP_OPERATOR_EQUALS | DEVPROP_OPERATOR_MODIFIER_IGNORE_CASE, DEVPROP_OPERATOR_NOT_EQUALS_IGNORE_CASE = DEVPROP_OPERATOR_MODIFIER_NOT | DEVPROP_OPERATOR_EQUALS | DEVPROP_OPERATOR_MODIFIER_IGNORE_CASE, ```
Nitpick but lets keep it in the same order as the name (not + ignore_case + equals could be confusing).
```suggestion:-0+0 DECLARE_HANDLE(HDEVQUERY); typedef HDEVQUERY *PHDEVQUERY; ```
Strictly speaking HDEVQUERY is not the same type as HANDLE.
Fwiw in general output parameters aren't checked in the error cases, because it shouldn't matter what happens to them. For cases where it *does* matter (like whether they should be zeroed), the params are set before calling to some arbitrary and different value (0xdeadbeef) from the expected.
In particular, at this point these tests succeed already because the stub doesn't change the parameter values. Same thing below.
```suggestion:-3+0 hr = DevGetObjects( DevObjectTypeDeviceInterface, DevQueryFlagNone, 1, NULL, 0, NULL, &len, &objects ); todo_wine ok( hr == HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ), "got hr %#lx\n", hr ); ```