Recent

Author Topic: Runtime exception when setting a boolean value, why?  (Read 1288 times)

BosseB

  • Sr. Member
  • ****
  • Posts: 468
Runtime exception when setting a boolean value, why?
« on: September 22, 2020, 12:38:00 pm »
I am porting an application from Delphi to FPC.
It uses an old logging class I have adapted for FPC.
I am using FPC 3.0.4 and Lazarus 2.0.8 on Windows 10. Later I want to port to Linux.

The application using this class crashes on start when the logging is initialized. It is very strange since the same unit has been used in a few other applications without issues.
Here is some excerpts of the code to describe what happens:
Code: Pascal  [Select][+][-]
  1. TLogAGI = class(TObject)
  2. private
  3.   ...
  4.      FLogDirectory,              // Directory for logs
  5.   ...
  6.     FLogExtension: string;  // Extension for log files
  7.   ...
  8.     FbDirChecked,             // Whether FLogDirecory has been checked.
  9.     FbLogging,                  // Turns logging on or off.
  10.   ...
  11.     FbMilliseconds: boolean;      // Whether to show milliseconds in log.
  12.     function  CheckDir(var sDir: string): boolean;
  13.     function  CheckAndCreateDir(var sDir: string): boolean; //<= Problem here
  14.   ...
  15.   public
  16.     constructor Create();
  17.     destructor  Destroy; override;
  18.   ...
  19.     procedure StartLog(const Option: integer = 1);
  20.   ...
  21. end;
  22.  
  23. implementation
  24.   ...
  25. function TLogAGI.CheckAndCreateDir(var sDir: string): boolean;
  26. {-------------------------------------------------------------------------
  27.   Purpose:  Checks if the directory sDir exists. If not, tries to create it.
  28.             If this fails, a default directory is created.
  29.             Returns false if even this fails.
  30. -------------------------------------------------------------------------}
  31. var
  32.   sTmpDir: string;
  33. begin
  34.   ...
  35.         if DirectoryExists(sTmpDir) then // wanted directory already exists, fine!
  36.     Result := true
  37.   else
  38.   begin // try to create the directory
  39.     if TryToCreateDir(sTmpDir) then
  40.       Result := true
  41.     else
  42.     begin  // try to use the last part of sTmpDir as a directory under the application directory
  43.       sTmpDir := ExtractFilePath(ParamStr(0)) + ExtractFileName(sTmpDir);
  44.       if TryToCreateDir(sTmpDir) then
  45.         Result := true
  46.       else
  47.       begin  // use default directory
  48.         sTmpDir := ExtractFilePath(ParamStr(0)) + DEFAULT_DIR;
  49.         if TryToCreateDir(sTmpDir) then
  50.           Result := true
  51.         else
  52.         begin  // Could not create any directory at all
  53.           Result := false;
  54.           FLastError := 'Could not create any log file directory. Not even ' + sTmpDir;
  55.         end;
  56.       end;  // end use default directory
  57.     end;  // end try to use last part of sTmpDir
  58.   end;  // end create directory
  59.   { Show warning if not the wanted directory was used }
  60.   if Result then //All OK so mark check completed
  61.   begin
  62.     FbDirChecked := true;  //For some reason an exception here!!!!
  63.     ...
  64.   end;
  65. end;
  66.  
When I start this program there is an exception, so I traced the code in Lazarus to find out where it happened and this is the line...
The error message is shown in the first attachment.
Note that the debugger does not pick it up even though I have stepped all the way to this line. I am standing on the line and then I hit F8 to execute it and the dialog pops up...
Starting from the command prompt just shows it as an exception. (Second attachment)

Any ideas?
--
Bo Berglund
Sweden

BosseB

  • Sr. Member
  • ****
  • Posts: 468
