Delphi Tipps

Inhaltsverzeichnis

INI-Datei oder Registryeintrag ?

Immer wieder wird der Coder vor die Frage gestellt: Mein Programm muß oder soll sich irgend etwas merken, aber wohin damit? Einige werden jetzt den Kopf schütteln und antworten: Natürlich in die Registry. Nicht etwa, das ich so etwas nicht kann. Ich finde jedoch, diese Unart kann gelassen werden. Es sei denn, Windows selbst  macht die Einträge, dann soll sich auch RegClean von Windows darum kümmern und das macht es in der Regel auch.

Was geschieht jedoch mit den Einträgen, die unser Programm dort macht. Mister B.G. sagt zwar, dafür ist die Registry auch da. Wer jedoch dort einmal ernsthaft hinein gesehen hat, wird mit Erschrecken feststellen, das kaum noch eine Selektion bezüglich des Nutzens der Einträge möglich ist. Da gibt es hochgelobte Registryreiniger, die diese Datenbank bis zur Funktionsunfähigkeit  von Windows säubern. Ein Glück, das es da noch in den jüngeren Betriebssystemen die Systemwiederherstellung gibt. Bei anderen Reinigungsprogrammen hat man den Eindruck, diese tun überhaupt nichts oder sie fragen tatsächlich den Enduser, ob denn auch wirklich jeder einzelne Eintrag benötigt wird.
Woher soll der das wissen ?

Wir, die Programmierer sind noch in der Lage, unnötige Schlüssel von verblichenen Anwendungen zu löschen. Was jedoch macht der geplagte Endanwender, dessen  System offensichtlich immer langsamer wird?

Dabei hat Delphi ein sehr gutes Handling mit INI-Dateien. In unserem Code müssen wir uns nicht wie bei anderen Programmierspachen mit der API herumschlagen. Und wenn doch, dann bietet Delphi meist auch die richtige Schnittstelle an.

Nun ist noch die Frage zu klären, wo soll sich denn die INI-Datei befinden. Früher war es üblich, diese im Windowsverzeichnis zu deponieren. Dieses Verzeichnis sah dann nicht viel anders aus, als die  heutige Registry. Damit ist die Frage nach dem Standort der INI eigendlich schon beantwortet. Nur das Verzeichnis der Anwendung kommt dafür in Frage! Wird die Anwendung irgendwann mal gelöscht (von Hand oder von einem Setup), wird ja auch das Verzeichnis gelöscht. Dann ist die INI auch verschwunden und RegClean muß sich nur noch um das kümmern, was Windows selbst in die Registry geschrieben hat.

Wie stellt nun unser Programm fest, wo der Anwender es hin kopiert hat:

....
implementation
{$R *.dfm}
var MeinDir : String;

procedure TForm1.FormCreate(Sender: TObject);
begin
   MeinDir := ExtractFilePath(Application.ExeName);
   ....
end;

ist die einfache Formel. MeinDir beinhaltet dann den String, welcher die komplette Pfadangabe (incl. des letzten Backslash) der Anwendung enthält. Diese Variable kann an jeder beliebigen Stelle unseres Programmes benutzt und weiter verarbeitet werden.

Zum Inhaltsverzeichnis

Einen hocheffektiven Manager für Programminstanzen wurde gefunden.

Nicht immer ist es erwünscht, daß ein Programm mehrmals aufgerufen wird und manchmal hat es sich nur unter einem Stabel Windows versteckt oder es wurde aus dem Desktop geschoben. Hier hilft diese kleine wiederverwendbare Unit  InstanceManger.pas von Evan Simpsen. Diese wurde schon vor sechs Jahren bei der Internetfachzeitschrift undu.com veröffentlicht und ist auch unter Delphi 3 bis 6 voll funktionsfähig. Der gesamte Artikel ist hier abrufbar: http://www.undu.com/Articles/980422a.htm

Bitte beachtet unbedingt die ersten Kommentarzeilen.

unit InstanceManager;
{---------------------------------------------------------------------
Autor: Evan Simpson - esimpson@eramp.net
Veröffentlicht in http://www.undu.com/Articles/980422a.htm
Notes: make InstanceManager the *very first* unit in your program's USES
clause. To take advantage of the notification and launch-string, put a
method with no parameters in one of your forms and assign it to triggerProc.
Once triggerProc is called, rcvStr contains the command line of the launch
attempt.
If the only reaction you want is to bring the first instance to the front,
just put a method like the following in your main form, and in the form's
OnCreate set:

InstanceManager.triggerProc:=ToFront;

procedure TForm1.ToFront;
begin
   Application.Restore;
   Application.BringToFront;
end;

If you don't have a dependable main form, make ToFront a class
procedure of any old class.
Customize these constants before using

-------------------------------------------------------------------------}

