いろいろ備忘録日記

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

Goメモ-679 (cgoメモ-39)(Cの関数呼び出しで戻り値とerrorが受け取れる)

関連記事

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の関数をコールバックする) - いろいろ備忘録日記

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

Goメモ-630 (cgoメモ-33)(LD_PRELOAD を利用したモック) - いろいろ備忘録日記

Goメモ-641 (cgoメモ-34)(C側で定義された配列の操作)(アクセサ関数を用意) - いろいろ備忘録日記

Goメモ-644 (cgoメモ-35)(マクロ定数は参照できるけどマクロは使えない) - いろいろ備忘録日記

Goメモ-654 (cgoメモ-36)(structs.HostLayout) - いろいろ備忘録日記

Goメモ-660 (cgoメモ-37)(go-md4c, MD4Cのラッパーライブラリ) - いろいろ備忘録日記

Goメモ-661 (cgoメモ-38)(cgoのランタイムメトリクス)(/cgo/go-to-c-calls:calls) - いろいろ備忘録日記

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

概要

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

これまでのサンプルは以下のリポジトリにアップしています。

github.com

cgo では C 関数を多値返却形式で呼び出すと、Go コンパイラが呼び出し直後に errno を読み取り、error インターフェースとして返します。

Go references to C に以下の記載があります。

Any C function (even void functions) may be called in a multiple assignment context to retrieve both the return value (if any) and the C errno variable as an error (use _ to skip the result value if the function returns void).

n, err = C.sqrt(-1)
_, err := C.voidFunc()
var n, err = C.sqrt(1)

内部的には以下の型変換が行われています。

errno (C の int)
  → syscall.Errno (Go の uintptr、error インターフェースを実装)
  → error インターフェースとして返される

注意点として

err != nil だけで判定してはいけない。

Cの関数が成功しても errno が前回呼び出しの残留値のままの場合があります。(実務では結構あったりしますね)

正しいパターンは「戻り値で成否を判断し、失敗時に err を参照する」です。

通常のGoでのセオリーに反しますが、このあたりはCと同じです。

サンプル

main.go

package main

/*
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

// open(2) は glibc で varargs 宣言されている。そのため cgo から直接呼べないので
// 固定引数の static inline ラッパーを用意して回避する。
// macOSの場合は、openが固定引数で見えるので、この問題は発生しない。
static inline int go_open(const char *p, int flags) {
   return open(p, flags);
}
*/
import "C"
import (
    "context"
    "errors"
    "fmt"
    "log"
    "slices"
    "syscall"
    "unsafe"
)

const (
    exists    = "README.md"
    notExists = "NOT-EXISTS.md"
)

func main() {
    log.SetFlags(0)

    var (
        ctx = context.Background()
        err error
    )
    if err = run(ctx); err != nil {
        log.Println(err)
    }
}

func run(_ context.Context) error {
    var (
        p1 = C.CString(exists)
        p2 = C.CString(notExists)
    )
    defer C.free(unsafe.Pointer(p1))
    defer C.free(unsafe.Pointer(p2))

    type (
        loopVal struct {
            fpath string
            cpath *C.char
        }
    )
    var (
        fd   C.int
        err  error
        vals = []loopVal{{exists, p1}, {notExists, p2}}
    )
    for v := range slices.Values(vals) {
        // cgo経由でCの関数を呼び出す場合、Goの多値返却の仕組みにより
        // 本来の戻り値にプラスerrorが返ってくる。この error には、C側のerrnoの値がセットされる。
        //
        // errも返ってきているが C の関数を扱う場合は err != nil だけで判定するのは危険。
        // Cのセオリー通り、まず戻り値で判定し、その後で必要であれば err を使う。
        // errが存在する場合 syscall.Errno となる。(呼び出しが成功している場合、通常nilとなる)
        fd, err = C.go_open(v.cpath, C.int(syscall.O_RDONLY))
        if fd == -1 {
            // err は、実際には syscall.Errno となっている
            // 今回の場合では、ファイルが存在しないため、syscall.ENOENT となる。
            var (
                errno   = err.(syscall.Errno)
                eno     = uintptr(errno)
                isNOENT = errors.Is(err, syscall.ENOENT)
            )
            return fmt.Errorf("NG: open(%s): fd=%d (%w)(0x%x:ENOENT=%v)", v.fpath, fd, err, eno, isNOENT)
        }

        C.close(fd)

        log.Printf("OK: open(%s): fd=%d (%v)", v.fpath, fd, err)
    }

    return nil
}

実行

$ task
OK: open(README.md): fd=5 (<nil>)
NG: open(NOT-EXISTS.md): fd=-1 (no such file or directory)(0x2:ENOENT=true)

ちゃんと errno(syscall.ENOENT) になってますね。

参考情報

個人的Goのおすすめ書籍

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


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

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