第一章
1、获得前一个错误的错误码:
DWORD WINAPI GetLastError(void);
可以使用VS带的工具查看错误码的含义:工具->错误查找
2、在DeBug模式下(F5)可以在Watch窗口中使用$err,hr来显示错误的具体含义
-------------------------------------------------------------------
第二章
1、使用通用的数据类型(TCHAR或PTSTR)来表示文本字符和字符串
2、使用明确的数据类型BYTE或PBYTE来表示字节、字节指针和数据缓冲区
3、使用TEXT或_T宏表示字面常量字符和字符串
4、使用malloc(nCharacter*sizeof(TCHAR)),而不是使用malloc(nCHaracter)
5、使用MultiByteToWideChar和WideCharToMultiByte函数(P26)
6、使用安全的函数处理字符串(_s)如memcpy_s、memmove_s等
7、使用IsTextUnicode来判断字符串是否是UNICODE
------------------------------------------------------------------
第三章、内核对象
在Windows操作系统中我们常常接触的有三种对象类型:
1、Windows内核对象 (事件对象,文件对象,进程对象,I/O完成端口对象,互斥量对象,进程对象,线程对象等等):
由执行体(Excutive)对象管理器(Object Manager)管理,内核对象结构体保存在系统内存空间(0x80000000-0xFFFFFFFF),句柄值与进程相关。
2、Windows GDI对象 (画笔对象,画刷对象等):
由Windows子系统管理,句柄值在系统,会话范围 (system-wide /session-wide) 有效。
3、Windows USER对象 (窗口对象,菜单对象等) :
由Windows子系统管理,句柄值在系统,会话范围 (system-wide /session-wide) 有效。
一、内核对象
1、每个内核对象都是一个内存块,由操作系统内核分配,并只由操作系统内核访问,该内存块是一个数据结构,其成员维护者与对象相关的信息。
2、每个内核对象都一个计数器用于记录有多少进程正在使用该内核对象,当计数为0时,操作系统内核将销毁该内核对象。
3、创建内核对象后,返回一个句柄,该句柄作为进程内核对象表的索引使用,索引句柄是与进程相关的,它只能提供给本进程的所有线程使用,不能在其他进程内使用。
4、判断对象是否是内核对象:内核对象的创建函数一般都含有一个PSECURITY_ATTRIBUTE参数,而用户对象和GDI对象创建函数不具备该参数。如函数CreateThread、CreateFile、CreateFileMapping、CreateMutex等。
5、关闭内核对象:BOOL CloseHandle(HANDLE hobject):函数首先检查主调进程的句柄表,确认句柄的有效性,然后系统将获得内核对象的数据结构地址,并将计数器减1,如果计数为0时,操作系统内核将销毁该内核对象。关闭后hobject= NULL(谨记)
二、跨进程边界共享内核对象
有三种不同机制来允许进程共享内核对象:使用对象句柄继承;为对象命名;复制对象句柄。
A. 对象句柄继承
//为父进程初始化一个SECURITY_ATTRIBUTES结构体 SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = NULL; //设置句柄表为可继承的 sa.bInheritHandle = TRUE; HANDLE hFile = CreateFile(TEXT("d:\\win32.txt"),GENERIC_READ|GENERIC_WRITE,0,&sa,OPEN_EXISTING,0,NULL); STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi; //将宽字常量字符串复制到一个临时缓冲区 TCHAR szCommandLine[] = TEXT("NOTEPAD"); //其中第五个参数为TRUE,也就是从父进程继承句柄表 CreateProcess(NULL, szCommandLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
B. 对象命名
HANDLE CreateMutex( PSECURITY_ATTRIBUTES psa, BOOL bInitialOwner, PCTSTR pszName); //使用pszName为内核对象命名
HANDLE hMutex = CreateMutex(&sa, FALSE, TEXT("JeffObj")); if (GetLastError() == ERROR_ALREADY_EXISTS) { // Opened a handle to an existing object.//存在内核对象 // sa.lpSecurityDescriptor and the second parameter // (FALSE) are ignored. } else { // Created a brand new object. // sa.lpSecurityDescriptor and the second parameter // (FALSE) are used to construct the object. }
CreateXXX()和OpenXXX()的区别:
1)、OpenXXX()的第三个参数不能为NULL,只能是一个以0为终止符的字符串
2)、对象不存在时,CreateXXX()会创建一个,而OpenXXX()只是简单的以调用失败告终
可以使用命名对象来防止运行一个应用程序的多个实例。P50
BOOL CPhone_contact_newApp::InitInstance() { CreateMutex(NULL,TRUE,AfxGetAppName()); //为内核对象命名 if(GetLastError()==ERROR_ALREADY_EXISTS) return FALSE; //.... ... }
C. 复制对象句柄
------------------------------------------------------------------
第四章、进程
一、进程
1、进程是一个运行的程序的一个实例,它由两部分组成:
2、进程实例句柄:
int APIENTRY _tWinMain(HINSTANCE hInstance,//进程实例句柄 HINSTANCE hPrevInstance,//遗留问题,永远为NULL LPTSTR lpCmdLine, int nCmdShow);
32位Windows中,HMODULE与HINSTANCE完全是一回事。
进程实例句柄实际是一个内存基地址。可以通过下方法获得主调进程的可执行文件的基地址
HMODULE hAddr = ::GetModuleHandle(NULL);
3、进程的命令行
4、进程的环境块变量:
1)LPTCH WINAPI GetEnvironmentStrings(void),用完后要FreeEnvironmentStrings
2)控制台程序,从main参数TCHAR *env[]获得。
3)子进程可以获得父进程的环境块的一个副本,该副本由子进程专用。
4)DWORD WINAPI GetEnvironmentVariable:获得某环境变量的值(可用于判断是否存在)P78
5) BOOL WINAPI SetEnvironmentVariable:添加、修改或删除一个环境变量
5、进程的关联性
子进程继承父进程的关联性
6、进程的错误模式
UINT SetErrorMode(UINTfuErrorMode)
子进程继承父进程的错误模式
7、进程当前所在的驱动器和目录
DWORD WINAPI GetCurrentDirectory 使用MAX_PATH
BOOL WINAPI SetCurrentDirectory
进程中的一个线程更改了当前驱动器或目录,则进程的所有线程来说,这些信息都被改变
二、创建进程
调用CreateProcess函数创建新的进程时,系统首先创建一个新的进程内核对象,并为新进程创建一个虚拟地址空间,将可执行文件和所有DLL的代码及数据加载到该地址空间。之后系统为新进程创建一个主线程来执行启动例程
三、终止进程的方法
1、主线程的入口点函数返回(推荐)
2、进程中的一个线程调用ExitProcess函数(避免使用)
3、另一个进程中的线程调用TerminateProcess函数(避免使用)
四、子进程
在CreateProcess返回后立即关闭到子进程的主线程的内核句柄是一种好的编程习惯:
PROCESS_INFORMATION pi;
BOOL fSuccess = ::CreateProcess(...,&pi);
if (fSuccess){
::CloseHandle(pi.hThread);
}
假定子进程的主线程生成了另一个线程,主线程终止,此时系统就可以从内存对象中释放子进程的主进程对象,前提是父进程没有打开到这个线程的对象的句柄。如果父线程打开的到子进程的主线程对象的一个句柄,系统就不会释放该主线程的内核对象,除非父进程关闭这个句柄。
PROCESS_INFORMATION pi; DWORD dwExitCode; BOOL fSuccess = ::CreateProcess(...,&pi); if (fSuccess){ ::CloseHandle(pi.hThread); ::WaitForSingleObject(pi.hProcess,INFINITE);//等待子进程返回结果 ::GetExitCodeProcess(pi.hProcess,&dwExitCode); ::CloseHandle(pi.hProcess); }
创建独立运行的子进程:
父进程调用CloseHandle来关闭子进程及子进程的主线程的句柄
PROCESS_INFORMATION pi; BOOL fSuccess = ::CreateProcess(...,&pi); if (fSuccess){ ::CloseHandle(pi.hThread); ::CloseHandle(pi.hProcess); }
MFC型的父进程获得控制台型的子进程的输出:(使用匿名管道)
SECURITY_ATTRIBUTES sa; HANDLE hRead,hWrite; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE; if (!CreatePipe(&hRead,&hWrite,&sa,0)) { //创建匿名管道 AfxMessageBox( TEXT("Error On CreatePipe() ")); return; } STARTUPINFO si; PROCESS_INFORMATION pi; si.cb = sizeof(STARTUPINFO); GetStartupInfo(&si); si.hStdError = hWrite; si.hStdOutput = hWrite; //si.wShowWindow = SW_HIDE; si.dwFlags = STARTF_USESTDHANDLES; if (!::CreateProcess(TEXT("lip-vireo.exe"),cmdStr.GetBuffer() , NULL,NULL,TRUE,0,NULL,NULL,&si,π)) { MessageBox( TEXT("Error on CreateProcess() ")); return; } CloseHandle(hWrite); char buffer[4096] = {0}; DWORD bytesRead; CString str; while (true) { if (ReadFile(hRead,buffer,4096,&bytesRead,NULL) == NULL) break; buffer[bytesRead] = 0; str = (TCHAR*)(_bstr_t(buffer)); AfxMessageBox(str); Sleep(200); } }
---------------------------------------------------------------------------------------------------
第五章、作业
Windows提供了一个作业(job)内核对象,它允许将进程组合在一起并创建一个“沙箱”(即施加限制)来限制进程能做什么,相当于进程的容器。
如果一个进程已与一个作业关联,就无法将其或其子进程从进程中移除。
1、判断一个进程是否已和一个现有的作业关联:
BOOL WINAPI IsProcessInJob( __in HANDLE ProcessHandle, __in_opt HANDLE JobHandle, //为NULL则表示所有作业 __out PBOOL Result );
2、创建新的作业内核对象:
HANDLE WINAPI CreateJobObject( __in_opt LPSECURITY_ATTRIBUTES lpJobAttributes, __in_opt LPCTSTR lpName );
3、关闭一个作业内核对象:
CloseHandle
关闭一个作业对象,不会迫使其所有进程终止运行,作业对象只是被加了一个删除标记,只有在作业中的所有进程都已终止后才会自动销毁。
关闭作业的句柄会导致所有的进程都不可再访问此作业,即使是它依然存在。
4、对作业中的进程施加限制:
限制类型:
- 基本限额和扩展基本限额,用于防止作业中的进程独占系统资源
- 基本的UI限制,用于防止作业中的进程更改用户界面
- 安全限额,用于防止作业中的进程访问安全资源(文件、注册表子项等)
添加限制的函数:
BOOL WINAPI SetInformationJobObject( __in HANDLE hJob, //要限制的作业 __in JOBOBJECTINFOCLASS JobObjectInfoClass, //限制类型 __in LPVOID lpJobObjectInfo, //具体限制设置 __in DWORD cbJobObjectInfoLength //lpJobObjectInfo结构大小 );
5、将进程放入作业中
BOOL bResult = ::CreateProcess(NULL,szCmdLine,NULL,NULL,FALSE, CREATE_SUSPENDED | CREATE_NEW_CONSOLE,NULL,NULL,&si,&pi); //进程创建时还不属于作业,为了防止其脱离沙箱的限制立即执行,要将其SUSPENDED后加入作业: ::AssignProcessToJobObj(hjob,pi.hProcess); ::ResumeThread(pi.hProcess);1、一旦一个进程已经属于了一个作业的一部分,他就不能再移动到另一个作业中
2、当一个作业的进程产生了子进程,子进程自动成为父进程所属的作业的一部分。这种行为是可以改变的。P131
6、终止作业中的所有线程
“杀死”作业内部的所有进程:
BOOL WINAPI TerminateJobObject( __in HANDLE hJob, __in UINT uExitCode //nExitCode );
7、查询作业的统计信息和限制
BOOL WINAPI QueryInformationJobObject
8、作业通知
创建一个I/O完成端口内核对象,并将我们的作业与完成端口关联,然后有一个或者多个工作线程等待作业通知到达完成端口,以便对它们进行处理。
一旦创建了I/O端口,就可以调用SetInformationJobObject来将它与一个作业关联起来。
JOBOBJECT_ASSOCIATE_COMPLETION_PORT joacp; joacp.CompletionKey = 1; joacp.CompletionPort = hIOCP; ::SetInformationJobObject(hJob,JobObjectAssociateCompletionPortInformation, &joacp,sizeof(joacp));线程通过调用GetQueuedCompletionStatus来监视完成端口。P136
第六章、线程基础
1、线程组成:
2、创建线程:
HANDLE WINAPI CreateThread( __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, //安全属性,通常为NULL __in SIZE_T dwStackSize, //线程栈大小 __in LPTHREAD_START_ROUTINE lpStartAddress, //线程函数地址 __in_opt LPVOID lpParameter, //传递给线程函数的参数 __in DWORD dwCreationFlags, //控制创建,0或CREATE_SUSPENDED __out_opt LPDWORD lpThreadId //存储线程ID );
系统从进程的地址空间中分配内存给线程栈使用。新线程在与负责创建的那个线程在相同的进程上下文中运行,因此新线程可以访问进程内核对象的所有句柄、进程中的所有内存以及同一个进程中其他的线程栈。
应该使用_beginthreadex函数来代替CreateThread。
3、终止线程
1)、线程函数返回(推荐)
2)、线程调用ExitThread函数杀死自己(避免使用)
3)、另一个进程或同一进程中的线程调用TerminateThread函数(避免使用)
4)、包含线程的进程终止运行(避免使用)
4、线程内幕
线程的上下文反映了当线程上一次执行时,线程的CPU寄存器的状态。线程的CPU寄存器全部保存在一个CONTEXT结构,CONTEXT结构本身保存在线程内核对象中。
5、C/C++运行库注意事项
在创建线程时,一定不要使用操作系统提供的CreateThread函数,相反的而是调用C/C++运行库函数_beginthreadex
关于_beginthreadex:
关于_threadstartex:
6、获得线程或进程的句柄
HANDLE GetCurrentProcess();
HANDLE GetCurrentThread();
这两个函数返回主调进程内核对象或线程对象的一个伪句柄(pseudohandle),它们在主调进程句柄表中新建句柄,且调用者两个函数不会影响线程或进程内核对象的使用计数。
将伪句柄转换为真正的句柄:DuplicateHandle函数,该函数递增了指定内核对象的使用计数,所以在用完复制的对象句柄后要CloseHandle.
7、用户模式下的线程和内核模式下的线程:(《C++多核高级编程》)
在用户模式下,进程或线程是执行程序或链接库中的指令,它们不对操作系统内核进行任何调用。在内核模式下,进程或线程是在进行系统调用,例如访问资源或抛出异常。同时,在内核模式下,进程或线程可以访问在内核空间中定义的对象。
用户级线程驻留在用户空间或模式。运行时库管理这些线程,它也位于用户空间。它们对于操作系统是不可见的,因此无法被调度到处理器内核。每个线程并不具有自身的线程上下文。因此,就线程的同时执行而言,任意给定时刻每个进程只能够有一个线程在运行,而且只有一个处理器内核会被分配给该进程。对于一个进程,可能有成千上万个用户级线程,但是它们对系统资源没有影响。运行时库调度并分派这些线程。如同下图中看到的那样,库调度器从进程的多个线程中选择一个线程,然后该线程和该进程允许的一个内核线程关联起来。内核线程将被操作系统调度器指派到处理器内核。用户级线程是一种"多对一"的线程映射。
内核级线程驻留在内核空间,它们是内核对象。有了内核线程,每个用户线程被映射或绑定到一个内核线程。用户线程在其生命期内都会绑定到该内核线程。一旦用户线程终止,两个线程都将离开系统。这被称作"一对一"线程映射,如下图所示。操作系统调度器管理、调度并分派这些线程。运行时库为每个用户级线程请求一个内核级线程。操作系统的内存管理和调度子系统必须要考虑到数量巨大的用户级线程。您必须了解每个进程允许的线程的最大数目是多少。操作系统为每个线程创建上下文。进程的每个线程在资源可用时都可以被指派到处理器内核。
第七章线程调度、优先级和关联性
上下文切换:Windows在可调度的线程内核对象中选择一个,并将上次保存在线程上下文中的值载入CPU寄存器的操作。
1、线程的挂起和恢复
DWORD WINAPI ResumeThread(__in HANDLE hThread);
DWORD WINAPI SuspendThread( __in HANDLE hThread);
只有在确切的知道目标线程是哪个(或它在做什么),而且采用完备的措施避免出现因挂起线程而引起问题或者死锁的时候,调用SuspendThread才是安全的。
OpenThread:找到与线程ID匹配的线程内核对象,并将内核对象的使用计数递增1,然后返回对象的句柄。
2、睡眠
VOID WINAPI Sleep(__in DWORD dwMilliseconds);
3、切换到另一个线程
BOOL WINAPI SwitchToThread(void);
调用该函数时,系统查看是否存在正急需CPU时间的饥饿线程,没有则立即返回,存在则调度该线程。与Sleep传入0不同的是该函数允许执行低优先级的线程。
4、线程的执行时间
BOOL WINAPI GetThreadTimes( __in HANDLE hThread, __out LPFILETIME lpCreationTime, //创建时间 __out LPFILETIME lpExitTime, //退出时间 __out LPFILETIME lpKernelTime, //内核时间 __out LPFILETIME lpUserTime //用户时间:线程执行应用程序代码所用的时间 );
5、在实际上下文中谈CONTEXT结构
CONTEXTcontext; context.ContextFlags = CONTEXT_CONTROL|CONTEXT_INTEGER;//指定需要那种寄存器, //CONTEXT_FULL为所有寄存器 ::GetThreadContext(::GetCurrentThread(),&context);
6、线程的优先级
7、优先级编程
系统通过线程的相对优先级加上线程所属的进程的优先级来确定线程的优先级值(基本优先级值)。
BOOL WINAPI SetPriorityClass(
__in HANDLE hProcess, //进程句柄
__in DWORD dwPriorityClass//优先级标识,如NORMAL_PRIORITY_CLASS
);
BOOL WINAPI SetThreadPriority(
__in HANDLE hThread,
__in int nPriority
);
CreateThread总是创建优先级为normal的线程,要创建其他优先级线程,需先挂起线程: DWORD dwThreadID; HANDLE hThread=::CreateThread(NULL,0,ThreadFuc,NULL,CREATE_SUSPENDED,&dwThreadID); SetThreadPriority(hThread,THREAD_BASE_PRIORITY_IDLE); ResumeThread(hThread); CloseHandle(hThread);
系统只提升优先级值在0~15的线程,该范围称为动态优先级范围
禁止系统对线程进行优先级的动态提升:
SetProcessPriorityBoost //禁止提升进程内的所有线程的优先级
SetThreadPriorityBoost //禁止提升线程的优先级
当系统检测到有的线程已经处在饥饿状态3~4秒,它会动态提升线程优先级到15,并允许线程运行两个时间片,运行完后恢复到基本优先级值。