解决系统下以管理员身份运行的程序接收不到拖放文件消息[WM_DROPFILES]问题的方法
大多数程序都有接收拖放文件的功能,即是用鼠标把文件拖放到程序窗口上方,符合格式的文件就会自动被程序打开。最近自己对编写的程序增加了一个拖放文件的功能,在 Windows XP、Windows Server 2003 系统上拖放文件功能正常,而在 Windows 7 系统上拖放文件功能不管用,毫无反应。经过一番探讨,顺利解决,故对相关知识的吸收与实践整合于此。
OK,使用 Visual Studio 新建一个简单的对话框程序,将【对话框】-【属性】-【行为】-【Accept Files】置为【True】后,再使用菜单中【项目】-【类向导】添加对于拖放文件消息【WM_DROPFILES】的消息映射处理方法,代码如下所示:
1 | [WM_DROPFILES]消息处理方法代码: |
生成可执行程序,并直接运行程序后,并拖放文件于对话框上方,完全没问题,程序接收到了【WM_DROPFILES】消息,如下图所示:
然而,找到生成的可执行程序,【右键】-【以管理员身份运行】后,你再拖放文件于上图所示对话框上,则不会弹出上图所示【WM_DROPFILES】消息框,即测试对话框窗口没有接收到【WM_DROPFILES】消息,上方的消息处理方法代码就不会调用了,如下图所示:
因此,问题的关键在于你有没有【以管理员身份运行】程序;如果【以管理员身份运行】程序,则程序窗口接收不到【WM_DROPFILES】消息;反之,直接运行程序,则可以接收之。这是为何呢?这与 Windows 7 的安全权限机制体系有关,本文下半部分再讲,先介绍:【解决Win7系统下以管理员身份运行的程序接收不到拖放文件消息[WM_DROPFILES]问题的方法】。
如本例简单的对话框程序,在 CExampleDlg::OnInitDialog() 方法中添加如下代码:
1 | //添加如下高亮处代码: |
另外提下,如果你自己创建窗口而不是创建对话框,则无法像对话框一样可以在【属性】-【行为】-【Accept Files】置为【True】以表示你的程序是否对消息【WM_DROPFILES】有兴趣处理,当然能不能接收得到,是另一外码事,但这是前提。因此可在你的窗口初始化函数(如CFuckWnd::OnCreate)或其它适合的位置调用API接口:
1 | //Registers whether a window accepts dropped files. |
ChangeWindowMessageFilter 与 ChangeWindowMessageFilterEx 这两个 API 为 Win7 系统新增的API接口,而 WinXP 系统没有这两个接口。查看MSDN可知,ChangeWindowMessageFilter 作用:从 UIPI【用户界面特权隔离】消息过滤器中增加或移除一个消息。ChangeWindowMessageFilterEx 作用:为特定窗口修改 UIPI【用户界面特权隔离】消息过滤器。关于【用户界面特权隔离】,本文下部分再讲。
简而言之,上述代码即是让系统对该测试程序不再过滤掉【WM_DROPFILES】消息和【WM_COPYGLOBALDATA】消息(此消息 Windows 未公开),那么该测试程序进程或对话框窗口能接收到【WM_DROPFILES】消息。
再次生成可执行程序,然后【以管理员身份运行】,程序窗口就可以接收到拖放文件消息【WM_DROPFILES】了。
另外,如果以 Release 方式生成可执行程序后,在 WinXP、Win2003 系统下运行,则会出现如下图报错提示:
原因是 WinXP 及 Win2003 系统用户库 USER32.dll 没有 ChangeWindowMessageFilter、ChangeWindowMessageFilterEx 这两个接口,而 Windows 7 系统下,则新增之。由于 WinXP、Win2003 系统安全机制不会拦截消息,因此也就没有必要运行上述新增的代码了。但若想该测试程序在 WinXP、Win2003 系统下运行正常,且要在 Win7 系统下能正常接收上述消息,可直接从 Win7 之 USER32.dll 库中获取上述 API 函数入口地址,在 Win7 系统下肯定是可以获取到入口地址的;而在 XP 系统下就肯定获取不到其入口地址,但没关系,不会影响程序运行,亦不会报错。代码如下:
1 | //在示例 CExampleDlg 类中添加如下方法代码: |
更改为如下高亮处代码行:
1 | BOOL CExampleDlg::OnInitDialog() |
接下来生成可执行程序,在任何系统环境下,均能运行正常,且能接收到【WM_DROPFILES】消息了。
Windows 7: Message Integrity Check(消息完整性检查)
消息完整性检查(Message Integrity Check),是 Windows 7 增加的 Windows 安全对象访问控制安全机制,系统利用完整性级别对一个安全对象进行标记,通过降低进程的完整性级别可以限制其对安全对象的写入权限,这一点类似于用户帐户组的成员被限制访问系统组件这种方式。完整性检查机制使得用更少的权限或以更低的完整性级别运行一些程序,会降低进程修改系统或损害用户数据文件的可能性。在 Windows 7 中消息完整性检查分为 6 个等级,如表 1 所示:
表1 MIC等级
MIC等级 说明
SECURITY_MANDATORY_UNTRUSTED_RID 不信任的MIC等级
SECURITY_MANDATORY_LOW_RID 低MIC等级,如IE
SECURITY_MANDATORY_MEDIUM_RID 中MIC等级,默认为这个等级,如Explorer
SECURITY_MANDATORY_HIGH_RID 高MIC等级,以管理员身份运行的程序
SECURITY_MANDATORY_SYSTEM_RID 系统MIC等级,一般是服务应用程序
SECURITY_MANDATORY_PROTECTED_PROCESS_RID 被保护进程的MIC等级
MIC 等级的获取是和创建该对象的进程完整性级别相关的。比如在 Windows 7 系统中 Explorer.exe 的完整性级别是中 MIC 等级,因为权限继承的关系,其启动的进程具有的 MIC 等级也都默认是中 MIC 等级。一个安全对象的 MIC 等级由其访问控制列表项中的某一项标识,可以使用工具 Process Explorer 查看一个进程的 MIC 等级。
Windows 7 系统正常启动后,Explorer.exe 进程的 MIC 等级默认为:中 MIC 等级,如下图所示:
Windows 7: User Interface Privilege Isolation (用户界面特权隔离)
用户界面特权隔离(User Interface Privilege Isolation),则是 Windows 7 通过 MIC 机制新引入的一种安全特性,用于拦截接收比自身进程 MIC 等级低的进程发来的消息。UIPI 的目的是为了规范不同进程窗口之间的窗口消息处理过程,默认情况下,高权限进程不会接收到低权限进程发送的窗口消息的,但是低权限进程能够接收到高权限进程的窗口消息。UIPI 的本质是系统检查目标窗口和发送方是否具有相同的 MIC 等级或者发送方具有更高的 MIC 等级,如果符合上述条件,则允许消息的传递,否则将消息丢弃。
因此,在 Windows 7 操作系统中运行的用户进程,如果运行时具有不同的完整性等级,即具有不同的 MIC 等级,那么相互间的通信将会无法像 Windows XP 那样正常进行。
Windows 7: 高低权限进程通信的实现
通过以上分析可以看出,Windows 7 为了保证系统的安全性,严格地限制了高低权限进程间的通信。以下就进程是否属于同一个会话给出 Windows 7 高低权限进程通信的方案。
基于 Windows XP 应用程序经常需要互相之间能传递消息,Windows Vista 引入了 Change WindowMessageFilter(UINT message, UINT dwFlag)编程接口,用来添加或删除隔离级别的消息。Windows 7 引入一个新的编程接口 ChangeWindowMessageFilterEx(HWND hWnd, UINT message, DWORD action,PCHANGEFILTERSTRUCT pChangeFilterStruct),事件的参数可以是 MSGFLT_ALLOW、MSGFLT_DISALLOW 和 MSGFLT_RESET,将窗口重设成默认筛选器,可选的架构允许操作结果以接收更多的信息。通过在高权限进程中对指定的某一个低权限进程所创建的窗口进行这样的设置,则低权限进程就可以通过发消息的形式与高权限进程通信了。以下是在 Windows 7 下两个进程通过窗口消息通信的测试结果:
表2 通过窗口消息完成进程通信的测试结果
发送方进程权限 接收方进程权限 进程通信结果
低 低 成功
中 中 成功
高 高 成功
低 中
高 失败
中 高 失败
中 低 成功
高 中
低 成功
低 中/高+ChangeWindowMessageFilterEx 自定义消息成功,系统消息失败
中 高+ChangeWindowMessageFilterEx 自定义消息成功,系统消息失败
问世间,explorer.exe 进程为何物?
explorer.exe进程是微软为其Windows操作系统定义的的系统核心进程,它是较老的Windows 3.x文件管理器的替代品,在后来的系统中微软将其称为“Windows 资源管理器”。在功能上,explorer.exe进程为用户提供了图形用户界面(也称为图形壳),简单的说就是用来显示系统的桌面环境的,包括开始菜单、桌面下方的任务栏、桌面图标和文件管理。
【文件管理】为explorer.exe进程众多功能之一,平常我们双击桌面上的程序快捷方式或从资源管理器里双击某个文件以打开之,一般explorer.exe进程便会创建相应的程序进程,也就是说explorer.exe进程是你手动打开的程序进程的父进程。
OK,回归上述实例,再加阐释:
到此时,我们已了解到 MIC【消息完整性检查】、UIPI【用户界面特权隔离】、【Windows 7 高低权限进程通信的实现】等观念。且我们已知晓 explorer.exe 进程为何物,在本文实例中,那么我们从【桌面】或从【资源管理器】里用鼠标拖放文件到程序窗口上,其实就是【explorer.exe】和【测试[WM_DROPFILES]消息.exe】两个进程之间的通信,亦即【explorer.exe】向本例【测试[WM_DROPFILES]消息.exe】发送【WM_DROPFILES】消息。在 XP 系统下,两者无任何代沟,能顺利收发消息;而在 Win7 系统下,彼此能够收发消息,则没那么容易了,导致收发消息不畅的“第三者”便是MIC和UIPI。
情况一、explorer.exe进程MIC等级为:中MIC等级,实例程序进程MIC等级为:中MIC等级,两者相同
在Win7系统下,找到本实例生成的可执行程序,直接双击运行:测试[WM_DROPFILES]消息.exe,用【Process Explorer】查看此进程的MIC等级:中MIC等级,和Win7系统正常启动后的Explorer.exe进程的MIC等级相同,则能成功从Explorer.exe进程管理的桌面或资源管理器中拖放文件到本实例程序窗口上,亦即本实例程序能正常接收到【WM_DROPFILES】消息并能处理之了。
情况二、explorer.exe进程MIC等级为:中MIC等级,实例程序进程MIC等级为:高MIC等级
在Win7系统下,找到本实例生成的可执行程序,右键【以管理员身份运行】,查看MIC等级为:高MIC等级,然后从资源管理器中拖放一个文件到本实例程序窗口,由于本实例程序进程MIC等级高于explorer.exe进程MIC等级,因此explorer.exe程序发送到本实例程序的消息,会被UIPI拦截掉,导致本实例程序无法接收之。
此种情况,要想接收到低MIC等级进程发来的拖放文件消息,则可以通过 ChangeWindowMessageFilter 在 UIPI【用户界面特权隔离】消息过滤器中添加【WM_DROPFILES】【WM_COPYGLOBALDATA】这两个消息,注意其消息过滤器相当于白名单,添加到过滤器,那么发送给对方进程的消息不会被UIPI机制拦截掉,会被放行,从而名正言顺地到达对方进程,而被接收处理之。具体代码在本文上半部分已讲述。
情况三、explorer.exe进程MIC等级为:高MIC等级,实例程序进程MIC等级为:高MIC等级
在Win7系统下,找到本实例生成的可执行程序,右键【以管理员身份运行】,查看MIC等级为:高MIC等级在【Windows 7: Message Integrity Check(消息完整性检查)】一节中,正常启动的explorer.exe进程MIC等级为中MIC等级,如果把其MIC等级调为高MIC等级,那么从explorer.exe所管理的资源管理器中拖放文件到本实例程序窗口,由于等级同为高MIC等级,则消息不会被UIPI拦截了。那么打开任务管理器,先结束掉explorer.exe进程后,再在【任务管理器】-【文件】-【新建任务(运行…)】(如下图所示),则再从资源管理器中拖放文件到本实例程序窗口,也能正常被接收处理之。
结语:
对于上述情况一,如果你的程序不需要【以管理员身份运行】,则不需要额外操作即可正常接收拖放文件消息。
对于上述情况二、若程序需要【以管理员身份运行】,则另外需要添加额外代码(本文上半部分所述)修改UIPI消息过滤器以实现拖放文件消息的正常接收处理。
对于上述情况三、只为实验调试MIC等级高低来验证MIC等级机制,现实编程中,不好结束系统explorer.exe进程,再以管理员身份运行explorer.exe来调高其MIC等级,以免影响用户体验。