Software einrichten und Einführung in C++

In diesem Kapitel richten wir uns die nötige Software ein um unser Projekt mit C++ und OpenGL realisieren zu können. Wir setzen dabei ausschließlich Open Source Werkzeuge ein, die auf Windows, macOS und Linux gleichermaßen nutzbar sind.

Unser Setup ist komplett unabhängig von evtl. bereits vorhandenen IDEs oder Compilern und kann bei Bedarf am Ende der Veranstaltung wieder restlos gelöscht werden.

Organisatorisches: Dieses Kapitel erarbeiten wir am ersten Termin in der Präsenzveranstaltung gemeinsam. Sie müssen also für den ersten Termin nichts vorbereiten.

Inhalt


Software installieren

Für die Entwicklung in C++ benötigen wir einige Programme. Dazu gehören unter anderem ein Quelltexteditor und natürlich der Compiler. Hierfür gibt es zweifelsfrei eine große Auswahl von Möglichkeiten, viele davon sind aber spezifisch für eine bestimmte Plattform oder müssen von einigen Personen käuflich erworben werden. Damit wir alle auf das gleiche Pferd setzen habe ich für diese Veranstaltung eine Reihe von Tools ausgewählt die Ihnen die Arbeit erleichtern und trotzdem Open Source und plattformunabhängig sind. Ich möchte Sie daher bitten für diese Veranstaltung die hier vorgeschlagene Software zu installieren.

Visual Studio Code

Wir nutzen in dieser Veranstaltung Visual Studio Code als Quelltexteditor. VSCode stammt Microsoft, wird als Open Source Projekt entwickelt und funktioniert unter Windows, macOS und Linux. Falls Sie bereits andere Editionen von Visual Studio auf Ihrem Rechner haben sollten, ist dies kein Problem. Sie können VSCode problemlos zusätzlich installieren. Zur Installation besuchen Sie die Visual Studio Code Website und laden sich die passende Installationsdatei herunter.

Übrigens: Der Chefentwickler von Visual Studio Code ist Erich Gamma. Er ist Mitglied der “Gang of Four” und Mitautor des Buches “Design Patterns – Elements of Reusable Object-Oriented Software”. Dieses Standardwerk zum Thema Entwurfsmuster wird Ihnen vermutlich in Ihrem Studium an der THM das eine oder andere mal begegnen.

Erweiterungen installieren

Wir benötigen zwei Erweiterungen für Visual Studio Code die uns bei der Arbeit in C bzw. C++ unterstützen. Starten Sie dazu VSCode und öffnen Sie das Erweiterungsmenü (Extensions). Über die Suche können Sie nach Erweiterungen suchen. Suchen und installieren Sie die beiden Erweiterungen C/C++ und CodeLLDB


GCC/G++ und Make

GCC – die GNU Compiler Collection – beinhaltet den Compiler den wir nutzen werden um unseren Quellcode in Maschinencode zu übersetzen. Genauer gesagt nutzen wir das Kommando “g++” damit unser Code nicht als C sondern als C++ interpretiert und übersetzt wird. Make ist ein zusätzliches Werkzeug welches uns dabei unterstützt diesen Übersetzungsvorgang zu automatisieren. Auf diese Weise muss der Compiler nur das kompilieren was sich auch geändert hat. Die Installation dieser Software unterscheidet sich von Plattform zu Plattform, daher ist dieser Bereich in 3 Abschnitte gegliedert:

Linux

Unter Linux ist die Installation denkbar einfach. Nutzen Sie Ihren bevorzugten Paketmanager und installieren Sie die GNU Entwicklertools. Unter Ubuntu öffnen Sie dazu ein Terminal und geben folgenden Befehl ein:

sudo apt install build-essential

macOS

Unter macOS müssen Sie zuerst XCode auf Ihrem Rechner installieren. XCode ist Apples Entwicklungsumgebung und kann kostenlos über den Mac App Store installiert werden. Wir nutzen XCode zwar nicht direkt, das Paket beinhaltet aber alle nötigen Tools und Bibliotheken die wir benötigen um auf dem Mac Software entwicklen zu können.

