VC++分别使用WinExec、CreateProcess、ShellExecute和ShellExecuteEx来启动程序(附源码)

       有时,我们需要在主程序中启动另一个exe程序(创建一个进程),可以有多种实现方法,可以调用WinExec、CreateProcess、ShellExecute和ShellExecuteEx多个API函数来实现,今天我们就来简单地介绍这方面的内容。

1、使用WinExec启动

        可以调用API函数WinExec启动一个程序(进程)。WinExec API函数的声明如下:

WINBASEAPI
UINT
WINAPI
WinExec(
    __in LPCSTR lpCmdLine,
    __in UINT uCmdShow
    );

该函数有两个参数:

1)LPCSTR lpCmdLine:一般传入的是要启动的exe程序名称与要传递给exe程序命令行参数的构成式,中间用空格隔开,即“exe名称+空格+命令行参数”。
2)UINT uCmdShow:使用ShowWindow API函数的参数,一般使用SW_SHOW和SW_HIDE,即显示窗口和掩藏窗口。

       我们可以调用WinExec将cmd.exe窗口起来并执行指定的命令行命令,可以给cmd.exe传递“/c 命令行命令”,其中/c是用来指定要执行的命令行命令,cmd.exe程序支持的命令行参数可以在cmd窗口输入cmd.exe /?来查看,如下:

 比如我们要打开系统的设备管理器页面,让cmd.exe执行hdwwiz.cpl命令即可,如下:

// 打开设备管理器页面
WinExec( "cmd.exe /c hdwwiz.cpl",SW_HIDE );

其中,SW_HIDE的含义是执行命令的cmd.exe以掩藏的方式启动,即用户看不到cmd.exe窗口,只会看到最终打开的设备管理器窗口,设备管理器窗口如下:

       再比如我们要打开系统的麦克风设备列表页面,代码如下:

// 打开设备列表页面
WinExec( "cmd.exe /c rundll32.exe shell32.dll,Control_RunDLL mmsys.cpl,,1",SW_HIDE );

打开的麦克风设备列表页面如下:

       但WinExec函数有个问题,其参数lpCmdLine只支持ANSI窄字节字符串编码,如果要启动的进程路径中包含中文等字符,在支持多国语言时会有问题。程序要支持多国语言,要在非中文的操作系统上正常运行,必须要使用支持Unicode版本的API函数。

2、使用CreateProcess启动

       可以调用API函数CreateProcess启动一个程序,该API函数提供两个版本,一个ANSI版本的ShellExecuteA,一个Unicode版本的ShellExecuteW,函数声明如下:

#ifdef UNICODE
#define CreateProcess  CreateProcessW
#else
#define CreateProcess  CreateProcessA
#endif // !UNICODE

WINBASEAPI
BOOL
WINAPI
CreateProcessA(
    __in_opt    LPCSTR lpApplicationName,
    __inout_opt LPSTR lpCommandLine,
    __in_opt    LPSECURITY_ATTRIBUTES lpProcessAttributes,
    __in_opt    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    __in        BOOL bInheritHandles,
    __in        DWORD dwCreationFlags,
    __in_opt    LPVOID lpEnvironment,
    __in_opt    LPCSTR lpCurrentDirectory,
    __in        LPSTARTUPINFOA lpStartupInfo,
    __out       LPPROCESS_INFORMATION lpProcessInformation
    );

WINBASEAPI
BOOL
WINAPI
CreateProcessW(
    __in_opt    LPCWSTR lpApplicationName,
    __inout_opt LPWSTR lpCommandLine,
    __in_opt    LPSECURITY_ATTRIBUTES lpProcessAttributes,
    __in_opt    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    __in        BOOL bInheritHandles,
    __in        DWORD dwCreationFlags,
    __in_opt    LPVOID lpEnvironment,
    __in_opt    LPCWSTR lpCurrentDirectory,
    __in        LPSTARTUPINFOW lpStartupInfo,
    __out       LPPROCESS_INFORMATION lpProcessInformation
    );

这里为什么会有ANSI和Unicode编码之分呢?因为函数参数中有字符串,有字符串的地方就涉及到字符编码。该函数主要使用到三个参数:

1)LPWSTR lpCommandLine:用来指定要启动的exe程序路径,以及传递给exe程序的命令行参数;
2)LPSTARTUPINFOW lpStartupInfo:用来设置一些启动参数;
3)LPPROCESS_INFORMATION lpProcessInformation:此参数是传出参数,主要包含启动起来的进程句柄、进程的主线程句柄以及进程id等信息。

        调用该接口启动程序的示例代码如下:

PROCESS_INFORMATION pi;
memset( &pi, 0, sizeof(pi) );
STARTUPINFO si;
memset( &si, 0, sizeof(si) );
si.cb = sizeof(STARTUPINFO);
si.wShowWindow = SW_SHOW;
si.dwFlags = STARTF_USESHOWWINDOW;

