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语言库需满足以下条件

  1. 编译选项使用-buildmode=c-archive或-buildmode=c-shared
  2. 编译源码里必须有main package
  3. 源码必须import “C”
  4. 导出符号位于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  /* 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 

#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语言中的函数。

参考资料

Golang的构建模式

golang插件系统

Go 语言编译模式 (buildmode) 之 C 源码归档模式 ( c-archive) 静态库模式

你可能感兴趣的:(golang,golang,c语言)