Anschließend, wenn XCode auf Ihrem Rechner installiert ist, öffnen Sie ein Terminal und führen folgenden Befehl aus um die Command Line Tools zu installieren:

sudo xcode-select --install

Windows 10

Unter Windows 10 benötigen wir MSYS2. Dabei handelt es sich um eine Sammlung vom Tools die auf Linux und macOS von Hause aus vorhanden sind. Im Grunde genommen hat man mit MSYS2 ein kleines Mini-Linux auf seinem Rechner.

Besuchen Sie die MSYS2 Website und laden Sie sich die aktuellste Version des Installers herunter. Im Folgenden gehe ich davon aus, dass Sie den Standard Installationsort “C:\msys64” auswählen. Falls Sie davon abweichen, achten Sie darauf, auch in den folgenden Anweisungen entsprechende Anpassungen vorzunehmen.

Wenn die Installation fertig ist, lassen Sie das Häkchen “Run MSYS2 now” aktiviert und Sie landen direkt in einem Terminal. Alternativ erreichen Sie das MSYS2 Terminal auch über das Startmenü indem Sie nach MSYS2 suchen. In diesem Terminal geben Sie folgenden Befehl ein und bestätigen Sie eventuelle Rückfragen mit der Enter Taste.

pacman -S make mingw-w64-x86_64-toolchain

Alles was Sie innerhalb von MSYS2 installieren ist standardmäßig vom Rest Ihres Rechners isoliert. Es sollte also zu keinen Konflikten mit bereits installierten anderen Programmen kommen. Damit unser Visual Studio Code aber in der Lage ist die Tools zum Kompilieren zu finden müssen wir die entsprechenden Pfade dem System bekannt geben.

Suchen Sie im Startmenü nach “Umgebungsvariablen” bzw. “Environment variables” und öffnen Sie die passende Seite der Systemsteuerung. Im oberen Teil des Fensters unter “Benutzervariablen für …” wählen Sie den Eintrag “Path” aus und klicken auf Bearbeiten. Hier fügen Sie die folgenden beiden Einträge hinzu:

C:\msys64\mingw64\bin
C:\msys64\usr\bin

Achten Sie auf die korrekte Reihenfolge der beiden Einträge. Wir wollen, dass Windows zuerst im mingw64 Unterverzeichnis sucht.

Testen ob alles funktioniert

Auf allen 3 Systemen sollten Sie nun in der Lage sein in Ihrem Terminal g++ und make aufzurufen. Unter Windows müssen Sie eventuell alle Terminalfenster einmal schließen und ganz wichtig: Von nun an nutzen wir unter Windows nicht mehr das MSYS2 Terminal und auch nicht die Power Shell sondern die sog. Eingabeaufforderung oder Command Prompt. Unter Linux und macOS gibt es diese Verwirrung nicht. Tippen Sie die folgenden beiden Befehle in Ihr jeweiliges Terminal ein und sie sollten jeweils eine Versionsnummer angezeigt bekommen:

g++ -v
make -v

Falls Sie stattdessen einen Fehler wie “command not found” bekommen, müssen Sie die bisherigen Schritte nochmal gewissenhaft überprüfen.


Projektordner anlegen

Für alle weiteren Aufgaben dieses Kurses benötigen wir einen Projektorder. Erstellen Sie dazu einen leeren Ordner irgendwo auf Ihrem Computer. In diesem Ordner werden wir alles ablegen was wir im Laufe dieses Semesters erarbeiten. Wenn Sie möchten können Sie diesen Ordner auch direkt zu einem .git Repository machen.

Hinweis: Wenn Sie mehrere meiner Kurse besuchen, dann können Sie den gleichen Ordner auch für die anderen Module verwenden. Es ist daher ein legitimer Ansatz den Projektorder einfach “Computergrafik” zu nennen.

Starten Sie anschließend Visual Studio Code und öffnen Sie den gerade erstellten Ordner. Das funktioniert am einfachsten über das Menü.

Innerhalb des Projektordners erstellen wir von nun an für jedes Kapitel einen Unterordner. Für das erste Kapitel der Computergrafik Master Veranstaltung (CGM), erstellen Sie jetzt einen Ordner cgm_01.

Einen neuen Ordner erstellen

Ebenfalls auf der obersten Ebene des Projektordners legen wir uns eine README.md Datei an. In dieser Datei können Sie sich persönliche Notizen machen oder Fragen für die nächste Plenumsveranstaltung notieren.


Hello World in C++

Die Erfinder der Programmiersprache C sind auch die Urväter der Hello-World-Programme. Was liegt also näher, als dass wir unseren ersten Einstieg in die C/C++ Programmierung ebenfalls mit einem Hello World! beginnen.

Wir widmen uns nun dem Unterordner cgm_01. In diesem Ordner erstellen wir unser erstes Programm. Hier erstellen wir zwei neue leere Dateien “makefile” und “main.cpp”. Wenn alles geklappt hat, sollte es nun bei Ihnen wie folgt aussehen:

main.cpp

Der folgende Programmcode kommt in die main.cpp Datei:

// Hello World in C-Style C++

#include <cstdio>

int main()
{
    printf("Hello World!\n");

    return 0;
}

Hier eine Erklärung der einzelnen Zeilen:

// Hello World in C++
In C und C++ ist jede Zeile die mit // beginnt ein Kommentar. Kommentare erlauben es dem Entwickler, Notizen zu machen. So kann er selber, aber auch andere den Code besser verstehen. Der Compiler ignoriert diese Zeile komplett.

#include <cstdio>
Mit Hilfe von #include Statements werden Funktionen aus anderen Bibliotheken oder Dateien in unser Programm hinzugeladen. Hier laden wir die Bibliothek stdio (kurz für Standard-Input-Output). Dies ist nötig damit wir später die Funktion printf nutzen können um Text im Terminal auszugeben.

int main()
Hier definieren wir die Funktion main. Ein C-Programm kann aus vielen Funktionen bestehen, benötigt aber mindestens eine Funktion mit dem Namen main. Dies ist der Einstiegspunkt unseres Programms und wird immer aufgerufen, wenn unser Programm startet. Der Parameter int vor dem Funktionsnamen gibt an, dass diese Funktion einen numerischen (int = Integer) Wert als Ergebnis zurück gibt.

printf(“Hello World!\n”);
Hier wird die Funktion printf aufgerufen. Es wird ein String als Parameter übergeben. Hiermit wird ein Text auf der Konsole ausgegeben. Das abschließende \n ist ein Zeilenumbruch damit der Cursor in die neue Zeile springt.

return 0;
Gibt 0 als Rückgabewert unserer main Funktion zurück. Dieser Rückgabewert ist wichtig. Hiermit teilen wir dem Betriebssystem mit, dass unser Programm keinen Fehler verursacht hat. Falls innerhalb unseres Programms ein Fehler auftritt, würden wir hier einen Wert ungleich 0 zurückgeben.

Wie Sie sicher bemerkt haben, ist der Quellcode fast zu 100% identisch mit dem Quellcode eines Hello-World-Programms in C. Das liegt daran, dass wir hier in diesem Bespiel einfach die C-Standardbibliothek einbinden und damit die C Syntax verwenden können. C++ ist lediglich eine Erweiterung von C und es ist daher vollkommen okay in C-Style zu programmieren. Wenn wir allerdings von den “neueren” Möglichkeiten in C++ gebrauch machen wollen, dann bietet sich für Hello World die folgende Änderung an:

// Hello World in C++

#include <iostream>

int main()
{
    std::cout << "Hello World!" << std::endl;

    return 0;
}

