この記事は Go4 Advent Calendar 2019 の 6 日目の記事です。
Cgo を使うと C 言語で書かれた関数を定義したり呼び出したりできます。これを使って、 C 言語で書かれた関数をデバッグしてみます。
背景
組込開発 (C 言語) において、 AES を用いた処理があり検算が面倒だった。 Go だと自分の中ではサクッと書けるので、 C 言語で作ったものを Go で検算するようにしてみたら快適だったので共有しようと考えた。
Cgo とは
Cgo enables the creation of Go packages that call C code.
https://golang.org/cmd/cgo/
Cgo の簡単なサンプル
Go では import "C"
の直前のコメントに C 言語ソースを書くことで、手軽に C 言語ソースと接続することができます。
直前に、というのがポイントで、 import "C"
の上に空行を挟むと動かないので注意。
package main
/*
int max(int x, int y) {
return x < y ? y : x;
}
*/
import "C"
import (
"fmt"
)
func main() {
num := C.int(1)
ret := C.max(num, 2)
fmt.Println("max(1, 2) => ", ret)
fmt.Printf("type(ret) : %T\n", ret)
i := int(ret)
fmt.Printf("type(i) : %T\n", i)
}
C で書かれた max という関数は、 Go の世界からは C.max
という名前でコールすることが可能です。
この時点で実行すると以下のような結果になります。
戻り値は見慣れない型 main._Ctype_int
になっていますが、 int
型にキャストして受け取れば問題ないです。
$ go build && go-cgo.exe
max(1, 2) => 2
type(ret) : main._Ctype_int
type(i) : int
このような形でシンプルな C ソースは Go から簡単に使う事が出来ます。
また、 Go の関数を C からも使うことができるので、興味がある人は以下を読むと良いです。
struct を使うケースや char *
と Go のstring
の変換なども書かれています。
C で書かれた max 関数をテストする
上で作成した C.max()
をテストしようとして以下のようなソースを書いたとしてもそのままでは実行できません。
package main
import "testing"
func TestCgoMax(t *testing.T) {
if g, e := 3, C.max(2, 3); g != e {
t.Errorf("got %d, want %d", g, e)
}
}
実際に実行すると以下のようになります。
$ go test -v
# github.com/sago35/go-cgo [github.com/sago35/go-cgo.test]
.\main_test.go:6:16: undefined: C
FAIL github.com/sago35/go-cgo [build failed]
undefined: C
と出るので、テストコード内に import "C"
等を書いても以下のように怒られます。 Cgo はテストコード内では使えないようです。
$ go test -v
can't load package: package github.com/sago35/go-cgo: use of cgo in test C:\Users\takasago.masaaki\dev\src\github.com\sago35\go-cgo\main_test.go not supported
ということで、 Go で wrap してみましょう。テストコード側も、新しく作った cgoMax()
を呼ぶようにしてみます。
package main
/*
int max(int x, int y) {
return x < y ? y : x;
}
*/
import "C"
import (
"fmt"
)
func cgoMax(x, y int) int {
cx := C.int(x)
cy := C.int(y)
ret := C.max(cx, cy)
return int(ret)
}
package main
import "testing"
func TestCgoMax(t *testing.T) {
if g, e := 3, cgoMax(2, 3); g != e {
t.Errorf("got %d, want %d", g, e)
}
}
実行してみると、うまくテストできるようになりました。
$ go test -v
=== RUN TestCgoMax
--- PASS: TestCgoMax (0.00s)
PASS
ok github.com/sago35/go-cgo 0.070s
まとめ
C 言語で書かれたもののテストを Go で行うことができました。
Go でライブラリを書いていると、できるだけ pure Go で書こうと思ったりしちゃうわけですが、 Cgo もとても便利なので使えるようになっておくとよさそう。