Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 15095] ファイルの概要

このコミットは、Go言語の標準ライブラリの一部である src/pkg/syscall/dll_windows.go ファイルに対する変更です。このファイルは、Windowsオペレーティングシステム上でシステムコール(Windows API関数)を呼び出すための低レベルな機能を提供します。具体的には、ダイナミックリンクライブラリ(DLL)内のプロシージャ(関数)を呼び出すための Proc および LazyProc 型の Call メソッドに関連する修正が含まれています。

コミット

  • コミットハッシュ: dcf16bd83d3c36430771f36df53044be3dda67fa
  • 作者: Shenghou Ma minux.ma@gmail.com
  • コミット日時: 2013年2月3日(日)01:42:17 +0800
  • 変更ファイル: src/pkg/syscall/dll_windows.go (1ファイル)
  • 変更行数: 16行追加、4行削除

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/dcf16bd83d3c36430771f36df53044be3dda67fa

元コミット内容

    syscall: (*Proc).Call should return nil error when no error occurs
    Fixes #4686.
    
    R=alex.brainman, rsc
    CC=golang-dev
    https://golang.org/cl/7174047

このコミットメッセージは、syscallパッケージの(*Proc).Callメソッドが、エラーが発生していない場合にnilエラーを返すように修正することを示しています。これはGoのイディオムに沿ったエラーハンドリングであり、Fixes #4686という記述から、GoのIssueトラッカーで報告された問題4686を解決するためのものであることがわかります。

変更の背景

Windows APIの呼び出しにおいて、多くの関数は成功/失敗を示す戻り値を持ち、詳細なエラー情報はGetLastErrorという関数を通じて取得されます。Goのsyscallパッケージは、これらのWindows APIをGoから呼び出すためのラッパーを提供しています。

従来の(*Proc).Callおよび(*LazyProc).Callメソッドは、Windows APIの呼び出し結果に関わらず、常にGetLastErrorの結果をerror戻り値として返していました。これは、API呼び出しが成功した場合でも、GetLastErrorが以前のエラーコードを保持している可能性があるため、nilではないエラーが返されるという問題を引き起こしていました。

Goのエラーハンドリングの慣習では、エラーが発生しなかった場合はnilを返すことが期待されます。この乖離は、syscallパッケージを利用する開発者にとって混乱の原因となり、エラーチェックのロジックを複雑にしていました。Issue #4686は、この問題点を指摘し、CallメソッドがGoのエラーハンドリングの慣習に従うべきであることを提唱していました。

このコミットは、Callメソッドのerror戻り値のセマンティクスを明確にし、Windows API呼び出しが成功した場合にはnilエラーを返すように修正することで、この問題を解決することを目的としています。

前提知識の解説

Go言語のsyscallパッケージ

Go言語のsyscallパッケージは、オペレーティングシステムが提供する低レベルなプリミティブ(システムコール)へのアクセスを提供します。これにより、ファイル操作、プロセス管理、ネットワーク通信など、OS固有の機能に直接アクセスできます。特にWindows環境では、このパッケージを通じてWindows API関数を呼び出すことが可能です。

Windows APIとGetLastError

Windows APIは、Windowsオペレーティングシステムの機能にアクセスするためのプログラミングインターフェースです。多くのWindows API関数は、成功した場合は非ゼロの値や特定の成功コードを返し、失敗した場合はゼロやエラーを示す値を返します。詳細なエラー情報(エラーコード)は、GetLastErrorという関数を呼び出すことで取得できます。GetLastErrorは、現在のスレッドで最後に発生したエラーのコードを返します。このエラーコードは、エラーの種類を特定するために使用されます。

uintptr

Go言語のuintptr型は、ポインタを保持するのに十分な大きさの符号なし整数型です。これは、GoのポインタとC言語のポインタ(またはOSのハンドルやアドレス)の間で変換を行う際に使用されます。syscallパッケージでは、Windows API関数に引数を渡したり、戻り値を受け取ったりする際に、このuintptr型が頻繁に用いられます。

技術的詳細

このコミットの核心は、syscallパッケージ内のProcおよびLazyProc型のCallメソッドのエラーハンドリングのセマンティクスを修正することです。

Windows APIの呼び出しでは、関数が成功したかどうかは通常、その関数の戻り値によって判断されます。例えば、CreateFile関数は成功するとファイルハンドルを返し、失敗するとINVALID_HANDLE_VALUEを返します。そして、失敗した場合にGetLastErrorを呼び出すことで具体的なエラーコードを取得します。

