无法从Go语言访问的C对象的编组

huangapple go评论77阅读模式
英文:

Marshalling C objects that cannot be accessed from Go

问题

有一些C对象,比如包含位字段的联合体和结构体,以及与Go的ABI对齐方式不同的结构体,无法从Go中访问。其中一些结构体无法更改为从Go代码中访问,因为它们是现有库的API的一部分。

为了将这些对象编组到Go结构体中,我们不能真正使用Go代码。相反,我们必须在C中编写编组代码。这样做是可以的,但我还没有找到一种可行的方法来定义在Go代码中定义的类型上操作的C函数。目前,我正在在C端定义我要编组的数据类型,并在我的Go代码中使用这些数据类型。

如果我想将编组的类型作为我的Go代码中的API公开,这真的很糟糕,因为我无法将C类型作为我的包接口的一部分公开。我的当前方法涉及将已编组的对象重新编组为在Go代码中定义的类型。

是否有更优雅的方法来实现我想要的目标,即将无法从Go代码访问的C结构体编组为在Go代码中定义的数据类型?

根据评论部分的要求,这里是一组无法从Go访问的C对象。

#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;
};
英文:

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 &lt;complex.h&gt;
#include &lt;stdbool.h&gt;

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

答案1

得分: 1

标准包encoding/binary可用于操作原始的C结构体。
您可以扩展Read和Write函数以支持自定义类型:

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
}

// 从r中读取foo到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
}

// 将data中的foo写入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
}
或者使用getter和setterhttp://pastebin.com/H1QW5AFb)

使用位操作来编组位字段

```go
type bar struct {
    x bool
    y uint
    z uint
}

// 从r中读取bar到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
    }

    // 从位字段读取
    *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
}

// 将data中的bar写入w
func writeBar(w io.Writer, order binary.ByteOrder, data bar) error {
    b := make([]byte, 1)

    var x uint8
    if data.x {
        x = 1
    }
    // 创建位字段
    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可以直接编组。

英文:

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] &gt;&gt; 7),          // bool x:1;
y: uint((b[0] &amp; 0x70) &gt;&gt; 3), // unsigned int y:3;
z: uint(b[0] &amp; 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 &amp; 0x01 &lt;&lt; 7) &amp; // bool x:1;
(uint8(data.y) &amp; 0x03 &lt;&lt; 4) &amp; // unsigned int y:3;
(uint8(data.z) &amp; 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.

huangapple
  • 本文由 发表于 2014年5月29日 02:54:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/23919677.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定