ClassJc_en.html: Reflection and Types

applstdef_emC_h.html …​ Why variants are necessary

1. What is ObjectJc

The struct ObjectJc is a basic struct that can be used for all data instances (struct and class) with some common maybe important information with less effort.

To use ObjectJc the application should

#include <applstdef_emC.h>

Depending on the compiler switches in applstdef_emC_h.html

/**Define the granularity of the ObjectJc base class: */
//#define DEF_NO_ObjectJc_emC
#define DEF_ObjectSimple_emC
#define DEF_ObjectJc_SYNCHANDLE
#define DEF_ObjectJcpp_REFLECTION
#define DEF_ObjectJc_OWNADDRESS
//#define DEF_ObjectJc_LARGESIZE

/**Define of the offering of Reflection information: */
//#define DEF_REFLECTION_NO
#define DEF_REFLECTION_SIMPLE
//#define DEF_REFLECTION_OFFS
//#define DEF_REFLECTION_FULL

different approaches for ObjectJc are possible.

1.1. Simple application - without usage of specific headerfiles

If DEF_NO_ObjectJc_emC is defined, you don’t need any emC/Base/Object*.* in your sources. It means it is for startup or for simple applications.

The struct ObjectJc is nevertheless defined in emC/Base/applstded_common.h also with some interesting features:

typedef struct  ObjectJc_T { int identSize; } ObjectJc;

as well as the type / reflection class

typedef struct  ClassJc_T { int id; } ClassJc;

Because this types are used in some emC sources and maybe also in the application sources, it is not possible to prevent it. But the content is reduced to only one integer value, 16 bit for cheap controller, which is also usable for a proper identification number. That should not be a disadvantage.

All relevant macros which are used in all sources of emC are properly defined, usual empty. Hence all sources of emC are able to compile.

There are some ObjectJc-relevant simple operations, which are nevertheless useable:

  • set…​ / isInitialized_ObjectJc(…​): to detect a successfully intialization, important for distinguish init- and run mode. See chapter The isInitialized_ObjectJc(…​) state of an object

  • setReflection_ObjectJc(…​, ID) / getID_ObjectJc(…​): This both routines sets and gets a numeric ID to recognize a specific instance. The instance can only have 10 bit, from 1..1023.

  • lock / islocked / unlock_ObjectJc(…​): See chapter Simple lock in Interrupt / backloop systems. This is interesting and maybe necessray especially in simple Interrupt / backloop applications.

  • synchronized / wait / notify_ObjectJc: Also this features are supported, but only for experience with no more as three mutex instances, see chapter Mutex, data access synchronization between threads and following.

The ObjectJc-relevant functionality for size and instance check are not given. This is regarded in tests, see especially Test_emC/src/test/cpp/emC_Test_ObjectJc/* The adequate operations returns always true. It means they can be used, for compatibility, but not with really checking capability.

1.2. Using ObjectSimple_emC.h

If the compiler switch

#define DEF_ObjectSimple_emC

is set, the emC/Base/ObjectSimple_emC.h defines the ObjectJc class depending of some more switches:

  • either as simplest if no other switch is defined:

    typedef struct  ObjectJc_T
    { uint32 identSize;
    } ObjectJc;
  • with an own 16 bit field for synchronization handle if DEF_ObjectJc_SYNCHANDLE is set:

    typedef struct  ObjectJc_T
    { uint32 identSize;
      uint16 offsetToInstanceAddr;
      uint16 handleBits;
    } ObjectJc;

    in this case the offsetToInstanceAddr is unnecessary, left 0.

  • if DEF_REFLECTION_NO is not set, including a reference to a type information ClassJc:

    typedef struct  ObjectJc_T
    { uint32 identSize;
      uint16 offsetToInstanceAddr;     //optional depending on DEF_ObjectJcpp_REFLECTION
      uint16 handleBits;               //optional depending on DEF_ObjectJc_SYNCHANDLE
      struct ClassJc_t const* reflection;
    } ObjectJc;

    The other both defines can be set. Especially for C++ an offset from the position of the ObjectJc struct in a class and the start address of the class is necessary. For C-usage this is anytime 0.

    For the DEF_ObjectSimple_emC the reflection information contains either only the type with optional one base class, or a so named Reflection-offset-Field, see ClassJc_en.html#DEF_REFLECTION

  • if DEF_ObjectJc_OWNADDRESS is set, the last element is a reference to the instance itself:

    typedef struct  ObjectJc_T
    { uint32 identSize;
      //optional offsetToInstanceAddr, handleBits, reflection see above
       void const* ownAddress;
    } ObjectJc;

    The ownAddress is usefull for a data dump to detect the address of data for references. The address of itself can be used if data are copied to any file (a memory map), and references between the data should be readjusted.

    Depending on the memory layout the reflection and the ownAddress has 2 Byte (in 16-bit-Systems), 4 Byte or 8 Byte (for 64-bit-Adressing). In all cases the alignment is correct. Note that 64-bit-addresses should aligned to a memory word boundary wich is usually 8 Byte.

The idea for ObjectJc came from Java. In Java all instances have a base ('super') class java.lang.Object with adequate information. It is a proven concept.

The simplest form contains in only 32 bit:

  • An identifier for the type, important for type check on downcast.

  • Information about initialized status: init or run, one bit. An instance should be switched to the run mode if all aggregations are complete.

  • Information whether it is an head of an array

  • A lock bit. This lock information can be used for example for a polling of the lock state in an interrupt.

  • Size information.

For these pieces of information only 4 Byte (an uint32_t) is necessary;

//in src_emC/emC/Base/ObjectSimple_emC.h:
typedef struct  ObjectJc_T
{
 /**The identSize is helpfull to recognize the instance.
  * The bit31 is used to detect whether it is initialized or not. */
 uint32 identSize;
 #define mInitialized_ObjectJc  0x80000000
 #define mArray_ObjectJc        0x40000000
 #define mLocked_ObjectJc       0x20000000
 #define mInstanceType_ObjectJc 0x1fff0000
 #define kBitInstanceType_ObjectJc 16
 #define mSize_ObjectJc         0x0000ffff