Re: Runtime exception when setting a boolean value, why?
« Reply #1 on: September 22, 2020, 01:10:57 pm »
I have one additional piece of information:
When I stop at the line causing the error and hover the mouse over FbDirChecked the following "tooltip" appears (see attachment)
Why can it not access memory? This is a call from within the same sourcefile where the variable is declared....
--
Bo Berglund
Sweden

PascalDragon

  • Hero Member
  • *****
  • Posts: 5446
  • Compiler Developer
Re: Runtime exception when setting a boolean value, why?
« Reply #2 on: September 22, 2020, 01:13:20 pm »
If I had to guess (and the tooltip you shouldn't confirms that) I'd say that you did not correctly create the instance of TLogAGI. Maybe you did YourVar.Create instead of YourVar := TLogAGI.Create?

BosseB

  • Sr. Member
  • ****
  • Posts: 468
Re: Runtime exception when setting a boolean value, why?
« Reply #3 on: September 22, 2020, 03:09:36 pm »
If I had to guess (and the tooltip you shouldn't confirms that) I'd say that you did not correctly create the instance of TLogAGI. Maybe you did YourVar.Create instead of YourVar := TLogAGI.Create?
But I cannot wrap my head around the fact that I could step through the code until I get to the assignment line for the boolean, when it crashed....
If it was not created surely it would crash directly on any access including the code???

Anyway, concerning the creation etc...
The original application used a (nonvisual) logging component, which in Delphi was placed on the main form.
In the ported version I have removed the non-visual component dropped on the form and instead create it in Form.Create():

Code: Pascal  [Select][+][-]
  1. procedure TfrmSSRemoteClient.FormCreate(Sender: TObject);
  2. begin
  3.   btnCopyServerFile.Caption := '';
  4.   btnCopyClientFile.Caption := '';
  5.   FixWindows;
  6.   Log3R1 := TLogAGI.Create;  //<== Created here when the form is created
  7.   Log3R1.LogPrefix := 'RSC';
  8.   Log3R1.LogExtension := 'log';
  9. end;
  10.  

However there is also a Form.Loaded procedure implemented in this application and it starts out like this where it sets some properties of the log component and then calls StartLog().
It is when running StartLog the exception happens.

Code: Pascal  [Select][+][-]
  1. procedure TfrmSSRemoteClient.Loaded;
  2. var
  3.   slSect: TStringList;
  4.   i: integer;
  5.   LI: TlistItem;
  6. begin
  7.   inherited;
  8.   FOriginalCaption := Caption;
  9.   {Set up logging and initialize the program}
  10.    Log3R1.LogDirectory := ReadIniString('Logging','LogDir', ExtractFilePath(ParamStr(0)) + 'log');
  11.   Log3R1.LogPrefix := 'RC';
  12.   Log3R1.Debug := ReadIniBool('Logging','Debug',true);
  13.   Log3R1.StartLog();
  14.   FSSRemoteClient := TSSRemoteClient.Create(Self);
  15.   FSSRemoteClient.Log := Log3R1;
  16.   ....
  17.  

So the order of execution of form.FormCreate and form.Loaded matters here....
When I moved the logging component from being dropped to being created I put its creation into FormCreate.

Question:
Which happens first, Create or Loaded?
So to find out I put a break point at the top of both and it reaches the Loaded break first!

Which means that as you suspected the object was not yet created when it was used.
I moved create into Loaded() and now everything works!

Thanks for putting me on the right track for this error hunt!  :D :)
--
Bo Berglund
Sweden

Warfley

  • Hero Member
  • *****
  • Posts: 1499
Re: Runtime exception when setting a boolean value, why?
« Reply #4 on: September 22, 2020, 06:52:47 pm »
But I cannot wrap my head around the fact that I could step through the code until I get to the assignment line for the boolean, when it crashed....
If it was not created surely it would crash directly on any access including the code???
Nope, a method of a class is basically just a function that has a hidden parameter self.
Code: Pascal  [Select][+][-]
  1. procedure TSomeClass.Foo;
  2. begin
  3.   x := 42; // same as Self.x := 42
  4. end;
  5. // is pretty much the same as
  6. procedure Foo(_Self: TSomeClass);
  7. begin
  8.   _Self.x := 42;
  9. end;

And as with any other object variable or parameter, it only crashes when you access it.
This btw allows for things like this:
Code: Pascal  [Select][+][-]
  1.       procedure TObject.Free;
  2.  
  3.         begin
  4.            // the call via self avoids a warning
  5.            if self<>nil then
  6.              self.destroy;
  7.         end;
Which makes Free be safe to call on nil (i.e. TObject(nil).Free works perfectly fine). Other things you can do:
Code: Pascal  [Select][+][-]
  1. procedure TMyClass.Suicide;
  2. begin
  3.   Free;
  4.   WriteLn('RIP');
  5. end;
work perfectly fine because of this, otherwise it would need to crash after the free call.

Question:
Which happens first, Create or Loaded?
I don't know that much about the inner workings of the LCL, but in the OnCreate you already have access to all child controls, so they must have been loaded first.

That said, the OnCreate and Create are two different things. You can overload your forms constructor (not recommended but possible), and the constructor is always the first thing called on an object (as it constructs the object) so to be precise, Create is called before OnLoaded (because it initiates the loading process) but OnCreate is called after.

PascalDragon

  • Hero Member
  • *****
  • Posts: 5446
  • Compiler Developer
Re: Runtime exception when setting a boolean value, why?
« Reply #5 on: September 23, 2020, 10:01:31 am »
If I had to guess (and the tooltip you shouldn't confirms that) I'd say that you did not correctly create the instance of TLogAGI. Maybe you did YourVar.Create instead of YourVar := TLogAGI.Create?
But I cannot wrap my head around the fact that I could step through the code until I get to the assignment line for the boolean, when it crashed....
If it was not created surely it would crash directly on any access including the code???

The code is always there no matter if an object is instantiated or not. You'll only get an access violation if your code tries to access Self (which is done for example by accessing a field or by calling a virtual method).

Question:
Which happens first, Create or Loaded?

The constructor Create is executed first, but what you have here is an event handler for the form's OnCreate event. Events that are assigned using the designer are loaded (see where I'm going with this? ;) ) from the form resource which is done through the constructor, then Loaded is called and in the form's AfterConstruction method the OnCreate event handler is called.

