Programmierung

Aus dem Miranda-IM.de Wiki

Wechseln zu: Navigation, Suche


Dieser Artikel befasst sich mit den Grundlagen zum Thema Programmierung in Miranda IM, das einrichten des Compilers, die Verwendung der Sprache C++, die Besonderheiten der Miranda IM Programmierung und einige nützlichen Tipps.

Einige Grundlagen werden hier einfach vorausgesetzt, zum Beispiel den Umgang mit Windows oder des Compilers (zum Beispiel Visual Studio). Andere Sachen werden so detailliert wie möglich beschrieben, um Anfängern den Einstieg in die Programmierung zu erleichtern oder um wichtiges hervorzuheben, da eins auf dem anderen Aufbaut.

An dieser Stelle sei gesagt das Forum, der Chat oder die Diskussionsseite nicht dafür da ist Probleme mit eurem Compiler, eurem Code oder eurer Konfiguration zu lösen, sondern vielmehr dafür da sind um artikelbezogene Fragen zu klären, falsches zu verbessern und Sachen die wichtig sind zu ergänzen.

Important.png
Auf diesen Artikel wird besonderes Augenmerk gelegt, da es Miranda IM aktuell an fähigen Programmierern fehlt und so vielleicht gerade Neulingen erleichtert wird künftig mit am Projekt mitwirken zu können, in welcher Weise das auch immer ist.
Under-construction.png

Diese Seite ist derzeitig leider unvollständig!
Es kann sein, dass einige Informationen falsch sind oder noch fehlen!
Helfen Sie bitte mit, sie zu ergänzen oder zu verbessern!

Inhaltsverzeichnis

Einleitung

Im weitesten Sinne gibt es bei Miranda IM folgende Arten der Programmierung:

  1. Programmierung von eigenständig ablaufenden Plugin-DLL-Dateien in der Programmiersprache C
  2. Programmierung von eigenständig ablaufenden Plugin-DLL-Dateien in anderen Programmiersprachen wie zum Beispiel Delphi
  3. Programmierung von Plugins unter Zuhilfenahme anderer Plugins, die Schnittstellen zu anderen Programmiersprachen liefern, wie zum Beispiel C# mittels Hyphen
  4. Programmierung von Scripten auf PHP- oder Python-Basis mithilfe der Plugins mBot, MSP oder MirPy
  5. Programmierung von Texten für irgendwelche INI-Dateien (z. B. für Weather, mTooltip) oder für das Plugin Variables

Dieser Text hier befasst sich ausschließlich mit Punkt 1, also der Programmierung von Plugins mit der Programmiersprache C. Es wird dabei aber nicht auf die Programmiersprache C selbst eingegangen, sondern nur, vereinfacht, auf den Teil, wie man ein Miranda-IM-Plugin programmiert.

Eine Anmerkung noch zu der Programmiersprache C und Miranda IM: Es werden zwar in einigen Plugins Teile des C++-Standards verwendet, wie zum Beispiel die Speicherbelegung mittels new-Operators, Miranda IM ist aber nicht objektorientiert programmiert mit einer Klassenhierarchie und Objekten, wie zum Beispiel eMule.

Wahl des Compilers

Theoretisch kann jeder C/C++-Compiler für Windows verwendet werden. Die Entwickler der Plugins/Dateien, die in dem Miranda-IM-Paket bzw. den Testing-Versionen enthalten sind, verwenden immer Visual Studio 2005, 2008, 2010 und Visual Studio 6.0. Die Entwickler anderer Plugins überwiegend auch, allerdings gibt es auch vereinzelnd Entwickler, die mit Delphi (keine Programmierung möglich) compilieren, hierzu wird dann zum Beispiel das Shareware CodeGear Delphi benötigt, oder als Freeware Alternative Free Pascal. Microsoft Visual Studio Express steht kostenlos zur Verfügung und ist prinzipiell auch zum Kompilieren von Miranda IM ausreichend, Microsoft Visual Studio Professional & Delphi sind kostenpflichtig, bietet jedoch mehr Möglichkeiten für Programmierer. Wenn Sie sich für einen anderen Compiler als von Microsoft entscheiden, ist es, besonders für Anfänger hilfreich, wenn dieser zumindest die Projektdateien lesen kann. Eine Liste von Compilern finden Sie unter anderem hier.

Welchen Compiler Sie auch immer verwenden, vor der Erstellung von Miranda IM-Plugins sollten Sie die grundlegenden Schritte kennen, wie man die jeweilige Entwicklungsumgebung bedient und dort eine Windows-DLL-Datei erstellt.

Dieser Artikel bezieht sich ausschließlich auf Visual Studio 2008/2010 unter dem Betriebssystem Windows Vista/7 und der Verwendung dieser Vorlage.

Wahl der Programmiersprache

C++ ist die Programmiersprache die bei der Mehrheit der professionellen Programmierer eingesetzt wird, da diese Sprache schnelle, kleine Programme ausgeben können, die in einer robusten und portablen Umgebung entwickelt werden können. Da viele Werkzeuge immer wieder benutzt werden, wird die Programmierung an sich schnell „fast “zur Routine.

Sollte man zuerst C lernen? Meistens kommen die Fragen, welche Programmiersprache man als Anfänger sich angucken soll, oft auch im selben Zuge die Frage ob C die einfacher Einstiegsmöglichkeit ist. Dies ist eigentlich nicht erforderlich oder empfehlenswert, da viele Sachen relativ auf den gleichen Grundprinzipien aufbauen aber anders interpretiert werden und so mit evtl. sogar falsche Aussagen getroffen werden. Es ist also nicht „schlecht“ als Beginner mit C++ den Einstieg zu wagen, schließlich hat man dann eine solide Grundsprache die überall Anwendung finden kann. Hat man dies geschafft ist der Sprung auf andere Sprachen viel leichter.

Um es ganz banal aus zu sagen, man lernt nicht erst wie man eine CD einwirft, wenn es Mp3's gibt, es sei denn man will aus nostalgischen Gründen dies unbedingt.

Wieso werden weiter unten nicht alle Befehle/Anweisungen usw. aufgelistet die für die Miranda IM Entwicklung interessant sind?

Ganz einfach, um programmieren zu können muss man verstehen können wie etwas funktioniert und sich darüber hinaus seinen eigenen Gedanken machen. Es ist keinem geholfen dutzende Beispiele zu bringen, die sinnlos kopiert werden und derjenige dann nur vor Problemen steht. Außerdem entwickelt sich die Sprach teilweise, genau wie Miranda IM weiter und viele Sachen ändern sich mit der Zeit.

Es ist sinnvoll sich vorhandenen Programmcode von Miranda IM anzuschauen und daraus zu "lernen", allerdings bedeutet dies nicht das copy&paste dieser eigentliche Lernprozess sein soll. Es ist dadurch weder euch, noch dem Entwickler vom Original Source geholfen, macht euch also eure eigene Gedanken dazu und hinterfragt prinzipiell alles.

Falls Fragen zur Programmierung oder Verständnisprobleme auftreten sollten, versucht bitte erst einmal alleine und über Suchmaschinen klar zu kommen. Denn viele Themen wurden schon hunderten Male gefragt und die passende Lösung bereits festgehalten. Erst wenn ihr keine Lösung mehr findet oder wirklich fest hängt solltet ihr in den IRC-Hilfechannel oder Jabber-Channel kommen und nachfragen, damit die Helfer nicht immer die selben Antworten geben müssen und entnervt aufgeben.

Grundlagen

Bevor wir anfangen können etwas zu Programmieren müssen wir verstehen, was wir machen, wie das ganze aufgebaut ist und wo es Unterschiede gibt. Das soll mit den nachfolgenden Texten und einigen kleineren Beispielen dargestellt und erklärt werden. Aber es sei an dieser Stelle schon einmal gesagt, das wir nicht alles ansprechen können, da die C++ Sprache relativ komplex ist und hier niemals mit all Ihren Eigenarten, Befehlen, Anweisungen usw. komplett aufgeführt werden kann.

