Theheartoflove的文章

  • 使用TerminateProcess函数实现结束指定进程

    背景对于计算机上面的进程我们比较熟悉了,每次只要我们想关闭那些关闭掉的窗口或程序的时候,我们就会打开任务管理器,然后在里面选中进程,右击结束进程,那么程序就会关掉了。
    本文要介绍的,正是如何使用 TerminateProcess 函数结束进程,实现与任务管理器相同的结束进程功能。现在,我就把程序实现的过程和原理整理成文档,分享给大家。
    函数介绍OpenProcess 函数
    打开现有的本地进程对象。
    函数声明
    HANDLE WINAPI OpenProcess( _In_ DWORD dwDesiredAccess, _In_ BOOL bInheritHandle, _In_ DWORD dwProcessId);
    参数

    dwDesiredAccess [in]访问进程对象。此访问权限针对进程的安全描述符进行检查。此参数可以是一个或多个进程访问权限。如果调用该函数的进程启用了SeDebugPrivilege权限,则无论安全描述符的内容如何,都会授予所请求的访问权限。bInheritHandle [in]若此值为TRUE,则此进程创建的进程将继承该句柄。否则,进程不会继承此句柄。dwProcessId [in]要打开的本地进程的标识符。如果指定的进程是系统进程(0x00000000),则该函数失败,最后一个错误代码为ERROR_INVALID_PARAMETER。如果指定的进程是空闲进程或CSRSS进程之一,则此功能将失败,并且最后一个错误代码为ERROR_ACCESS_DENIED,因为它们的访问限制会阻止用户级代码打开它们。如果您使用GetCurrentProcessId作为此函数的参数,请考虑使用GetCurrentProcess而不是OpenProcess,以提高性能。
    返回值

    如果函数成功,则返回值是指定进程的打开句柄。如果函数失败,返回值为NULL。 要获取扩展错误信息,请调用GetLastError。

    TerminateProcess 函数
    终止指定的进程及其所有线程。
    函数声明
    BOOL WINAPI TerminateProcess( _In_ HANDLE hProcess, _In_ UINT uExitCode);
    参数

    hProcess [in]要终止的进程的句柄。句柄必须具有PROCESS_TERMINATE访问权限。 uExitCode [in]进程使用的退出代码和作为此调用的结果而终止的线程。
    返回值

    如果函数成功,则返回值不为零。如果函数失败,返回值为零。 要获取扩展错误信息,请调用GetLastError。

    实现过程从上面的函数介绍知道,要想使用 TerminateProcess 函数,首先,我们先要获取进程句柄。那么,获取进程的句柄,我们可以调用 OpenProcess 函数根据进程的 PID 打开进程并获取进程句柄。
    对于 OpenProcess 函数,第 1 个参数表示设置访问进程的权限,本文指定了权限 PROCESS_TERMINATE ,是因为 TerminateProcess 函数要求进程句柄要有访问权限 PROCESS_TERMINATE;第 2 个参数表示是否继承句柄,FALSE表示不继承;第 3 个参数表示要打开进程的PID。 在 OpenProcess 函数成功执行后,便返回进程句柄。
    // 打开进程, 获取进程句柄 HANDLE hProcess = ::OpenProcess(PROCESS_TERMINATE, FALSE, dwProcessId); if (NULL == hProcess) { ShowError("OpenProcess"); return FALSE; }
    然后,我们使用 TerminateProcess 函数结束进程。其中,第 1 个参数表示进程的句柄,要求进程句柄包含有 PROCESS_TERMINATE 访问权限;第 2 个参数表示设置一个退出代码,这个值可以任意设值。
    // 结束进程 BOOL bRet = ::TerminateProcess(hProcess, 0); if (FALSE == bRet) { ::CloseHandle(hProcess); ShowError("TerminateProcess"); return FALSE; }
    这样,经过上面的两步操作,就可以实现将指定进程结束掉。
    编程实现BOOL TerminateProcessTest(DWORD dwProcessId){ // 打开进程, 获取进程句柄 HANDLE hProcess = ::OpenProcess(PROCESS_TERMINATE, FALSE, dwProcessId); if (NULL == hProcess) { ShowError("OpenProcess"); return FALSE; } // 结束进程 BOOL bRet = ::TerminateProcess(hProcess, 0); if (FALSE == bRet) { ::CloseHandle(hProcess); ShowError("TerminateProcess"); return FALSE; } // 关闭进程句柄 ::CloseHandle(hProcess); return TRUE;}
    程序测试我们直接运行程序,输入要结束进程的PID,指定进程成功被结束。

    总结要注意的是,使用 OpenProcess 函数打开进程的时候,设置的访问权限一定要包含 PROCESS_TERMINATE 权限,否则调用 TerminateProcess 函数会出错。
    这个程序可以结束普通权限的进程,也可以结束一些以管理员权限运行的进程。但是,对于一些系统进程,或者进行进程保护处理的程序,则超过此程序的功能范围了。
    参考参考自《Windows黑客编程技术详解》一书
    1  留言 2018-12-20 12:08:25
  • 使用SHFormatDrive函数实现格式化磁盘

    背景某天,无意中在网上搜索资料的时候,看到一篇帖子,就是将如何编程实现格式化操作的。我便看了下,原来调用的是 SHFormatDrive 函数实现的。和我们选中磁盘驱动器,鼠标右击选择“格式化(A)…”弹出来的格式化窗口是同一个。也就是说,SHFormatDrive 实现的就是我们选中磁盘,点击格式化操作的过程,并不能实现静默格式化磁盘。
    好吧,确实让你失望了,我们这篇文章还是讲解下 SHFormatDrive 函数的使用,实现弹出格式化窗口的操作。对于静默格式化,目前我没有深究过。但是,我也想到一种感觉或许可行的静默实现思路,就是隐藏弹出的格式化的选择窗口,然后发送开始格式化的消息给隐藏的窗口,这样,就可以静静地进行格式化操作了。不过这个方法我没有试过,等以后闲来无事而且想深究的时候,我再试吧。
    现在,我就把 SHFormatDrive 实现有弹窗格式化磁盘的过程整理成文档,分享给大家。
    函数介绍SHFormatDrive 函数
    打开Shell的格式化对话框。
    函数声明
    DWORD SHFormatDrive( _In_ HWND hwnd, UINT drive, UINT fmtID, UINT options);
    参数

    hwnd [in]对话框的父窗口的句柄。格式对话框必须有父窗口,因此,此参数不能为NULL。driver驱动器格式化。该参数的值表示从 A 开始为 0 的字母驱动器。例如,值 2 代表C:驱动器。fmtID物理格式的ID。目前仅定义了:SHFMT_ID_DEFAULT(0xFFFF),表示默认格式ID。options此值必须为 0 或以下值之一才能更改对话框中的默认格式选项。该值被视为一个位域,应该相应地对待。SHFMT_OPT_FULL(0x0001):如果设置了此标志,则选择快速格式选项。SHFMT_OPT_SYSONLY(0x0002):选择创建MS-DOS启动磁盘选项,创建一个系统引导磁盘。
    返回值

    返回上一个成功格式的格式ID或以下值之一。 该值的LOWORD可以作为fmtID参数在后续调用中传递,以重复最后一个格式。



    VALUE
    MEANING




    SHFMT_ERROR
    最后一个格式出现错误。 这不表示驱动器是不可格式化的


    SHFMT_CANCEL
    最后一个格式被取消


    SHFMT_NOFORMAT
    驱动器无法格式化




    实现过程由上述的函数介绍中,我们知道,SHFormatDrive 的第一个参数是要关联一个窗口的句柄,而且这个参数不能为NULL。所以,这需要获取我们程序窗口的句柄,传递给它。本文给的例子程序,是一个控制台程序,所以,我们可以调用WIN32 API函数 GetConsoleWindow 获取当前控制台程序的窗口句柄。
    // 获取控制台程序窗口句柄 HWND hWnd = ::GetConsoleWindow();
    然后,我们就可以大胆地调用 SHFormatDrive 函数打开指定驱动器的格式化窗口了。其中,第 1 个参数表示关联窗口的窗口句柄;第 2 个参数表示要格式化的驱动器,该参数的值是以大写字母 A 开始为 0 的驱动器;第 3 个参数目前只有一个固定的值SHFMT_ID_DEFAULT,表示默认格式ID;第 4 个参数可以设置格式化对话框的格式化选项。
    ::SHFormatDrive(hParentWnd, (cDriverName - 'A'), SHFMT_ID_DEFAULT, 0);
    编码实现导入库文件#include <ShlObj.h>#pragma comment(lib, "Shell32.lib")
    格式化操作BOOL FormatDriver(HWND hParentWnd, char cDriverName){ ::SHFormatDrive(hParentWnd, (cDriverName - 'A'), SHFMT_ID_DEFAULT, 0); return TRUE;}
    程序测试我们运行程序,程序成功弹窗格式化E盘的格式化窗口。

    总结这个功能实现,关键是对 SHFormatDrive 函数的理解。大家在编码实现之前,可以先仔细阅读函数介绍部分的内容,这样,在调用 SHFormatDrive 函数的时候,就可以做到知其然,知其所以然了。
    参考参考自《Windows黑客编程技术详解》一书
    1  留言 2018-12-20 12:10:44
  • 基于Skin++库实现的换肤功能

    背景之前自己经常使用MFC来开发一些界面程序,这些程序大都是自己练手用的。但,也会有极个别是帮别人开发,给别人使用。当你辛苦做出来的作品拿出去给别人用的时候,你总想让自己的作品给人留下深刻印象,无论是从功能,还是用程序界面上。
    对于,我们使用 VC6.0 或者 VS2008、VS2010等以上版本开发出来的界面程序,界面通常都是千篇一律,非常质朴的。所以,自己就像用最简单的方式去修改下界面,因为界面虽说重要,但也并不是说不可或缺的,所以自己不想花费太多的精力在界面上。
    后来,经过搜索查找,还真让我找到一个不错的、方便易用的界面修改方法,即使用 Skin++界面库 来实现。Skin++ 属于第二代的外挂式的界面库,提供了Skin Builder 工具将所有控件的资源全部设计成为一个独立的文件,便于在应用程序外部独立地进行增删改操作,采用Hook与子类化技术来实现应用程序的自动换肤。
    现在,本文就基于 Skin++界面库 实现给 VC 或 VS 开发的界面程序更换皮肤。把实现的过程整理成文档,分享给大家。
    实现过程程序要使用到的 Skin++库 文件包括:SkinPPWTL.h、SkinPPWTL.lib、skinppwtl.dll 以及 48个 .ssk 的皮肤文件。
    首先,我们需要把 Skin++ 库文件加载到程序里,做法如下:

    把 SkinPPWTL.h 头文件以及 SkinPPWTL.lib 库文件拷贝到我们的项目工程目录下面,然后在程序声明头文件并加载库文件:
    #include <afxcmn.h>#include <Windows.h>#include "skin\\SkinPPWTL.h"#pragma comment(lib, "skin\\SkinPPWTL.lib")
    然后,我们编译下程序,看看有没有错误提示。若出现类似这样的错误提示: “… _CRT_SECURE_NO_WARNINGS …”,则在 项目属性 —> C/C++ —> 预处理器 —> 预处理器定义 中添加 “__CRT_SECURE_NO_WARNINGS” 这个预定义即可。

    经过上面两步操作,就可以正确地把 Skin++ 库文件加载到程序中了。
    接下来,我们就直接调用 skinppLoadSkin 函数加载 .ssk 格式的皮肤库就可以了。但是要注意调用 skinppLoadSkin 函数的地方,一定要在界面实现出来之前的初始化操作里就开始调用,不能再界面显示出来后调用,否则会出问题。对于 MFC 程序和 Windows应用程序,它们调用 skinppLoadSkin 函数加载界面库文件的地方可以是:

    对于 MFC 程序,可以是在 CxxxAPP::InitInstance() 初始化函数的开头调用,也可以是在 CxxxDlg::OnInitDialog() 主窗口类初始化函数里调用。
    对于 Windows 应用程序,可以是在 WinMain 函数的开头调用,也可以是在窗口消息处理过程函数中的 WM_INITDIALOG 消息中加载。

    调用 skinppLoadSkin 函数加载界面库文件的代码如下所示:
    // 加载皮肤::skinppLoadSkin("skins\\XP-Home.ssk");
    要更换皮肤,只需要更改上面加载的 .ssk 库文件就好,本文有 48 个 .ssk 文件,所以,可以实现 48 种换肤。
    最后,我们编译链接生成可执行文件,在运行程序之前,只需要按照上面库文件 .ssk 的路径放置库文件,同时还需要把 skinppwtl.dll 动态链接库放在和可执行程序同一目录下,就可以正常运行程序了。
    程序测试按照上面的方法,我们分别创建了一个 MFC 程序和 Windows应用程序 来进行测试,程序成功更换皮肤。


    总结对于 Skin++界面库使用比较简单,主要在窗口初始化的时候,就对界面库初始化,并加载界面库就好,剩下的,不需要我们去理会,我们只需要正常开发我们的程序功能就好。
    3  留言 2018-12-20 12:10:51
eject