いろいろ備忘録日記

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

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

関連記事

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関数について) - いろいろ備忘録日記

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

概要

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

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

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

Cgo is not Go

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

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

github.com

今回は cgoを利用して作成したsoファイル経由での init() について。

初期化処理などを init() に書きたいけど、ちゃんと呼び出されるのか?という話ですね。

結論を言うと「ちゃんと呼び出される」となりますが、挙動に少し注意が必要となります。

  • cgoヘッダで定義したC言語ネイティブの関数がC言語側で最初に呼び出された場合、まだ init() は走らない
  • cgo経由でエクスポートしたGo関数が初回呼び出しされた際に init() が走る

まあ、当然と言えば当然の動きとなります。cgoの境界に入った初回呼び出し時に init() が呼び出されるということですね。

サンプル

複数ファイルに init() を定義してみます。

cfuncs.go

package main

/*
#include <stdio.h>

void my_printf(const char *msg) {
   printf("[C ] %s\n", msg);
}
*/
import "C"

func init() {
    println(">> cfuncs.init() called")
    m["cfuncs"] = true
}

exports.go

package main

/*
#include <stdlib.h>
*/
import "C"
import "log"

func init() {
    println(">> exports.init() called")
    m["exports"] = true
}

//export gofunc1
func gofunc1() {
    log.Printf("[GO] gofunc1")
    log.Printf("[GO] %v", m)
}

main.go

package main

import "log"

var (
    m = make(map[string]bool)
)

func init() {
    println(">> main.init() called")
    log.SetFlags(0)
    m["main"] = true
}

func main() {}

main.c

#include <stdlib.h>

extern void my_printf(const char *);
extern void gofunc1(void);

int main(void) {
    my_printf("hello world");
    gofunc1();

    return EXIT_SUCCESS;
}

Taskfile.yml

# https://taskfile.dev

version: "3"

env:
  LD_LIBRARY_PATH: .

tasks:
  default:
    cmds:
      - go build -o libgo.so -buildmode=c-shared .
      - gcc -c -o main.o main.c
      - gcc -o capp main.o -L. -lgo
      - ldd ./capp
      - ./capp

実行

$ task
task: [default] go build -o libgo.so -buildmode=c-shared .
task: [default] gcc -c -o main.o main.c
task: [default] gcc -o capp main.o -L. -lgo
task: [default] ldd ./capp
        linux-vdso.so.1 (0x00007fff5778a000)
        libgo.so => ./libgo.so (0x00007ff4bda66000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff4bd836000)
        /lib64/ld-linux-x86-64.so.2 (0x00007ff4bdc1f000)
task: [default] ./capp
[C ] hello world
>> main.init() called
>> cfuncs.init() called
>> exports.init() called
[GO] gofunc1
[GO] map[cfuncs:true exports:true main:true]

main.c にて、最初に cgoヘッダ で定義したC言語ネイティブな関数を呼び出していますが、init() は呼び出されません。

次に cgo 経由でエクスポートしている gofunc1() を呼び出したときに init() が呼び出されています。

参考情報

Goのおすすめ書籍


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

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