Golang中无法通过CGO返回[]byte的问题探讨
Golang中无法通过CGO返回[]byte的问题探讨 我正在尝试用Go创建一个DLL,通过加密技术保护免版权内容,这样我用C++开发的产品的用户就无法修改或窃取内容。我选择这样做是因为在Go中进行加密和解密(我认为)比在C++中安全得多,而且我更愿意用Go来编写这部分代码。(这里说的"安全"不是指安全性方面,而是考虑到我在Go代码中完全不使用指针,在内存管理方面比C++安全得多)。另外,我没有自己编写加密库,只是编写函数来使用Go现有的加密功能。
加密功能正常。但解密功能有问题。当我在测试用的C++应用程序中运行解密循环时,会出现panic: panic: runtime error: cgo result has Go pointer
goroutine 17 [running, locked to thread]: main._cgoexpwrap_2edcd036b113_LoadRFMusic.func1(0xc000041ea0) _cgo_gotypes.go:59 +0x75 main._cgoexpwrap_2edcd036b113_LoadRFMusic(0x4e7f75fbf0, 0xc, 0xc00165c000, 0x1593742, 0x1593742) _cgo_gotypes.go:61 +0x96
我粘贴了部分解密代码(出于明显原因没有全部展示)。LoadRFMusic()应该返回一个Go切片([]byte)。显然,Cgo不喜欢这样…
代码如下:
//export LoadRFMusic
func LoadRFMusic(file string) []byte {
// 检查确保输入文件存在。虽然冗余,但在ioutil可能遗漏的情况下很有用。
if _, err := os.Stat(file); err==nil {
message, err := ioutil.ReadFile(file)
if err != nil {
return nil
}
if len(message) <= NonceSize+15 {
return nil
}
// 确定canary标识符并确保它与正确的匹配。
// 对GCM来说不必要,但我还是做了。:)
idReader:=bytes.NewReader(message[:15])
id, err := binary.ReadUvarint(idReader)
if err!=nil {
return nil
}
if id!=/*...*/ {
return nil
}
// 设置GCM密码,确定nonce,并解密。(其他代码已省略。)
out, err := gcm.Open(nil, nonce, message[15+NonceSize:], aad)
if err != nil {
return nil
}
return out
} else {
return nil
}
}
有人知道这是怎么回事吗?
更多关于Golang中无法通过CGO返回[]byte的问题探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html
你是如何将其与你的C++应用程序进行接口对接的?是否有封装层?
你并没有返回任何cgo类型,你的函数仅处理和返回Go类型。
更多关于Golang中无法通过CGO返回[]byte的问题探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
我正在将其作为动态链接库加载:
// typedefs
// Other go types defined before this one...
typedef GoUint64 (*f_SaveRFMusic)(GoString, GoString);
typedef GoSlice(*f_LoadRFMusic) (GoString);
// in main...
// replaced all the macros for windows, linux and unix detection with WINDOWS, LINUX and UNIX for easier reading
#ifdef WINDOWS
HMODULE crfc = LoadLibrary("crfc.dll");
if (!crfc) {
cout << "DLL load error" << endl;
return 1;
}
f_SaveRFMusic SaveRFMusic = (f_SaveRFMusic)GetProcAddress(crfc, "SaveRFMusic");
f_LoadRFMusic LoadRFMusic = (f_LoadRFMusic)GetProcAddress(crfc, "LoadRFMusic");
#elif LINUX||UNIX
void *crfc = dlopen("./libcrfc.so", RTLD_NOW);
if (!crfc) {
cout << "SO load error" << endl;
return 1;
}
f_SaveRFMusic SaveRFMusic = dlsym(crfc, "SaveRFMusic");
f_LoadRFMusic LoadRFMusic = dlsym(crfc, "LoadRFMusic");
#endif
问题出在CGO不允许直接返回包含Go指针的数据结构,包括[]byte切片。当Go切片传递给C时,它包含指向底层数组的Go指针,这违反了CGO的指针传递规则。
解决方案是通过C内存分配返回数据。以下是修正后的代码:
/*
#include <stdlib.h>
*/
import "C"
import "unsafe"
//export LoadRFMusic
func LoadRFMusic(file string) *C.char {
if _, err := os.Stat(file); err == nil {
message, err := ioutil.ReadFile(file)
if err != nil {
return nil
}
if len(message) <= NonceSize+15 {
return nil
}
idReader := bytes.NewReader(message[:15])
id, err := binary.ReadUvarint(idReader)
if err != nil {
return nil
}
if id != /*...*/ {
return nil
}
out, err := gcm.Open(nil, nonce, message[15+NonceSize:], aad)
if err != nil {
return nil
}
// 使用C分配内存并复制数据
cData := C.malloc(C.size_t(len(out)))
if cData == nil {
return nil
}
C.memcpy(cData, unsafe.Pointer(&out[0]), C.size_t(len(out)))
// 返回C字符指针,调用方需要负责释放内存
return (*C.char)(cData)
}
return nil
}
在C++调用方中,需要这样使用:
extern "C" {
char* LoadRFMusic(const char* file);
}
// 使用示例
char* data = LoadRFMusic("encrypted_file.bin");
if (data) {
// 处理数据...
free(data); // 必须释放内存
}
如果需要同时返回数据长度,可以创建另一个导出函数:
//export LoadRFMusicSize
func LoadRFMusicSize(file string) C.int {
// 实现类似的逻辑,但只返回数据长度
// 或者修改LoadRFMusic返回结构体包含指针和长度
}
另一种方法是使用C.CBytes():
//export LoadRFMusic
func LoadRFMusic(file string) *C.char {
// ... 前面的解密逻辑相同
if out != nil {
// C.CBytes会分配C内存并复制数据
return (*C.char)(C.CBytes(out))
}
return nil
}
记住在C++侧调用free()来释放这些函数返回的内存。

