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

标签 c++ c go cgo

我拥有一个 Go 库,gofileseq ,为此我想尝试创建一个 C/C++ 绑定(bind)。

能够导出使用简单类型(整数、字符串...)的函数非常简单。通过定义 C 结构并将 Go 类型转换为它,将数据从自定义 Go 类型导出到 C 甚至很容易,以便在导出的函数中使用,因为您正在分配 C 内存来执行此操作。但是有了 go 1.5 cgo rules我发现很难弄清楚如何从存储状态的更复杂结构中导出功能。

我想以某种方式导出到 C++ 绑定(bind)的来自 gofileseq 的结构示例:

// 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 中创建对象,这些对象存储在 Go 端的静态 map[int]*FrameSet 中,然后将 int id 返回给 C++。然后,所有 C++ 操作都使用 id 向 Go 发出请求。这听起来像是一个有效的解决方案吗?

更新

目前,我正在测试一个使用全局 map 和唯一 ID 来存储对象的解决方案。 C++ 会请求创建一个新对象,但只会返回一个不透明的 ID。然后他们可以调用所有导出为函数的方法,使用该 id,包括请求在完成时将其销毁。

如果有比这更好的方法,我很乐意看到答案。一旦我得到一个完整的工作原型(prototype),我将添加我自己的答案。

更新 #2

我写了一篇关于我最终使用的最终解决方案的博文:http://justinfx.com/2016/05/14/cpp-bindings-for-go/

最佳答案

由于没有更好的解决方案,我最终解决这个问题的方法是在 Go 端使用私有(private)全局 map (ref)。这些映射会将 Go 对象的实例与随机 uint64 id 相关联,并且该 id 将作为“不透明句柄”返回给 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++ 交互都通过不透明句柄进行通信:

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

不幸的是,它确实意味着在多线程 C++ 环境中,所有线程都将通过一个互斥锁到达映射。但它只是在创建和销毁对象时是一个写锁,对于一个对象上的所有方法调用它是一个读锁。

关于c++ - 是否可以将复杂的 Go 结构导出/包装到 C?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36861569/

相关文章:

opengl - 我的 OpenGL Go 矩阵函数(Perspective/Frustum & Lookat 矩阵)有什么问题?

c++ - C++中的非类类型错误

c++ - 当我试图通过我的函数打印我的列表时出现段错误

CreateThread x3000 返回标准输出句柄

c - 类函数宏 vs 类对象宏

Python 无法使用 Python 的 ctypes 读取包含 char 数组的结构

string - 如何测试一个值是否是模板中的字符串

http - 带有非本地主机的实时站点的 Go Revel 框架

python - 如何终止在后端执行长时间运行的 C/C++ 代码的 python 解释器?

c++ - 在 C# 的 WPF 应用程序中单击按钮时如何调用 C++ 代码?