interface

const UniqueAppName = 'Unbekannter Name einer Anwendung';
AppNotifyValue: integer = 0;

var rcvStr: string;
   rcvValue: integer;
   ForbidOtherInstance: boolean = True;
   triggerProc: procedure of object;

implementation

uses Windows, SysUtils, Messages;

var mutex, thisWnd: HWND;
   IMWndClass: TWndClassA;
   mustHalt: boolean;
   copydata: TCOPYDATASTRUCT;

function IMWndProc(HWindow: HWnd; Message, WParam: Longint; LParam: Longint): Longint; stdcall;
begin
   if Message=WM_COPYDATA then
   begin
     rcvStr := StrPas(PCOPYDATASTRUCT(lParam).lpData);
     rcvValue := PCOPYDATASTRUCT(lParam).dwData;
     if Assigned(triggerProc) then triggerProc;
     Result := Ord(ForbidOtherInstance);
   end
   else
   Result := DefWindowProc(hWindow, Message, WParam, LParam);
end;

initialization

FillChar(IMWndClass, SizeOf(IMWndClass), 0);
IMWndClass.lpfnWndProc := @IMWndProc;
IMWndClass.hInstance := HINSTANCE;
IMwndClass.lpszClassName := 'TInstanceManager';
if Windows.RegisterClass(IMWndClass) = 0 then RaiseLastWin32Error;
mutex := CreateMutex(nil, True, UniqueAppName);
if GetLastError = ERROR_ALREADY_EXISTS then
begin
   mustHalt := True;
   if WaitForSingleObject(mutex, 5000)=WAIT_OBJECT_0 then
   begin
     thisWnd := FindWindow(IMwndClass.lpszClassName, UniqueAppName);
     if thisWnd = 0 then RaiseLastWin32Error;
     CopyData.dwData := AppNotifyValue;
     CopyData.lpData := CmdLine;
     CopyData.cbData := StrLen(CmdLine);
     mustHalt := (SendMessage(thisWnd,WM_COPYDATA,0,Integer(@CopyData))>0);
   end;
   thisWnd := 0;
   ReleaseMutex(mutex);
   if mustHalt then Halt;
end
else begin
   thisWnd := CreateWindow(IMwndClass.lpszClassName,
   UniqueAppName,0,0,0,0,0,0,0,hInstance, nil);
   if thisWnd = 0 then RaiseLastWin32Error;
   ReleaseMutex(mutex);
end;
finalization
   if thisWnd>0 then DestroyWindow(thisWnd);
end.

Zum Inhaltsverzeichnis

Nur eine Programminstanz, zum Zweiten


Weiter oben habe ich die Unit InstanceManager.pas vorgestellt und damit der berechtigten Kritik von Mathias Symmack  ausgesetzt. Zu meiner Entschuldigung ist zu sagen, daß ähnliches von mir schon mit weniger Erfolg angewendet und verworfen wurde. Auch wird in der Literatur oft auf die Ergebnisse meiner bisherigen Erfahrungen verwiesen. Da wird teilweise sogar vorgeschlagen, die Projektdatei mit Notepad zu editieren. Damit ist selbstverständlich kein Blumentopf zu gewinnen. Der Vorschlag von Mathias Symmack wird ohne irgendwelche Kunstkniffe innerhalb der IDE in die Projektdatei und in die Hauptunit des  Programmes geschrieben, erzeugt keine Meckerei durch diese und ist in D3 bis D6 ohne irgendwelchen Warnungen funktionsfähig.

Da Funktionen der Unit Messages benutzt werden, muß diese in der Projektdatei unbedingt genannt werden.
Die begin -Anweisung in der Programmdatei wird durch folgenden Code ersetzt:

var
hm : THandle = 0;
aWnd : HWND;
begin
   // Ein Mutex der Anwendung wird erstellt und erhält den Namen der Anwendung
   hm := CreateMutex(nil,false,'Hier steht der Anwendungsname');
   // Falls der Name der Anwendung schon als Mutex vorhanden, dann abarbeiten:
   if(GetLastError = ERROR_ALREADY_EXISTS) then
   begin
     // Hauptfenster der Anwendung anhand des Form-Namens finden
     aWnd := findwindow('TForm1',nil);
     // Dieses Fenster wiederherstellen
     SendMessage(aWnd,WM_SYSCOMMAND,SC_RESTORE,0);
     // Fenster in den Vordergrund holen, bei VCL-Programm besonders wichtig,
     // in Verbindung mit der
     // procedure TUPXForm1.WMSysCommand(var Message: TMessage);
     SetForegroundWindow(aWnd);
     // diese jetzt neue Instanz beenden
     Halt;
   end;
   // Haben Sie alles  richtig gemacht, stehen jetzt hier die Formularnamen der Anwendung beginnend mit
   // Application.Initialize;
   // Application.Title := 'Titel ihrer Anwendung';
   { usw. }
   // und zum Schluss, unmittelbar vor dem end. der Programmdatei einfügen:
   CloseHandle(hm);
end.

Vom Prinzip her reicht das schon, aber bei VCL-Programmen kann es passieren, daß sich diese plötzlich nicht mehr korrekt minimieren lassen,  usw. In dem Fall muss die Nachricht WM_SYSCOMMAND/SC_RESTORE in der Hauptunit des Programmes abgefangen werden. Auch hier wird die Unit Messages zwingend mit genannt.

type
TForm1 = class(TForm)
{ ... }
private
   procedure WMSysCommand(var Message: TMessage); message WM_SYSCOMMAND;
   { ... }
end;

procedure TForm1.WMSysCommand(var Message: TMessage);
begin
   if(Message.Msg = WM_SYSCOMMAND) and (Message.wParam  = SC_RESTORE) then
   Application.Restore;
   inherited;
end;

 

Zum Inhaltsverzeichnis

Eine schnelle Alternative zu FileFind

Es kann vorkommen, daß das eigene Programm ein anderes, sei es eine Textverarbeitung oder sonst etwas, unbedingt benötigt und dieses  auf den Tiefen der unbekannten Festplatte gesucht werden muß. Ein Weg ist die Funktion ProgiSuch. Ihr wird der String mit dem Namen der Datei übergeben und man erhält den kompletten Namen mit Pfadangabe oder nichts, bzw. eine Fehlermeldung zurück. Wenn das zu suchende Programm sich in einem aktuellen Arbeitsverzeichnis befindet, also der User hat es auf der Festplatte selbst gefunden hat und ruft von da aus das eigene Programm (seltener Fall, aber nicht auszuschliesen), sollte das Result  ebenfalls in einer INI vermerkt und festgehalten werden.

Durchsucht werden das Verzeichnis der gesuchten Anwendung (vom User selbst gefunden), das Verzeichnis der eigenen Anwendung und die Umgebungsvariable PATH.

function ProgiSuch(ProgiName:string):string;
var iValue : integer;
   pBuf : pchar;
   tmp : string; // bisher unbekannter Pfad von ProgiName
begin
   GetDir(0,tmp); // aktuelles Verzeichnis
   // benötigter Speicher zum Lesen der Umgebungsvariable "PATH"
   iValue := GetEnvironmentVariable('PATH',nil,0);
   if(iValue > 0) then // also Umgebungsvariable 'PATH' vorhanden
   begin
     GetMem(pBuf,iValue); // Speicher reservieren
     try
       // Variable lesen, aber eben nur wenn vorhanden
       if(GetEnvironmentVariable('PATH',pBuf,iValue) > 0) then
       // SysUtils-Funktion aufrufen
       tmp := FileSearch(ProgiName, ExtractFilePath(paramstr(0))
                                   + ';' + tmp + ';' + pBuf);
       // in tmp befindet sich jetzt entweder ein Rückgabeergebnis oder eben ''
       // '' kann auch mitgeteilt werden und wird im späteren Programmlauf
       // extra behandelt
       if tmp = '' then ShowMessage('Das gesuchte Programm '
       + ProgiName +' wurde nicht gefunden.');
       // Rückgabeergebnis ist der Dateiname inkl. Pfad,
       // wenn ProgiName gefunden wurde oder ''
       result := tmp;
     finally
     // Speicher freigeben
       FreeMem(pBuf,iValue);
     end;
   end;
end;

 

Zum Inhaltsverzeichnis