C/C++-Headerfiles mit XMI abbilden

C/C++-Headerfiles mit XMI abbilden

Inhalt


Topic:.Cheader2Xmi.

pStyle=std tableStyle=stdTable

.


1 Motivation

Topic:.Cheader2Xmi..

pStyle=std tableStyle=stdTable

Headerfiles sind Bestandteil der Programmierung in C und C++. Gleichzeitig sind Headerfiles auch Strukturbeschreibungen von Daten und Definition von Konstanten. In dieser Eigenschaft ragen sie über die reine Verarbeitung in C/++ hinaus:

Um doppelte Datenhaltung zu vermeiden und den C/++-Programmierern nahe zu kommen, sollte man Informationen unmittelbar aus Headerfiles ziehen. Die Headerfiles sind also die primäre Quelle. Für die Weiterverarbeitung gibt es nun verschiedene Möglichkeiten. Erwartet man nur recht einfache struct und define, dann tut es auch ein perl-script mit regular expression. Aber man stößt hier schnell an Grenzen.

Das XMI-Format XML Metadata Interchange als übergreifend definiertes Standardformat zumindestens vom Ansatz her repräsentiert, Informationen aus Headerfiles zu codieren. Aufgrund der Basis XML kann man dann in der XML-Welt alle möglichen Darstellungen daraus generieren.

Demzufolge braucht es einen Konverter vom Header-Format (C/++) nach XMI. Es gibt mehrere Möglichkeiten für solche Konverter. Basis sollte jedenfalls eine C/++-Syntax sein, die einem Parser zugrunde liegt. Nur so lassen sich alle Nuancen der Formulierung in Headerfiles fassen. Für die Abbildung nach XMI muss man den C-Gedanken Abbild von Daten- (Byte-) Anordnungen und Definitionen in den Objektorientierten Gedanken Abbild der Strukturierung von Daten überführen.


1.1 Grundsätzliche Überlegungen zu Klassen und Beziehungen

Topic:.Cheader2Xmi...

pStyle=std tableStyle=stdTable

Eine struct in C kann recht einfach als Klasse aufgefasst werden. Man kann in C objektorientiert programmieren, indem man Subroutinen schreibt, die hauptsächlich auf die Daten einer struct orientiert sind. Zugriffe auf die Daten erfolgen aus einer Anwenderebene nie direkt sondern immer über solche Subroutinen. Es ist auch möglich, Daten zu kapseln. Und zwar mit einem Factory-Pattern, dass mittels einer create-Methode eine struct anlegt, nach außen aber nur die Referenz auf die struct als Vorwärtsdeklaration preisgibt. Damit kann der Compiler typgerecht prüfen. ohne dass der Aufbau der struct nach außen hin bekannt sein muss.

Ein Zeiger (innerhalb einer struct) auf eine andere struct ist eine Assoziation.

Aber auch eine eingebettete Struktur stellt eine Assoziation dar, genauer eine Komposition:

 typedef struct A_t
 { int x;
   struct B_t
   { float y;
     float z;
   } b;
   struct C_t* c;
 }A_s;

