Question

I want to compile a Delphi program from another delphi program, to do this I use ShellExecuteEx so that the program waits until the compiling is done. This works fine but I want the .cmd output in a .txt file, this isnt working properly.

    StartAndWait('cmd', '/c  c:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild xyz.dproj /t:Build /p:Config=release >> log.txt');

StartAndWait starts the function with ShellExecuteEx and waits until the program is finished,

IF ShellExecuteEx(@SEInfo) THEN
BEGIN
  REPEAT
    Application.ProcessMessages;
    // Damit die Prozessorauslastung sinkt :-)
    Sleep(100);
    GetExitCodeProcess(SEInfo.hProcess, ExitCode);
  UNTIL (ExitCode <> STILL_ACTIVE) OR Application.Terminated;
  Result := True;
END;

Thank you!

Was it helpful?

Solution

I personally think it is easier to do this with a call to CreateProcess. You need to create a file handle, and pass that to CreateProcess to use as stdout. There are a few details that you need to take care over, mostly concerning handle inheritance. You can get it all done with this function:

procedure ExecuteCommandAndAppendToFile(
  const Executable: string;
  const Arguments: string;
  const WorkingDir: string;
  const OutputFile: string
);
const
  sa: TSecurityAttributes = (nLength: SizeOf(sa); bInheritHandle: True);
var
  hstdout: THandle;
  StartupInfo: TStartupInfo;
  ProcessInfo: TProcessInformation;
  cmd: string;
begin
  hstdout := CreateFile(PChar(OutputFile), GENERIC_WRITE, 0, @sa, OPEN_ALWAYS, 
    FILE_ATTRIBUTE_NORMAL, 0);
  Win32Check(hstdout<>INVALID_HANDLE_VALUE);
  try
    //move to end of file since we wish to append
    SetFilePointer(hstdout, 0, nil, FILE_END);

    ZeroMemory(@StartupInfo, SizeOf(StartupInfo));
    StartupInfo.cb := SizeOf(StartupInfo);
    StartupInfo.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
    StartupInfo.wShowWindow := SW_HIDE;
    StartupInfo.hStdOutput := hstdout;
    StartupInfo.hStdError := hstdout;

    cmd := Executable + ' ' + Arguments;
    UniqueString(cmd);//not needed but let's be explicit
    if not CreateProcess(nil, PChar(cmd), nil, nil, True,
      CREATE_NO_WINDOW or NORMAL_PRIORITY_CLASS, nil, PChar(WorkingDir),
      StartupInfo, ProcessInfo) then
    begin
      RaiseLastOSError;
    end;
    try
      WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
    finally
      CloseHandle(ProcessInfo.hProcess);
      CloseHandle(ProcessInfo.hThread);
    end;
  finally
    CloseHandle(hstdout);
  end;
end;

This uses WaitForSingleObject to wait for completion. That will block the thread that runs this code. You could move this code into a different thread if you have GUI that you wish not to block. Or you could use MsgWaitForMultipleObjects. Or you could use your Sleep based loop if you must (I don't much care for Sleep but it's your choice).

So, you may wish to tweak bits of this, but the basics are all present here.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top