diff --git a/file_windows.go b/file_windows.go index 15ad749..2d37dab 100644 --- a/file_windows.go +++ b/file_windows.go @@ -3,6 +3,7 @@ package zenity import ( "fmt" "path/filepath" + "runtime" "syscall" "unicode/utf16" "unsafe" @@ -10,7 +11,52 @@ 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 { + err := win.CoInitializeEx(0, win.COINIT_APARTMENTTHREADED|win.COINIT_DISABLE_OLE1DDE) + if err != nil && err != win.RPC_E_CHANGED_MODE && err != win.S_FALSE { + return err + } + return nil +} + func selectFile(opts options) (string, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if err := coInitializeEx(); err != nil { + return "", err + } + if opts.directory { res, _, err := pickFolders(opts, false) return res, err @@ -54,6 +100,12 @@ func selectFile(opts options) (string, error) { } func selectFileMultiple(opts options) ([]string, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if err := coInitializeEx(); err != nil { + return nil, err + } + if opts.directory { _, res, err := pickFolders(opts, true) return res, err @@ -122,6 +174,12 @@ func selectFileMultiple(opts options) ([]string, error) { } func selectFileSave(opts options) (string, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if err := coInitializeEx(); err != nil { + return "", err + } + if opts.directory { res, _, err := pickFolders(opts, false) return res, err @@ -174,16 +232,8 @@ 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() - } - 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 { diff --git a/internal/win/ole32.go b/internal/win/ole32.go index be88c70..9b13e36 100644 --- a/internal/win/ole32.go +++ b/internal/win/ole32.go @@ -24,6 +24,7 @@ const ( E_CANCELED = windows.ERROR_CANCELLED | windows.FACILITY_WIN32<<16 | 0x80000000 RPC_E_CHANGED_MODE = syscall.Errno(windows.RPC_E_CHANGED_MODE) + S_FALSE = syscall.Errno(windows.S_FALSE) ) func CoInitializeEx(reserved uintptr, coInit uint32) error {