news 2026/7/31 6:14:54

Windows C++进程启动:CreateProcess、ShellExecute等四种API详解与实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Windows C++进程启动:CreateProcess、ShellExecute等四种API详解与实战

1. 项目概述:在Windows上启动外部程序的四种经典方式

在Windows平台的C++开发中,启动另一个可执行程序是一个再基础不过,却又充满细节和陷阱的操作。无论是需要调用一个命令行工具来处理数据,还是从你的GUI程序中打开一个关联的文档,亦或是构建一个启动器或安装程序,你都需要与系统交互,请求它“运行另一个程序”。很多新手开发者,甚至一些有经验的同行,在面对WinExecCreateProcessShellExecuteShellExecuteEx这四个函数时,往往会感到困惑:它们看起来功能相似,我该用哪个?为什么有这么多选择?

简单来说,这四个函数代表了Windows API在进程创建和Shell集成方面不同时期、不同层次和不同需求的解决方案。WinExec是16位Windows时代的遗老,简单但限制多;CreateProcess是底层的、功能强大的“瑞士军刀”,给你完全的控制权;ShellExecuteShellExecuteEx则是更高级的、与Windows Shell(资源管理器)深度集成的接口,它们不仅能运行.exe,还能“打开”任何已注册文件类型的关联程序。选择哪一个,取决于你的具体场景:是需要精细控制新进程的每一个细节,还是仅仅想“打开”一个文件或URL。

本文将深入拆解这四种方法,从历史背景、核心原理、参数详解到实战代码,并附上可直接编译运行的完整源码。我会分享在实际项目中踩过的坑和总结的最佳实践,比如权限问题、路径解析、错误处理以及如何避免内存泄漏等。无论你是正在学习Windows编程的初学者,还是需要优化现有项目启动逻辑的资深工程师,这篇文章都能为你提供清晰的指引和实用的代码模板。

2. 核心API深度解析与选型指南

2.1 WinExec:简单历史的遗留产物

WinExec可能是这四个函数中最“古老”的一个,它的设计可以追溯到16位的Windows 3.1时代。它的函数签名极其简单:UINT WinExec(LPCSTR lpCmdLine, UINT uCmdShow)。你只需要传入一个命令行字符串和一个窗口显示方式(如SW_SHOWSW_HIDE),它就会尝试执行。

它的“简单”既是优点也是致命的缺点。由于历史原因,WinExec内部最终会调用CreateProcess函数,但它屏蔽了几乎所有底层的控制选项。你无法指定环境变量、无法设置进程安全属性、无法重定向标准输入输出、也无法获取新进程的句柄和ID。更麻烦的是,它的命令行参数lpCmdLine长度限制在1024个字符(在旧系统上可能更少),这在处理复杂长路径时可能成为问题。

注意:在微软的官方文档中,WinExec已被明确标记为“已弃用”。对于新项目,强烈建议不要使用它。它存在的意义主要是为了向后兼容那些非常古老的代码。如果你在维护一个遗留系统,看到它还能理解;但自己写新代码时,请直接跳过它,从CreateProcessShellExecute开始。

2.2 CreateProcess:底层控制的终极武器

如果说WinExec是玩具枪,那么CreateProcess就是可定制化的精密机床。它是Windows NT内核家族(包括所有现代Windows系统)中创建进程最根本、最强大的函数。几乎所有其他创建进程的高层API(包括system_spawn等C运行时库函数)最终都会调用它。

CreateProcess的函数原型看起来有些吓人,因为它提供了极其精细的控制:

BOOL CreateProcessW( LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation );

