Skip to content

Commit

Permalink
Fix crash issue on some special Windows machines.
Browse files Browse the repository at this point in the history
  • Loading branch information
lonnywong committed Jan 25, 2024
1 parent 341ccb8 commit 57b7e68
Showing 1 changed file with 47 additions and 8 deletions.
55 changes: 47 additions & 8 deletions file_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 57b7e68

Please sign in to comment.