2016-04-26 4 views
0

У меня есть библиотека Go, gofileseq, для которой я хотел бы попробовать и сделать привязку C/C++.Можно ли экспортировать/обернуть комплекс Go struct в C?

Это довольно просто, чтобы иметь возможность экспортировать функции, которые используют простые типы (ints, string, ...). Достаточно легко экспортировать данные из пользовательских типов Go в C, определив структуру C и переведя в нее тип Go, который будет использоваться в экспортируемых функциях, поскольку вы выделяете C-память для этого. Но с go 1.5 cgo rules мне трудно понять, как экспортировать функциональность из более сложной структуры, которая хранит состояние.

Пример структуры из 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 с идентификатором. Это похоже на правильное решение?

Update

На данный момент я исхожу с тестированием решение, которое использует глобальные карты и уникальные идентификаторы для хранения объектов. C++ запрашивает создание нового объекта и возвращает только непрозрачный идентификатор. Затем они могут вызывать все методы, экспортируемые как функции, используя этот идентификатор, включая запрос на его уничтожение, когда это делается.

Если есть лучший подход, чем это, я хотел бы получить ответ. Как только я получу полноценный прототип, я добавлю свой собственный ответ.

Update # 2

Я написал в блоге о том, что окончательное решение я кончался с помощью: http://justinfx.com/2016/05/14/cpp-bindings-for-go/

ответ

0

так, как я в конечном итоге решить это, из-за отсутствия лучшего решения, должен был использовать частные глобальные карты на стороне Go (ref). Эти карты связывают экземпляры объектов 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); 
} 

к сожалению, это означает, что в multhreaded среде C++, все потоки будут идти через мьютекс на карте. Но это только блокировка записи, когда объекты создаются и уничтожаются, а для всех вызовов методов на объекте это блокировка чтения.