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

3 回复

你是如何将其与你的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()来释放这些函数返回的内存。

回到顶部