Home Computer Pascal Kurs Site Map counter

Jetzt lerne ich Pascal... Teil 8

Standard Prozeduren

Eigentlich wollte ich nach dem 7.ten Kapitel Schluss machen, doch weil ich auf den Kurs so wohlmeinende E-Mails und Einträge ins Gästebuch bekommen habe, nun noch ein paar Kapitel mehr. Es geht um einige wichtige Prozeduren und Funktionen die bei Pascal mitgeliefert werden. Leider muss ich nun auch den Weg des gemeinsamen Standards verlassen und beziehe mich nur noch auf Turbo Pascal.

Grafik

Fangen wir an mit einem Thema das im Usenet die meisten Anfängerfragen verursacht: Fehler bei der Einbindung von Grafik Prozeduren.

Hier gibt es einen Unterschied zu anderen Units, die Sie selber schreiben oder vom System einbinden. Das Problem ist: Es gibt nicht einen Grafikadapter sondern viele. Als Turbo Pascal 7.0 erschien war seit 13 Jahren der letzte gemeinsame Standard VGA, das sind 640 × 480 Punkte in 16 Farben - schon damals eine viel zu geringe Auflösung. Das Problem ist, dass alles was höhere Auflösungen liefert kein Standard mehr ist.

So hat man bei Borland eine Lösung erfunden, die dynamisch abläuft. Sie geht nach folgendem Prinzip:

D.H. wenn ihr Programm läuft, so benötigt es die Dateien mit der Endung.bgi (Grafiktreiber) und.chr (Zeichensätze). Es empfiehlt sich diese mit der Anwendung weiterzugeben.

Programmtechnisch läuft dies so ab:

Hier ein minimales Zeichenprogramm, das eine rote Linie quer über den Bildschirm malt:
USES Graph;

VAR Driver,Modus : Integer;

BEGIN
Driver:=Detect;
Modus:=Detect;
InitGraph(Driver,Modus,'C:\TP\BGI');
IF GraphResultGrOk THEN
BEGIN
SetColor(Red);
Line(0,0,GetMaxX,GetMaxY);
ReadLn;
CloseGraph;
END
ELSE
WriteLn('Kein Treiber gefunden!');
END.
Ich habe hier für Demozwecke etwas gemacht, was Sie, wenn sie die Dateien weitergeben wollen nicht machen sollten: Das Verzeichnis wo die.bgi Dateien sind "hart" kodiert. GetmaxX und GetmaxY sind zwei Konstanten welche die höchsten verfügbaren Bildschirmkoordinaten enthalten, und daher sehr nützlich wenn das Programm auf Rechnern mit verschiedenen Auflösungen arbeiten soll.

Der einzig schwierige Punkt ist Initgraph. Diese Prozedur hat 3 Parameter

C:\work> C:\tp\run\gdemo

Dann ist das aktuelle Verzeichnis nicht C:\tp\run sondern C:\work. Das kann man aber leicht lösen, denn der Programmname mit Pfad steht nach dem Start in der Kommandozeile und kann mit paramstr(0) ausgelesen werden und daraus der Pfad extrahiert werden.

Solange nun dieser Grafiktreiber aktiv ist, kann man beliebiges machen, vor allem mehrmals mit RestoreCRTMode in den Textmodus und mit SetGraphmode wieder in den Grafikmodus wechseln. Geschlossen wird der Grafiktreiber erst durch Closegraph. Diesen Befehl brauchen sie wenn sie mehrere Treiber ausprobieren wollen.

Das Problem mit den Grafikkarten

Nun was ist problematisch? Es ist die Auflösung! 640 × 480 sind schon lange nicht mehr Stand der Technik. Lange Zeit gab es von den Herstellern der Grafikkarten daher Treiber für höhere Modi. Doch so ab 1992/93 hörte das mit dem Windows Boom auf, und seit 1995 mit dem Windows 95 Erscheinen auch DOS Spiele ausstarben, ist es so, dass Grafikkarten unter DOS weniger können als ältere Modelle. DOS Grafikmodi werden mangelhaft unterstützt.

Die Lösung ist daher nicht allgemeingültig. Hier mein Vorgehen:

Eine Reihe von Grafiktreibern mit hoher Auflösung einbinden, und programmtechnisch diese durchchecken und den mit der höchsten Auflösung nehmen. Mit immer besser werden Grafikkarten wird die Komptabilität immer kleiner und so verwende ich nur noch 3 Grafiktreiber:

Diese drei decken so ziemlich alle Grundtypen ab. Was bei Ihnen läuft ist absolute Glückssache, hier z.B. die Ergebnisse meiner 3 letzten Grafikkarten: Da hilft nur eins: die Treiber programmtechnisch durchlaufen lassen und sich den besten Modus merken. Doch wie bindet man die Fremdtreiber ein? Nun dies geht, indem man eine Prozedur voranschaltet: installuserdriver. Diese Prozedur hat zwei Parameter: Im ersten den Namen des Treibers (ohne.bgi), im zweiten die Adresse eine Autodetect Funktion die den besten unterstützten Grafikmodus zurückgibt. Existiert eine solche nicht (Normalfall) so gibt man hier NIL an. (Nil ist eine Zeigerkonstante die eine nicht gültige Adresse anzeigt, in unserem Fall also gar keine Adresse).

Alles was danach kommt ist leider sehr schlecht implementiert. Eine Abfrage mittels Graphresult ob der Treiber geladen und initialisiert. werden konnte geht ja noch. Leider haben alle BGI Treiber die ich kenne, keine Unterstützung für die GetModeRange Prozedur welche die gültigen Grafikmodi ermittelt, so das ich im nächsten Demo die ersten 10 Abfrage. Manche Treiber stürzen aber hier ab, wenn man einen Modus wählt den es nicht gibt, z.B. Treiber die einen Drucker oder Plotter ansteuern und nicht auf den Bildschirm schreiben.

Hat man einen Treiber mittels Installuserdriver eingebunden so nimmt man den Rückgabewert dieser Funktion als Driver für Initgraph und kann dann wie gewohnt arbeiten. Die folgende Unit nutzt dies aus um nach der höchsten installierten Auflösung zu suchen:

UNIT Suche_Driver;

INTERFACE

FUNCTION
Get_Best_Driver(VAR Driver,Mode : Integer; VAR Name : STRING): Boolean;
{true wenn andere Treiber als VGA anwesend}
IMPLEMENTATION

USES
Graph,Dos;

FUNCTION Get_Best_Driver(VAR Driver,Mode : Integer; VAR Name : STRING): Boolean;

VAR Info : SearchRec;
Max : Integer;
Path : STRING;

PROCEDURE Suche_Driver;

VAR Mydriver,Mymode,Lomode,I : Integer;
Dir,Ext,Tempname : STRING;

CONST Bekannt : ARRAY [1..6] OF STRING[8] = ('ATT','EGAVGA','CGA','PC3270','IBM8514','HERC');

BEGIN
FSplit(Info.Name,Dir,Tempname,Ext);
FOR I:=1 TO 6 DO IF TempnameBekannt[I] THEN Exit;
Mydriver:=InstallUserDriver(Tempname,NIL);
Mymode:=0;
InitGraph(Mydriver,Mymode,Path);
IF GraphResult<>grOk THEN
BEGIN
CloseGraph;
Exit;
END;
GetModeRange(Mydriver,Lomode,Mymode);
IF Mymode<=0 THEN Mymode:=10;
FOR I:=0 TO Mymode DO
BEGIN
SetGraphMode(I);
IF GraphResult<>grOk THEN
BEGIN
CloseGraph;
Exit;
END;
IF GetMaxX> Max THEN
BEGIN
Driver:=Mydriver;
Mode:=Mymode;
Name:=Tempname;
Max:=GetMaxX;
END;
END;
CloseGraph;
END;

BEGIN
Max:=0;
Path:=Name;
Name:='EGAVGA';
Driver:=VGA;
Mode:=VGAHi; {Initzialsierung}
Get_Best_Driver:=False;
IF Path'' THEN Path:='.';
IF Path[Length(Path)]<>'\' THEN Path:=Path+'\';
FindFirst(Path+'*.bgi',AnyFile,Info);
WHILE DosError0 DO
BEGIN
Get_Best_Driver:=True;
Suche_Driver;
FindNext(Info);
END;
END;

BEGIN
END
.

Man ruft diese dann so auf :

USES Suche_Driver,Graph;

VAR Name : STRING;
Driver,Mode : Integer;

BEGIN
Name:='C:\tp\bgi';
Get_Best_Driver(Driver,Mode, Name);
InitGraph(Driver,Mode, Name);
Mode:=GetMaxX;
CloseGraph;
WriteLn(Driver);
WriteLn(Mode);
WriteLn(Name);
ReadLn;
END.
Man ruft diese dann so auf: Für praktische Zwecke ist dies natürlich nicht in ihrem Anwendungsprogramm gedacht, sondern in einem Installer, denn dieses Codefragement bindet ja alle BGI Treiber ein, und nicht nur den einen den man braucht.

Ansonsten finden sie in der Grafikunit vieles, was keine großen Erklärung Bedarf. Es empfiehlt sich hier besonders mit den vordefinierten Konstanten zu arbeiten, sie machen das Programm lesbarer. Ihr Programm sollte immer mit Getmaxx und Getmaxy arbeiten. Eine Besonderheit gegenüber dem mathematischen Koordinatensystem ist übrigens das die Koordinaten (0,0) nicht links unten sondern links oben sind, d.h. höhere Y Koordinaten bedeuten das die sie tiefer und nicht höher liegen.

Für dieses und andere Umrechnungen hier zwei Routinen von mir:

const Offset 32;
Halfoffset Offset div 2;

var faky,faky : double;

procedure Set_Koord(const Xu,Yu,Xo,Yo : double);

begin
Fakx:=(Xo-Xu)/GetmaxX-Offset);
Faky:=(Yo-Yu)/GetMaxY-Offset);
end;

procedure Point(const X,Y : double; var ix,iy : integer);

begin
Ix:=Halfoffset+Trunc((X-Xu)/Fakx);
Iy:=ClientHeight-Halfoffset-(Trunc((Y-Yu)/Faky));
end;
Diese Paar von Prozeduren richtet eine Möglichkeit ein eine Grafik einzupassen. Wenn Sie einen Funktionsplot zwischen x=-5 und +6 und Y=-1 und +3 machen wollen so richten sie mit Set_koord(-5,-1,6,3) die Umrechnungsfaktoren fest und können dann jeden Punkt in die wahren Bildschirmkoordinaten ix und iy umrechnen. So was braucht man fast immer wenn man Messwerte visualisiert oder Kurven zeichnet (In diesem Fall stammt das Schnipsel aus meinem Programm Lineare Regression). Der Offset bewirkt, das die Grafik an der Rändern etwas Platz für Beschriftungen lässt.

DOS und Konsorten

Über DOS und seine Routinen will ich mich hier nicht so lange ausbreiten, vor allem, da es in nächster Zeit aussterben wird. Trotzdem gibt es noch ein paar nützliche Routinen. Eine ist schon in der oberen Unit zu finden: Es ist das Paar FindFirst / FindNext, womit man Verzeichnisse nach Dateien abklappern kann. Sehr gut um Listen zu erstellen oder auch nur festzustellen ob es eine Datei gibt bevor man sie öffnen möchte. Als Beispiel mag obige Unit genügen.

Oftmals will man über die Kommandozeile Parameter übergeben. Hier gibt es zwei Funktionen:

Paramcount liefert die Anzahl der Kommondozeilen parameter (sie sind durch Leerzeichen getrennt)

Paramstr(n). Liefert einen String zurück der dem n.ten parameter entspricht. Über Paramstr(0) bekommt man heraus wie denn heute gerade die EXE Datei des Programmes heißt und in welchem Pfad sie liegt (sehr nützlich für Aufrufe von Chdir....)

So liest man z.B. die Parameter aus:

for i:=1 to paramcount do writeln(paramstr(i));

Will man DOS einen Statuscode zurückgeben so sollte man das Programm mit Halt(nr) verlassen. Nr. ist eine Integerzahl die DOS in der Variable ERRORLEVEL ablegt. So kann man z.B. sagen ob das Programm fehlerfrei lief.

Ach ja, gehört eigentlich nicht hierher: Man kann auch eigene Exit Prozeduren definieren. Was passiert wenn ihr Programm einen Runtime Fehler hat? Na sie wollen es doch sicher artig beenden und zumindest die offenen Dateien schließen, damit es keine verlorenen Inhalte gibt. Daher kann man eine Prozedur definieren die zu Programmende aber auch in Fehlerfällen aufgerufen wird. Dies geschieht in 3 Schritten:

Dazu dient eine Variable vom Typ Pointer.
Var Exitsave: Pointer;
PROCEDURE Error; FAR;
BEGIN
  ExitProc:=Exitsave;
  CloseGraph;
  IF NOT Update THEN Speichern;
  IF offen THEN Dateiclose;
END;
{Zu Beginn des Hauprogrammes Zeiger retten und dann neu setzen}
BEGIN
  Exitsave:=ExitProc;
  ExitProc:=@Error;
  {Alternative Schreibweise: ExitProc:=Addr(Error);}
END.

