今天跟大家唠唠我这几天折腾的`ShellExecuteEx`,一开始我看到这玩意儿也是一脸懵,不知道是干啥的,但是没办法,工作需要,硬着头皮上。
起因
起因是这样的,有个需求,需要在我们的程序里启动另一个程序,而且还要带参数,还得知道启动结果。最开始我想着直接`system()`函数一把梭,简单粗暴,但是后来发现不行,`system()`没法获取详细的启动信息,而且还阻塞主线程,体验太差。
探索
然后我就开始各种查资料,Google、Stack Overflow,各种搜,终于发现`ShellExecuteEx`这个东西。一看介绍,这玩意儿能满足我的需求,可以启动程序,可以传递参数,还能获取启动结果,简直是量身定做!
- 我得包含头文件:
#include <shellapi.h>
- 然后,定义一个
SHELLEXECUTEINFO
结构体变量,这个结构体里包含所有需要的信息,比如要启动的程序路径,参数,工作目录等等。 - 就要给这个结构体赋值,这里面坑还是挺多的。
cbSize
:这个必须设置成sizeof(SHELLEXECUTEINFO)
,不然会出错。fMask
:这个很重要,决定你要获取哪些信息,我这里需要获取进程句柄,所以要设置SEE_MASK_NOCLOSEPROCESS
。hwnd
:这个是父窗口句柄,可以设置为NULL
。lpVerb
:这个是操作类型,比如"open"
、"print"
等等,我这里要启动程序,所以设置为NULL
。lpFile
:这个是要启动的程序路径。lpParameters
:这个是传递给程序的参数。lpDirectory
:这个是工作目录,可以设置为NULL
。nShow
:这个是窗口显示方式,比如SW_SHOWNORMAL
、SW_HIDE
等等,我这里要正常显示,所以设置为SW_SHOWNORMAL
。
- 调用
ShellExecuteEx(&sei)
启动程序。
踩坑
看似简单,但是实际操作起来,各种问题。
- COM 初始化:一开始我直接调用
ShellExecuteEx
,结果程序直接崩溃。后来查资料才知道,在使用ShellExecuteEx
之前,需要先初始化COM库,调用CoInitialize(NULL)
。 - 路径问题:程序路径一定要写对,最好用绝对路径,不然可能会找不到程序。
- 参数问题:参数如果包含空格,需要用双引号括起来,不然可能会被分割成多个参数。
- 权限问题:如果启动的程序需要管理员权限,需要设置
* = "runas"
。
代码
cpp
#include
#include
#include
int main() {
SHELLEXECUTEINFO sei = {0};
* = sizeof(SHELLEXECUTEINFO);
* = SEE_MASK_NOCLOSEPROCESS;
* = NULL;
* = NULL; // 或者 "runas"
* = "C:\\Windows\\System32\\*"; // 绝对路径
* = "*"; // 参数
* = NULL;
* = SW_SHOWNORMAL;
CoInitialize(NULL); // 初始化COM
if (ShellExecuteEx(&sei)) {
std::cout << "程序启动成功!" << std::endl;
WaitForSingleObject(*, INFINITE); // 等待程序结束
DWORD exitCode;
GetExitCodeProcess(*, &exitCode);
std::cout << "程序退出码:" << exitCode << std::endl;
CloseHandle(*); // 关闭句柄
} else {
std::cerr << "程序启动失败!错误码:" << GetLastError() << std::endl;
CoUninitialize(); // 卸载COM
return 0;
总结
经过这几天的折腾,总算是把`ShellExecuteEx`搞明白。虽然过程有点痛苦,但是也学到很多东西。以后再遇到类似的需求,就不用抓瞎。`ShellExecuteEx`还是挺强大的,可以满足很多复杂的启动程序的需求。但是要注意一些细节问题,不然很容易出错。