Dieses Beispiel verwendet Streams um mit der Textausgabe zu kommunizieren. In diesem einfachen Beispiel ist es eine Geschmacksfrage welche Variante besser ist. Wer aber beispielsweise eine Software zum komprimieren von Daten oder Streamen von Musik entwickelt der wird die Vorzüge dieser Art der Programmierung zu schätzen wissen.

Kompilieren

Um das Programm auszuführen, muss es in Maschinencode übersetzt werden. Diesen Vorgang nennt man Kompilieren. Um unser Programm nun zu kompilieren wechseln wir in das Integrierte Terminal von Visual Studio Code. Klicken Sie dazu mit der rechten Maustaste auf den Ordner cgm_01 und wählen Sie “Open in Integrated Terminal”. Anschließend sollte das integrierte Terminal unten rechts aufgehen und bereits im Ordner cgm_01 sein.

Im Terminal können Sie nun die Kompilierung starten indem Sie den folgenden Befehl eingeben:

g++ -g src/main.c -o hello.exe

Wenn alles geklappt hat, dann sollte nun eine neue Datei mit dem Namen hello.exe in Ihrem Ordner auftauchen. Dies ist unser ausführbares Programm. Der -o Parameter im obigen Kommando gibt den Namen der Binärdatei an, die erstellt werden soll. Die Dateiendung .exe ist dabei nicht notwendig und hier lediglich zu Anschauungszwecken für die Windows Nutzer verwendet.

Um das Programm zu starten, tippen Sie nun folgendes in das Terminal ein:

./hello.exe

Auf der Konsole sollte nun Hello World! erscheinen.

Makefile erstellen

Unser Befehl zum Kompilieren wird im Laufe dieses Kurses immer komplizierter. Das liegt daran, dass wir später noch weitere Bibliotheken hinzu laden müssen. Damit wir uns diesen Befehl nicht merken müssen und auch andere Dinge etwas leichter werden, nutzen wir ein Makefile.

Es gibt ein sehr gutes Video auf YouTube, in dem Makefiles erklärt werden. Schauen Sie sich das Video an und falls Sie Fragen haben, dann beantworte ich diese gerne im Plenum.

Für dieses Kapitel erhalten Sie von mir das fertige Makefile, mit der Bitte es genau so zu übernehmen. In zukünftigen Aufgaben gehe ich davon aus, dass Sie das Konzept verinnerlicht haben und eigenständig die nötigen Anpassungen vornehmen können.

Kopieren Sie den folgenden Inhalt in cgm_01/makefile:

bin/engine: obj/main.o bin
	g++ -g obj/*.o -o bin/engine

obj/main.o: main.cpp obj
	g++ -g -c main.cpp -o obj/main.o

bin:
	mkdir -p bin

obj: 
	mkdir -p obj

run: bin/engine
	bin/engine

clean:
	rm -rf obj
	rm -rf bin

Sie sehen, dass ich den Kompiliervorgang in 2 Schritte aufgeteilt habe. Zuerst wird eine .o Datei im Ordner obj erzeugt und anschließend die endgültige Binärdatei im Order bin. Der Compiler besteht intern aus 3 Komponenten: Präprozessor, Compiler und Linker.

Der Präprozessor bereitet den Quellcode für die Kompilierung vor. Er entfernt beispielsweise Kommentare und fügt Code ein welcher aus anderen Dateien inkludiert wird.

Der Compiler überprüft den Code auf Syntaxfehler, optimiert den Code und übersetzt ihn anschließend in binäre Objektdateien (.o-Dateien)

Der Linker ergänzt die Komponenten um eingebundene Bibliotheken und setzt alles zu einem ausführbaren Gesamtprogramm zusammen.

Mit diesem Makefile haben wir nun 3 wichtige Kommandos zur Verfügung:

make
Kompiliert unser Programm und speichert die Binärdatei als bin/engine ab.

make run
Kompiliert unser Programm falls sich etwas am Code geändert hat und startet es direkt. Das nutzen wir zum Testen im Terminal.

make clean
Löscht alle Binärdateien und unnötigen Ordner. Das tun wir üblicherweise bevor wir das Projekt zippen oder gitten.

Testen Sie ob alle 3 Befehle korrekt funktionieren.


Debuggen in Visual Studio Code

Um die wichtigsten Aktionen die wir bisher über das Terminal ausgeführt haben, auch innerhalb von Visual Studio Code ausführen zu können, legen wir uns zwei neue Konfigurationsdateien an.

Falls noch nicht geschehen erstellen Sie einen neuen Ordner, auf der obersten Ebene Ihres Projektordners mit dem Namen .vscode – Der Punkt am Anfang ist wichtig. In diesem Ordner liegt die Projektkonfiguration für VSCode.

Tasks

Tasks sind eine Funktion von VSCode um vordefinierte Aktionen über Menüs und Tastenkombinationen bereitzustellen. Alle Tasks werden in der Konfigurationsdatei /.vscode/tasks.json definiert. Falls diese Datei bei Ihnen noch nicht existiert, erzeugen sie sie jetzt und fügen sie folgenden Inhalt ein:

{
    "version": "2.0.0",
    "tasks": [
        {
            "type": "shell",
            "label": "Make",
            "command": "make -C ${fileDirname}",
            "group": {
                "kind": "build",
                "isDefault": true
            }
        },
        {
            "type": "shell",
            "label": "Make Run",
            "command": "make run -C ${fileDirname}",
            "group": "none"
        },
        {
            "type": "shell",
            "label": "Make Clean",
            "command": "make clean -C ${fileDirname}",
            "group": "none"
        }
    ]
}

Hiermit haben wir 3 Tasks definiert. Sie finden diese Tasks in VSCode im Menü Terminal. Über den Menüpunkt Run Build Task oder auch direkt mit der Tastenkombination Ctrl+Shift+B wird der Make Task ausgeführt, und es passiert das gleiche als würden Sie manuell im Terminal make eintippen. Die anderen beiden Tasks können über den Menüpunkt Run Task… erreicht werden.

Testen Sie ob Make Clean und Make Run korrekt funktionieren.

Launch Configurations

Launch Configurations defineren welche Aktionen im Debug Bereich von VSCode angezeigt werden und ausgeführt werden können. Wir definieren hier lediglich einen einzigen Task, der jeweils das aktuell ausgewählte Kapitel startet und zum Debuggen bereit macht.

Alle Launch Configurations werden in der Konfigurationsdatei /.vscode/launch.json definiert. Falls diese Datei bei Ihnen noch nicht existiert, erzeugen Sie sie jetzt und fügen Sie folgenden Inhalt ein:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug Active Project",
            "type": "lldb",
            "request": "launch",
            "program": "${fileDirname}/bin/engine",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${fileDirname}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "lldb",
            "preLaunchTask": "Make"
        }
    ]
}

Wenn Sie nun Ihre main.c Datei im Editor öffnen, dann in den Debug Bereich der Anwendung wechseln, erscheit dort die hier definierte Config “Debug Active Project”. Durch Klick auf den grünen Pfeil können Sie das Debugging starten. Alternativ können Sie auch einfach F5 drücken.

Setzen Sie einen Breakpoint und testen Sie ob das Debugging funktioniert.

Prüfungsvorbereitung

  • Was sind Umgebungsvariablen, wofür werden Sie genutzt und wo werden Sie auf Ihrem Betriebssystem festgelegt?
  • Was bedeutet #include <cstdio> und was genau tut ein Compiler wenn er ein solches Statement irgendwo im Code vorfindet?
  • Wenn Sie zweimal hintereinander make aufrufen, wird Ihnen beim zweiten Aufruf “… is up to date” angezeigt. Wenn Sie zweimal hintereinander make clean aufrufen passiert das nicht. Können Sie erklären warum?
  • Können Sie aus dem Kopf die einzelnen Komponenten einer Makefile Regel (target, dependencies, actions) beschreiben?
  • Können Sie erklären was std::endl bedeutet und wie es sich von “\n” im ersten Beispiel unterscheidet?