[PATCH] user32/class: Automatically load the module implementing redirected class

Nikolay Sivov nsivov at codeweavers.com
Wed Feb 7 07:19:49 CST 2018


Signed-off-by: Nikolay Sivov <nsivov at codeweavers.com>
---

When the program does not load comctl32.dll, linking to it or dynamically,
new builtin controls behavior still could be easily made available with an
external manifest. Very simple test program to demostrate that is attached
to https://bugs.winehq.org/show_bug.cgi?id=30103. Tests show some differences
between system versions, but important part is consistent - module is loaded
right on context activation, or lazily when first window/class call is made.

 dlls/user32/class.c       |   5 ++
 dlls/user32/tests/class.c | 223 +++++++++++++++++++++++++++++++++-------------
 2 files changed, 167 insertions(+), 61 deletions(-)

diff --git a/dlls/user32/class.c b/dlls/user32/class.c
index 441d5cb132..dce9bf2269 100644
--- a/dlls/user32/class.c
+++ b/dlls/user32/class.c
@@ -336,6 +336,7 @@ const WCHAR *CLASS_GetVersionedName( const WCHAR *name, UINT *basename_offset )
         ULONG module_len;
         ULONG module_offset;
     } *wndclass;
+    const WCHAR *module;
 
     if (basename_offset)
         *basename_offset = 0;
@@ -354,6 +355,10 @@ const WCHAR *CLASS_GetVersionedName( const WCHAR *name, UINT *basename_offset )
     if (basename_offset)
         *basename_offset = wndclass->name_len / sizeof(WCHAR) - strlenW(name);
 
+    module = (const WCHAR *)((BYTE *)data.lpSectionBase + wndclass->module_offset);
+    if (!GetModuleHandleW( module ))
+        LoadLibraryW( module );
+
     return (const WCHAR *)((BYTE *)wndclass + wndclass->name_offset);
 }
 
diff --git a/dlls/user32/tests/class.c b/dlls/user32/tests/class.c
index 9ae306e090..2ff3649b6e 100644
--- a/dlls/user32/tests/class.c
+++ b/dlls/user32/tests/class.c
@@ -38,6 +38,42 @@
 
 #define IS_WNDPROC_HANDLE(x) (((ULONG_PTR)(x) >> 16) == (~0u >> 16))
 
+#ifdef __i386__
+#define ARCH "x86"
+#elif defined __x86_64__
+#define ARCH "amd64"
+#elif defined __arm__
+#define ARCH "arm"
+#elif defined __aarch64__
+#define ARCH "arm64"
+#else
+#define ARCH "none"
+#endif
+
+static const char comctl32_manifest[] =
+"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
+"<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">\n"
+"  <assemblyIdentity\n"
+"      type=\"win32\"\n"
+"      name=\"Wine.User32.Tests\"\n"
+"      version=\"1.0.0.0\"\n"
+"      processorArchitecture=\"" ARCH "\"\n"
+"  />\n"
+"<description>Wine comctl32 test suite</description>\n"
+"<dependency>\n"
+"  <dependentAssembly>\n"
+"    <assemblyIdentity\n"
+"        type=\"win32\"\n"
+"        name=\"microsoft.windows.common-controls\"\n"
+"        version=\"6.0.0.0\"\n"
+"        processorArchitecture=\"" ARCH "\"\n"
+"        publicKeyToken=\"6595b64144ccf1df\"\n"
+"        language=\"*\"\n"
+"    />\n"
+"</dependentAssembly>\n"
+"</dependency>\n"
+"</assembly>\n";
+
 static LRESULT WINAPI ClassTest_WndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
 {
     if (msg == WM_NCCREATE) return 1;
@@ -1041,6 +1077,45 @@ static void test_icons(void)
     DestroyWindow(hwnd);
 }
 
