1. Why variants are necessary

Software for embedded systems should be run under different conditions:

  • very fast realtime

  • small memory available

  • C or C++ usage

  • elaborately error checks

  • safety or fast

The user software itself should not be changed for this challenges, that is the concept of emC: embedded multiplatform C/C++

The variants can be controlled by:

  • a) choice of a proper include path for a given <applstdef_emC.h>

  • b) apply a specific <applstdef_emC.h> (which can contain some defines and then include one of the standard ones)

  • c) Set defines as compiler call arguments combined with a) and b)

All three possibilities of control are adequate in effect. The b) documents the selected variant in the application specific file <applstdef_emC.h>. It is the best case because the file is version controlled. It should be favored. For variant c) the make script may be version controlled, but if more as one IDE is used for (more) target systems and PC-simulation, the variant selection may be more obscure.

The emC basic headers regard the variants of compiling conditions. The user software can call operations, which may be macros specified to the variants. The user software should not regard the variants.

One of the basic idea of emC is: Test in a PC-based high level C++ compilation environment - and deploy the well tested software on target with the target conditions. All sophisticated software checks are done on PC testing, the software should be widely error-free after them and can deploy without these checks on target to run fastly - without adapting the sources. It means the software can be run and checked again on PC for enhancements, without change the sources, and without sophisticated #ifdef constructs.

In conclusion the target compilation and the PC test compilation can or should use different variants, for example on PC with exception handling in C++ which may not available on target.

2. C++ and C usage

  • __cplusplus: Selecting *.c-sources for C++ compilation. This can be done by compiler switch only. If C++-Projects are mixed with c-compiled sources, a correct handling of extern "C" is necessary.

  • DEF_CPP_COMPILE: This compiler switch have to be set if C-Sources should be compiled with C++. That should be matching with compiler options to force CPP-compilation. It is /TP for Visual Studio and -x c++ for GNU compiler.

All Functions which are implemented in C-Sources should be declared in the header with

extern_C Type myOperation_Type(...);  //always as C-functions
....
extern_CCpp Type myOperation_Type(...); //defined depending DEF_CPP_COMPILE

The extern_C is replaced by extern "C" for C++-compilation. That prefix forces a C-like Linker label with only the name of the operation, without argument-sensitivity (signature of the operation). A C-compiled object file contains this simple label only. The C++ linker regards it.

The second form regards the compiler switch DEF_CPP_COMPILE. Only if it is not set then the macro extern_CCpp is defined as extern "C". Elsewhere it assumes that the associated c-source is compiled with C++. The call of the operation in a C++ environment expects a linker label with argument sensitivity (signature of the operation). It should be offered by the C-routine and requests the C++ compilation for C-sources.

There may be some source which are never compiled with C++ because there are very simple (then C++ is never necessary) or they define const-data for a const memory segment (for example immediately contained and read from a Flash Memory in an embedded device). For that sources extern_C should be used anyway.

3. ObjectJc capability

The base struct ObjectJc is possible as base for some data. With this unique base the type and size of data can be evaluate in debugging situations outside an IDE (data view on target), for evaluating extracted data, and for safety in an application using type and size checks. See ObjectJc.en.html.

In cohesion with Reflection.en.html the data can be evaluated without knowledge of the data structure especially for unknown software versions, for example in data evaluating situations on legacy software. The ObjectJc references ClassJc const* reflection.

