Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - HWND von Mainthread?


Kay E. - Fr 28.01.11 03:49
Titel: HWND von Mainthread?
Hallo!

ICh habe ein Programm, dass einen Thread startet. Diesem Thread will ich das Handle zum Mainthread mitgeben, damit der Nebenthread mit SendMessage/PostMessage Nachrichten an den Mainthread schicken kann.
Ich hab mir nen Wolf gesucht und ausprobiert, was mir eingefallen ist, aber ich komm nicht (sinnvoll) an das Handle. Mach ich die Initialisierung so

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
ThreadObj := MyThread.Create(True);
with ThreadObj do
begin
  FMainHwnd := Handle;
  Start;
end;

dann hat FMainHwnd immer den Wert 276;
Nehm ich statt Handle ClientHandle ist FMainHwnd immer 0;

Nur wenn ich über FindWindow nach dem Mainthread such, bekomm ich einen funktionierenden Handle auf den Mainthread. Das kann ja aber nicht die Krönung aller Lösungen sein. Spätestens bei 2 Instanzen meines Programms gehts schief.
In der Hilfe steht, dass Application.CreateHandle das Mainfenster aufbaut (sofern noch nicht existent) und den Wert des Handles in "Handle" schreibt. Davon merk ich nur leider nix. Aber davon ausgehend müsste Handle doch richtig sein.
Wie komm ich denn nun an den (eigentlich) wichtigsten Handle im kompletten Programm? Für Tipps bin ich wie immer sehr dankbar!

Grüße Kay


jaenicke - Fr 28.01.11 06:51

Also erstens solltest du den Thread gar nicht erst pausieren. Erstelle lieber einen eigenen Konstruktor, der die entsprechenden Parameter bekommt.

Delphi-Quelltext
1:
2:
3:
4:
5:
constructor Create(ATargetHandle: HWND);
begin
  FTargetHandle := ATargetHandle;
  inherited Create(False);
end;
Grundsätzlich geht es natürlich theoretisch auch so wie du es machst, aber dann musst du sehr aufpassen was du tust. Einfacher ist es so wie ich es geschrieben habe, weil so im Grunde nicht viel schiefgehen kann.

Ja, das Handle, das du benutzt, ist das deines Formulars, wenn du den geposteten Code in einer Formularmethode zu stehen hast. Wenn es das ist was du willst, dann ist das so richtig.
Bei mir hat das allerdings immer andere und deutlich größere Werte.

So aus dem Zusammenhang gerissen (also wo der Code eigentlich steht), kann aber hier auch niemand wissen woher Handle in dem Fall eigentlich kommt.


elundril - Fr 28.01.11 11:19

Außerdem sin diese with-konstrukte mMn mehr fehlerquelle als wirklich sinnvoll. Da schreib ich lieber ein paar mal mehr den objektnamen und weiß dann dafür von wo das feld bzw die variable herkommt.


jaenicke - Fr 28.01.11 11:28

Vor allem funktioniert ohne with das Debuggen ordentlich.


Kay E. - Fr 28.01.11 13:04

Ok, wenn ihr mehr Kontext, sollt ihr ihn bekommen :) Das hier ist ein kleiner Bildschirmtimer, den ich auf Threads umgebaut hab. Das eigentliche Projekt ist deutlich größer, aber das Prinzip bleibt das selbe.


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:
unit Main;  //MainThread

interface

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

const
  WM_USERTICK = WM_USER + 0;

type
  TForm1 = class(TForm)
//...
  private
    { Private declarations }
    FTime: Integer;
    TimerThreadObj: TTimerThread;
    procedure MyStrToTime;
    procedure WMUSERTICK(var msg: TMessage); message WM_USERTICK;
    function MyTimeToStr(vTime: Integer): string;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.StartButtonClick(Sender: TObject);
