本文介绍了如何从CreateProcess执行的命令中重定向大量输出?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要从命令行运行sqlite backup命令。我不想使用 cmd / c。命令为:

我在SO上找不到任何显示如何执行此操作的示例。



我到目前为止从各种SO帖子中收集的代码是这样,但是非常不完整:

 函数StartProcess(const ACommandLine:string; AShowWindow:boolean = True; 
AWaitForFinish:boolean = False):整数;
var
命令行:字符串;
StartupInfo:TStartupInfo;
ProcessInformation:TProcessInformation;
StdOutPipeRead,StdOutPipeWrite:THandle;
句柄:布尔值;
开始
结果:= 0;
FillChar(StartupInfo,SizeOf(TStartupInfo),0);
FillChar(ProcessInformation,SizeOf(TProcessInformation),0);
StartupInfo.cb:= SizeOf(TStartupInfo);

StartupInfo.hStdInput:= GetStdHandle(STD_INPUT_HANDLE);
StartupInfo.hStdOutput:= StdOutPipeWrite;
StartupInfo.hStdError:= StdOutPipeWrite;

如果不是(AShowWindow),则
开始
StartupInfo.dwFlags:= STARTF_USESHOWWINDOW;
StartupInfo.wShowWindow:= SW_SHOWNORMAL;
结尾;

CommandLine:= ACommandLine;
UniqueString(CommandLine);
句柄:= CreateProcess(nil,PChar(CommandLine),nil,nil,False,
CREATE_NEW_PROCESS_GROUP + NORMAL_PRIORITY_CLASS,nil,nil,StartupInfo,ProcessInformation);

CloseHandle(StdOutPipeWrite);

如果Handle则


结果:= ProcessInformation.dwProcessId;

如果AWaitForFinish则
WaitForSingleObject(ProcessInformation.hProcess,INFINITE);

CloseHandle(ProcessInformation.hProcess);
CloseHandle(ProcessInformation.hThread);
结尾;

由于dump命令的输出很大,所以我不确定如何捕获输出从标准输出,然后将其重定向。重定向到什么?复制骗局?还是到TFileStream.Write?



我见过,但是在实现重定向到输出文件方面还不完善。我想我应该问最有效的方法是什么?



如果以前有人这样做过,请发布代码示例以说明我该如何做。



TIA。



编辑:



基于关于David Heffernan的答案,这是我修改后的代码,确实可以正常工作:

  function StartProcessWithRedirectedOutput(const ACommandLine:string; const AOutputFile:字符串; 
AShowWindow:boolean = True; AWaitForFinish:boolean = False):整数;
var
命令行:字符串;
StartupInfo:TStartupInfo;
ProcessInformation:TProcessInformation;
StdOutFileHandle:THandle;
ProcessResult:布尔值;
开始
结果:= 0;

StdOutFileHandle:= CreateFile(PChar(AOutputFile),GENERIC_WRITE,FILE_SHARE_READ,nil,CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,0);
Win32Check(StdOutFileHandle<> INVALID_HANDLE_VALUE);

Win32Check(SetHandleInformation(StdOutFileHandle,HANDLE_FLAG_INHERIT,1));

试试
FillChar(StartupInfo,SizeOf(TStartupInfo),0);
FillChar(ProcessInformation,SizeOf(TProcessInformation),0);

StartupInfo.cb:= SizeOf(TStartupInfo);
StartupInfo.dwFlags:= StartupInfo.dwFlags或STARTF_USESTDHANDLES;
StartupInfo.hStdInput:= GetStdHandle(STD_INPUT_HANDLE);
StartupInfo.hStdOutput:= StdOutFileHandle;
StartupInfo.hStdError:= StdOutFileHandle;

如果不是(AShowWindow),则
开始
StartupInfo.dwFlags:= StartupInfo.dwFlags或STARTF_USESHOWWINDOW;
StartupInfo.wShowWindow:= SW_HIDE;
结尾;

CommandLine:= ACommandLine;
UniqueString(CommandLine);

ProcessResult:= Win32Check(CreateProcess(nil,PChar(CommandLine),nil,nil,True,
CREATE_NEW_PROCESS_GROUP + NORMAL_PRIORITY_CLASS,nil,nil,StartupInfo,ProcessInformation));

如果ProcessResult然后
开始
尝试
结果:= ProcessInformation.dwProcessId;

如果AWaitForFinish则
WaitForSingleObject(ProcessInformation.hProcess,INFINITE);如果ProcessInformation.hProcess<>

最终
INVALID_HANDLE_VALUE然后
CloseHandle(ProcessInformation.hProcess);

如果ProcessInformation.hThread<> INVALID_HANDLE_VALUE然后
CloseHandle(ProcessInformation.hThread);
结尾;
结尾;

最后
CloseHandle(StdOutFileHandle);
结尾;
结尾;

过程TfAdmin.DoDBBackup(ADBBackupFile:String);
var
b,p,q:字符串;
开始

