![]() |
Re: Problem mit Dateiaufruf per paramstr
Hier also die lauffähige Version. Einfach diese Unit in das Projekt einbinden und die Methode TSingleInstance.OnStartUp mit eigenen Leben füllen. Nicht vergessen den Wert sTitle mit einem eigenen eindeutigen Wert zu ändern.
Gruß Hagen
Delphi-Quellcode:
unit SingleInstance;
interface implementation uses Windows, SysUtils, Controls, Messages, Dialogs, Forms; type TSingleInstance = class class procedure WndProc(var Msg: TMessage); class procedure Start; class procedure Stop; class function GetParamStr(P: PChar; var Param: string): PChar; class function ParamCount: Integer; class function ParamStr(Index: Integer): string; class procedure OnStartup; end; const sTitle = 'my_ProgramXYZ$123456789'; // dieser Wert MUSS individuell angepasst werden class procedure TSingleInstance.OnStartup; // diese Methode muß mit eigenen Inhalt gefüllt werden, // als Beispiel wird hier die 1. Instance sichtbar gemacht // und der ParamStr() der 2. Instance angezeigt. var S: String; I: Integer; begin Application.Minimize; Application.Restore; S := ''; for I := 0 to ParamCount do S := S + ParamStr(I) + #10; ShowMessage(S); end; // ab hier Implementierung const cMagic = $BADF00D; // dient zur Idententifizierung der Message wm_CopyData cResult = $DAED; var WndHandle: hWnd = 0; // die 1. Instance erzeugt ein Fensterhandle CmdLine: PChar = nil; // ParamStr() der 2. Instance per wm_CopyData transportiert class function TSingleInstance.GetParamStr(P: PChar; var Param: string): PChar; // diese funktion musste aus System.pas kopiert werden für unser // ParamStr() udn ParamCount() nötig var Len: Integer; Buffer: array[0..4095] of Char; begin while True do begin while (P[0] <> #0) and (P[0] <= ' ') do Inc(P); if (P[0] = '"') and (P[1] = '"') then Inc(P, 2) else Break; end; Len := 0; while (P[0] > ' ') and (Len < SizeOf(Buffer)) do if P[0] = '"' then begin Inc(P); while (P[0] <> #0) and (P[0] <> '"') do begin Buffer[Len] := P[0]; Inc(Len); Inc(P); end; if P[0] <> #0 then Inc(P); end else begin Buffer[Len] := P[0]; Inc(Len); Inc(P); end; SetString(Param, Buffer, Len); Result := P; end; class function TSingleInstance.ParamCount: Integer; // diese Funktion musste aus System.pas kopiert werden für unser // ParamStr() und ParamCount() nötig da System.pas NICHT auf die // globale Variable System.CmdLine zugreift sondern per Funktion GetCommandLine() arbeitet. var P: PChar; S: string; begin P := GetParamStr(CmdLine, S); // CmdLine statt GetCommandLine Result := 0; while True do begin P := GetParamStr(P, S); if S = '' then Break; Inc(Result); end; end; class function TSingleInstance.ParamStr(Index: Integer): string; // siehe ParamCount var P: PChar; Buffer: array[0..260] of Char; begin if Index = 0 then SetString(Result, Buffer, GetModuleFileName(0, Buffer, SizeOf(Buffer))) else begin P := CmdLine; // CmdLine statt GetCommandLine while True do begin P := GetParamStr(P, Result); if (Index = 0) or (Result = '') then Break; Dec(Index); end; end; end; class procedure TSingleInstance.WndProc(var Msg: TMessage); // das ist die Fensterprocedure von WndHandle, sie empfängt innerhalb // der 1. Instance die wm_CopyData Message mit der CommandLine der // 2. Instance begin with Msg do if (Msg = wm_CopyData) and (PCopyDataStruct(lParam).dwData = cMagic) then begin Result := cResult; CmdLine := PCopyDataStruct(lParam).lpData; OnStartup; end else Result := DefWindowProc(WndHandle, Msg, wParam, lParam); end; class procedure TSingleInstance.Start; var PrevWnd: hWnd; Data: TCopyDataStruct; begin if MainInstance = GetModuleHandle(nil) then // nur in EXE's möglich, nicht in DLL's oder packages begin PrevWnd := FindWindow('TPUtilWindow', sTitle); // suche unser Fenster if IsWindow(PrevWnd) then begin // 1. Instance läuft also schon, sende CommandLine an diese Data.dwData := cMagic; Data.cbData := StrLen(GetCommandLine) +1; Data.lpData := GetCommandLine; if SendMessage(PrevWnd, wm_CopyData, 0, Integer(@Data)) = cResult then Halt; end; // keine 1. Instance gefunden, wir sind also die 1. Instance WndHandle := AllocateHWnd(WndProc); SetWindowText(WndHandle, sTitle); // falls auch bei der 1. Instance OnStartup aufgerufen werden soll // CmdLine := System.CmdLine; // OnStartup; end; end; class procedure TSingleInstance.Stop; begin if IsWindow(WndHandle) then DeallocateHWnd(WndHandle); end; initialization TSingleInstance.Start; finalization TSingleInstance.Stop; end. |
Re: Problem mit Dateiaufruf per paramstr
tut mir leid das is mir zu hoch kann sich mal jemand meinen source ansehen bitte... es funktioniert einwandfrei ausser das beim zuerst gestarteten programm der falsche paramstr ankommt...
Code:
procedure TForm1.WndProc(var msg : TMessage); begin if msg.Msg = dwMessage then begin if msg.lParam <> 0 then begin m1.Lines.Add(PChar(msg.lParam)); end else begin if msg.WParam > -1 then begin m1.Lines.Add(IntToStr(msg.wParam)); end else begin m1.Lines.Add('Keine Daten...'); end; end; end else begin inherited WndProc(msg); end; end; function FindSyncDemo(const p_hWindow : DWORD;const p_szCaption : PChar) : Boolean; stdcall; var dwLen : DWORD; szWork : PChar; begin Result := true; dwLen := SendMessage(p_hWindow,WM_GETTEXTLENGTH,0,0) + 1; if Length(p_szCaption) <> (dwLen-1) then exit; szWork := AllocMem(dwLen); try SendMessage(p_hWindow,WM_GETTEXT,dwLen,lParam(szWork)); if StrLComp(szWork,p_szCaption,dwLen) = 0 then begin Form1.m1.Lines.Add('Sende '+Pfad); SendMessage(p_hWindow, dwMessage,0, lParam(Pfad)); end; finally FreeMem(szWork,dwLen); end; end; procedure TForm1.FormShow(Sender: TObject); var FName: String; begin FName:='myprogramm'; if ParamCount > 0 then begin Pfad:= paramstr(1); Form1.Caption:= FName; mHandle := CreateMutex(nil,True, 'FKILLER-36545tf456-34tf34tf23-23r3'); if GetLastError=ERROR_ALREADY_EXISTS then begin Form1.Caption:= 'xxxxxxxxxxxxxxxxxxxx'; // jetzt paramstr(1) an exe1 senden dwMessage := RegisterWindowMessage(3654-5tf-456-34tf3-4tf23); EnumWindows(@FindSyncDemo,integer(FName)); end; end; |
Re: Problem mit Dateiaufruf per paramstr
Zitat:
In deinem Source gibt es gleich mehrere Probleme. 1.) CreateMutex() kann fehlschlagen wenn unter Win9x der Prozess der diesen Mutex angelegt hat gecrasht ist. Dann besteht dieser Mutex im System weiterhin obwohl es diesen Prozess nicht mehr gibt. Normalerweise würde eine Anwendung die auf CreateMutex() aufbaut dann nicht mehr starten. In deinem Falle würde die 1. Instance dann per EnumWindow() nach dem Fenster suchen, aber keines finden. Somit ist der CreateMutex() Aufruf sinnlos. 2.) Benötigt man zwecks Datenaustausch von ParamStr() ein Fensterhandle dann kann auch CreateMutex() verzichtet werden. 3.) Daten die von einem Prozess zu einem anderen Prozess, über Fensterhandles, ausgetauscht werden sollen müssen per wm_CopyData gesendet werden. wm_CopyData stellt sicher das der Speicherbereich in TCopyDataStruct aus der Gültigkeit des sendenden Prozesses auch gültig im empfangenden Prozess ist. Deshalb wird wm_CopyData immer NUR mit SendMessage() benutzt. Und deshalb funktioniert dein SendMessage() eben nicht. 4.) Dein EnumWindows() kann leicht durch FindWindow() ersetzt werden. Statt also mit EnumWindows() über alle TopLevel Fenster zu iterieren reicht FindWindow(ClassName, WindowText) völlig aus. 5.) die Überprüfung in .FormShow() und das Erzeugen des Mutex ist an dieser Stelle denkbar ungünstig. Der beste Zeitpunkt, und auch der frühestmögliche, ist innerhalb einer Unit-Initialization. Wird meine obige Unit im Projektquelltext an 1. Stelle eingefügt dann wird sie noch vor der Erstellung jeglicher Forms ausgeführt. Damit würde also die 2. Instance der Anwendung erst garnicht langwierige Initialisierungen durchführen. Aber im Grunde macht mein Vorschlag genau das was du versucht zu erreichen. 1.) TSingleInstance.Start sucht im System nach einem Fenster vom Typ "TPUtilWindow" + Caption "my_programXYZ12345". Dies erhöt die Sicherheit. 2.) wenn es gefunden wird wird der aktuelle ParamStr() = GetCommandLine an dieses Fenster per wm_CopyData gesendet. Wir senden also nicht nur ParamStr(1) sondern die komplette Kommandozeile. 3.) sollte das Fenster korrekt antworten so wird die aktuelle Anwendung angehalten und terminiert 4.) sollte kein Fenster "PrevWnd" gefunden worden sein, oder das Fenster hat nicht korrekt geantwortet dann wird die aktuelle Anwendung zur 1. Instance. Sie erzeugt ein Fenster vom Typ "TPUtilWindow" + Caption "my_programxyz123445" 5.) TSingleInstance.WndProc() wartet nun auf die Message wm_CopyData die durch eine 2. Instance der Anwendung aufgerufen wird. Punkte 1.) bis 3.) oben. 6.) sollte TSingleInstance.WndProc() eine gültige wm_CopyData Message erhalten so setzt sie die globale Variable CmdLine auf die Daten die per wm_CopyData empfangen wurde. CmdLine entspricht in diesem Moment der Kommandozeile der zweiten Instance der Anwendung. 7.) nun wird TSingleInstance.OnStartup() aufgerufen, die DEINEN Code enthalten sollte. Innerhalb .OnStartup() kannst du über die Methoden .ParamCount und .ParamStr() auf die Kommandozeile der zweiten Anwendung zugreifen. 8.) TSingleInstance.Stop wird bei der Terminierung der Anwendung aufgerufen und zerstört unser Helperfenster. So was ist daran kompliziert ? besonders weil es auch sauberer und lesbarer Source ist ! :) D.h. für dich: 1.) füge Unit Singleinstance zu deinem Projekt hinzu 2.) ändere den Wert von sTitle auf einen eindeutigen String 3.) ändere den Code in .OnStartup so wie du ihn brauchst. Innerhalb von .OnStartup nutzt du ParamCount und ParamStr() so als würdest du ganz normal damit wie in deiner Anwendung arbeiten. Das wars. Gruß Hagen |
Re: Problem mit Dateiaufruf per paramstr
Sooo danke noch mal für die Hilfe gestern abend, heute morgen usw *gg*...
war gar nich so schwer wie ich dachte aber manchmal muss man nach stundenlangem verzweifeln einfach ne Pause machen und dann später nochmal schauen... soweit so gut jetzt taucht das nächste problem auf :(... wenn ich mit deiner Unit alle dateien fein nacheinander auswäle und öffne dann geht es wunderbar, wenn ich aber 3 markiere und sage mit myprogramm öffnen dann öffnet sich myprogramm 3x ... woran könnte das liegen ? |
Re: Problem mit Dateiaufruf per paramstr
Jay, jetzt's wirds kritisch. Das lässt sich so ohne weiteres nicht ändern, leider. Es liegt an der Vorgehensweise wie die Shell = Explorer eine Anwendung startet. Sie hat die 3 Dateien und erzeugt mit CreateProcess() 3 mal deinen Prozess. ABER! sie gibt diesen 3 neuen Prozessen erst dann eine Chance anzulaufen wenn alle 3 Prozesse schon erzeugt wurden. Deshalb ist es enorm wichtig den "Einmal-Startcode" so früh und so effizient wie möglich einzubauen. Leider bewirkt die Benutzung von Fensterhandles in unserem "Einmal-Startcode" das auch die beiden anderen Prozesse Rechenzeit bekommen, noch bevor überhaupt ein Fensterhandle vollständig erzeugt wurde.
Man kann dies ändern, aber das wird vom Verständnis echt komplizierter. Dafür müssen wir auf Semaphores und Interprozesskommunikation ausweichen. D.h. alle 3 Prozesse laufen korrekt an, wissen aber welche die 1. 2. und 3. Instance ist. Wenn die 1. Instance ordentlich läuft senden die zwei nachfolgenden Prozesse ihre Parameter an die 1. Instance und terminieren. D.h. falls die 2 nachfolgenden Prozesse anhand der Semaphore bemerken das sie nicht die 1. Instance sind müssen die solange warten bis sie mit der 1. Instance kommunizieren können. Allerdings, irgendwie verstehe ich dein Problem noch nicht so recht. Wenn ich mich richtig erinnere kann man den Registryschlüssel für ShellEx/Commmand so anlegen das der Explorer multiple Files an EINE Instance der Anwendung sendet. Er startet dann nur eine Instance und übergibt alle Files als Params. Ansonsten, gibts noch den Weg über COM Interfaces ein eigenes Shell-Interface zu programmieren. Statt dann über ParamStr() und ein EXE zu gehen, codet man eine DLL als Handler. In dieser kann über die Shellextension direkt auf die ausgewählte Dateiliste im Explorer zugegriffen werden. Bei Delphi sind zwei Beispiel dafür vorhanden IShellContextMenu und IShellExecuteHook. Gruß Hagen |
Re: Problem mit Dateiaufruf per paramstr
Zitat:
???????????????????????????????????????????? das geht ? dann hätten wir uns die Aktion doch sparen können ich habe doch mehrmals geschrieben wofür ich das benutzen will :) wie geht das mit der einstellung für multiple files ? |
Re: Problem mit Dateiaufruf per paramstr
Zitat:
Zitat:
Vielleicht weiß ja ein anderer DP'ler schnell die Antwort. Gruß Hagen |
Re: Problem mit Dateiaufruf per paramstr
Zitat:
Zitat:
Zitat:
also ich hab schon in der msdn gesucht bin aber nich so wirklich fündig geworden :( |
Re: Problem mit Dateiaufruf per paramstr
Ich habe mal auf die schnelle meine Registry gescannt.
Es gäbe den Weg über DDE, ein sehr alter Weg, oder eben über die Shell Extensionen. Lass mir ein bißchen Zeit, am Wochenende werde ich mal TSingleInstance erweitern um die Semaphores, eventuell mit Memory Mapped Files + Events in Threads. Auf jeden Fall habe ich noch nie einen absolut sauber arbeitenden Single Instance Code gefunden. Zur Zeit muß ich erstmal Geld verdienen. Gruß Hagen |
Re: Problem mit Dateiaufruf per paramstr
hmm falls jemand anderes weiß wie man mutltiple files an eine instance senden kann bitte trotzdem posten mal schauen ob wir was finden und was letztendlich dann der beste weg ist :)
@Hagen danke erstmal, mal schauen ob du es hinbekommst |
Alle Zeitangaben in WEZ +1. Es ist jetzt 13:28 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz