一、项目结构
example/
├── main.go # Go 主程序
└── cpp/
├── foo.cpp # C++ 实现文件
├── foo.h # C++ 头文件
├── wrap.cpp # C 接口实现
└── wrap.h # C 包装头文件
二、C++模块实现
1.foo.h - C++ 类声明
#ifndef FOO_H
#define FOO_H
class Foo {
public:
Foo();
~Foo();
void sayHello();
int add(int a, int b);
};
#endif
代码解析:
-
#ifndef FOO_H和#define FOO_H是标准的头文件保护,防止重复包含 -
声明了一个简单的
Foo类,包含构造函数、析构函数和两个方法 -
sayHello()方法用于演示简单的输出 -
add()方法演示带参数和返回值的函数
2.foo.cpp - C++ 类实现
#include "foo.h"
#include <iostream>
Foo::Foo() {
std::cout << "Foo created" << std::endl;
}
Foo::~Foo() {
std::cout << "Foo destroyed" << std::endl;
}
void Foo::sayHello() {
std::cout << "Hello from C++!" << std::endl;
}
int Foo::add(int a, int b) {
return a + b;
}
代码解析:
-
实现
Foo类的所有成员函数 -
构造函数和析构函数中添加了输出语句,便于追踪对象生命周期
-
sayHello()简单地输出问候语 -
add()方法实现两个整数的加法运算
三、C 包装层实现
1.wrap.h - C 接口声明
#ifndef WRAP_H
#define WRAP_H
#ifdef __cplusplus
extern "C" {
#endif
void* createFoo();
void destroyFoo(void* foo);
void sayHello(void* foo);
int add(void* foo, int a, int b);
#ifdef __cplusplus
}
#endif
#endif
代码解析:
-
extern "C"告诉 C++ 编译器按 C 语言的规则编译这些函数 -
createFoo()和destroyFoo()用于管理 C++ 对象的生命周期 -
sayHello()和add()是对 C++ 类方法的包装 -
使用
void*作为不透明的对象句柄,隐藏 C++ 对象细节
2.wrap.cpp - C 接口实现
#include "wrap.h"
#include "foo.h"
extern "C" {
void* createFoo() {
return new Foo();
}
void destroyFoo(void* foo) {
delete static_cast<Foo*>(foo);
}
void sayHello(void* foo) {
static_cast<Foo*>(foo)->sayHello();
}
int add(void* foo, int a, int b) {
return static_cast<Foo*>(foo)->add(a, b);
}
}
代码解析:
-
createFoo()使用new创建Foo对象并返回指针 -
destroyFoo()将void*转换回Foo*并删除对象 -
sayHello()和add()方法通过类型转换调用对应的 C++ 方法 -
所有函数都在
extern "C"块中声明,确保 C 兼容性
四、Go 主程序实现
1.main.go - Go 调用代码
package main
/*
#cgo CXXFLAGS: -std=c++11
#cgo LDFLAGS: -L${SRCDIR}/cpp -lfoo -lstdc++
#include "cpp/wrap.h"
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
// 创建 Foo 实例
foo := C.createFoo()
defer C.destroyFoo(foo) // 确保释放内存
// 调用 sayHello 方法
C.sayHello(foo)
// 调用 add 方法
a, b := 3, 4
result := C.add(foo, C.int(a), C.int(b))
fmt.Printf("%d + %d = %d\n", a, b, int(result))
// 演示指针安全转换
ptr := unsafe.Pointer(foo)
fmt.Printf("Foo object address: %p\n", ptr)
}
代码解析:
-
CGO 指令:
-
#cgo CXXFLAGS: 设置 C++ 编译标志 -
#cgo LDFLAGS: 设置链接器标志,指定库路径和需要链接的库
-
-
C 导入:
-
import "C"是特殊的 CGO 导入语句,必须紧跟在注释块后
-
-
对象管理:
-
C.createFoo()创建 C++ 对象 -
defer确保对象最终被释放
-
-
方法调用:
-
C.sayHello()和C.add()调用包装的 C 函数 -
Go 的
int需要转换为C.int
-
-
指针处理:
-
使用
unsafe.Pointer处理 C 指针 -
打印指针地址用于演示
-
五、编译运行
1. 编译 C++ 代码为共享库
cd cpp
g++ -c -fPIC foo.cpp wrap.cpp -std=c++11
g++ -shared -o libfoo.so foo.o wrap.o
cd ..
命令解析:
-
-c只编译不链接 -
-fPIC生成位置无关代码,这是共享库必需的 -
-std=c++11指定 C++ 标准版本 -
-shared生成共享库 -
-o libfoo.so指定输出文件名
2. 运行 Go 程序
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)/cpp
go run main.go
-
设置
LD_LIBRARY_PATH让系统能找到我们的共享库 -
go run编译并运行 Go 程序
六、关键解析
-
名称修饰(Name Mangling)
-
C++ 编译器会对函数名进行修饰以支持重载
-
extern "C"禁用这种修饰,使函数能按名称被 C 代码调用
-
-
对象生命周期管理
-
Go 的 GC 不会管理 C++ 对象
-
必须显式创建和销毁对象
-
使用
defer确保资源释放
-
-
类型安全
-
C 和 Go 之间的类型需要显式转换
-
C.int和 Go 的int是不同的类型
-
-
指针处理
-
C 指针在 Go 中表示为
unsafe.Pointer -
需要小心处理指针以避免内存问题
-
-
编译模型
-
CGO 会先处理 C/C++ 代码
-
然后编译 Go 代码并链接所有组件
-
1025

被折叠的 条评论
为什么被折叠?



