いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-629 (cgoメモ-32)(Cの関数からGoの関数をコールバックする(2))

関連記事

Goメモ-502 (cgoメモ-01)(cgoヘッダ) - いろいろ備忘録日記

Goメモ-506 (cgoメモ-02)(cgoヘッダ) - いろいろ備忘録日記

Goメモ-507 (cgoメモ-03)(C.int) - いろいろ備忘録日記

Goメモ-508 (cgoメモ-04)(C言語の構造体) - いろいろ備忘録日記

Goメモ-509 (cgoメモ-05)(C.CString)(Cの文字列) - いろいろ備忘録日記

Goメモ-510 (cgoメモ-06)(C.GoString)(Cの文字列をGoの文字列へ) - いろいろ備忘録日記

Goメモ-511 (cgoメモ-07)(C.CBytes)([]byteをCのバイト列に) - いろいろ備忘録日記

Goメモ-512 (cgoメモ-08)(C.GoBytes)(Cのバイト列をGoの[]byteへ) - いろいろ備忘録日記

Goメモ-514 (cgoメモ-09)(C.GoStringN)(C.GoStringのサイズ指定版) - いろいろ備忘録日記

Goメモ-515 (cgoメモ-10)([]byteを(void *)へ変換) - いろいろ備忘録日記

Goメモ-516 (cgoメモ-11)([]byteを(char *)へ変換) - いろいろ備忘録日記

Goメモ-518 (cgoメモ-12)(Cのmallocをcgo経由で呼び出し) - いろいろ備忘録日記

Goメモ-519 (cgoメモ-13)(ポインタ演算) - いろいろ備忘録日記

Goメモ-520 (cgoメモ-14)(Goの関数をCの世界に公開 (export)) - いろいろ備忘録日記

Goメモ-522 (cgoメモ-15)(Goでsoファイルを作成してC言語から呼び出し) - いろいろ備忘録日記

Goメモ-525 (cgoメモ-16)(C側にて関数ポインタを引数に要求する関数にGo側で定義した関数を設定) - いろいろ備忘録日記

Goメモ-526 (cgoメモ-17)(cgoとdlopen関数を使って既存ライブラリの呼び出しをフックする) - いろいろ備忘録日記

Goメモ-527 (cgoメモ-18)(cgoを利用している場合のinit関数について) - いろいろ備忘録日記

Goメモ-528 (cgoメモ-19)(cgoを利用して作成したsoファイル経由でのinit関数の呼び出し) - いろいろ備忘録日記

Goメモ-529 (cgoメモ-20)(C言語のNULLをcgoから渡す) - いろいろ備忘録日記

Goメモ-530 (cgoメモ-21)(CGOヘッダーで指定出来るCFLAGS, LDFLAGS) - いろいろ備忘録日記

Goメモ-531 (cgoメモ-22)(CGOで利用するコンパイラを変更する) - いろいろ備忘録日記

Goメモ-532 (cgoメモ-23)(CGOヘッダで使えるSRCDIR変数) - いろいろ備忘録日記

Goメモ-534 (cgoメモ-24)(C側の構造体にて固定要素数の文字配列をGo側で文字列に変換) - いろいろ備忘録日記

Goメモ-535 (cgoメモ-25)(cgo.Handleを用いてCとGoの間で値をやり取りする) - いろいろ備忘録日記

Goメモ-536 (cgoメモ-26)(C側の構造体をGo側で利用する) - いろいろ備忘録日記

Goメモ-537 (cgoメモ-27)(Go側でCの構造体のサイズを知る方法) - いろいろ備忘録日記

Goメモ-539 (cgoメモ-28)(Go側でCの文字列リスト(**char)を扱う) - いろいろ備忘録日記

Goメモ-623 (cgoメモ-29)(memcpyの呼び出し) - いろいろ備忘録日記

Goメモ-627 (cgoメモ-30)(Goでビルドした静的ライブラリのC言語からの利用) - いろいろ備忘録日記

Goメモ-628 (cgoメモ-31)(Cの関数からGoの関数をコールバックする) - いろいろ備忘録日記

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。

今回から複数回に渡って cgo についてメモしていこうと思います。

cgo は、文字通りGoからCにアクセスすることが出来るようになるものなのですが、とても便利な反面、結構クセが強いのでメモでも残しておかないとすぐ頭から消えてしまいそうだなって思いました。

Cgo is not Go

