用Qt写软件系列三:一个简单的系统工具(上)

2018-01-18 10:00:59 浏览数 (1)

导言

      继上篇《用Qt写软件系列二:QIECookieViewer》之后,有一段时间没有更新博客了。这次要写的是一个简单的系统工具,需求来自一个内部项目。功能其实很简单,就是查看当前当前系统中运行的进程信息以及系统中已安装软件信息。说出来也就这么两句话,然而做起来的时候,问题却层出不穷。另外,一直想研究一下Qt中的样式表(Style Sheet)的使用,就这这个机会实践了一下,也算收获颇多。

      这一篇主要讲该工具的底层实现。前面也说过,这个小工具总共有有两个功能:查看进程信息和已安装软件信息。因此我们分成两个部分看。首先说明,我的开发环境为Visual Studio 2010旗舰版,Qt库版本为Qt 5.2.1 (OpenGL)。操作系统为Windows 7 64bits英文版。

查看进程信息

(1)32位系统

      对于32位操作系统,情况很简单,直接调用Process32First()和Process32Next()这一对函数进行遍历就可以得到当前正在运行的程序列表。将PROCESSENTRY32结构体变量作为这两对函数的参数传递进去,该结构体中的字段即为相应进程的基本信息。当然,这个结构体中的信息还是过于简单,如果要获得对应进程所占用的内存情况,还得调用其他Windows API来获取,如:GetProcessMemoryInfo(),VirtualQueryEx().这两个函数获取的内存信息则比较详尽。

问题来了,如果我们需要列出一个进程对应的可执行文件所在的路径该怎么办呢?上面也提到,PROCESSENTRY32结构体中的信息过于简单,只包含了对应进程的进程名称。网上有解决方案说,可以用GetModuleFileNameEx()来获取。于是立马在程序中调用,又发现了一个问题:该函数对于32位进程而言正常工作无疑,但是当查询的进程是64位的时候,这个函数直接返回0了,查询失败。显然,该函数对32位进程和64位进程的运行结果不一样。后来在MSDN在线帮助文档GetModuleFileNameEx()该页面下有评论说:To clarify a bit, this function DOES work to find the module names that are the same "bitness" as the running application. 意思是说,当前进程如果编译为32位,那么在该进程中调用GetModuleFileNameEx()只能查询32位的进程,对于64位进程是一样的道理。而我的VS2010编译的目标机器类型也是32位,这也就难怪了。

(2)兼容64位系统

将目标机器类型改为64位的显然又无法查询到32位进程的信息。那么只好另寻他法了。就在该评论页面,另外一个人有提到一个可行的解决方案:

使用GetProcessImageFileName()来提取信息。然而,该评论也指出:这个函数返回的进程文件路径是windows内核格式的(形如:DeviceHarddiskVolume1***)。要转换成我们熟悉的格式还得做些额外的工作。不过转换原理也简单:从驱动盘符A到盘符Z逐个扫描对比,将形如DeviceHarddiskVolume1的前缀替换为C:,D:……。转换代码如下:

