导言

继上篇《用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内核格式的(形如:\Device\HarddiskVolume1\***)。要转换成我们熟悉的格式还得做些额外的工作。不过转换原理也简单:从驱动盘符A到盘符Z逐个扫描对比,将形如\Device\HarddiskVolume1\的前缀替换为C:,D:……。转换代码如下:

 bool RetrieveHelper::NormalizeNTPath(wchar_t* pszPath, size_t nMax)
{
wchar_t* pszSlash = wcschr(&pszPath[], '\\');
if (pszSlash) pszSlash = wcschr(pszSlash+, '\\');
if (!pszSlash)
return false;
wchar_t suffix[MAX_PATH];
wcscpy_s(suffix, MAX_PATH, pszSlash);
*pszSlash = ; wchar_t szNTPath[MAX_PATH];
wchar_t szDrive[MAX_PATH] = TEXT("A:");
// We'll need to query the NT device names for the drives to find a match with pszPath
for (wchar_t cDrive = 'A'; cDrive < 'Z'; ++cDrive)
{
szDrive[] = cDrive;
szNTPath[] = ;
if ( != QueryDosDevice(szDrive, szNTPath, MAX_PATH) && == _wcsicmp(szNTPath, pszPath))
{
// Match
wcscat_s(szDrive, MAX_PATH, suffix);
wcscpy_s(pszPath, nMax, szDrive);
return true;
}
}
return false;
}

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

 

已安装软件信息

(1)一般情况

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

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall                                         (1)

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

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

HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall                               (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_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall这个路径下放的是64位程序的注册信息,而HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall放的则是32位程序的信息。为什么这么命名,仔细想想就会发现这样命名其实是自然而然的。上面提到的博客也有说明。

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

     typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
BOOL bIsWow64 = false; // assume 32 bit // can't call IsWow64Process on x32, so first look up the entry point in kernel32
LPFN_ISWOW64PROCESS fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")),"IsWow64Process");
// if we have an entry point for IsWow64Process, we can call it
if (NULL != fnIsWow64Process)
{
if (!fnIsWow64Process(GetCurrentProcess(), &bIsWow64))
{
bIsWow64 = FALSE;
}
}
return bIsWow64;

界面设计

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

界面截图及代码

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

用Qt写软件系列三:一个简单的系统工具(上)的更多相关文章

  1. 用Qt写软件系列三:一个简单的系统工具之界面美化

    前言 在上一篇中,我们基本上完成了主要功能的实现,剩下的一些导出.进程子模块信息等功能,留到后面再来慢慢实现.这一篇来讲述如何对主界面进行个性化的定制.Qt库提供的只是最基本的组件功能,使用这些组件开 ...

  2. 用Qt写软件系列四:定制个性化系统托盘菜单

    导读 一款流行的软件,往往会在功能渐趋完善的时候,通过改善交互界面来提高用户体验.毕竟,就算再牛逼的产品,躲藏在糟糕的用户界面之后总会让用户心生不满.界面设计需综合考虑审美学.心理学.设计学等多因素, ...

  3. 用Qt写软件系列五:一个安全防护软件的制作(3)

    引言 上一篇中讲述了工具箱的添加.通过一个水平布局管理器,我们将一系列的工具按钮组合到了一起,完成了工具箱的编写.本文在前面的基础上实现窗体分割效果.堆栈式窗口以及Tab选项卡. 窗体分割 窗体分割是 ...

  4. 用Qt写软件系列五:一个安全防护软件的制作(1)

    引言 又有许久没有更新了.Qt,我心爱的Qt,为了找工作不得不抛弃一段时间,业余时间来学一学了.本来计划要写一系列关于Qt组件美化的博文,但是写了几篇之后就没坚持下去了.技术上倒是问题不大,主要是时间 ...

  5. 用Qt写软件系列五:一个安全防护软件的制作(2)

    引言 在上一篇中讲述了主窗体的创建和设计.主窗体的无边框效果.阴影效果.拖动事件处理.窗体美化等工作在前面的博客中早就涉及,因此上篇博文中并未花费过多笔墨.这一篇继续讲述工具箱(Tool Button ...

  6. 用Qt写软件系列二:QCookieViewer(浏览器Cookie查看器)

    预备 继上篇<浏览器缓存查看器QCacheViewer>之后,本篇开始QCookieViewer的编写.Cookie技术作为网站收集用户隐私信息.分析用户偏好的一种手段,广泛应用于各大网站 ...

  7. 用Qt写软件系列一:QCacheViewer(浏览器缓存查看器)

    介绍 Cache技术广泛应用于计算机行业的软硬件领域.该技术既是人们对新技术探讨的结果,也是对当前软硬件计算能力的一种妥协.在浏览器中使用cache技术,可以大幅度提高web页面的响应速度,降低数据传 ...

  8. 造轮子系列(三): 一个简单快速的html虚拟语法树(AST)解析器

    前言 虚拟语法树(Abstract Syntax Tree, AST)是解释器/编译器进行语法分析的基础, 也是众多前端编译工具的基础工具, 比如webpack, postcss, less等. 对于 ...

  9. ROS与Matlab系列:一个简单的运动控制

    ROS与Matlab系列:一个简单的运动控制 转自:http://blog.exbot.net/archives/2594 Matlab拥有强大的数据处理.可视化绘图能力以及众多成熟的算法函数,非常适 ...

随机推荐

  1. 【Vegas原创】RHEL6.2安装vmtools

    1,mount虚拟cdrom       # mount /dev/cdrom /mnt/cdrom   2, ls一下,看有没有      # cd /mnt/cdrom     # ls –l   ...

  2. Centos7 改名问题

    先看这篇 深入理解Linux修改hostname http://www.cnblogs.com/kerrycode/p/3595724.html  我使用如下方法 http://www.linuxid ...

  3. Android Multiple Screens Android 屏幕适配的一些总结

    作为一名Android应用开发程序猿,最痛苦的事莫过于在屏幕适配了,这与历史原因有关,具体就不深究了. 直到最近才搞明白dpi是怎么换算的,在开发的过程中,一个应用运行的屏幕标准应该是分辨率为320x ...

  4. Difference between LET and LET* in Common LISP

    Difference between LET and LET* in Common LISP   LET   Parallel binding which means the bindings com ...

  5. 修改 Semantic UI 的默认字体

    Semantic UI 默认使用的是谷歌提供的字体,并且是直接使用了谷歌的官方链接.由于大家都知道的原因,谷歌网站在国内访问速度很差,甚至根本无法访问,还有就是可能会在离线环境下使用 Semantic ...

  6. 【Cocos2d-x 3.X 资源及脚本解密】

    加密就不用说了,看上一篇2.X加密的方式,怎么弄都可以.的保证解密规则就行: 现在重点说3.X解密: 在新的3.X引擎中官方整合了大部分获取资源的方法,最终合成一个getdata: 可以从源码,和堆栈 ...

  7. iOS 内存管理机制和循环引用处理方法

    简述 ARC: 自动引用计数, Automatic Reference Counting MRC: Mannul Reference Counting ARC工作原理 1.当每次创建一个新实例时,AR ...

  8. 游戏服务器ID生成器组件

    游戏服务器程序中,经常需要生成全局的唯一ID号,这个功能很常用,本文将介绍一种通用ID生成组件.游戏服务器程序中使用此组件的场景有: 创建角色时,为其分配唯一ID 创建物品时,每个物品需要唯一ID 创 ...

  9. LINUNX下PHP下载中文文件名代码

            function get_basename($filename){                 return preg_replace('/^.+[\\\\\\/]/', '',  ...

  10. script标签块的独立性与共享性

    块间独立,变量与方法共享 每个script块之间不会相互影响,某个script块出错,不会影响其它块的运行 块之间定义的非局部变量和方法可以共享,(其实只是共享了全局变量和方法) <script ...