《Windows核心编程》读书笔记九 用内核模式进行线程同步

第九章 用内核对象进行线程同步


本章内容

9.1 等待函数

9.2 等待成功所引起的副作用

9.3 事件内核对象

9.4 可等待的计时器内核对象

9.5 信号量内核对象

9.6 互斥量内核对象

9.7 线程同步对象速查表

9.8 其他的线程同步函数


用户模式下的线程同步高性能,但是存在一些局限。例如无法进行进程间线程的同步,Iterlocked系函数不会把线程切换到等待状态,

进入临界区无法设置最长等待时间等。


内核对象来进行线程同步,功能强大许多。但是唯一的缺点就是性能。


对于线程内核对象可能处于触发(signaled)和未触发(nosignaled)

进程内核对象,在创建时其内部有一个BOOL变量是FALSE, 单进程终止时该内核对象会变成TRUE 表示已经触发

但是这个过程是不可逆的。


以下列出可能处于未触发也可以处于触发状态的内核对象:

进程,线程,作业,文件以及控制台的标准输入流/输出流/错误流

事件,可等待的计时器, 信号量,互斥量


9.1 等待函数

等待函数使一个线程资源进入等待状态,直到指定的内核对象被触发为止。如果在调用一个等待函数时,响应的内核对象已经处于触发状态,那么线程是不会进入等待状态的。

WINBASEAPI
DWORD
WINAPI
WaitForSingleObject(
    _In_ HANDLE hHandle,
    _In_ DWORD dwMilliseconds
    );

hObject用来标识等待的内核对象,可以处于触发或未触发状态。

dwMilliseconds 指定线程最多愿意花多长时间来等待对象被触发

一直等待直到目标进程终止

WaitForSingleObject(hProcess, INFINITE);

	DWORD dw = WaitForSingleObject(hProcess, 5000);
	switch (dw) {
	case WAIT_OBJECT_0:
		// The process terminated.
		break;
	case WAIT_TIMEOUT:
		// The process did not terminate within 5000 milliseconds.
		break;
	case WAIT_FAILED:
		// Bad call to function (invalid handle?)
		break;
	}


WaitForSingleObject的返回值表示为什么调用线程又能继续执行了。


以下函数可以等待多个内核对象的触发状态

WINBASEAPI
DWORD
WINAPI
WaitForMultipleObjects(
    _In_ DWORD nCount,
    _In_reads_(nCount) CONST HANDLE *lpHandles,
    _In_ BOOL bWaitAll,
    _In_ DWORD dwMilliseconds
    );

nCount表示希望函数检查内核对象的数量。 必须在(1~MAXIMUM_WAIT_OBJECTS之间)

lpHandles 指向内核对象句柄的数组

bWaitAll 是否等待全部触发,FALSE 表示只要一个触发即可

dwMilliseconds 等待时间


一个例子说明WaitForMultipleObjects的返回值

	HANDLE h[3];
	h[0] = hProcess1;
	h[1] = hProcess2;
	h[2] = hProcess3;
	DWORD dw = WaitForMultipleObjects(3, h, FALSE, 5000);
	switch (dw) {
	case WAIT_FAILED:
		// Bad call to function (invalid handle?)
		break;

	case WAIT_TIMEOUT:
		// None of the objects became signaled within 5000 milliseconds.
		break;

	case WAIT_OBJECT_0 + 0:
		// The process identified by h[0] (hProcess1) terminated.
		break;

	case WAIT_OBJECT_0 + 1:
		// The process identified by h[1] (hProcess1) terminated.
		break;

	case WAIT_OBJECT_0 + 2:
		// The process identified by h[2] (hProcess1) terminated.
		break;
	}


如果给bWaitAll传递TRUE那么所有内核对象都触发了以后返回值是 WAIT_OBJECT_0


9.2 等待成功锁引起的副作用

如果WaitForXXXObject成功返回,那么传入的句柄对象发生了变化,成为“等待成功锁引起的副作用”。

有些内核对象在WaitForXXX返回以后会被自动设置为非触发状态(例如自动重置事件对象)


例如一个例子

HANDLE h[2];

WaitForMultipleObjects(2, h, TRUE, INFINITE);

1)两个线程执行同样的代码,

2)其中一个事件触发了,两个线程都能检测到,但是由于另外一个事件未触发,所以线程会继续等待

3)另一个事件也触发了,其中一个线程检测到两个事件都触发以后,将两个事件又设置为非触发状态并返回

4)另一个线程会继续等待直到两个事件同时触发为止。


WaitForMultipleObjects是以原子操做的方式工作的,当他检查内核对象的状态时,任何其他线程都不能在背后修改对象的状态。这就防止了死锁的发生。


9.3 事件内核对象

事件包含一个使用计数器,一个用来标识事件是自动重置还是手动重置的布尔值,以及另一个布尔值用来表示事件有没有被触发。


当一个手动重置事件被触发的时候,正在等待该事件的所有线程都变成可调度状态。

当一个自动重置事件被触发时,只有一个正在等待该事件的线程会变成可调度状态。


事件常用于让一个线程执行初始化工作,然后再触发另一个线程,让它执行剩余的工作。

WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
CreateEventW(
    _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
    _In_ BOOL bManualReset,
    _In_ BOOL bInitialState,
    _In_opt_ LPCWSTR lpName
    );

bManualRest TRUE(手动触发), FALSE(自动触发)

bInitialState 初始状态 TRUE(已触发), FALSE (未触发)

创建以后会返回一个事件内核对象句柄,该事件和当前进程相关联。

还有一个CreateEventEx函数用于创建事件。

WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
CreateEventExW(
    _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
    _In_opt_ LPCWSTR lpName,
    _In_ DWORD dwFlags,
    _In_ DWORD dwDesiredAccess
    );

dwFlags参数可以接受两个位的掩码


dwDesiredAccess 允许指定在创建事件时返回的句柄对事件有何种访问权限。(ex可以限制权限)

如果要调用SetEvent, ResetEvent, PulseEvent。必须使用

EVENT_MODIFY_STATE


其他进程中的线程可以通过多种方式来访问该事件对象,

