Entwickler-Ecke

Grafische Benutzeroberflächen (VCL & FireMonkey) - Programm beeendet sich nicht selbst


UweK - Do 30.06.22 15:12
Titel: Programm beeendet sich nicht selbst
Hallo,

Ich habe ein Miniprogramm geschrieben, das von einem LabView-Programm eine Nachricht über einen gerade neu entstandenen Datenfile direkt an ein ebenfalls von mir geschriebenes Auswerteprogramm schickt. Das funktioniert einwandfrei. Das Miniprogramm wird von LabView mit den Kommadozeilenparametern aufgerufen, die die Information enthalten. Das Miniprogramm sendet diese Informationen per DDE an das Auswerteprogramm. Danach - soll - das Miniprogramm sich selbst gleich wieder beenden, tut es aber oft nicht. Bei mehreren aufeinander folgenden Versuchen schließt es sich manchmal, und manchmal nicht. Ob ich dabei "Exit" oder "Application.Terminate" verwende, scheint egal zu sein.

An DDE kann das nicht liegen. Selbst wenn ich die einzige Zeile mit "PokeDataLines", die wirklich etwas tut, auskommentiere, bleibt das Verhalten so. Das musss doch irgend etwas ganz Simples sein??? Kann mir jemand helfen?

Hier ist der komplette Quelltext des Miniprogramms:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
program FlexCopyStarter;

uses
  Forms,
  Main in 'Main.pas' {MainForm};

{$R *.res}

begin
  Application.Initialize;
  Application.Title := 'FlexCopyStarter';
  Application.CreateForm(TMainForm, MainForm);
  Application.Run;
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:
unit Main;

interface

uses
  Classes, Forms, Vcl.DDEMan;
type
  TMainForm = class(TForm)
    MainDDEClientConv: TDdeClientConv;
    MainDDEClientItem: TDdeClientItem;
    procedure MainFormOnShow(ASender: TObject);
  private
  public
  end{class}
var
  MainForm: TMainForm;

{$R *.DFM}

procedure TMainForm.MainFormOnShow(ASender: TObject);
var
  TempStringList: TStringList;
begin
  TempStringList:= TStringList.Create;
  try
    TempStringList.Add(ParamStr(1));
    TempStringList.Add(ParamStr(2));
    MainDDEClientConv.PokeDataLines('MainDDEServerItem', TempStringList);
  finally
    TempStringList.Free;
  end{finally}

//  Application.Terminate;

Exit;

end;

end.



Moderiert von user profile iconNarses: Topic aus Delphi Language (Object-Pascal) / CLX verschoben am Di 05.07.2022 um 22:31


Th69 - Do 30.06.22 16:40

Wäre es nicht besser, du schließt einfach per Close das Fenster (und dann wird ja automatisch die Anwendung geschlossen)?

Macht dein Programm denn mehr als die Daten nur zu schicken (also zeigt es irgendwelche Infos an)? Weil sonst wäre wohl ein Konsolenprogramm geeigneter.


Ralf Jansen - Do 30.06.22 16:59

DDE braucht 'ne Message Loop. Ich vermute mal auch in Delphi hat eine Konsolenanwendung keine.


Th69 - Do 30.06.22 17:23

Aber wenn die Anwendung sofort im OnShow-Ereignis nach dem Senden geschlossen wird, dann wird auch keine Message-Loop mehr durchlaufen.

Dann hätte ich wohl besser "fensterlose Applikation" schreiben sollen.


Sinspin - Fr 01.07.22 13:39

Das geht nicht. Im OnShow kann man nicht schließen, egal wie.
Ich würde einen Timer auf die Form packen, gleich aktiv lassen oder im OnShow anschalten, eingestellt auf ein paar ms.
Im Timer machst du den DDE Call.
Keine Ahnung ob DDE fertig sendet oder das Programm noch ein bisschen leben muss. Sonst könntest du gleich Close (Self.Close) für das Fenster aufrufen. Dann ist auch dein Programm wieder weg.
Das Exit bewirkt nie ein Programmende, sondern immer nur das verlassen der aktuellen Procedure.


UweK - Mo 04.07.22 12:10

Danke, funktioniert. Die DDE-Übertragung bleibt in "OnShow". Danach starte ich den Timer, in dessen "OnTimer" Ereignisbehandlung nur das Schließen steht.


jaenicke - Di 05.07.22 09:35

Hast du es denn einmal ganz simpel so versucht? Denn wozu soll das Fenster gut sein, wenn du es gar nicht verwendest?

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:
program FlexCopyStarter;

uses
  System.Classes, Vcl.DDEMan;

{$R *.res}

var
  MainDDEClientConv: TDdeClientConv;
  TempStringList: TStringList;
begin
  MainDDEClientConv := TDdeClientConv.Create(nil);
  try
    TempStringList:= TStringList.Create;
    try
      TempStringList.Add(ParamStr(1));
      TempStringList.Add(ParamStr(2));
      MainDDEClientConv.PokeDataLines('MainDDEServerItem', TempStringList);
    finally
      TempStringList.Free;
    end;
  finally
    MainDDEClientConv.Free;
  end;
end.


UweK - Di 05.07.22 10:20

Zuerst: Meine gestrige Erfolgsmeldung muss ich wieder zurückziehen. Auch in der Variante mit dem Timer schließt das Fenster nicht richtig. Außerdem gibt es Verzögerungen bei der DDE-Übertragung. Mit dem erfolglosen "Close"-Versuch direkt in "MainForm.OnShow", blieb das Fenster öfters (aber nicht immer) offen, aber der übertragene Text wurde zumindest immer sofort im Server angezeigt. Mit dem "Close" im Timerereignis wird es eher schlechter. Das Fenster bleibt auch manchmal offen, aber dann wird der Text erst angezeigt, wenn ich das Client-Fenster von Hand schließe.

@Jaenicke:
Ganz ohne Fenster wäre natürlich ideal, denn dann würde das unerwünschte Aufblitzen des Fensters auch entfallen. So etwas habe ich auch schon mal probiert:


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:
program FlexCopyStarter;

uses
  System.Classes, Vcl.DDEMan;

{$R *.res}

var
  MainDDEClientConv: TDDEClientConv;
  MainDDEClientItem: TDDEClientItem;
  TempStringList: TStringList;
begin
  MainDDEClientConv:= TDDEClientConv.Create(nil);
  MainDDEClientItem:= TDDEClientItem.Create(nil);
  with MainDDEClientConv do
  begin
    DDEService:= 'FlexCopy'{Serverprogramm: "FlexCopy.exe".}
    DDETopic:= 'MainDDEServerConv'{Variablenname in "FlexCopy".}
    ConnectMode:= ddeAutomatic;
  end;
  with MainDDEClientItem do
  begin
    DDEConv:= MainDDEClientConv;
    DDEItem:= 'MainDDEServerItem'{Variablenname in "FlexCopy".}
  end;

  try
    TempStringList:= TStringList.Create;
    try
      TempStringList.Add(ParamStr(1));
      TempStringList.Add(ParamStr(2));
      MainDDEClientConv.PokeDataLines('MainDDEServerItem', TempStringList);
    finally
      TempStringList.Free;
    end;
  finally
    MainDDEClientItem.Free;
    MainDDEClientConv.Free;
  end;
end.

Aber in dieser Form ohne Fenster habe ich gar keine Kommunikation zustande bekommen, nicht einmal das automatische Starten des Servers durch "ConnectMode:= ddeAutomatic" passierte. Mit dem Fenster gehr es aber. So ganz erschließt sich mir auch nicht, was in der Version mit Fenster und den aus der Palette hineingezogenen DDE-Komponenten der Objektinspektor im Hintergrund tut. Die entsprechenden Werte kann ich dort nur eintragen, während der Server-Exefile läuft. Dann klappt es aber. Die vorliegende Version ohne Fenster habe ich sicherheitshalber auch bei laufendem Server compiliert, aber das bringt auch keine Verbesserung.


jasocul - Di 05.07.22 10:32

Wenn du gar keine Form brauchst, dann musst die Aktionen aus dem FormShow in eine Prozedur verlagern. Anschließend veränderst du den Source in der dpr-Datei wie folgt:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
program FlexCopyStarter;

uses
  Forms,
  Main in 'Main.pas' {MainForm};

{$R *.res}

begin
  Application.Initialize;
  Application.Title := 'FlexCopyStarter';
  Application.CreateForm(TMainForm, MainForm);
  Application.ShowMainForm := False; // Neu
  MainForm.DeineNeueProzedur; // Neu
//  Application.Run; // Muss dann weg
end.

Da ich gerade kein Delphi zur Verfügung habe, hoffe ich, dass ich nichts vergessen habe. Ist schon ein paar Jahre her, dass ich sowas gebraucht habe. :wink:

Man kann das auch über eine Konsolenanwendung lösen, aber so solltest du den wenigsten Aufwand haben.


Th69 - Di 05.07.22 11:04

Hallo zusammen,

user profile iconRalf Jansen hat folgendes geschrieben Zum zitierten Posting springen:
DDE braucht 'ne Message Loop. Ich vermute mal auch in Delphi hat eine Konsolenanwendung keine.

Ohne Application.Run wird aber auch keine Message Loop ausgeführt (und daher klappt es wohl auch in einer Konsolenanwendung nicht).

@UweK: Um kein Fenster anzuzeigen, kannst du aber

Delphi-Quelltext
1:
Application.ShowMainForm := False;                    

übernehmen (das meinte ich auch mit "fensterloser Applikation").

Welches Intervall hast du denn beim Timer eingestellt (unter 15ms macht keinen Sinn, am besten einige Hundert ms oder sogar einige Sekunden)?
Denn wenn die Anwendung zu schnell automatisch geschlossen wird, dann kann ja auch keine DDE-Message mehr übermittelt werden.

Vllt. ist es aber auch so, daß gerade noch nicht versendete Messages verhindern, daß die Form (und damit die Applikation) automatisch geschlossen werden?!


Sinspin - Di 05.07.22 11:18

Ich hatte nicht umsonst geschrieben das die Arbeit im Timer gemacht werden soll und nicht im OnShow!


UweK - Di 05.07.22 12:02

Hallo allerseits,
Hier der aktuelle Zwischenstand:

@jasocul:
Deine Variante führt die Übertragung aus, aber läuft danach auf einen Laufzeitfehler "216" (Zugriffsverletzung).

@Th69:
Einfügen dieser Zeile bei sonst unverändertem Programm hat keine Wirkung. Das Fenster öffnet sich trotzdem und bleibt dann stehen, bis ich es von Hand schließe. Die Übertragung bleibt ebenso hängen wie ohne diese Zeile. Der übertragene Text wird im Server erst angezeigt, wenn ich das Fenster von hand schließe.

Dieser Verzögerungseffekt scheint nur daran zu liegen, dass ich das "Close" jetzt in "OnTimer" verlagert habe. In der früheren Version mit dem "Close" in "MainForm.OnShow" kam der Text immer sofort an.

@Sinspin:
Zur Abkürzung habe ich jetzt mal "die Arbeit" in eine separate Prozedur verpackt, um sie bequemer an verschiedene Stellen verschieben zu können, und um auch die Quelltexte in unserer weiteren Unterhaltung zu kürzen. Diese Einheit in sich funktioniert, wenn sie im TForm steht und damit auf die DDE-Komponenten aus der Komponentenliste zugreift. Sie funktioniert (wie ich bereits vorher schrieb) aber nicht mehr in der Version von jaenicke, wenn die DDE-Komponenten ganz ohne TForm von Hand erzeugt werden:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
procedure TMainForm.DoIt;
var
  TempStringList: TStringList;
begin
  TempStringList:= TStringList.Create;
  try
    TempStringList.Add(ParamStr(1));
    TempStringList.Add(ParamStr(2));
    MainDDEClientConv.PokeDataLines('MainDDEServerItem', TempStringList);
  finally
    TempStringList.Free;
  end{finally}
end{procedure TMainForm.DoIt}

Ob ich "DoIt" in TForm.OnShow oder in "TTimer.OnTimer" ausführe, scheint ebenfalls keinen Unterschied zu machen.


Th69 - Di 05.07.22 12:15

