WINDOWS核心编程-----框架--更新2024.08.27

系列文章目录

提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
例如:第一章 Python 机器学习入门之pandas的使用


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


前言

windows核心编程学习心得

目 录
译者序
前言

第一部分 程序员必读

第1章 对程序错误的处理 1

1.1 定义自己的错误代码 4

在这里插入图片描述

1.2 ErrorShow示例应用程序 5

ErrorShow.cpp
/******************************************************************************
01_ErrorShow.cpp
windows核心编程(2024)
windoesx.h头文件
展示了如何获取错误代码的文本描述的方法
(c)by zhangYongJiang
******************************************************************************/


#include "..\CommonFiles\CmnHdr.h"     /* 编译配置头文件 */
#include <Windowsx.h>                   //消息处理宏
#include <tchar.h>                      //通用类型
#include <winerror.h>                   //错误代码定义文件
#include "Resource.h"


///

//自定义消息
#define ESM_POKECODEANDLOOKUP    (WM_USER + 100)
//const TCHAR g_szAppName[] = TEXT("Error Show");


///
INT_PTR WINAPI Dlg_Proc(HWND hwnd,UINT  uMsg,WPARAM wParam,LPARAM lParam);
BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam);
void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify);

///

//增加一个函数入口
int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int) {

    HWND hwnd = FindWindow(TEXT("#32770"), TEXT("Error Show"));
    //预防多开的代码
    if (IsWindow(hwnd)) {
        // An instance is already running, activate it and send it the new #
        //如果在运行,就激活并发送自定义的消息
        SendMessage(hwnd, ESM_POKECODEANDLOOKUP, _ttoi(pszCmdLine), 0);
    }
    else {
        DialogBoxParam(hinstExe, MAKEINTRESOURCE(IDD_ERRORSHOW),
            NULL, Dlg_Proc, _ttoi(pszCmdLine));
    }
    return(0);
}

///


INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

    switch (uMsg) {
        //定义消息处理函数(宏)
        chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog);
        chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand);

    case ESM_POKECODEANDLOOKUP:
        SetDlgItemInt(hwnd, IDC_ERRORCODE, (UINT)wParam, FALSE);
        FORWARD_WM_COMMAND(hwnd, IDOK, GetDlgItem(hwnd, IDOK), BN_CLICKED,
            PostMessage);//获取控件消息,送入消息队列中去
        SetForegroundWindow(hwnd);//将创建指定窗口的现场带到前台并激活窗口
        break;
    }

    return(FALSE);
}

//查阅windowsx.h定义FORWARD_WM_INITDIALOG
BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) {

    chSETDLGICONS(hwnd, IDI_ERRORSHOW);//添加图标

    // Don't accept error codes more than 5 digits long
    Edit_LimitText(GetDlgItem(hwnd, IDC_ERRORCODE), 5);//显示可以输入到编辑器中的数字长度
    //查看命令行传递的错误码
    // Look up the command-line passed error number
    SendMessage(hwnd, ESM_POKECODEANDLOOKUP, lParam, 0);
    return(TRUE);
}


///


void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) {
          
    switch (id) {

    case IDCANCEL:
        EndDialog(hwnd, id);
        break;

    case IDC_ALWAYSONTOP:
        //将窗口设置为最顶层
        SetWindowPos(hwnd, IsDlgButtonChecked(hwnd, IDC_ALWAYSONTOP)
            ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
        break;

    case IDC_ERRORCODE:
        //启用或禁用窗口或空间的鼠标和键盘输入
        EnableWindow(GetDlgItem(hwnd, IDOK), Edit_GetTextLength(hwndCtl) > 0);
        break;

    case IDOK:
        // Get the error code
        //获取错误代码
        DWORD dwError = GetDlgItemInt(hwnd, IDC_ERRORCODE, NULL, FALSE);

        HLOCAL hlocal = NULL;   // Buffer that gets the error message string
        //使用错误的系统语言环境,参数系统默认的语言
        // Use the default system locale since we look for Windows messages.
        // Note: this MAKELANGID combination has 0 as value
        //注意:这个MAKELANGID组合的置为0
        DWORD systemLocale = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL);
        //获取错误代码的文本描述,返回存储在输出缓冲区中的TCHAR数
        // Get the error code's textual description
        BOOL fOk = FormatMessage(
            FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS |
            FORMAT_MESSAGE_ALLOCATE_BUFFER,
            NULL, dwError, systemLocale,
            (PTSTR)&hlocal, 0, NULL);

        if (!fOk) {
            //是否网络相关的错误
            // Is it a network-related error?
            HMODULE hDll = LoadLibraryEx(TEXT("netmsg.dll"), NULL,
                DONT_RESOLVE_DLL_REFERENCES);

            if (hDll != NULL) {
                fOk = FormatMessage(
                    FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS |
                    FORMAT_MESSAGE_ALLOCATE_BUFFER,
                    hDll, dwError, systemLocale,
                    (PTSTR)&hlocal, 0, NULL); 
                FreeLibrary(hDll);
            }
        }

        if (fOk && (hlocal != NULL)) {
            //显示错误信息
            SetDlgItemText(hwnd, IDC_ERRORTEXT, (PCTSTR)LocalLock(hlocal));
            LocalFree(hlocal);
        }
        else {
            SetDlgItemText(hwnd, IDC_ERRORTEXT,
                TEXT("No text found for this error number."));
        }

        break;
    }
}





 End of File //



程序运行情况:
1,输入2.
2,输入1001.
3,输入1000.
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第2章 Unicode 11

2.1 字符集 11

