いろいろ備忘録日記

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

Goメモ-502 (cgoメモ-01)(cgoヘッダ)

関連記事

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

概要

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

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

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

Cgo is not Go

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

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

github.com

今回は初回なので、cgoヘッダについてのメモです。cgoを利用する場合は必ず cgoヘッダ を利用することになります。なんかコード内にてコメントでいろいろ書いていますが、このあたりは後々のメモで出てきますので、今は「こういうもの」って見ておいと良い感じです。

文字でいろいろ説明するより実際のサンプル見るほうが早いと思います。

サンプル

sample.h

#ifndef SAMPLE_H
#define SAMPLE_H

typedef struct {
    int  Id;
    char Name[128];
} ST_A;

#endif /* SAMPLE_H */

main.go

package main

/*
#cgo CFLAGS: -I.

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

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

void printStA(const ST_A* st) {
   printf("[C] %d\t%s\n", st->Id, st->Name);
}
*/
import "C" // cgoを利用する場合は必ずこれが必要。この上にコメントでcgoヘッダを記載する。
import (
    "fmt"
    "unsafe"
)

func main() {
    // cgoヘッダで include しておけば、C側の定数を C.定数名 で利用できる
    fmt.Println("[GO]", C.EXIT_SUCCESS, C.EXIT_FAILURE)

    // これはGoでの普通の文字列
    str := "hello world"

    // C側の文字列を利用する場合は C.CString を使う
    // C.CStringで作成した値は (char *) と同じ。
    //
    // 以下のように記載されている
    //      CString converts the Go string s to a C string.
    //      The C string is allocated in the C heap using malloc.
    //      It is the caller's responsibility to arrange for it to be freed, such as by calling C.free
    //      (be sure to include stdlib.h if C.free is needed).
    cStr := C.CString(str)

    // C.CString は、malloc() にてメモリが確保されているので必ず free() が必要
    // C.free() には、(void *) を指定しないといけないので unsafe.Pointer() で (void *) にして渡す
    defer C.free(unsafe.Pointer(cStr))

    // cgoには以下の制約事項がある
    //
    //   - 可変長引数を持つ関数は呼び出せない
    //   - マクロは利用できない
    //
    // printf()は、可変長引数を持っているので cgo から直接呼び出すことは出来ない.
    // 同様に、マクロも cgo からは利用できない。
    C.myPrintf(cStr)

    // C.CStringはC側の文字列(自動的に末尾に 終端記号('\0') が追加される)されたもの。
    // これをGo側の文字列にするには C.GoString を使う。
    //
    // 以下のように記載されている
    //     GoString converts the C string p into a Go string.
    goStr := C.GoString(cStr)

    fmt.Printf("[C.CString ] %[1]v (%[1]T)\n", cStr)
    fmt.Printf("[C.GoString] %[1]v (%[1]T)\n", goStr)

    // 独自定義している構造体もCGOヘッダにてincludeしていれば
    // C.構造体名 で利用できるようになる
    //
    // ただ、文字列が絡むと unsafe.Pointer() の嵐となる
    var stA C.ST_A

    stA.Id = C.int(999)
    C.strncpy((*C.char)(unsafe.Pointer(&stA.Name)), (*C.char)(unsafe.Pointer(cStr)), C.ulong(len(str)+1))

    C.printStA(&stA)

    // stA は、Go側で変数宣言してスタックに確保しているため C.free() は必要ない
    // C.CString は、C側で malloc でヒープメモリに確保されるため C.free() が必要
}

Taskfile.yml

# https://taskfile.dev

version: '3'

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

実行

$ task
task: [default] go run main.go
[GO] 0 1
[C] hello world
[C.CString ] 0x24b6140 (*main._Ctype_char)
[C.GoString] hello world (string)
[C] 999 hello world

参考情報

Goのおすすめ書籍


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

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