TCHAR achCmdLine[MAX_PATH*2] = {0};   

_stprintf( achCmdLine, _T("D:\\Test.exe") );
//_tcscat( achCmdLine, _T(" ") ); // 有一个空格
//_tcscat( achCmdLine, lpszCmdLineParam ); // 此处可以用来传递给exe的命令行参数
if( !CreateProcess( NULL, achCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi ) )
{
    WriteLog( _T("[CreateWbProcess] 进程创建失败,GetLastError:%d"), GetLastError() );

    return;
}

// 将进程句柄和线程句柄关闭掉,否则会有句柄泄漏
CloseHandle( pi.hThread );
CloseHandle( pi.hProcess );

如果创建进程失败,可以调用GetLastError函数获取进程创建失败的错误码,然后拿到VS的错误查找窗口查找错误码对应的含义:

       此外,在调用完CreateProcess之后,需要将lpProcessInformation参数返回的进程及线程句柄关闭掉,因为创建进程时会增加进程及线程句柄的引用,比如调用CloseHanlde将引用关闭掉,如果不关闭,可能会导致句柄泄漏。

3、使用ShellExecute启动

       我们可以调用ShellExecute API函数来启动一个进程,该函数也提供两个版本,一个ANSI版本的ShellExecuteA,一个Unicode版本的ShellExecuteW,函数声明如下:

#ifdef UNICODE
#define ShellExecute  ShellExecuteW
#else
#define ShellExecute  ShellExecuteA
#endif // !UNICODE

SHSTDAPI_(HINSTANCE) ShellExecuteA(__in_opt HWND hwnd, __in_opt LPCSTR lpOperation, __in LPCSTR lpFile, __in_opt LPCSTR lpParameters,
    __in_opt LPCSTR lpDirectory, __in INT nShowCmd);
SHSTDAPI_(HINSTANCE) ShellExecuteW(__in_opt HWND hwnd, __in_opt LPCWSTR lpOperation, __in LPCWSTR lpFile, __in_opt LPCWSTR lpParameters,
    __in_opt LPCWSTR lpDirectory, __in INT nShowCmd);

其中,参数1 hwnd,我们不关心,直接填写NULL就好了;参数2 lpOperation是要执行的操作,该参数的说明如下:

[in, optional] lpOperation

Type: LPCTSTR

A pointer to a null-terminated string, referred to in this case as a verb, that specifies the action to be performed. The set of available verbs depends on the particular file or folder. Generally, the actions available from an object's shortcut menu are available verbs. The following verbs are commonly used:

edit
Launches an editor and opens the document for editing. If lpFile is not a document file, the function will fail.

explore
Explores a folder specified by lpFile.

find
Initiates a search beginning in the directory specified by lpDirectory.

open
Opens the item specified by the lpFile parameter. The item can be a file or folder.

print
Prints the file specified by lpFile. If lpFile is not a document file, the function fails.

NULL
The default verb is used, if available. If not, the "open" verb is used. If neither verb is available, the system uses the first verb listed in the registry.

一般我们主要用open操作,比如打开文件。比如我们可以启动explorer.exe资源管理器进程,打开并选中一个文件,代码如下:

CString strParameter;
strParameter.Format( _T("/select, \"%s\""), strFilePath );
ShellExecute( NULL, _T("open"), _T("explorer.exe"), strParameter, NULL, SW_SHOWNORMAL );

4、使用ShellExecuteEx启动

       可以调用API函数ShellExecuteEx来启动一个进程,该函数也提供了两个版本:

#ifdef UNICODE
#define ShellExecuteEx  ShellExecuteExW
#else
#define ShellExecuteEx  ShellExecuteExA
#endif // !UNICODE

SHSTDAPI_(BOOL) ShellExecuteExA(__inout SHELLEXECUTEINFOA *pExecInfo);
SHSTDAPI_(BOOL) ShellExecuteExW(__inout SHELLEXECUTEINFOW *pExecInfo);

其参数是SHELLEXECUTEINFO结构体对象,该结构体也提供了两个版本:

#ifdef UNICODE
typedef SHELLEXECUTEINFOW SHELLEXECUTEINFO;
typedef LPSHELLEXECUTEINFOW LPSHELLEXECUTEINFO;
#else
typedef SHELLEXECUTEINFOA SHELLEXECUTEINFO;
typedef LPSHELLEXECUTEINFOA LPSHELLEXECUTEINFO;
#endif // UNICODE