Allerdings ist es gerade für Einsteiger essentiell wichtig, sich nachfolgende Texte sorgfältig durchzulesen damit wenigstens einige Grundlagen vorhanden sind und weiter unten stehende Programmcode Beispiele auch richtig interpretiert werden können.

Hier gibt es einen kleinen Ausflug in folgende Themen:

  • Schreibweise und Syntax beachten
  • Unterschiede Debug und Release
  • Variablen
  • Schleifen und Bedingungen
  • Funktionen und Module
  • Arrays und Strukturen
  • Zeiger und Referenzen
  • Klassen
  • Die STL
  • Eigenständiges lernen

Schreibweise und Syntax beachten

Syntax ist der Aufbau des Codes. Nur wenn dieser richtig aufgebaut ist, kann man ihn compilieren. Das heißt unter anderen, dass jede Anweisung mit einem Semikolon zu beenden ist. Danach fängt man am besten eine neue Zeile an aber dies gehört nicht zum Syntax sondern zur besseren Übersicht. Man kann auch alles hintereinander schreiben ohne Leerzeichen und Tab's, aber ist schlichtweg einfach unübersichtlich.

Der Compiler ist case-sensitive, das bedeutet er unterschiedet Groß- und Kleinschreibung. Versucht man also etwas wie "miranda" zu ändern und hat diese an anderer Stelle "Miranda" genannt, so kann das nicht gehen. Da "miranda" nicht "Miranda" ist. "M" und "m" ist also nicht das gleiche, genauso wie bei jedem anderen Buchstaben auch.

Wenn nachfolgend etwas in Anführungszeichen steht, ist das nur der Inhalt. Diese Anführungszeichen haben also außer bei der textausgabe "cout" nichts zu bedeuten.

Unterschiede zwischen Debug und Release

Zunächst einmal gibt es verschiedene Konfigurationen um Eure Projekte zu kompilieren. Die Standardeinstellungen ist hierbei meistens "Debug" und die andere heißt "Release". Doch wo genau liegt hier der Unterschied zwischen den beiden? Um dies herauszufinden, stellt ihr am besten mal die Konfiguration auf Release um. In vielen Fällen geht das direkt über die Toolbar oder über den Konfigurationsmanager. Wenn Ihr nun die Einstellung geändert habt, müsst ihr das Projekt neu kompilieren. In Eurem Arbeitsverzeichnis gibt es jetzt einen Ordner namens "Release" indem die .exe-Datei oder .dll-Datei bzw. andere projektspezifische Dateien erzeugt werden. Vergleicht Ihr nun die Größe der beiden Dateien (zwischen Debug und Release), werdet Ihr feststellen das die Release-Version um einiges kleiner ist. Der Grund hierfür ist recht einfach: Die Debug Version enthält eine Fülle von Informationen, die beim Debuggen des Programms nützlich sind. Unter "Debuggen" versteht man die gezielte Fehlersuche in einem Programm. Dies ist zum Beispiel nützlich wenn Ihr Euer Projekt im Einzelschrittmodus laufen lassen und kontrollieren wollt. Außerdem bietet die Debug-Version ein mehr an Stabilität, sprich; diese ist absturzsicherer, da bestimmte Fehler einfach abgefangen werden. Dies ist aber keine Garantie, das diese nicht auch Crashen kann. Der Preis für die Stabilität ist also hier eindeutig die Dateigröße und der Geschwindigkeitsunterschied. Allerdings ist der Geschwindigkeitsunterschied nur dann "spürbar" oder messbar wenn rechenintensives passiert, was bei kleineren Projekten meist nicht der Fall ist. Prinzipiell sollten aber alle Ressourcen so gut wie möglich benutzt werden, stellt also wenn möglich in der finalen Version eures Projektes auf Release um und kompiliert es anschließend.

Variablen

Ohne Variablen könnte man keine veränderlichen Werte in einem Programm haben/nutzen. Dies bedeutet, dass es unmöglich wäre anzugeben, wie viele Tage es noch bis zum Jahresende sind, um nur ein Beispiel zu nennen. Die Variable ist noch besser zu vergleichen mit einem Aktenordner, man kann diese beschriften und legt verschiedenste Inhalte hinein. Anhand der Beschriftung kann der Inhalt also wiedergefunden werden. Zum Beispiel eine Punktezahl für einen Spieler. Man erstellt eine Variable und gibt ihr einen Namen (wir beschriften also den Aktenordner bzw. dessen Schublade). Jetzt bestimmen wir, wie wir die Punkte des Spielers verteilen wollen, dazu schreiben wir auf einen Zettel 10 und legen diese in entsprechende Schublade ab. Hat der Spieler nun ein Hindernis vor sich, wird nun anhand der Punktezahl die richtige Schublade herausgesucht. Dort wird dann die 10 ausradiert und eine neue Zahl wird hingeschrieben.

Datentypen, Werte und Variablennamen

Hier wird von Beginn an festgelegt wie viel Platz man effektiv benötigt. Der so genannte Variablentyp oder auch Datentyp bestimmt, was und wie viel eine Variable "aufnehmen" kann. Es gibt zahlreiche Datentypen, die wir verwenden können. Einige können nur kleine, ganze Zahlen aufnehmen, andere nur positive Zahlen und wieder andere negative und positive Gleitkommazahlen. Bei einer Gleitkommazahl handelt es sich anders ausgedrückt um eine Kommazahl. Dabei ist es so, dass die Genauigkeit und der Wertebereich sozusagen automatisch angepasst werden, je nachdem, wie viele Stellen hinter dem Komma benötigt werden. Um nun eine Variable mit C++ zu erzeugen muss man dem Compiler erst einmal mitteilen, von welchem Typ diese Variable denn sein soll. Dann erst erfolgt der Variablenname.

Nachfolgend die wichtigsten unterstützten Datentypen mit den jeweiligen Wertebereich.

  • Datentyp Boolean, Werte True oder False (Wahr oder Falsch)
  • Datentyp Byte, ganze Zahlen von 0 bis 255
  • Datentyp Char, einzelne Zeichen
  • Datentyp Date, Datumsangaben vom 1. Januar des Jahres 1 bis zum 31. Dezember 9999
  • Datentyp Double, Gleitkommazahl mit doppelter Genauigkeit, Werte von -1,79769313486231570 mal 10 hoch 308 bis 4,94065645841246544 mal 10 hoch -324 im negativen Bereich und von 1,79769313486231570 mal 10 hoch 308 im positiven Bereich
  • Datentyp 'Integer, ganze Zahlen von -2.147.483.648 bis 2.147.483.647
  • Datentyp Long, ganze Zahlen von -9.223.372.036.854.775.808 bis 9.223.372.036.854.775.807
  • Datentyp Object, beliebiger Wert
  • Datentyp Short, ganze Zahl von -32768 bis 32767
  • Datentyp Single, Gleitkommazahl mit einfacher Genauigkeit, Werte von 3,4028235 mal 10 hoch 38 bis -1,401298 mal 10 hoch -45 bis 3,4028235 mal 10 hoch 38 im positiven Bereich

Sicherlich gibt es noch viel mehr, aber wir wollen hier nur auf die grundlegendsten Sachen eingehen damit der Überblick noch gewährleistet werden kann.

Definieren und deklarieren von Variablen

