Skip to content

Instantly share code, notes, and snippets.

@nathan-osman
Last active December 6, 2024 16:41
Show Gist options
  • Save nathan-osman/18c2e227ad00a223b61c0b3c16d452c3 to your computer and use it in GitHub Desktop.
Save nathan-osman/18c2e227ad00a223b61c0b3c16d452c3 to your computer and use it in GitHub Desktop.
Simple Windows GUI application written in Go
package main
import (
"log"
"syscall"
"unsafe"
)
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
pGetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
)
func getModuleHandle() (syscall.Handle, error) {
ret, _, err := pGetModuleHandleW.Call(uintptr(0))
if ret == 0 {
return 0, err
}
return syscall.Handle(ret), nil
}
var (
user32 = syscall.NewLazyDLL("user32.dll")
pCreateWindowExW = user32.NewProc("CreateWindowExW")
pDefWindowProcW = user32.NewProc("DefWindowProcW")
pDestroyWindow = user32.NewProc("DestroyWindow")
pDispatchMessageW = user32.NewProc("DispatchMessageW")
pGetMessageW = user32.NewProc("GetMessageW")
pLoadCursorW = user32.NewProc("LoadCursorW")
pPostQuitMessage = user32.NewProc("PostQuitMessage")
pRegisterClassExW = user32.NewProc("RegisterClassExW")
pTranslateMessage = user32.NewProc("TranslateMessage")
)
const (
cSW_SHOW = 5
cSW_USE_DEFAULT = 0x80000000
)
const (
cWS_MAXIMIZE_BOX = 0x00010000
cWS_MINIMIZEBOX = 0x00020000
cWS_THICKFRAME = 0x00040000
cWS_SYSMENU = 0x00080000
cWS_CAPTION = 0x00C00000
cWS_VISIBLE = 0x10000000
cWS_OVERLAPPEDWINDOW = 0x00CF0000
)
func createWindow(className, windowName string, style uint32, x, y, width, height int32, parent, menu, instance syscall.Handle) (syscall.Handle, error) {
ret, _, err := pCreateWindowExW.Call(
uintptr(0),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(className))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(windowName))),
uintptr(style),
uintptr(x),
uintptr(y),
uintptr(width),
uintptr(height),
uintptr(parent),
uintptr(menu),
uintptr(instance),
uintptr(0),
)
if ret == 0 {
return 0, err
}
return syscall.Handle(ret), nil
}
const (
cWM_DESTROY = 0x0002
cWM_CLOSE = 0x0010
)
func defWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
ret, _, _ := pDefWindowProcW.Call(
uintptr(hwnd),
uintptr(msg),
uintptr(wparam),
uintptr(lparam),
)
return uintptr(ret)
}
func destroyWindow(hwnd syscall.Handle) error {
ret, _, err := pDestroyWindow.Call(uintptr(hwnd))
if ret == 0 {
return err
}
return nil
}
type tPOINT struct {
x, y int32
}
type tMSG struct {
hwnd syscall.Handle
message uint32
wParam uintptr
lParam uintptr
time uint32
pt tPOINT
}
func dispatchMessage(msg *tMSG) {
pDispatchMessageW.Call(uintptr(unsafe.Pointer(msg)))
}
func getMessage(msg *tMSG, hwnd syscall.Handle, msgFilterMin, msgFilterMax uint32) (bool, error) {
ret, _, err := pGetMessageW.Call(
uintptr(unsafe.Pointer(msg)),
uintptr(hwnd),
uintptr(msgFilterMin),
uintptr(msgFilterMax),
)
if int32(ret) == -1 {
return false, err
}
return int32(ret) != 0, nil
}
const (
cIDC_ARROW = 32512
)
func loadCursorResource(cursorName uint32) (syscall.Handle, error) {
ret, _, err := pLoadCursorW.Call(
uintptr(0),
uintptr(uint16(cursorName)),
)
if ret == 0 {
return 0, err
}
return syscall.Handle(ret), nil
}
func postQuitMessage(exitCode int32) {
pPostQuitMessage.Call(uintptr(exitCode))
}
const (
cCOLOR_WINDOW = 5
)
type tWNDCLASSEXW struct {
size uint32
style uint32
wndProc uintptr
clsExtra int32
wndExtra int32
instance syscall.Handle
icon syscall.Handle
cursor syscall.Handle
background syscall.Handle
menuName *uint16
className *uint16
iconSm syscall.Handle
}
func registerClassEx(wcx *tWNDCLASSEXW) (uint16, error) {
ret, _, err := pRegisterClassExW.Call(
uintptr(unsafe.Pointer(wcx)),
)
if ret == 0 {
return 0, err
}
return uint16(ret), nil
}
func translateMessage(msg *tMSG) {
pTranslateMessage.Call(uintptr(unsafe.Pointer(msg)))
}
func main() {
className := "testClass"
instance, err := getModuleHandle()
if err != nil {
log.Println(err)
return
}
cursor, err := loadCursorResource(cIDC_ARROW)
if err != nil {
log.Println(err)
return
}
fn := func(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
switch msg {
case cWM_CLOSE:
destroyWindow(hwnd)
case cWM_DESTROY:
postQuitMessage(0)
default:
ret := defWindowProc(hwnd, msg, wparam, lparam)
return ret
}
return 0
}
wcx := tWNDCLASSEXW{
wndProc: syscall.NewCallback(fn),
instance: instance,
cursor: cursor,
background: cCOLOR_WINDOW + 1,
className: syscall.StringToUTF16Ptr(className),
}
wcx.size = uint32(unsafe.Sizeof(wcx))
if _, err = registerClassEx(&wcx); err != nil {
log.Println(err)
return
}
_, err = createWindow(
className,
"Test Window",
cWS_VISIBLE|cWS_OVERLAPPEDWINDOW,
cSW_USE_DEFAULT,
cSW_USE_DEFAULT,
cSW_USE_DEFAULT,
cSW_USE_DEFAULT,
0,
0,
instance,
)
if err != nil {
log.Println(err)
return
}
for {
msg := tMSG{}
gotMessage, err := getMessage(&msg, 0, 0, 0)
if err != nil {
log.Println(err)
return
}
if gotMessage {
translateMessage(&msg)
dispatchMessage(&msg)
} else {
break
}
}
}
@mrichman
Copy link

