Skip to content

WindowsのProc.Callは何を返すのか

GoでWindowsのDLLから関数をロードして利用するとき、典型的には以下のような書き方になる。

import (
"syscall"
)
var (
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
getDriveType = modkernel32.NewProc("GetDriveTypeW")
)
r, _, err := getDriveType.Call(args...)
if err != 0 {
...
}

ここで成功した場合でも、errnil ではなく

The operation completed successfully.

が返ってくる。これはgolang.org/x/sys/windowsで、

const (
ERROR_SUCCESS syscall.Errno = 0
NO_ERROR = 0
)

のように定義されていて、エラーという体裁だけど正常を表している。

ところで、2番目の戻り値は何だろうか。Callはsyscall.LazyProc.Callだけど、ドキュメントにはsyscall.Proc.Callを読めとあり、そこには

The returned error is always non-nil, constructed from the result of GetLastError. Callers must inspect the primary return value to decide whether an error occurred (according to the semantics of the specific function being called) before consulting the error. The error always has type syscall.Errno. On amd64, Call can pass and return floating-point values. To pass an argument x with C type “float”, use uintptr(math.Float32bits(x)). To pass an argument with C type “double”, use uintptr(math.Float64bits(x)). Floating-point return values are returned in r2. The return value for C type “float” is math.Float32frombits(uint32(r2)). For C type “double”, it is math.Float64frombits(uint64(r2)).

syscall/dll_windows.go では宣言だけ。

func SyscallN(trap uintptr, args ...uintptr) (r1, r2 uintptr, err Errno)

runtime/syscall_windows.go で定義されている。

//go:linkname syscall_SyscallN syscall.SyscallN
//go:nosplit
func syscall_SyscallN(trap uintptr, args ...uintptr) (r1, r2, err uintptr) {
...
c := &getg().m.syscall
c.fn = trap
c.n = uintptr(nargs)
c.args = uintptr(noescape(unsafe.Pointer(&args[0])))
cgocall(asmstdcallAddr, unsafe.Pointer(c))
return c.r1, c.r2, c.err

中で呼ばれているcgocallは runtime/cgocall.go にある

func cgocall(fn, arg unsafe.Pointer) int32 {
...
entersyscall() // Ring 0に移動
osPreemptExtEnter(mp)
errno := asmcgocall(fn, arg)
osPreemptExtExit(mp)
exitsyscall() // Ring 3に戻る
...
return errno
}

実際に戻り値をストアしているのは runtime2.go に書かれている。

type libcall struct {
fn uintptr
n uintptr // number of parameters
args uintptr // parameters
r1 uintptr // return values
r2 uintptr
err uintptr // error number
}
type m struct {
syscall libcall // stores syscall parameters on windows
}

r1, r2 はこのコミットで追加されている

32bit以下の値を返すときは EAX レジスタを使うが、32bit環境で64bit値を EAX レジスタに保持できない。なのでWindows/386では64bit値を返すとき、下位ビットを EAX、上位ビットを EDX レジスタにストアしてから返す。r1EAX に相当して、r2EDX に対応する。

64bit値を返す関数はたとえばGetTickCount64がある。LONGLONGはCのlong longなので最低64bit整数となる。

そういった都合で返却される値なので、64bit環境の場合は RAX レジスタだけで対応できるため r2 の値は必要ない。