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

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

このコミットでは、Go言語のcgoツールにおいて、動的ライブラリからのコールバックをサポートするための変更が導入されました。具体的には、以下のファイルが変更または新規追加されています。

  • misc/cgo/testso/Makefile: 新規追加。動的ライブラリとテスト実行のためのMakefile。
  • misc/cgo/testso/cgoso.go: 新規追加。Go側でCから呼び出されるコールバック関数を定義。
  • misc/cgo/testso/cgoso_c.c: 新規追加。動的ライブラリとしてビルドされるCコードで、Goのコールバックを呼び出す。
  • misc/cgo/testso/main.go: 新規追加。テスト用のGoプログラム。
  • src/Make.pkg: 変更。_cgo1_.oの依存関係にCGO_DEPSを追加。
  • src/cmd/cgo/out.go: 変更。#pragma dynexportディレクティブの追加。
  • src/run.bash: 変更。misc/cgo/testsoのテスト実行を追加。

コミット

commit 11e73b89ca8ca5ca80df431e1c78565a9c9028ae
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Tue Nov 22 17:57:49 2011 +0300

    cgo: add support for callbacks from dynamic libraries
    
    R=golang-dev, rsc
    CC=golang-dev, mpimenov
    https://golang.org/cl/5375042

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

https://github.com/golang/go/commit/11e73b89ca8ca5ca80df431e1c78565a9c9028ae

元コミット内容

cgo: add support for callbacks from dynamic libraries

変更の背景

Go言語はcgoツールを通じてC言語との相互運用性を提供していますが、これまでのcgoは主にGoからCの関数を呼び出すことに焦点を当てていました。しかし、より複雑なシステム連携、特に既存のC言語で書かれた動的ライブラリ(Shared Object: .soやDynamic Link Library: .dllなど)とGoプログラムを連携させる場合、Cライブラリ側からGoの関数を呼び出す、いわゆる「コールバック」の機能が必要となります。

このコミットの背景には、GoプログラムがCの動的ライブラリを利用する際に、Cライブラリが特定のイベント発生時や処理完了時にGo側の関数を呼び出すようなシナリオを可能にするという目的があります。これにより、GoとCの間のより柔軟で双方向的な連携が実現され、既存のC資産をGoプロジェクトに統合する際の障壁が低減されます。

前提知識の解説

Cgo

