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
Related
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.
I have an application, and I put a shortcut to it inside win+startup folder, and all is ok.
Now I wish to change this approach, by coding it, and so I have used the code listed at the bottom of this post.
The code inputs a key inside HKLM, but there is a windows error when system starts:
Access violation at address 004815EB in module 'ap1.exe'. Read of address 00000000.
This error is similar on 3 different computers, running win xp or win 7.
procedure SetAutoStart(AppName, AppTitle: string; bRegister: Boolean);
const RegKey = '\Software\Microsoft\Windows\CurrentVersion\Run'; // Run or
RunOnce
var Registry: TRegistry;
begin
Registry := TRegistry.Create;
try Registry.RootKey := HKEY_LOCAL_MACHINE;
if Registry.OpenKey(RegKey, False)
then begin
if bRegister = False then Registry.DeleteValue(AppTitle)
else Registry.WriteString(AppTitle,
AppName);
end;
finally Registry.Free;
end;
end;
The error is raised by the program that is executed on startup. It has nothing at all to do with the code in the question. You can verify that the code in the question behaves as expected by checking the registry entries using the Registry Editor.
You will need to debug the program that is being executed at startup. You won't be able to attach an interactive debugger. Instead you will need to use trace debugging.
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'"
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;
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.