高权限进程实现拖曳文件操作

在 Vista/Win7 系统中,由于 UAC 和 UIPI 的存在,低权限的进程是无法向高权限的进程发送任何高于 WM_USER 的消息,而低于 WM_USER 的消息一部分也会因为安全原因被禁止。详情可见:《UAC 的前世今生》 。但这样就会带来很多麻烦,比如文件拖曳的操作在管理员权限的进程中就无法进行。

解决这个问题的方法大致有两种:

  • 如果进程一定需要高权限做一些操作,可以考虑将 UI 相关的部分设定为普通用户权限,而一些需要特殊权限的事情全丢到另外一个管理员权限的进程中进行,两个进程通过除消息之外的其他方式进行通信,比如管道,共享内存等等。(这种方法实在太蛋疼,除非有很多类型的消息或者特殊事件需要处理,否则不推荐)
  • 调用 ChangeWindowMessageFilter 这个 API 进行消息过滤。在 UIPI 的作用下,当低权限进程向高权限进程发送消息时,大多数情况都会被 Block。而 ChangeWindowMessageFilter 这个 API 的作用就是提供一个过滤器:往过滤器中添加消息就可以允许低权限的进程发送消息给当前进程,而移除消息则可以起到阻塞消息的作用。当然这也不是绝对的:某些低于 WM_USER 的消息无论我们怎么去设置这个过滤器都是可以通行的。

回到正题:管理员权限的进程之所以无法进行拖曳文件的操作就是因为 explorer.exe 本身的权限低于它,WM_DROPFILES 的消息被堵塞了。所以只要在程序或者窗口初始化的时候调用 ChangeWindowMessageFilter 这个 API 就可以了。 ChangeWindowMessageFilter(WM_DROPFILES, MSGFLT_ADD);

嗯,这样就 OK 了?把这代码往自己的程序中一拷,却发现被坑爹了:还是不行。正确的做法应该是需要放行以下三条消息:

ChangeWindowMessageFilter(WM_DROPFILES, MSGFLT_ADD); ChangeWindowMessageFilter(WM_COPYDATA, MSGFLT_ADD); ChangeWindowMessageFilter(WM_COPYGLOBALDATA , MSGFLT_ADD);

其中 WM_COPYGLOBALDATA 在 winodws.h 中并没有相应的定义,也可以直接用它的数值 0×0049 来代替。至于为啥要这么做,并没有很明确的官方文档说明,不过也可以猜出个大概:WM_COPYDATA 和 WM_COPYGLOBALDATA 是用于 explorer.exe 和当前进程进行进程通信的消息。不过 MSDN 上并不推荐使用这个方法,因为这个方法杀伤范围太大,影响的是当前整个进程,更推荐使用 ChangeWindowMessageFilterEx,对某个特地窗口进行消息过滤:这样减少了 filter 对程序的影响,保证了安全又不至于无法实现某些特定功能。