每一个参数都至关重要:

  • lpApplicationName / lpCommandLine: 指定要运行的程序。这里有一个经典陷阱:lpApplicationName可以是完整路径或可执行文件名(依赖PATH),而lpCommandLine是整个命令行字符串。通常,我们将lpApplicationName设为NULL,将可执行文件路径和参数全部放入lpCommandLine。但要注意,lpCommandLine要求是可写的字符串(非常规要求),因为函数可能会修改它。
  • lpProcessAttributes / lpThreadAttributes: 用于设置进程和主线程的安全描述符和继承性。在绝大多数不需要特殊安全控制的场景下,设为NULL即可。
  • bInheritHandles: 决定新进程是否继承当前进程的可继承句柄(如文件、管道句柄)。这是实现进程间通信(IPC)的基础之一。
  • dwCreationFlags: 这是一组标志位的组合,功能极其强大。例如:
    • CREATE_NEW_CONSOLE: 为新控制台程序创建一个新的控制台窗口。
    • CREATE_NO_WINDOW: 运行程序但不创建任何窗口(适用于后台服务或工具)。
    • CREATE_SUSPENDED: 创建进程后,主线程处于挂起状态,便于你进行注入或修改内存等操作后再恢复运行。
    • DETACHED_PROCESS: 用于运行没有控制台窗口的控制台程序。
  • lpEnvironment: 指向新进程的环境变量块。设为NULL则继承父进程环境。
  • lpCurrentDirectory: 新进程的工作目录。NULL表示使用父进程的工作目录。
  • lpStartupInfo: 一个STARTUPINFO结构体,用于指定新进程的主窗口特性,如窗口位置、大小、标准句柄(stdin, stdout, stderr)的重定向等。这是实现子进程输入输出捕获的关键。
  • lpProcessInformation: 一个PROCESS_INFORMATION结构体指针,函数成功返回后,会在此填充新进程的句柄、ID以及主线程的句柄、ID。这是CreateProcess的核心价值之一:你拿到了控制新进程的“遥控器”

为什么选择CreateProcess当你需要:1) 完全控制新进程的创建属性;2) 重定向其输入输出(例如,运行cmd.exe并获取其输出);3) 等待进程结束并获取退出码;4) 通过句柄管理进程(终止、查询状态);5) 进行进程注入等高级操作。它的复杂度是换取控制力必须付出的代价。

2.3 ShellExecute 与 ShellExecuteEx:Shell集成的便捷之道

ShellExecuteShellExecuteEx属于Shell API,它们位于比CreateProcess更高的抽象层。它们的思想不是“创建一个进程”,而是“对一个文件或系统对象执行一个操作”。这个操作可以是“打开”(open)、“编辑”(edit)、“打印”(print)、“查找”(find)等,具体取决于文件类型在注册表中的关联。

  • ShellExecute: 函数原型为HINSTANCE ShellExecute(HWND hwnd, LPCSTR lpOperation, LPCSTR lpFile, LPCSTR lpParameters, LPCSTR lpDirectory, INT nShowCmd)。它的参数更贴近用户操作:

    • lpOperation: 动词,如"open","edit","print"。设为NULL"open"最常见。
    • lpFile: 可以是可执行文件(.exe)、文档(.txt,.pdf)、URL(http://...)甚至是一个文件夹路径。
    • lpParameters: 如果lpFile是可执行文件,这里传递命令行参数。
    • lpDirectory: 工作目录。
    • nShowCmd: 窗口显示状态。

    它的返回值比较特殊,成功时返回一个大于32的值(HINSTANCE),失败时返回一个小于等于32的错误码。它用起来非常简单,一行代码就能打开一个网页或文档:ShellExecute(NULL, "open", "https://www.example.com", NULL, NULL, SW_SHOWNORMAL);

  • ShellExecuteEx: 这是ShellExecute的增强版,接受一个SHELLEXECUTEINFO结构体作为参数。这个结构体包含了ShellExecute的所有功能,并增加了更多控制和信息返回能力,例如:

    • 可以获取新进程的句柄(hProcess),从而可以像使用CreateProcess一样等待进程结束。
    • 可以指定更详细的环境变量。
    • 可以设置父窗口句柄,用于UI归属。
    • 可以接收更多错误和状态信息。

为什么选择ShellExecute/Ex?当你需要:1) 打开一个文档或URL,让系统用关联程序处理;2) 以管理员身份运行程序(通过lpVerb设置为"runas");3) 进行简单的“打开”操作,且不需要对子进程进行精细控制;4) 操作非可执行文件(如图片、PDF)。它们更智能,但控制粒度不如CreateProcess