代码语言:javascript复制
 1 bool RetrieveHelper::NormalizeNTPath(wchar_t* pszPath, size_t nMax)
 2 {
 3     wchar_t* pszSlash = wcschr(&pszPath[1], '\');
 4     if (pszSlash) pszSlash = wcschr(pszSlash 1, '\');
 5     if (!pszSlash)
 6         return false;
 7     wchar_t suffix[MAX_PATH];
 8     wcscpy_s(suffix, MAX_PATH, pszSlash);
 9     *pszSlash = 0;
10 
11     wchar_t szNTPath[MAX_PATH];
12     wchar_t szDrive[MAX_PATH] = TEXT("A:");
13     // We'll need to query the NT device names for the drives to find a match with pszPath
14     for (wchar_t cDrive = 'A'; cDrive < 'Z';   cDrive)
15     {
16         szDrive[0] = cDrive;
17         szNTPath[0] = 0;
18         if (0 != QueryDosDevice(szDrive, szNTPath, MAX_PATH) && 0 == _wcsicmp(szNTPath, pszPath))
19         {
20             // Match
21             wcscat_s(szDrive, MAX_PATH, suffix);
22             wcscpy_s(pszPath, nMax, szDrive);
23             return true;
24         }
25     }
26     return false;
27 }

如此转换之后,结果令人满意:

已安装软件信息

(1)一般情况

      什么是一般情况?在32位程序和系统仍然大行其道的今天,要是抛弃32位程序,完全拥抱64位是不现实的。所以我们的程序必须要能处理32位系统的情况,这是最基本的要求。那么,在32位系统环境下,如何来提取系统已经安装的程序的信息呢?不知道360安全卫士、金山卫士等软件是怎么做的,反正我最自然的想法就是去读注册表。每一款软件安装后都会在注册表里留下记录,除非是绿色免安装软件。那么,要读注册表的话要去哪个位置读呢?网上的很多资料都指向了一个固定的位置:

代码语言:javascript复制
HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionUninstall                                         (1)

在注册表编辑器regedit中打开这个路径,果然可以看到一些软件的注册信息,如下:

      等等,稍微一扫描我就觉得有什么不对劲了:我每天用的QQ去哪了?没道理这么大一款软件不使用注册表啊?想来难道又是64位系统的缘故?于是又在网上查到了一些关键信息,一个路径:

代码语言:javascript复制
HKEY_LOCAL_MACHINESOFTWAREWow6432NodeMicrosoftWindowsCurrentVersionUninstall                               (2)

路径存在着这么一个结点名:Wow6432Node。显然,我们就得了解下这个Wow64是个什么东西了。

(2)Wow64是什么?

咋一看Wow这个缩写,让我莫名的熟悉,难道是这个Wow(World of Warcraft)?显然和魔兽世界没有什么联系。一查资料才知道,原来是32bits Windows On Windows 64bits的缩写。照这字面意思,就是微软在64为系统上模拟了一个32位程序运行的环境,这也解释了,为什么我的电脑上会有两个这样的文件夹:

这篇博客讲的很详细,对于是什么、为什么、怎么样都有详细叙述。后面还有一些参考文献,也是相关的解释。我就不再卖弄什么是Wow了。

(3)兼容64位系统

好了,那么我们既然知道了Wow是个什么东西,就去上面的路径(2)瞧个清楚啊。一路打开, QQ的注册信息赫然在列:

      照这么一分析,就明白了:HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionUninstall这个路径下放的是64位程序的注册信息,而HKEY_LOCAL_MACHINESOFTWAREWow6432NodeMicrosoftWindowsCurrentVersionUninstall放的则是32位程序的信息。为什么这么命名,仔细想想就会发现这样命名其实是自然而然的。上面提到的博客也有说明。

      好嘛,余下的事情就很明朗了:判断是否为64位系统,如果是,则读取上述两个位置的信息。至于读取出来的信息完整不完整,我也不确定,毕竟又没有一个统一的标准,不过相信也八九不离十了。关于怎么判断系统的位数,网上流传着一段代码,这里经过了修改也一并贴上:

代码语言:javascript复制
 1     typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
 2     BOOL bIsWow64 = false; // assume 32 bit
 3 
 4     // can't call IsWow64Process on x32, so first look up the entry point in kernel32
 5     LPFN_ISWOW64PROCESS fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")),"IsWow64Process");
 6     // if we have an entry point for IsWow64Process, we can call it
 7     if (NULL != fnIsWow64Process)
 8     {
 9         if (!fnIsWow64Process(GetCurrentProcess(), &bIsWow64))
10         {
11             bIsWow64 = FALSE;
12         }
13     }
14     return bIsWow64;

界面设计

这个工具的功能较为简单,只需要两个表格来显示底层获取的信息即可。考虑使用QTableView来做数据视图,QStandardItemModel做数据存储,Qt MVC框架的好处自不必赘述。整体使用垂直布局管理器来进行部件管理。另外,我们还想实现上下文菜单,那么还得去继承QTableView重写虚函数contextMenuEvent()达到目的。最终的界面看下面。

界面截图及代码

典型的Windows 7默认主题,看起来普通平凡,没有一丝个性。下一篇《用Qt写软件系列二:一个简单的系统工具之界面美化》将对该界面进行个性化定制。

0 人点赞