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

[インデックス 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のコードとリンクします。

  • 共有ライブラリ(.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.csetCallback関数が追加されています。この関数は、void *fという引数を取り、これをgoCallbackという関数ポインタに代入します。goCallbackは、Go側で定義されたコールバック関数を指します。
  • __declspec(dllexport)キーワードがsetCallbacksofuncに付与されています。これにより、これらの関数がDLLからエクスポートされ、Goのコードから呼び出せるようになります。
  • misc/cgo/testso/cgoso.goTest()関数内で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がメイン実行ファイルのシンボルを直接解決できるため、このような明示的なコールバック設定が不要だからです。

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以外の場合: goCallbackexternで宣言され、setCallbackはダミーの実装です。これは、UNIX系ではDLLが直接Goの関数を参照できるため、setCallbackのようなメカニズムが不要だからです。
  • 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\testsocall test.batが追加されました。これにより、misc/cgo/testsoディレクトリに移動し、そこで定義されているtest.batスクリプトを呼び出すことで、Windows上でのCGO共有ライブラリテストがGoの標準テストプロセスに組み込まれます。
  • テストの成功/失敗を%FAIL%変数で管理し、エラーが発生した場合は全体のテストを失敗させるロジックも含まれています。

これらの変更により、Windows環境におけるCGOの共有ライブラリテストが、OS固有のDLLの制約を適切に処理し、自動化されたテストスイートの一部として実行できるようになりました。

関連リンク

参考にした情報源リンク