先說 Mac os 下面吧
一路上順風順水。
總結(jié)了一下,CGO所使用的原理,在Go里面調(diào)用C的程序
其實就是使用了C++的一個特性,extern
// 網(wǎng)上是這樣解釋的
extern可置于變量或者函數(shù)前,以表示變量或者函數(shù)的定義在別的文件中
提示編譯器遇到此變量和函數(shù)時在其他模塊中尋找其定義。
當你對一個函數(shù) extern 聲明后,這個函數(shù)你就可以使用了,但是其實它并不存在,需要Go去生成
// Go 生成有個前地,就是在這個函數(shù)上面加一行(//export cgo_connect),cgo_connect 就是函數(shù)名
但是當使用的時候,其實Go已經(jīng)生成好了,編譯到程序里面,所以運行的就是所以 Cgo 的程序
// 可以通過一個命令來看 Go 中間做了什么
go tool cgo xxx.go
// 這 xxx.go 里面要寫的就是下面這些C與Go的混合代碼
/*
// 這里看到 extern 了把,使用這個的函數(shù),只有三個地方存在
// 一,注釋內(nèi)
// 二,Go 內(nèi)用 //export drv_cgo_connect 實現(xiàn)的
// 三,在 C 里面實現(xiàn)的(可能是文件,也可能是庫)
extern void drv_cgo_connect(int (*)(void *, int));
static void init_callback()
{
extern int cgo_connect(void *, int);
drv_cgo_connect(&cgo_connect);
}
*/
// #include <stdio.h>
// #include <stdlib.h>
// #cgo LDFLAGS: -L./ -lexamples // 庫內(nèi)有一切,不僅調(diào)用,還可以互訪
import "C"
import "unsafe"
func start() {
C.init_callback()
}
// 這里就是生成了一個 C 的 cgo_connect 函數(shù)
// 想要在 C 中使用這個函數(shù),就需要在 C 里面 extern int cgo_connect(...) 才可以.
//export cgo_connect
func cgo_connect(_content unsafe.Pointer, _size C.int) C.int {
device := string(C.GoBytes(_content, _size))
log.Println("Go->", device)
if err := StaticConn.Connect(device); err != nil {
return C.int(0)
}
return C.int(1)
}
這樣一來,你就可以在 Go 里面調(diào)用 C 里面實現(xiàn)的函數(shù)
同樣原理,如果 C 想要調(diào)用 Go 的函數(shù),相同方法,只是把 extern 調(diào)換一下!
上面簡單的了解了一下,CGO 原理,現(xiàn)在說一下具體情況,比如 QT 這樣龐大的程序
對于 QT 這樣的大程序,而且涉及到 QMake 編譯,所以使用 CGO 本身混編(.go,.c.,.h)就沒戲了
所以怎么辦呢,這位同學說對了,就是這樣,使用庫(windows是dll)(linux是so)(mac是dylib)
將 QT 的 .Pro 文件改一下 TEMPLATE = lib 這樣就會生成庫文件
但是上面的同學問了,庫文件能被Go調(diào)用,那庫文件怎么調(diào)用Go呢
問題就來了,我試驗了無數(shù)次??樱?,坑!
你想到了,extern 沒錯,但是 extern 的函數(shù)需要在本庫中找到實現(xiàn)的函數(shù)
對于Cgo里面,你沒有實現(xiàn)是因為你設置了 //export 所以 Go 就幫你做了
所以怎么辦,對了,你想到,go tool cgo 把 C 編譯出來,導入到 QT 中不就了。
錯,大錯特錯,編譯出來的文件,不完整,可遠觀,不可近玩嫣!
我試的時候雖然能編譯過,但是 Go 轉(zhuǎn) C 的函數(shù)根本沒運行
這樣一來,樓上的同學哭了,那怎么辦啊。
當當當當~!在這里用到了 C++ 的另外一個關鍵字 inline 隆重出馬
// 網(wǎng)上這樣介紹的
inline關鍵字用來定義一個類的內(nèi)聯(lián)函數(shù),引入它的主要原因是用它替代C中表達式形式的宏定義。
也就是說,你在C庫中用inline定義一個空函數(shù),然后使用TA,然后在Go里面用//export 定義一個同名函數(shù)。
這樣一來,C里面就使用的不是inline定義的函數(shù),而是你Go里面的函數(shù)了,天哪,太簡單,太方便了。