ObjectJc as base struct also for classes can be used, but does not need to be used for all data. For the reflection concept only the root class needs the root reference to reflection, and especially via interface references the implementation type can be gotten.

  • DEF_ObjectJc_SIMPLE: If that compiler switch is set firstly before other files are included (in the user specific <applstdef_emC.h> or as compiler switch), Then the ObjectJc contains only 4 Bytes for a type and size identification. The reflection concept is not supported. This variant should be used if very less RAM is available on the target, but the full ObjectJc capability should be used on another target or for PC test. Then ObjectJc is used as base class, for some data, but it does not need significant RAM space. The application can check the type and size of the data object, but only the implementation type, not a supported base type on runtime. But note that a proper base type check is supported on compile time in C++ via static_cast<BasetType*>(instanceRef).

  • DEF_ObjectJc_REFLREF: If this compiler switch is set than the ObjectJc base struct contains the reference to its ClassJc reflection data. See chapter Reflection handling. Only then a base type can be detected for pointer type casting from a more basically type, especially ObjectJc*, to a base type of an instance for a C-like or reinterpret_cast<..>(..) It is a similar capability as dynamic_cast<…​> but without the necessity of RunTimeTypeInformation on compiling level. It works for struct too and it works in C. For symbolic access (reflection handling) all information are accessible. Activating this switch needs additionally 4 bytes for ObjectJc in 32-bit-systems.

  • DEF_ObjectJcpp_REFLECTION: Using reflection in class an additional offset between the position of the partial ObjectJc data and the start address of an instance is necessary. If C++ is used with interfaces or multiple inheritance and with reflection this switch have to be set. It needs additional 4 bytes. But in complex C++ systems this amount of memory should be available.

  • DEF_ObjectJc_SYNCHANDLE: If this switch is set then the operations synchronized_ObjectJc(..), wait…​ and notify_ObjectJc(..) and its C++ variants defined in ObjectJcpp are available. ObjectJc contains an index for mutex and notify objects. It is a Java-adequate concept which associates the handle of operation system resources for this operations to the data instance. It needs the same additional 4 byte as DEF_ObjectJcpp_REFLECTION because both information are stored as two 16 bit words in a 4 byte location, for assurance of 4-byte-boundary.

  • DEF_ObjectJc_OWNADDRESS: If this switch is set, any ObjectJc partial data contains its own address. This is helpfully if data are stored as memory map and evaluated. references between data contains the real hardware address references. Using the information about the hardware address of own instance the references can be associated though the data are contained as memory map on other addresses.

  • DEF_ObjectJc_FULL: This is a simple switch which forces all capability of ObjectJc. The other switches can be additionally set, but do not need. If DEF_ObjectJc_SIMPLE is set at once, an #error message is forced.

4. String handling

  • DEF_NO_StringJcCapabilities: Some DSP processors (Digital Signal Processor) do not handle String operations in a proper way. Only simple `char const*`can be stored. A String processing is not desired. For that variant this define is set. It reduces String handling in the basic emC functionality.

  • DEF_ONLY_CHAR_StringJc

5. Exception and Thread Context

For exception handling see ThCxtExc_emC.html: Stacktrace, ThreadContext and Exception handling. The Strategy is: Test on PC with full exception handling, deploy maybe in a poor target well tested without exception, but with unchanged sources. To control the exception handling and meaning of THROW, macros are used.

