這是一篇備份的文章,預防之後消失,來源是
http://jnkjava.blogspot.tw/2013/06/parsing-cc-struct-in-java.html
enum Gender { MALE, FEMALE };
struct msg {
#ifdef INTEL_STYLE
UCHAR spare4:4;
UCHAR octal:3;
UCHAR bool:1;
#else
UCHAR bool:1;
UCHAR octal:3;
UCHAR spare4:4;
#endif
UINT uint;
char str[5];
float flt;
enum Gender gender;
}
In this blog entry we are going to see how you can parse such a message in your receiving Java application.
What will you need?
- the javolution library to parse the C/C++ struct in Java
- a calculator that handles binaries, hexadecimals and decimals (Windows, Linux and MacOSX already provide such calculators. However, they don't handle decimal point numbers, so this online converter will prove useful, too).
C | Java (Javolution Struct) |
---|---|
UCHAR
|
Unsigned8
|
UWORD
|
Unsigned16
|
UINT
|
Unsigned32
|
byte
|
Signed8
|
short
|
Signed16
|
int
|
Signed32
|
long
|
Signed64
|
long long
|
Signed64
|
float
|
Float32
|
double
|
Float64
|
pointer
|
Reference32
|
char[]
|
UTF8String
|
enum
|
Enum32
|
Let's get started.
The following Java class represents the above C/C++ struct in Java:
import java.nio.ByteBuffer;
public class Message extends javolution.io.Struct {
private final Unsigned8 bool = new Unsigned8(1);
private final Unsigned8 octal = new Unsigned8(3);
private final Unsigned8 spare2 = new Unsigned8(4);
private final Unsigned32 uint = new Unsigned32();
private final UTF8String str = new UTF8String(5);
private final Float32 flt = new Float32();
private final Enum32 gender = new Enum32(Gender.values());
public Message (byte[] b) {
this.setByteBuffer(ByteBuffer.wrap(b), 0);
}
public boolean getBool() {
return bool.get() != 0;
}
public int getOctal() {
return octal.get();
}
public long getUInt() {
return uint.get();
}
public String getStr() {
return str.get();
}
public float getFlt() {
return flt.get();
}
public Gender getGender() {
return gender.get();
}
}
enum Gender { MALE, FEMALE };
The string char[5] is represented by UTF8String(5). float by Float32.
Finally, the enum is represented by Enum32.
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class MessageTest {
private Message msg;
@Before
public void setUp() {
byte[] bb = new byte[] {
(byte) 0x90, // 1001 0000
(byte) 0x00, (byte) 0x00, (byte) 0x00, // alignment with previous!
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, // uint
(byte) 0x48, (byte) 0x41, (byte) 0x4C, (byte) 0x4C, (byte) 0x4F, // str
(byte) 0x00, (byte) 0x00, (byte) 0x00, // alignment with previous!
(byte) 0x3F, (byte) 0xC0, (byte) 0x00, (byte) 0x00, // flt
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, // gender
};
msg = new Message(bb);
}
@After
public void tearDown() {
}
@Test
public void testMessage() {
assertTrue(msg.getBool()); // 1 = true
assertEquals(1, msg.getOctal()); // 001
assertEquals(2, msg.getUInt());
assertEquals("HALLO", msg.getStr());
assertEquals(1.5, msg.getFlt(), 0.0);
assertEquals(Gender.FEMALE, msg.getGender());
}
}
The next 4 bytes correspond to the uint. The next 5 ASCII characters correspond to the string "HALLO". Again, another alignment, and then the float field. The last 4 bytes represent the Gender enum which contains the value 1, i.e. Gender.FEMALE.
Packed
However, your data might be packed, i.e. no alignment/padding is happening. To do this, you override theisPacked() method of javolution.io.Struct:
public class Message extends javolution.io.Struct {
...
@Override
public boolean isPacked() {
return true;
}
...
}
Now your test case data should contain no padding in order to pass:
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;import static org.junit.Assert.assertTrue;
public class MessageTest {
private Message msg;
@Before
public void setUp() {
byte[] bb = new byte[] {
(byte) 0x90, // 1001 0000
// (byte) 0x00, (byte) 0x00, (byte) 0x00, // alignment with previous!
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, // uint
(byte) 0x48, (byte) 0x41, (byte) 0x4C, (byte) 0x4C, (byte) 0x4F, // str
// (byte) 0x00, (byte) 0x00, (byte) 0x00, // alignment with previous!
(byte) 0x3F, (byte) 0xC0, (byte) 0x00, (byte) 0x00, // flt
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, // gender
};
msg = new Message(bb);
}
@Afterpublic void tearDown() {
}
@Testpublic void testMessage() {
assertTrue(msg.getBool()); // 1 = true
assertEquals(1, msg.getOctal()); // 001
assertEquals(2, msg.getUInt());
assertEquals("HALLO", msg.getStr());
assertEquals(1.5, msg.getFlt(), 0.0);
assertEquals(Gender.FEMALE, msg.getGender());
}
}
Conclusion
- All Structs should be declared final.
- Javolution doesn't support nested structs; you need to flaten your C/C++ structs in Java. E.g.
struct Identification {
byte b;
long l;
}
struct msg {
struct Identification id;
int i;
}
should be represented by:
public class Message extends javolution.io.Struct {
private final Signed8 b = new Signed8();
private final Signed64 l = new Signed64();
private final Signed32 i = new Signed32();
...
}
- Javolution array can only accept members of Struct.Member. The following will not work:
private final Reference32[] refs = array(new Reference32[2]);
private final Signed32[] refs = array(new Signed32[2]);
Happy parsing!
Place your comment