Geht man wie oben beschrieben vor und teilt dem Compiler mit, von welchem Typ eine Variable ist und welchen Namen sie haben soll, so hat man eine Variable deklariert. Weist man der Variable danach einen Wert zu, so hat man die Variable definiert. Es ist sehr wichtig, diese beiden Begriffe zu kennen und auseinander halten zu können. Sobald man eine Variable deklariert, wird automatisch der benötigte Speicher vom Compiler reserviert und für unsere frisch gebackene Variable zur Verfügung gestellt (logischerweise erst nachdem das Programm kompiliert wurde). Deklariert man nun eine Variable, ohne ihr danach einen Wert zuzuweisen, wird ihr Inhalt unbestimmt sein. Würde man sich nun den Wert ausgeben lassen, der momentan in der variable steht, so würde man irgendeine wirre Zahlenkombination erhalten. Dies hat einfach den Grund, dass der Compiler unsere Variable einen freuen Speicherbereich zuweist. Die Variable enthält nun natürlich den Wert, der zuletzt an dieser Speicherstelle stand. Damit nun durch initialisierte (definierte) Variablen keine Fehler entstehen können, gibt uns der Compiler in einem solchen Fall eine Warnung aus (unten im Ausgabefeld von Visual Studio), die auch beachtet werden sollte. Sicherlich werdet Ihr früher oder später Bekanntschaft mit dieser Warnung machen. Es ist außerdem möglich eine Variable gleichzeitig zu deklarieren und zu definieren. Des weiteren ist es möglich mehrere Variablen gleichen Datentyps in einem Rutsch zu erzeugen.

Verschiedene Datentypen und ihre Bedeutungen

Es gibt (wie weiter oben erwähnt), viele verschiedene Datentypen. Ich werde an dieser Stell nicht alle aufzählen und auch nicht detailliert mit Beispielcode zeigen, diese Liste ist nur dazu da um einen Überblick zu bekommen, was die häufigsten Datentypen sind.

int-Datentypen können nur ganze Zahlen aufnehmen, die sowohl positiv als auch negativ sein können. Es sei denn, man gibt explizit an, dass nur positive Zahlen verwendet werden sollen. Weist man einer Integervariablen trotzdem eine Kommazahl zu, so wird immer nach unten gerundet (abgerundet). Also sozusagen alles hinter dem Komma gestrichen.

char-Datentypen dienen zur Speicherung von Zeichen und Buchstaben (char = Charakter, sprich Zeichen). Wenn wir nun Texte oder ähnliches verwalten möchten, ist dieser Datentyp der richtige für uns. Insgesamt kann man mit einer char-Variablen 256 verschiedene Zeichen darstellen (ein char = ein Byte). Allerdings sind nicht alle 256 Zeichen interessant, da sie nicht immer anzeigbar sind.

short-Datentypen stellen, wie ein Integer auch, ganze Zahlen dar. Der Unterschied liegt im Wertebereich. In der Regel besteht ein "normaler" Integer (also int) aus vier Bytes und ein short aus zwei Bytes. Dies muss jedoch nicht immer so sein.

long-Datentypen stellen ebenfalls ganze Zahlen dar. Der Wertebereich kann hier jedoch größer ausgelegt sein.

float-Datentypen können ganz banal gesagt "Kommazahlen" speichern. Dabei steht float für Gleitkommazahl. Mit diesem Datentyp kann man sowohl positive als auch negative Zahlen speichern. Im Gegensatz zu anderen Sprachen verwendet man bei C++ einen Punkt, um die Nachkommastellen zu bestimmen. Wichtig ist, dass der Kommazahl ein kleines f folgen muss.

double-Datentypen entsprechen im Grunde den float-Datentypen. Der wesentliche Unterschied besteht in der Genauigkeit und der Größe des Wertebereichs. Mit einer double-Variable kann man also größere bzw. genauer Zahlen als mit einer float-Variablen darstellen. Natürlich belegt eine double-Variable dadurch auch mehr Speicherplatz.

bool-Datentypen (auch boolescher Wert oder boolean) dienen sozusagen der Wahrheitsfindung, Sie können genau zwei Zustände annehmen: wahr oder falsch. Um diese Information zu speichern, wäre theoretisch ein Bit genug. Da es jedoch zu aufwändig und zu langsam ist, einzelne Bits anzusprechen, belegen auch die bool-Datentypen mindestens ein Byte an Speicherplatz (meistens aber mehr). Deshalb gilt die Faustregel bei einem Wert von null der Zustand "falsch" (false) und bei allen anderen Werten "wahr" (true).

Gegenteil von variabel? Konstant!

Bis jetzt wurde nur über Variablen geschrieben, deren Werte kann man, wie wir ja jetzt wissen, jederzeit im Programm ändern. Allerdings ist das nicht immer wünschenswert bzw. gewünscht, da man manchmal Werte hat, die sich während des gesamten Programmablaufes nicht verändern dürfen. Ein Beispiel wäre die Anzahl der Monate. Einige werden sich jetzt fragen, wieso man keine Variable benutzt, dieser ein einziges Mal einen Wert zuweist und sie dann nicht mehr ändert. Nun, diese Frage ist sehr gut und an dieser Stelle berechtigt. Doch schnell ist eine Gegenfrage da: Wer kann garantieren, dass in einem sehr großen Quelltext, an dem womöglich auch noch mehrere Leute arbeiten, wirklich niemand aus versehen diese Variable doch noch ändert? Und eben solche Fehler passieren des öfteren. Konstanten bieten außerdem noch den Vorteil, dass man einen häufiger vorkommenden Wert nicht immer direkt als Zahl im Quelltext angibt, sondern eben einer Konstanten zuweist. Diese kann an einer zentralen Stelle verändert werden und man muss dann nicht den gesamten Quelltext durchsuchen. Sobald man versucht, den Wert nach der Zuweisung erneut zuzuweisen oder durch eine Rechenoperation zu verändern, klopft einem der Compiler auf die Finger und wirft Fehler aus. Und ein Fehler während des Kompilierens ist weitaus harmloser, als ein Fehler während des Programmdurchlaufs. Es gibt drei Hauptmöglichkeiten eine Konstante zu erzeugen. Einmal durch das Schlüsselwort const, einmal durch die Präprozessor-Direktive #define und einmal durch das Schlüsselwort enum. Gehen wir einfach alle drei Möglichkeiten durch.

const
Will man eine Konstante erzeugen, so geht man zuerst einmal so vor, wie man das bei der "normalen" Variablen auch tun würde. Man gibt den Datentypen gefolgt vom Namen der Variablen und ein Semikolon an. Um dem Compiler nun mitzuteilen, dass es sich eben nicht um einen variablen Wert, sondern tatsächlich um eine Konstante handelt, schreibt man noch vor den Datentypen das Schlüsselwort const. Das Ganze kann dann so aussehen:

const int Anzahl_Monate = 12;  // Konstante erzeugen
cout << Anzahl_Monate << endl; // Wert ausgeben

Der Wert, der nun in Anzahl_Monate steht, wird nur an diesem Punkt angegeben. Versucht man, irgendwo im Programm den Wert zu ändern, wirft der Compiler einen Fehler aus. Somit ist sichergestellt, dass keine heimtückischen Fehler mehr passieren.


#define
Die zweite Möglichkeit, einen konstanten Wert zu erzeugen, bietet uns die Präprozessor-Direktive #define, die folgendermaßen funktioniert:

#define ANZAHL_MONATE 12        // Konstanten Wert erzeugen
cout << ANZAHL_MONATE << end;  // Wer ausgeben

Das #define führt einfach nur eine Textersetzung durch, bevor der Quellcode endgültig kompiliert wird. Wie weiter oben erwähnt, werden solche Präprozessor-Direktiven immer als erstes behandelt. Überall dort wo nun "ANZAHL_MONATE" steht, wird einfach eine 12 eingesetzt. Wie im vorigen Abschnitt angesprochen, enden solche Präprozessordirektiven nicht mit einem Semikolon. Außerdem darf hier kein Zuweisungsoperator (=) verwendet werden. Man gibt einfach den zu ersetzenden Text gefolgt vom neuen Text an. Denjenigen die aufgepasst haben, sollte aufgefallen sein, dass diese Variante nicht geeignet ist, um Konstanten zu erzeugen, da hier nur eine Textersetzung durchgeführt wird und dem Compiler daher nicht bekannt ist, um welchen Datentyp es sich handelt. Der Compiler meckert auch, wenn man versucht den Wert "ANZAHL_MONATE" zu verändern. Euch wird aufgefallen sein dass #define-Version ausschließlich mit Großbuchstaben geschrieben wurde. Dies ist nicht unbedingt notwendig, wird aber sehr oft zur Übersichtlichkeit gemacht, damit man sieht, dass es sich um eine mit #define erzeugte Textersetzung handelt

