Маршаллинг объектов C, к которым нельзя получить доступ из Go

Есть некоторые объекты C, такие как объединения, структуры, содержащие битовые поля, и структуры, выравнивание которых отличается от ABI Go, к которым нельзя получить доступ из Go. Некоторые из этих структур нельзя изменить, чтобы они были доступны из кода Go, поскольку они являются частью API существующей библиотеки.

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

Это действительно неприятно, если я хочу выставить упорядоченные типы как API в моем коде Go, поскольку я не могу выставить тип C как часть интерфейса моего пакета. Мой текущий подход включает переупорядочение уже упорядоченного объекта в тип, определенный в коде Go.

Есть ли более элегантный способ сделать то, что я хочу, то есть сортировать структуры C, к которым нельзя получить доступ из кода Go, в типы данных, определенные в коде Go?

Как и просили в разделе комментариев, вот коллекция объектов C, к которым нельзя получить доступ из Go.

#include <complex.h>
#include <stdbool.h>

union foo {
    int i;
    float f;
};

struct bar {
    bool x:1;
    unsigned int y:3;
    unsigned int z:4;
};

struct baz {
    float f;
    complex float c;
};

#pragma pack 1
struct quux {
    char c;
    short s;
    int i;
};

person fuz    schedule 28.05.2014    source источник
comment
Пробовали ли вы [протокольные буферы][code.google.com/p/protobuf/] ? вы должны иметь возможность маршалировать его из C и демаршалировать из go и наоборот.   -  person fabrizioM    schedule 29.05.2014
comment
@fabrizioM Объекты не покидают адресное пространство моей программы. Причина, по которой я не могу изменить их определение, заключается в том, что они являются частью API существующей библиотеки. Изменение его означало бы его форк и необходимость поддерживать форк.   -  person fuz    schedule 29.05.2014
comment
Буферы протокола @fabrizioM не подходят в моем случае, поскольку они предполагают создание дополнительных копий данных (в данном случае сначала сериализацию данных в буферы протокола, а затем в структуры Go), чего я стараюсь активно избегать.   -  person fuz    schedule 29.05.2014
comment
Это может не решить вашу проблему, но капитан прото решает эту проблему буферов протокола   -  person c00w    schedule 02.06.2014
comment
@ c00w Я не понимаю твой комментарий. Буферы протокола в моей проблеме не участвуют.   -  person fuz    schedule 02.06.2014
comment
Не могли бы вы добавить пример структуры C, которую вы не можете маршалировать/демаршалировать в чистом Go? Вы пытаетесь получить доступ к структурам C напрямую, не преобразовывая их в отдельный тип Go?   -  person Green    schedule 17.06.2014
comment
@Green Смотрите обновленный вопрос. По сути, я хочу преобразовать их в тип Go, но невозможно написать этот код преобразования на C, поскольку вы не можете использовать типы Go в коде C. Я также не могу использовать код Go, поскольку эти объекты не могут быть представлены в Go и, следовательно, не могут быть доступны из Go.   -  person fuz    schedule 17.06.2014


Ответы (1)


Стандартный пакет encoding/binary можно использовать для управления необработанными структурами C. Вы можете расширить функции чтения и записи для поддержки пользовательских типов:

func Read(r io.Reader, order binary.ByteOrder, data interface{}) error {
    switch data := data.(type) {
    case *foo:
        return readFoo(r, order, data)
    // (...)
    default:
        return binary.Read(r, order, data)
    }
}

func Write(w io.Writer, order binary.ByteOrder, data interface{}) error {
    switch data := data.(type) {
    case foo:
        return writeFoo(r, order, data)
    // (...)
    default:
        return binary.Write(r, order, data)
    }
}

Используйте структуру, содержащую все поля объединения, и используйте контекст приложения, чтобы решить, какое значение кодировать в объединение C.

type foo struct {
    is_i bool
    i    int32
    f    float32
}

// Read a foo from r into data
func readFoo(r io.Reader, order binary.ByteOrder, data *foo) error {
    b := make([]byte, 4)
    if _, err := io.ReadFull(r, b); err != nil {
        return err
    }

    *data = foo{
        i: int32(order.PutUint32(b)),
        f: float32(order.PutUint32(b)),
    }

    return nil
}

// Write a foo from data into w
func writeFoo(w io.Writer, order binary.ByteOrder, data foo) error {
    b := make([]byte, 4)

    if data.is_i {
        order.PutUint32(b, uint32(data.i))
    } else {
        order.PutUint32(b, uint32(data.f))
    }

    _, err := w.Write(b)
    return err
}

(В качестве альтернативы можно использовать геттеры и сеттеры: http://pastebin.com/H1QW5AFb)

Используйте побитовые операции для маршалирования битовых полей

type bar struct {
    x bool
    y uint
    z uint
}

// Read a bar from r into data
func readBar(r io.Reader, order binary.ByteOrder, data *foo) error {
    b := make([]byte, 1)
    if _, err := io.ReadFull(r, b); err != nil {
        return err
    }

    // Read from bitfield
    *data = bar{
        x: bool(b[0] >> 7),          // bool x:1;
        y: uint((b[0] & 0x70) >> 3), // unsigned int y:3;
        z: uint(b[0] & 0x0f),        // unsigned int z:4;
    }

    return nil
}

// Write a bar from data into w
func writeBar(w io.Writer, order binary.ByteOrder, data bar) error {
b := make([]byte, 1)

    var x uint8
    if data.x {
        x = 1
    }
    // Create bitfield
    b[0] = (x & 0x01 << 7) & // bool x:1;
        (uint8(data.y) & 0x03 << 4) & // unsigned int y:3;
        (uint8(data.z) & 0x04) // unsigned int z:4;
    _, err := w.Write(b)
    return err
}

Сериализованная форма baz зависит от внутреннего определения компилятора complex. При использовании encoding.binary поля имеют выравнивание по 1 байту, поэтому quux можно маршалировать напрямую.

person Green    schedule 17.06.2014
comment
Это не портативно. Я не могу делать предположения о том, как данные размещаются внутри битовых полей, поскольку каждая платформа имеет свое представление о порядке, в котором данные размещаются в битовых полях. Кроме того, решение, которое вы предоставляете, очень сложное, так как мне приходится вычислять все смещения и выравнивания структур вручную, что я не могу сделать переносимым (помните: выравнивание определяется ABI платформ). Не совсем решение. - person fuz; 17.06.2014
comment
Перефразируя это по-другому, я должен использовать C для деупорядочения объектов C, поскольку невозможно сделать это в Go переносимым, поскольку Go не предоставляет возможности доступа к объектам C переносимым и правильным способом. - person fuz; 17.06.2014
comment
Я неправильно понял ваш вопрос. Будет ли размещение объекта C в неэкспортируемом поле объекта Go и использование функций C для доступа к нему приемлемым решением? Невозможно напрямую манипулировать одной и той же структурой данных на обоих языках без оболочки. - person Green; 17.06.2014
comment
Что ж, это в принципе возможно, но очень медленно, так как у вас есть cgo-вызов для доступа к каждому полю. Вероятно, у меня нет другого выбора, кроме как выполнить сортировку дважды (один раз из структуры, предназначенной только для C, в структуру C, которую можно прочитать из Go, и еще раз из этой структуры в структуру Go). Эм-м-м... - person fuz; 17.06.2014