Einleitung:
|
Die einfachste Form der Verwendung von Pointern, ist das Auslesen der Speicheradresse von Elementen. Wichtig hierbei ist, dass das ganze statisch ist, das heißt man kann diese Adresse nur lesen, nicht aber verändern. Diese Art der Handhabung wird im Kapitel 3 beschrieben.
|
Nutzen:
|
Hat man die Speicheradresse, von zum Beispiel einer LONG Variable, so kann man diese direkt mit PokeL() verändern. Dies ist zwar meistens nicht besonders nützlich, aber in manchen Fällen schon. Häufiger findet diese Art und Weise Nutzen bei der Programmierung mit der Windows API (=Application Programming Interface, dass sind von Windows bereitgestellte Funktionen, die man mit PureBasic verwenden kann. Das sind all die Funktionen mit einem Unterstrich. ) .
Die Funktionen der WinAPI verlangen of nach Pointern zu STRINGS zum Beispiel. In PureBasic selber braucht man auch manchmal den Pointer zu einer Prozedur, zum Beispiel für die Funktion SetWindowCallback() oder PackerCallback(). Darauf gehe ich aber bei dem Punk "Pointer zu Prozeduren" noch genauer ein.
|
Notation:
|
Um den Pointer zu einem Element zu erhalten, notiert man einfach ein @ davor (Ausnahme sind Labels, da muss man ein ? davor notieren). Wichtig ist, dass man bei Prozeduren und Linked Lists die Klammern () nicht vergisst, denn sonst sieht PureBasic dieses Element als eine Variable an.
Wichtig: Man kann den Pointer zu einem Element auch in einer Variable speichern, wichtig dabei ist, dass man vor dieser Variable nichts besonderes notiert (auch dann nicht, wenn man diesen Pointer wieder aus der Variable auslesen will.)
Beispiel:
Variable1 = 5 ; Beliebige Variable
Variable2 = @Variable1 ; Pointer in Variable2 speichern.
Debug Variable1 ; Vergleich
Debug Variable2
Debug @Variable1
Debug @Variable2
Dieses Beispiel soll verdeutlichen, wo viele Leute ein Problem beim Verständnis von Pointern haben. Es ist nämlich immer die Frage, ob man von der Adresse, oder dem Wert spricht.
Man könnte meinen, wenn man einen Pointer mit @ ausliest, und in einer Variablen Speichert, brauche man auch wieder ein @ vor dieser Variable, um den gespeicherten Pointer wieder auszulesen.
In Wirklichkeit hat man aber hier im Beispiel den Pointer zu Variable1 als Wert in Variable2 gespeichert. (Wie man ja bei dem Debug Output sieht, stimmt der Pointer zu Variable1 mit dem Wert von Variable2 überein.) Würde man jetzt, wie hier im Beispiel vor Variable2 auch ein @ schreiben, so erhält man den Pointer zu dieser Variable, also einen komplett anderen Wert. Diese Variable ist ja auch an irgend einer anderen Stelle im Arbeitsspeicher gespeichert.
Hier darf man in seinem Programm nichts durcheinander bringen, weil man dann, wie hier gezeigt komplett andere Werte oder Pointer bekommen kann, was dann zum Beispiel bei einer Poke() oder Peek() Anweisung direkt zu einem Programmabsturz führen kann.
|
Pointer zu Variablen:
|
Wie bereits gesagt, ist die wohl häufigste Verwendungsmöglichkeit hierfür die Programmierung mit der WinAPI.
Beispiel: Übergeben eines Strings an eine WinAPI Funktion.
Windows.s = Space(256)
GetWindowsDirectory_(@Windows.s, 256)
Debug Windows.s
Da Strings eine variable Länge haben, muss man vorher dem String eine Länge geben, dir größer ist, als die des Strings, den man erhalten will.
Dir Funktion verlangt als erstes Parameter den Pointer zu einem String, und als zweiten Parameter die Länge, die der String hat. (Diese Informationen kann man in jeder API Dokumentation nachlesen. Zum Beispiel hier: http://msdn.microsoft.com/Library )
Das Prinzip hierbei ist immer das selbe, wenn nach einem Pointer gefragt wird (Sieht man meist an einem "lp" vor dem Namen des Parameters) muss man in PureBasic ein @ vor die Variable schreiben.
Übrigens werden bei der WinAPI Strings immer als Pointer übergeben.
|
Pointer zu Strukturen:
|
Hier gilt das gleiche, wie bei Strings. In Verwendung mit der WinAPI kann man Strukturen nur als Pointer übergeben. Unter Verwendung der in Kapitel 3 beschriebenen Pointer kann man diese Art auch verwenden, um Strukturen als Parameter an eine Prozedur zu übergeben.
Wichtig hierbei ist Folgendes: Man gibt nur den Namen der Variable, die die Struktur beinhaltet, mit einem @ davor an. Würde man danach noch ein Element dieser Struktur notieren, so erhält man den Pointer zu diesem Element, und eben nicht zu der Struktur.
Beispiel:
Structure TestStruktur
Element1.l
Element2.l
EndStructure
Variable.TestStruktur
Debug @Variable
Debug @Variable\Element1
Debug @Variable\Element2
Wie man sieht, stimmt der Pointer zum ersten Element der Struktur mit dem zur Variable, die die Struktur enthält überein. Dies hängt einfach damit zusammen, dass eine Struktur ja auch nichts anderes ist, als eine Aneinanderreihung von Datentypen im Arbeitsspeicher. Wichtig ist nur, dass man weiß, dass die Notation hier zu unterschiedlichen Ergebnissen führt, falls es sich eben nicht um das erste Element handelt.
Ein Beispiel zur Verwendung befindet sich im Kapitel 3 unter "Strukturen als Parameter bei Prozeduren"
|
Pointer zu Elementen in Strukturen:
|
Wie bereits gesagt, notiert man hierbei den Name der ein @, dann die Variable, die eine Struktur enthält mit dem Element, zu dem man den Pointer erhalten möchte.
Also zum Beispiel "@Variable\Element2".
Man kann dieses Element der Struktur eigentlich wie eine Einzelne Variable betrachten, deshalb gibt es eigentlich nicht mehr dazu zu sagen, als schon bei "Pointer zu Variablen" steht.
|
Pointer zu Arrays:
|
Um den Pointer zu einem Array zu erhalten, notiert man einfach den Namen des Arrays, mit einem @ davor, und einer leeren Klammer () danach. Man kann auch den Pointer zu einem Element des Arrays bekommen, indem man in der Klammer die Nummer des Elementes angibt. Auch hier gilt, wie bei den Strukturen, dass der Pointer zum ersten Element (Bei PB Arrays ist das die Nummer 0) mit dem Pointer zum Array an sich übereinstimmt.
Nützlich sind Pointer bei Arrays einerseits, da man so Variablen in einem Array speichern kann, und dann trotzdem die einzelnen Elemente einzeln an zum Beispiel eine WinAPI Funktion übergeben kann.
Auch fordern manche WinAPI Funktionen den Pointer zu einem Array voller Daten. In diesem Fall gibt man dann kein Element in der Klammer an, und erhält somit den Pointer zum ganzen Array.
Beispiel:
Dim Array.l(10)
Debug @Array()
Debug @Array(0)
Debug @Array(5)
|
Pointer zu Linked Lists:
|
Um den Pointer zu einer Linked List zu erhalten, notiert man den Namen der Linked List mit einem @ davor. Wichtig ist, dass man die Klammern hinter dem Namen der Liste nicht vergisst.
Mit dieser Notation erhält man immer den Pointer zum aktuellen Element der Liste.
Ich will jetzt hier nur darauf eingehen, wie Pointer in Verwendung mit den normalen Linked List Befehlen nützlich sind. Die vielen weiteren Möglichkeiten, die man mit Pointern und Linked Lists hat, würden den Rahmen dieses Tutorials sprengen.
Man kann die Adresse eines Listenelementes in einer Variable abspeichern, dann Änderungen an der Liste vornehmen (z.B. die Liste durchsuchen) und dann anhand des Pointers die Liste wieder zurück auf das vorherige Element setzen.
Beispiel:
NewList Liste.l()
AddElement(Liste()): Liste() = 1
AddElement(Liste()): Liste() = 2
AddElement(Liste()): Liste() = 3
FirstElement(Liste())
; (das aktuelle Element ist jetzt 1)
Debug Liste()
Element.l = @Liste() ; Pointer speichern
ResetList(Liste()) ; Liste durchsuchen
While NextElement(Liste())
Debug Liste()
Wend
; Element zurück auf 1 setzet (mit Pointer)
ChangeCurrentElement(Liste(), Element)
Debug Liste()
|
Pointer zu Prozeduren:
|
Um den Pointer zu einer Prozedur zu erhalten, notiert man einfach den Namen der Prozedur mit einem @ davor, und einer leeren Klammer () danach.
Den Pointer zu einer Prozedur braucht man zum Beispiel, um einen WindowCallback, einen PackerCallback, oder einen Thread zu erzeugen. Auch in der WinAPI Programmierung kann dies gebrauch werden, wenn für einen bestimmten Vorgang eine Callback Prozedur gebraucht wird. (Ein Callback ist eine Prozedur, deren Pointer man an Windows übergibt, und die dann von Windows aufgerufen werden kann)
Beispiel: (Erstellen eines neuen Threads)
Procedure Thread(Parameter.l)
For i = 0 To 1000000
Next i
EndProcedure
CreateThread(@Thread(), 0)
|
Pointer zu Labels:
|
Da der Programmcode sich ja auch im Arbeitsspeicher befindet, liegt es nahe, dass man auch dazu Pointer auslesen kann. Man kann, indem man den Namen eines Labels (deutsch: Sprungmarke, die Marken, die man mit Gosub/Goto aufrufen kann) mit einem ? davor notiert, die Adresse von genau dieser Stelle des Codes im Arbeitsspeicher ermitteln.
Man wird sich jetzt die Frage stellen, wozu das gut sein soll. Nun, der Pointer zu einfachem PB Code ist auch relativ nutzlos. Interessant wird es allerdings, wenn bei dem Label eine Datei mit IncludeBinary eingebunden wurde. Man kann nämlich dann mit den Speicherbefehlen auf die in dieser Datei gespeicherten Daten zugreifen.
Beispiel 1: (Einbinden von Bitmaps in die Exe Datei)
; im Code (Bild wird aus dem Speicher geladen)
CatchImage(1, ?BildLabel)
; hier kommt der Rest vom Code...
End
BildLabel:
IncludeBinary "MeinBild.Bmp"
Wichtig hierbei ist, dass ‚End' vor dem eingebundenen Bild steht. Ansonsten werden die eingebundenen Bilddaten, als Programmcode angesehen und ausgeführt. Das wird garantiert zu einem Absturz führen.
Beispiel 2: (Einbinden beliebiger Dateien in die Exe Datei)
; im Code
If OpenFile(0, "Daten.dat")
WriteData(?DateiLabel1, ?DateiLabel2 - ?DateiLabel1)
CloseFile(0)
EndIf
; Rest vom Code
End
DateiLabel1:
IncludeBinary "Daten.dat"
DateiLabel2:
Hier wird die ganze Datei, die sich ja an der Stelle ?DataiLabel1 befindet mit Hilfe des WriteData() Befehls in eine Datei geschrieben. Da die Datei ja genau zwischen den zwei Labels liegt, kann man über die Differenz der beiden Pointer die Größe der dazwischenliegenden Datei ausrechnen.
|
Zusammenfassung:
|
- Pointer zu Elementen erhält man, indem man ein @ vor die Elementnamen setzt (? Bei Labels)
- Handelt es sich um ein Array, eine Linked List oder eine Prozedur, so muss man danach eine leere Klammer notieren.
- Diese Pointer kann man nur lesen, nicht verändern.
|
|