У меня есть библиотека Go, gofileseq, для которого я хотел бы попытаться сделать привязку C / C ++.
Это довольно просто, чтобы иметь возможность экспортировать функции, которые используют простые типы (целые числа, строки, …). Даже достаточно просто экспортировать данные из пользовательских типов Go в C, определив структуру C и переведя в нее тип Go, который будет использоваться в экспортируемых функциях, поскольку вы выделяете память C для этого. Но с перейти на 1,5 сго правила Мне трудно понять, как экспортировать функциональность из более сложной структуры, в которой хранится состояние.
Пример структуры из 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
Тип, который я хочу предоставить, содержит часть указателей на базовый тип, каждый из которых хранит состояние.
В идеале я хотел бы иметь возможность хранить void*
в классе C ++ и сделать его простым прокси для обратного вызова экспортированных функций Go с void*
, Но правила cgo запрещают C хранить указатель Go дольше, чем вызов функции. И я не вижу, как я мог бы использовать подход определения классов C ++, которые можно было бы распределять и использовать для работы с моей библиотекой Go.
Можно ли обернуть сложные типы для воздействия на C / C ++?
Есть ли шаблон, который позволил бы клиенту C ++ создавать Go FrameSet
?
редактировать
Одна идея, о которой я могу подумать, — позволить C ++ создавать объекты в Go, которые хранятся на стороне Go в статическом виде. map[int]*FrameSet
а затем вернуть идентификатор int в C ++. Затем все операции C ++ делают запросы в Go с идентификатором. Это похоже на правильное решение?
Обновить
Сейчас я продолжаю тестировать решение, которое использует глобальные карты и уникальные идентификаторы для хранения объектов. C ++ будет запрашивать создание нового объекта и получать только непрозрачный идентификатор. Затем они могут вызывать все методы, экспортируемые как функции, используя этот идентификатор, в том числе запрашивая его уничтожение по завершении.
Если есть лучший подход, чем это, я хотел бы увидеть ответ. Как только я получу полностью работающий прототип, я добавлю свой ответ.
Обновление № 2
Я написал сообщение в блоге об окончательном решении, которое я использовал в итоге: http://justinfx.com/2016/05/14/cpp-bindings-for-go/
Я решил, что из-за отсутствия лучшего решения я решил использовать частные глобальные карты на стороне Go (ссылка). Эти карты будут ассоциировать экземпляры объектов Go со случайным идентификатором uint64, и идентификатор будет возвращен в 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;
}
}
И все взаимодействия C ++ с экспортированной библиотекой Go связываются через непрозрачный дескриптор:
// C++
size_t FileSequence::length() const {
return internal::FileSequence_Len(m_id);
}
К сожалению, это означает, что в многопоточной среде C ++ все потоки проходят через мьютекс на карту. Но это только блокировка записи, когда объекты создаются и уничтожаются, а для всех вызовов методов объекта это блокировка чтения.
Других решений пока нет …