Reflection und Inspector

Reflection und Inspector

Inhalt


Topic:.ReflectionJc.


1 Reflection-Übersicht

Topic:.ReflectionJc.usage.

Last changed: 2007-09-23

Laut Wikipedia bedeutet Reflection: dass ein Programm Erkenntnisse über seine eigene Struktur gewinnen kann. (Stand 23. Sep. 2007). Die Strukturinformationen sind normalerweise in Maschinencodeabläufe, Einzel-Datenadressen und Labels für Subroutinen übersetzt und damit nicht mehr unmittelbar von außen zugänglich. Die Zugänglichkeit besteht nur, wenn diese im Compilierungsprozess berücksichtigt wird. Ein Zugang von außen mit Angabe der Daten oder Subroutinen per Name (Symbol) erfordert ein aufgesetztes zusätzliches System. Solche symbolischen Zugänge sind erforderlich oder interessant

Die Reflection erlauben diesen symbolischen Zugang ohne zusätzliche Informationen wie Mapfiles und Listings und daraus abgeleitete Zugangsdaten.

Die Reflection-Informationen verbinden interne Strukturen mit symbolischer Zugriffsmöglichkeit aus der Quellcode-Ebene. Um diese Informationen gegenüber der unbeabsichtigten Nutzung zu verbergen, sollen textuelle Anteile gegebenenfalls auch chiffriert werden können.


1.1 Reflection in Java

Topic:.ReflectionJc.usage.java.

Last changed: 2013-12-09

Reflection sind in einigen Programmiersprachen wie Java, C#, Python fester Sprachbestandteil und erlauben dort eine flexible Programmierung insbesondere bei Nutzung von Klassen über Netzwerkgrenzen oder dynamischer Programmbestandteile. Die Reflections sind im Java-Standard-Sprachumfang mit den Klassen java.lang.Class und das Package java.lang.reflect repräsentiert. Von jedem Object sind die Reflection-Infos zugänglich. Beim Zugriff wird aber der Zugriffsschutz wie beim Compilieren beachtet. Standardgemäß zugreifbar sind nur public-Elemente. Das entspricht dem objektorientiertem Ansatz. Elemente, die von außen gelesen oder gesetzt werden sollen, müssen public sein. Für einen Zugriff aus rein informatorischen Gründen auf alle Elemente gibt es die Möglichkeit, den private-Zugriff freizuschalten: Field.setAccessible(...).

In Java umfassen die Reflection auch die Aktivierung von Klassen über den Klassenloader und deren Instanziierung. So kann eine Funktionalität in einem Java-Programm rein über externe textuelle Steuerung beeinflusst werden. Eine entsprechende symbolisch benannte Klasse muss lediglich im Klassenpfad (CLASSPATH) auffindbar sein. Sie wird bei Bedarf geladen und kann aus fest programmiertem Code angesprochen werden, wenn sie ein bestimmtes Interface implementiert.


1.2 Reflection - Anwendungen in C und C++

Topic:.ReflectionJc.usage.Cpp.

Last changed: 2007-09-23

Für C/C++ wird hier eine Reflection-Lösung benutzt, die sich an Java anlehnt. Dabei wird nicht die gesamte Funktionalität der Reflections aus Java unterstützt, folgende Teile sind ausgenommen:

Der Klassenlader ist eine typische Eigenschaft der virtuellen Maschine und für C(++)-Anwendungen nicht zutreffend. Die Security-check.Mechanismen sind insbesondere für den Einsatz von Java in unbekannten entfernten Umgebungen (Internet-Applikationen) gedacht und ebenfalls nicht zutreffend. Die strenge Unterscheidung der Zugriffsrechte sind ein Grundkonzept der Kapselung in der Objektorientierung. So ist es in Java zwar möglich, zu erfahren, dass es ein bestimmtes private-Attribute gibt, jedoch der Zugriff darauf wird mit einer Security-Exception beantwortet. Für die C(++)-Anwendung ist diese strenge Kapselung gelockert, in die Verantwortung des Anwendungsprogrammierers gelegt.

Die drei Hauptklassen der Reflections sind:


1.3 Definition und Einbindung der Reflection in C(++) mit emC - vollständig

Topic:.ReflectionJc.usage.genRefl.

Last changed: 2018-12-24

Die Reflection bilden den Aufbau einer struct oder class ab. Dafür ist im Header emc/source/emC/Object_emC.h der Typ ClassJc und FieldJc definiert.

In einer Instanz sind die Reflection-Informationen entweder direkt eingebunden, indem die Instanz mit der struct ObjectJc_t beginnt, oder die Reflection-Informationen ergeben sich aus dem Typ der Referenz auf die Daten (aus FieldJc herausgelesen).

Eine TypInformation in ClassJc enthält in 92 Bytes für ein 32-bit-System:

Die Information für Datenelemente einer struct oder class enthält in FieldJc in 48 Byte:

Die Modifier-Bits der Elemente enthalten:


1.4 Definition der Reflection in C mit emC - TargetProxy

Topic:.ReflectionJc.usage.genRefl.

Last changed: 2018-12-24

Für eine weniger leistungsfähige Zielhardware (Target), bei der man auf Textverarbeitung, verzichten möchte, weniger Speicherplatz hat und den Zugriffs-Service auf Reflection minimal halten möchte (etwa bei einer kleinen 16-bit-CPU) gibt es das Konzept des Target-Proxy. Dabei sind die im Vorabschnitt genannten Reflection-Informationen in einem Proxy - Stellvertreter enthalten, das entweder in einem 32-bit-Verwaltungsprozessor realisiert ist oder das Proxy läuft auf einem angeschlossenem PC. Die Reflection sind dabei als Binärfile generiert.

Eine Instanz kann hier ebenfalls auf ObjectJc basieren. Diese struct ist aber im Headerfile emc/sourceApplSpecific/applConv/ObjectJc_simple.h minimalistisch definiert und besteht nur aus einem 32-bit-Integerwert, der in je 16 bit einen Typindex und einen Instanzindex enthält. Der Typindex referenziert auf ein globales Array int32 const* const reflectionOffsetArrays[] im Target selbst und im Proxy auf die betreffende ClassJc-Typinformation. Damit können auch in einem einfachen 16-bit-System ein Polymorphismus (abgeleitete und Basisklassen) verwendet werden.

Die Position der Elemente in einer Datenstruktur im Zielsystem ist nicht im Proxy enthalten, da nicht vorausgesetzt werden kann, dass im Proxy in den dort gespeicherten Reflection-Informationen der genauen Byteaufbau der struct und class des Target bekannt ist. Daher sind diese Informationen im Target als Quelle enthalten. Es wird pro Element ein int-Wert (16 oder 32 bit) benötigt. Auch bei Softwareänderungen im Target, bei denen keine Reflection-Neugenerierung erfolgte, ist damit die Richtigkeit der Positionen der Elemente im Speicher des Target sichergestellt. Außerdem kann die Targetsoftware je nach Optionen beim Compilieren Füllbytes erzeugen (alignment), die damit den Datenaufbau bestimmen.


1.5 Nutzung der Reflectiondaten für den Inspector

Topic:.ReflectionJc.usage.inspcService.

Last changed: 2018-12-24

Auf der Zielplattform muss ein sogenannter Inspector-Service enthalten sein. Dieser wird angesprochen wenn Daten im laufenden Betrieb inspiziert werden sollen.

