关于Visual C ++:如何等待ShellExecute运行?

How to wait for ShellExecute to run?

我已经设法在VC ++中使用ShellExecute来启动文档。
现在,我希望运行一个接收一些参数的命令行工具,并在后台运行(隐藏而不是最小化),并使其阻止我的程序流,以便我可以等待它完成。
我如何更改以下命令行:

1
ShellExecute(NULL,"open",FULL_PATH_TO_CMD_LINE_TOOL,ARGUMENTS,NULL,SW_HIDE);

问题是,我有一个将html转换为pdf的工具,希望该工具完成后(也就是pdf)准备好,让另一个ShellExecute对其进行查看。


有一篇CodeProject文章显示了如何使用ShellExecuteEx代替ShellExecute

1
2
3
4
5
6
7
8
9
10
11
12
13
SHELLEXECUTEINFO ShExecInfo = {0};
ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
ShExecInfo.hwnd = NULL;
ShExecInfo.lpVerb = NULL;
ShExecInfo.lpFile ="c:\\\\MyProgram.exe";        
ShExecInfo.lpParameters ="";  
ShExecInfo.lpDirectory = NULL;
ShExecInfo.nShow = SW_SHOW;
ShExecInfo.hInstApp = NULL;
ShellExecuteEx(&ShExecInfo);
WaitForSingleObject(ShExecInfo.hProcess, INFINITE);
CloseHandle(ShExecInfo.hProcess);

关键点是标志SEE_MASK_NOCLOSEPROCESS,正如MSDN所说,

Use to indicate that the hProcess member receives the process handle. This handle is typically used to allow an application to find out when a process created with ShellExecuteEx terminates

另外,请注意:

The calling application is responsible for closing the handle when it is no longer needed.


您也可以使用CreateProcess代替ShellExecute / ShellExecuteEx。此函数包括一个cmd.exe包装器选项,返回退出代码,并返回stdout。 (其中的内容可能并不完美)。

注意:在我的使用中,我知道必须有stdout结果,但是PeekedNamePipe函数并不总是在第一次尝试时就返回字节数,因此会在那里循环。也许,有人可以弄清楚这一点并发布修订?另外,也许应该产生一个替代版本,分别返回stderr?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <Shellapi.h>


/*
Note:
    The exitCode for a"Cmd Process" is not the exitCode
    for a sub process launched from it!  That can be retrieved
    via the errorlevel variable in the command line like so:
    set errorlevel=&[launch command]&echo.&echo exitCode=%errorlevel%&echo.
    The stdOut vector will then contain the exitCode on a seperate line
*/
BOOL executeCommandLine( const CStringW &command,
                         DWORD &exitCode,
                         const BOOL asCmdProcess=FALSE,
                         std::vector<CStringW> *stdOutLines=NULL )
{
    // Init return values
    BOOL bSuccess = FALSE;
    exitCode = 0;
    if( stdOutLines ) stdOutLines->clear();

    // Optionally prepend cmd.exe to command line to execute
    CStringW cmdLine( (asCmdProcess ? L"cmd.exe /C" : L"" ) +
                      command );

    // Create a pipe for the redirection of the STDOUT
    // of a child process.
    HANDLE g_hChildStd_OUT_Rd = NULL;
    HANDLE g_hChildStd_OUT_Wr = NULL;
    SECURITY_ATTRIBUTES saAttr;
    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
    saAttr.bInheritHandle = TRUE;
    saAttr.lpSecurityDescriptor = NULL;
    bSuccess = CreatePipe( &g_hChildStd_OUT_Rd,
                           &g_hChildStd_OUT_Wr, &saAttr, 0);
    if( !bSuccess ) return bSuccess;        
    bSuccess = SetHandleInformation( g_hChildStd_OUT_Rd,
                                     HANDLE_FLAG_INHERIT, 0 );
    if( !bSuccess ) return bSuccess;        

    // Setup the child process to use the STDOUT redirection
    PROCESS_INFORMATION piProcInfo;
    STARTUPINFO siStartInfo;    
    ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );
    ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
    siStartInfo.cb = sizeof(STARTUPINFO);
    siStartInfo.hStdError = g_hChildStd_OUT_Wr;
    siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
    siStartInfo.dwFlags |= STARTF_USESTDHANDLES;

    // Execute a synchronous child process & get exit code
    bSuccess = CreateProcess( NULL,
      cmdLine.GetBuffer(),  // command line
      NULL,                 // process security attributes
      NULL,                 // primary thread security attributes
      TRUE,                 // handles are inherited
      0,                    // creation flags
      NULL,                 // use parent's environment
      NULL,                 // use parent's current directory
      &siStartInfo,         // STARTUPINFO pointer
      &piProcInfo );        // receives PROCESS_INFORMATION    
    if( !bSuccess ) return bSuccess;        
    WaitForSingleObject( piProcInfo.hProcess, (DWORD)(-1L) );
    GetExitCodeProcess( piProcInfo.hProcess, &exitCode );  
    CloseHandle( piProcInfo.hProcess );
    CloseHandle( piProcInfo.hThread );

    // Return if the caller is not requesting the stdout results
    if( !stdOutLines ) return TRUE;

    // Read the data written to the pipe
    DWORD bytesInPipe = 0;
    while( bytesInPipe==0 ){
        bSuccess = PeekNamedPipe( g_hChildStd_OUT_Rd, NULL, 0, NULL,
                                  &bytesInPipe, NULL );
        if( !bSuccess ) return bSuccess;
    }
    if( bytesInPipe == 0 ) return TRUE;
    DWORD dwRead;
    CHAR *pipeContents = new CHAR[ bytesInPipe ];    
    bSuccess = ReadFile( g_hChildStd_OUT_Rd, pipeContents,
                         bytesInPipe, &dwRead, NULL);
    if( !bSuccess || dwRead == 0 ) return FALSE;

    // Split the data into lines and add them to the return vector
    std::stringstream stream( pipeContents );
    std::string str;
    while( getline( stream, str ) )
        stdOutLines->push_back( CStringW( str.c_str() ) );

    return TRUE;
}

如果使用COM,则有时无法使用ShellExecuteEx,因此必须考虑以下注意事项。

Because ShellExecuteEx can delegate execution to Shell extensions
(data sources, context menu handlers, verb implementations) that are
activated using Component Object Model (COM), COM should be
initialized before ShellExecuteEx is called. Some Shell extensions
require the COM single-threaded apartment (STA) type. In that case,
COM should be initialized as shown here:

1
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)

There are instances where ShellExecuteEx does not use one of these
types of Shell extension and those instances would not require COM to
be initialized at all. Nonetheless, it is good practice to always
initalize COM before using this function.

来自MSDN的更多信息
https://docs.microsoft.com/zh-CN/windows/win32/api/shellapi/nf-shellapi-shellexecuteexa