首页>>后端>>Golang->Golang实践录:调用C++函数

Golang实践录:调用C++函数

时间:2023-12-01 本站 点击:0

本文介绍如何在 Golang 中调用 C++ 函数。

起因

因工作需求,需要将一个工具由终端行的运行方式迁移到 web 上,核心代码由 c++ 动态库实现,另一部门的同事使用 Java 实现了一个版本,部门同事安排我做部署,由于服务器是离线的,且由专人管理,JDK 和 Tomcat 安装稍麻烦,个人操作自由度不够,——一是没有研究过 Java,二来部署麻烦。因此,决定使用 Golang 实现。预计展开的内容有:Golang 调用 C++ 动态库;Golang Web 服务及整合 html/css资源;(大)前端框架使用。

本文主要研究 C++ 动态库及函数的调用。

思路

Golang 只支持 C 语言的编译,对于 C++ 的编译,有2种方法: 1、不使用类,在 C++ 代码头文件添加extern "C" {,将函数声明为 C 格式。 2、如出现类的情况,再用另外的文件将其封装成 C 格式函数。

实现

C/C++代码

没有类的文件,但后缀名为cpp:

//bar.h文件:#ifndefBAR_H#defineBAR_H#ifdef__cplusplusextern"C"{#endifintbar();#ifdef__cplusplus}#endif#endif//bar.cpp文件:#include<stdio.h>#include"bar.h"intbar(){printf("C|hellbar\n");#ifdefMACRO_TESTprintf("C|macro...\n");#endifreturn0;}

有类的文件:

//foo.hforclass#ifndefFOO_H#defineFOO_HclassCFoo{public:CFoo(intvalue):m_value(value){};~CFoo(){};voidBar();private:intm_value;};#endif//foo.cpp#include<stdio.h>#include<iostream>#include"foo.h"voidCFoo::Bar(void){printf("C++Class|%s():num:%d\n",__func__,m_value);//std::cout<<this->a<<std::endl;}

封装代码:

//foo.h#ifndefOUT_H#defineOUT_H#ifdef__cplusplusextern"C"{#endiftypedefstructPoint{intx;inty;charinname[16];//传入buffchar*pinname;//传入指针charname[16];//传出buffchar*pname;//传出指针}Point;//普通类型赋值intFooSetValue(inta,unsignedintb,floatc,char*str);voidPrintString(char*str);//结构体intFooSetPointC(Pointpoint);//结构体指针intFooSetPoint(Point*point);//结构体指针,传入传出intFooSetPointA(Point*point,Point*point1);//调用内部的类intFooCall(intnum);#ifdef__cplusplus}#endif#endif//out.cpp#include<stdio.h>#include<stdlib.h>#include<string.h>#include"out.h"#include"foo.h"intFooSetValue(inta,unsignedintb,floatc,char*str){printf("C++|basetype:%d%d%.4f%s\n",a,b,c,str);return0;}voidPrintString(char*str){printf("C++|string=%s\n",str);}intFooSetPointC(Pointpoint){printf("C++|thepointincforvalue:%d%d\n",point.x,point.y);return0;}intFooSetPoint(Point*point){printf("C++|thepointinc:%d%d\n",point->x,point->y);point->x=250;point->y=500;strcpy(point->name,"nameinc++");return24;}intFooSetPointA(Point*point,Point*point1){printf("C++|gotbuf:%s\n",point->inname);if(point->pinname!=NULL)printf("C++|pname:%s\n",point->pinname);point1->x=point->x+1;point1->y=point->y+1;strcpy(point1->name,"nameinc++");point1->pname=newchar[16];sprintf(point1->pname,"%s|nameinc++malloc",point->inname);//strcpy(point1->pname,"nameinc++malloc");printf("C++|ptr:%p\n",point1->pname);return0;}intFooCall(intnum){CFoo*ret=newCFoo(num);ret->Bar();return0;}

使用 Makefile 将上面文件编译为 libfoo.so 动态库。

动态库调用

完整测试代码如下:

packagemain/*#cgoCFLAGS:-I.#cgoLDFLAGS:-L.-lfoo#include<stdlib.h>#include"out.h"*/import"C"import("fmt""unsafe")funcso_test(){fmt.Println("goc++sotest")//简单函数调用cstr:=C.CString("callCfunc")deferC.free(unsafe.Pointer(cstr))variC.inti=100C.FooSetValue(i,C.uint(250),C.float(3.14159),cstr)C.PrintString(cstr);//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)//结构体传值C.FooSetPointC(myPoint)//结构体指针传入传出ret:=C.FooSetPointA(&myPoint,&myPoint1)//注:C++中使用字符串数组形式,转成stringvarcarr[]byte//carr=C.GoBytes(myPoint1.name,16)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))C.FooCall(250)C.FooCall(C.int(250))}funcmain(){so_test()}

源码要点如下: 1、需设置编译参数 LDFLAGS,指定库位置和名称,本例中是当前目录的 libfoo.so。 2、需包含相应的头文件,stdlib.h 为 free 函数所在文件。 3、内嵌的 C 源码在包的前面,且import "C"后需空一行。 4、传递到 C 函数的内存,使用C.CString申请,C 申请的内存使用C.GoString获取,均需要手动释放。

结果分析

在运行前,需要设置动态库路径:

exportLD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD

否则运行时无法找到动态库:

./test:errorwhileloadingsharedlibraries:libfoo.so:cannotopensharedobjectfile:Nosuchfileordirectory

运行结果如下:

goc++sotestC++|basetype:1002503.1416callCfuncC++|string=callCfuncGolang|orgstruct{100200[721011081083200000000000]0x25b2a30[0000000000000000]<nil>}single:1002000x25b2a30C++|thepointincforvalue:100200C++|gotbuf:HellC++|pname:HelloC++|ptr:0x25b2a50Golang|c++callret:0101nameinc++[11097109101321051103299434300000]Golang|outpointer:Hell|nameinc++malloc0x25b2a50C++Class|Bar():num:250C++Class|Bar():num:250

从上述结果中可看出,C 中申请的内存,其指针与在 Go 中获取的指针是一样的,即 0x25b2a50。结构体中的 nil 是因为字段 pname 未赋值。

源码编译

前面的动态库代码,不能全部内嵌到 Go 代码中,因此选取其中的 bar.h/cpp,测试代码如下:

packagemain/*#cgoCFLAGS:-I.-DMACRO_TEST#include<stdlib.h>#include"bar.h"#include"bar.cpp"*/import"C"import("fmt")funccpp_test(){fmt.Println("goc++sotest")C.bar();}funcmain(){cpp_test()}

源码要点: 1、可用 CFLAGS 指定头文件,添加宏定义等。 2、将所有的 C 源码包含到代码中。(存疑:似乎应该是头文件,在编译过程中自动找对应的实现文件,这里包含进来,相当于所有源码都在 Go 代码中)

结果分析

运行结果如下:

goc++sotestC|hellbarC|macro...

使用此方法,如果修改 C 代码,还需更新包含 C 代码的 go 文件,否则不会被编译。

总结

上面对2种形式的调用进行了实践,在功能和使用上各有千秋,对于简单的 C 语言代码(包含C++形式的简单函数),直接使用内嵌的形式会更高效。 本文使用的动态库例子,在运行前还需要设置运行路径,当然可以将动态库放到系统目录的,但笔者认为不是正道,下面将去掉动态库路径的依赖。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/Golang/5854.html