Golang实践录:调用C++函数的优化
Posted 李迟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang实践录:调用C++函数的优化相关的知识,希望对你有一定的参考价值。
趁着五一放假,趁着有时间,把欠的一些技术集中研究研究,写写文章,好给自己一个交待。
本文继续介绍如何在 Golang 中调用 C++ 函数。
起因
前面文章介绍的方式,在运行时需要指定动态库位置,或将动态库放置系统目录,对笔者而言,还是略有麻烦,本文将使用dl
系列函数,在运行时加载动态库,这样就去掉了路径的依赖。
实现
为减少篇幅,仅摘录必要的源码。
封装
在动态库版本源码基础上,额外添加封装动态库头文件 c_callso.h:
#ifdef __cplusplus
extern "C" {
#endif
int cso_init(char* soname);
int cso_uninit();
// 结构体指针,传入传出
int CSetPointA(Point* point, Point* point1);
// 调用内部的类
//int FooCall(void);
#ifdef __cplusplus
}
#endif
对应实现文件主要代码如下:
#include <dlfcn.h>
void* g_sohandle = NULL;
int cso_init(char* soname)
{
g_sohandle = dlopen(soname, RTLD_LAZY);
if (g_sohandle == NULL) return -1;
return 0;
}
int cso_uninit()
{
if (g_sohandle != NULL) dlclose(g_sohandle);
return 0;
}
int CSetPointA(Point* point, Point* point1)
{
typedef int (*ptr)(Point*, Point*);
printf("in c file call so\\n");
ptr fptr = (ptr)dlsym(g_sohandle, "FooSetPointA");
return (*fptr)(point, point1);
}
其中,CSetPointA 函数就是对接 FooSetPointA 函数的,仅做简单的封装。
调用
Golang 测试完整代码如下:
package main
/*
#cgo LDFLAGS: -ldl
#include <stdlib.h>
#include "c_callso.h"
#include "c_callso.c"
*/
import "C"
import (
"fmt"
"unsafe"
)
var (
csoname = "./libfoo.so1"
//csoname = "./aXi3n0fr1.rd"
)
func so_test() {
fmt.Println("go c++ so test")
soname := C.CString(csoname)
ret := C.cso_init(soname)
if ret != 0 {
fmt.Println("cso_init failed ", ret)
return
}
defer C.free(unsafe.Pointer(soname))
defer C.cso_uninit()
// C形式 结构体
var myPoint, myPoint1 C.Point
myPoint.x = 100;
myPoint.y = 200;
myPoint.pinname = C.CString("Hello ") // 指针形式
defer C.free(unsafe.Pointer(myPoint.pinname))
// 固定长度数组,麻烦点
arr := [16]C.char{}
mystr := "Hell "
for i := 0; i < len(mystr) && i < 15; i++ {
arr[i] = C.char(mystr[i])
}
myPoint.inname = arr // 数组形式
fmt.Println("Golang | org struct ", myPoint, "single: ", myPoint.x, myPoint.y, myPoint.pinname)
// 结构体指针 传入传出
ret = C.CSetPointA(&myPoint, &myPoint1)
// 注:C++中使用字符串数组形式,转成string
var carr []byte
//carr = C.GoBytes(myPoint1.name, 100)
for i := range myPoint1.name {
if myPoint1.name[i] != 0 {
carr = append(carr, byte(myPoint1.name[i]))
}
}
gostr := string(carr) // 转成go的string
fmt.Println("Golang | c++ call ret: ", ret, myPoint1.x, gostr, myPoint1.name)
// 注:直接用指针形式转换,此处的指针值,与在C中申请的值,是一致的
// 注:如果指针没有分配内存,返回string为空,用unsafe.Pointer返回<nil>
gostr = C.GoString(myPoint1.pname)
defer C.free(unsafe.Pointer(myPoint1.pname))
fmt.Println("Golang | out pointer:", gostr, unsafe.Pointer(myPoint1.pname))
}
func main() {
so_test()
}
与前面文章示例不同的地方,主要是调用了 C.cso_init 初始化动态库,最终调用 cso_uninit 释放。
结果分析
运行时,只需要保持动态库的位置和名称与 Golang 中指定的一致即可,无须设置 LD_LIBRARY_PATH 环境变量。
go c++ so test
Golang | org struct {100 200 [72 101 108 108 32 0 0 0 0 0 0 0 0 0 0 0] 0xe2fc10 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] <nil>} single: 100 200 0xe2fc10
in c file call so
C++ | got buf: Hell
C++ | pname: Hello
C++ | ptr: 0xe2fc30
Golang | c++ call ret: 0 101 name in c++ [110 97 109 101 32 105 110 32 99 43 43 0 0 0 0 0]
Golang | out pointer: Hell | name in c++ malloc 0xe2fc30
总结
本文的方法,却增加了源码级别的复杂度,不一定都符合要求,因此仅作参考。
Linux 的动态库,其名称一般为 libXXX.so
,但经测试,任意名称也是可以的。
李迟 2021.5.2
以上是关于Golang实践录:调用C++函数的优化的主要内容,如果未能解决你的问题,请参考以下文章