2.1.1 单字节与双字节字符集 11
safeString.cpp
```cpp
/*
2_SafeString.cpp
自定义错误处理函数
*/
#include<tchar.h>
#include<stdlib.h>
#include<Windows.h>
#include<stdint.h>
#include<crtdbg.h>//要用到_CrtSetReportMode函数
#include<strsafe.h>//放到最后一个
//自定义的函数调用失败的处理程序----只有放在Debug版本才有效,Release中所有的参数将被传入NULL或者0.
//当某个函数调用失败,系统会调用该函数,同时出入“错误描述文本”,出错的函数名称,文件名及出错所在行
void InvalidParameterHandl(PCTSTR expression, PCTSTR function, PCTSTR file, unsigned int line, uintptr_t)
{
	_tprintf(_T("expression %s,\nfunction %s,\nfile %s,\nline %d\n"), expression, function, file, line);
}
int _tmain()
{
	_CrtSetReportMode(_CRT_ASSERT, 0);//禁用“调试失败断言”对话框
	TCHAR szBefore[5] = { _T('B'),_T('B'), _T('B'), _T('B'), '\0' };
	TCHAR szBuffer[10]= { _T('-'),_T('-'), _T('-'), _T('-'),_T('-'),
						  _T('-'),_T('-'), _T('-'),'\0'};
	TCHAR szAffer[5] = { _T('A'),_T('A'), _T('A'), _T('A'), '\0' };

	//注册函数调用失败的处理程序
	_set_invalid_parameter_handler(InvalidParameterHandl);

	//源字符串10个字符(不含\0),目标缓冲区,只能容纳9个,会出错(发生错误时,不弹出Debug
	//Assertion Failure对话框而是调用自定义的InvalidParameterHandle函数
	errno_t ret = _tcscpy_s(szBuffer, _countof(szBuffer), _T("0123456789"));
	system("pause");
	return 0;
}

#### 2.1.2   Unicode:宽字节字符集	12

```cpp
/*
2_UpperAndLower.cpp
大小写转换测试程序
2024.07.16
*/
#include <Windows.h>
#include <tchar.h>
#include <locale.h>
int _tmain()
{
	//C库Unicode函数,必须这样写,否则会乱码
	_tsetlocale(LC_ALL, TEXT("chs"));
	//大小写转换
	TCHAR chLower[] = _T("abc αβγδεζηθμνξο");
	TCHAR* chUpper = NULL;
	_tprintf(_T("Lower Char = %s\n"), chLower);

	//转换为大写
	chUpper = CharUpper(chLower);
	_tprintf(_T("Upper Char = %s\n"), chUpper);
	_tprintf(_T("Upper Char Address = 0x%08X,\nLower Char Address = 0x%08X\n"), (UINT)chUpper, (UINT)chLower);
	CharLower(chUpper);
	_tprintf(_T("Convert Lower Char = %s\n"), chLower);
	
	//含有全角
	TCHAR pString[] = _T("张三李四王二麻子ABCabcde123 4 5 6 0");
	int iLen = lstrlen(pString);//字符个数
	TCHAR* pNext = pString;//第一个字符
	TCHAR* pPrev = pString + sizeof(pString) / sizeof(pString[0]) - 1;
	_tprintf(_T("\nOrigin String = %s\n"), pString);

	for (int i = 0; i < iLen; i++)
	{
		pPrev = CharPrev(pString, pPrev);
		_tprintf(_T("Next Char = '%c'\tPrev Char = '%c' "), *pNext, *pPrev);
		if (IsCharAlpha(*pNext))
		{
			_tprintf(_T("'%c' is Alpha"), *pNext);
		}
		else if (IsCharAlphaNumeric(*pNext))
		{
			_tprintf(_T("'%c' is Alpha Numberic"), *pNext);
		}
		else
		{
			_tprintf(_T("'%c' is Unkown Type"), *pNext);
		}

		pNext++;
		_tprintf(_T("\n"));
	}
	
	return 0;
}

Unicode字符长度的问题。这里我们先用一个例子

自定义错误处理函数
*/
#include<tchar.h>
#include<stdlib.h>
#include<Windows.h>
#include<stdint.h>
#include<crtdbg.h>//要用到_CrtSetReportMode函数
#include<strsafe.h>//放到最后一个
//自定义的函数调用失败的处理程序----只有放在Debug版本才有效,Release中所有的参数将被传入NULL或者0.
//当某个函数调用失败,系统会调用该函数,同时出入“错误描述文本”,出错的函数名称,文件名及出错所在行
void InvalidParameterHandl(PCTSTR expression, PCTSTR function, PCTSTR file, unsigned int line, uintptr_t)
{
	_tprintf(_T("expression %s,\nfunction %s,\nfile %s,\nline %d\n"), expression, function, file, line);
}
int _tmain()
{
	_CrtSetReportMode(_CRT_ASSERT, 0);//禁用“调试失败断言”对话框
	TCHAR szBefore[5] = { _T('B'),_T('B'), _T('B'), _T('B'), '\0' };
	TCHAR szBuffer[10]= { _T('-'),_T('-'), _T('-'), _T('-'),_T('-'),
						  _T('-'),_T('-'), _T('-'),'\0'};
	TCHAR szAffer[5] = { _T('A'),_T('A'), _T('A'), _T('A'), '\0' };

	//注册函数调用失败的处理程序
	_set_invalid_parameter_handler(InvalidParameterHandl);

	//源字符串10个字符(不含\0),目标缓冲区,只能容纳9个,会出错(发生错误时,不弹出Debug
	//Assertion Failure对话框而是调用自定义的InvalidParameterHandle函数
	errno_t ret = _tcscpy_s(szBuffer, _countof(szBuffer), _T("0123456789"));
	system("pause");
	return 0;
}

