Running 64 bit console application from a 32 bit Delphi GUI app?
I want to improve the usability of a 64 bit command line program so it
can be handled by fairly inexperienced users.
For this I need a normal Delphi program to have data specifiers etc,
which in the end results in a command file for the console
application.
This is no big deal.
The problem is the following:
------------------------------
1) Can I start the 64 bit console app in Windows7X64 using the
CreateProcess API like I am used to for 32 bit applications?
Or are 64 bit programs different?
2) And if that is possible, can I somehow what it sends to
standard out (the console) and show this in the GUI application while
keeping the console minimized? How could that be done in such case?
What I want to do with the last part is show the output in a built-in
console but also analyze the output such that errors and such can be
grabbed and handled.
--
Bo Berglund
Developer in Sweden
0 Bo8/23/2012 9:28:30 PM
it's been awhile but i used something like this along time ago..
http://www.delphipages.com/forum/archive/index.php/t-84171.html
Can give it a try and see if close.. i don't have xe2 to try out. But did with D7 and win7 64bit cmd.exe
and that way worked.
0 steven8/24/2012 1:48:49 AM
Bo wrote:
> 1) Can I start the 64 bit console app in Windows7X64 using the
> CreateProcess API like I am used to for 32 bit applications?
Yes.
> 2) And if that is possible, can I somehow what it sends to
> standard out (the console) and show this in the GUI application while
> keeping the console minimized? How could that be done in such case?
CreateProcess() allows you to redirect stdin, stdout, and stderr of the spawned
process, via the members of the STARTUPINFO record, but I don't know if that
works between a 32-bit process and a 64-bit process due to differences in
handle sizes. You can try it and see what happens, though.
--
Remy Lebeau (TeamB)
0 Remy8/24/2012 5:21:03 PM
On Thu, 23 Aug 2012 18:48:49 -0700, steven chesser <> wrote:
>it's been awhile but i used something like this along time ago..
>
>http://www.delphipages.com/forum/archive/index.php/t-84171.html
>
>Can give it a try and see if close.. i don't have xe2 to try out. But did with D7 and win7 64bit cmd.exe
>and that way worked.
The 4th message (from midiman) on this page contained code, which I
tested with some modifications to the GUI part.
For example there are extra blank chars retrieved with the
FProcess.ReadStrFromChild() call as observed by the OP too. But a
simple Trim() removes those and then a check for blank content before
adding the read line to the memo is all that is required to block the
extra data.
Worked fine as far as I could see by a quick test.
Except of course that the example command (cmd /c dir /s) returned
strange characters instead of the decimal point in the file length
display. Not to worry, I will fix that if it returns.
My target 64 bit application was compiled using Visual Studio 2012 (it
is a C++ program) and it ran well enough to make me go forward on this
track!
Thanks for the pointer, most valuable!
--
Bo Berglund
Developer in Sweden
0 Bo8/29/2012 9:00:22 AM
On Fri, 24 Aug 2012 10:21:03 -0700, Remy Lebeau (TeamB)
wrote:
>Bo wrote:
>
>> 1) Can I start the 64 bit console app in Windows7X64 using the
>> CreateProcess API like I am used to for 32 bit applications?
>
>Yes.
>
>> 2) And if that is possible, can I somehow what it sends to
>> standard out (the console) and show this in the GUI application while
>> keeping the console minimized? How could that be done in such case?
>
>CreateProcess() allows you to redirect stdin, stdout, and stderr of the spawned
>process, via the members of the STARTUPINFO record, but I don't know if that
>works between a 32-bit process and a 64-bit process due to differences in
>handle sizes. You can try it and see what happens, though.
Remy,
yes that is used and works also to a 64 bit application.
The example in the link given by Steven works fine as in my case there
will not be any user input sent to the process, I just want to watch
the data in my GUI wrapper window.
By having a GUI front end to the command line application I can make
sure that execution needs are satisfied before launching the process.
Like changing dir to it, preparing data folders needed by the program,
making the proper command line switches and ini files etc.
All of which today has to be done by reading a paper manual and typing
away in the command window.....
(I wonder why I always wind up with these screwy old programs that
need to be maintained and/or improved?)
Thanks as always for your pointers and help, much appreciated!
--
Bo Berglund
Developer in Sweden
0 Bo8/29/2012 9:07:02 AM
Bo Berglund wrote:
> On Fri, 24 Aug 2012 10:21:03 -0700, Remy Lebeau (TeamB)
> wrote:
>
> > Bo wrote:
> >
> >> 1) Can I start the 64 bit console app in Windows7X64 using the
> >> CreateProcess API like I am used to for 32 bit applications?
> >
> > Yes.
> >
> >> 2) And if that is possible, can I somehow what it sends to
> >> standard out (the console) and show this in the GUI application
> while >> keeping the console minimized? How could that be done in
> such case?
> >
> > CreateProcess() allows you to redirect stdin, stdout, and stderr of
> > the spawned process, via the members of the STARTUPINFO record, but
> > I don't know if that works between a 32-bit process and a 64-bit
> > process due to differences in handle sizes. You can try it and see
> > what happens, though.
>
> Remy,
> yes that is used and works also to a 64 bit application.
> The example in the link given by Steven works fine as in my case there
> will not be any user input sent to the process, I just want to watch
> the data in my GUI wrapper window.
>
> (I wonder why I always wind up with these screwy old programs that
> need to be maintained and/or improved?)
>
That's called 'reputation'.
Once they find someone with the tenacity to solve these problems, they
just keep coming back
0 Malcolm8/29/2012 10:12:37 AM
On Wed, 29 Aug 2012 02:07:02 -0700, Bo Berglund
wrote:
>>CreateProcess() allows you to redirect stdin, stdout, and stderr of the spawned
>>process, via the members of the STARTUPINFO record, but I don't know if that
>>works between a 32-bit process and a 64-bit process due to differences in
>>handle sizes. You can try it and see what happens, though.
>
>Remy,
>yes that is used and works also to a 64 bit application.
>The example in the link given by Steven works fine as in my case there
>will not be any user input sent to the process, I just want to watch
>the data in my GUI wrapper window.
Turns out that on closer inspection it does not work as well as I had
hoped for...
There are extra lines in the memo and sometimes I see lines being
combined (losing the line ending) and even be in the wrong sequence...
So there is something very strange about this
I also discovered a very similar thread in the NativeAPI forum so I
give this link as a reference to that:
https://forums.embarcadero.com/thread.jspa?threadID=75839&tstart=0
Unfortunately example code there is only referencing C++ and MSDN KB
articles (also for C++)....
Is there a way to modify the code example linked to by Steven Chesser
such that it handles the reception of StdOut by itself and perhaps
fires an event (OnStdOutMsg), which can be implemented inside my main
application to retrieve the incoming data?
Like how many serial comm components work where they buffer internally
all incoming data and fire an OnRxData event when there is new data to
read for the parent application....
If so, how would one go about adding this functionality to the object?
For reference here is the unit code in the old post by midiman, but
hopefully better formatted for readability:
{code}
unit ChildProc;
interface
uses
Windows, SysUtils, StrUtils;
resourcestring sSetStdHandleMsg = 'SetStdHandle(STD_INPUT_HANDLE, Value)';
resourcestring sCreatePipeMsg = 'Pipe.'+Chr(13)+Chr(13)+'CreatePipe(FChildStdoutRd , FChildStdoutWr, saAttr, 0)';
resourcestring sDuplicateHandleMsg = 'DuplicateHandle(GetCurrentProcess(), FChildStdoutRd, GetCurrentProcess(), @FChildStdoutRdDup, 0, False, DUPLICATE_SAME_ACCESS)';
resourcestring sCreateChildProcessMsg = 'CreateChildProcess(ExeName, CommadLine, FChildStdoutWr)';
type
ESetStdHandleErr = class(Exception);
ECreatePipeErr = class(Exception);
EDuplicateHandleErr = class(Exception);
ECreateChildProcessErr = class(Exception);
{ THandledObject }
{ This is a generic class for all encapsulated WinAPI's which need to call
CloseHandle when no longer needed. This code eliminates the need for
3 identical destructors in the TEvent, TMutex, and TSharedMem classes
which are descended from this class. }
THandledObject = class(TObject)
protected
FHandle: THandle;
public
destructor Destroy; override;
property Handle: THandle read FHandle;
end;
{ TMutex }
{ This class encapsulates the concept of a Win32 mutex. See "CreateMutex"
in the Win32 reference for more information }
TMutex = class(THandledObject)
public
constructor Create(const Name: string);
function Get(TimeOut: Integer): Boolean;
function Release: Boolean;
end;
{ TChildProc }
TChildProc = class(TObject)
FStdIn: THandle;
FStdOut: THandle;
FsaAttr: PSecurityAttributes;
FChildStdoutRd: THandle;
FChildStdoutWr: THandle;
FChildStdinRd: THandle;
FChildStdinWr: THandle;
piProcInfo: TProcessInformation;
private
function CreateChildProcess(ExeName, CommadLine: String; StdIn: THandle; StdOut: THandle; ShowWindow: word): Boolean;
public
constructor Create(ExeName, CommadLine: String; ShowWindow: word);
destructor Destroy; override;
function ReadStrFromChild(Timeout: Integer=1000): String;
function WriteToChild(Data: String; Timeout: Integer=1000): Boolean;
function Running: boolean;
end;
implementation
{ THandledObject }
destructor THandledObject.Destroy;
begin
if FHandle <> 0 then
CloseHandle(FHandle);
end;
{ TMutex }
constructor TMutex.Create(const Name: string);
begin
FHandle := CreateMutex(nil, False, PChar(Name));
if FHandle = 0 then abort;
end;
function TMutex.Get(TimeOut: Integer): Boolean;
begin
Result := WaitForSingleObject(FHandle, TimeOut) = WAIT_OBJECT_0;
end;
function TMutex.Release: Boolean;
begin
Result := ReleaseMutex(FHandle);
end;
{ TChildProc }
constructor TChildProc.Create(ExeName, CommadLine: String; ShowWindow: word);
Var
Tmp1, Tmp2: THandle;
begin
New(FsaAttr);
Try
FsaAttr.nLength:=SizeOf(SECURITY_ATTRIBUTES);
FsaAttr.bInheritHandle:=True;
FsaAttr.lpSecurityDescriptor:=Nil;
If not CreatePipe(FChildStdoutRd, FChildStdoutWr, FsaAttr, 0) Then
raise ECreatePipeErr.CreateRes(@sCreatePipeMsg)
Else
If not CreatePipe(FChildStdinRd, FChildStdinWr, FsaAttr, 0) Then
raise ECreatePipeErr.CreateRes(@sCreatePipeMsg)
Else
begin
If not DuplicateHandle(GetCurrentProcess(), FChildStdoutRd, GetCurrentProcess(), @Tmp1, 0, False, DUPLICATE_SAME_ACCESS) Then
raise EDuplicateHandleErr.CreateRes(@sDuplicateHandleMsg )
Else
If not DuplicateHandle(GetCurrentProcess(), FChildStdinWr, GetCurrentProcess(), @Tmp2, 0, False, DUPLICATE_SAME_ACCESS) Then
raise EDuplicateHandleErr.CreateRes(@sDuplicateHandleMsg )
Else
begin
CloseHandle(FChildStdoutRd);
CloseHandle(FChildStdinWr);
FChildStdoutRd:=Tmp1;
FChildStdinWr:=Tmp2;
If not CreateChildProcess(ExeName, CommadLine, FChildStdinRd, FChildStdoutWr, ShowWindow ) Then
raise ECreateChildProcessErr.CreateRes(@sCreateChildProcessMsg)
end;
end;
Finally
Dispose(FsaAttr);
End;
end;
function TChildProc.CreateChildProcess(ExeName, CommadLine: String; StdIn, StdOut: THandle; ShowWindow: word): Boolean;
Var
siStartInfo: TStartupInfo;
begin
// Set up members of STARTUPINFO structure.
ZeroMemory(@siStartInfo, SizeOf(TStartupInfo));
siStartInfo.cb := SizeOf(TStartupInfo);
siStartInfo.hStdInput := StdIn;
siStartInfo.hStdOutput := StdOut;
siStartInfo.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
siStartInfo.wShowWindow := ShowWindow;
// Create the child process.
Result := CreateProcess(Nil,
PChar(ExeName + ' ' + CommadLine), // command line
Nil, // process security attributes
Nil, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
Nil, // use parent's environment
Nil, // use parent's current directory
siStartInfo, // STARTUPINFO pointer
piProcInfo); // receives PROCESS_INFORMATION
end;
destructor TChildProc.Destroy;
begin
CloseHandle(FChildStdoutRd);
CloseHandle(FChildStdoutWr);
CloseHandle(FChildStdinRd);
CloseHandle(FChildStdinWr);
end;
function TChildProc.ReadStrFromChild(Timeout: Integer): String;
Var
i: Integer;
dwRead, BufSize, DesBufSize: DWORD;
chBuf: PChar;
Res: Boolean;
begin
Try
BufSize:=0;
New(chBuf);
Repeat
For i:=0 to 9 do
begin
Res:=PeekNamedPipe(FChildStdoutRd, nil, 0, nil, @DesBufSize, nil);
Res:=Res and (DesBufSize > 0);
If Res Then
Break;
Sleep(Round(Timeout/10));
end;
If Res Then
begin
If DesBufSize > BufSize Then
begin
FreeMem(chBuf);
GetMem(chBuf, DesBufSize);
BufSize:=DesBufSize;
end;
Res:=ReadFile(FChildStdoutRd, chBuf^, BufSize, dwRead, Nil);
Result:=Result+LeftStr(chBuf, dwRead);
end;
Until not Res;
Except
Result:='Read Err';
End;
end;
function TChildProc.Running: boolean;
var
tmpHandle: THandle;
begin
tmpHandle := OpenProcess( PROCESS_QUERY_INFORMATION, false, piProcInfo.dwProcessId );
if tmpHandle > 0 then
begin
Result := true;
CloseHandle( tmpHandle );
end
else
Result := false;
end;
function TChildProc.WriteToChild(Data: String; Timeout: Integer=1000): Boolean;
Var
dwWritten, BufSize: DWORD;
chBuf: PChar;
begin
chBuf:=PChar(Data+Chr($0D)+Chr($0A));
BufSize:=Length(chBuf);
Result:=WriteFile(FChildStdinWr, chBuf^, BufSize, dwWritten, Nil);
Result:=Result and (BufSize = dwWritten);
end;
end.
{code}
--
Bo Berglund
Developer in Sweden
Edited by: Bo Berglund on Aug 30, 2012 9:38 AM
(Had to change the code formatting tag to make it readable)