Using Loaded and OnCreate is one of those few situations where one really needs to take care of the order.

avra

  • Hero Member
  • *****
  • Posts: 2514
    • Additional info
Re: Runtime exception when setting a boolean value, why?
« Reply #6 on: September 23, 2020, 11:40:23 am »
The original application used a (nonvisual) logging component, which in Delphi was placed on the main form.
In the ported version I have removed the non-visual component dropped on the form and instead create it in Form.Create():
My wild guess would be that this has disturbed creation order and you are trying to access something that does not exist yet. You could confirm this if you temporary move relevant code from Form.Create to Form.Show and see if access violation message is gone.
ct2laz - Conversion between Lazarus and CodeTyphon
bithelpers - Bit manipulation for standard types
pasettimino - Siemens S7 PLC lib

PascalDragon

  • Hero Member
  • *****
  • Posts: 5446
  • Compiler Developer
Re: Runtime exception when setting a boolean value, why?
« Reply #7 on: September 23, 2020, 03:35:00 pm »
The original application used a (nonvisual) logging component, which in Delphi was placed on the main form.
In the ported version I have removed the non-visual component dropped on the form and instead create it in Form.Create():
My wild guess would be that this has disturbed creation order and you are trying to access something that does not exist yet. You could confirm this if you temporary move relevant code from Form.Create to Form.Show and see if access violation message is gone.

BosseB already said that he found the problem in his creation order:

Which means that as you suspected the object was not yet created when it was used.
I moved create into Loaded() and now everything works!

Thanks for putting me on the right track for this error hunt!  :D :)

 

TinyPortal © 2005-2018