begin
  MyStrToTime;  //Berechnet die Zeit aus mehreren EditFeldern zusammengesetzte Zeit, speichert Ergebnis in FTime
  TimeLabel.Caption := MyTimeToStr(FTime); 
  TimeLabel.Visible := true;
  MinsEdit.SetFocus;

  if assigned(TimerThreadObj) then
    TimerThreadObj.Terminate;

  TimerThreadObj := TTimerThread.Create(True);
  with TimerThreadObj do
  begin
    FMainThreadHandle := Handle;
    FreeOnTerminate := True;
    Start;
  end;
end;

procedure TForm1.WMUSERTICK(var msg: TMessage);
begin
  dec(FTime);  //Ftime wird in Sekunden gespeichert
  TimeLabel.Caption := MyTimeToStr(FTime);

  if FTime <= 0 then
  begin
    TimerThreadObj.Terminate;
    Form1.BringToFront;
    Form1.FormStyle := fsStayOnTop;
    ShowMessagePos('Countdown abgelaufen', Form1.Left, Form1.Top);
    Form1.FormStyle := fsNormal;
    TimeLabel.Visible := false;
  end;
end;

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:
unit TimerThread; //Thread-Unit

interface

uses
  Classes, Windows;

type
  TTimerThread = class(TThread)
  protected
    procedure Execute; override;
  public
    FMainThreadHandle: HWnd;
  end;

implementation

uses Main;

procedure TTimerThread.Execute;
var
  vHwnd: Hwnd;
begin
  vHwnd := FindWindow('TForm1''Bildschirm Timer'); //Liefert den richtigen Handle; 6-7 stellige Zahl
  while not Terminated do
  begin
    sleep(1000);
   {PostMessage(FMainThreadHandle, WM_USERTICK, 0, 0);} // FMainThreadHandle hat immer den Wert 276

    PostMessage(vHwnd, WM_USERTICK, 00);
  end;
end;

end.


Was die eigene Create-Methode angeht: Das ist im großen Programm ein mächtiger Packen Daten, den ich da übergeben muss. Wenn ich das alles in den Parameter von Create schreib... Und aus dem Thread heraus auf die Variablen zugreifen wollte ich eigentlich vermeiden, da der Thread autonom lauffähig sein sollte.
Das mit der With-Anweisung stimmt, aber in dem Fall ist es nicht weiter schlimm, weil TThread kein "Handle" besitzt und somit sicher auf Form1 zugegriffen wird. Aber ich werds im Hinterkopf behalten. Danke!

Grüße Kay


Kay E. - Fr 28.01.11 20:31

Oh mann, wie peinlich :oops:
Ihr hattet Recht mit der with-Anweisung. Das Problem besteht darin, dass TThread ja DOCH ein Handle-Property hat (muss ich mich falsch erinnert haben :\ ). Und von demher weise ich dem Thread-Feld seinen eigenen Threadhandle zu. Ohne With-Anweisung bzw. mit Referenzierer zum Mainform gehts einwandfrei.

Vielen Dank euch!

Grüße Kay


sky21 - Fr 28.01.11 20:36

Alternativ kann man auch einen eigenen Handle erzeugen:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
procedure WM_WMUSERTICK(var Msg: TMessage); // handler for incoming messages (should be public procedure of class)


constructor TMainform.Create();
begin
  FMsgHandle := AllocateHWnd(WM_WMUSERTICK); // call DeallocateHWnd() in destructor
end;


jaenicke - Fr 28.01.11 20:52

user profile iconKay E. hat folgendes geschrieben Zum zitierten Posting springen:
Ihr hattet Recht mit der with-Anweisung. Das Problem besteht darin, dass TThread ja DOCH ein Handle-Property hat (muss ich mich falsch erinnert haben :\ ).
Und genau das ist das Problem mit with. Man muss sehr genau hinschauen und findet Fehler schwer. Das ist mir ja hier auch nicht aufgefallen.

