【问题标题】:CreateProcess using netsh hangs/freezes the application [Delphi]使用 netsh 的 CreateProcess 挂起/冻结应用程序 [Delphi]
【发布时间】:2013-12-20 21:42:17
【问题描述】:

我正在使用下面的代码运行一些“netsh wlan”命令,以检查 wifi 状态、连接到 wifi 配置文件等。

我遇到的问题是,应用程序时不时会挂在任何命令上,这只是一件随机的事情,另外,有时返回的输出会被“无”覆盖,当我调试时它似乎比如时间问题。

我尝试了用 Pascal 运行命令的常规方法,但它不适用于 netsh,方法是“cmd.exe /C netsh wlan....”。

我很感激任何关于让这个冷冻程序更好地工作或其他方法的建议。

我正在运行 DelphiXE5。

谢谢

示例命令:netsh wlan show profiles、netsh wlan show interfaces 等

procedure GetDosOutput(const ACommand, AParameters: String; CallBack: TArg<PAnsiChar>);
const
CReadBuffer = 2400;
var
saSecurity: TSecurityAttributes;
hRead: THandle;
hWrite: THandle;
suiStartup: TStartupInfo;
piProcess: TProcessInformation;
pBuffer: array [0 .. CReadBuffer] of AnsiChar;
dBuffer: array [0 .. CReadBuffer] of AnsiChar;
dRead: DWord;
dRunning: DWord;
begin
saSecurity.nLength := SizeOf(TSecurityAttributes);
saSecurity.bInheritHandle := True;
saSecurity.lpSecurityDescriptor := nil;

