cgo调用,高效快速稳定,无内存碰撞

一、背景

因为密码学有很多较快的算法是基于c或c++纂修,而工程上主要以go语言为主,所以在此梳理一些go调用c常见问题和用例。

有很多奇特的方式进行传输,但是想要性能最优还是以指针传输作为主要传输方式。

一些简单的计算可以直接使用c编写成.h进行引用,但在工程部署常常拥有大量依赖库,若在服务器上部署时间太慢,还可能存在网络问题。所以最佳方式是将所有依赖库编译成动态库.so和.dylib供部署方使用。

二、cgo调用

1.cgo依赖库调用需要建设环境:

CGO_LDFLAGS

-L/Users/admin/Desktop/pir/pir_arm64/pir_cpp/cmake_arm -lpir

-L{path} -l{动态库}:比如libpir.so

DYLD_LIBRARY_PATH

/Users/admin/Desktop/pir/pir_arm64/pir_cpp/cmake_arm

动态库位置

CGO_CFLAGS

-I/Users/admin/Desktop/pir/pir_arm64/pir_cpp/thirdparty/pir 

.h头文件位置

可以在环境中export全局设置,也可以启用之前单次引入:

CGO_CFLAGS="-I${PIR_HOME}/pir_cpp/thirdparty/pir" 
DYLD_LIBRARY_PATH="${PIR_HOME}/pir_cpp/build" 
CGO_LDFLAGS="-L${PIR_HOME}/pir_cpp/build -lpir" go run main.go

注意:同时饮用两个依赖库:

CGO_ENABLED=1  CGO_CFLAGS="-I${PIR_HOME}/pir_cpp/thirdparty/pir -I${GMSSL_HOME}/include" 
CGO_LDFLAGS="-L${PIR_HOME}/pir_cpp/build  -lpir2 
-L${GMSSL_HOME}/lib -lgmcrypto -lgmssl"  
go build -tags=jsoniter  -ldflags "$(API_LINK_OPTIONS)" -o ${BIN_PATH}/${API_APP_NAME} ${API_CMD_PATH}

在Goland中配置全景变量CGO_LDFLAGS时不要加双引号。

2.go如何调用c函数并获得计算结果(方法很多,介绍最为稳定,速度最快的方案)

(1)数据结构简单:

func OprfBlind(id []byte, key []byte) ([]byte, error) {

if len(key_receiver) != 32 {

return nil, ErrInvalidKey

}

if id_receiver == nil {

return nil, ErrEmptyParameter

}

size := C.size_t(32)

array := C.malloc(size)

defer C.free(array)

C.oprf_mul_value((*C.char)(unsafe.Pointer(&id[0])), (*C.char)(unsafe.Pointer(&key[0])), (*C.char)(array), (C.int(len(id))))  //可以返回整数作为密文的长度

ciphertext := C.GoBytes(unsafe.Pointer(array), C.int(32)) //知晓返回的数据长度为32

return ciphertext, nil

}

(2)结构较为复杂,借助结构体传输。

func Oprf2Key(ciphertexts_r []byte, key_sender []byte) ([]byte, error) {

if len(ciphertexts_r) == 0 {

return nil, ErrInvalidPoint

}

if len(key_sender) != 32 {

return nil, ErrInvalidKey

}

key_sender = string2Key(key_sender, 32)

cgodata := (*C.cgoData)(C.malloc(C.size_t(unsafe.Sizeof(C.cgoData{}))))

defer C.free(unsafe.Pointer(cgodata))

C.oprf_mul_A((*C.char)(unsafe.Pointer(&ciphertexts_r[0])), (*C.char)(unsafe.Pointer(&key_sender[0])), cgodata, (C.int)(len(ciphertexts_r)))

ciphertext := C.GoBytes(unsafe.Pointer(cgodata.data), C.int(32))

return ciphertext, nil

}

typedef struct {

   char *data;

   int data_len;

}cgoData;

#cgodata.data在go中没有给它分配内存,所以我们需要在c++中分配:

int oprf_div_A(const char *ids, const char *A, cgoData *plaintext, int size)   {

    myECC ecc;

    plaintext->data =  new char [32];

   int flag = ecc.point_div(A, ids, plaintext->data);

   return flag;

}
func SealInit(NumberItems int, MaxSizeItem int) ([]unsafe.Pointer, error) {

if NumberItems <= 0 || MaxSizeItem <= 0 {

return nil, errors.New("sealGenerateQuery Error: NumberItems,SizePerItem should be >0")

}

cSealData := (*C.SealData)(C.malloc(C.size_t(unsafe.Sizeof(C.SealData{}))))

sealParams := make([]unsafe.Pointer, 2)

defer C.free(unsafe.Pointer(cSealData))

cSealData.poly_degree = C.int(4096)

cSealData.logt = C.int(20)

cSealData.number_of_items = C.int(NumberItems)

cSealData.size_per_item = C.int(MaxSizeItem % 10000)

sealParams[0] = C.seal_init(cSealData)

if MaxSizeItem > 10000 {

cSealData.size_per_item = C.int(10000)

sealParams[1] = C.seal_init(cSealData)

}

return sealParams, nil

}

(3)指针传递多个参数:

三、bug修复:

1.内存如何分配:

___go_build_data_create_go(90164,0x16f6f3000) malloc: Heap corruption detected, free list is damaged at 0x6000002fc000

*** Incorrect guard value: 15577313418704380084

___go_build_data_create_go(90164,0x16f6f3000) malloc: *** set a breakpoint in malloc_error_break to debug

func EncryptElement(values []byte, key []byte) ([]byte, error) {

if len(values) == 0 || len(values) == 0 {

return nil, ErrEmptyParameter

}

if values == nil || key == nil {

return nil, ErrEmptyParameter

}

size := C.size_t(16 * int((len(values)+31)/16)) //在内存一定多分配一些!!!

array := C.malloc(size)                 //分配内存和释放\也可以在内部生成一定要大一些。

defer C.free(array)

C.encrypt_element((*C.char)(unsafe.Pointer(&values[0])), (*C.char)(unsafe.Pointer(&key[0])), (*C.char)(array), (C.int)(len(values)))

ciphertext := C.GoBytes(unsafe.Pointer(array), C.int(size-16))

return ciphertext, nil

}

//无论外部还是内部一定记住内存分配。

2.for循环容易丢失

func SealDecrypt(sealParams []unsafe.Pointer, reply [][]byte, position int, keys SealKeys) ([]byte, error) {

// 分配 MyData 结构体的内存



if len(keys.SecretKey) <= 0 || len(keys.PublicKey) <= 0 || len(reply) <= 0 {

return nil, errors.New("sealDecrypt Error: SecretKey , PublicKey and Ciphertext is NULL")

}

var plaintexts []byte

for i := 0; i < len(reply); i++ {

cSealData := (*C.SealData)(C.malloc(C.size_t(unsafe.Sizeof(C.SealData{}))))

cSealData.ele_index = C.int(position) //放在外面容易丢失,建议每次赋值一次

cSealData.secret_key = (*C.char)(unsafe.Pointer(&keys.SecretKey[0]))

cSealData.public_key = (*C.char)(unsafe.Pointer(&keys.PublicKey[0]))

if i == len(reply)-1 {

cSealData.seal_params = sealParams[0]

} else {

cSealData.seal_params = sealParams[1]

}



cSealData.reply = (*C.char)(unsafe.Pointer(&reply[i][0]))



// 调用 C 函数,并传递 MyData 结构体的指针

C.seal_decrypt(cSealData)

// 将 C 返回的数据转换为 Go 的切片类型

Plaintext := C.GoBytes(unsafe.Pointer(cSealData.plaintext), C.int(cSealData.plaintext_len))



plaintexts = append(plaintexts, Plaintext...)

C.free(unsafe.Pointer(cSealData)) //即用即清!当数据量一大会内存报错!

}

return plaintexts, nil

}

四、用例

cgo调用,高效快速稳定,无内存碰撞_第1张图片

simple.h

//
// Created by ybx on 2023/7/21.
//

#ifndef CGO_EXEMPLE_SIMPLE_H
#define CGO_EXEMPLE_SIMPLE_H
#ifdef __cplusplus
extern "C" {
#endif

typedef struct {
    char *data;
    int data_len;
}cgo_simple_struct;

int swap_value(char *value1, char *value2);

int swap_value_struct(cgo_simple_struct *struct1, cgo_simple_struct *struct2);

int get_value(char *data, cgo_simple_struct *cgo);

#ifdef __cplusplus
}
#endif

#endif //CGO_EXEMPLE_SIMPLE_H

simple.cpp

//
// Created by admin on 2023/7/21.
//
#include 
#include "simple.h"
int swap_value(char *value1, char *value2){
    // 交换两个字符数组的内容
    char temp[strlen(value1) + 1];
    strcpy(temp, value1);
    strcpy(value1, value2);
    strcpy(value2, temp);
    return 0; // 返回一个标志,表示交换成功
}

int swap_value_struct(cgo_simple_struct *struct1, cgo_simple_struct *struct2) {
    // 交换两个结构体中的指针和长度
    char *temp_data = struct1->data;
    int temp_data_len = struct1->data_len;

    struct1->data = struct2->data;
    struct1->data_len = struct2->data_len;

    struct2->data = temp_data;
    struct2->data_len = temp_data_len;

    return 0; // 返回一个标志,表示交换成功
}


int get_value(char *data, cgo_simple_struct *cgo) {
    // 为 cgo->data 分配内存,并复制 ids 的内容到其中
    cgo->data = new char[len + 1];
    strncpy(cgo->data, data, cgo->data_len);

    // 保存 ids 的长度
    cgo->data_len = len;

    return 0; // 返回一个标志,表示复制成功
}

cmakelist:

cmake_minimum_required(VERSION 3.25)
project(c++)

set(CMAKE_CXX_STANDARD 17)

add_executable(c++ main.cpp src/simple.cpp src/simple.h)
add_library(cgo SHARED src/simple.cpp src/simple.h)

cgo.go

package main

// #cgo LDFLAGS: -L./c++/build -lcgo
// #cgo CFLAGS: -I./c++/src
/*
#include "simple.h"
#include 
#include 
*/
import "C"

import (
	"fmt"
	"unsafe"
)

func main() {
	// 调用 swap_value 函数
	str1 := []byte("Hello")
	str2 := []byte("World")
	fmt.Printf("Before swap: struct1 = %s, struct2 = %s\n", string(str1), string(str2))
	C.swap_value((*C.char)(unsafe.Pointer(&str1[0])), (*C.char)(unsafe.Pointer(&str2[0])))
	fmt.Printf("After swap: struct1 = %s, struct2 = %s\n", string(str1), string(str2))

	// 调用 get_value 函数
	cgodata1 := (*C.cgo_simple_struct)(C.malloc(C.size_t(unsafe.Sizeof(C.cgo_simple_struct{}))))
	defer C.free(unsafe.Pointer(cgodata1))
	cgodata1.data_len = C.int(len(str1))

	C.get_value((*C.char)(unsafe.Pointer(&str1[0])), cgodata1)
	getData := C.GoBytes(unsafe.Pointer(cgodata1.data), C.int(cgodata1.data_len))
	fmt.Printf("After get: struct->data = %s, struct->data_len = %d\n", string(getData), cgodata1.data_len)

	cgodata2 := (*C.cgo_simple_struct)(C.malloc(C.size_t(unsafe.Sizeof(C.cgo_simple_struct{}))))
	defer C.free(unsafe.Pointer(cgodata2))
	cgodata2.data = (*C.char)(unsafe.Pointer(&str2[0]))
	cgodata2.data_len = C.int(len(str2))

	fmt.Printf("After swap: struct1->data = %s, struct2->data = %s\n", string(getData), string(str2))

	C.swap_value_struct(cgodata1, cgodata2)
	getData1 := C.GoBytes(unsafe.Pointer(cgodata1.data), C.int(cgodata1.data_len))
	getData2 := C.GoBytes(unsafe.Pointer(cgodata1.data), C.int(cgodata1.data_len))

	// 打印交换后的结果
	fmt.Printf("After swap: struct1->data = %s, struct2->data = %s\n", string(getData1), string(getData2))

}

结果:

cgo调用,高效快速稳定,无内存碰撞_第2张图片

 

你可能感兴趣的:(go)