分享

关于最近用Golang和Qt混写程序所遇到的坑

 quasiceo 2016-11-20

关于最近用Golang和Qt混写程序所遇到的坑

先说 Mac os 下面吧

一路上顺风顺水。

总结了一下,CGO所使用的原理,在Go里面调用C的程序

其实就是使用了C++的一个特性,extern

  1. // 网上是这样解释的
  2. extern可置于变量或者函数前,以表示变量或者函数的定义在别的文件中
  3. 提示编译器遇到此变量和函数时在其他模块中寻找其定义。

当你对一个函数 extern 声明后,这个函数你就可以使用了,但是其实它并不存在,需要Go去生成

// Go 生成有个前地,就是在这个函数上面加一行(//export cgo_connect),cgo_connect 就是函数名

但是当使用的时候,其实Go已经生成好了,编译到程序里面,所以运行的就是所以 Cgo 的程序

  1. // 可以通过一个命令来看 Go 中间做了什么
  2. go tool cgo xxx.go
  3. // 这 xxx.go 里面要写的就是下面这些C与Go的混合代码
  4. /*
  5. // 这里看到 extern 了把,使用这个的函数,只有三个地方存在
  6. // 一,注释内
  7. // 二,Go 内用 //export drv_cgo_connect 实现的
  8. // 三,在 C 里面实现的(可能是文件,也可能是库)
  9. extern void drv_cgo_connect(int (*)(void *, int));
  10. static void init_callback()
  11. {
  12. extern int cgo_connect(void *, int);
  13. drv_cgo_connect(&cgo_connect);
  14. }
  15. */
  16. // #include <stdio.h>
  17. // #include <stdlib.h>
  18. // #cgo LDFLAGS: -L./ -lexamples // 库内有一切,不仅调用,还可以互访
  19. import "C"
  20. import "unsafe"

  21. func start() {
  22. C.init_callback()
  23. }

  24. // 这里就是生成了一个 C 的 cgo_connect 函数
  25. // 想要在 C 中使用这个函数,就需要在 C 里面 extern int cgo_connect(...) 才可以.
  26. //export cgo_connect
  27. func cgo_connect(_content unsafe.Pointer, _size C.int) C.int {
  28. device := string(C.GoBytes(_content, _size))
  29. log.Println("Go->", device)
  30. if err := StaticConn.Connect(device); err != nil {
  31. return C.int(0)
  32. }
  33. return C.int(1)
  34. }

这样一来,你就可以在 Go 里面调用 C 里面实现的函数

同样原理,如果 C 想要调用 Go 的函数,相同方法,只是把 extern 调换一下!


上面简单的了解了一下,CGO 原理,现在说一下具体情况,比如 QT 这样庞大的程序

对于 QT 这样的大程序,而且涉及到 QMake 编译,所以使用 CGO 本身混编(.go,.c.,.h)就没戏了

所以怎么办呢,这位同学说对了,就是这样,使用库(windows是dll)(linux是so)(mac是dylib)

将 QT 的 .Pro 文件改一下 TEMPLATE = lib 这样就会生成库文件

但是上面的同学问了,库文件能被Go调用,那库文件怎么调用Go呢

问题就来了,我试验了无数次。坑,坑,坑!

你想到了,extern 没错,但是 extern 的函数需要在本库中找到实现的函数

对于Cgo里面,你没有实现是因为你设置了 //export 所以 Go 就帮你做了

所以怎么办,对了,你想到,go tool cgo 把 C 编译出来,导入到 QT 中不就了。

错,大错特错,编译出来的文件,不完整,可远观,不可近玩嫣!

我试的时候虽然能编译过,但是 Go 转 C 的函数根本没运行

这样一来,楼上的同学哭了,那怎么办啊。

当当当当~!在这里用到了 C++ 的另外一个关键字 inline 隆重出马

  1. // 网上这样介绍的
  2. inline关键字用来定义一个类的内联函数,引入它的主要原因是用它替代C中表达式形式的宏定义。

也就是说,你在C库中用inline定义一个空函数,然后使用TA,然后在Go里面用//export 定义一个同名函数。

这样一来,C里面就使用的不是inline定义的函数,而是你Go里面的函数了,天哪,太简单,太方便了。


再说 Windows 这个坑吧

当你 Mac 或者 Linux 按照上面方法,写完了,你可能兴高采烈的跑去 windows 编译

当你 费了 九牛二虎之力,把环境编译好了,之后呢,编译呗,坑坑坑,恭喜,你又掉坑里了。

这个问题,我找啊找,查啊查,费了就牛二老之力,找到了问题了。

  1. 问题就是 inline // 网上是这样说的
  2. inline说明对编译器来说只是一种建议,编译器可以选择忽略这个建议。
  3. 比如,你将一个长达1000多行的函数指定为inline,编译器就会忽略这个inline,将这个函数还原成普通函数。

天哪,看编译器心情吗,在 Mac 里面就用 Go 里面 原始函数,在 Windows 下面就选择了,inline 定义的空函数,这可怎么办啊。

