【问题标题】:Good quality code example of how to call an application and wait for it to exit如何调用应用程序并等待它退出的优质代码示例
【发布时间】:2014-05-26 20:57:18
【问题描述】:

使用:Delphi XE2; Windows 32 位 VCL 应用程序

在我的 Delphi 应用程序中,我需要使用 ShellExecute 调用应用程序并等待它完成后再继续。

我在这里看到很多关于 ShellExecute 和 MsgWaitForMultipleObjects 的示例,但不知道哪一个是最好的,因为他们大多在做不推荐的事情,即。还使用了很多人不推荐的 Application.ProcessMessages。

我在这个post 中看到NFX 的答案,它不使用Application.ProcessMessages,但不确定它是否正确或最佳,因此这个问题。

如果您能提供高质量的代码示例,我们会很高兴。

TIA 的任何答案。

【问题讨论】:

  • 好吧,我会使用 CreateProcess fwiw
  • CreateProcess 怎么样?
  • 如果您知道要调用哪个可执行文件,为什么还要涉及 shell? ShellExecuteEx 将致电 CreateProcess。为什么不自己调用呢。
  • 更新:HeartWare 的回答对我来说效果很好。

标签: delphi delphi-xe2 shellexecute


【解决方案1】:

ShellExecuteEx() 和 CreateProcess() 都返回一个可以等待的 HANDLE。当衍生的进程退出时,会发出 HANDLE 信号。

如果您必须在等待时抽取消息队列,请使用MsgWaitForMultipleObjects() 检测新消息何时等待处理。否则,您可以改用WaitForSingleObject()。

NFX 的答案没有使用Application.ProcessMessages(),但它仍然会发送消息,因此根本问题仍然存在。如果您在主线程中进行等待,除非您不介意向用户(或操作系统)呈现无响应的 UI,否则您无法避免这种情况。您也可以在工作线程中进行等待,这样您就不需要在等待时发送消息,并且您的 UI 也不会被阻塞。如果需要,您可以在等待时禁用 UI 或显示状态 UI。

