5

There are some C objects like unions, structs that contain bitfields and structs whose alignment differs from Go's ABI, that cannot be accessed from Go. Some of these structures cannot be changed to be accessible from Go code as they are part of the API of an existing library.

To marshall such objects into Go structures we thus cannot really use Go code. Instead w have to write the marshalling code in C. This works fine but I have not found a feasible way to define C functions that operate on types defined in Go code. Right now I am defining the data types I am marshalling into on the C side and use these data types in my Go code.

This is really nasty if I want to expose the marshalled types as an API in my Go code, as I cannot expose a C type as a part of my package interface. My current approach involves remarshalling the already marshalled object into a type that is defined in Go code.

Is there a more elegant way to do what I want to do, i.e. marshalling C structs that cannot be accessed from Go code into data types defined in Go code?

As requested in the comment section, here is a collection of C objects that cannot be accessed from 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;
};
fuz
  • 88,405
  • 25
  • 200
  • 352
  • Have you tried [protocol buffers][https://code.google.com/p/protobuf/] ? you should be able to marshal it from C and unmarshal it from go and viceversa. – fabrizioM May 28 '14 at 20:42
  • @fabrizioM The objects do not leave the address space of my program. The reason I cannot modify their definition is that they are part of the API of an existing library. Changing it would mean to fork it and needing to maintain the fork. – fuz May 28 '14 at 23:18
  • @fabrizioM Protocol buffers are not suitable in my case as they would involve making additional copies of the data (in this case, first serializing the data into protocol buffers and then into Go structures), which is what I'm trying to actively avoid. – fuz May 28 '14 at 23:19
  • This may not solve your problem but captain proto solves this problem of protocol buffers – c00w Jun 01 '14 at 22:51
  • @c00w I don't understand your comment. Protocol buffers are not involved in my problem. – fuz Jun 01 '14 at 22:56
  • Could you add an example of a C struct which you can not marshal/unmarshal in pure Go ? Are you trying to access the C structures directly without converting them to a separate Go type ? – Green Jun 17 '14 at 12:07
  • @Green See updated question. Basically, I want to convert them to a Go type but it is impossible to write this conversion code in C as you can't use Go types in C code. I also cannot use Go code as these objects cannot be represented in Go and thus cannot be accessed from Go. – fuz Jun 17 '14 at 12:57

1 Answers1

1

The standard package encoding/binary can be used for manipulating raw C structs. You can extend Read and Write functions to support custom types :

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)
    }
}

Use a struct containing all the union's fields and use application context to decide which value to encode into the C union.

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
}

(Alternatively, using getters and setters: http://pastebin.com/H1QW5AFb)

Use bitwise operations to marshal bitfields

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
}

The serialized form of baz depends on the compiler's internal definition of complex. When using encoding.binary, fields have a 1-byte alignement so quux can be marshaled directly.

Green
  • 382
  • 3
  • 8
  • This is not portable. I can't make assumptions about how the data is layed out inside bitfields since every platform has a different idea of the order in which data is placed in bitfields. Also, the solution you provide is very complicated, as I have to essentially calculate all struct offsets and alignments manually, something that I can't do portably (remember: alignment is defined by the platforms ABI). Not really a solution. – fuz Jun 17 '14 at 15:00
  • To rephrase it differently, I *must* use C to unmarshall the C objects as it is *impossible* to do this in Go portably as Go *does not provide* the capabilities to access the C objects in a portable and correct way. – fuz Jun 17 '14 at 15:06
  • I had misunderstood your question. Would putting the C object in an unexported field of the Go object and using C functions to access it be an acceptable solution ? It is not going to be possible to directly manipulate the same data structure in both languages without a wrapper. – Green Jun 17 '14 at 15:49
  • Well, that would in principle be possible but very slow as you had a cgo-call for every field access. I probably have no choice but marshalling twice (once from the C-only struct to a C struct that can be read from Go and once again from that struct to a Go struct). uh... – fuz Jun 17 '14 at 17:40