When comctl32 v6 is loaded and v6 manifest is disabled, comctl32 v5 should be used when creating a common control window. However, before this patch, GetModuleHandleW(L"comctl32") return a handle to comctl32 v6 in this case, so comctl32 v5 doesn't get loaded and creating comctl32 v5 windows fails. We need to check if comctl32 v5 is actually loaded by using GetModuleHandleW() with the absolute path of the comctl32 v5 dll.
Fix Word 2010 file open dialog doesn't show a listview.
From: Zhiyi Zhang zzhang@codeweavers.com
--- dlls/comctl32/tests/misc.c | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/dlls/comctl32/tests/misc.c b/dlls/comctl32/tests/misc.c index 29ba90cffb2..064f7d06da3 100644 --- a/dlls/comctl32/tests/misc.c +++ b/dlls/comctl32/tests/misc.c @@ -45,6 +45,7 @@ static BOOL (WINAPI *pRemoveWindowSubclass)(HWND, SUBCLASSPROC, UINT_PTR); static LRESULT (WINAPI *pDefSubclassProc)(HWND, UINT, WPARAM, LPARAM);
static HMODULE hComctl32; +static BOOL todo_create_v5_window;
/* For message tests */ enum seq_index @@ -415,7 +416,10 @@ static void check_class( const char *name, int must_exist, UINT style, UINT igno ok( !wc.hInstance, "System class %s has hInstance %p\n", name, wc.hInstance );
hwnd = CreateWindowA(name, 0, 0, 0, 0, 0, 0, 0, NULL, GetModuleHandleA(NULL), 0); + todo_wine_if(todo_create_v5_window) ok( hwnd != NULL, "Failed to create window for class %s.\n", name ); + if (!hwnd) + return; GetClassNameA(hwnd, buff, ARRAY_SIZE(buff)); ok( !strcmp(name, buff), "Unexpected class name %s, expected %s.\n", buff, name );
@@ -1448,5 +1452,10 @@ START_TEST(misc) test_CCM_SETVERSION(TRUE);
unload_v6_module(ctx_cookie, hCtx); + + /* Now that v6 manifest is deactivated, test that comctl32 v5 windows can be created */ + todo_create_v5_window = TRUE; + test_comctl32_classes(FALSE); + FreeLibrary(hComctl32); }
From: Zhiyi Zhang zzhang@codeweavers.com
When comctl32 v6 is loaded and v6 manifest is disabled, comctl32 v5 should be used when creating a common control window. However, before this patch, GetModuleHandleW(L"comctl32") return a handle to comctl32 v6 in this case, so comctl32 v5 doesn't get loaded and creating comctl32 v5 windows fails. We need to check if comctl32 v5 is actually loaded by using GetModuleHandleW() with the absolute path of the comctl32 v5 dll.
Fix Word 2010 file open dialog doesn't show a listview. --- dlls/comctl32/tests/misc.c | 5 ----- dlls/user32/class.c | 11 ++++++----- 2 files changed, 6 insertions(+), 10 deletions(-)
diff --git a/dlls/comctl32/tests/misc.c b/dlls/comctl32/tests/misc.c index 064f7d06da3..c61b71b6309 100644 --- a/dlls/comctl32/tests/misc.c +++ b/dlls/comctl32/tests/misc.c @@ -45,7 +45,6 @@ static BOOL (WINAPI *pRemoveWindowSubclass)(HWND, SUBCLASSPROC, UINT_PTR); static LRESULT (WINAPI *pDefSubclassProc)(HWND, UINT, WPARAM, LPARAM);
static HMODULE hComctl32; -static BOOL todo_create_v5_window;
/* For message tests */ enum seq_index @@ -416,10 +415,7 @@ static void check_class( const char *name, int must_exist, UINT style, UINT igno ok( !wc.hInstance, "System class %s has hInstance %p\n", name, wc.hInstance );
hwnd = CreateWindowA(name, 0, 0, 0, 0, 0, 0, 0, NULL, GetModuleHandleA(NULL), 0); - todo_wine_if(todo_create_v5_window) ok( hwnd != NULL, "Failed to create window for class %s.\n", name ); - if (!hwnd) - return; GetClassNameA(hwnd, buff, ARRAY_SIZE(buff)); ok( !strcmp(name, buff), "Unexpected class name %s, expected %s.\n", buff, name );
@@ -1454,7 +1450,6 @@ START_TEST(misc) unload_v6_module(ctx_cookie, hCtx);
/* Now that v6 manifest is deactivated, test that comctl32 v5 windows can be created */ - todo_create_v5_window = TRUE; test_comctl32_classes(FALSE);
FreeLibrary(hComctl32); diff --git a/dlls/user32/class.c b/dlls/user32/class.c index 43636975e89..a754720caec 100644 --- a/dlls/user32/class.c +++ b/dlls/user32/class.c @@ -198,11 +198,6 @@ void get_class_version( UNICODE_STRING *name, UNICODE_STRING *version, BOOL load
if (IS_INTRESOURCE( name->Buffer ) || is_builtin_class( name->Buffer )) return;
- if (is_comctl32_class( name->Buffer )) - { - if (load && !(hmod = GetModuleHandleW( L"comctl32" ))) hmod = LoadLibraryW( L"comctl32" ); - } - if (!RtlFindActivationContextSectionString( 0, NULL, ACTIVATION_CONTEXT_SECTION_WINDOW_CLASS_REDIRECTION, name, &data )) { struct wndclass_redirect_data @@ -228,6 +223,12 @@ void get_class_version( UNICODE_STRING *name, UNICODE_STRING *version, BOOL load name->Length = wndclass->name_len; name->Buffer[name->Length / sizeof(WCHAR)] = 0; } + /* comctl32 v5 */ + else if (load && is_comctl32_class( name->Buffer )) + { + hmod = GetModuleHandleW( L"C:\windows\system32\comctl32.dll" ); + if (!hmod) hmod = LoadLibraryW( L"C:\windows\system32\comctl32.dll" ); + }
if (load && hmod) {
From: Zhiyi Zhang zzhang@codeweavers.com
--- dlls/comctl32/tests/imagelist.c | 71 +++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+)
diff --git a/dlls/comctl32/tests/imagelist.c b/dlls/comctl32/tests/imagelist.c index 3b547732fd5..8647fd5a62a 100644 --- a/dlls/comctl32/tests/imagelist.c +++ b/dlls/comctl32/tests/imagelist.c @@ -36,6 +36,7 @@ #include "initguid.h" #include "commoncontrols.h" #include "shellapi.h" +#include "shlwapi.h"
#include "wine/test.h" #include "v6util.h" @@ -2896,6 +2897,75 @@ static void init_functions(void) #undef X2 }
+static void test_imagelist_interop(void) +{ + HRESULT (WINAPI *pDllGetVersion_v5)(DLLVERSIONINFO *); + HRESULT (WINAPI *pDllGetVersion_v6)(DLLVERSIONINFO *); + HIMAGELIST (WINAPI *pImageList_Create_v5)(int, int, UINT, int, int); + BOOL (WINAPI *pImageList_Destroy_v5)(HIMAGELIST); + BOOL (WINAPI *pImageList_GetIconSize_v5)(HIMAGELIST, int *, int *); + DLLVERSIONINFO info; + HMODULE comctl32_v5; + HIMAGELIST himl; + HRESULT hr; + INT cx, cy; + BOOL ret; + + comctl32_v5 = LoadLibraryW(L"C:\windows\system32\comctl32.dll"); + ok(!!comctl32_v5, "Failed to load comctl32 v5.\n"); + + pDllGetVersion_v5 = (void *)GetProcAddress(comctl32_v5, "DllGetVersion"); + pDllGetVersion_v6 = (void *)GetProcAddress(GetModuleHandleA("comctl32.dll"), "DllGetVersion"); + pImageList_Create_v5 = (void *)GetProcAddress(comctl32_v5, "ImageList_Create"); + pImageList_Destroy_v5 = (void *)GetProcAddress(comctl32_v5, "ImageList_Destroy"); + pImageList_GetIconSize_v5 = (void *)GetProcAddress(comctl32_v5, "ImageList_GetIconSize"); + + /* Make sure we are testing the correct versions of comctl32 */ + info.cbSize = sizeof(info); + hr = pDllGetVersion_v5(&info); + ok(hr == S_OK, "DllGetVersion failed, hr %#lx.\n", hr); + ok(info.dwMajorVersion == 5, "Got unexpected major version %lu.\n", info.dwMajorVersion); + + info.cbSize = sizeof(info); + hr = pDllGetVersion_v6(&info); + ok(hr == S_OK, "DllGetVersion failed, hr %#lx.\n", hr); + ok(info.dwMajorVersion == 6, "Got unexpected major version %lu.\n", info.dwMajorVersion); + + /* Create a v5 imagelist and use it with v6 imagelist functions */ + himl = pImageList_Create_v5(1, 1, ILC_COLOR, 0, 1); + ok(!!himl, "ImageList_Create failed.\n"); + + cx = 0; + cy = 0; + ret = pImageList_GetIconSize(himl, &cx, &cy); + todo_wine + ok(ret, "ImageList_GetIconSize failed.\n"); + todo_wine + ok(cx == 1 && cy == 1, "Got unexpected size %dx%d.\n", cx, cy); + + ret = pImageList_Destroy(himl); + todo_wine + ok(ret, "ImageList_Destroy failed.\n"); + + /* Create a v6 imagelist and use it with v5 imagelist functions */ + himl = pImageList_Create(1, 1, ILC_COLOR, 0, 1); + ok(!!himl, "ImageList_Create failed.\n"); + + cx = 0; + cy = 0; + ret = pImageList_GetIconSize_v5(himl, &cx, &cy); + todo_wine + ok(ret, "ImageList_GetIconSize failed.\n"); + todo_wine + ok(cx == 1 && cy == 1, "Got unexpected size %dx%d.\n", cx, cy); + + ret = pImageList_Destroy_v5(himl); + todo_wine + ok(ret, "ImageList_Destroy failed.\n"); + + FreeLibrary(comctl32_v5); +} + START_TEST(imagelist) { ULONG_PTR ctx_cookie; @@ -2945,6 +3015,7 @@ START_TEST(imagelist) test_ImageList_DrawIndirect(); test_shell_imagelist(); test_iimagelist(); + test_imagelist_interop();
test_IImageList_Add_Remove(); test_IImageList_Get_SetImageCount();
From: Zhiyi Zhang zzhang@codeweavers.com
Use a magic value to check if an image list is valid instead of using vtable addresses. So that an image list from comctl32 v5 can be used with comctl32 v6 image list functions and vice versa.
Fix missing icons in the listview window of the Word 2010 file open dialog. --- dlls/comctl32/imagelist.c | 5 ++++- dlls/comctl32/tests/imagelist.c | 6 ------ 2 files changed, 4 insertions(+), 7 deletions(-)
diff --git a/dlls/comctl32/imagelist.c b/dlls/comctl32/imagelist.c index 493aebd0b45..93c502ec0bd 100644 --- a/dlls/comctl32/imagelist.c +++ b/dlls/comctl32/imagelist.c @@ -71,6 +71,7 @@ struct _IMAGELIST INT nOvlIdx[MAX_OVERLAYIMAGE]; /* 38: overlay images index */
/* not yet found out */ + DWORD magic; HBRUSH hbrBlend25; HBRUSH hbrBlend50; INT cInitial; @@ -3214,6 +3215,7 @@ static ULONG WINAPI ImageListImpl_Release(IImageList2 *iface) if (This->hbrBlend50) DeleteObject (This->hbrBlend50);
This->IImageList2_iface.lpVtbl = NULL; + This->magic = 0; Free(This->item_flags); Free(This); } @@ -3797,7 +3799,7 @@ static BOOL is_valid(HIMAGELIST himl) BOOL valid; __TRY { - valid = himl && himl->IImageList2_iface.lpVtbl == &ImageListImpl_Vtbl; + valid = himl && himl->magic == IMAGELIST_MAGIC; } __EXCEPT_PAGE_FAULT { @@ -3844,6 +3846,7 @@ static HRESULT ImageListImpl_CreateInstance(const IUnknown *pUnkOuter, REFIID ii if (!This) return E_OUTOFMEMORY;
This->IImageList2_iface.lpVtbl = &ImageListImpl_Vtbl; + This->magic = IMAGELIST_MAGIC; This->ref = 1;
ret = IImageList2_QueryInterface(&This->IImageList2_iface, iid, ppv); diff --git a/dlls/comctl32/tests/imagelist.c b/dlls/comctl32/tests/imagelist.c index 8647fd5a62a..c8a1a25b71d 100644 --- a/dlls/comctl32/tests/imagelist.c +++ b/dlls/comctl32/tests/imagelist.c @@ -2938,13 +2938,10 @@ static void test_imagelist_interop(void) cx = 0; cy = 0; ret = pImageList_GetIconSize(himl, &cx, &cy); - todo_wine ok(ret, "ImageList_GetIconSize failed.\n"); - todo_wine ok(cx == 1 && cy == 1, "Got unexpected size %dx%d.\n", cx, cy);
ret = pImageList_Destroy(himl); - todo_wine ok(ret, "ImageList_Destroy failed.\n");
/* Create a v6 imagelist and use it with v5 imagelist functions */ @@ -2954,13 +2951,10 @@ static void test_imagelist_interop(void) cx = 0; cy = 0; ret = pImageList_GetIconSize_v5(himl, &cx, &cy); - todo_wine ok(ret, "ImageList_GetIconSize failed.\n"); - todo_wine ok(cx == 1 && cy == 1, "Got unexpected size %dx%d.\n", cx, cy);
ret = pImageList_Destroy_v5(himl); - todo_wine ok(ret, "ImageList_Destroy failed.\n");
FreeLibrary(comctl32_v5);
On Windows GetModuleHandle("comctl32.dll") definitely reflects context. So doing:
GetModuleHandle("comctl32.dll") <- returns v5 module activate context + loadlibrary("comctl32.dll") + GetModuleHandle("comctl32.dll") <- returns v6 module deactivate + GetModuleHandle("comctl32.dll") <- returns v5 module
On current Wine, with or without this MR, last step returns v6 module for me.
Is it possible we should be fixing that instead of using full paths?
If the intent is to load v5 from user32 regardless of the context, we should just do that dance with fast-frame context push/pop.
On Mon Nov 3 02:24:40 2025 +0000, Nikolay Sivov wrote:
On Windows GetModuleHandle("comctl32.dll") definitely reflects context. So doing:
- GetModuleHandle("comctl32.dll") <- returns v5 module
- activate context + loadlibrary("comctl32.dll") +
GetModuleHandle("comctl32.dll") <- returns v6 module
- deactivate + GetModuleHandle("comctl32.dll") <- returns v5 module
On current Wine, with or without this MR, last step returns v6 module for me. Is it possible we should be fixing that instead of using full paths? If the intent is to load v5 from user32 regardless of the context, we should just do that dance with fast-frame context push/pop.
From [MSDN](https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloade...)
If lpModuleName does not include a path and there is more than one loaded module with the same base name and extension, you cannot predict which module handle will be returned. To work around this problem, you could specify a path, use side-by-side assemblies, or specify a memory location rather than a DLL name in the lpModuleName parameter.
So it seems like using only the DLL name is not recommended in this case. Thus, I am not sure we should make changes to GetModuleHandle(). For example, on Windows, there is also comctl32 v5 in WinSxS. What happens then? Using a full path is one of the workaround suggested by MSDN.
If the intent is to load v5 from user32 regardless of the context, we should just do that dance with fast-frame context push/pop.
We could. However, its effect is the same as using a full path. Using fast-frame context push/pop also requires two more function calls, making the window class loader a bit slower.
So it seems like using only the DLL name is not recommended in this case. Thus, I am not sure we should make changes to GetModuleHandle(). For example, on Windows, there is also comctl32 v5 in WinSxS. What happens then? Using a full path is one of the workaround suggested by MSDN.
Whatever is in winsxs, it must be specified in a manifest. Presumably that includes some special v5, we don't have that and won't support it at the moment anyway.
What I'm describing is clearly visible, for unqualified path, after context deactivation, original module is returned returned. To me that looks like an important difference if we are dealing with mix of v5 and v6 and the same process.
We could. However, its effect is the same as using a full path. Using fast-frame context push/pop also requires two more function calls, making the window class loader a bit slower.
Those functions set a few pointers and nothing else. But yes, we could use a full path, that leaves relative path case still not working right.
On Mon Nov 3 06:15:01 2025 +0000, Nikolay Sivov wrote:
So it seems like using only the DLL name is not recommended in this
case. Thus, I am not sure we should make changes to GetModuleHandle(). For example, on Windows, there is also comctl32 v5 in WinSxS. What happens then? Using a full path is one of the workaround suggested by MSDN. Whatever is in winsxs, it must be specified in a manifest. Presumably that includes some special v5, we don't have that and won't support it at the moment anyway. What I'm describing is clearly visible, for unqualified path, after context deactivation, original module is returned returned. To me that looks like an important difference if we are dealing with mix of v5 and v6 and the same process.
We could. However, its effect is the same as using a full path. Using
fast-frame context push/pop also requires two more function calls, making the window class loader a bit slower. Those functions set a few pointers and nothing else. But yes, we could use a full path, that leaves relative path case still not working right.
I see. You worry that user applications might use GetModuleHandle() with a relative path without activation contexts and expect the handle to be comctl32 v5, right?