いろいろ備忘録日記

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

Goメモ-506 (cgoメモ-02)(cgoヘッダ)

関連記事

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

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

概要

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

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

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

Cgo is not Go

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

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

github.com

今回は cgo の制約事項についてです。

cgo では以下がサポートされません。

  • 可変長引数を持つ関数
  • マクロ
  • sizeof演算子

可変長引数を持つ関数の代表例が printf() であり、最初に cgo を試す際に大抵誰もが試す関数だと思いますが

printf() は、可変長引数を持っている関数であるため、そのまま C.printf() とは呼べません。

マクロは実務現場では、かなり利用されるものですが、 cgo ではそのまま呼べません。

sizeof演算子は、コンパイル時に展開されるものであるため、cgo ではそのまま呼べません。

利用する方法として、cgoヘッダにラッパー関数を定義しておいて、それを呼び出すというやり方が使えます。

サンプル

sample.h

#ifndef SAMPLE_H
#define SAMPLE_H

#define prt(f) printf("%s\n", f)

#endif /* SAMPLE_H */

main.go

package main

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

typedef struct {
   int value1;
   char value2[20];
} ST1;

void myprintf(char *s) {
   printf("%s\n", s);
}

void myprintf2(char *s) {
   prt(s);
}

size_t mysizeof(ST1 *v) {
   return sizeof(*v);
}
*/
import "C"
import (
    "fmt"
    "unsafe"
)

func main() {

    s := C.CString("helloworld")
    defer C.free(unsafe.Pointer(s))

    //
    // 可変長引数を持つ関数は呼べない。
    //
    // printf()は、可変長引数を持っているため、そのまま呼べない。
    // 以下はコンパイルエラーとなる。
    // (cgo: 02.Restrictions/main.go:21:2: unexpected type: ...)
    //
    //C.printf(unsafe.Pointer(s))

    // 代わりに cgo ヘッダ部に定義した関数を呼び出すようにする
    C.myprintf(s)

    //
    // マクロは呼べない
    //
    // 以下の C.prt は、C側に定義されているマクロを呼び出そうとしているが
    // コンパイルエラーとなる。
    // (./main.go:35:2: could not determine kind of name for C.prt)
    //
    // 同様に cgo 側にラッパー関数を用意して呼び出すのは問題無い。
    //
    //C.prt(s)
    C.myprintf2(s)

    //
    // sizeof演算子は使えない
    //
    // sizeof演算子はコンパイル時にデータ型のサイズを決定するC言語の演算子なので
    // cgoからは利用できない。
    //
    // 同様に cgo 側にラッパー関数を用意して呼び出すのは問題無い。
    //
    var st1 C.ST1

    st1.value1 = 999

    buf := make([]byte, 20)
    copy(buf, []byte("helloworld"))

    cBuf := C.CBytes(buf)
    defer C.free(cBuf)
    C.memcpy(unsafe.Pointer(&st1.value2), cBuf, C.ulong(len(buf)))

    szSt1 := C.mysizeof((*C.ST1)(unsafe.Pointer(&st1)))

    fmt.Printf("%+v (%d bytes)\n", st1, szSt1)
}

Taskfile.yml

# https://taskfile.dev

version: '3'

tasks:
  default:
    cmds:
      - go run main.go

実行

$ task
task: [default] go run main.go
helloworld
helloworld
{value1:999 value2:[104 101 108 108 111 119 111 114 108 100 0 0 0 0 0 0 0 0 0 0]} (24 bytes)

参考情報

Goのおすすめ書籍


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

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