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 {};
}
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.
// This is needed in case dialog changes the current directory.
class CurrentDirBackup
@ -348,12 +357,10 @@ public:
: _cRef(1), _dialog(dlg), _customize(dlg), _filterSpec(filterSpec), _currentType(fileIndex + 1),
_lastSelectedType(fileIndex + 1), _wildcardType(wildcardIndex >= 0 ? wildcardIndex + 1 : 0)
{
_staticThis = this;
}
~FileDialogEventHandler()
{
_staticThis = nullptr;
}
const generic_string& getLastUsedFolder() const { return _lastUsedFolder; }
@ -376,9 +383,12 @@ private:
HRESULT hr = pOleWnd->GetWindow(&hwndDlg);
if (SUCCEEDED(hr) && hwndDlg)
{
EnumChildWindows(hwndDlg, &EnumChildProc, 0);
if (_staticThis->_hwndButton)
_staticThis->_okButtonProc = (WNDPROC)SetWindowLongPtr(_staticThis->_hwndButton, GWLP_WNDPROC, (LPARAM)&OkButtonWndProc);
EnumChildWindows(hwndDlg, &EnumChildProc, reinterpret_cast<LPARAM>(this));
if (_hwndButton && !GetWindowLongPtr(_hwndButton, GWLP_USERDATA))
{
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.
// 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;
static TCHAR buffer[bufferLen];
auto* inst = reinterpret_cast<FileDialogEventHandler*>(param);
if (!inst)
return FALSE;
if (IsWindowEnabled(hwnd) && GetClassName(hwnd, buffer, bufferLen) != 0)
{
if (lstrcmpi(buffer, _T("ComboBox")) == 0)
{
// 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(""));
if (hwndChild && !_staticThis->_hwndNameEdit)
if (hwndChild && !inst->_hwndNameEdit && !GetWindowLongPtr(hwndChild, GWLP_USERDATA))
{
_staticThis->_fileNameProc = (WNDPROC)SetWindowLongPtr(hwndChild, GWLP_WNDPROC, (LPARAM)&FileNameWndProc);
_staticThis->_hwndNameEdit = hwndChild;
SetWindowLongPtr(hwndChild, GWLP_USERDATA, reinterpret_cast<LPARAM>(inst));
inst->_fileNameProc = (WNDPROC)SetWindowLongPtr(hwndChild, GWLP_WNDPROC, reinterpret_cast<LPARAM>(&FileNameWndProc));
inst->_hwndNameEdit = hwndChild;
}
}
else if (lstrcmpi(buffer, _T("Button")) == 0)
@ -493,19 +508,19 @@ private:
if ((type == BS_PUSHBUTTON || type == BS_DEFPUSHBUTTON) && (appearance == BS_TEXT))
{
// Get the leftmost button.
if (_staticThis->_hwndButton)
if (inst->_hwndButton)
{
RECT rc1 = {};
RECT rc2 = {};
if (GetWindowRect(hwnd, &rc1) && GetWindowRect(_staticThis->_hwndButton, &rc2))
if (GetWindowRect(hwnd, &rc1) && GetWindowRect(inst->_hwndButton, &rc2))
{
if (rc1.left < rc2.left)
_staticThis->_hwndButton = hwnd;
inst->_hwndButton = hwnd;
}
}
else
{
_staticThis->_hwndButton = hwnd;
inst->_hwndButton = hwnd;
}
}
}
@ -532,13 +547,22 @@ private:
pressed = (wparam == VK_RETURN);
break;
}
auto* inst = reinterpret_cast<FileDialogEventHandler*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
if (inst)
{
if (pressed)
_staticThis->onPreFileOk();
return CallWindowProc(_staticThis->_okButtonProc, hwnd, msg, wparam, lparam);
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)
{
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.
// So watch for the keyboard input while the control has focus.
// Initially, the control has focus.
@ -547,30 +571,28 @@ private:
switch (msg)
{
case WM_SETFOCUS:
_staticThis->_monitorKeyboard = true;
inst->_monitorKeyboard = true;
break;
case WM_KILLFOCUS:
_staticThis->_monitorKeyboard = false;
inst->_monitorKeyboard = false;
break;
}
// Avoid unnecessary processing by polling keyboard only on some messages.
bool checkMsg = msg > WM_USER;
if (_staticThis->_monitorKeyboard && !processingReturn && checkMsg)
if (inst->_monitorKeyboard && !processingReturn && checkMsg)
{
SHORT state = GetAsyncKeyState(VK_RETURN);
if (state & 0x8000)
{
// Avoid re-entrance because the call might generate some messages.
processingReturn = true;
_staticThis->onPreFileOk();
inst->onPreFileOk();
processingReturn = false;
}
}
return CallWindowProc(_staticThis->_fileNameProc, hwnd, msg, wparam, lparam);
return CallWindowProc(inst->_fileNameProc, hwnd, msg, wparam, lparam);
}
static FileDialogEventHandler* _staticThis;
long _cRef;
com_ptr<IFileDialog> _dialog;
com_ptr<IFileDialogCustomize> _customize;
@ -586,8 +608,6 @@ private:
bool _monitorKeyboard = true;
};
FileDialogEventHandler* FileDialogEventHandler::_staticThis;
///////////////////////////////////////////////////////////////////////////////
// Private implementation to avoid pollution with includes and defines in header.
@ -723,10 +743,16 @@ public:
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);
if (!_dialog)
return false;
isActive = true;
HRESULT hr = S_OK;
DWORD dwCookie = 0;
com_ptr<IFileDialogEvents> dialogEvents = _events;
@ -755,6 +781,8 @@ public:
if (dialogEvents)
_dialog->Unadvise(dwCookie);
isActive = false;
return okPressed;
}