enum
Mit dem Schlüsselwort enum kann man sich genannte Aufzählkonstanten erstellen. Diese sind immer dann nützlich, wenn man mehrere aufeinander folgende Konstanten benötigt. Stellt euch vor, in eurem Projekt würde es unterschiedliche Arten von Raumschiffen geben und jede Art würde durch eine bestimmte Kennziffer repräsentiert, durch die man sie später unterscheiden kann. Man könnte dies mit bisher erworbenen Wissen folgendermaßen lösen:

const int Jaeger = 0;
const int Transporter = 1;
const int Bomber = 2;
const int Klingonen = 3;

Im Grunde spricht nichts gegen diese Lösung, jedoch ist sie etwas unbeholfen. Wir müssen für jeden Raumschifftyp, der eventuell hinzukommt, eine neue Zeile schreiben und eine neue Kennzahl zuweisen. Das Ganze geht jedoch mit enum einfacher und schneller. So zum Beispiel:

enum Raumschifftyp {JAEGER, TRANSPORTER, BOMBER, KLINGONEN};
Raumschifftyp Spieler1 = BOMBER;

Mit enum erstellen wir uns also einen eigenen Aufzählungstypen. Wenn wir so vorgehen, dann stellen JAEGER, TRANSPORTER, BOMBER und KLINGONEN unsere möglichen Raumschifftypen dar. Dabei bekommt jeder dieser Typen automatisch einen Wert zugeteilt und zwar von 0 beginnend aufwärts. JAEGER hat somit den Wert 0, TRANSPORTER 1 usw. Es handelt sich hierbei also eindeutig um Integer. Wir können an dieser Stelle unseren Aufzählungstypen nun wie eine normale Konstante verwenden. Somit ist es auch nicht möglich, etwa BOMBER später einen anderen Wert zuzuweisen und wir sind auf der sicheren Seite. Möchte man aus irgend einem Grund die Werte selbst zuweisen, so kann man dies natürlich auch machen. Es ist nicht zwingend, erforderlich das bei Null begonnen wird und jeder Folgewert um ein erhöht ist. Man kann dann zum Beispiel folgendermaßen vorgehen:

enum Raumschifftyp {JAEGER = 3, TRANSPORTER, KLINGONEN = 18, BOMBER};

Nun hat JAEGER den Wert 3, TRANSPORTER 4 KLINGONEN 18 und BOMBER 19. Wie man sieht erfolgt die automatische Durchnummerierung immer dann, wenn man nichts angegeben ist bzw. wird nach dem zuletzt zugewiesenen Wert fortgesetzt. Hier ist aber zu beachten, dass es sich zum Beispiel oben bei Spieler1 nicht um eine Konstante handelt. Es sind nur die eigentlichen Aufzählungen konstant.

Welche der drei Möglichkeiten ist die Beste? Wir wissen nun das um eine Konstante zu erzeugen es drei Möglichkeiten gibt. Man kommt also jetzt unweigerlich auf die logische Frage, welche denn die Beste Möglichkeit ist eine Konstante zu erzeugen. Um das ganze zu erläutern sollte man sich ein paar Gedanken über die Vor- und Nachteile der einzelnen Varianten machen.

Die Verwendung von #define hat den Vorteil das keinerlei Speicher verbraucht wird, da ja nur eine Textersetzung durchgeführt wird, anstatt tatsächlich eine Konstante zu erzeugen. Dieser Vorteil hat allerdings nur wenig Gewicht, da Speicherplatz heute nicht mehr so teuer ist, wie noch vor 15 Jahren. Außerdem wird man aller Wahrscheinlichkeit nach niemals so viele Konstanten in seinem Programm haben, dass der gesparte Speicher wirklich von Bedeutung wäre.

Eine mit const erzeugte Konstante besitzt jedoch etwas, was sofort klar machen sollte, welche der drei Lösungen die bessere ist: einen echten Datentyp! C++ ist eine typensichere Sprache, dass sollte man nicht durch Textersetzungen zunichte machen. Es ist immer besser, wenn man den Datentyp mitführt. Möchte man Kennziffern vergeben oder etwas aufzählen, so ist enum am sinnvollsten. Man spart sich damit eine Menge Schreibarbeit und der Quelltext wird um einiges übersichtlicher. Außerdem ist es einfacher, neue Elemente hinzuzufügen.

Konstanten sollte man immer dann verwenden, wenn klar ist, das sich ein Wert niemals ändert. Das bisschen Mehraufwand an Schreibkram lohnt sich auf jeden Fall, da vertrackte Fehler vermieden werden können. Am besten gewöhnt Ihr euch den Gebrauch von const gleich an.

Erzwungene Typenwandlung - das Casting

Beim sogenannten Casting in C++ geht es nicht darum, einen neuen Deutschland sucht den Superstar zu sichten, sondern um die erzwungene Umwandlung von einem Datentyp in einen anderen. Wozu das dient und warum es so wichtig ist, soll im folgendem kleinen Code-Fragment veranschaulicht werden:

int Punkte = 15;
float Faktor = 2.5f; // Das kleine "f" kennzeichnet einen float
int Gesamtpunkte;
 
Gesamtpunkte = Punkte * Faktor;
cout << "Gesamtpunktzahl: " << Gesamtpunkte << endl;

Hier soll einfach eine Punktzahl mit einem Faktor multipliziert und das Ergebnis in Gesamtpunkte gespeichert werden. Das Problem, das nun auftaucht, ist die Tatsache, dass hier unterschiedliche datentypen miteinander vermischt werden. Bei der Variablen Faktor handelt es sich um einen float, wobei das Endergebnis von Typ int ist. Das Ergebnis der Rechnung kann nun jedoch auch eine Kommazahl sein. Da diese ja dann in einem int-Datentyp (Gesamtpunkte) gespeichert werden soll, wird es wohl oder übel zu Problemen kommen. Um zu sehen, was passiert solltet Ihr das kleine Code-Fragment mal abtippen und kompilieren lassen. Der Compiler wird nicht abbrechen, sondern eine lauffähige Version erzeugen. Jedoch wird er eine Warnung ausgeben, die ungefähr so aussehen kann:

warning c4244: '=' Konvertierung von 'float' in 'int' ,
möglicher Datenverlust

Dass diese Warnung begründet ist, zeigt sich wenn man das Programm ausführt. Das korrekte Ergebnis wäre 37,5, aber es wird 37 ausgegeben. Die Warnung sagt etwas über einen möglichen Datenverlust und genau das ist es in diesem Fall auch. Da das Ergebnis einem int zugewiesen wird, kann es unmöglich eine Kommazahl sein. Aus diesem Grund wird das Ergebnis einfach gerundet. Der Compiler rundet immer ab, nicht auf, da eigentlich nur die Nachkommastellen abgeschnitten werden. Genau genommen kann man hier eigentlich nicht von Runden sprechen. Dieses Problem kann sowohl unbeabsichtigt als auch beabsichtigt auftauchen. Wenn wir ein wenig geschlafen haben und versehentlich mit falschen Datentypen jonglieren, so ist die entsprechende Warnung vom Compiler für uns hilfreich und wir können unseren Fehler beheben. Doch kann es natürlich auch sein, dass wir ein solches Vorgehen beabsichtigen und eben wollen, dass das Ergebnis gerundet wird. Dies könnte beispielsweise der Fall sein, wenn wir Bildschirmkoordinaten von unseren Mauscursour berechnen. Dabei kann es vorkommen dass bei einer Berechnung Kommazahlen auftauchen. Logischerweise wird eine Bildschirmposition aber nur als ganze Zahlen bestehen. Runden ist in diesem Fall also kein Fehler und beabsichtigt. Es wäre ja jetzt wirklich nervig, wenn der Compiler einen Dutzende solcher Warnungen zuwirft obwohl wir uns im Klaren darüber sind und genau wissen was wir machen. Genau hier kommt das Casting ins Spiel. Wenn man vom Casten einer Variablen spricht, meint man immer das beabsichtigte Umwandeln von einem Datentypen in einen anderen. Wir sagen also dem Compiler (zum Beispiel Microsoft Visual Studio) dass wir ohne Rücksicht auf Verlust und mit voller Absicht handeln und er uns doch bitte mit Warnungen verschonen möchte. So gut sie auch gemeint sind.

Casting im C-Stil Die Art des Castens, die hier nun vorgestellt wird ist sozusagen out of date. Man sollte sie nicht mehr verwenden, da es in C++ einfach wesentlich bessere Möglichkeiten gibt. Allerdings sieht man diese doch noch sehr häufig und daher wird diese hier auch gelistet. Hier findet die eigentliche Berechnung immer in den {} Klammern statt. In der linken Klammer steht immer der Datentyp, in den wir ein Ergebnis gerne casten möchten. Wir erinnern uns an dieser Stelle wieder etwas zurück (eins baut wie gesagt aufs andere auf) und springen zu den Variablen und deren Rechenarten. Dort wurde ja beschrieben das Zuweisungen immer von rechts nach links stattfinden. Wir sagen also dem Compiler, dass er uns erst das Ergebnis von Punkte*Faktor berechnen und dieses dann in einen Integer casten soll. Somit wurde Entwarnung gegeben und wir versichern dem Compiler dass wir wiklich wissen was wir eigentlich machen. Aber kommen wir zu den Klammern, auf den ersten Blick würde es ja genügen wenn wir einfach Variable Faktor in einen Integer casten. Wenn man das dann auch probieren sollte, stellt sich heraus das der Compiler nicht meckert und man glaubt es wäre so okay. So zum Beispiel:

Gesamtanzahl = Monate * (int)Faktor;

Leider ist das nun aber dennoch falsch. Gehen wir einfach mal an dieser Stelle ein Beispiel durch. Monate soll hier den Wert 15 haben, Faktor beträgt 2,5. Mit der Rechnung wird dies jetzt erst multipliziert und dann gecastet. Wir erhalten also 15*2,5 = 37,5. Da durch das Casten der Wert abgerundet wird, erhalten wir als finales Ergebnis den Wert 37. Machen wir das Gleiche jetzt, indem wir zuerst die Variable Faktor casten, so wird diese zuerst abgerundet. Aus 2,5 wird nun also zu einer 2. Und 15*2 ergibt 30. Wie man sieht, ist es ähnlich wie bei der Punkt-vor-Strich-Rechnung. Man muss genau darauf achten, was man zuerst macht. Wenn wir nicht casten, warnt uns der Compiler, dass wir wissen was wir tun. Dies hat jedoch einen Preis, und zwar müssen wir dann jedesmal auf uns allein gestellt darauf achten. Wenn man also castet, sollte man mehrmals hinschauen was genau Sache ist. Oft schleichen sich Fehler ein, der im Nachinein nur recht schwer bis gar nicht zu finden ist.

Casting mit C++ An dieser Stelle ist es nicht wirklich einfach zu erklären wieso man diese Variante benutzen sollte. In unserem Beispiel ist es eigentlich Piep egal ob man im C-Stil oder eben im C++-Stil castet, da das die Funktionalität nicht betrifft. Doch wenn "Zeiger" ins Spiel kommen wird es kompliziert und nicht ungefährlich wenn man sich für die C-Variante entscheidet. Beschränkt euch einfach darauf das die C++-Variante übersichtlicher ist und etwas logischer aufgebaut ist.

Gucken wir uns ein Beispiel an:

Gesamtmonate = static_cast<int> (Punkte*Faktor);

Hier fällt nun auf das der Compiler den static_cast blau einfärbt. Und die Syntax ist auch nicht schwer zu verstehen. So gibt static_cast lediglich an, das wir den datentyp in einen anderen casten möchten. Dabei steht innerhalb der spitzen Klammern der Zieltyp und innerhalb der runden Klammern der Term oder Quelltyp. Dazu gibt es einiges zu erzählen, aber wir beschränken uns wie immer nur auf das Wesentliche. Hier wäre zu erwähnen das die Arten des Cast reinterpret_cast oder dynamic_cast als Allzweckwaffe dienen. An dieser Stelle erkläre ich aber deren Bedeutung jetzt ersteinmal nicht.

Fehler im Quelltext

Wie zum Laufen lernen das Hinfallen gehört, gehören Fehler zum Programmieren. Jedoch mit einem riesen Unterschied! Da der Programmierer sitzt, kann er schlecht Fallen, allerdings macht er dafür umso mehr Fehler. Anfänger wie wir es noch sind, werden nicht herumkommen Fehler zu produzieren. Wichtig ist allerdings das man diese erkennt und daraus lernt. Und selbst erfahrenste Entwickler machen noch Fehler, dies ist einfach so, da keiner Perfekt ist. Meistens sind dies schlichtweg Syntaxfehler, den man schon beim Kompilieren erkennt, oder eben ein schwereres Problem das erst zur Laufzeit auftritt. Ich behaupte das ein Mensch alleine kein großes Programm schreiben kann ohne einen einzigen (Tipp-)Fehler. Die Kunst ist es nun, nicht nur Fehler nach Möglichkeit zu vermeiden, sondern sie auch schnell zu finden und auszumerzen, wenn sie auftauchen. Gerade zu beginn können einen die Meldungen des Compilers ganz schön frustrieren, da nicht immer sofort klar ist, was eigentlich nicht stimmt. Um das Ganze besser in den Griff zu bekommen, gibt es wie bereits erwähnt gelegentlich Quelltexte, in die ich ganz bewusst Fehler eingebaut habe. Diese zu finden und zu eliminieren ist ein wichtiger Punkt, den Ihr wahrnehmen solltet. Gebt also nicht immer sofort auf und schmeißt die Flinte ins Korn, Google und MSDN sind eure verbündeten auf der Suche nach Antworten und Problemlösungen.


Schleifen und Bedingungen - wozu ?

Nein, hier sind keine Schleifen von Geschenken gemeint. Sondern bisher können wir mit unserem bisherigem Wissen zwar Werte über die Tastatur eingeben, mit diesen rechnen und sie wieder ausgeben, aber das wars auch schon. Es läuft noch alles ausschließlich linear ab, was bedeutet dass wir keine Verzweigungen haben und auch keine Entscheidungen treffen können. Wir brauchen Bedingungen um bestimmte Sachen abzufragen.

Bedingungen kennen wir ja aus dem richtigen Leben zur Genüge. Wir bekommen ständig welche gestellt, stellen selbst welche und treffen seine Entscheidungen dementsprechend. Wir überlegen uns ob wir uns eine Software für einen bestimmten Betrag kaufen oder es bleiben lassen. Außerdem fehlt uns an dieser Stelle die Möglichkeit, bestimmte Programmteile beliebig oft wiederholen zu können.


Funktionen und Module

Wir haben mit Schleifen die Möglichkeit, Codeabschnitte zu wiederholen. Was aber, wenn diese Codeabschnitte an verschiedenen Stellen im Programm vorkommt? Hier nehmen wir als Arbeitsgrundlage folgendes Codefragment:

cout << "Miranda IM ist toll" << endl;
cout << "                   " << end;
cout << "Miranda IM ist toll" << endl;

Die Ausgabe von »Miranda IM ist toll«, die hier stellvertretend für komplexere Codeabschnitte steht — erfolgt in identischer Art und Weise an zwei verschiedenen Stellen im Code. Obwohl zweimal dasselbe geschieht, steht der dazu notwendige Code zweimal im Programm. Nicht nur, wird das Programm dadurch länger, sondern es müssen bei eventuellen Änderungen an den Codeabschnitten viele mögliche Abschnitte geändert werden. Die Gefahr, dass nicht alle im gleichem Maße geändert werden oder ein Abschnitt übersehen wird, ist hier sehr groß. Es wäre doch viel praktischer, wenn die Ausgabe von »Miranda IM ist toll« nur einmal im Programm vorkäme und wir noch darauf verweisen müssten.

