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

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

このコミットは、Go言語のsyscallパッケージにおけるWindows固有の実装に関するものです。具体的には、syscall_windows.goファイル内にコメントアウトされていたWindows API呼び出しのサンプルコードを、syscall_windows_test.goに独立したテスト可能なExample関数として抽出し、同時に古くなったコメントを修正しています。

コミット

commit f78ead3ca461e2e3bd003e11c49eae610a10ab97
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Wed Aug 29 21:44:46 2012 +0800

    syscall: extract an ExampleLoadLibrary from comment
       while we are at it, fix some out-of-date comments.
    
    R=golang-dev, dave, r
    CC=golang-dev
    https://golang.org/cl/6498054

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

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

元コミット内容

このコミットの目的は以下の通りです。

  1. src/pkg/syscall/syscall_windows.go内のコメントアウトされたWindows API呼び出しのデモコードを、src/pkg/syscall/syscall_windows_test.goExampleLoadLibraryという名前のExample関数として抽出する。
  2. src/pkg/syscall/asm_windows_386.ssrc/pkg/syscall/asm_windows_amd64.s内の古くなったコメントを修正する。

変更の背景

Go言語の標準ライブラリでは、コードの利用方法を示すためにExample関数が推奨されています。Example関数は、go testコマンドで実行可能であり、godocによって自動的にドキュメントとして生成されます。これにより、ユーザーは実際のコード例を通じてAPIの利用方法を理解しやすくなります。

このコミット以前は、Windows APIのLoadLibraryGetProcAddressGetVersionといった重要な関数群の利用例が、syscall_windows.goファイル内の大きなコメントブロックとして存在していました。コメント内のコードはテストやドキュメント生成の対象とならないため、その有用性が限定的でした。

この変更の背景には、以下の意図が考えられます。

  • ドキュメンテーションの改善: コメント内のコードをExample関数として抽出することで、godocを通じて公式ドキュメントに組み込まれ、ユーザーがsyscallパッケージのWindows固有の機能を利用する際の参考になる。
  • テスト可能性の向上: Example関数はgo testによって実行されるため、サンプルコードが常に動作することを保証できる。これにより、APIの変更や環境の変化によってサンプルコードが古くなることを防ぐ。
  • コードベースの整理: 不要な大きなコメントブロックを削除し、関連するテストファイルにコード例を移動することで、コードの可読性と保守性を向上させる。
  • コメントの正確性の維持: アセンブリファイル内の古くなったコメントを修正することで、コードベース全体の正確性を保つ。特に、システムコールが実装されている場所に関する記述の更新は重要です。

前提知識の解説

このコミットを理解するためには、以下の知識が役立ちます。

1. Go言語のsyscallパッケージ

syscallパッケージは、Goプログラムからオペレーティングシステム(OS)のプリミティブな機能(システムコール)にアクセスするためのインターフェースを提供します。ファイル操作、ネットワーク通信、プロセス管理など、OSレベルの低レベルな操作を行う際に使用されます。OSごとに異なる実装を持つため、syscall_windows.goのようにOS固有のファイルが存在します。

2. Windows API (Win32 API)

Windows APIは、Microsoft Windowsオペレーティングシステムが提供するアプリケーションプログラミングインターフェースの集合体です。C言語で記述されており、DLL(Dynamic Link Library)として提供されます。Go言語のsyscallパッケージは、このWindows APIをGoから呼び出すためのラッパーを提供します。

  • LoadLibrary: 指定されたモジュール(DLL)を呼び出し元のプロセスのアドレス空間にロードします。これにより、そのDLL内の関数を利用できるようになります。
  • GetProcAddress: ロードされたモジュール(DLL)から、指定された関数名に対応する関数のエントリポイント(アドレス)を取得します。
  • GetVersion: 現在実行中のWindowsオペレーティングシステムのバージョン情報を取得する関数です。メジャーバージョン、マイナーバージョン、ビルド番号などが含まれます。
  • Syscall: Go言語から直接システムコールを呼び出すための汎用関数です。引数として関数のエントリポイント(uintptr型)と、システムコールに渡す引数を取ります。

3. Go言語のExample関数

Go言語のテストパッケージ(testing)には、Exampleというプレフィックスを持つ関数を記述する慣習があります。これらの関数は、通常のテスト関数(Testプレフィックス)とは異なり、コードの動作例を示すために使用されます。

  • 自動実行: go testコマンドを実行すると、Example関数も自動的に実行されます。
  • 出力検証: Example関数のコメントにOutput:という行を記述することで、そのExample関数が標準出力に出力する内容を検証できます。これにより、サンプルコードが期待通りの出力を生成するかどうかをテストできます。
  • ドキュメント生成: godocツールは、Example関数を自動的に抽出し、パッケージのドキュメントに含めます。これにより、ユーザーはgodocを通じてコード例を参照できます。