ExitProc ist ein Zeiger auf die Standard Prozedur am ende. Er muss gesichert und auf jeden Fall am Ende jeder eigenen Fehlerroutine wieder auf die richtige Routine zeigen. In diesem Beispiel wird der alte Exitcode Zeiger zuerst in der Variablen Exitsave gesichert (erste Zeile des Hauptprogrammes!) Und in der benutzerdefinierten Routine als erste Anweisung wieder restauriert. Danach kann ExitProc die Adresse der eignen Fehlerroutine zugewiesen werden. Dieses ist eine Prozedur ohne Parameter die - das ist wichtig! - als far deklariert werden muss, denn sie kann von überall aus angesprungen werden. Dort sollte man dann Grafiktreiber schließen (sonst endet das Programm im Grafikmodus) oder offene Dateien schließen. Alles was Ihnen im Fehlerfall wichtig ist.

Sie können auch mehrere Routinen haben, die dann wie in einer Liste nacheinander abgearbeitet werden, z.B. für jede Unit eine, die lokale Datein schließt Sie brauchen aber für jede dieser Routinen so einen Zeiger, der sich die letzte ExitProc Routine merkt. Bei Units werden sie diese im Initailisierungsteil (begin...end am Schluss) anlegen.

Interrupts

Interrupts will ich nur streifen, zum einen weil sie nicht ganz ungefährlich ist (habe damit schon ganze Verzeichnisse weggeputzt...) zum anderen dürften sie als erstes von DOS verschwinden. Trotzdem kann man mit Interrupts Dinge machen, die sonst nicht gehen. Was brauchen Sie also um mit Interrupts zu arbeiten? Also etwas Ahnung von DOS und Assembler, eine Liste der Interrupts aus dem Internet und einen alten Rechner, auf dem am besten nur DOS läuft. Für Interrupt muss die Unit DOS eingebunden werden.

Alle Prozeduren mit Interrupts haben in etwa solche Form:

PROCEDURE Hardcopy;

VAR
I,J : Byte;
S : STRING [82];
R : Registers;

BEGIN
FOR
J:=0 TO 24 DO
BEGIN
S:='';
FOR I:=0 TO 79 DO
BEGIN
R.AH:=2; R.BH:=0;
R.DH:=J; R.DL:=I;
Intr($10,R); {Cursor Positionieren}
R.AH:=8; R.BH:=0;
Intr($10,R);
S:=S+Chr(R.AL); {Zeichen lesen und einfügen}
END;
WHILE S[Length(S)]=' ' DO Delete(S,Length(S),1);
{überflüssige Leerzeichen Löschen}
Ausgabe(S+);
END;
END;
Diese Routine macht eine Bildschirmhardcopy - nicht nur auf den Drucker sondern auch in eine Datei oder wohin immer sie wollen. Dabei wichtig ist der Interrupt $10 (Dezimal 16) und die Funktion 8, die das Zeichen beim Cursor ausliest. Den zweiten Interruptaufruf zum Cursor positionieren hätte man auch mit gotoxy machen können.

Sie finden hier alle Elemente für Interrupts: Sie brauchen einen Record namens Registers in dem die einzelnen Felder die Register des Prozessors repräsentieren. Diese füttern Sie mit den Daten und rufen dann über die Funktion intr den Interrupt auf. Rückgabewerte stehen dann auch in dem Register Record. Für komplexere Typen ist es ratsam sich mal durch die Kapitel über Parameterübergaben an Assembler und Interrupts im Handbuch durchzuarbeiten.

Drucken

Wenn Sie die Unit Printers einbinden können Sie einfach über Write(lst,....) oder Writeln(lst,...) drucken als wäre dies eine Datei. Sogar Reset, Rewrite gehen, was sehr geschickt ist, für Programme deren Ausgaben in Dateien, auf dem Bildschirm oder auf dem Drucker erscheinen sollen. Universeller, weil auch auf Druckerport 2 und 3 oder auf Comports (allerdings noch nie getestet...) anwendbar, ist es DOS Geräte Dateien zuzuordnen. Das ist bomben einfach: Nur Geräte wie andere Textdateien deklarieren und benutzen, nur Geräte als Dateinamen verwenden:

LPT1: für den Drucker 1 (analog LPT2:, LPT3:)

COM1:... COM4: für die serielle Schnittstelle. Die Doppelpunkte sind wichtig und gehören zum Dateinamen.

Um auch den Bildschirm einer Datei zuordnen zu können gibt es die Procedure AssignCRT(dateivariable) die wie Assign arbeitet nur das der Dateiname entfallen kann.

