Lite-C für C/C++-Programmierer

Lite-C unterstützt eine Syntax-Untermenge der Sprache C/C++. In den meisten Fällen - sofern keine C++-eigene Features wie STL benutzt werden - läßt sich ein C/C++-Programm ganz einfach in lite-C konvertieren. Beispiele hierfür sind die DirectX-Programme im samples Ordner - es handelt sich um kaum veränderte Versionen der Originalprogramme aus dem DirectX SDK. Um ein C/C++-Programm in lite-C zu konvertieren, müssen jedoch die folgenden subtilen Unterschiede beachtet werden:

Automatische Pointer-Konversion

Lite-C unterstützt Pointer, benötigt aber nicht den '->' Operator (wenngleich er benutzt werden kann). Der Zugriff auf Struct-Elemente kann stets per '.' Operator erfolgen, egal ob es sich um einen Struct-Pointer handelt oder nicht. Wenn eine Function einen Pointer erwartet, hängt lite-C, wenn nötig, automatisch den '&' Operator an.

&&, ||

In C/C++ werden Vergleiche vorzeitig abgebrochen, wenn ein Ausdruck links von && zu false oder links von || zu true evaluiert. Lite-C bricht Vergleiche nicht vorzeitig ab. Dies erfordert eine leicht unterschiedliche Syntax, wenn vorzeitiger Abbruch ausgenutzt wird, um z.B. den Wert eines Struct-Pointers und eines seiner Elemente im gleichen Ausdruck zu testen:
if((ptr != NULL) && (ptr->element == ..)) .. // C/C++
if(ptr != NULL) if(ptr->element == ..) .. // lite-C 

Trinäre Operatoren:

Die Vergleichssyntax comparison ? expression : expression; wird von lite-C nicht unterstützt, daher muß man ein If-Statement verwenden:
x = (x<0 ? -1 : 1); // C/C++
if (x<0) x=-1; else x=1;  // lite-C 

Struct Alignment

In lite-C belegen Struct-Elemente stets nur den Speicherplatz, der ihrer eigenen Größe entspricht. Ein char belegt wirklich nur 1 Byte und ein short 2 Bytes. C++ verhält sich hier anders. Um Structs zwischen lite-C und externen Sprachen auszutauschen, setzen Sie das Alignment auf 1 Byte (in VC++: Properties / C/C++ / Code Generation / Struct Member Alignment / 1 Byte). Aus Geschwindigkeitsgründen wird empfohlen, 4 chars oder 2 shorts in Structs jeweils zusammenzufassen, so dass 4-Byte Variablen stets auf 4-Byte Grenzen aligned sind.

Struct- und Array-Initialisierung:

In C/C++ lassen sich Structs genauso wie Arrays durch Angeben einer Liste von Werten einzelner Glieder initialisieren, beispielsweise VECTOR myvector = { 0,0,0 };. In lite-C wird dies lediglich bei globalen Arrays und unterstützt. Lokale Structs können auf diese Weise nicht initialisiert werden und lokal initialisierte Arrays werden automatisch statisch. Lite-C intitialisiert sämtliche globealen Arrays und Structs auf Null. Lokale Arrays und Structs werden normalerweise nicht initialisiert und haben daher einen zufälligen Inhalt. Werwenden Sie darum das (in acknex.h definierte) Makro zero() zum Auf-Null-Initialisieren lokaler Structs und vec_zero() zum Initialisieren von Vektoren, die aus drei vars bestehen:
VECTOR speed;
...

vec_zero(speed);	// initializes the VECTOR "speed" to x=0,y=0,z=0

Zum Konvertieren und Testen kann lite-C auch mit der Definition PRAGMA_ZERO sämtliche lokalen Variablen automatisch auf null initalisieren. Wenn Sie am Anfang Ihres Skripts

#define PRAGMA_ZERO   // initialize variables

einfügen, werden alle nicht-initialisierten lokalen Variablen und Structs auf null gesetzt. Dies verlangsamt die Funktionsaufrufe ein wenig, in Ihrer Endversion sollten Sie daher auf PRAGMA_ZERO besser verzichten.

Kopieren von Structs

Mit dem Operatoren '=' lassen sich in C++ Stucts ineinander kopieren. In C oder lite-C verwenden Sie zum Kopieren von Structs memcpy:
// C++:
D3DXVector3 vecA, vecB;
...
vecA = vecB;

// lite-C:
D3DXVector3 vecA, vecB;
...
memcpy(vecA,vecB,sizeof(D3DXVector3));