选型决策矩阵

需求场景推荐API关键理由
运行一个命令行工具并捕获其输出CreateProcess必须使用STARTUPINFO重定向hStdOutputhStdError
打开用户选择的PDF文件ShellExecute/Ex系统会自动调用已安装的PDF阅读器(如Acrobat, Edge)。
实现一个安装包,需要提权运行ShellExecuteEx设置lpVerb"runas",可触发UAC管理员权限请求。
创建并完全控制一个后台服务进程CreateProcess可设置CREATE_NO_WINDOW,精细控制安全属性和环境。
简单的“运行记事本”演示ShellExecute代码最简洁,一目了然。
新项目,任何情况避免使用WinExec已弃用,功能有限,无未来保障。

3. 实战代码详解与避坑指南

理解了理论,我们来看代码。下面我将为每种方法提供一个健壮的、带错误处理的实现示例,并附上详细的注释和避坑说明。

3.1 使用CreateProcess启动并监控进程

这是最完整的示例,展示了如何启动一个进程,等待它结束,并获取退出码。

#include <windows.h> #include <iostream> #include <string> bool RunProcessWithCreateProcess(const std::wstring& appPath, const std::wstring& args) { // 准备命令行字符串。CreateProcess要求lpCommandLine是可写的,所以我们需要一个副本。 std::wstring cmdLine = L"\"" + appPath + L"\" " + args; // 给路径加引号,防止空格问题 std::vector<wchar_t> cmdLineVec(cmdLine.begin(), cmdLine.end()); cmdLineVec.push_back(L'\0'); // 确保以空字符结尾 // 初始化STARTUPINFO和PROCESS_INFORMATION结构体 STARTUPINFOW si = { sizeof(si) }; PROCESS_INFORMATION pi = { 0 }; // 可以在这里配置si,例如重定向标准输出 // si.dwFlags = STARTF_USESTDHANDLES; // si.hStdOutput = ...; // 指向一个可写的管道句柄 // 创建进程 BOOL success = CreateProcessW( NULL, // 应用程序名(不使用,包含在命令行中) cmdLineVec.data(), // 命令行(可修改的缓冲区) NULL, // 进程安全属性 NULL, // 线程安全属性 FALSE, // 句柄继承性 CREATE_NEW_CONSOLE, // 创建标志:为新进程创建新控制台 NULL, // 环境块(继承父进程) NULL, // 当前目录(继承父进程) &si, // STARTUPINFO &pi // 接收进程和线程信息 ); if (!success) { DWORD err = GetLastError(); std::wcerr << L"CreateProcess failed with error: " << err << std::endl; return false; } std::wcout << L"Process started. PID: " << pi.dwProcessId << std::endl; // **关键步骤:立即关闭不需要的句柄** // 我们拿到了进程和主线程的句柄。如果不使用线程句柄,必须关闭它,防止资源泄漏。 // 进程句柄我们稍后还要用,先不关。 CloseHandle(pi.hThread); // 等待进程结束(无限等待) std::wcout << L"Waiting for process to exit..." << std::endl; WaitForSingleObject(pi.hProcess, INFINITE); // 获取进程退出码 DWORD exitCode = 0; if (GetExitCodeProcess(pi.hProcess, &exitCode)) { std::wcout << L"Process exited with code: " << exitCode << std::endl; } // 关闭进程句柄 CloseHandle(pi.hProcess); return true; }

避坑指南与心得

  1. 命令行参数构造:这是最常见的坑。如果路径或参数包含空格,必须用双引号括起来。上面的代码展示了如何安全地构造命令行。直接拼接字符串如appPath + L" " + args在遇到空格路径时会失败。
  2. 句柄泄漏CreateProcess成功后会返回进程和线程句柄。你必须负责关闭它们!通常,主线程句柄(pi.hThread)在创建后立即关闭(除非你要操作该线程)。进程句柄(pi.hProcess)在你使用完毕后(如等待结束、获取退出码后)关闭。忘记关闭会导致句柄泄漏,在长时间运行的程序中耗尽系统资源。
  3. 等待与超时WaitForSingleObject(pi.hProcess, INFINITE)会阻塞当前线程直到子进程结束。在生产环境中,使用INFINITE(无限等待)可能不是好主意,可以考虑使用WaitForMultipleObjects配合超时,或者将等待放在单独的线程中。
  4. 错误码GetLastError()返回的是Win32错误码。失败时,使用FormatMessage函数可以将其转换为可读的错误信息,这对调试至关重要。

