go系列&运维系列&Windows系列:golang学习7:编译为可被C语言调用的库
golang学习7:编译为可被C语言调用的库
golang学习7:编译为可被C语言调用的库
说明
在使用 go build
或 go install
两个命令编译代码的时候,可以使用-buildmode
指定生成什么样的文件。
go build -buildmode=<mode>
或者
go install -buildmode=<mode>
使用 go help buildmode
可以查看所有支持的buildmode选项
。
-buildmode=archive
Build the listed non-main packages into .a files. Packages named
main are ignored.
-buildmode=c-archive
Build the listed main package, plus all packages it imports,
into a C archive file. The only callable symbols will be those
functions exported using a cgo //export comment. Requires
exactly one main package to be listed.
-buildmode=c-shared
Build the listed main package, plus all packages it imports,
into a C shared library. The only callable symbols will
be those functions exported using a cgo //export comment.
Requires exactly one main package to be listed.
-buildmode=default
Listed main packages are built into executables and listed
non-main packages are built into .a files (the default
behavior).
-buildmode=shared
Combine all the listed non-main packages into a single shared
library that will be used when building with the -linkshared
option. Packages named main are ignored.
-buildmode=exe
Build the listed main packages and everything they import into
executables. Packages not named main are ignored.
-buildmode=pie
Build the listed main packages and everything they import into
position independent executables (PIE). Packages not named
main are ignored.
-buildmode=plugin
Build the listed main packages, plus all packages that they
import, into a Go plugin. Packages not named main are ignored.
分类说明下:
-
-buildmode=shared
是为了生成go编译
编译期间使用的库,概念类似c语言
的动态库。使用这些库编译go文件
需加选项-linkshared
。 -
-buildmode=c-archive
和-buildmode=c-shared
是为了生成C语言
的库和头文件,分别对应静态库和动态库 -
-buildmode=plugin
是为了生成go语言
运行期间可加载的动态库(目前只能加载,无法卸载,是不是可以通过销毁协程来卸载呢?)
其它几个选项暂时不太明白。
将go脚本编译为C语言库
将go源码
编译为C语言库
需满足以下条件
-
编译选项使用
-buildmode=c-archive
或-buildmode=c-shared
-
编译源码里必须有
main package
-
源码必须
import “C”
-
导出符号位于
main package
,且前一行有//export NAME
由于导出的符号只能位于main package
,所以必须定义main()函数
,但是这个函数可以为空。而且导出的函数的参数和返回值类型只能使用golang
的基础类型,无法使用go语言内部
的结构体等复杂类型。
现在定义一个简单的go文件
用于生成C语言
的库
package main
import (
"C"
"fmt"
)
//export hello
func hello(num int32, name string, size float32) uint8 {
fmt.Printf("Hello, world, %v, %v, %v\n", num, name, size)
return 0
}
func main() {
fmt.Printf("Hello, in main.\n")
hello(1, "Yuan", 3.14)
}
直接运行测试下:
$ go run main.go
Hello, in main.
Hello, world, 1, Yuan, 3.14
现在来生成C语言可以调用的库:
$ go build -buildmode=c-archive -o hello.a main.go
$ ls
go.mod hello.a hello.h main.go
可以看到,生成了两个文件:hello.a和hello.h。C语言开发的伙伴肯定不陌生了。
go生成的头文件
来看看上一步生成的头文件里都有啥:
/* Code generated by cmd/cgo; DO NOT EDIT. */
/* package command-line-arguments */
#line 1 "cgo-builtin-export-prolog"
#include <stddef.h> /* for ptrdiff_t below */
#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
#endif
#endif
/* Start of preamble from import "C" comments. */
/* End of preamble from import "C" comments. */
/* Start of boilerplate cgo prologue. */
#line 1 "cgo-gcc-export-header-prolog"
#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H
typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef __SIZE_TYPE__ GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;
/*
static assertion to make sure the file is being used on architecture
at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
#endif
/* End of boilerplate cgo prologue. */
#ifdef __cplusplus
extern "C" {
#endif
extern GoUint8 hello(GoInt32 num, GoString name, GoFloat32 size);
#ifdef __cplusplus
}
#endif
可以看到go语言内
的基本类型都被typedef
重新定义了。其中GoString
稍微复杂点,被定为一种含指针和长度的结构体:
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
#endif
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
使用C语言调用golang生成的库
现在编写一个简单的C文件
,调用刚刚生成的库里的hello()这个函数
。
#include <stdio.h>
#include "hello.h"
int main()
{
GoInt32 num = 3;
GoFloat32 size = 3.14;
GoString name = {0};
name.p = "Yuan";
name.n = 4; //上面字符串的长度
GoUint8 ret = hello(num, name, size);
printf("ret=%d\n", ret);
return 0;
}
现在来编译运行下:
$ mv hello.a libhello.a
$ gcc hello_test_go.c -L ./ -l hello -lpthread
$ ./a.out
Hello, world, 3, Yuan, 3.14
ret=0
可以看到,完美运行了go语言
中的函数。
参考资料
Go 语言编译模式 (buildmode) 之 C 源码归档模式 ( c-archive) 静态库模式