Goのsyscall.Syscall関数(およびそのバリアント)は、Windows APIを呼び出すための低レベルなラッパーであり、r1, r2, errの3つの戻り値を持ちます。ここでerrは、GetLastErrorの結果に基づいて設定されます。

問題は、(*Proc).Call(*LazyProc).Callが、Syscallerr戻り値をそのまま自身のerr戻り値として公開していた点にありました。これにより、Windows API呼び出し自体は成功しているにもかかわらず、GetLastErrorが以前の呼び出しのエラーコードを保持しているために、Callメソッドがnilではないエラーを返してしまうという状況が発生していました。これはGoのエラーハンドリングの慣習(エラーがない場合はnilを返す)に反していました。

このコミットでは、Callメソッドのコメントを更新し、err戻り値のセマンティクスを明確にしています。新しいセマンティクスでは、lastErr(以前のerr)は常に非nilであり、GetLastErrorの結果から構築されることが明記されています。そして、呼び出し元は、実際にエラーが発生したかどうかを判断するために、r1r2といった主要な戻り値を検査する必要がある、と強調されています。これにより、Goの慣習に沿ったエラーハンドリングを維持しつつ、Windows APIのGetLastErrorの挙動を正確に反映できるようになります。

コアとなるコードの変更箇所

変更はsrc/pkg/syscall/dll_windows.goファイルに集中しています。

--- a/src/pkg/syscall/dll_windows.go
+++ b/src/pkg/syscall/dll_windows.go
@@ -114,8 +114,14 @@ func (p *Proc) Addr() uintptr {
 	return p.addr
 }
 
-// Call executes procedure p with arguments a.
-func (p *Proc) Call(a ...uintptr) (r1, r2 uintptr, err error) {
+// Call executes procedure p with arguments a. It will panic, if more then 15 arguments
+// are supplied.
+//
+// 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 will be guaranteed to contain syscall.Errno.
+func (p *Proc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) {
 	switch len(a) {
 	case 0:
 		return Syscall(p.Addr(), uintptr(len(a)), 0, 0, 0)
@@ -260,8 +266,14 @@ func (p *LazyProc) Addr() uintptr {
 	return p.proc.Addr()
 }
 
-// Call executes procedure p with arguments a.
-func (p *LazyProc) Call(a ...uintptr) (r1, r2 uintptr, err error) {
+// Call executes procedure p with arguments a. It will panic, if more then 15 arguments
+// are supplied.
+//
+// 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 will be guaranteed to contain syscall.Errno.
+func (p *LazyProc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) {
 	p.mustFind()
 	return p.proc.Call(a...)
 }

コアとなるコードの解説

このコミットでは、ProcLazyProcCallメソッドのシグネチャとコメントが変更されています。

  1. 戻り値の変数名変更:

    • func (p *Proc) Call(a ...uintptr) (r1, r2 uintptr, err error)
    • func (p *Proc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) errという変数名がlastErrに変更されました。これは、このエラーが常にGetLastErrorの結果であり、API呼び出し自体の成功/失敗を示すものではないことをより明確にするためのものです。
  2. コメントの追加と修正: 最も重要な変更は、Callメソッドのドキュメンテーションコメントです。

    • // Call executes procedure p with arguments a. (変更前)
    • // Call executes procedure p with arguments a. It will panic, if more then 15 arguments // are supplied. // // 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 will be guaranteed to contain syscall.Errno. (変更後)

    新しいコメントは以下の点を明確にしています。

    • 引数の制限: 15個を超える引数が渡された場合にパニックが発生すること。
    • エラーのセマンティクス:
      • 返されるエラー(lastErr)は**常に非nil**であり、GetLastErrorの結果から構築されること。
      • 呼び出し元は、主要な戻り値(r1, r2)を検査して、実際にエラーが発生したかどうかを判断する必要があること。これは、呼び出された特定のWindows API関数のセマンティクスに従うべきです。
      • 返されるエラーはsyscall.Errno型であることが保証されること。

この変更により、syscall.Callを使用する開発者は、lastErrnilであるかどうかでエラーの有無を判断するのではなく、Windows APIのドキュメントに従ってr1r2などの主要な戻り値をチェックし、その上でlastErrから詳細なエラー情報を取得するという、より正確なエラーハンドリングを行うことができるようになります。

関連リンク

参考にした情報源リンク