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

yufei       1 月, 2 周 前       65

Go 语言的编译模式,简单的来说就是在使用 go buildgo install 两个命令编译代码的时候要生成什么样的文件。

使用方式简单来说就是

go build -buildmode=<mode>

go install -buildmode=<mode>

例如我们今天要讲的 C 语言源码归档模式 c-archive

其实吧, C 语言源码归档模式 c-archive 就是把 go 语言的代码编译成 C 语言静态链接库

c-charve 模式的几个主要点:

  1. 编译的时候 -buildmode=c-archive
  2. 编译结果只会有一个 .h 和一个 .a 文件,这就是 archive 归档的由来,也就是不管 go 源码里导入了多少第三方库和标准库,多少代码,结果只有 2 个文件
  3. 所有的 结构 struct 和接口 interface 和函数 func 默认是不导出的,也就是你不能调用,只有添加了 // export <方法名> 注释的东西才会被导出。
  4. 所有需要导出的方法必须处于 main 包下,其它包下的方法是不会被导出的。
  5. go 语言包里必须包含 import "C" 代码

范例

  1. 首先使用下面的命令行创建项目

    mkdir bm
    cd bm
    go mod init bm
    touch main.go
    
  2. 使用你最喜欢的编辑器打开 main.go 文件然后复制下面的代码

    // 文件 main.go
    package main
    
    import "C"    // 这是必须的
    import "fmt"
    
    /**
     *  所有需要在静态库中使用的代码,都必须 export
     */
    
    //export SayHello
    func SayHello(name string) {
        fmt.Printf("func in Golang SayHello says: Hello, %s!\n", name)
    }
    
    //export SayHelloByte
    func SayHelloByte(name []byte) {
        fmt.Printf("func in Golang SayHelloByte says: Hello, %s!\n", string(name))
    }
    
    //export SayBye
    func SayBye() {
        fmt.Println("func in Golang SayBye says: Bye!")
    }
    
    func main() {
        // main() 方式没有任何实质代码,但又是必须的
    }
    
  3. 然后我们使用下面的命令来编译

    go build -buildmode=c-archive
    

    编译有点耗时,具体多久取决于你加载了多少类库,编译完成后我们可以在当前名录下发现几个新文件

    yufei@twle.cn bm % ls
    bm.a    bm.h    go.mod  main.go main.md
    
  4. bm.abm.h 就是我们编译出来的文件。 bm.a 是一个二进制文件,没啥好看的,我们来看看 bm.h

    /* Code generated by cmd/cgo; DO NOT EDIT. */
    
    /* package bm */
    
    #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 void SayHello(GoString p0);
    
    extern void SayHelloByte(GoSlice p0);
    
    extern void SayBye();
    
    #ifdef __cplusplus
    }
    #endif
    

    内容非常长,总的来说分为三大块:

    1. C 语言开发相关的头文件导入
    2. boilerplate cgo prologue Go 语言相关内置的类型在 C 语言下重新定义
    3. extern 相关的函数就是我们自定义的需要导出的函数
  5. 那要怎么使用我们导出的 bm.abm.h 文件呢?

    在当前目录下新建一个 main.c 文件,然后,

    如果你会 C 语言开发,那就相当的简单了,话不多说,直接上代码

    // file main.c
    #include <stdio.h>
    #include "bm.h"
    
    int main() {
        printf("This is a C Application.\n");
    
        GoString name = {(char*)"Jane", 4};
        SayHello(name);
    
        GoSlice buf = {(void*)"Jane", 4, 4};
        SayHelloByte(buf);
    
        SayBye();
        return 0;
    }
    

    几个注意点:

    1. 传递给静态库里的函数的参数必须是 *重新定义的符合 Go 类型的类型。 比如上面的{(char)"Jane", 4}` 字符串。

    接着我们使用 gcc 来编译

    gcc main.c bm.a
    

    然后运行一下

    // linux or mac
    ./a.out
    
    // window
    ./a
    

    输出结果如下

    This is a C Application.
    func in Golang SayHello says: Hello, Jane!
    func in Golang SayHelloByte says: Hello, Jane!
    func in Golang SayBye says: Bye!
    

这样我们就了解了 c-archive 模式。

目前尚无回复
简单教程 = 简单教程,简单编程
简单教程 是一个关于技术和学习的地方
现在注册
已注册用户请 登入
关于   |   FAQ   |   我们的愿景   |   广告投放   |  博客

  简单教程,简单编程 - IT 入门首选站

Copyright © 2013-2018 简单教程 twle.cn All Rights Reserved.