Die Lösung dieses Problems lautet „Funktionen“. Mithilfe einer Funktion haben wir die Möglichkeit, Programmcode in einer unabhängigen Einheit zu kapseln. Schauen wir uns zuerst aber hierfür den grundlegenden Aufbau der Funktionen an.

Unsere Funktion soll keinen Rückgabewert besitzen (wir wissen aktuell ja nicht, was dies ist), deswegen muss vor dem Funktionsnamen ein void stehen. Sprich Rückgabetyp Funktionsname(Parameter) { und }. Der Funktionsname sollte aussagekräftig sein, damit der Anwender schnell erfassen kann, welchem Zweck die Funktion genau dient. passend wäre in unserem Beispiel MirandaIMausgabe.

Hinter dem Funktionsnamen stehen die Funktionsparameter. Da wir hier noch nicht besprochen haben, was damit gemacht wird, lassen wir das an dieser Stelle und lassen die runden Klammern leer.

Im Anweisungsblock hinter dem Funktionskopf steht der zur Funktion gehörende Programmcode, in diesem Fall unsere Ausgabe »Miranda IM ist toll«. Dies ergibt nachstehende Funktion:

void MirandaIMausgabe() {
cout << "MirandaIMausgabe" <<endl;
}

Dieser Schritt ist für unsere späteren Funktionen wichtig, außerdem muss hier die neue Funktion vor main stehen. Allerdings werden wir weiter unten in diesem Fall noch etwas flexibler.

  • Auch wenn die Funktion keine Parameter besitzt, muss das leere, runde Klammerpaar vorhanden sein!
  • Auch wenn die geschweiften Klammern des Funktionsanweisungsblock nicht anwendbar ist, muss trotzdem der komplette Anwendungsblock geschrieben werden — selbst wenn er nur aus einer Anweisung besteht.
  • Die Angabe von void bei nicht erwünschtem Rückgabewert ist zwingend.


MirandaIMausgabe();

Doch sehen wir uns das vollständige Programm dazu an:

#include <iostream>
 
using namespace std:
 
void MirandaIMausgabe() {
cout << "Miranda IM ist toll" << endl;
}
 
int main() {
  MirandaIMausgabe();
  cout << "                   " << endl;
  MirandaIMausgabe();
}

Jedes Mal, wenn die Funktion MirandaIMausgabe aufgerufen wird, springt das Programm zur Funktion und merkt sich diesen Punkt, von dem aus es zur Funktion gesprungen ist. Nach Beendigung der Funktion springt das Programm dann an die Stelle zurück, an der die Funktion aufgerufen wurde und fährt dort mit der Ausführung fort.

Kompilieren von Miranda IM

Installation der Miranda IM Dateien

(Die Ordnernamen im Folgenden sind nur Vorschläge)

  1. Wählen Sie nun den Projekte-Ordner von Viaual Studio aus (unter Visual Studio 2010 und Windows 7 bspw C:\Users\xx\Documents\Visual Studio 2010\Projects).
  2. Erstellen Sie dort einen Unterordner Miranda Trunk und einen anderen, zum Beispiel TestMiranda.
  3. Installieren Sie im Ordner TestMiranda ein einfachstes Miranda IM der aktuellen Testing, also zum Beispiel clist_classic, SRMM, dbx_mmap und ein Protokoll, wie zum Beispiel ICQ. Es ist ratsam, sich für dieses Test-Miranda auch einen neuen Protokollaccount zuzulegen.

Download des Miranda-IM-Quellcodes

Miranda IM Sourcecode in Visual Studio 2010
  1. Installieren Sie das Programm TortoiseSVN und evtl. die deutsche Sprachdatei dazu. Beachten Sie auch hier wieder das die Installation der zu verwendeten Version von Ihrem Betriebssystem abhängt, haben sie ein 64-Bit System installieren Sie auch nur dessen 64-Bit Software. Es ist an dieser Stelle nicht nötig auch noch 32-Bit Software zu installieren. Ein weiterer guter SVN-Client, der sich in Visual Studio einfügt, ist AnkhSVN. Alternative SVN-Clienten finden sie hier.
  2. Folgende Anleitung bezieht sich nur auf die TortoiseSVN Software.
  3. Öffnen Sie jetzt mit einem Rechtsklick das Kontextmenü von Miranda Trunk und wählen Sie dort SVN Checkout aus. In das Feld URL geben Sie http://miranda.googlecode.com/svn/trunk/ ein und warten dann, bis alle Dateien übertragen wurden. Dies ist abhängig von der Datenmenge, Ihrer Hardware und der Internetverbindung.
  4. Suchen Sie jetzt in diesem Ordner nach einem Unterordner Miranda/include und kopieren Sie die komplette Pfadangabe irgendwo hin, also z. B. C:\Users\xx\Documents\Visual Studio 2010\Projects\Miranda Trunk\miranda\include.
  5. Nun öffnen Sie Ihre Entwicklungsumgebung und suchen dort die Einstellungen, es sollte möglich sein, einen zusätzlichen Include-Pfad für alle Projekte anzugeben. Machen Sie das. Bei Visual Studio 2005/2008 ist dieser unter dem Punkt Extras → Optionen → Projekte und Projektmappen → VC++-Verzeichnisse → Verzeichnisse anzeigen für: Includedateien anzugeben.
  6. In Visual Studio 2010 muss für jedes Projekt separat eine Includeumgebung geschaffen werden, gehen Sie dafür mit einem Rechtsklick auf das 'Projekt' (auf der rechten Seite im 'Projektmappen-Explorer'), dann unter → 'Eigenschaften', öffnen Sie nun die 'Konfigurationseigenschaften' → 'VC++-Verzeichnisse' und tragen Sie besagten Pfad unter 'Includeverzeichnisse' ein. Sie können aber auch diese Einstellung einmal für alle Projekte vornehmen, genaueres dazu finden sie hier.
  7. Downloaden und entpacken Sie jetzt diese Vorlage. Innerhalb der Dateien finden Sie die Datei install.bat. Öffnen Sie diese mit einem Texteditor und überprüfen Sie die Angabe unter VCPATH ggf. muss diese an Ihre Umgebung angepasst werden. Führen Sie anschließend die Datei aus.
  8. Sie finden diese Vorlage dann unter Datei → Neues Projekt → Visual C++.

Wenn Sie diesen Wizard verwenden und das Verzeichnis mit den Includedateien in der Entwicklungsumgebung angegeben haben, sollten Sie die Miranda IM Include Dir-Angabe innerhalb des Wizards entfernen.

Erläuterung der Ordner im Verzeichnis Trunk/miranda

.svn
Diese Ordner werden von dem Programm TortoiseSVN erzeugt und dienen unter anderem der Versionskontrolle.
bin
Diese sind eigentlich nur interessant, wenn Sie die miranda32.exe oder miranda64.exe selbst erstellen wollen. Verwenden Sie aber besser die Version aus der jeweils letzten Testing-Version um auf dem aktuellsten Entwicklungsstand zu sein.
docs
Enthält nur ein paar .txt-Dateien über den offiziellen Release-Build, die Liste einiger Entwickler und eine Kopie der GNU General Public License; die einzigen interessanten Dateien sind evtl. die beiden .ini-Dateien mirandaboot.ini und autoexec_sample.ini, für die Pluginentwicklung sind diese aber unwichtig.
i18n
Enthält Texte zur Sprachdatei. Die deutsche Sprachdatei hat eher historischen Charakter, und die englische ist auch nicht aktuell.
include
Das hier ist eigentlich der wichtigste Ordner für Entwickler. Er enthält sämtliche Deklarationen zur Miranda-IM-API (Application Programming Interface), also sämtliche Funktionsaufrufe mit (meistens) einer Beschreibung der Parameter. Besonders wichtig sind hier m_database.h, newpluginapi.h, m_utils.h, m_plugins.h und m_system.h, diese sollten Sie sich auf jeden Fall einmal ansehen. Die anderen Dateien sind mehr oder weniger logisch sortiert, das bedeutet, wenn Sie etwas mit Avataren machen wollen, schauen Sie sich zum Beispiel die Datei m_avatars.h an. Im Unterverzeichnis delphi finden Sie die Includedateien für die Programmiersprache Delphi.
plugins
Enthält den Quellcode der Plugins, die in den Testing-Versionen enthalten sind.
  • Sollten Sie sich den Quellcode anderer Plugins, zum Beispiel von der Addons-Seite, ansehen und kompilieren wollen, sollten Sie den entsprechenden Quellcode in entsprechende Unterverzeichnisse hierher entpacken. Einige Plugins können Pfadangaben enthalten, die sich auf diese Ordnerstruktur beziehen, unter anderem ..\..\include für das Include-Verzeichnis.
  • Das Testplugin enthält grundsätzliche Funktionen, die jedes Plugin beinhalten muss, Sie sollten sich diese Dateien ruhig einmal ansehen. Probieren Sie aus, ob Sie diese Datei kompilieren können.
