DelphiX-Tutorial für Anfänger

Ursprünglich erstellt von MiGa Mail Homepage (Danke!!)
Design-Anpassung und Veränderung von Simon

 

Kapitel 9

In diesem Kapitel geht es um DirectPlay - Florian Sievert zeigt uns, wie man einen Chat programmiert.

Tutorial für DirectPlay - Grundkenntnisse

I. Theorie und Praxis

Ich werde nun einfach mal versuchen zu erklären, wie man DirectPlay verwendet und wie es funktionieren soll. Dieser Part besteht nur aus purer Theorie! Wer sich gerne mit Praxis beschäftigt, der kann sich das beiliegende Beispielprogramm ansehen. Es handelt sich dabei um ein kleines Chat-Programm und es sollte eigentlich leicht verständlich sein. Ich schreibe absichtlich alles sehr vereinfacht und detailliert, damit auch relativ unerfahrene Programmierer ihre eigenen Netzwerkprogramme schreiben können. Ich selbst weiß nur zu gut, wie es ist, wenn man mit einem Profitutorial konfrontiert wird und anschließend meist weniger weiß als vorher. Deshalb sollten alle Fortgeschrittenen Verständnis für unsere "Neuen" haben und sich nicht beleidigt fühlen, wenn erklärt wird, was ein CHAR ist ...

 

II. "Wir erstellen ein Interface"

OK! Fangen wir an! Wir gehen davon aus, dass wir vor uns ein einfaches leeres Formular haben und erstellen darauf nun einfach die Komponente DXPlay von DelphiX und nennen diese DPlay. Dies ist schon praktisch der erste Schritt um Zugang zu DirectPlay zu bekommen. Ohne Komponenten müssten wir uns erst stundenlang ein Interface basteln, doch diese Arbeit wurde uns Gott sei Dank schon abgenommen. Als nächstesmuss nur noch DirectPlay initialisiert werden; dies geschieht ganz leicht mit DPlay.Open;
Nun erscheint für den Anwender ein Dialog in dem er die Verbindungsart auswählen und seinen Namen und den Host eingeben kann. Hier muss man sich um nichts weiter kümmern; auch diese Arbeit wird uns abgenommen.

 

III. Definieren eines Nachrichtentyps

Als nächstes sollte man erst mal verschiedene Nachrichtenarten auswählen, die DirectPlay dann später verwenden soll. Hier sind seiner Phantasie praktisch keine Grenzen gesetzt, man sollte nur aufpassen,dass diese Strukturen nach Möglichkeit nicht zu groß werden, aber dies sollte eigentlich kein Problem darstellen, außer man will Festplattenimages übers Netz schicken... ;)

Nehmen wir einfach mal an, dass wir ein Chat-Programm schreiben wollen. Als erstes müssen wir uns überlegen, was wollen wir eigentlich verschicken. Die Antwort ist eigentlich ganz simpel: einen String.
Jetzt muss man allerdings wissen, dass so ein Delphi-String sehr groß ist und sich nicht dazu eignet, über ein Netzwerk geschickt zu werden. Wir haben nun natürlich die Möglichkeit, diesen String zu begrenzen, sodass der Anwender immer nur eine begrenzte Anzahl von Zeichen (z.B. 100) verschicken kann. Dies wäre allerdings keine sehr elegante Lösung dieses Problems, da der User nicht sehr glücklich sein wird, wenn er plötzlich nur die Hälfte der Nachricht bekommt, weil die andere einfach gelöscht wird. Deshalb wäre es besser, ein ARRAY OF CHAR zu verschicken. Je mehr Zeichen der User eingibt, desto mehr werden verschickt.
Definieren wir nun einfach eine solche Message:

TYPE
  TDXChatMessage=RECORD
    dwType:DWORD;
    Len:WORD;
    Msg:ARRAY[0..0] OF Char;
  END;