For this basic features the type identification has 13 bits, which can distinguish between 8191 types qualified by a number. With this information RTTI (Run Time Type Information) for a qualified checked dynamic cast<…​> may be unnecessary, a normal cast can be done with the user programmed simple type check, for example for data variants. Note that casts may be a source of programming errors and/or unreliability. A check is usual a good decision. The disadvantage, definition of the type-identification number in the users sources, can be seen as advantage, because the numbers can be synchronized over more applications in a project.

The possible additionally reflection information, also available for the ObjectSimple_emC.h usage, offer more possibilities (allow check also of a base class, also possible to use for C++ derivation), and it enables using the 13 Bits instead for the type for an instance identification. Hence the mask mInstance_ObjectJc is given:

  #ifndef DEF_REFLECTION_NO
    #define mInstance_ObjectJc   0x1fff0000
    #define kBitInstance_ObjectJc 16
    /**The reference to the type information. */
    struct ClassJc_t const* reflection;
  #endif
} ObjectJc;

The here shown ClassJc doesn’t contain information about fields, for the simple variant, only the type but basic types too. See also ClassJc_en.html

typedef struct ClassJc_t
{
  /**Index of the class in the reflectionOffsetArrays array if DEF_REFLECTION_OFFS is used,
   * elsewhere a simple Type ID. It may be used to compare types in different executables
   * (via connections). */
  uint32 idType;

  #ifndef DEF_NO_StringUSAGE
    char const* name;
  #endif

  #ifdef DEF_REFLECTION_OFFS
    /**Refers the table with offset and type of the elements of this class. */
    int32 const* reflOffs;
  #endif
  struct ClassJc_t const* superClass;
} ClassJc;

The ClassJc contains a textual name, it is proper for debugging. For small embedded systems with less memory all textual information can be prevented. For test of this software on PC or usage in a larger target hardware it can be present.

1.3. Using ObjectRefl_emC.h

The file ObjectRefl_emC.h offers a more complex system:

The header file <emC/Base/ObjectSimple_emC.h is also included on a proper position the struct ObjectJc is defined there, it means with the same capability.

But

  • reflection can be defined in a more complex way, see above.

  • the usage of the size can be more fine gradual bit usage for the size. The size for some instances can be more as 64k: This depends on the compiler switch DEF_ObjectJc_LARGESIZE in the applstdef_emC.h:

    #define DEF_ObjectJc_LARGESIZE
  • 4095 instances(0x0fff) with < = 64 kByte,

  • 255 instances (0x0ff0) with < = 1 MByte,

  • 31 instances (0x1F00) with < = 16 MByte

This are the bit ranges with a designation inside the identSize variable:

#define mSizeBits_ObjectJc     0x30000000
#define kIsSmallSize_ObjectJc  0x00000000
#define kIsMediumSize_ObjectJc 0x10000000
#define mIsLargeSize_ObjectJc  0x20000000

Reflection:

  • The ClassJc is defined with complete reflection information, especially fields (elements) of a struct or class.

  • Possibility to manage Mutex and Notify Objects for Multithreading operations (similar as in Java for java.lang.Object: synchronized and wait / notify) - it is only an ident numer for the Mutex and Notify Instance managed in the RTOS adaption.

  • Offset for Reflection- und ObjectJc-usage in C++

  • Address of the instance (necessary for memory images, association of data)

  • Some support for a BlockHeap concept

For usage of ObjectJc in a C++ context with symbolic information about fields in the derived class an additional offset between start of the data class and position of the ObjectJc inside the data are necessary. Additionally a field for handle values is intended here. This both fields are available if the following compiler switch is set:

#ifdef DEF_ObjectJcpp_REFLECTION
  /**Offset from the data-instance start address to the ObjectJc part.
   * It is especially for symbolic field access (reflection) in {cpp}. */
  uint16 offsetToStartAddr;
  /**Some handle bits to use an ObjectJc for lock (mutex). */
  uint16 handleBits;
  #define kNoSyncHandles_ObjectJc 0x0fff;
#endif

In this case, but also independently if not DEF_REFLECTION_NO is defined, a reference from ObjectJc to ClassJc named reflection is available, as also in the simple form of ObjectJc. This improves the type test capability, especially recognizing base type references (derivation), and it opens the possibility to support full symbolic information about the fields in the data. It is the 'reflection' capability, see ClassJc_en.html.

#ifndef DEF_REFLECTION_NO
  ....
  #define mInstance_ObjectJc   0x3fff0000
  ....
  /**The reference to the type information. */
  struct ClassJc_t const* reflection;
#endif

If a ClassJc instance can be referenced, the identifier in the first word identSize is now used as instance identifier.

2. Using ObjectJc for C struct

The usage of ObjectJc is independent of its definition (Simple, with Refl, Jc). The capability is different of course but the sources are indentically.

A C struct for C and C++ compilation should be defined as:

typedef struct MyData_T {
  union { MyBaseData super; ObjectJc obj; } base;
  int32_t anyData;
} MyData_s;
  • The usage of typedef is recommended. Some compilers expect it, it is the clarified form.

  • The MyData_T is the tag name. The tag name should not be the same as the type name, some compilers may have problems elsewhere! It can be used for forward-declaration.

    struct MyData_T;
    ....
    extern struct MyData_T anyData; //data are only declared
    .....
    struct MyData_T* ref = getRef(...)  //only use the reference without access
  • The type name MyData_s is written with suffix _s to offer the possibility for a wrapping C++ class which should be named MyData. This writing rules are regarded by ClassJc_en, chapter "The reflection generator".