Hier haben wir aus Objektorientierter Sicht eine Klasse A, die eine Klasse B enthält. Das ist ganz klar eine Komposition. c ist dagegen eine Assoziation, kann aber auch Aggregation oder Komposition sein, falls dies bei der Datenanlage so bestimmt wird:

 struct A_t* create_A()
 { struct A_t* a= (A_s*)malloc(sizeof(A_s));
   memset(a, 0, sizeof(A_s);
   a->c = (C_s*)malloc(sizeof(C_s));
 }

Das obige Beispiel legt die Komposition aus A_S, struct B_t und C_s an.


2 Formulierungskonventionen

Topic:.Cheader2Xmi..

pStyle=std tableStyle=stdTable

Grundsätzlich sollte es nur wenige Konventionen außerhalb dessen, was C-Syntax ist, geben. Jeder hat bereits seine eigenen. Doch einige Konventionen, die verträglich eingeführt werden können, sind nicht schlecht.


2.1 Namenskonventionen typedef struct tagname {...} typename;

Topic:.Cheader2Xmi...

pStyle=std tableStyle=stdTable

Eine struct kann in C als auch in C++ in zweierlei Art bezeichnet werden:

 struct tagname
 typename

Der typename kann nur mit einem typedef gewonnen werden, was in C für eine Strukturdefinition Pflicht ist. Erst mit C++ gibt es die Vorwärtsdeklaration bzw. struct-Deklaration ohne typedef:

struct name;

struct name { int a; float b;};

Einige C-Compiler können das auch. Will man aber kompatibel programmieren, sollte mantypedef benutzen.

Einige C-Compiler, aber nicht alle, vertragen den selben Namen als tagname und typename. Wiederum, kompatibel programmiert ist das nicht voraussetzbar.

Da es im XMI-Umfeld dann nur noch um Typnamen geht, sind Konventionen für den Tagnamen nicht schlecht. Man kann diesbezüglich aber kaum etwas vorschreiben, da es bereits in verschiedenen Umfeldern festgeschriebene Konventionen gibt. Folglich muss ein Header2Xmi-Konverter den Zusammenhang zwischen Tagnamen und Typenamen aus den Headerfiles selbst finden. Als Konvention wird aber in allen Beispielen die Schreibweise wie oben benutzt hier verwendet.


3 Konvertierung

Topic:.Cheader2Xmi..

pStyle=std tableStyle=stdTable

.


3.1 Parser

Topic:.Cheader2Xmi...

pStyle=std tableStyle=stdTable

Wichtig ist, den Headerfile nach C/++-Regeln zu zerlegen. Mit einfachen regular-expression-Mitteln wird man schnell an Grenzen stoßen. Sinnvoll ist jedenfalls der Einsatz von http://www.de.wikipedia.org/wiki/yacc. Der Autor ist aber einen anderen Weggegangen:Einsatz eines ZBNF-Parsers.

Der ZBNF-Parser arbeitet interpretativ, damit ist die zugrundeliegende Syntax leicht anpassbar. Außerdem wird der semantische Aspekt von Anfang an berücksichtigt, was die Interpretation der Daten unterstützt. Mit dem Java-Programm javadoc:_org/vishia/zbnfXml/Zbnf2Xml kann man beliebige Flachtexte unmittelbar nach XML konvertieren. Das notwendige ZBNF-Script für Headerfiles ist XmlDocu_zbnf/Cheader.sbnf. Damit hat man auf einfache Weise aus Header XML, aber noch nicht XMI.


3.2 Konvertierung nach XMI

Topic:.Cheader2Xmi...

pStyle=std tableStyle=stdTable

Die Konvertierung aus dem geparsten XML nach XMI erweist sich als nicht einfach. Da hilft auch ein yacc nicht weiter. Man muss die Logik des XMI kennen und richtig anwenden.

Die Konvertierung erfolgt mit einem XSLT-Script aus XML nach XMI.


3.3 Feststellen aller Typen

Topic:.Cheader2Xmi...

pStyle=std tableStyle=stdTable

In C ist es üblich, Typen per Identifier direkt anzugeben. Die Zuordnung erledigt der Compiler und Linker. Damit verbunden ist aber auch die Möglichkeit des Unterschiebens falscher Typen (ein falscher Headerfile wird includiert) mit den bekannten Problemen von Softwarefehlverhalten. In XMI sind dagegen die Typen eindeutig. Sie sind nur an einer Stelle mit einer Bezeichnung definiert und werden ansonsten über eine eindeutige Nummer im Raum des XMI-Modells referenziert.

Damit ist der erste Schritt der Konvertierung Header-XML nach XMI die Auflistung aller Typen, die gefunden werden. Das sind sowohl die definierten und ggf. nicht selbst verwendeten Typen, als auch die verwendeten aber nicht definierten Typen.

Ein Compiler muss alle Typen definiert sehen. Die Definition kann in anderen Headerfiles stehen, die dann in der zu compilierenden Einheit zuvor includiert werden müssen. Die Header2Xmi-Konvertierung geht aber nicht von einer solchen Vollständigkeit aus. Typen, die nicht definiert vorgefunden werden, werden im XMI als extern dargestellt.

Um alle Typen ausfindig zu machen, hat der ZBNF-Parser schon gute Vorarbeit geleistet. Er erkennt, welcher Bezeichner eine Typ darstellt, anhand der Stellung in der Syntax. Diese Information ist im Header-XML abgespeichert.

Damit ist ein erstes XSLT-Script zur Feststellung aller Typen gar nicht mal so umfangreich. Etwas verkürzt wird das original Xml_Toolbase/xsl/CheaderTypes.xsl wie folgt wiedergegeben:

 <xsl:variable name="allTypes">
   <xsl:for-each select="//*[boolean(type)]">
     <usage type="{type/name}" tag="{local-name()}" name="{type/name}" source="type" extern="true" />
   </xsl:for-each>
   <xsl:for-each select="//classDef | //structDefinition | //attribute[@implicitStruct]">
     <xsl:variable name="classIdent"><xsl:call-template name="classIdent" /></xsl:variable>
     <usage type="{$classIdent}" tag="{local-name()}" name="{@name}" source="classDef" />
   </xsl:for-each>
 </xsl:variable>

Diese Variable enthält damit in <usage...>-Elementen alle vorgefundenen Typen. Diese treten aber mehrfach auf. Daher hilft eine Sortierung:

 <xsl:variable name="sortedTypes">
   <xsl:for-each select="$allTypes/usage" >
   <xsl:sort select="@type" />
     <xsl:copy-of select="." />
   </xsl:for-each>
 </xsl:variable>

Nun kann über die sortierten Typen iteriert und jeder Typ nur einmal benutzt werden:

   <xsl:for-each select="$sortedTypes/usage[position()=last() or @type != following-sibling::usage[1]/@type]">
     <xsl:variable name="type" select="@type" />
     <xsl:variable name="simpleTypeName" select="@name" />
     <usedType name="{$type}" xmi.id="{generate-id()}" tag="{@tag}" source="{@source}" >
       <xsl:if test="../usage[@type=$type and @extern]">
       <xsl:if test="not(  boolean($input//class[@name=$simpleTypeName])
                        or boolean($input//classDef[@name=$simpleTypeName or @tagname=$simpleTypeName])
                        or boolean($input//structDefinition[@name=$simpleTypeName or @tagname=$simpleTypeName])
                        or boolean($input//attribute[@tagname=$simpleTypeName])
                        )">
         <xsl:attribute name="external">true</xsl:attribute>
       </xsl:if>
     </usedType><xsl:value-of select="$indent" />
   </xsl:for-each>

Hier erfolgt auch die Feststellung, ob der jenige Typ innerhalb der gegebenen Sourcen definiert ist oder nicht. Der XSL-Translator verarbeitet als Input alle aus den C-Headern gewonnenen XML-Files gleichzeitig. Die Aussage, ob der Typ extern ist, bezieht sich also auf das gesamte Modell, nicht auf einen einzelnen Header.

Als Ergebnis dieser Übersetzung wird ein XML-File erzeugt, der sortiert alle Typen enthält, die in den Quellen vorkommen. Dieser, generiert aus ZBNF_example:_MyStruct.h hat in etwa als Beispiel folgende Form:

 <?xml version="1.0" encoding="iso-8859-1"?>
 <Types xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xhtml="http://www.w3.org/1999/xhtml">
   <comment>This is a file containing all founded types in all header files,
     as input for XMI.</comment>
   <usedType name="HeadData" xmi.id="d2e2" tag="structDefinition" source="structDefintion"/>
     <usedType name="MyStruct" xmi.id="d2e3" tag="structDefinition" source="structDefintion"/>
     <usedType name="MyStruct2" xmi.id="d2e5" tag="structDefinition" source="structDefintion"/>
     <usedType name="MyStruct3" xmi.id="d2e7" tag="structDefinition" source="structDefintion"/>
     <usedType name="float" xmi.id="d2e10" tag="attribute" source="type" external="true"/>
     <usedType name="int16" xmi.id="d2e17" tag="attribute" source="type" external="true"/>
     <usedType name="int32" xmi.id="d2e20" tag="attribute" source="type" external="true"/>
   </Types>

3.4 Eigentliche Konvertierung

Topic:.Cheader2Xmi...

pStyle=std tableStyle=stdTable

Die eigentliche Konvertierung wird mit dem Script Xml_Toolbase/xsl/Cheader2Xmi.xslp ausgeführt. Zur Extension xslp siehe Preprozessor_XSLP. Das Script berücksichtigt, dass die zu einer C-Klasse (struct) gehörende Methoden sich möglicherweise in verschiedenen Files befinden. Daher wird zunächst die C-Klassendefinitionen gesucht, über deren Namen erfolgt damit danach das Aufsuchen zugehöriger Elemente im gesamten XML-Baum. Daher gibt es typisch XSLT-Konstrukte wie

   <xsl:for-each select="$ClassNames/class">
     <xsl:variable name="ClassName" select="@name" />
     <!-- NOTE: use $Input as reference to the input tree to select because otherwise / means the root in context $Classname -->
     <xsl:for-each select="$Input">
       <xsl:call-template name="GenerateNamedClass">
         <xsl:with-param name="className" select="$ClassName" />
       </xsl:call-template>
     </xsl:for-each>
   </xsl:for-each>

und

 <xsl:for-each select="/root/Cheader//CLASS_C[@name=$classNameFull]/structDefinition[@name=$className]
                           | /root/Cheader/outside//classDef[@name=$className]
                           | /root/Cheader/outside/structDefinition[@name=$classNameFull]
                            ">

Die Konvertierung von C-Headerfiles nach XMI wurde zuerst betrieben, um Dokumentation teils aus einem UML-Modell aber auch teils aus Headerfiles einheitlich verarbeiten zu können. Schnittstelle ist in beiden Fällen das XMI-Format. Damit brauchte es nicht verschiedene XSLT-Templates für einzelne Darstellungen.

Für die eindeutige Zuordnung von Subroutinen ('C-Funktionen') und defines zu C-Klassen ist in den Headerfiles eine zusätzliche Notationsvereinbarung eingeführt, geschrieben mt

 /*@CLASS_C Name *********************************/

Damit sind in den Headerfiles Abteilungen geschaffen worden, die auch einer Lesbarkeit dienen. Allerdings ist das keine allgemeingültige Konvention. Für struct-Definitionen ohne diese Kennzeichnung erzeugt der ZBNF-Parser das auch oben sichtbare Element outside. Für einen allgemeingültigen Algorithmus der Erzeugung von XMI aus C-Headerfiles kann das aber nicht vorausgesetzt werden. Es müssen auch Fremd-Quellen unbearbeitet übersetzt werden können. Häufig wird bei Methoden, um zu klassifizieren, im Namen Prä- oder Postfixe verwendet. Auch diese sind zur Klassifizierung verwendbar. Man muss hier verschiedene Programmierstils akzeptieren und parametrieren können.

Die Erzeugung des XMI über ein reines XSLT-Script ist damit aber nicht mehr ganz einfach. Es sind zuviel Vergleichs-, Substring- und Sortierfunktionen notwendig, so dass eine Verarbeitung des Parserergebnisses der C-Headerfiles in einem Java-Programm als angemessener erscheint. Dies ist ein TODO.

Für die Dokumentationsgenerierung ist die Byte-Position der Daten in einer Struktur interessant. Diese wird bei der Konvertierung auch im XSLT ermittelt. Da man in XSLT keine echten Variablen hat, gibt es hierzu einen rekursiven Aufruf von

 <xsl:template name="attributeRecursive" >
 <xsl:param name="bytePosBefore" select="0" />
   ....
   <xsl:variable name="bytePos">
     <xsl:value-of select="number($bytePosBefore) + number($sizeType)" />
   </xsl:variable>
   ....
   <xsl:call-template name="attributeRecursive">
     <xsl:with-param name="bytePosBefore" select="$bytePos" />
   </xsl:call-template>

Diese Byteposition wird im XMI als Zusatzinformation untergebracht:

 <UML:TaggedValue tag="byteOffset" value="{$bytePos}"/>

Die Verarbeitung mehrerer XML-Input-Files im XSLT-Prozess ist möglich mit dem vishia-XSLT.