Entwickler-Ecke

Windows API - ContextMenuHandler


schitho - Mi 03.09.03 21:10
Titel: ContextMenuHandler
Hi,

ihr kennt sicher die Kontext-Menüerweiterungen von WinZip.

Genau soetwas möchte ich für mein Programm auch haben :D

Also wenn man mit der rechten Maustaste auf eine oder mehrere Dateien klickt soll im Kontextmenü der Name meines Programmes aufscheinen. Und wenn man nun auf diesen Namen klickt, soll mein Programm geöffnet werden und die Namen (mit Pfad) der Dateien übergeben werden.

Ich weiß, dass ich dies mit HKEY_CLASSES_ROOT\*\shell\MeinProgramm\Command und dem Eintrag MeinProgramm "%1" machen könnte.

Doch leider wird das Programm dann mehrmals gestartet, wenn mehrer Dateien ausgewählt wurden. :cry:

WinZip verwendet daher stattdessen - soweit ich das verstanden habe - shellex und einen eigenen ContexMenuHandler.

Es gibt dazu auch mehrer Registry-Einträge. Und so weit ich in Erfahrung bringen konnte muss man eben so einen COM-Handle (oder so ähnlich) dafür programmieren.

Da ich diesbezüglich aber vollkommen ahnungslos bin (wie ihr sicher schon bemerkt habt :wink: ) suche ich nach diesbzüglichen Infos, wie ich so etwas programmieren kann, damit ich im Kontextmenü mein Programm anzeigen kann und es nicht doppelt gestartet wird.

Oder gibt es vielleicht eine Komponente, die einem das abnimmt bzw. wesentlich erleichtert???


Delete - Mi 03.09.03 23:00

Nein, aber eine Demo von Borland im Ordner "ShellExt", die auf diese Weise den Punkt Compile in das Kontextmenü von Dateien schreibt. Das läuft allerdings auf eine DLL hinaus. Sprich: du müsstest dann doch irgendwie wieder dein Programm aufrufen ...

Du musst aber keine Shell-Erweiterung machen. Dein Problem mit dem mehrfachen Aufruf lässt sich durch einen Mutex oder Semaphore umgehen, der dein Programm nur einmal startet. Wird der Mutex/Semaphore noch einmal gefunden, könntest du den Parameter der zweiten Instanz bspw. über WM_COPYDATA an die erste Instanz weiterreichen.

Dazu könnte ich dich an die UPX-Shell auf meiner Seite verweisen. Da habe ich das gemacht, denn die registriert sich mit beiliegender INF-Datei auch im System. :)

Langsam sollte ich Lizenz- bzw. Adaptionsgebühren verlangen. :wink:


schitho - Mi 03.09.03 23:18

MathiasSimmack hat folgendes geschrieben:
Du musst aber keine Shell-Erweiterung machen. Dein Problem mit dem mehrfachen Aufruf lässt sich durch einen Mutex oder Semaphore umgehen, der dein Programm nur einmal startet. Wird der Mutex/Semaphore noch einmal gefunden, könntest du den Parameter der zweiten Instanz bspw. über WM_COPYDATA an die erste Instanz weiterreichen.


Das hab ich auch schon ausprobiert funktioniert aber nicht richtig (zumindest unter W98). Ich bekomm dann immer die Fehlermeldung, dass nicht genügend Arbeitsspeicher zur Verfügung steht (trotz 512 MB RAM).

Trotzdem würde ich mir mal gerne Deine Seite ansehen. Aber wo find ich diese????


Delete - Mi 03.09.03 23:45

Ich dachte, du hast schon ein Programm von mir gekl... äh ... adaptiert. :wink: Die UPX UI gibt´s auch da.


schitho - Do 04.09.03 21:55

Hab mir Dein Programm angesehen. Puhhh! Ist das Delphi? :wink:

Also was ich (halbwegs) verstanden habe ist:

Mit diesem Code verhinderst Du, dass Dein Programm doppelt gestartet wird und dass die Parameter an die andere Instanz gesendet werden:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
 
const
  szUniqueId = 'UPX-UI-2003_D311E2A0-3084-11D7-BD2C-FC698EE3C743';
var
  hm         : THandle = 0;
  aWnd       : HWND;
  cpydata    : TCopyDataStruct;
  iccex      : TInitCommonControlsEx = (
    dwSize:sizeof(TInitCommonControlsEx);
    dwICC:ICC_BAR_CLASSES;);