【讨论】:

    【解决方案2】:

    我使用这些函数异步执行子进程,并在进程终止时回调它。它通过创建一个线程来工作,该线程等待进程终止,然后通过给定的事件方法回调主程序线程。请注意,您的程序会在子进程运行时继续运行,因此您需要某种形式的逻辑来防止生成子进程的无限发生。

    UNIT SpawnFuncs;
    
    INTERFACE
    
    {$IF CompilerVersion >= 20 }
      {$DEFINE ANONYMOUS_METHODS }
    {$ELSE }
      {$UNDEF ANONYMOUS_METHODS }
    {$ENDIF }
    
    TYPE
      TSpawnAction  = (saStarted,saEnded);
      TSpawnArgs    = RECORD
                        Action      : TSpawnAction;
                        FileName    : String;
                        PROCEDURE   Initialize(Act : TSpawnAction ; CONST FN : String); INLINE;
                        CLASS FUNCTION Create(Act : TSpawnAction ; CONST FN : String) : TSpawnArgs; static;
                      END;
      {$IFDEF ANONYMOUS_METHODS }
        TSpawnEvent = REFERENCE TO PROCEDURE(Sender : TObject ; CONST Args : TSpawnArgs);
      {$ELSE }
        TSpawnEvent = PROCEDURE(Sender : TObject ; CONST Args : TSpawnArgs) OF OBJECT;
      {$ENDIF }
    
    FUNCTION ShellExec(CONST FileName,Tail : String ; Event : TSpawnEvent = NIL ; Sender : TObject = NIL) : BOOLEAN; OVERLOAD;
    FUNCTION ShellExec(CONST FileName : String ; Event : TSpawnEvent = NIL ; Sender : TObject = NIL) : BOOLEAN; OVERLOAD;
    FUNCTION ShellExec(CONST FileName : String ; VAR EndedFlag : BOOLEAN) : BOOLEAN; OVERLOAD;
    FUNCTION ShellExec(CONST FileName,Tail : String ; VAR EndedFlag : BOOLEAN) : BOOLEAN; OVERLOAD;
    
    PROCEDURE ShellExecExcept(CONST FileName : String ; Event : TSpawnEvent = NIL ; Sender : TObject = NIL); OVERLOAD:
    PROCEDURE ShellExecExcept(CONST FileName,Tail : String ; Event : TSpawnEvent = NIL ; Sender : TObject = NIL); OVERLOAD;
    PROCEDURE ShellExecExcept(CONST FileName : String ; VAR EndedFlag : BOOLEAN); OVERLOAD;
    PROCEDURE ShellExecExcept(CONST FileName,Tail : String ; VAR EndedFlag : BOOLEAN); OVERLOAD;
    
    IMPLEMENTATION
    
    USES Windows,SysUtils,Classes,ShellApi;
    
    TYPE
      TWaitThread   = CLASS(TThread)
                        CONSTRUCTOR Create(CONST FileName : String ; ProcessHandle : THandle ; Event : TSpawnEvent ; Sender : TObject); REINTRODUCE; OVERLOAD;
                        CONSTRUCTOR Create(CONST FileName : String ; ProcessHandle : THandle ; EndedFlag : PBoolean); OVERLOAD;
                        PROCEDURE   Execute; OVERRIDE;
                        PROCEDURE   DoEvent(Action : TSpawnAction);
                      PRIVATE
                        Handle      : THandle;
                        Event       : TSpawnEvent;
                        EndedFlag   : PBoolean;
                        FN          : String;
                        Sender      : TObject;
                        {$IFNDEF ANONYMOUS_METHODS }
                          Args      : TSpawnArgs;
                          PROCEDURE RunEvent;
                        {$ENDIF }
                      END;
    
    CONSTRUCTOR TWaitThread.Create(CONST FileName : String ; ProcessHandle : THandle ; Event : TSpawnEvent ; Sender : TObject);
      BEGIN
        INHERITED Create(TRUE);
        Handle:=ProcessHandle; Self.Event:=Event; FN:=FileName; Self.Sender:=Sender; FreeOnTerminate:=TRUE;
        Resume
      END;
    
    {$IFNDEF ANONYMOUS_METHODS }
    PROCEDURE TWaitThread.RunEvent;
      BEGIN
        Event(Sender,Args)
      END;
    {$ENDIF }
    
    CONSTRUCTOR TWaitThread.Create(CONST FileName : String ; ProcessHandle : THandle ; EndedFlag : PBoolean);
      BEGIN
        INHERITED Create(TRUE);
        Handle:=ProcessHandle; EndedFlag^:=FALSE; Self.EndedFlag:=EndedFlag; FreeOnTerminate:=TRUE;
        Resume
      END;
    
    PROCEDURE TWaitThread.DoEvent(Action : TSpawnAction);
      BEGIN
        IF Assigned(EndedFlag) THEN
          EndedFlag^:=(Action=saEnded)
        ELSE BEGIN
          {$IFDEF ANONYMOUS_METHODS }
            Synchronize(PROCEDURE BEGIN Event(Sender,TSpawnArgs.Create(Action,FN)) END)
          {$ELSE }
            Args:=TSpawnArgs.Create(Action,FN);
            Synchronize(RunEvent)
          {$ENDIF }
        END
      END;
    
    PROCEDURE TWaitThread.Execute;
      BEGIN
        DoEvent(saStarted);
        WaitForSingleObject(Handle,INFINITE);
        CloseHandle(Handle);
        DoEvent(saEnded)
      END;
    
    FUNCTION ShellExec(CONST FileName,Tail : String ; Event : TSpawnEvent ; Sender : TObject ; EndedFlag : PBoolean) : BOOLEAN; OVERLOAD;
      VAR
        Info  : TShellExecuteInfo;
        PTail : PChar;
    
      BEGIN
        ASSERT(NOT (Assigned(Event) AND Assigned(EndedFlag)),'ShellExec called with both Event and EndedFlag!');
        IF Tail='' THEN PTail:=NIL ELSE PTail:=PChar(Tail);
        FillChar(Info,SizeOf(TShellExecuteInfo),0);
        Info.cbSize:=SizeOf(TShellExecuteInfo);
        Info.fMask:=SEE_MASK_FLAG_NO_UI;
        Info.lpFile:=PChar(FileName);
        Info.lpParameters:=PTail;
        Info.nShow:=SW_SHOW;
        IF NOT (Assigned(Event) OR Assigned(EndedFlag)) THEN
          Result:=ShellExecuteEx(@Info)
        ELSE BEGIN
          Info.fMask:=Info.fMask OR SEE_MASK_NOCLOSEPROCESS;
          Result:=ShellExecuteEx(@Info) AND (Info.hProcess>0);
          IF Result THEN
            IF Assigned(Event) THEN
              TWaitThread.Create(FileName,Info.hProcess,Event,Sender)
            ELSE
              TWaitThread.Create(FileName,Info.hProcess,EndedFlag)
        END
      END;
    
    FUNCTION ShellExec(CONST FileName,Tail : String ; Event : TSpawnEvent = NIL ; Sender : TObject = NIL) : BOOLEAN;
      BEGIN
        Result:=ShellExec(FileName,Tail,Event,Sender,NIL)
      END;
    
    FUNCTION ShellExec(CONST FileName : String ; Event : TSpawnEvent = NIL ; Sender : TObject = NIL) : BOOLEAN;
      BEGIN
        Result:=ShellExec(FileName,'',Event,Sender)
      END;
    
    FUNCTION ShellExec(CONST FileName,Tail : String ; VAR EndedFlag : BOOLEAN) : BOOLEAN;
      BEGIN
        Result:=ShellExec(FileName,Tail,NIL,NIL,@EndedFlag)
      END;
    
    FUNCTION ShellExec(CONST FileName : String ; VAR EndedFlag : BOOLEAN) : BOOLEAN;
      BEGIN
        Result:=ShellExec(FileName,'',EndedFlag)
      END;
    
    PROCEDURE ShellExecExcept(CONST FileName : String ; Event : TSpawnEvent = NIL ; Sender : TObject = NIL);
      BEGIN
        IF NOT ShellExec(FileName,Event,Sender) THEN RaiseLastOSError
      END;
    
    PROCEDURE ShellExecExcept(CONST FileName,Tail : String ; Event : TSpawnEvent = NIL ; Sender : TObject = NIL);
      BEGIN
        IF NOT ShellExec(FileName,Tail,Event,Sender) THEN RaiseLastOSError
      END;
    
    PROCEDURE ShellExecExcept(CONST FileName : String ; VAR EndedFlag : BOOLEAN);
      BEGIN
        IF NOT ShellExec(FileName,EndedFlag) THEN RaiseLastOSError
      END;
    
    PROCEDURE ShellExecExcept(CONST FileName,Tail : String ; VAR EndedFlag : BOOLEAN);
      BEGIN
        IF NOT ShellExec(FileName,Tail,EndedFlag) THEN RaiseLastOSError
      END;
    
    { TSpawnArgs }
    
    CLASS FUNCTION TSpawnArgs.Create(Act : TSpawnAction ; CONST FN : String) : TSpawnArgs;
      BEGIN
        Result.Initialize(Act,FN)
      END;
    
    PROCEDURE TSpawnArgs.Initialize(Act : TSpawnAction ; CONST FN : String);
      BEGIN
        Action:=Act; FileName:=FN
      END;
    
    END.
    

    如下使用:

    USES SpawnFuncs;
    
    ShellExec(ProgramToRun,CommandLineArgs,Event,Sender)
    

    或

    ShellExec(ProgramToRunOrFileToOpen,Event,Sender)
    

    在哪里

    ProgramToRun = Name of program to run
    ProgramToRunOrFileToOpen = Program to run, or file to open (f.ex. a .TXT file)
    CommandLineArgs = Command line parameters to pass to the program
    Event = The (perhaps anonymous) method to run upon start and termination of program
    Sender = The Sender parameter to pass to the method
    

    或者,如果您只是想知道子进程何时终止,则有两个简化版本可以接受一个布尔变量,该变量将在子程序终止时立即设置为 TRUE。您无需先将其设置为 FALSE,因为它会自动完成:

    ShellExec(ProgramToRun,ChildProcessEnded);
    

    如果您不提供事件处理程序或 BOOLEAN 变量,ShellExec 过程只会运行/打开给定的文件并且不执行回调。

    如果您不提供 Sender,则 Sender 参数将在事件处理程序中未定义。

    事件处理程序必须是具有以下签名的方法(匿名或其他):

    PROCEDURE SpawnEvent(Sender : TObject ; CONST Args : TSpawnArgs);
    

    其中 Args 包含以下字段:

    Action = either saStarted or saEnded
    FileName = the name of the file that passed to ShellExec
    

    如果您更喜欢使用 SEH(结构化异常处理)而不是错误返回值,您可以使用 ShellExecExcept PROCEDUREs 而不是 ShellExec FUNCTIONs。如果执行请求失败,这些将引发操作系统错误。

    【讨论】:

    • 因为到目前为止我在使用中还没有需要它......但你说得有道理。更新代码以使 PROCEDUREs FUNCTIONs 改为允许调用者知道结果...
    • @HeartWare:感谢您发布此消息!非常感谢。
    • @SteveF:好吧,您删除了有关如何防止“无限地产生子进程”的问题,但无论如何,我的回答是:这完全取决于您希望主应用程序在外部程序正在运行。您是否希望它完全无响应(即根本不允许用户进行交互,如果用户尝试,可能会给出(臭名昭著的)“进程无响应”)?或者你想允许用户在外部程序运行时在你的程序中执行其他任务,除了运行一个新的外部程序?
    • @HeartWare:我希望它等待而不响应任何其他用户输入。
    • @DavidHeffernan:好的,我现在在所有情况下都使用 SEE,只是为了让您满意 :-)。现在还有什么理由反对“值得生产使用的优质代码”。不是“使用 SE”。不是“不检查错误”。那么还剩下什么?
    【解决方案3】:
    function StartProcess(Exename: string; CmdLineArgs: string = ''; ShowWindow: boolean = True;
      WaitForFinish: boolean = False): integer;
    var
      StartInfo: TStartupInfo;
      ProcInfo: TProcessInformation;
      CreateOK: boolean;
    begin
      // Simple wrapper for the CreateProcess command
      // returns the process id of the started process.
      FillChar(StartInfo, SizeOf(TStartupInfo), #0);
      FillChar(ProcInfo, SizeOf(TProcessInformation), #0);
      StartInfo.cb := SizeOf(TStartupInfo);
    
      if not(ShowWindow) then
      begin
        StartInfo.dwFlags := STARTF_USESHOWWINDOW;
        StartInfo.wShowWindow := SW_HIDE;
      end;
    
      CreateOK := CreateProcess(nil, PChar(Exename + ' ' + CmdLineArgs), nil, nil, False,
        CREATE_NEW_PROCESS_GROUP + NORMAL_PRIORITY_CLASS, nil, nil, StartInfo, ProcInfo);
    
      Result := ProcInfo.dwProcessId;
    
      if CreateOK then
      begin
        // may or may not be needed. Usually wait for child processes
        if WaitForFinish then
          WaitForSingleObject(ProcInfo.hProcess, Infinite);
      end
      else
      begin
        // ShowMessage('Unable to run '+ProgramName);
        SysErrorMessage(GetLastError());
      end;
    
      // close process & thread handles
      CloseHandle(ProcInfo.hProcess);
      CloseHandle(ProcInfo.hThread);
    end;
    

    【讨论】:

    • 请不要在没有解释的情况下发布从其他地方复制的代码。零化ProcInfo 是不必要的。我想我会将第一个和第二个参数都指定为CreateProcess。我会尝试/最终保护把手。在您的主线程上运行它,它将阻塞而不是泵送队列。你想要那个。
    • @DavidHeffernan:你为什么认为我在 SO 上发布我的原始问题 - 这样我才能得到答案,但你所做的只是将我重定向到 MSDN。我开始使用 Turbo C 进行编程,并且了解 C 和 C++ 编程。但我仍然无法理解 MSDN 中记录的 Windows API。这就是为什么我在这里发帖希望得到答案。我不想参考 MSDN。这很愚蠢,当程序员在 SO 上提出问题时,任何人都会无意识地建议“参考”MSDN。我不是 Visual C++ 程序员,也永远不想成为一名程序员。
    • 我觉得这种态度令人沮丧。我不会经常用 C 或 C++ 编写 Windows 代码。不会阻止我阅读文档。拒绝阅读您平台的文档会给您带来巨大的障碍。无法想象你为什么会选择这样做。
    • 这是因为我过去常常阅读 MSDN 上的文档,发现很难理解,尤其是 Windows API 的东西。然后将 C++ 代码和约定翻译成 Delphi 是另一个故事,它涉及反复试验,直到“专家”看到您翻译的 Delphi 代码并制作 cmets 之前,您仍然不知道您是否正确。因此,虽然 Windows API 的 MSDN 文档对于尝试实现 API 功能的 Delphi 程序员来说是一个有用的指南,但它也可能适得其反(对某些人来说?)。
    猜你喜欢
    • 1970-01-01
    • 2017-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多