Und zudem kann man eben nicht direkt debuggen, sondern muss umständlich den kompletten Ausdruck manuell jedesmal zur Auswertung zusammenbasteln.

Und das alles nur weil man ein paar Zeichen weniger schreiben will? :lol:


Regan - Fr 28.01.11 22:02

Ich möchte hier bloß nochmal eine Lanze für das with brechen: with ist nützlich und praktisch, wenn man sich zum Beispiel innerhalb einer Klasse befindet.
AFAIK hätte sich das Problem auch über ein self lösen können.


jaenicke - Fr 28.01.11 22:39

Er ist ja innerhalb einer Klasse. Und genau durch das with hat er eben nicht mehr auf die Eigenschaft genau dieser Klasse zugegriffen.

Klar geht das auch mit Self. Nur muss man das eben auch sehen. Und genau das ist eben auch das Problem, wodurch with eben Fehler provoziert.

Und wie gesagt: Ich sehe keinen echten Vorteil an with, die paar Buchstaben wird man ja wohl noch tippen können. Ich halte with für einen der größten Fehler bei der Entwicklung von Delphi.


Kay E. - Sa 29.01.11 00:30

Ich finde with (mittlerweile *g*) nur noch bedingt gut. Es hat seine Daseinsberechtigung, solange man ausschließlich nur mit einem Objekt, und zwar mit dem der With-Anweisung, arbeitet. Zum Beispiel, wenn ich größere Sachen auf einer Canvas zeichnen will. Dann will ich nicht immer Image1.Canvas. davor schreiben, um ein MoveTo zu setzen.
Umständlich und fehleranfällig wirds dann, wenn andere Einflüsse von außen reinkommen, also wenn man zum Beispiel auf die Properties eines anderen Objekts zugreifen will. Das werd ich mir jetzt ziemlich schnell abgewöhnen, denke ich. Das hat mich jetzt einen Tag Zeit gekostet -.-

Grüße Kay


jaenicke - Sa 29.01.11 06:00

user profile iconKay E. hat folgendes geschrieben Zum zitierten Posting springen:
Dann will ich nicht immer Image1.Canvas. davor schreiben, um ein MoveTo zu setzen.

Delphi-Quelltext
1:
2:
3:
4:
5:
var
  MyCanvas: TCanvas;
begin
  MyCanvas := Image1.Canvas;
  MyCanvas.MoveTo...
Und wenn der Codeabschnitt nicht zu lang ist (ok, sollte eh nicht passieren), kann der Variablenname ja auch einfach C sein oder so. Ist immer noch übersichtlicher als with und gar nix davor. ;-)


elundril - Sa 29.01.11 07:18

bzw hat man ja eh die vervollständigungsliste mit strg+leertaste.


jaenicke - Sa 29.01.11 07:19

Oder ganz automatisch aufklappend mit den CnWizards. ;-)


delfiphan - Sa 29.01.11 10:50

Da hast du ganz Recht. SendMessage/PostMessage ist für GUI Anwendungen viel zu lowlevel. Das kann's wirklich nicht sein.

=> Gib dem Thread doch eine Referenz auf die Form und implementiere eine Dispatcher [http://msdn.microsoft.com/de-de/library/system.windows.threading.dispatcher.aspx] Klasse. Mit der kannst du dann entweder mit Invoke (synchron, wie SendMessage) oder BeginInvoke (asynchron, wie PostMessage) eine Methode auf der Form ausführen. => Am besten verwendest du anonyme Methoden, dann kannst du über Closures beliebige Methodensignaturen mit beliebigen Parameter aufrufen.

Alternativ dazu kannst du auch Synchronize bzw Queue verwenden und von dort aus den Call auf den Form machen. Parameter kannst du dann aber nicht direkt mitgeben.

Ausserdem macht dein Thread-Timer nicht viel mehr als ein ganz gewöhnlicher VCL Timer... Dafür brauchst du keine Threads.