Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   Delphi In Konsolenanw. auf Firebird-Events reagieren? (https://www.delphipraxis.net/171974-konsolenanw-auf-firebird-events-reagieren.html)

RSE 5. Dez 2012 08:30

In Konsolenanw. auf Firebird-Events reagieren?
 
Hallo,

ich möchte eine kleine Konsolenanwendung schreiben, die auf Firebird-Events wartet und ein Script ausführt, wenn der Event eintritt. Als DB-Kompos verwende ich TIBCConnection und TIBCAlerter der IBDAC Komponentensammlung. In meiner GUI-Anwendung kann ich damit problemlos die Events empfangen, in der Konsolenanwendung nicht. Durch einen Blick in meine magische Glaskugel bin ich darauf gekommen, dass das daran liegen könnte, dass Konsolenanwendungen keine Messages empfangen.
  1. Könnte das der Grund sein?
  2. Wie könnte man das Problem lösen/umgehen (Ansatz reicht mir)
Natürlich könnte ich das einfach in eine GUI-App packen, aber ist das wirklich notwendig?

DeddyH 5. Dez 2012 08:54

AW: In Konsolenanw. auf Firebird-Events reagieren?
 
Ohne jetzt groß nachgedacht zu haben: kannst Du Dir nicht mit Delphi-Referenz durchsuchenAllocateHWnd einfach ein Botschaftsfenster erzeugen?

RSE 5. Dez 2012 11:05

AW: In Konsolenanw. auf Firebird-Events reagieren?
 
Könnt ich schon, aber wie soll mein Window dafür sorgen, dass der TIBCAlerter Messages empfängt? Ich weiß ja nicht mal was der als "Antwortadresse" angibt. Der Alerter registriert sich ja für Messages beim Firebird-Server, das heißt, dass der seine Antworten an irgendwen schicken muss. Ich halte es für unwahrscheinlich, dass der Alerter mein Fenster als "Antwortadresse" angibt.

Wenn man davon ausgeht, dass der Alerter sich die Messages an den aktuellen Thread kommen lässt, dann könnte ich eine Message-Queue für den Thread einrichten, indem ich einmal PeekMessage aufrufe. Aber wie bekomme ich dann die Message(s) an den Alerter? Da muss ich sicherlich irgendwas mit Dispatch aufrufen. Aber so konkret kenn ich mich damit nicht aus. Ich werde mal nach dem Mittag in den Quellen von TApplication stöbern und etwas experimentieren.

RSE 5. Dez 2012 12:28

AW: In Konsolenanw. auf Firebird-Events reagieren?
 
Also mit folgendem Konstrukt empfange ich schon mal den Event:
Delphi-Quellcode:
  repeat
    if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
    begin
      DispatchMessage(Msg);
    end
    else
      Sleep(0); // Rest der Zeitscheibe verwerfen
  until False;
Jetzt muss nur noch ein Abbruchkriterium her, damit ich nicht nur die Möglichkeit habe die Konsole zu schließen... Dazu werde ich mir jetzt http://msdn.microsoft.com/en-us/libr...=vs.85%29.aspx ansehen.

RSE 5. Dez 2012 13:02

AW: In Konsolenanw. auf Firebird-Events reagieren?
 
Wenn ich die Schleife folgendermaßen erweitere, dann kann ich das Programm zwar sauber beenden, aber dann bekomme ich die Events nicht mehr:
Delphi-Quellcode:
  repeat
    if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
      DispatchMessage(Msg)
    else
      if ReadConsole(GetStdHandle(STD_INPUT_HANDLE), @Key, 1, NumRead, nil) and
         (NumRead > 0) then
        Exit
      else
        Sleep(0); // Rest der Zeitscheibe verwerfen
  until False;
Kommentiere ich die Erweiterung wieder aus, dann bekomme ich die Events wieder. Was hat das eine mit dem anderen zu tun? :shock:

RSE 5. Dez 2012 14:49

AW: In Konsolenanw. auf Firebird-Events reagieren?
 
ReadConsole hat tatsächlich gewartet, bis eine Eingabe erfolgt, bevor es zurückkehrt. Damit hat es natürlich den restlichen Programmablauf blockiert...
Die funktionierende Lösung ist die folgende:
Delphi-Quellcode:
procedure ProcessMessages;
var
  SIN: Cardinal;
  Num: Cardinal;
  IR: TInputRecord;
begin
  SIN := GetStdHandle(STD_INPUT_HANDLE);
  repeat
    if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
      DispatchMessage(Msg)
    else
    begin
      while PeekConsoleInput(SIN, IR, 1, Num) and (Num > 0) do
        if (IR.EventType = KEY_EVENT) and (IR.Event.KeyEvent.bKeyDown) then
          Exit
        else
          FlushConsoleInputBuffer(SIN); // sonst wird immer wieder der gleiche Puffer getestet und nie geleert;
      Sleep(0); // Rest der Zeitscheibe verwerfen
    end;
  until False;
end;

Medium 5. Dez 2012 15:46

AW: In Konsolenanw. auf Firebird-Events reagieren?
 
Manchmal hilft es einem schon, wenn man seine Ideen nur schriftlich dokumentiert :)

