cgoを使ったCとGoのリンクの裏側 (2)

[先の記事](http://qiita.com/yugui/items/e71d3d0b3d654a110188)ではGoコードからCの関数を呼び出す(import)場合について見た。
この記事では逆にCの関数からGoのコードを呼び出す、つまりGoの関数をCにexportする場合を扱う。

ただし、ここで言うのはあくまでも「Goのパッケージの一部をCで実装するにあたって、CコードがGoの機能を利用できる」ということだ。[CプログラムにGoライブラリを埋め込む話](https://talks.golang.org/2015/state-of-go-may.slide#14)(`-buildmode=c-archive`, `-buildmode=c-shared`)とは別で、そっちの話は別途扱う予定だ。
今回の話では、プログラム全体はあくまでもGoで書かれてる前提で、前回と同様にそのごく一部だけをCで書くことを想定している。


# サンプルコード
今度は次のような4つのファイルからなるパッケージ [github.com/yugui/cgo-explained/example2](https://github.com/yugui/cgo-explained/tree/master/example2)を考える。
`//export`ディレクティブで`goVersion`をCにexportするように指示している。

```go:export_example.go
package main

import (
	"C"
	"runtime"
)

//export goVersion
func goVersion() string {
	return runtime.Version()
}
```


```c:use_exported.h
void print_go_version(void);
```

```c:use_exported.c
#include <stdio.h>
#include "_cgo_export.h"

void print_go_version(void) {
  const GoString version = goVersion();
  printf("%.*s\n", (int)version.n, version.p);
}
```

```go:main.go
package main

// #include "use_exported.h"
import "C"

func main() {
	C.print_go_version()
}
```

`exported.go`の機能を`use_exported.c`が利用しているのがこの記事で主に扱う部分である。ただし、プログラム自体はGoで書かねばならないため、Goの`func main`がどこかで`use_exported.c`の機能を更にimportしていないと`use_exported.c`の存在する意味がない。このため、`main.go`はこのようになっている。

ソースコードの依存関係は下図の通りである。

![deps.png](https://qiita-image-store.s3.amazonaws.com/0/1806/fdd7bcdc-4864-f518-983c-0959ca96f4e3.png)


# ビルドプロセス
ビルドプロセス自体は[importの場合](http://qiita.com/yugui/items/e71d3d0b3d654a110188#%E3%83%93%E3%83%AB%E3%83%89%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9)と大差ない。

1. コード生成
   * `go tool cgo export_example.go`
   * `go tool cgo main.go`
2. Cコードのコンパイル
   * `gcc -c SOURCES`
3. Cコードのリンク
   * `gcc -o _cgo_.o OBJS`
4. インポート宣言の生成
   * `go tool cgo -dynimport ....`
5. Goコードのコンパイル
   * `go tool compile -o example2.a -pack GO_FILES`
6. Cコードの再リンク
   * `gcc -o _all.o OBJS`
7. Cオブジェクトをアーカイブに追加
   * `go tool pack r example2.a _all.o`
8. アーカイブ内のオブジェクトをリンク
   * `go tool link -o example2 example2.a`

データフローは下図の通りである。

![flow.png](https://qiita-image-store.s3.amazonaws.com/0/1806/e4420d03-4eec-f2a5-5140-af51342abef6.png)


これもimportの場合と概ね同じ構造であるが、下記2点が異なる。

1. `go tool cgo`に掛けるソースファイルが2個に増えたので、その分生成されるファイルが増えて複雑になった
2. `use_exported.c`由来のオブジェクトファイル`use_exported.o`が加わった。これをコンパイルする際に`go tool cgo`で生成された`_cgo_export.h`が`#include`されている。

特に後者のほうを詳しく見てみよう。

# use_exported.c

まず、exportされたGoの機能を利用するCソース`use_exported.c`は次のようになっている(再掲)。

```c:use_exported.c
#include <stdio.h>
#include "_cgo_export.h"

void print_go_version(void) {
  const GoString version = goVersion();
  printf("%.*s\n", (int)version.n, version.p);
}
```

ここで`#include`している`_cgo_export.h`は`go tool cgo`で`export_example.go`と`main.go`から生成された物だ。

```c:_cgo_export.h
/* Created by "go tool cgo" - DO NOT EDIT. */

/* (中略) */

/* Start of preamble from import "C" comments.  */
/* End of preamble from import "C" comments.  */

/* Start of boilerplate cgo prologue.  */
/* (中略) */
typedef signed char GoInt8;
/* (中略) */
typedef struct { const char *p; GoInt n; } GoString;
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;

/* (中略) */

extern GoString goVersion();
```

このファイルは主に3つの部分から構成される。

1. `import "C"`の直前のコメントで書いたpreamble部のコピー

    preambleに`//#include <stdio.h>`とか書いた場合だと、`#include <stdio.h>`がこの部分にコピーされる訳だ。
2. Goの基本型をCで表現したもの (boilerplate cgo prologue)
3. `//export`を宣言したGoの関数に対応する関数プロトタイプ

これらを利用して`use_exported.c`では`export_example.go`内の`func goVersion() string`を呼び出せるわけだ。ただし、正確に言うと`func goVersion() string`と関数プロトタイプ内の`goVersion()`は同一ではない。それは次節で見てみよう。

# グルーコード

まず、Cから見える`goVersion()`は生成された`_cgo_export.c`の中にある。これは引数を適切にpackしてGoの世界に引き渡すためのグルーコードである。

```c:_cgo_export.c
/* Created by cgo - DO NOT EDIT. */
#include "_cgo_export.h"

extern void crosscall2(void (*fn)(void *, int), void *, int);
extern void _cgo_wait_runtime_init_done();

extern void _cgoexp_5e3e4b09c83e_goVersion(void *, int);

GoString goVersion()
{
	_cgo_wait_runtime_init_done();
	struct {
		GoString r0;
	} __attribute__((__packed__)) a;
	crosscall2(_cgoexp_5e3e4b09c83e_goVersion, &a, 16);
	return a.r0;
}
```

`crosscall2`の実体は`runtime.cgo.crosscall2`でレジスタ待避などをやっている。`_cgo_wait_runtime_init_done`はその名の通り`runtime`パッケージの初期化が終わるまでブロックする。

そして、ここで呼び出されている`_cgoexp_5e3e4b09c83e_goVersion`は`_cgo_gotypes.go`にある。

```go:_cgo_gotypes.go
/* (略) */

//go:linkname _cgo_runtime_cgocallback runtime.cgocallback
func _cgo_runtime_cgocallback(unsafe.Pointer, unsafe.Pointer, uintptr)

/* (略) */

//go:cgo_export_dynamic goVersion
//go:linkname _cgoexp_5e3e4b09c83e_goVersion _cgoexp_5e3e4b09c83e_goVersion
//go:cgo_export_static _cgoexp_5e3e4b09c83e_goVersion
//go:nosplit
//go:norace
func _cgoexp_5e3e4b09c83e_goVersion(a unsafe.Pointer, n int32) {
	fn := _cgoexpwrap_5e3e4b09c83e_goVersion
	_cgo_runtime_cgocallback(**(**unsafe.Pointer)(unsafe.Pointer(&fn)), a, uintptr(n));
}

func _cgoexpwrap_5e3e4b09c83e_goVersion() (r0 string) {
	defer func() {
		_cgoCheckResult(r0)
	}()
	return goVersion()
}

```

`runtime.cgocallback`がスタック周りを調整しつつ、最終的に`goVersion`を呼び出している。

以上のグルーコードによってGoで定義した関数`goVersion`をCの関数`print_go_version`から呼び出すことができた。

# まとめ

exportの場合もimportの場合とビルド仮定は大差ない。importの場合は割愛した`_cgo_export.[ch]`が重要になるだけである。

実行時は`_cgo_export.c`と`_cgo_gotypes.go`にそれぞれ定義されている2段階のグルーコードを経てCの世界からGoの世界に制御が移る。