b == ExtractFilePath(ParamStr(0))+‘PPDB.bak’;
p:= ExtractFilePath(ParamStr(0))+'sqlite3.exe';
q:= ExtractFilePath(ParamStr(0))+'PPDB.db .dump';

fMain.UniConnection1.Close;
试试
StartProcessWithRedirectedOutput(p +’+ q,b,True,True);
最终
fMain.UniConnection1.Open;
结尾;

ZipMaster1.FSpecArgs.Add(b);
ZipMaster1.ZipFileName:= ADBBackupFile;
ZipMaster1.Add;

DeleteFile(b);

ShowMessage(备份完成!);

结尾;


解决方案

为重定向创建文件句柄。这就是您的cmd脚本所做的。它将重定向到名为'MYDB.bak'的文件。



因此,调用 CreateFile 创建一个具有该名称的文件,并将返回的句柄分配为 StartupInfo.hStdOutput 。外部过程完成后,在文件句柄上调用 CloseHandle 以关闭文件。您需要决定如何处理标准错误处理。一种常见的选择是将其与标准输出合并。为 hStdOutput hStdError 分配相同的句柄。



您的代码正在分配标准句柄,但不要求外部进程使用它们。您需要在 StartupInfo.dwFlags 中包含 STARTF_USESTDHANDLES



CreateFile 的调用将如下所示:

  StdOutFileHandle:= CreateFile(
'MYDB.bak',
GENERIC_WRITE,
FILE_SHARE_READ,
nil,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
0
);

检查 CreateFile 返回的值是否为不等于 INVALID_HANDLE_VALUE



正如我在上一个问题中提到的那样,您需要外部过程来继承文件处理您通过它。如果您不允许继承句柄,则外部进程将无法使用您传递的句柄。因此,将 True 传递给 CreateProcess bInheritHandles 参数。 / p>

CreateFile 创建的文件句柄默认情况下不可继承。您可以传递使其可继承的安全属性。或者,您可以在创建后对其进行显式设置。后者看起来像这样:

  Win32Check(SetHandleInformation(StdOutFileHandle,HANDLE_FLAG_INHERIT,1)); 

前者的示例(在管道中)可以在我的回答中看到:。尽管它使用管道,但其原理是相同的。进行完全相同的操作,但不是使用管道,而是使用调用 CreateProcess 返回的句柄。


I need to run a sqlite backup command from the command line. I don't want to use "cmd /c". The command is:

I could not find any example on SO that shows how to do this.

Code that I have so far, collected from various SO posts is this, but is very much incomplete:

function StartProcess(const ACommandLine: string; AShowWindow: boolean = True;
  AWaitForFinish: boolean = False): Integer;
var
  CommandLine: string;
  StartupInfo: TStartupInfo;
  ProcessInformation: TProcessInformation;
  StdOutPipeRead, StdOutPipeWrite: THandle;
  Handle: boolean;
begin
   Result := 0;
   FillChar(StartupInfo, SizeOf(TStartupInfo), 0);
   FillChar(ProcessInformation, SizeOf(TProcessInformation), 0);
   StartupInfo.cb := SizeOf(TStartupInfo);

   StartupInfo.hStdInput := GetStdHandle(STD_INPUT_HANDLE);
   StartupInfo.hStdOutput := StdOutPipeWrite;
   StartupInfo.hStdError := StdOutPipeWrite;

   if not(AShowWindow) then
   begin
   StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
   StartupInfo.wShowWindow := SW_SHOWNORMAL;
   end;

   CommandLine := ACommandLine;
   UniqueString(CommandLine);
   Handle := CreateProcess(nil, PChar(CommandLine), nil, nil, False,
   CREATE_NEW_PROCESS_GROUP + NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInformation);

   CloseHandle(StdOutPipeWrite);

   if Handle then


   Result := ProcessInformation.dwProcessId;

   if AWaitForFinish then
   WaitForSingleObject(ProcessInformation.hProcess, INFINITE);

   CloseHandle(ProcessInformation.hProcess);
   CloseHandle(ProcessInformation.hThread);
end;

Since the output from the dump command is very large, I'm not sure how to capture the output from stdout and then redirect it. Redirect it to what? COPY CON? or to a TFileStream.Write?

I've seen this post, but its incomplete with regard to implementing the redirection to the output file. I guess I should ask "What is the most efficient way to implement this?"

If anyone has done this before, please post a code sample illustrating how I can do it.

TIA.

EDIT:

Based on David Heffernan's answer, here is my revised code that indeed works properly:

function StartProcessWithRedirectedOutput(const ACommandLine: string; const AOutputFile: string;
  AShowWindow: boolean = True; AWaitForFinish: boolean = False): Integer;
var
  CommandLine: string;
  StartupInfo: TStartupInfo;
  ProcessInformation: TProcessInformation;
  StdOutFileHandle: THandle;
  ProcessResult: boolean;