begin
 // Mutex erzeugen
  hm := CreateMutex(nil,false,szUniqueId);
  if(GetLastError = ERROR_ALREADY_EXISTS) then begin
    // Fenster-Handle der bereits aktiven Instanz holen
    aWnd := findwindow(nil,'UPX UI 2003');

    // den Parameter (= Datei) an die aktive Instanz senden!
    if(paramcount = 1and
      (fileexists(paramstr(1))) and
      (aWnd <> 0then
    begin
      cpydata.dwData := 0;
      cpydata.cbData := length(paramstr(1)) + 1;
      cpydata.lpData := @paramstr(1)[1];

      SendMessage(aWnd,WM_COPYDATA,0,LPARAM(@cpydata));
    end;

    // diese laufende Instanz in den Vordergrund holen
    if(aWnd <> 0then begin
      SendMessage(aWnd,WM_SYSCOMMAND,SC_RESTORE,0);
      SetForegroundWindow(aWnd);
    end;

    // und den Mehrfachstart unterbinden!
    Halt;
  end;


Diesen Code kann ich fast 1:1 in mein Programm einbauen. Wenn ich es richtig verstanden habe, muss ich nur den Fensternamen ändern und szUniqueId adaptieren. Ist es bei der szUniqueId eigentlich egal, was da drinnen steht???

Hab den Code adaptiert und unter OnCreate integriert. Damit wird der Doppelstart verhindert. :)

Um die Parameter abzufangen (zu erhalten), verwendest Du (scheinbar) folgenden Code:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
(* Nachricht von einer anderen Instanz *)
    WM_COPYDATA:
      begin
        // Dateinamen aus dem übermittelten Puffer lesen, ...
        ZeroMemory(@buf,sizeof(buf));
        lstrcpyn(buf,PCopyDataStruct(lp)^.lpData,PCopyDataStruct(lp)^.cbData);

        // & ggf. in die Liste einfügen
        if(buf[0] <> #0and
          (SendDlgItemMessage(hwndDlg,IDC_FILELIST,LB_FINDSTRINGEXACT,-1,
            LPARAM(@buf)) = LB_ERR) and
          (lstrcmpi(buf,@paramstr(0)[1]) <> 0then
        SendDlgItemMessage(hwndDlg,IDC_FILELIST,LB_ADDSTRING,0,LPARAM(@buf));


Und jetzt steig ich endgültig aus.

Zu welchen Ereignis muss den Code einfügen? Wie bekomm ich die Parameter in ein Memofeld?

Versteh nur Bahnhof :cry: Ich glaub ich lern das nie :nixweiss:

Kannst Du mir sagen, wie der Code aussehen müsste, wenn sich in meinem Programm in der Form ein Memofeld (Memo1) befindet und ich die Parameter dort anzeigen möchte?

Recht herzlich Dank!


Delete - Do 04.09.03 22:36

:mrgreen:

Also, wie du richtig erkannt hast, kannst du den "Sende"-Teil quasi 1:1 übernehmen. Die UniqueId ist nicht egal. Du solltest bitte eine andere benutzen. Du kannst dir zwar ausdenken, was du willst, aber du solltest nach Möglichkeit nicht meine nehmen. Es könnte ja sein, dass jemand mal zufälligerweise dein und mein Programm benutzt. Dann würde u.U. mein Programm reagieren, weil deins den Mutex nicht mehr erzeugen kann.

Das "Empfangen" läuft über die Nachricht WM_COPYDATA. Die kannst du auch in der VCL abfangen, indem du bspw. die "WndProc" direkt bearbeitest.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
type
  TForm1 = class(TForm)
    { ... }
  protected
    procedure WndProc(var Message: TMessage); override;
  end;


{ ... }


procedure TForm1.WndProc(var Message: TMessage);
begin
  if(Message.Msg = WM_COPYDATA) then begin
    { ... }
  end else
    inherited WndProc(Message);
end;

Du musst nur beachten, dass "uMsg", "wParam" (wp) und "lParam" (lp) bei der VCL Teil von "Message" sind. Na ja, und was den Code angeht. Das ist eben nonVCL. Hier wird der Name aus dem übermittelten Puffer ausgelesen

Delphi-Quelltext
1:
2:
ZeroMemory(@buf,sizeof(buf));
lstrcpyn(buf,PCopyDataStruct(lp)^.lpData,PCopyDataStruct(lp)^.cbData);

Wenn das erste Zeichen ungleich Null ist (pchar)

Delphi-Quelltext
1:
if(buf[0] <> #0and                    

dann wird geguckt, ob´s den String evtl. schon in meiner Liste gibt

Delphi-Quelltext
1:
2:
  (SendDlgItemMessage(hwndDlg,IDC_FILELIST,
    LB_FINDSTRINGEXACT,-1,LPARAM(@buf)) = LB_ERR) and

und er darf auch nicht dem Namen der UPX-Shell selbst entsprechen

Delphi-Quelltext
1:
  (lstrcmpi(buf,@paramstr(0)[1]) <> 0then                    

dann (und nur dann!) wird er in die Listbox eingefügt

Delphi-Quelltext
1:
2:
SendDlgItemMessage(hwndDlg,IDC_FILELIST,
  LB_ADDSTRING,0,LPARAM(@buf));

Damit kannst du den Code wohl jetzt auch 1:1 kopieren? :)


Delete - Do 04.09.03 22:48

Noch eine andere Variante, gleich mit praktischem Beispiel:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    procedure WMCopyData(var Msg: TWMCopyData); message WM_COPYDATA;
  end;


{ ... }


procedure TForm1.WMCopyData(var Msg: TWMCopyData);
var
  buf : array[0..MAX_PATH]of char;
begin
  inherited;

  ZeroMemory(@buf,sizeof(buf));
  lstrcpyn(buf,Msg.CopyDataStruct^.lpData,Msg.CopyDataStruct^.cbData);

  ShowMessage(buf);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  cpydata : TCopyDataStruct;
begin
  cpydata.dwData := 0;
  cpydata.cbData := length('Hallo, schitho') + 1;
  cpydata.lpData := pchar('Hallo, schitho');

  SendMessage(self.Handle,WM_COPYDATA,0,LPARAM(@cpydata));
end;

Bitte schön.


schitho - Sa 06.09.03 14:03

Cool. Jetzt funktioniert es (halbwegs).

Allerdings bekomm ich die Nachricht nicht ins Memofeld. Mit ShowMessage kann ich sie jedoch anzeigen. Wieso funktioniert Memo.Lines.Add bzw. Append nicht?


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
procedure TFormTest.WMCopyData(var Msg: TWMCopyData);
var 
  buf : array[0..MAX_PATH]of char; 
begin 
  inherited

  ZeroMemory(@buf,sizeof(buf));
  lstrcpyn(buf,Msg.CopyDataStruct^.lpData,Msg.CopyDataStruct^.cbData);
  Memo.Lines.Append(buf); //funktioniert nicht
  ShowMessage(buf); // funktioniert
end;


Ich bekomm zwar keine Fehlermeldung, allerdings wird im Memofeld auch nicht angezeigt. Versteh ich nicht :(


schitho - Sa 06.09.03 22:39

Hab den Code nun geändert! Und er funktioniert trotzdem nicht :bawling:

Hier der Code:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
unit test;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TFormTest = class(TForm)
    Label1: TLabel;
    Memo: TMemo;
    Button1: TButton;
    LMutex: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);

  private
    { Private-Deklarationen }
    protected 
    procedure WndProc(var Message: TMessage); override

  public
    { Public-Deklarationen }
  end;

var
  FormTest: TFormTest;
  Zeit : TDateTime;

implementation

{$R *.dfm}




procedure TFormTest.WndProc(var Message: TMessage);
var
  buf : array[0..MAX_PATH]of char;
begin
  if(Message.Msg = WM_COPYDATA) then begin
    // Dateinamen aus dem übermittelten Puffer lesen, ...
        ZeroMemory(@buf,sizeof(buf));
        lstrcpyn(buf,PCopyDataStruct(Message.LParam)^.lpData,PCopyDataStruct(Message.LParam)^.cbData);

        // & ggf. in die Liste einfügen
        if(buf[0] <> #0and
          (SendDlgItemMessage(self.Handle,Memo.ControlCount,LB_FINDSTRINGEXACT,-1,
            LPARAM(@buf)) = LB_ERR) and
          (lstrcmpi(buf,@paramstr(0)[1]) <> 0then
        SendDlgItemMessage(self.Handle,Memo.ControlCount,LB_ADDSTRING,0,LPARAM(@buf));

  end else
    inherited WndProc(Message);
end;

procedure TFormTest.FormCreate(Sender: TObject);
const
  szUniqueId = 'TEST_D311E2A0-3084-11D7-BD2C-FC698EE3C743';
var
  hm         : THandle;
  aWnd       : HWND;
  cpydata    : TCopyDataStruct;
begin
  Zeit:=now;
  // Mutex erzeugen
  hm:=0;
  hm := CreateMutex(nil,false,szUniqueId);
  if(GetLastError = ERROR_ALREADY_EXISTS) then begin
    // Fenster-Handle der bereits aktiven Instanz holen
    aWnd := findwindow(nil,'Test');

    // den Parameter (= Datei) an die aktive Instanz senden!
    if(paramcount = 1and (aWnd <> 0then
    begin
      cpydata.dwData := 0;
      cpydata.cbData := length(paramstr(1)) + 1;
      cpydata.lpData := @paramstr(1)[1];
      SendMessage(aWnd,WM_COPYDATA,0,LPARAM(@cpydata));
    end;

    // diese laufende Instanz in den Vordergrund holen
    if(aWnd <> 0then begin
      SendMessage(aWnd,WM_SYSCOMMAND,SC_RESTORE,0);
      SetForegroundWindow(aWnd);
    end;

    // und den Mehrfachstart unterbinden!
    Halt;
  end;

end;

procedure TFormTest.Button1Click(Sender: TObject);
var
  cpydata : TCopyDataStruct; 
begin
  cpydata.dwData := 0;
  cpydata.cbData := length('Hallo, schitho') + 1;
  cpydata.lpData := pchar('Hallo, schitho');

  SendMessage(self.Handle,WM_COPYDATA,0,LPARAM(@cpydata)); 

end;

end.


Wahrscheinlich voller Fehler....... :oops:


Delete - So 07.09.03 09:12

1. Du solltest den Code mit der Mehrfachabfrage im Projektquelltext (*.dpr) unterbringen, bevor ein Formular erzeugt wird. Die Units "Windows" und "Messages" bitte nicht vergessen.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
begin
  hm := CreateMutex(nil,false,szUniqueId);
  if(GetLastError = ERROR_ALREADY_EXISTS) then begin
    { ... }
  end;

  // hier kommt der ganze Form-bezogene Code
  Application.Initialize;
  { ...}


  // am Schluss: nicht vergessen -->
  CloseHandle(hm);
end;



2. Die Variante mit einem String anstelle eines Char-Puffers. Um vorzugreifen: ich hab´s getestet, und es funktioniert.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
type
  TForm1 = class(TForm)
    { ...}
  private
    procedure WMCopyData(var Msg: TWMCopyData); message WM_COPYDATA;
  end;


procedure TForm1.WMCopyData(var Msg: TWMCopyData);
var
  szBuf : string;
begin
  inherited;

  SetLength(szBuf,Msg.CopyDataStruct^.cbData + 1);
  ZeroMemory(@szBuf[1],length(szBuf));
  lstrcpyn(@szBuf[1],
    Msg.CopyDataStruct^.lpData,
    Msg.CopyDataStruct^.cbData);

  if(szBuf <> ''and
    (Memo1.Lines.IndexOf(szBuf) = -1then
    Memo1.Lines.Add(szBuf);
end;



3. Eine Anmerkung noch dazu:
schitho hat folgendes geschrieben:

Delphi-Quelltext
1:
aWnd := findwindow(nil,'Test');                    

Du solltest lieber den Klassennamen abfragen, denn der richtet sich bei der VCL netterweise nach dem Namen der Form. Das heißt, wenn du x Programme hast, deren Formulare alle "TForm1" heißen, dann hast du ein Problem. Wenn du deinem Programm aber einen passenden Namen gibst, dann kannst du mit

Delphi-Quelltext
1:
aWnd := findwindow('TMeineGesuchteForm',nil);                    

das Fenster relativ einfach und unabhängig vom Text in der Titelleiste finden. Im Fall deines Beispielcodes also

Delphi-Quelltext
1:
aWnd := findwindow('TFormTest',nil);                    


Gruß.


schitho - So 07.09.03 12:31

Hallo Matthias,

Danke für Deine Geduld 8)

Irgendwie bin ich aber scheinbar nicht mal fähig Deinen Code richtig abzutippen :autsch:

Es funktioniert noch immer nicht richtig :oops:

Hier nun der Projektquelltext:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
program mutextest;

uses
  Forms,
  test in 'test.pas' {FormMutexTest},
  Windows,
  Messages;

{$R *.res}

const
  szUniqueId = 'MUTEXTEST_D311E2A0-3084-11D7-BD2C-FC698EE3C743';
var
  hm         : THandle;
  aWnd       : HWND;
  cpydata    : TCopyDataStruct;
begin
  hm := CreateMutex(nil,false,szUniqueId);
  if(GetLastError = ERROR_ALREADY_EXISTS) then begin
       aWnd := findwindow(nil,'TFormMutexTest');

    // den Parameter (= Datei) an die aktive Instanz senden! 
    if(paramcount = 1and (aWnd <> 0then 
    begin 
      cpydata.dwData := 0
      cpydata.cbData := length(paramstr(1)) + 1
      cpydata.lpData := @paramstr(1)[1]; 
      SendMessage(aWnd,WM_COPYDATA,0,LPARAM(@cpydata));
    end

    // diese laufende Instanz in den Vordergrund holen 
    if(aWnd <> 0then begin 
      SendMessage(aWnd,WM_SYSCOMMAND,SC_RESTORE,0); 
      SetForegroundWindow(aWnd); 
    end

    // und den Mehrfachstart unterbinden! 
    Halt; 

  end;

  Application.Initialize;
  Application.CreateForm(TFormMutexTest, FormMutexTest);
  Application.Run;
  CloseHandle(hm); 

end.


und hier der Rest:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
unit test;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TFormMutexTest = class(TForm)
    Label1: TLabel;
    Memo: TMemo;
    Button1: TButton;
    LMutex: TLabel;
    procedure Button1Click(Sender: TObject);

  private
    { Private-Deklarationen }
    procedure WMCopyData(var Msg: TWMCopyData); message WM_COPYDATA;
  public
    { Public-Deklarationen }
  end;

var
  FormMutexTest: TFormMutexTest;

implementation

{$R *.dfm}

procedure TFormMutexTest.WMCopyData(var Msg: TWMCopyData);
var 
  szBuf : string;
begin
  inherited

  SetLength(szBuf,Msg.CopyDataStruct^.cbData + 1); 
  ZeroMemory(@szBuf[1],length(szBuf));
  lstrcpyn(@szBuf[1], 
    Msg.CopyDataStruct^.lpData,
    Msg.CopyDataStruct^.cbData);

  if(szBuf <> ''and
    (Memo.Lines.IndexOf(szBuf) = -1then
    Memo.Lines.Add(szBuf);
end;

procedure TFormMutexTest.Button1Click(Sender: TObject);
var
  cpydata : TCopyDataStruct; 
begin
  cpydata.dwData := 0;
  cpydata.cbData := length('Hallo, schitho') + 1;
  cpydata.lpData := pchar('Hallo, schitho');

  SendMessage(self.Handle,WM_COPYDATA,0,LPARAM(@cpydata)); 

end;

end.


Irgendwas stimmt da noch nicht :cry:


Delete - So 07.09.03 16:02

schitho hat folgendes geschrieben:
Es funktioniert noch immer nicht richtig :oops:

Und was heißt das genau? Was funktioniert nicht?

Ich sehe im Moment nur einen Fehler

Delphi-Quelltext
1:
aWnd := findwindow(nil,'TFormMutexTest');                    

Laut Deklaration am Anfang deiner DPR-Datei ist "TFormMutexTest" der Klassenname - nicht der Fenstertitel. Der Klassenname ist der erste Parameter von "findwindow". Richtig, und das hast du in meinem Punkt #3 überlesen, ist also

Delphi-Quelltext
1:
aWnd := findwindow('TFormMutextTest',nil);                    

Gruß.


schitho - So 07.09.03 16:52

So ist es - peinlich, peinlich :oops:

Jetzt funktioniert es :D

Danke, Danke, Danke!!!!!!


webrage - Mo 08.09.03 11:33
Titel: hmmm ich hab das jetzt auch mal probiert...
das problem ist dabei aber das natürlich erst die daten gesendet werden können wenn die


Quelltext
1:
  aWnd := findwindow('TFormMutexTest',nil);                    


die anwendung auch findet...
so wie es jetzt ist empfängt mein programm bei 3 markierten dateien nur 1 oder mal gar keine wenn die exe zu spät startet


webrage - Mo 08.09.03 12:41
Titel: ok habs :)
so geht es :D


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
program mutextest;

uses
  Forms,
  test in 'test.pas' {FormMutexTest},
  Windows,
  Messages;

{$R *.res}
const 
  szUniqueId = 'MUTEXTEST_D311E2A0-3084-11D7-BD2C-FC698EE3C743';
var 
  hm1,hm2    : THandle;
  aWnd       : HWND;
  cpydata    : TCopyDataStruct;
begin

 hm1 := CreateMutex(nil,false,szUniqueId);

  if Not(GetLastError = ERROR_ALREADY_EXISTS) then begin
   Application.Initialize;
   Application.CreateForm(TFormMutexTest, FormMutexTest);
   Application.Run;
  end;

 hm2 := CreateMutex(nil,false,szUniqueId);
 if (GetLastError = ERROR_ALREADY_EXISTS) then begin
   aWnd := findwindow('TFormMutexTest',nil);

   // warten bis Anwendung vorhanden ist
       while(paramcount = 1and (aWnd=0do begin
        aWnd := findwindow('TFormMutexTest',nil);
        Application.ProcessMessages;
        sleep(5);
       end;

    // den Parameter (= Datei) an die aktive Instanz senden!
    if(paramcount = 1and (aWnd <> 0then begin
      cpydata.dwData := 0;
      cpydata.cbData := length(paramstr(1)) + 1;
      cpydata.lpData := @paramstr(1)[1];
      SendMessage(aWnd,WM_COPYDATA,0,LPARAM(@cpydata));
    end;

    // diese laufende Instanz in den Vordergrund holen
    if(aWnd <> 0then begin
      SendMessage(aWnd,WM_SYSCOMMAND,SC_RESTORE,0);
      SetForegroundWindow(aWnd);
    end;

    // und den Mehrfachstart unterbinden!
    Halt;

end;
    CloseHandle(hm1);
    CloseHandle(hm2);
end.



Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
unit test;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Registry;

type
  TFormMutexTest = class(TForm)
    Memo: TMemo;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);

  private
    { Private-Deklarationen }
    procedure WMCopyData(var Msg: TWMCopyData); message WM_COPYDATA; 
  public
    { Public-Deklarationen }
  end;

var
  FormMutexTest: TFormMutexTest;
  Reg : TRegistry;
  RegName,Title,Adress : String;
implementation

{$R *.dfm}

procedure TFormMutexTest.FormCreate(Sender: TObject);
begin
RegName := 'TestMutex';
Title   := 'Öffnen mit TestMutexTitle';
Adress  := ExtractFilePath(Application.ExeName)+'mutextest.exe'' %1';
IF (paramcount = 1then Memo.Lines.Add(IntToStr(memo.Lines.Count+1)+': '+paramstr(1));
end;

procedure TFormMutexTest.WMCopyData(var Msg: TWMCopyData);
var  
  szBuf : string
begin
  inherited;
  SetLength(szBuf,Msg.CopyDataStruct^.cbData + 1);  
  ZeroMemory(@szBuf[1],length(szBuf)); 
  lstrcpyn(@szBuf[1],
  Msg.CopyDataStruct^.lpData,
  Msg.CopyDataStruct^.cbData);
  if(szBuf <> ''and (Memo.Lines.IndexOf(szBuf) = -1then Memo.Lines.Add(IntToStr(memo.Lines.Count+1)+': '+szBuf);
end

procedure TFormMutexTest.Button1Click(Sender: TObject);
begin
reg:=tregistry.create();
Reg.RootKey:=HKEY_CLASSES_ROOT;

  // Dateien ins Context Menü
  Reg.OpenKey('*\shell',true); Reg.CloseKey;
  Reg.OpenKey('*\shell\'+RegName,true);
  Reg.WriteString('',Title);
  Reg.CloseKey;
  Reg.OpenKey('*\shell\'+RegName+'\command',true);
  Reg.WriteString('',Adress); // adress="c:\test\meinprogramm.exe %1"
  Reg.CloseKey;
  reg.free;
end;

procedure TFormMutexTest.Button3Click(Sender: TObject);
begin
memo.Clear;
end;

procedure TFormMutexTest.Button2Click(Sender: TObject);
begin
reg:=tregistry.create();
  Reg.RootKey:=HKEY_CLASSES_ROOT;
  // Dateien aus Context Menü entfernen
   if Reg.OpenKey('*\shell',false) then begin
    Reg.DeleteKey(RegName);
    Reg.CloseKey;
   end;
end;

end.


Moderiert von user profile iconTino: Code- durch Delphi-Tags ersetzt.


schitho - Mo 08.09.03 21:52

Hi,

also bei mir funktionieren beide Versionen!

Nur wenn wenig System-Resourcen zur verfügung stehen, dann funktionieren (meist) auch beide Versionen nicht.

Getestet unter Window 98, Athlon 1 GHz und 512 MB RAM

Wie sieht deine Konfiguration aus?


webrage - Di 09.09.03 00:19

echt bei dir gehen beide manchmal nicht ???
theoritisch und eigentlich auch logischerweise müsste meine variante aber unabhängig der systemleistung arbeiten da lle mehrfach aufgerufenen instancen warten bis die erste vollständig geladen wurde...

vieleicht hat ja jemand anders ne idee ???

1. wieviele dateien hast du markiert ?
2. hast du es 1:1 übernommen oder etwas geändert?

512 MB Ram
1,2 Athlon

bei mir läuft es mit meiner Variante perfekt egal viele Dateien ich auswähle, die erste Variante hat immer den ersten Wert verschluckt...

logischerweise:

ich markiere 10 Dateien und sage öffnen mit xxx

1 öffnet und muss da bleiben paramstr(1) in die listbox schreiben
9 öffnen und müssen ihre paramstr an die 1 senden


Delete - Di 09.09.03 10:01

webrage hat folgendes geschrieben:
bei mir läuft es mit meiner Variante perfekt egal viele Dateien ich auswähle, die erste Variante hat immer den ersten Wert verschluckt...

Das ist dein Denkfehler! Für mich war und ist es vollkommen normal und darum überhaupt nicht erwähnenswert, dass das Programm seinen Parameter normalerweise im "OnCreate" bearbeitet

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
procedure TForm1.FormCreate(Sender: TObject);
begin
  if(paramcount = 1then
    szFileName := paramstr(1);

  // usw.
end;

(@schitho: Schau mal in den Code der UPX-Shell. Du müsstest etwas ähnliches im WM_INITDIALOG-Teil finden. :)) Nur die Parameter jeder weiteren Instanz werden über WM_COPYDATA an die bereits laufende Anwendung übergeben. Und darum ist dein Codevorschlag a) umständlich, und b) auch mit einer potentiellen Bombe versehen
Zitat:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
   aWnd := findwindow('TFormMutexTest',nil);

   // warten bis Anwendung vorhanden ist
       while(paramcount = 1and (aWnd=0do begin
        aWnd := findwindow('TFormMutexTest',nil);
        Application.ProcessMessages;
        sleep(5);
       end;

Stell dir vor, dass aus irgendeinem Grund dein Programm hängen bleibt. Es erzeugt noch den Mutex (passiert ja im Projektquelltext) und will dann die Formulare erzeugen und laden, stürzt dabei jedoch ab. Das Hauptfenster (= Hauptform) ist noch nicht erzeugt, und jede weitere Instanz wartet durch die while-Schleife dann ewig.
Du solltest also entweder einen Zähler einbauen, der die Übergabe des Parameters nach x Versuchen abbricht. Oder du erhöhst den Sleep-Wert, oder du nimmst (wie ich) in Kauf, dass im ungünstigsten Fall evtl. ein paar Dateien nicht berücksichtigt werden, wenn die allererste Instanz nicht schnell genug lädt.

Letztlich hängt das aber auch wieder vom Programm ab. Wenn es seinerseits weitere Formulare erstellt oder andere zeitintensive Dinge beim Start ausführt, dann -ja!- dann hast du schon recht: es kann passieren, dass einige Dateien verloren gehen.


webrage - Di 09.09.03 11:02

Zitat:
Für mich war und ist es vollkommen normal und darum überhaupt nicht erwähnenswert, dass das Programm seinen Parameter normalerweise im "OnCreate" bearbeitet


das für dich und für mich und für schitho klar ist kann ich mir denken, hast du aber vielleicht mal daran gedacht das weniger versierte user irgendwann mal vielleicht den code benutzen wollen und wir nicht davon ausgehen sollten das es sich jeder denken kann ???

Zitat:
Du solltest also entweder einen Zähler einbauen, der die Übergabe des Parameters nach x Versuchen abbricht. Oder du erhöhst den Sleep-Wert, oder du nimmst (wie ich) in Kauf, d


da hast du natürlich recht, das kann passieren ...
ich werde also am besten noch einen zähler integrieren der nach x versuchen abbricht...

in meinem fall benötige ich nur die paramstr(1) in einer listbox daher ist das wenig rechenintensiv, ich habe aber leider keine möglichkeit das auf einem langsamen rechner zu testen, daher sollte man eine sicherheit (zähler) einbauen, da stimme ich uneingeschränkt zu :)


Delete - Di 09.09.03 12:51

webrage hat folgendes geschrieben:
Zitat:
Für mich war und ist es vollkommen normal und darum überhaupt nicht erwähnenswert, dass das Programm seinen Parameter normalerweise im "OnCreate" bearbeitet


das für dich und für mich und für schitho klar ist kann ich mir denken, hast du aber vielleicht mal daran gedacht das weniger versierte user irgendwann mal vielleicht den code benutzen wollen und wir nicht davon ausgehen sollten das es sich jeder denken kann ???

Das beweist mir, dass du dich noch nicht so richtig mit der Suchfunktion in diesem Forum beschäftigt hast. Suche in: Delphi-Forum, Delphi-Library PARAMSTR und Suche in: Delphi-Forum, Delphi-Library PARAMCOUNT bringen diverse Treffer, teilweise sogar mit Vorschlägen, wo man den Parameter am besten abfragt.

Abgesehen davon ist dein Denkansatz unglücklich. Du bist in diesen Beitrag gekommen und hast wahrscheinlich gedacht, WM_COPYDATA ist der einzig mögliche Weg, Parameter abzufragen bzw. zu übergeben.

Tatsächlich läuft es aber so ab: Man möchte, dass das eigene Programm Dateien als Parameter akzeptiert und kümmert sich dabei nicht um andere Sachen. Und dadurch geht man so vor, wie ich das als normal und üblich erachte - man prüft im "OnCreate", ob ein Parameter benutzt wurde und lädt ggf. die Datei.
Meist kommt erst dann die Idee: "Hey, das kann ich ja ins System einbauen, damit sich beim Klick auf XYZ-Dateien mein Programm öffnet." Gesagt, getan - funktioniert.
Und dann kommt der Gedanke, der auch schitho zu Beginn bewegt hat: "Moment mal, jetzt starten hier zig Instanzen des Programms. Wie kann ich das verhindern?"


Voilà. :wink:

Nichts für ungut, aber speziell die Sache mit dem Parameter ist für mich (wie ich schon schrieb) ein alter Hut und nicht erwähnenswert, weil´s der eigentlich typische Weg ist. Ich will nichts falsches behaupten, aber ich glaube, selbst in der Delphi-Hilfe ist ein entsprechendes Beispiel. Und, wie gesagt, du findest hier im Forum auch mehr als genug, in denen das vorgemacht wird. Das kann man also durchaus in Erfahrung bringen. So gesehen: warum soll ich´s also zum x-ten Mal erwähnen?

Gruß.


webrage - Di 09.09.03 13:48
Titel: man man
du bist ja nen toller :)

Zitat:
du dich noch nicht so richtig mit der Suchfunktion in diesem Forum beschäftigt hast.


wer lesen kann ist klar im vorteil oder ???
es geht doch dabei nicht um mich ?! ich habe doch den quelltext dazu gepostet wie man es machen kann wozu soll ich da die suche benutzen ?

Zitat:
Abgesehen davon ist dein Denkansatz unglücklich. Du bist in diesen Beitrag gekommen und hast wahrscheinlich gedacht, WM_COPYDATA ist der einzig mögliche Weg, Parameter abzufragen bzw. zu übergeben.


und wieder falsch :lol: ich habs bisher mit den DDE's gemacht das war mir zu langsam und aus diesem grund wollte ich es damit probieren...

wenn du so grantig bist weil du dich angeriffen fühlst tut es mir leid, ich kann dir versichern ich wollte dich in keiner weise beleidigen ...


UGrohne - Di 09.09.03 13:54
Titel: Re: man man
webrage hat folgendes geschrieben:
wenn du so grantig bist weil du dich angeriffen fühlst tut es mir leid, ich kann dir versichern ich wollte dich in keiner weise beleidigen ...

Ich konnte nicht feststellen, dass sich Mathias in irgendeiner Weise angegriffen fühlte. Lasst am besten solche Gefühlssachen raus, das gibt nur Ärger :wink:


webrage - Di 09.09.03 14:00
Titel: ok
ich war mir nicht sicher... darum habe ich mich schonmal vorsichtshalber entschuldigt :)


Delete - Di 09.09.03 18:16

Kein Problem. Ich nehm so was nicht krumm. Es soll ja schließlich jeder seine Meinung sagen dürfen, und ich habe die Allwissenheit nicht gepachtet ... noch nicht ... *g*

Aber nebenbei: Ja, ich bin ein ganz Toller. :)


schitho - Di 09.09.03 22:42

Hab noch eine Frage zu


Delphi-Quelltext
1:
2:
const 
  szUniqueId = 'MUTEXTEST_D311E2A0-3084-11D7-BD2C-FC698EE3C743';


Wie erstellt man eigentlich die UniqueId. Hat die ein bestimmtes Format?


Delete - Di 09.09.03 23:17

Kannst du dir selbst ausdenken. Ist ein ganz normaler String. Ich nehme meist den Namen der Anwendung und drücke dann Shift+Strg+G und habe ´ne GUID, die ich anhänge (ohne die geschweiften Klammern) - das ist eigentlich immer recht eindeutig ... um nicht einzigartig zu sagen. :D

Kurz gesagt: so gibt´s keine Probleme mit den Mutexen anderer Anwendungen.


schitho - Di 09.09.03 23:35

Cool. 8)

Die Tastenkombination Shift+Strg+G kannte ich noch nicht.

Nochmals Danke!


Delete - Mi 10.09.03 08:25

Ach so, damit nicht wieder einer kommt und sagt: "Hey, du musst solche Sachen auch für weniger versierte Leute erwähnen" ... :wink: ...

Es ist hoffentlich klar, dass die gewählte Bezeichnung in jeder Version deines Programms immer identisch bleiben muss. Will sagen: du hast jetzt die Version 1.0 von deinem Programm, dann schreibst du die 1.1 -> 1.5 -> 2.0 -> ...

Jede neue Version sollte den selben Mutex und am besten auch den gleichen Klassennamen (= Form-Name in dem Fall) verwenden. Ich benutze solche Spielereien eigentlich hauptsächlich, um das bereits geladene Fenster in den Vordergrund zu holen, wenn jemand versucht, das selbe Programm noch mal zu starten. Und dazu brauchst du ja den Mutex (zum Herausfinden, ob das Programm schon läuft) und das Fenster-Handle (zum in den Vordergrund holen).

Oder ich habe mal ein Konkurrenzprogramm damit ausgeschaltet und gehässigerweise dessen Mutex zusätzlich zu meinem registriert, wodurch es nicht mehr gestartet werden konnte. :twisted:

Na ja, das übliche eben ... *g*


webrage - Mi 10.09.03 09:44
Titel: lol
so was freches ...