関連記事
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側で文字列に変換) - いろいろ備忘録日記
GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ
概要
以下、自分用のメモです。
今回から複数回に渡って cgo についてメモしていこうと思います。
cgo は、文字通りGoからCにアクセスすることが出来るようになるものなのですが、とても便利な反面、結構クセが強いのでメモでも残しておかないとすぐ頭から消えてしまいそうだなって思いました。
Cgo is not Go
という格言があったりするので、Go界隈で標準で推奨されていない技術かもしれません。が、実務ではC言語で作成されたライブラリなどは山のようにあります。んで、プロジェクトの方針でGoで作り直すことも出来ない場合も多々あります。そのような場合に非常に便利です。
これからのサンプルは以下のリポジトリにアップしてありますので、良ければご参考ください。
今回は cgo.Handle を用いて、CとGoの間で値をやり取りすることについて。
cgo.Handleは、GoとCの間で安全にGoの値を渡すためのメカニズムです。cgo.Handleは整数値(Cではuintptr_t, Goではuintptr)であり、任意のGoの値を表現できます。
このハンドルを使用することで、Goのポインタを直接Cに渡すことなく、GoとCの間でデータをやり取りすることができます。
これにより、cgoのポインタ渡しルールを破ることなく、安全にデータをやり取りできます。
主にCからGoでエクスポートした関数を呼ぶ際などに利用します。
Cのuintptr_t型
uintptr_tは、C言語における特別な整数型で、ポインタの値を格納するために十分な大きさの符号なし整数型です。
この型は、ポインタを整数として扱う際に使用され、特にアドレス演算やシステムコールなどの低レベルの操作で役立ちます。
stdint.hに含まれています。
#include <stdio.h> #include <stdint.h> int main() { int x = 42; int *p = &x; uintptr_t u = (uintptr_t)p; printf("Pointer: %p, Uintptr: %lu\n", (void*)p, (unsigned long)u); return 0; }
Goのuintptr型
uintptrは、Go言語における特別な整数型で、ポインタの値を格納するために十分な大きさの符号なし整数です。
これは、メモリアドレスを直接扱うために使用され、特にシステムコールや低レベルのメモリ操作で重要な役割を果たします
package main import ( "fmt" "unsafe" ) func main() { var x int = 42 p := unsafe.Pointer(&x) u := uintptr(p) fmt.Printf("Pointer: %p, Uintptr: %d\n", p, u) }
サンプル
main.go
package main /* #include <stdio.h> #include <stdlib.h> #include <stddef.h> #include <stdint.h> // Go側でexternされる関数 extern void GoCallback(uintptr_t handle); extern void GoCallback2(void *context); // Goのコールバックを呼び出すためのラッパー関数 static void C_Callback(uintptr_t handle) { GoCallback(handle); } static void C_Callback2(void *context) { GoCallback2(context); } */ import "C" import ( "log" "runtime/cgo" "unsafe" ) type GoStruct struct { Value1 string Value2 string Value3 int } //export GoCallback func GoCallback(handle C.uintptr_t) { var ( cgoHandle = cgo.Handle(handle) value = cgoHandle.Value().(GoStruct) ) defer cgoHandle.Delete() // 利用が終わったら廃棄 log.Printf("[HANDLE] value ptr=%p", &value) log.Printf("[GoCallback ] %v", value) } //export GoCallback2 func GoCallback2(context unsafe.Pointer) { var ( cgoHandle = (*(*cgo.Handle)(context)) // *cgo.Handleにキャストして実体取得 value = cgoHandle.Value().(*GoStruct) ) defer cgoHandle.Delete() // 利用が終わったら廃棄 log.Printf("[HANDLE] value2 ptr=%p", value) log.Printf("[GoCallback2] %v", value) } func init() { log.SetFlags(0) } func main() { // ----------------------------------- // [cgo.Handle](https://pkg.go.dev/runtime/cgo#Handle) を uintptr_t として利用 // // Goの値を cgo.Handle でラップ var ( value = GoStruct{"hello", "world", 999} handle = cgo.NewHandle(value) ptr = C.uintptr_t(handle) ) // Cを経由して、Goの関数に戻り処理する log.Printf("[MAIN ] value ptr=%p", &value) C.C_Callback(ptr) log.Println("-----------------------------------") // ----------------------------------- // cgo.Handle を void * として利用 // // Goの値を cgo.Handle でラップ var ( value2 = &GoStruct{"world", "hello", 888} handle2 = cgo.NewHandle(value2) ptr2 = unsafe.Pointer(&handle2) ) // Cを経由して、Goの関数に戻り処理する log.Printf("[MAIN ] value2 ptr=%p", value2) C.C_Callback2(ptr2) }
Taskfile.yml
# https://taskfile.dev version: '3' tasks: default: cmds: - task: run clean: cmds: - rm -f ./app run: cmds: - go build -o app . - ./app
実行
$ task task: [run] go build -o app . task: [run] ./app [MAIN ] value ptr=0xc0000b0120 [HANDLE] value ptr=0xc0000b01b0 [GoCallback ] {hello world 999} ----------------------------------- [MAIN ] value2 ptr=0xc0000b0210 [HANDLE] value2 ptr=0xc0000b0210 [GoCallback2] &{world hello 888}
参考情報
- C? Go? Cgo!
- Go Wiki: cgo
- cmd/cgo
- runtime/cgo
- cgoを使ったCとGoのリンクの裏側 (1)
- cgoを使ったCとGoのリンクの裏側 (2)
- ebitengine/purego
- JupiterRider/ffi
個人的Goのおすすめ書籍
個人的に読んでとても勉強になった書籍さんたちです。
過去の記事については、以下のページからご参照下さい。
サンプルコードは、以下の場所で公開しています。