CreateEvent并在pszName参数中传入相同的值,

使用继承(子进程继承父进程,两者的句柄表中的位置完全一样)

使用DuplicateHandle

或者调用OpenEvent

WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
OpenEventW(
    _In_ DWORD dwDesiredAccess,
    _In_ BOOL bInheritHandle,
    _In_ LPCWSTR lpName
    );


设置事件为触发状态

BOOL SetEvent(HANDLE hEvent); 


设置事件为非触发状态

BOOL ResetEvent(HANDLE hEvent);


自动事件由于等待成功所引起的副作用的影响,当事件被线程等待以后会自动设置为非触发,因此不需要ResetEvent。


HANDLE g_hEvent;

DWORD WINAPI WordCount(PVOID pvParam) {
	
	// Wait until the file's data is in memory.
	WaitForSingleObject(g_hEvent, INFINITE);

	// Access the memory block.
	// ...

	return 0;
}


DWORD WINAPI SpellCheck(PVOID pvParam) {

	// Wait until the file's data is in memory.
	WaitForSingleObject(g_hEvent, INFINITE);

	// Access the memory block.
	// ...

	return 0;
}

DWORD WINAPI GrammarCheck(PVOID pvParam) {

	// Wait until the file's data is in memory.
	WaitForSingleObject(g_hEvent, INFINITE);

	// Access the memory block.
	// ...
	return 0;
}



int _tmain(int argc, TCHAR* argv[], TCHAR * env[])
{
	// Crate the manual-reset, nosignaled event.
	g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

	// Spawn 3 new threads.
	HANDLE hThread[3];
	DWORD dwThreadID;
	hThread[0] = CreateThread(NULL, 0, WordCount, NULL, 0, &dwThreadID);
	hThread[1] = CreateThread(NULL, 0, SpellCheck, NULL, 0, &dwThreadID);
	hThread[2] = CreateThread(NULL, 0, GrammarCheck, NULL, 0, &dwThreadID);

	OpenFileAndReadContentsIntoMemory(...);

	// Allow all 3 threads to access the memory.
	SetEvent(g_hEvent);
	return 0;
}

因为这里使用了手动重置事件,所以当主线程准备好数据以后3个子线程都能同时运行。


如果使用自动重置事件,那么3个子线程只会有一个能继续运行。为了让3个子线程都能执行代码,修改了一下3个子线程的代码

DWORD WINAPI WordCount(PVOID pvParam) {
	
	// Wait until the file's data is in memory.
	WaitForSingleObject(g_hEvent, INFINITE);

	// Access the memory block.
	// ...
	SetEvent(g_hEvent);
	return 0;
}


DWORD WINAPI SpellCheck(PVOID pvParam) {

	// Wait until the file's data is in memory.
	WaitForSingleObject(g_hEvent, INFINITE);

	// Access the memory block.
	// ...
	SetEvent(g_hEvent);
	return 0;
}

DWORD WINAPI GrammarCheck(PVOID pvParam) {

	// Wait until the file's data is in memory.
	WaitForSingleObject(g_hEvent, INFINITE);

	// Access the memory block.
	// ...
	SetEvent(g_hEvent);
	return 0;
}

这个3个线程都会被系统调用,而且每个线程都能独占的读写资源。


BOOL PulseEvent(HANDLE hEvent);

将一个事件变成触发状态以后立即恢复到未触发状态。


Handshake 示例程序