Danke, dass du dies hier getan hast! Sicherlich interessant für einige, und verallgemeinert könnte man daraus ggf. sogar einen schönen Code-Lib Eintrag machen: "Windows Messages in Konsolenanwendungen verarbeiten" o.ä.

RSE 5. Dez 2012 16:46

AW: In Konsolenanw. auf Firebird-Events reagieren?
 
Zitat:

Zitat von Medium (Beitrag 1194468)
Manchmal hilft es einem schon, wenn man seine Ideen nur schriftlich dokumentiert :)

Sehr richtig. Ich hatte schon Angst, dass jemand etwa sagen würde, weil ich mir ständig selbst geantwortet habe... :roll: Ich danke dir deshalb sehr für deinen Post! :-D

tsteinmaurer 5. Dez 2012 16:55

AW: In Konsolenanw. auf Firebird-Events reagieren?
 
Ich mach das in einer Konsolenanwendung mit IBObjects einfach durch Attachen eines eigenes Event-Handlers für das entsprechende OnEventAlert Event der EventAlerter Komponente. Kann nicht sagen, ob das auch mit IBDAC funktionieren würde.

RSE 5. Dez 2012 17:43

AW: In Konsolenanw. auf Firebird-Events reagieren?
 
@tsteinmaurer: Wenn du meinen ersten Beitrag aufmerksam gelesen hättest, wüsstest du, dass ich genau das gleiche mit den IBDACs mache. Nur kommt der Event eben durch eine Windows-Message in dein Programm, und wenn die nie bei der Komponente ankommt, kann die den Event nicht auslösen. Und da eine Konsolenanwendung nicht von Haus aus eine Message-Schleife hat, musste ich die selbst bauen. Das wird bei dir mit den IBOs nicht anders laufen.

tsteinmaurer 5. Dez 2012 19:46

AW: In Konsolenanw. auf Firebird-Events reagieren?
 
Klappt auch mit IBDAC.

Delphi-Quellcode:
program IBDACEventConsoleTest;

{$APPTYPE CONSOLE}

uses
  SysUtils, IBC, IBCAlerter, Forms, Types, Windows;

type
  TMyEventHandler = class
  public
    procedure DoEventAlert(Sender: TObject; AEventName: string; AEventCount: Longint);
  end;

var
  conn: TIBCConnection;
  alerter: TIBCAlerter;
  isShutdown: Boolean;
  eventHandler: TMyEventHandler;

function ConsoleEventProc(CtrlType : DWord) : Bool; stdcall; far;
begin
  case CtrlType of
    CTRL_C_EVENT,
    CTRL_BREAK_EVENT,
    CTRL_CLOSE_EVENT,
    CTRL_LOGOFF_EVENT,
    CTRL_SHUTDOWN_EVENT: isShutdown := True;
  end;
  Result := True;
end;

{ TMyEventHandler }

procedure TMyEventHandler.DoEventAlert(Sender: TObject; AEventName: string; AEventCount: Integer);
begin
  Writeln(Format('Event: %s received. Count: %d', [AEventName, AEventCount]));
end;

begin
  SetConsoleCtrlHandler(@ConsoleEventProc, True);
  try
    eventHandler := TMyEventHandler.Create;
    try
      conn := TIBCConnection.Create(nil);
      try
        conn.LoginPrompt := False;
        conn.Database := 'localhost/3051:tourism.fdb';
        conn.Username := 'tourism';
        conn.Password := 'tourism';
        conn.Connect;
        alerter := TIBCAlerter.Create(nil);
        try
          alerter.OnEvent := eventHandler.DoEventAlert;
          alerter.Connection := conn;
          alerter.Events.Add('Event1');
          alerter.Active := True;
          isShutdown := False;
          while not isShutdown do
          begin
            Application.HandleMessage;
          end;
        finally
          alerter.Active := False;
          alerter.Free;
        end;
      finally
        conn.Disconnect;
        conn.Free;
      end;
    finally
      eventHandler.Free;
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.
Dann noch in PSQL den Event feuern und nicht vergessen zu committen.

Code:
set term !! ;
execute block
as
begin
  post_event('Event1');
end
!!
set term ; !!

commit;
Ich bekomme in der Konsole dann den entsprechenden Output aus meinem Event-Handler. Ob das jetzt hingepfuscht oder sauber ist, können uns die echten Delphi-Geeks hier sagen. :thumb:

RSE 6. Dez 2012 09:09

