GMT (jeeamtee) – Mein erstes Projekt bei Google-Code

GMT ist eine C++ Klasse für Manipulation von Speicherblöcken. Ich habe sie während eines hardware-nahes Projektes implementiert, um die Hardware (FPGA) auf einem Embedded-Board mit einem komfortablen Konsolen-Tool testen zu können. Eine solche Konsolenapplikation wird mit GMT zu einem Zehnzeiler.

Die ursprüngliche Applikation hieß gmm (Generic Memory Manipulator) und war zum Teil auch beim Kunden entwickelt. GMT ( Generic Memory Tool ) wurde neu geschrieben und hat von gmm nur die Idee geerbt. Außerdem habe ich mich entschlossen, das Projekt nicht als eine Applikation zu entwickeln sondern als eine Klasse (oder auch eine winzige Klassenbibliothek).

Die Einfachheit und das Komfort, bei Google Open-Source Projekte zu hosten, hat mich sehr positiv überrascht. Man kann von dem Workflow und von der Projektmanagementumgebung viel lernen. Ich bin mir sicher, es wird nicht mein letztes Google-Code Projekt sein.

Das Projekt ist gehostet bei: https://code.google.com/p/jeeamtee/

Viel Erfolg damit und vielen Dank für alle Anregungen!
Valentin Heinitz

P.S.: Ah ja, warum so seltsamer Name? Der Projektname GMT war bei google-code bereits belegt, deshalb bin ich auf jeeamtee ausgewichen.

Fehlermeldungen von GCC in Visual Studio ausgeben

In vielen C/C++ Embedded-Projekten an denen ich mitgearbeitet habe, wurde als Compiler eine Variante von GCC genommen. Als Entwicklungsumgebung hat allerdings MS Visual Studio gedient. Für Visual Studio als IDE spricht vieles. MSVS ist schnell, bietet sehr gute Navigationsmöglichkeiten und löst die Symbole recht zuverlässig auch bei nicht kompilierbarem Code und losen Source-Dateien auf. (Leider kann ihmo Eclipse/CDT mit MSVS nicht mithalten und kommt z.B. bei (nicht trivialen) Templates mit der Symbolauflösung schnell durcheinander.)

In der Regel wurde der Buildvorgang mit einem Batch-Skript gestartet und die Buildergebnisse wie Fehler, Warnungen, etc. in eine Log-Datei umgeleitet.
Um eventuelle Fehler zu beseitigen, musste man mühselig nach dem Dateinamen und der Zeilennummer in der Log-Datei suchen und zu der Stelle in Visual Studio manuell gehen.
Da die Fehlerformate von GNU und MSVS unterschiedlich sind, kann Visual Studio mit den GCC Fehlermeldungen nichts anfangen.

Dabei kann ein sehr einfacher Konverter die Arbeit erleichtern, so dass man mit dem Doppelklick auf die Fehlermeldung sofort zur richtigen Stelle gelangt.
Der Fehlerformat von GNU ist:
GCC format: source.cpp:123: error: a gcc error message
Der Fehlerformat von MSVS ist:
MSVC++ format: source.cpp(123) : error: a MSVS error message
Der Konverter ist ein Einzeiler:
grep ": error:\|: warning:" "%1" | sed "s/:\([0-9]\+\): \(error\|warning\):/(\1) : \2: /"
Es werden UNIX-Tools grep und sed benutzt. Sie dürften auf keinem Entwicklerrechner fehlen, auch unter Windows: http://gnuwin32.sourceforge.net/.
Der Skript sollte als eine Batch-Datei implementiert werden (einfach die Zeile in z.B. gcc2msvs.bat kopieren), und mit der Fehlerlogdatei als Parameter aufgerufen werden.

Zum Einbinden in Visual Studio benutze ich immer folgendes:
In MSVS Menu anklicken “Tools->External Tools”, “Add” und folgende Felder ausfüllen:
Title: BugFixing
Command: Path/gcc2msvs.bat
Arguments: “Path/GccBuildLogOutput”
Use Output window: YES

