[インデックス 16214] ファイルの概要
このコミットは、Go言語のリポジトリにおけるmisc/cgo/testso
ディレクトリ内のテストがWindows環境で実行できるようにするための変更です。具体的には、CGO(GoとC言語の相互運用機能)を使用して共有ライブラリ(WindowsではDLL)を扱うテストにおいて、Windows特有のコールバックメカニズムの制約を回避し、テストスクリプトを追加することで、Windows上でのテストの実行を可能にしています。
コミット
commit 8c72b8113263977b3882f551d26ee4e31ce8bd4a
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Tue Apr 23 04:42:04 2013 +0800
misc/cgo/testso: enable test on windows
Depends on CL 8715043 and CL 8676050.
Fixes #5273.
R=alex.brainman, r
CC=gobot, golang-dev
https://golang.org/cl/8764043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8c72b8113263977b3882f551d26ee4e31ce8bd4a
元コミット内容
misc/cgo/testso: enable test on windows
Depends on CL 8715043 and CL 8676050.
Fixes #5273.
R=alex.brainman, r
CC=gobot, golang-dev
https://golang.org/cl/8764043
変更の背景
このコミットの背景には、Go言語のCGO機能におけるWindows環境での特定の制約と、それによって発生していたテストの失敗があります。コミットメッセージに記載されているFixes #5273
が示すように、この変更はGoのIssue 5273を解決することを目的としています。
Issue 5273は、「misc/cgo/testso
テストがWindowsで失敗する」という問題でした。このテストは、GoのコードからCの共有ライブラリ(WindowsではDLL)を呼び出し、さらにそのCの共有ライブラリからGoの関数をコールバックするという、双方向の呼び出しを検証するものです。
しかし、WindowsのDLL(Dynamic Link Library)のメカニズムには、DLLが直接メインの実行ファイル内の任意の関数を呼び出すことができないという制約があります。これは、DLLがロードされる際に、メイン実行ファイルのシンボルテーブルにアクセスする方法がUNIX系の共有ライブラリ(.soファイル)とは異なるためです。この制約により、CのDLLからGoの関数を直接コールバックしようとすると問題が発生していました。
このコミットは、このWindows特有の制約を回避するためのメカニズムを導入し、misc/cgo/testso
テストがWindows上でも正しく動作するように修正しています。また、Depends on CL 8715043 and CL 8676050
とあるように、この変更は他の関連する変更リスト(Change List)に依存しており、それらの変更が先行して適用されていることが前提となっています。これらのCLは、おそらくCGOのWindowsサポートや、共有ライブラリのビルドに関する基盤的な改善を含んでいたと考えられます。
前提知識の解説
このコミットを理解するためには、以下の前提知識が必要です。
1. CGO
CGOは、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoの機能です。これにより、既存のCライブラリをGoから利用したり、パフォーマンスが重要な部分をCで記述したりすることが可能になります。CGOを使用する際には、GoのビルドプロセスがCコンパイラ(通常はGCCやClang)を呼び出してCコードをコンパイルし、Goのコードとリンクします。
2. 共有ライブラリ(Shared Libraries / Dynamic Link Libraries: DLL)
- 共有ライブラリ(.soファイル - Linux/Unix系): 複数のプログラムで共有されるコードやデータを含むファイルです。プログラムの実行時にメモリにロードされ、複数のプロセスで同じライブラリのインスタンスを共有することで、メモリ使用量を削減し、ディスクスペースを節約します。
- ダイナミックリンクライブラリ(.dllファイル - Windows): Windowsにおける共有ライブラリの形式です。基本的な概念は.soファイルと同じですが、内部的な構造やリンケージのメカニズムにOS固有の違いがあります。
3. コールバック関数
コールバック関数とは、ある関数に引数として渡され、その関数内で特定のイベントが発生した際に呼び出される関数のことです。CGOの文脈では、CのコードからGoの関数を呼び出す場合、Goの関数がCのコードにとってのコールバック関数となります。
4. Windows DLLのリンケージとエクスポート/インポート
WindowsのDLLは、UNIX系の共有ライブラリとは異なるリンケージメカニズムを持っています。
- エクスポート (Export): DLLが外部のプログラムから呼び出される関数や変数を公開することを指します。C/C++では、
__declspec(dllexport)
キーワードを使用して、関数や変数をDLLからエクスポートすることを明示的に指定します。これにより、リンカはDLLのインポートライブラリ(.libファイル)を生成し、他のプログラムがそのDLLの関数を呼び出すための情報を提供します。 - インポート (Import): 実行ファイルや他のDLLが、別のDLLからエクスポートされた関数や変数を使用することを指します。C/C++では、
__declspec(dllimport)
キーワードを使用して、外部DLLから関数や変数をインポートすることを明示的に指定します。
WindowsのDLLでは、DLLがロードされた実行ファイル内の関数を直接参照するメカニズムが複雑です。特に、DLLがメイン実行ファイル内の任意の関数を「コールバック」として呼び出す場合、その関数のアドレスをDLLに明示的に渡す必要があります。これは、DLLがメイン実行ファイルのシンボルテーブルを直接参照できないためです。UNIX系では、共有ライブラリがメイン実行ファイルのシンボルを直接解決できる場合が多いのとは対照的です。
5. go test
とテストスクリプト
Goプロジェクトでは、go test
コマンドを使用してテストを実行します。複雑なテストや、特定の環境設定が必要なテストの場合、シェルスクリプト(Windowsでは.bat
ファイル)を使用してテストの準備(コンパイル、リンクなど)を行い、テストを実行することがあります。
技術的詳細
このコミットの技術的な核心は、WindowsのDLLがメイン実行ファイル内のGo関数を直接コールバックできないという制約を回避するためのメカニズムの実装です。
1. コールバック関数のアドレスの明示的な受け渡し
WindowsのDLLは、メイン実行ファイル内のGo関数を直接参照できないため、Go側からCのDLLに対して、Go関数のアドレスを明示的に渡す必要があります。このために、setCallback
というC関数が導入されました。
misc/cgo/testso/cgoso_c.c
にsetCallback
関数が追加されています。この関数は、void *f
という引数を取り、これをgoCallback
という関数ポインタに代入します。goCallback
は、Go側で定義されたコールバック関数を指します。__declspec(dllexport)
キーワードがsetCallback
とsofunc
に付与されています。これにより、これらの関数がDLLからエクスポートされ、Goのコードから呼び出せるようになります。misc/cgo/testso/cgoso.go
のTest()
関数内でC.init()
が呼び出されています。このinit()
関数(cgoso.c
で定義)は、Windows環境ではsetCallback(goCallback)
を呼び出します。ここでgoCallback
はGoの関数であり、そのアドレスがCのDLLに渡されます。
2. 条件付きコンパイル (#ifdef WIN32
)
コードの変更は、Windows環境にのみ適用されるように#ifdef WIN32
プリプロセッサディレクティブを使用しています。これにより、UNIX系システムでは既存の動作が維持され、Windowsでのみ新しいコールバックメカニズムが有効になります。
3. Windows用テストスクリプト (test.bat
) の追加
Windows環境でテストを実行するために、misc/cgo/testso/test.bat
というバッチファイルが新しく追加されました。このスクリプトは以下の手順を実行します。
gcc -c cgoso_c.c
: Cソースファイルをオブジェクトファイルにコンパイルします。gcc -shared -o libcgosotest.dll cgoso_c.o
: オブジェクトファイルから共有ライブラリ(DLL)をビルドします。libcgosotest.dll
という名前は、cgoso.go
の#cgo LDFLAGS: -L. -lcgosotest
で指定されているライブラリ名と一致します。go build main.go
: Goのメイン実行ファイルをビルドします。main.exe
: ビルドされた実行ファイルを実行します。この実行ファイルがDLLをロードし、テストロジックを実行します。- エラーチェックとクリーンアップ: ビルドや実行が成功したかを確認し、一時ファイルを削除します。
4. メインのテスト実行スクリプト (src/run.bat
) の更新
Goの全体的なテストスイートを実行するsrc/run.bat
ファイルに、新しく追加されたmisc/cgo/testso
のテストを実行するための呼び出しが追加されました。これにより、Windows上でのGoのCI/CDパイプラインやローカルテスト実行時に、このCGOテストが自動的に含まれるようになります。
コアとなるコードの変更箇所
misc/cgo/testso/cgoso.c
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "_cgo_export.h"
+
+#ifdef WIN32
+extern void setCallback(void *);
+void init() {
+ setCallback(goCallback);
+}
+#else
+void init() {}
+#endif
misc/cgo/testso/cgoso.go
// +build ignore
/*
#cgo LDFLAGS: -L. -lcgosotest
+void init(void);
void sofunc(void);
*/
import "C"
func Test() {
+\tC.init()
C.sofunc()
}
misc/cgo/testso/cgoso_c.c
// +build ignore
+#ifdef WIN32
+// A Windows DLL is unable to call an arbitrary function in
+// the main executable. Work around that by making the main
+// executable pass the callback function pointer to us.
+void (*goCallback)(void);
+__declspec(dllexport) void setCallback(void *f)
+{
+ goCallback = (void (*)())f;
+}
+__declspec(dllexport) void sofunc(void);
+#else
+extern void goCallback(void);
+void setCallback(void *f) { (void)f; }
+#endif
+
void sofunc(void)
{
-\textern void goCallback(void);\n \tgoCallback();
}
misc/cgo/testso/test.bat
(新規追加)
:: Copyright 2013 The Go Authors. All rights reserved.
:: Use of this source code is governed by a BSD-style
:: license that can be found in the LICENSE file.
@echo off
gcc -c cgoso_c.c
gcc -shared -o libcgosotest.dll cgoso_c.o
if not exist libcgosotest.dll goto fail
go build main.go
if not exist main.exe goto fail
main.exe
goto :end
:fail
set FAIL=1
:end
del /F cgoso_c.o libcgosotest.dll main.exe 2>NUL
src/run.bat
@@ -87,6 +87,14 @@ echo # ..\misc\cgo\test
go test ..\misc\cgo\test
if errorlevel 1 goto fail
echo.
+
+echo # ..\misc\cgo\testso
+cd ..\misc\cgo\testso
+set FAIL=0
+call test.bat
+cd ..\..\\..\\src
+if %FAIL%==1 goto fail
+echo.
:nocgo
echo # ..\doc\progs
コアとなるコードの解説
misc/cgo/testso/cgoso.c
このファイルは、CGOテストで使用されるC言語の補助関数を定義しています。
#include "_cgo_export.h"
: CGOによって生成されるヘッダファイルで、GoからエクスポートされたC関数やGo関数へのポインタなどが宣言されています。#ifdef WIN32 ... #else ... #endif
: Windows環境とそれ以外の環境で異なるinit()
関数の実装を提供します。- Windows (
WIN32
) の場合:init()
関数内でsetCallback(goCallback)
を呼び出しています。goCallback
はGo側で定義された関数であり、そのアドレスをCのDLLに渡すことで、DLLがGoの関数を呼び出せるようにします。 - Windows以外の場合:
init()
関数は空の実装です。これは、UNIX系システムではDLLがメイン実行ファイルのシンボルを直接解決できるため、このような明示的なコールバック設定が不要だからです。
- Windows (
misc/cgo/testso/cgoso.go
このGoファイルは、CGOを使用してCの共有ライブラリの関数を呼び出すテストロジックを含んでいます。
void init(void);
がCGOのコメントブロックに追加されました。これにより、GoコードからCのinit
関数を呼び出すことが可能になります。func Test()
内でC.init()
が呼び出されています。これにより、テストが開始される前にCのDLLがGoのコールバック関数アドレスを受け取るための初期化が行われます。
misc/cgo/testso/cgoso_c.c
このCファイルは、Cの共有ライブラリの主要なロジックを含んでいます。
#ifdef WIN32 ... #else ... #endif
: ここでもWindowsとそれ以外の環境で異なる実装を提供します。- Windows (
WIN32
) の場合:void (*goCallback)(void);
: Goのコールバック関数を指す関数ポインタを宣言しています。__declspec(dllexport) void setCallback(void *f)
: この関数はDLLからエクスポートされ、Go側から呼び出されます。引数f
として渡されたGo関数のアドレスをgoCallback
に格納します。これにより、CのDLLがGoの関数を呼び出すための参照を得ます。__declspec(dllexport) void sofunc(void);
:sofunc
もDLLからエクスポートされ、Go側から呼び出される関数です。
- Windows以外の場合:
goCallback
はextern
で宣言され、setCallback
はダミーの実装です。これは、UNIX系ではDLLが直接Goの関数を参照できるため、setCallback
のようなメカニズムが不要だからです。
- Windows (
void sofunc(void) { goCallback(); }
:sofunc
関数は、格納されたgoCallback
関数ポインタを介してGoの関数を呼び出します。これが、CのDLLからGoへのコールバックを実現する部分です。
misc/cgo/testso/test.bat
このバッチファイルは、Windows環境でmisc/cgo/testso
テストを実行するための自動化スクリプトです。
gcc -c cgoso_c.c
:cgoso_c.c
をコンパイルし、オブジェクトファイルcgoso_c.o
を生成します。gcc -shared -o libcgosotest.dll cgoso_c.o
:cgoso_c.o
から共有ライブラリlibcgosotest.dll
をビルドします。-shared
オプションはDLLを生成するために必要です。go build main.go
:main.go
(テストのエントリポイントとなるGoプログラム)をビルドし、実行可能ファイルmain.exe
を生成します。main.exe
: ビルドされたmain.exe
を実行します。この実行により、Goのテストが開始され、CGOを介してDLLがロードされ、コールバックメカニズムがテストされます。- エラーチェックとクリーンアップ: ビルドや実行の成功を確認し、生成された中間ファイルや実行ファイルを削除して、テスト環境をクリーンに保ちます。
src/run.bat
Goの全体的なテストスイートを実行するメインのバッチファイルです。
cd ..\misc\cgo\testso
とcall test.bat
が追加されました。これにより、misc/cgo/testso
ディレクトリに移動し、そこで定義されているtest.bat
スクリプトを呼び出すことで、Windows上でのCGO共有ライブラリテストがGoの標準テストプロセスに組み込まれます。- テストの成功/失敗を
%FAIL%
変数で管理し、エラーが発生した場合は全体のテストを失敗させるロジックも含まれています。
これらの変更により、Windows環境におけるCGOの共有ライブラリテストが、OS固有のDLLの制約を適切に処理し、自動化されたテストスイートの一部として実行できるようになりました。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/8c72b8113263977b3882f551d26ee4e31ce8bd4a
- Go Issue 5273: https://github.com/golang/go/issues/5273
- Go Change List 8764043: https://golang.org/cl/8764043
参考にした情報源リンク
- Go Issue 5273 (詳細な議論と背景情報): https://github.com/golang/go/issues/5273
- Go Change List 8764043 (このコミットのレビューページ): https://golang.org/cl/8764043
- Go Change List 8715043 (依存関係): https://golang.org/cl/8715043
- Go Change List 8676050 (依存関係): https://golang.org/cl/8676050
- CGOに関するGo公式ドキュメント (一般的なCGOの知識): https://pkg.go.dev/cmd/cgo
- Microsoft Learn:
__declspec(dllexport)
,__declspec(dllimport)
(Windows DLLのリンケージに関する詳細): https://learn.microsoft.com/ja-jp/cpp/cpp/dllexport-dllimport?view=msvc-170 - GCCのドキュメント (
-shared
オプションなど): https://gcc.gnu.org/onlinedocs/ - Windows Batch Scripting (
.bat
ファイルの構文): https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/windows-commands