drkjzk3359 2016-04-26 09:59
浏览 53
已采纳

是否可以将复杂的Go结构导出/包装到C?

I own a Go library, gofileseq, for which I would like to try and made a C/C++ binding.

It is pretty straightforward to be able to export functions that use simple types (ints, strings, ...). It is even easy enough to export data from custom Go types to C by defining a C struct and translating the Go type to it, to be used in the exported functions, since you are allocating C memory to do it. But with the go 1.5 cgo rules I am finding it difficult to figure out how to export functionality from a more complex struct that stores state.

Example of a struct from gofileseq that I would like to export somehow to a C++ binding:

// 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
}

As you can see, the FrameSet type that I want to expose contains a slice of pointers to an underlying type, each of which stores state.

Ideally, I would love to be able to store a void* on a C++ class, and make it just a simple proxy for calling back into exported Go functions with the void*. But the cgo rules disallow C storing a Go pointer longer than the function call. And I am failing to see how I could use an approach of defining C++ classes that could be allocated and used to operate with my Go library.

Is it possible to wrap complex types for exposure to C/C++? Is there a pattern that would allow a C++ client to create a Go FrameSet?

Edit

One idea I can think of would be to let C++ create objects in Go that get stored on the Go side in a static map[int]*FrameSet and then return the int id to C++. Then all the C++ operations make requests into Go with the id. Does that sound like a valid solution?

Update

For now, I am proceeding with testing a solution that uses global maps and unique ids to store objects. C++ would request a new object to be created and only get back an opaque id. Then they can call all of the methods exported as functions, using that id, including requesting for it to be destroyed when done.

If there is a better approach than this, I would love to see an answer. Once I get a fully working prototype, I will add my own answer.

Update #2

I've written a blog post about the final solution that I ended up using: http://justinfx.com/2016/05/14/cpp-bindings-for-go/

  • 写回答

1条回答 默认 最新

  • donglie1994 2016-05-06 05:04
    关注

    The way I ended up solving this, for lack of a better solution, was to use private global maps on the Go side (ref). These maps would associate instances of the Go objects with a random uint64 id, and the id would be returned to C++ as an "opaque handle".

    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
    ", fset.String(), id)
        m.lock.Lock()
        id := FrameSetId(m.rand.Uint64())
        m.m[id] = &frameSetRef{fset, 1}
        m.lock.Unlock()
        return id
    }
    

    Then I use reference counting to determine when C++ no longer needs the object, and remove it from the map:

    // 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
    ", 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
    ", ref, refs)
        if refs != 0 {
            return
        }
    
        m.lock.Lock()
        if atomic.LoadUint32(&ref.refs) == 0 {
            // fmt.Printf("Deleting %v
    ", 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;
        }
    }
    

    And all C++ interactions with the exported Go library communicate via the opaque handle:

    // C++
    size_t FileSequence::length() const {
        return internal::FileSequence_Len(m_id);
    }
    

    Unfortunately it does mean that in a multhreaded C++ environment, all threads would go through a mutex to the map. But it is only a write lock when objects are created and destroyed, and for all method calls on an object it is a read lock.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 在获取boss直聘的聊天的时候只能获取到前40条聊天数据
  • ¥20 关于URL获取的参数,无法执行二选一查询
  • ¥15 液位控制,当液位超过高限时常开触点59闭合,直到液位低于低限时,断开
  • ¥15 marlin编译错误,如何解决?
  • ¥15 有偿四位数,节约算法和扫描算法
  • ¥15 VUE项目怎么运行,系统打不开
  • ¥50 pointpillars等目标检测算法怎么融合注意力机制
  • ¥20 Vs code Mac系统 PHP Debug调试环境配置
  • ¥60 大一项目课,微信小程序
  • ¥15 求视频摘要youtube和ovp数据集