//自定义的函数调用失败的处理程序----只有放在Debug版本才有效,Release中所有的参数将被传入NULL或者0.
//当某个函数调用失败,系统会调用该函数,同时出入“错误描述文本”,出错的函数名称,文件名及出错所在行
源字符串10个字符(不含\0),目标缓冲区,只能容纳9个,会出错(发生错误时,不弹出Debug
//Assertion Failure对话框而是调用自定义的InvalidParameterHandle函数
在这里插入图片描述

这里看出来是缓冲区大小不够,我们改一下内容大小,这里改为

errno_t ret = _tcscpy_s(szBuffer, _countof(szBuffer), _T("012345678"));

不在报错。
在这里插入图片描述

2.2 为什么使用Unicode 13

2.3 Windows 2000与Unicode 13

2.4 Windows 98与Unicode 13

2.5 Windows CE与Unicode 14

2.6 需要注意的问题 14

2.7 对COM的简单说明 14

2.8 如何编写Unicode源代码 15

2.8.1 C运行期库对Unicode的支持 15
2.8.2 Windows定义的Unicode数据类型 17
2.8.3 Windows中的Unicode函数和ANSI
       函数	17
2.8.4 Windows字符串函数 19

2.9 成为符合ANSI和Unicode的应用程序 19

2.9.1 Windows字符串函数 19
2.9.2 资源
2.9.3 确定文本是ANSI文本还是Unicode
2.9.4 在Unicode与ANSI之间转换字符串 23

第3章 内核对象 27

在介绍Windows API的时候,首先要讲述内核对象以及它们的句柄。本章将要介绍一些比较抽象的概念,在此并不讨论某个特定内核对象的特性,相反只是介绍适用于所有内核对象的
特性。首先介绍一个比较具体的问题,准确地理解内核对象对于想要成为一名 Windows软件开发能手的人来说是至关重要的。内核对象可以供系统和应用程序使用来管理各种各样的资源,比如进程、线程和文件等。本章讲述的概念也会出现在本书的其他各章之中。但是,在你开始使用实际的函数来操作内核对象之前,是无法深刻理解本章讲述的部分内容的。因此当阅读本书的其他章节时,可能需要经常回过来参考本章的内容。

3.1 什么是内核对象 27

作为一个Windows软件开发人员,你经常需要创建、打开和操作各种内核对象。系统要创建和操作若干类型的内核对象,比如存取符号对象、事件对象、文件对象、文件映射对象、I0完成端口对象、作业对象、信箱对象、互斥对象、管道对象、进程对象、信标对象、线程对象和等待计时器对象等。这些对象都是通过调用函数来创建的。例如,CreateFileMapping函数可使系统能够创建一个文件映射对象。每个内核对象只是内核分配的一个内存块,并且只能由该内核访问。该内存块是一种数据结构,它的成员负责维护该对象的各种信息。有些数据成员(如安全性描述符、使用计数等)在所有对象类型中是相同的,但大多数数据成员属于特定的对象类型。例如,进程对象有一个进程ID、一个基本优先级和一个退出代码,而文件对象则拥有一个字节位移、一个共享模式和一个打开模式。
由于内核对象的数据结构只能被内核访问,因此应用程序无法在内存中找到这些数据结构并直接改变它们的内容。Microsof规定了这个限制条件,目的是为了确保内核对象结构保持状态的一致。这个限制也使Microsoft能够在不破坏任何应用程序的情况下在这些结构中添加、删
除和修改数据成员。如果我们不能直接改变这些数据结构,那么我们的应用程序如何才能操作这些内核对象呢?解决办法是,Windows提供了一组函数,以便用定义得很好的方法来对这些结构进行操作。这些内核对象始终都可以通过这些函数进行访问。当调用一个用于创建内核对象的函数时,该函数就返回一个用于标识该对象的句柄。该句柄可以被视为一个不透明值,你的进程中的任何线程都可以使用这个值。将这个句柄传递给 Windows的各个函数,这样,系统就能知道你想操
作哪个内核对象。本章后面还要详细讲述这些句柄的特性。为了使操作系统变得更加健壮,这些句柄值是与进程密切相关的。因此,如果将该句柄值传递给另一个进程中的一个线程(使用某种形式的进程间的通信)那么这另一个进程使用你的进程的句柄值所作的调用就会失败。在3.3节“跨越进程边界共享内核对象”中,将要介绍 3种机制,使多个进程能够成功地共享单个内核对象。

3.1.1 内核对象的使用计数 27

内核对象由内核所拥有,而不是由进程所拥有。换句话说,如果你的进程调用了一个创建内核对象的函数,然后你的进程终止运行,那么内核对象不一定被撤消。在大多数情况下,对象将被撤消,但是如果另一个进程正在使用你的进程创建的内核对象,那么该内核知道,在另一个进程停止使用该对象前不要撤消该对象,必须记住的是,内核对象的存在时间可以比创建该对象的进程长。
内核知道有多少进程正在使用某个内核对象,因为每个对象包含一个使用计数。使用计数是所有内核对象类型常用的数据成员之一。当一个对象刚刚创建时,它的使用计数被置为1 s然后,当另一个进程访问一个现有的内核对象时,使用计数就递增 1。当进程终止运行时,内核就自动确定该进程仍然打开的所有内核对象的使用计数。如果内核对象的使用计数降为0.内核就撤消该对象。这样可以确保在没有进程引用该对象时系统中不保留任何内核对象。

3.1.2 安全性 28

内核对象能够得到安全描述符的保护。安全描述符用于描述谁创建了该对象,谁能够访问或使用该对象,谁无权访问该对象。安全描述符通常在编写服务器应用程序时使用,如果你编写客户机端的应用程序,那么可以忽略内核对象的这个特性。
Windows 98 根据原来的设计,Windows98并不用作服务器端的操作系统。为此Microsoft公司没有在Windows 98中配备安全特性。不过,如果你现在为 Windows 98设计软件,在实现你的应用程序时仍然应该了解有关的安全问题,并且使用相应的访问信息,以确保它能在Windows 2000上正确地运行
用于创建内核对象的函数几乎都有一个指向 SECURITY ATTRIBUTES结构的指针作为其参数,下面显示了CreateFileMapping函数的指针:

HANDLE CreateFileMapping(
HANDLE hFile.
PSECURITY ATTRIBUTES pSa.
DWORD fiProtect.
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow.
PCTSTR pszName);

大多数应用程序只是为该参数传递 NULL,这样就可以创建带有默认安全性的内核对象默认安全性意味着对象的管理小组的任何成员和对象的创建者都拥有对该对象的全部访问权而其他所有人均无权访问该对象。但是,可以指定一个SECURITY_ATTRIBUTES结构,对它进行初始化,并为该参数传递该结构的地址。SECURITY_ATTRIBUTES结构类似下面的样子:

typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength:
LPVOID ipSecurityDescriptor;
B00l bInheritHandle:
}SECURITY ATTRIBUTES;

尽管该结构称为SECURITY ATTRIBUTES,但是它包含的与安全性有关的成员实际上只有一个,即IpSecurityDescriptor。如果你想要限制人们对你创建的内核对象的访问,必须创建一个安全性描述符,然后像下面这样对SECURITY ATTRIBUTES结构进行初始化:

SECURITY_ATTRIBUTES Sa;
sa.nLength=sizeof(sa):// Used for versioningsa.1pSecurityDescriptor=pSD;// Address of an initialized SDsa.bInheritHandle=FALSE:// Discussed laterHANDLE hFileMapping=CreateFileMapping(INVALID_HANDLE_VALUE, &sa.
PAGE_READWRITE,01024"MyFileMapping"):

由于bInheritHandle这个成员与安全性毫无关系,因此准备推迟到本章后面部分继承性一节
中再介绍bInheritHandle这个成员。当你想要获得对相应的一个内核对象的访问权(而不是创建一个新对象)时,必须设定要对该对象执行什么操作。例如,如果想要访问一个现有的文件映射内核对象,以便读取它的数那么应该调用下面这个OpenfileMapping函数:活,

HANDLE hFileMapping=0penFileMapping(FILE_MAP_READ, FALSE"MyFileMapping"):

通过将FILE_MAP_READ作为第一个参数传递给0penFileMapping,指明打算在获得对该文件映象的访问权后读取该文件,0penFileMapping函数在返回一个有效的句柄值之前,首先丸行一次安全检查。如果(已登录用户)被允许访问现有的文件映射内核对象,0penFileMapping就返回一个有效的句柄。但是,如果被拒绝访问该对象, OpenFileMapping将返回NULL,而调用GetLastError函数则返回5(ERROR_ACCESS_DENIED),同样,大多数应用程多并不使用该安全性,因此将不进一步讨论这个问题。Windows 98 虽然许多应用程序不需要考虑安全性问题,但是Windows的许多函数要求传递必要的安全访问信息。为Windows98设计的若干应用程序在Windows2000上无法正确地运行,因为在实现这些应用程序时没有对安全问题给于足够的考虑。例如,假设一个应用程序在开始运行时要从注册表的子关键字中读取一些数据。为了正确地进行这项操作,你的代码应该调用 RegOpenKeyEx,传递KEY_QUERYVALUE,以便获得必要的访问权。
但是,许多应用程序原先是为Windows 98开发的,当时没有考虑到运行Windows2000的需要。由于Windows 98没有解决注册表的安全问题,因此软件开发人员常常要调用RegOpenKeyEx函数,传递KEY_AII_ACCESS,作为必要的访问权。开发人员这样做的原因是,它是一种比较简单的解决方案,意味着开发人员不必考虑究竟需要什么访问权。问题是注册表的子关键字可以被用户读取,但是不能写入。因此,当该应用程序现在放在Windows 2000上运行时,用KEY ALL_ACCESS调用RegOpenKeyEx就会失败,而且,没有相应的错误检查方法,应用程序的运行就会产生椗媤釘Q乞茧可霊罨驩预料的结果。
如果开发人员想到安全问题,把KEY_ALL_ACCESS改为KEY_QUERY_VALUE,则该产品可适用于两种操作系统平台。
开发人员的最大错误之一就是忽略安全访问标志。使用正确的标志会使最初为
Windows 98 设计的应用程序更易于向Windows 2000 转换。除了内核对象外,你的应用程序也可以使用其他类型的对象,如菜单、窗口、鼠标光标刷子和字体等。这些对象属于用户对象或图形设备接口(GDI)对象,而不是内核对象。当初次着手为Windows编程时,如果想要将用户对象或 GDI对象与内核对象区分开来,你一定会感到不知所措。比如,图标究竟是用户对象还是内核对象呢?若要确定一个对象是否属于内核对象,最容易的方法是观察创建该对象所用的函数。创建内核对象的所有函数几乎都有一个参数你可以用来设定安全属性的信息,这与前面讲到的 CreateFileMapping函数是相同的。用于创建用户对象或GDI对象的函数都没有PSECURITY ATTRIBUTES参数。例如,让我们来看一看下面这个CreateIcon函数:

