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 如何让企业微信机器人实现消息汇总整合
  • ¥50 关于#ui#的问题:做yolov8的ui界面出现的问题
  • ¥15 如何用Python爬取各高校教师公开的教育和工作经历
  • ¥15 TLE9879QXA40 电机驱动
  • ¥20 对于工程问题的非线性数学模型进行线性化
  • ¥15 Mirare PLUS 进行密钥认证?(详解)
  • ¥15 物体双站RCS和其组成阵列后的双站RCS关系验证
  • ¥20 想用ollama做一个自己的AI数据库
  • ¥15 关于qualoth编辑及缝合服装领子的问题解决方案探寻
  • ¥15 请问怎么才能复现这样的图呀