用Qt写软件系列三:一个简单的系统工具(上)
导言
继上篇《用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写软件系列三:一个简单的系统工具(上)的更多相关文章
- 用Qt写软件系列三:一个简单的系统工具之界面美化
前言 在上一篇中,我们基本上完成了主要功能的实现,剩下的一些导出.进程子模块信息等功能,留到后面再来慢慢实现.这一篇来讲述如何对主界面进行个性化的定制.Qt库提供的只是最基本的组件功能,使用这些组件开 ...
- 用Qt写软件系列四:定制个性化系统托盘菜单
导读 一款流行的软件,往往会在功能渐趋完善的时候,通过改善交互界面来提高用户体验.毕竟,就算再牛逼的产品,躲藏在糟糕的用户界面之后总会让用户心生不满.界面设计需综合考虑审美学.心理学.设计学等多因素, ...
- 用Qt写软件系列五:一个安全防护软件的制作(3)
引言 上一篇中讲述了工具箱的添加.通过一个水平布局管理器,我们将一系列的工具按钮组合到了一起,完成了工具箱的编写.本文在前面的基础上实现窗体分割效果.堆栈式窗口以及Tab选项卡. 窗体分割 窗体分割是 ...
- 用Qt写软件系列五:一个安全防护软件的制作(1)
引言 又有许久没有更新了.Qt,我心爱的Qt,为了找工作不得不抛弃一段时间,业余时间来学一学了.本来计划要写一系列关于Qt组件美化的博文,但是写了几篇之后就没坚持下去了.技术上倒是问题不大,主要是时间 ...
- 用Qt写软件系列五:一个安全防护软件的制作(2)
引言 在上一篇中讲述了主窗体的创建和设计.主窗体的无边框效果.阴影效果.拖动事件处理.窗体美化等工作在前面的博客中早就涉及,因此上篇博文中并未花费过多笔墨.这一篇继续讲述工具箱(Tool Button ...
- 用Qt写软件系列二:QCookieViewer(浏览器Cookie查看器)
预备 继上篇<浏览器缓存查看器QCacheViewer>之后,本篇开始QCookieViewer的编写.Cookie技术作为网站收集用户隐私信息.分析用户偏好的一种手段,广泛应用于各大网站 ...
- 用Qt写软件系列一:QCacheViewer(浏览器缓存查看器)
介绍 Cache技术广泛应用于计算机行业的软硬件领域.该技术既是人们对新技术探讨的结果,也是对当前软硬件计算能力的一种妥协.在浏览器中使用cache技术,可以大幅度提高web页面的响应速度,降低数据传 ...
- 造轮子系列(三): 一个简单快速的html虚拟语法树(AST)解析器
前言 虚拟语法树(Abstract Syntax Tree, AST)是解释器/编译器进行语法分析的基础, 也是众多前端编译工具的基础工具, 比如webpack, postcss, less等. 对于 ...
- ROS与Matlab系列:一个简单的运动控制
ROS与Matlab系列:一个简单的运动控制 转自:http://blog.exbot.net/archives/2594 Matlab拥有强大的数据处理.可视化绘图能力以及众多成熟的算法函数,非常适 ...
随机推荐
- 如何使Session永不过期
转载:http://blog.csdn.net/wygyhm/article/details/2819128 先说明情况:公司做监控系统,B/S结构,主要用在局域网内部!监控系统开机可能要开好长时间, ...
- ASP lable标签显示过长,自动换行。
<asp:Label ID="lab_BeforPostR" runat="server" CssClass="labSty" Wid ...
- 在Android Studio进行“简单配置”单元测试(Android Junit)
起因 在Android studio 刚出.本人就想弄单元测试,可惜当时Android studio不知道抽什么风(准确来说,应该是我不会弄而已).无法执行到相应的代码.后来今天突然自己又抽风.又想去 ...
- Flash Builder中“Error: #2036 加载未完成”错误的解决方法
复制了一个名称为A的widget包,重命名为B,包含B.mxml和B.xml(配置文件),编译后无法加载B包创建的widget,报错为: 解决办法: 1.在工程的根目录下找到.actionScript ...
- centos7.0 手动编译 lamp环境
首先新建用户 lamper,并添加 sodu权限 两种方法:is not in the sudoers file 解决(转) xx is not in the sudoers file 问题解决[转载 ...
- js 与或运算符 || && 妙用
js 与或运算符 || && 妙用,可用于精简代码,降低程序的可读性. 首先出个题: 如图: 假设对成长速度显示规定如下: 成长速度为5显示1个箭头: 成长速度为10显示2个箭头: ...
- MemSQL start[c]up Round 2 - online version C. More Reclamation(博弈)
题目大意 额,写来写去,我还是直接说抽象之后的题目大意吧: 有一个 r*2 的矩形,两个人轮流的在矩形上面减去一个 1*1 的小正方形,要求在减的过程中,不能使矩形“断开”,也就是说,如果一个人减去了 ...
- fdisk命令使用说明
CentOS我新建了几个分区,比如/dev/sda4,sda5我想挂在一个目录下,用mount /dev/sda5 /disk ,总提示mount:you must specify the fil ...
- golang 图片处理,剪切,base64数据转换,文件存储
本文主要介绍: 1. 图片文件的读写. 2. 图片在go缓存中如何与base64互相转换 3. 图片裁剪 本文中,为了方便查看,去掉所有错误判断 base64 -> file ddd, _ := ...
- Android样式的开发:drawable汇总篇
Android有很多种drawable类型,除了前几篇详细讲解的shape.selector.layer-list,还有上一篇提到的color.bitmap.clip.scale.inset.tran ...