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:
- Finden Sie heraus, welches Windows-DLL die betreffende Funktion enthält.
C-Bibliotheksfunktionen finden sich meist in mscvrt.dll.
Zum Prüfen, welche Funktion in welcher DLL enthalten ist, können Sie
einen kostenlosen DLL-Browser, beispielsweise den DLL
Export Viewer vonhttp://www.nirsoft.net
verwenden.
- Legen Sie in Ihrem Source-Code einen Funktions-Prototypen fest. In
den meisten Fällen können Sie den Prototypen direkt aus der .h-Header-Datei,
die mit dem DLL einhergeht, kopieren oder aus der Dokumentation zur
C-Bibliothek.
- Zusätzlich zum Prototypen fügen Sie entweder eine #define
PRAGMA_API-Zeile zum Initialisieren
des Funktionsprototypen ein z.B. wie im API-Beispiel beschrieben #define
PRAGMA_API MessageBox;user32!MessageBoxA, oder, zur dynamischen
Initialisierung, fügen Sie in Ihrer Main-Funktion einen DefineApi-Aufruf
hinzu.
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):
- Starten Sie VC++. Zuerst müssen 'Sie ein leeres Projekt erstellen.
Wählen Sie hierzuFile
/ New / Project / Win32 Project. Im Feld darunter geben Sie den Projektnamen,
etwa "Mandelbrot_Legacy", ein, dann klicken Sie auf OK.
Im folgenden Dialog machen Sie unter Application Settings einen
Haken bei Empty Project und klicken danach auf Finish.
VC++ erstellt Ihnen nun ein Projekt.
- Kopieren Sie mandelbrot_legacy.c in den von VC++ erstellten Pojektordner
und ändern Sie dessen Namen in .cpp (Sie verwenden Klassen, also handelt
es sich um ein C++-Programm). Fügen Sie das nun ins Projekt ein: In VC++s
Solution-Explorer klicken Sie rechts auf Source Files und
wählen Sie Add / Add Existing Item / mandelbrot_legacy.cpp.
Es sollte die einzige Datei des Projektes sein.
- Da VC++ nichts von Ihren lite-C-Includes weiß, müssen Sie den Pfad zur
Datei litec.h angeben. Editieren Sie die Zeile "#include <litec.h>" indem
Sie den entsprechenden Pfad in doppelten Anführungszeichen eingeben, etwa: #include "C:\program
files\litec\include\litec.h".
- Kompilieren Sie per Build \ Build Solution und
dann starten Sie mit Debug
\ Start.
Siehe auch:
Pointers, Structs, Functions, Windows
API
► Aktuelle Version Online