関連記事
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から渡す) - いろいろ備忘録日記
GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ
概要
以下、自分用のメモです。
今回から複数回に渡って cgo についてメモしていこうと思います。
cgo は、文字通りGoからCにアクセスすることが出来るようになるものなのですが、とても便利な反面、結構クセが強いのでメモでも残しておかないとすぐ頭から消えてしまいそうだなって思いました。
Cgo is not Go
という格言があったりするので、Go界隈で標準で推奨されていない技術かもしれません。が、実務ではC言語で作成されたライブラリなどは山のようにあります。んで、プロジェクトの方針でGoで作り直すことも出来ない場合も多々あります。そのような場合に非常に便利です。
これからのサンプルは以下のリポジトリにアップしてありますので、良ければご参考ください。
今回は cgoヘッダーで指定できる CFLAGS, LDFLAGS について。C言語ではお馴染みのフラグですね。
上記のフラグは cgoヘッダー でも指定できます。
今回のサンプルはちょっとファイル数が多いです。C言語でsoファイルを作成して、それをC言語アプリとcgoを使ったGoアプリから呼び出して試しています。
ファイル構成
今回のサンプルは以下のような構成としています。
$ tree . . ├── capp │ ├── main.c │ └── Makefile ├── goapp │ └── main.go ├── lib │ ├── lib.c │ ├── lib.h │ └── Makefile ├── README.md └── Taskfile.yml 3 directories, 8 files
- capp
- C言語のアプリ
- goapp
- Go言語のアプリ
- lib
- 共有ライブラリ
共有ライブラリ
lib.h
#ifndef __LIB_H #define __LIB_H extern void myPrint(const char *msg); #endif /* __LIB_H */
lib.c
#include <stdio.h> void myPrint(const char *msg) { printf("[C ] %s\n", msg); }
Makefile
DESTDIR = .
PROGRAM = libmylib.so
SRCS = $(shell find $(PWD) -name "*.c" -type f)
OBJS = $(SRCS:%.c=%.o)
CC = gcc
INCDIRS =
CFLAGS = -g -O0 -Wall -Wextra -std=c17
LDFLAGS =
LDLIBS =
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@ $(INCDIRS)
$(PROGRAM): $(OBJS)
$(CC) $(CFLAGS) -fPIC -shared $(LDFLAGS) -o $(DESTDIR)/$(PROGRAM) $(OBJS) $(LDLIBS)
.PHONY: build
build: $(PROGRAM)
.PHONY: clean
clean:
$(RM) $(OBJS) $(DESTDIR)/$(PROGRAM)
C言語アプリ
main.c
#include "lib.h" int main() { myPrint("hello world"); }
Makefile
DESTDIR = .
PROGRAM = cApp
SRCS = $(shell find $(PWD) -name "*.c" -type f)
OBJS = $(SRCS:%.c=%.o)
CC = gcc
INCDIRS = -I../lib
CFLAGS = -g -O0 -Wall -Wextra -std=c17
LDFLAGS = -L../lib
LDLIBS = -lmylib
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@ $(INCDIRS)
$(PROGRAM): $(OBJS)
$(CC) $(CFLAGS) $(LDFLAGS) -o $(DESTDIR)/$(PROGRAM) $(OBJS) $(LDLIBS)
.PHONY: build
build: $(PROGRAM)
.PHONY: clean
clean:
$(RM) $(OBJS) $(DESTDIR)/$(PROGRAM)
Go言語アプリ (cgo)
main.go
package main /* #cgo CFLAGS: -I../lib #cgo LDFLAGS: -L../lib -lmylib #include <stdlib.h> #include "lib.h" */ import "C" import "unsafe" func main() { var ( goStr = "hello world" cStr = C.CString(goStr) ) defer C.free(unsafe.Pointer(cStr)) C.myPrint(cStr) }
Taskfile.yml
# https://taskfile.dev version: '3' tasks: default: cmds: - task: build-lib - task: build-capp - task: build-goapp - task: run-capp - task: run-goapp - task: list-symbols - task: ldd clean: cmds: - make -C lib clean - make -C capp clean - rm -f goapp/goApp build-lib: dir: lib cmds: - make build build-capp: dir: capp cmds: - make build build-goapp: dir: goapp cmds: - go build -o goApp run-capp: dir: capp cmds: - LD_LIBRARY_PATH=../lib ./cApp run-goapp: dir: goapp cmds: - LD_LIBRARY_PATH=../lib ./goApp list-symbols: cmds: - nm --extern-only capp/cApp | grep -E ' [T|U] (main|myPrint)' - nm --extern-only goapp/goApp | grep -E ' [T|U] (main|myPrint)' ldd: cmds: - LD_LIBRARY_PATH=lib ldd capp/cApp - ldd goapp/goApp
実行
$ task task: [build-lib] make build gcc -g -O0 -Wall -Wextra -std=c17 -c /workspace/try-golang-cgo/20.C_CFLAGS_LDFLAGS/lib/lib.c -o /workspace/try-golang-cgo/20.C_CFLAGS_LDFLAGS/lib/lib.o gcc -g -O0 -Wall -Wextra -std=c17 -fPIC -shared -o ./libmylib.so /workspace/try-golang-cgo/20.C_CFLAGS_LDFLAGS/lib/lib.o task: [build-capp] make build gcc -g -O0 -Wall -Wextra -std=c17 -c /workspace/try-golang-cgo/20.C_CFLAGS_LDFLAGS/capp/main.c -o /workspace/try-golang-cgo/20.C_CFLAGS_LDFLAGS/capp/main.o gcc -g -O0 -Wall -Wextra -std=c17 -L../lib -o ./cApp /workspace/try-golang-cgo/20.C_CFLAGS_LDFLAGS/capp/main.o -lmylib task: [build-goapp] go build -o goApp task: [run-capp] LD_LIBRARY_PATH=../lib ./cApp [C ] hello world task: [run-goapp] LD_LIBRARY_PATH=../lib ./goApp [C ] hello world task: [list-symbols] nm --extern-only capp/cApp | grep -E ' [T|U] (main|myPrint)' 0000000000001149 T main U myPrint task: [list-symbols] nm --extern-only goapp/goApp | grep -E ' [T|U] (main|myPrint)' 00000000004642a0 T main U myPrint task: [ldd] LD_LIBRARY_PATH=lib ldd capp/cApp linux-vdso.so.1 (0x00007ffffa77c000) libmylib.so => lib/libmylib.so (0x00007f5b71474000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5b71244000) /lib64/ld-linux-x86-64.so.2 (0x00007f5b71480000) task: [ldd] LD_LIBRARY_PATH=lib ldd goapp/goApp linux-vdso.so.1 (0x00007fffd0998000) libmylib.so => lib/libmylib.so (0x00007f776fe38000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f776fc08000) /lib64/ld-linux-x86-64.so.2 (0x00007f776fe3f000)
ちゃんと、 ビルド時に CFLAGS と LDFLAGS の指定が反映されてビルドされていますね。
参考情報
- C? Go? Cgo!
- Go Wiki: cgo
- cmd/cgo
- runtime/cgo
- cgoを使ったCとGoのリンクの裏側 (1)
- cgoを使ったCとGoのリンクの裏側 (2)
- ebitengine/purego
- JupiterRider/ffi
Goのおすすめ書籍
過去の記事については、以下のページからご参照下さい。
サンプルコードは、以下の場所で公開しています。