我拥有一个Go库,gofileseq,我想尝试并创建一个C / C ++绑定。
能够导出使用简单类型(整数,字符串......)的函数非常简单。通过定义一个C结构并将Go类型转换为它来将数据从自定义Go类型导出到C,以便在导出的函数中使用,这是非常容易的,因为您正在分配C内存来执行它。但是使用go 1.5 cgo rules我发现很难弄清楚如何从存储状态的更复杂的结构中导出功能。
gofileseq中我想以某种方式导出到C ++绑定的结构示例:
// package fileseq
//
type FrameSet struct {
frange string
rangePtr *ranges.InclusiveRanges
}
func NewFrameSet(frange string) (*FrameSet, error) {
// bunch of processing to set up internal state
}
func (s *FrameSet) Len() int {
return s.rangePtr.Len()
}
// package ranges
//
type InclusiveRanges struct {
blocks []*InclusiveRange
}
type InclusiveRange struct {
start int
end int
step int
cachedEnd int
isEndCached bool
cachedLen int
isLenCached bool
}
如您所见,我想要公开的FrameSet
类型包含一个指向基础类型的指针,每个指针都存储状态。
理想情况下,我希望能够在C ++类上存储void*
,并使其成为使用void*
回调导出的Go函数的简单代理。但是cgo规则禁止C存储Go指针比函数调用更长。而且我没有看到如何使用一种方法来定义可以分配并用于与Go库一起操作的C ++类。
是否可以将复杂类型包装为C / C ++?
是否有一种模式允许C ++客户端创建Go FrameSet
?
修改的
我能想到的一个想法是让C ++在Go中创建对象,这些对象以静态map[int]*FrameSet
存储在Go端,然后将int id返回给C ++。然后,所有C ++操作都使用id向Go发出请求。这听起来像是有效的解决方案吗?
更新
目前,我正在测试使用全局地图和唯一ID来存储对象的解决方案。 C ++会请求创建一个新对象,并且只返回一个不透明的id。然后,他们可以使用该id调用导出为函数的所有方法,包括请求在完成时销毁它。
如果有比这更好的方法,我很乐意看到答案。一旦我得到一个完整的原型,我将添加我自己的答案。
更新#2
我撰写了一篇关于我最终使用的最终解决方案的博文:http://justinfx.com/2016/05/14/cpp-bindings-for-go/
答案 0 :(得分:0)
由于缺乏更好的解决方案,我最终解决这个问题的方法是在Go端使用私有全局地图(ref)。这些映射会将Go对象的实例与随机的uint64 id相关联,并且id将作为" opaque句柄返回到C ++"。
type frameSetMap struct {
lock *sync.RWMutex
m map[FrameSetId]*frameSetRef
rand idMaker
}
//...
func (m *frameSetMap) Add(fset fileseq.FrameSet) FrameSetId {
// fmt.Printf("frameset Add %v as %v\n", fset.String(), id)
m.lock.Lock()
id := FrameSetId(m.rand.Uint64())
m.m[id] = &frameSetRef{fset, 1}
m.lock.Unlock()
return id
}
然后我使用引用计数来确定C ++何时不再需要该对象,并将其从地图中删除:
// Go
func (m *frameSetMap) Incref(id FrameSetId) {
m.lock.RLock()
ref, ok := m.m[id]
m.lock.RUnlock()
if !ok {
return
}
atomic.AddUint32(&ref.refs, 1)
// fmt.Printf("Incref %v to %d\n", ref, refs)
}
func (m *frameSetMap) Decref(id FrameSetId) {
m.lock.RLock()
ref, ok := m.m[id]
m.lock.RUnlock()
if !ok {
return
}
refs := atomic.AddUint32(&ref.refs, ^uint32(0))
// fmt.Printf("Decref %v to %d\n", ref, refs)
if refs != 0 {
return
}
m.lock.Lock()
if atomic.LoadUint32(&ref.refs) == 0 {
// fmt.Printf("Deleting %v\n", ref)
delete(m.m, id)
}
m.lock.Unlock()
}
//C++
FileSequence::~FileSequence() {
if (m_valid) {
// std::cout << "FileSequence destroy " << m_id << std::endl;
m_valid = false;
internal::FileSequence_Decref(m_id);
m_id = 0;
m_fsetId = 0;
}
}
与导出的Go库的所有C ++交互都通过opaque句柄进行通信:
// C++
size_t FileSequence::length() const {
return internal::FileSequence_Len(m_id);
}
不幸的是,它确实意味着在多线程的C ++环境中,所有线程都会通过互斥锁到达地图。但是在创建和销毁对象时它只是一个写锁定,而对于对象的所有方法调用,它都是一个读锁定。