/******************************************************************************
Module:  Handshake.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/

#include "..\CommonFiles\CmnHdr.h"
#include <windowsx.h>
#include <tchar.h>
#include "Resource.h"

//
#define BUFFERSIZ	1024


// This event is signaled when the client has a request for the server
HANDLE	g_hevtRequestSubmitted;

// This event is signaled when the server has a result for the client
HANDLE	g_hevtResultReturned;

// The buffer shared between the client and server threads
TCHAR	g_szSharedRequestAndResultBuffer[BUFFERSIZ];

// The special value sent from the client that causes the
// server thread to terminate cleanly.
TCHAR	g_szServerShutdown[] = TEXT("Server Shutdown");

// The server thread will check that the main dialog is no longer alive
// When the shutdown message is received.
HWND	g_hMainDlg;

//

// This is the code executed by the server thread
DWORD WINAPI ServerThread(PVOID pvParam) {

	// Assume that the server thread is to run forever
	BOOL fShutdown = FALSE;

	while (!fShutdown) {

		// Wait for the client to submit a request
		WaitForSingleObject(g_hevtRequestSubmitted, INFINITE);

		// Check to see if the client wants the server to terminate
		fShutdown =
			(g_hMainDlg == NULL) &&
			(_tcscmp(g_szSharedRequestAndResultBuffer, g_szServerShutdown) == 0);

		if (!fShutdown) {
			// Process the client's request (reverse the string)
			_tcsrev(g_szSharedRequestAndResultBuffer);
		}

		// Let the client process the request's result
		SetEvent(g_hevtResultReturned);
	}

	// The client wants us to shut down, exit
	return 0;
}


//

BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) {

	chSETDLGICONS(hwnd, IDI_HANDSHAKE);

	// Initialize the edit control with some test data request
	Edit_SetText(GetDlgItem(hwnd, IDC_REQUEST), TEXT("Some test data"));

	// Store the main dialog window handle
	g_hMainDlg = hwnd;

	return TRUE;
}

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

	switch (id) {

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

	case IDC_SUBMIT:		// Submit a request to the server thread

		// Copy the request string into the shared data buffer
		Edit_GetText(GetDlgItem(hwnd, IDC_REQUEST),
			g_szSharedRequestAndResultBuffer,
			_countof(g_szSharedRequestAndResultBuffer));

		// Let the server thread know that a request is ready in the buffer
		// Wait for the server to process the request and give us the result
		SignalObjectAndWait(g_hevtRequestSubmitted, g_hevtResultReturned, INFINITE, false);

		// Let the user know the result
		Edit_SetText(GetDlgItem(hwnd, IDC_RESULT),
			g_szSharedRequestAndResultBuffer);

		break;
	}
}

//

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);
	}

	return FALSE;
}


//

int WINAPI _tWinMain(HINSTANCE hInstanceExe, HINSTANCE, PTSTR, int) {

	// Create & initialize the 2 nonsignaled, auto-reset events
	g_hevtRequestSubmitted = CreateEvent(NULL, FALSE, FALSE, NULL);
	g_hevtResultReturned = CreateEvent(NULL, FALSE, FALSE, NULL);

	// Spawn the server thread
	DWORD dwThreadID;
	HANDLE hThreadServer = chBEGINTHREADEX(NULL, 0, ServerThread, NULL,
		0, &dwThreadID);

	// Execute the client thread's user interface
	DialogBox(hInstanceExe, MAKEINTRESOURCE(IDD_HANDSHAKE), NULL, Dlg_Proc);
	g_hMainDlg = NULL;

	// The client's UI is closing, have the server thread shutdown
	_tcscpy_s(g_szSharedRequestAndResultBuffer,
		_countof(g_szSharedRequestAndResultBuffer), g_szServerShutdown);
	SetEvent(g_hevtRequestSubmitted);

	HANDLE h[2];
	h[0] = g_hevtResultReturned;
	h[1] = hThreadServer;
	WaitForMultipleObjects(2, h, TRUE, INFINITE);

	// Properly clean up everything
	CloseHandle(hThreadServer);
	CloseHandle(g_hevtRequestSubmitted);
	CloseHandle(g_hevtResultReturned);

	// The client thread terminates with the whole process
	return 0;
}

运行结果



使用CONDITION_VARIABLE实现的handshake例子。虽然用CONDITION_VARIABLE和SRWLOCK效率高,但是使用起来代码的复杂度更高。

Console版本的HandShake

#define  _CRT_SECURE_NO_WARNINGS
#include <tchar.h>
#include <windows.h>
#include <stdio.h>
#include <Shlobj.h>
#include <strsafe.h>
#include <malloc.h>
#include <process.h>
#include <winnt.h>
#include <ctype.h>

CONDITION_VARIABLE		g_cvReturnResult;
CONDITION_VARIABLE		g_cvSubmitRequest;
SRWLOCK					g_srwLock;

TCHAR					g_szSharedBuffer[BUFSIZ];
TCHAR					g_szShutdown[] = TEXT("Server Shutdown");

unsigned __stdcall serverThread(void * pParam) {

	bool  bShutdown = false;
	_tprintf(TEXT("Server Started! \n"));
	while (!bShutdown) {
		AcquireSRWLockExclusive(&g_srwLock);
		SleepConditionVariableSRW(&g_cvSubmitRequest,
			&g_srwLock, INFINITE, 0); // for Exclusive SRWLock

		bShutdown =
			(_tcscmp(g_szSharedBuffer, g_szShutdown) == 0);

		if (!bShutdown)
			_tcsrev(g_szSharedBuffer);

		ReleaseSRWLockExclusive(&g_srwLock);
		WakeConditionVariable(&g_cvReturnResult);
	}
	_tprintf(TEXT("Server exit with 0! \n"));
	return 0;
}


int _tmain(int argc, TCHAR* argv[], TCHAR * env[])
{
	// Init Condition variable and srwlock
	InitializeConditionVariable(&g_cvReturnResult);
	InitializeConditionVariable(&g_cvSubmitRequest);
	InitializeSRWLock(&g_srwLock);

	_tprintf(TEXT("Handshake Console Version v 0.1\n"));

	unsigned int iThreadID;
	HANDLE hThreadServer = (HANDLE)_beginthreadex(NULL, 0,
		serverThread, NULL, 0, &iThreadID);

	bool bShutdown = false;
	while (!bShutdown) {
		
		_tprintf(TEXT("Please input the request:\n"));
#ifdef UNICODE
		_getws(g_szSharedBuffer);
#else
		gets(g_szSharedBuffer);
#endif

		bShutdown = (_tcscmp(g_szSharedBuffer, g_szShutdown) == 0);
		if (!bShutdown) {
			AcquireSRWLockExclusive(&g_srwLock);
			WakeConditionVariable(&g_cvSubmitRequest); // signal the server thread
			SleepConditionVariableSRW(&g_cvReturnResult, &g_srwLock, INFINITE, 0);
			_tprintf(TEXT("Result:\t%s\n"), g_szSharedBuffer);
			ReleaseSRWLockExclusive(&g_srwLock);
		}
		else {
			WakeConditionVariable(&g_cvSubmitRequest); // signal the server thread to shutdown.	
		}
	}

	WaitForSingleObject(hThreadServer, INFINITE);

	// Clean UP
	CloseHandle(hThreadServer);
	system("pause");
	return 0;
}





9.4 可等待的计时器内核对象


可等待的计时器是这样一种内核对象:他们会在某个指定的时间触发,或每间隔一段时间触发。

创建一个可等待的计时器

WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
CreateWaitableTimerW(
    _In_opt_ LPSECURITY_ATTRIBUTES lpTimerAttributes,
    _In_     BOOL bManualReset,
    _In_opt_ LPCWSTR lpTimerName
    );

打开一个可等待的计时器

WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
OpenWaitableTimerW(
    _In_ DWORD dwDesiredAccess,
    _In_ BOOL bInheritHandle,
    _In_ LPCWSTR lpTimerName
    );


bManualReset表示要创建的是手动重置还是自动重置的计时器。

手动重置,等待该计时器的所有线程都变成可调度状态

自动重置,只有一个等待该计时器的线程会变成可调度状态


调用SetWaitableTimer设置计时器,能让其触发

WINBASEAPI
BOOL
WINAPI
SetWaitableTimer(
    _In_ HANDLE hTimer,
    _In_ const LARGE_INTEGER * lpDueTime,
    _In_ LONG lPeriod,
    _In_opt_ PTIMERAPCROUTINE pfnCompletionRoutine,
    _In_opt_ LPVOID lpArgToCompletionRoutine,
    _In_ BOOL fResume
    );


hTimer :计时器内核对象句柄

pDueTime : 计时器第一次触发的时间应该在什么时候

lPeriod:计时器在第一次触发以后应该以怎样的频度触发。

例如以下代码把计时器第一次触发时间设为2018年1月1日下午1:00,以后没间隔6小时触发一次:

	// Declare our local variables.
	HANDLE				hTimer;
	SYSTEMTIME			st;
	FILETIME			ftLocal, ftUTC;
	LARGE_INTEGER		liUTC;

	// Create an auto-reset timer.
	hTimer = CreateWaitableTimer(NULL, FALSE, NULL);

	// First signaling is a January 1, 2018, at 1:00 P.M. (local time).
	st.wYear			= 2018;	// Year
	st.wMonth			= 1;	// January
	st.wDayOfWeek		= 0;	// Ignored
	st.wDay				= 1;	// The first of the month
	st.wHour			= 13;	// 1PM
	st.wMinute			= 0;	// 0 minutes into the hour
	st.wSecond			= 0;	// 0 seconds into the minute
	st.wMilliseconds	= 0;	// 0 milliseconds into the second

	SystemTimeToFileTime(&st, &ftLocal);

	// Convet local time to UTC time.
	LocalFileTimeToFileTime(&ftLocal, &ftUTC);
	// Convert FILETIME to LARGE_INTEGER because of different alignment.
	liUTC.LowPart = ftUTC.dwLowDateTime;
	liUTC.HighPart = ftUTC.dwHighDateTime;

	// Set the timer.
	SetWaitableTimer(hTimer, &liUTC, 6 * 60 * 60 * 1000,
		NULL, NULL, FALSE);


注意这里有一个FILETIME 和LARGE_INTEGER结构转换的问题

因为前者是32位对齐,后者是64位对齐。如果直接传递可能会导致对齐错误抛出一个(EXCEPTION_DATATYPE_MISALIGNMENT异常)


还可以给pDueTime传入一个相对时间,给其传入负值。(100纳秒的整数倍)

1秒 = 1000 毫秒 = 1000 000 微妙 = 10 000 000 (个 100 纳秒)


以下例子把计时器第一次触发时间设置为SetWaitableTimer调用结束的5秒钟后;

	// Declare our local variables.
	HANDLE				hTimer;
	LARGE_INTEGER		li;

	// Create an auto-reset timer.
	hTimer = CreateWaitableTimer(NULL, FALSE, NULL);

	// Set the timer to go off 5 seconds after calling SetWaitableTimer.
	// Timer unit is 100 nanoseconds.
	const int nTimerUnitsPerSecond = 10000000;

	// Negate the time so that SetWaitableTimer knows we
	// want relative time instead of absolute time.
	li.QuadPart = -(5 * nTimerUnitsPerSecond);

	// Set the timer.
	SetWaitableTimer(hTimer, &li, 6 * 60 * 60 * 1000,
		NULL, NULL, FALSE);


对于一次性计时器,只要给lPeriod 传递0 。然后调用CloseHandle关闭计时器即可。 或者调用SetWaitableTimer来重置计时器。


bResume(TRUE) 会使的计算机结束挂起模式(如果机器处于挂起模式下)并唤醒等待该计时器的线程。

FALSE 计时器会被触发,但是在机器继续执行前(挂起状态),被唤醒的线程都得不到CPU时间。


CancelWaitableTimer

WINBASEAPI
BOOL
WINAPI
CancelWaitableTimer(
    _In_ HANDLE hTimer
    );

取消计时器内核对象句柄所对应的计时器。

另外每次调用SetWaitableTimer都会重置计时器。

一个倒计时的例子。从9倒计时到0

#define  _CRT_SECURE_NO_WARNINGS
#include <tchar.h>
#include <windows.h>
#include <stdio.h>
#include <Shlobj.h>
#include <strsafe.h>
#include <malloc.h>
#include <process.h>
#include <winnt.h>
#include <ctype.h>

// Declare our local variables.
HANDLE				hTimer;


unsigned __stdcall OnTimer(void * param) {
	int nCount = 10;
	while (nCount--) {
		WaitForSingleObject(hTimer, INFINITE);
		_tprintf(TEXT("count:\t%d\n"), nCount);
	}
	return 0;
}

int _tmain(int argc, TCHAR* argv[], TCHAR * env[])
{

	LARGE_INTEGER		li;

	// Create an auto-reset timer.
	hTimer = CreateWaitableTimer(NULL, FALSE, NULL);

	// Set the timer to go off 5 seconds after calling SetWaitableTimer.
	// Timer unit is 100 nanoseconds.
	const int nTimerUnitsPerSecond = 10000000;

	// Negate the time so that SetWaitableTimer knows we
	// want relative time instead of absolute time.
	li.QuadPart = -(5 * nTimerUnitsPerSecond);

	// Set the timer.
	SetWaitableTimer(hTimer, &li, 1000,
		NULL, NULL, FALSE);

	// Create the OnTimer Thread
	unsigned int ThreadID;
	HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, OnTimer, NULL, NULL, &ThreadID);

	WaitForSingleObject(hThread, INFINITE);
	
	system("pause");
	return 0;
}





9.4.1 让可等待的计时器添加APC调用

APC异步过程调用(asynchronous procedure call)

SetWaitableTimer允许传入一个APC过程,触发了计时器会调用该过程。

当计时器触发时,当且仅当SetWaitableTimer调用的线程处于可提醒状态(Alertable stabe)(SleepEx, WaitForSingleObjectEx, WaitForMultipleObjectEx, MsgWaitForMultipleObjectEx,SignalObjectAndWait而进入的状态)

如果非处于可提醒状态,系统不会把计时器的APC函数添加到队列中。


#define  _CRT_SECURE_NO_WARNINGS
#include <tchar.h>
#include <windows.h>
#include <stdio.h>
#include <Shlobj.h>
#include <strsafe.h>
#include <malloc.h>
#include <process.h>
#include <winnt.h>
#include <ctype.h>
#include <io.h>
#include <fcntl.h>

VOID APIENTRY TimerAPCRountine(PVOID pvArgToCompletionRountine,
	DWORD dwTimerLowValue, DWORD dwTimerHighValue) {

	FILETIME	ftUTC, ftLocal;
	SYSTEMTIME	st;
	TCHAR		szBuf[256];

	// Put the time in a FILETIME structure.
	ftUTC.dwLowDateTime = dwTimerLowValue;
	ftUTC.dwHighDateTime = dwTimerHighValue;

	// Convert the UTC time to the user's local time.
	FileTimeToLocalFileTime(&ftUTC, &ftLocal);

	// Convert the FILETIME to the SYSTEMTIME structure
	// required by GetDateFormat and GetTimeFormat.
	FileTimeToSystemTime(&ftLocal, &st);

	// Construct a string with the
	// date/time that the timer went off.
	GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE,
		&st, NULL, szBuf, _countof(szBuf));
	_tcscat_s(szBuf, _countof(szBuf), TEXT(" "));
	GetTimeFormat(LOCALE_USER_DEFAULT, 0,
		&st, NULL, _tcschr(szBuf, TEXT('\0')),
		(int)(_countof(szBuf) - _tcslen(szBuf)));

	// Show the time to the user.
	//MessageBox(NULL, szBuf, TEXT("Timer went off at..."), MB_OK);
	_tprintf(TEXT("%s\n"), szBuf);
}

void SomeFunc() {
	// Create a timer. (It doesn't matter  whether it's manual-reset
	// or auto-reset.)
	HANDLE hTimer = CreateWaitableTimer(NULL, TRUE, NULL);

	// Set timer to go off in 5 seconds.
	LARGE_INTEGER li = { 0 };
	SetWaitableTimer(hTimer, &li, 5000, TimerAPCRountine, NULL, FALSE);

	// Wait in an alertable state for the timer to go off.
	SleepEx(INFINITE, TRUE);

	CloseHandle(hTimer);
}

int _tmain(int argc, TCHAR* argv[], TCHAR * env[])
{
	_setmode(_fileno(stdout), _O_WTEXT);
	SomeFunc();

	system("pause");
	return 0;
}




不应该同时使用等待函数又同时以可提醒的方式等待一个计时器。

例如

SetWaitableTimer(hTimer,..., TimerAPCRountine,...);

WaitForSingleObjectEx(hTimer, INFINITE, TRUE);


9.4.2 计时器的剩余问题

在通信协议中会大量用到计时器,但是通常为每个请求创建计时器内核对象,将严重影响系统性能。

有一个CreateThreadpoolTimer可以创建线程池函数对应的计时器。

大多数应用程序不使用APC, 而是使用IO完成端口


用户计时器SetTimer :在应用程序中使用大量的用户界面基础设置,从而消费更多的资源。而且通过消息机制触发,只有一个线程能得到通知

(WM_TIMER不一定准时,因为其具有最低的优先级)

可等待计时器是内核对象,可以在多个线程间共享。多个线程可以得到通知。


9.5 信号量内核对象

信号量内核对象用来对资源进行计数,除了使用计数器。还包含(32bit值)一个最大资源计数和当前资源计数。

如果当前资源计数大于0,信号量处于触发状态。

如果当前资源计数等于0,信号量处于未触发状态。


创建信号量

WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
CreateSemaphoreW(
    _In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
    _In_     LONG lInitialCount,
    _In_     LONG lMaximumCount,
    _In_opt_ LPCWSTR lpName
    );

psa pszName 前面讲过了。


dwFlags是系统保留的设为0.

参数lMaximumCount 系统能够处理的资源的最大数量

lInitialCoun 初始化有多少资源可用。

例如给服务器进程初始化,没有客户端请求,因此使用一下代码来调用CreateSemaphore

HANDLE hSemaphore = CreateSemaphore(NULL, 0, 5, NULL);


打开一个信号量

WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
OpenSemaphoreW(
    _In_ DWORD dwDesiredAccess,
    _In_ BOOL bInheritHandle,
    _In_ LPCWSTR lpName
    );

dwDesiredAccess参数指定访问权限


线程通过调用ReleaseSemahore来递增信号量的当前资源计数:

WINBASEAPI
BOOL
WINAPI
ReleaseSemaphore(
    _In_ HANDLE hSemaphore,
    _In_ LONG lReleaseCount,
    _Out_opt_ LPLONG lpPreviousCount
    );

lReleaseCount 的值会加到信号量当前资源计数上。


9.6 互斥量内核对象

互斥量(mutex)内核对象用来确保一个线程独占一个资源的访问。

互斥量与临界区的行为完全相同。(内部保护递归计数)


互斥量的规则:

1)如果线程ID为0,那么互斥量不为任何线程所占用,它处于触发状态

2)如果线程ID为非零值,那么一个线程已经占用了该互斥量,它处与未触发状态。

3)与所有其他内核对象不同,操作系统对互斥量进行了特殊处理,允许它们违反一些常规的规则。(递归计数器的存在,运行同一个线程ID多次进入)


创建互斥量

WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
CreateMutexW(
    _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes,
    _In_ BOOL bInitialOwner,
    _In_opt_ LPCWSTR lpName
    );


bInitialOwner控制互斥量的初始状态。FALSE, 互斥量的线程ID和递归计数都被设为0.处于触发状态。

给bInitialOwner穿TRUE,那么对象的线程ID被设为调用线程的ID,递归计数器被设为1.(未触发状态)



或者使用CreateMutexEx


WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
CreateMutexExW(
    _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes,
    _In_opt_ LPCWSTR lpName,
    _In_ DWORD dwFlags,
    _In_ DWORD dwDesiredAccess
    );

dwDesiredAccess指定访问权限

dwFlags(代替bInitialOwned) 0表示FALSE,  CREATE_MUTEX_INITIAL_OWNER等价于TRUE


另一个进程可以调用OpenMutex来得到一个已经存在的互斥量句柄。

WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
OpenMutexW(
    _In_ DWORD dwDesiredAccess,
    _In_ BOOL bInheritHandle,
    _In_ LPCWSTR lpName
    );


BOOL ReleaseMutex(HANDLE hMutex); 

使互斥量对象的递归计数器减1, 当递归计数器为0时,还会设置线程ID为0,这就触发了对象。


9.6.1 遗弃问题

互斥量具有线程所有权的功能,即使未触发也能多次进入。


如果占用互斥量的线程在释放互斥量之前终止(ExitThread ,TerminateThread ,ExitProcess 或TerminateProcess)

系统认为互斥量被遗弃(abandoned) 此时会自动将互斥量线程ID设为0,递归计数器设为0,并检查有没有正在等待该互斥量的线程。

等待函数返回WAIT_ABANDONED(只适用互斥量)


9.6.2 互斥量与关键段(临界区)的对比


互斥量的任意时间长度等待修正为: WaitForSingleObject(hmtx, dwMilliseconds);


9.6.3 Queue 示例程序

本章的Queue使用了互斥量和信号量来对一个队列的简单数据元素进行控制。

/******************************************************************************
Module:  Queue.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/


#include "..\CommonFiles\CmnHdr.h"
#include <windowsx.h>
#include <tchar.h>
#include <StrSafe.h>
#include "Resource.h"

//

class CQueue {
public:
	struct ELEMENT {
		int m_nThreadNum, m_nRequestNum;
		// Other element data should go there
	};
	typedef ELEMENT * PELEMENT;

private:
	PELEMENT	m_pElements;		// Array of elements to be processed
	int			m_nMaxElements;		// Maximum # of elements in the array
	HANDLE		m_h[2];				// Mutex & semaphore handles
	HANDLE		&m_hmtxQ;			// Reference to m_h[0]
	HANDLE		&m_hsemNumElements;	// Reference to m_h[1]

public:
	CQueue(int nMaxElements);
	~CQueue();

	BOOL Append(PELEMENT pElement, DWORD dwMilliseconds);
	BOOL Remove(PELEMENT pElement, DWORD dwMilliseconds);
};

//

CQueue::CQueue(int nMaxElements)
	: m_hmtxQ(m_h[0]),
	m_hsemNumElements(m_h[1]) {

	m_pElements = (PELEMENT)
		HeapAlloc(GetProcessHeap(), 0, sizeof(ELEMENT) * nMaxElements);
	m_nMaxElements = nMaxElements;
	m_hmtxQ = CreateMutex(NULL, FALSE, NULL);
	m_hsemNumElements = CreateSemaphore(NULL, 0, nMaxElements, NULL);
}

//

CQueue::~CQueue() {

	CloseHandle(m_hsemNumElements);
	CloseHandle(m_hmtxQ);
	HeapFree(GetProcessHeap(), 0, m_pElements);
}

//

BOOL CQueue::Append(PELEMENT pElement, DWORD dwTimeout) {

	BOOL fOk = FALSE;
	DWORD dw = WaitForSingleObject(m_hmtxQ, dwTimeout);

	if (dw == WAIT_OBJECT_0) {
		// This thread has exclusive access to the queue

		// Increment the number of elements in the queue
		LONG lPrevCount;
		fOk = ReleaseSemaphore(m_hsemNumElements, 1, &lPrevCount);
		if (fOk) {
			// The queue is not full, append the new element
			m_pElements[lPrevCount] = *pElement;
		}
		else {

			// The queue is full, set the error code and return failure
			SetLastError(ERROR_DATABASE_FULL);
		}

		// Allow other threads to access the queue
		ReleaseMutex(m_hmtxQ);
	}
	else {
		// Timeout, set error code and return failure
		SetLastError(ERROR_TIMEOUT);
	}

	return fOk;		// Call GetLastError for more info
}

//

BOOL CQueue::Remove(PELEMENT pElement, DWORD dwTimeout) {

	// Wait for exclusive access to queue and for queue to have element.
	BOOL fOk = (WaitForMultipleObjects(_countof(m_h), m_h, TRUE, dwTimeout)
		== WAIT_OBJECT_0);

	if (fOk) {
		// The queue has an element, pull it from the queue
		*pElement = m_pElements[0];

		// Shift the remaining elements down
		MoveMemory(&m_pElements[0], &m_pElements[1],
			sizeof(ELEMENT) * (m_nMaxElements - 1));

		// Allow other threads to access the queue
		ReleaseMutex(m_hmtxQ);
	}
	else {
		// Timeout, set error code and return failure
		SetLastError(ERROR_TIMEOUT);
	}

	return fOk;		// Call GetLastError for more info
}


//

CQueue g_q(10);						// The shared queue
volatile LONG g_fShutdown = FALSE;	// Signals client/server threads to die
HWND g_hwnd;						// How client/server threads give status

// Handles to all client/server threads & number of client/server threads
HANDLE	g_hThreads[MAXIMUM_WAIT_OBJECTS];
int		g_nNumThreads = 0;

//

DWORD WINAPI ClientThread(PVOID pvParam) {

	int nThreadNum = PtrToUlong(pvParam);
	HWND hwndLB = GetDlgItem(g_hwnd, IDC_CLIENTS);

	int nRequestNum = 0;
	while (1 != InterlockedCompareExchange(&g_fShutdown, 0, 0)) {

		// Keep track of the current processed element
		nRequestNum++;

		TCHAR sz[1024];
		CQueue::ELEMENT e = { nThreadNum, nRequestNum };

		// Try to put an element on the queue
		if (g_q.Append(&e, 200)) {

			// Indicate which thread sent it and which request
			StringCchPrintf(sz, _countof(sz), TEXT("Sending %d:%d"),
				nThreadNum, nRequestNum);
		}
		else {

			// Couldn't put an element on the queue
			StringCchPrintf(sz, _countof(sz), TEXT("Sending %d:%d (%s)"),
				nThreadNum, nRequestNum, (GetLastError() == ERROR_TIMEOUT)
				? TEXT("timeout") : TEXT("full"));
		}

		// Show result of appending element
		ListBox_SetCurSel(hwndLB, ListBox_AddString(hwndLB, sz));
		Sleep(2500);	// Wait before appending another element
	}

	return 0;
}

//

DWORD WINAPI ServerThread(PVOID pvParam) {

	int nThreadNum = PtrToUlong(pvParam);
	HWND hwndLB = GetDlgItem(g_hwnd, IDC_SERVERS);

	while (1 != InterlockedCompareExchange(&g_fShutdown, 0, 0)) {

		TCHAR sz[1024];
		CQueue::ELEMENT e;

		// Try to get an element from the queue
		if (g_q.Remove(&e, 5000)) {

			// Indicate which thread is processing it, which thread
			// sent it and which request we're processing
			StringCchPrintf(sz, _countof(sz), TEXT("%d: Processing %d:%d"),
				nThreadNum, e.m_nThreadNum, e.m_nRequestNum);

			// The server takes some time to process the request
			Sleep(2000 * e.m_nThreadNum);
		}
		else {
			// Couldn't get an element from the queue
			StringCchPrintf(sz, _countof(sz), TEXT("%d: (timeout)"), nThreadNum);
		}

		// Show result of processing element
		ListBox_SetCurSel(hwndLB, ListBox_AddString(hwndLB, sz));
	}

	return 0;
}

//

BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) {

	chSETDLGICONS(hwnd, IDI_QUEUE);

	g_hwnd = hwnd;		// Used by client/server threads to show status

	DWORD dwThreadID;

	// Create the client threads
	for (int x = 0; x < 4; x++)
		g_hThreads[g_nNumThreads++] =
		chBEGINTHREADEX(NULL, 0, ClientThread, (PVOID)(INT_PTR)x,
		0, &dwThreadID);

	// Create the server threads
	for (int x = 0; x < 2; x++)
		g_hThreads[g_nNumThreads++] =
		chBEGINTHREADEX(NULL, 0, ServerThread, (PVOID)(INT_PTR)x,
		0, &dwThreadID);

	return TRUE;
}

//

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

	switch (id) {
	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_INITDIALOG, Dlg_OnInitDialog);
		chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand);
	}

	return FALSE;
}

//

int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR pszCmdLine, int) {

	DialogBox(hInstExe, MAKEINTRESOURCE(IDD_QUEUE), NULL, Dlg_Proc);
	InterlockedExchange(&g_fShutdown, TRUE);

	// Wait for all the threads to terminate & then cleanup
	WaitForMultipleObjects(g_nNumThreads, g_hThreads, TRUE, INFINITE);
	while (g_nNumThreads--)
		CloseHandle(g_hThreads[g_nNumThreads]);

	return 0;
}

 End of File //




9.7 线程同步速查表




9.8 其他线程同步函数

9.8.1 异步设备I/O

异步设备IO(asynchronous device I/O)允许线程开始读取操作或写入操作,但不必等待读取操作或写入操作完成。

设备对象是是可同步的内核对象,可以调用WaitForSingleObject并传入句柄,套接字,通信端口等。


9.8.2 WaitForInputIdle函数

线程可以调用此函数将自己挂起

WINUSERAPI
DWORD
WINAPI
WaitForInputIdle(
    _In_ HANDLE hProcess,
    _In_ DWORD dwMilliseconds);

常用于等待子进程,父进程知道子进程已经初始化完毕的唯一方法,就是等待子进程,直到它不再处理任何输入为止。


当我们要强制在应用程序中输入一些按键的时候,也可以使用WaitForInputIdle。

当向目标进程发送一系列按键消息以后,调用WaitForInputIdle等待其处理完按键消息,然后再发送后续的按键消息。


9.8.3 MsgWaitForMultipleObjects(Ex)函数

WINUSERAPI
DWORD
WINAPI
MsgWaitForMultipleObjects(
    _In_ DWORD nCount,
    _In_reads_opt_(nCount) CONST HANDLE *pHandles,
    _In_ BOOL fWaitAll,
    _In_ DWORD dwMilliseconds,
    _In_ DWORD dwWakeMask);

WINUSERAPI
DWORD
WINAPI
MsgWaitForMultipleObjectsEx(
    _In_ DWORD nCount,
    _In_reads_opt_(nCount) CONST HANDLE *pHandles,
    _In_ DWORD dwMilliseconds,
    _In_ DWORD dwWakeMask,
    _In_ DWORD dwFlags);

函数功能:阻塞时仍可以响应消息


MsgWaitForMultipleObjects()函数类似WaitForMultipleObjects(),


但它会在“对象被激发”或“消息到达队列”时被唤醒而返回。


MsgWaitForMultipleObjects()多接收一个参数,允许指定哪些消息是观察对象。

一个应用的例子 该函数同时等待对象,若有消息到底也返回。运行主线程处理消息后继续等待。

	DWORD dwRet = 0;
	MSG msg;
	DWORD dwStartTime = GetTickCount();
	while (TRUE)
	{
		//超时判断  5s  
		dwRet = GetTickCount() - dwStartTime;
		if ((GetTickCount() - dwStartTime) > 10000)
		{
			AfxMessageBox(_T("获取数据超时,请检测设备网络连接!"), MB_OK | MB_ICONERROR);
			return NULL;
		}

		//wait for m_hThread to be over,and wait for    
		//QS_ALLINPUT(Any message is in the queue)   
		//dwRet = WaitForSingleObject(g_hRetEvent, INFINITE);  
		dwRet = MsgWaitForMultipleObjects(1, &g_hRetEvent, FALSE, 100, QS_ALLINPUT);
		switch (dwRet)
		{
		case WAIT_OBJECT_0: //返回数据达到  
			break; //break the loop    
		case WAIT_OBJECT_0 + 1: //界面消息  
			//get the message from Queue    
			//and dispatch it to specific window    
			if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			continue;
		case WAIT_TIMEOUT: //超时  
			continue;
		default:
			AfxMessageBox(_T("数据获取失败,未知错误!"), MB_OK | MB_ICONERROR);
			return NULL;
			break; // unexpected failure    
		}
		break;
	}

9.8.4 WaitForDebugEvent 函数

调试器Attach到被调试程序以后,调用WaitForDebugEvent来等待调试事件。

WINBASEAPI
BOOL
APIENTRY
WaitForDebugEvent(
    _Out_ LPDEBUG_EVENT lpDebugEvent,
    _In_ DWORD dwMilliseconds
    );

9.8.5 SignalObjectAndWait函数

WINBASEAPI
DWORD
WINAPI
SignalObjectAndWait(
    _In_ HANDLE hObjectToSignal,
    _In_ HANDLE hObjectToWaitOn,
    _In_ DWORD dwMilliseconds,
    _In_ BOOL bAlertable
    );
使用一个原子操作来触发一个内核对象,并等待另一个内核对象。

hObjectToSignal必须是一个互斥量,信号量或事件。(其他任何对象将导致函数返回WAIT_FAILD)调用GetLastError返回ERROR_INVALID_HANDLE.

hObjectToWaitOn可以是互斥量,信号量,事件,进程,线程,作业,控制台输入变更通知,作业。等等

dwMilliseconds 函数最多花多长时间来等待。

bAlertable表示当线程处于等待状态的时候,是否能够堆添加到队列中的异步过程调用进行处理。

返回值:WAIT_OBJECT_0, WAIT_TIMEOUT, WAIT_FAILED, WAIT)ABANDONED, WAIT_IO_COMPLETION


配合PulseEvent使用。

SignalObjectAndWait释放一个对象,同时立即等待(原子方式)

能确保其100%能看见别的线程调用的PulseEvent。


9.8.6 使用等待链遍历API来检测死锁

Vista系统以上提供了等待链遍历(Wait Chain Traversal, WCT)API,这些函数可以让我们列出所有的锁,并检测进程内部,甚至是进程之间的死锁。




LockCop示例程序

LockCop展示如何使用WCT函数来创建一个非常有用的工具。


等待链

一条等待链是一个序列,在这个序列中线程和同步对象交替出现,每个线程等待它后面的对象,而该对象却为等待链中更后面的线程所占用。

例如3212正在等待线程2260释放一个关键段,而线程2260正在等待线程3212释放另外一个关键段。这就是典型的死锁。

代码LockCop.cpp

/******************************************************************************
Module:  LockCop.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/


#include "..\CommonFiles\CmnHdr.h"     /* See Appendix A. */
#include "..\CommonFiles\ToolHelp.h"
#include "ChainParser.h"
#include "resource.h"