"Moment!" werden Sie sich jetzt sicher fragen! Was hat eigentlich dwType dort zu suchen?! Diese Variable wird benötigt, um später zu erkennen, um welchen Typ der empfangenen Nachricht es sich eigentlich handelt. Dieser Typmuss immer in einer Nachricht sein! Er ist das absolute Minimum!. In Len wird die Länge der zu übermittelnden Nachricht gespeichert; man könnte nun natürlich ermitteln, wie lang die Nachricht ist und könnte sich diese Variable schenken, aber wir wollen das Programm doch nicht zu komplex werden lassen;-)
Msg ist die eigentliche Nachricht die geschickt werden soll und besteht aus einer Kette von CHAR (einzelnen Zeichen). Sicherlich wundern Sie sich, warum ihr Bereich von 0 bis 0 geht. Wir sagen damit einfach nur,dass die Nachricht einfach nur 0 Zeichen lang sein soll; wenn mehr erforderlich ist werden wir diesen Bereich vergrößern, sodass wirklich nur die Nachricht geschickt wird und nicht ein Haufen von Leerzeichen.

Sehr geschickt ist es, vor allem bei großen Projekten die mehrere verschiedene Nachrichten verschicken, die einzelnen ID (dwType) der Typen als Konstanten festzuhalten.

CONST
  DX_ChatMessage = 1;

Nun ist das Formelle erledigt und wir können uns nun darum kümmern, Nachrichten zu verschicken oder natürlich auch zu empfangen. Wir fangen hierbei mit dem Empfangen an, weil das Senden ein klein wenig schwieriger ist. (Aber nur ein gaaanz klein wenig ...);-)

 

IV. Empfangen von Nachrichten

Wenn der Computer eine neue Nachricht zugeschickt bekommt, so wird dies in einem Ereignis von der DXPlay-Komponente gemeldet und zwar bei OnMessage. Dies ist der richtige Ansatzpunkt, um diese Nachrichten weiter zu verarbeiten.
Dazu sollte man sich mal den Kopf dieses Ereignisses ansehen:

PROCEDURE TForm1.DXPlayMessage(Sender: TObject; From: TDXPlayPlayer;
  Data: Pointer; DataSize: Integer);

Das Sender Objekt sollte eigentlich klar sein; es handelt sich nicht um einen Bestandteil von DXPlay, sondern ist bei jedem Ereignis vorhanden. Interessanter wird es danach allerdings bei From vom Typ TDXPlayPlayer. Diese Variable beinhaltet einige Information über denjenigen, der die Nachricht abgeschickt hat, zu Beispiel eine ID oder einen Namen. InData ist die eigentliche Nachricht, die DXPlay empfangen hat, und in DataSize wurde gespeichert, wie groß Data ist.

Bevor man diese Nachricht nun bearbeiten kann, muss man erst mal ermitteln, um welchen Nachrichtentyp es sich hierbei eigentlich handelt, um sie dann entsprechend zu behandeln. Es würde nämlich fatale Folgen haben, wenn man nun statt einer Textnachricht plötzlich ein Bitmap in die Hände bekommt und dieses dann wie eine Nachricht behandelt ...

Deshalb wird erst mal abgefragt, um welchen Nachrichtentyp es sich eigentlich handelt:

IF DXPlayMessageType(Data) = DX_ChatMessage THEN

Mit anderen Worten: "Wenn der Typ der Nachricht, die gerade in Data geschickt worden ist, vom Typ DX_ChatMessage ist, dann..."
Deshalb ist es auch so wichtig, dass bei einer Nachricht mindestens immer dwType enthalten seinmuss, ansonsten kann diese Abfrage nicht funktionieren. Darunter könnte man nun zum Beispiel auch auf andere Typen von Nachrichten reagieren und diese entsprechend behandeln. Wir bleiben allerdingserst mal bei unserer Textnachricht. Da wir nun wissen, um welchen Typ es sich bei dieser Nachricht handelt, können wir nun auch aus ihr lesen, müssen allerdings bedenken,dass es sich hierbei um einen Pointer, also einen Zeiger im Speicher, handelt, die immer mit einem ^angesprochen werden müssen. Am leichtesten ist es deshalb sowieso, mit WITH-DO zu arbeiten:

WITH TDXChatMessage(Data^) DO

