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).
Related
I'm trying to capture the debugger notification text, Firedac connection
//FDScript1.SQLScripts.Add.SQL.LoadFromFile('C:\SGI\Rodar_Apos_Atualizar_3.txt');
//FDScript1.ValidateAll;
//FDScript1.ExecuteAll;
//MSScript1.SQL.LoadFromFile('C:\SGI\Rodar_Apos_Atualizar_3.txt');
//MSScript1.Execute;
try
FDConnection1.Connected := True;
FDScript1.SQLScripts.Add.SQL.LoadFromFile('C:\SGI\Rodar_Apos_Atualizar_3.txt');
FDScript1.ExecuteAll;
FDScript1.ValidateAll;
except
//EMSSQLNativeException
on E: EMSSQLNativeException do
begin
//ShowMessage('Erro'+FDGUIxErrorDialog1.ErrorDialog.Caption);
//Memo1.Clear;
Memo1.Text := 'Erro de Sistema: '+#13#10+ E.Message;
end;
end;
It really would help if you would show us the scripts you are trying to execute,
or at least the script which does not execute correctly. In any case, your code is wrong
because the documentation
states
It is good practice to call the ValidateAll method before the ExecuteAll method.
Note the 'before'. Your ValidateAll is after ExecuteAll, not before. Both are Boolean
functions but you are not checking their results, which you should.
With some trivial experimenting I found that I can provoke a EMSSQLNativeException
using SqlServer 2014 with the code below:
procedure TForm2.Button1Click(Sender: TObject);
const
sScript = 'select m.*, d.* from master m join detail d on, m.masterid = d.masterid';
var
FDScript : TFDSqlScript;
begin
try
FDConnection1.Connected := True;
FDScript := FDScript1.SQLScripts.Add;
FDScript.SQL.Text := sScript;
//FDScript1.ExecuteAll;
//FDScript1.ValidateAll;
// FDScript1.ValidateAll then
//FDScript1.ExecuteAll;
FDQuery1.SQL.Text := sScript;
FDQuery1.Open();
except
//EMSSQLNativeException
on E: EMSSQLNativeException do
begin
//ShowMessage('Erro'+FDGUIxErrorDialog1.ErrorDialog.Caption);
//Memo1.Clear;
Memo1.Text := 'Erro de Sistema: '+#13#10+ E.Message;
end;
end;
end;
Note the blatantly wrong syntax in the Sql statement, namely the comma after the on.
When FDQuery1.Open is called, this exception is raised (and caught initially by the debugger)
---------------------------
Debugger Exception Notification
---------------------------
Project sqlerror.exe raised exception class EMSSQLNativeException with message '[FireDAC][Phys][ODBC][Microsoft][SQL Server Native Client 11.0][SQL Server]Incorrect syntax near 'd'.'.
---------------------------
Break Continue Help
---------------------------
When I click Continue, execution proceeds into your exception handler, exactly as #TomBrunberg described in a comment
and the exception's message text is inserted into Memo1.
So I cannot reproduce the behaviour you describe based on the information in your question. It
must be caused by something you are not telling us, possibly in code you have
not included in your q or some property setting of the components you are
using.
Hopefully, trying the code above, you will find the debugger behaving as I
have described and this may give you some clue as to why you are getting the
problem you've described. Note that it is very important that you try the code
above in a new project, not your existing one. The only property setting
you need to do before executing it is to set FDConnection1 so that it can connect
to your server.
FWIW, if I uncomment-out the lines
FDScript1.ValidateAll then
FDScript1.ExecuteAll;
they execute without complaint, and I get the exact same behaviour as i've described without them.
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'"
Does anyon know how to create word ole object in DLL.
I have one application that load a DLL which in turn create word ole object.
My application crash every time.
MSWord:= CreateOleObject('Word.Application');
Assuming that Word is installed, then the primary reason why you code might fail is that COM has not been initialized in the calling thread. That is not something that should be attempted from the DLL, because you want the DLL to be able to work with consumers that have already initialized COM.
So, the correct way to tackle this is to state as part of the DLL's interface contract that COM must be initialized by the caller. Typically by calling CoInitialize or CoInitializeEx.
One further comment, is that it if the application crashes, that suggests that you error handling is broken. All the functions in your DLL should take steps to catch any exceptions and convert into error codes to be returned to the caller. I suspect that you have not done this and are throwing a Delphi exception out of the DLL. You must never do that.
Note that I have given a broad and general answer. That matches the broad nature of the question, and the fact that there are few details in the question. If you had provided an MCVE we could have offered a more detailed response.
As DavidH points out, CoInitialize has to be called in the calling thread.
A point to watch out for in connection with the main thread of a VCL application is that whether a VCL application calls CoInitialize automatically depends on whether it uses the ComObj unit: if it does the CoInitialize is called via TApplication.Initialize and the InitComObj routine in ComObj; if it does not, you must call it (or CoInitializeEx) yourself.
The easy way to test this is to call the DLL from a TApplication-less console application - this will avoid being misled by ComObj being used some other than your main unit.
Suppose you have a DLL that contains the following exported procedure:
procedure CreateWordDoc;
var
DocText : String;
MSWord,
Document : OleVariant;
begin
MSWord := CreateOleObject('Word.Application');
MSWord.Visible := True;
Document := MSWord.Documents.Add;
DocText := 'Hello Word!';
MSWord.Selection.TypeText(DocText);
end;
then you could call it like this:
program WordCaller;
{$APPTYPE CONSOLE}
uses
SysUtils, Windows, ActiveX;
type
TWordProc = procedure;
var
LibHandle : THandle;
WordProc : TWordProc;
begin
CoInitialize(Nil);
LibHandle := LoadLibrary('WordDll.Dll');
try
if LibHandle <> 0 then begin
try
WordProc := GetProcAddress(LibHandle, 'CreateWordDoc');
if Assigned(WordProc) then
WordProc;
finally
FreeLibrary(LibHandle);
end;
end;
finally
CoUnInitialize;
Readln;
end;
end.
I have a procedure to call a function named [main()] from a DLL , this is the Caller procedure :
procedure call_dll(path:string);
var
lib: HMODULE;
mainfn: procedure(); stdcall;
begin
if FileExists(path) then
begin
try
lib := LoadLibrary(PAnsiChar(path));
Win32Check(lib <> 0);
try
#mainfn := GetProcAddress(lib, 'main');
Win32Check(Assigned(mainfn));
mainfn();
finally
FreeLibrary(lib);
end;
except
end;
end;
end;
Until yet every thing is working fine , I mean after writing the correct path of the DLL everything work without a problem but if I write a wrong path (other file path) in the path parameter it show me a popup error that this is is not a Win32 DLL but I don't want to bother the user with this type of errors , so I need a function to check the DLL and if it's not then it will automatically ask for another file again without showing the popup error ?
It is your code that is raising the exception. Your code makes an explicit choice to handle errors by raising exceptions. The exception is raised by your code here:
Win32Check(lib <> 0);
If you don't want to raise an exception, don't use Win32Check. Instead check the value of lib and handle any errors by whatever means you see fit.
The same issue is present for your other use of Win32Check.
Of course you are swallowing all exceptions with your catch all exception handler. A catch all exception handler is usually a bad idea. I don't understand why you have included that. I think you should remove it.
So if you are seeing dialogs when running outside the debugger it follows that the system is producing the dialogs. You should be disabling the system's error message dialogs by calling SetErrorMode on startup passing SEM_FAILCRITICALERRORS.
var
Mode: DWORD;
....
Mode := SetErrorMode(SEM_FAILCRITICALERRORS);
SetErrorMode(Mode or SEM_FAILCRITICALERRORS);
The somewhat clunky double call is explained here: http://blogs.msdn.com/b/oldnewthing/archive/2004/07/27/198410.aspx
How could I create a Console Application that could work with or without a GUI?
For example, say if I had a console application, If i tried launching this console app from Windows Explorer it will not work it will just close, but I could call it from my GUI Application or the Windows Command Console (cmd.exe) and pass some switches (parameters?) to it.
That way some useful functions can be used without even starting the GUI Application, they can be called from the command line.
EDIT
I am not sure how to create the Console Application, especially that would accept flags (switches, parameters?).
I have seen some Applications that do something similar. For example they might have a Console Application that will convert a bmp to a png, and the GUI calls this Console Application and passes the arguments etc to it.
Hope that makes sense.
So how could I employ something like this?
Thanks.
For example, say if I had a console application, If i tried launching this console app from Windows Explorer it will not work it will just close, but I could call it from my GUI Application or the Windows Command Console (cmd.exe) and pass some switches (parameters?) to it.
It will work. However, the console window will disappear as soon as your program has exited. If you want to give the user a chance to read the output of your console application before the window is closed, simply end your program with a single
Readln;
or
Writeln('Press Enter to exit.');
Readln;
If you want to use a console window for output (or input) in a GUI application, you can give the AllocConsole and FreeConsole functions a try.
Command-line arguments (such as myapp.exe /OPEN "C:\some dir\file.txt" /THENEXIT) can be used in all types of Windows applications, both GUI apps and console apps. Just use the ParamCount and ParamStr functions.
How to Create a Console Application that Accepts Command-Line Arguments
In the Delphi IDE, choose File/New/Console Application. Then write
program Project1;
{$APPTYPE CONSOLE}
uses
Windows, SysUtils;
var
freq: integer;
begin
if ParamCount = 0 then
Writeln('No arguments passed.')
else if ParamCount >= 1 then
if SameText(ParamStr(1), '/msg') then
begin
if ParamCount = 1 then
Writeln('No message to display!')
else
MessageBox(0, PChar(ParamStr(2)), 'My Console Application',
MB_ICONINFORMATION);
end
else if SameText(ParamStr(1), '/beep') then
begin
freq := 400;
if ParamCount >= 2 then
if not TryStrToInt(ParamStr(2), freq) then
Writeln('Invalid frequency: ', ParamStr(2));
Windows.Beep(freq, 2000);
end;
end.
Compile the program. Then open a command processor (CMD.EXE) and go to the directory where Project1.exe is.
Then try
C:\Users\Andreas Rejbrand\Documents\RAD Studio\Projects>project1
No arguments passed.
C:\Users\Andreas Rejbrand\Documents\RAD Studio\Projects>project1 /msg
No message to display!
C:\Users\Andreas Rejbrand\Documents\RAD Studio\Projects>project1 /msg "This is a test."
C:\Users\Andreas Rejbrand\Documents\RAD Studio\Projects>project1 /beep
C:\Users\Andreas Rejbrand\Documents\RAD Studio\Projects>project1 /beep 600
C:\Users\Andreas Rejbrand\Documents\RAD Studio\Projects>
How to pass three arguments
if ParamCount >= 1 then
begin
if SameText(ParamStr(1), '/CONVERT') then
begin
// The user wants to convert
if ParamCount <= 2 then
begin
Writeln('Too few arguments!');
Exit;
end;
FileName1 := ParamStr(2);
FileName2 := ParamStr(3);
DoConvert(FileName1, FileName2);
end;
end;
That way some useful functions can be used without even starting the GUI Application, they can be called from the command line.
If you want the application to be a GUI application, but you want to override the GUI by passing command line parameters, then try someting like this:
program Project1;
uses
Forms,
SysUtils,
Windows,
Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
begin
if ParamCount > 0 then
Windows.MessageBox(GetDesktopWindow, PChar(ParamStr(1)), PChar('Test'), 0)
else
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end;
end.
I am not sure how to achieve this in delphi but in C# I just checked in the Main method if any command line arguments had been passed in, if there were then run the application with a console if there were none run the GUI.