再說 Windows 這個坑吧
當你 Mac 或者 Linux 按照上面方法,寫完了,你可能興高采烈的跑去 windows 編譯
當你 費了 九牛二虎之力,把環(huán)境編譯好了,之后呢,編譯唄,坑坑坑,恭喜,你又掉坑里了。
這個問題,我找啊找,查啊查,費了就牛二老之力,找到了問題了。
問題就是 inline // 網(wǎng)上是這樣說的
inline說明對編譯器來說只是一種建議,編譯器可以選擇忽略這個建議。
比如,你將一個長達1000多行的函數(shù)指定為inline,編譯器就會忽略這個inline,將這個函數(shù)還原成普通函數(shù)。
天哪,看編譯器心情嗎,在 Mac 里面就用 Go 里面 原始函數(shù),在 Windows 下面就選擇了,inline 定義的空函數(shù),這可怎么辦啊。
網(wǎng)上一頓亂找,什么,強制 inline ,編譯器不優(yōu)化,一頓亂找,搞不定啊。
后來聽說,windows 對 dll 有特殊限制,有些 關鍵字無法傳遞,比如 inline ,所以,唉?。。?,沒辦法.
既然不用 inline 也就不能用 extern 因為 只有加了 extern inline 的函數(shù)才變成實際存在的,如果去掉 inline
編譯器會一直提示你,沒有找到,沒有找到,沒有找到。啊啊啊啊啊啊??!
找啊找,查啊查,坑啊坑??戳艘恍﹦e人的代碼,涉及到的太少,幾乎沒有
翻了一下 liteide 的代碼,哈哈,看到曙光了,怎么辦呢
是怎么處理的呢,比較復雜,但是可行
就是在 Cgo 里面自己調(diào)用自己的函數(shù),然后 CGo 的方法,把函數(shù)指針傳到庫里去,在庫里面搞個全局保存一下.
/*
extern void drv_cgo_connect(int (*)(void *, int));
static void init_callback()
{
extern int cgo_connect(void *, int);
drv_cgo_connect(&cgo_connect);
}
*/
// #include <stdio.h>
// #include <stdlib.h>
// #cgo LDFLAGS: -L./ -lexamples
import "C"
import "unsafe"
func start() {
C.init_callback()
}
//export cgo_connect
func cgo_connect(_content unsafe.Pointer, _size C.int) C.int {
device := string(C.GoBytes(_content, _size))
log.Println("Go->", device)
if err := StaticConn.Connect(device); err != nil {
return C.int(0)
}
return C.int(1)
}
// 在 C 里面是這樣
typedef int (*COMMAND_CGO_CONNECT_FUNCTION)(void *, int);
typedef int (*COMMAND_CGO_CHECKCONN_FUNCTION)();
COMMAND_CGO_CONNECT_FUNCTION cgo_connect;
COMMAND_CGO_CHECKCONN_FUNCTION cgo_checkconn;
extern "C" void drv_cgo_connect(int (*_a)(void *, int))
{
cgo_connect = _a;
}
extern "C" void drv_cgo_checkconn(int (*_a)())
{
cgo_checkconn = _a;
}
這樣一來,問題解決了,但是復雜了一些,對 Cgo 減10分.
補充,當 windows 運行的時候 會顯示 DOS 窗口,怎么辦呢
go build -ldflags -H=windowsgui
go build -ldflags -H=windowsgui XXX.go
再次補充:
// 可以把函數(shù)當指針傳,這樣就不需要那么多的 drv_cgo_xxx
/*
extern void cgo_init();
extern int cgo_start();
extern void cgo_callback(void *);
extern void drv_cgo_callback(int, void*);
extern void drv_cgo_callback_2(int, void*);
static void init_callback()
{
int _cgo_connect = 1;
int _cgo_checkconn = 2;
int _cgo_disconn = 3;
int _cgo_command = 4;
int _cgo_shortcuts = 5;
int _cgo_message = 6;
extern int cgo_connect(void *, int);
extern int cgo_checkconn();
extern void cgo_disconn();
extern void cgo_command(void *, int);
extern void cgo_shortcuts(void *, int);
extern void * cgo_message();
drv_cgo_callback_2(_cgo_connect, &cgo_connect);
drv_cgo_callback_2(_cgo_checkconn, &cgo_checkconn);
drv_cgo_callback(_cgo_checkconn, &cgo_checkconn);
drv_cgo_callback(_cgo_disconn, &cgo_disconn);
drv_cgo_callback(_cgo_command, &cgo_command);
drv_cgo_callback(_cgo_shortcuts, &cgo_shortcuts);
drv_cgo_callback(_cgo_message, &cgo_message);
}
*/
typedef int (*COMMAND_CGO_CONNECT_FUNCTION)(void *, int);
typedef int (*COMMAND_CGO_CHECKCONN_FUNCTION)();
static COMMAND_CGO_CONNECT_FUNCTION cgo_connect = 0;
static COMMAND_CGO_CHECKCONN_FUNCTION cgo_checkconn = 0;
extern "C" void drv_cgo_callback_2(int _a, void * _b)
{
/*
int _cgo_connect = 1;
int _cgo_checkconn = 2;
*/
switch (_a) {
case 1:
cgo_connect = (COMMAND_CGO_CONNECT_FUNCTION)_b;
break;
case 2:
cgo_checkconn = (COMMAND_CGO_CHECKCONN_FUNCTION)_b;
break;
}
}
