C, Python から Go を呼ぶ
Go は動的ライブラリを生成する機能があるため、 Go のライブラリを別のプログラミング言語で呼び出したいというケースにおいて非常に有用である。
本記事では、その機能の基本的な使い方について説明する。
Go
Source Code
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]
メンバを持つクラスを argtypes
や rettype
に指定する必要があるなど、若干扱いが面倒であるため割愛した。
今後時間のある時に別記事にて説明するかもしれない。
Build
go build -buildmode=c-shared -o main.so main.go
C
Source Code
#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
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
が引数や返り値であった場合、 argtypes
や rettype
にクラスを指定する必要がある。
Execute
$ python main.py
Hello, mopeneko!
Discussion