mrichman commented Jun 23, 2016

I got a build error on this. .\win32.go:223: constant 2147483648 overflows int32. It's around "Test Window".

image

@sgravitz
Copy link

I also received the same error:
at line 223, file main.goconstant 2147483648 overflows int32

running go1.6 windows/amd64

@valkiril
Copy link

valkiril commented Jun 23, 2016

Changing int32to int64in line 53 solves this (might not be the best solution):

x, y, width, height int32 --> x, y, width, height int64

@waspoza
Copy link

waspoza commented Jun 23, 2016

@mrichman

I'm sorry for off topic, but what editor is that? Looks pretty :)

@javierprovecho
Copy link

@waspoza i'm pretty sure it is atom (from github) with go-plus plugin

@myleshorton
Copy link

Yup atom for sure @waspoza

@pdouglasssi
Copy link

pdouglasssi commented Jun 24, 2016

I don't know GO, but 2147483648 is 0x80000000, if an unsigned 32 bit integer (or even signed, if larger, eg. int64).

Presumably the setting of cSW_USE_DEFAULT = 0x80000000 is meant to be equivalent tocSW_USE_DEFAULT = -1 (or maybe -0 or -2147483647).

The locations it is used (specifying x, y, width, height, etc. are signed int32's (again suggesting that -1 means default).
I would assume that rather than inserting the bit pattern 0x80000000 (i.e. "10000000000000000000000000000000" where the leftmost bit is the sign flag, the checker at least (and maybe the interpreter/compiler) is either: type guessing and storing the no-type-specified constant (lines 37-39) cSW_USE_DEFAULT = 0x80000000 as either uint32 or int64 and therefore choking on assigning it to an int32 when it gets to the function, or: converting it to a number 2147483648 in some intermediary representation and then checking if that will fit into the int32 (which it won't, thus overflow).

Try changing cSW_USE_DEFAULT = 0x80000000 to cSW_USE_DEFAULT = -1 or casting the constant as an uint32.

I expect somewhere there is documentation that says something along the lines of "Values specified in hex default to unsigned int64" or "... unsigned int of the minimum size to hold the value" or something like that.

@yummyweb
Copy link

Pretty cool.

@yummyweb
Copy link

Using the windows api documentation, a more complex gui app is possible.

@i-e-b
Copy link

i-e-b commented Aug 25, 2021

Changing int32to int64in line 53 solves this (might not be the best solution):

x, y, width, height int32 --> x, y, width, height int64

x, y, width, height int32 --> x, y, width, height uint32 looks like it works too

@maito1201
Copy link

I confirmed that both of them had worked well.

x, y, width, height int32 --> x, y, width, height uint32
x, y, width, height int32 --> x, y, width, height int64

Anyway this is just what I want to know, Cool.

@pankajby
Copy link

Awesome! Thanks for posting this. It's working fine. The only change required was updating int32 to int64 for parameter within CreateWindow function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment