C言語 Advent Calendar 2016を書く人が少ないので、、
C言語に触っていたころを思い出して書いてみようと思いました。
ここから
昨年度の私はマルチプラットフォーム対応へ苦悩した挙句、
マルチプラットフォーム対応したライブラリを再開発するのはそろそろやめたい、と考えているあなた、jreのライブラリをlinkしてしまえばいいのでは...(震え声)
http://qiita.com/nothingcosmos/items/935cd0b9d62ef01ddddc
などとつぶやいていましたが、身近なところに
便利なマルチプラットフォーム対応したライブラリが存在することに気づきました。
そう、みんな大好き、Golangである。
Golangの資産をshared library化するc-shared
c-sharedとは、GolangをC言語向けのshared libraryとしてビルドするモードです。
Golangの資産をCから利用できるって最高だと思いませんか。
c-sharedに関してはこちらの記事を参考にしました。
試す場合はLinuxやMac環境を推奨します。
Golangのcgo周りには暗黙の了解とかが多くて、
その暗黙の了解に反すると意味不明なエラーメッセージで煙にまかれてしまうことが多い
です。
上述のリンク先でも説明されていますが、重要な点は以下の4点です。※ 抜粋
- パッケージは main でないと生成できない。
- main関数は実行されないが宣言する必要がある。
- エクスポートしたい関数にコメントで「//export funcname をつける。」(cgoと同じ)
- init は ライブラリロード時に実行される
package main
import (
"C"
"fmt"
"io/ioutil"
"net/http"
)
//export request
func request(url string) ([]byte, error) {
resp, err := http.Get(url)
//error握り潰し、だめ、ぜったい
if (err != nil) {
return nil, err
}
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
}
func init() {
fmt.Println("Loaded!!")
}
func main() {
}
build方法(Linux(x64)で試してます)
$ time go build -buildmode=c-shared -o libgo.so main.go
real 0m6.494s
user 0m9.264s
sys 0m1.432s
-rw-rw-r-- 1 elise elise 1393 12月 4 00:47 libgo.h
-rw-rw-r-- 1 elise elise 6406448 12月 4 00:47 libgo.so
結構時間かかります、さすがnetライブラリ、サイズ6MBだよ
生成された関数を確認してみます。libgo.hを参照
/* Return type for request */
struct request_return {
GoSlice r0;
GoInterface r1;
};
extern struct request_return request(GoString p0);
typedef struct { const char *p; GoInt n; } GoString;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
typedef GoInt64 GoInt;
//exportでコメントしたGoの関数がexportされています。
また、Goで生成されるcgoのboilerplateは非常に興味深いです、
特にstructをreturnするあたり、しかしここでは割愛。
Golangのerrorってinterface{}で困ってしまいます。
どうしようこれ、error codeに変換するhelper書かないとだめかなー?
CからGoのライブラリを呼び出してみる
goのソースとCのソースを同居させると、
golangでbuildした際にCのソースコードまでcgoの対象だと勘違いして読みに行ってしまうようなので、
Cのソースコードはディレクトリを分けて書いてみました。
#include "libgo.h"
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[]) {
GoString arg = {argv[1], strlen(argv[1])};
struct request_return ret = request(arg);
printf("%lld,%s\n", ret.r0.len, (char*)ret.r0.data);
return 0;
}
$ ls
C libgo.h libgo.so main.go
$ cd C
$ clang main.c -lgo -L.. -I..
$ export LD_LIBRARY_PATH=..
$ export GODEBUG=cgocheck=0 #良い子は真似しちゃいけない
$ a.out http://example.com
GODEBUG=cgocheck=0ではGoのポインタの保護チェックをスキップしています。
上記コードはGoで確保されたメモリをポインタでそのまま指しちゃってますので、
GCされたらinvalid pointerになる可能性があります。
Loaded!!
1270,<!doctype html>
<html>
<head>
<title>Example Domain</title>
...
まとめ
ここまで書いたが、Windowsではbuildmode=c-sharedが動かず、
c-archiveモードしかサポートしていないようなので、
もうひと工夫必要である。。
http://mattn.kaoriya.net/software/lang/go/20160405114638.htm