关于MFC中使用ShellExecute出现的进程冲突问题

2023-11-17

目录

问题背景

问题分析


问题背景

       现在有一个MFC写的界面程序,以及一个外部exe文件。用户通过界面选择文件a,MFC将文件a的路径作为参数,调用exe文件生成一个解析文件b,然后MFC再读取这个文件b。

       为了完成这一目的,就需要在MFC中调用外部exe文件,我这里选用的是ShellExecute函数。

//function
...
HINSTANCE proc = ShellExecute(this->m_hWnd, L"open", L"pdf2txt.exe", FilePath, L".\\", SW_HIDE);  //异步调用
//伪代码
  if(!open(b))
  {
     Warn("Open failed !") and return;
  }
  read(b);
...

       看似逻辑正确,但是实际运行时会出现这样的情况:在第一次执行function时会出现文件b打开失败的情况,但是接着执行第二次function又变成正常的了,这是什么原因呢?

问题分析

       按道理来说,第一次执行时先调用外部exe文件就会生成文件b,那么接着再打开文件b应该是可以打开的,而这里打开文件b失败,由于文件b是由无到有,因此最有可能的就是在打开b文件的时候b文件不存在导致的。

       为什么b文件会不存在呢?一种可能的原因是ShellExecute函数调用失败,但是通过测试发现,第一次function执行结束后,b文件是存在的,也就是说,ShellExecute是的执行是成功的;那么,为什么b文件明明能够生成,但是open时不存在呢?最大的可能就是b文件是在open之后生成的,也就是说,函数实际运行的顺序并没有按照逻辑来

       换句话说,ShellExecute开辟一个进程去执行exe文件,当前进程继续按照当前程序执行,两个进程的执行顺序是不定的,这就有可能出现当前进程比运行exe的进程更快地就执行到open函数了,此时open函数时发现b文件不存在,然后较慢的进程才执行exe文件生成了b文件。

       引起这个问题的原因,当然就要考虑ShellExecute函数本身了。查阅相关资料发现,ShellExecute实际上是一个异步函数,它并不会等到exe执行结束后才返回,而是立即返回,很显然,程序的逻辑是希望等待exe执行结束后ShellExecute再返回的,也就是同步执行,为了解决这个问题,就可以考虑使用WaitForSingleObject来进行阻塞,如下所示。

//function
...

HINSTANCE proc = ShellExecute(this->m_hWnd, L"open", L"pdf2txt.exe", FilePath, L".\\", SW_HIDE);  //异步调用
WaitForSingleObject(proc, INFINITE);  //等待结束

//伪代码
  if(!open(b))
  {
     Warn("Open failed !") and return;
  }
  read(b);
...

        这样程序是不是就正常了呢?再来测试一下:

        问题依旧!这又是什么原因呢?

       实际上,这是因为 ShellExecute返回的是一个HINSTANCE句柄,而WaitForSingleObject所需要的是一个HANDLE句柄,二者是虽然类似,但是也是有差别的,HINSTANCE句柄指向一个模块的地址,而HANDLE句柄指向一个具体的进程、线程等资源,因此,这里不应该直接通过HINSTANCE来调用WaitForSingleObject。

       可是ShellExecute只返回一个HINSTANCE,这该怎么办呢?可能也有一些其他的办法通过HINSTANCE来获得HANDLE,但是应该会很麻烦,这里一个比较好的办法就是调用更强大的ShellExecuteEx函数。其定义如下:

BOOL ShellExecuteEx(
  _Inout_ SHELLEXECUTEINFO *pExecInfo
);

       ShellExecuteEx函数只有一个SHELLEXECUTEINFO类型的结构体参数,而SHELLEXECUTEINFO结构体内的成员则非常丰富了:

typedef struct _SHELLEXECUTEINFO {
  DWORD     cbSize;//结构大小,sizeof(SHELLEXECUTEINFO)
  ULONG     fMask;//掩码,指定结构成员的有效性
  HWND      hwnd;//父窗口句柄或出错时显示错误父窗口的句柄,可以为 NULL
  LPCTSTR   lpVerb;//指定该函数的执行动作
  LPCTSTR   lpFile;//操作对象路径
  LPCTSTR   lpParameters;//执行参数,可以为 ULL
  LPCTSTR   lpDirectory;//工作目录,可以为 NULL
  int       nShow;//显示方式
  HINSTANCE hInstApp;//如果设置了 SEE_MASK_NOCLOSEPROCESS ,并且调用成功则该值大于32,调用失败者被设置错误值
  LPVOID    lpIDList;//ITEMIDLIST结构的地址,存储成员的特别标识符,当fMask不包括SEE_MASK_IDLIST或SEE_MASK_INVOKEIDLIST时该项被忽略
  LPCTSTR   lpClass;//指明文件类别的名字或GUID,当fMask不包括SEE_MASK_CLASSNAME时该项被忽略
  HKEY      hkeyClass;//获得已在系统注册的文件类型的Handle,当fMask不包括SEE_MASK_HOTKEY时该项被忽略
  DWORD     dwHotKey;//程序的热键关联,低位存储虚拟关键码(Key Code),高位存储修改标志位(HOTKEYF_),当fmask不包括SEE_MASK_HOTKEY时该项被忽略
  union {
    HANDLE hIcon;//取得对应文件类型的图标的Handle,当fMask不包括SEE_MASK_ICON时该项被忽略
    HANDLE hMonitor;//将文档显示在显示器上的Handle,当fMask不包括SEE_MASK_HMONITOR时该项被忽略
  } DUMMYUNIONNAME;
  HANDLE    hProcess;//指向新启动的程序的句柄。若fMask不设为SEE_MASK_NOCLOSEPROCESS则该项值为NULL。
                     //但若程序没有启动,即使fMask设为SEE_MASK_NOCLOSEPROCESS,该值也仍为NULL。
                     //如果没有新创建进程,也会为空
} SHELLEXECUTEINFO, *LPSHELLEXECUTEINFO;

fMask定义

fMask 用于指定结构成员的内容和有效性,可为下列值的组合:

SEE_MASK_DEFAULT (0)默认
SEE_MASK_CLASSNAME 使用 lpClass 参数,如果 SEE_MASK_CLASSKEY 也有效,则用后者
SEE_MASK_CLASSKEY 使用 hkeyClass 参数
SEE_MASK_IDLIST 使用 lpIDList 参数
SEE_MASK_INVOKEIDLIST 使用选定项目的快捷菜单 IContextMenu 接口处理程序
SEE_MASK_ICON 使用 hIcon 给出的菜单,不能与 SEE_MASK_HMONITOR 共用,Vista之后
SEE_MASK_HOTKEY 使用 dwHotKey 参数
SEE_MASK_NOCLOSEPROCESS 如果执行之后需要返回进程句柄,或者等待执行完毕的话,则需要指定该参数,从结构参数意义可以看到 hProcess 和 hInstApp 都依赖该选项
SEE_MASK_CONNECTNETDRV 验证共享并连接到驱动器号
SEE_MASK_NOASYNC 不等待操作完成,直接返回,会创建一个后台线程运行。
SEE_MASK_FLAG_DDEWAIT 弃用,使用 SEE_MASK_NOASYNC
SEE_MASK_DOENVSUBST 环境变量会被展开
SEE_MASK_FLAG_NO_UI 出现错误,不显示错误消息框,比如不会弹出找不到文件之类的窗口,直接返回失败
SEE_MASK_UNICODE UNICODE 程序
SEE_MASK_NO_CONSOLE 继承父进程的控制台,而不是创建新的控制台,与 CREATE_NEW_CONSOLE 相反
SEE_MASK_ASYNCOK 执行在后台线程,调用立即返回
SEE_MASK_NOQUERYCLASSSTORE 弃用
SEE_MASK_HMONITOR 使用 hmonitor,不能与 SEE_MASK_ICON 共存
SEE_MASK_NOZONECHECKS 不执行区域检查
SEE_MASK_WAITFORINPUTIDLE 创建新进程后,等待进程变为空闲状态再返回,超时时间为1分钟
SEE_MASK_FLAG_LOG_USAGE 跟踪应用程序启动次数
SEE_MASK_FLAG_HINST_IS_SITE

lpVerb参数定义如下: 

lpVerb 参数与 ShellExecute 的 lpOperation 参数一致:

edit 用编辑器打开 lpFile 指定的文档,如果 lpFile 不是文档,则会失败
explore 浏览 lpFile 指定的文件夹
find 搜索 lpDirectory 指定的目录
open 打开 lpFile 文件,lpFile 可以是文件或文件夹
print 打印 lpFile,如果 lpFile 不是文档,则函数失败
properties 显示属性
runas 请求以管理员权限运行,比如以管理员权限运行某个exe
NULL 执行默认”open”动作

如果设置了 SEE_MASK_NOCLOSEPROCESS ,调用成功则 hInstApp 返回大于32的值,调用失败会返回: 

