写一个Windows上的守护进程(8)获取进程路径
要想守护某个进程,就先得知道这个进程在不在。我们假设要守护的进程只会存在一个实例(这也是绝大部分情形)。
我是遍历系统上的所有进程,然后判断他们的路径和要守护的进程是否一致,以此来确定进程是否存在。
遍历进程大家都知道用CreateToolhelp32Snapshot系列API,但是他们最后取得的是进程exe名称,不是全路径,如果仅依靠名称就可以达到目的也就罢了,但是有的时候还是得取到全路径,这样会更靠谱一些。
那么问题来了,如何取到进程全路径?
首先登场的是GetModuleFileNameEx。大多数时候,我们都可以用这个函数来取得进程的全路径,而且这个函数是全平台都有,但是你有没有注意到这样一段话:
(来自VS2008的MSDN文档)
这个函数有缺陷。除了这个问题之外,还有一个问题:如果你是32进程要获取某个64位进程的全路径,有可能也会出错,具体请看我很早前写过的一片帖子http://blog.csdn.net/mkdym/article/details/8688597。
当然微软也说了,你还可以用GetProcessImageFileName和QueryFullProcessImageName两个函数呀。
那么接下来说GetProcessImageFileName。这函数确实不存在GetModuleFileNameEx的两个问题,但是它不支持Windows2000,当然如果你不需要支持Windows2000这种老古董,你完全不用理会这个缺憾。然而,它还有别的问题:
1. 这家伙返回的是个内核名称,形如DeviceHarddisk0Partition1WINNTSystem32Ctype.nls。内核名称咱不怕,咱用QueryDosDevice反向获取:先用QueryDosDevice获取到所有磁盘的驱动器名称和内核名称的对应关系,然后把刚获取到的内核名称的前面部分换掉就可以了。你到网上一搜,也全是这种办法。
以为这就结束了吗?请看https://msdn.microsoft.com/en-us/library/windows/desktop/ms683217(v=vs.85).aspx 下面关于动态磁盘的问题,这个问题是我提的:在动态磁盘的情况下,驱动器的链接会有两层,这个你可以从Winobj中看到,而QueryDosDevice只能获取到第一层,GetProcessImageFileName返回的是第二层的!我勒个去!网上一番搜索之后,许多人给出的答案都是在结果上反复调用QueryDosDevice,我只能对这些人呵呵,也许他们的电脑比较特殊,QueryDosDevice可以那样使用。
关于动态磁盘的问题,你还可以看我在上面给出的那个csdn的帖子,那是我发现这个问题的过程。
怎么办?后来忘了在那个论坛里边看到的,用内核函数NtQuerySymbolicLinkObject可以做到QueryDosDevice一样的事情,经过试验,这个函数是可以在结果上重复调用的,能够获取到最底层的内核名称。嗯,这个问题就这样解决了。
当然,一般的机子不会有动态磁盘。
2. 当你看MSDN页面上我说的“动态磁盘”的问题的时候,应该会注意到,下面有个哥们说,这个函数会返回短路径名,所以咱也得注意了。把每个返回的路径都转换成长路径名,不管它是不是短路径名(我也不知道它是不是短路径名啊)。虽然我测试的时候它返回的都是长路径名。
这里还有一点,咱给进程们做了快照了,快照的那一刻驱动器们是确定的,所以完全不需要获取每个进程路径的时候都去获取所有驱动器的名称对应关系,只需要在遍历开始的时候,准备一下这些名称,然后遍历的时候直接拿来用。
嗯,用这个API就是这么麻烦。
最后咱们说QueryFullProcessImageName。这个函数啥都好,就是平台要求太高,得是Vista及以上啊,所以只能是能用则用。
最终我获取进程路径的方案是这样的:如果有QueryFullProcessImageName函数,就用这个函数,没有的话先上GetModuleFileNameEx,如果GetModuleFileNameEx出错了再上GetProcessImageFileName。把最麻烦的放到最后。
类名CProcessPathQuery。
大家看代码的时候可能会注意到,我在处理的时候用了个for循环,这是因为我也不知道进程路径有多长,如果返回错误码说缓冲区不够,就加长重试,但是也不能无限重试,有个限度。
进程路径获取出来之后,因为有可能是GetProcessImageFileName获取出来的,那就是一个内核名称,需要转换,转换类是CDosPathConverter。
进程遍历类是CProcessScanner,可以看到我在里面放了一个CDosPathConverter的实例,随着遍历类的初始化一起初始化。
遍历进程的时候需要注意,有几类特殊的进程我们是获取不到路径的,或者获取他们的路径是没有意义的,可以看CProcessPathQuery的注释。
有一点需要注意,一定要提权,否则就会有好多进程因为我们自己权限不够而query失败。
源码:https://git.oschina.net/mkdym/DaemonSvc.git (主)&& https://github.com/mkdym/DaemonSvc.git (提升逼格用的)。
2015年11月19日星期四