typedef struct _SHELLEXECUTEINFOA
{
    DWORD cbSize;               // in, required, sizeof of this structure
    ULONG fMask;                // in, SEE_MASK_XXX values
    HWND hwnd;                  // in, optional
    LPCSTR   lpVerb;            // in, optional when unspecified the default verb is choosen
    LPCSTR   lpFile;            // in, either this value or lpIDList must be specified
    LPCSTR   lpParameters;      // in, optional
    LPCSTR   lpDirectory;       // in, optional
    int nShow;                  // in, required
    HINSTANCE hInstApp;         // out when SEE_MASK_NOCLOSEPROCESS is specified
    void *lpIDList;             // in, valid when SEE_MASK_IDLIST is specified, PCIDLIST_ABSOLUTE, for use with SEE_MASK_IDLIST & SEE_MASK_INVOKEIDLIST
    LPCSTR   lpClass;           // in, valid when SEE_MASK_CLASSNAME is specified
    HKEY hkeyClass;             // in, valid when SEE_MASK_CLASSKEY is specified
    DWORD dwHotKey;             // in, valid when SEE_MASK_HOTKEY is specified
    union                       
    {                           
        HANDLE hIcon;           // not used
#if (NTDDI_VERSION >= NTDDI_WIN2K)
        HANDLE hMonitor;        // in, valid when SEE_MASK_HMONITOR specified
#endif // (NTDDI_VERSION >= NTDDI_WIN2K)
    } DUMMYUNIONNAME;           
    HANDLE hProcess;            // out, valid when SEE_MASK_NOCLOSEPROCESS specified
} SHELLEXECUTEINFOA, *LPSHELLEXECUTEINFOA;

typedef struct _SHELLEXECUTEINFOW
{
    DWORD cbSize;               // in, required, sizeof of this structure
    ULONG fMask;                // in, SEE_MASK_XXX values
    HWND hwnd;                  // in, optional
    LPCWSTR  lpVerb;            // in, optional when unspecified the default verb is choosen
    LPCWSTR  lpFile;            // in, either this value or lpIDList must be specified
    LPCWSTR  lpParameters;      // in, optional
    LPCWSTR  lpDirectory;       // in, optional
    int nShow;                  // in, required
    HINSTANCE hInstApp;         // out when SEE_MASK_NOCLOSEPROCESS is specified
    void *lpIDList;             // in, valid when SEE_MASK_IDLIST is specified, PCIDLIST_ABSOLUTE, for use with SEE_MASK_IDLIST & SEE_MASK_INVOKEIDLIST
    LPCWSTR  lpClass;           // in, valid when SEE_MASK_CLASSNAME is specified
    HKEY hkeyClass;             // in, valid when SEE_MASK_CLASSKEY is specified
    DWORD dwHotKey;             // in, valid when SEE_MASK_HOTKEY is specified
    union                       
    {                           
        HANDLE hIcon;           // not used
#if (NTDDI_VERSION >= NTDDI_WIN2K)
        HANDLE hMonitor;        // in, valid when SEE_MASK_HMONITOR specified
#endif // (NTDDI_VERSION >= NTDDI_WIN2K)
    } DUMMYUNIONNAME;           
    HANDLE hProcess;            // out, valid when SEE_MASK_NOCLOSEPROCESS specified
} SHELLEXECUTEINFOW, *LPSHELLEXECUTEINFOW;

该函数是ShellExecute扩展版本,其中传入结构体中的lpVerb字段除了支持ShellExecute支持的操作,还支持runas操作,通过runas操作来启动一个有管理员权限的程序,runas操作的详细说明如下:

runas: Launches an application as Administrator. User Account Control (UAC) will prompt the user for consent to run the application elevated or enter the credentials of an administrator account used to run the application.

       比如我们调用ShellExecuteEx函数,传入runas参数来启动test.exe程序,因为test.exe是runas启动起来的,所以具有管理员权限,代码如下:

SHELLEXECUTEINFO si;
RtlZeroMemory( &si, sizeof( SHELLEXECUTEINFO ) );
si.cbSize = sizeof(SHELLEXECUTEINFO);
si.lpFile = _T("D:\\test.exe");
si.lpParameters = lpCmdParam;
si.nShow = SW_SHOWNORMAL;
si.lpVerb = _T("runas");
BOOL bRet = ShellExecuteEx( &si );
if ( !bRet ) // TL启动失败
{
TCHAR achLog[256] = { 0 };

// 先取lasterror值
DWORD dwLastErr = GetLastError();
_stprintf( achLog, _T("ShellExecuteEx failed, GetLastError: %d."), dwLastErr );
WriteLog( achLog );

// 再取hInstApp错误代码
int nHInsVal = (int)si.hInstApp;
if ( nHInsVal <= 32 )
{
_stprintf( achLog, _T("ShellExecuteEx failure, errcode: %d."), nHInsVal );
WriteLog( achLog );
}
}

       如果进程启动失败,则ShellExecuteEx会返回FALSE,可以通过SHELLEXECUTEINFO结构体中的hInstApp字段获取启动失败的错误码,还可以使用GetLastError函数获取LastError值。

猜你喜欢

转载自blog.csdn.net/chenlycly/article/details/125015420