Visual Studio EXE in DLL Projekt ändern
In diesem Tutorial zeige ich wie man ein EXE Projekt unter Visual Studio in ein DLL Projekt ändert. Zuletzt habe ich das bereits in ein LIB Projekt geändert, eine DLL bietet zudem aber noch einige Vorteile auf die man vor allem in größeren Projekten nicht verzichten möchte.
Visual Studio EXE in DLL Projekt ändern
Ich arbeite aktuell in einer Visual Studio Projektmappe in der sich 3 Projekte befinden. Alle 3 bauen eine ausführbare *.exe Datei und haben folgende Bedeutung:
- Core
enthält alle Basisklassen der Business Logic und außerdem Import- und Exportfunktionalität. Aktuell wird die main.cpp verwendet um schnell neue Funktionen zu testen. - Editor
ein grafisches User Interface, welches mit den Basisklassen aus dem Core arbeiten soll. - Tests
Unit Tests für Klassen aus dem Core.
Wie du vielleicht aus der Auflistung festgestellt hast sollte der Core keine EXE bauen, sondern eine DLL, die ich in den anderen Projekten einbinden kann. Das ist das optimale Setup. Aktuell werden im Tests Projekt die Komponenten über dessen Header eingebunden und damit das auch kompiliert werden die einzelnen *.obj Dateien, die im Core Projekt erstellt werden, dem Linker vom Tests Projekt bekanntgegeben. Bei einem schnell wachsenden Projekt ist diese laufende manuelle Anpassung der Projekteinstellungen ein Wahnsinn.
DLL statt EXE
Will man eine DLL erstellen, dann wählt man bei der Erstellung eines neuen Projekts das DLL Template. Ich habe jedoch ein leeres Projekt erstellt, dass standardmäßig eine Ausführbare EXE baut. Es ist aber gar kein Problem zu einem späteren Zeitpunkt diese Einstellung zu ändern. Dazu öffnet man die Projekteinstellungen und ändert den Konfigurationstyp von „Anwendung (.exe)“ in „Dynamische Bibliothek (.dll)“. Siehe dazu folgenden Screenshot:
Wenn man nun das Projekt neu baut erscheint im Ausgabeordner die DLL Datei. In meinem Verzeichnis liegt aus einem früheren Build auch noch die EXE Datei, wie man am Datum schön erkennen kann. Die DLL ist aufgrund der Fehlenden main.cpp Datei um ganze 1 KB kleiner.
DLL testen
Da wir nun wissen wie man statt einer EXE eine DLL baut stellt sich nun die nächste Frage: wie verwendet man diese? Im folgenden zeige ich wie man in einem neuen Projekt die zuvor im Core verwendete main.cpp mit der erstellten Core DLL verwendet. Ich erstelle das neue Projekt Game in der selben Projektmappe und importiere dort die main.cpp vom Core Projekt. Im Core Projekt wird diese Datei entfernt. Damit Game compiliert füge ich die das Verzeichnis der Header Dateien vom Core Projekt über die $(SolutionDir) Variable hinzu und zusätzlich noch die Include Pfade aller externen Bibliotheken (in diesem Fall nur Boost).
Der Verweis zur DLL wird wie für statische Bibliotheken in Visual Studio über einen Verweis auf das Core Projekt hinzugefügt.
Compiliert man nun das Projekt endet man in einem Fehler:
Das Problem ist, dass keine LIB Datei für die DLL erstellt wurde. Doch warum benötigen wir diese? Irgendwo muss definiert werden wie die Schnittstelle zur DLL aussieht. Standardmäßig sind alle Variablen, Funktionen und Klassen nicht von außen verfügbar. Jede Klasse die in der main.cpp verwendet wird mache ich nun mit dem Zusatz __declspec(dllexport) für externe Ressourcen verfügbar.
Wenn man nun das Projekt neu erstellt, dann wird neben der DLL auch noch eine sehr kleine LIB Datei mit dem selben Namen erstellt:
Die LIB Datei ist ganze 8 KB groß. Diese enthält nur die Schnittstelle für die DLL. Wenn man nun das Game Projekt erneut erstellt und alle verwendeten Klassen die __declspec(dllexport) Anweisung enthalten, dann wird die EXE erfolgreich erstellt.
Warum DLLs erstellen?
Es gibt folgende Gründe warum man in C++ Projekten gerne Code in DLL Dateien auslagert:
- Modularisierung/Organisation
Es macht Sinn, dass man Code in mehrere Module organisiert. Die Entwicklung eines Monolithen (eine EXE die den gesamten Code des Programms enthält) ist nicht mehr zeitgemäß. Um Vorteile von einer Microservice Architektur zu nutzen sollte man möglichst kleine Module entwickeln, die man einzeln gut testen kann. - Wiederverwendbarkeit
In sich gekapselte Funktionalität lässt sich wieder verwenden. Es kommt nicht selten vor, dass man bestimmte Funktionalität einer bestimmten DLL für das eigene Projekt verwendet (zum Beispiel von DirectX). Ich habe eine solche Wiederverwendbarkeit in meinem Projekt geplant. Alle Klassen für Input/Output und die Daten persistieren werden im Core zusammengefasst. Diese Core.dll wird später sowohl vom Editor (exe) als auch vom eigentlichen Programm (exe) verwendet werden. - Reduktion der Buildzeit
Eine einmal erstellte DLL muss nicht jedesmal für jede EXE neu erstellt werden. Auf lange Sicht spart man sich dadurch erheblich viel Buildzeit.
Alternativen
Eine dynamische Bibliothek ist eine Möglichkeit Source Code wiederverwendbar zu machen. Die zweite ist über eine statische Bibliothek, unter C++ eine LIB Datei. Der Unterschied der beiden Methoden ist, dass die statische Bibliothek vom Linker in die EXE Datei integriert wird. Verwende ich also eine LIB Datei für beide Programme (Editor und das eigentliche Programm), dann werden beide EXE Dateien um die Größe der LIB größer. Das heißt effektiv habe ich auf dem Datenträger zur Auslieferung meiner Software den Code doppelt und verschwende somit Platz. Der Vorteil gegenüber einer dynamischen Bibliothek ist, dass diese zur Laufzeit nicht geladen werden muss und der Code potentiell schneller läuft.
Fazit
Ein DLL Projekt bietet den Vorteil, eine fertige wiederverwendbare Bibliothek zu bauen, welche für andere Projekte verwendbar ist und die nicht jedesmal in die ausführbare Datei gelinkt wird. Das spart zum einen viel Buildzeit, zum anderen kann man diese Bibliothek auch in Projekten die auf andere Programmiersprachen setzen (zum Beispiel C#) verwenden. Leider ist man aber auf die Plattform Windows begrenzt.