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.

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

报告相同问题?

悬赏问题

  • ¥35 平滑拟合曲线该如何生成
  • ¥100 c语言,请帮蒟蒻写一个题的范例作参考
  • ¥15 名为“Product”的列已属于此 DataTable
  • ¥15 安卓adb backup备份应用数据失败
  • ¥15 eclipse运行项目时遇到的问题
  • ¥15 关于#c##的问题:最近需要用CAT工具Trados进行一些开发
  • ¥15 南大pa1 小游戏没有界面,并且报了如下错误,尝试过换显卡驱动,但是好像不行
  • ¥15 自己瞎改改,结果现在又运行不了了
  • ¥15 链式存储应该如何解决
  • ¥15 没有证书,nginx怎么反向代理到只能接受https的公网网站