Enums

Enums werden nicht unterstützt und können durch defines ersetzt werden:
enum RGB { RED=1; BLUE=2; GREEN=3 }; // C/C++
#define RED 1 // lite-C #define BLUE 2 #define GREEN 3

Unions

Unions desselben Typs lassen sich durch ein #define ersetzen; Unions unterschiedlichen Typs können als verschiedene Teile des Structs behandelt werden. Beispiel:
typedef struct S_UNION { 
   int data1;
   union { int data2; float data3; };
   union { int data4; int data5; };
} S_UNION; // C/C++

typedef struct S_UNION { int data1; int data2; float data3; int data4; } S_UNION; // lite-C #define data5 data4

Darf sich die Größe des Structs nicht verändern oder verlangt das Programm aus irgendwelchen Gründen verschiedene Variablentypen auf der gleichen Position im Struct, kann eine spezielle Umwandlungsfunktion zum Konvertieren des Typus' einer Variablen verwendet werden, ohne den Inhalt zu verändern:

typedef struct S_UNION { 
   int data1;
   union { int data2; float data3; };

} S_UNION; // C/C++
...
S_UNION s_union;
s_union.data3 = 3.14;

typedef struct S_UNION { int data1; int data2; } S_UNION; // lite-C #define data3 data2 ... int union_int_float(float x) { return *((int*)&x); } ... S_UNION s_union; s_union.data3 = union_int_float(3.14);

Funktions-Pointer

In C/C++, sind Funktions-Pointer wie folgt deklariert: int (*foo)(int a, int b);. In lite-C gibt es keinen Unterschied zwischen Funktions-Prototypen und Funktions-Pointern: int foo(int a, int b);. Näheres finden Sie unter Pointers.

Mit Vorzeichen behaftete oder vorzeichenlose Variablen

Float-, var-, long- und int-Variablen sind In lite-C generell mit Vorzeichen behaftet, Pointer, char- und short-Variablen grundsätzlich vorzeichenlos, so wie sie normalerweise benutzt werden. Die signed und unsigned Modifier werden zwar akzeptiert, haben aber keine Auswirkung. Für alle gewöhnlichen, vorzeichenlosen Variablen wie etwa die in Windows-Funktionen benutzten DWORD oder WORD enthält die Datei include\litec.h Definitionen. Daher lassen sich vorzeichenlose Variablen in der Regel problemlos verwenden. Wenn Variablen ihren Wertebereich überschreiten, müssen Sie allerdings aufpassen. Zieht man beispielsweise 1 von (DWORD)0 ab, ergibt das unter lite-C -1, in Standard-C/C++ aber 0xFFFFFFFF und das würde zu unterschiedlichem Verhalten in Vergleichen führen.

Prefix / postfix Operatoren

Lite-C macht keinen Unterschied zwischen Prefix und Postfix Increment-Operatoren; sie erhöhen stets zuerst den Counter und liefern dann das Ergebnis. i++ ist das gleiche wie ++i.

min, max

Diese oft gebrauchten Makros sind durch einfache Funktionen ersetzt worden: minv und maxv für var und minf und maxf für float. Die Letzteren sind keine Enginefunktionen, aber in include\windows.h definiert.

sin, cos, tan, asin, acos, atan

Die trigonometrischen Funktionen sind mit ihrem lite-C Gegenstück überladen und benutzen Grad statt Rad, wenn sie mit einem var oder int Argument aufgerufen werden. Um die Verwendung von Rad zu erzwingen, benutzen Sie den (float) Cast-Operator, wie in sin((float)1).

printf

Diese altertümliche DOS-Ausgabefunktion wird in normalen Windows-Programmen nicht funktionieren, im lite-C-Pure-Modus wird sie aber aus Gründen der Annehmlichkeit unterstützt. Sie öffnet eine Nachrichtenbox (s. printf). Achten Sie darauf, in der Argumentenliste der Funktion eine var zu (long) oder (double) zu casten und konvertieren Sie STRING* mit der Funktion _chr in char*.

Main()-Funktion

In C/C++ beendet die main()-Funktion das Programm bei Return. In lite-C wird ein expliziter Aufruf von sys_exit(NULL) (oder ein Klick auf den Schliessen-Knopf) verwendet, um das Programm zu beenden. In C öffnet die main()-Funktion auch kein Fenster, in lite-C hingegen öffnet die main()-Funktion automatisch ein DirectX-Fenster im ersten Frame wenn dies nicht explizit durch Setzen von video_screen=0; unterbunden wird.

