Lazarus

Programming => General => Topic started by: Hartmut on January 06, 2019, 12:34:42 pm

Title: [CLOSED] How to call a routine when my program terminates by OS shutdown?
Post by: Hartmut on January 06, 2019, 12:34:42 pm
I have a small console program which runs in the background and has no normal end. I want this program to write something into a logfile, when it terminates, e.g. because of the operating system is shutdown. How can this be done?

I want this for Windows 7 (32 bit) and Linux (Ubuntu 18.04 64 bit). I'm using FPC 3.0.4. Thanks a lot in advance.
Title: Re: How to call a routine when my program terminates e.g. by OS shutdown?
Post by: jamie on January 06, 2019, 02:45:01 pm
I guess you could put a "Finalization" section at the bottom where you can then test the "ExitCode"....

With windows, you may want to use the "GetLastError" to see what it is..
etc.

I wanted to add, at the start of the program if you Set a Variable to a state and when "YOU" end the
program you can clear this state indicating things ended normally..

In the finalization section you test for this state because it comes after your code ends the program properly..

So

Begin
  IAmRunning := true;
 
 ///  yourcode''''

 IAmRunning := False;

Finalization
  If IamRunning then
   begin
     So someLog stuff;
End.

Title: Re: How to call a routine when my program terminates e.g. by OS shutdown?
Post by: Hartmut on January 06, 2019, 04:31:51 pm
Thank you jamie for your suggestion.
I tried it at the end of a unit, but this code was not executed when Windows 7 was shutdown.
I wanted to place it at the end of the main program, but the compiler says Error: Identifier not found "Finalization".
Do you mean to place that Finalization code at the end of a unit or at the end of the main program?

Code: Pascal  [Select][+][-]
  1. program test;
  2. begin
  3. writeln('Start');
  4.  
  5. Finalization
  6. writeln('End');
  7. end.
Title: Re: How to call a routine when my program terminates e.g. by OS shutdown?
Post by: jamie on January 06, 2019, 08:41:25 pm
I see you must be using the Main unit only...

Try this, I know it works but I have not tested it on a OS forced terminate..
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. var
  4.   OldExitProc: CodePointer;
  5.   ExitNormal: boolean;
  6.  
  7.   procedure NewExitProc;
  8.   begin
  9.     if ExitNormal then
  10.       WriteLn('Exiting propertly')
  11.     else
  12.       WriteLn('OS is killing US');
  13.     Readln;
  14.     ExitProc := OldExitProc;
  15.   end;
  16.  
  17. begin
  18.   ExitNormal := False;
  19.   OldExitProc := ExitProc;
  20.   ExitProc := @NewExitProc;
  21.   WriteLn('Test');
  22.   ReadLn;
  23.   ExitNormal := True;
  24. end.        
  25.  
Title: Re: How to call a routine when my program terminates e.g. by OS shutdown?
Post by: Hartmut on January 06, 2019, 10:32:56 pm
Thank you for your help. Will test it tomorrow and then report. Good night.
Title: Re: How to call a routine when my program terminates e.g. by OS shutdown?
Post by: engkin on January 07, 2019, 01:53:32 am
Windows:
Use SetConsoleCtrlHandler to tell the OS you need to be notified, and pass a suitable function
Code: Pascal  [Select][+][-]
  1. uses Windows
  2. ...
  3. function HandlerRoutine(dwCtrlType :DWORD):WINBOOL;stdcall;
  4. begin
  5.   { This handler will run in a different thread!! }
  6.   case dwCtrlType of
  7.   CTRL_C_EVENT,
  8.   CTRL_BREAK_EVENT,
  9.   CTRL_CLOSE_EVENT,
  10.   CTRL_LOGOFF_EVENT,
  11.   CTRL_SHUTDOWN_EVENT: {end your app} Result := True;
  12.   else
  13.     Result := False
  14.   end;
  15. end;
  16. ...
  17.   SetConsoleCtrlHandler(@HandlerRoutine, True {'True' means add handler, while 'false' means remove handler });
Title: Re: How to call a routine when my program terminates e.g. by OS shutdown?
Post by: Hartmut on January 07, 2019, 07:51:47 pm
I'm sorry jamie but it does not work. If the OS shuts down, NewExitProc is not called. I think this type of ExitProc is only called if the Program terminates "normally", e.g. by halt or ^C. This was my code:

Code: Pascal  [Select][+][-]
  1. program test;
  2.  
  3. uses sysutils;
  4.  
  5. procedure appendLogStr(s: string);
  6.    {adds string 's' to the end of Logfile 'Fspec'}
  7.    const Fspec = 'x:\test.log';
  8.    var f1: text;
  9.        e: word;
  10.    begin
  11.    assign(f1,Fspec); {$I-} append(f1); {$I+} e:=ioresult;
  12.    if e=2 then rewrite(f1)            {create new file if not exists}
  13.       else if e <> 0 then append(f1); {else raise error}
  14.    writeln(f1,s);
  15.    close(f1);
  16.    end;
  17.  
  18. var OldExitProc: CodePointer;
  19.     ExitNormal: boolean;
  20.  
  21. procedure NewExitProc;
  22.   begin
  23.   if ExitNormal then appendLogStr('Exiting propertly')
  24.                 else appendLogStr('OS is killing us');
  25.   ExitProc := OldExitProc;
  26.   end;
  27.  
  28. begin
  29. writeln('hello');
  30. appendLogStr('Start');
  31.  
  32. ExitNormal := False;
  33. OldExitProc := ExitProc;
  34. ExitProc := @NewExitProc;
  35. ReadLn;
  36. ExitNormal := True;
  37. end.
Title: Re: How to call a routine when my program terminates e.g. by OS shutdown?
Post by: Hartmut on January 07, 2019, 07:53:38 pm
Hello engkin, I can't get your suggestion working too. Maybe I make some basic mistake?
I use FPC 3.0.4 on Windows 7 Home Premium, 32 bit. I open a console window, start test.exe and then shutdown Windows via START / Shutdown.
After reboot I check my logfile. Only the "Start" message is there. Here is my code: Do you see a mistake?

Code: Pascal  [Select][+][-]
  1. program test;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses windows;
  6.  
  7. procedure appendLogStr(s: string);
  8.    {adds string 's' to the end of Logfile 'Fspec'}
  9.    const Fspec = 'x:\test.log';
  10.    var f1: text;
  11.        e: word;
  12.    begin
  13.    assign(f1,Fspec); {$I-} append(f1); {$I+} e:=ioresult;
  14.    if e=2 then rewrite(f1)            {create new file if not exists}
  15.       else if e <> 0 then append(f1); {else raise error}
  16.    writeln(f1,s);
  17.    close(f1);
  18.    end;
  19.  
  20. function HandlerRoutine(dwCtrlType: DWORD): WINBOOL; stdcall;
  21.    {This handler will run in a different thread!!}
  22.    begin
  23.    case dwCtrlType of
  24.       CTRL_LOGOFF_EVENT,
  25.       CTRL_SHUTDOWN_EVENT: begin
  26.                            appendLogStr('HandlerRoutine called');
  27.                            exit(true);
  28.                            end;
  29.       else exit(false);
  30.    end;
  31.    end;
  32.  
  33. var ok: boolean;
  34.  
  35. begin
  36. writeln('hello');
  37. appendLogStr('Start');
  38. ok:=windows.SetConsoleCtrlHandler(@HandlerRoutine, True);
  39. writeln(ok);                     {True/False = add/remove handler}
  40. ReadLn;
  41. appendLogStr('Normal End');
  42. end.
Title: Re: How to call a routine when my program terminates e.g. by OS shutdown?
Post by: ASerge on January 07, 2019, 08:49:57 pm
I use FPC 3.0.4 on Windows 7 Home Premium, 32 bit. I open a console window, start test.exe and then shutdown Windows via START / Shutdown.
After reboot I check my logfile. Only the "Start" message is there. Here is my code: Do you see a mistake?
Read the Windows 7+ remark in the Windows documentation https://docs.microsoft.com/en-us/windows/console/setconsolectrlhandler (https://docs.microsoft.com/en-us/windows/console/setconsolectrlhandler).
A console application (on FPC) by default loads the user32.dll and gdi32.dll, as a result SetConsoleCtrlHandler is not called.
In the same place indicated the solution.
Title: Re: How to call a routine when my program terminates e.g. by OS shutdown?
Post by: engkin on January 07, 2019, 09:53:23 pm
I just checked, user32.dll is used because the console app imports:
MessageBoxA
CharUpperBuffW
CharLowerBuffW