4. Go言語のアセンブリファイル (.sファイル)

Go言語のランタイムや一部の低レベルな処理は、アセンブリ言語で記述されています。src/pkg/syscall/asm_windows_386.ssrc/pkg/syscall/asm_windows_amd64.sは、それぞれ32ビットおよび64ビットWindows環境におけるシステムコール関連のアセンブリコードを含んでいます。これらのファイル内のコメントは、そのアセンブリコードがGoランタイムのどの部分と連携しているかを示す重要な情報を含んでいます。

技術的詳細

このコミットの技術的な変更点は以下の通りです。

1. syscall_windows.goからのコメントブロック削除

src/pkg/syscall/syscall_windows.goから、約30行にわたるWindowsバージョン検出のデモコードがコメントアウトされた状態で削除されました。このコードは、kernel32.dllをロードし、GetVersion関数を呼び出してWindowsのバージョン情報を取得し、それを標準出力に表示するものでした。

削除されたコメントブロックの冒頭部分:

/*

small demo to detect version of windows you are running:

package main

import (
	"syscall"
)

func abort(funcname string, err error) {
	panic(funcname + " failed: " + err.Error())
}

func print_version(v uint32) {
	major := byte(v)
	minor := uint8(v >> 8)
	build := uint16(v >> 16)
	print("windows version ", major, ".", minor, " (Build ", build, ")\\n")
}

func main() {
	h, err := syscall.LoadLibrary("kernel32.dll")
	if err != nil {
		abort("LoadLibrary", err)
	}
	defer syscall.FreeLibrary(h)
	proc, err := syscall.GetProcAddress(h, "GetVersion")
	if err != nil {
		abort("GetProcAddress", err)
	}
	r, _, _ := syscall.Syscall(uintptr(proc), 0, 0, 0, 0)
	print_version(uint32(r))
}

*/

2. syscall_windows_test.goへのExample関数の追加

削除されたコメントブロックのコードは、src/pkg/syscall/syscall_windows_test.goExampleLoadLibraryという新しいExample関数として追加されました。この関数は、元のデモコードとほぼ同じロジックを持ちますが、GoのExample関数の慣習に従って記述されています。

func ExampleLoadLibrary() {
	h, err := syscall.LoadLibrary("kernel32.dll")
	if err != nil {
		abort("LoadLibrary", err)
	}
	defer syscall.FreeLibrary(h)
	proc, err := syscall.GetProcAddress(h)
	if err != nil {
		abort("GetProcAddress", err)
	}
	r, _, _ := syscall.Syscall(uintptr(proc), 0, 0, 0, 0)
	major := byte(r)
	minor := uint8(r >> 8)
	build := uint16(r >> 16)
	print("windows version ", major, ".", minor, " (Build ", build, ")\\n")
}

注釈: GetProcAddressの引数が元のコメントではGetProcAddress(h, "GetVersion")でしたが、コミット後のコードではGetProcAddress(h)となっています。これは、GetProcAddressのシグネチャが変更されたか、またはこのExample関数が簡略化されたことを示唆しています。しかし、Go 1.0のsyscallパッケージのGetProcAddressfunc GetProcAddress(handle Handle, procname string) (proc uintptr, err error)というシグネチャなので、これはコミットログの表示上の問題か、あるいはGoのバージョンアップに伴う変更の可能性が高いです。実際のGo 1.0のコードではGetProcAddress(h, "GetVersion")が正しいです。このコミットは2012年のものであり、Go 1.0がリリースされた直後であるため、このような過渡期の変更が見られる可能性があります。

3. アセンブリファイル内のコメント修正

src/pkg/syscall/asm_windows_386.ssrc/pkg/syscall/asm_windows_amd64.sのコメントが修正されました。

変更前:

// System calls for 386, Windows are implemented in ../runtime/windows/syscall.goc

変更後:

// System calls for 386, Windows are implemented in ../runtime/syscall_windows.goc

