综述
写 cgo
不是写出来的,而是调试出来的,想要给一个c库写 gobinding
往往会遇得到你意向不大到的困难。
- 内存泄露
- 类型转换
- 函数调用
- 非标准c接口(c++问题)
这里以我的经历开始记录一下,我给公司写一个动态库 so
的 gobinding
的过程。简述一下我曲折的道路:
-
一开始,公司只给一个
so
,和原来用python
写的一个gobinding
,我发现原来的py脚本用的类似于ffi
的方法,就是动态load
,总之我也用这种方法,但是无论如何不是这个类找不到就是哪个函数无法调用。看完教程之后我知道大家都有源文件,难度会容易一些。 -
问了前辈说能给提供一个.h头文件,遂开始写了。想
开始
在 golang
中引入头文件
如果头文件不是很长的话,建议直接写在一个文件里。
|
|
其中 import "C"
和其他的引入不能放在一个括号里,必须单独,并且必须贴紧上方注释。
将自定义的C类型 -> golang
目的是,在包外调用时无需再 针对C相关的包 申明任何东西,也就是无需再外部 import "C"
类型转化只要注意一个问题就好,就是类型是否经过 typedef
类型定义,如果经过了,在golang中无需指定struct
|
|
否则的话
|
|
我在头文件中看到了,所有类都经过了typedef,无需我申明了。
于是我首先给所有类的用type定义到golang这边来。
翻译函数
把C的函数放到golang这边,并不难,但是在类型转化过程中会有一些困难。
举例1
|
|
这个函数写到golang这边,首先返回值,两个参数都是指针
-
尽量每次操作指针都转换,golang并不会隐式的帮你转换类型
-
往c传时强制转换成c的类型,返回golang就相反
-
转化
- 返回值强制转化(*SkynetT)()
- 传入c强制转化
(*C.skynet_conf_t)(conf)
和(*C.char)(unsafe.Pointer(&policy[0]))
|
|
举例2
|
|
这个函数有五个参数,分别是: - skynet_request_t(c类型) - uint32 - void * - ulong(size_t) - skynet_result_t(c类型)
返回值是一个int32在调用它的过程中,搞出了很多问题(内存泄露、参数传输错误)
先写出结论来,再说到底怎么回事
|
|
第一个参数: 首先,这个函数传了request作为第一个参数,我这应该是可以看做this指针,因此我将此方法,定义到该类型上,并且使用了指针:func (s *SkynetRequest) Classify...
,这样可以方便使用s来作为函数的第一个参数
第二个参数: 是一个uint而已,但是为了安全仍然进行转换。
第三个参数: 是一个const void *data
但是根据猜测,payload是一个字符串,第四个参数: 指定了他的长度,因此一开始我直接用了char也就是C.CString()
传入,报错了char as uchar
,但是却不知道uchar
怎么传,网上各种骚办法,最后都没人说其实uchar=byte
, 使用C.CBytes(data)
传入完美解决,这里data的传入gc只会搞定byte slice
,C那边的内存需要使用free
来操作。
第五个参数: 是一个库内的指针,直接传入引用即可,但是一定要在传入前进行强制类型转换,否则也会报错。
其中没什么经验,很多都是一点一点试出来的,网络上资料很少,走了很多弯路,仍然有很多问题,这时候意识到自己基础的薄弱,有时候连个作用域都拿不准,不敢free又造成了内存泄露的问题出现,长路漫漫,加油。