mirror of
https://github.com/chai2010/advanced-go-programming-book.git
synced 2025-05-24 04:22:22 +00:00
Merge branch 'master' of github.com:chai2010/advanced-go-programming-book
This commit is contained in:
commit
0963fe8917
@ -1,14 +1,14 @@
|
||||
# 2.5. C++ 类包装
|
||||
|
||||
CGO是C语言和Go语言之间的桥梁,原则上无法直接支持C++的类。CGO不支持C++语法的根本原因是C++至今为止还没有一个二进制接口规范(ABI)。一个C++类的构造函数在编译为目标文件时如何生成链接符号名称到方法在不太平台甚至是C++到不同版本之间都是不一样的。但是C++的最大优势是兼容C语言,我们可以通过增加一组C语言函数接口作为C++类和CGO之间的桥梁,这样就可以间接地实现C++和Go之间的互联。当然,因为CGO只支持C语言中值类型的数据类型,我们是无法直接使用C++的引用参数等特性的。
|
||||
CGO是C语言和Go语言之间的桥梁,原则上无法直接支持C++的类。CGO不支持C++语法的根本原因是C++至今为止还没有一个二进制接口规范(ABI)。一个C++类的构造函数在编译为目标文件时如何生成链接符号名称、方法在不同平台甚至是C++的不同版本之间都是不一样的。但是C++是兼容C语言,所以我们可以通过增加一组C语言函数接口作为C++类和CGO之间的桥梁,这样就可以间接地实现C++和Go之间的互联。当然,因为CGO只支持C语言中值类型的数据类型,所以我们是无法直接使用C++的引用参数等特性的。
|
||||
|
||||
## C++ 类到 Go 语言对象
|
||||
|
||||
要实现C++类到Go语言对象的包装需要经过以下几个步骤:首先是用纯C函数接口包装该C++类;其次是通过CGO将纯C函数接口映射到Go函数;最后是做一个Go包装对象,将C++类到方法用Go对象的方法实现。
|
||||
实现C++类到Go语言对象的包装需要经过以下几个步骤:首先是用纯C函数接口包装该C++类;其次是通过CGO将纯C函数接口映射到Go函数;最后是做一个Go包装对象,将C++类到方法用Go对象的方法实现。
|
||||
|
||||
### 准备一个 C++ 类
|
||||
|
||||
为了演示简单,我们基于`std::string`做一个最简单的缓存对象MyBuffer。除了构造函数和析构函数之外,只有两个成员函数分别是返回底层的数据指针和缓存的大小。因为是二进制缓存,我们可以在里面中放置任意数据。
|
||||
为了演示简单,我们基于`std::string`做一个最简单的缓存类MyBuffer。除了构造函数和析构函数之外,只有两个成员函数分别是返回底层的数据指针和缓存的大小。因为是二进制缓存,所以我们可以在里面中放置任意数据。
|
||||
|
||||
```c++
|
||||
// my_buffer.h
|
||||
@ -33,7 +33,7 @@ struct MyBuffer {
|
||||
};
|
||||
```
|
||||
|
||||
我们在构造函数中指定缓存的大小并分配空间,在使用完之后通过哦析构函数释放内部分配到内存空间。下面是简单的使用方式:
|
||||
我们在构造函数中指定缓存的大小并分配空间,在使用完之后通过析构函数释放内部分配的内存空间。下面是简单的使用方式:
|
||||
|
||||
```c++
|
||||
int main() {
|
||||
@ -46,7 +46,7 @@ int main() {
|
||||
}
|
||||
```
|
||||
|
||||
为了方便向C语言接口过度,我们故意没有定义C++的拷贝构造函数。我们必须以new和delete来分配和释放缓存对象,而不能以值风格的方式来使用。
|
||||
为了方便向C语言接口过渡,在此处我们故意没有定义C++的拷贝构造函数。我们必须以new和delete来分配和释放缓存对象,而不能以值风格的方式来使用。
|
||||
|
||||
### 用纯C函数接口封装 C++ 类
|
||||
|
||||
@ -110,7 +110,7 @@ int MyBuffer_Size(MyBuffer_T* p) {
|
||||
}
|
||||
```
|
||||
|
||||
因为头文件`my_buffer_capi.h`是用于CGO,必须是采用C语言规范的名字修饰规则。在C++源源文件包含时需要用`extern "C"`语句说明。另外MyBuffer_T的实现只是从MyBuffer继承的类,这样可以简化包装代码的实现。同时,和CGO通信时必须通过`MyBuffer_T`指针,我们无法将具体的实现暴漏给CGO,因为实现中包含了C++特有的语法,CGO无法识别C++特性。
|
||||
因为头文件`my_buffer_capi.h`是用于CGO,必须是采用C语言规范的名字修饰规则。在C++源文件包含时需要用`extern "C"`语句说明。另外MyBuffer_T的实现只是从MyBuffer继承的类,这样可以简化包装代码的实现。同时和CGO通信时必须通过`MyBuffer_T`指针,我们无法将具体的实现暴露给CGO,因为实现中包含了C++特有的语法,CGO无法识别C++特性。
|
||||
|
||||
将C++类包装为纯C接口之后,下一步的工作就是将C函数转为Go函数。
|
||||
|
||||
@ -152,11 +152,11 @@ func cgo_MyBuffer_Size(p *cgo_MyBuffer_T) C.int {
|
||||
|
||||
为了区分,我们在Go中的每个类型和函数名称前面增加了`cgo_`前缀,比如cgo_MyBuffer_T是对应C中的MyBuffer_T类型。
|
||||
|
||||
为了处理简单,在包装纯C函数到Go函数时,除了cgo_MyBuffer_T类型本书,我们对输入参数和返回值的基础类型依然是用的C语言的类型。
|
||||
为了处理简单,在包装纯C函数到Go函数时,除了cgo_MyBuffer_T类型外,对输入参数和返回值的基础类型,我们依然是用的C语言的类型。
|
||||
|
||||
### 包装为Go对象
|
||||
|
||||
在将纯C接口包装为Go函数之后,我们就可以基于包装的Go函数很容易地构造出Go对象来。因为cgo_MyBuffer_T是从C语言空间导入的类型,它无法定义自己的方法,因此我们构造了一个新的MyBuffer类型,里面的成员持有cgo_MyBuffer_T指向的C语言缓存对象。
|
||||
在将纯C接口包装为Go函数之后,我们就可以很容易地基于包装的Go函数构造出Go对象来。因为cgo_MyBuffer_T是从C语言空间导入的类型,它无法定义自己的方法,因此我们构造了一个新的MyBuffer类型,里面的成员持有cgo_MyBuffer_T指向的C语言缓存对象。
|
||||
|
||||
```go
|
||||
// my_buffer.go
|
||||
@ -188,7 +188,7 @@ func (p *MyBuffer) Data() []byte {
|
||||
|
||||
同时,因为Go语言的切片本身含有长度信息,我们将cgo_MyBuffer_Data和cgo_MyBuffer_Size两个函数合并为`MyBuffer.Data`方法,它返回一个对应底层C语言缓存空间的切片。
|
||||
|
||||
现在我们可以很容易在Go语言中使用包装后的缓存对象了(底层是基于C++的`std::string`实现):
|
||||
现在我们就可以很容易在Go语言中使用包装后的缓存对象了(底层是基于C++的`std::string`实现):
|
||||
|
||||
```go
|
||||
package main
|
||||
@ -263,7 +263,7 @@ int person_get_age(person_handle_t p);
|
||||
|
||||
然后是在Go语言中实现这一组C函数。
|
||||
|
||||
需要注意的是,通过CGO导出C函数时,输入参数和返回值类型都不支持const修饰,同时也不支持可变参数的函数类型。同时如内存模式一节所述,我们无法在C/C++中直接长期访问Go内存对象。因此我们前一节所讲述的技术将Go对象映射为一个整数id。
|
||||
需要注意的是,通过CGO导出C函数时,输入参数和返回值类型都不支持const修饰,同时也不支持可变参数的函数类型。同时如内存模式一节所述,我们无法在C/C++中直接长期访问Go内存对象。因此我们使用前一节所讲述的技术将Go对象映射为一个整数id。
|
||||
|
||||
下面是`person_capi.go`文件,对应C接口函数的实现:
|
||||
|
||||
@ -398,6 +398,6 @@ struct Person {
|
||||
};
|
||||
```
|
||||
|
||||
我们在Person类中增加类一个New静态成员函数,用于创建新的Person实例。在New函数中通过调用person_new来创建Person实例,返回的是`person_handle_t`类型的id,我们将其强制转型作为`Person*`类型指针返回。在其它的成员函数中,我们通过将this指针再反向转型为`person_handle_t`类型,谈话通过C接口调用对应的函数。
|
||||
我们在Person类中增加了一个叫New静态成员函数,用于创建新的Person实例。在New函数中通过调用person_new来创建Person实例,返回的是`person_handle_t`类型的id,我们将其强制转型作为`Person*`类型指针返回。在其它的成员函数中,我们通过将this指针再反向转型为`person_handle_t`类型,然后通过C接口调用对应的函数。
|
||||
|
||||
到此,我们就实现了将Go对象导出为C接口,然后基于C接口再包装为C++对象便于使用。
|
||||
到此,我们就达到了将Go对象导出为C接口,然后基于C接口再包装为C++对象以便于使用的目的。
|
||||
|
Loading…
x
Reference in New Issue
Block a user