Hast du schon in die Doku dazu geschaut: Applikation.ShowMainForm [https://docwiki.embarcadero.com/Libraries/Sydney/de/Vcl.Forms.TApplication.ShowMainForm] ?
Zitat:
Um das Hauptformular beim Start der Anwendung auszublenden, setzen Sie ShowMainForm vor dem Aufruf der Methode Run in der Haupt-Projektdatei auf false und stellen sicher, dass die Eigenschaft Visible des Formulars ebenfalls den Wert false hat.
(Hervorhebungen von mir)
Dann wird aber selbstverständlich auch nicht mehr OnShow aufgerufen (aber es gibt ja noch andere Möglichkeiten: Konstruktor, OnCreate).

Und wie ist jetzt der Wert vom Timer.Interval?


jasocul - Di 05.07.22 13:33

user profile iconUweK hat folgendes geschrieben Zum zitierten Posting springen:

@jasocul:
Deine Variante führt die Übertragung aus, aber läuft danach auf einen Laufzeitfehler "216" (Zugriffsverletzung).

216 bedeutet, dass irgendwas noch nicht freigegeben ist.
Vielleicht genügt es in dem Fall, wenn du nach deinem DoIt noch MainForm.Free machst. Das sollte eigentlich automatisch beim Beenden des Programms erfolgen, wenn ich mich richtig erinnere. Kannst du ja mal ausprobieren. Eventuell hast du ja noch was in der Form, das nur verzögert freigegeben werden kann. Das könnte eventuell dann auch erklären, warum deine eigene Programmvariante mal beendet wird und manchmal nicht.


Narses - Di 05.07.22 22:28

Moin zusammen!

Jetzt mal allen ernstes: warum eine VCL-Anwendung schreiben, wenn man doch gar keine will?! :zwinker:

Wenn ich das richtig verstehe, dann ist die "Minianwendung" lediglich ein "Adapter", um einer anderen Anwendung vom gleichen Entwickler eine Nachricht zukommen zu lassen, weil die Hostanwendung (LabView?) offensichtlich nix anderes kann, als einen Prozess mit Parametern zu starten.

Wie wäre es denn mit einem anderen IPC-Ansatz, wenn DDE nicht der passende ist? Windows-Message registrieren und an die "große" Anwendung senden? TCP-Socket aufmachen und darüber IPC abwickeln? Das dürfte noch nicht vollständig sein, was da geht... :idea:

cu
Narses


UweK - Mi 06.07.22 14:21

@Narses:
Genau das ist der Zweck dieser Mini-Anwendung. Das Problem war dabei aber nicht DDE an sich, sondern der Ablauf 1. "Irgendwie" die Botschaft übertragen (egal ob DDE, Windows Message oder ...) UND 2. Die Mini-Anwendung sofort von selbst wieder beenden. Entweder das Eine oder das Andere ging bei den verschiedenen Versionen nicht, obwohl es nach aller Logik eigentlich hätte gehen müssen.

@Alle:
Nun geht es. Vielen Dank für eure verschiedenen Tipps! Daraus konnte ich jetzt das unten komplett abgedruckte Programm zusammensetzen. Das funktioniert aber nur mit dieser Aufteilung der Funktionen zwischen Projektdatei und TForm. Erzeugte ich die DDE-Komponenten von Hand in der Projektdatei mit denselben Einstellungen wie im Objektinspektor und ließ dafür das eigentlich überflüssige TForm weg, kam keine Kommunikation mit dem Server zustande. Startete ich den TTimer in "MainFormOnShow", wurde die Anwendung in "MainTimerOnTimer" nicht geschlossen. Warum? Das muss ich in meiner Gehaltsklasse vielleicht gar nicht verstehen...

Uwe


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:
program FlexCopyStarter;
uses
  Forms,
  Main in 'Main.pas' {MainForm};
{$R *.res}

begin
  Application.Initialize;
  Application.Title := 'FlexCopyStarter';
  Application.CreateForm(TMainForm, MainForm);
  Application.ShowMainForm:= false;
  MainForm.MainTimer.Interval:= 1000;
  MainForm.MainTimer.Enabled:= true;
  Application.Run;
end.

unit Main;
interface
uses
  Classes, Forms, ExtCtrls, DDEMan;
type
  TMainForm = class(TForm)
    MainTimer: TTimer;
    MainDDEClientConv: TDdeClientConv;
    MainDDEClientItem: TDdeClientItem;
    procedure MainTimerOnTimer(ASender: TObject);
  private
  public
  end;
var
  MainForm: TMainForm;

implementation
{$R *.DFM}

procedure TMainForm.MainTimerOnTimer(ASender: TObject);
var
  TempStringList: TStringList;
begin
  MainTimer.Enabled:= false;
  TempStringList:= TStringList.Create;
  try
    TempStringList.Add(ParamStr(1));
    TempStringList.Add(ParamStr(2));
    MainDDEClientConv.PokeDataLines('MainDDEServerItem', TempStringList);
  finally
    TempStringList.Free;
  end;
  Close;
end;

end.


Th69 - Mi 06.07.22 16:04

Der Code sieht ja jetzt auch gut aus. :zustimm:

Einzig das Setzen der MainForm.Timer-Eigenschaften solltest du in den Konstruktor/OnCreate packen (bzw. gleich im Form-Designer setzen).