我想运行用户在INI文件中定义的命令。
命令可以是EXE文件,也可以是其他文件(例如DOC文件)和参数。
由于WinExec()可以处理参数(例如" cmd /?"),但ShellExec()可以处理非EXE文件(例如" Letter.doc"),I我正在使用这两者的组合。
我担心未来的Windows版本,因为WinExec()已被弃用,甚至是16位时代。
这是我目前的职能:
procedure RunCMD(cmdLine: string; WindowMode: integer);
procedure ShowWindowsErrorMessage(r: integer);
begin
MessageDlg(SysErrorMessage(r), mtError, [mbOK], 0);
end;
var
r, g: Cardinal;
begin
// We need a function which does following:
// 1. Replace the Environment strings, e.g. %SystemRoot% --> ExpandEnvStr
// 2. Runs EXE files with parameters (e.g. "cmd.exe /?") --> WinExec
// 3. Runs EXE files without path (e.g. "calc.exe") --> WinExec
// 4. Runs EXE files without extension (e.g. "calc") --> WinExec
// 5. Runs non-EXE files (e.g. "Letter.doc") --> ShellExecute
// 6. Commands with white spaces (e.g. "C:\Program Files\xyz.exe") must be enclosed in quotes.
cmdLine := ExpandEnvStr(cmdLine);
// TODO: Attention: WinExec() is deprecated, but there is no acceptable replacement
g := WinExec(PChar(cmdLine), WindowMode);
r := GetLastError;
if g = ERROR_BAD_FORMAT then
begin
// e.g. if the user tries to open a non-EXE file
ShellExecute(0, nil, PChar(cmdLine), '', '', WindowMode);
r := GetLastError;
end;
if r <> 0 then ShowWindowsErrorMessage(r);
end;
function ExpandEnvStr(const szInput: string): string;
// http://stackoverflow.com/a/2833147/3544341
const
MAXSIZE = 32768;
begin
SetLength(Result, MAXSIZE);
SetLength(Result, ExpandEnvironmentStrings(pchar(szInput),
@Result[1],length(Result)));
end;
Microsoft建议使用CreateProcess(),但我不接受它作为WinExec()的真正替代品。
例如,给出以下命令行:
"C:\Program Files\xyz.exe" /a /b /c
由于ShellExecute()和CreateProcess()需要严格分离命令和参数,我必须自己解析这个字符串。这真的是我唯一能走的路吗?是否有人编写了具有此功能的公共可用代码?
附加说明:该过程不应附加到呼叫者。命令启动后,我的程序将关闭。
答案 0 :(得分:4)
CreateProcess()
是WinExec()
的替代品。文档明确说明了这一点。
而BTW,原始代码中的错误处理是完全错误的。你在滥用GetLastError()
。实际上,WinExec()
和ShellExecute()
都不会报告GetLastError()
开头的错误。因此,即使WinExec()
或ShellExecute()
成功(并且您甚至不检查ShellExecute()
是成功还是失败),您也有可能报告早期API调用中的随机错误。
尝试更像这样的事情:
procedure RunCMD(cmdLine: string; WindowMode: integer);
procedure ShowWindowsErrorMessage(r: integer);
var
sMsg: string;
begin
sMsg := SysErrorMessage(r);
if (sMsg = '') and (r = ERROR_BAD_EXE_FORMAT) then
sMsg := SysErrorMessage(ERROR_BAD_FORMAT);
MessageDlg(sMsg, mtError, [mbOK], 0);
end;
var
si: TStartupInfo;
pi: TProcessInformation;
sei: TShellExecuteInfo;
err: Integer;
begin
// We need a function which does following:
// 1. Replace the Environment strings, e.g. %SystemRoot% --> ExpandEnvStr
// 2. Runs EXE files with parameters (e.g. "cmd.exe /?") --> WinExec
// 3. Runs EXE files without path (e.g. "calc.exe") --> WinExec
// 4. Runs EXE files without extension (e.g. "calc") --> WinExec
// 5. Runs non-EXE files (e.g. "Letter.doc") --> ShellExecute
// 6. Commands with white spaces (e.g. "C:\Program Files\xyz.exe") must be enclosed in quotes.
cmdLine := ExpandEnvStr(cmdLine);
{$IFDEF UNICODE}
UniqueString(cmdLine);
{$ENDIF}
ZeroMemory(@si, sizeof(si));
si.cb := sizeof(si);
si.dwFlags := STARTF_USESHOWWINDOW;
si.wShowWindow := WindowMode;
if CreateProcess(nil, PChar(cmdLine), nil, nil, False, 0, nil, nil, si, pi) then
begin
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
Exit;
end;
err := GetLastError;
if (err = ERROR_BAD_EXE_FORMAT) or
(err = ERROR_BAD_FORMAT) then
begin
ZeroMemory(@sei, sizeof(sei));
sei.cbSize := sizeof(sei);
sei.lpFile := PChar(cmdLine);
sei.nShow := WindowMode;
if ShellExecuteEx(@sei) then Exit;
err := GetLastError;
end;
ShowWindowsErrorMessage(err);
end;
答案 1 :(得分:2)
虽然类似的ShellExecute和CreateProcess有不同的用途。
ShellExecute可以打开不可执行的文件。它在注册表中查找给定文件的关联程序的信息并执行它。 ShellExecute也非常适合启动默认Web浏览器,您可以将URL传递给它。
因此,如果您要将TXT文件传递给ShellExecute,它将打开相关程序,例如记事本。但是它会因CreateProcess而失败。
CreateProcess是一个较低级别的函数,它允许您更好地控制进程的输入和输出。例如,您可以使用CreateProcess调用具有文本输出的命令行程序,并捕获该输出并作出反应。
考虑到您的担忧,您需要使用ShellExecute。但是,您需要从参数中拆分命令。这将是第一个非转义的空白字符。
我个人很少直接调用ShellExecute或CreateProcess。我倾向于使用包含这些函数的JCL中的以下函数。
JclMiscel.pas
JclShell.pas
JclSysUtils.pas