• Keine Ergebnisse gefunden

Die Verwendung von INI-Dateien

Im Dokument Win32API-Tutorials für Delphi (Seite 191-197)

5. Systemfunktionen

5.5. Die Verwendung von INI-Dateien

5.5.1. Vorwort

Dieser kleine Beitrag befasst sich mit den wichtigsten Funktionen bezüglich INI-Dateien. Auch wenn Microsoft die Nutzung der Registry vorschlägt, spricht nichts gegen die Verwendung dieser Dateien zur Speicherung von Konfigurationsdaten.

Das Hauptaugenmerk liegt allerdings auf den "privaten" Funktionen (ich nenne sie einmal so), die das Laden und Speichern von Daten in eigenen Dateien und Ordnern erlauben. Um die Funktionen, die auf die "win.ini" zugreifen, wollen wir einen Bogen machen, denn unsere Konfigurationsdaten sollten nach Möglichkeit immer in eigenen Dateien gespeichert werden.

5.5.2. Werte aus der INI-Datei lesen

Gehen wir im Beispiel von folgender INI-Datei aus, die ein paar Benutzerangaben enthält (Ähnlichkeiten mit lebenden Personen sind rein zufällig!):

[names]

Paul=Paul [Paul]

location=Salzburg age=45

Wie Sie sehen können, befindet sich in der INI-Datei eine Sektion names, in der zweimal der Name "Paul" aufgeführt ist.

Der Hintergedanke dabei wird zu einem späteren Zeitpunkt erklärt werden. Weiterhin sehen Sie, dass der Name "Paul"

noch einmal als eigene Sektion erscheint und dass dort Wohnort und Alter eingetragen sind.

Wir wollen nun folgendes machen: Wir gehen davon aus, dass uns der gesuchte Name bekannt ist - "Paul" in diesem Fall.

Also öffnen wir direkt die gleichnamige Sektion und lesen Wohnort und Alter aus. Dazu steht uns für Strings die Funktion

"GetPrivateProfileString" zur Verfügung. Es wäre natürlich auch möglich, damit numerische Werte zu lesen, aber das wäre nicht im Sinne dieses Beitrags. Daher lesen wir das Alter mit der Funktion "GetPrivateProfileInt".

5.5.2.1. GetPrivateProfileString

Der erste Parameter der Funktion "GetPrivateProfileString" ist der Name der Sektion, aus der wir einen String auslesen wollen:

GetPrivateProfileString('Paul',

Der nächste Parameter ist der Name des Topics, der auf den gesuchten String verweist

'location',

Es folgt ein Vorgabewert, der im Fehlerfall benutzt wird. Kann der gesuchte Topic nicht gefunden werden, wird stattdessen dieser Wert zurückgeliefert

nil,

Nun folgt der Textpuffer, der den gesuchten String aufnehmen soll, und dessen Größe:

location, BUFSIZE,

Und zu guter Letzt muss der Name der INI-Datei angegeben werden

pchar(ExtractFilePath(paramstr(0)) + szIniFile));

Der Rückgabewert der Funktion ist die Anzahl der kopierten Zeichen. Das ist sehr hilfreich, da Sie so auch mit String-Variablen arbeiten können; beispielsweise:

SetLength(szBuffer,MAX_PATH);

SetLength(szBuffer,GetPrivateProfileString('Paul',

'location',nil,@szBuffer[1],MAX_PATH,'userdata.ini'));

In unserem Fall interessiert uns der Rückgabewert allerdings nicht, da der Puffer entweder den Wohnort enthält oder (im Fehlerfall) auf den Vorgabewert nil gesetzt wird. Mit anderen Worten: entweder wird der Wohnort der ausgewählten Person angezeigt, oder das Feld bleibt leer.

5.5.2.2. GetPrivateProfileInt

Das Prinzip zum Auslesen von numerischen Werten ähnelt dem der Strings. Es entfällt eigentlich nur der Textpuffer und dessen Größe. Und der Vorgabewert ist hier natürlich auch ein numerischer Typ:

age := GetPrivateProfileInt('Paul', // Sektionsname 'age', // Topic

-1, // Vorgabewert pchar(ExtractFilePath(paramstr(0)) + szIniFile)); // Datei

Allerdings spielt diesmal der Rückgabewert eine sehr große Rolle, denn er ist der ausgelesene Wert, bzw. im Fehlerfall unser Vorgabewert.

5.5.3. Werte in die INI-Datei eintragen

Natürlich enthält das Beispielprogramm auch die Möglichkeit, selbst Namen in die INI-Datei einzutragen. Dazu stehen Ihnen drei Eingabefelder zur Verfügung, die Sie entsprechend füllen können.

Anzumerken ist, dass es keine spezielle Funktion zum Speichern von numerischen Werten gibt. In unserem Fall ist das nicht weiter tragisch, da das Alter aus dem Eingabefeld ohnehin als Text (String) ausgelesen wird. Sollten Sie in Ihren Anwendungen jedoch mit echten numerischen Werten arbeiten, müssen Sie diese zunächst in einen String konvertieren.

Schauen wir uns aber zuerst einmal das Speichern eines normalen Strings an. In diesem Fall habe ich auch mit einer Variable vom Typ string gearbeitet, um den Namen der Person aus dem Eingabefeld zu lesen:

SetLength(username,MAX_PATH);

SetLength(username,GetWindowText(hName,@username[1],MAX_PATH));

Dazu muss wohl nichts mehr gesagt werden, denn das Tutorial über Eingabefelder dürfte ja inzwischen bekannt sein.

Schauen wir uns daher an, wie der String nun in die INI-Datei gespeichert wird. Wir tragen ihn dabei in die erste Sektion names ein. Dieser Sektionsname ist dann auch der erste Parameter der Funktion "WritePrivateProfileString":

Result := WritePrivateProfileString('name',

Da wir in dieser Sektion die Namen doppelt aufführen (und im nächsten Kapitel erkläre ich auch den Grund), benutzen wir nun den zuvor gelesenen Namen zweimal:

@username[1], // Topic (vor dem Gleichheitszeichen) @username[1], // Wert (nach dem Gleichheitszeichen)

Zum Schluss wird wieder die Datei angeben:

pchar(ExtractFilePath(paramstr(0)) + szIniFile));

Es mag verwirrend aussehen, dass in der Anweisung zweimal der Name der Person benutzt wird. Würden wir z.B. eine Nummerierung verwenden, könnte die selbe Anweisung so aussehen:

WritePrivateProfileString('name','3',@username[1],'userdata.ini');

5.5.3.1. Numerische Werte speichern

Nehmen wir einmal an, wir haben eine Integer-Variable, "i", deren Wert wir in der INI-Datei speichern wollen. Da uns dafür keine eigene Funktion zur Verfügung steht, müssen wir den Wert in eine String-Variable übertragen.

Etwas umständlich, aber machbar, ist die Variante, die Integer-Variable mit Hilfe von "wvsprintf" in einen Textpuffer zu übertragen und diesen Puffer dann in die INI-Datei zu schreiben. Normalerweise ist der Textpuffer ein pchar-Typ (oder ein array[x..y]of char), in dem Fall reicht aber wieder ein normaler String:

SetLength(s,MAX_PATH);

ZeroMemory(@s[1],MAX_PATH);

wvsprintf(@s[1],'%d',pchar(@i));

Leichter geht es mit der Funktion "inttostr", mit der Sie den Integer-Wert direkt in einen String konvertieren und in die INI-Datei schreiben lassen:

WritePrivateProfileString('test','integertest',pchar(inttostr(i)),'int.ini');

Allerdings wird Ihnen diese Funktion nur von der Unit "SysUtils.pas" zur Verfügung gestellt. Da wir diese Unit aber weitgehend meiden (weil sie das ausführbare Programm in vielen Fällen nur unnötig vergrößert), bleibt nur die Rückbesinnung auf die "gute alte Zeit":

function IntToStr(const i: integer): string;

begin

Str(i,Result);

end;

:o)