The ObjectJc is arranged as the last or only one element inside a union. The other parts of the union should be base struct (super struct), whereby the immediate super struct should be arranged first, necessary for INIZ_…​ initialization with { …​ }. This writing rule enables the access to ObjectJc in an unified form independent of super struct nesting (inheritance in C) writing:

ObjectJc* obj = &myDataRef->base.obj;

For C usage it is the same as a simple pointer casting ((ObjectJc*)myDataRef) because the ObjectJc is the first part in memory. But usage pointer castings is not recommended because it is an additional (supposed unsafe) cast. Secondly it may be faulty if myDataRef is a C++ class where the ObjectJc is member of. Unnecessary casting is an example of dirty software which runs some years, then somebody extends it, and the assumption for the cast is no longer true. Hence an important rule for C-programming is: "Avoid unchecked casting of pointers!".

3. Using ObjectJc in C++ classes

It is possible and may be recommended for state variables which may be exported (serialized) to define the data of a C++ class as C-struct. Then this struct contains ObjectJc in the form above.

There are generally three forms to inherit from a C-struct:

class MyData: public MyData_s { ...

with possible access to data and immediately to myDatab→base.obj.

class MyData: proctected MyData_s {
  ...
  public: ObjectJc const* toObject() {
    return this->base.obj;
  }

It has protected access to the data, but a individual operation toObject() which returns the ObjectJc const* reference only to the read only ObjectJc data. This form does not need virtual operations for that.

class MyData: public ObjectJcpp, proctected MyData_s {
  ...
  public: ObjectJc const* toObject() {
    return this->base.obj;
  }

The interface ObjectJcpp contains the operation toObject() as virtual, hence a reference of type ObjectJcpp is generally useable to access the ObjectJc data. But this form needs virtual operations, it may not be desired in some embedded applications.

It is a question of C++ using philosophy:

  • If C++ should be used only because of some C++ language features, for example operator definition (float operator+(…​)), but virtual operations are forbidden by style guide for safety than the first or second form is appropriate.

  • For common C++ usage the third form is recommended.

See test sources, it contains some casting situations too: emC_Base/src/test/cpp/emC_Test_ObjectJc/test_ObjectJcpp.cpp.

4. Initializing of data with ObjectJc

4.1. static and const initializing with initializer list in C

To get const data in a const memory section (Flash Rom) only a const initializing can be done with an so named initializer list. Thas is the same situation in C as in C++ (!).

Type const myData = { ..... };  //hint: write const right side.
const Type myData = { ..... };  //it is the same

In C it is not possible to initialize const data in any operations in runtime, other than in C++. The immediately initializing C-style is necessary if data should be stored in a const memory section (on Flash-ROM, for embedded Processors). This topic is irrelevant for C++ programming on a PC platform,

For non const data the same initializing with an initializer list is possible for all non-allocated data (not from heap). If static data are used an initializing may be seen as recommended.

Type myData;  //The initial data are undefined - prone of error
Type myData = {0}; //at least forced 0-initialization.

An initializer list with given data is often complex to write, it is a challenge for the programmer. Macros to initialize some parts of nested data are helpful.

For example some struct may be defined as:

typedef struct BaseType_T {
  union{ ObjectJc obj;} base;
  int32 data1;
  float data2;
} Base_Type;
//
typedef struct InnerData_T {
  float x,y,z;
} InnerData;
//
typedef struct MyType_T {
  union { BaseType BaseType; ObjectJc obj; } base;
  int32 m, n;
  InnerData data1;
  int p,q;
} MyType_s;

then a initializer is complex. Especially if some types are defined in another module or component it is difficult to handle. At least for ObjectJc a macro INIZ_ObjectJc can be used. It is recommended to write such an INIZ…​ macro for any type:

#define INIZ_VAL_BaseType( OBJ, REFL, ID, VAL) \
 { { INIZ_ObjectJc(OBJ, REFL, ID) } \
 , (int32)VAL, (float)VAL \
 }
#define INIZ_InnerData( ) \
 { 3.14f, 42.0f, -3.0f }  //it is only a const initizalization
#define INIZ_VAL_MyType( OBJ, ID, VAL1, VAL2) \
{ { INIZ_VAL_MyBaseType(OBJ, refl_MyType, ID, VAL1) } \
, VAL2, -(VAL2) \
, INIZ_InnerData() \
, 0,0 \
}

This macros should be written near to the struct definitions, to see the association.

  • The arguments of the macro may have a free meaning and order But the initializing values have to be able to calculate on compile time.

  • Because the BaseType has base.obj, it uses the INIZ_ObjectJc(…​).

  • Because the BaseType is used as base type, it is necessary to give the reflection information as argument REFL here.

  • The INIZ_VAL_MyType(…​) does not need information about the data arrangement of the inner struct data. It invokes only the INIZ…​ macro of the nested data. Hence the information about the data arrangement is encapsulated.

