Golang中使用CGO与C++ vector交互的方法

Golang中使用CGO与C++ vector交互的方法 你好。 我猜这是一个C++问题,但我不知道在哪里发布我的问题,所以就在这里问了。

我尝试在cgo绑定中使用vector,但输出的第一个索引总是显示奇怪的数据,如下所示。 structs 是cgo二进制文件,company.exe 是cpp二进制文件。

$ ./structs
# 在声明vector的函数内部
  0: wlrbbmqb, $6421.00
  1: darzowk, $9172.00
  2: hiddqs, $7862.00
  3: xrjmowfr, $6393.00
  4: jybldb, $8784.00
# 在声明vector的函数外部
#### 员工列表:
  0: , $6421.00
  1: darzowk, $9172.00
  2: hiddqs, $7862.00
  3: xrjmowfr, $6393.00
  4: jybldb, $8784.00

$ ./company.exe
# 在声明vector的函数内部
  0: wlrbbmqb, $6421.00
  1: darzowk, $9172.00
  2: hiddqs, $7862.00
  3: xrjmowfr, $6393.00
  4: jybldb, $8784.00
# 在声明vector的函数外部
#### 员工列表:
  0: , $6421.00
  1: darzowk, $9172.00
  2: hiddqs, $7862.00
  3: xrjmowfr, $6393.00
  4: jybldb, $8784.00

我在Windows 11上尝试了msvc 2022 / mingw gcc 12.2.0,在ubuntu 20.04上尝试了gcc 9.4.0。 我是否遗漏了什么或者做错了什么?

任何提示都欢迎。

源代码在这里

抱歉我的英语不好。


更多关于Golang中使用CGO与C++ vector交互的方法的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

感谢 @skillian,你的代码对我非常有帮助!

更多关于Golang中使用CGO与C++ vector交互的方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


感谢您的回答。

也许是的,我也按照您提到的尝试了 emp_list_malloc,它运行良好。

尽管函数 generate_employee_list_vector 不工作,我猜测下面的重新声明会保留向量销毁后的值。

void generate_employee_list_vector(void* cmp, int count) {
....
    c->emp_list = new employee_list{emp_list.data(), emp_list.size(), false};
}

我的疑问是,这是由于我的代码不成熟,还是C++编译器的问题。

你好,@edp1096,欢迎来到论坛!

我不是 C++ 程序员,所以我的理解可能不对,但我认为这可能vector 本身在 generate_employee_list_vector 函数结束时超出作用域有关。此时,vector(及其底层数组)会被销毁,因此当你稍后使用 emp_list 时,我认为你遇到的是未定义行为。我看到你还有另一个使用 malloc 的版本;你在那里也遇到了同样的情况吗?

不,数组的所有权并没有从向量转移到你的 employee_listdata 成员函数返回一个“非拥有”指针,该指针在向量存在期间可以使用。据我所知,无法将向量的后备数组移出向量。

我建议将向量放入 company 中,这样只要 company 存在,其生命周期就能得以保留。如果你想使用向量,我提交了一个拉取请求,其中包含了我建议的更改。我不能 100% 确定我做对了,但对我来说它是有效的。

这是一个典型的C++对象生命周期管理问题。当std::vector在C++函数中创建并返回指针时,函数结束后vector会被销毁,导致悬空指针。

以下是正确的交互方法:

1. C++头文件 (company.h)

#ifndef COMPANY_H
#define COMPANY_H

#include <vector>
#include <string>

#ifdef __cplusplus
extern "C" {
#endif

struct Employee {
    char name[50];
    double salary;
};

// 创建并返回vector指针
std::vector<Employee>* create_employee_vector();

// 获取vector大小
size_t get_vector_size(std::vector<Employee>* vec);

// 获取vector数据指针
Employee* get_vector_data(std::vector<Employee>* vec);

// 释放vector
void free_employee_vector(std::vector<Employee>* vec);

#ifdef __cplusplus
}
#endif

#endif

2. C++实现 (company.cpp)

#include "company.h"
#include <iostream>
#include <cstring>

extern "C" {

std::vector<Employee>* create_employee_vector() {
    auto* vec = new std::vector<Employee>();
    
    Employee emp1;
    strncpy(emp1.name, "wlrbbmqb", sizeof(emp1.name));
    emp1.salary = 6421.00;
    vec->push_back(emp1);
    
    Employee emp2;
    strncpy(emp2.name, "darzowk", sizeof(emp2.name));
    emp2.salary = 9172.00;
    vec->push_back(emp2);
    
    std::cout << "# 在声明vector的函数内部" << std::endl;
    for (size_t i = 0; i < vec->size(); ++i) {
        std::cout << "  " << i << ": " << (*vec)[i].name 
                  << ", $" << (*vec)[i].salary << std::endl;
    }
    
    return vec;
}

size_t get_vector_size(std::vector<Employee>* vec) {
    return vec ? vec->size() : 0;
}

Employee* get_vector_data(std::vector<Employee>* vec) {
    return vec ? vec->data() : nullptr;
}

void free_employee_vector(std::vector<Employee>* vec) {
    delete vec;
}

}

3. Go包装器 (main.go)

package main

/*
#cgo CXXFLAGS: -std=c++11
#cgo LDFLAGS: -lstdc++

#include "company.h"
*/
import "C"
import (
    "fmt"
    "unsafe"
)

type Employee struct {
    Name    string
    Salary  float64
}

func getEmployees() []Employee {
    // 创建vector
    vec := C.create_employee_vector()
    defer C.free_employee_vector(vec)
    
    // 获取vector信息
    size := C.get_vector_size(vec)
    data := C.get_vector_data(vec)
    
    // 转换为Go切片
    employees := make([]Employee, size)
    
    // 使用unsafe.Pointer访问C数组
    cArray := (*[1 << 30]C.struct_Employee)(unsafe.Pointer(data))[:size:size]
    
    for i := 0; i < int(size); i++ {
        emp := cArray[i]
        employees[i] = Employee{
            Name:   C.GoString(&emp.name[0]),
            Salary: float64(emp.salary),
        }
    }
    
    return employees
}

func main() {
    employees := getEmployees()
    
    fmt.Println("#### 员工列表:")
    for i, emp := range employees {
        fmt.Printf("  %d: %s, $%.2f\n", i, emp.Name, emp.Salary)
    }
}

4. 编译命令

# 编译C++代码
g++ -c -fPIC -std=c++11 company.cpp -o company.o

# 编译Go代码
go build -o structs

# 或者使用go build自动编译
CGO_ENABLED=1 CGO_CXXFLAGS="-std=c++11" go build -o structs

关键点:

  1. 使用new在堆上分配vector,确保生命周期延续到Go端
  2. 通过defer确保C++对象被正确释放
  3. 使用vector::data()获取底层数组指针
  4. 使用unsafe.Pointer进行类型转换
  5. 所有内存管理都在C++端完成,Go只负责调用和释放

这样就能正确地在Go和C++ std::vector之间传递数据,避免悬空指针问题。

回到顶部