if CreatePipe(hRead, hWrite, @saSecurity, 0) then
begin
    FillChar(suiStartup, SizeOf(TStartupInfo), #0);
    suiStartup.cb := SizeOf(TStartupInfo);
    suiStartup.hStdInput := hRead;
    suiStartup.hStdOutput := hWrite;
    suiStartup.hStdError := hWrite;
    suiStartup.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
    suiStartup.wShowWindow := SW_HIDE;

    if CreateProcess(nil, pChar(ACommand + ' ' + AParameters), @saSecurity, @saSecurity, True, NORMAL_PRIORITY_CLASS, nil, nil, suiStartup, piProcess) then
    begin
        repeat
            dRunning := WaitForSingleObject(piProcess.hProcess, 100);
            Application.ProcessMessages();
            repeat
                dRead := 0;
                ReadFile(hRead, pBuffer[0], CReadBuffer, dRead, nil);
                pBuffer[dRead] := #0;

                //OemToAnsi(pBuffer, pBuffer);
                //Unicode support by Lars Fosdal
                OemToCharA(pBuffer, dBuffer);
                CallBack(dBuffer);
            until (dRead < CReadBuffer);
        until (dRunning <> WAIT_TIMEOUT);
        CloseHandle(piProcess.hProcess);
        CloseHandle(piProcess.hThread);
    end;
    CloseHandle(hRead);
    CloseHandle(hWrite);
end;
end;

遵循所有建议后,我更改了这部分代码,到目前为止,该应用程序不再挂起。 非常感谢!

procedure GetDosOutput(const ACommand, AParameters: String; CallBack: TArg<PAnsiChar>);
const
CReadBuffer = 2400;
var
saSecurity: TSecurityAttributes;
hRead: THandle;
hWrite: THandle;
suiStartup: TStartupInfo;
piProcess: TProcessInformation;
pBuffer: array [0 .. CReadBuffer] of AnsiChar;
dBuffer: array [0 .. CReadBuffer] of AnsiChar;
dRead: DWord;
dRunning: DWord;
begin
saSecurity.nLength := SizeOf(TSecurityAttributes);
saSecurity.bInheritHandle := True;
saSecurity.lpSecurityDescriptor := nil;

if CreatePipe(hRead, hWrite, @saSecurity, 0) then
begin
    FillChar(suiStartup, SizeOf(TStartupInfo), #0);
    suiStartup.cb := SizeOf(TStartupInfo);
    suiStartup.hStdInput := hRead;
    suiStartup.hStdOutput := hWrite;
    suiStartup.hStdError := hWrite;
    suiStartup.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
    suiStartup.wShowWindow := SW_HIDE;

    if CreateProcess(nil, pChar(ACommand + ' ' + AParameters), @saSecurity, @saSecurity, True, NORMAL_PRIORITY_CLASS, nil, nil, suiStartup, piProcess) then
    begin
        Application.ProcessMessages();
        repeat
            dRunning := WaitForSingleObject(piProcess.hProcess, 100);

            repeat
                dRead := 0;

                try
                  ReadFile(hRead, pBuffer[0], CReadBuffer, dRead, nil);
                except on E: Exception do
                  Exit;
                end;

                pBuffer[dRead] := #0;

                //OemToAnsi(pBuffer, pBuffer);
                //Unicode support by Lars Fosdal
                OemToCharA(pBuffer, dBuffer);
                CallBack(dBuffer);
            until (dRead < CReadBuffer);

        until (dRunning <> WAIT_TIMEOUT);
        CloseHandle(piProcess.hProcess);
        CloseHandle(piProcess.hThread);
    end;
    CloseHandle(hRead);
    CloseHandle(hWrite);
end;
end;

我创建了这个包装器来简化流程:

function GetDosOutputSimple(const ACommand, AParameters: String) : String;
var
  Tmp, S : String;
begin
  GetDosOutput(ACommand, AParameters, procedure (const Line: PAnsiChar)
  begin
    Tmp := Line;
    S := S + Tmp;
  end);

  GetDosOutputSimple := S;
end;

【问题讨论】:

  • 您没有检查错误。很难看到过去。为什么会调用 Win32 函数,遇到问题,而忽略错误检查?
  • 对不起,我没有写这个,如果你能发布任何很棒的改进。
  • @David - 在这种情况下,这只会导致对问题的更好描述,因为这是关于没有返回的 api。
  • 您需要学习如何检查错误。并避免给子进程所有的句柄。阅读文档。
  • @paul - re(编辑):你现在在哪里关闭把手?

标签: delphi winapi pascal


【解决方案1】:

如果在您调用ReadFile 时出于任何原因,该进程尚未完成写入操作,或者您的缓冲区未填满,ReadFile 将阻塞。通常它应该失败,但它不能,因为你拿着一个写结束的句柄。见documentation

... 父进程关闭它的句柄很重要 在调用 ReadFile 之前写入管道的结尾。如果不这样做, ReadFile 操作不能返回零,因为父进程 对管道的写入端有一个打开的句柄。

在从管道读取之前关闭“hWrite”。

请注意,在这种情况下 - 如果进程还不能向管道写入任何内容,而不是阻塞,ReadFile 将正确失败 - 并且GetLastError 将报告 ERROR_BROKEN_PIPE。在这种情况下,你可能也会优雅地失败。所以最好检查ReadFile的返回。


或者,等到进程终止。这样你就不会冒ReadFile 阻塞等待写入的风险,因为孩子侧的句柄将被关闭。

    ...
repeat
    dRunning := WaitForSingleObject(piProcess.hProcess, 100);
    Application.ProcessMessages();
until (dRunning <> WAIT_TIMEOUT);
repeat
    dRead := 0;
    ...

如果您有可能获得一些相当大的输出,请在应用程序运行时从管道中读取:

  saSecurity.nLength := SizeOf(TSecurityAttributes);
  saSecurity.bInheritHandle := True;
  saSecurity.lpSecurityDescriptor := nil;

  if CreatePipe(hRead, hWrite, @saSecurity, 0) then begin
    try
      FillChar(suiStartup, SizeOf(TStartupInfo), #0);
      suiStartup.cb := SizeOf(TStartupInfo);
      suiStartup.hStdInput := hRead;
      suiStartup.hStdOutput := hWrite;
      suiStartup.hStdError := hWrite;
      suiStartup.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
      suiStartup.wShowWindow := SW_HIDE;

      if CreateProcess(nil, pChar(ACommand + ' ' + AParameters), @saSecurity,
                      @saSecurity, True, NORMAL_PRIORITY_CLASS, nil, nil,
                      suiStartup, piProcess) then begin
        CloseHandle(hWrite);
        try
          repeat
            dRunning := WaitForSingleObject(piProcess.hProcess, 100);
            Application.ProcessMessages();

            repeat
              dRead := 0;
              if ReadFile(hRead, pBuffer[0], CReadBuffer, dRead, nil) then begin
                pBuffer[dRead] := #0;
                OemToCharA(pBuffer, dBuffer);
                CallBack(dBuffer);
              end;
            until (dRead < CReadBuffer);

          until (dRunning <> WAIT_TIMEOUT);
        finally
          CloseHandle(piProcess.hProcess);
          CloseHandle(piProcess.hThread);
        end;

      end;
    finally
      CloseHandle(hRead);
      if GetHandleInformation(hWrite, flags) then
        CloseHandle(hWrite);
    end;
  end;

【讨论】:

  • 伙计们,我不太擅长这个 API 的东西,这完全是从互联网上复制的,不记得我从哪里得到的。我将尝试按照建议进行更改,但如果您能在此处发布任何改进,我将不胜感激。
  • @paul - 在这种情况下,如果你不明白你的代码在做什么,你最好的选择可能是等到进程终止并从管道中读取。将外部until 移动到Application.ProcessMessages 之后。而且我相信您可以检查ReadFile的返回,如果出现任何问题,您可以自己退出。
猜你喜欢
  • 2012-01-10
  • 1970-01-01
  • 1970-01-01
  • 2011-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多