Hinzufügen von C-Bibliotheksfunktionen

Lite-C enthält lediglich eine Untermenge sämtlicher Funktionen aus den Standardbibliotheken von C/C++. Sie können aber jede beliebige Funktion, die Sie brauchen wie unter Using the Windows API beschrieben, einfügen. Hier eine kurze Anleitung wie man lite-C eine API-Funktion hinzufügt:

Sollten Sie bestimmte Stucts oder Variablentypen brauchen, die noch nicht in include\windows.h oder den anderen Standard-Include-Dateien enthalten sind, fügen Sie sie einfach aus ihrer Originaldatei in Ihr Skript ein. Wenn Sie meinen, eine bestimmte Funktion, ein gewisses Struct oder ein besonderer Variablentyp wird oft gebraucht, schlagen Sie lieber auf Gamestudios Future-Forum die Aufnahme des Jeweiligen in api.def vor, als daß Sie api.def selbst verändern. Dateien, die individuell modifiziert wurden, werden von lite-C-Updates überschrieben.

Converting DirectX programs to lite-c

DirectX und andere Windows SDKs benutzen, wie im API-Kapitel beschrieben, das Component Object Model (COM). Aus Annehmlichkeitsgründen sind Windows Interface-Definitionen (wie DECLARE_INTERFACE_ und STDMETHOD_) bereits in der lite-C Datei include\d3d9.h enthalten, sowie die grundlegenden Klassen darin bereits definiert. Dadurch ist es einfach, falls nötig weitere Interface-Klassen hinzuzufügen. Kopieren Sie einfach die Klassen-Definition aus der Original-Datei d3d9.h oder der DirectX-Header-Datei in lite-Cs Entsprechung und fügen Sie (so vorhanden) die geerbte Methode aus der Parent-Klasse hinzu.

Auch wenn lite-C alle DirectX-Klassen und -Funktionen verwenden kann, so fehlen ihm doch einige erweiterte C++-Features wie Operatoren-Overloading. Daher können zum Aufstellen, Addieren, Subtrahieren und Multiplizieren von D3DX-Vektoren und -Matritzen die Overloaded-Operatoren =, +, -, und * nicht verwendet werden. Verwenden Sie stattdessen DirectX-Bibliotheksfunktionen wie etwa D3DXVec3Add. Um Vektoren Aufzustellen, wurden der Einfachheit halber die folgenden Funktionen in include\d3d9.h aufgenommen:

D3DXVECTOR3* D3DXVec3Set(D3DXVECTOR3 *pOut,float x,float y,float z,float w);
D3DXVECTOR3* D3DXVec3Set(D3DXVECTOR3 *pOut, CONST D3DXVECTOR3 *pIn);
D3DXVECTOR3* D3DXVec3Set(D3DXVECTOR3 *pOut, CONST D3DXVECTOR4 *pIn);
D3DXVECTOR4* D3DXVec4Set(D3DXVECTOR4 *pOut,float x,float y,float z,float w);
D3DXVECTOR4* D3DXVec4Set(D3DXVECTOR4 *pOut, CONST D3DXVECTOR3 *pIn);
D3DXVECTOR4* D3DXVec4Set(D3DXVECTOR4 *pOut, CONST D3DXVECTOR4 *pIn);

Beim Konvertieren eines Engine-VECTORs in einen D3DXVECTOR3, müssen Sie darauf achten, die y- und z-Koordinaten auszutauschen. DirectX verwendet ein anderes Koordinatensystem.

Mit den obigen, einfachen Modifikationen lassen sich fast sämtliche DirectX-Beispiele, die Sie im Internet oder im DirectX-SDK finden können, einfach innerhalb von ein paar Stunden konvertieren und unter lite-C kompilieren. Im Beispielordner (samples) finden Sie eine konvertierte Version von DirectX-Tutorialbeispielen.

Kompilieren eines lite-C-Skripts mit anderen C++-Compilern

Solange Sie keine spezielle lite-C-Syntax, wie globale Struct-Initialisierung oder Pointer-Autodetection verwenden, können Sie ein Legacy-Mode-Skript aus lite-C für gewöhnlich mit irgendeinem anderen C++-Kompilierer kompilieren. Hier beispielhaft wie man die Datei mandelbrot_legacy.c mit Visual Studio kompiliert (je nach VC++ Version kann sich das Vorgehen leicht unterscheiden):

Siehe auch:

Pointers, Structs, Functions, Windows API

► Aktuelle Version Online