#include <windowsx.h>
#include <tchar.h>
#include <StrSafe.h>


///


// Global Variables
HINSTANCE		g_hInstance;
HWND			g_hDlg;

#define DETAILS_CTRL	GetDlgItem(g_hDlg, IDC_EDIT_DETAILS)
//

// Adds a String to the "Details" edit control
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 OnRefreshProcesses()
{
	HWND hwndList = GetDlgItem(g_hDlg, IDC_COMBO_PROCESS);
	SetWindowRedraw(hwndList, FALSE);
	ComboBox_ResetContent(hwndList);

	CToolhelp thProcesses(TH32CS_SNAPPROCESS);
	PROCESSENTRY32 pe = { sizeof(pe) };
	BOOL fOk = thProcesses.ProcessFirst(&pe);
	for (; fOk; fOk = thProcesses.ProcessNext(&pe)) {
		TCHAR sz[1024];

		// Place the process name(without its path) & ID in the list
		PCTSTR pszExeFile = _tcschr(pe.szExeFile, TEXT('\\'));
		if (pszExeFile == NULL) {
			pszExeFile = pe.szExeFile;
		}
		else {
			pszExeFile++;		// skip over the slash
		}

		StringCchPrintf(sz, _countof(sz), TEXT("%04u - %s"), pe.th32ProcessID, pszExeFile);
		int n = ComboBox_AddString(hwndList, sz);

		// Associate the process ID with the added item
		ComboBox_SetItemData(hwndList, n, pe.th32ProcessID);
	}
	ComboBox_SetCurSel(hwndList, 0);	// Select the first entry

	// Simulate the user selecting this first item so that the
	// results pane shows something interesting
	FORWARD_WM_COMMAND(g_hDlg, IDC_COMBO_PROCESS,
		hwndList, CBN_SELCHANGE, SendMessage);

	SetWindowRedraw(hwndList, TRUE);
	InvalidateRect(hwndList, NULL, FALSE);
}