+static void create_manifest_file(const char *filename, const char *manifest)
+{
+    WCHAR path[MAX_PATH];
+    HANDLE file;
+    DWORD size;
+
+    MultiByteToWideChar( CP_ACP, 0, filename, -1, path, MAX_PATH );
+    file = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+    ok(file != INVALID_HANDLE_VALUE, "CreateFile failed: %u\n", GetLastError());
+    WriteFile(file, manifest, strlen(manifest), &size, NULL);
+    CloseHandle(file);
+}
+
+static HANDLE create_test_actctx(const char *file)
+{
+    WCHAR path[MAX_PATH];
+    ACTCTXW actctx;
+    HANDLE handle;
+
+    MultiByteToWideChar(CP_ACP, 0, file, -1, path, MAX_PATH);
+    memset(&actctx, 0, sizeof(ACTCTXW));
+    actctx.cbSize = sizeof(ACTCTXW);
+    actctx.lpSource = path;
+
+    handle = CreateActCtxW(&actctx);
+    ok(handle != INVALID_HANDLE_VALUE, "failed to create context, error %u\n", GetLastError());
+
+    ok(actctx.cbSize == sizeof(actctx), "cbSize=%d\n", actctx.cbSize);
+    ok(actctx.dwFlags == 0, "dwFlags=%d\n", actctx.dwFlags);
+    ok(actctx.lpSource == path, "lpSource=%p\n", actctx.lpSource);
+    ok(actctx.wProcessorArchitecture == 0, "wProcessorArchitecture=%d\n", actctx.wProcessorArchitecture);
+    ok(actctx.wLangId == 0, "wLangId=%d\n", actctx.wLangId);
+    ok(actctx.lpAssemblyDirectory == NULL, "lpAssemblyDirectory=%p\n", actctx.lpAssemblyDirectory);
+    ok(actctx.lpResourceName == NULL, "lpResourceName=%p\n", actctx.lpResourceName);
+    ok(actctx.lpApplicationName == NULL, "lpApplicationName=%p\n", actctx.lpApplicationName);
+    ok(actctx.hModule == NULL, "hModule=%p\n", actctx.hModule);
+
+    return handle;
+}
 static void test_comctl32_class( const char *name )
 {
     WNDCLASSA wcA;
@@ -1050,23 +1125,82 @@ static void test_comctl32_class( const char *name )
     WCHAR nameW[20];
     HWND hwnd;
 
-    module = GetModuleHandleA( "comctl32" );
-    ok( !module, "comctl32 already loaded\n" );
-    ret = GetClassInfoA( 0, name, &wcA );
-    ok( ret || broken(!ret) /* <= winxp */, "GetClassInfoA failed for %s\n", name );
-    if (!ret) return;
-    MultiByteToWideChar( CP_ACP, 0, name, -1, nameW, sizeof(nameW)/sizeof(WCHAR) );
-    ret = GetClassInfoW( 0, nameW, &wcW );
-    ok( ret, "GetClassInfoW failed for %s\n", name );
-    module = GetModuleHandleA( "comctl32" );
-    ok( module != 0, "comctl32 not loaded\n" );
-    FreeLibrary( module );
-    module = GetModuleHandleA( "comctl32" );
-    ok( !module, "comctl32 still loaded\n" );
-    hwnd = CreateWindowA( name, "test", WS_OVERLAPPEDWINDOW, 0, 0, 10, 10, NULL, NULL, NULL, 0 );
-    ok( hwnd != 0, "failed to create window for %s\n", name );
-    module = GetModuleHandleA( "comctl32" );
-    ok( module != 0, "comctl32 not loaded\n" );
+    if (name[0] == '!')
+    {
+        char path[MAX_PATH];
+        ULONG_PTR cookie;
+        HANDLE context;
+
+        name++;
+
+        GetTempPathA(sizeof(path)/sizeof(path[0]), path);
+        strcat(path, "comctl32_class.manifest");
+
+        create_manifest_file(path, comctl32_manifest);
+        context = create_test_actctx(path);
+        ret = DeleteFileA(path);
+        ok(ret, "Failed to delete manifest file, error %d.\n", GetLastError());
+
+        module = GetModuleHandleA( "comctl32" );
+        ok( !module, "comctl32 already loaded\n" );
+
+        ret = ActivateActCtx(context, &cookie);
+        ok(ret, "Failed to activate context.\n");
+
+        /* Some systems load modules during context activation. In this case skip the rest of the test. */
+        module = GetModuleHandleA( "comctl32" );
+        ok( !module || broken(module != NULL) /* Vista/Win7 */, "comctl32 already loaded\n" );
+        if (module)
+        {
+            win_skip("Module loaded during context activation. Skipping tests.\n");
+            goto skiptest;
+        }
+
+        ret = GetClassInfoA( 0, name, &wcA );
+        ok( ret || broken(!ret) /* WinXP */, "GetClassInfoA failed for %s\n", name );
+        if (!ret)
+            goto skiptest;
+
+        MultiByteToWideChar( CP_ACP, 0, name, -1, nameW, sizeof(nameW)/sizeof(WCHAR) );
+        ret = GetClassInfoW( 0, nameW, &wcW );
+        ok( ret, "GetClassInfoW failed for %s\n", name );
+        module = GetModuleHandleA( "comctl32" );
+        ok( module != 0, "comctl32 not loaded\n" );
+        FreeLibrary( module );
+        module = GetModuleHandleA( "comctl32" );
+        ok( !module, "comctl32 still loaded\n" );
+        hwnd = CreateWindowA( name, "test", WS_OVERLAPPEDWINDOW, 0, 0, 10, 10, NULL, NULL, NULL, 0 );
+        ok( hwnd != 0, "failed to create window for %s\n", name );
+        module = GetModuleHandleA( "comctl32" );
+        ok( module != 0, "comctl32 not loaded\n" );
+        DestroyWindow( hwnd );
+
+    skiptest:
+        ret = DeactivateActCtx(0, cookie);
+        ok(ret, "Failed to deactivate context.\n");
+        ReleaseActCtx(context);
+    }
+    else
+    {
+        module = GetModuleHandleA( "comctl32" );
+        ok( !module, "comctl32 already loaded\n" );
+        ret = GetClassInfoA( 0, name, &wcA );
+        ok( ret || broken(!ret) /* <= winxp */, "GetClassInfoA failed for %s\n", name );
+        if (!ret) return;
+        MultiByteToWideChar( CP_ACP, 0, name, -1, nameW, sizeof(nameW)/sizeof(WCHAR) );
+        ret = GetClassInfoW( 0, nameW, &wcW );
+        ok( ret, "GetClassInfoW failed for %s\n", name );
+        module = GetModuleHandleA( "comctl32" );
+        ok( module != 0, "comctl32 not loaded\n" );
+        FreeLibrary( module );
+        module = GetModuleHandleA( "comctl32" );
+        ok( !module, "comctl32 still loaded\n" );
+        hwnd = CreateWindowA( name, "test", WS_OVERLAPPEDWINDOW, 0, 0, 10, 10, NULL, NULL, NULL, 0 );
+        ok( hwnd != 0, "failed to create window for %s\n", name );
+        module = GetModuleHandleA( "comctl32" );
+        ok( module != 0, "comctl32 not loaded\n" );
+        DestroyWindow( hwnd );
+    }
 }
 
 /* verify that comctl32 classes are automatically loaded by user32 */
