diff --git a/ch2-cgo/ch2-05-class.md b/ch2-cgo/ch2-05-class.md index e3b2bb6..6b94b0f 100644 --- a/ch2-cgo/ch2-05-class.md +++ b/ch2-cgo/ch2-05-class.md @@ -11,13 +11,14 @@ CGO是C语言和Go语言之间的桥梁,原则上无法直接支持C++的类 为了演示简单,我们基于`std::string`做一个最简单的缓存对象MyBuffer。除了构造函数和析构函数之外,只有两个成员函数分别是返回底层的数据指针和缓存的大小。因为是二进制缓存,我们可以在里面中放置任意数据。 ```c++ +// my_buffer.h #include -class MyBuffer { +struct MyBuffer { std::string* s_; MyBuffer(int size) { - this->s_ = new std::string(n, char('\0')); + this->s_ = new std::string(size, char('\0')); } ~MyBuffer() { delete this->s_; @@ -26,8 +27,8 @@ class MyBuffer { int Size() const { return this->s_->size(); } - const char* Data() { - return this->s_->data(); + char* Data() { + return (char*)this->s_->data(); } }; ``` @@ -80,17 +81,22 @@ int MyBuffer_Size(MyBuffer_T* p); 然后就可以基于C++的MyBuffer类定义这些C语言包装函数。我们创建对应的`my_buffer_capi.cc`文件如下: ```c++ -// my_buffer_capi.h +// my_buffer_capi.cc + +#include "./my_buffer.h" extern "C" { - #incude "./my_buffer_capi.h" + #include "./my_buffer_capi.h" } -struct MyBuffer_T: MyBuffer {}; +struct MyBuffer_T: MyBuffer { + MyBuffer_T(int size): MyBuffer(size) {} + ~MyBuffer_T() {} +}; MyBuffer_T* NewMyBuffer(int size) { - auto p = new MyBuffer(size); - return (MyBuffer_T*)(p); + auto p = new MyBuffer_T(size); + return p; } void DeleteMyBuffer(MyBuffer_T* p) { delete p; @@ -110,8 +116,96 @@ int MyBuffer_Size(MyBuffer_T* p) { ### 将纯C接口函数转为Go函数 +将纯C函数包装为对应的Go函数的过程比较简单。需要注意的是,因为我们的包中包含C++11的语法,因此需要通过`#cgo CXXFLAGS: -std=c++11`打开C++11的选项。 + +```go +// my_buffer_capi.go + +package main + +/* +#cgo CXXFLAGS: -std=c++11 + +#include "my_buffer_capi.h" +*/ +import "C" + +type cgo_MyBuffer_T C.MyBuffer_T + +func cgo_NewMyBuffer(size int) *cgo_MyBuffer_T { + p := C.NewMyBuffer(C.int(size)) + return (*cgo_MyBuffer_T)(p) +} + +func cgo_DeleteMyBuffer(p *cgo_MyBuffer_T) { + C.DeleteMyBuffer((*C.MyBuffer_T)(p)) +} + +func cgo_MyBuffer_Data(p *cgo_MyBuffer_T) *C.char { + return C.MyBuffer_Data((*C.MyBuffer_T)(p)) +} + +func cgo_MyBuffer_Size(p *cgo_MyBuffer_T) C.int { + return C.MyBuffer_Size((*C.MyBuffer_T)(p)) +} +``` + +为了区分,我们在Go中的每个类型和函数名称前面增加了`cgo_`前缀,比如cgo_MyBuffer_T是对应C中的MyBuffer_T类型。 + +为了处理简单,在包装纯C函数到Go函数时,除了cgo_MyBuffer_T类型本书,我们对输入参数和返回值的基础类型依然是用的C语言的类型。 + ### 包装为Go对象 +在将纯C接口包装为Go函数之后,我们就可以基于包装的Go函数很容易地构造出Go对象来。因为cgo_MyBuffer_T是从C语言空间导入的类型,它无法定义自己的方法,因此我们构造了一个新的MyBuffer类型,里面的成员持有cgo_MyBuffer_T指向的C语言缓存对象。 + +```go +// my_buffer.go + +package main + +import "unsafe" + +type MyBuffer struct { + cptr *cgo_MyBuffer_T +} + +func NewMyBuffer(size int) *MyBuffer { + return &MyBuffer{ + cptr: cgo_NewMyBuffer(size), + } +} + +func (p *MyBuffer) Delete() { + cgo_DeleteMyBuffer(p.cptr) +} + +func (p *MyBuffer) Data() []byte { + data := cgo_MyBuffer_Data(p.cptr) + size := cgo_MyBuffer_Size(p.cptr) + return ((*[1 << 31]byte)(unsafe.Pointer(data)))[0:int(size):int(size)] +} +``` + +同时,因为Go语言的切片本身含有长度信息,我们将cgo_MyBuffer_Data和cgo_MyBuffer_Size两个函数合并为`MyBuffer.Data`方法,它返回一个对应底层C语言缓存空间的切片。 + +现在我们可以很容易在Go语言中使用包装后的缓存对象了(底层是基于C++的`std::string`实现): + +```go +package main + +//#include +import "C" +import "unsafe" + +func main() { + buf := NewMyBuffer(1024) + copy(buf.Data(), []byte("hello\x00")) + C.puts((*C.char)(unsafe.Pointer(&(buf.Data()[0])))) +} +``` + +例子中,我们创建了一个1024字节大小的缓存,然后通过copy函数向缓存填充了一个字符串。为了方便C语言字符串函数处理,我们在填充字符串的默认用'\0'表示字符串结束。最后我们直接获取缓存的底层数据指针,用C语言的puts函数打印缓存的内容。 + ## Go 语言对象到 C++ 类 TODO diff --git a/examples/ch2-05/class-cc2go/main.go b/examples/ch2-05/class-cc2go/main.go new file mode 100644 index 0000000..7d6d51e --- /dev/null +++ b/examples/ch2-05/class-cc2go/main.go @@ -0,0 +1,11 @@ +package main + +//#include +import "C" +import "unsafe" + +func main() { + buf := NewMyBuffer(1024) + copy(buf.Data(), []byte("hello\x00")) + C.puts((*C.char)(unsafe.Pointer(&(buf.Data()[0])))) +} diff --git a/examples/ch2-05/class-cc2go/my_buffer.cc b/examples/ch2-05/class-cc2go/my_buffer.cc new file mode 100644 index 0000000..6b6ecba --- /dev/null +++ b/examples/ch2-05/class-cc2go/my_buffer.cc @@ -0,0 +1 @@ +#include "./my_buffer.h" diff --git a/examples/ch2-05/class-cc2go/my_buffer.go b/examples/ch2-05/class-cc2go/my_buffer.go new file mode 100644 index 0000000..5e63af5 --- /dev/null +++ b/examples/ch2-05/class-cc2go/my_buffer.go @@ -0,0 +1,23 @@ +package main + +import "unsafe" + +type MyBuffer struct { + cptr *cgo_MyBuffer_T +} + +func NewMyBuffer(size int) *MyBuffer { + return &MyBuffer{ + cptr: cgo_NewMyBuffer(size), + } +} + +func (p *MyBuffer) Delete() { + cgo_DeleteMyBuffer(p.cptr) +} + +func (p *MyBuffer) Data() []byte { + data := cgo_MyBuffer_Data(p.cptr) + size := cgo_MyBuffer_Size(p.cptr) + return ((*[1 << 31]byte)(unsafe.Pointer(data)))[0:int(size):int(size)] +} diff --git a/examples/ch2-05/class-cc2go/my_buffer.h b/examples/ch2-05/class-cc2go/my_buffer.h new file mode 100644 index 0000000..e9e37e5 --- /dev/null +++ b/examples/ch2-05/class-cc2go/my_buffer.h @@ -0,0 +1,19 @@ +#include + +struct MyBuffer { + std::string* s_; + + MyBuffer(int size) { + this->s_ = new std::string(size, char('\0')); + } + ~MyBuffer() { + delete this->s_; + } + + int Size() const { + return this->s_->size(); + } + char* Data() { + return (char*)this->s_->data(); + } +}; diff --git a/examples/ch2-05/class-cc2go/my_buffer_capi.cc b/examples/ch2-05/class-cc2go/my_buffer_capi.cc new file mode 100644 index 0000000..cd927c9 --- /dev/null +++ b/examples/ch2-05/class-cc2go/my_buffer_capi.cc @@ -0,0 +1,25 @@ +#include "./my_buffer.h" + +extern "C" { + #include "./my_buffer_capi.h" +} + +struct MyBuffer_T: MyBuffer { + MyBuffer_T(int size): MyBuffer(size) {} + ~MyBuffer_T() {} +}; + +MyBuffer_T* NewMyBuffer(int size) { + auto p = new MyBuffer_T(size); + return p; +} +void DeleteMyBuffer(MyBuffer_T* p) { + delete p; +} + +char* MyBuffer_Data(MyBuffer_T* p) { + return p->Data(); +} +int MyBuffer_Size(MyBuffer_T* p) { + return p->Size(); +} diff --git a/examples/ch2-05/class-cc2go/my_buffer_capi.go b/examples/ch2-05/class-cc2go/my_buffer_capi.go new file mode 100644 index 0000000..f41de88 --- /dev/null +++ b/examples/ch2-05/class-cc2go/my_buffer_capi.go @@ -0,0 +1,27 @@ +package main + +/* +#cgo CXXFLAGS: -std=c++11 + +#include "my_buffer_capi.h" +*/ +import "C" + +type cgo_MyBuffer_T C.MyBuffer_T + +func cgo_NewMyBuffer(size int) *cgo_MyBuffer_T { + p := C.NewMyBuffer(C.int(size)) + return (*cgo_MyBuffer_T)(p) +} + +func cgo_DeleteMyBuffer(p *cgo_MyBuffer_T) { + C.DeleteMyBuffer((*C.MyBuffer_T)(p)) +} + +func cgo_MyBuffer_Data(p *cgo_MyBuffer_T) *C.char { + return C.MyBuffer_Data((*C.MyBuffer_T)(p)) +} + +func cgo_MyBuffer_Size(p *cgo_MyBuffer_T) C.int { + return C.MyBuffer_Size((*C.MyBuffer_T)(p)) +} diff --git a/examples/ch2-05/class-cc2go/my_buffer_capi.h b/examples/ch2-05/class-cc2go/my_buffer_capi.h new file mode 100644 index 0000000..213f7b2 --- /dev/null +++ b/examples/ch2-05/class-cc2go/my_buffer_capi.h @@ -0,0 +1,7 @@ +typedef struct MyBuffer_T MyBuffer_T; + +MyBuffer_T* NewMyBuffer(int size); +void DeleteMyBuffer(MyBuffer_T* p); + +char* MyBuffer_Data(MyBuffer_T* p); +int MyBuffer_Size(MyBuffer_T* p);