Reflection emC

Reflection emC

Inhalt


Topic:.Refl.

The Reflection principle is known from Java, C# and some other programming languages. It offers the possibility to access data and calling operations from outside a compiled software with its symbolic names on the one hand, and working with symbolic and type informations inside the software on the other hand.

The Reflection in emC are firstly used to access data from outside for debugging on runtime. Another possibility is internal usage for symbolic access to data or symbolic invocation of operations.


1 Control of usage Reflection in the emC-Sources

Topic:.Refl.DEF_REFLECTION.

Last changed: 2019-09-29

The user can / should define the following compiler switches:

#define DEF_REFLECTION_FULL
#define DEF_REFLECTION_OFFS
#define DEF_REFLECTION_NO

It is possible to define this switches as compiler option. This defines are contained in the different applstdef_emC.h in proper kind, whereby the definition of a switch from compiler level is checked.

The emC source files uses the following pattern to include reflection information (example from emC/Base/Time_emC.c):

#ifdef DEF_REFLECTION_FULL
  #include <emC/Base/genRefl/Time_emC.crefl>
#endif

The full reflection information are generated from the appropriate header file into the shown *.crefl file in a sub directory below the header itself. That is the convention for emC sources itself, it can be used for user sources in the same kind.

The DEF_REFLECTION_OFFS is a shorter form especially for simple target systems without String processing capabilities or with low communication outfit. For that systems the InspcTargetProxy is used for access. The necessary reflection information are generated in only one file proper to the used target sources, see TODO.

The DEF_REFLECTION_NO prevents reflection usage.

The header files of emC sources defines to any struct (example Time_emC.h)

typedef struct Clock_MinMaxTime_emC_t {

 .....

} Clock_MinMaxTime_emC;

extern_C ClassJc const reflection_Clock_MinMaxTime_emC;

It defines the reflection_xxx-label in C-kind. This label is contained both in the associated *.crefl file for DEF_REFLECTION_FULL and in the specific generated reflection_offset.c file. If reflections are not used (DEF_REFLECTION_NO), then the label is declared in fact but not used.

To set the reflection information to the instance data the operation

void ctor_Clock_MinMaxTime_emC(Clock_MinMaxTime_emC* thiz, int nrofEntries) {
  ....
  initReflection_ObjectJc(&thiz->base.object, thiz, sizeof(*thiz), &reflection_Clock_MinMaxTime_emC, 0);

is used, (example in Time_emC.c). This operation is conditional defined repectively written as #define-macro, becaus it is adapted to the three different Reflection usages:

In emC/Base/ObjectJc.h:

extern_C ObjectJc* initReflection_ObjectJc_Impl(ObjectJc* ythis, void* addrInstance, int sizeObj, struct ClassJc_t const* reflection, int identObj);
#ifdef DEF_REFLECTION_NO
  #define initReflection_ObjectJc(THIZ, ADDR, SIZE, REFL, IDENT)  initReflection_ObjectJc_Impl(THIZ, ADDR, SIZE, REFL, IDENT)
#else
  #define initReflection_ObjectJc(THIZ, ADDR, SIZE, REFL, IDENT)  initReflection_ObjectJc_Impl(THIZ, ADDR, SIZE, null, IDENT)
#endif

In ObjectJc_simple.h:

#ifdef DEF_REFLECTION_FULL
  #error do not support DEF_REFLECTION_FULL
#elif DEF_REFLECTION_OFFS
  /**initializes with reflection which are defined with the ,,ClassJc,, struct in this header. */
  #define initReflection_ObjectJc(THIZ, ADDR, SIZE, REFL, IDENT) { (THIZ)->idInstanceType = ((IDENT)<<16) + ((REFL)->ixType & 0xffff); }
#else
  /**Initialize with ignoring the REFL information, for compatibility with the same source which uses but does not define the reflection instance. */
  #define initReflection_ObjectJc(THIZ, ADDR, SIZE, REFL, IDENT) { (THIZ)->idInstanceType = ((IDENT)<<16); }
#endif

The ObjectJc_simple.h defines a simple struct ObjectJc for numeric targets without dynamic linked operations etc and only for a InspcTargetProxy access. The struct ClassJc is a simple struct too. If DEF_REFLECTION_NO is defined, the argument for reflection_XY is not used, so no compiler error is forced.

Hence with same sources this three kinds of reflection usage are supported.


2 Generated or manual written const data of Reflection

Topic:.Refl.ClassJc_const.

The Reflection are generated from the typedef struct and from the class information in the header source files. There are parsed, with the parsing result C-sources which contain constant data for reflection access are generated:

/**A base class to demonstrate which is single inherition in C, for this simpe example. */
typedef struct MyBaseData_t {
 /**The struct is based on ObjectJc. In the compilation situation of targetNumericSimple
 * that is only a struct with 2 int32 elements.
 * Use the notation with union ... base to unify the access
 */
 union { ObjectJc object; } base;

 /**It is 1 on startup. Set to 0 to abort the execution. */
 int32 bRun : 1;

} MyBaseData;

This is a content of a headerfile (D:/vishia/emcTest/TestNumericSimple/src/TestNumericSimple.h) which is parsed. The comments can be parsed too, but they are not part of the reflection.

The parser and reflection generator generates the following file (code snippet from .../emcTest/TestNumericSimple/genRefl/TestNumericSimple.crefl):

The first const Object is the definition of the superclass, in this case only ObjectJc:

extern_C const ClassJc reflection_MyBaseData;  //the just defined reflection_ used in the own fields.

const struct SuperClasses_MyBaseData_ClassOffset_idxMtblJcARRAY_t  //Type for the super class
{ ObjectArrayJc head;
  ClassOffset_idxMtblJc data[1];
}  superClasses_MyBaseData =   //reflection instance for the super class
{ INIZ_ObjectArrayJc(superClasses_MyBaseData, 1, ClassOffset_idxMtblJc, null, INIZ_ID_ClassOffset_idxMtblJc)
 , { &reflection_ObjectJc
   , 0 //TODO Index of mtbl of superclass
     //The field which presents the superclass data in inspector access.
   , { "object"
     , 0 //arraysize
     , &reflection_ObjectJc  //type of super
     , kEmbeddedContainer_Modifier_reflectJc //hint: embd helps to show the real type.
     , 0 //offsetalways 0 (C++?)
     , 0  //offsetToObjectifcBase
     , &reflection_ObjectJc
     }
   }
};

Because the reflection system have to be support multi-inheritance which is used in C++, there is an array of superclasses. For simple struct without a derivation concept this block is not generated. For single inheritance the data[1] hase 1 element. This block is generated because the input struct starts with union{ ObjectJc object; } base; The Type ClassOffset_idxMtblJc is defined in emC/Object_emC.h. It contains a FieldJc which presents the superclass as element.

The next block contains all data elements named Field from Java slang:

const struct Reflection_Fields_MyBaseData_t
{ ObjectArrayJc head;
  FieldJc data[1];
} reflection_Fields_MyBaseData =
{ INIZ_ObjectArrayJc(reflection_Fields_MyBaseData, 1, FieldJc, null, INIZ_ID_FieldJc)
, {
   { "bRun"
   , (uint16)(0 + (1 << kBitNrofBitsInBitfield_FieldJc))
   , REFLECTION_BITFIELD
   , kBitfield_Modifier_reflectJc //bitModifiers
   , 0 + sizeof(ObjectJc)/* offset on bitfield: offset of element before + sizeof(element before) */
   , 0  //offsetToObjectifcBase
   , &reflection_MyBaseData
   }
} };

That are the 'fields', the data elements of a struct. Any field entry needs 48 byte. This information is important because the reflection can be generated as binary data too for usage in an Inspector Target Proxy. The name of a field is at least 30 Characters, it is limited. It is not stored as reference to any const memory, but it is an embedded char name[30] in the reflection struct, That is because the image as binary data.

Here only 1 field is given, the FieldJc data[...]; is usually larger. This struct, similar the superClasses_MyBaseData, starts with the INIZ_ObjectArrayJc(...). This is a initializer-macro for the head data, defined in emC/Object_emC.h. The INIZ_ID_FieldJc is a special value which is placed in the objectIdentSize field of the base class ObjectJc which is used here too.

The anchor of the reflection of this class (struct) is the following, only this identifier should be used extern:

const ClassJc reflection_MyBaseData =
{ INIZ_objReflId_ObjectJc(reflection_MyBaseData, &reflection_ClassJc, INIZ_ID_ClassJc)
, "MyBaseData"
, 0
, sizeof(MyBaseData)
, (FieldJcArray const*)&reflection_Fields_MyBaseData  //attributes and associations
, null  //method
, (ClassOffset_idxMtblJcARRAY*)&superClasses_MyBaseData  //superclass
, null  //interfaces
, mObjectJc_Modifier_reflectJc
, null  //virtual table
};

This is the class information for the struct MyBaseData. Note: class does not mean a C++ class, it means a class from Object Oriented aspect. In This case, see also ObjO_emC.html, the struct is a class.

The initialization of the ObjectJc part of the type ClassJc is done with the INIZ_objReflId_ObjectJc(...) which is used inside the INIZ_ObjectArrayJc(...) too. The INIZ_ID_ClassJc identifies the Object as Type classJc, if the &reflection_ClassJc are not able to access. With it the data can be detected if they are given binary without embedding in a compiled application. This is the case in two approaches:

The ClassJc-instance knows further information especially for methods, interfaces and the virtual table, here set to null. Furthermore there is a offset posObjectBase here initialized with 0. That is for C++ classes where the ObjectJc data are not located on the base position of the struct data.


3 The reflection generator

Topic:.Refl.Header2Refl.

The file which should be maintenanced from the user is for the above example (emCTest/TestNumericSimple/genRefl.jz.cmd):

REM start problem: The batch needs the zbnf.jar File.
REM Either the ZBNFJAX_HOME environment variable is set already,
REM or it is set via a found setZBNFJAX_HOME.bat,
::call setZBNFJAX_HOME.bat
REM if not found, set it immediately, you might adapt this line:
if "%ZBNFJAX_HOME%" == "" set ZBNFJAX_HOME=D:/vishia/ZBNF/zbnfjax
java -cp %ZBNFJAX_HOME%/zbnf.jar org.vishia.jztxtcmd.JZtxtcmd %0
if ERRORLEVEL 1 pause
exit /B
==JZtxtcmd==
include $ZBNFJAX_HOME/jzTc/Cheader2Refl.jztxt.cmd;
currdir=scriptdir;
Fileset headers =
( src:*.h
);
main()
{
  mkdir T:/Msc15/TestNumericSimple/refl/;
  mkdir genRefl/;
  zmake "genRefl/*.crefl" := genReflection(.&headers, html="T:/Msc15/TestNumericSimple/refl");
  <+out>success<.+n>
}

That is all. The generator itself runs in Java with the common JZtxtcmd tool which is controlled by scripts. The scripts contains the rules to parse and translate. This is the here included translating script .../zbnfjax/jzTc/Cheader2Refl.jztxt.cmd and the there called .../zbnfjax/zbnf/Cheader.zbnf syntax script for the header parsing.

To determine which header files are used to generate reflection, the Fileset headers should be adapted. A wildcard usage make it easy to select all files in specific directories.

The zmake starts the generation with the given input files. The output is given as local path with wildcard. Any header file produces one *.refl file in the determined directory. The argument html=... is optional. It is the directory for html log files. They contain the parsed content to check what is reading from the header.


4 Including reflection in the sources

Topic:.Refl.inclRefl.

If the application is tested on PC, the reflection can be included as part of the application. If the application is compiled for a target with less ressources, the reflection may not be necessary, or the InspectorTargetProxy is used instead. Then the reflection should not be used in the sources. Both will be distinguish with a compiler switch:

#ifdef __DONOTUSE_REFLECTION__
 char const reflection_MyData[] = "REFLMyData";
#else
 #include "../genRefl/TestNumericSimple.crefl"
#endif

If reflection are not used but the types are based on a simplified ObjectJc the reflection are provided as simple String.

On static definition of the data:

MyData data = INIZ_MyData(data, &reflection_MyData);

the reflection are used. The INIZ_MyData(...) is a macro which calls at last INIZ_objReflId_ObjectJc(...) defined either in emC/Object_emC.h for reflection using or in appl_emC_h/ObjectJc_simple.h. The two different forms of the macro allows different usage.

Another possibility to set the reflection is with an operation on startup:

initReflection_ObjectJc(&thiz->base.object, thiz, sizeof(MyData), &reflection_MyData, 0);

This operation call is a macro for the simple ObjectJc or it can be implemented in 2 different ways for simpe not-using reflection applications and for full qualified one.


5 Access data with the inspector

Topic:.Refl.inspcTarget.

For the comprehensive explaination of the Inspector concept see Inspc. This chapter shows only how the Inspector target service can be included in an application.

Follow the example of emcTest/TestNumericSimple/TestExcHandlingCpp.sn Project:

#ifndef __DONOTUSE_REFLECTION__
 #define __Use_Inspector__
 #include <Inspc/Service_Inspc.h>
#endif

This header is part of emc/source/.... It includes some more headers, especially Inspc/DataNode_Inspc.h.

#ifdef __Use_Inspector__
 //The inspector service, it is a part of the runtime environment.
 Inspector_Inspc_s theInspector = { 0 };
#endif //__Use_Inspector__

They are static instances for the whole inspector service, which contains a socket communication, and for one DataNode_Inspc instance for the root.

int main(int nArgs, char** sArgs) {
  STACKTRC_ENTRY("main");
  ....
#ifdef __Use_Inspector__
  ctorO_Inspector_Inspc(&theInspector.base.object, s0_StringJc("UDP:0.0.0.0:60094"), _thCxt);
  start_Inspector_Inspc_F(&theInspector, &data.base.object, _thCxt);
#endif //__Use_Inspector__

The inspector will be initialized with the UDP port. In this case it listen at all existing TCP adapters (Address 0), the communication from outside is also able to use. For a only local access use "UDP:127.0.0.1:..." with any desired port.


Bild: InspcFields-root The start_Inspector_Inspc_F(...) starts the communication thread. Before that the root Object is assigned. This root data struct should be have Reflection information. There are basicly for the data access and presentation. The Inspectors shows the information of this root data firstly. For non-primitive data (here super the memory address is shown. The concept is toString- a String presentation from the content, adequate Java. But the toString-opeation should be existing and invoked via dynamic operation call (virtual operation). The address is the simple fallback.


Bild: InspcFields-super A click on the super field opens it and shows the content of the superclass. All / elements describe the path from root, + is a substruct. This list presentation does not show a tree view of the data. For some cases it is better.


Bild: ReflectPro access Another tool (not open source) shows the tree with an proper view.

The Inspector access enables selecting, showing and changing from any data location. For a usage on any target the access can (should) be password-protected. Write-accesses can be enabled only by a special password, just as well accesses to determined data which should be hidden for a simple operator. Of course the whole network communication should be protected. But with this tool all data can be accessed as a maintenance action from far.