ByteDataAccess in Java and C

ByteDataAccess in Java and C

Inhalt


Topic:.J1C_ByteD.

.


1 principle

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:

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 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.


2 Availability of sources for ByteDataAccess

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.


3 Children

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:


4 The data reference

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.


5 The sizeHead value

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.


6 Read and write access, expand

Topic:.J1C_ByteD.expand.

They are two kinds:

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:

The difference of both operation is the handling of the idxEnd value:

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.