この変更は、システムコールが実装されているGoランタイムのソースファイルのパスが、../runtime/windows/syscall.gocから../runtime/syscall_windows.gocに変わったことを反映しています。これは、Goの内部構造の変更に伴うパスの更新であり、コメントの正確性を保つための重要な修正です。.gocファイルは、GoのコンパイラがC言語のコードをGoのオブジェクトファイルに変換する際に使用する中間ファイルのようなものです。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルです。

  • src/pkg/syscall/syscall_windows.go: 大規模なコメントブロックの削除。
  • src/pkg/syscall/syscall_windows_test.go: ExampleLoadLibrary関数の新規追加。
  • src/pkg/syscall/asm_windows_386.s: コメントの修正。
  • src/pkg/syscall/asm_windows_amd64.s: コメントの修正。

コアとなるコードの解説

syscall_windows.goからのコメント削除

このファイルから削除されたコメントブロックは、Windows APIのLoadLibraryGetProcAddressSyscallを使ってkernel32.dllからGetVersion関数を呼び出し、Windowsのバージョン情報を取得する具体的な手順を示していました。このコードは完全に機能するものでしたが、コメント内に埋め込まれていたため、Goのツールチェーン(go testgodoc)による恩恵を受けることができませんでした。この削除は、コード例をより適切な場所(テストファイル内のExample関数)に移動するための前段階です。

syscall_windows_test.goへのExampleLoadLibrary追加

このファイルに追加されたExampleLoadLibrary関数は、削除されたコメントブロックのコードをExample関数として再構築したものです。

func ExampleLoadLibrary() {
	// kernel32.dllをロード
	h, err := syscall.LoadLibrary("kernel32.dll")
	if err != nil {
		// エラーが発生した場合、パニック
		abort("LoadLibrary", err)
	}
	// 関数終了時にFreeLibraryを呼び出してDLLをアンロード
	defer syscall.FreeLibrary(h)

	// ロードしたDLLから"GetVersion"関数のアドレスを取得
	// 注: コミットログの表示では引数が省略されているが、実際には "GetVersion" が必要
	proc, err := syscall.GetProcAddress(h, "GetVersion") // 正しい呼び出し方
	if err != nil {
		abort("GetProcAddress", err)
	}

	// GetVersion関数をシステムコールとして呼び出す
	// GetVersionは引数を取らないため、0, 0, 0, 0 を渡す
	r, _, _ := syscall.Syscall(uintptr(proc), 0, 0, 0, 0)

	// 戻り値 (uint32) からメジャーバージョン、マイナーバージョン、ビルド番号を抽出
	major := byte(r)         // 下位8ビットがメジャーバージョン
	minor := uint8(r >> 8)   // 次の8ビットがマイナーバージョン
	build := uint16(r >> 16) // 次の16ビットがビルド番号

	// 抽出したバージョン情報を標準出力に表示
	print("windows version ", major, ".", minor, " (Build ", build, ")\\n")
}

// abort関数は、エラー発生時にパニックを発生させるヘルパー関数
func abort(funcname string, err error) {
	panic(funcname + " failed: " + err.Error())
}

このExample関数は、Goのsyscallパッケージを使ってWindows APIを呼び出す典型的なパターンを示しています。

  1. syscall.LoadLibraryで必要なDLLをロードします。
  2. defer syscall.FreeLibrary(h)で、関数が終了する際にDLLが適切にアンロードされるようにします。これはリソースリークを防ぐための重要なパターンです。
  3. syscall.GetProcAddressで、ロードしたDLLから特定の関数のアドレスを取得します。
  4. syscall.Syscallを使って、取得した関数アドレスを直接呼び出します。Syscallは可変引数を取りますが、ここではGetVersionが引数を取らないため、ダミーの0を渡しています。
  5. GetVersionの戻り値(uint32)をビットシフトと型変換で解析し、メジャーバージョン、マイナーバージョン、ビルド番号を抽出しています。

このExample関数がsyscall_windows_test.goに配置されたことで、go test実行時にこのコードが実際に動作するか検証され、godocによってGoの公式ドキュメントに組み込まれるようになりました。

アセンブリファイル内のコメント修正

asm_windows_386.sasm_windows_amd64.sのコメント修正は、Goランタイムの内部構造の変更を反映したものです。Goのシステムコール実装が、以前は../runtime/windows/syscall.gocというパスにあったものが、../runtime/syscall_windows.gocというパスに移動したことを示しています。これは、Goのビルドシステムやランタイムのディレクトリ構造が進化する中で行われた、パスの正規化または再編成の一環と考えられます。このようなコメントの修正は、コードベースの正確性と、将来の開発者がシステムコールの実装を追跡する際の助けとなります。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Microsoft Learn (Windows APIドキュメント)
  • Go言語のソースコード (GitHub)
  • Go言語のExample関数に関するブログ記事やチュートリアル (一般的な知識として)