2013-09-27 8 views
7

In Dephi ich einen Thread erstellen, wie diese, die Nachricht an Hauptform von Zeit zu Zeit senden wirdSenden String-Daten von Themen zur Hauptform

Procedure TMyThread.SendLog(I: Integer); 
Var 
    Log: array[0..255] of Char; 
Begin 
    strcopy(@Log,PChar('Log: current stag is ' + IntToStr(I))); 
    PostMessage(Form1.Handle,WM_UPDATEDATA,Integer(PChar(@Log)),0); 
End; 

procedure TMyThread.Execute; 
var 
    I: Integer; 
begin 
    for I := 0 to 1024 * 65536 do 
    begin 
    if (I mod 65536) == 0 then 
    begin 
     SendLog(I); 
    End; 
    End; 
end; 

wo WM_UPDATEDATA ist eine benutzerdefinierte Nachricht, wie unten definiert:

const 
    WM_UPDATEDATA = WM_USER + 100; 

Und in Haupt-Form wird es tun, wie die Liste zu aktualisieren folgt:

procedure TForm1.WMUpdateData(var msg : TMessage); 
begin 
    List1.Items.Add(PChar(msg.WParam)); 
end; 

jedoch als Log, Die an das Hauptformular gesendete Zeichenfolge ist eine lokale Variable, die nach dem Aufruf von SendLog gelöscht wird. Während TForm1.WMUpdateData die Nachricht asynchron verarbeitet, ist es möglich, dass die Protokollzeichenfolge beim Aufruf bereits zerstört wurde. Wie man dieses Problem löst?

Ich denke vielleicht kann ich den Zeichenfolgenraum in einem globalen Systemraum zuordnen und dann an die Nachricht übergeben, dann, nachdem TForm1.WMUpdateData die Nachricht verarbeitet, kann es den Zeichenfolgenraum im globalen Raum zerstören. Ist das eine praktikable Lösung? Wie implementiert man das?

Danke

+1

Schauen Sie hier http://stackoverflow.com/questions/9932164/postmessage-lparam-truncation Hoffentlich hilft es – Arkady

+0

Sie benötigen eine Log-Variable als globale Variable deklarieren. –

+0

@ S.MAHDI Nein! Was passiert, wenn zwei Nachrichten in der Warteschlange stehen? –

Antwort

0

Verwenden Sie SendMessage().

PostMessage() wird Ihre Nachricht asynchron verarbeiten, im Grunde legt sie in die Ziel-Nachrichtenwarteschlange und kehrt sofort zurück. Zu dem Zeitpunkt, an dem der Handler-Code auf die in wparam/lparam gesendeten Daten zugreift, hat Ihr Aufrufer die Zeichenfolge bereits freigegeben.

Im Gegensatz dazu umgeht SendMessage() die Nachrichtenwarteschlange und ruft das Fenster proc direkt (synchron) auf. Zu dem Zeitpunkt, zu dem SendMessage() zurückkehrt, ist es sicher, die Zeichenfolge freizugeben.

+0

Ich kann hinzufügen, es ist nicht die einzige Lösung und möglicherweise nicht in allen Fällen geeignet. Ihr spezifischer Fall. In einigen Fällen kann die Verwendung von Synchonize() eine andere zu berücksichtigende Option sein. Es hängt grundsätzlich davon ab, wie streng Sie die beiden Threads miteinander verbinden (oder sollten). Der lostest-Ansatz wäre ein refcounted-Helper-Objekt, das ausschließlich für den Datenaustausch verwendet wird. Der Refcount stellt sicher, dass jeder Thread frei ist, um ihn zuerst freizugeben, ohne den anderen zu beeinflussen. Natürlich benötigt das Hilfsobjekt eine Sperre oder einen anderen Mechanismus, um die Nebenläufigkeit zu steuern. Und es gibt noch mehr mögliche Lösungen ... – JensG

+0

Die Leute wählen PostMessage, weil sie eine asynchrone Zustellung wünschen. Sie ignorieren dies und schlagen stattdessen synchron vor. Das wird Auswirkungen auf die Leistung haben. Denn jetzt müssen die Worker-Threads darauf warten, dass der Haupt-Thread bereit ist, die Nachricht zu versenden. –

+0

Und deshalb habe ich den Kommentar hinzugefügt, dass es nicht die einzige und/oder beste Lösung ist. Sicher hast du das gelesen, nicht wahr? Ihr Argument ist nur spekulativ, obendrein. Der OP hat nirgends über seine Absichten geschrieben, warum er PostMessage wählte. – JensG

7

Zusätzlich zu der Tatsache, dass Sie eine lokale Variable veröffentlichen, ist die TWinControl.Handle-Eigenschaft auch nicht Thread-sicher. Sie sollten stattdessen die TApplication.Handle-Eigenschaft verwenden oder AllocateHWnd() verwenden, um ein eigenes Fenster zu erstellen.

Sie müssen die Zeichenfolge im Heap dynamisch zuweisen, diesen Zeiger auf den Hauptthread setzen und dann den Speicher freigeben, wenn Sie damit fertig sind.

Zum Beispiel:

procedure TForm1.FormCreate(Sender: TObject); 
begin 
    Application.OnMessage := AppMessage; 
    // or use a TApplicationEvents component... 
end; 

procedure TForm1.FormDestroy(Sender: TObject); 
begin 
    Application.OnMessage := nil; 
end; 

procedure TForm1.AppMessage(var Msg: TMsg; var Handled: Boolean); 
var 
    S: PString; 