AW: In Konsolenanw. auf Firebird-Events reagieren?
 
Ich habe gar keine Unit Forms benutzt, zumindest steht es nicht in MEINER Uses-Liste. Habe ich trotzdem ein Application-Objekt?

Ich habe dein ConsoleEventProc übernommen, diese Möglichkeit war mir noch unbekannt. So kann ich jetzt wenigstens auch darauf reagieren, dass einfach die Konsole geschlossen wird. Allerdings verstehe ich den Ablauf noch nicht ganz. ConsoleEventProc sieht bei mir genauso aus wie bei dir. Wenn isShutdown True ist, beende ich meine Aufgaben und verlasse das Programm. Wenn ich allerdings durchdebugge, dann bricht die Ausführung kurz nach (nicht bei!) Beendigung von ConsoleEventProc ab. Den Grund dafür kann ich mir nicht erklären. Die Finallys im Haupt-Begin-End-Block werden (zumindest beim Debuggen) nicht durchlaufen. Ich rufe auch nirgends Halt auf.

Ich will quasi das Beenden der Konsole (Beenden meines Prozesses von außen) unterbinden, um meinen Prozess selbst sauber beenden zu können. Ich muss dabei ggf. auch auf die Beendung des externen Prozesses warten (Ausführung des vom Firebird-Event durch mein Programm getriggerte Batch-Skript). Sind das Schwierigkeiten, die ich mit der Verwendung von Forms und TApplication verhindern könnte? Um das zu testen müsst ich das halbe Prog umschreiben...

RSE 7. Dez 2012 10:36

AW: In Konsolenanw. auf Firebird-Events reagieren?
 
Lösung: Die Prozedur ConsoleEventProc, die mit SetConsoleCtrlHandler registriert wird, läuft asynchron zum Programm in einem anderen Thread. Wenn diese Prozedur beendet wird, wird das Programm "von außen" beendet, sobald offenbar ein weiterer Thread wieder rechenzeit bekommt. Jedenfalls werden nach Beendigung der Prozedur ConsoleEventProc noch ein paar wenige Befehle ausgeführt, bevor das Programm unkontrolliert abbricht. Abhilfe: ConsoleEventProc nie beenden. Damit hat man zumindest bis zum Timeout Zeit, das Programm ordentlich zu beenden.
Delphi-Quellcode:
function ConsoleEventProc(CtrlType: DWord): Bool; stdcall; far;
begin
  // Diese Prozedur läuft asynchron zum Programm in einem eigenen Thread
  case CtrlType of
    CTRL_C_EVENT,
    CTRL_BREAK_EVENT,
    CTRL_LOGOFF_EVENT,
    CTRL_SHUTDOWN_EVENT,
    CTRL_CLOSE_EVENT:
      begin
        // Globale Variable setzen, die regelmäßig abgefragt werden muss
        Terminated := True;
        WriteLn('Shutdown Request received');
        // unendlich warten, um das Programm ordentlich beenden zu können
        // Die Unendlichkeit ist durch einen Windows-Timeout begrenzt
        Sleep(INFINITE);
        // Schließt die Konsole und bricht damit das Programm "von außen" ab
        Result := True;
      end;
    else
      Result := False; // Standard-Handler aufrufen
  end;
end;
Application ändert an der ganzen Geschichte übrigens nichts, also hab ich die Unit Forms wieder rausgeschmissen und nutze meine eigene Message-Schleife weiter.

mjustin 7. Dez 2012 11:02

AW: In Konsolenanw. auf Firebird-Events reagieren?
 
Zitat:

Zitat von RSE (Beitrag 1194636)
Abhilfe: ConsoleEventProc nie beenden. Damit hat man zumindest bis zum Timeout Zeit, das Programm ordentlich zu beenden.

Einige Informationen dazu habe ich hier auf Stackoverflow gefunden:

http://stackoverflow.com/questions/3...-routine-issue

Selbst wenn man eine Endlosschleife einbaut, hat man ab Windows 7 nur zehn Sekunden Zeit (ab dem Eintreten des Ctrl Close Events) für den Prgrammabschluss, dann wird das Programm "abgeschossen".

RSE 7. Dez 2012 13:36

AW: In Konsolenanw. auf Firebird-Events reagieren?
 
Man hat 5 Sekunden für CTRL_CLOSE_EVENT und 20 für CTRL_LOGOFF_EVENT und CTRL_SHUTDOWN_EVENT.

http://www.codeproject.com/Articles/...Event-Handling

Auf diesen Timeout habe ich auch im Kommentar in meinem Quelltext hingewiesen. Beziffert habe ich ihn nicht, da ich weder verlässliche Quellen (Microsoft-Dokument) noch Hinweise auf Unterschiede in verschiedenen Windows-Versionen gefunden habe.


Alle Zeitangaben in WEZ +1. Es ist jetzt 08:40 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz