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 Bo 8/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 steven 8/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 Remy 8/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 Bo 8/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 Bo 8/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 Malcolm 8/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)
0 Bo 8/30/2012 7:41:02 AM
Reply:

(Thread closed)