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++函数的优化的主要内容,如果未能解决你的问题,请参考以下文章

Golang实践录:反射reflect的一些研究及代码汇总

Golang实践录:ssh及scp实现的优化

Golang实践录:ssh及scp实现的优化

Golang实践录:ssh及scp实现的优化

Golang实践录:查询数据表的几种方式

Golang实践录:查询数据表的几种方式