Win32 API and GUI in idiomatic Go.
Windigo is designed to be familiar to C/C++ Win32 programmers, using the same concepts, and an API as close as possible to the original Win32 API. This allows most C/C++ Win32 tutorials and examples to be translated to Go.
Notably, Windigo is written 100% in pure Go – CGo is not used, just native syscalls.
go get -u github.com/rodrigocfd/windigo
In the examples below, error checking is ommited for brevity.
GUI window
The example below creates a window programmatically, and handles the button click. Also, it uses the minimal.syso
provided in the resources folder.
package main
import (
"fmt"
"runtime"
"github.com/rodrigocfd/windigo/ui"
"github.com/rodrigocfd/windigo/win/co"
)
func main() {
runtime.LockOSThread() // important: Windows GUI is single-threaded
myWindow := NewMyWindow() // instantiate
myWindow.wnd.RunAsMain() // ...and run
}
// This struct represents our main window.
type MyWindow struct {
wnd *ui.Main
lblName *ui.Static
txtName *ui.Edit
btnShow *ui.Button
}
// Creates a new instance of our main window.
func NewMyWindow() *MyWindow {
wnd := ui.NewMain( // create the main window
ui.OptsMain().
Title("Hello you").
Size(ui.Dpi(340, 80)).
ClassIconId(101), // ID of icon resource, see resources folder
)
lblName := ui.NewStatic( // create the child controls
wnd,
ui.OptsStatic().
Text("Your name").
Position(ui.Dpi(10, 22)),
)
txtName := ui.NewEdit(
wnd,
ui.OptsEdit().
Position(ui.Dpi(80, 20)).
Width(ui.DpiX(150)),
)
btnShow := ui.NewButton(
wnd,
ui.OptsButton().
Text("&Show").
Position(ui.Dpi(240, 19)),
)
me := &MyWindow{wnd, lblName, txtName, btnShow}
me.events()
return me
}
func (me *MyWindow) events() {
me.btnShow.On().BnClicked(func() {
msg := fmt.Sprintf("Hello, %s!", me.txtName.Text())
me.wnd.Hwnd().MessageBox(msg, "Saying hello", co.MB_ICONINFORMATION)
})
}
To compile the final .exe
file, run the command:
go build -ldflags "-s -w -H=windowsgui"
Registry access
package main
import (
"github.com/rodrigocfd/windigo/win"
"github.com/rodrigocfd/windigo/win/co"
)
func main() {
// Open a registry key
hKey, _ := win.HKEY_CURRENT_USER.RegOpenKeyEx(
"Control Panel\\Mouse",
co.REG_OPTION_NONE,
co.KEY_READ) // open key as read-only
defer hKey.RegCloseKey()
// Read a single value from this key
regVal, _ := hKey.RegQueryValueEx("Beep") // data can be string, uint32, etc.
if strVal, ok := regVal.Sz(); ok { // try to extract a string value
println("Beep is", strVal)
}
// Enumerate all values under this key
namesVals, _ := hKey.RegEnumValue()
for _, nameVal := range namesVals {
if str, ok := nameVal.Val.Sz(); ok { // does it contain a string?
println("Value str", nameVal.Name, str)
} else if num, ok := nameVal.Val.Dword(); ok { // does it contain an uint32?
println("Value int", nameVal.Name, num)
} else {
println("Value other", nameVal.Name)
}
}
}
Enumerating running processes
The example below takes a process snapshot to list the running processes:
package main
import (
"github.com/rodrigocfd/windigo/win"
"github.com/rodrigocfd/windigo/win/co"
)
func main() {
hSnap, _ := win.CreateToolhelp32Snapshot(co.TH32CS_SNAPPROCESS, 0)
defer hSnap.CloseHandle()
processes, _ := hSnap.EnumProcesses()
for _, nfo := range processes {
println("PID:", nfo.Th32ProcessID, "name:", nfo.SzExeFile())
}
println(len(processes), "found")
}
Taking a screenshot
This complex example takes a screenshot using GDI and saves it to a BMP file.
package main
import (
"unsafe"
"github.com/rodrigocfd/windigo/win"
"github.com/rodrigocfd/windigo/win/co"
)
func main() {
cxScreen := win.GetSystemMetrics(co.SM_CXSCREEN)
cyScreen := win.GetSystemMetrics(co.SM_CYSCREEN)
hdcScreen, _ := win.HWND(0).GetDC()
defer win.HWND(0).ReleaseDC(hdcScreen)
hBmp, _ := hdcScreen.CreateCompatibleBitmap(uint(cxScreen), uint(cyScreen))
defer hBmp.DeleteObject()
hdcMem, _ := hdcScreen.CreateCompatibleDC()
defer hdcMem.DeleteDC()
hBmpOld, _ := hdcMem.SelectObjectBmp(hBmp)
defer hdcMem.SelectObjectBmp(hBmpOld)
_ = hdcMem.BitBlt(
win.POINT{X: 0, Y: 0},
win.SIZE{Cx: cxScreen, Cy: cyScreen},
hdcScreen,
win.POINT{X: 0, Y: 0},
co.ROP_SRCCOPY,
)
bi := win.BITMAPINFO{
BmiHeader: win.BITMAPINFOHEADER{
BiWidth: cxScreen,
BiHeight: cyScreen,
BiPlanes: 1,
BiBitCount: 32,
BiCompression: co.BI_RGB,
},
}
bi.BmiHeader.SetBiSize()
bmpObj, _ := hBmp.GetObject()
bmpSize := bmpObj.CalcBitmapSize(bi.BmiHeader.BiBitCount)
rawMem, _ := win.GlobalAlloc(co.GMEM_FIXED|co.GMEM_ZEROINIT, bmpSize)
defer rawMem.GlobalFree()
bmpSlice, _ := rawMem.GlobalLockSlice()
defer rawMem.GlobalUnlock()
_, _ = hdcScreen.GetDIBits(
hBmp,
0,
uint(cyScreen),
bmpSlice,
&bi,
co.DIB_RGB_COLORS,
)
var bfh win.BITMAPFILEHEADER
bfh.SetBfType()
bfh.SetBfOffBits(uint32(unsafe.Sizeof(bfh) + unsafe.Sizeof(bi.BmiHeader)))
bfh.SetBfSize(bfh.BfOffBits() + uint32(bmpSize))
fout, _ := win.FileOpen(
"C:\\Temp\\screenshot.bmp",
co.FOPEN_RW_OPEN_OR_CREATE,
)
defer fout.Close()
_, _ = fout.Write(bfh.Serialize())
_, _ = fout.Write(bi.BmiHeader.Serialize())
_, _ = fout.Write(bmpSlice)
}
Component Object Model (COM)
Windigo has full support for C++ COM objects. The cleanup is performed by a win.OleReleaser
object, which calls Release
on multiple COM objects at once, much like an arena allocator. Every function which produces a COM object requires a win.OleReleaser
to take care of its lifetime.
The example below uses COM objects to display the system native Open File window:
package main
import (
"github.com/rodrigocfd/windigo/win"
"github.com/rodrigocfd/windigo/win/co"
)
func main() {
runtime.LockOSThread() // important: Windows GUI is single-threaded
_, _ := win.CoInitializeEx(
co.COINIT_APARTMENTTHREADED | co.COINIT_DISABLE_OLE1DDE)
defer win.CoUninitialize()
releaser := win.NewOleReleaser() // will release all COM objects created here
defer releaser.Release()
var fod *win.IFileOpenDialog
_ = win.CoCreateInstance(
releaser,
co.CLSID_FileOpenDialog,
nil,
co.CLSCTX_INPROC_SERVER,
&fod,
)
defOpts, _ := fod.GetOptions()
_ = fod.SetOptions(defOpts |
co.FOS_FORCEFILESYSTEM |
co.FOS_FILEMUSTEXIST,
)
_ = fod.SetFileTypes([]win.COMDLG_FILTERSPEC{
{Name: "Text files", Spec: "*.txt"},
{Name: "All files", Spec: "*.*"},
})
_ = fod.SetFileTypeIndex(1)
if ok, _ := fod.Show(win.HWND(0)); ok { // in real applications, pass the parent HWND
item, _ := fod.GetResult(releaser)
fileName, _ := item.GetDisplayName(co.SIGDN_FILESYSPATH)
println(fileName)
}
}
COM Automation
Windigo has bindings to IDispatch
COM interface and VARIANT
parameters, allowing you to invoke Automation methods.
The example below manipulates an Excel spreadsheet, saving a copy of it:
package main
import (
"github.com/rodrigocfd/windigo/win"
"github.com/rodrigocfd/windigo/win/co"
)
func main() {
_, _ = win.CoInitializeEx(
co.COINIT_APARTMENTTHREADED | co.COINIT_DISABLE_OLE1DDE)
defer win.CoUninitialize()
rel := win.NewOleReleaser()
defer rel.Release()
clsId, _ := win.CLSIDFromProgID("Excel.Application")
var excel *win.IDispatch
_ = win.CoCreateInstance(
rel,
clsId,
nil,
co.CLSCTX_LOCAL_SERVER,
&excel,
)
books, _ := excel.InvokeGetIDispatch(rel, "Workbooks")
file, _ := books.InvokeMethodIDispatch(rel, "Open", "C:\\Temp\\foo.xlsx")
_, _ = file.InvokeMethod(rel, "SaveAs", "C:\\Temp\\foo copy.xlsx")
_, _ = file.InvokeMethod(rel, "Close")
}
The library is divided in two main packages:
ui
– high-level windows and controls;win
– low-level native Win32 bindings.
More specifically:
Package | Description |
---|---|
ui |
High-level UI windows and controls. |
win |
Native Win32 structs, handles and functions. |
win/co |
Native Win32 constants, all typed. |
win/wstr |
String and UTF-16 wide string management. |
Internal package dependency:
flowchart BT
internal/utl([internal/utl]) --> win/co
ui --> win
win --> internal/dll([internal/dll])
win --> internal/utl
win --> win/wstr
Licensed under MIT license, see LICENSE.md for details.