SE_ERR_FNF (2) 文件未找到
SE_ERR_PNF (3) 路径未找到
SE_ERR_ACCESSDENIED (5) 拒绝访问
SE_ERR_OOM (8) 内存不足
SE_ERR_DLLNOTFOUND (32) 动态库未找到
SE_ERR_SHARE (26) 无法共享打开的文件
SE_ERR_ASSOCINCOMPLETE (27) 文件关联信息不完整
SE_ERR_DDETIMEOUT (28) 操作超时
SE_ERR_DDEFAIL (29) 操作失败
SE_ERR_DDEBUSY (30) DDE 操作忙
SE_ERR_NOASSOC (31) 文件关联不可用

返回值: 

函数执行成功,返回 TRUE ,否则返回 FALSE ,可使用 GetLastError 获取错误码。

ERROR_FILE_NOT_FOUND 文件不存在
ERROR_PATH_NOT_FOUND 路径不存在
ERROR_DDE_FAIL DDE(动态数据交换)失败
ERROR_NO_ASSOCIATION 未找到与指定文件拓展名关联的应用
ERROR_ACCESS_DENIED 拒绝访问
ERROR_DLL_NOT_FOUND 未找到dll
ERROR_CANCELLED 功能提示用户提供额外信息,但是用户取消请求。
ERROR_NOT_ENOUGH_MEMORY 内存不足
ERROR_SHARING_VIOLATION 发生共享冲突

        可以看到,SHELLEXECUTEINFO结构体里面是包含有HANDLE成员的,也就是说ShellExecuteEx函数可以获得执行的exe进程的HANDLE,那么就可以根据这个来等待并关闭进程,如下所示:

SHELLEXECUTEINFO exeInfo;
	memset(&exeInfo, 0, sizeof(SHELLEXECUTEINFO));

	exeInfo.hwnd = this->m_hWnd;     //设置当前窗口为调用窗口
	exeInfo.cbSize = sizeof(SHELLEXECUTEINFO);    //设置大小
	exeInfo.fMask = SEE_MASK_NOCLOSEPROCESS;    //掩码设置为可以返回HANDLE
	exeInfo.lpVerb = L"open";     //打开操作
	exeInfo.lpFile = L"pdf2txt.exe";    //执行的程序名
	exeInfo.nShow = SW_HIDE;    //后台执行
	exeInfo.lpParameters = FilePath;    //执行的路径
	ShellExecuteEx(&exeInfo);   //调用ShellExecuteEx

	WaitForSingleObject(exeInfo.hProcess, INFINITE);    //阻塞等待进程执行结束
	CloseHandle(exeInfo.hProcess);    //释放HANDLE

         此时运行程序后可以感觉到阻塞,结果也显示正确:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

关于MFC中使用ShellExecute出现的进程冲突问题 的相关文章

  • 【C/C++】报错问题积累

    1 出现Deprecated declaration XXX give arg types c文件中 有没有参数的函数时 声明需要加void即 main c void fun main h void fun void
  • C/C++语言实现WiFi(socket)数据收发(客户端和服务端)

    目录 客户端 client 服务端 server C C 实现TCP通信 接收WIFI数据 编程环境 VC 6 0 手机端 使用WiFi调试助手 提示 整个过程在局域网中进行 很多编程语言都可以实现socket通信 本博客将通过C C 实现
  • [原]Pro*C介绍-内嵌SQL

    Translate by Z Jingwei Document address http www db stanford edu ullman fcdb oracle or proc html Pro C介绍内嵌SQL 概要 Pro C语法
  • SQL 查询指定行数的数据。

    今天遇到一个关于 查询指定行数的数据 的sql查询语句问题 突然发现以前没怎么接触过 刚才想起来了 赶紧看了下文档 又上网搜了下 有了下面的东西 不知道有没有什么地方不对 oracle 先看一下文档中关于any和all的例子 很不错噢 An
  • C++ 中的虚函数及虚函数表

    C 中的虚函数及虚函数表 一 虚函数及虚函数表的定义 二 虚函数表指针和虚函数表的创建时机 三 虚函数实现多态的原理 一 虚函数及虚函数表的定义 虚函数 虚函数就是在基类中定义一个未实现的函数名 使用虚函数的核心目的就是通过基类访问派生类定
  • Qt5学习之路(vs2012下创建一个QT应用程序)2013-10-14

    刚开始学习QT在网上找的资料基本都是使用QT Create进行开发的 VS下开发的学习资料感觉很少很难找的到 视频教程也基本没看到过貌似 因为我们研发中心是使用MFC进行开发开发工具是VS2010 使用QT开发的话基本我们不会再使用QT C
  • 简析多级指针解引用

    转自 简析多级指针解引用 指针是C语言中公认的最为强大的语法要素 但同时也是最难理解的语法要素 它曾给程序员带来了无数麻烦和痛苦 以致于在C语言之后诞生的很多新兴 语言中我们再也难觅指针的身影了 下面是一个最简单的C语言指针的例子 int
  • 使用QZXing生成并解析二维码

    QZxing 是对 zxing 的一个封装 用于在 Qt 程序中加入条形码和二维码识别的功能 这里就讲讲如何编译和使用这个库 前几年 QZXing 的代码是放到 sourceforge net 上的 现在迁移到了 github com 所以
  • GDAL多光谱与全色图像融合简单使用

    目录 简述 C 代码 效果对比 GDAL融合效果和原始多光谱波段对比 GDAL融合效果和原始全色波段对比 ARCGIS融合效果与原始全色和多光谱对比 GDAL融合效果与ArcGIS融合效果对比 简述 最近在GDAL的代码中看见了gdalpa
  • LeetCode题目笔记——17.19消失的两个数字

    文章目录 题目描述 题目难度 困难 方法一 暴力 代码 代码优化 方法二 数学方法 代码 总结 题目描述 题目直达 题目难度 困难 方法一 暴力 虽然题目说你能在 O N 时间内只用 O 1 的空间找到它们吗 但是也没有限制我们不能用暴力
  • R----dplyr包介绍学习

    dplyr包 plyr包的替代者 专门面对数据框 将ddplyr转变为更易用的接口 gt 来自dplyr包的管道函数 其作用是将前一步的结果直接传参给下一步的函数 从而省略了中间的赋值步骤 可以大量减少内存中的对象 节省内存 可惜的是应用范
  • mfc窗口创建的create与oncreate

    在view类中 create 是虚函数由框架调用 是用来 生成一个窗口的子窗口 oncreate 消息响应函数 是用来 表示一个窗口正在生成 某个CWnd的Create函数由当前CWnd的Owner调用 而在CWnd Create中 又会调
  • Open3D(C++)实现建筑物点云立面和平面分割提取

    Open3D C 实现建筑物点云立面和平面分割提取 近年来 点云技术在城市规划 机器人地图构建等领域得到广泛应用 本篇文章将介绍如何利用Open3D C 库实现建筑物点云立面和平面分割提取 准备工作 首先需要编译安装Open3D库 本文使用
  • 检查内存泄露

    自己编写的视频处理程序出现了一个问题 每帧的运行时间随着运行时间在不断增长 很大可能是出现了内存泄露 于是学习了一些查看内存泄露的方法 做了两种尝试 一是VS自带的DEBUG下的检测 view pl html view plain copy
  • 虚函数不能声明为static

    虚函数申明为static报错 class Foo public Foo default static virtual Foo int main Foo foo return 0 main cpp 10 25 error member Foo
  • enable_shared_from_this使用介绍

    文章目录 enable shared from this定义 使用场合 源码实现 注意 enable shared from this定义 定义于头文件 template lt class T gt class enable shared
  • Java反序列化漏洞-CC1利用链分析

    文章目录 一 前置知识 1 反射 2 Commons Collections是什么 3 环境准备 二 分析利用链 1 Transform
  • C/C++编程中的算法实现技巧与案例分析

    C C 编程语言因其高效 灵活和底层的特性 被广大开发者用于实现各种复杂算法 本文将通过10个具体的算法案例 详细探讨C C 在算法实现中的技巧和应用 一 冒泡排序 Bubble Sort 冒泡排序 Bubble Sort 是一种简单的排序
  • C 语言运算符详解

    C 语言中的运算符 运算符用于对变量和值进行操作 在下面的示例中 我们使用 运算符将两个值相加 int myNum 100 50 虽然 运算符通常用于将两个值相加 就像上面的示例一样 它还可以用于将变量和值相加 或者将变量和另一个变量相加
  • C 语言文件读取全指南:打开、读取、逐行输出

    C 语言中的文件读取 要从文件读取 可以使用 r 模式 FILE fptr 以读取模式打开文件 fptr fopen filename txt r 这将使 filename txt 打开以进行读取 在 C 中读取文件需要一点工作 坚持住 我

随机推荐