use Console Application with GUI Application? - delphi

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.

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).

Is there a way to execute only code from a unit from a big exe placed on shared drive?

I admit i am in the dirty tricks area in this question.
PROBLEM: exe is deployed on shared folder(I call it ShaExe from now on), without changing deployment rules somehow I want to cache the file locally (I call it LocExe fromnow on) and while launching ShaExe i finally end up running LocExe. LocExe must be updated (copied from ShaExe) everytime ShaExe is modified (=updated).
Somehow what I am researching is setting up something like this:
Prerequisite: LocExe path is an user\AppData\Local\MyApp harcoded path so ShaExe knows where LocExe is.
ShaExe is launched
ShaExe compares its size with the size of LocExe, if different it copies ShaExe over LocExe
ShaExe executes LocExe with the -skiplauncher command line parameter
ShaExe terminates itself
when -skiplauncher is used points 2 3 4 of the above list are not executed (this is to avoid an endless loop of executing LocExe and terminating the applcation)
Now this approach works, but the bottleneck is (1) because ShaExe size is 110MB so somehow launching an exe from shared path takes time (Even if I do not use the flag {$SetPEFlags IMAGE_FILE_NET_RUN_FROM_SWAP}).
I tried to prepare a very small version of ShaExe (basically built without units but only with the launcher logic in it ending up in 6MB of exe).
WIth this "light but useless ShaExe" the whole process is faster.
I would like that (2) (3) and (4) are executed immediately, is this possible or not with Windows?
I am perfectly aware the best practice would be either to copy manually the file locally and run it or create a simple launcher exe that does the job of running LocExe doing the cache when necessary, but I have a very big customer that requires me to do this without changing the deployment procedure.
THis is why I ask for expert advice here.
This is my code (of course simplified, instead of Unit1 imagine 100 units):
//this is the dpr
program MYProgram;
uses
Vcl.Forms,
execache in 'execache.pas',
Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
begin
RunCachedExe;
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
//this is the execache unit
unit execache;
interface
uses
Vcl.Forms,
Windows,
ShellAPi,
sysutils,
dialogs,
system.IOUtils;
procedure RunCachedExe;
implementation
procedure RunCachedExe;
function GetFileSize(const aFilename: String): Int64;
var
info: TWin32FileAttributeData;
begin
result := -1;
if NOT GetFileAttributesEx(PWideChar(aFileName), GetFileExInfoStandard, #info) then
EXIT;
result := Int64(info.nFileSizeLow) or Int64(info.nFileSizeHigh shl 32);
end;
var
CurrentInstanceSize, CachedFileSize, i: integer;
CachedFilePath, Outs: string;
SkipLauncher: Boolean;
begin
SkipLauncher := False;
for i := 0 to paramcount -1 do
begin
if Paramstr(i) = '-SkipLauncher' then
SkipLauncher := True;
end;
if not SkipLauncher then
begin
// in this code path is hardcoded, in real life I use a function that calls:
// SHGetSpecialFolderPath with CSIDL_LOCAL_APPDATA
CachedFilePath := 'C:\Users\myuser\AppData\Local\MyProject\bincache\MyProject.exe';
CurrentInstanceSize := GetFileSize(Application.ExeName);
// this will return -1 if file not found
CachedFileSize := GetFileSize (CachedFilePath);
if CachedFileSize <> CurrentInstanceSize then
begin
ForceDirectories('C:\Users\myuser\AppData\Local\MyProject\bincache\');
CopyFile(PChar(Application.ExeName), PCHar(CachedFilePath), False);
end;
// launch local exe and close this one
ShellExecute (0, 'open', PWideChar( CachedFilePath ), PWideChar('-SkipLauncher'), nil, SW_SHOWNORMAL);
Application.Terminate;
end;
end;
This code uses some functions from other units but I could move all in the execache unit, if this could help to speed up the execution of the exe.
Somehow I would like to run only an initialization part, containing the logic described here and then terminate the app (when ShaExe is in place, that basically means when -skipLauncher is not used).
So, is it possible to run only an initialization logic of a big exe file?
I hope I expressed myself, thanks.
Regardless of the code you have, what you are essentially asking is if partially loading an executable is possible, to have it conditionally execute only a portion of the code it contains. Or a streaming executable perhaps? These are not possible, the OS will have to fetch the entire runtime from the remote location, together with any static dependencies, to the local computer. Even if the executable is to exit immediately.
You can have the behavior you want by separating the conditional part to a different executable though: a launcher. Without any VCL dependency you can have it really small. Even then, the better alternative is to run the executable locally to have it check if a remote location contains an updated version, since networked access is more prone to performance and security issues.

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

Delphi Windows Service with Console App Stdout Stream Included

I have written a number of services in the past for Delphi using the framework. I would now like to extend a service with some console like features.
The easiest example I can provide is that I'd like to run the service executable with something like the following from a Command prompt.
> myservice.exe /version
MyService Version 1.0
In the project file, I'd handle the parameter and exit prior to the service initializing and be done.
If ParamStr(1) = '/version' then
begin
writeln ('MyService Version 1.0');
exit;
end;
// Other standard service launch code is after this for proper initialization
// when run as a service, i.e.
if not Application.DelayInitialize or Application Installing then
...
However to get a writeln statement to work, typically I would need the directive {$APPTYPE CONSOLE} in the project file which then breaks the service app Destroy event.
Is there another way to wire up standard output to a console without using the {$APPTYPE CONSOLE} directive for a Delphi Windows Service App?
New own console
begin
if paramstr(1)='/?' then
begin
if Windows.AllocConsole then
try
WriteLn;
// irgendeine sinnvolle Information, z.B.:
WriteLn('Your Info');
readln;
finally
FreeConsole;
end;
end
else
begin
//Your Appcode
or attach to console, without creating own console
begin
if paramstr(1) = '/?' then
begin
if AttachConsole($FFFFFFFF) then
begin
WriteLn('Your Info');
Readln;
FreeConsole;
end;
end
else
begin
// Your Appcode

Added the {APPTYPE CONSOLE} directive and now my application runs very slowly. Moving the mouse makes it run faster

I am trying to extend a 3rd party application so that it can be invoked via command line in addition to using the windows form GUI (mixed mode is desired). It's a fairly simple program which basically loads a file and then you click a button it starts sending UDP network packets.
I need to invoke the application from another and would like to pass in an argument and need to be able to return the ExitCode to the calling app. From what i've read, in order to do so you need to add the compiler directive {APPTYPE CONSOLE}.
I did this and my application worked as I wanted it to except sending the network packets slowed down to a crawl. I found that whenever I moved my mouse around on the form. That the network transfer rate increased significantly. I suspect there is some type of Windows Message queue problem and moving mouse is causing interrupts which in turn is causing the message queue to be processed?
I have googled around and tried calling Application.ProcessMessages and PeekMessages in a Timer with a 1ms interval and that didn't help at all. I found in this user manual for some other application it says that Indy 10 is supported in both APPTYPE CONSOLE and GUI types. Quite frankly this just confuses me as I would have assumed that all network library would work in both modes... but like I said I'm not familiar with Delphi.
I am positive that the issue is isolated to a single line in my application and that is whether or not {APPTYPE CONSOLE} is included or not.
Anyone have any ideas?
Version Info:
Delphi 7 Personal (Build 4.453)
Indy 9.0.4
If you add {APPTYPE CONSOLE} to your application even though you desire mixed mode execution, then you will have to live with a console even when the application is in GUI mode. You can of course close the console, but this will cause some flicker and feels a bit hackish to me.
You should be able to do what you want without a console program. A small test program proves that the exit code can be read from a GUI program:
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Close;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ExitCode := 42;
Timer1.Interval := 1000;
Timer1.Enabled := TRUE;
end;
If this is executed with the following cmd file:
#echo off
start /WAIT project1.exe
echo %ERRORLEVEL%
the program shows its main form for 1 second, closes, and the script prints 42 to the console window.
Now for capturing the output - doing this from a GUI program is actually easier than doing it from a console program, if you allow for the use of a temporary file. You need to start the program with a command line parameter anyway, so why not give it the name of a temporary file, wait for the application to finish, read in the file and delete it afterwards?
If you want an application to return an "error" code there is no need to make it a console application. You only need to set the ExitCode, e.g.
ExitCode := 10;
in a batch file
#Echo off
project1
echo %errorlevel%
Will display the application, then display 10 when.
Note: It is also possible to create a console window dynamically from the windows API using AllocConsole or to attach using AttachConsole.
I created an object wrapper for this once, but no longer have the code available. From memory it didn't support redirection (because I didn't need it).
If I understand you correctly, then you want your app to have two modes:
If no argument is passed, run in GUI mode
Run in non-GUI mode otherwise
The easiest is if you can centralize your logic so it can be called from one method (CoreLogic in my example).
The below app then should work fine.
Two tricks:
Application.ShowMainForm := False; that will not make the MainForm show at all.
ExitCode := 327; which will set your return code (like mghie and Gerry already mentioned).
A few notes:
because the CoreLogic does not process any windows messages, anything in your application that depends on Windows messages being processed will stall.
if you need windows message processing, then just all Application.ProcessMessages() inside your CoreLogic
if you need your form to be visible, then you change the logic inside your MainForm to test for the commandline parameters, and exit when it's work as been done (by calling Application.Terminate()). The best place to put that logic in is the event method for the MainForm.OnShow event.
Hope this helps :-)
program VCLAppThatDoesNotShowMainForm;
uses
Forms,
MainFormUnit in 'MainFormUnit.pas' {MainForm},
Windows;
{$R *.res}
procedure CoreLogic;
begin
Sleep(1000);
ExitCode := 327;
end;
procedure TestParams;
begin
if ParamCount > 0 then
begin
MessageBox(0, CmdLine, PChar(Application.Title), MB_ICONINFORMATION or MB_OK);
CoreLogic();
Application.ShowMainForm := False;
end;
end;
begin
Application.Initialize();
Application.MainFormOnTaskbar := True;
TestParams();
Application.CreateForm(TMainForm, MainForm);
Application.Run();
end.
A timer with 1ms will only fire about every 40 ms (due to Windows limitations), so it won't help. I have seen effects like you describe with mixed console and GUI apps, another is that they don't minimize properly.
Instead of enabling the console in the project, you could probably use the CreateConsole API call (Not sure whether the name is correct) to create one after the programm was started. I have seen no adverse effects in the one (!) program I have done this.
But this is only necessary if you want to write to the console. If you only want to process command line parameters and return an exit code, you do not need a console. Just evaluate the ParamCount/ParamStr functions for the parameters and set ExitCode for the return value.
If some threads in your console application call Synchronize (and I guess the Indy stuff is actually doing that), you have to make some preparations:
Assign a method to the WakeMainThread variable. This method must have the signature of TNotifyEvent.
Inside this method call CheckSynchronize.
For additional information see the Delphi help for these two items.

Resources