I don't know why/where, yet. Is this a bug?
Title: Re: How to call a routine when my program terminates e.g. by OS shutdown?
Post by: Hartmut on January 08, 2019, 02:17:14 pm
I tried to follow the suggestion from ASerge (thank you very much):

Read the Windows 7+ remark in the Windows documentation https://docs.microsoft.com/en-us/windows/console/setconsolectrlhandler (https://docs.microsoft.com/en-us/windows/console/setconsolectrlhandler).
A console application (on FPC) by default loads the user32.dll and gdi32.dll, as a result SetConsoleCtrlHandler is not called.
In the same place indicated the solution.

Seems that I managed to create a hidden console window:

Code: Pascal  [Select][+][-]
  1. {$mode objfpc}{$H+}
  2.  
  3. uses windows;
  4.  
  5. function createHiddenWindow: HANDLE;
  6.    {creates a hidden console application window and returns it's Handle}
  7.    const
  8.       dwExStyle = 0; {create a hidden window}
  9.       lpClassName: PChar = 'Button'; {Predefined System Class}
  10.       lpWindowName: PChar = 'hello'; {window name}
  11.       dwStyle = WS_DISABLED; {style of the window =  initially disabled}
  12.       x = 0; {initial horizontal position of the window}
  13.       y = 0; {initial vertical position of the window}
  14.       nWidth  = 1; {width of the window in device units}
  15.       nHeight = 1; {height of the window in device units}
  16.       hWndParent = 0; {handle to the parent or owner window}
  17.       hMenu = 0;      {handle to a menu to be used with the window}
  18.       hInstance = 0;  {handle to the module to be associated with the window}
  19.       lpParam = nil;  {Pointer to a value to be passed to the window}
  20.    var h: HANDLE;
  21.    begin
  22.    h:=CreateWindowExA(dwExStyle, lpClassName,lpWindowName, dwStyle, x,y,
  23.                       nWidth,nHeight, hWndParent,hMenu,hInstance, lpParam);
  24.    writeln('create: h=', h, ' GetLastError=', GetLastError);
  25.    if h=0 then halt(1);
  26.    exit(h);
  27.    end;
  28.  
  29. procedure destroyHiddenWindow(h: HANDLE);
  30.    {destroys the window with Handle 'h'}
  31.    var ok: boolean;
  32.    begin
  33.    ok:=DestroyWindow(h);
  34.    writeln('destroy: ok=', ok);
  35.    if not ok then halt(1);
  36.    end;
  37.  
  38. var h: HANDLE;
  39.  
  40. begin
  41. h:=createHiddenWindow;
  42. readln;
  43. destroyHiddenWindow(h);
  44. end.

But I stuck completely with:
Quote
... then handle the WM_QUERYENDSESSION and WM_ENDSESSION window messages that the hidden window receives ...

Must I handle both WM_QUERYENDSESSION and WM_ENDSESSION or is one of them enough for my purpose (to write something in a Logfile at OS shutdown)?
How do I manage a CALLBACK function in Free Pascal:

Code: [Select]
LRESULT CALLBACK WindowProc(
  HWND hwnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // not used
  LPARAM lParam   // logoff option
);

I'm a complete beginner to Messages and Callback functions. I read the documentation but didn't understand it. Can anybody please give an example? Thanks a lot.
Title: Re: How to call a routine when my program terminates e.g. by OS shutdown?
Post by: ASerge on January 08, 2019, 05:55:24 pm
I'm a complete beginner to Messages and Callback functions. I read the documentation but didn't understand it. Can anybody please give an example? Thanks a lot.
Then it is easier to do so:
1. Create a new Application project.
2. Remove the main form from the project.
3. Add a new data module.
4. Put the ApplicationProperties component in the data module.
5. Add code to the OnEndSession event:
Code: Pascal  [Select][+][-]
  1. procedure AppendToFile(const FileName, Data: string);
  2. var
  3.   Stream: TFileStream;
  4.   EndOfLine: string;
  5. begin
  6.   try
  7.     Stream := TFileStream.Create(FileName, fmOpenWrite);
  8.   except
  9.     on E: EFOpenError do
  10.       Stream := TFileStream.Create(FileName, fmCreate);
  11.   end;
  12.   try
  13.     Stream.Seek(0, soFromEnd);
  14.     Stream.WriteBuffer(Pointer(Data)^, Length(Data) * SizeOf(Char));
  15.     EndOfLine := LineEnding;
  16.     Stream.WriteBuffer(Pointer(EndOfLine)^, Length(EndOfLine) * SizeOf(Char));
  17.   finally
  18.     Stream.Free;
  19.   end;
  20. end;
  21.  
  22. procedure TDataModule1.ApplicationProperties1EndSession(Sender: TObject);
  23. begin
  24.   AppendToFile('.\test.log', 'Session terminated');
  25. end;
Run and test.

Yes, now it is not a small console application. It is possible to choose another solution, but we need to understand what else your application should do.
Title: Re: How to call a routine when my program terminates e.g. by OS shutdown?
Post by: Hartmut on January 08, 2019, 08:36:09 pm
I could realize the last suggestion from ASerge (thank you very much for your help) in a demo program and it works! (on Windows)

I'm not sure how difficult it would be for me to convert my console program into an "Application". I'm not familiar with Applications and don't know how to insert my code into Application.Run() and more. So if there are other solutions, I'm interested in them (especially if they work with or can be "translated" to Linux):

Yes, now it is not a small console application. It is possible to choose another solution, but we need to understand what else your application should do.

Here is what my small console application does: it is started together with the OS via AutoStart and runs all the time in the background until OS shuts down. Every 30 seconds it looks for certain programs/processes if they are running and if yes, writes this into a SQLite database.
My program can run twice at a time: once in the background (see above) and secondly in the foreground using some command line parameters to show statistics or to control or kill the background program. I have this program finished for Windows and Linux (Ubuntu). Now I want to write a message into the logfile, when the OS (Windows or Linux) shuts down.

Thanks again to all for your help.
Title: Re: How to call a routine when my program terminates e.g. by OS shutdown?
Post by: ASerge on January 10, 2019, 05:03:48 pm
Here is what my small console application does: it is started together with the OS via AutoStart and runs all the time in the background until OS shuts down. Every 30 seconds it looks for certain programs/processes if they are running and if yes, writes this into a SQLite database.
My program can run twice at a time: once in the background (see above) and secondly in the foreground using some command line parameters to show statistics or to control or kill the background program. I have this program finished for Windows and Linux (Ubuntu). Now I want to write a message into the logfile, when the OS (Windows or Linux) shuts down.
It will then be easier. Use the main form instead of the data module. For ApplicationProperties, set the ShowMainForm=False property, and show the form only when you run the program with special keys to show statistics. Modify the form for easy display of statistics. Put on it the TTimer and configure it to regularly update the information in the database. I don't have Linux at hand, but I suspect it's a cross-platform solution.
Title: Re: How to call a routine when my program terminates by OS shutdown?
Post by: Hartmut on January 11, 2019, 10:21:08 pm
Thank you very much ASerge for your new reply. Before continuing I wanted to make sure that your suggestion (from reply #11) runs also on Linux (I expected this) but unfortunately AppendToFile() is not called when Linux (Ubuntu 18.04) shuts down. Maybe I made a mistake, so I attached my little demo project, if you or someone else wants to look in.

But I found this, which sounds interesting to me: http://wiki.freepascal.org/Main_Loop_Hooks#Pipes_and_Process_Termination says (abbreviated):

Quote
Pipes and Process Termination
To provide a cross-platform solution for ... processes ... additional functions have been added:

TChildExitReason = (cerExit, cerSignal);
 
TChildExitEvent = procedure(AData: PtrInt; AReason: TChildExitReason; AInfo: dword) of object;
 
function AddProcessEventHandler(AHandle: THandle; AEventHandler: TChildExitEvent; AData: PtrInt): PProcessEventHandler;

When a process terminates the event handler specified will be called. AInfo will contain the exit code if AReason is cerExit, or (on unix only) the termination signal if AReason is cerSignal. For gtk/unix, use the PID to watch as AHandle. Internally, a signal handler is installed to catch the SIGCHLD signal. On win32, AddEventHandler is used to watch process termination.

Could this be (another) solution to my problem? I found no documentation for AddProcessEventHandler(), so I can't estimate whether with this function I can write something into a logfile when the OS shuts down. Is there anybody who can tell this? If yes, where do I get the 'AHandle' from which I must pass to AddProcessEventHandler()? Thanks in advance.
Title: Re: How to call a routine when my program terminates by OS shutdown?
Post by: lucamar on January 11, 2019, 10:44:39 pm
[. . .]
Could this be (another) solution to my problem? I found no documentation for AddProcessEventHandler(), so I can't estimate whether with this function I can write something into a logfile when the OS shuts down. Is there anybody who can tell this? If yes, where do I get the 'AHandle' from which I must pass to AddProcessEventHandler()? Thanks in advance.

I may wrong but I think AddProcessEventHandler() is meant to be used for child processes, not for the main process. But you can try it and see whether it works..

As for AHandle, it should be the handle/PID returned when you create a child process; there is a way to get the application handle (or PID in Unix) but I don't remember what it is ATM, sorry.
Title: Re: How to call a routine when my program terminates by OS shutdown?
Post by: Hartmut on January 12, 2019, 02:03:13 pm
Hello again, lucamar. Thanks for your reply. To get the PID of a program you can use function system.GetProcessID().

I created a little demo for the idea in http://wiki.freepascal.org/Main_Loop_Hooks#Pipes_and_Process_Termination:
Code: Pascal  [Select][+][-]
  1. program test2;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses InterfaceBase,LCLIntf; // need Package "LCLBase"
  6.  
  7. type
  8.    TMyClass = Class(TObject)
  9.    procedure ChildExitEvent(AData: PtrInt; AReason: TChildExitReason; AInfo: dword); // of object;
  10.    end;
  11.  
  12. procedure TMyClass.ChildExitEvent(AData: PtrInt; AReason: TChildExitReason; AInfo: dword);
  13.    {my Exit-Procedure: must be a method of a Class, because it's usage in
  14.     AddProcessEventHandler() is declared as "procedure ... of object"}
  15.    begin
  16.    writeln('ChildExitEvent: ', AData, ' ', AReason, ' ', AInfo);
  17.    end;
  18.  
  19. var MyClass: TMyClass;
  20.  
  21. procedure install_ExitProc;
  22.    const AData = 0;
  23.    var pPEH: PProcessEventHandler; {=pointer}
  24.        h: THandle;
  25.    begin
  26.    h:=system.GetProcessID;
  27.    writeln('handle=', h);
  28.    pPEH:=AddProcessEventHandler(h, @MyClass.ChildExitEvent, AData);
  29.    end;
  30.  
  31. begin
  32. writeln('hello');
  33. MyClass:=TMyClass.Create;
  34. MyClass.ChildExitEvent(33,cerSignal,55); // only test if this method works
  35. install_ExitProc;
  36. readln;
  37. MyClass.Free;
  38. end.

It compiles with Lazarus 1.8.4 (FPC 3.0.4) but when I start it on Windows 7 (32 bit) I get:
Code: [Select]
An unhandled exception occurred at $00413F33:
EAccessViolation: Access violation
  $00413F33  ADDPROCESSEVENTHANDLER,  line 44 of ./include/lclintf.inc
  $0040180D  INSTALL_EXITPROC,  line 5779 of test2.pas
  $00401881  main,  line 35 of test2.pas 

On Linux (Ubuntu 18.04 64 bit) I get:
Code: [Select]
An unhandled exception occurred at $000000000043387D:
EAccessViolation: Access violation
  $000000000043387D line 44 of include/lclintf.inc
  $0000000000400DF0 line 28 of test2.pas
  $0000000000400E82 line 35 of test2.pas

Line 44 of include/lclintf.inc is:
Code: [Select]
function AddProcessEventHandler(AHandle: THandle; AEventHandler: TChildExitEvent;
  AData: PtrInt): PProcessEventHandler;
begin
  Result:=WidgetSet.AddProcessEventHandler(AHandle, AEventHandler, AData);
end;

I ran the program with the debugger, but that did not help me more. I tried older versions of Lazarus and FPC but got the same result, beside that FPC 2.6.4 says also "Runtime Error 210", which means "Object not initialized: When compiled with range checking on, a program will report this error if you call a virtual method without having called its object's constructor". But as far as I can see, the only object is MyClass and that is initialized...

I attached my little demo project. Can anybody help to avoid the Access violation?
Title: Re: How to call a routine when my program terminates by OS shutdown?
Post by: Hartmut on January 17, 2019, 12:06:16 pm
I closed this topic and opened a new one for questions about function AddProcessEventHandler() which has a better matching headline in https://forum.lazarus.freepascal.org/index.php/topic,43941.0.html
TinyPortal © 2005-2018