Bei der vollständigen (großen) Lösung arbeitet der Inspector-Service typisch mit Socketkommunikation, empfängt Telegramme die in InspComm beschrieben sind, analysiert den Zugriffsbefehl und liefert Daten (Strukturdaten aus dem Zielsystem oder Werte). Das ist ein umfängliches System, dass in der emC-Library unter emc/source/InspcJ2c abgelegt ist. Die gleichen Algorithmen laufen auch in Java-Anwendungen. Die Quellen in .../InspcJ2c wurden aus Java nach C übersetzt.

Bei einem Targetproxy läuft im Proxy eben dieser InspectorService. Der Proxy vefügt über die Strukturdaten als geladene Binärdatei. Die Werte werden aus dem Target über einen sehr viel einfacheren Zugriff geholt, dessen Kern in emc/source/Inspc/Target2Proxy_Inspc.c programmiert ist. Dieser Dienst lässt sich nun auch seriell oder über den Mechanismus des SharedMemory betreiben. Die Schnittstelle ist in emc/source/Inspc/IfcTargetProxy_Inspc.h beschrieben. Es werden in beiden Richtungen 16 Byte pro Zugriff ausgetauscht.

Die SharedMemory-Verbindung ist nutzbar sowohl für eine Zielsystemsoftware, die auf dem PC simuliert wird, als auch im Zielsystem wenn etwa zwei Prozessoren über einen Dual-Port-Memory oder einen gemeinsamen Speicherbereich etwa in einem FPGA verbunden sind. Eine Zielsystemsoftware, die auf dem Zielsystem seriell an den Proxy gekoppelt ist, kann auf dem PC getestet auch den SharedMemory-Zugriff benutzen, lediglich der Datentransport ist speziell angepasst.


1.6 Generierung der Reflectiondaten

Topic:.ReflectionJc.usage.genRefl.

Last changed: 2018-12-24

Man kann die Reflection-Quellen generell in C per Hand schreiben. Auf die Strukturelemente wird symbolisch zugegriffen. Es sind keinerlei Spezial-Algorithmen notwendig wie etwa die Auswertung von Listings und Map-Files.

Da die Quellen der Reflection-Informationen sich aber vollständig aus den gegebenen Typdefinitionen in den Headerfiles ableiten lassen, ist eine automatische Generierung angebracht. Diese wird verwendet:

Die Headerfiles werden über einen ZBNF-Parser geparst. Die Syntax ist in zbnfjax/zbnf/Cheader.zbnf definiert. Die Generierung der Reflection-Quellen erfolgt dann aus den geparsten Daten mit einem JZtxtcmd-Script. Das Kern-Script ist zbnfjax/jzTc/Cheader2Refl.jzTc Aus der Anwendung kann der Parser und der Generator über ein anwenderdefiniertes JZtxtcmd-Script gerufen werden, dass die Headerfiles benennt.

Der Generator erzeugt einen *.crefl-File mit C-sources für die Konstantendefinitionen pro Headerfile, der in die jeweiligen Anwender-C-file includiert werden soll. Wenn angegeben wird zusätzlich der Bin-File für den Proxy, , und einen refloffs.c-File mit den Offset-Informationen für ein kleines Target über Proxy angesprochen erzeugt. Die *.crefl-Files werden dann nicht benötigt dienen aber der Dokumentation. Die Reflection-Informationen sind im bin-File fertig aufbereitet enthalten.


2 Aufbau der Reflection-Daten

Topic:.ReflectionJc.ReflData.

Last changed: 2007-09-23

Die Reflectiondaten sollen im Anwenderprogramm vorliegen und eingebunden werden. Grundsätzlich besteht aber auch die Möglichkeit, die Reflectiondaten außerhalb beispielsweise in einer Datei zu halten. Das ist interessant für den Fall, dass Daten übertragen und gespeichert werden, und mit den Daten die zugehörigen Reflection-Infos. Dabei muss aber beachtet werden, dass die Reflectiondaten selbst verzeigert sind (Klasse zeigt auf ihre Attribut-Informationen, Attribut-Informationen zeigt auf die Klassen entsprechend dem Attributtyp usw.). Die Verzeigerungen sind im Anwenderprogramm vom Linker vergebene Speicheradressen. Diese Verzeigerung muss dann nachvollziehbar und im Zielsystem nachgezogen werden. Dafür sind Vorkehrungen getroffen.


2.1 Alle Daten sind Konstante

Topic:.ReflectionJc.ReflData.const.

Last changed: 2007-09-23

Grundsätzlich sind die gesamten Reflection-Infos als Konstante ausgeführt. Das bedeutet, dass kein zusätzlicher Code zum Erstellen der Daten, auch nicht beim Hochlauf nötig ist. Wichtiger ist gegebenenfalls noch, dass diese Konstanten sich auch in einem ROM-Bereich befinden können. Das ist wichtig, weil


2.2 Daten in Headerfiles definieren

Topic:.ReflectionJc.ReflData.header.

Last changed: 2013-12-09

In C ist es teilweise üblich, nur in einem C-File verwendete Daten dort im C-File als Struktur zu definieren. Nur wenn die Daten in mehreren Compilierungseinheiten (C-Files) verwendet werden, werden sie in einem Headerfile definiert.

Es ist aber durchaus angebracht, Datendefinitionen und Programmcode schon aus dokumentarischen und softwarestilistischen Gründen zu trennen. Also Datenstrukturen grundsätzlich in Headerfiles zu definieren - weil sie Datendefinitionen sind und keinen Code erzeugen. Der Sichtbarkeit - private Kapselung für C - ist damit Genüge getan, dass die Strukturen nicht in irgendwelchen globalen Headerfiles definiert werden sondern sehr geordnet in Headerfiles, die nur bestimmte, oder nur ein C-File includiert, wenn sie nur für diese Compilierungseinheit sichtbar sein sollen. Das ist softwarestilistisch Datenpflege.

Die automatische Reflectiongenerierung hat damit dann ein leichteres Vorgehen: Es werden aus allen Headerfiles die Reflection generiert.


3 C-File mit den Konstantendefinitionen

Topic:.ReflectionJc.constReflection.

Last changed: 2007-09-23

Die Konstantendefinitionen erfolgen nach folgendem Schema (gilt für manuell erstellte Reflection ebenso wie für die Generierung):

 #define protected public  //only active inside this compilation unit
 #define private public    //  to enable the access to all elements.
 #include "Jc/ReflectionJc.h"
 #include "User.h"         //The file where the data are defined.
 //...
 extern const ClassJc reflection_User_XYZ
 const struct Reflection_Fields_UserABC_t
 { ObjectArrayJc head;
   FieldJc data[2];
 } reflection_Fields_UserABC =
 { CONST_ObjectArrayJc(FieldJc, 2, OBJTYPE_FieldJc, null,
                      &reflection_Fields_UserABC)
 , {
     { "intValue"
     , 0 //nrofArrayElements
     , REFLECTION_int32
     , (4<<kBitPrimitiv_Modifier_reflectJc) //bitModifiers
     , (int16)((int32)(&((UserABC*)(0x1000))->intValue) -
               (int32)(UserABC*)0x1000)
     , 0  //offsetToObjectifcBase
     , &reflection_UserABC
     }
   , { "reference"
     , 0 //nrofArrayElements
     , &reflection_UserXYZ
     , 0 //bitModifiers
     , (int16)((int32)(&((UserABC*)(0x1000))->reference) -
               (int32)(UserABC*)0x1000)
     , 0  //offsetToObjectifcBase
     , &reflection_UserABC
     }
 } };
 //
 const ClassJc reflection_UserABC =
 { CONST_ObjectJc(OBJTYPE_ClassJc + sizeof(ClassJc)
                 , &reflection_UserABC, null)
 , "UserABC"
 , 0
 , sizeof(userABC)
 , (FieldJcArray const*)&reflection_Fields_UserABC
 , null  //method
 , null  //superclass
 , null  //interfaces
 , 0
 };