So spart man sich das "Dächlein" bei jedem Kommando und hat auch gleich die Möglichkeit, direkt auf die Variablen zuzugreifen. Eigentlich ist nun schon alles fertig und man benötigtDXPlay nicht mehr, sondern arbeitet nur noch mit dem"normalen" Delphi weiter, da das Umformen jedoch nicht unbedingt leicht ist, will ich es hier auch noch mal kurz ansprechen. Nun sollte manerst mal einen einfachen String anlegen, in dem die Textnachricht wieder"lesbar" gemacht werden soll, um die Nachricht dann wieder auszugeben. Nehmen wir an,dass unsere String-Variable kreativer Weise S heißt.
Als erstes müssen wir angeben wie lang S ist. Wir benutzen dazu einfach die Angaben die in Len stehen.

SetLength(S,Len);

Schon hat S exakt die Länge der empfangenen Textnachricht.

Nun kopieren wir den Inhalt von Msg in S, da es sich bei Msg um eine Kette von Chars (auch pChar genannt) handelt,muss wieder eine Typ-Umwandlung erfolgen:

StrLCopy(pChar(S) , Msg , Length(S));

Als erstes wird das Ziel angegeben, das ein pChar sein muss (einfach pChar schreiben und es wird passend in den String kopiert), dann wird die Quelle angegeben, in unserem Fall Msg, und dann noch die Länge von S, die man ja vorher schon eingelesen hat, man könnte natürlich auch hier Len nehmen ...
Fertig ist das Ganze! Die Nachricht ist nun in dem String S und lässt sich beliebig weiter verwenden, z.B. in einem TMemo anzeigen.

Das ist der ganze Zauber um DXPlay! Ist doch eigentlich nicht so kompliziert, oder? Wer immer noch verunsichert ist, der wird dann spätestens beimBeispiel-Source-Code alles verstehen. Aber man will ja schließlich nicht nur Nachrichten empfangen können, sondern auch die Möglichkeit haben, dem Menschen am anderen Rechner zu antworten. Das Verschicken funktioniert praktisch genauso; nur andersherum.;-)

 

V. "Und ab die Post"

Wann eine Nachricht abgeschickt werden soll, bestimmt der Programmierer selbst. Es kommt dabei ganz auf die Art des Programms an. Bei einem Spiel ist es vielleicht notwendig, jede Sekunde ein Paket an seinen Mitspieler zu schicken, während bei einem Chat wirklich nur dann etwas geschickt werden soll, wenn der User etwas sagen will. Deshalb empfiehlt es sich, für unser Programm einen TButton auf das Formular zu setzen. Immer wenn der User eine Nachricht senden will, drückt er einmal auf diesen Knopf und ab geht die Post!
Doch wie soll der Anwender etwas sagen können?! Ganze einfach: Wir setzen eineTEdit-Komponente daneben, deren Inhalt jedes Mal verschickt werden soll, wenn der User auf den Knopf drückt. Wir nennen diese Komponente einfach Message_Edit. Bis jetzt sollte das eigentlich jeder kennen ...;-)

Zum eigentlichen Verschicken brauchen wir zwei Variablen: Zum einen einen Pointer vom Typ der Nachricht, die wir verschicken wollen (in diesem Fall TDXChatMessage), und eine andere Variable in der die Größe der Nachricht gespeichert wird. Am besten vereinbart man diese Variablen als lokale, da man sie nach dem Senden nicht mehr braucht:

PROCEDURE TForm1.Send_ButtonClick(Sender: TObject);
VAR
  ChatMsg: ^TDXChatMessage;
  MsgSize: Integer;

Danach muss man erst mal ermitteln wie groß unsere zu sendende Nachricht ist; dies geschieht wie folgt:

  MsgSize:=SizeOF(TDXChatMessage)+Length(Message_Edit.Text);

"Die Größe ist gleich der Größe des Nachrichttypes + der Länge der Textnachricht!"
Erinnern Sie sich noch, dass wir TDXChatMessage.Msg mit einer Länge von 0 Zeichen definiert haben? Dies darf hier nun auf gar keinen Fall vergessen werden, sondernmuss noch zusätzlich zu der normalen Größe addiert werden. Dann braucht man nur noch wissen,dass 1 Zeichen exakt 1 Byte groß ist und es deshalb addiert die Größe des benötigten Speichers ergibt. Wenn Sie zum Beispiel einen anderen Nachrichtentyp verwenden, der nur eine Zahl verschickt, dann brauchen sie nichts mehr dazu zu addieren, sondern es reicht, mitSizeOF(NachrichtenTyp) zu arbeiten. (Beispiel ist im Source-Code!)