という格言があったりするので、Go界隈で標準で推奨されていない技術かもしれません。が、実務ではC言語で作成されたライブラリなどは山のようにあります。んで、プロジェクトの方針でGoで作り直すことも出来ない場合も多々あります。そのような場合に非常に便利です。

これからのサンプルは以下のリポジトリにアップしてありますので、良ければご参考ください。

github.com

今回もcgoを利用してCの関数からGoの関数をコールバックする方法について。

前回は、Goの関数ポインタをCに渡す際の一般的なイディオムとして、(*[0]byte) への直接キャストを利用しました。本サンプルでは、C側で関数ポインタの typedef を定義し、それを利用することで、よりcgoの型変換の仕組みに沿った形で関数ポインタを取得する方法を示します。

サンプル

export.go

package main

import "C"
import (
    "fmt"
    "time"
)

//export export_func
func export_func(x, y C.int) C.int {
    fmt.Println("[Go] sleep 1sec")
    time.Sleep(1 * time.Second)
    ans := (x * y)
    fmt.Printf("[Go] x=%d, y=%d, ans=%d\n", x, y, ans)
    return ans
}

main.go

package main

/*
#cgo CFLAGS: -Wall -Wextra -g3 -O0

#include <stdio.h>

// c_func関数が要求している関数ポインタのtypedef定義
typedef int (*callback_fn)(int, int);

// Go側でexportした関数のプロトタイプ宣言
extern int export_func(int x, int y);

// 実際に呼び出すC側の関数。引数に関数ポインタを要求している。
int c_func(int x, int y, int (*fn)(int, int)) {
   printf("-------------------------------\n");
   printf("[C ] x=%d, y=%d\n", x, y);
   int ans = fn(x, y);
   printf("[C ] ans=%d\n", ans);

   return ans;
}
*/
import "C"
import (
    "fmt"
    "unsafe"
)

func main() {
    //
    // 関数ポインタを取るのに (*[0]byte) にキャストするというやり方が
    // よく利用されているイディオムであるが、安全な方法としては unsafe.Pointer を経由させることである
    //
    // ただし、この場合は 関数ポインタ を表現する typedef が必要となる
    //
    // (*[0]byte) は
    //   - [0]byte が要素ゼロの配列を表し、*[0]byteでそれのポインタとなる
    //   - 要素ゼロの配列へのポインタは、実質的にメモリアドレスそのものを表現する
    //   - Goの型システム上、unsafe.Pointerは任意のポインタ型に変換可能
    // という理屈で変換可能となっている
    //

    var (
        fn unsafe.Pointer = C.export_func             // cgo生成で var export_func unsafe.Pointer となる
        p  *[0]byte       = C.callback_fn(fn)         // cgo生成で type callback_fn *[0]byte となる
        p2                = (*[0]byte)(C.export_func) // 上の過程を省いたもの
    )

    //
    // cgo生成で
    //     func c_func(p0 C.int, p1 C.int, p2 *[0]byte) (r1 C.int)
    // となるため、上の p が渡せるようになる。
    //
    // 結局 *[0]byte を渡せば良いので、cgoでは上の過程を省いて 31.C_callback のように
    //     p = (*[0]byte)(C.export_func)
    // とすることが多い。
    //
    var (
        x = C.int(2)
        y = C.int(3)

        z  = C.c_func(x, y, p)
        z2 = C.c_func(x, y, p2)
    )
    fmt.Printf("[MAIN] %d:%d\n", int(z), int(z2))
}

一番の違いは、Cのコードブロック内で関数ポインタの型を typedef で定義している点です。

typedef int (*callback_fn)(int, int);

cgoは、このように typedef で定義された関数ポインタ型 callback_fn を、Go側では *[0]byte 型として特別に扱います。そして、C.callback_fn というGoの型としても利用できるようになります。

Taskfile.yml

# https://taskfile.dev

version: '3'

vars:
  APP: app

tasks:
  default:
    cmds:
      - go run .

実行

$ task
-------------------------------
[C ] x=2, y=3
[Go] sleep 1sec
[Go] x=2, y=3, ans=6
[C ] ans=6
-------------------------------
[C ] x=2, y=3
[Go] sleep 1sec
[Go] x=2, y=3, ans=6
[C ] ans=6
[MAIN] 6:6

参考情報

個人的Goのおすすめ書籍

個人的に読んでとても勉強になった書籍さんたちです。


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。