在golang里调用c

Index

Intro

golang有一个贼牛的东西叫做cgo,这个可以让你在golang里运行c, 当然也可以调用c的library,总之就是很牛皮

cgo 官方文档: https://pkg.go.dev/cmd/cgo

使用方法

这边简单说一下golang里调用c的基本方法

基本

我们首先来看一下最简单的hello world.

  • 如果要使用c, 首先需要import "C", 并且在这行代码的上面要加上对应的c code, 比如引入头文件, 定义一些函数之类的
  • 调用C的函数的话就用C.<function name>, 但是要注意的是,返回的值都是C的类型。 如果要把他变成golang的类型,需要进行转换
  • 调用C的函数的话,传入的参数同样必须为C的类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

/*
#include <stdio.h>

void println(char * s) {
printf("%s\n",s);
}

*/
import "C"

func main() {
C.println(C.CString("Hello World"))
return
}

如何引入外部的C文件

比如,我有一个c文件

a.c

1
2
3
4
5
#include <stdio.h>

void println(char * s) {
printf("%s\n",s);
}

a.h

1
void println(char * s);

那么要在golang里调用的话就是

1
2
3
4
5
6
7
8
9
10
11
12
package main

/*
#include <a.h>
*/
import "C"

func main() {
C.println(C.CString("Hello World"))
return
}

调用第三方库

这里以调用libmpv为例子.

注意mpv/client.h以及对应的库文件(mpv-2.dll或者libmpv.so)必须在PATH环境变量中。

如果不在的话,需要修改LDFLAGS #cgo LDFLAGS: -L./lib -lmpv

1
2
3
4
5
6
7
8
9
10
/*
#include <mpv/client.h>
#include <stdint.h>
#cgo LDFLAGS: -lmpv
*/
import "C"

func ClientApiVersion() uint32 {
return uint32(C.mpv_client_api_version())
}

踩坑

不同的系统有不同的Primitive data

比如

在windows上,uint64 或者说 long 他的大小是 4 byte.
但是在unix/linux上, uint64 的大小是 8 byte.

这就导致了一些问题,比如我有一个项目需要同时支持两个系统,那么当我想传入uint64的时候,在windows上得用ulong,在linux上就得用 ulonglong.

解决方式就是使用引入stdint.h 然后使用 uint64_t.

1
2
3
4
5
6
7
8
/*
#include <stdint.h>
*/
import "C"

func main() {
C.some_function(C.uint64_t(1000))
}

常见问题

Q: 遇到unexpected type: ...是怎么回事呢

... 在c里面代表可变的函数参数,但是cgo无法识别他,所以会报错。解决方法就是写一个函数,在c里面调用这个函数,给golang的函数里就不要出现可变的部分了

举个例子, 比如以下情况就会出现这个报错,因为printf 里有个可变部分。int printf(const char *format, ...)

1
2
3
4
5
6
7
8
9
/*
#include <stdio.h>
*/
import "C"

func main() {
C.printf(C.CString("Hello World"))
return
}