Inhalt
Topic:.ReflectionJc.
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
für einen Zugriff auf alle Daten in einer laufenden Software, ohne diese im Schritt-Test anzuhalten,
für einen Zugriff auf Daten außerhalb eines Compilierungsprozesses für aufgesetzte Funktionalitäten. Beispielsweise können Parameter zur Laufzeit via XML vorgegeben werden und dann mittels symbolischen Zugriff in die vorhandenen Datenstrukturen abgelegt werden.
für interpretative Abarbeitungen. Beispielsweise kann eine Zustandsmaschine projektiert werden. Die Zustandsmaschine muss bestimmte Bedingungen abfragen und Subroutinen aufrufen (Actions). Wenn dies unabhängig vom Compilierungsprozess erfolgen soll, dann muss ein Interpreter des Zustandsmaschinen-Codes per symbolischen Zugriff die entsprechenden Aktionen ausführen.
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.
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.
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:
Klassenlader-Funktionalitäten (classLoader)
Sicherheitstest-Funktionalitäten (security check)
Strenge private / package private / protected / public - Unterscheidung
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:
class:_ClassJc
class:_FieldJc
class:_MethodJc
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:
Name des Typs als Text char name[32]
Referenz auf das Array der Element-Reflection FieldJc
Referenz auf das Array der wichtigen einfachen Operationen MethodJc
Referenz auf das Array der Basisklassen und Interfaces ClassOffset_idxMtblJc
.
Modifier-Bits siehe enum Modifier_reflectJc_t
.
Adresse der Tabelle der virtuellen Operationen (nicht zu den Reflection zugehörig) für sichere dynamische Operationen auch in C.
Die Information für Datenelemente einer struct
oder class
enthält in FieldJc
in 48 Byte:
Name als Text char name[30]
Anzahl statischer Feldelemente (max. 65535) oder Positionen eines Bitfield (max. 16 bit, in 512 aufeinanderfolgenden Bytes möglich)
Datentyp als Referenz auf dessen ClassJc
bzw. Kurzcode für Standard-Typen
Position des Elements in der struct
oder class
, die Größe der struct ist dabei auf 65536 begrenzt.
Modifier-Bits, siehe Modifier_reflectJc.
Die Modifier-Bits der Elemente enthalten:
Art des Elementes, Referenz, direkt, Bitfield, Array, bestimmte Containertypen
Zugriffslevel für den Inspector-Dienst (Passwortschutz)
private/protected, static usw. Kennzeichnung des Elementes wie in Java.
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.
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.
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.
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.
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
bei kleinen (preiswerten) Prozessorboards/Prozessoren ist oft wenig RAM vorhanden, aber durchaus ein angemessener ROM-Bereich,
der Code lässt sich von vornherein hardwareseitig schreibgeschützt laden, wichtig für die Sicherheit der Software beispielsweise auch bei Zugriffen nach einem Software-Absturz: Auf die Reflection-Infos kann man sich verlassen, sie sind keinesfalls überschrieben.
Keine doppelte Haltung von Initialisierungsinfos und abgelegten Daten.
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.
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:
Einerseits sind damit Signifikanztests der Daten möglich, die die Software auch bei Fehlern in Form von Datenüberschreibern (Querschlägern) sicherer machen.
Andererseits stehen damit die eigenen Adressen an definierter Stelle innerhalb jedes struct:_ObjectJc in den Daten. Das ist wichtig, wenn Daten übertragen werden und die Reflections außerhalb angewendet werden.
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.
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.
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.
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.
Topic:.ReflectionJc.ifcUserData.
Last changed: 2009-09-23
Topic:.ReflectionJc.ifcUserData..
Last changed: 2007-09-23
Die zugehörigen Reflection-Informationen zu Anwenderdaten sind auf folgende Weise zu erhalten:
Entweder die Anwenderdaten basieren auf ObjectJc
und enthalten dort den Zeiger auf die Reflection (ClassJc const*
). Die Subroutine getClass_ObjectJc()
liefert die entsprechende Referenz.
Oder es wird von einer übergeordneten Instanz ausgegangen, für die die Reflection in der eben genannten Art vorliegen. Ausgehend
von der übergeordneten Instanz wird eine referenzierte Instanz ermittelt, parallel dazu wird in den Reflection der Typ der
referenzierten Instanz ermittelt. Dann braucht die referenzierte Instanz nicht auf ObjectJc basieren, es kann sich beispielsweise
um eine einfache Datenstruktur handeln. Wenn die Referenz der Instanz von einem Basistyp ist, die Instanz selbst abgeleitet
(C++), dann zeigt der Zeiger im Maschinencode auch auf die Adresse, auf der die Basisdaten stehen. Referenz und Typ passen
also immer zueinander. Man kann aber auf diesen Weg keine Daten der abgeleiteren Klasse sehen. Die aufzurufende Subroutine
dafür ist getClassAndObj_FieldJc(...)
, siehe in folgendem Text.
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.
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.
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:
int32 getInt_FieldJc(field, obj)
liefert den int-Wert, gegebenfalls gecasted wenn das Element nicht vom int-Typ ist. Diese und die folgenden Methoden sind
gleichartig auch in Java vorhanden.
float getFloat_FieldJc(field, obj)
liefert den float-Wert, gegebenfalls gecasted.
double getDouble_FieldJc(field, obj)
liefert den double-Wert, gegebenfalls gecasted.
bool getBool_FieldJc(field, obj)
liefert den bool-Wert, gegebenfalls gecasted.
int64 getInt64_FieldJc(field, obj)
liefert den int64-Wert, gegebenfalls gecasted.
void* get_FieldJc(field, obj, idx)
liefert die Adresse einer referenzierten Instanz. Dabei wird die Adresse so korrigiert, dass eine vorhandene Basisklasse
ObjectJc innerhalb der Instanz adressiert wird. In C++ ist es möglich, dass verschiedene Basisklassen innerhalb einer Instanz
angeordnet werden. Für die Weiterarbeit mit Reflection muss die Referenz auf ObjectJc innerhalb einer Anwenderinstanz benutzt
werden. Wenn die Anwenderinstanz nicht auf ObjectJc basiert, wird deren unmittelbare Adresse geliefert.
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:
void getFloat_FieldJc(field, obj, float)
setzt den float-Wert, gegebenfalls gecasted.
void setDouble_FieldJc(field, obj, double)
setzt den double-Wert, gegebenfalls gecasted.
void setBool_FieldJc(field, obj, bool)
setzt den bool-Wert, gegebenfalls gecasted.
void setInt64_FieldJc(field, obj, int64 )
setzt den int64-Wert, gegebenfalls gecasted.
void set_FieldJc(field, obj, idx)
setzt die Adresse einer referenzierten Instanz. Diese Methode ist derzeit nicht implementiert, da bisher nicht benötigt.
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:
void* getRefAddr_FieldJc(field, obj, idx)
liefert die Adresse einer referenzierten Instanz. Das Verhalten ist ähnlich wie get_FieldJc(...)
, hier erfolgt aber keine Korrektur der Adresse auf ein ObjectJc
. Die Methode wird einerseits innen benutzt, andererseits dient sie zur Anzeige des tatsächlichen Inhaltes einer Referenz
ohne Korretur für Inspector-Zwecke.
void* getMemoryAddress_FieldJc(field, obj)
liefert die Adresse des Feldes. Diese Methode ist innere Methode und dient zur Speicheradress- und Inhaltsanzeige (Inspector).
ClassJc const* getClassAndObj_FieldJc(field, obj, idx, void** retObj)
liefert Referenz und Typ eines Fields. Diese Methode existiert nicht in Java, da in Java der Typ eines Objektes immer aus
dem Object selbst ermittelt werden kann (Object.getClass
). In den Reflections unter C/C++ ist das aber nicht so, da die Basisklasse Object nicht generell vorausgesetzt werden kann.
Die Typinformation wird aus dem gegebenem FieldJc
gelesen. Die Referenz zeigt auf die Basisklasse ObjectJc
wie get_FieldJc(...)
wenn vorhanden.
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.
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).
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).
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.
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()) {...}
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.