protocols
Enthält den Quellcode der einzelnen Protokolle.
src
Diese Dateien sind der Quellcode der Datei miranda32.exe bzw. miranda64.exe.
miranda-tools
Enthält den Quellcode von externen Tools wie zum Beispiel dem DBTool (Datenbank-Tool).

Sonstige Downloads

Es ist evtl. hilfreich, sich ebenfalls den Quellcode der auf miranda-plugins (Google Code) gehosteten Plugins herunterzuladen, gerade Anfänger finden dort eine Fülle von Beispielen für Programmcode.

Programmcode

Allgemeines zur Miranda-IM-Pluginprogrammierung

Vereinfacht gesagt beschränkt sich die Programmierung von Miranda IM auf 3 Kategorien:

Service
Ein Service ist vergleichbar mit einer Funktion: Man ruft Servicefunktionen anderer Module auf und stellt eventuell selbst welche zu Verfügung. Bei den Modulen kann es sich um eigenständige Plugins handeln oder um Servicefunktionen innerhalb der miranda32.exe bzw. miranda64.exe. Das Ganze läuft dabei über definierte Schnittstellen ab.
Hooks
Hooks sind im Prinzip vergleichbar mit den Hooks des Windows-API. Wenn irgendwo eine bestimmte Aktion erfolgt, wird eine entsprechende Funktion innerhalb des eigenen Plugins aufgerufen bzw. das eigene Plugin löst diese Aktion aus und teilt dieses anderen Modulen mit.
Hilfsfunktionen
Miranda IM stellt außerdem Funktionen zu Verfügung, die von Plugins genutzt werden können, sollten oder auch müssen, wie z. B. einfachere Funktionen wie das Öffnen eines URL in einem Browser, aber auch Funktionen zur Speicherverwaltung, Stringbearbeitung, Fensterverwaltung, Threads etc.

Servicefunktionen

int ServiceExists(const char *name)

Überprüft, ob ein Service existiert.

Name
Name des Services
Rückgabewert
Nicht 0, wenn Service existiert, sonst 0

INT_PTR CallService(const char *name,WPARAM wParam,LPARAM lParam)

Ruft einen Service auf.

Name
Name des Services
wParam, lParam
Aufrufparameter, vom jeweiligen Service abhängig.
Rückgabewert
vom jeweiligen Service abhängig, 1 zeigt i. d. R. einen Fehler an, 0 keinen Fehler, CALLSERVICE_NOTFOUND, wenn Service nicht existiert. Der Rückgabewert ist immer vom Typ INT_PTR, Sie müssen diesen also ggf. in den eigendlichen Typ casten.

HANDLE CreateServiceFunction(const char *name, MIRANDASERVICE serviceProc)

Erzeugt eine Servicefunktion und meldet diese bei Miranda IM an, damit sie auf die globale Serviceliste gesetzt wird und damit anderen Plugins zu Verfügung steht.

Name
Name des Services
serviceProc
Name der jeweiligen Funktion, diese muss definiert sein als INT_PTR ServiceProc(WPARAM wParam,LPARAM lParam).
Rückgabewert
Handle auf die Funktion; hat keine besondere Funktion, außer dass, wenn der Name schon existiert, NULL zurückgegeben wird, und für die Destroyfunktion:

int DestroyServiceFunction(HANDLE hService)

Teilt Miranda IM mit, dass dieser Service nicht länger zu Verfügung steht,entfernt es diesen dann von der globalen Serviceliste. Die Destroyfunktion wird zwar automatisch beim Beenden von Miranda IM aufgerufen, es ist aber allgemein guter Stil, generell allen geforderten Ressourcen auch selbst wieder freizugeben.

hService
Handle, welches von einer Createfunktion zurückgeliefert wird.
Rückgabewert
0 bei Erfolg, nicht 0, wenn das Handle falsch war.

INT_PTR CallServiceSync(const char *name, WPARAM wParam, LPARAM lParam)

Ähnlich wie CallService, mit einer Ausnahme: Der Service wird im Kontext des aktuellen Hauptthreads aufgerufen. Es gibt einige Services, die nicht threadsicher sind, diese Funktion dient zum Aufruf solcher Services.

int CallFunctionAsync(void (__stdcall *func)(void *), void *arg)

Ruft den angegebenen Funktionszeiger auf. Intern wird die Funktion QueueUserAPC des Windows-API aufgerufen.

HANDLE CreateServiceFunctionParam(const char *name,MIRANDASERVICEPARAM serviceProc,LPARAM lParam)

Im Prinzip identisch zu CreateServiceFunction. serviceProc muss als INT_PTR ServiceProc(WPARAM wParam,LPARAM lParam,LPARAM fnParam) definiert sein. Ist evtl. sinnvoll, wenn Sie dieselbe Funktion mit einem Index immer wieder aufrufen wollen, wie es z. B. beim XStatus-Menü des ICQ-Protokolls der Fall ist.

HANDLE CreateTransientServiceFunction(const char *name, MIRANDASERVICE serviceProc)

Intern anscheinend identisch zu CreateServiceFunction, genaue Funktion unklar. "Transient" bedeutet "dauerhaft".

Hooks

HANDLE HookEvent(const char* name, MIRANDAHOOK hookProc)

Die Funktion hookProc(int HookProc(WPARAM wParam,LPARAM lParam)) wird zu Liste der Funktionen hinzufügt, die aufgerufen werden, wenn das Ereignis name eintritt. Die hookProc-Funktion muss 0 zurückgeben, damit andere Hooks aufgerufen werden, bei einem Wert ungleich 0 wird das Ereignis nicht an die anderen Hookfunktionen weitergereicht. -1 darf nicht zurückgegeben werden, da diese bei NotifyEventHooks eine spezielle Bedeutung hat.

Rückgabewert
NULL, wenn das Event oder ein Handle darauf ungültig ist.

int UnhookEvent(HANDLE hHook)

Entfernt den Hook aus der Liste, dieser wird dann nicht mehr aufgerufen. Sie können diese Funktion auch während des normalen Programmablaufs aufrufen. Es werden zwar beim Beenden von Miranda IM alle Hooks aus der Liste entfernt, es ist trotzdem guter Stil, angeforderte Ressourcen bei Programmende selbst freizugeben.

HANDLE HookEventMessage(const char* name, HWND hwnd, UINT message)

Funktioniert ähnlich wie HookEvent. Es wird aber keine Funktion aufgerufen, sondern mittels SendMessage eine Message an das Fensterhandle hwnd gesendet. Der Rückgabewert von SendMessage() is entweder 0 oder nicht 0, bei nicht 0 wird die weitere Verarbeitung dieses Ereignisses durch andere Hooks unterbrochen.

int NotifyEventHooks(HANDLE hEvent, WPARAM wParam, LPARAM lParam)

