cgoでGoのコードからCの関数を利用する
Published at 2014-10-09
Goの練習がてらコマンドラインツールを書いている最中に詰まったので作業ログとして残す。
Goでコマンドラインのインターフェースを実装するには、os
やflag
等のデフォルトライブラリ群でそこそこ簡単に作れるけど、今回やりたいことに「Cで書かれたバイナリをGoから利用したい」というものがあった。
同じことをNode.jsでやろうとするなら、スクリプトを含めたパッケージ群としてnpmで配布することが可能なので、postinstall
あたりで 都度ソースコードをダウンロードしコンパイル 、あるいは コンパイル済みバイナリをダウンロード くらいで良かった。以下Node.jsの例。
Goの場合はどうだろう。
@1000ch linkすればいいんじゃないかな。Cのライブラリみたいに
— すぎもと@ (@sugimoto1981) 2014, 10月 2
色々ググっててもcgoというワードが出てくるけど、これはgoのコンパイラでcのプログラムも一緒にコンパイルするみたい。詳しい内部処理わからないけど、GoからコンパイルされたバイナリとCからコンパイルされたバイナリをリンクという処理は見えない。表面上は。
cgoを使ったミニマルな例
main.c
とmain.go
というファイル名で用意。
#include <stdio.h>
void
func_to_export(const char* message) {
printf("(✌՞ټ՞)✌ %s!!!\n", message);
}
package main
/*
#include <stdlib.h>
extern void func_to_export(const char*);
*/
import "C"
import "unsafe"
func main() {
p := C.CString("lorem ipsum")
defer C.free(unsafe.Pointer(p))
C.func_to_export(p)
}
このくらい単純なら僕でもわかるんだけど、やりたいのは既存のcで書かれたライブラリをgo build
でコンパイル。
jpegoptimをGoでラップしたい
本当は画像をアレするライブラリを全てまとめたものを作るという大いなる野望を持っているけど、いきなりは無理なのでまずは単一ライブラリをラップするところから。 CとMakefileのお作法が絡みまくっており、このラップ作業ですらさっぱりわからなくて、多方面にご教授頂きました(特に@vitaminless氏には大部分のデバッグを助けていただきましたorz)。
アーカイブファイルを作る
jpegoptimそのものはもちろんそれだけで実行可能形式にコンパイル可能だが、.o
ファイルをくっつけた.a
ファイルを出力するビルドが用意されていない。
なのでlibjpegoptim.a
というアーカイブファイルを生成するビルドをMakefile中に追加したい。具体的には以下の3行を然るべきところに追加するパッチファイルを作る。
PKGLIB = libjpegoptim.a
$(PKGLIB): $(OBJS)
ar crsv $(PKGLIB) $(OBJS)
$ diff -u jpegoptim/Makefile Makefile-updated > Makefile.patch
で、このパッチを適用する処理をGoラッパー側のMakefileに追加した。
$ patch -u jpegoptim/Makefile < Makefile.patch
jpegoptim.c
のmain関数の削除
また、今回はGoラッパーを作るので、Go側にmain関数が必要だが、そのままだとjpegoptim側のmain関数とバッティングしてコンパイルができなくなる。
そのため、jpegoptim.c
のmain関数を削除する処理が必要になるが、こちらもGoラッパー側のMakefileに追加しておく。
$ sed -e "287,854d" jpegoptim/jpegoptim.c
っていう雑なことをしていたら精神衛生的に汚されてきたので、こちらもパッチを作った。
$ patch -u jpegoptim/jpegoptim.c < jpegoptim.c.patch
出来上がったMakefile
このGo側のmain関数に何も処理を書いていない時点で、ひとまずコンパイルまでは通せるようになった。
GO=go
[email protected]:tjko/jpegoptim.git
SRC=jpegoptim-src
default: patch build
$(SRC):
# prepare source
@mkdir $(SRC)
@git clone $(REPO) $(SRC)
build: $(SRC) jpegoptim.go
# make
@cd $(SRC);make libjpegoptim.a
# build
@$(GO) build jpegoptim.go
patch: $(SRC)
# configure
@cd $(SRC);./configure --prefix=`pwd`/local
# apply patch to jpegoptim/Makefile
@patch -u $(SRC)/Makefile < Makefile.patch
# apply patch to jpegoptim/jpegoptim.c
@patch -u $(SRC)/jpegoptim.c < jpegoptim.c.patch
.PHONY: patch build
削除したC側のmain関数
コマンドラインのインターフェースと最適化実行部分がmain関数内にがっちり書かれていたので、今度はこれをどうにかしなければならならず、しばし考える。
jpegoptimのmain関数で行っている処理をGoで再実装しようとも考えたけど、長いし、一部だけexternするというのも微妙かと考え、main関数をリネームしGo側のmain関数からコールすることに。
それに応じて、jpegoptim.c.patch
も、main()
の削除ではなくjpegoptim_main()
とリネームするように作りなおす。
jpegoptim_main()
とリネームしたCのmain関数を、jpegoptim.go
内でexternする。
その上で、Goで受け取るコマンドライン引数をjpegoptim_main()
にそのまま受け渡すことに。
func main() {
var argc C.int
var argv []*C.char
argc = C.int(len(os.Args))
argv = make([]*C.char, argc)
for i, arg := range os.Args {
argv[i] = C.CString(arg)
}
C.jpegoptim_main(argc, (**C.char)(&argv[0]))
}
そのままだと渡せないので、Goの型をCの型に変換している。Goのos.Args
のそれぞれを*C.char
にするのは正攻法で良さそうだけど、
ポインタのポインタに変換どうすればいいのこれというところで、Cgo の基本的な使い方とポインタ周りのTips (Go v1.2)というエントリに助けてもらった。
ここでやりたい、「[]*C.type
から**type
へのキャストができる」ということがずばり書いてある。先人の力は偉大だ。
go-jpegoptim
一応動く形になったものがこちら。
ビルドはリポジトリをクローンして、make
すれば実行可能バイナリ作成までされる。Goのv1.3.2で確認済み。
所感
終わってみればGoのコードは殆どなくなってしまったわけだけど、何かの足しになればと思って記事として残す。 今のところはCLIツールを書くにもNode.jsのほうがお手軽感あるし、漠然とバイナリのほうが速いだろうと思っている実行速度もどこまで差が出るのかわからない。 お手軽云々に関しては慣れの問題もあるだろうから、コレを期に、今までNode.jsで書いていたようなWebアプリもGoで書くようにしようかな。実行速度に関してはまた今度。
以下お世話になった資料です。
Cのmain関数をGo側に移植しようと頑張ってた最中
- Go言語でコマンドラインオプションを扱う
- Go言語でコマンドライン引数を取り扱う
- ʕ ゚皿゚ ʔ Golangからv8を使う
- Package filepath - The Go Programming Language
諦めた辺あたりから