Das Beispiel definiert die Reflections für eine struct oder class mit dem Name UserABC, die 2 Elemente enthält, darunter eine Referenz auf UserXYZ. Zunächst werden die FieldJc-Elemente definiert, und zwar als Array mit Kopfdaten. Die Anzahl der Elemente, hier 2, muss zweimal angegeben werden. Die Angaben der eigenen Adresse &reflection_Fields_UserABC wird durch das Makro CONST_ObjectArrayJc passend umgesetzt. Die eigene Adresse steht aus zwei Gründen in den const-Daten:

Wesentlich ist die Offsetbildung der Daten im 5. Element eines FieldJc. Im Falle von abgeleiteten C++-Klassen müssen hier zusätzlich static_cast<ObjectJc> erfolgen, da der Offset auf die Position der Basisklasse ObjectJc innerhalb einer C++-class bezogen ist. Das ist in diesem Beispiel nicht dargestellt. Der Bezug auf eine fiktive Instanz auf der Adresse 0x1000 ist notwendig (0 wäre intuitiver), da ein GNU-Compiler mit einer Adresse 0 nicht arbeiten möchte.

In der folgenden Definition der konstanten ClassJc-Instanz ist die Referenz auf die Fields eingetragen. Im Beispiel werden nur Fields verwendet. Der Zugriff auf Methoden und Superclasses ist vorgesehen.

Die Reflections für die skalaren Basistypen, REFLECTION_char usw., sind in Reflection_Jc.h als Konstante im kleinen Bereich (bis max. 100) definiert und als (Class_Jc const *) umgecastet. Das hat den Vorteil, dass bei ausgelagerten Dateien diese Zeiger problemlos erkannt werden können, ohne diese Reflectiondaten selbst zu haben. Damit kommt ein Datenabbild nur mit seinen eigenen Reflectiondaten aus, wenn davon ausgegangen wird, das ein Datenabbild neben seinen eigenen Strukturen meist nur skalare Basistypen verwendet, und Zeiger auf fremde Typen (Interfaces), die dann aber sowieso nicht im Datenabbild dabei sind.

Für alle anderen Typen muss der Linker die Definition des jeweiligen Types, enthalten in irgendeinem compilierten C-Files wenn ein komplexes System vorliegt, finden.


4 Automatische Generierung von Reflection aus Headerfiles

Topic:.ReflectionJc.genReflection.

Last changed: 2018-12-24

Die Reflectiongenerierung ist in 2018 überarbeitet. Zuvor gab es eine Java-Class org.vishia.header2Reflection.CmdHeader2Reflection die per Java-Algorithmus den ZBNF-Parser aufgerufen hat und danach die C-Quellen per Java-Programmierung erzeugt hat. Diese Java-class war eine der ersten, die das ZBNF-Parserergebnis verarbeitet hat und ist in ca. 2006 mit dem Parser entstanden. In der Folgezeit gab es aber einige Neuerungen be der Verwertung des Parserergebisses, insbesondere die automatische Speicherung in vorbereitete Java-Daten. Dazu gekommen ist die Textgenerierung über JZtxtcmd. Beides zusammen und die einfache Steuerung der Generierung über ein JZtxtcmd-Anwenderscript haben dann zu der hier vorgestellten neueren Lösung geführt.


4.1 Muster eines Anwenderscripts

Topic:.ReflectionJc.genReflection..

Das Muster ist aus dem Smlk_ObjO_Inspc...zip (Smlk-Download) entnommen, dort Smlk_ObjO_Inspc/test/+ObjOModule_ExmplCtrl/Cprj/genRefl_Inspc.jz.cmd. Es soll gekürzt das Prinzip zeigen:

Der erste Teil des Scripts dient dem Start der JZtxtcmd-Abarbeitung dieses Scripts. Das zbnf.jar enthält alle class-Files (ca. 1 MByte). Es sind keine weiteren Abhängigkeiten notwendig, Standard-Java.

call setZBNFJAX_HOME.bat
if "%ZBNFJAX_HOME%" == "" set ZBNFJAX_HOME=D:/vishia/ZBNF/zbnfjax
java -cp %ZBNFJAX_HOME%/zbnf.jar org.vishia.jztxtcmd.JZtxtcmd %0
pause
if ERRORLEVEL 1 pause
exit /B

Nach der ==JZtxtcmd-Marke beginnt im selben File das eigentliche Script, das mit %0 oben als Parameter angegeben wird. Aufgrund der gesetzten ZBNFJAX_HOME-Umgebungsvariable wird das zu includierende Script aufgefunden. Das Arbeitsverzeichnis currdir ist in diesem Fall das Scriptverzeichnis selbst, typisch ansonsten eines der Parent-Verzeichnisse.

==JZtxtcmd==
include $ZBNFJAX_HOME/jzTc/Cheader2Refl.jzTc;
currdir=<:><&scriptdir><.>;

Mit den folgenden Fileset werden die zu berücksichtigenden Headerfiles angegeben.

Fileset headers =
( ../../../emC/sourceApplSpecific/applConv/ObjectJc_simple.h
, ../../../emC/source/Ctrl/pid_Ctrl.h
, ../srcC:MeasPrep.h
, genSrc:*.h
, srcWinMain/TestObjOModules_genc-main.h
);

Im main() wird die Übersetzung im includierten Kern-Script mit den angegebenen Argumenten gestartet.

main()
{
  mkdir T:/header/Jc;
  zmake "genRefl/*.crefl" := genReflection(&headers
  //, html="T:/header"
  , fileBin = <:><&currdir>/genRefl/refl1.bin<.>
  , fileOffs = <:><&currdir>/genRefl/refloffs.c<.>
  );
  <+out>success<.+n>
}

Daraufhin entstehen in den angegebenen Zielverzeichnis, hier in allen drei Fällen genRefl die jeweiligen Files, die dann bei der C-Compilierung bzw. beim Proxy (refl1.bin) benutzt werden.


4.2 Details der Generierung

Topic:.ReflectionJc.genReflection..

Das includierte jzTc/Cheader2Refl.jzTc lässt sich anpassen. Die Anpassung ist nicht unkomplex, sollte aber in speziellen Anwendungsfällen zweckdienlich sein. Dazu folgend Hinweise:

Obj headerTranslator = java new org.vishia.header2Reflection.CheaderParser(console);

Diese Zeile instanziiert die genannte Java-class. Diese ruft sowohl den Parser auf als auch speichert die Daten. Bei Syntaxänderungen im Headerfile (Adaptionen an andere Styles) muss diese Java-class als Datenaufnehmer erweitert oder angepasst werden.

