goで実装した関数をpythonから呼び出す

# やりたいこと

下図のようにGoで記述した関数をPythonから呼び出します。

まずはGoプログラムをビルドして共有ライブラリ(※)を作成し、ビルドした共有ライブラリをPythonから呼び出します。
※ Linuxだと共有ライブラリ`.so`、Windowsだとダイナミックライブラリ`.dll`。

![イメージ図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705474/7828f3da-acb2-d688-5724-530b60686929.png)

# 1. 共有ライブラリを作成

まずはGoでスクリプトを記述します。
記述する上でのポイントは以下です。

- package名はmainにする
- main関数を記述する
- 特殊コメントの`export`を記述する

```go:test.go
// package名はmainにする
package main

import (
    "fmt"
    "C"
)

func main() {
    // main関数を記述
}

//export HelloWorld
func HelloWorld() {
    fmt.Println("Hello World")
}

//export AddNum
func AddNum (x1,x2 C.int) C.int {
    result := int(x1)+int(x2)
    return C.int(result)
}

//export AddStr
func AddStr (s1,s2 *C.char) *C.char {
    result := C.GoString(s1)+C.GoString(s2)
    return C.CString(result)
}
```

ここでインポートしている"C"パッケージですが、Goにはそのような名前のパッケージは存在しません。
[C? Go? Cgo!](https://go.dev/blog/cgo)の記事には以下のように記載されています。

> C is a “pseudo-package”, a special name interpreted by cgo as a reference to C’s name space.

共有ライブラリの作成は以下のコマンドにより行います。
これにより`test.so`という名前のファイルが作成されます。

```:ビルド
go build -buildmode=c-shared -o test.so test.go
```

共有ライブラリに記述した関数が含まれていることを確認します。
`test.so`のあるフォルダで以下の`nmコマンド`を実行します。

実行結果にAddNumなど、先ほど記述した関数の名前が含まれていることが確認できます。

```関数の確認
[Go] $ nm test.so | grep " T "
00000000000ab480 T _AddNum
00000000000ab4e0 T _AddStr
00000000000ab440 T _HelloWorld
00000000000ab540 T __cgo_0e9602f8d2bc_Cfunc__Cmalloc
00000000000ab920 T __cgo_get_context_function
000000000006a4c0 T __cgo_panic
00000000000ab580 T __cgo_release_context
00000000000ab620 T __cgo_sys_thread_start
00000000000668c0 T __cgo_topofstack
00000000000ab790 T __cgo_try_pthread_create
00000000000ab830 T __cgo_wait_runtime_init_done
00000000000ab380 T __cgoexp_0e9602f8d2bc_AddNum
00000000000ab3e0 T __cgoexp_0e9602f8d2bc_AddStr
00000000000ab320 T __cgoexp_0e9602f8d2bc_HelloWorld
000000000006a520 T _crosscall2
00000000000aba30 T _crosscall_amd64
00000000000ab980 T _x_cgo_callers
00000000000ab5b0 T _x_cgo_init
00000000000ab8b0 T _x_cgo_notify_runtime_init_done
00000000000ab8f0 T _x_cgo_set_context_function
00000000000ab950 T _x_cgo_setenv
00000000000ab700 T _x_cgo_sys_thread_create
00000000000ab9e0 T _x_cgo_thread_start
00000000000ab970 T _x_cgo_unsetenv
```

# 2. Pythonから呼び出す

作成した共有ライブラリをPythonから使用するには`ctypes`モジュールを使用します。
引数を渡して結果を受け取るには`argtypes`および`restype`でそれらの型を指定します。

実装例は以下のようになります。

```python:test.py
from ctypes import *

# 共有ライブラリの読み込み
lib = cdll.LoadLibrary("test.so")

# HelloWorld関数を使用
f = lib.HelloWorld
f()

# AddNum関数を使用
f = lib.AddNum
f.argtypes = [c_int, c_int]
f.restype = c_int
result = f(1, 2)
print("AddNum :", result)

# lib.AddStr関数を使用
f = lib.AddStr
f.argtypes = [c_char_p, c_char_p]
f.restype = c_char_p
result = f(b"Hoge", b"Piyo")
print("AddStr :", result)
```

作成したpythonスクリプトを実行すると、以下の通りPython側で結果を受け取れていることが確認できます。

```:実行結果
[Go] $ python test.py
Hello World
AddNum : 3
AddStr : b'HogePiyo'
```

# 参考資料

- [cgo command](https://pkg.go.dev/cmd/cgo)
- [Go Programming Language Wiki](https://zchee.github.io/golang-wiki/cgo/)
- [C? Go? Cgo!](https://go.dev/blog/cgo)
- [ctypes --- Pythonのための外部関数ライブラリ](https://docs.python.org/ja/3/library/ctypes.html)