3.2 使用ShellExecuteEx以管理员身份运行并获取句柄

ShellExecute简单但功能有限,ShellExecuteEx更强大。下面演示如何用它请求管理员权限(触发UAC)并获取进程句柄以便等待。

#include <windows.h> #include <shellapi.h> // 必须包含此头文件 #include <iostream> bool RunAsAdminWithShellExecuteEx(const std::wstring& filePath) { SHELLEXECUTEINFOW sei = { sizeof(sei) }; sei.lpFile = filePath.c_str(); sei.lpVerb = L"runas"; // 关键:请求管理员权限 sei.nShow = SW_SHOWNORMAL; sei.fMask = SEE_MASK_NOCLOSEPROCESS; // 关键:告诉API我们想要进程句柄 if (!ShellExecuteExW(&sei)) { DWORD err = GetLastError(); // 错误码 1223 (ERROR_CANCELLED) 表示用户取消了UAC对话框 if (err != 1223) { std::wcerr << L"ShellExecuteEx failed with error: " << err << std::endl; } else { std::wcout << L"User cancelled the UAC prompt." << std::endl; } return false; } // 如果成功且我们请求了句柄,hProcess会被填充 if (sei.hProcess != NULL) { std::wcout << L"Process started with admin rights. Waiting..." << std::endl; WaitForSingleObject(sei.hProcess, INFINITE); CloseHandle(sei.hProcess); // 同样,记得关闭句柄! std::wcout << L"Admin process finished." << std::endl; } else { // 对于某些操作(如打开文档),系统可能不会返回进程句柄 std::wcout << L"Operation launched successfully (no process handle returned)." << std::endl; } return true; }

避坑指南与心得

  1. runas动词lpVerb设置为"runas"是触发UAC的标准方法。但请注意,用户完全可以点击“否”来取消。你的程序必须能优雅地处理这种取消(错误码1223),而不是将其视为致命错误。
  2. 句柄与掩码:默认情况下,ShellExecuteEx不会返回进程句柄。你必须显式地在fMask成员中设置SEE_MASK_NOCLOSEPROCESS标志。即使设置了,也不是所有操作都会返回句柄(例如打开一个.txt文件可能直接由已运行的记事本实例处理,不会创建新进程)。
  3. Shell API的异步性ShellExecuteEx是异步的,函数调用成功仅仅表示请求已被接受,不表示操作(如打开一个复杂文档)已经完成。获取进程句柄并等待,是确保“可执行程序”运行完毕的一种方式,但对于文档关联操作,可能没有合适的句柄可供等待。

3.3 使用ShellExecute进行最简单的“打开”操作

对于不需要等待、不需要提权、只是简单地打开文件或URL的场景,ShellExecute是最简洁的选择。

#include <windows.h> #include <shellapi.h> #include <iostream> bool OpenFileOrURLWithShellExecute(const std::wstring& target) { HINSTANCE hInst = ShellExecuteW( NULL, // 父窗口句柄 L"open", // 操作动词 target.c_str(), // 目标文件或URL NULL, // 参数(如果目标是exe) NULL, // 工作目录 SW_SHOWNORMAL // 显示命令 ); // ShellExecute成功时返回值大于32 auto result = reinterpret_cast<INT_PTR>(hInst); if (result <= 32) { // 常见错误码:2(文件未找到), 3(路径未找到), 5(拒绝访问), 31(没有关联程序) std::wcerr << L"ShellExecute failed with error code: " << result << std::endl; return false; } std::wcout << L"Successfully opened: " << target << std::endl; return true; } // 使用示例: // OpenFileOrURLWithShellExecute(L"notepad.exe"); // OpenFileOrURLWithShellExecute(L"C:\\Users\\test.docx"); // OpenFileOrURLWithShellExecute(L"https://www.bing.com");

