【发布时间】:2013-10-13 06:23:14
【问题描述】:
我需要从命令行运行一个 sqlite 备份命令。我不想使用“cmd /c”。命令是:
sqlite3.exe MYDB.db .dump > MYDB.bak
我在 SO 上找不到任何示例来说明如何执行此操作。
到目前为止,我从各种 SO 帖子中收集的代码是这样的,但非常不完整:
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;
由于 dump 命令的输出非常大,我不知道如何从 stdout 捕获输出然后重定向它。重定向到什么?复制骗局?还是到 TFileStream.Write?
我见过这个post,但它在实现到输出文件的重定向方面是不完整的。我想我应该问“实现这一点的最有效方法是什么?”
如果有人以前这样做过,请发布一个代码示例来说明我是如何做到的。
TIA。
编辑:
根据 David Heffernan 的回答,这是我修改后确实可以正常工作的代码:
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;
【问题讨论】:
-
另见stackoverflow.com/q/9119999/33732 和stackoverflow.com/q/19019714/33732。和stackoverflow.com/q/19054789/33732。这三个都包含运行控制台程序并使用您的程序捕获输出的代码。
-
你抛出一个异常,然后调用exit。这不是异常的工作方式。一旦引发异常,控制就会向上移动异常链,最后是处理程序。此外,使用
RaiseLastOSError或Win32Check来引发可以通过调用GetLastError来识别的错误。 -
现在您添加了对
RaiseLastOSError的同样毫无意义的调用,因为您已经引发了异常,所以它永远无法执行。您只需致电RaiseLastOSError。这就是引发异常的调用。就我个人而言,我会将整个if语句替换为Win32Check(StdOutFileHandle <> INVALID_FILE_HANDLE); -
好的,现在对
Win32Check的调用更好,但在错误的地方。应该在try之前。想想如果Win32Check失败会发生什么。看看 finally 块。是的,你刚刚打电话给CloseHandle(INVALID_FILE_HANDLE)。 -
好的,还有一些错误处理。为什么CreateProcess的返回值叫
Handle。该函数返回一个布尔值!变量被键入为布尔值?你为什么在致电CreateProcess之前尝试一下。如果CreateProcess失败,它不会初始化两个句柄,因此它们不应该被关闭。仅当您知道它们已被初始化时才关闭句柄。在检查CreateProcess返回 true 后尝试。你可以写Win32Check(CreateProces(...))。