begin 
    if Msg.Message = WM_UPDATEDATA then 
    begin 
    S := PString(msg.LParam); 
    try 
     List1.Items.Add(S^); 
    finally 
     Dispose(S); 
    end; 
    Handled := True; 
    end; 
end; 

procedure TMyThread.SendLog(I: Integer); 
var 
    Log: PString; 
begin 
    New(Log); 
    Log^ := 'Log: current stag is ' + IntToStr(I); 
    if not PostMessage(Application.Handle, WM_UPDATEDATA, 0, LPARAM(Log)) then 
    Dispose(Log); 
end; 

Alternativ:

var 
    hLogWnd: HWND = 0; 

procedure TForm1.FormCreate(Sender: TObject); 
begin 
    hLogWnd := AllocateHWnd(LogWndProc); 
end; 

procedure TForm1.FormDestroy(Sender: TObject); 
begin 
    if hLogWnd <> 0 then 
    DeallocateHWnd(hLogWnd); 
end; 

procedure TForm1.LogWndProc(var Message: TMessage); 
var 
    S: PString; 
begin 
    if Message.Msg = WM_UPDATEDATA then 
    begin 
    S := PString(msg.LParam); 
    try 
     List1.Items.Add(S^); 
    finally 
     Dispose(S); 
    end; 
    end else 
    Message.Result := DefWindowProc(hLogWnd, Message.Msg, Message.WParam, Message.LParam); 
end; 

procedure TMyThread.SendLog(I: Integer); 
var 
    Log: PString; 
begin 
    New(Log); 
    Log^ := 'Log: current stag is ' + IntToStr(I); 
    if not PostMessage(hLogWnd, WM_UPDATEDATA, 0, LPARAM(Log)) then 
    Dispose(Log); 
end; 
+3

Das Zuweisen von Daten und das Aufheben der Zuordnung basierend auf der Postnachricht ist keine Sache, die ich empfehlen würde. Ihre Nachricht wird möglicherweise nie verarbeitet, und Sie erstellen ein Speicherleck, das schwer zu erkennen ist. Wenn dieses Muster in der WINAPI nicht ein einziges Mal vorkommt, kann dies ein Hinweis darauf sein, dass das keine gute Idee ist. Zum Beispiel sind Nachrichtenwarteschlangengrößen begrenzt. Oder der Zielcode kann sich ändern und das Speicherleck einführen. Oder das Zielfenster verarbeitet möglicherweise WM_UPDATEDATA über das Standardfenster proc und so wird der Speicher nie freigegeben. Und so weiter ... – JensG

+0

@JensG Mach dir keine Sorgen. Dies ist die Hauptform der gleichen App. Der Autor der App kontrolliert die Handhabung von 'WM_UPDATEDATA'. Es ist nicht schwer zu überprüfen, dass der Speicher freigegeben ist. Und du sorgst dich um ein Speicherleck, wenn die Warteschlange deines Hauptthreads voll wird ?! Das ist die geringste Ihrer Sorgen. Meine Haupt-Thread-Nachrichtenwarteschlange ist voll, aber, OMG, ich habe einiges an Speicher durchgesickert !!!! –

+0

@David Heffernan: Wie eine Lösung, die mehrere offensichtliche Nachteile hat und trotz der Tatsache, dass es bessere, robustere Lösungen gibt, auf diese Weise begrüßt wird, übersteigt mein Verständnis. Und jeder, der jemals seltsame Speicherlecks in einer Server-App aufgespürt hat, die Tage und Monate ohne Unterbrechung laufen soll und stattdessen durch plötzliche OOMs stirbt, wird meinem Kommentar zustimmen. Du kannst einfach nicht vorsichtig genug sein. Das hast du offensichtlich nicht erlebt, sonst hättest du nicht so geredet. Aber natürlich ist es großartig für dich, also kannst du glänzen, wenn es um die Fehlersuche geht. – JensG

9

Wenn Sie Version D2009 oder höher haben, gibt es eine andere Möglichkeit, Nachrichten an Ihr Hauptformular zu senden. TThread.Queue ist ein asynchroner Aufruf von einem Thread, in dem eine Methode oder Prozedur im Hauptthread ausgeführt werden kann.

Der Vorteil hier ist, dass der Rahmen zum Einrichten der Nachrichtenübergabe weniger komplex ist. Übergeben Sie einfach Ihre Rückrufmethode beim Erstellen Ihres Threads. Keine Handles und keine explizite Behandlung der Stringzuweisung/Freigabe.

Type 
    TMyCallback = procedure(const s : String) of object; 

    TMyThread = class(TThread) 
    private 
     FCallback : TMyCallback; 
     procedure Execute; override; 
     procedure SendLog(I: Integer); 
    public 
     constructor Create(aCallback : TMyCallback); 
    end; 

constructor TMyThread.Create(aCallback: TMyCallback); 
begin 
    inherited Create(false); 
    FCallback := aCallback; 
end; 

procedure TMyThread.SendLog(I: Integer); 
begin 
    if not Assigned(FCallback) then 
    Exit; 
    Self.Queue( // Executed later in the main thread 
    procedure 
    begin 
     FCallback('Log: current stag is ' + IntToStr(I)); 
    end 
); 
end; 

procedure TMyThread.Execute; 
var 
    I: Integer; 
begin 
    for I := 0 to 1024 * 65536 do 
    begin 
    if ((I mod 65536) = 0) then 
    begin 
     SendLog(I); 
    End; 
    End; 
end; 

procedure TMyForm.TheCallback(const msg : String); 
begin 
    // Show msg 
end; 

procedure TMyForm.StartBackgroundTask(Sender : TObject); 
begin 
    ... 
    FMyThread := TMyThread.Create(TheCallback); 
    ... 
end;