心得ShellExecute的返回值处理是另一个小坑。它成功时返回一个实例句柄(值>32),失败时返回错误码(≤32)。不要用SUCCEEDED宏去判断,直接与32比较即可。它的代码非常干净,适合放在按钮响应事件里。

4. 高级应用场景与性能安全考量

掌握了基础用法后,我们来看看一些更复杂的实际场景和需要注意的深层次问题。

4.1 场景一:启动进程并实时捕获其控制台输出

这是自动化脚本工具中的常见需求。核心在于使用CreateProcess,并通过管道(Pipe)重定向子进程的标准输出和错误输出。

实现思路

  1. 创建两个匿名管道:一个用于子进程的stdout,一个用于stderr
  2. 配置STARTUPINFO结构,将hStdOutputhStdError设置为管道写端,并设置dwFlags = STARTF_USESTDHANDLES
  3. 调用CreateProcess,并确保bInheritHandlesTRUE,使得子进程能继承管道句柄。
  4. 在父进程中,关闭不必要的管道写端,然后从管道读端循环读取数据,直到子进程结束且管道无数据。
  5. 关键点:必须创建一个线程或使用异步I/O来同时读取stdoutstderr,防止其中一个管道缓冲区满导致死锁。

这个实现较为复杂,涉及管道、句柄继承和异步I/O。一个更现代、更简单的替代方案是使用_popen(MSVC)或定向到临时文件再读取。但对于需要高性能和实时交互的场景,管道方案是唯一选择。

4.2 场景二:进程的权限与UAC处理

在Vista及之后的Windows中,用户账户控制(UAC)改变了游戏规则。即使当前用户是管理员组成员,进程默认也运行在标准权限下。

  • 如果你的程序自身需要提权:在清单文件(.manifest)中设置requestedExecutionLevelrequireAdministrator
  • 如果你需要从标准权限进程中启动一个高权限进程:使用ShellExecuteEx并设置lpVerb"runas",如上文所示。
  • 使用CreateProcess提权:直接使用CreateProcess无法触发UAC提示。你需要配合ShellExecuteEx("runas")或者使用CreateProcessAsUser等更复杂的API,这通常需要你先获得一个提升的令牌(Token),过程非常繁琐。因此,ShellExecuteEx("runas")是标准做法。

4.3 安全与可靠性最佳实践

  1. 路径验证与引用:永远不要相信用户输入或拼接的路径。使用PathCchCanonicalize等函数规范化路径,并对路径进行引号转义,防止命令注入攻击。如果程序路径来自不可信源,应严格校验其是否在允许的目录内。
  2. 避免不可控的搜索路径:当CreateProcesslpApplicationNameNULL时,系统会按照一定顺序搜索可执行文件(当前目录、系统目录、PATH等)。这可能导致恶意程序被意外执行(经典的“当前目录优先”漏洞)。最佳实践是:总是使用绝对路径,或者至少使用SearchPathAPI来安全地查找可执行文件。
  3. 处理长路径:Windows API默认支持的最大路径长度是260字符(MAX_PATH)。要支持更长的路径(最多约32767字符),你需要使用Unicode版本的API(带W后缀),并在路径前添加\\\\?\\前缀(如\\\\?\\C:\\very\\long\\path...)。CreateProcessWShellExecuteExW都支持长路径。
  4. 环境变量考虑:通过CreateProcesslpEnvironment参数,你可以为子进程提供一个全新的环境块。如果你选择NULL(继承),请注意父进程的环境变量变化可能会影响到子进程。在服务或后台程序中,一个干净、确定性的环境往往更可靠。
  5. 错误处理:永远检查API的返回值。使用GetLastError()获取错误码,并用FormatMessage将其转换为可读信息记录到日志中。这对于调试部署在客户机器上的问题至关重要。

5. 完整源码示例与集成测试

下面提供一个整合了上述四种方法的控制台程序源码。你可以直接复制、编译并运行,通过输入数字选择不同的启动方式来观察效果。

