LoginSignup
9
4

More than 5 years have passed since last update.

マルチプラットフォーム対応したライブラリ Golang

Last updated at Posted at 2016-12-03

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 は ライブラリロード時に実行される
main.go

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)で試してます)

example

$ 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を参照

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のソースコードはディレクトリを分けて書いてみました。

C/main.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

9
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4