begin
  Result := 0;

  StdOutFileHandle := CreateFile(PChar(AOutputFile), GENERIC_WRITE, FILE_SHARE_READ, nil, CREATE_ALWAYS,
    FILE_ATTRIBUTE_NORMAL, 0);
  Win32Check(StdOutFileHandle <> INVALID_HANDLE_VALUE);

  Win32Check(SetHandleInformation(StdOutFileHandle, HANDLE_FLAG_INHERIT, 1));

  try
    FillChar(StartupInfo, SizeOf(TStartupInfo), 0);
    FillChar(ProcessInformation, SizeOf(TProcessInformation), 0);

    StartupInfo.cb := SizeOf(TStartupInfo);
    StartupInfo.dwFlags := StartupInfo.dwFlags or STARTF_USESTDHANDLES;
    StartupInfo.hStdInput := GetStdHandle(STD_INPUT_HANDLE);
    StartupInfo.hStdOutput := StdOutFileHandle;
    StartupInfo.hStdError := StdOutFileHandle;

    if not(AShowWindow) then
    begin
      StartupInfo.dwFlags := StartupInfo.dwFlags or STARTF_USESHOWWINDOW;
      StartupInfo.wShowWindow := SW_HIDE;
    end;

    CommandLine := ACommandLine;
    UniqueString(CommandLine);

    ProcessResult := Win32Check(CreateProcess(nil, PChar(CommandLine), nil, nil, True,
      CREATE_NEW_PROCESS_GROUP + NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInformation));

    if ProcessResult then
    begin
      try
        Result := ProcessInformation.dwProcessId;

        if AWaitForFinish then
          WaitForSingleObject(ProcessInformation.hProcess, INFINITE);

      finally
        if ProcessInformation.hProcess <> INVALID_HANDLE_VALUE then
          CloseHandle(ProcessInformation.hProcess);

        if ProcessInformation.hThread <> INVALID_HANDLE_VALUE then
          CloseHandle(ProcessInformation.hThread);
      end;
    end;

  finally
    CloseHandle(StdOutFileHandle);
  end;
end;

procedure TfAdmin.DoDBBackup(ADBBackupFile: String);
var
  b, p, q: String;
begin

  b := ExtractFilePath(ParamStr(0)) + 'PPDB.bak';
  p := ExtractFilePath(ParamStr(0)) + 'sqlite3.exe';
  q := ExtractFilePath(ParamStr(0)) + 'PPDB.db .dump';

  fMain.UniConnection1.Close;
  try
    StartProcessWithRedirectedOutput(p + ' ' + q, b, True, True);
  finally
    fMain.UniConnection1.Open;
  end;

  ZipMaster1.FSpecArgs.Add(b);
  ZipMaster1.ZipFileName := ADBBackupFile;
  ZipMaster1.Add;

  DeleteFile(b);

  ShowMessage('Backup complete!');

end;
解决方案

Create a file handle for the redirection. That's what your cmd script does. That redirects to a file named 'MYDB.bak'.

So, call CreateFile to create a file with that name, and assign the handle returned as StartupInfo.hStdOutput. When the external process has finished, call CloseHandle on the file handle to close the file. You'll need to decide what to do about the standard error handle. One common choice is to merge it with standard output. Assign the same handle to both hStdOutput and hStdError.

Your code is assigning standard handles, but not asking that the external process uses them. You need to include STARTF_USESTDHANDLES in StartupInfo.dwFlags.

The call to CreateFile will look like this:

StdOutFileHandle := CreateFile(
  'MYDB.bak',
  GENERIC_WRITE,
  FILE_SHARE_READ,
  nil,
  CREATE_ALWAYS,
  FILE_ATTRIBUTE_NORMAL,
  0
);

Check that the value returned by CreateFile is not equal to INVALID_HANDLE_VALUE.

As I mentioned in your previous question, you need the external process to inherit the file handle that you pass it. If you don't allow inheritance of handles then the external process cannot use the handle that you pass it. So pass True for the bInheritHandles parameter of CreateProcess.

The file handle created by CreateFile is not, by default inheritable. You can either pass security attributes that make it inheritable. Or you can set it explicitly after you have created. The latter looks like this:

Win32Check(SetHandleInformation(StdOutFileHandle, HANDLE_FLAG_INHERIT, 1));

Examples of the former (in the context of pipes) can be seen in my answer here: How to redirect binary gbak output to a Delphi stream?

The code that mentions StdOutPipeWrite all needs to be deleted. It cannot work at the moment because you are not initializing the handle.

You should make good use of try/finally to ensure that you don't leak any handles even in the face of exceptions.

Finally, your code contains a lot of errors, and little error checking. I suggest that you read and re-read the documentation for CreateProcess. Also have a good read of this example on MSDN: http://msdn.microsoft.com/en-us/library/windows/desktop/ms682499.aspx. Although it uses pipes, the principals are the same. Do exactly the same, but instead of pipes use the handle returned by the call to CreateProcess.

这篇关于如何从CreateProcess执行的命令中重定向大量输出?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-05 18:21