|  | | |  |
 | | | 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 |
|
|