Skip to content

Calling Windows API from Go

Hanjun Kim edited this page Mar 11, 2019 · 19 revisions

Calling Windows API from Go is pretty simple and straightforward as documented in official repository. So what I'm gonna list here are just some additional information about it.

Should I use syscall, or golang.org/x/sys/windows?

It's recommended using golang.org/x/sys/windows instead of standard syscall if possible:

Yes, you should switch to x/sys. Some things crept into syscall under the covers, others went in because it's the job of that package to support the main repo and changes were required. But external packages should be using x/sys.

- Rob Pike from Google Groups

You can see there are lots of Windows APIs already defined in golang.org/x/sys/windows. If you're gonna use only those Windows APIs listed there, it's fine and encouraged to just use them. But if you find you need other APIs that are not listed there, you should define it by your hands.

ANSI and Unicode versions of Windows API

If a Windows API takes string as its parameter, there are two different versions of same API. As you probably know, ANSI and Unicode versions. Strings in Go are basically unicode strings, so we'd use Unicode version of Windows API that has W prefix(e.g. MessageBoxW) most of the time.

Windows Data Types

I recommend you to have Windows Data Types link in your bookmark bar. It's really helpful in situations like, when you get stuck wondering what LGRPID type is.

For common data types that can be found on frequently used Windows APIs, I wrote a list of corresponding data type in Go. See Data Type Cheat Sheet for details.

Defining Windows API by Hands

Let's call MessageBoxW from Go.

First you need to load system DLLs and find procedures. You can find several ways to do it, but here I'm gonna explain the easiest one:

libuser32 := windows.NewLazySystemDLL("user32.dll")  // Load 'user32.dll' from Windows system directory
procMessageBoxW := libuser32.NewProc("MessageBoxW")  // Find 'MessageBoxW' procedure inside the DLL

Notice that we use LazyDLL and LazyProc here so that our program only loads libraries it uses through runtime.

And that's all we need for our first API call. We can now call the MessageBoxW procedure from user32.dll like this:

procMessageBoxW.Call(0, text, caption, 0)

But, here, as we pass strings to API, we have to convert strings to something that Windows API can understand. golang.org/x/sys/windows.UTF16PtrFromString is for this purpose. You can use StringToUTF16Ptr as well, but documentation says it is deprecated and use UTF16PtrFromString instead. So here's our code for text and caption:

text, err := windows.UTF16PtrFromString("Hello, World!")
if err != nil { ... }
caption, err := windows.UTF16PtrFromString("Go Windows Programming")
if err != nil { ... }

That's it. You can now see message box popping out.

GetLastError Problem

In general, when your API call fails, you would call GetLastError to get the reason. But in Go, you can't do it simply with `GetLastError. Let me explain why.

Take a look at the documentation. It says:

The returned error is always non-nil, constructed from the result of GetLastError. ...

This results in the loss of any previous API call errors at the time you call GetLastError. Strange, right? But you can still get the reason of failure for that single API call, by inspecting returned error from Proc.Call. So here's how I deal with it.

  1. If API sets last error(some APIs doesn't), store the returned error in global variable.
  2. Define custom GetLastError function, make it return that global variable(if there was an error).

You can find real implementations and examples in example/syscall/lasterror.

Dealing with 64 bit arguments on 32 bit machine

Did you know that there was a secret feature in syscall package? Well, it's actually not secret but most of people might not know it. Until now we used only the first return value from syscalls like this:

r1, _, _ := procSomething.Call(...)

Most of the time second return value will be just 0, and is something that can be ignored. But when it comes to dealing with 64 bit APIs on 32 bit machine, the second value must be used in combination with the first value.

(WIP)

Examples

See example/syscall for details.