Signed-off-by: Nikolay Sivov nsivov@codeweavers.com ---
Some applications depend on this and expect COM to be initialized. Examples:
- Path Of Exile inability to create WIC objects (was fixed in game itself); - missing sound in games after failing to create xaudio objects https://github.com/ValveSoftware/Proton/issues/1855 https://github.com/ValveSoftware/Proton/issues/937
dlls/imm32/Makefile.in | 2 +- dlls/imm32/imm.c | 129 ++++++++++++++++++++++++++++++++++++- dlls/imm32/imm32.spec | 1 + dlls/user32/focus.c | 2 + dlls/user32/misc.c | 2 + dlls/user32/user_private.h | 1 + 6 files changed, 135 insertions(+), 2 deletions(-)
diff --git a/dlls/imm32/Makefile.in b/dlls/imm32/Makefile.in index b190888659..ad10fc2fa4 100644 --- a/dlls/imm32/Makefile.in +++ b/dlls/imm32/Makefile.in @@ -1,6 +1,6 @@ MODULE = imm32.dll IMPORTLIB = imm32 -IMPORTS = user32 gdi32 advapi32 +IMPORTS = user32 gdi32 advapi32 ole32
C_SRCS = \ imm.c diff --git a/dlls/imm32/imm.c b/dlls/imm32/imm.c index 28eb00f355..129f7e8cb5 100644 --- a/dlls/imm32/imm.c +++ b/dlls/imm32/imm.c @@ -19,6 +19,8 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
+#define COBJMACROS + #include <stdarg.h> #include <stdio.h>
@@ -32,6 +34,8 @@ #include "ddk/imm.h" #include "winnls.h" #include "winreg.h" +#include "initguid.h" +#include "objbase.h" #include "wine/list.h" #include "wine/unicode.h"
@@ -95,8 +99,16 @@ typedef struct _tagIMMThreadData { HWND hwndDefault; BOOL disableIME; DWORD windowRefs; + IInitializeSpy IInitializeSpy_iface; + ULARGE_INTEGER spy_cookie; + BOOL apt_initialized; } IMMThreadData;
+static inline IMMThreadData *impl_from_IInitializeSpy(IInitializeSpy *iface) +{ + return CONTAINING_RECORD(iface, IMMThreadData, IInitializeSpy_iface); +} + static struct list ImmHklList = LIST_INIT(ImmHklList); static struct list ImmThreadDataList = LIST_INIT(ImmThreadDataList);
@@ -227,6 +239,88 @@ static DWORD convert_candidatelist_AtoW( return ret; }
+static HRESULT WINAPI initializespy_QueryInterface(IInitializeSpy *iface, REFIID riid, void **obj) +{ + if (IsEqualIID(&IID_IInitializeSpy, riid) || + IsEqualIID(&IID_IUnknown, riid)) + { + *obj = iface; + IInitializeSpy_AddRef(iface); + return S_OK; + } + + *obj = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI initializespy_AddRef(IInitializeSpy *iface) +{ + return 2; +} + +static ULONG WINAPI initializespy_Release(IInitializeSpy *iface) +{ + return 1; +} + +static void imm_couninit_thread(IMMThreadData *thread_data) +{ + if (!thread_data->apt_initialized) + return; + + thread_data->apt_initialized = FALSE; + CoUninitialize(); +} + +static HRESULT WINAPI initializespy_PreInitialize(IInitializeSpy *iface, DWORD coinit, DWORD refs) +{ + IMMThreadData *thread_data = impl_from_IInitializeSpy(iface); + + /* Application requested initialization of different apartment type. */ + if (!(coinit & COINIT_APARTMENTTHREADED)) + imm_couninit_thread(thread_data); + + return S_OK; +} + +static HRESULT WINAPI initializespy_PostInitialize(IInitializeSpy *iface, HRESULT hr, DWORD coinit, DWORD refs) +{ + IMMThreadData *thread_data = impl_from_IInitializeSpy(iface); + + /* Explicit initialization call should return S_OK first time. */ + if (thread_data->apt_initialized && hr == S_FALSE && refs == 2) + hr = S_OK; + + return hr; +} + +static HRESULT WINAPI initializespy_PreUninitialize(IInitializeSpy *iface, DWORD refs) +{ + IMMThreadData *thread_data = impl_from_IInitializeSpy(iface); + + /* Account for explicit uninitialization calls. */ + if (thread_data->apt_initialized && refs == 1) + thread_data->apt_initialized = FALSE; + + return S_OK; +} + +static HRESULT WINAPI initializespy_PostUninitialize(IInitializeSpy *iface, DWORD refs) +{ + return S_OK; +} + +static const IInitializeSpyVtbl initializespyvtbl = +{ + initializespy_QueryInterface, + initializespy_AddRef, + initializespy_Release, + initializespy_PreInitialize, + initializespy_PostInitialize, + initializespy_PreUninitialize, + initializespy_PostUninitialize, +}; + static IMMThreadData *IMM_GetThreadData(HWND hwnd, DWORD thread) { IMMThreadData *data; @@ -253,6 +347,7 @@ static IMMThreadData *IMM_GetThreadData(HWND hwnd, DWORD thread) if (data->threadID == thread) return data;
data = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*data)); + data->IInitializeSpy_iface.lpVtbl = &initializespyvtbl; data->threadID = thread; list_add_head(&ImmThreadDataList,&data->entry); TRACE("Thread Data Created (%x)\n",thread); @@ -281,6 +376,7 @@ static void IMM_FreeThreadData(void) list_remove(&data->entry); LeaveCriticalSection(&threaddata_cs); IMM_DestroyContext(data->defaultContext); + imm_couninit_thread(data); HeapFree(GetProcessHeap(),0,data); TRACE("Thread Data Destroyed\n"); return; @@ -1627,6 +1723,32 @@ static BOOL needs_ime_window(HWND hwnd) return TRUE; }
+void WINAPI __wine_activate_window(HWND hwnd) +{ + IMMThreadData *thread_data; + + TRACE("(%p)\n", hwnd); + + if (!needs_ime_window(hwnd)) + return; + + thread_data = IMM_GetThreadData(hwnd, 0); + if (!thread_data) + return; + + if (thread_data->disableIME || disable_ime) + { + TRACE("IME for this thread is disabled\n"); + LeaveCriticalSection(&threaddata_cs); + return; + } + + if (!thread_data->apt_initialized) + thread_data->apt_initialized = SUCCEEDED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)); + + LeaveCriticalSection(&threaddata_cs); +} + /*********************************************************************** * __wine_register_window (IMM32.@) */ @@ -1656,6 +1778,8 @@ BOOL WINAPI __wine_register_window(HWND hwnd) /* Create default IME window */ if (thread_data->windowRefs == 1) { + CoRegisterInitializeSpy(&thread_data->IInitializeSpy_iface, &thread_data->spy_cookie); + /* Do not create the window inside of a critical section */ LeaveCriticalSection(&threaddata_cs); new = CreateWindowExW( 0, szwIME, szwDefaultIME, @@ -1697,8 +1821,11 @@ void WINAPI __wine_unregister_window(HWND hwnd) thread_data->windowRefs, thread_data->hwndDefault);
/* Destroy default IME window */ - if (thread_data->windowRefs == 0 && thread_data->hwndDefault) + if (thread_data->windowRefs == 0) { + CoRevokeInitializeSpy(thread_data->spy_cookie); + thread_data->spy_cookie.QuadPart = 0; + imm_couninit_thread(thread_data); to_destroy = thread_data->hwndDefault; thread_data->hwndDefault = NULL; } diff --git a/dlls/imm32/imm32.spec b/dlls/imm32/imm32.spec index 4197bb81e2..d9cdc794e9 100644 --- a/dlls/imm32/imm32.spec +++ b/dlls/imm32/imm32.spec @@ -117,3 +117,4 @@ @ stdcall __wine_get_ui_window(ptr) @ stdcall __wine_register_window(long) @ stdcall __wine_unregister_window(long) +@ stdcall __wine_activate_window(long) diff --git a/dlls/user32/focus.c b/dlls/user32/focus.c index f1c883167e..50b3323ae9 100644 --- a/dlls/user32/focus.c +++ b/dlls/user32/focus.c @@ -156,6 +156,8 @@ static BOOL set_active_window( HWND hwnd, HWND *prev, BOOL mouse, BOOL focus ) (LPARAM)previous ); if (GetAncestor( hwnd, GA_PARENT ) == GetDesktopWindow()) PostMessageW( GetDesktopWindow(), WM_PARENTNOTIFY, WM_NCACTIVATE, (LPARAM)hwnd ); + + imm_activate_window( hwnd ); }
/* now change focus if necessary */ diff --git a/dlls/user32/misc.c b/dlls/user32/misc.c index d28cd9fd05..cfaa1b94db 100644 --- a/dlls/user32/misc.c +++ b/dlls/user32/misc.c @@ -43,6 +43,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(win); static HWND (WINAPI *imm_get_ui_window)(HKL); BOOL (WINAPI *imm_register_window)(HWND) = NULL; void (WINAPI *imm_unregister_window)(HWND) = NULL; +void (WINAPI *imm_activate_window)(HWND) = NULL;
/* MSIME messages */ static UINT WM_MSIME_SERVICE; @@ -480,6 +481,7 @@ BOOL WINAPI User32InitializeImmEntryTable(DWORD magic) imm_get_ui_window = (void*)GetProcAddress(imm32, "__wine_get_ui_window"); imm_register_window = (void*)GetProcAddress(imm32, "__wine_register_window"); imm_unregister_window = (void*)GetProcAddress(imm32, "__wine_unregister_window"); + imm_activate_window = (void*)GetProcAddress(imm32, "__wine_activate_window"); if (!imm_get_ui_window) FIXME("native imm32.dll not supported\n"); return TRUE; diff --git a/dlls/user32/user_private.h b/dlls/user32/user_private.h index 514cf6753f..b86831d7d9 100644 --- a/dlls/user32/user_private.h +++ b/dlls/user32/user_private.h @@ -197,6 +197,7 @@ C_ASSERT( sizeof(struct user_thread_info) <= sizeof(((TEB *)0)->Win32ClientInfo) extern INT global_key_state_counter DECLSPEC_HIDDEN; extern BOOL (WINAPI *imm_register_window)(HWND) DECLSPEC_HIDDEN; extern void (WINAPI *imm_unregister_window)(HWND) DECLSPEC_HIDDEN; +extern void (WINAPI *imm_activate_window)(HWND) DECLSPEC_HIDDEN;
struct user_key_state_info {
Hi,
While running your changed tests on Windows, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=46859
Your paranoid android.
=== debian9 (64 bit WoW report) ===
user32: msg.c:8713: Test failed: WaitForSingleObject failed 102 msg.c:8719: Test failed: destroy child on thread exit: 0: the msg 0x0082 was expected, but got msg 0x000f instead msg.c:8719: Test failed: destroy child on thread exit: 1: the msg 0x000f was expected, but got msg 0x0014 instead msg.c:8719: Test failed: destroy child on thread exit: 2: the msg sequence is not complete: expected 0014 - actual 0000
Nikolay Sivov nsivov@codeweavers.com wrote:
--- a/dlls/imm32/Makefile.in +++ b/dlls/imm32/Makefile.in @@ -1,6 +1,6 @@ MODULE = imm32.dll IMPORTLIB = imm32 -IMPORTS = user32 gdi32 advapi32 +IMPORTS = user32 gdi32 advapi32 ole32
According to dumpbin output imm32 in Windows doesn't import ole32 directly, most likely it loads it manually only when really needed. So, it would probably be better to either use delay loading, or also load it manually, and make sure that ole32 is never loaded for languages that don't need IME.
On 1/29/19 10:36 AM, Dmitry Timoshkov wrote:
Nikolay Sivov nsivov@codeweavers.com wrote:
--- a/dlls/imm32/Makefile.in +++ b/dlls/imm32/Makefile.in @@ -1,6 +1,6 @@ MODULE = imm32.dll IMPORTLIB = imm32 -IMPORTS = user32 gdi32 advapi32 +IMPORTS = user32 gdi32 advapi32 ole32
According to dumpbin output imm32 in Windows doesn't import ole32 directly, most likely it loads it manually only when really needed. So, it would probably be better to either use delay loading, or also load it manually, and make sure that ole32 is never loaded for languages that don't need IME.
I doesn't depend on the language, it will work as long as IME is not disabled for thread/process.
Why would it better to delay-load? It will be called practically always for GUI applications.
Nikolay Sivov nsivov@codeweavers.com wrote:
--- a/dlls/imm32/Makefile.in +++ b/dlls/imm32/Makefile.in @@ -1,6 +1,6 @@ MODULE = imm32.dll IMPORTLIB = imm32 -IMPORTS = user32 gdi32 advapi32 +IMPORTS = user32 gdi32 advapi32 ole32
According to dumpbin output imm32 in Windows doesn't import ole32 directly, most likely it loads it manually only when really needed. So, it would probably be better to either use delay loading, or also load it manually, and make sure that ole32 is never loaded for languages that don't need IME.
I doesn't depend on the language, it will work as long as IME is not disabled for thread/process.
As far as I can see from the tests IME messages are optional, and that means that IME is completely disabled in some Windows configurations. For instance I don't get IME messages in 2 of 3 my Windows machines.
Why would it better to delay-load?
Because loading ole32 is very expensive.
It will be called practically always for GUI applications.
Adding a convincing test case would be also helpful.
On 1/29/19 10:52 AM, Dmitry Timoshkov wrote:
Nikolay Sivov nsivov@codeweavers.com wrote:
--- a/dlls/imm32/Makefile.in +++ b/dlls/imm32/Makefile.in @@ -1,6 +1,6 @@ MODULE = imm32.dll IMPORTLIB = imm32 -IMPORTS = user32 gdi32 advapi32 +IMPORTS = user32 gdi32 advapi32 ole32
According to dumpbin output imm32 in Windows doesn't import ole32 directly, most likely it loads it manually only when really needed. So, it would probably be better to either use delay loading, or also load it manually, and make sure that ole32 is never loaded for languages that don't need IME.
I doesn't depend on the language, it will work as long as IME is not disabled for thread/process.
As far as I can see from the tests IME messages are optional, and that means that IME is completely disabled in some Windows configurations. For instance I don't get IME messages in 2 of 3 my Windows machines.
That's hardly relevant, if it's disabled patch will skip initialization.
Why would it better to delay-load?
Because loading ole32 is very expensive.
What's expensive about it?
It will be called practically always for GUI applications.
Adding a convincing test case would be also helpful.
I don't think so, it's clearly internal behavior. If you're really interested there is a test at https://bugs.winehq.org/show_bug.cgi?id=42695, that works regardless of your input language, and doesn't work if application disables IME for itself.
Nikolay Sivov nsivov@codeweavers.com wrote:
According to dumpbin output imm32 in Windows doesn't import ole32 directly, most likely it loads it manually only when really needed. So, it would probably be better to either use delay loading, or also load it manually, and make sure that ole32 is never loaded for languages that don't need IME.
I doesn't depend on the language, it will work as long as IME is not disabled for thread/process.
As far as I can see from the tests IME messages are optional, and that means that IME is completely disabled in some Windows configurations. For instance I don't get IME messages in 2 of 3 my Windows machines.
That's hardly relevant, if it's disabled patch will skip initialization.
This means that under Windows ole32 is probably not always get loaded, and that basically means that your patch is wrong.
Why would it better to delay-load?
Because loading ole32 is very expensive.
What's expensive about it?
Mostly dependencies and initialization.
It will be called practically always for GUI applications.
Adding a convincing test case would be also helpful.
I don't think so, it's clearly internal behavior. If you're really interested there is a test at https://bugs.winehq.org/show_bug.cgi?id=42695, that works regardless of your input language, and doesn't work if application disables IME for itself.
The tests don't work this way, especially if you change how low level functionality works. Adding an ole32 dependency to the user32 subsystem needs very convincing arguments.
On Tue, 29 Jan 2019 at 11:32, Dmitry Timoshkov dmitry@baikal.ru wrote:
Nikolay Sivov nsivov@codeweavers.com wrote:
According to dumpbin output imm32 in Windows doesn't import ole32
directly,
most likely it loads it manually only when really needed. So, it
would
probably be better to either use delay loading, or also load it
manually,
and make sure that ole32 is never loaded for languages that don't
need IME.
I doesn't depend on the language, it will work as long as IME is not disabled for thread/process.
As far as I can see from the tests IME messages are optional, and that means that IME is completely disabled in some Windows configurations. For instance I don't get IME messages in 2 of 3 my Windows machines.
That's hardly relevant, if it's disabled patch will skip initialization.
This means that under Windows ole32 is probably not always get loaded, and that basically means that your patch is wrong.
So why is that important?
Why would it better to delay-load?
Because loading ole32 is very expensive.
What's expensive about it?
Mostly dependencies and initialization.
Be more specific please. Which dependency and what is slow about initialization.
It will be called practically always for GUI applications.
Adding a convincing test case would be also helpful.
I don't think so, it's clearly internal behavior. If you're really interested there is a test at https://bugs.winehq.org/show_bug.cgi?id=42695, that works regardless of your input language, and doesn't work if application disables IME for itself.
The tests don't work this way, especially if you change how low level functionality works. Adding an ole32 dependency to the user32 subsystem needs very convincing arguments.
Thanks. I already explained why, not going to repeat myself. I’ll wait for a second opinion on that one.
-- Dmitry.
Nikolay Sivov bunglehead@gmail.com wrote:
According to dumpbin output imm32 in Windows doesn't import ole32
directly,
most likely it loads it manually only when really needed. So, it
would
probably be better to either use delay loading, or also load it
manually,
and make sure that ole32 is never loaded for languages that don't
need IME.
I doesn't depend on the language, it will work as long as IME is not disabled for thread/process.
As far as I can see from the tests IME messages are optional, and that means that IME is completely disabled in some Windows configurations. For instance I don't get IME messages in 2 of 3 my Windows machines.
That's hardly relevant, if it's disabled patch will skip initialization.
This means that under Windows ole32 is probably not always get loaded, and that basically means that your patch is wrong.
So why is that important?
That basically means that your patch is wrong.
On Tue, Jan 29, 2019 at 04:32:19PM +0800, Dmitry Timoshkov wrote:
Nikolay Sivov nsivov@codeweavers.com wrote:
Why would it better to delay-load?
Because loading ole32 is very expensive.
What's expensive about it?
Mostly dependencies and initialization.
Just to set the record straight, both ole32 and rpcrt4 (which ole32 loads) essentially have no initialization code. So the overhead involved is just that of the loader itself.
Huw.
Huw Davies huw@codeweavers.com wrote:
On Tue, Jan 29, 2019 at 04:32:19PM +0800, Dmitry Timoshkov wrote:
Nikolay Sivov nsivov@codeweavers.com wrote:
Why would it better to delay-load?
Because loading ole32 is very expensive.
What's expensive about it?
Mostly dependencies and initialization.
Just to set the record straight, both ole32 and rpcrt4 (which ole32 loads) essentially have no initialization code. So the overhead involved is just that of the loader itself.
Starting up services and performing IPC takes most of the initialization time I'd guess, not mentioning that is not quite fair IMO.
On Wed, Jan 30, 2019 at 06:09:47PM +0800, Dmitry Timoshkov wrote:
Huw Davies huw@codeweavers.com wrote:
On Tue, Jan 29, 2019 at 04:32:19PM +0800, Dmitry Timoshkov wrote:
Nikolay Sivov nsivov@codeweavers.com wrote:
Why would it better to delay-load?
Because loading ole32 is very expensive.
What's expensive about it?
Mostly dependencies and initialization.
Just to set the record straight, both ole32 and rpcrt4 (which ole32 loads) essentially have no initialization code. So the overhead involved is just that of the loader itself.
Starting up services and performing IPC takes most of the initialization time I'd guess, not mentioning that is not quite fair IMO.
Sure, calling CoInitialize[Ex]() takes time, but you were talking about loading the dll (wrt to delay loading it).
Huw.