Recent

Author Topic: [CLOSED] How to call a routine when my program terminates by OS shutdown?  (Read 3750 times)

Hartmut

  • Full Member
  • ***
  • Posts: 242
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.
« Last Edit: January 17, 2019, 12:06:53 pm by Hartmut »

jamie

  • Hero Member
  • *****
  • Posts: 1275
Re: How to call a routine when my program terminates e.g. by OS shutdown?
« Reply #1 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.

« Last Edit: January 06, 2019, 02:57:02 pm by jamie »

Hartmut

  • Full Member
  • ***
  • Posts: 242
Re: How to call a routine when my program terminates e.g. by OS shutdown?
« Reply #2 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.

jamie

  • Hero Member
  • *****
  • Posts: 1275
Re: How to call a routine when my program terminates e.g. by OS shutdown?
« Reply #3 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.  

Hartmut

  • Full Member
  • ***
  • Posts: 242
Re: How to call a routine when my program terminates e.g. by OS shutdown?
« Reply #4 on: January 06, 2019, 10:32:56 pm »
Thank you for your help. Will test it tomorrow and then report. Good night.

engkin

  • Hero Member
  • *****
  • Posts: 2286
Re: How to call a routine when my program terminates e.g. by OS shutdown?
« Reply #5 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 });

Hartmut

  • Full Member
  • ***
  • Posts: 242
Re: How to call a routine when my program terminates e.g. by OS shutdown?
« Reply #6 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.

Hartmut

  • Full Member
  • ***
  • Posts: 242
Re: How to call a routine when my program terminates e.g. by OS shutdown?
« Reply #7 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.

ASerge

  • Hero Member
  • *****
  • Posts: 1113
Re: How to call a routine when my program terminates e.g. by OS shutdown?
« Reply #8 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.
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.

engkin

  • Hero Member
  • *****
  • Posts: 2286
Re: How to call a routine when my program terminates e.g. by OS shutdown?
« Reply #9 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?
« Last Edit: January 08, 2019, 04:53:03 am by engkin »

Hartmut

  • Full Member
  • ***
  • Posts: 242
Re: How to call a routine when my program terminates e.g. by OS shutdown?
« Reply #10 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.
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.

ASerge

  • Hero Member
  • *****
  • Posts: 1113
Re: How to call a routine when my program terminates e.g. by OS shutdown?
« Reply #11 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.

Hartmut

  • Full Member
  • ***
  • Posts: 242
Re: How to call a routine when my program terminates e.g. by OS shutdown?
« Reply #12 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.
« Last Edit: January 11, 2019, 10:22:26 pm by Hartmut »

ASerge

  • Hero Member
  • *****
  • Posts: 1113
Re: How to call a routine when my program terminates e.g. by OS shutdown?
« Reply #13 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.

Hartmut

  • Full Member
  • ***
  • Posts: 242
Re: How to call a routine when my program terminates by OS shutdown?
« Reply #14 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.