I am writing a MDI Text Editor and I was wondering how can I open all text files with my app. (If I associate te *.txt to my app I want that each time someone double-clicks on a txt file to open it in my app, in a new child window)
thanks
The solution to this is also the solution to not allowing more than one application to run at the same time. What you want to do is first detect that the program is already running, then pass a parameter to the running application and shut down.
There are several methods to determine if your application is already running. Once you pick one that fits your programming preferences, the next step is to feed the file to open to your running program. This can be done via named pipes, messages (although messages do fail on Vista/Win7 if your app is running in another security context), or any other method of IPC.
I Currently have the following implementation for this :
The .dpr file
var
PrevWindow : HWND;
S : string;
CData : TCopyDataStruct;
begin
PrevWindow := 0;
if OpenMutex(MUTEX_ALL_ACCESS, False, 'YourUniqueStringHere') <> 0 then
begin
PrevWindow:=FindWindow('TYourMainFormClassName', nil);
if IsWindow(PrevWindow) then
begin
SendMessage(PrevWindow, WM_SYSCOMMAND, SC_RESTORE, 0);
BringWindowToTop(PrevWindow);
SetForegroundWindow(PrevWindow);
if FileExists(ParamStr(1)) then
begin
S:=ParamStr(1);
CData.dwData:=0;
CData.lpData:=PChar(S);
CData.cbData:=1+Length(S);
SendMessage(PrevWindow, WM_COPYDATA, 0, DWORD(#CData) );
end;
end;
end
else
CreateMutex(nil, False, 'YourUniqueStringHere');
in the main unit we process the WM_COPYDATA message :
we declare the message handler
procedure ReceiveData_Handler ( var msg : TWMCopyData ) ; message WM_COPYDATA;
procedure TForm1.ReceiveData_Handler(var msg: TWMCopyData);
begin
// Your file name is in the msg.CopyDataStruct.lpData
// Cast it to PChar();
end;
Hope it works for you.
Check out the Windows DDE documentation. I modify the DDEExec options in the registry, so the shell correctly directs the opened file to my existing application instance. The following code makes the registry changes necessary. Replace "AppName" with your application name (and remove the brackets).
// add the ddeexec key
if not reg.OpenKey( '\Software\Classes\<AppName>.file\shell\open\ddeexec', true ) then
raise Exception.Create( 'Error setting ddeexec key' );
try
reg.WriteString( '', 'FileOpen("""%1""")' );
finally
reg.CloseKey;
end;
// modify the command key to not include the parameter, as we don't use it
if not reg.OpenKey( '\Software\Classes\<AppName>.file\shell\Open\command', true ) then
raise Exception.Create( 'Error opening command key.' );
try
strTemp := reg.ReadString( '' );
strTemp := StringReplace( strTemp, '"%1"', '', [] );
reg.WriteString( '', strTemp );
finally
reg.CloseKey;
end;
I don't know the version of Delphi that you're using, but in Delphi 7 at the examples folder you will see a MDI Text Editor example.
Related
I'm trying to create a service to communicate with a COM object that controls on BarCode Scanner (model Motorola Symbol LS9208).
I created the TLB unit from ocx installed with scanner driver application.
In Delphi, I had create a DataModule that make all the work I put the start and stop code of scanner in ServiceExecute procedure:
Initialize the TScanner COM object (declared in TLB)
Set some scanner properties, "claim" the control and set one TScanner event with one of the procedures in DataModule Service to fire when barcode is readed
After all, before close, release and free the TScanner object
procedure TInteliPEDCheckService.ServiceExecute(Sender: TService);
begin
debugLog( 'initialization of service thread...' );
CheckInitParams;
//DataModule_Create;
startScanner;
//loopback
while (not Terminated) do
begin
Sleep(100);
ServiceThread.ProcessRequests(False);
end;
debugLog( 'end of service loop...' ); //only reached if stop service before read codbar
stopScanner;
//DataModule_Free;
debugLog( 'finalization of service thread...' );
end;
procedure TInteliPEDCheckService.startScanner;
var HR : HRESULT;
begin
//CoInitialize( nil );
//CoInitializeEx( nil, COINIT_MULTITHREADED );
//CoInitializeEx( nil, COINIT_MULTITHREADED or COINIT_DISABLE_OLE1DDE );
//CoInitializeEx( nil, COINIT_APARTMENTTHREADED );
CoInitializeEx( nil, COINIT_APARTMENTTHREADED or COINIT_DISABLE_OLE1DDE );
// I've tried all options above, but without success
HR := CoInitializeSecurity(
nil, -1, nil, nil,
1, // RPC_C_AUTHN_LEVEL_NONE
1, // RPC_C_IMP_LEVEL_ANONYMOUS
nil,
0, // EOAC_NONE
nil);
debugLog( 'HRESULT of CoInitializeSecurity: ' + IntToStr( HR ) );
// the log always shows 0 as HRESULT
Scanner := TScanner.Create( nil );
Scanner.Open( 'STI_USBSCANNER' );
if Scanner.Claim(0) = 0 then
begin
Scanner.OnDataEvent := ScannerDataEvent;
//Scanner.OnDirectIOEvent := ScannerDirectIOEvent;
Scanner.DeviceEnabled := TRUE;
Scanner.DataEventEnabled := TRUE;
Scanner.FreezeEvents := FALSE;
end
else
begin
Scanner.DeviceEnabled := FALSE;
Scanner.DataEventEnabled := FALSE;
Scanner.FreezeEvents := FALSE;
end;
// Caption := IfThen( Scanner.Claimed, 'OK', 'FAIL' );
end;
procedure TInteliPEDCheckService.stopScanner;
begin
if ( Scanner <> nil ) then
begin
Scanner.Release;
Scanner.Free;
CoUninitialize();
end;
end;
procedure TInteliPEDCheckService.ScannerDataEvent(ASender: TObject; lStatus: Integer);
begin
debugLog( 'will fire...' ); //never reached
debugLog( 'FIRED: ' + Scanner.ScanData );
//Scanner.DeviceEnabled := FALSE;
//Sleep(1000);
Scanner.DeviceEnabled := TRUE;
Scanner.DataEventEnabled := TRUE;
end;
When I create the DataModule run this code into an EXE application, everything works fine. But now I'm migrating this funcionality to a Windows Service.
As first problem, the error of "CoInitialize not called" fired and nothing works.
So, I tried to CoInitialize() in service's DPR, before Application.Initialize but don't solve, and error keep raising.
The error only gone when I put CoInitialize() just before TScanner.Create(nil), so the scanner starts and I could see the laser ready to read barcodes.
Now the scanner starts fine with the service start, and stop fine too when service stops.
But if I read some barcode, and the event OnDataEvent is about to be triggered, the service is halted, and no data is received, an (of course) I lost the control of the scanner, needing to restart the service.
I read some instructions of how to CoInitialize the COM objects (https://msdn.microsoft.com/en-us/library/windows/desktop/ff485844(v=vs.85).aspx) and found some mention about marshalling technique to transport data between COM objects and other threads. But I'm not sure if this is the problem... and I don't know how to do that.
The service always halt when I scan some barcode. If not, starts and stops without errors, and the laser lights on when start, and light off when stop, demonstrating normal and correct command and operation.
Please, some one can help me to solve this problem?
Thanks in advance!
I once solved COM trouble within an NT-service by calling CoInitializeSecurity right after CoInitialize. I wrote more about it here. Apart from that it is important to get the threading right. So be sure by debugging the 'normal' application version and using GetCurrentThreadId that the incoming data from the scanner is handled by the exact thread you expect it on.
In my program, the user completes a form and then presses Submit. Then, a textfile or a random extension file is created, in which all the user's information is written. So, whenever the user runs the application form, it will check if the file, which has all the information, exists, then it copies the information and pastes it to the form. However, it is not working for some reason (no syntax errors):
procedure TForm1.FormCreate(Sender: TObject);
var
filedest: string;
f: TextFile;
info: array[1..12] of string;
begin
filedest := ExtractFilePath(ParamStr(0)) + 'User\Identity\IdentityofMyself.txt';
if FileExists(filedest) then
begin
AssignFile(f,filedest);
Reset(f);
ReadLn(info[1], info[2], info[3], info[4], info[5], info[6], info[7],
info[8], info[9], info[10], info[11], info[12]);
Edit1.Text := info[1];
Edit2.Text := info[2];
ComboBox1.Text := info[3];
ComboBox5.Text := info[4];
ComboBox8.Text := info[4];
ComboBox6.Text := info[5];
ComboBox7.Text := info[6];
Edit3.Text := info[7];
Edit4.Text := info[8];
Edit5.Text := info[11];
Edit6.Text := info[12];
ComboBox9.Text := info[9];
ComboBox10.Text := info[10];
CloseFile(f);
end
else
begin
ShowMessage('File not found');
end;
end;
The file exists, but it shows the message File not found. I don't understand.
I took the liberty of formatting the code for you. Do you see the difference (before, after)? Also, if I were you, I would name the controls better. Instead of Edit1, Edit2, Edit3 etc. you could use eFirstName, eLastName, eEmailAddr, etc. Otherwise it will become a PITA to maintain the code, and you will be likely to confuse e.g. ComboBox7 with ComboBox4.
One concrete problem with your code is this line:
readln(info[1], info[2], info[3], info[4], info[5], info[6], info[7],
info[8], info[9], info[10], info[11], info[12]);
You forgot to specify the file f!
Also, before I formatted your code, the final end of the procedure was missing. Maybe your blocks are incorrect in your actual code, so that ShowMessage will be displayed even if the file exists? (Yet another reason to format your code properly...)
If I encountered this problem and wanted to do some quick debugging, I'd insert
ShowMessage(BoolToStr(FileExists(filedest), true));
Exit;
just after the line
filedest := ...
just to see what the returned value of FileExists(filedest) is. (Of course, you could also set a breakpoint and use the debugger.)
If you get false, you probably wonder what in the world filedest actually contains: Well, replace the 'debugging code' above with this one:
ShowMessage(filedest);
Exit;
Then use Windows Explorer (or better yet: the command prompt) to see if the file really is there or not.
I'd like to mention an another possibility to output a debug message (assuming we do not know how to operate real debugger yet):
{ ... }
filedest := ExtractFilePath(ParamStr(0)) + 'User\Identity\IdentityofMyself.txt';
AllocConsole; // create console window (uses Windows module) - required(!)
WriteLn('"' + filedest + '"'); // and output the value to verify
if FileExists(filedest) then
{ ... }
I want to create and then open a txt file using the ShellExecute command.
I have used this code for years with Delphi 7 and it worked:
function Execute(CONST ExeName, Parameters: string): Boolean;
begin
Result:= ShellExecute(0, 'open', PChar(ExeName), PChar(Parameters), nil, SW_SHOWNORMAL)> 32;
end;
Now, I switched to Windows 7 and the code is not working anymore when it runs from IDE. Delphi shows the CPU window with the caption "CPU-Process unknown (2352)". I close the CU windows and everything works fine until I close the application, when Delphi shows the CPU window one more time.
If I run the app from outside IDE, it works fine.
Looks like the debugger has something to say to me, but I don't know what.
Sounds to me like you have the "debug spawned processes" option turned on. When that's enabled, the debugger interrupts the new process at the earliest possible time. Press the "run" button to let it continue running.
You can confirm this hypothesis the next time you debug your program. Compare the process ID (2352, in your example) with the list of processes shown by Task Manager. Which process in that list matches the process ID reported by the debugger?
This is not the answer for your question (I vote for Rob Kennedy & Chris Thornton), but you can write your routine in a more compact way:
function Executa(const ExeName, Parameters: string): Boolean;
begin
Result :=
(ShellExecute(0, 'open', PChar(ExeName), Pointer(Parameters), nil, SW_SHOWNORMAL) > 32);
end;
Note Pointer() instead of PChar() for 4th argument. This is a documented behaviour of PChar/Pointer casts (see help).
I had a problem yesterday with the debugger crashing my application, but running it outside the IDE it would run fine. I was using packages in my development.
I used process explorer to verify I found I was loading a copy from another location than expected. I had two copies of the same BPL floating around. Once I removed the one I was not compiling I was fine.
Applying that to this problem, I would check to make sure you don't have any copies of compiled code that includes: .DCU, .DCP, .BPL, .EXE around. Then I would also make sure you you can ctrl-click on "ShellExecute" to and see the declaration. You may have your library path setup in a way that it can't find the source.
Shot in the dark here, but try running the IDE as administrator, and then not as administrator. That may be a factor. Some users make a shortcut with the administrator option set, so that the auto-update runs successfully. So you may be running the IDE as admin, if you've done that.
Same by me , i solved it by replacing ShellExecute with following:
function TformMain.CreateProcessSimple(
sExecutableFilePath : string )
: string;
function GetExeByExtension(sExt : string) : string;
var
sExtDesc:string;
begin
with TRegistry.Create do
begin
try
RootKey:=HKEY_CLASSES_ROOT;
if OpenKeyReadOnly(sExt) then
begin
sExtDesc:=ReadString('') ;
CloseKey;
end;
if sExtDesc <>'' then
begin
if OpenKeyReadOnly(sExtDesc + '\Shell\Open\Command') then
begin
Result:= ReadString('') ;
end
end;
finally
Free;
end;
end;
end;
var
pi: TProcessInformation;
si: TStartupInfo;
fapp: string;
begin
fapp:=GetExeByExtension(ExtractFileExt(sExecutableFilePath));
FillMemory( #si, sizeof( si ), 0 );
si.cb := sizeof( si );
if Pos('%1',fApp)>0 then begin
sExecutableFilePath:=StringReplace(fapp,'%1',sExecutableFilePath,[rfReplaceAll]);
end else begin
sExecutableFilePath:=fApp+' "'+sExecutableFilePath+'"';
end;
CreateProcess(
Nil,
// path to the executable file:
PChar( sExecutableFilePath ),
Nil, Nil, False,
NORMAL_PRIORITY_CLASS, Nil, Nil,
si, pi );
// "after calling code" such as
// the code to wait until the
// process is done should go here
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
end;
ShellExecuteW solve my problems (XE2/Win7/32bit) with "debug spawned processes" option turned off
:)
mybe because strings and pchar are wide pointer from 2010
I'm using a TFileSteam to open a log file. I would like to be able to read through this log file from other processes. I thought the fmShareDenyWrite mode would allow this.
However if I try to open the file from other processes, I get an error. For example, if I try and type the file from the command line, I get "the process can not access the file because it is being used by another process".
Here is the file initialization code:
if FileExists(AutoLogFileName) then
_ActivityLogStream := TFileStream.Create(AutoLogFileName,
fmOpenReadWrite or fmShareDenyWrite)
else
_ActivityLogStream := TFileStream.Create(AutoLogFileName,
fmCreate or fmShareDenyWrite);
NOTE:
I am using Delphi version 6.
Don't know whether this was already a bug in D6, but that is a distinct possibility. There is a QC report on this reported against D2007: QC 65767: http://qc.embarcadero.com/wc/qcmain.aspx?d=65767. This report is now closed, as it was resolved in D2010 (14.0.3467.22472 to be exact).
Update (prompted by Gabr's comment):
You can create your own TFileStream descendant that does honor the mode. Just override the Create(const AFileName: string; Mode: Word; Rights: Cardinal) constructor (there are two overloaded constructors) and handle the mode parameter yourself. Copy the code from the original constructor and change the
if Mode = fmCreate then
begin
inherited Create(FileCreate(AFileName, Rights));
to
if (Mode and fmCreate = fmCreate) then
begin
myMode := Mode and $FF;
if myMode = $FF then
myMode := fmShareExclusive;
inherited Create(FileCreate(AFileName, myMode, Rights));
where myMode is a local var of type Word.
mfCreate mode does not behave/work correctly with any share attribute. To work around, you must create file handle yourself and pass it to the constructor
Cheer
A Delphi console application can be run from the command line of an existing console window, and it can be run by double-clicking on its icon. In the latter case it will create its own console window, and close it once the application terminates.
How can I tell if my console application has created its own window?
I want to detect this so that I can display a message like "Press Enter to close the window", to let the user read what's displayed before the window closes. Obviously, it wouldn't be appropriate to do that if the application is being run from the command line.
I'm using Delphi 2010, in case that's significant.
You have basically two things to test for:
Is the application console shared between processes? If you use cmd.exe to run a console application it will per default share the console, so you won't need to show the "Press Enter to close the window" message.
Is the output redirected to a file? If so it's not necessary to show the message either.
For the first one there is a simple solution in form of the GetConsoleProcessList() Windows API function. Unfortunately it is available only on Windows XP and later versions, but maybe that's good enough for you. It's not in the Delphi 2009 Windows unit, so you will have to import it yourself:
function GetConsoleProcessList(lpdwProcessList: PDWORD;
dwProcessCount: DWORD): DWORD; stdcall; external 'kernel32.dll';
Of course, if your software is otherwise able to run on earlier Windows versions you should use LoadLibrary() and GetProcAddress() instead.
Since you are only interested in whether the number of process handles is higher than 1 you can call it with a very small buffer for the handles, for example like this:
var
HandleCount: DWORD;
ProcessHandle: DWORD;
begin
HandleCount := GetConsoleProcessList(#ProcessHandle, 1);
// ...
end;
If your handle count is larger than 1 you have other processes keeping the console open, so you can skip showing the message.
You can use the GetFileInformationByHandle() Windows API function to check whether your console output handle references a real file or not:
var
StdOutHandle: THandle;
IsNotRedirected: boolean;
FileInfo: TByHandleFileInformation;
begin
StdOutHandle := GetStdHandle(STD_OUTPUT_HANDLE);
IsNotRedirected := not GetFileInformationByHandle(StdOutHandle, FileInfo)
and (GetLastError = ERROR_INVALID_HANDLE);
// ...
end;
This code is intended to get you started only, I'm sure there are some corner cases not handled properly.
I've used something like the below in the past:
program ConsoleTest;
{$APPTYPE CONSOLE}
uses Windows;
function GetConsoleWindow: HWND; stdcall; external kernel32 name 'GetConsoleWindow';
function IsOwnConsoleWindow: Boolean;
//ONLY POSSIBLE FOR CONSOLE APPS!!!
//If False, we're being called from the console;
//If True, we have our own console (we weren't called from console)
var pPID: DWORD;
begin
GetWindowThreadProcessId (GetConsoleWindow,pPID);
Result:= (pPID = GetCurrentProcessId);
end;
begin
writeln ('Hello ');
if IsOwnConsoleWindow then begin
writeln ('Press enter to close console');
readln;
end;
end.
I know, this is a old thread but i have a nice solution to this.
You don't have to mess around with batch files. The trick is in the type of exe, it's subsystem attribute. After compiling the exe as GUI application (without the {$APPTYPE CONSOLE} directive, you must change it's subsystem attribute IMAGE_SUBSYSTEM_WINDOWS_GUI to IMAGE_SUBSYSTEM_WINDOWS_CUI. Nice thing is when you execute the console app from a console it doesn't show an extra console window and at that point you don't need a message like "Press Enter to close the window". EDIT: In case you starting another console app inside a console app like i did in a project of mine)
When you run it from explorer etc by clicking it or by start|run, Windows opens automaticly a console window when the subsystem attribute is IMAGE_SUBSYSTEM_WINDOWS_CUI. You don't need to specify {$APPTYPE CONSOLE} directive, it's all about the subsystem attribute.
The solution of RRUZ is a solution i also using but with one important difference. I check the subsystem of the parent process to show a "Press Enter to close this window". RUZZ it's solution only works in two cases, when it is cmd or explorer. By simply check if it's parent process has the attribute is NOT IMAGE_SUBSYSTEM_WINDOWS_CUI, you can display the message.
But how to check the exe subsystem? I found a solution on torry tips (http://www.swissdelphicenter.ch/torry/showcode.php?id=1302) to get the PE Header info and modify it into two functions: setExeSubSys() and getExeSubSys(). With the setExeSubSys() i made a little console app so that i can change the exe's subsystem attribute after compiling (it is only 50 kb!).
After you have the parent/potential process filename, you can simply do something like this:
//In the very beginning in the app determine the parent process (as fast as is possible).
// later on you can do:
if( getExeSubSys( parentFilename ) <> IMAGE_SUBSYSTEM_WINDOWS_CUI ) then
begin
writeln( 'Press Enter to close the window' );
readln;
end;
Here are the two functions i made but it is not working with streams (like the torry example), i use my own easy unit for files for it without the silly exeption stuff. But basically i think you get the idea around it.
To set (and also to get when you not specifying a pointer to a longint (nil)):
type
PLongInt = ^LongInt;
function setExeSubSys( fileName : string; pSubSystemId : PLongInt = nil ) : LongInt;
var
signature: DWORD;
dos_header: IMAGE_DOS_HEADER;
pe_header: IMAGE_FILE_HEADER;
opt_header: IMAGE_OPTIONAL_HEADER;
f : TFile;
begin
Result:=-1;
FillChar( f, sizeOf( f ), 0 );
if( fOpenEx( f, fileName, fomReadWrite )) and ( fRead( f, dos_header, SizeOf(dos_header)))
and ( dos_header.e_magic = IMAGE_DOS_SIGNATURE ) then
begin
if( fSeek( f, dos_header._lfanew )) and ( fRead( f, signature, SizeOf(signature))) and ( signature = IMAGE_NT_SIGNATURE ) then
begin
if( fRead( f, pe_header, SizeOf(pe_header))) and ( pe_header.SizeOfOptionalHeader > 0 ) then
begin
if( fRead( f, opt_header, SizeOf(opt_header))) then
begin
if( Assigned( pSubSystemId )) then
begin
opt_header.Subsystem:=pSubSystemId^;
if( fSeek( f, fPos( f )-SizeOf(opt_header) )) then
begin
if( fWrite( f, opt_header, SizeOf(opt_header)) ) then
Result:=opt_header.Subsystem;
end;
end
else Result:=opt_header.Subsystem;
end;
end;
end;
end;
fClose( f );
end;
To get:
function GetExeSubSystem( fileName : string ) : LongInt;
var
f : TFile;
signature : DWORD;
dos_header: IMAGE_DOS_HEADER;
pe_header : IMAGE_FILE_HEADER;
opt_header: IMAGE_OPTIONAL_HEADER;
begin
Result:=IMAGE_SUBSYSTEM_WINDOWS_CUI; // Result default is console app
FillChar( f, sizeOf( f ), 0 );
if( fOpenEx( f, fileName, fomRead )) and ( fRead( f, dos_header, SizeOf(dos_header)))
and ( dos_header.e_magic = IMAGE_DOS_SIGNATURE ) then
begin
if( fSeek( f, dos_header._lfanew )) and ( fRead( f, signature, SizeOf(signature))) and ( signature = IMAGE_NT_SIGNATURE ) then
begin
if( fRead( f, pe_header, SizeOf(pe_header))) and ( pe_header.SizeOfOptionalHeader > 0 ) then
begin
if( fRead( f, opt_header, SizeOf(opt_header))) then
Result:=opt_header.Subsystem;
end;
end;
end;
fClose( f );
end;
If you want more info at the subsystem, just google or go to the MSDN website.
Hope it was helpful to anyone.
Greetz,
Erwin Haantjes
I use (can't remember where I found it):
function WasRanFromConsole() : Boolean;
var
SI: TStartupInfo;
begin
SI.cb := SizeOf(TStartupInfo);
GetStartupInfo(SI);
Result := ((SI.dwFlags and STARTF_USESHOWWINDOW) = 0);
end;
And then use it as such:
if (not WasRanFromConsole()) then
begin
Writeln('');
Writeln('Press ENTER to continue');
Readln;
end;
Wow Nick, that is really impressive! I have test your solution and works great.
So you can do something like this:
function isOutputRedirected() : boolean;
var
StdOutHandle : THandle;
bIsNotRedirected : boolean;
FileInfo : TByHandleFileInformation;
begin
StdOutHandle:= GetStdHandle(STD_OUTPUT_HANDLE);
bIsNotRedirected:=( NOT GetFileInformationByHandle(StdOutHandle, FileInfo)
and (GetLastError = ERROR_INVALID_HANDLE));
Result:=( NOT bIsNotRedirected );
end;
function isStartedFromConsole() : boolean;
var
SI: TStartupInfo;
begin
SI.cb := SizeOf(TStartupInfo);
GetStartupInfo(SI);
Result := ((SI.dwFlags and STARTF_USESHOWWINDOW) = 0);
end;
function GetConsoleSize() : _COORD;
var
BufferInfo: TConsoleScreenBufferInfo;
begin
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), BufferInfo);
Result.x:=BufferInfo.srWindow.Right - BufferInfo.srWindow.Left + 1;
Result.y:=BufferInfo.srWindow.Bottom - BufferInfo.srWindow.Top + 1;
end;
And finally:
var
cKey : Char;
fCursorPos : _COORD;
if( NOT isOutputRedirected() ) and( NOT isStartedFromConsole() ) then
begin
// Windows app starts console.
// Show message in yellow (highlight) and at the bottom of the window
writeln;
fCursorPos:=getConsoleSize();
Dec( fCursorPos.y );
Dec( fCursorPos.x, 40 );
SetConsoleTextAttribute( GetStdHandle(STD_OUTPUT_HANDLE), 14 );
SetConsoleCursorPosition( GetStdHandle(STD_OUTPUT_HANDLE), fCursorPos );
write( '<< Press ENTER to close this window >>' );
read(cKey);
end;
Cheers mate!
Erwin Haantjes
For a program foo.exe, make a batch file named foo_runner.bat. Don't document that command, since it's not intended to be used by name by anyone, but use it as the target of any shortcut icons your installer makes. Its contents will be simple:
#echo off
%~dp0\foo.exe %*
pause
That %~dp0 part gives the directory where the batch file lives, so you're ensured of running the foo.exe in the batch file's directory instead of grabbing one from some other place on the search path.