Jan 29, 2006

Marshaling: C's struct's bit fields

If you are C programmer you might have noticed the existence of the bit fields in structs:

typedef struct {
int Some : 1;
int Bit : 2;
int Fields : 5;
} MyStruct;

Marshaling that code could be a pain in the ass. Before all, read Jonathan's doc, then forget about the C's sizeof, sum all the bit fields and get the value, in our sample struct we need to get that as 1 byte (1 + 2 + 5 = 8 bits = 1 byte) and then use the System.Collections.BitArray class, and convert that to their data type value. (I wrote a wrapper for doing this in a easiest way)

See the graphical explanation for understanding this.

See this sample

/* libsample.h */

typedef struct {
unsigned int Some : 1;
unsigned int Bit : 2;
unsigned int Fields : 5;
} MyStruct;

MyStruct get_mystruct (void);

int some (void);

/* libsample.c */
#include
#include "libsample.h"

MyStruct get_mystruct (void)
{
MyStruct a;
a.Some = 0x01; //One bit: 1 - 0
a.Bit = 0x02; //Two bits: 10 - 2
a.Fields = 0x1A; //Five bits: 0001 1010 - 26
//Full return: 1101 1010
return a;
}

int some (void)
{
return 0;
}

Then compiling


gcc -fPIC -Wall -g -c libsample.c
gcc -g -shared -Wl,-soname,libsample.so.0 \
-o libsample.so.0.0 libsample.o -lc
/sbin/ldconfig -n .
ln -sf libsample.so.0 libsample.so

Will create the shared libraries, let's see the C# PInvoke.

using System;
using System.Collections;
using System.Runtime.InteropServices;

public class Sample
{
[StructLayout (LayoutKind.Sequential, Size=1, Pack=4)]
public struct MyStruct
{
public byte Value;

public uint GetSome ()
{
BitArray bArray = new BitArray (new byte[] { Value });
BitFieldFormater a = new BitFieldFormater (bArray);
return a.GetInt (0, 1);
}

public uint GetBit ()
{
BitArray bArray = new BitArray (new byte[] { Value });
BitFieldFormater a = new BitFieldFormater (bArray);
return a.GetInt (1, 2);
}

public uint GetFields ()
{
BitArray bArray = new BitArray (new byte[] { Value });
BitFieldFormater a = new BitFieldFormater (bArray);
return a.GetInt (3, 5);
}

public override string ToString ()
{
return "Some "+GetSome ()+" Bit "+GetBit ()+" Fields "+GetFields ();
}
}

[DllImport ("libsample.so")]
public static unsafe extern MyStruct get_mystruct (); //notice the return value

[DllImport ("libsample.so")]
public static unsafe extern int some (); //notice the return value

public static void Main ()
{
MyStruct myStruct = get_mystruct ();
System.Console.WriteLine ("some (): "+some ());
System.Console.WriteLine ("get_mystruct (): "+myStruct);
}
}

I'm using thre a class called BitFieldFormater for doing the magic:

using System;
using System.Collections;

public class BitFieldFormater
{
public BitFieldFormater (BitArray bitArray)
{
_bitArray = bitArray;
}

public uint GetUInt (int beginBit, int bitLength)
{
if ((beginBit + bitLength) > _bitArray.Count)
throw new ArgumentException ("Begin bit plus bit length is greater than array's length");

string requestedString = "";

//Getting bit values from the BitArray
for (int i = 0; i < bitLength; i++)
requestedString = Convert.ToInt32 (_bitArray [beginBit + i]) + requestedString;

return (uint) BinaryToInteger (requestedString);
}

private int BinaryToInteger (string binary)
{
char []elements = binary.ToCharArray ();
int result = 0, i = 0, pow = 0;
for (i = elements.Length - 1; i >= 0; i--) {
if (elements[i] == '1')
result += (int) Math.Pow (2, pow);
pow++;
}
return result;
}

private BitArray _bitArray;
}

Any bugs, please, let me know. I haven't yet tested this class with structs with byte-size greater than 1... further testing is required.

UPDATED. Feb 3rd. Lot of useless code removed... what the fuck was I thinking!?