HICON Createlcon(
HINSTANCE hinst,
int nwidth,
int nHeight,
BYTE CPlanes
BYTE cBitsPixel,
CONST BYTE *pbANDbits,
CONST BYTE *pbXORbits);

3.2 进程的内核对象句柄表 30

当一个进程被初始化时,系统要为它分配一个句柄表。该句柄表只用于内核对象不用于用户对象或GDI对象。句柄表的详细结构和管理方法并没有具体的资料说明。通常我并不介绍操作系统中没有文档资料的那些部分。不过,在这种情况下,我会进行例外处理,因为,作为一个称职的Windows程序员,必须懂得如何管理进程的句柄表。由于这些信息没有文档资料,因此不能保证所有的详细信息都正确无误,同时,在Windows2000、Windows98和WindowsCE中,它们的实现方法是不同的。为此,请认真阅读下面介绍的内容以加深理解,在此不学习系统是如何进行操作的。表3-1显示了进程的句柄表的样子。可以看到,它只是个数据结构的数组。每个结构都包-个指向内核对象的指针、一个访问屏蔽和一些标志。
在这里插入图片描述

3.2.1 创建内核对象 30

当进程初次被初始化时,它的句柄表是空的。然后,当进程中的线程调用创建内核对象的函数时,比如CreateFileMapping,内核就为该对象分配一个内存块,并对它初始化。这时,内核对进程的句柄表进行扫描,找出一个空项。由于表 3-1中的句柄表是空的,内核便找到索引1位置上的结构并对它进行初始化。该指针成员将被设置为内核对象的数据结构的内存地址,访问屏蔽设置为全部访问权,同时,各个标志也作了设置(关于标志,将在本章后面部分的继承性一节中介绍)。
下面列出了用于创建内核对象的一些函数(但这决不是个完整的列表):

HANDLE CreateThread(
PSECURITY_ATTRIBUTES pSa
DWORD dwStackSize,
LPTHREAD_START_ROUTINE pfnStartAddr,
PVOID pvParam,
DWORD dwCreationFlags,
PDWORD pdwThreadId);

HANDLE CreateFile(
PCTSTR pszFileName.
DWORD dwDesiredAccess
DWORD dwShareMode,
PSECURITY_ATTRIBUTES pSa,
DWORD dwCreationDistribution,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile):

HANDLE CreateFileMapping(
HANDLE hFile,
PSECURITY_ATTRIBUTES Sa,
DWORD fiProtect.
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
PCTSTR pszName);

HANDLE CreateSemaphore(
PSECURITY_ATTRIBUTES pSa,
LONG lInitialCount,
LONG MaximumCount,
PCTSTR pszName);

用于创建内核对象的所有函数均返回与进程相关的句柄,这些句柄可以被在相同进程中运行的任何或所有线程成功地加以使用。该句柄值实际上是放入进程的句柄表中的索引,它用于标识内核对象的信息存放的位置。因此当调试一个应用程序并且观察内核对象句柄的实际值时会看到一些较小的值,如1,2等。请记住,句柄的含义并没有记入文档资料,并且可能随时变更。实际上在Windows 2000中,返回的值用于标识放入进程的句柄表的该对象的字节数,而不
是索引号本身。每当调用一个将内核对象句柄接受为参数的函数时,就要传递由一个 Create*&函数返回的值。从内部来说,该函数要查看进程的句柄表,以获取要生成的内核对象的地址,然后按定义
得很好的方式来生成该对象的数据结构。如果传递了一个无效索引(句柄),该函数便返回失败,而GetLastError则返回 6(ERROR_INVALID_HANDLE)。由于句柄值实际上是放入进程句柄表的索引,因此这些句柄是与进程相关的,并且不能由其他进程成功地使用。
如果调用一个函数以便创建内核对象,但是调用失败了,那么返回的句柄值通常是0(NULL)。发生这种情况是因为系统的内存非常短缺,或者遇到了安全方面的问题。不过有少数函数在运行失败时返回的句柄值是-1(INVALID_HANDLE_VALUE)。例如,如果CreateFile未能打开指定的文件,那么它将返回INVALID_HANDLE_VALUE,而不是返回NULL。当查看创建内核对象的函数返回值时,必须格外小心。特别要注意的是,只有当调用CreateFile函数时才能将该值与INVALID HANDLE _VALUE进行比较。下面的代码是不正确的:

