系列文章目录
提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
例如:第一章 Python 机器学习入门之pandas的使用
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 系列文章目录
- 前言
- 第一部分 程序员必读
- windows核心编程:第3章内核对象防止多开
- 防止多开
- 3_Singleton.cpp
- 3,对上面的代码进行改进防止ddos攻击
- 第二部分 编程的具体方法
-
- 第4章 进程 45
- 第5章 作业 91
- 第6章 线程的基础知识 121
- 第7章 线程的调度、优先级和亲缘性 142
- 第8章 用户方式中线程的同步 172
- 第9章 线程与内核对象的同步 190
- 第10章 线程同步工具包 228
- 第11章 线程池的使用 274
- 第12章 纤程 287
- 第三部分 内 存 管 理
- 第16章 线程的堆栈 385
- 第四部分 动态链接库
-
- 第19章 DLL基础 463
- 第20章 DLL的高级操作技术 477
- 第21章 线程本地存储器 509
- 第22章 插入DLL和挂接API 515
- 第五部分 结构化异常处理
-
- 第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
- 第25章 未处理异常和C++异常 598
- 第六部分 窗 口
- 第七部分 附 录
- 附录A 建立环境 675
- 附录B 消息分流器、子控件宏和API宏 686
- 总结
前言
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,0,1024,"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章内核对象防止多开
文章目录
- 系列文章目录
- 前言
- 第一部分 程序员必读
- windows核心编程:第3章内核对象防止多开
- 防止多开
- 3_Singleton.cpp
- 3,对上面的代码进行改进防止ddos攻击
- 第二部分 编程的具体方法