📑

C, Python から Go を呼ぶ

2021/05/28に公開

Go は動的ライブラリを生成する機能があるため、 Go のライブラリを別のプログラミング言語で呼び出したいというケースにおいて非常に有用である。
本記事では、その機能の基本的な使い方について説明する。

Go

Source Code

main.go
package main

import "C"
import "fmt"

//export foo
func foo(name *C.char) *C.char {
	nameString := C.GoString(name)
	result := fmt.Sprintf("Hello, %s!", nameString)
	return C.CString(result)
}

func main() {}

ライブラリ本体となるコード。

Cパッケージをimportすることでcgoの各種関数・型を呼び出せる。

export ディレクティブによってexportされた関数が共有ライブラリから使用できる。

引数と返り値を *C.char にしているが、これは string だと GoString という型が共有ライブラリで用いられるためである。
GoString は struct { const char *p; ptrdiff_t n; } という型であり、ヘッダファイルを読めないプログラミング言語、例えば Python においては _fields_: List[Tuple] メンバを持つクラスを argtypesrettype に指定する必要があるなど、若干扱いが面倒であるため割愛した。
今後時間のある時に別記事にて説明するかもしれない。

Build

go build -buildmode=c-shared -o main.so main.go

C

Source Code

main.c
#include <stdio.h>
#include "main.h"

int main() {
	char* result = foo("mopeneko");
	puts(result);
}

ライブラリをビルドした際に生成される main.h を include することで、 Go が生成する型や export された関数の呼び出しを行うことができる。

もし string を用いていた場合、 main.h に記述された構造体定義に則ってアクセスしなければならない。

Build

cc -o main main.c main.so

先程ビルドされた main.so をコンパイラに渡してビルド。

Execute

$ ./main
Hello, mopeneko!

Python

Source Code

main.py
from ctypes import CDLL, c_char_p

lib = CDLL("./main.so")
foo = lib.foo
foo.restype = c_char_p
print(foo(b"mopeneko").decode("utf-8"))

restype はデフォルトで ctypes.c_int であるため、返り値に応じて指定する必要がある。

引数と返り値は char* 、つまりはbytes型とほぼ同義であるため、 b""str.decode() を用いて適宜型変換をしなければならない。

前述の通り、もし string が引数や返り値であった場合、 argtypesrettype にクラスを指定する必要がある。

Execute

$ python main.py
Hello, mopeneko!

Discussion