  • The first INIZ…​ macro inside INIZ_VAL_MyType(…​) should have a REFL-argument. Because the MyType is never used as base class the reflection are not given as argument, but they are given immediately.

The INIZ_ObjectJc macro is defined depending on the variants of ObjectJc in different forms. The arguments are the same in any case. As special feature the REFL argument is used in case of DEF_ObjectJc_SIMPLE as

#define INIZ_ObjectJc(OBJ, REFL, ID)  \
{ ((((uint32)(ID_##REFL))<<kBitInstanceType_ObjectJc) & mInstanceType_ObjectJc) \
| (sizeof(OBJ) & mSize_ObjectJc) \
}

It means, the identifier for the reflection class is used as identifier for the numerical ID_refl_MyType because the simple variant of an ObjectJc has not a reference to the reflection but only the ID. The definition of an

ClassJc const refl_MyType = INIZ_ClassJc(refl_MyType, "MyType");

it not necessary and may not be given if DEF_REFLECTION_NO is set. The type-ID already stored in a also given ClassJc const cannot be used for a const initialization because it is not able to calculate on compile time:

Getting a const value from a given another const instance inside an initializer list is not possible in C and not possible for C++ const-memory-segment-initialization. The access to refl→idType fails though it is a instance defined before. It is too complex for the compiler’s initializer value calculation.

Adequate it is not possible to use the address of the instance shifted and masked for the correct bit position. An address value inside a constant initializer list is only possible by linker replacement, the address value can only be set as const reference as a whole from the linker. Some numeric calculations afterwards cannot be done with it because they would need to be done by the compiler.

4.2. CTOR_/ctor_ObjectJc(…​) - initialization on run time

The ObjectJc part on an instance is the core part but it contains information for the whole instance: The type and size. Hence it should be initialized firstly with respect to the instance:

MyType_s data;
CTOR_ObjectJc( &data.base.obj, &data, sizeof(data)
             , refl_MyType, ID_Obj);

It uses the first argument as argument to the ObjectJc part inside the data, and the second argument as void* instance pointer. This is necessary for C++ usage in derived classes, where ObjectJc is not on top of the data.

Then the construction of the instance can be done:

ctor_MyType(&data, 42.0f, 234);

The CTOR_ObjectJc is a macro which regards DEF_REFLECTION_NO. In this case it uses the given identifier for the reflection type and invokes

ctor_ObjectJc(&data.base.obj, &data, sizeof(data), null, ID_refl_MyType);

It does not assume the existence of a ClassJc instance. But the ID_refl…​ should be defined, see ClassJc_en.html#ID_refl. But if a ClassJc instance is given anyway, the

ctor_ObjectJc(&data.base.obj, &data, sizeof(data), refl_MyType, ID_Obj);

can be used. In case of DEF_ObjectJc_SIMPLE the type-ID is taken from the ClassJc instance and the ID_Obj is not use.

The constructor of the user types should not invoke the ctor_ObjectJc(…​). Instead a check of consistence can be done, which assures that the given instance has a proper size and the given type is matching. This can be done as assertion:

ASSERT_emC( CHECKstrict_ObjectJc( &thiz->base.obj, sizeof(*thiz)
                                , refl_MyType, 0)
          , "not matching instance and type", 0,0);

It has the advantage that the check-code is not existing if ASSERT_IGNORE_emC is set. On embedded platforms usually the construction is done only on startup. The startup can be tested well on PC platform with assertion check and with Exception handling, so errors are detected on PC-test.

The CHECKstrict_ObjectJc(…​) is a macro again which invokes in case of DEF_REFLECTION_NO:

checkStrict_ObjectJc(OTHIZ, SIZE, null, ID_##REFL)

It does only test, an Exception is thrown only in conclusion with ASSERT_emC.


Some details to the arguments both for ctor_…​ and CTOR_…​

  • REFL For the reflection argument (See chapter "Reflection and Types").

  • ADDR The second argument of the ctor ADDR is expected of type void* and should be the address of the instance itself. It has the same value for C-compilation as the ObjectJc reference because ObjectJc is the first element in a struct. But for C++ there may be small differences between the address of the instance and the ObjectJc data part. This is if inheritance and virtual tables are used. The difference between both address values are stored in the ObjectJc::offsetToInstanceAddr, which requires setting DEF_ObjectJcpp_REFLECTION. If it is not set but DEF_REFLECTION_FULL is set, and C++ compiling is used, then an compiler error message is forced (#error …​). The offsetToInstanceAddr is necessary to access data via reflection (FieldJc). Hence in C++ this form of initializing should be used. The initializer list is not suitable for use.

  • SIZE: The ObjectJc part stores the size of the whole instance. Hence it can be tested only with knwoledge of the ObjectJc reference whether a safe access to memory is possible with a given reference. Faulty pointer castings can be detected on runtime. It is important that the memory bounds are resepected. Data error because of software errors are acceptable (can be still found), but memory violations causes dubios behavior and ard hard to debug.

  • ID The last argument ID of the CTOR_ObjectJc-Macro is not used in case of DEF_ObjectJc_SIMPLE because the type-ID is stored in the only one identSize element. But if the ID contains the mArrayId_ObjectJc flag bit, it is set in the ObjectJc. It is necessary for the ObjectArrayJc which contains the reflection reference to the elements, not for the whole instance. Note that the size information in the ObjectJc part is for the whole array, it is always for the whole instance.

4.3. Initializing for C++

In C++ allocation and construction are combined. It is true in both kinds of creation:

MyData* data = new MyData(...);
MyData data(...);  //ctor is invoked with data definition

That is a consequent C++ feature and prevents errors because of non-initialized data.

The concept of the ObjectJc as core part which contains information about the whole instance seems to be primary not regarded. Some special constructs and style guides are necessary:

typedef struct MyPlainData_T {                  //Data in C manner, plain
 union { ObjectJc obj; } base;             //with ObjectJc as core
 int32 d1; //:Any data
 float d2;  //Note: padding any struct to 8-Byte-align if possible,
} MyPlainData_s;
class MyBaseClass: protected MyPlainData_s      //contains ObjectJc as core
{
  public: MyBaseClass(int idObj);
  public: MyBaseClass(ObjectJc const* othis = null);
}
class MyClass: protected MyBaseClass      //contains ObjectJc as core too
{
  public: MyClass(int idObj);
  protected: MyClass(ObjectJc const* objectJc);
}

The ObjectJc should contain the size of all data, but only of the plain data, except organization data of C++ (vtable pointer). It should be initialized firstly, because the ctor of C data parts may check the size and type. To initialize ObjectJc firstly, its CTOR_ObjectJc(…​) should be called fistly. This is done in the following way:

  • The public constructor which should only act as instance constructor have not an ObjectJc* argument but it may need an argument for the idObj, the ident designation. It should call CTOR_ObjectJc(…​) with size and reflection argument of the whole instance, from the C++ class.

    • either in the argument preparation of the base constructor to fullfil its ObjectJc* argument,

      MyClass::MyClass(int idObj)
      //firstly call the base ctor in C++ syntax:
      : MyBaseClass( CTOR_ObjectJc(&this->base.obj
                     , this, sizeof(*this), refl_MyClass, idObj))
      {
        //...further special init, for this derived data ...
      }
    • or, if has not a further C++ base class, it should call CTOR_ObjectJc(…​) in the body of the constructor.

      MyBaseClass::MyBaseClass(int idObj)
      { CTOR_ObjectJc(&this->base.obj, this, sizeof(*this), refl_MyBaseClass, idObj);
        //...further init, especially call the plain data constructor in C manner:
        ctor_MyPlainData(&this->base.obj);
      }
  • A constructor able to use as base constructor should have an argument ObjectJc* objectJc. It should be protected to designate it as base class constructor. Or, to simplify it may have a null as default value:

    • Either this class has a further C++ base class, then it forwards it:

      MyBaseClass::MyBaseClass(ObjectJc const* othiz)
      : MyBaseBase(othiz)
      { //...further init
      }
    • Or it has not a further C++ class. Then it should check (assert) whether the given othiz is proper:

      MyBaseClass::MyBaseClass(ObjectJc const* othiz)
      { if(othiz == null) { //assumes that this class is the instance class:
          CTOR_ObjectJc(&this->base.obj, this, sizeof(*this), refl_MyBaseClass, idObj);
        } else {
          ASSERT_emC(othiz == &this->base.obj, "C++ ObjectJc initialization error", 0,0);
        }
        //...further init, especially call the plain data constructor in C manner:
        ctor_MyPlainData(&this->base.obj);
      }

4.4. The isInitialized_ObjectJc(…​) state of an object

This is not related to the rest of the chapter above. The operations

  • setInitialized_ObjectJc(ObjectJc* othiz)

  • isInitialized_ObjectJc(ObjectJc* othiz)

are related to the instance data, not to the core data of the ObjectJc struct. The setInitialized_ObjectJc(ObjectJc* othiz) should be called if the instance is proper to work. Then also isInitialized_ObjectJc(ObjectJc* othiz) returns true.

Usual software have an 'initialization' phase after construction. That is used in praxis but to less mentioned in the ObjectOrientation theory. Basic data should be calculated, and especially all aggregations should be set.

If an application has aggregations only as a tree (there is a correct order which classes refer which other) then aggregations can all set only in the constructor. This is the best and maybe theoretical correct case. It is especially supported for example in Java by using final references. But such a system does not allow aggregations between instances one another or circular. Such is defacto often necessary. To succeed theoretical approaches sometimes UML tools handle such situations as aggregation only for one direction, and the other direction is an association. But this is not the desired truth. The aggregations are one another or circular in fact. They are aggregations because they are never changed in runtime.

For that the 'initialization' phase after construction is necessary. In this phase all aggregations are set of all known and hence constructed instances, and maybe some values are calculated using the associations, for example derived parameters of controlling. If this is done, then the initialization should be ended with setInitialized_ObjectJc(ObjectJc* othiz) for the appropriate instance. For value calculations you may test isInitialized_ObjectJc(ObjectJc* othiz) and get values only from already initialized instances. Because even the success of initialization may depend on a succeeded initialization of another object, and the order of initializations cannot be pre-defined, this should be done in a while loop. If all instances have the data isInitialized_ObjectJc(ObjectJc* othiz), checked in this while-loop, the while loop for initialization can be left, initialization is finished.

The situation that the successing initialization depends on the succeeded initialization of another instance and vice versa is of course a design error. Then the loop is endless. It should be checked by a maximum of loops, which is usual less (1..3.. 10) or at maximum the number of instances if the order of initializing is stupidness exact reverse to the necessary one.

Not that getting the reference to an instance does not need the succeeded initialization of this instance. Elsewhere one another or circular aggregations will not be possible. But using values for initial calculation of the own values should be gotten only from initialized instances. For that a tree approach is necessary.

5. ALLOC_ObjectJc()

The macro-wrapped function call of

ALLOC_ObjectJc(SIZE, REFL, ID)

is for C-usage or for creation of non-class-data based on ObjectJc in C++. Depending on DEF_ObjectJc_SIMPLE it expands either / or to

allocReflid_ObjectJc(SIZE, ID_##REFL, ID, _thCxt)
allocRefl_ObjectJc(SIZE, &(REFL), ID, _thCxt)

adequate to CTOR_ObjectJc in the chapter above. Additionally it requires the pointer to a Thread context see Stacktrace, ThreadContext and Exception handling because generally the allocation can fail, then an Exception handling is recommended.

The core prototype with reflection reference is

extern_C ObjectJc* alloc_ObjectJc ( const int size, const int32 typeInstanceIdent
                struct ClassJc_T const* refl  , struct ThreadContext_emC_t* _thCxt);

This routine allocates and initializes the core data of the ObjectJc.

6. Using of the ObjectJc data

6.1. Type check for casting and safety

In classic C programming, sometimes in C++ too, often a pointer is stored and/or transferred as void*-pointer if the precise type is not known in the transfer or storing environment. Before usage a casting to the required type is done. But such casting turns off the compiler error checking capability. An unchecked cast is a leak for source safety. A void* pointer should only be used for very general things. For example for memcpy.

In C++ some casting variants are present. The static_cast<Type*>(ref) checks on compile time whether the cast is admissible in an inheritance of classes, and adjusts the correct address value toward the start address of the part inside the instance which is adequate to the given type. If there isn’t an inheritance relation between the given type and the cast destination type, it forces a compiler error. But the static_cast<Type*> does not check the really given instance on runtime. On downcast (toward to a derived class) it assumes that the instance is of this type. A upcast (toward to the base class) is always true.

It means the static_cast<Type*>(ref) can cause runtime errors if the assumption of the instance type is false.

The dynamic_cast<Type*>(ref) does the same for 'downcast', but additionally the type is checked. This requires activation of RTTI (RunTime Type Information). If the type is faulty, either a null pointer is delivered or an Exception is thrown, depending on the compiler version.

The reinterpret_cast<Type*>(ref) or a ordinary C-Cast (Type*)(ref) delivers faulty results if it is used for inheritance class Types. It is an lackadaisical programming error to use reinterpret or C casts for class inheritance. Such an error is inconspicuous so long as no virtual operations are present. Unchecked or lax usage of C-casts or reinterpret-casts are a prone of error. Because a simple C-cast can be used by accident, a C++ compiler emits a warning. To assure compatibility between C and C++ a macro CAST_C(Type, ref) is defined in emC/Base/types_def_common.h which is adapted for C++ to a reinterpret_cast<Type*>.

In C only the known (Type*)(ref) written via macro as C_CAST(Type*, ref) is available. The capability of static and dynamic casts are only necessary in respect of class hierarchie in C++. The problem is the same: Unchecked or lax usage of C-casts or reinterpret-casts are a prone of error.

Independently of the question C or C++ or with or without RTTI the ObjectJc base class delivers the type information. It works for C++ too either using the ObjectJcpp-Base class or with immediate access to the C data which contains ObjectJc. The type check can be done with

extern_C ClassJc const refl_MyType;
.....
bool bTypeOk = instanceof_ObjectJc((&myDataObj->base.obj, &refl_MyType);

This routine recognizes and returns true for a base type too if a type information is available using DEF_REFLECTION_…​ not NO. The base type is referred in the ClassJc instance referred as reflection type information. See Variants of emC-usage in Applications - chapter ClassJc and Reflection. Note that for class inheritance in C++ with multiple inheritance or with virtual operations a static_cast<TYPE>(OBJ) has to be used for cast because addresses should be tuned. For C inheritance using a base type struct as first element of the inherited struct of course a C_CAST(TYPE, OBJ) is only possible and necessary.

The cast seems to be safe and might not be necessarily be tested if the type is known in the user programming environment, because the same software module stores the instance pointer, and gets it back. But there may be programming errors, if the algorithm is enhanced etc.etc. Hence it is recommended to check the type too, but with an assertion, which can be switched off for fast runtime request. With a side glance to Java the type is always checked on runtime for castings. In Java a casting error is never possible. For that the reflection info in java.lang.Object is used. Because castings are not the operations most commonly used in ordinary programs, a little bit of calculation time is admissible for that.

The type check only as safety check, as assertion should be written as:

ASSERT_emC(INSTANCEOF_ObjectJc((&myData->base.obj, reflection_MyType))
              , "faulty instance", 0, 0);
MyType* myData = C_CAST(MyType*, myData);
  ...

The assertion ASSERT_emC(…​) macro is empty if assertions are not activated, for fast real time execution. If assertions are activated, it causes a THROW, see ThCxtExc_emC.html. It means the program is continued on the CATCH statement in a calling level, not aborted as assert() of standard C/++.

The C_CAST is an reinterpret_cast for C++ usage and a normal ((MyType*) myData) for C usage.

The reflection_MyType is the type information, see next chapter.

If the type of an instance is really unknown, especially if a base reference is delivered and the derived type should be a point of interest, the

if(INSTANCEOF_ObjectJc&myData->base.obj, reflection_MyType) {
  MyType* myDataderived = static_cast<MyType*>(myDate);
  ...

can be a part of the functional code. This example shows a C++ class reference where obj is member on.

For DEF_ObjectJc_SIMPLE whereby ObjectJc contains only an int32 value only the really instance is able to check. If the instance in this example is derived from MyType the INSTANCEOF_ObjectJc(…​) returns false though the instance has MyType as base class. It is a restriction, but nevertheless often useful. If at least DEF_ObjectJc_DEFLREF is defined and the reflection are generated via DEF_REFLECTION_FULL all information of base types are contained there. Then INSTANCEOF_ObjectJc(…​) returns true also for base classes. Ot is a question of effort and a question of necessities in the application.

6.2. Lock, mutex, notify

6.2.1. Simple lock in Interrupt / backloop systems

There is a very simple functionality for locking basically supported by ObjectJc:

//any algorithm for example in interrupt:
  if(!islocked_ObjectJc(&myData->base.obj) {
    lock_ObjectJc(&myData->base.obj);
    //consistently access to data
    unlock_ObjectJc(&myData->base.obj);
  } //if locked

This is a mechanism for polling. It needs only one bit in the ObjectJc data, no mechanism of an Operation System. The principle is:

  • The consistency of data is requested.

  • A simple application usual with interrupt and main loop without Operation System is given.

  • The data access can be omitted, then new data are not checked and used. It’s admissible.

The usage is:

  • If the data are attempt to change in the backloop or a lower prior interrupt, this interrupt locks firstly. If the higher using interrupt comes in that time, it detects the lock state and prevents the access.

    After writing new data the unlock frees after a short time. The next higher interrupt can access.

  • If the data should be written in a higher prior interrupt, the reading in a lower level locks it. Then the higher interrupt do not write new data. It writes the new data usual in the next cycle.

Often, this approach is possible. The advantage is, no more support from an operation system is necessary.

6.2.2. Mutex, data access synchronization between threads

Mutex means "mutual exclusion". It is a known mechanism to access data controlled by an operation system. The necessity is: If a data portion is locked (have a mutex or monitor), then another access should be prevented. This is done with waiting for access. Often the term "Semaphore" is used for that. It is really like a Semaphore on the railway: The train should wait till the track is free. In Operation Systems there are traditionally similar and multifaceted operations which hinder a compatibility of the application software for different Operation Systems. The next disadvantage is, the association between a mutex or monitor or a semaphore to the relevant data are not immediately, so mistakes are possible.

In Java this problem was solved with a simple approach already in the first versions:

synchronized(theData) {
  theData.set...(...)
}

The important decision is: The mutex is related to the data. The base class java.lang.Object contains the capability of the mutex. Of course it uses the underlaid Operation System organized from the Java Runtime Environment. In this kind the binding of mutex to the data is clarified. This is the first idea. The second important idea is, that the mutex or monitor or semaphore is wrapped with this synchronized operation. And the third is, that there is a program block enclosed by the curly braces. Hence the freeing of the mutex cannot be forgotten or held a too long time.

This system is transferred to he ObjectJc class for C/++ programming to ensure also a compatible access to the operation system:

synchronized_ObjectJc(&myData->base.obj); {
  myData->... //access
} endSynchronized_ObjectJc(&myData->base.obj);

Of course the curly braces here are only a style guide. In Java it is controlled by the compiler. But it’s the same style.

For synchronization or mutex / monitor / semaphore of course there is an instance of that necessary. This is not part of the ObjectJc base data. It will be too much, and not compatible. Furthermore, the most data based on ObjectJc don’t need a mutex. Hence, the solution is: Threre are only a few bits, 12 for the full capability if DEF_ObjectJc_SYNCHANDLE is set, enough for 4094 different mutex / monitor / semaphore instances. If an ObjectJc basing instance was one time associated to a mutex instance, then it is for all time. Usual the mutex is used repeatedly or cyclically. It means there is only one time an effort to get the instance from the Operation System, and the consistency is guaranteed. The limitation is, the application should have no more as 4094 mutex instances. But that seems to be really enough.

For a more simple system, either using the DEF_ObjectSimple_emC or DEF_NO_ObjectJc_emC there are only 3 instances for mutex possible (2 bit in the only one identSize element). But it is supposed that a system using threads can also use DEF_ObjectJc_SYNCHANDLE. It means this 2 bits are only for test and compatibility.

The implementation of the both synchronized_ObjectJc and endSynchronized_ObjectJc operations are part of the OSAL - "Operation System Adaption Layer" and should be written proper to the given Operation System. Sometimes an Operation System offers semaphore with more as one level or such, this seems to be over engineered, not necessary. If it might to be necessary, than with specific programming, which usual prevents the compatibility (the multi platform idea of emC).

6.2.3. Wait and notify

The "wait - notify" is another approach often implemented using semaphores. It is also systematized in the first Java versions with:

synchronized(myData) {
  myData.setWaiting(true);
  myData.wait(timeout);
  myData.setWaiting(false);
}
....
//other thread:
synchronized(myData) {
  if(myData.isWaiting()) {
    myData.notify();
  }
}

As you see on this principally examples, the problem is a little bit more complex. If it is solved using manual programming by semaphores, sometimes not all possibilities are considerate. So software fails after a longer time, if accidently situations comes together. This is also cared by the Java solution:

  • The establishing of a wait state should be a mutual excluded operation, hence wrapping in synchronized is necessary.

  • Sometimes notify is bad, it the other process is not waiting. Then unclarified situations can be occur. Hence it is advisable to set the wait state able to quest in user level. May be also more data for notification should be set in this condition.

This proper and compatible (multi platform portable) solution is also supported by ObjectJc:

synchronized_ObjectJc(&myData->base.obj)); {
  myData->stateWait = val;
  wait_ObjectJc(&myData->base.obj, 100);
  myData->stateWait = 0;
} endSynchronized_ObjectJc(&myData->base.obj);
....
//other thread:
synchronized_ObjectJc(&myData->base.obj)); {
  if(myData->stateWait !=0) {
    notify_ObjectJc(&myData->base.obj);
  }
} endSynchronized_ObjectJc(&myData->base.obj);

As shown in this basically example also the timeout is used. This is often a proper idea to held the system running. After timeout there can be checked some conditions for example to abort the thread because missing a resource in the whole system. If the waiting has not a timeout, usual the reaction in the application may have deadlock situations, the application may hang.

Also the wait_ObjectJc as the notify_ObjectJc is programmed in the OSAL, in an Operation System specific kind.

6.3. Plain Data exchange

Instances have a 'state'. The 'state' is contained in several elements of the instance. It can be the state variable of a state machine, the value of a controller intergral part or such other.

If there is any complex 'situation' in an application, the analyzes of the state variables can help to explore what was happen. This can be done off line later, after the occurrence and fast fixing the situation.

It can be helpfully to have a snapshot of the data on time of the situation. Then it is possible to load the snapped data in a simulation of the same software on PC, or a special data exploration software, which contains the same instances but maybe with special relationships.

  • Data from C++ classes cannot be copied because it contains virtual pointer etc.

  • Hence it is proper to separate the state data from the rest of the organization data. The state data should be defined in a struct which is based on ObjectJc.

  • In the exploration software the instances can be created and wired (aggregation) on startup.

  • Then the state data are copied into from the snapshot of the data on the occurred situation. If the data contain associations one another, this addresses should be adjusted.

  • To recognize which instance were associated together in the originally snapped data, the element ownAddress helps. An association (variale connection) is a state, whereby an aggregation or composition (UML) is invariant, not a state. Hence the last one can be part only of the C++ class data. Associations may be part of the state data, its addresses should be adjusted with the other memory addresses using the ownAdress information.

  • To check whether the data are compatible (version) the size and reflection helps. It is stupid if the exploration software uses another version as the snapped data from a maybe older device which another, older version.

  • The ObjectJc contains the head information for the data to correctly support exploration.

7. Reflection and Types

In the full capability of ObjectJc reflections contains symbolic information for all data elements. A reflection instance of type ClassJc contains the type information, all base type information and the fields and maybe operations (methods) too. With the information about base types (super types) the instanceof_ObjectJc(…​) can check whether a given instance is proper for a basic type too. The construction of full reflection is described in ClassJc_en, chapter "The reflection generator".

For simple capability of ObjectJc use-able in embedded platforms maybe without String processing with fast realtime or less hardware resources there are four variant forms of reflections:

  • a) In the simplest form, only an idType is stored which is contained in the ObjectJc instance too to compare it. In this case the ClassJc is defined as:

    typedef struct ClassJc_t {
     int idType;   // sizeReflOffs;
    } ClassJc;
  • b) Reflection access with Inspector target proxy. In this case reflection data are generated in form of positions of data in a struct and a number (index) of any struct type. In this case the ClassJc is defined as:

    typedef struct ClassJc_t {
     int idType;   // sizeReflOffs;
     //
     int const* reflOffs;
    } ClassJc;
  • c) The reference reflOffs refers to the generated reflection data. As the reflection data are defined in succession in a "const" memory area, the low 16-bit of this pointer address can be used as a type identifier.

  • d) No Reflection access, DEF_REFLECTION_NO is set: The reflections are only defined to have information about the type:

    typedef struct ClassJc_t {
     int idType;   // sizeReflOffs;
     //
     char const* nameType;
    } ClassJc;

The nameType is optional depending on DEF_NO_StringJcCapabilities. See org/vishia/emC/sourceApplSpecific/SimpleNumCNoExc/ObjectJc_simple.h

The kind to build the idType depends on some possibilities on initialization of the reflection_…​Type instance and can be defined by the users programming. For example additional information, which can be used for debugging, are given outside a fast realtime and low resource CPU, the idType is a simple index. It is important that the idType of all reflection instances are unique. The instanceof_ObjectJc(…​) compares only the idType given with the reflection…​ argument with the type information in ObjectJc. It is the low 16 bit of idInstanceType for the simple ObjectJc.

For the reflection with full capability see ClassJc_en.html.

8. Using of the sources for ObjectJc variants in Projects

If the emC approach should be used also in less and new projects. Firstly it may be recommended to use only a subset, not all possibilities. It is too much for starting. For example the reflection approach may be a novelty, which shouldn’t impose on a new user. Hence, the simple form of ObjectJc (see chapter What is ObjectJc) can be used firstly. It is defined in the header and source files emC/Base/ObjectSimple_emC.*

For complete usage of the capabilities of ObjectJc the files emC/Base/ObjectRefl_emC.* can be used.

Only for usage the full capability of Java-like approaches the emC/Jc/ObjectJc.* offers more possibilities.

8.1. emC/Base/applstdef_common.h includes the correct headers

You should set the usage decision in applstdef_emC.h which is able to include in your path:

#include <applstdef_emC.h>

Inside the <applstdef_emCh.h.> the correct settings should be selected, for example:

/**Define the granularity of the ObjectJc base class: */
//#define DEF_NO_ObjectJc_emC
#define DEF_ObjectSimple_emC
//#define DEF_ObjectJc_SYNCHANDLE
//#define DEF_ObjectJcpp_REFLECTION
//#define DEF_ObjectJc_OWNADDRESS
//#define DEF_ObjectJc_LARGESIZE

/**Define of the offering of Reflection information: */
//#define DEF_REFLECTION_NO
#define DEF_REFLECTION_SIMPLE
//#define DEF_REFLECTION_OFFS
//#define DEF_REFLECTION_FULL
.....
.....

and below, after all settings on end of this file:

#include <emC/Base/applstdef_common.h>

should be included. The applstdef_common.h should be left unchange, do not change it for project specifics. This file includes if necessary:

#if !defined(DEF_NO_THCXT_STACKTRC_EXC_emC)
  #include <emC/Base/Assert_emC.h>
  #include <emC_srcApplSpec/applConv/EnhanceRef_simple.h>
  #include <emC/Base/Exception_emC.h>
#else
  ....
  ....
#elif defined(DEF_ObjectSimple_emC)
  #include <emC/Base/ObjectSimple_emC.h>
#else
  #define DEF_ObjectJc_FULLCAPABILITY   //to compile content of ObjectRefl_emC.c
  #include <emC/Base/ObjectRefl_emC.h>
#endif

It means for the simple case nothing more is included and necessary. For working without full reflection capability only the ObjectSimple_emC.h should be present with the ObjectSimple_emC.c including as source. If ObjectRefl_emC.h is used, it needs ObjectRefl_emC.c and some more files which’s capabilities are declared.

Adequate it is with Exception, Assertion etc.

8.2. Which sources

Refer to the test environment. The test is done for all variants.

…​TODO in test cases decide between DEF_ObjectSimple_emC to use different file sets.