// ProcessStarterDemo.cpp #define _CRT_SECURE_NO_WARNINGS #include <windows.h> #include <shellapi.h> #include <iostream> #include <string> #include <vector> // 1. 演示已弃用的WinExec (仅作对比,不推荐使用) void DemoWinExec() { std::cout << "\n=== 演示 WinExec ===" << std::endl; // 启动记事本 UINT result = WinExec("notepad.exe", SW_SHOW); if (result <= 31) { std::cout << "WinExec failed with error: " << result << std::endl; } else { std::cout << "WinExec succeeded (return value >31)." << std::endl; std::cout << "注意:WinExec已弃用,且无法控制进程或获取详细错误。" << std::endl; } } // 2. 使用CreateProcess启动进程并等待 bool DemoCreateProcess(const std::wstring& app, const std::wstring& args) { std::wcout << L"\n=== 演示 CreateProcess ===" << std::endl; std::wstring cmdLine = L"\"" + app + L"\" " + args; std::vector<wchar_t> cmdLineVec(cmdLine.begin(), cmdLine.end()); cmdLineVec.push_back(L'\0'); STARTUPINFOW si = { sizeof(si) }; PROCESS_INFORMATION pi = { 0 }; std::wcout << L"命令行: " << cmdLine << std::endl; if (!CreateProcessW( NULL, cmdLineVec.data(), NULL, NULL, FALSE, CREATE_NEW_CONSOLE, // 给子进程新控制台 NULL, NULL, &si, &pi)) { std::wcerr << L"CreateProcess 失败! 错误码: " << GetLastError() << std::endl; return false; } std::wcout << L"进程已启动,PID: " << pi.dwProcessId << std::endl; CloseHandle(pi.hThread); // 立即关闭不需要的线程句柄 std::wcout << L"等待进程退出..." << std::endl; WaitForSingleObject(pi.hProcess, INFINITE); DWORD exitCode = 0; GetExitCodeProcess(pi.hProcess, &exitCode); std::wcout << L"进程退出码: " << exitCode << std::endl; CloseHandle(pi.hProcess); return true; } // 3. 使用ShellExecute打开文件/URL void DemoShellExecute() { std::cout << "\n=== 演示 ShellExecute ===" << std::endl; // 示例1: 打开计算器 HINSTANCE h1 = ShellExecuteA(NULL, "open", "calc.exe", NULL, NULL, SW_SHOWNORMAL); std::cout << "打开 calc.exe 结果码: " << reinterpret_cast<INT_PTR>(h1) << std::endl; // 示例2: 用默认浏览器打开网页 HINSTANCE h2 = ShellExecuteA(NULL, "open", "https://www.bing.com", NULL, NULL, SW_SHOWNORMAL); std::cout << "打开网页结果码: " << reinterpret_cast<INT_PTR>(h2) << std::endl; } // 4. 使用ShellExecuteEx以管理员身份运行(会触发UAC) void DemoShellExecuteExRunAs() { std::cout << "\n=== 演示 ShellExecuteEx (runas) ===" << std::endl; std::cout << "将尝试以管理员身份启动记事本。请注意UAC提示!" << std::endl; std::cout << "(如果点击‘否’,程序会继续运行)" << std::endl; SHELLEXECUTEINFOA sei = { sizeof(sei) }; sei.lpFile = "notepad.exe"; sei.lpVerb = "runas"; sei.nShow = SW_SHOWNORMAL; sei.fMask = SEE_MASK_NOCLOSEPROCESS; if (!ShellExecuteExA(&sei)) { DWORD err = GetLastError(); if (err == ERROR_CANCELLED) { std::cout << "用户取消了UAC提示。" << std::endl; } else { std::cout << "ShellExecuteEx 失败,错误码: " << err << std::endl; } return; } if (sei.hProcess) { std::cout << "进程已启动,等待中..." << std::endl; WaitForSingleObject(sei.hProcess, INFINITE); CloseHandle(sei.hProcess); std::cout << "管理员进程结束。" << std::endl; } } int main() { std::cout << "Windows进程启动方式演示程序" << std::endl; std::cout << "==========================" << std::endl; int choice = 0; do { std::cout << "\n请选择要演示的功能:\n"; std::cout << "1. 演示 WinExec (不推荐)\n"; std::cout << "2. 演示 CreateProcess (启动记事本并等待)\n"; std::cout << "3. 演示 ShellExecute (打开计算器和网页)\n"; std::cout << "4. 演示 ShellExecuteEx (以管理员身份运行记事本)\n"; std::cout << "0. 退出\n"; std::cout << "您的选择: "; std::cin >> choice; switch (choice) { case 1: DemoWinExec(); break; case 2: // 启动记事本,并打开一个不存在的文件,记事本会弹出错误对话框,关闭后继续 DemoCreateProcess(L"C:\\Windows\\System32\\notepad.exe", L\"不存在的文件.txt\"); break; case 3: DemoShellExecute(); break; case 4: DemoShellExecuteExRunAs(); break; case 0: std::cout << "再见!" << std::endl; break; default: std::cout << "无效选择,请重试。" << std::endl; } // 清空输入缓冲区,防止下次循环出错 std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); } while (choice != 0); return 0; }