//

void OnUpdateLocks()
{
	SetWindowText(DETAILS_CTRL, TEXT(""));	// Clear the output box

	// GetCurrent process from the combo box
	HWND hwndCtl = GetDlgItem(g_hDlg, IDC_COMBO_PROCESS);
	DWORD dwSelection = ComboBox_GetCurSel(hwndCtl);
	DWORD PID = (DWORD)ComboBox_GetItemData(hwndCtl, dwSelection);

	AddText(TEXT("Thread in process %u\r\n"), PID);

	CChainParser parser(DETAILS_CTRL);
	parser.ParseThreads(PID);
}


//

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

	switch (id) {
	case IDOK:
	case IDCANCEL:
		// User has clicked on the OK button
		// or dismissed the dialog with ESCAPE
		EndDialog(hwnd, id);
		break;

	case IDC_COMBO_PROCESS:
		if (codeNotify == CBN_SELCHANGE) {
			OnUpdateLocks();
		}
		break;

	case IDC_BTN_REFRESH:
		OnRefreshProcesses();
		break;

	case IDC_BTN_UPDATE:
		OnUpdateLocks();
		break;
	}
}

//

BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) {

	chSETDLGICONS(hwnd, IDI_LOCKCOP);

	// Keep track of the main dialog window handle
	g_hDlg = hwnd;

	// Have the results window use a fixed-pitch font
	SetWindowFont(GetDlgItem(hwnd, IDC_EDIT_DETAILS),
		GetStockFont(ANSI_FIXED_FONT), FALSE);

	// Fill up the process combo-box
	OnRefreshProcesses();

	return TRUE;
}

//

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 APIENTRY _tWinMain(HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPTSTR lpCmdLine,
	int nCmdShow) {

	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);

	// Keep track of the module handle
	g_hInstance = hInstance;

	// Enabling the debug privilege allows the application to see
	// Information about service applications
	CToolhelp::EnablePrivilege(SE_DEBUG_NAME, TRUE);

	// Show main window
	DialogBox(hInstance, MAKEINTRESOURCE(IDD_LOCKCOP), NULL, Dlg_Proc);

	// Restore privileges
	// Even though it is not really important since the process is existing
	CToolhelp::EnablePrivilege(SE_DEBUG_NAME, FALSE);

	return 0;
}



运行结果(检测例子BadLock)




LockCop不支持WaitForMultileObjects


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值