With no thanks to whoever organizes Go documentation, and in the hope that I can prevent someone else from wasting hours figuring out how to access Windows DLLs, this attempts to explain how to use the new syscall.SyscallN
functionality to access custom DLLs on Windows (tested on 64-bit Win10 with a legacy 64-bit DLL written in C and compiled using Visual Studio 2019). For reference, as of June 28 2022 the underlying Go code for SyscallN
is apparently only at https://github.com/golang/go/blob/master/src/syscall/dll_windows.go
The mkwinsyscall.go
source may also be helpful.
- Make sure to import the necessary Go modules (this example assumes Go v1.18 +):
import (
. . .
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
- Load the DLL (this will Panic if DLL is not found):
declare a lazy DLL reference and lazy pointer to the proc (procedure) address of the C function in the DLL:
var pLazyDll *syscall.LazyDLL
var pLazyProc *syscall.LazyProc
Then get an actual reference to the DLL (this assumes your DLL is in
Windows\System32 on 64 Windows - if not you'll need to look at Windows rules for locating DLLs):
pLazyDll = syscall.NewLazyDLL("name-of-dll-with-extension")
For example:
syscall.NewLazyDLL("myDLL.dll")
- Get a reference to the procedure (this will Panic if the procedure is not found):
pLazyProc = pLazyDll.NewProc(“name-of-proc-in-dll”) // use dumpbin or some other tool to list names
- Define a
**Go**
function to encapsulate calls to the C function in the DLL
YOU MUST KNOW THE TYPES OF ALL PARAMETERS AND THE RETURN TYPE OF THE C FUNCTION (the Procedure in DLL speak) !
As an example, for a **C**
function that:
-
expects a 32-bit
int
and a C char buffer (const *char
) as arguments (where the C function will fill the buffer with a NULL terminated C string) -
returns a 32-bit
int
as an error code
The **Go**
function wrapper (which uses syscall.SyscallN
to call the C function in the DLL) would look like:
func myGoFunction(p1 uint32, buf *byte) (err int32) {
r0, _, _ := syscall.SyscallN(pLazyProc.Addr(), uintptr(p1), uintptr(unsafe.Pointer(buf)))
err = int32(r0)
return
}
NOTE the Addr member of the pLazyProc
structure contains the address of the function being called, and that ALL arguments must be cast to uintptr
. The returned value in r0
must be cast to int32
to match the function signature. Refer to the source at the link above for more information about the values returned by syscall.SyscallN
that are not used here.
- Prepare to call the function. Since in this example the
**Go**
function expects the DLL procedure to return a (NULL terminated) C string, a Gobyte array
must be created (allocated) by the caller to pass to the function (along with theuint32
argument value):
buffer := make([]byte, 256) // size of buffer depends on size of strings that might be returned
var p1 uint32 = 42 // arbitrary for example
-
Call the function and get results (this will Panic if buffer is too small):
result := myGoFunction(p1, &buffer[0]) // NOTE use of address of first byte of buffer
The
result
returned will be anint32
.To access the (NULL terminated) string from the C buffer, convert it to a Go string using a
function from the Gowindows
module:
goString := windows.BytePtrToString(&buffer[0])
And there you have it.