[PATCH v8 0/3] MR10565: shcore: Implement Set/GetCurrentProcessExplicitAppUserModelID.
Wine Bug: https://bugs.winehq.org/show_bug.cgi?id=59625 Set/GetCurrentProcessExplicitAppUserModelID were stubs. Set returned S_OK but discarded the value, Get always returned NULL with E_NOTIMPL. This broke .NET WPF applications that set an AppUserModelID during startup and later retrieve it, causing "String argument cannot be null or empty" crashes. Implement both using a process-wide variable protected by a critical section. Callers receive CoTaskMemAlloc'd copies per the API contract. -- v8: shcore/tests: Add tests for Set/GetCurrentProcessExplicitAppUserModelID. shcore: Implement Set/GetCurrentProcessExplicitAppUserModelID. include: Add identity string length constants to appmodel.h. https://gitlab.winehq.org/wine/wine/-/merge_requests/10565
From: Robert Gerigk <Robert-Gerigk@online.de> Add APPLICATION_USER_MODEL_ID, PACKAGE_RELATIVE_APPLICATION_ID, and all PACKAGE_* length constants from the Windows SDK. APPLICATION_USER_MODEL_ID and PACKAGE_RELATIVE_APPLICATION_ID lengths include space for the NULL terminator. PACKAGE_* lengths do not. Signed-off-by: Jan Robert Gerigk <Robert-Gerigk@online.de> --- include/appmodel.h | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/include/appmodel.h b/include/appmodel.h index 8c219e8080a..fba40d3f2ed 100644 --- a/include/appmodel.h +++ b/include/appmodel.h @@ -18,6 +18,46 @@ #ifndef _APPMODEL_H_ #define _APPMODEL_H_ +/* Application User Model ID (space included for NULL terminator) */ +#define APPLICATION_USER_MODEL_ID_MIN_LENGTH 21 +#define APPLICATION_USER_MODEL_ID_MAX_LENGTH 130 + +/* Package architecture (no space for NULL terminator) */ +#define PACKAGE_ARCHITECTURE_MIN_LENGTH 3 +#define PACKAGE_ARCHITECTURE_MAX_LENGTH 7 + +/* Package family name (no space for NULL terminator) */ +#define PACKAGE_FAMILY_NAME_MIN_LENGTH 17 +#define PACKAGE_FAMILY_NAME_MAX_LENGTH 64 + +/* Package full name (no space for NULL terminator) */ +#define PACKAGE_FULL_NAME_MIN_LENGTH 30 +#define PACKAGE_FULL_NAME_MAX_LENGTH 127 + +/* Package name (no space for NULL terminator) */ +#define PACKAGE_NAME_MIN_LENGTH 3 +#define PACKAGE_NAME_MAX_LENGTH 50 + +/* Package publisher ID (no space for NULL terminator) */ +#define PACKAGE_PUBLISHERID_MIN_LENGTH 13 +#define PACKAGE_PUBLISHERID_MAX_LENGTH 13 + +/* Package publisher (no space for NULL terminator) */ +#define PACKAGE_PUBLISHER_MIN_LENGTH 4 +#define PACKAGE_PUBLISHER_MAX_LENGTH 8192 + +/* Package relative application ID (space included for NULL terminator) */ +#define PACKAGE_RELATIVE_APPLICATION_ID_MIN_LENGTH 2 +#define PACKAGE_RELATIVE_APPLICATION_ID_MAX_LENGTH 65 + +/* Package resource ID (no space for NULL terminator) */ +#define PACKAGE_RESOURCEID_MIN_LENGTH 0 +#define PACKAGE_RESOURCEID_MAX_LENGTH 30 + +/* Package version (no space for NULL terminator) */ +#define PACKAGE_VERSION_MIN_LENGTH 7 +#define PACKAGE_VERSION_MAX_LENGTH 23 + #if defined(__cplusplus) extern "C" { #endif -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10565
From: Robert Gerigk <Robert-Gerigk@online.de> Wine's shcore.dll had Set/GetCurrentProcessExplicitAppUserModelID as stubs. SetCurrentProcessExplicitAppUserModelID returned S_OK but discarded the value, and GetCurrentProcessExplicitAppUserModelID always returned NULL with E_NOTIMPL. This broke applications that set an AppUserModelID and later retrieve it, causing "String argument cannot be null or empty" crashes in .NET WPF applications. Implement both functions using a process-wide static variable protected by a critical section. SetCurrentProcessExplicitAppUserModelID validates the input (rejects NULL and strings longer than 127 characters) and stores a CoTaskMemAlloc'd copy. GetCurrentProcessExplicitAppUserModelID returns a CoTaskMemAlloc'd copy to the caller. Signed-off-by: Jan Robert Gerigk <Robert-Gerigk@online.de> --- dlls/shcore/main.c | 56 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/dlls/shcore/main.c b/dlls/shcore/main.c index f04853cb9f5..de05a1a17fe 100644 --- a/dlls/shcore/main.c +++ b/dlls/shcore/main.c @@ -30,6 +30,7 @@ #include "featurestagingapi.h" #include "shellscalingapi.h" #include "shcore.h" +#include "appmodel.h" #define WINSHLWAPI #include "shlwapi.h" @@ -249,17 +250,66 @@ HRESULT WINAPI IUnknown_SetSite(IUnknown *obj, IUnknown *site) return hr; } +static WCHAR *explicit_app_user_model_id; +static CRITICAL_SECTION appid_cs; +static CRITICAL_SECTION_DEBUG appid_cs_debug = +{ + 0, 0, &appid_cs, + { &appid_cs_debug.ProcessLocksList, &appid_cs_debug.ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": appid_cs") } +}; +static CRITICAL_SECTION appid_cs = { &appid_cs_debug, -1, 0, 0, 0, 0 }; + HRESULT WINAPI SetCurrentProcessExplicitAppUserModelID(const WCHAR *appid) { - FIXME("%s: stub\n", debugstr_w(appid)); + WCHAR *new_id = NULL; + DWORD len; + + TRACE("%s\n", debugstr_w(appid)); + + if (!appid) + return E_INVALIDARG; + + len = lstrlenW(appid); + if (len > APPLICATION_USER_MODEL_ID_MAX_LENGTH - 3) + return E_INVALIDARG; + + new_id = CoTaskMemAlloc((len + 1) * sizeof(WCHAR)); + if (!new_id) return E_OUTOFMEMORY; + memcpy(new_id, appid, (len + 1) * sizeof(WCHAR)); + + EnterCriticalSection(&appid_cs); + CoTaskMemFree(explicit_app_user_model_id); + explicit_app_user_model_id = new_id; + LeaveCriticalSection(&appid_cs); + return S_OK; } HRESULT WINAPI GetCurrentProcessExplicitAppUserModelID(const WCHAR **appid) { - FIXME("%p: stub\n", appid); + TRACE("%p\n", appid); + + if (!appid) return E_INVALIDARG; + *appid = NULL; - return E_NOTIMPL; + + EnterCriticalSection(&appid_cs); + if (explicit_app_user_model_id) + { + DWORD len = (lstrlenW(explicit_app_user_model_id) + 1) * sizeof(WCHAR); + WCHAR *copy = CoTaskMemAlloc(len); + if (copy) + { + memcpy(copy, explicit_app_user_model_id, len); + *appid = copy; + } + } + LeaveCriticalSection(&appid_cs); + + if (*appid) + return S_OK; + return E_FAIL; } /************************************************************************* -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10565
From: Robert Gerigk <Robert-Gerigk@online.de> Test max length validation (127 chars = S_OK, 128 chars = E_INVALIDARG), set/get round-trip, and ID update behavior. Signed-off-by: Jan Robert Gerigk <Robert-Gerigk@online.de> --- dlls/shcore/tests/Makefile.in | 2 +- dlls/shcore/tests/shcore.c | 63 +++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/dlls/shcore/tests/Makefile.in b/dlls/shcore/tests/Makefile.in index 2b7eaa65686..d40498fda15 100644 --- a/dlls/shcore/tests/Makefile.in +++ b/dlls/shcore/tests/Makefile.in @@ -1,5 +1,5 @@ TESTDLL = shcore.dll -IMPORTS = advapi32 +IMPORTS = advapi32 ole32 SOURCES = \ shcore.c diff --git a/dlls/shcore/tests/shcore.c b/dlls/shcore/tests/shcore.c index 539c2e5c1bc..96df5812277 100644 --- a/dlls/shcore/tests/shcore.c +++ b/dlls/shcore/tests/shcore.c @@ -23,10 +23,14 @@ #include <windows.h> #include "initguid.h" #include "objidl.h" +#include "objbase.h" #include "shlwapi.h" +#include "appmodel.h" #include "wine/test.h" +static HRESULT (WINAPI *pSetCurrentProcessExplicitAppUserModelID)(const WCHAR *); +static HRESULT (WINAPI *pGetCurrentProcessExplicitAppUserModelID)(const WCHAR **); static HRESULT (WINAPI *pGetProcessReference)(IUnknown **); static void (WINAPI *pSetProcessReference)(IUnknown *); static HRESULT (WINAPI *pSHGetInstanceExplorer)(IUnknown **); @@ -62,6 +66,8 @@ static const char * initial_buffer ="0123456789"; static void init(HMODULE hshcore) { #define X(f) p##f = (void*)GetProcAddress(hshcore, #f) + X(SetCurrentProcessExplicitAppUserModelID); + X(GetCurrentProcessExplicitAppUserModelID); X(GetProcessReference); X(SetProcessReference); X(SHUnicodeToAnsi); @@ -777,6 +783,62 @@ static void test_stream_size(void) DeleteFileA(filename); } +static void test_AppUserModelID(void) +{ + const WCHAR *appid = NULL; + HRESULT hr; + + if (!pSetCurrentProcessExplicitAppUserModelID || !pGetCurrentProcessExplicitAppUserModelID) + { + win_skip("AppUserModelID functions not available.\n"); + return; + } + + /* String length validation. Native rejects strings with 128+ characters. */ + { + WCHAR long_id[APPLICATION_USER_MODEL_ID_MAX_LENGTH + 10]; + memset(long_id, 'A', sizeof(long_id)); + + /* 128 chars — should fail */ + long_id[128] = 0; + hr = pSetCurrentProcessExplicitAppUserModelID(long_id); + ok(hr == E_INVALIDARG, "Got hr %#lx for 128-char string.\n", hr); + + /* 127 chars — should succeed */ + long_id[127] = 0; + hr = pSetCurrentProcessExplicitAppUserModelID(long_id); + ok(hr == S_OK, "Got hr %#lx for 127-char string.\n", hr); + } + + /* Set a valid ID */ + hr = pSetCurrentProcessExplicitAppUserModelID(L"Wine.Test.AppId"); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + /* Get should return the ID we set */ + appid = NULL; + hr = pGetCurrentProcessExplicitAppUserModelID(&appid); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(appid != NULL, "Expected non-NULL appid.\n"); + if (appid) + { + ok(!lstrcmpW(appid, L"Wine.Test.AppId"), "Got %s.\n", wine_dbgstr_w(appid)); + CoTaskMemFree((void *)appid); + } + + /* Set a different ID */ + hr = pSetCurrentProcessExplicitAppUserModelID(L"Wine.Test.AppId2"); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + appid = NULL; + hr = pGetCurrentProcessExplicitAppUserModelID(&appid); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + if (appid) + { + ok(!lstrcmpW(appid, L"Wine.Test.AppId2"), "Got %s.\n", wine_dbgstr_w(appid)); + CoTaskMemFree((void *)appid); + } +} + START_TEST(shcore) { HMODULE hshcore = LoadLibraryA("shcore.dll"); @@ -789,6 +851,7 @@ START_TEST(shcore) init(hshcore); + test_AppUserModelID(); test_process_reference(); test_SHUnicodeToAnsi(); test_SHAnsiToUnicode(); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10565
On Wed Apr 8 20:07:58 2026 +0000, Jan Robert Gerigk wrote:
I will keep the length check at \> 128 since it matches the CI results. Pushing the update now with a new code quality fix too. And thanks for your patience and Feedback, I only started looking into Wine a few months ago as part of a project to get KNX ETS (a building automation software) running on Linux, and I have only been contributing upstream for a few weeks now. My understanding of Wines internals is still very surface-level, so I really appreciate the feedback. Another push, perhaps this time with no errors!
Updated: length check now rejects strings with 128+ characters (\> 127) to match native behavior. Also fixed the new code quality warning (perhaps). Side note: I notice some Tests in unrelated areas (d3d9, user32, wininet) that sometimes pass, sometimes fail across my pipelines, even though I never touched those Components. Is that expected CI behavior, or should I be concerned? Still new with WineHQ. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10565#note_135597
On Wed Apr 8 22:16:14 2026 +0000, Jan Robert Gerigk wrote:
Another push, perhaps this time with no errors! Updated: length check now rejects strings with 128+ characters (\> 127) to match native behavior. Also fixed the new code quality warning (perhaps). Side note: I notice some Tests in unrelated areas (d3d9, user32, wininet) that sometimes pass, sometimes fail across my pipelines, even though I never touched those Components. Is that expected CI behavior, or should I be concerned? Still new with WineHQ. Looks good to me. Weird about the constants being wrong, but that's MSDN for you. Maybe someone else will have a feeling about whether it's even worth checking the lengths.
As for unrelated tests failing, yeah, that happens. Some of our tests are flaky, and sometimes failures sneak in. In this case it's pretty clear those aren't your fault. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10565#note_135821
participants (3)
-
Jan Robert Gerigk (@RgSg86) -
Robert Gerigk -
Tim Clem (@tclem)