Reporting memory leaks on shutdown with a console application - delphi

I've created a console application and set ReportMemoryLeaksOnShutdown := True.
I've created a TStringList but did not free it.
When the program finishes executing, I see the memory leak for a brief second but then the console closes.
I've tried adding a ReadLn; to the end, but it only shows a blank console window when I do that, which makes sense.
I need to find a way to pause executing after the memory leak report, but before complete program shutdown.
I'm using Delphi 10 Seattle.
program Project1;
{$APPTYPE CONSOLE}
uses
System.Classes,
System.SysUtils;
var
s : TStringList;
begin
try
ReportMemoryLeaksOnShutdown := True;
s := TStringList.Create;
//ReadLn doesn't work here, which makes sense.
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
//I need to be able to pause the program somewhere after the end statement here.
end.

The easiest is to simply run the application in a previously opened command window.
If you insist on seeing the memory leak report while running in the IDE, do as follows:
Locate the ShowMessage procedure in GetMem.inc (line 4856 in Delphi 10 Seattle)
Place a breakpoint on the end; of that procedure.
Alternatively, as Sertac Akyuz commented, put a break point on the end. of the system unit.
You can also redirect the memory leak report to a file. Download the full version of FastMM from
https://sourceforge.net/projects/fastmm/
or better, thanks to Arioch 'The, from here:
https://github.com/pleriche/FastMM4
and set the needed options in FastMM4Options.inc

var
SaveExitProcessProc: procedure;
s: TStringList;
procedure MyExitProcessProc;
begin
ExitProcessProc := SaveExitProcessProc;
{$I-}
ReadLn;
{$I+}
end;
begin
SaveExitProcessProc := ExitProcessProc;
ExitProcessProc := MyExitProcessProc;
ReportMemoryLeaksOnShutdown := True;
s := TStringList.Create;
end.

That is a bug in recent Delphi versions. I just checked it in that recent free Delphi 10.1 Starter and it behaves as you describe - but as it provides no RTL sources I can not check the exact reason.
In Delphi XE2 it behaves as expected: creates the task-modal dialog and waits for you to react, just like described by Sertak.
In Delphi 10.1 the leak is indeed reported to the console window, but the program is not stopped to wait for user attention. That is poor solution, for both this reason and for the possible use of console programs in scripting (CMD or PS scripts would not "understand" this message and might confuse it with legitimate output and fail execution of further stages programs.
I think you have to open regression-type bug report over Delphi 10.0 - but I do not think they would fix it until 10.2 release.
I also switched your application from Delphi-forked memory manager to the original one, and then the erroneous behavior was reverted: the program displayed the message box and waited until I dismiss it before exiting into IDE.
Currently i suggest you to use the mentioned original memory manager rather than Delphi fork of it.
program Project1;
{$APPTYPE CONSOLE}
uses
FastMM4,
System.Classes,
System.SysUtils;
...
The original memory manager resides at http://github.com/pleriche/FastMM4
You can use Git client in your Delphi or a standalone one to keep yourself updated, or you can download the code once and stop updating, up to you.
The relevant quotes of its code are:
{$ifdef LogErrorsToFile}
{Set the message footer}
LMsgPtr := AppendStringToBuffer(LeakMessageFooter, LMsgPtr, Length(LeakMessageFooter));
{Append the message to the memory errors file}
AppendEventLog(#LLeakMessage[0], UIntPtr(LMsgPtr) - UIntPtr(#LLeakMessage[1]));
{$else}
{Set the message footer}
AppendStringToBuffer(LeakMessageFooter, LMsgPtr, Length(LeakMessageFooter));
{$endif}
{$ifdef UseOutputDebugString}
OutputDebugStringA(LLeakMessage);
{$endif}
{$ifndef NoMessageBoxes}
{Show the message}
AppendStringToModuleName(LeakMessageTitle, LMessageTitleBuffer);
ShowMessageBox(LLeakMessage, LMessageTitleBuffer);
{$endif}
end;
end;
{$endif}
end;
and
{Shows a message box if the program is not showing one already.}
procedure ShowMessageBox(AText, ACaption: PAnsiChar);
begin
if (not ShowingMessageBox) and (not SuppressMessageBoxes) then
begin
ShowingMessageBox := True;
MessageBoxA(0, AText, ACaption,
MB_OK or MB_ICONERROR or MB_TASKMODAL or MB_DEFAULT_DESKTOP_ONLY);
ShowingMessageBox := False;
end;
end;
This code depends upon being run on desktop Windows, so maybe Embarcadero tried to "fix" it to make it cross-platform. However the way they did it broken it on Windows console....
Also consider using adding other forms of logging - into the file and/or into the Windows Debug Strings. They would not be so attention-catching as the modal window, but would at least help you save the information, if you would know where to look for it.

This is certainly a hack, don't use in production :)
ReportMemoryLeaksOnShutdown:= True;
IsConsole:= False;
TStringList.Create;
However, it causes the leak message (and some other messages) to be displayed in a message box (where all text can be copied by pressing Ctrl+C).
(Tested with Delphi 10.2, please report any side effects we wouldn't like)

Set a breakpoint on "end." in system.pas.
However this solution is not completely ideal, because exit procedures/unit finalizations will still execute after this "end." statement.
This can be "checked" by F7/debugging/stepping into the "end." statement, it will lead to some assembler function and once the assembler function is exited by stepping over assembler instructions with F8 it will return to a function called "FinalizeUnits" in system.pas, where this function recursively calls itself to clean up the finalize sections of units I suppose.
So as long as you don't have to pause after the cleaning up of the finalization sections of units, this solution is not so bad.
However, cleaning up of units/finalization sections follows a certain order, it's likely that your own units's finalization section will be executed, before the memory manager is shutdown in the "end." statement.
Otherwise a different solution will have to be used.
To get into system.pas add it temporarely to a uses clausule or so, and choose open file, later removed it to prevent compile errors like:
"[dcc32 Error] TestProgram.dpr(8): E2004 Identifier redeclared: 'System'"

Related

Delphi {Form1} fails if WriteLN is present

I have a Delphi code where the program has a unit with {Form1} next to it. That unit uses another unit where I have try/except procedure and write a message to the screen. Below are the code snippets for detailed explanation.
Important Note1 : I tried to elaborate my question and re-posted it. I hope this is clear.
Important Note2 : If I remove the WriteLn inside the exception (third code snippet below) the code works even if it is called from GUI
Main Program
program PROGNAME;
uses
Forms,
View in 'FView.pas' {Form1},
SubUnit in 'FSubUnit.pas';
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Form1.Left := Screen.WorkAreaLeft;
Form1.Top := Screen.WorkAreaTop;
Application.Run;
end
In Fview.pas I have
procedure TForm1.FileListBox1Click(Sender: TObject);
ReadData(filename);
end
In SUBUNIT which reads data I HAVE
try
Read(F, result);
except // IO error
on E: EInOutError do
begin
writeln('No info is given default taken')
end;
end;
Normally my code works and skips if there is an IO error with the above warning. If I do the same and call from the GUI I get a screen message IOError and it does not move on the
Is this a common issue? Do I need to suppress that part of the code?
This behaviour is exepcted.
Writeln writes output to the console. A GUI application (by default) has no console.
Hence, if you do Writeln('No info...') in a typical GUI app, you tell the system to write 'No info...' to the console, but there is no console! Hence the I/O error (105, I assume).
Either add a console manually to your GUI app (AllocConsole), or -- probably much better -- use a GUI error message instead: ShowMessage('No info...') or MessageBox(Handle, 'No info...', 'My App', MB_ICONERROR).

Can't catch access violation

We have a Delphi app that has been running for a few years and now suddenly we get weird access violations. We used Eurekalog to trace where it comes from and this is even more weird. They are so far all on the Free call of an object, but inside a try except block. One of them is even in 2 try except blocks and still when the Access Violation occurs it jumps totally out of the program and ignores the try excepts, nothing excepts EurekaLog catches it at the end. Really confused on why this is suddenly happening (both instances are old code that has not been touched for years and also other codes changes is not related to it).
A example of the code is
try
if Assigned(ClientCommunication) then begin
if ClientCommunication.isConnected then begin
if ClientCommunication.closeServerConnection then begin
try
ClientCommunication.Free;
ClientCommunication := nil;
Except
on e:Exception do begin
ClientCommunication := nil; //suppress weird AV error.. get read for new object
end
end;
Now the last try except was added later to try and suppress the AV as we just want the object cleared to be restarted if required, but this is mostly called when closing the app. But it still just jumps out of that and I can’t catch it at all.
It works on our developers PCs and not at the client.
If this line: ClientCommunication.Free; is causing an exception.
There are a few things you can do here.
FreeAndNil
Replace calls to AObject.free with calls to freeandnil(AObject).
If you just do the free, the old pointer will still have a non-nil reference and Assigned(AObject) will not be able to tell a freed and active object apart.
Beware of clones
Another mistake you may make is that you've cloned the object like so:
Object1:= TObject1.Create;
//.... lots of code
Object2:= Object1;
//.... lots of code
FreeAndNil(Object2); <<-- freeing the clone-reference is a mistake
//.... lots of code
Object1.Free; <<-- exception: it's already freed
FastMM4 options
Download the latest FastMM4 from http://sourceforge.net/projects/fastmm/
It has a few extra bells and whistles from the one included with Delphi.(*)
Among those whistles is an extra diagnostic mode that makes your program dead slow, but also catches out a lot of those heap corruption bugs you seem to be suffering from.
Open up defines.inc and change this:
{$ifdef DEBUG}
{.$define EnableMemoryLeakReporting}
{.$define FullDebugMode}
{.$define RawStackTraces}
{$endif DEBUG}
Into this
{$ifdef DEBUG}
{$define EnableMemoryLeakReporting}
{$define FullDebugMode}
{$define RawStackTraces}
{$define CatchUseOfFreedInterfaces} <<-- very useful
{$define LogMemoryLeakDetailToFile}
{$define LogErrorsToFile}
{$define CheckHeapForCorruption} <<-- :-)
{$endif}
There's a nice article here: http://wiert.me/2009/07/29/delphi-fastmm-using-fastmm4-for-debugging-your-memory-allocations-part-1-introduction/
If you don't feel like mucking about with the inc file, there a little utility that will do it for you at: http://jedqc.blogspot.com/2007/07/new-fastmm4-options-interface.html
(*) I think the Delphi one has most of the debug stuff as well, but not 100% sure. Regardless can't hurt to get the latest (greatest) version.
Or buffer overflow
If your ClientCommunication object has some internal structure free will do extra work.
Imagine the following code:
TUnrelatedObject = class
buffer: array[0..99] of integer;
procedure DoWork;
end;
TUnrelatedObject.DoWork;
var
i: integer;
begin
for i:= 0 to 100 do buffer[i]:= maxint; <<-- buffer overrun
end;
Imagine that ClientCommunication is right next to UnrelatedObject in the heap.
The call to DoWork will overwrite data of ClientCommunication as well.
This may or may not trigger an access violation in DoWork. If it doesn't then the error will be very hard to trace, because it will show up in a different and totally unrelated places.
Enable range checking {$R+} in your application.
Warning
Remember to not ship the debug version to your clients.
With all the debugging your program will be dead slow.

Windows 7 logon screensaver in Delphi

I'm having problems while using Delphi application as Windows 7 logon screensaver (for both 32-bit and 64-bit Windows). Even blank application (New Project without any extra code) throws an error.
Delphi 7 application throws "The memory could not be read" error and Delphi 2010 application throws "The exception unknown software exception occurred in the application" and then "Runtime error 217". This error happens before any form initialization and before any initialization of exception handlers.
Setting notepad.exe as logon screensaver works fine.
Any ideas what goes on here?
As I said in my comment, it's not "invisible code", just code in the initialization section of some unit that's causing the problem. I've managed to track down the culprit (well at least one of them - there may be others).
When you use the Forms unit, it has a dependency on the Classes unit.
The initialization section calls InitThreadSynchronization, which amongst other things calls the following:
SyncEvent := CreateEvent(nil, True, False, '');
if SyncEvent = 0 then
RaiseLastOSError;
It seems the API call CreateEvent fails when called from within the login screen. Unfortunately I'm unsure whether the login screen: (a) forbids CreateEvent altogether (b) requires CreateEventEx instead or (c) would work with an appropriate lpEventAttributes argument. I've posted a more specific question to hopefully find out: CreateEvent from Windows-7 Logon Screen
You can verify the problem with the following console app:
program TestLoginScreensaver;
{$APPTYPE CONSOLE}
uses
Windows,
SysUtils;
var
SyncEvent: THandle;
begin
try
SyncEvent := CreateEvent(nil, True, False, '');
if SyncEvent = 0 then
RaiseLastOSError;
CloseHandle(SyncEvent); //So handle is closed if it was created (e.g. while logged in)
except
on E:Exception do
Writeln(E.Classname, ': ', E.Message);
end;
Readln;
end.
The purpose of SyncEvent is to enable TThread instances to synchronise back to the main thread. So if you write a single threaded app, or create your threads using something other than TThread, you don't actually need/use SyncEvent at all.
SIDE-RANT: This is a prime example of the problem with using the initialization section. Merely including a unit has the potential to introduce unnecessary side-effects. They're Mostly Harmless, but not in this case. Now you may argue that Classes.pas is bloated, and I won't argue. But the point is that if Classes initialization were called explicitly from the DPR, this problem would have been easier to identify and find a workaround for.
EDIT: New Solution
As Remy Lebeau noted in the other question I posted.
The line:
SyncEvent := CreateEvent(nil, True, False, '');
Must be changed to:
SyncEvent := CreateEvent(nil, True, False, nil);
Since this solution involves recompiling VCL units, you may want to go through a few of the previous questions on this subject
With this as the only change (compiled in D2009) I was able to successfully show a blank form at the Logon screen. However, bear in mind that some things you may normally expect to be able to do will be off limits due to the security restrictions at the Logon screen.
After a little playing around. This has to be connected to Delphi's hidden main (real main) window you will need to look seriously at Application.initialise or Application.HookMainWindow().
Because amazingly this code does not cause a problem:
program w7logonsaver;
{$APPTYPE CONSOLE}
var
i: Integer;
begin
for i := 1 to 20 do
writeln;
write('K ');
ReadLn;
end.
Just hit enter to quit.

Why does Themes.pas leak the TThemeServices singleton when linked into a DLL

Update: The changes to introduce VCL styles in XE2 have removed the memory leak. So I guess it was unintentional after all.
I came across a VCL memory leak today, in Themes.pas. It only occurs for DLLs. The unit finalization code is as so:
finalization
if not IsLibrary then
InternalServices.Free;
InternalServices is a singleton that is created on demand when you call the ThemeServices function. Many DLLs do not have UI and so do not ever create this singleton. However, I happen to have a COM add-in to Excel which does result in this leak manifesting.
The leak doesn't particularly bother me because this DLL is never repeatedly loaded and unloaded from the same process. And I know how I could fix the leak using the ThemeServicesClass global variable.
My question though, is to ask if anyone can explain why this code is the way it is. It seems quite deliberately coded this way. For the life of me I cannot come up with an explanation for this intentional leak.
One explanation is that the unit finalization section is executed while the OS loader lock is active. During that time, there are significant restrictions on what a DLL is allowed to do without risking deadlock.
We run into this same problem as well. Here is what we are currently doing to prevent the leak in our projects:
In the dll .dpr file add Themes to the uses section.
Add the following to clean up the leak on shutdown:
procedure DLLEntryProc(EntryCode: integer);
begin
case EntryCode of
DLL_PROCESS_DETACH:
begin
ThemeServices.Free;
end;
DLL_PROCESS_ATTACH:
begin
end;
DLL_THREAD_ATTACH:
begin
end;
DLL_THREAD_DETACH:
begin
end;
end;
end;
begin
{$IFDEF DEBUG}
ReportMemoryLeaksOnShutdown := True;
{$ENDIF}
DllProc := #DLLEntryProc;
end.
On the same idea that there must be a good reason to leave it leaking (indy leaks a couple of things on purpose as well, or at least it used to) I prefer to ignore it, like so:
{$I FastMM4Options.inc}
...
{$IFDEF EnableMemoryLeakReporting}
if IsLibrary then// ThemeServices is leaked when created in a DLL.
RegisterExpectedMemoryLeak(ThemeServices);
{$ENDIF}

Delphi - Form in DLL - Hints not showing

I have a Delphi form inside a DLL (I know that this restricts the use of the DLL to Delphi but this is not a problem in this case).
The DLL exports a function ShowForm that looks roughly like this:
procedure ShowForm (App : TApplication);
begin
OldApp := Application;
try
Application := App;
MyForm := TMyForm.Create (nil);
try
MyForm.ShowModal;
finally
FreeAndNil (MyForm);
end;
finally
Application := OldApp;
end;
end;
Now on the form I use a TAdvOfficeHint (from the TMS component pack). Unfortunately the hints do not show up.
Am I missing something here? How can I make the form behave exactly as it would if I showed it from the main application?
Thanks!
I don't know TAdvOfficeHint but I guess it hooks Application.OnShowHint to set its own THintWindowClass, and even if both the main executable and the DLL are linking in the TMS unit, they each have their own copy of the class which is where things go wrong.
Assigning Application is not enough: there are other global variables, like Screen, Mouse, etc. Others are even hidden in the implementation so I'd say your chances to make the form behave exactly as from the main application are slim.
Just found the reason why it does not work. As TOndrej states, TAdvOfficeHinthooks Application.OnShowHint and internally executes the following line of code:
FHintInfo.Assign (AHintInfo);
Assign internally uses a dynamic type check
if (Source is TAddvHintInfo) then ...
which fails due to the separate type registries of the DLL and the main application.
I have run into this problem a few times now and maybe I really have to switch to runtime packages to avoid all this stuff.
Anyway, if there's anything I can do to prevent this, please comment.
Wrong setting of Application.
Try this and see if it solves your problem:
procedure ShowForm (AppHandle : THandle);
begin
OldAppHandle := Application.Handle;
try
Application.Handle := AppHandle;
........
finally
Application.Handle := OldAppHandle;
end;
end;
I guess in Delphi 2006 and later versions you can call System.ShareMemoryManager method in the EXE code, so that its memory manager is shared with other modules loaded in the process memory space.

Resources