5.5.4. Alle Namen einer Sektion lesen

Angesprochen habe ich es bereits: es gibt einen Grund, warum die INI-Datei so aufgebaut ist:

[names]

Paul=Paul

Johannes=Johannes ...

Natürlich wäre es einfacher, wenn man eine Nummerierung hätte, die man der Reihe nach durchgehen könnte:

[names]

1=Paul 2=Johannes

Hier könnte man z.B. eine Art Endlosschleife benutzen, die erst verlassen wird, wenn kein zum Schleifenwert passender Topic gefunden wurde.

i := 1;

while(true) do begin

if(GetPrivateProfileString('names',pchar(inttostr(i)), nil,buf,BUFSIZE,'userdata.ini') = 0) or

(buf = nil) then break;

{ ... } inc(i);

end;

Dies birgt allerdings die Gefahr, dass bei Manipulationen der INI-Datei die Schleife vorzeitig oder evtl. auch nie verlassen wird. Besser ist es daher, wenn man sich nicht darum kümmern muss, wie die Topics der Sektion heißen. Von der Unit

"IniFiles.pas" kennen Sie das als Funktion "ReadSection", mit der Sie alle Topicnamen einer Sektion in eine Stringliste eintragen können.

Ohne diese Unit lässt sich die gleiche Funktionalität natürlich auch erreichen. Wir benötigen hierfür einen Puffer. Das Beispiel verwendet dazu eine Größe von 65k. Das sollte eigentlich ausreichen; im Zweifelsfall können Sie ihn auch einen höheren Wert benutzen.

Des Weiteren brauchen wir eine Art Stringliste, die alle Sektionsnamen aufnehmen soll. Ich denke, ein dynamisches String-Array ist eine gute Wahl.

Zuerst reservieren wir also den Platz für unseren Puffer und setzen das String-Array auf Null:

SetLength(kl,0);

GetMem(buf,BUFSIZE);

try

Beim Aufruf von "GetPrivateProfileString" ist es nun wichtig, dass wir keinen Topicnamen angeben:

if(GetPrivateProfileString(szNameKey,nil,nil,buf,BUFSIZE, pchar(ExtractFilePath(paramstr(0)) + szIniFile)) <> 0) then begin

Damit befinden sich die Namen aller Topics in unserem Puffer. Sie sind durch das Zeichen #0 voneinander getrennt. Der letzte Name endet mit zwei #0-Zeichen.

Mit Hilfe einer zweiten pchar-Variablen lesen wir nun die einzelnen Namen aus und tragen Sie in unser String-Array ein, das vorher immer entsprechend vergrößert wird:

p := buf;

Wie funktioniert es? Die Variable "p" wird auf den Beginn des ermittelten Puffers gesetzt und liefert natürlich nur den ersten String zurück, da das erste #0-Trennzeichen als dessen Ende interpretiert wird. Durch den Aufruf von

inc(p,lstrlen(p)+1);

wird die Länge des ersten Strings benutzt, um die pchar-Variable innerhalb des Puffers zu verschieben. Durch das "+1"

wird auch das Trennzeichen übersprungen, so dass sich die Variable nun auf dem ersten Zeichen des zweiten Strings befindet. Und so geht es dann weiter bis alle Strings ausgelesen sind.

Danach kann der Puffer dann auch wieder freigegeben werden

finally

FreeMem(buf,BUFSIZE);

end;

denn nun steht uns ja unsere Stringliste zur Verfügung:

for i := 0 to length(kl) - 1 do

MessageBox(0,pchar(kl[i]),pchar(inttostr(i+1)),0);

Die Topicnamen in dieser Liste benutzen wir nun zum Auslesen der Strings, die uns eigentlich interessieren. Benötigen wir auch die Stringliste nicht mehr, geben wir ihren Speicher ebenfalls wieder frei:

SetLength(kl,0);

5.5.4.1. Alle Namen und Werte einer Sektion lesen

Wenn wir schon dabei sind, dann wollen wir uns auch noch ansehen, wie wir sowohl Topicnamen als auch den dazu gehörenden Wert auf einmal lesen können. Dazu benutzen wir die Funktion "GetPrivateProfileSection".

Problematisch dabei ist, dass der Puffer unter Windows 95/98/ME nur max. 32k (32.767) groß sein darf, während es unter den NT-Systemen keine Beschränkung gibt - obwohl unser Beispiel dort auch "nur" einen 65k großen Puffer verwendet.

Gehen wir also davon aus, wir haben unseren Puffer entsprechend reserviert und initialisiert, dann können wir nun die Funktion aufrufen:

GetPrivateProfileSection(szNameKey, buf,

dwLen,

pchar(ExtractFilePath(paramstr(0)) + szIniFile))

Wenn der Puffer nicht ausreicht, um alle Werte aufzunehmen, dann ist das Rückgabeergebnis der Funktion identisch mit der Puffergröße minus Zwei. Sie sollten also darauf achten! Andernfalls enthält der Puffer die Namen und Werte, die durch das Zeichen #0 voneinander getrennt sind.

Das Auslesen der einzelnen Strings kann wie oben beschrieben durchgeführt werden. Sie benötigen nur eine zusätzliche pchar-Variable, um von einem null-terminierten Strings zum nächsten zu gelangen. Angezeigt wird Ihnen dann sowohl der Topicname als auch der Wert, also z.B.

Paul=Paul

5.5.4.2. Alle Sektionen lesen

Wenn Sie alle Topics und Werte einer Sektion ermitteln können, dann können Sie natürlich auch die Namen aller Sektionen in einer INI-Datei herausfinden. Das entspricht der Funktion "ReadSections", die Sie vielleicht von der Unit

"IniFiles.pas" kennen.

Wie gehabt: wir brauchen auch hier einen Puffer, der die Namen aufnehmen soll. Eine Maximalgröße wird im PSDK nicht genannt. Das Beispiel verwendet den üblichen 65k-Puffer; Sie können im Zweifelsfall einen größeren benutzen.

Mit Hilfe der Funktion "GetPrivateProfileSectionNames" lassen sich dann die Namen aller Sektionen in den Puffer schreiben:

GetPrivateProfileSectionNames(buf,BUFSIZE,

pchar(ExtractFilePath(paramstr(0)) + szIniFile));

Auch hier sind die einzelnen Namen wieder durch #0 voneinander getrennt, so dass Sie eine zusätzliche pchar-Variable benutzen können, um sie der Reihe nach in eine Stringliste o.ä. einzulesen:

p := buf;

while(p^ <> #0) do begin SetLength(kl,length(kl)+1);

kl[length(kl)-1] := p;

inc(p,lstrlen(p)+1);

end;

Ist der Puffer zu klein, entspricht der Rückgabewert der Funktion wieder der Puffergröße minus Zwei.

5.5.5. Werte und Sektionen löschen

Dieses Tutorial wäre nicht komplett, wenn wir nicht auch auf das Löschen von Werten und ganzen Sektionen eingehen würden. Etwas völlig Neues ist hier allerdings auch nicht zu erfahren, denn zum Löschen wird

"WritePrivateProfileString" benutzt.

Wollen wir den Topic in einer Sektion löschen, geben wir dessen Namen an, lassen aber den Puffer, den wir hier verwendet haben, ungenutzt:

WritePrivateProfileString('names','Paul',nil,'userdata.ini');

Wollen wir die ganze Sektion "ausradieren", dann lassen wir auch den Namen des Topics leer:

WritePrivateProfileString('names',nil,nil,'userdata.ini');

Im Dokument Win32API-Tutorials für Delphi (Seite 191-197)