Unser ChatMsg zeigt derzeit noch auf gar nichts und hat noch keine richtige Größe; dies sollte schnell behoben werden indem wir Speicher in der passenden Größe reservieren:

  GetMem(ChatMsg,MsgSize);

Danach steht uns ChatMsg voll zur Verfügung und wir können damit arbeiten; wieder nicht zu vergessen ist,dass es sich hierbei um einen Pointer handelt und diese mit dem^-Zeichen angesprochen werden müssen.
Besser ist es deshalb wieder mit WITH-DO zu arbeiten. Danach vergeben wir nur noch die Wert anChatMsg:

  dwType : = DX_ChatMessage;
  Len : = Length(Message_Edit.Text);
  StrLCopy(Msg,pChar(Message_Edit.Text),Length(Message_Edit.Text));

In dwType geben wir an, um was für einen Nachrichtentyp es sich handeln soll; in unserem Fall um eine Textnachricht. Dies ist wichtig, um beim Empfangen den Typ der Nachricht wieder richtig zu bestimmen. Dann ermitteln wir wie lang die Textnachricht ist, mit der FunktionLength, die eigentlich auch jeder kennen sollte.
Anschließend muss man nur noch die Textnachricht in Msg reinquetschen. Dies geschieht mitStrLCopy. Als erstes das Ziel angeben, dann die Quelle (die vorher mit pChar( ) umgewandelt werdenmuss!) und dann noch die Länge der Nachricht. Die Informationen sind nun alle in ChatMsg gespeichert und müssen nur nach abgeschickt werden. Dies ist allerdings wieder sehr leicht:

  DXPlay.SendMessage(DPID_AllPlayers,ChatMsg,MsgSize);

So schickt man die Nachricht los. DPID_AllPlayers schickt an alle Teilnehmer im Netz diese Nachricht, man könnte hier zum Beispiel auch die ID eines einzelnen Computers angeben oder die einer Gruppe. Dann folgt die Nachricht, die wir vorher erstellt haben und geben dann noch ihre Größe mit auf den Weg.

Schlussendlich sollten Sie nicht vergessen, den Speicher, den Sie mit GetMem reserviert haben, auch wieder freizugeben:

  FreeMem(ChatMsg);

Ein guter Stil wäre es dann natürlich, beim Beenden des Programms DirectPlay wieder freizugeben mitDXPlay.Close, damit es nicht zu Konflikten beim nächsten Start des Programms kommt.

 

VI. Zu Guter letzt!

So, das war es dann schon. Wenn Sie das alles verstanden haben, dann können Sie nun mit DXPlay Nachrichtendurchs Netz schicken! Das komplizierteste an diesen Beispielen ist eigentlich die Typ-Umwandlung; der Rest sollte eigentlich kein Problem darstellen! Wenn Sie sich dann noch denSource-Code des Beispielprogramms ansehen, sollte es wirklich keine Probleme mehr geben. Hier werden neben unseren Textnachrichten auch noch andere verschickt. Man kann daraus sehr leicht erkennen, wie das Prinzip der Nachrichten funktioniert. Außerdem reagiert das Programm auch noch darauf,dass ein neuer Spieler das Netz betreten oder verlassen hat. Dies sollte aber wirklich leicht verständlich sein undlässt sich demSource-Code entnehmen.
Falls es doch Probleme geben sollte, dann schreiben Sie eine eMail an Phobeus@T-Online.de und ich werde gerne versuchen, Ihnen zu helfen. Vielleicht gibt es dann demnächst ein weiteres Tutorial, das sich speziell mit dem Synchronisieren von Spielen beschäftigt. Auch überKritiken, Anregungen oder Wünsche würde ich mich sehr freuen!

 

<<Vorheriges Kapitel>>   <<Nächstes Kapitel>>