The following compiler switches in <applstdef_emC.h> or as compiler definition settings ar used:

  • DEF_ThreadContext_SIMPLE: If this is set,

  • DEF_Exception_TRYCpp: The C++ Compilation should be used, especially on PC-Test. The emC-Exception handling with TRY CATCH THROW uses the C++ try catch throw Keywords. A catch is always implemented as the 'native C++ catch(…​)’ which is necessary for the Visual Studio 'asynchronous exceptions'. They are handled if the compiler switch `/EHa is set. See https://docs.microsoft.com/de-de/cpp/build/reference/eh-exception-handling-model It means, memory errors because faulty pointers, division by zero etc. forces catching this exception too. It may be substantial for programs in test. (TODO clarify this topic for gcc compilation.)

  • DEF_Exception_longjmp: The longjmp mechanism is used for the emC-Exception handling with TRY CATCH THROW. C or C++ compilation is possible, whereby for C++ sources destructors are not invoked on THROW. Hence this mode should only be used if temporary class instances are not used or all of their destructors are empty. On a target system this condition may be true, the longjmp exception handling may be some time faster.

  • DEF_Exception_NO: In this case a THROW macro does only invoke logSimple_ExceptionJc(…​) which is implemented either in …​TODO

  • DEF_ThreadContext_SIMPLE: The thread context is a memory area which is thread specific, or interrupt execution specific too (this is also a thread). The `

6. 64-bit-Addresses versus 32 bit

  • DEF_HandlePtr64: In some 64-bit-Applications, for example S-Functions in Simulink, all memory addresses of instances (…​of Function Blocks) are held in a global accessable address table. The references are handles - index to the table, as uint32 word. For deployment the code to a 32-bit-System the same uint32 words as connection data between function blocks (aggregations in UML-slang) contains the really memory addresses, for fast access. This is regarded by code generation in Simulink (® Mathworks) - via specific tlc files (tlc = target language control).

7. ClassJc and Reflection handling

The struct ClassJc contains the type information for any data, it is referenced in ObjectJc. The ClassJc can have a full capability to present full symbolic access to all data (then DEF_REFLECTION_FULL is present). Or it is a small struct only to support type check capability. See ObjectJc.en.html, chapter Reflection and Types.

  • DEF_REFLECTION_FULL: The reflection (see Reflection.en.html) contains the possibility of symbolic access to all data. This feature allows dynamic programming in C and C++, for example find out a data element because of its textual identifier name gotten via a communication telegram, or executed an operation by symbolic specification. For example symbolic data access via Inspector.en.html can be used. That features are proper not only for PC programming but for rich powerful embedded applications. For that the symbolic information (reflection) should be generated from the header file information with the tool CHeader2Reflection.en.html.

  • DEF_REFLECTION_OFFS: To prevent effort on target but allow symbolic data access via the Inspector.en.html tool a inspector target proxy can be used. That proxy contains the textual information and communicates with the target via simple memory accesses. The target should contain generated Reflection information which contains only the offsets# to all data in a struct, because the offsets may be specific on target compilation (cannot be presumed by a compiler- and situation-independent tool). For that this compiler switch can be set.

  • DEF_REFLECTION_SIMPLE: If this macro is set, only type information is contained in reflection data (Type ClassJc) to support safety type checks.

  • DEF_REFLECTION_NO: Whether type nor symbolic access can be done. This is the simplest form - no reflection usage. Instances of ClassJc cannot be defined.

The ClassJc definition does not depend on the Reflection definition but depends on the capability of ObjectJc. But it should be match to the reflection usage.

  • DEF_ObjectJc_FULL as opposite to DEF_ObjectJc_Simple: In this case the ClassJc is referred from ObjectJc and it is able to present the DEF_REFLECTION_FULL - full reflection information. But it the other compiler switches are set for reflection generation, the ClassJc may contains only type information, type information about base classes too, or no information.

  • DEF_ObjectJc_SIMPLE: Then the ClassJc definition is only simple too. It cannot contain full qualified Reflection (DEF_REFLECTION_FULL), because this definition is proper for a poor target system with less resources. But any instance of data based on ObjectJc contains a type information if initialized. In the poorest form this is a 16 bit identification number in the only one 32-bit data member idInstanceType in ObjectJc. It is the low part of the address of a given reflection_MyType instance. If all reflection-Type-instances are localized in one memory section of maximal 64 k memory address range, it is unique.

  • DEF_REFLECION_OFFS together with DEF_ObjectJc_SIMPLE: The ClassJc contains the index number of the generated Reflection offset data. In this case it is possible to check whether a data object refers the correct reflection offset information, respectively the reflection information gotten from information of the Inspector target proxy matches to the data object. If DEF_ObjectJc_REFLREF is not defined, then the 16-bit-part of the only one information idInstanceType in ObjectJc contains this index of the reflection offset table.

  • DEF_ObjectJc_REFLREF together with DEF_ObjectJc_SIMPLE: Then the simple definition of ObjectJc contains a dedicated reference to its ClassJc type information. The ClassJc contains a reference to a possible super class (only for simple inheritance). Hence it can be tested whether a given data instance based on ObjectJc is type of or has base data which are type of a given reflection (instance of ClassJc). This enables type checks for derived data structures or classes in poor embedded applications. This is independently of given generated reflection-offset data (DEF_REFLECION_OFFS is set or not).

  • The ClassJc contains a String given type name if DEF_NO_StringJcCapabilities is not set. For simple numeric applications without any string processing it is not necessary to spend memory space for identification strings because there are never compared or processed otherwise. For such applications the DEF_NO_StringJcCapabilities can be set in the <applstdef_emC.h> for generally. If DEF_NO_StringJcCapabilities is not set, the string literal in ClassJc contains the plain text name of the type. Hence more as one instances of ClassJc which presents the same type, especially in different independent compiling units (with static-keyword definition) or in different dynamic linked parts of the application can be existing, and the unique recognition of the type is assured. The plain text type name may be helpful in debugging situations too.

7.1. Definition of the type instances of ClassJc

If the reflection are generated via the CHeader2Reflection.en.html tool, there are generated files. The following form are preferred to include it:

With them the type of an instance can be checked whether it is from a given type:

bool ok = instanceof_ObjectJc(myData, &reflection_MyDataType);
#ifdef DEF_REFLECTION_FULL
 #include "genRefl/MyType.crefl"
#elif defined(DEF_REFLECTION_OFFS)
 #include <emC/Base/genRefl/Time_emC.crefloffs>
#else //DEF_REFLECTION_SIMPLE, or DEF_REFLECTION_NO
 ClassJc const reflection_Clock_MinMaxTime_emC = INIZ_ClassJc(reflection_Clock_MinMaxTime_emC, "Clock_MinMaxTime_emC");
#endif