From 57b7e68883e1af82fa56afd2c718d945c82c6e60 Mon Sep 17 00:00:00 2001 From: Lonny Wong Date: Thu, 25 Jan 2024 23:50:06 +0800 Subject: [PATCH] Fix crash issue on some special Windows machines. --- file_windows.go | 55 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/file_windows.go b/file_windows.go index 15ad749..0d330b2 100644 --- a/file_windows.go +++ b/file_windows.go @@ -10,12 +10,50 @@ import ( "github.com/ncruces/zenity/internal/win" ) +// coInitializeEx initializes the COM library for use by the calling thread, +// sets the thread's concurrency model, and creates a new apartment for the thread if one is required. +// +// The second call to GetOpenFileName crashes on some special machines: +// https://stackoverflow.com/questions/35366998/2nd-call-to-getopenfilename-crashes-without-error-on-win-8-1-64-bit-machine +// +// The following was tested on a crashed machine ( Windows 10 Professional 22H2 19045.3803 ): +// +// Case 1: +// - CoInitializeEx was not called. +// - The first call to GetOpenFileName ( with OFN_EXPLORER ) succeeds. +// - The second call to GetOpenFileName ( with OFN_EXPLORER ) crashes. +// +// Case 2: +// - The first call to CoInitializeEx succeeds. +// - The first call to GetOpenFileName ( with OFN_EXPLORER ) succeeds. +// - Call CoUninitialize to close the COM library on the current thread. +// - The second call to CoInitializeEx succeeds on the same thread. +// - The second call to GetOpenFileName ( with OFN_EXPLORER ) crashes. +// +// Case 3: +// - The first call to CoInitializeEx succeeds. +// - The first call to GetOpenFileName ( with OFN_EXPLORER ) succeeds. +// - CoUninitialize was not called. +// - The second call to CoInitializeEx on the same thread fails but ignores it. +// - The second call to GetOpenFileName ( with OFN_EXPLORER ) succeeds. +// +// https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-couninitialize +// The documentation says CoUninitialize should be called on application shutdown. +// It is hard to call CoUninitialize in each thread when the process exits in Golang. +// So, we let the operating system to clean it up after the process exits. +func coInitializeEx() error { + return win.CoInitializeEx(0, win.COINIT_APARTMENTTHREADED|win.COINIT_DISABLE_OLE1DDE) +} + func selectFile(opts options) (string, error) { if opts.directory { res, _, err := pickFolders(opts, false) return res, err } + // Ignore the error and don't call CoUninitialize before application shutdown. + _ = coInitializeEx() + var args win.OPENFILENAME args.StructSize = uint32(unsafe.Sizeof(args)) args.Owner, _ = opts.attach.(win.HWND) @@ -59,6 +97,9 @@ func selectFileMultiple(opts options) ([]string, error) { return res, err } + // Ignore the error and don't call CoUninitialize before application shutdown. + _ = coInitializeEx() + var args win.OPENFILENAME args.StructSize = uint32(unsafe.Sizeof(args)) args.Owner, _ = opts.attach.(win.HWND) @@ -127,6 +168,9 @@ func selectFileSave(opts options) (string, error) { return res, err } + // Ignore the error and don't call CoUninitialize before application shutdown. + _ = coInitializeEx() + var args win.OPENFILENAME args.StructSize = uint32(unsafe.Sizeof(args)) args.Owner, _ = opts.attach.(win.HWND) @@ -174,16 +218,11 @@ func pickFolders(opts options, multi bool) (string, []string, error) { owner, _ := opts.attach.(win.HWND) defer setup(owner)() - err := win.CoInitializeEx(0, win.COINIT_APARTMENTTHREADED|win.COINIT_DISABLE_OLE1DDE) - if err != win.RPC_E_CHANGED_MODE { - if err != nil { - return "", nil, err - } - defer win.CoUninitialize() - } + // Ignore the error and don't call CoUninitialize before application shutdown. + _ = coInitializeEx() var dialog *win.IFileOpenDialog - err = win.CoCreateInstance( + err := win.CoCreateInstance( win.CLSID_FileOpenDialog, nil, win.CLSCTX_ALL, win.IID_IFileOpenDialog, unsafe.Pointer(&dialog)) if err != nil {