网上一顿乱找,什么,强制 inline ,编译器不优化,一顿乱找,搞不定啊。

后来听说,windows 对 dll 有特殊限制,有些 关键字无法传递,比如 inline ,所以,唉!!!,没办法.

既然不用 inline 也就不能用 extern 因为 只有加了 extern inline 的函数才变成实际存在的,如果去掉 inline

编译器会一直提示你,没有找到,没有找到,没有找到。啊啊啊啊啊啊啊!


找啊找,查啊查,坑啊坑。看了一些别人的代码,涉及到的太少,几乎没有

翻了一下 liteide 的代码,哈哈,看到曙光了,怎么办呢

是怎么处理的呢,比较复杂,但是可行

就是在 Cgo 里面自己调用自己的函数,然后 CGo 的方法,把函数指针传到库里去,在库里面搞个全局保存一下.

  1. /*
  2. extern void drv_cgo_connect(int (*)(void *, int));
  3. static void init_callback()
  4. {
  5. extern int cgo_connect(void *, int);
  6. drv_cgo_connect(&cgo_connect);
  7. }
  8. */
  9. // #include <stdio.h>
  10. // #include <stdlib.h>
  11. // #cgo LDFLAGS: -L./ -lexamples
  12. import "C"
  13. import "unsafe"

  14. func start() {
  15. C.init_callback()
  16. }

  17. //export cgo_connect
  18. func cgo_connect(_content unsafe.Pointer, _size C.int) C.int {
  19. device := string(C.GoBytes(_content, _size))
  20. log.Println("Go->", device)
  21. if err := StaticConn.Connect(device); err != nil {
  22. return C.int(0)
  23. }
  24. return C.int(1)
  25. }

  26. // 在 C 里面是这样

  27. typedef int (*COMMAND_CGO_CONNECT_FUNCTION)(void *, int);

  28. typedef int (*COMMAND_CGO_CHECKCONN_FUNCTION)();

  29. COMMAND_CGO_CONNECT_FUNCTION cgo_connect;

  30. COMMAND_CGO_CHECKCONN_FUNCTION cgo_checkconn;

  31. extern "C" void drv_cgo_connect(int (*_a)(void *, int))
  32. {
  33. cgo_connect = _a;
  34. }

  35. extern "C" void drv_cgo_checkconn(int (*_a)())
  36. {
  37. cgo_checkconn = _a;
  38. }

这样一来,问题解决了,但是复杂了一些,对 Cgo 减10分.


补充,当 windows 运行的时候 会显示 DOS 窗口,怎么办呢

  1. go build -ldflags -H=windowsgui
  2. go build -ldflags -H=windowsgui XXX.go

再次补充:

  1. // 可以把函数当指针传,这样就不需要那么多的 drv_cgo_xxx
  2. /*
  3. extern void cgo_init();
  4. extern int cgo_start();
  5. extern void cgo_callback(void *);
  6. extern void drv_cgo_callback(int, void*);
  7. extern void drv_cgo_callback_2(int, void*);
  8. static void init_callback()
  9. {
  10. int _cgo_connect = 1;
  11. int _cgo_checkconn = 2;
  12. int _cgo_disconn = 3;
  13. int _cgo_command = 4;
  14. int _cgo_shortcuts = 5;
  15. int _cgo_message = 6;
  16. extern int cgo_connect(void *, int);
  17. extern int cgo_checkconn();
  18. extern void cgo_disconn();
  19. extern void cgo_command(void *, int);
  20. extern void cgo_shortcuts(void *, int);
  21. extern void * cgo_message();
  22. drv_cgo_callback_2(_cgo_connect, &cgo_connect);
  23. drv_cgo_callback_2(_cgo_checkconn, &cgo_checkconn);
  24. drv_cgo_callback(_cgo_checkconn, &cgo_checkconn);
  25. drv_cgo_callback(_cgo_disconn, &cgo_disconn);
  26. drv_cgo_callback(_cgo_command, &cgo_command);
  27. drv_cgo_callback(_cgo_shortcuts, &cgo_shortcuts);
  28. drv_cgo_callback(_cgo_message, &cgo_message);
  29. }
  30. */

  31. typedef int (*COMMAND_CGO_CONNECT_FUNCTION)(void *, int);

  32. typedef int (*COMMAND_CGO_CHECKCONN_FUNCTION)();

  33. static COMMAND_CGO_CONNECT_FUNCTION cgo_connect = 0;

  34. static COMMAND_CGO_CHECKCONN_FUNCTION cgo_checkconn = 0;

  35. extern "C" void drv_cgo_callback_2(int _a, void * _b)
  36. {
  37. /*
  38. int _cgo_connect = 1;
  39. int _cgo_checkconn = 2;
  40. */
  41. switch (_a) {
  42. case 1:
  43. cgo_connect = (COMMAND_CGO_CONNECT_FUNCTION)_b;
  44. break;
  45. case 2:
  46. cgo_checkconn = (COMMAND_CGO_CHECKCONN_FUNCTION)_b;
  47. break;
  48. }
  49. }



    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多