Search code examples
delphiexception64-bitaccess-violation

In Delphi, how to fix access violation on a FreeAndNil in 64-bit release build (when 64-bit debug build works)?


Here is some simplified code context to my question:

TSomeone = record
    FirstName: String;
    LastName: String;
    Picture: TGraphic;
end;

TSomeoneHelper = record helper for TSomeone
    public
        procedure Clear();
        procedure LoadFromFile(const Filename: String);
end;

procedure TSomeoneHelper.Clear();
begin
    Self.FirstName := '';
    Self.LastName:= '';

    try
        if Assigned(Self.Picture) then
            FreeAndNil(Self.Picture); // <---- Crash here in 64-bit release
    except
        Self.Picture := nil;
    end;
end;

Normally someone would declare a TSomeone variable and then call myVar.LoadFromFile('myfile.blah') to fill the record. In the LoadFromFile procedure a TJPEGImage (TGraphic descendant) is created and then assigned to Picture.

Since I'm not in a class (no constructor in record helper) I have no way to initialize Picture to nil. Because of that FreeAndNil crashes. What's weird is that on 32-bit builds, it seems initialized to nil but in 64 bit builds it's not (it's "Inaccessible value"). I added the try except for that reason. But even weirder in 64-bit release I get an access violation that is not catched by the try except.

To sum up:

  • 32-bit release + debug builds are OK since Picture is initialized to nil because magic (?), so no exception - all is good
  • 64-bit debug Picture is initialized "Inaccessible value" which trigger Assigned then FreeAndNil does an access violation but the try catches it so all is good
  • 64-bit release (don't know how to debug CPU assembly so not sure what happens) but the try don't catch the access violation and the error is thrown to the user so bad

What can I do to fix this?


Solution

  • The fact that it works differently between build type and architecture is just luck. This should not work anywhere (the OP comments just confirmed what I thought).

    If using Delphi 10.4+ you use Custom Managed Records to initialize the (unmanaged) var to nil and the Assigned() call will not do access violations.

    In the end I just refactored my code to use classes instead of records. In the constructor I initialize the var to nil. It's cleaner and work with any version of Delphi.