Map idSimpleTypes = {
 Num void =     1;
 Num int =      4;
 .....

Diese Map definiert die Zuordnung von bestimmten Type zu deren numerischen Kürzel anstatt einer ClassJc-Referenz in den Typen. Dies muss konform laufen mit der C-Quelle emc/source/emC/Reflection_emC.c, dort der const ClassJc* simpleTypes_ClassJc[...]. Die darüber stehenden Map definieren zu Basistypen weitere Eigenschaften adäquat der emC-internen Definitionen. Wenn man weitere Basistypbezeichnungen hat, beispielsweise INT32_T oder WORD, wie sie häufig stilistisch verschiedenartig üblich sind, so kann man hier einfach ergänzen.

Die Generierung einzelner Teile ist in entsprechende Subroutinen aufgeteilt.

sub genReflStruct(Obj struct, Obj fileBin, Obj fileOffs, Obj fileOffsTypetable)
{

Diese Subroutine wird gerufen im Kontext der Ausgabe für den einelnen file.crefl:

sub genReflectionHeader(Obj headerfile, Obj fileBin, Obj fileOffs, Obj fileOffsTypetable, String fileDst)
{
  FileSystem.mkDirPath(fileDst);  ##Note: makes only the directory, because fileDst does not end with /
  Openfile outRefl = fileDst;
  ....
  for(classC: headerfile.listClassC) {
    for(entry: classC.entries) {
      if(  (entry.whatisit == "structDefinition" || entry.whatisit == "unionDefinition")
        .....
        <+outRefl><:call:genReflStruct:struct=entry, fileOffs=fileOffs, fileOffsTypetable = fileOffsTypetable, fileBin=fileBin><.+>
      }

Damit werden die Ausgaben <:>...<.> auch in der Subroutine in den entsprechenden File geöffnet als outRefl geschrieben:

    <:>
====const struct Reflection_Fields_<&struct.name>_t
===={ ObjectArrayJc head;
====  FieldJc data[<&retEntries.nrofEntries>];
====} reflection_Fields_<&struct.name> =
===={ INIZ_ObjectArrayJc(reflection_Fields_<&struct.name>, <&retEntries.nrofEntries>, FieldJc, null, INIZ_ID_FieldJc)
====, {
    <&wrFields>
====} };
====
    <.>

Die Kennzeichnung mit ==== erleichtert die Erkennung des Ausgabetexts, diese Kennzeichnung wird bis zur Position des zugehörigen <:> in der Zeile nicht ausgegeben. Der Aufruf von <&wrFields> gibt den zuvor in der Stringvariablen wrFields gespeicherten Text wieder. Das ist deshalb hier so programmiert, weil im Algorithmus der Ausgabe der Fields deren Anzahl festgestellt wird, die mit <&retEntries.nrofEntries> zuvor ausgegeben werden muss.

Insgesamt gesehen kann man bei notwendigen Änderungen hier experimentieren. Die Beschreibung der Syntax und Semantik von JZtxtcmd kann entsprechend zu Hilfe genommen werden. Der erzeugte Quelltext lässt sich mit der Compilierung gut kontrollieren.

Für die Erzeugung des Binärfiles wird die class instanziiert:

sub genDstFiles(Obj target: ..... , String sfileBin, .....
{
 if(sfileBin) {
   Bool endian = false;  ##Note: depends on the target processor, false for PC platform.
   fileBin = new org.vishia.header2Reflection.BinOutPrep(sfileBin, endian, false, 0);

Diese Klasse bildet die Binärdaten als Java-Algorithmus. Dazu werden deren Operationen gerufen:

if(fileBin) {
  ....
  fileBin.addField(nameRefl, idType, typename, mModifier,nArraySize);

Der Binäraufbau wird in der Java-class letzlich mit den classen in org.vishia.byteData.Class_Jc usw. bestimmt, die auf org.vishia.byteDate.ByteDataAccessBase basieren.

Es arbeiten hier also Kern-Algorithmen in Java eng zusammen mit der Organisation der Aufrufe in einem JZtxtcmd-Script.


5 Zugriff auf Daten in Anwenderstrukturen unter Nutzung der Reflection

Topic:.ReflectionJc.ifcUserData.

Last changed: 2009-09-23


5.1 Erhalt der Reflection-Info ClassJc zu Anwenderdaten

Topic:.ReflectionJc.ifcUserData..

Last changed: 2007-09-23

Die zugehörigen Reflection-Informationen zu Anwenderdaten sind auf folgende Weise zu erhalten:

In beiden Fällen gilt, das falsche Reflection-Daten selbstverständlich falsche Zugriffe nach sich ziehen. Das gilt insbesondere für den zweiten Fall, da hier eine Fehlerfortpflanzung möglich ist. Solche Fälle können auftreten, wenn eine Datenstruktur geändert ist, der Reflectionfile wurde aber nicht neu compiliert. Das ist ein Fehler im Make-Prozess, der auch an anderen Stellen fatal ist und ausgeschlossen werden sollte. Ein Verschreiber des Anwenders (Reflection-Typ verwechselt) ist freilich ähnlich fatal wie eine Verwechslung bei einem cast. In einer ordnungsgemäß compilierten Software ist das Verfahren fehlerfrei.


5.2 Erhalt der ReflectionInfo FieldJc oder MethodJc zu einer gegebenen ClassJc

Topic:.ReflectionJc.ifcUserData..

Last changed: 2007-09-23

Wenn den Name eines Element in der Klasse oder struct vorgegeben ist, dann kann mit Aufruf von

 FieldJc const* field = getDeclaredField_ClassJc
                       (clazz, StringJc* sName);

das benannte Element (Attribut oder Referenz) gesucht werden. Die Suche ist eine Iteration über alle Fields der ClassJc und dauert eine kurze Zeit. Das Ergebnis ist die FieldJc-Instanz im const-Bereich. Dieser Zugriff erfolgt nur unter Kenntnis der ClassJc, unabhängig von den Anwenderdaten. In Java gibt es die adäquate Methode. Auch in Java werden dabei Fields auch mit private-Kapselung gefunden.

Wenn das erforderliche Element nicht vorhanden ist, dann wird eine Exception NoSuchField erzeugt. Das ist adäquat Java. Der Anwender sollte also um diese Methoden ein TRY-CATCH anordnen, wie es in Kapitel beschrieben ist.

Unter Kenntnis der Instanz FieldJc können nun die weiteren statischen Eigenschaften des Fields wie Typ, Sichtbarkeit, Modifikatoren getestet werden. immer noch ohne auf Anwenderdaten zuzugreifen.

Ein Array aller Fields bekommt man mit dem Aufruf von

 FieldJcArray const* fields = getDeclaredFields_ClassJc(clazz);

Damit kann über alle Fields iteriert werden. Ein FieldJcArray basiert auf ObjectArrayJc, die Anzahl der Fields kann also über fields->head.lenght ermittelt werden, die einzelnen FieldJc sind über fields->data[...] erreichbar.


5.3 Zugriff auf Datenfelder in Anwenderinstanzen

Topic:.ReflectionJc.ifcUserData..

Last changed: 2007-09-23

Mit den korrekten Reflection-Informationen FieldJc und dem Zeiger auf die Anwenderdaten kann auf die Attribute der Anwenderinstanz zugegriffen werden. Dabei muss die Referenz auf die Anwenderdaten auf die Adresse der ObjectJc-Basisklasse angegeben werden, wenn diese auf ObjectJc basiert. Damit ist ein static_cast<ObjectJc*>(instance) notwendig bei gegebener Instanzadresse. Die Methoden für Datenzugriffe von FieldJc übernehmen einen void-Zeiger, damit werden keinerlei Prüfungen zur Compilezeit ausgeführt. Der void-Zeiger ist aber deshalb notwendig, weil auch auf Instanzen zugegriffen werden soll, die nicht auf ObjectJc basieren. Im allgemeinen C/C++-Umfeld kann eine Vererbung von ObjectJc als wesentlichstes Paradigma nicht erzwungen werden, da mit Fremdcode auch umgegangen werden muss. Der Zwang zur fehlerfreien Programmierung ist der selbe, wie er etwa bei Operationen wie memcpy oder C-castings bzw. reinterpret_cast angewendet werden muss.

Lesen von Werten wie in Java:

Die letzte Methode enthält einen Index, den es in Java nicht gibt. Ist das Datenelement kein Array, dann muss hier -1 übergegeben werden. Die Nutzung eines solchen Index sollte auch auf die anderen oben genannten Methoden erweitert werden.

Setzen von Werten wie in Java:

Zugriffe für C/C++-Belange als Erweiterung

Die folgenden Methoden gibt es in dieser Art nicht in Java. Sie sind für die Datenanalyse in C/C++ notwendig:

Verfahrensweise bei Arrays und Containern

In Java müssen Arrays und Container manuell durchsucht werden. Bei einem Array kann man immer die Länge ermitteln, unabhängig von dessen Typ. Der Elementtyp eines Arrays ist aus der Abfrage der ClassJc des Arrays ermittelbar. Dann kann ein gezielter Zugriff auf die Elemente erfolgen, wie am Beispiel des folgenden Java-Codes gezeigt wird:

 int[] testArray = new int[10]; //the users program
 //
 //inspect it:
 Object inspectObj = testArray; //type unknown...
 Class clazz = inspectObj.getClass();
 String clazzName = clazz.getName();
 if(clazzName.equals("[I"))
 { //it is an integer array:
   int[] inspectInt = (int[])inspectObj; //cast
   int length = inspectInt.length;
   inspectInt[length-1] = 333;
 }

Handelt es sich um ein Array von Referenzen auf eine Klasse, dann wird als Name "[L..." angegeben. Das L steht für Object, danach folgt die Typbezeichnung der Referenzen des Arrays. Das ist lediglich eine Zusatzinformation. Auf die Array-Elemente muss nun ähnlich dem int[]-Beispiel zugegriffen werden, wobei die Zielreferenz entweder zum angegebenen Typ passen muss oder Object ist. Von diesem Object kann dann wieder die tatsächliche getClass() ermittelt werden, eine abgeleitete Instanz ist möglich.

Handelt es sich um einen Container wie List dann muss auf den Container entsprechend zugegriffen werden, um danach die Class der Elemente zu ermitteln.

Dieses Verfahren ist umständlich aber vollständig. In C/C++ ist es deshalb nicht allgemein anwendbar, weil es in C/C++ eine größere Vielfalt von Möglichkeiten der Gestaltung von Arrays gibt. Instanzen in Arrays oder Container sind nicht immer von Object abgeleitet. Es gibt es auch den typischen Fall der embedded Arrays wie

 typedef struct MyStruct_t{ int a; float b;}MyStruct;
 MyStruct array[25];

Hierbei kann ein allgemein programmierter Zugriff nur über sizeof-Kenntnis und Adressrechnung erfolgen, weil ein casting dort nicht erfolgen kann.

Die Vielfalt der Gestaltung von Arrays und Containern wäre nur durch aufwändigere Anwenderprogrammierung als in Java zu lösen, dabei müssen die Eigenschaften der Arrays jeweils spezifisch aus den Reflection ermittelt werden. Das Verfahren ist auch nicht Java-kompatibel Daher wurde die Array/Container-Erkennung und deren Auflösung in die Grundfunktionen von FieldJc eingebaut. Ein Zusatzparameter idx sorgt für eine entsprechende Indizierung eines Elementes in einem Array oder Container. In den Reflection wird getestet, ob es sich um ein statisches Array oder um ein embedded Array fester Größe handelt. Dann ist in der jeweiligen Methode die Adressrechnung intern möglich, da die Feldgröße und Byteanzahl in den Reflection steht. Handelt es sich um ein dynamisches Array des Grundtyps ObjectArrayJc dann ist die Größe in dessen Kopfdaten ermittelbar. Auch bestimmte Typen von Containern werden berücksichtigt, wobei die Container eines UML-Frameworks mit zusätzlich zugelinkten Methoden einbezogen werden können. Die Information über den Containertyp steht in den Reflection. Die Anwenderprogrammierung ist damit wesentlich vereinfacht. Für den Fall von Object-basierenden abgeleiteten Klassen kann wie in Java von der Referenz des Array- oder Container-Elementes dessen ClassJc bestimmt werden. Das wird aber insgesamt von der oben genannten Methode getClassAndObj_FieldJc(...) ausgeführt, die von get_FieldJc() intern gerufen wird. Die Indizierung wurde derzeit noch nicht bei den set- und get-Methoden für skalare Werte ausgeführt, sollte aber als Erweiterung genauso ausgeführt werden. Damit ändern sich die Signaturen der get- und set-Methoden auf skalare Werte um diesen zusätzlichen Parameter. Ein doppelter Satz von Methoden mit oder ohne Index ist nicht empfehlenswert, da damit die Anzahl der Methoden zunimmt, andererseits mit dem idx=-1 immer eine Kompatibilität zum nichtindizierten Fall durch einfache Quellenänderung herstellbar ist.

Um solche Funktionalitäten kompatibel auch in Java abzubilden (Javalike-Konzept), ist dort eine entsprechende Klasse als Erweiterung zu java.lang.reflection.Field möglich (org.vishia.bridgeC.FieldJc) , so dass die C-Erweiterungen wieder in Java ähnlich anzutreffen sind.


6 Detaildarstellung Zugriffe auf Anwenderdaten

Topic:.ReflectionJc.accessToUserDataViaReflection.

Last changed: 2007-09-23

Bei den Datenzugriffen wird die Referenz auf die Instanz als Argument angegeben. In C ist auch bei C-like-Interfaces der Zeiger auf die Instanz immer identisch mit dem Zeiger auf die Basisklasse Object_Jc. Bei den Datenzugriffen auf Feldelemente, die selbst nicht auf Object_Jc basieren, sondern nur deren Feld-Kopfstruktur ObjectArrayHead_Jc, ist der Zeiger auf die Instanzdaten nicht auf Object_Jc* konvertierbar. Daher wird bei den Methoden des Zugriffes für C wie

 METHOD_C int getInt_Field_Jc(const Field_c* ythis, void* obj);

der Zeiger auf die Instanz in der Form void* übergeben, siehe Abschnitt "Zugriff auf Daten von Arrays ohne Reflectioninfo der einzelnen Elemente"

Bei C++ ist dagegen der Zeiger auf die Instanzdaten meist nicht identisch mit dem Zeiger auf die Basisklasse ObjectJc. Es wäre eher ein Zufall bzw. es ist nur garantiert bei einfachen Klassen, die nur auf ObjectJc basieren. Eine Referenz kann vom Typ eines Interfaces auf eine Instanz sein. In diesem Fall zeigt die Referenz auf die Daten des Interface, beginnend mit dessen virtueller Tabelle inmitten der Instanzdaten, an der das Interface angeordnet ist.

Das nachfolgende Schema zeigt ein Speicherlayout, entnommen dem TestReflection.* für die Instanz myData. Für den Aufruf per Interface kennt die aufgerufene Methode, im Bespiel accessViaInterface(MyInterface* refIfc) die Daten ab der Position refIfc. Dort steht die virtuelle Tabelle des Interfaces und danach etwaige Daten der Interfaceklasse (wenn es kein reines Interface ist). Diejenigen Methoden, die in MyData überladen sind, kennen den Offset zwischen MyData und MyInterfaceBase und korrigieren den this-Zeiger um -8. Das leistet der C++-Compiler.

 myData: 9C 99 43 00 MyData::vtbl
         DA DA 5E BA       :;baseData
 refIfc: 90 99 43 00 MyInterfaceBase::vtbl
         80 99 43 00 ObjectifcBaseJcpp::vtbl
         5E BA FC 0E                  ::significance
         0C 3E 33 00                  ::significanceAddress
 333E18: 00 00 31 02 ObjectJc::objectIdentSize
         18 3E 33 00         ::ownAddress
         CD CD FF FF         ::idSyncHandles
         C0 FA 43 00         ::reflectionClass
         70 3E 33 00 MyData::refData

Damit mit Reflection gearbeitet wird, ist die Referenz auf ObjectJc notwendig. Diese steht zwar kurz dahinter, aber die Position hängt von der Datenkonstellation insgesamt ab. Keine Zeigerarithmetik kann diese bestimmen, sondern nur der Aufruf der Methode

 ObjectJc* obj = refIfc->toObject();

die im Basis-Interface ObjectifcBaseJcpp von MyInterface definiert ist und mit

 ObjectJc* MyData::toObject(){ return this; }

implementiert ist. Dieses return this gibt nicht den original übergebenen this-Zeiger zurück, sondern addiert den Offset, um die Basisklasse ObjectJc zu referenzieren, wie es der return-Typ verlangt, ein implizites cast. Das leistet der C++-Compiler. Mit der Referenz auf die ObjectJc-Basis können nun reflection-Methoden gerufen werden:

 ClassJc const* clazz = getClass_ObjectJc(obj);
 char const* clazzName = getName_ClassJc(clazz);
 StringJc fieldName = s0_StringJc("refData");
 FieldJc const* field = getDeclaredField_ClassJc(clazz, &fieldName);
 void* refAddr = getMemoryAddress_FieldJc(field, obj);
 void* refPoor = getRefAddr_FieldJc(field, obj, -1);
 void* refObj;
 ClassJc const* clazzField =
   getClassAndObj_FieldJc(field, obj, -1, &refObj);

In diesem Beispiel wird eine Referenz auf ein Interface MyInterface ermittelt, diese Referenz zeigt im Beispiel auf eine Instanz von MyData2. Die drei Zeiger verdeutlichen das C++-typische Speicherlayout hier von der Instanz von MyData2:

 myData2:E8 99 43 00  MyData2::vtbl
         DA DA 5E BA        :;baseData
 refPoor:DC 99 43 00  MyInterfaceBase::vtbl
 333E74: CC 99 43 00  ObjectifcBaseJcpp::vtbl
         5E BA FC 0E                   ::significance
         74 3E 33 00                   ::significanceAddress
 333E80: 00 00 34 02  ObjectJc::objectIdentSize
         80 3E 33 00          ::ownAddress
         CD CD FF FF          ::idSyncHandles
         10 FB 43 00          ::reflectionClass
         56 34 12 A0  MyData2::value

MyData2 hat im Beispiel ein ähnliches Speicherlayout. Die refAddr aus den obigen Anweisungen ist diejenige, auf der die Referenz steht. Es ist der Wert0x333E28 der im oberen Speicherlayout zu finden ist. Dort steht die Referenz mit dem Wert 0x333E70, in refPoor gespeichert. Um von dieser Adresse ausgehend weiterhin mit Reflections zu arbeiten, muss man irgendwie geeignet die Adresse des ObjectJc-Anteils der Instanz zeigern. Es ist allerdings so, dass im Umfeld des C++-compilierten Codes der Typ der Referenz nicht bekannt ist. Es handelt sich um ein void*, der durch Lesen der Referenz ermittelt wird. Der Typ steht allerding in der zuständigen FieldJc-Instanz, hier field, und zwar im Typ des Fields. Dieser ist reflection_MyInterface. Für diesen Typ ist bekannt, dass er auf ObjectifcBaseJcpp basiert, entsprechend der Notation im Headerfile. In den ClassJc-Daten für reflection_MyInterface ist sowohl das entsprechende Bit in modifiers gesetzt, als auch das Datenelement posObjectBase ist gesetzt mit dem Wert 0x0004. Der Wert ergibt sich bei der Compilierung der generierten Reflection aus dem Ausdruck

 (int)(static_cast<ObjectifcBaseJcpp*>((MyInterface*)0x1000)) - 0x1000

Der Ausdruck wird vom Compiler als Konstante berechnet mit den gleichen Bedingungen, wie anderer Code compiliert wird.

Mit diesen Informationen kann nun eine Adresskorrektur des void*-Zeigers der Referenz um 4 und ein nachfolgendes cast auf den Typ ObjectIfcBaseJcpp erfolgen. Die Korrektur und cast-Aktion wird innerhalb der Routine getClassAndObj_FieldJc(...) ausgeführt:

 ObjectifcBaseJcpp* objifc = (ObjectifcBaseJcpp*)
                             ((MemUnit*)(ref) + offsetBase);

Eine Adresskorrektur und ein hartes cast ist allerdings eine Vermutung, dass alle Daten richtig sind. Wenn im Anwenderprogramm ein Fehler vorliegt, dann würde der nachfolgende Aufruf

 retObjBase = objifc->toObject();

zu einem fatalem Absturz führen, da der Maschinencode über den Speicherinhalt die virtuelle Tabelle aufsucht und dort einen indirekten Call ausführt. Im Speicher kann im Grunde genommen irgend etwas stehen, erzeugt durch gegebenenfalls einen Anwenderfehler. Ein durchaus typischer anderer Anwender-Fehler ist, dass die Compilierung der Reflection-Information möglicherweise nicht aktuell ist, da das Make-System nicht korrekt arbeitet. Daher ist vor dem Aufruf ein Sicherheitscheck eingebaut, der kaum Rechenzeit benötigt aber den letzteren Fehler entdeckt und Datenfehler mit einer höhereren Wahrscheinlichkeit entdeckt: Im ObjectIfcBaseJcpp sind zwei Datenzellen enthalten, die einerseits eine signifikante Belegung 0xefcba5e und andererseits die eigene Speicheradresse enthalten. Das ist im obigen Memory-Layout erkennbar. Bei dem Signifikanz-Wert kann man ifcbase lesen, wenn man das e als i und die 5 als s liest. Das ist im Debugmodus eine kleine Merkhilfe. Die Wahrscheinlichkeit, dass bei einem Datenfehler diese 2 Belegungen korrekt sind, aber die vtbl-Adresse falsch, ist mindestens 1 : 3.

Nach diesem Signifkanz-Check kann nun objifc->toObject() gerufen werden und damit über die korrekte Implementierung dieser Methode in der instanziierten Klasse der ObjectJc-Zeiger ermittelt werden.

Wenn eine Referenz vorliegt von einen Typ, der auf ObjectJc selbst basiert, dann ist das Verfahren adäquat. Das entsprechende Bit in den ClassJc des Referenztyps ist gesetzt, und der Offset zu ObjectJc ist bekannt, beispielsweise für den Typ MyData mit

 (int)(static_cast<ObjectJc*>((MyData*)0x1000)) - 0x1000

niedergelegt. Ein Signifikanztest ist auch hier möglich, weil in einem ObjectJc im Datenlement ownAddress immer die selbe Speicheradresse stehen soll, auf der ObjectJc liegt. Dieser Signifikanztest ist derzeit nicht aktiv, weil diese Festlegung ggf. nicht in allen Produktiv-Codes derzeit beachtet wird (TODO). Es ist allerdings auch hier nicht so kritisch, da keine Methode gerufen wird sondern im Fehlerfall lediglich ein falscher Zeiger auf ClassJc ermittelt wird. Dieser ist einerseits auch signifikanz-testbar, andererseits gibt es dann falsche Anzeigen, die von der Anwendung bemerkt werden. Der scharfe Signifikanztest sollte hier per Compileroption abschaltbar sein wo nötig und ansonsten erfolgen (TODO).


7 Methoden in Reflection

Topic:.ReflectionJc.accessToMethod.

Last changed: 2007-09-23

Der Zugriff auf Methoden in Reflection ist derzeit nicht aktiv genutzt und getestet. Die Vor-Überlegungen und -Untersuchungen und vorhandene Codes sollen allerdings hier dargestellt werden.

Die relevante Struktur ist struct:_MethodJc, der Zeiger auf ein Array von methoden in ist struct:_ClassJc als

 MethodJcARRAY const* methods;

enthalten. Allerdings werden Methoden bei der automatischen Reflection-Generierung aus Headerfiles derzeit nicht unterstützt.

Die Methoden sind in MethodJc aufgeführt als

 Object__VoidMethod adress;

Das ist die Startadresse. Derzeit nicht ausreichend berücksichtigt sind rein abstrakte virtuelle Methoden von Interfaces, insbesondere nicht Methoden eines C-like-Interfaces über eine Mtbl (Methodentabelle).

Die Startadressse ist zwecks einheitlicher Speicherung auf den dargestellten Zeiger auf eine void-Methode gecastet, in C++ mit

 typedef void(ObjectJc::*Object__VoidMethod)();

definiert, für C mit

 typedef void (*Object__VoidMethod)();

Die Parameterabhängigkeit ist etwas vereinfacht aufgeführt, und zwar mit einem

 Type_MethodJc eType;

Die zugehörige enum-Definition

 typedef enum Type_MethodJc_t
 { kNothing_Method
 , kInt_void, kInt_Int, kInt_2Int, kInt_3Int, kInt_Float, kInt_2Float
 , kFloat_void, kFloat_Int, kFloat_2Int, kFloat_3Int
 , kFloat_Float, kFloat_2Float
 , kVoid_void, kVoid_Int, kVoid_2Int, kVoid_3Int, kVoid_Float, kVoid_2Float
 }Type_MethodJc;

berücksichtigt typische Konstellationen, aber nicht alle. Der Gedanke hierbei ist: Methoden über Reflection sind nur dann zweckmäßig, wenn es einfache von außen aufrufbare Methoden sind. Diese dürften kaum referenzierte Daten benötigen, sondern eine Kombination von skalaren Werten. Die Kombinationen sind aber nicht hinreichend. Hier kann eine bessere Auswahlmöglichkeit für zukünftige Entwicklungen zweckdienlich sein. Erste Erfolge sind mit dem hier vorgestelltem System jedenfalls möglich.

Die entscheidende Methode für den Aufruf ist adäquat zu Java

 invoke_v_MethodJc(const MethodJc* ythis, void* obj, int32ARRAY* params);

Die Parameter sind hier wieder auf eine einfache Art übergeben, ein Ausbau ähnlich wie in Java wäre denkbar. Der Zeiger auf die Anwender-Instanz wird als void* übergeben, weil auch hier nicht vorausgesetzt wird, dass alle Instanzen auf ObjectJc basieren. Wie bei den FieldJc-Zugriffen ist der Anwender für die Korrektheit des Zeigers verantwortlich bzw. Suchfunktionen sorgen für eine entsprechende Korrektur aufgrund der gegebenen Reflectiondaten ähnlich wie bei getClassAndObj_FieldJc(..).

Der Aufruf invoke_v_MethodJc(...) ist in C ein einfacher Aufruf über einen Funktionszeiger. In C++ muss die Instanz mit übergeben werden. An dieser Stelle ist noch Klärungsbedarf (TODO).


8 Zugriff auf Basisklassen und Interfaces

Topic:.ReflectionJc.accessToIfc.

Last changed: 2007-09-23

Der Zugriff auf Interfaces und Basisklasse in Reflection ist derzeit aktiv genutzt für den Aufruf von Methoden über das C-Interface, für Datenzugriffe aber derzeit niht genutzt und getestet.

Der Zeiger auf ein Array von Interfaces in ist struct:_ClassJc als

 struct ClassOffset_idxMtblJcARRAY_t const* superClasses;
 struct ClassOffset_idxMtblJcARRAY_t const* interfaces;

enthalten. Sowohl Interfaces als auch Basisklassen werden als Array gefürt. Javalike müsste es nur eine Basisklasse geben, das ist aber in einigen C++-Anwendungen nicht so. Daher wurde auch hier auf ein Array orientiert. Diese Arrays werden bei der automatischen Reflection-Generierung aus Headerfiles derzeit nicht unterstützt, sind allerdings in einigen manuell erstellen Reflection getestet.

Der Typ ClassOffset_idxMtblJc, auf dem dem Array-Typ basiert, ist wie folgt definiert:

 typedef struct ClassOffset_idxMtblJc_t
 {
   /** The reflection definition*/
   ClassJc const* clazz;
   /** Index of the virtual table inside its parent.*/
   int idxMtbl;
 }ClassOffset_idxMtblJc;

Der zweite Parameter dieser Struktur ist entscheidend für das dynamische Binden in C. Dieses läuft insbesondere über die Reflection-Daten und ist gesondert in Topic:_DynamicCallInC dargestellt.


9 Zugriff auf Daten von Arrays ohne Reflectioninfo der einzelnen Elemente

Topic:.ReflectionJc.oldoldold.

Last changed: 2007-09-23

In Java sind alle Feldelemente, die keine einfache skalare Variable darstellen (int, float, ...), grundsätzlich über Referenzen geführt. Die referenzierten Instanzen erben von Object und sind demzufolge mit Reflections zugänglich. Beispiel:

 final class Data
 { int a;
   float f;
 }
 ...
 Data[] dataArray = new Data[12];

In dem gezeigtem Beispiel wird nicht etwa, wie in C(++) zu erwarten, ein Feld mit 12 Elementen vom Typ Data angelegt, sondern es wird nur ein Feld mit 12 nichtinitialisierten Referenzen angelegt. Würde Data nicht final sein, so könnten dort auch von Data abgeleitete Klassen referenziert werden. Um die Feldelemente tatsächlich anzulegen, ist nochmal Arbeit erforderlich:

for(int i = 0; i < dataArray.length(); i++){ dataArray[i] = new Data(); }

Erst damit bekommt man das in C erwartete Ergebnis. Java ist hier etwas komplizierter, aber dafür universell und sicher. Das entspricht dem Referenzkonzept, Java kennt keine "eingebetteten Daten", in C(++) sind eingebetteten Daten dagegen oft erwünscht und durchaus oppurtun. Ein Feld aus zusammengesetzten Elementen soll direkt angelegt werden:

 typedef struct Data_t
 { int a;
   float f;
 }Data;
 Data* dataArray = new Data[12];  //(C++)
 Data* dataArray = (Data*)(malloc(12*sizeof(Data));  //C-Variante

Dieses einfache und übliche Konstrukt aus C kennt jedoch weder eine Basisklasse Object, damit keine Reflection, noch ist eine Information über die Anzahl der Feldelemente direkt mit dem Feld verbunden vorhanden (muss geeignet extra programmiert werden).

Basisklasse ObjectArrayHead_Jc ist zu verwenden

Aus obigen Grund ist in der CRuntime_Javalike eine Basisklasse ObjectArrayHead_Jc definiert. Diese enthält die Kopfdaten eines beliebigen Feldes und ist von Object_Jc abgeleitet. Es ist möglich, dass die Feldelemente unmittelbar nach den Kopfdaten folgen.Dann bekommt man eine ähnlich kompakte Datenstruktur wie man es von C aus gewöhnt ist. Auf dieser Basis arbeitet das Reflectionprinzip für Arrays. Beispiel:

 typedef struct Data_t
 { int a;
   float f;
 }Data;

 typedef struct DataArray_t
 { ObjectArrayHead_Jc array;
   Data data[100];    //NOTE: 100 is only a debug size, use dynamic size, see sizeof_DataArray.
 }DataArray;
 /*Constructor sets default values at the elements and set the head data of array.*/
 DataArray* constructor_DataArray(DataArray* ythis, int size);
 #define sizeof_DataArray(SIZE) (sizeof(ObjectArrayHead_Jc) + SIZE * sizeof(Data))
 #define new_DataArray(SIZE) constructor_DataArray( (DataArray*)malloc(sizeof_DataArray(SIZE)), SIZE ))
 DataArray* dataArray = new_DataArray(12);

Im Speicher werden die Kopfdaten und die angegebene Anzahl von Feldelementen des Typs Data in einem Block hintereinander angeordnet. Das ist die Basis für Reflections auf Arrays.

In C: Die Reflections des Arraytypes enthalten die Informationen der Elemente In Java enthält die Reflectioninformation (Class) einer Feldinstanz lediglich den Namen des Feldes, keine weiteren Informationen. Auch das Attribut length, Bestandteil jeder Arrayinstanz, wird in den Reflections nicht angezeigt. Es ist selbstverständlich.

 final class Data
 { int a;
   float f;
 }
 ...
 Data[] dataArray = new Data[12];
 for(int i = 0; i < dataArray.length(); i++){ dataArray[i] = new Data(); }
 Class classDataArray = dataArray.getClass();
 Class classData      = dataArray[0].getClass();
 System.out.println(classDataArray.getName());
 Field[] fieldsDataArray = classDataArray.getDeclaredFields();
 System.out.println(classData.getName());
 Field[] fieldsData      = classData.getDeclaredFields();
 Field field_a           = classData.getField("a");

Die erste Ausgabe für classDataArray.getName() liefert "[Lpackage.Data" als Anzeige, das es sich um ein Feld vom Referenztyp Data handelt. Das fieldsDataArray ist ein Feld der Länge 0. Die zweite Ausgabe für classData.getName() liefert "package.Data" als Typ des Feldelementes, das fieldsData enthält die Attribute von Data.

In C(++) ist für ein mit ObjectArrayHead_Jc gebautes Feld keine Reflection für die Elemente abrufbar, statt dessen ist beides kombiniert mit

 Class_Jc* classDataArray = getClass_Object_Jc(&dataArray->array.object);
 Field_Jca* fieldsDataArray = getDeclaredFields(classDataArray);
 Field_Jc field_a           = getField_Class(classDataArray, const_String("a"));
 println_PrintStream(&System.out, getName_Class_Jc(classDataArray);

abrufbar. Die Ausgabe liefert hier wie in Java "[LData", aber fieldsDataArray enthält die Attribute des Feldelementes. Das ist möglich, da die Elemente mit genau diesem Datentyp (und im Gegensatz zu Java nicht mit einem möglicherweise abgeleitetem Datentyp oder mit null) besetzt sind.

Probleme beim Test der Gültigkeit des angegebenen Feldelementes In Java wird ein Wert eines Attributes gelesen oder gesetzt mit

 Field field_a = dataArray[0].getClass().getField("a");
 int a = field_a.getInt(dataArray[5]);
 field_a.setInt(dataArray[5], 123);

Das field_a ist dabei aus der Reflection eines Feldelementes bestimmt, aber nicht notwendigerweise aus dem des 5. Feldelementes, sondern beispielsweise aus dem ersten, da alle Elemente vom Aufbau her identisch sind. Grundsätzlich gilt in Java, dass beim Zugriff auf ein Element getestet wird, ob die angegebene Instanz dem selben Typ entspricht wie der class-Typ, der dem Field entspricht (ermittelbar auch manuell programmiert mit field_a.getDeclaringClass(). Nur so kann gesichert werden, dass keine unpassenden Instanzen beim Feldzugriff angegeben werden. Der Aufwand zur Runtime ist ähnlich kritisch/unkritisch zu bewerten wie die Tatsache in Java, dass jeder Index eines Feldes zur Runtime getestet wird. Die VM optimiert hier einiges, es handelt sich jeweils nur im Nanosekunden bei den üblichen Prozessoren.

In C(++) stehen beim adäquaten Aufruf

 Field_Jc* field_a = getField_Class_Jc(getClass_Object_Jc(dataArray),const_String_Jc("a"));
 int a = getInt_Field_Jc(field_a, dataArray[5]);
 setInt_Field_Jc(field_a, dataArray[5], 123);

die Reflection-Infos aus dataArray[i] nicht zur Verfügung, da ein Feldelement nicht auf Object basiert. Daher muss in diesem Fall der Test der Zulässigkeit des Zugriffes entfallen. Das entspricht aber dem allgemeinem Programmierparadigma in C(++). Es werden an vielen Stellen Zeiger verwendet, ohne nochmaligen expliziten Zulässigkeitstest.

Die Zulässigkeit kann per manueller Programmierung aber wie folgt getestet werden:

 if(getDeclaringClass_Field_Jc(field_a) == getClass_Object_Jc(dataArray))
 {...}

Das kann in Java ebenfalls genauso explizit erfolgen. Basis für den Test ist hier das Feld selbst, in Java steht an dieser Stelle adäquat wie oben bei der Ermittlung von field_a das Feldelement:

 if(field_a.getDeclaringClass() == dataArray[5].getClass())
 {...}

10 Beispiele

Topic:.ReflectionJc..

Im Downloadbereich emC-Download efinden sich Beispiele, die mit Reflection arbeiten. Die Reflection spielen in fast allen Beispielen eine Rolle, da es sich um eine maßgebliche Eigenschaft der emC-Programmierung handelt.

Im Downloadbereich Smlk-Download sind ebenfalls bei allen Beispielen die Reflection, hier meist mit dem TargetProxy-Konzept realisiert: Die Reflection sind in den Inspc-Simulink-Sfunctions realisiert bzw. unterstützt, insbesondere der SFblock lib_Inspc/Service_Inspc bildet den Service für Simulink ab. Die Videos zeigen die Arbeit mit dem Inspector-Tool. Die generierten Codes arbeiten dann mit dem Target-Proxy-Konzept.

Der Target-Proxy als C-Quellen mit einem Visual-Studio-15 Projekt zum übersetzen befindet sich in den emC-Download. Fertig compiliert als Windows-exe ist der Target-Proxy im Inspc-Download enthalten. Dort enthalten ist ebenfalls die Inspc-GUI, bzw. diese ist im download-vishiajar enthalten.