I solved this problem by writing my own code for reading byte arrays with C structs into Java objects. This code is written for 32 bit input, whatever word size the host application is using.
Example:
some_struct s = Bridj32.readObject(some_struct.class,
new byte[] { 0x01, 0x23, 0x45, 0x67 });
The Bridj32
class contains the implementation of this. It takes as input classes that are annotated with BridJ annotations (Field
, Struct
, Ptr
etc) and byte arrays with data. In gives as output parsed Java objects that contain the data of from the input array.
The most tricky part of Bridj32
is that it implements the C struct packing algorithm.
Code of Bridj32
:
import static java.util.stream.Collectors.toMap;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import org.bridj.BridJ;
import org.bridj.Pointer;
import org.bridj.SizeT;
import org.bridj.StructIO;
import org.bridj.StructObject;
import org.bridj.ann.Array;
import org.bridj.ann.Bits;
import org.bridj.ann.Field;
import org.bridj.ann.Ptr;
import org.bridj.ann.Struct;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Primitives;
/**
* Code for using {@link BridJ} in 32 bit mode, even in a 64 bit application.
* Reads byte arrays into objects, gets offset of fields and gets the size of structs.
* <p>
* Assumes little-endian byte order for all input.
* <p>
* Input struct types must be annotated with BridJ annotations, as if they where generated
* with Jnaerator.
*/
public class Bridj32 {
private static final Map<Class<?>, StructDescription> structCache = new ConcurrentHashMap<>();
private static class StructDescription {
public final int byteSize;
public final int largestMemberSize;
public final List<MemberDescription> members;
@SuppressWarnings("unused")
public final Class<? extends StructObject> structClass;
public StructDescription(Class<? extends StructObject> structClass) {
this.structClass = structClass;
Map<Integer, Method> memMap = structFieldMethods(structClass)
.collect(toMap(m -> m.getAnnotation(Field.class).value(), e -> e));
Struct structAnn = structClass.getAnnotation(Struct.class);
if (structAnn != null && structAnn.pack() > 1) {
throw new UnsupportedOperationException("Packed structs are not supported. Struct: " + structClass);
}
ImmutableList.Builder<MemberDescription> mems = ImmutableList.builder();
int offset = 0;
int maxLargestMem = 0;
for (Entry<Integer, Method> e : memMap.entrySet()) {
Method memberMethod = e.getValue();
int alignSize = alignSize(memberMethod);
maxLargestMem = Math.max(maxLargestMem, alignSize);
int size = calcByteSize(memberMethod);
offset += alignPadSize(offset, alignSize);
mems.add(new MemberDescription(memberMethod, e.getKey(), offset, size));
offset += size;
}
largestMemberSize = maxLargestMem;
members = mems.build();
byteSize = offset;
}
public MemberDescription findMember(String memName) {
for (MemberDescription desc : members) {
if (desc.method.getName().equals(memName)) {
return desc;
}
}
throw new NoSuchElementException(memName);
}
}
static int alignPadSize(int size, int alignment) {
int p = size % alignment;
if (p == 0) return 0;
else return alignment - p;
}
private static class MemberDescription {
Method method;
int index;
int byteOffset;
@SuppressWarnings("unused")
int byteSize;
public MemberDescription(Method method, int index, int byteOffset, int byteSize) {
this.method = method;
this.index = index;
this.byteOffset = byteOffset;
this.byteSize = byteSize;
}
public Class<?> getType() {
return method.getReturnType();
}
}
/**
* @return The size of the C struct that corresponds to the input class
* argument. This size includes trailing padding in the struct.
*/
public static int paddedSizeOf(Class<?> cls) {
StructDescription desc = getStructDescription(cls);
return desc.byteSize + alignPadSize(desc.byteSize, desc.largestMemberSize);
}
private static Stream<Method> structFieldMethods(Class<? extends StructObject> structClass) {
return Arrays.stream(structClass.getMethods())
.filter(m -> m.getAnnotation(Field.class) != null)
.filter(m -> m.getParameterCount() == 0);
}
private static StructDescription getStructDescription(Class<?> cls) {
StructDescription result = structCache.get(cls);
if (result == null) {
@SuppressWarnings("unchecked")
Class<? extends StructObject> structCls = (Class<? extends StructObject>) cls;
result = new StructDescription(structCls);
structCache.put(cls, result);
}
return result;
}
@SuppressWarnings("unused")
private static int nrFields(Class<? extends StructObject> structClass) {
int maxFieldNr = -1;
for (Method f : structClass.getMethods()) {
Field fieldAnn = f.getAnnotation(Field.class);
if (fieldAnn != null) {
maxFieldNr = Math.max(maxFieldNr, fieldAnn.value());
}
}
return maxFieldNr + 1;
}
/**
* @return The size of the C struct that corresponds to the input class
* argument. This size does not include trailing padding
* in the struct.
*/
public static int sizeOf(Class<? extends StructObject> structClass) {
return getStructDescription(structClass).byteSize;
}
private static int calcByteSize(Method memMeth) {
Array arrayAnn = memMeth.getAnnotation(Array.class);
int mult = arrayAnn == null
? 1 : (int) Arrays.stream(arrayAnn.value()).reduce(1, (a, b) -> a * b);
int sizeSingle = calcByteSizeSingle(memMeth);
return mult * sizeSingle;
}
private static int alignSize(Method memMeth) {
if (StructObject.class.isAssignableFrom(memMeth.getReturnType())) {
return getStructDescription(memMeth.getReturnType()).largestMemberSize;
} else {
return primitiveByteSize(memMeth);
}
}
private static int primitiveByteSize(Method memMeth) {
Class<?> cls = Primitives.wrap(memMeth.getReturnType());
if (memMeth.getAnnotation(Ptr.class) != null) return 4;
if (memMeth.getAnnotation(Bits.class) != null) {
throw new UnsupportedOperationException("Bit fields are not supported. Method: " + memMeth);
}
if (cls == Boolean.class) return 1;
if (cls == Byte.class ) return 1;
if (cls == Short.class ) return 2;
if (cls == Integer.class) return 4;
if (cls == Long.class ) return 8;
if (cls == Float.class ) return 4;
if (cls == Double.class ) return 8;
if (cls == String.class ) return 4;
if (cls == Pointer.class) return 4;
if (cls == SizeT.class ) return 4;
throw new IllegalArgumentException("Unknown type: " + cls);
}
private static int calcByteSizeSingle(Method memMeth) {
Class<?> cls = memMeth.getReturnType();
if (StructObject.class.isAssignableFrom(cls)) {
return paddedSizeOf(cls);
} else {
return primitiveByteSize(memMeth);
}
}
/**
* Reads one object (and all its members) from the bytes array.
*/
public static <T extends StructObject> T readObject(Class<T> structClass, byte[] bytes) {
ByteBuffer buff = ByteBuffer.wrap(bytes, 0, bytes.length).order(ByteOrder.LITTLE_ENDIAN);
return readObject(structClass, buff, 0);
}
private static <T extends StructObject> T readObject(Class<T> structClass, ByteBuffer buff, int offset) {
StructDescription desc = getStructDescription(structClass);
Preconditions.checkArgument(buff.capacity() - offset >= desc.byteSize);
StructIO io = StructIO.getInstance(structClass);
T struct;
try {
struct = structClass.getConstructor().newInstance();
} catch (ReflectiveOperationException exc) {
throw new RuntimeException(exc);
}
for (MemberDescription memDesc : desc.members) {
setField(memDesc, struct, io, buff, offset);
}
return struct;
}
public static int offsetOfField(Class<? extends StructObject> structClass, String memName) {
return getStructDescription(structClass).findMember(memName).byteOffset;
}
private static void setField(MemberDescription desc, StructObject struct, StructIO io, ByteBuffer bytes, int structOffset) {
Class<?> cls = Primitives.wrap(desc.getType());
int offset = desc.byteOffset + structOffset;
int ix = desc.index;
if (cls == Boolean.class) io.setBooleanField(struct, ix, bytes.get(offset) != 0);
else if (cls == Byte.class ) io.setByteField (struct, ix, bytes.get(offset));
else if (cls == Short.class ) io.setShortField (struct, ix, bytes.getShort(offset));
else if (cls == Integer.class) io.setIntField (struct, ix, bytes.getInt(offset));
else if (cls == Long.class ) io.setLongField (struct, ix, bytes.getLong(offset));
else if (cls == Float.class ) io.setFloatField (struct, ix, bytes.getFloat(offset));
else if (cls == Double.class ) io.setDoubleField (struct, ix, bytes.getDouble(offset));
else if (cls == String.class) throw new UnsupportedOperationException();
else if (SizeT.class.isAssignableFrom(cls)) {
io.setSizeTField(struct, offset, Integer.toUnsignedLong(bytes.getInt(offset)));
} else if (Pointer.class.isAssignableFrom(cls)) {
@SuppressWarnings("deprecation")
Pointer<?> p = Pointer.pointerToAddress(Integer.toUnsignedLong(bytes.getInt(offset)));
io.setPointerField(struct, ix, p);
} else if (StructObject.class.isAssignableFrom(cls)) {
@SuppressWarnings("unchecked")
StructObject o = readObject((Class<? extends StructObject>) cls, bytes, offset);
io.setNativeObjectField(struct, ix, o);
}
}
/**
* Reads all objects of type objCls form arrData and returns them. The number of objects
* depend on the binary size of objCls objects.
* <p>
* If the size of arrData is not evenly devisible by the size of objCls
*
*/
public static <T extends StructObject> List<T> readAllObjects(Class<T> objCls, byte[] arrData) {
int strideSize = paddedSizeOf(objCls);
List<T> result = new ArrayList<>();
ByteBuffer buff = ByteBuffer.wrap(arrData).order(ByteOrder.LITTLE_ENDIAN);
for (int offset = 0; offset < arrData.length; offset += strideSize) {
result.add(readObject(objCls, buff, offset));
}
return result;
}
}