[Okta Webinar] Learn how to a build a cloud-first strategyRegister Now

x
?
Solved

problems calling a DOS app from Delphi and capturing output

Posted on 2004-11-28
6
Medium Priority
?
1,944 Views
Last Modified: 2008-01-09
This really is a dos app .. not a console app. It is Wordnet and Quickview says that it is a dos executable.

I am using the code below, which works just fine for console apps.

It works OK for a simple dos test app and some of the time it works OK for Wordnet .. I am passing through stuff on the commandline.

Under W98 it attempts to access the floppy when I call a DOS app, and I can see no explanation for this. I AM using short pathnames. A: is not on the path. This happens irrespective of which dos app I am calling (even command/c dir *.* on W98).

So, maybe the attempted access to floppy on W98 is a clue (by tracking through the code it happens at the CreateProcess call). It does not do this on XP. Rebooting on W98 does not help.

The problem with calling Wordnet from Delphi is that it sometimes returns empty stuff. I can send it valid command line stuff, stuff that works when run from DOS and sometimes is OK when called in the Delphi call, and I get this non output.

I figured it might be something to do with handles or timing, but I can construct up a very heavy stress test and run it under DOS (ie a bunch of direct calls to Wordnet in a DOS batch file )and it works OK. So I don't think Wordnet itself is the culprit.

btw, I tried calling a batch file (from my Delphi app) that then invoked Wordnet . Same result as calling the app direct.

So, is there something wrong with my code ?

----------------------------------------------------------------------
procedure RunConsoleApp2Strings(const DosOrConsoleApp:String;
                                 const Args:String;
                                 const outStrings:Tstrings);

 const
    ReadBuffer = 2400;
 var
  Security            : TSecurityAttributes;
  ReadPipe,WritePipe  : THandle;
  start               : TStartUpInfo;
  ProcessInfo         : TProcessInformation;
  Buffer              : Pchar;
  BytesRead           : DWord;
  Apprunning          : DWord;
var
  app : string;
 begin


  app := sysutils.trim(DosOrConsoleApp);

   if args <> '' then
   app := app+' ' + Args;

  With Security do begin
   nlength              := SizeOf(TSecurityAttributes);
   binherithandle       := true;
   lpsecuritydescriptor := nil;
  end;
   // create pipes for stdin and stdout
  if Createpipe (ReadPipe, WritePipe,     //  1 pipe, 2 handles
                 @Security, 0) then begin
   Buffer  := AllocMem(ReadBuffer + 1);
   FillChar(Start,Sizeof(Start),#0);
   start.cb          := SizeOf(start);
   // setup pipes for stdin and stdout
   start.hStdOutput  := WritePipe;
   start.hStdInput   := ReadPipe;

   start.dwFlags     := STARTF_USESTDHANDLES
                          +
                        STARTF_USESHOWWINDOW

                        ;
   start.wShowWindow := SW_HIDE;

   if CreateProcess     (nil,
          PChar(app),  // should normally be a FULL command line
          @Security,
          @Security,
          true,           // new process inherits handles from the calling process
          CREATE_NEW_CONSOLE+       // JA based on examination of CodeCentral code
          NORMAL_PRIORITY_CLASS,   // creation flags and process priority
          nil,   // environment (if different from calling process)
          nil,   // current drive and directory for the new process
          start,       // Startup info
          ProcessInfo  // ProcessInfor
          )
   then
   begin

    repeat
     Apprunning := WaitForSingleObject(ProcessInfo.hProcess,INFINITE);
     application.processmessages;
     until (Apprunning <> WAIT_TIMEOUT);


     Repeat
       BytesRead := 0;
       ReadFile(ReadPipe,Buffer[0],
                  ReadBuffer,BytesRead,nil);
       Buffer[BytesRead]:= #0;
       OemToAnsi(Buffer,Buffer);
       outStrings.Text := outStrings.text + String(Buffer);
     until (BytesRead < ReadBuffer);
  end;
  FreeMem(Buffer);

  // free handles for the spawned process and the thread
  CloseHandle(ProcessInfo.hProcess);
  CloseHandle(ProcessInfo.hThread);

  // the pipe handles need to be freed

  CloseHandle(ReadPipe);
  CloseHandle(WritePipe);
  end;
 end;

0
Comment
Question by:Mutley2003
6 Comments
 
LVL 17

Expert Comment

by:Wim ten Brink
ID: 12692210
What if you just call your DOS application from outside Delphi? Just from the command prompt. It could be that WordNet is just always trying to access the A: drive.

http://www.arts.cuhk.edu.hk/humftp/Linguistics/wordnet/readme.pc Is this the WordNet that you are talking about? Can't you use a newer version instead?
0
 

Author Comment

by:Mutley2003
ID: 12693652
hi Alex

wordnet 1.6 is the latest one there is.

the problems with hunting the A drive occur with other dos apps if I call them from my code

I can call Wordnet from the command line and redirect output to a file, but that is a lot of work since I want to "follow up" .. one word suggests another etc.

can you see anyhting wrong with my code?

regards

0
 
LVL 2

Accepted Solution

by:
Bobcsi earned 1000 total points
ID: 12723706
Try this:


procedure GetDosOutput(const Exe,Param:string;Amemo:TMemo);
var
  SA: TSecurityAttributes;
  SI: TStartupInfo;
  PI: TProcessInformation;
  StdOutPipeRead, StdOutPipeWrite: THandle;
  WasOK: Boolean;
  Buffer: array[0..255] of Char;
  BytesRead: Cardinal;
  WorkDir, Line: String;
begin
  Amemo.Lines.Clear;
  Application.ProcessMessages;
  with SA do
  begin
    nLength := SizeOf(SA);
    bInheritHandle := True;
    lpSecurityDescriptor := nil;
  end;
  // create pipe for standard output redirection
  CreatePipe(StdOutPipeRead,  // read handle
             StdOutPipeWrite, // write handle
             @SA,             // security attributes
             0                // number of bytes reserved for pipe - 0 default
             );
  try
    // Make child process use StdOutPipeWrite as standard out,
    // and make sure it does not show on screen.
    with SI do
    begin
      FillChar(SI, SizeOf(SI), 0);
      cb := SizeOf(SI);
      dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
      wShowWindow := SW_HIDE;
      hStdInput := GetStdHandle(STD_INPUT_HANDLE); // don't redirect stdinput
      hStdOutput := StdOutPipeWrite;
      hStdError := StdOutPipeWrite;
    end;

    // launch the command line compiler
    WorkDir := ExtractFilePath(application.exename);
    WasOK := CreateProcess(pchar(Exe), PChar(Param), nil, nil, True, 0, nil,
PChar(WorkDir), SI, PI);

    // Now that the handle has been inherited, close write to be safe.
    // We don't want to read or write to it accidentally.
    CloseHandle(StdOutPipeWrite);
    // if process could be created then handle its output
    if not WasOK then
      raise Exception.Create('Could not execute command line!')
    else
      try
        // get all output until dos app finishes
        Line := '';
        repeat
          // read block of characters (might contain carriage returns and line feeds)
          WasOK := ReadFile(StdOutPipeRead, Buffer, 255, BytesRead, nil);

          // has anything been read?
          if BytesRead > 0 then
          begin
            // finish buffer to PChar
            Buffer[BytesRead] := #0;
            // combine the buffer with the rest of the last run
            Line := Line + Buffer;
            Amemo.Lines.Text := Amemo.Lines.Text+Buffer;
//            amemo.SelStart:=length(amemo.lines.text);
//            Sendmessage(amemo.handle, WM_KEYDOWN, VK_NEXT, 0);
//            Sendmessage(amemo.handle, WM_KEYUP, VK_NEXT, 0);
//            Application.ProcessMessages;

          end;
        until not WasOK or (BytesRead = 0);
        // wait for console app to finish (should be already at this point)
        WaitForSingleObject(PI.hProcess, INFINITE);
      finally
        // Close all remaining handles
        CloseHandle(PI.hThread);
        CloseHandle(PI.hProcess);
      end;
  finally
      CloseHandle(StdOutPipeRead);
  end;
end;

Bobcsi
0
VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

 
LVL 2

Assisted Solution

by:SaLz
SaLz earned 1000 total points
ID: 12728064
ok, if you want to run a dos app and show the results in your app and put them into a memo1.text then the below code should help, I use this code to convert .rc to .res files using brcc32.exe file and display the dos results.

uses shellapi;

function GetShortFilename(const FileName: TFileName): TFileName;
var
  buffer: array[0..MAX_PATH-1] of char;
begin
  SetString(Result, buffer, GetShortPathName(
    pchar(FileName), buffer, MAX_PATH-1));
end;

procedure TMain1.Button1Click(Sender: TObject);
procedure RunDosInMemo(DosApp:String;AMemo:TMemo);
 const
    ReadBuffer = 2400;
 var
  Security            : TSecurityAttributes;
  ReadPipe,WritePipe  : THandle;
  start               : TStartUpInfo;
  ProcessInfo         : TProcessInformation;
  Buffer              : Pchar;
  BytesRead           : DWord;
  Apprunning          : DWord;
 begin
  With Security do begin
   nlength              := SizeOf(TSecurityAttributes);
   binherithandle       := true;
   lpsecuritydescriptor := nil;
  end;
  if Createpipe (ReadPipe, WritePipe,
                 @Security, 0) then begin
   Buffer  := AllocMem(ReadBuffer + 1);
   FillChar(Start,Sizeof(Start),#0);
   start.cb          := SizeOf(start);
   start.hStdOutput  := WritePipe;
   start.hStdInput   := ReadPipe;
   start.dwFlags     := STARTF_USESTDHANDLES +
                        STARTF_USESHOWWINDOW;
   start.wShowWindow := SW_HIDE;

   if CreateProcess(nil,
          PChar(DosApp),
          @Security,
          @Security,
          true,
          NORMAL_PRIORITY_CLASS,
          nil,
          nil,
          start,
          ProcessInfo)
   then
   begin
    repeat
     Apprunning := WaitForSingleObject
                  (ProcessInfo.hProcess,100);
     Application.ProcessMessages;
    until (Apprunning <> WAIT_TIMEOUT);
     Repeat
       BytesRead := 0;
       ReadFile(ReadPipe,Buffer[0],
                  ReadBuffer,BytesRead,nil);
       Buffer[BytesRead]:= #0;
       OemToAnsi(Buffer,Buffer);
       AMemo.Text := AMemo.text + String(Buffer);
     until (BytesRead < ReadBuffer);
  end;
  FreeMem(Buffer);
  CloseHandle(ProcessInfo.hProcess);
  CloseHandle(ProcessInfo.hThread);
  CloseHandle(ReadPipe);
  CloseHandle(WritePipe);
  end;
 end;

begin {button 1 code}
//u can take off the +GetShortFilename(copen), its the last bits of the command from my app.
   RunDosInMemo('C:\Program Files\Borland\Delphi7\Bin\brcc32.exe '+GetShortFilename(copen),Memo1);

   ShowMessage(ExtractFileName(GetShortFilename(copen))+' Created');
end;

Sal.
0
 
LVL 2

Expert Comment

by:SaLz
ID: 12728071
change procedure TMain1.Button1Click(Sender: TObject); to procedure TForm1.Button1Click(Sender: TObject);
0
 

Author Comment

by:Mutley2003
ID: 12882207
hello people

the answers I have been given here are more or less refactorings of the code I started with, and I appreciate the help but it does not fix my problem.

I think the issue comes down to flushing buffers. But anyway I have had success with porting
http://www.codeproject.com/threads/redir.asp
- note the use of threads
0

Featured Post

What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Creating an auto free TStringList The TStringList is a basic and frequently used object in Delphi. On many occasions, you may want to create a temporary list, process some items in the list and be done with the list. In such cases, you have to…
In my programming career I have only very rarely run into situations where operator overloading would be of any use in my work.  Normally those situations involved math with either overly large numbers (hundreds of thousands of digits or accuracy re…
This video shows how to quickly and easily deploy an email signature for all users in Office 365 and prevent it from being added to replies and forwards. (the resulting signature is applied on the server level in Exchange Online) The email signat…
As many of you are aware about Scanpst.exe utility which is owned by Microsoft itself to repair inaccessible or damaged PST files, but the question is do you really think Scanpst.exe is capable to repair all sorts of PST related corruption issues?

834 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question