@@ -1099,7 +1233,9 @@ static void test_comctl32_classes(void)
         TOOLTIPS_CLASSA,
         TRACKBAR_CLASSA,
         WC_TREEVIEWA,
-        UPDOWN_CLASSA
+        UPDOWN_CLASSA,
+        "!Button",
+        "!Edit",
     };
 
     winetest_get_mainargs( &argv );
@@ -1162,46 +1298,6 @@ static void test_IME(void)
     ok(!lstrcmpiA(ptr, "user32.dll") || !lstrcmpiA(ptr, "ntdll.dll"), "IME window proc implemented in %s\n", ptr);
 }
 
-static void create_manifest_file(const char *filename, const char *manifest)
-{
-    WCHAR path[MAX_PATH];
-    HANDLE file;
-    DWORD size;
-
-    MultiByteToWideChar( CP_ACP, 0, filename, -1, path, MAX_PATH );
-    file = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
-    ok(file != INVALID_HANDLE_VALUE, "CreateFile failed: %u\n", GetLastError());
-    WriteFile(file, manifest, strlen(manifest), &size, NULL);
-    CloseHandle(file);
-}
-
-static HANDLE create_test_actctx(const char *file)
-{
-    WCHAR path[MAX_PATH];
-    ACTCTXW actctx;
-    HANDLE handle;
-
-    MultiByteToWideChar(CP_ACP, 0, file, -1, path, MAX_PATH);
-    memset(&actctx, 0, sizeof(ACTCTXW));
-    actctx.cbSize = sizeof(ACTCTXW);
-    actctx.lpSource = path;
-
-    handle = CreateActCtxW(&actctx);
-    ok(handle != INVALID_HANDLE_VALUE, "failed to create context, error %u\n", GetLastError());
-
-    ok(actctx.cbSize == sizeof(actctx), "cbSize=%d\n", actctx.cbSize);
-    ok(actctx.dwFlags == 0, "dwFlags=%d\n", actctx.dwFlags);
-    ok(actctx.lpSource == path, "lpSource=%p\n", actctx.lpSource);
-    ok(actctx.wProcessorArchitecture == 0, "wProcessorArchitecture=%d\n", actctx.wProcessorArchitecture);
-    ok(actctx.wLangId == 0, "wLangId=%d\n", actctx.wLangId);
-    ok(actctx.lpAssemblyDirectory == NULL, "lpAssemblyDirectory=%p\n", actctx.lpAssemblyDirectory);
-    ok(actctx.lpResourceName == NULL, "lpResourceName=%p\n", actctx.lpResourceName);
-    ok(actctx.lpApplicationName == NULL, "lpApplicationName=%p\n", actctx.lpApplicationName);
-    ok(actctx.hModule == NULL, "hModule=%p\n", actctx.hModule);
-
-    return handle;
-}
-
 static void test_actctx_classes(void)
 {
     static const char main_manifest[] =
@@ -1220,10 +1316,15 @@ static void test_actctx_classes(void)
     HINSTANCE hinst;
     char buff[64];
     HWND hwnd;
+    char path[MAX_PATH];
+
+    GetTempPathA(sizeof(path)/sizeof(path[0]), path);
+    strcat(path, "actctx_classes.manifest");
 
-    create_manifest_file("main.manifest", main_manifest);
-    context = create_test_actctx("main.manifest");
-    DeleteFileA("main.manifest");
+    create_manifest_file(path, main_manifest);
+    context = create_test_actctx(path);
+    ret = DeleteFileA(path);
+    ok(ret, "Failed to delete manifest file, error %d.\n", GetLastError());
 
     ret = ActivateActCtx(context, &cookie);
     ok(ret, "Failed to activate context.\n");
-- 
2.15.1




More information about the wine-devel mailing list