我需要从命令行运行sqlite backup命令。我不想使用“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捕获输出然后重定向它。将它重定向到什么? COPY CON?还是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;
答案 0 :(得分:5)
为重定向创建文件句柄。这就是你的cmd脚本的作用。重定向到名为'MYDB.bak'
的文件。
因此,调用CreateFile
创建具有该名称的文件,并将返回的句柄指定为StartupInfo.hStdOutput
。外部进程完成后,在文件句柄上调用CloseHandle
以关闭文件。您需要决定如何处理标准错误句柄。一种常见的选择是将其与标准输出合并。为hStdOutput
和hStdError
分配相同的句柄。
您的代码正在分配标准句柄,但不会要求外部进程使用它们。您需要在STARTF_USESTDHANDLES
中加入StartupInfo.dwFlags
。
对CreateFile
的调用如下所示:
StdOutFileHandle := CreateFile(
'MYDB.bak',
GENERIC_WRITE,
FILE_SHARE_READ,
nil,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
0
);
检查CreateFile
返回的值是否不等于INVALID_HANDLE_VALUE
。
正如我在上一个问题中提到的,您需要外部进程来继承传递它的文件句柄。如果您不允许继承句柄,则外部进程无法使用您传递它的句柄。因此,True
bInheritHandles
参数传递CreateProcess
。
CreateFile
创建的文件句柄默认不可继承。您可以传递使其可继承的安全属性。或者您可以在创建后明确设置它。后者看起来像这样:
Win32Check(SetHandleInformation(StdOutFileHandle, HANDLE_FLAG_INHERIT, 1));
前者的例子(在管道的背景下)可以在我的答案中看到:How to redirect binary gbak output to a Delphi stream?
提及StdOutPipeWrite
的所有代码都需要删除。它暂时无法工作,因为您没有初始化句柄。
您应该充分利用try/finally
,以确保即使遇到异常也不会泄漏任何句柄。
最后,您的代码包含许多错误,并且几乎没有错误检查。我建议您阅读并重新阅读CreateProcess
的文档。在MSDN上也很好地阅读了这个例子:http://msdn.microsoft.com/en-us/library/windows/desktop/ms682499.aspx。虽然它使用管道,但主体是相同的。完全相同,但不是管道使用调用CreateProcess
返回的句柄。
答案 1 :(得分:2)
对于更完整的答案,还说明了输入重定向,我发布了我的代码。感谢David Heffernan的指导,没有这个指导,这是不可能的。
该代码涉及通过使用CreateProcess调用Sqlite3.exe可执行文件来备份和恢复SQLite数据库。显然,输入和输出需要重定向到此命令,并且下面的代码说明了如何实现此目的:
function StartProcessWithRedirectedOutput(const ACommandLine: string; const AOutputFile: string;
AShowWindow: boolean = True; AWaitForFinish: boolean = False): Integer;
var
CommandLine: string;
StartupInfo: TStartupInfo;
ProcessInformation: TProcessInformation;
StdOutFileHandle: THandle;
begin
Result := 0;
StdOutFileHandle := CreateFile(PChar(AOutputFile), GENERIC_WRITE, FILE_SHARE_READ, nil, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, 0);
Win32Check(StdOutFileHandle <> INVALID_HANDLE_VALUE);
try
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 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);
Win32Check(CreateProcess(nil, PChar(CommandLine), nil, nil, True,
CREATE_NEW_PROCESS_GROUP + NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInformation));
try
Result := ProcessInformation.dwProcessId;
if AWaitForFinish then
WaitForSingleObject(ProcessInformation.hProcess, INFINITE);
finally
CloseHandle(ProcessInformation.hProcess);
CloseHandle(ProcessInformation.hThread);
end;
finally
CloseHandle(StdOutFileHandle);
end;
end;
function StartProcessWithRedirectedInput(const ACommandLine: string; const AInputFile: string;
AShowWindow: boolean = True; AWaitForFinish: boolean = False): Integer;
var
CommandLine: string;
StartupInfo: TStartupInfo;
ProcessInformation: TProcessInformation;
StdInFileHandle: THandle;
begin
Result := 0;
StdInFileHandle := CreateFile(PChar(AInputFile), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, 0);
Win32Check(StdInFileHandle <> INVALID_HANDLE_VALUE);
try
Win32Check(SetHandleInformation(StdInFileHandle, HANDLE_FLAG_INHERIT, 1));
FillChar(StartupInfo, SizeOf(TStartupInfo), 0);
FillChar(ProcessInformation, SizeOf(TProcessInformation), 0);
StartupInfo.cb := SizeOf(TStartupInfo);
StartupInfo.dwFlags := StartupInfo.dwFlags or STARTF_USESTDHANDLES;
StartupInfo.hStdInput := StdInFileHandle;
StartupInfo.hStdOutput := GetStdHandle(STD_OUTPUT_HANDLE);
StartupInfo.hStdError := GetStdHandle(STD_OUTPUT_HANDLE);
if not(AShowWindow) then
begin
StartupInfo.dwFlags := StartupInfo.dwFlags or STARTF_USESHOWWINDOW;
StartupInfo.wShowWindow := SW_HIDE;
end;
CommandLine := ACommandLine;
UniqueString(CommandLine);
Win32Check(CreateProcess(nil, PChar(CommandLine), nil, nil, True,
CREATE_NEW_PROCESS_GROUP + NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInformation));
try
Result := ProcessInformation.dwProcessId;
if AWaitForFinish then
WaitForSingleObject(ProcessInformation.hProcess, INFINITE);
finally
CloseHandle(ProcessInformation.hProcess);
CloseHandle(ProcessInformation.hThread);
end;
finally
CloseHandle(StdInFileHandle);
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;
procedure TfAdmin.DoDBRestore(ADBBackupFile: String);
var
b, p, q, q2, r: String;
begin
b := ExtractFilePath(ParamStr(0)) + 'PPDB.bak';
p := '"' + ExtractFilePath(ParamStr(0)) + 'sqlite3.exe"';
q := '"' + ExtractFilePath(ParamStr(0)) + 'PPDB.db"';
ZipMaster1.ExtrBaseDir := ExtractFilePath(ParamStr(0));
ZipMaster1.ExtrOptions := [ExtrOverWrite];
ZipMaster1.ZipFileName := ADBBackupFile;
ZipMaster1.Extract;
fMain.UniConnection1.Close;
try
q2 := StringReplace(q, '"', '', [rfReplaceAll]);
r := ChangeFileExt(q2, '.db$');
if FileExists(r) then
DeleteFile(r);
if not RenameFile(q2, r) then
RaiseLastOSError;
StartProcessWithRedirectedInput(p + ' ' + q, b, True, True);
finally
fMain.UniConnection1.Open;
end;
DeleteFile(b);
ShowMessage('Restore complete!');
end;