cgoはGo言語に組み込まれているツールで、GoプログラムからC言語のコードを呼び出したり、逆にC言語のコードからGoの関数を呼び出したりすることを可能にします。Goのソースファイル内でimport "C"と記述することでcgoが有効になり、Cの関数宣言やGoの関数をCにエクスポートするための特別なコメントディレクティブ(//exportなど)を使用します。

動的ライブラリ (DLL/SO)

動的ライブラリ(Dynamic Link Library: DLL - Windows, Shared Object: SO - Linux/Unix系)は、複数のプログラムで共有される再利用可能なコードとデータの集合体です。プログラムのコンパイル時に実行ファイルに直接埋め込まれる静的ライブラリとは異なり、動的ライブラリはプログラムの実行時またはロード時にメモリに読み込まれます。これにより、メモリ効率の向上、ディスクスペースの削減、そしてライブラリの更新が容易になるという利点があります。

コールバック

プログラミングにおけるコールバックとは、ある関数に引数として別の関数を渡し、その渡された関数が後で(特定のイベント発生時や処理完了時などに)呼び出される仕組みです。非同期処理、イベントハンドリング、または関数の振る舞いをカスタマイズする際によく利用されます。動的ライブラリの文脈では、CのライブラリがGoの関数を呼び出すことで、Goプログラムに処理の完了や状態の変化を通知するメカニズムとして機能します。

//export ディレクティブ

Goの関数をC言語から呼び出せるようにするために、cgoでは//exportディレクティブを使用します。Goの関数定義の直前に//export FunctionNameと記述することで、cgoはそのGo関数に対応するC言語のラッパー関数を生成し、Cコードから呼び出し可能にします。

#pragma dynexport

#pragma dynexportcgoが生成するCコード内で使用されるディレクティブで、Goの関数をCのシンボルとしてエクスポートし、特に共有ライブラリとしてビルドされた際にCコードから呼び出し可能にするために利用されます。これは//go:cgo_export_dynamicのエイリアスであり、動的リンクのシナリオにおいて、Goのシンボルを実行時のプログラムのシンボルテーブルで利用可能にするために重要です。

技術的詳細

このコミットの核心は、Goの関数をCの動的ライブラリからコールバックとして呼び出せるようにするメカニズムの確立です。これは主に以下の要素によって実現されます。

  1. Go関数のCへのエクスポート: misc/cgo/testso/cgoso.goでは、//export goCallbackというディレクティブを使ってgoCallbackというGo関数をCにエクスポートしています。cgoはこのディレクティブを認識し、Cコードから呼び出し可能なラッパー関数を生成します。
  2. #pragma dynexportの導入: src/cmd/cgo/out.goの変更により、cgoはエクスポートされるGo関数に対して#pragma dynexportディレクティブを生成するようになりました。このディレクティブは、Goの関数が動的ライブラリの外部から参照可能なシンボルとして公開されることを保証します。これにより、Cの動的ライブラリがGoの関数をシンボル名で解決し、呼び出すことが可能になります。
  3. C側でのGoコールバックの呼び出し: misc/cgo/testso/cgoso_c.cでは、extern void goCallback(void);としてGoからエクスポートされた関数を宣言し、sofunc関数内でgoCallback();を呼び出しています。このcgoso_c.clibcgoso.soという動的ライブラリとしてビルドされます。
  4. ビルドプロセスの調整: misc/cgo/testso/Makefileは、cgoso_c.clibcgoso.soとしてコンパイルし、Goのテストプログラムmain.goがこの動的ライブラリをリンクして実行できるように設定しています。src/Make.pkgの変更は、cgoが生成するオブジェクトファイルに動的ライブラリの依存関係を適切に含めるためのものです。
  5. テストの追加: misc/cgo/testsoディレクトリ全体が、この新しいコールバック機能が正しく動作するかを検証するためのテストケースとして機能します。src/run.bashにこのテストの実行が追加され、Goのビルドシステムの一部として自動的に検証されるようになります。

この一連の変更により、GoプログラムはCの動的ライブラリをロードし、そのライブラリがGoの関数をコールバックとして呼び出すという、双方向の連携が可能になります。

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

misc/cgo/testso/Makefile (新規追加)

# Copyright 2011 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 ../../../src/Make.inc

TARG=cgosotest

CGO_DEPS+=libcgoso.so
CGO_LDFLAGS+=-lcgoso -L.
CLEANFILES+=out libcgoso.so
CGOFILES=\
	cgoso.go\

include ../../../src/Make.pkg

libcgoso.so: cgoso_c.c
	gcc cgoso_c.c -fPIC -o $@ $(_CGO_CFLAGS_$(GOARCH)) $(_CGO_LDFLAGS_$(GOOS))

out: install main.go
	$(GC) main.go
	$(LD) -o $@ main.$O
  • libcgoso.soという動的ライブラリをcgoso_c.cからビルドするルールを定義。
  • CGO_DEPSlibcgoso.soを追加し、Goプログラムがこのライブラリに依存することを指定。
  • CGO_LDFLAGSでリンクオプションを設定。

misc/cgo/testso/cgoso.go (新規追加)

// Copyright 2011 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.

package cgosotest

//void sofunc(void);
import "C"

func Test() {
	C.sofunc()
}

//export goCallback
func goCallback() {
}
  • //export goCallbackにより、goCallback関数がCから呼び出し可能になる。
  • Test関数はCのsofuncを呼び出す。

misc/cgo/testso/cgoso_c.c (新規追加)

// Copyright 2011 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.

void sofunc(void)
{
	extern void goCallback(void);
	goCallback();
}
  • extern void goCallback(void);でGoからエクスポートされたgoCallback関数を宣言。
  • sofunc関数内でgoCallbackを呼び出す。

misc/cgo/testso/main.go (新規追加)

// Copyright 2011 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.

package main

import "cgosotest"

func main() {
	cgosotest.Test()
}
  • cgosotestパッケージをインポートし、その中のTest関数を呼び出す。

src/Make.pkg (変更)

--- a/src/Make.pkg
+++ b/src/Make.pkg
@@ -162,7 +162,7 @@ endif
 # After main we have to define all the symbols that will be provided
 # by Go code.  That's crosscall2 and any exported symbols.
 
-_cgo1_.o: _cgo_main.o $(CGO_OFILES)\
+_cgo1_.o: _cgo_main.o $(CGO_OFILES) $(CGO_DEPS)\
 	$(HOST_CC) $(_CGO_CFLAGS_$(GOARCH)) -g -fPIC -O2 -o $@ $^ $(CGO_LDFLAGS) $(_CGO_LDFLAGS)\
 
 _obj/_cgo_import.c: _cgo1_.o
  • _cgo1_.oの依存関係に$(CGO_DEPS)を追加。これにより、cgoが生成するオブジェクトファイルが動的ライブラリに依存することを明示。

src/cmd/cgo/out.go (変更)

--- a/src/cmd/cgo/out.go
+++ b/src/cmd/cgo/out.go
@@ -501,6 +501,7 @@ func (p *Package) writeExports(fgo2, fc, fm *os.File) {\
 	\t\tif fn.Recv != nil {\
 	\t\t\tgoname = \"_cgoexpwrap\" + cPrefix + \"_\" + fn.Recv.List[0].Names[0].Name + \"_\" + goname\
 	\t\t}\
+\t\tfmt.Fprintf(fc, \"#pragma dynexport %s %s\\n\", goname, goname)\
 	\t\tfmt.Fprintf(fc, \"extern void ·%s();\\n\", goname)\
 	\t\tfmt.Fprintf(fc, \"\\nvoid\\n\")\
 	\t\tfmt.Fprintf(fc, \"_cgoexp%s_%s(void *a, int32 n)\\n\", cPrefix, exp.ExpName)\
  • writeExports関数内で、エクスポートされるGo関数に対して#pragma dynexportディレクティブを出力するように変更。これにより、Go関数が動的ライブラリの外部から参照可能なシンボルとして公開される。

src/run.bash (変更)

--- a/src/run.bash
+++ b/src/run.bash
@@ -73,6 +73,15 @@ gomake clean
 gotest
 ) || exit $?\
 
+[ "$CGO_ENABLED" != 1 ] ||
+[ "$GOHOSTOS" == windows ] ||
+[ "$GOHOSTOS" == darwin ] ||
+(xcd ../misc/cgo/testso
+gomake clean
+gomake out
+LD_LIBRARY_PATH=. ./out
+) || exit $?\
+
 (xcd ../doc/progs
 time ./run
 ) || exit $?\
  • misc/cgo/testsoディレクトリでのテスト実行を追加。LD_LIBRARY_PATHを設定して動的ライブラリが正しくロードされるようにしている。

コアとなるコードの解説

このコミットの主要な目的は、Goの関数をCの動的ライブラリからコールバックとして呼び出せるようにすることです。そのために、cgoツールとGoのビルドシステムにいくつかの重要な変更が加えられています。

  1. misc/cgo/testsoディレクトリの追加:

    • このディレクトリは、動的ライブラリからのGoコールバックをテストするための最小限の実行可能な例を提供します。
    • cgoso.goはGo側で定義されたコールバック関数goCallbackを含みます。この関数は//exportディレクティブによってCから呼び出し可能にマークされています。また、Cのsofuncを呼び出すTest関数も含まれています。
    • cgoso_c.cはC言語で書かれた部分で、libcgoso.soという動的ライブラリとしてコンパイルされます。このファイルはextern void goCallback(void);という宣言を通じてGoのgoCallback関数を参照し、sofunc関数内で実際にgoCallbackを呼び出します。
    • main.goはGoのメインプログラムで、cgosotest.Test()を呼び出すことで、Cの動的ライブラリ内のsofuncを間接的に実行します。これにより、sofuncがGoのgoCallbackを呼び出すというコールバックの流れがテストされます。
    • Makefileは、これらのファイルをビルドし、libcgoso.soを生成し、テストを実行するための手順を定義しています。特に、LD_LIBRARY_PATH=.を設定して、生成された動的ライブラリが実行時に見つけられるようにしています。
  2. src/cmd/cgo/out.goの変更:

    • このファイルはcgoコマンドの出力生成ロジックを扱います。
    • writeExports関数にfmt.Fprintf(fc, "#pragma dynexport %s %s\\n", goname, goname)という行が追加されました。これは、//exportディレクティブでエクスポートされたGo関数に対応するCのラッパー関数が生成される際に、そのシンボルが動的にエクスポートされるように#pragma dynexportディレクティブをCのソースコードに挿入することを意味します。
    • #pragma dynexportは、Goの関数が共有ライブラリの外部から参照可能なシンボルテーブルに登録されることを保証します。これにより、Cの動的ライブラリがGoの関数をシンボル名で解決し、実行時に呼び出すことが可能になります。
  3. src/Make.pkgの変更:

    • _cgo1_.oのビルドルールにおいて、依存関係に$(CGO_DEPS)が追加されました。CGO_DEPSMakefileで定義される動的ライブラリのリスト(この場合はlibcgoso.so)を含みます。
    • この変更により、cgoが生成する中間オブジェクトファイルが、Goプログラムが依存する動的ライブラリの存在を考慮するようになり、ビルドプロセスがより堅牢になります。
  4. src/run.bashの変更:

    • Goのテストスイートにmisc/cgo/testsoのテスト実行が追加されました。これにより、動的ライブラリからのGoコールバック機能がGoの公式テストの一部として継続的に検証されるようになります。

これらの変更が連携することで、Go言語はCの動的ライブラリとの間で、より高度な相互作用、特にC側からのGo関数へのコールバックをサポートするようになりました。

関連リンク

参考にした情報源リンク