HANDLE hMutex=CreateMutex(.);
if(hMutex== INVALID HANDLE VALUE){
// We will never execute this code because
//CreateMutex returns NULL if it fails.

同样,下面的代码也不正确

HANDLE hFile=CreateFile(..);
if(hFie == NULL){
// We will never execute this code because CreateFile
//returnS INVALID_HANDLE_VALUE(-1)if it faiis.
3.2.2 关闭内核对象 32

无论怎样创建内核对象,都要向系统指明将通过调用CloseHandle来结束对该对象的操作:B00L CoseHand1e(HANDLE hobj):
该函数首先检查调用进程的句柄表,以确保传递给它的索引(句柄)用于标识一个进程实际上无权访问的对象。如果该索引是有效的,那么系统就可以获得内核对象的数据结构的地址,并可确定该结构中的使用计数的数据成员。如果使用计数是0,该内核便从内存中撤消该内核对象。如果将一个无效句柄传递给 CloseHandle,将会出现两种情况之一。如果进程运行正常CloseHandle返回FALSE,而GetLastError则返回ERROR INVALID HANDLE。如果进程正在排除错误,系统将通知调试程序,以便能排除它的错误。在CloseHandle返回之前,它会清除进程的句柄表中的项目,该句柄现在对你的进程已经无效,不应该试图使用它。无论内核对象是否已经撤消,都会发生清除操作。当调用C1oseHandle函数之后,将不再拥有对内核对象的访问权,不过,如果该对象的使用计数没有递减为0,那么该对象尚未被撤消。这没有问题,它只是意味着一个或多个其他进程正在使用该对象。当其他进程停止使用该对象时(通过调用 CloseHandle),该对象将被撤消。假如忘记调用 CloseHandle函数,那么会不会出现内存泄漏呢?答案是可能的,但是也不一定。在进程运行时,进程有可能泄漏资源(如内核对象)。但是,当进程终止运行时,操作系统能够确保该进程使用的任何资源或全部资源均被释放,这是有保证的。对于内核对象来说系统将执行下列操作:当进程终止运行时,系统会自动扫描进程的句柄表。如果该表拥有任何无效项目(即在终止进程运行前没有关闭的对象),系统将关闭这些对象句柄。如果这些对象中的任何对象的使用计数降为0,那么内核便撤消该对象。因此,应用程序在运行时有可能泄漏内核对象,但是当进程终止运行时,系统将能确保所有内容均被正确地清除。另外,这个情况适用于所有对象、资源和内存块,也就是说,当进程终止运行时,系统将保证进程不会留下任何对象。

3.3 跨越进程边界共享内核对象 32

3.3.1 对象句柄的继承性 32
3.3.2 改变句柄的标志 35
3.3.3 命名对象 36

windows核心编程:第3章内核对象防止多开

`
windows核心编程:第3章内核对象防止多开

文章目录


防止多开

实现的原理:内核对象创建的时候不能重名(GUID)
重名是不允许的。我们利用这一个规则,来防止多开

3_Singleton.cpp

/*
* 3:内核对象
利用互斥量对象实现

*/
#include <stdio.h>
#include <Windows.h>

int main()
{
	//利用GUID生成唯一的锁名,放置内核对象重名
	HANDLE h = CreateMutex(NULL, FALSE,
		TEXT("{349210d3-ef54-4ec9-8313-9f47435d785d}"));

	if (GetLastError() == ERROR_ALREADY_EXISTS)
	{
		CloseHandle(h);
		return 0;
	}

	printf("单例程序正在运行中...\n");
	//为了演示,这里暂停一下cmd输出窗口
	system("pause");

	CloseHandle(h);
	return 0;
}

总结:
1,利用互斥量防止多开
2,这种方法,很容易被破解,只需要修改GUID就能破解

3,对上面的代码进行改进防止ddos攻击

/*
展示了如何利用专有命名空间,以一种更安全的方式来实现单例应用程序。Ddos攻击
*/

#include "CmnHdr.h" //编译配置文件
#include <windowsx.h>
#include <tchar.h>
#include "Resource.h"
#include <sddl.h> //for SID mamagment此表头由安全和身份使用
#include <strsafe.h> 


HWND g_hDlg;//对话框句柄
//互斥量,边界和命名空间,用于检测以前运行的实例
HANDLE g_hSingleton = NULL;
HANDLE g_hBoundary = NULL;
HANDLE g_hNamespace = NULL;

//跟踪命名空间是否被创建和打开以进行清理
BOOL g_bNamespaceOpened = FALSE;

//边界和私有命名空间的名称
PCTSTR g_szBoundary = TEXT("3-Boundary");
PCTSTR g_szNamespace = TEXT("3-Namespace");
//获取编辑控制句柄
#define DETAILS_CTRL GetDlgItem(g_hDlg, IDC_EDIT_DETAILS)

INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam);
VOID Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtrl, UINT codeNotify);
VOID CheckInstances();
void AddText(PCTSTR pszFormat, ...);

void ShowErrMsg()
{
	LPVOID lpMsgBuf;
	DWORD dw = GetLastError();

	FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER |
		FORMAT_MESSAGE_FROM_SYSTEM,
		NULL,
		dw,
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPTSTR)&lpMsgBuf,
		0,
		NULL);

	MessageBox(NULL, (LPCWSTR)lpMsgBuf, L"系统错误", MB_OK | MB_ICONSTOP);

	LocalFree(lpMsgBuf);
}
void AddText(PCTSTR pszFormat, ...) {

	va_list argList;
	va_start(argList, pszFormat);

	TCHAR sz[20 * 1024];

	Edit_GetText(DETAILS_CTRL, sz, _countof(sz));
	_vstprintf_s(
		_tcschr(sz, TEXT('\0')), _countof(sz) - _tcslen(sz),
		pszFormat, argList);
	Edit_SetText(DETAILS_CTRL, sz);
	va_end(argList);
}

//核心代码
VOID CheckInstances()
{
	//第一步:创建boundary descriptor
	g_hBoundary = CreateBoundaryDescriptor(g_szBoundary, 0);
	//第二步:创建与“”组对应的SID
	BYTE localAdminSID[SECURITY_MAX_SID_SIZE];//安全描述符SID值得最大长度。目前是68
	PSID pLocalAdminSID = &localAdminSID;
	DWORD cbSID = sizeof(localAdminSID);
	//常见一个用于预定义别名SID,NULL表示本地计算机
	if (!CreateWellKnownSid(
		WinBuiltinAdministratorsSid, NULL, pLocalAdminSID, &cbSID))//指定与管理员匹配得SID
	{
		AddText(TEXT("AddSIDToBoundaryDescriptor failed:%u\r\n"), GetLastError());
		 
		return;
	}

	//第三步:将本地管理SID关联到边界描述符
	//---->仅用于管理员用户下运行的应用程序
	//将能够访问同一个命名空间的内核对象
	if (!AddSIDToBoundaryDescriptor(&g_hBoundary, pLocalAdminSID))
	{
		AddText(TEXT("AddSIDToBoundaryDescriptor failed:%u\r\n"), GetLastError());
		return;
	}

	//第四步:仅“Local Adminstators"创建namespace
	SECURITY_ATTRIBUTES sa;//安全描述符
	sa.nLength = sizeof(sa);
	sa.bInheritHandle = FALSE;//新进程不会继承句柄
	//将一个安全描述符格式的字符串抓好换成一个有效的安全描述符结构
	//安全描述符字符串的格式:ACE strings
	//语法:ace_type;ace_flags;rights;object_guid;inherit_object_guid;account_side;(resource_attribute
	//"D"访问拒绝ace类型,"A"允许访问ace类型 “GA”所有泛型,“BA”内置管理元
	if (!ConvertStringSecurityDescriptorToSecurityDescriptor(
		TEXT("D:(A;;GA;;;BA)"), SDDL_REVISION_1, &sa.lpSecurityDescriptor, NULL))
	{
		AddText(TEXT("Security Descriptor creation failed:%u\r\n"), GetLastError());
		return;
	}

	//创建一个私有的命名空间
	g_hNamespace = CreatePrivateNamespace(&sa, g_hBoundary, g_szNamespace);

	//不要忘记为为安全描述符释放内存
	LocalFree(sa.lpSecurityDescriptor);

	//查看私有namespace的创建结果
	DWORD dwLastError = GetLastError();
	if (g_hNamespace == NULL)
	{
		//如果访问被拒绝,则不做任何事情//--->此代码必须在本地管理员账户下运行
		if (dwLastError == ERROR_ACCESS_DENIED)
		{
			AddText(TEXT("Access denied when creating the namespace.\r\n"));
			AddText(TEXT("    You must be running as adminstror,\r\n\r\n"));
		}
		else 
		{
			if (dwLastError == ERROR_ALREADY_EXISTS)
			{
				//如果另一个实例已经创建了命名空间,我们需要打开他
				AddText(TEXT("CreateprivateNamespace failed:%u\r\n"), dwLastError);
				g_hNamespace = OpenPrivateNamespace(g_hBoundary, g_szNamespace);
				if (g_hNamespace == NULL) {
					AddText(TEXT("    and OpenPrivateNamespace failed:%u\r\n"), dwLastError);
					return;
				}
				else {
					g_bNamespaceOpened = TRUE;
					AddText(TEXT("    but OpenPrivateNamespace succeeded \r\n\r\n"));
				}
			}
			else {
				AddText(TEXT("Unexpected error occured:%u\r\n\r\n"), dwLastError);
				return;
			}
		}
	}
	
	//第五步:尝试创建一个带有名称的互斥对象
	//基于私有命名空间
		TCHAR szMuxteName[64];
		StringCchPrintf(szMuxteName, _countof(szMuxteName), TEXT("%s\\%s"),
			g_szNamespace, TEXT("Singleton"));
		g_hSingleton = CreateMutex(NULL, FALSE, szMuxteName);
		/*
		if (GetLastError()!=0)
		{
			ShowErrMsg();
		}
		*/
		if (dwLastError == ERROR_ALREADY_EXISTS)
		{
			//这个singleton对象已经有一个实例了。
			ShowErrMsg();
			AddText(TEXT("Another instance of Singleton is running:\r\n"));
			AddText(TEXT("-->Impossible to access application features无法访问应用程序特征。\r\n"));
		}
		else
		{
			//第一次创建Singleton对象
			AddText(TEXT("First instance of Singleton:\r\n"));
			AddText(TEXT("--> Access application features now.\r\n"));
		}			 
}


BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
{
	chSETDLGICONS(hwnd, IDI_SINGLETON);
	//跟踪主对话框装口句柄
	g_hDlg = hwnd;
	//检查是否有其他实例正在运行
	CheckInstances();
	return(TRUE);
}
VOID Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtrl, UINT codeNotify)
{
	switch (id)
	{
	case IDOK:
	case IDCANCEL:
		EndDialog(hwnd, id);
		break;
	}
}
INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
		chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand);
		chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog);
	}
	return FALSE;
}
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
	//展开传递的参数和表达式。其目的是避免编译器关于未引用参数的警告
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);

	//创建对话框窗口
	DialogBox(hInstance, MAKEINTRESOURCE(IDD_SINGLETON), NULL, Dlg_Proc);

	//不要忘记清理和释放内核资源
	if (g_hSingleton != NULL)
	{
		CloseHandle(g_hSingleton);
	}
	if (g_hNamespace != NULL)
	{
		 //关闭一个打开的命名空间句柄
		if (g_bNamespaceOpened)
		{
			ClosePrivateNamespace(g_hNamespace,0);//已打开--->关闭
		}
		else
		{
			ClosePrivateNamespace(g_hNamespace, PRIVATE_NAMESPACE_FLAG_DESTROY);//未打开,销毁
		}
	}
	if (g_hBoundary != NULL)
	{
		DeleteBoundaryDescriptor(g_hBoundary);
	}
	return 0;
}


在这里插入图片描述

3.3.4 终端服务器的名字空间 39
3.3.5 复制对象句柄 39

第二部分 编程的具体方法

第4章 进程 45

4.1 编写第一个Windows应用程序 46

4.1.1 进程的实例句柄 49
4.1.2 进程的前一个实例句柄 50
4.1.3 进程的命令行 50
4.1.4 进程的环境变量 51
4.1.5 进程的亲缘性 54
4.1.6 进程的错误模式 54
4.1.7 进程的当前驱动器和目录 54
4.1.8 进程的当前目录 55
4.1.9 系统版本 56

4.2 CreateProcess函数 58

4.2.1 pszApplicationName和
       pszCommandLine	59
4.2.2 psa Process、psa Thread和
       binherit Handles	60
4.2.3 fdwCreate 62
4.2.4 pvEnvironment 64
4.2.5 pszCurDir 64
4.2.6 psiStartInfo 64
4.2.7 ppiProcInfo 67

4.3 终止进程的运行 69

4.3.1 主线程的进入点函数返回 69
4.3.2 ExitProcess函数 69
4.3.3 TerminateProcess函数 70
4.3.4 进程终止运行时出现的情况 71

4.4 子进程 72

4.5 枚举系统中运行的进程 73

第5章 作业 91

5.1 对作业进程的限制 93

5.2 将进程放入作业 99

5.3 终止作业中所有进程的运行 99

5.4 查询作业统计信息 100

5.5 作业通知信息 103

5.6 JobLab示例应用程序 104

/*
05-ProcessInfo.cpp
将一个进程放入一个作业中,以限制此进程具体能够做那些事情
*/

#include <Windows.h>
#include <tchar.h>
#include <strsafe.h>

void StartRestrictedProcess();

int WINAPI WinMain(HINSTANCE hinstExe, HINSTANCE, LPSTR pszCmdLine, int)
{
	StartRestrictedProcess();
}



void StartRestrictedProcess()
{
	//检查当前进程是否已经与一个作业关联,如果是这种情况,没有办法切换到另一个作业
	BOOL bInJob = FALSE;
	/* */
	IsProcessInJob(GetCurrentProcess(), NULL, &bInJob);
	if (bInJob)
	{
		MessageBox(NULL, TEXT("Process already in job"),
			TEXT(" "), MB_ICONINFORMATION | MB_OK);
		return;
	}
	
	//第一步:创建一个作业内核对象
	HANDLE hjob = CreateJobObject(NULL,//指向SECURITY_ATTRIBUTES结构体的指针,获得默认的安全描述符
		TEXT("Wintellect_RestrictedProcessJob"));

	//第二部:对作业中的流程进行一些限制
	//1,设置一些基本的限制
	JOBOBJECT_BASIC_LIMIT_INFORMATION jobli = { 0 };

	//进程总是运行在空闲的优先级类中
	jobli.PriorityClass = IDLE_PRIORITY_CLASS;
	//作业占用cpu时间不能超过1毫秒= 0.001s
	jobli.PerJobUserTimeLimit.QuadPart = 10000;//1sec以100ns为单位
	//这里是我们希望对作业关联进程施加的唯一的两个限制
	jobli.LimitFlags = JOB_OBJECT_LIMIT_PRIORITY_CLASS | JOB_OBJECT_LIMIT_JOB_TIME;
	SetInformationJobObject(hjob, JobObjectBasicLimitInformation, &jobli, sizeof(jobli));

	//设置UI限制
	JOBOBJECT_BASIC_UI_RESTRICTIONS jobuir;
	jobuir.UIRestrictionsClass = JOB_OBJECT_UILIMIT_NONE;
	//进程不能退出系统
	jobuir.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_EXITWINDOWS;
	//在系统中,进程不能访问USER对象(例如其他窗口)
	jobuir.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_HANDLES;
	SetInformationJobObject(hjob, JobObjectBasicUIRestrictions, &jobuir, sizeof(jobuir));

	//第三步:创建作业中的进程。
	//注意:你必须首先生成进程,然后将进程放置作业中
	STARTUPINFO si = { sizeof(si) };
	PROCESS_INFORMATION pi;
	TCHAR szCmdLine[8];//必须时可以修改的缓冲区
	_tcscpy_s(szCmdLine, _countof(szCmdLine), TEXT("CMD"));
	//CREATE_SUSPENDED:新进程的主线程处于挂起状态创建:EREATE_NEW_CONSOLE;新进程具有新控件
	BOOL bResult =
		CreateProcess(
			NULL, szCmdLine, NULL, NULL, FALSE,
			CREATE_SUSPENDED | CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);

	//第四步:将进程放置在作业中
	//注意:如果这个进程生成任何子进程,则子进程时自动生成同一工作的一部分
	AssignProcessToJobObject(hjob, pi.hProcess);

	//第五步:现在我们可以运行子进程的现场执行代码
	ResumeThread(pi.hThread);
	CloseHandle(pi.hThread);

	//等待进程终止活拨给作业的所有CPU时间已经用完
	HANDLE h[2];
	h[0] = pi.hProcess;
	h[1] = hjob;
	DWORD dw = WaitForMultipleObjects(2, h, FALSE, INFINITE);
	switch (dw-WAIT_OBJECT_0)
	{
	case 0:
	//进程终止。。。
		break;
	case 1:
		//拨给作 业的所有的cpu时间已经用完
		break;
	}

	//第六步:检索指定进程的计时信息
	FILETIME CreationTime;
	FILETIME ExitTime;
	FILETIME KernelTime;
	FILETIME UserTime;
	TCHAR szInfo[MAX_PATH];

	GetProcessTimes(pi.hProcess, &CreationTime, &ExitTime, &KernelTime, &UserTime);
	StringCchPrintf(szInfo, _countof(szInfo),
		TEXT("CreationTime = %u | ExitTime = %u | Kernel = %u | User = %u\n"),
		CreationTime.dwLowDateTime / 10000,
		ExitTime.dwLowDateTime / 10000,
		KernelTime.dwLowDateTime / 1000,
		UserTime.dwLowDateTime / 10000);
	MessageBox(GetActiveWindow(), szInfo, TEXT("Restricted Process times"),
		MB_ICONINFORMATION | MB_OK);
	//清理
	CloseHandle(pi.hProcess);
	CloseHandle(hjob);
}

第6章 线程的基础知识 121

6.1 何时创建线程 121

6.2 何时不能创建线程 122

6.3 编写第一个线程函数 123

6.4 CreateThread函数 124

6.4.1 psa 124
6.4.2 cbStack 124
6.4.3 pfnStartAddr和pvParam 125
6.4.4 fdwCreate 126
6.4.5 pdwThreadID 126

6.5 终止线程的运行 127

6.5.1 线程函数返回 127
6.5.2 ExitThread函数 127
6.5.3 TerminateThread函数 127
6.5.4 在进程终止运行时撤消线程 128
6.5.5 线程终止运行时发生的操作 128

6.6 线程的一些性质 129

6.7 C/C++运行期库的考虑 131

6.7.1 Oops—错误地调用了Create Thread 138
6.7.2 不应该调用的C/C++运行期库函数 138

6.8 对自己的ID概念应该有所了解 139

第7章 线程的调度、优先级和亲缘性 142

7.1 暂停和恢复线程的运行 143

7.2 暂停和恢复进程的运行 144

7.3 睡眠方式 145

7.4 转换到另一个线程 145

7.5 线程的运行时间 146

7.6 运用环境结构 148

7.7 线程的优先级 152

7.8 对优先级的抽象说明 153

7.9 程序的优先级 156

7.9.1 动态提高线程的优先级等级 158
7.9.2 为前台进程调整调度程序 159
7.9.3 Scheduling Lab示例应用程序 160
7.10 亲缘性 167

第8章 用户方式中线程的同步 172

8.1 原子访问:互锁的函数家族 172

8.2 高速缓存行 177

8.3 高级线程同步 178

8.4 关键代码段 180

8.4.1 关键代码段准确的描述 182
8.4.2 关键代码段与循环锁 185
8.4.3 关键代码段与错误处理 185
8.4.4 非常有用的提示和技巧 186

第9章 线程与内核对象的同步 190

9.1 等待函数 191

9.2 成功等待的副作用 194

9.3 事件内核对象 195

9.4 等待定时器内核对象 204

9.4.1 让等待定时器给APC项排队 207
9.4.2 定时器的松散特性 209

9.5 信标内核对象 210

9.6 互斥对象内核对象 211

9.6.1 释放问题 213
9.6.2 互斥对象与关键代码段的比较 214
9.6.3 Queue示例应用程序 214

9.7 线程同步对象速查表 223

9.8 其他的线程同步函数 224

9.8.1 异步设备I/O 224
9.8.2 WaitForInputIdle 224
9.8.3 MsgWaitForMultipleObjects (Ex) 225
9.8.4 WaitForDebugEvent 225
9.8.5 SingleObjectAndWait 226

第10章 线程同步工具包 228

10.1 实现关键代码段:Optex 228

10.2 创建线程安全的数据类型和反信标 239

10.3 单个写入程序/多个阅读程序的保护 251

10.4 实现一个WaitForMultipleExpressions

   函数	259

第11章 线程池的使用 274

11.1 方案1:异步调用函数 275

11.2 方案2:按规定的时间间隔调用函数 277

11.3 方案3:当单个内核对象变为已通知状态

       时调用函数	283

11.4 方案4:当异步I/O请求完成运行时调用

      函数	285

第12章 纤程 287

12.1 纤程的操作 287

12.2 Counter示例应用程序 289

第三部分 内 存 管 理

第13章 Windows的内存结构 299

13.1 进程的虚拟地址空间 299

13.2 虚拟地址空间如何分区 300

13.2.1 NuLL 指针分配的分区—适用于
         Windows 2000和Windows 98	300
13.2.2 MS-DOS/16位Windows 应用程序兼容
         分区—仅适用于Windows 98	301
13.2.3 用户方式分区—适用于Windows 2000
         和Windows 98	301
13.2.4 64 KB禁止进入的分区—仅适用
         于Windows 2000	302
13.2.5 共享的MMF分区—仅适用于
         Windows 98	303
13.2.6 内核方式分区—适用于Windows
         2000和Windows 98	303

13.3 地址空间中的区域 303

13.4 提交地址空间区域中的物理存储器 304

13.5 物理存储器与页文件 304

13.6 保护属性 307

13.6.1 Copy-On-Write 访问 308
13.6.2 特殊的访问保护属性的标志 309

13.7 综合使用所有的元素 309

13.7.1 区域的内部情况 312
13.7.2 与Windows 98地址空间的差别 315

13.8 数据对齐的重要性 319

第14章 虚拟内存 323

14.1 系统信息 323

14.2 虚拟内存的状态 330

14.3 确定地址空间的状态 336

14.3.1 VMQuery函数 337
14.3.2 虚拟内存表示例应用程序 343

第15章 在应用程序中使用虚拟内存 354

15.1 在地址空间中保留一个区域 354

15.2 在保留区域中的提交存储器 355

15.3 同时进行区域的保留和内存的提交 356

15.4 何时提交物理存储器 357

15.5 回收虚拟内存和释放地址空间区域 358

15.5.1 何时回收物理存储器 359
15.5.2 虚拟内存分配的示例应用程序 360

15.6 改变保护属性 368

15.7 清除物理存储器的内容 369

15.8 地址窗口扩展—适用于

         Windows 2000	372

第16章 线程的堆栈 385

16.1 Windows 98下的线程堆栈 387

16.2 C/C++运行期库的堆栈检查函数 389

16.3 Summation示例应用程序 390

第17章 内存映射文件 397

17.1 内存映射的可执行文件和DLL

       文件	397
17.1.1 可执行文件或DLL的多个实例
         不能共享静态数据	398
17.1.2 在可执行文件或DLL的多个实
         例之间共享静态数据	400
17.1.3 AppInst示例应用程序 404

17.2 内存映射数据文件 409

17.2.1 方法 1:一个文件,一个缓存 409
17.2.2 方法 2:两个文件,一个缓存 409
17.2.3 方法 3:一个文件,两个缓存 410
17.2.4 方法 4:一个文件,零缓存 410

17.3 使用内存映射文件 410

17.3.1 步骤 1:创建或打开文件内核
         对象	411
17.3.2 步骤 2:创建一个文件映射内核
         对象	412
17.3.3 步骤 3:将文件数据映射到进程
           的地址空间	414
17.3.4 步骤4:从进程的地址空间中撤消
         文件数据的映像	416
17.3.5 步骤 5和步骤 6:关闭文件映射对象
         和文件对象	417
17.3.6 文件倒序示例应用程序 418

17.4 使用内存映射文件来处理大文件 426

17.5 内存映射文件与数据视图的相关性 427

17.6 设定内存映射文件的基地址 428

17.7 实现内存映射文件的具体方法 429

17.8 使用内存映射文件在进程之间共享

   数据	431

17.9 页文件支持的内存映射文件 431

17.10 稀疏提交的内存映射文件 438

第18章 堆栈 451

18.1 进程的默认堆栈 451

18.2 为什么要创建辅助堆栈 452

18.2.1 保护组件 452
18.2.2 更有效的内存管理 453
18.2.3 进行本地访问 453
18.2.4 减少线程同步的开销 453
18.2.5 迅速释放堆栈 453

18.3 如何创建辅助堆栈 454

18.3.1 从堆栈中分配内存块 455
18.3.2 改变内存块的大小 456
18.3.3 了解内存块的大小 456
18.3.4 释放内存块 457
18.3.5 撤消堆栈 457
18.3.6 用C++程序来使用堆栈 457

18.4 其他堆栈函数 460

第四部分 动态链接库

第19章 DLL基础 463

19.1 DLL与进程的地址空间 464

19.2 DLL的总体运行情况 465

19.3 创建DLL模块 467

19.3.1 输出的真正含义是什么 469
19.3.2 创建用于非Visual C++工具的DLL 471

19.4 创建可执行模块 472

19.5 运行可执行模块 474

第20章 DLL的高级操作技术 477

20.1 DLL模块的显式加载和

      符号链接	477
20.1.1 显式加载DLL模块 478
20.1.2 显式卸载DLL模块 479
20.1.3 显式链接到一个输出符号 480

20.2 DLL的进入点函数 481

20.2.1 DLL_PROCESS_ATTACH通知 482
20.2.2 DLL_PROCESS_DETACH通知 483
20.2.3 DLL_THREAD_ATTACH通知 485
20.2.4 DLL_THREAD_DETACH通知 485
20.2.5 顺序调用DllMain 486
20.2.6 DllMain与C/C++运行期库 488

20.3 延迟加载DLL 489

20.4 函数转发器 499

20.5 已知的DLL 499

20.6 DLL转移 500

20.7 改变模块的位置 501

20.8 绑定模块 506

第21章 线程本地存储器 509

21.1 动态TLS 509

21.2 静态TLS 513

第22章 插入DLL和挂接API 515

22.1 插入DLL:一个例子 515

22.2 使用注册表来插入DLL 517

22.3 使用Windows挂钩来插入DLL 518

22.4 使用远程线程来插入DLL 531

22.4.1 Inject Library 示例应用程序 534
22.4.2 Image Walk DLL 541

22.5 使用特洛伊DLL来插入DLL 544

22.6 将DLL作为调试程序来插入 544

22.7 用Windows 98上的内存映射文件

      插入代码	544

22.8 用CreateProcess插入代码 544

22.9 挂接API的一个示例 545

22.9.1 通过改写代码来挂接API 546
22.9.2 通过操作模块的输入节来挂接API 546
22.9.3 LastMsgBoxInfo示例应用程序 549

第五部分 结构化异常处理

第23章 结束处理程序 565

23.1 通过例子理解结束处理程序 566

23.2 Funcenstein1 566

23.3 Funcenstein2 566

23.4 Funcenstein3 568

23.5 Funcfurter1 568

23.6 突击测验:FuncaDoodleDoo 569

23.7 Funcenstein4 570

23.8 Funcarama1 571

23.9 Funcarama2 572

23.10 Funcarama3 572

23.11 Funcarama4:最终的边界 573

23.12 关于finally块的说明 574

23.13 Funcfurter2 575

23.14 SEH结束处理示例程序 576

第24章 异常处理程序和软件异常 578

24.1 通过例子理解异常过滤器和异常处理

   程序	578
24.1.1 Funcmeister1 578
24.1.2 Funcmeister2 579

24.2 EXCEPTION_EXECUTE_HANDLER 580

24.2.1 一些有用的例子 581
24.2.2 全局展开 583
24.2.3 暂停全局展开 585

24.3 EXCEPTION_CONTINUE_

      EXECUTION	586

24.4 EXCEPTION_CONTINUE_

      SEARCH	588

24.5 Get Exception Code 589

24.6 Get Exception Information 592

24.7 软件异常 595

第25章 未处理异常和C++异常 598

25.1 即时调试 600

25.2 关闭异常消息框 601

25.2.1 强制进程终止运行 601
25.2.2 包装一个线程函数 601
25.2.3 包装所有的线程函数 601
25.2.4 自动调用调试程序 602

25.3 程序员自己调用UnhandledException

   Filter	602

25.4 UnhandledExceptionFilter函数的一些

   细节	603

25.5 异常与调试程序 604

25.6 C++异常与结构性异常的对比 618

第六部分 窗 口

第26章 窗口消息 623

26.1 线程的消息队列 623

26.2 将消息发送到线程的消息队列中 625

26.3 向窗口发送消息 626

26.4 唤醒一个线程 630

26.4.1 队列状态标志 630
26.4.2 从线程的队列中提取消息的
         算法	631
26.4.3 利用内核对象或队列状态标
         志唤醒线程	634

26.5 通过消息发送数据 636

26.6 Windows如何处理ANSI/Unicode

   字符和字符串	642

第27章 硬件输入模型和局部输入状态 645

27.1 原始输入线程 645

27.2 局部输入状态 646

27.2.1 键盘输入与焦点 647
27.2.2 鼠标光标管理 649

27.3 将虚拟输入队列同局部输入状态挂接

   在一起	651
27.3.1 LISLab 示例程序 652
27.3.2 LISWatch 示例程序 666

第七部分 附 录

附录A 建立环境 675

附录B 消息分流器、子控件宏和API宏 686


总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值