Fix crash when several instances of the file dialog are shown

Pass a pointer to FileDialogEventHandler instance via GWLP_USERDATA
instead of using static variable. This way each window can have its
own pointer.

Fix #10290, close #10303
This commit is contained in:
mere-human 2021-08-01 10:11:10 +03:00 committed by Don Ho
parent 96e985dea5
commit 121f19d8f9
1 changed files with 54 additions and 26 deletions

View File

@ -180,6 +180,15 @@ namespace // anonymous
return {}; return {};
} }
LRESULT callWindowClassProc(const TCHAR* className, HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
WNDCLASSEX wndclass = {};
wndclass.cbSize = sizeof(wndclass);
if (GetClassInfoEx(nullptr, className, &wndclass) && wndclass.lpfnWndProc)
return CallWindowProc(wndclass.lpfnWndProc, hwnd, msg, wparam, lparam);
return FALSE;
}
// Backs up the current directory in constructor and restores it in destructor. // Backs up the current directory in constructor and restores it in destructor.
// This is needed in case dialog changes the current directory. // This is needed in case dialog changes the current directory.
class CurrentDirBackup class CurrentDirBackup
@ -348,12 +357,10 @@ public:
: _cRef(1), _dialog(dlg), _customize(dlg), _filterSpec(filterSpec), _currentType(fileIndex + 1), : _cRef(1), _dialog(dlg), _customize(dlg), _filterSpec(filterSpec), _currentType(fileIndex + 1),
_lastSelectedType(fileIndex + 1), _wildcardType(wildcardIndex >= 0 ? wildcardIndex + 1 : 0) _lastSelectedType(fileIndex + 1), _wildcardType(wildcardIndex >= 0 ? wildcardIndex + 1 : 0)
{ {
_staticThis = this;
} }
~FileDialogEventHandler() ~FileDialogEventHandler()
{ {
_staticThis = nullptr;
} }
const generic_string& getLastUsedFolder() const { return _lastUsedFolder; } const generic_string& getLastUsedFolder() const { return _lastUsedFolder; }
@ -376,9 +383,12 @@ private:
HRESULT hr = pOleWnd->GetWindow(&hwndDlg); HRESULT hr = pOleWnd->GetWindow(&hwndDlg);
if (SUCCEEDED(hr) && hwndDlg) if (SUCCEEDED(hr) && hwndDlg)
{ {
EnumChildWindows(hwndDlg, &EnumChildProc, 0); EnumChildWindows(hwndDlg, &EnumChildProc, reinterpret_cast<LPARAM>(this));
if (_staticThis->_hwndButton) if (_hwndButton && !GetWindowLongPtr(_hwndButton, GWLP_USERDATA))
_staticThis->_okButtonProc = (WNDPROC)SetWindowLongPtr(_staticThis->_hwndButton, GWLP_WNDPROC, (LPARAM)&OkButtonWndProc); {
SetWindowLongPtr(_hwndButton, GWLP_USERDATA, reinterpret_cast<LPARAM>(this));
_okButtonProc = (WNDPROC)SetWindowLongPtr(_hwndButton, GWLP_WNDPROC, (LPARAM)&OkButtonWndProc);
}
} }
} }
} }
@ -460,22 +470,27 @@ private:
// Enumerates the child windows of a dialog. // Enumerates the child windows of a dialog.
// Sets up window procedure overrides for "OK" button and file name edit box. // Sets up window procedure overrides for "OK" button and file name edit box.
static BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM) static BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM param)
{ {
const int bufferLen = MAX_PATH; const int bufferLen = MAX_PATH;
static TCHAR buffer[bufferLen]; static TCHAR buffer[bufferLen];
auto* inst = reinterpret_cast<FileDialogEventHandler*>(param);
if (!inst)
return FALSE;
if (IsWindowEnabled(hwnd) && GetClassName(hwnd, buffer, bufferLen) != 0) if (IsWindowEnabled(hwnd) && GetClassName(hwnd, buffer, bufferLen) != 0)
{ {
if (lstrcmpi(buffer, _T("ComboBox")) == 0) if (lstrcmpi(buffer, _T("ComboBox")) == 0)
{ {
// The edit box of interest is a child of the combo box and has empty window text. // The edit box of interest is a child of the combo box and has empty window text.
// Note that there might be other combo boxes (file type dropdown, address bar, etc). // We use the first combo box, but there might be the others (file type dropdown, address bar, etc).
HWND hwndChild = FindWindowEx(hwnd, nullptr, _T("Edit"), _T("")); HWND hwndChild = FindWindowEx(hwnd, nullptr, _T("Edit"), _T(""));
if (hwndChild && !_staticThis->_hwndNameEdit) if (hwndChild && !inst->_hwndNameEdit && !GetWindowLongPtr(hwndChild, GWLP_USERDATA))
{ {
_staticThis->_fileNameProc = (WNDPROC)SetWindowLongPtr(hwndChild, GWLP_WNDPROC, (LPARAM)&FileNameWndProc); SetWindowLongPtr(hwndChild, GWLP_USERDATA, reinterpret_cast<LPARAM>(inst));
_staticThis->_hwndNameEdit = hwndChild; inst->_fileNameProc = (WNDPROC)SetWindowLongPtr(hwndChild, GWLP_WNDPROC, reinterpret_cast<LPARAM>(&FileNameWndProc));
inst->_hwndNameEdit = hwndChild;
} }
} }
else if (lstrcmpi(buffer, _T("Button")) == 0) else if (lstrcmpi(buffer, _T("Button")) == 0)
@ -493,19 +508,19 @@ private:
if ((type == BS_PUSHBUTTON || type == BS_DEFPUSHBUTTON) && (appearance == BS_TEXT)) if ((type == BS_PUSHBUTTON || type == BS_DEFPUSHBUTTON) && (appearance == BS_TEXT))
{ {
// Get the leftmost button. // Get the leftmost button.
if (_staticThis->_hwndButton) if (inst->_hwndButton)
{ {
RECT rc1 = {}; RECT rc1 = {};
RECT rc2 = {}; RECT rc2 = {};
if (GetWindowRect(hwnd, &rc1) && GetWindowRect(_staticThis->_hwndButton, &rc2)) if (GetWindowRect(hwnd, &rc1) && GetWindowRect(inst->_hwndButton, &rc2))
{ {
if (rc1.left < rc2.left) if (rc1.left < rc2.left)
_staticThis->_hwndButton = hwnd; inst->_hwndButton = hwnd;
} }
} }
else else
{ {
_staticThis->_hwndButton = hwnd; inst->_hwndButton = hwnd;
} }
} }
} }
@ -532,13 +547,22 @@ private:
pressed = (wparam == VK_RETURN); pressed = (wparam == VK_RETURN);
break; break;
} }
if (pressed) auto* inst = reinterpret_cast<FileDialogEventHandler*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
_staticThis->onPreFileOk(); if (inst)
return CallWindowProc(_staticThis->_okButtonProc, hwnd, msg, wparam, lparam); {
if (pressed)
inst->onPreFileOk();
return CallWindowProc(inst->_okButtonProc, hwnd, msg, wparam, lparam);
}
return callWindowClassProc(_T("Button"), hwnd, msg, wparam, lparam);
} }
static LRESULT CALLBACK FileNameWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) static LRESULT CALLBACK FileNameWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{ {
auto* inst = reinterpret_cast<FileDialogEventHandler*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
if (!inst)
return callWindowClassProc(_T("Edit"), hwnd, msg, wparam, lparam);
// WM_KEYDOWN with wparam == VK_RETURN isn't delivered here. // WM_KEYDOWN with wparam == VK_RETURN isn't delivered here.
// So watch for the keyboard input while the control has focus. // So watch for the keyboard input while the control has focus.
// Initially, the control has focus. // Initially, the control has focus.
@ -547,30 +571,28 @@ private:
switch (msg) switch (msg)
{ {
case WM_SETFOCUS: case WM_SETFOCUS:
_staticThis->_monitorKeyboard = true; inst->_monitorKeyboard = true;
break; break;
case WM_KILLFOCUS: case WM_KILLFOCUS:
_staticThis->_monitorKeyboard = false; inst->_monitorKeyboard = false;
break; break;
} }
// Avoid unnecessary processing by polling keyboard only on some messages. // Avoid unnecessary processing by polling keyboard only on some messages.
bool checkMsg = msg > WM_USER; bool checkMsg = msg > WM_USER;
if (_staticThis->_monitorKeyboard && !processingReturn && checkMsg) if (inst->_monitorKeyboard && !processingReturn && checkMsg)
{ {
SHORT state = GetAsyncKeyState(VK_RETURN); SHORT state = GetAsyncKeyState(VK_RETURN);
if (state & 0x8000) if (state & 0x8000)
{ {
// Avoid re-entrance because the call might generate some messages. // Avoid re-entrance because the call might generate some messages.
processingReturn = true; processingReturn = true;
_staticThis->onPreFileOk(); inst->onPreFileOk();
processingReturn = false; processingReturn = false;
} }
} }
return CallWindowProc(_staticThis->_fileNameProc, hwnd, msg, wparam, lparam); return CallWindowProc(inst->_fileNameProc, hwnd, msg, wparam, lparam);
} }
static FileDialogEventHandler* _staticThis;
long _cRef; long _cRef;
com_ptr<IFileDialog> _dialog; com_ptr<IFileDialog> _dialog;
com_ptr<IFileDialogCustomize> _customize; com_ptr<IFileDialogCustomize> _customize;
@ -586,8 +608,6 @@ private:
bool _monitorKeyboard = true; bool _monitorKeyboard = true;
}; };
FileDialogEventHandler* FileDialogEventHandler::_staticThis;
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// Private implementation to avoid pollution with includes and defines in header. // Private implementation to avoid pollution with includes and defines in header.
@ -723,10 +743,16 @@ public:
bool show() bool show()
{ {
// Allow only one instance of the dialog to be active at a time.
static bool isActive = false;
if (isActive)
return false;
assert(_dialog); assert(_dialog);
if (!_dialog) if (!_dialog)
return false; return false;
isActive = true;
HRESULT hr = S_OK; HRESULT hr = S_OK;
DWORD dwCookie = 0; DWORD dwCookie = 0;
com_ptr<IFileDialogEvents> dialogEvents = _events; com_ptr<IFileDialogEvents> dialogEvents = _events;
@ -755,6 +781,8 @@ public:
if (dialogEvents) if (dialogEvents)
_dialog->Unadvise(dwCookie); _dialog->Unadvise(dwCookie);
isActive = false;
return okPressed; return okPressed;
} }