前言

Go 语言的一个优点是可以调用 C 代码,可以直接在 Go 源代码里写 C 代码,也可以引 C 语言的外部库。这样在性能遇到瓶颈的地方可以重写,或者某些功能 Go 和第三方缺失,但 C 语言有现成的库就可以直接用。
下面有几种方法来演示Go调用C, 并介绍向arm平台移植的交叉编译方法(其他平台的交叉编译方法类似).

内嵌C代码

下面的代码中直接在Go程序中嵌入了C编写的程序,Go中需要紧跟在C代码之后引入一个”C”包.定义一个add函数,并在main中调用.

// main.go
package main

/*
int add(int a, int b)
{
    return a + b;
}
*/
import "C"
import "fmt"

func main() {
    a := C.int(10)
    b := C.int(20)
    val := C.add(a,b)
    fmt.Printf("a+b=%v\n", val)
}

单个文件使用go run main.go可以直接运行.
交叉编译:

luxq@luxq-vmpc:go_call_c$CGO_ENABLED=1 GOOS=linux GOARCH=arm CC=arm-linux-gnueabihf-gcc go build -o main
luxq@luxq-vmpc:go_call_c$file main
main: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 2.6.31, BuildID[sha1]=e7912b8c5b526f52c64a9b33c3e9df751ee9b903, not stripped

独立的C源码文件

各文件内容如下:

// add.h
#ifndef ADD_H
#define ADD_H

int add(int a, int b);

#endif  /*ADD_H*/

add.c 中定义内部的add函数

// add.c 
#include "add.h"
int add(int a, int b )
{
    return a+b;
}

foo.h中定义外部使用接口

// foo.h
#ifndef FOO_H
#define FOO_H

#include <stdio.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
    extern int Num;
    extern void foo();
    extern int f_add(int a, int b);
#ifdef __cplusplus
}
#endif

#endif  /*FOO_H*/

foo.c 中实现接口,同时调用了add.h 中定义的add函数.

// foo.c
#include <stdio.h>
#include "add.h"

int Num = 8;

void foo()
{
    printf("First line.\n");
}

int f_add(int a, int b)
{
    return add(a,b);
}
1. 同级目录

这种情况是指C代码和Go代码混在同一目录下,Go中调用C代码的函数。
目录结构如下:

go_call_c/
├── add.c
├── add.h
├── foo.c
├── foo.h
└── main.go

在所有源码在同一目录下的情况下,main.go中只需要引入直接调用接口的头文件foo.h即可,如下:

// main.go 
package main

/*
#include "foo.h"
*/
import "C"
import "unsafe"
import "fmt"

func Prin(s string) {
    cs := C.CString(s)
    defer C.free(unsafe.Pointer(cs))
    C.fputs(cs, (*C.FILE)(C.stdout))
    C.fflush((*C.FILE)(C.stdout))
}

func main() {
    fmt.Println("rannum:%x\n", C.random())
    Prin("Hello CC")
    fmt.Println(C.Num)
    C.foo()

    a:=C.int(1)
    b:=C.int(2)
    value:= C.f_add(a,b)
    fmt.Println("a+b=%v\n", value);
}

由于有多个文件,所以需要使用go build来编译,再执行.

luxq@luxq-vmpc:go_call_c$go build -o main
luxq@luxq-vmpc:go_call_c$./main 
rannum:%x
 1804289383
Hello CC8
First line.
a+b=3
luxq@luxq-vmpc:go_call_c$

交叉编译:

luxq@luxq-vmpc:go_call_c$CGO_ENABLED=1 GOOS=linux GOARCH=arm CC=arm-linux-gnueabihf-gcc go build -o main
luxq@luxq-vmpc:go_call_c$file main
main: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 2.6.31, BuildID[sha1]=3bd34af6f6e5543cb2867490302ab1ac307c4fae, not stripped
luxq@luxq-vmpc:go_call_c$
2. 不同级目录

这种情况是指C代码和Go代码不在同一目录下,Go中调用C代码的函数。
目录结构如下:

go_call_c/
├── c_src
│   ├── add.c
│   ├── add.h
│   ├── foo.c
│   └── foo.h
└── main.go

在这种情况下,main.go中不仅需要引入直接调用接口的头文件foo.h,还需要引入相关的C文件,如下:

// main.go
package main

/*
#include "c_src/foo.h"
#include "c_src/foo.c"
#include "c_src/add.c"
*/
import "C"
import "unsafe"
import "fmt"

func Prin(s string) {
    cs := C.CString(s)
    defer C.free(unsafe.Pointer(cs))
    C.fputs(cs, (*C.FILE)(C.stdout))
    C.fflush((*C.FILE)(C.stdout))
}

func main() {
    fmt.Println("rannum:%x\n", C.random())
    Prin("Hello CC")
    fmt.Println(C.Num)
    C.foo()

    a:=C.int(1)
    b:=C.int(2)
    value:= C.f_add(a,b)
    fmt.Println("a+b=%v\n", value);
}

同样需要使用go build 进行编译,然后再执行生成的可执行文件.

luxq@luxq-vmpc:go_call_c$go build -o main
luxq@luxq-vmpc:go_call_c$./main 
rannum:%x
 1804289383
Hello CC8
First line.
a+b=3
luxq@luxq-vmpc:go_call_c$

交叉编译:

luxq@luxq-vmpc:go_call_c$CGO_ENABLED=1 GOOS=linux GOARCH=arm CC=arm-linux-gnueabihf-gcc go build -o main
luxq@luxq-vmpc:go_call_c$file main
main: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 2.6.31, BuildID[sha1]=63ca836b1b931d7dc058039fd4c86a210c347640, not stripped
luxq@luxq-vmpc:go_call_c$

外部库形式

此种方法可能是最常用的方法,就是首先将C代码编译成库文件,然后在Go中引用库.
各C代码文件内容和上述内容一样.
目录结构如下:

go_call_c/
├── c_src
│   └── foo.c
│   └── add.h
│   └── add.c
├── include
│   └── foo.h
├── lib
│   └── libfoo.so
└── src
    └── main.go

将c_src下文件编译成lib/libfoo.so(不讲述), 然后在src/main.go 中引用库.

// main.go
package main

/*
#cgo CFLAGS : -I../include/
#cgo LDFLAGS : -L../lib -lfoo

#include "foo.h"
*/
import "C"
import "unsafe"
import "fmt"

func Prin(s string) {
    cs := C.CString(s)
    defer C.free(unsafe.Pointer(cs))
    C.fputs(cs, (*C.FILE)(C.stdout))
    C.fflush((*C.FILE)(C.stdout))
}

func main() {
    fmt.Println("rannum:%x\n", C.random())
    Prin("Hello CC")
    fmt.Println(C.Num)
    C.foo()
}

同样先编译main.go 然后再执行.

luxq@luxq-vmpc:src$go build -o main
luxq@luxq-vmpc:src$./main 
rannum:%x
 1804289383
Hello CC8
First line.
luxq@luxq-vmpc:src$

交叉编译:
编译时需要确保库文件已经是arm平台格式的。

luxq@luxq-vmpc:src$CGO_ENABLED=1 GOOS=linux GOARCH=arm CC=arm-linux-gnueabihf-gcc go build -o main
luxq@luxq-vmpc:src$file main
main: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 2.6.31, BuildID[sha1]=bae4b9df39d88a81dad2f07281ee17d1065a0549, not stripped
luxq@luxq-vmpc:src$

总结

自己总结吧.

参考

参考:http://bastengao.com/blog/2017/12/go-cgo-c.html

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