Inhalt
Topic:.J1C_ByteD.
.
Topic:.J1C_ByteD.pr.
Last changed: 2014-08-17
How to read structured data if the structure depends on the content? A pointer to data is given, or an array of data.
In C it is usual to read the first content maybe with a specific struct
, maybe as array-access:
//Negative-Pattern!! void readData(void* data, int length) { int* idata = (int*) data; int cmd = idata[1]; //the command is written on byte position 4..7 cmd = ntohl(cmd); //because it is big endian on network net-to-host long if(cmd == myExpectedCmd){ struct MyStruct* sdata = (struct MyStruct*)(idata +2); //data after firts 8 headbytes ...etc.
Why is that pattern problematically:
Pointer casting and address calculation in C/C++ is an source of mistake often.
This example is simple. You should count bytes etc. if it is more complex.
You can forget net-to-host, or done twice (later in code twice), there is not any safety by compiler check.
Think about all big-endian conversions if you image the data with a C struct.
If a 2- or 4-byte-integer was gotten from a odd address respecively non able to divide by 4 - It isn't a problem by software running on a PC, but some embedded CPUs can't access memory if the memory address boundary does not match!
If you use the same code - well tested on PC - for an embedded processor, and that processor has 16-bit-integer, nothing works.
Using the functions of ByteDataAccess may be a better way.
How ByteDataAccess works:
Img:../img/ByteDataAccessBase-getCmd.png
The universal super class ByteDataAccessBase
contains a pointer to the data and indices to the actual positions. This basic class provides methods such
assign(data, length) getInt16(ix) getInt32(ix) setInt16(ix, value) setBigEndian(bool)
etc.
A derived class for an application offers methods like
getCmd()
which uses for example
int getCmd(){ return getInt32(4); }
The derived class knows the position of data, relativ to the start position. The user should invoke only getCmd()
without some address or index calculations.
The method getInt32()
(for example) regards big or little endian and it is able to read from any odd address position. It is a minimal effort for
machine code (read and shift bytes), programmed and tested only one time. If that were need to programm at user level, it
is possible of course, but effort for user.
The user should invoke:
MyByteDataAccess myByteDataAccess(); //instance for example in stack myByteDataAccess.assign(data, length); int cmd = myByteDataAccess.getCmd();
That's all for beginning in comparison to the negative pattern.
Topic:.J1C_ByteD.src.
The class ByteDataAccess
was developed originally in Java. It is contained as part of the software component srcJava_vishiaBase
in the package org/vishia/byteData/ByteDataAccessBase.java
, LPGL-license. It needs less dependencies, only for the toString()
-debug output the class org/vishia/util/StringFormatter.java
is used. Furthermore org/vishia/util/Java4C.java
which defines some annotations. All this depending classes are contained in the same software component. This Software-Component
is available as zip-File see www.vishia.org/indexDownload.html.
That Java-class is provided as translation to C- and C++-source with the Java2C-Translator. The result of Java2C-translation is readable like a manual written code. The benefit: Same functionality, same
features, tested code in two languages. The C/C++-Sources are part of the CRuntimeJavalike
source pool, provided with the sourceforge.net/projects/java2c download, see also www.vishia.org/indexDownload.html.
The C-source depends on ObjectJc.c
, StringJc.c
and ExceptionJc.c
contained in the same package.
For C++ usage in an independent environment a subset of a simple class ByteDataAccess
is available.
Topic:.J1C_ByteD.child.
Especially for datagram structures in communication often a header contains basic data. Depending on the header's data some following data have a specific structure. The following data are designated usual as payload.
If a header was read with ByteDataAccess
, the payload can be read with proper derived instances of BinaryDataAccessBase
. To positioning the indices to the correct data, they are arranged as child of the parent, whereby the parent is used to read the header.
How children work:
+----'----+----'----+----'----+----'----+----'----+----'----+----'----+----'----+---- | | | c m d | child's data bytes | ...further child +----'----+----'----+----'----+----'----+----'----+----'----+----'----+----'----+---- ^ ^ ^ | | | [idxBegin getCmd(); idxFirstChild ] | ' : instance for the head data | | [idxBegin getChildData(); ] | | [idxBegin ... ]
The user should program:
MyHeadAccess headAccess; //instance for head data MyChildAccess childAccess; //instance for a child MyChildBAccess childBAccess; //instance for another type of child data headAccess.assign(data, length); int cmd = headAccess.getCmd(); if(cmd == expected) { headAccess.addChild(childAccess); data = childAccess.getChildData(); ... } else if(cmd == anotherExpected) { headAccess.addChild(childBAccess(); dataB = childBAccess.getData(); childBAccess.addChild(childXY); //...substructure of the child. }
Children can consist of its children etc. Nesting any desired.
Adding a child to this, the idxCurrentChild
is set to idxFirstChild
(after head data) or after the yet current child. Adding a child to the child the parent of parent is too: Its idxEnd
is set to the current end of all grandchildren and the idxCurrentChildEnd
is set correctly. For this operations some methods should be implemented by the derived classes:
Topic:.J1C_ByteD.PtrVal.
Hint for C/C++-usage:
The argument data
is an Byte-rray in Java (byte[]
). In C it is a array of bytes too:
int8_t data[1400];
The reference data
consists of 2 elements: The int8_t*
-pointer and the number of elements of that array. It is defined as PtrVal_int8
type:
typedef PtrVal_int8_t { int8_t* ptr__; int32_t value__; } PtrVal_int8;
Because that type PtrVal_int8
is used as argument type of methods, it is a call by value. But this struct of 2 primitive types are implemented as register operation often, by a well optimized compiler.
Like in Java, both the reference to the array and the length of the array is stored in one composite data structure. That helps to minimize errors. A PtrVal_int8 data element can be filled with:
int8_t __data__[1400]; PtrVal_int8 data; //it is the reference to the array. data.ptr__ = & __data__[0]; data.value__ = sizeof( __data__) / sizeof(__data__[0]);
An application may (should) use only the data
which contains the pointer and the length.
Topic:.J1C_ByteD.sizeHead.
An instance of ByteDataAccessBase
of a defined type has a specified length of the head data. It may be 0 if head data are not defined. The value of the sizeHead
element of ByteDataAccessBase
should be set on initializing and not changed. An older version of ByteDataAccess
has defined a virtual method specifyLengthElementHead
which should return a usually constant value. The newer form does not need virtual methods, which is more proper for simple
C applications.
Topic:.J1C_ByteD.expand.
They are two kinds:
evaluate a given structure of data (read),
assemble a new structure of data (write).
For read, the first key operation is:
myByteDataAccess.assign(data, length);
For write, instead call:
myByteDataAccess.assign(data);
or
myByteDataAccess.assignClear(data);
The second method sets all elements in data to 0.
An addChild
-operation is suitable for both kinds of operation:
add a child to read data in a special structure
add a child to write new data.
The difference of both operation is the handling of the idxEnd
value:
For read operations the idxEnd
is set on assign(data, length)
.
For read operations any child's idxEnd
is set on on addChild(...)
to the idxEnd
of the parent. It means, any child my have that data.
for read operations the element bExpand
is set to =false
.
For write operations the idxEnd
referes the position after the head data initially, use assing(data)
to initialize a ByteDataAccessBase
for writing.
For write operaitons the idxEnd
will be expand on any addChild(...)-operation. It is done by the protected routine
expand(). The expandation will be executed for any parent of child too.
For write operations after adding all children the parent will contain the correct idxEnd
for the whole structure.
Reading:
The assignment of data supplies the length, usual the number of the received data bytes. That determines the ixEnd
.
Adding a child sets the child's index, the idxEnd
is transfered to the child too. There is a method
if(sufficingBytesForNextChild(sizeNextChild){ ....
and a method
int availdata = getMaxNrofBytesForNextChild();
which returns the number of bytes in the spread between ixChild ... ixEnd
to check whether a child is possible to add to check the data:
//code snippet: while(sufficingBytesForNextChild(knownSizeOfChild)) //check whether there are data for this child { parent.addChild(theChild); theChild.getSomething(); //and check it. }
Writing:
The assignment of data supplies only the length of data, stored in the association data
. The ixEnd
is set to the end of the head of this ByteDataAccess-derived type. The data should be set to 0 before this operation is done
or assignClear(data)
can be used to set the data to 0.
Adding a child expands ixEnd
both from the child and all of its parent. Therefore
int nrofBytesToSend = parent.getLength();
can be used to quest the number of bytes which are stored lastly.