CGO:Go 调用 C++ 代码

一、项目结构

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

代码解析

  1. #ifndef FOO_H 和 #define FOO_H 是标准的头文件保护,防止重复包含

  2. 声明了一个简单的 Foo 类,包含构造函数、析构函数和两个方法

  3. sayHello() 方法用于演示简单的输出

  4. 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;
}

代码解析

  1. 实现 Foo 类的所有成员函数

  2. 构造函数和析构函数中添加了输出语句,便于追踪对象生命周期

  3. sayHello() 简单地输出问候语

  4. 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

代码解析

  1. extern "C" 告诉 C++ 编译器按 C 语言的规则编译这些函数

  2. createFoo() 和 destroyFoo() 用于管理 C++ 对象的生命周期

  3. sayHello() 和 add() 是对 C++ 类方法的包装

  4. 使用 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);
    }
}

代码解析

  1. createFoo() 使用 new 创建 Foo 对象并返回指针

  2. destroyFoo() 将 void* 转换回 Foo* 并删除对象

  3. sayHello() 和 add() 方法通过类型转换调用对应的 C++ 方法

  4. 所有函数都在 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)
}

代码解析

  1. CGO 指令

    • #cgo CXXFLAGS: 设置 C++ 编译标志

    • #cgo LDFLAGS: 设置链接器标志,指定库路径和需要链接的库

  2. C 导入

    • import "C" 是特殊的 CGO 导入语句,必须紧跟在注释块后

  3. 对象管理

    • C.createFoo() 创建 C++ 对象

    • defer 确保对象最终被释放

  4. 方法调用

    • C.sayHello() 和 C.add() 调用包装的 C 函数

    • Go 的 int 需要转换为 C.int

  5. 指针处理

    • 使用 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 ..

命令解析

  1. -c 只编译不链接

  2. -fPIC 生成位置无关代码,这是共享库必需的

  3. -std=c++11 指定 C++ 标准版本

  4. -shared 生成共享库

  5. -o libfoo.so 指定输出文件名

2. 运行 Go 程序

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)/cpp
go run main.go
  1. 设置 LD_LIBRARY_PATH 让系统能找到我们的共享库

  2. go run 编译并运行 Go 程序

六、关键解析

  1. 名称修饰(Name Mangling)

    • C++ 编译器会对函数名进行修饰以支持重载

    • extern "C" 禁用这种修饰,使函数能按名称被 C 代码调用

  2. 对象生命周期管理

    • Go 的 GC 不会管理 C++ 对象

    • 必须显式创建和销毁对象

    • 使用 defer 确保资源释放

  3. 类型安全

    • C 和 Go 之间的类型需要显式转换

    • C.int 和 Go 的 int 是不同的类型

  4. 指针处理

    • C 指针在 Go 中表示为 unsafe.Pointer

    • 需要小心处理指针以避免内存问题

  5. 编译模型

    • CGO 会先处理 C/C++ 代码

    • 然后编译 Go 代码并链接所有组件

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值