いろいろ備忘録日記

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

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

関連記事

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言語から呼び出し) - いろいろ備忘録日記

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

概要

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

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

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

Cgo is not Go

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

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

github.com

今回は Go側の関数を公開し、それをC側から関数ポインタ経由で呼び出すについて。

cgoのドキュメントには以下の記載があり、cgoではCの関数ポインタがサポートされていません。しかし、下記のようにやり取りする方法があります。

Calling C function pointers is currently not supported, however you can declare Go variables which hold C function pointers and pass them back and forth between Go and C. C code may call function pointers received from Go.

Cの関数ポインターの呼び出しは現在サポートされていませんが、Cの関数ポインターを保持するGo変数を宣言し、GoとCの間で受け渡しすることができます。

C側にて関数ポインタを引数に要求する関数に、Go側で定義した関数を設定するのは手間がかかるが以下のようにします。

  1. 関数ポインタのtypedefが存在しない場合はC側で定義する
  2. Goの関数をexportしてCの世界に公開する
  3. exportしたGoの関数をtypedefした定義でラップする
  4. ラップした値を引数に受取り、実際のCの関数を呼び出すラッパー関数を用意

詳細については、ソースコードを参照くださいませ。

なお、注意点としてexportするGoの関数定義とcgoのプロトタイプ宣言を同じファイルでしてはいけません。(リンカーでエラーとなる)

この点については、cgoのドキュメントに以下の記載があります。

Using //export in a file places a restriction on the preamble: since it is copied into two different C output files, it must not contain any definitions, only declarations. If a file contains both definitions and declarations, then the two output files will produce duplicate symbols and the linker will fail. To avoid this, definitions must be placed in preambles in other files, or in C source files.

ファイル内で//exportを使用すると、プリアンブルに制約が課される。プリアンブルは2つの異なるC出力ファイルにコピーされるため、定義が含まれてはならず、宣言のみが含まれる。ファイルに定義と宣言の両方が含まれていると、2つの出力ファイルに重複したシンボルが生成され、リンカは失敗する。これを避けるには、定義を他のファイルのプリアンブルか、Cのソース・ファイルに記述しなければならない。

サンプル

sub.go

package main

import "C"
import (
    "fmt"
    "unsafe"
)

//export goCallback
func goCallback(cId C.int, cData unsafe.Pointer, cLength C.size_t) {
    var (
        id     = int(cId)
        data   = C.GoString((*C.char)(cData))
        length = int(cLength)
    )

    fmt.Println("[Go][goCallback] called")
    fmt.Printf("[Go][goCallback] id=%d, data=%q, length=%d\n", id, data, length)
}

main.go

package main

/*
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sample.h"

// func_with_callback関数にて要求される関数ポインタを型定義
typedef void (*c_callback)(int, void *, size_t);

// sub.go にて export しているGo側の関数のプロトタイプ
// プロトタイプと実装を同じファイルで書き込んではいけない (リンカーにてエラーになる)
void goCallback(int, void *, size_t);

// cgo側より呼び出すラッパー関数
void go_func_with_callback(int id, c_callback cb) {
   func_with_callback(id, cb);
}
*/
import "C"

func main() {
    //
    // cgo経由でコールバックを用意して呼び出し
    //
    var (
        id       = C.int(100)
        callback = C.c_callback(C.goCallback)
    )

    C.go_func_with_callback(id, callback)
}

sample.h

#ifndef SAMPLE_H
#define SAMPLE_H

#include <stdlib.h>

void func_with_callback(int, void (*callback)(int, void *, size_t));

#endif /* SAMPLE_H */

sample.c

#include <stdio.h>
#include <string.h>
#include "sample.h"

// 引数にコールバック用の関数ポインタを要求する関数
void func_with_callback(int id, void (*callback)(int id, void *data, size_t length)) {
    char data[] = "hello world";
    size_t szData = strnlen(data, 12);

    printf("[C ][func_with_callback] called\n");
    (*callback)(id, data, szData);
}

Taskfile.yml

# https://taskfile.dev

version: '3'

tasks:
  default:
    cmds:
      - go run .

実行

$ task
task: [default] go run .
[C ][func_with_callback] called
[Go][goCallback] called
[Go][goCallback] id=100, data="hello world", length=11

参考情報

Goのおすすめ書籍


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

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