Jetzt kann man sich die Fehlermeldungen vom letzten Build mit “Tools->BugFixing” anzeigen lassen. Das Gute daran ist, es funktioniert mit jeder MSVS Instanz, es bedarf keines gültigen Projektes oder Pfadeinstellungen.

Viel Spaß damit und viel Erfolg in Euren Projekten.
Valentin Heinitz

INI-FIle Parser als Einzeiler in Bash

Viele Programme speichern ihre Konfigurationsdaten immer noch im alten INI-Format ab. Das Format ist einfach: es gibt Sektionen, Schlüssel, Werte und Kommentare. Sektionen dürfen nicht ineinander geschachtelt werden, die Schlüssel innerhalb einer Sektion sollen eindeutig sein. Das Format ist zeilenbasiert, die Leerzeichen am Anfang und am Ende eines Tokens werden ignoriert.

[Section]
; Comment
Key = Value

Dieser Einzeiler ist ein Bash-Script zum Lesen der Werte aus solchen INI-Dateien. Das Script wird aufgerufen mit dem Dateinamen, dem Sektionsnamen und mit dem Schlüssel. Der Wert wird auf der Standardausgabe ausgegeben.
cat $INIFILE | sed -n /^\[$SECTION\]/,/^\[.*\]/p | grep "^[:space:]*$ITEM[:space:]*=" | sed s/.*=[:space:]*//
Zuerst wird der Inhalt entsprechender Sektion extrahiert. Danach wird die Zeile mit dem Schlüssel=Wert Paar für den gegebenen Schlüssel gefunden und davon der Wert genommen.

Viel Spaß damit, und denkt an XML als eine bessere Alternative für Konfigurationsdateien.
Hier ist das komplette Source-Listing für INI-Reader:

#!/bin/bash
#Valentin Heinitz, www.heinitz-it.de, 2008-11-13
#Reader for MS Windows 3.1 Ini-files
#Usage: inireader.sh

# e.g.: inireader.sh win.ini ERRORS DISABLE
# would return value "no" from the section of win.ini
#[ERRORS]
#DISABLE=no
INIFILE=$1
SECTION=$2
ITEM=$3
cat $INIFILE | sed -n /^\[$SECTION\]/,/^\[.*\]/p | grep "^[:space:]*$ITEM[:space:]*=" | sed s/.*=[:space:]*//

Enum2String

Beim Programmieren in C++ ist es oft nützlich, die enum-Werte nicht als Zahlen sondern als Symbole mit ihren Namen auszugeben. C++ bietet diese Funktionalität nicht an. Es kann aber mit kleinem Stück Code bewerkstelligt werden. Dieses Code wird nur dann aktiviert, wenn es erforderlich ist – meistens im Entwicklungsstadium und zur Fehlersuche.
Das hier vorgestellte Perl Skript erzeugt eine C++ Klasse, die viele überladene toString Methoden für alle im Projekt deklarierten Enums bietet. Außerdem wird hier kurz auf das nutzliche Tool ctags eingegangen.

Um alle enum-Deklarationen im Code zu finden, wäre das Parsen notwendig. C++ Code manuell (mit Perl) zu parsen, ist abenteuerlich. Es gibt eine viel einfachere und sicherere Mögligkeit – das Tool ctags.

Für jeden, der mit GNU Tool-Chain arbeitet, ist ctags sicherlich ein Begrif – das Tool parst Quellcode und erzeugt daraus Symboltabelle. Der Aufruf ctags -R * parst alle Verzeichnisse rekursiv und erzeugt eine Datei namens tags im Arbeitsverzeichnis. Zu jedem Symbol in der tags-Datei gibt es Information, in welcher Datei es deklariert wurde und mit welchem Regulärem Ausdruck es in der Datei zu finden ist. z.B. hier ist ein Ausschnitt aus der einer tags-Datei:

EnumA inc\enums1.h /^enum EnumA { eA1, eA2, eA3, };$/;" g
eA1 inc\enums1.h /^enum EnumA { eA1, eA2, eA3, };$/;" e enum:EnumA

Die erste Zeile besagt, dass in der Datei inc/enum1.h ein neuer Enum-Typ deklariert wird. Die zweite Zeile bedeutet – “enum eA1 ist ein enum vom Typ EnumA.”

Und tatsächlich, in der Testdatei inc/enums1.h findet man das EnumA:
///@file enums1.h
#ifndef ENUMS1_HG_
#define ENUMS1_HG_
enum
EnumA {
eA1, eA2, eA3, };
enum EnumB
{
eB1, // first enum
eB2, eB3=eA3,
// eBUnused,
/* eBUnused, */eB4
};
typedef enum ECStyle{
eCStyle1=0, eCStyle2=100,
};
typedef enum EStrangeStyle{
eStrangeStyle1, eStrangeStyle2
}EStrangeStyle;
#endif

Die Datei enums1.h ist eine gültige C++ Headerdatei. Ich habe dort vier Enumtypen deklariert. Die unterschiedlichen Schreibweisen habe ich absichtlich gewählt, um zu zeigen, dass manuelles Parsen keine triviale Aufgabe wäre. Diese Möglichkeiten, enums in C++ zu deklarieren, sind nicht hypothetisch. Ich habe sie alle schon im produktiven Code gesehen.
(Die Deklaration von EnumA und EnumB ist das, was man normalerweise in C++ benutzt. Die anderen, … na ja.)
Für Testzwecke habe ich im Verzeichnis inc eine weitere Datei hinterlegt:
///@file enums2.h
#ifndef ENUMS2_HG_
#define ENUMS2_HG_
enum EnumC { eC1, eC2, eC3, };
enum EnumD { eD1, eD2, eD3, };
#endif

Das komplete Perl-Skript samt dem Beispielprojekt findest Du unten, am Ende des Artikels. Hier ist eine kurze Erläuterung.

Das folgende Rreguläre Ausdruck enthält 2 “back-references” – Unterausdrucke, die nach dem Anwenden des Hauptausdrucks referenziert werden können. In Perl würden die reservierten Perl-Variablen $1 und $2 die Inhalte von den fettmarkierten Ausdrücken enthalten.
my $pat="^[^\t]+\t([^\t]+)\t.*enum:([a-zA-Z_][a-zA-Z0-9_]*)";
Zum Beispiel, nach Anwenden des oberen RegExp bei folgender Zeile würden die Perl-Variablen $1 und $2 die fettmarkierten Anteile enthalten:
eA1 inc\enums1.h /^ eA1, eA2, eA3, };$/;" e enum:EnumA

Folgende Schleife schreibt die gefundenen includes und enums in zwei Listen:
while () {
if (my ($m) = m/$pat/){
push @etypes, $2;
push @includes, $1;
}
}

Folgendes Codeschnipsel erzeugt aus beiden Listen, die mehrere gleiche Einträge enthalten können, Unique-Listen:
my %hlp1 = ();
my @uniqenums = grep { ! $hlp1{$_} ++ } @etypes;
my %hlp2 = ();
my @uniqinc = grep { ! $hlp2{$_} ++ } @includes;

Nun habe ich alle notwendigen Informationen für die Codegenerierung – Enum-Typen und Headerdateien.
Es wird eine Utility-Klasse (keine Instanzen möglich/sinnvoll) generiert, die mehrere überladenen statischen toString-Mehtoden hat z.B.:
static const char * toString( EnumA en ) {
switch( en ) {
case eA1: return "eA1";
case eA2: return "eA2";
case eA3: return "eA3";
}
}

Außerdem wird in der Klasse eine Testmethode definiert – static void testEnum2String(), die beim Aufruf alle toString Methoden testet. Enum-Wert und Enum-Symbolname werden auf der Standardausgabe ausgegeben.

Download

Copyright 2010, Valentin Heinitz

« Previous Page