はじめに
Qiita 初投稿です。
Go の golang.org/x/sys には、OS のシステムコールの Wrapper 関数が定義されていますが、全てが網羅されているわけではありません。
例えば、windows package を見てみると、WSARecv() などはありますが、WSASocket() がない。
本項では、これを自前で定義する方法について述べます。
検証環境
- Windows 10
- go version go1.13.3 windows/amd64
Syscall の構造
まず、WSARecv()
がどのように定義されているか見ていきます。
WSARecv() の定義は golang.org/x/sys/windows/zsyscall_windows.go
にあります。
func WSARecv(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, overlapped *Overlapped, croutine *byte) (err error) {
r1, _, e1 := syscall.Syscall9(procWSARecv.Addr(), 7, uintptr(s), uintptr(unsafe.Pointer(bufs)), uintptr(bufcnt), uintptr(unsafe.Pointer(recvd)), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(croutine)), 0, 0)
if r1 == socket_error {
if e1 != 0 {
err = errnoErr(e1)
} else {
err = syscall.EINVAL
}
}
return
}
はい、謎の関数 syscall.Syscall9()
が現れました。
意味不明すぎて考えることをやめたくなりますが、恐れることはありません。ファイル先頭に次のようにある通り、これは自動生成コードです。
// Code generated by 'go generate'; DO NOT EDIT.
関数定義
同ディレクトリの syscall_windows.go
に生成する関数の定義があります。
//sys WSARecv(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, overlapped *Overlapped, croutine *byte) (err error) [failretval==socket_error] = ws2_32.WSARecv
長いので細かく分けて見ていきます。
//sys
system call のコード生成であることを表す。このコメントを、後述の mkwinsyscall
が拾う。
WSARecv(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, overlapped *Overlapped, croutine *byte) (err error)
定義する関数シグネチャ。WSABuf とか Overlapped とかの型は、windows package で定義されている。
[failretval==socket_error]
system call が戻り値 socket_error を返したときにエラーとする。socket_error は windows package の package private な定数。
const socket_error = uintptr(^uint32(0))
MSDN より
If no error occurs and the receive operation has completed immediately, WSARecv returns zero. In this case, the completion routine will have already been scheduled to be called once the calling thread is in the alertable state. Otherwise, a value of SOCKET_ERROR is returned
= ws2_32.WSARecv
上述の関数を、ws2_32.dll 内の WSARecv 関数にマッピングする。
コード生成
生成コマンド定義は mksyscall.go
にあります。
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go eventlog.go service.go syscall_windows.go security_windows.go
//go:generate go run
までは、Go generate の書式。
golang.org/x/sys/windows/mkwinsyscall
が zsyscall_windows.go eventlog.go service.go syscall_windows.go security_windows.go
から //sys
の定義を拾い上げて、zsyscall_windows.go
を生成しているようです。
自前で WSASocket() を定義してみる
仕組みはわかったので、自前で WSASocket() を定義してみます。
import "golang.org/x/sys/windows"
const (
wsaprotocol_len = 255
max_protocol_chain = 7
invalid_socket = ^windows.Handle(0)
)
type GROUP uint32
type GUID struct {
Data1 uint32
Data2 uint16
Data3 uint16
Data4 [8]byte
}
type WSAPROTOCOLCHAIN struct {
ChainLen int32
ChainEntries [max_protocol_chain]uint32
}
type WSAPROTOCOL_INFO struct {
ServiceFlags1 uint32
ServiceFlags2 uint32
ServiceFlags3 uint32
ServiceFlags4 uint32
ProviderFlags uint32
ProviderID GUID
CatalogEntryID uint32
ProtocolChain WSAPROTOCOLCHAIN
Version int32
AddressFamily int32
MaxSockAddr int32
MinSockAddr int32
SocketType int32
Protocol int32
ProtocolMaxOffset int32
NetworkByteOrder int32
SecurityScheme int32
MessageSize uint32
ProviderReserved uint32
Protocols [wsaprotocol_len + 1]uint16
}
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zwinsys.go winsys.go
//sys WSASocket(af int32, tp int32, protocol int32, protocolInfo *WSAPROTOCOL_INFO, g GROUP, flags uint32) (socket windows.Handle, err error) [failretval==invalid_socket] = ws2_32.WSASocketW
なにやら構造体やら色々でてきましたが、これらは C の構造体やマクロ定義を移植したものです。(この辺の情報を一々調べて定義するのが面倒。。。)
ここで出てきた WORD, DWORD, int の対応は次の通り。
C | Go |
---|---|
WORD | uint16 |
DWORD | uint32 |
int | int32 |
コード生成
//go:generate
を記述した package のディレクトリで次のコマンドを実行すると、zmysys.go
が生成されます。
$ go generate
実際に使ってみる
正常ケースとエラーケースを試してみます。
まずは正常ケース。
sock, err := WSASocket(windows.AF_INET, windows.SOCK_STREAM, windows.IPPROTO_TCP, nil, 0, 0)
fmt.Println(sock, err)
// => 344 <nil>
正しくSOCKETが生成され、エラーも発生していないようです。
次にエラーケース。
const badArg int32 = 9999
sock, err := WSASocket(badArg, badArg, badArg, nil, 0, 0)
fmt.Println(sock, err)
// => 18446744073709551615 The support for the specified socket type does not exist in this address family.
不正な値を与えたので、WSAESOCKTNOSUPPORT のエラーが返ってきました。
このメッセージは FormatMessageWで取得されたものです。
エラーコードは syscall.Syscall<N>()
の3番目の戻り値として返ってきます。その実装は runtime のアセンブラで定義されていてコードを追いきれませんでしたが、GetLastError()
の結果を返してきている模様。(たぶんこの辺)
本来、WSAXXX()
のエラーコードは WSAGetLastError()
で取得するものですが、WSAGetLastError()
は GetLastError()
の Wrapper であるがため、問題なく動いているようです。
まとめ
標準package で定義されていない system call も、golang.org/x/sys/windows/mkwinsyscall
を使えば、比較的簡単に呼び出し可能。