编译与运行说明

  1. 使用Visual Studio或任何支持C++11及以上的编译器(如MSVC, MinGW)编译此代码。
  2. 需要链接Shell32.libAdvapi32.lib(通常编译器会自动链接)。
  3. 运行程序,按照菜单提示选择不同的功能进行测试。
  4. 在选择第4项(ShellExecuteEx runas)时,如果你的系统UAC是开启的,会弹出用户账户控制对话框,请选择“是”或“否”观察程序的不同反应。

这个演示程序将四种方法的调用、错误处理、资源管理(句柄关闭)和典型使用场景集中展示,是理解和测试这些API的绝佳起点。你可以修改其中的路径和参数,尝试启动不同的程序,观察其行为差异。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/31 6:13:46

AI生成简历工具功能对比:百度网盘、超级简历、AI简历姬三款实测

基于各平台公开功能梳理&#xff0c;帮助了解不同工具在简历生成及相关环节上的能力侧重。 一、关于AI简历工具的基本认知 在讨论具体工具之前&#xff0c;有必要先厘清一个根本问题&#xff1a;AI生成的简历&#xff0c;能否直接用于投递&#xff1f; 当前阶段的客观答案是&am…

作者头像 李华
网站建设 2026/7/31 6:13:24

C++函数重载:从原理到实战,掌握编译时多态的核心机制

1. 项目概述&#xff1a;为什么我们需要函数重载&#xff1f;在C的世界里&#xff0c;函数重载&#xff08;Function Overloading&#xff09;绝对是一个你绕不开的核心特性。它不是什么高深莫测的黑魔法&#xff0c;而是一个让代码变得更清晰、更直观、更“像人话”的实用工具…

作者头像 李华
网站建设 2026/7/31 6:12:10

Julia语言安装与卸载全攻略:从环境配置到彻底清理

1. 项目概述&#xff1a;为什么选择Julia&#xff0c;以及如何正确管理它 最近几年&#xff0c;在数据科学和高性能计算圈子里&#xff0c;Julia语言的热度一直居高不下。它被设计成要兼具Python的易用性、C的速度以及R的统计能力&#xff0c;口号“像Python一样写&#xff0c…

作者头像 李华
网站建设 2026/7/31 6:11:53

Windows风扇控制终极指南:打造完美静音散热系统

Windows风扇控制终极指南&#xff1a;打造完美静音散热系统 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/GitHub_Trending/fa/FanCo…

作者头像 李华
网站建设 2026/7/31 6:09:04

C++11类型转换:从C风格强制转换到四种安全操作符的演进

1. 项目概述&#xff1a;为什么C11要重塑类型转换&#xff1f;如果你写过一段时间的C&#xff0c;尤其是维护过一些老旧的代码库&#xff0c;那么对(int)ptr、(MyClass*)voidPtr这类C风格的类型转换一定不会陌生。它们简单、直接&#xff0c;但也像一把没有刀鞘的利刃&#xff…

作者头像 李华