Diese Funktion wird von Ihrem Plugin an die Liste der Funktionen gesendet, die sich für diesen Hook registriert haben. Sie können dabei wParam und lParam als Parameter übergeben. Sollte diese Funktion aus einem anderen Thread als dem des Miranda-Hauptfensters aufgerufen werden, werden ebenfalls alle Hookfunktionen der Liste aufgerufen. Der Thread, der diese Funktion aufgerufen hat, pausiert aber so lange, bis alle Funktionen, die diesem Hook zugeordnet sind, abgearbeitet wurden. Es kann also u. U. passieren, dass dieser Thread nicht normal beendet wird. Sie sollten dann Wait-Objekte verwenden.

Rückgabewert
0 = erfolgreich, -1 = ungültiges Handle.

HANDLE CreateHookableEvent(const char *name)

Erstellt für diesen Hook mit name eine Hookliste in Miranda IM, wo sich dann andere Plugins mittels HookEvent eintragen

Rückgabewert
Handle auf diesen Ereigniseintrag oder NULL, wenn dieser Name bereits existiert.

int DestroyHookableEvent(HANDLE hEvent)

Entfernt den Hook aus der Liste der möglichen Hooks von Miranda IM.

Rückgabewert
0 bei Erfolg, sonst ein Wert ungleich 0, wenn hEvent ungültig ist.

int SetHookDefaultForHookableEvent(HANDLE hEvent, MIRANDAHOOK pfnHook)

Ähnlich wie HookEvent mit dem entscheidenden Unterschied, dass hier pfnHook nur aufgerufen wird, wenn das Ereignis eintritt und kein anderer Hook für dieses Ereignis existiert. Kann z. B. verwendet werden um eine Standardverarbeitung des Ereignisses durchzuführen, die nur dann aufgerufen wird, wenn kein anderer Hook dieses Ereignis verarbeitet hat.

Unbedingt erforderliche Funktionen

Die folgenden Funktionen müssen in jedem Miranda-IM-Plugin enthalten sein und diese Funktionen müssen auch die entsprechenden Werte richtig zurückliefern, ansonsten wird das Plugin nicht erkannt.

DllMain

extern "C" BOOL APIENTRY DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved)
{
	hInst=hinstDLL;
	return TRUE;
}

Diese Funktion wird von Windows selbst aufgerufen, wenn Ihre DLL geladen und entladen wird. Zum Zeitpunkt des ersten Aufrufs dieser Funktion stehen allerdings noch keine Miranda-Funktionsaufrufe zu Verfügung. In der Regel wird hier nur das Instancehandle in einer globalen Variablen gespeichert, mehr nicht.

MirandaPluginInfoEx

extern "C" __declspec (dllexport) PLUGININFOEX* MirandaPluginInfoEx(DWORD mirandaVersion)
{
	return &pluginInfo;
}

Wird aufgerufen, wenn Ihr Plugin geladen wird.

mirandaVersion
Dieser Parameter gibt die Miranda-Version, welche der Benutzer verwendet, aus.
Rückgabewert
Sollten Sie bereits jetzt wissen, dass Ihr Plugin nicht funktionieren wird, etwa weil die verwendete mirandaVersion zu alt ist, geben Sie hier NULL zurück, und ihr Plugin wird nicht geladen. Anderenfalls geben Sie hier den Typ PLUGININFOEX* zurück, welcher wie folgt aufgebaut ist:
typedef struct
{
	int cbSize;
	char *shortName;
	DWORD version;
	char *description;
	char *author;
	char *authorEmail;
	char *copyright;
	char *homepage;
	BYTE flags;
	int replacesDefaultModule;
	MUUID uuid;
} PLUGININFOEX;

(Sie sollten Ihrem Projekt zusätzlich eine Ressource vom Typ Version hinzufügen, damit der User sofort an der DLL-Datei selbst sehen kann, worum es sich handelt)

Textfelder
Belegen Sie diese mit den entsprechenden Konstanten wie z. B. Ihrer E-Mail-Adresse. shortName ist der Kurzname Ihres Plugins, wie er unter Name in Einstellungen → Plugins angezeigt wird. Richten Sie sich bei den Textlängen an diesen Optionsdialog.
version
Sie können dazu das Makro PLUGIN_MAKE_VERSION verwenden, also z. B. PLUGIN_MAKE_VERSION(0,0,0,2).
flags
Das einzige mögliche Flag ist hier zzt. UNICODE_AWARE für ein Unicode-fähiges Plugin.
replacesDefaultModule
0 oder eine der DEFMOD_-Konstanten aus m_plugins.h, wenn Ihr Plugin eines der in Miranda bereits eingebauten Funktionen ersetzen soll. In Miranda 0.8 und aufwärts wird stattdessen der Rückgabewert der Funktion MirandaPluginInterfaces verwendet, wenn ein Plugin überschrieben werden sollte.
uuid
Eine GUID, die Ihr Plugin eindeutig identifiziert. Wenn Sie Visual Studio verwenden, können Sie den Menüpunkt Extras → GUID erstellen → static const GUID struct verwenden um diese GUID zu erstellen

MirandaPluginInterfaces

static const MUUID interfaces[] = {MIID_MYPLUGIN, MIID_LAST};
__declspec(dllexport) const MUUID* MirandaPluginInterfaces(void)
{
	return interfaces;
}
MIID_MYPLUGIN
Dies ist einfach eine Define-Konstante mit einer GUID, die Sie irgendwo in Ihrem Projekt festlegen. Es muss dieselbe GUID sein, die auch unter MirandaPluginInfoEx zurückgegeben wird. Sollten Sie wirklich planen, eine Funktion von Miranda IM zu ersetzen, können Sie hier die entsprechende(n) in newpluginapi.h definierte(n) MIID_-Konstante(n) hinzufügen. MIID_LAST ist einfach nur eine in newpluginapi.h definierte Konstante, die das Ende des Arrays anzeigt.

Load

int __declspec(dllexport) Load(PLUGINLINK *link)
{
	pluginLink=link; //notwendig, siehe Text
	...
	hModulesLoaded = HookEvent(ME_SYSTEM_MODULESLOADED, ModulesLoaded); //Beispiel, siehe Text
	...
	return 0;
}

Sie müssen den Pointer auf link mittels PLUGINLINK* pluginLink; irgendwo global, und zwar exakt mit diesem Namen, in Ihrem Projekt speichern. In newpluginapi.h sind Makros wie z. B. #define CallService(a,b,c) pluginLink->CallService(a,b,c) definiert, welche bei einer falschen Namensgebung fehlschlagen würden, Sie müssten dann die Funktionen mit link->Funktionsname aufrufen.

Diese Funktion wird aufgerufen wenn Ihr Plugin geladen wird. Beachten Sie dabei die Reihenfolge der Aufrufe in der Datei modules.c, Funktion LoadDefaultModules und dort die Zeile if (LoadNewPluginsModule()) return 1;. Alle Services, die von Modulen oberhalb zur Verfügung gestellt werden, sind zu diesem Zeitpunkt, beim Aufruf Ihrer Load-Funktion, verfügbar, andere nicht, bzw. Sie wissen gar nicht, ob zu diesem Zeitpunkt überhaupt ein anderes Plugin installiert ist, welches Sie aber für Ihr Plugin evtl. benötigen, also z. B. das Popup-Plugin. Verwenden Sie in einem solchen Fall einen EventHook auf ME_SYSTEM_MODULESLOADED; dieser wird dann ausgelöst, wenn bei allen Plugins die Load-Funktion aufgerufen wurde.

Rückgabewert: Sollten Sie bereits jetzt feststellen können, dass aus irgendwelchen Gründen Ihr Plugin nicht laufen wird, z. B. weil der User eine veraltete Mirandaversion verwendet, können Sie, evtl. nach Ausgabe einer Fehlermeldung, den Wert 1 zurückgeben, Ihr Plugin wird dann nicht geladen.

Unload

int __declspec(dllexport) Unload(void)
{
	...
	return 0;
}

Diese Funktion wird aufgerufen wenn Miranda IM beendet wird. Alle Ressourcen die Sie irgendwie angefordert haben sollten Sie spätestens hier wieder freigeben. Außer dem Datenbankmodul können Sie zu diesem Zeitpunkt nicht mehr sicher sein, dass noch irgendein Modul geladen ist.

Links