Application crash for unknown reasons on WinXP - delphi

I have Delphi XE3, Windows 7 Pro 64bit.
My application is working fine in my PC but my users told me application crashes upon start on their Win XP and also (!!) on their Win 7.
I tried to run the app on my Win 7 but logged as an ordinary user (no admin) - it works.
So now I have installed virtual machine with Windows XP and really - app crashes upon startup.
I need to find what can be the problem but I'm helpless.
As far as I can't use debugger (I dont have Delphi on that VM installed),
I tried to put some MessageBox(0, 'Hello', 'Test', MB_OK); in various places in my app to catch the place where it happens and this is what I found:
I have this in my project-source:
MessageBox(0, 'Hello', 'Test', MB_OK); // shows OK
Application.CreateForm(TfMain, fMain);
MessageBox(0, 'Hello', 'Test', MB_OK); // doesn't show - crash before this line
And this is in the OnCreate function of my main form called fMain:
procedure TfMain.FormCreate(Sender: TObject);
begin
MessageBox(0, 'Hello', 'Test', MB_OK); // doesn't show - crash before this line
...
So where can this app crash?
Not even first line in the OnCreate executes....
I have no idea... Anybody?
Don't know if this is important: I have some units in fMain uses clause under interface and also under implementation. Should I look there? But what happens just before OnCreate of my main form ?

Finally I got it !
place PRINT DIALOG component on your form (TPrintDialog)
set COPIES = 1 (or more than default zero) in the object inspector during design time
try to run such application on WinXP where NO PRINTERS are installed
Application just crashes upon start and in the details you will see only some kernel32.dll address...
I didn't test it on Win 7 without printers. I have no such system around...

Here's another way to track this down without Delphi on the VM...
Copy your project.
Remove all the units from Project Source except your main form.
Launch your application from XP see if it crashes.
If it crashes...then look at the units being dragged in from your main form...start removing them until your program stops crashing.
If it doesn't crash...start adding units/forms back into your project source until it crashes.
Do you have JCL/JVCL installed(JEDI)?
If so create a Logger...note the Logger needs to be created and hooked before your MainForm code executes...You will also need to set up a detailed Stack Trace from Unhandled Exceptions,
in Delphi select->Project/Options/Linker/Map file/Detailed}
You will need something like this in your Logger unit
procedure HookGlobalException(ExceptObj: TObject; ExceptAddr: Pointer; OSException: Boolean);
var
a_List: TStringList;
begin
if Assigned(TLogger._Instance) then
begin
a_List := TStringList.Create;
try
a_List.Add(cStar);
a_List.Add(Format('{ Exception - %s }', [Exception(ExceptObj).Message]));
JclLastExceptStackListToStrings(a_List, False, True, True, False);
a_List.Add(cStar);
// save the error with stack log to file
TLogger._Instance.AddError(a_List);
finally
a_List.Free;
end;
end;
end;
initialization
Lock := TCriticalSection.Create;
Include(JclStackTrackingOptions, stTraceAllExceptions);
Include(JclStackTrackingOptions, stRawMode);
// Initialize Exception tracking
JclStartExceptionTracking;
JclAddExceptNotifier(HookGlobalException, npFirstChain);
JclHookExceptions;
finalization
JclUnhookExceptions;
JclStopExceptionTracking;
Lock.Free;

Related

COM-object in service application cannot be accessed

I`m developing a service application with COM-object in it (OPC Data Access 2.05 server). I have this code for registration my object, which is executed after installation:
procedure TOPCService.ServiceAfterInstall(Sender: TService);
var
lcHResult: HRESULT;
lcCLSIDString: String;
begin
ComServer.UpdateRegistry(True);
lcCLSIDString:=GUIDToString(CLASS_TestOPCServerDA2);
ComObj.CreateRegKey('AppID\'+lcCLSIDString, '', 'Test OPC Server DA2');
ComObj.CreateRegKey('AppID\'+Application.ExeName, 'AppId', lcCLSIDString);
ComObj.CreateRegKey('CLSID\'+lcCLSIDString+'\VersionIndependentProgID', '', C_TEST_OPC_DA2_SERVER_NAME);
<opc server registration stuff>
RegisterAsService(lcCLSIDString, Name);
end;
The service and COM-object are properly register in the system, so i can see my service in SCM and COM-object in OLE/COM object viewer (and also in OPC clients).
The COM-object itself looks like this:
type
TTestOPCServerDA2 = class(TAutoObject, ITestOPCServerDA2, IConnectionPointContainer, IOPCServer, IOPCCommon, IOPCItemProperties, IOPCBrowseServerAddressSpace)
with its factory registration code:
initialization
TAutoObjectFactory.Create(ComServer, TTestOPCServerDA2, Class_TestOPCServerDA2, ciMultiInstance, tmApartment);
The problem is when i try to CoCreateInstance(CLASS_TestOPCServerDA2) (via CreateComObject wrapper), i got freeze for 120 second and 0x80080005 (CO_E_SERVER_EXEC_FAILURE) error after. In SCM and Task Manager i see my service is started when COM-object is requested, but nothing else happens. If i stop the serivce and try againg, service would be started again, so i assume Windows knows about my COM-object, its executable and the fact that executable is a service.
I also tried to change user which my service is running under (to the same with the invoking application), but that did not help.
What am i missing?
Edit 1. I created new project and got rid of OPC (just left COM support) to isolate the problem, so now my class is looks like this:
type
TTestCOMServer = class(TAutoObject, ITestCOMServer)
end;
...
initialization
TAutoObjectFactory.Create(ComServer, TTestCOMServer, Class_TestCOMServer, ciMultiInstance, tmApartment);
And the service thread:
procedure TCOMService.ServiceExecute(Sender: TService);
begin
while (not Terminated) do
begin
ReportStatus;
ServiceThread.ProcessRequests(False);
Sleep(25);
end;
The problem persists: when i try to CoCreateInstance, nothing happens and calling app hangs for 120 seconds.
But! If i make 1 change: uncommenting Application.DelayInitialize := True; in dpr, COM-object gets created well and calling app freezes no longer! Is it the service execute thread that (not service main thread) processes COM-requests?
Edit 2. It seems that only DelayInititalization is requred. ProcessRequests can be called with False argument and sleep can have its place - i must have not properly rebuilded my project.
So, i think the answer to my question is to uncomment Application.DelayInitialize := True; in DPR-file. Delphi autogenerate text about that, but it mentions only Windows 2003 Server condition and my OS is Windows 10.
In my case (Delphi XE3 under Windows 10 Pro) i had to uncomment
Application.DelayInitialize := True;
in DPR. After this change, COM-object is created properly.

Reporting memory leaks on shutdown with a console application

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'"

Access violation when open the teechart form at the second time

We're migrating our XE projects to XE5, however, we encountered the access violation exception about teechart during the test.
I've created a test application to recreate the issue. With the test app, it works fine when open the first teechart form but will get the access violation exception when open it second time or open a new form.
Please refer to the attached test app from the following QC (embarcadero).
http://qc.embarcadero.com/wc/qcmain.aspx?d=122729
When debug it with DCUs. The exception happened when notifying TDBChart's OnStateChange event.
procedure TDataSet.DataEvent(Event: TDataEvent; Info: NativeInt);
begin
...
if NotifyDataSources then
begin
for I := 0 to FDataSources.Count - 1 do
FDataSources[I].DataEvent(Event, Info); // <<---- Access Violation
if FDesigner <> nil then FDesigner.DataEvent(Event, Info);
end;
end;
As David Berneda said at Quality Central:
Its related to using an internal TObjectList generic collection inside
DBChart. The code has been improved so the error is fixed now (a new
code takes care of destroying the ObjectList items correctly).
As a workaround, you can add this code at your form's OnClose event:
type
TChartAccess=class(TDBChart);
procedure TOutcomesGraphFm.bbtnCloseClick(Sender: TObject);
begin
TChartAccess(dbcBar).RemovedDataSource(bsTestScores,bsTestScores.DataSource);
Close;
end;

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.

error in Delphi loadlibrary()

i have given a chance to my software user to select dll from openfile dialog.(so my user can download dlls form my website and use it with the main project ). everything is working fine and it can even find that dlls is provided by me or selected an invalid dll.but the problem raises if the user selects a renamed file(eg : apple.txt file renamed to apple.dll ). i typed the code like this
try
dllHandle := LoadLibrary( pwidechar(openfiledialog1.filename)) ;
catch
{ showmessage if it is not a dll (but it can be any dll, it checks this is my dll or 3rd party later )}
end;
error message shown by delphi is 'bad library image selected'
but try catch is not working if the user selects invalid dll it is showing its own error message and struck up.
can anyone help me,i am using delphi 2009
There's no exception to catch because an exception is not raised when LoadLibrary fails; it just returns '0'.
You should check if 'dllHandle' is 0 or not, if it is, show the error information to the user by using GetLastError as documented. Alternatively you can use the Win32Check function in the RTL which will raise an exception with the appropriate error message:
(edit: Documentation of 'LoadLibrary' states that: To enable or disable error messages displayed by the loader during DLL loads, use the SetErrorMode function. So if you don't want the OS to show an additional dialog you'd set the error mode before calling LoadLibrary.)
var
dllHandle: HMODULE;
ErrorMode: UINT;
begin
if OpenDialog1.Execute then begin
ErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS); // disable OS error messages
try
dllHandle := LoadLibrary(PChar(OpenDialog1.FileName));
finally
SetErrorMode(ErrorMode);
end;
if Win32Check(Bool(dllHandle)) then begin // exception raised if false
// use the libary
end;
end;
end;

Resources