// 11 february 2014 package ui import ( "syscall" "unsafe" "runtime" ) /* problem: messages have to be dispatched on the same thread as system calls, and we can't mux GetMessage() with select, and PeekMessage() every iteration is wasteful (and leads to lag for me (only) with the concurrent garbage collector sweep) solution: use PostThreadMessage() to send uimsgs out to the message loop, which runs on its own goroutine I had come up with this first but wanted to try other things before doing it (and wasn't really sure if user-defined messages were safe, not quite understanding the system); nsf came up with it independently and explained that this was really the only right way to do it, so thanks to him */ var uitask chan *uimsg type uimsg struct { call *syscall.LazyProc p []uintptr ret chan uiret } type uiret struct { ret uintptr err error } const ( _WM_APP = 0x8000 + iota msgRequested msgQuit msgSetAreaSize ) var ( _getCurrentThreadID = kernel32.NewProc("GetCurrentThreadId") _postThreadMessage = user32.NewProc("PostThreadMessageW") ) func ui(main func()) error { runtime.LockOSThread() uitask = make(chan *uimsg) err := doWindowsInit() if err != nil { return err } threadID, _, _ := _getCurrentThreadID.Call() go func() { for m := range uitask { r1, _, err := _postThreadMessage.Call( threadID, msgRequested, uintptr(0), uintptr(unsafe.Pointer(m))) if r1 == 0 { // failure panic("error sending message to message loop to call function: " + err.Error()) } } }() go func() { main() r1, _, err := _postThreadMessage.Call( threadID, msgQuit, uintptr(0), uintptr(0)) if r1 == 0 { // failure panic("error sending quit message to message loop: " + err.Error()) } }() msgloop() doWindowsQuitStuff() return nil } var ( _dispatchMessage = user32.NewProc("DispatchMessageW") _getMessage = user32.NewProc("GetMessageW") _postQuitMessage = user32.NewProc("PostQuitMessage") _sendMessage = user32.NewProc("SendMessageW") _translateMessage = user32.NewProc("TranslateMessage") ) var getMessageFail = -1 // because Go doesn't let me func msgloop() { var msg struct { Hwnd _HWND Message uint32 WParam _WPARAM LParam _LPARAM Time uint32 Pt _POINT } for { r1, _, err := _getMessage.Call( uintptr(unsafe.Pointer(&msg)), uintptr(_NULL), uintptr(0), uintptr(0)) if r1 == uintptr(getMessageFail) { // error panic("error getting message in message loop: " + err.Error()) } if r1 == 0 { // WM_QUIT message return } if msg.Message == msgRequested { m := (*uimsg)(unsafe.Pointer(msg.LParam)) r1, _, err := m.call.Call(m.p...) m.ret <- uiret{ ret: r1, err: err, } continue } if msg.Message == msgQuit { // does not return a value according to MSDN _postQuitMessage.Call(0) continue } _translateMessage.Call(uintptr(unsafe.Pointer(&msg))) _dispatchMessage.Call(uintptr(unsafe.Pointer(&msg))) } }