Zuletzt noch etwas Number Crunching....

Ich möchte zum Schluss auch noch etwas vorstellen, was Sie wahrscheinlich schon dutzendfach im Internet gefunden haben. Ein Programm womit man Fractale zeichnen oder Mandelbrotmengen erstellen kann. Mein Programm ist hier sehr schlicht, das hat auch seinen Grund: dieses Programm ist von einigen Anpassungen an die Grafik abgesehen 16 Jahre alt und lief zuerst auf einem CPC 464 (einem 8 Bit Rechner mit 4 MHz und 64 KB Speicher). Dafür können sie mit diesem Programm beurteilen warum man damals Stunden gebraucht hat um diese Grafiken zu zeichnen. (Das Übersichtsbild mit den Eingaben links -0.5, rechts 2.5, oben -1.2 und unten 1.2 brauchte damals 45 min zum Berechnen, eine komfortablere Version in BASIC brauchte für Teilausschnitte bis zu 8 Stunden). Sie verstehen nun sicher warum man das Aufbereiten der Grafiken damals "Number Crunching" nannte.

Die Mandelbrotmengen machen nun folgendes: Sie lösen die Gleichung z=z²+c. Dabei sind z und c komplexe Zahlen wobei man hier zum Zeichen die komplexe Zahl in einen Realteil (X Achse) und einen Imaginärteil (Y Achse) zerlegt. Man beginnt also mit den Koordinaten eines Punktes denn man dann in eine imaginärzahl konvertiert, führt die Rechnung aus und schaut nach ob z größer als der rechte Teil ist - ist dies der Fall so ist dies auch bei allen weiteren Näherungen der Fall, und man kann die Schleife verlassen. Andernfalls erhält man einen neuen Näherungswert für z und wiederholt die Schleife. Was man nun plotet sind die Anzahl der Iterationen bis zum Abbruch. Schwarz bleibt der Hintergrund wenn man bis zu einer bestimmten Tiefe nicht vorher aus der Schleife heraussprang. Bei meinem Ursprungsprogramm war die Abbruchbedingung auf 30 gesetzt, was bei der damaligen Auflösung von 320 × 200 Punkten gute 2 Millionen Schleifendurchläufe als Maximum bedeutet hat - und das bei einem Rechner der wenn er gut gelaunt war 200-500 Fliesskommarechnungen pro Sekunde ausführte....

Mein heutiger PC ist durch Fliesskommaprozessor, 1200 MHz Takt und 32 Bit etwa 11000 mal schneller, obgleich hier das Zeichnen der Punkte die meiste Zeit beansprucht, anders als früher wo man zuschauen konnte wie das Bild "gemalt" wurde. Das Programm ist vielleicht ein guter Einsteig wenn sie selber experimentieren wollen. Es zeigt aber auch das auch ich damals nicht gerade perfekte Programme schrieb. (Achten Sie mal auf globale Variablen....)

fractal.pas

und als Delphi 4 Projekt:

fractal.zip


Zum Thema Computer ist auch von mir ein Buch erschienen. "Computergeschichte(n)" beinhaltet, das was der Titel aussagt: einzelne Episoden aus der Frühzeit des PC. Es sind Episoden aus den Lebensäufen von Ed Roberts, Bill Gates, Steve Jobs, Stephen Wozniak, Gary Kildall, Adam Osborne, Jack Tramiel und Chuck Peddle und wie sie den PC schufen.

Das Buch wird abgerundet durch eine kurze Erklärung der Computertechnik vor dem PC, sowie einer Zusammenfassung was danach geschah, als die Claims abgesteckt waren. Ich habe versucht ein Buch zu schreiben, dass sie dahingehend von anderen Büchern abhebt, dass es nicht nur Geschichte erzählt sondern auch erklärt warum bestimmte Produkte erfolgreich waren, also auf die Technik eingeht.

Die 2014 erschienene zweite Auflage wurde aktualisiert und leicht erweitert. Die umfangreichste Änderung ist ein 60 Seiten starkes Kapitel über Seymour Cray und die von ihm entworfenen Supercomputer. Bedingt durch Preissenkungen bei Neuauflagen ist es mit 19,90 Euro trotz gestiegenem Umfang um 5 Euro billiger als die erste Auflage. Es ist auch als e-Book für 10,99 Euro erschienen.

Mehr über das Buch auf dieser eigenen Seite.

Sitemap Kontakt Neues Hier werben Bücher vom Autor Buchempfehlungen Top 99