本文在《Golang实践录:调用C++函数》基础上继续优化 Golang 中调用 C++ 函数。
起因
前面文章介绍的方式,在运行时需要指定动态库位置,或将动态库放置系统目录,对笔者而言,还是略有麻烦,本文将使用dl
系列函数,在运行时加载动态库,这样就去掉了路径的依赖。
实现
为减少篇幅,仅摘录必要的源码。
封装
在动态库版本源码基础上,额外添加封装动态库头文件 c_callso.h:
#ifdef__cplusplusextern"C"{#endifintcso_init(char*soname);intcso_uninit();//结构体指针,传入传出intCSetPointA(Point*point,Point*point1);//调用内部的类//intFooCall(void);#ifdef__cplusplus}#endif
对应实现文件主要代码如下:
#include<dlfcn.h>void*g_sohandle=NULL;intcso_init(char*soname){g_sohandle=dlopen(soname,RTLD_LAZY);if(g_sohandle==NULL)return-1;return0;}intcso_uninit(){if(g_sohandle!=NULL)dlclose(g_sohandle);return0;}intCSetPointA(Point*point,Point*point1){typedefint(*ptr)(Point*,Point*);printf("incfilecallso\n");ptrfptr=(ptr)dlsym(g_sohandle,"FooSetPointA");return(*fptr)(point,point1);}
其中,CSetPointA 函数就是对接 FooSetPointA 函数的,仅做简单的封装。
调用
Golang 测试完整代码如下:
packagemain/*#cgoLDFLAGS:-ldl#include<stdlib.h>#include"c_callso.h"#include"c_callso.c"*/import"C"import("fmt""unsafe")var(csoname="./libfoo.so1"//csoname="./aXi3n0fr1.rd")funcso_test(){fmt.Println("goc++sotest")soname:=C.CString(csoname)ret:=C.cso_init(soname)ifret!=0{fmt.Println("cso_initfailed",ret)return}deferC.free(unsafe.Pointer(soname))deferC.cso_uninit()//C形式结构体varmyPoint,myPoint1C.PointmyPoint.x=100;myPoint.y=200;myPoint.pinname=C.CString("Hello")//指针形式deferC.free(unsafe.Pointer(myPoint.pinname))//固定长度数组,麻烦点arr:=[16]C.char{}mystr:="Hell"fori:=0;i<len(mystr)&&i<15;i++{arr[i]=C.char(mystr[i])}myPoint.inname=arr//数组形式fmt.Println("Golang|orgstruct",myPoint,"single:",myPoint.x,myPoint.y,myPoint.pinname)//结构体指针传入传出ret=C.CSetPointA(&myPoint,&myPoint1)//注:C++中使用字符串数组形式,转成stringvarcarr[]byte//carr=C.GoBytes(myPoint1.name,100)fori:=rangemyPoint1.name{ifmyPoint1.name[i]!=0{carr=append(carr,byte(myPoint1.name[i]))}}gostr:=string(carr)//转成go的stringfmt.Println("Golang|c++callret:",ret,myPoint1.x,gostr,myPoint1.name)//注:直接用指针形式转换,此处的指针值,与在C中申请的值,是一致的//注:如果指针没有分配内存,返回string为空,用unsafe.Pointer返回<nil>gostr=C.GoString(myPoint1.pname)deferC.free(unsafe.Pointer(myPoint1.pname))fmt.Println("Golang|outpointer:",gostr,unsafe.Pointer(myPoint1.pname))}funcmain(){so_test()}
与前面文章示例不同的地方,主要是调用了 C.cso_init 初始化动态库,最终调用 cso_uninit 释放。
结果分析
运行时,只需要保持动态库的位置和名称与 Golang 中指定的一致即可,无须设置 LD_LIBRARY_PATH 环境变量。
goc++sotestGolang|orgstruct{100200[721011081083200000000000]0xe2fc10[0000000000000000]<nil>}single:1002000xe2fc10incfilecallsoC++|gotbuf:HellC++|pname:HelloC++|ptr:0xe2fc30Golang|c++callret:0101nameinc++[11097109101321051103299434300000]Golang|outpointer:Hell|nameinc++malloc0xe2fc30
实践总结
动态库初始化函数cso_init
等保留,动态库对外提供的业务接口,尽量少,这样减少 golang 和 C++ 之间的代码接口数量。
总结
本文的方法,却增加了源码级别的复杂度,不一定都符合要求,因此仅作参考。 Linux 的动态库,其名称一般为 libXXX.so
,但经测试,任意名称也是可以的。