WIN32编程视频的相关笔记一

 

编码中GB2312是利用ASIIC的127到256的两个确定一个汉字,所以不同语言不兼容,unicode只是一种编码,即所有语言符号有唯一值,而他的存储格式被分为unicode-8或者-16,即内核单元用几位,具体符号比如16的开头有个FFFE和FEFF是标志大端还是小段存储,在文本开头显示,具体存储看其的编码比如分3个段加不同的前缀,由于unicode最多四个字节存储,但有些一字节即可,所以浪费空间,网络传输一般以unicode-8传输
1、宽字符的使用

“中”字的编码:

ASCII:d6 d0
UNICODE:4e 2d

char x = ‘中’;

wchar_t x1 = ‘中’;

观察内存中的值,为什么会出现这种情况?

如何告诉编译器我们要使用的是Unicode的那张表呢?

wchar_t x1 = L’中’;

2、宽字符串的使用

char x[] = “中国”;
//d6 d0 b9 fa 00 使用拓展ASCII编码表 以00(\0)结尾

wchar_t x1[] = L”中国”;
//2d 4e fd 56 00 00 使用UNICODE编码表 以00 00(\0\0)结尾

3、在控制台打印

char x[] = “中国”;

wchar_t x1[] = L”中国”;

printf(“%s\n”,x); //使用控制台默认的编码

wprintf(L”%s\n”,x1); //默认使用英文

告诉编译器,使用控制台默认的编码格式

(1) 包含头文件 #include <locale.h>

(2) setlocale(LC_ALL,””); //使用控制台默认的编码

4、字符串长度(头文件:#include<string.h>)

char x[] = “中国”;

wchar_t x1[] = L”中国”;

strlen(x); //取得多字节字符串中字符长度,不包含 00

wcslen(x1); //取得多字节字符串中字符长度,不包含 00 00

5、字符串复制

char x[] = “china”;

char x1[] = “123”;

strcpy(x,x1);

wchar_t y[] = L”中国”;

wchar_t y1[] = L”好”;

wcscpy(y,y1);

C语言中的宽字符和多字符

char wchar_t //多字节字符类型 宽字符类型

printf wprintf //打印到控制台函数

strlen wcslen //获取长度

strcpy wcscpy //字符串复制

strcat wcscat //字符串拼接

strcmp wcscmp //字符串比较

strstr wcsstr //字符串查找

1、什么是Win32 API?有哪些?在哪里?

主要是存放在 C:\WINDOWS\system32 下面所有的dll

2、非常重要的几个DLL

Kernel32.dll:最核心的功能模块,比如管理内存、进程和线程相关的函数等.

User32.dll:是Windows用户界面相关应用程序接口,如创建窗口和发送消息等.

GDI32.dll:全称是Graphical Device Interface(图形设备接口),包含用于画图和显示文本的函数
比如要显示一个程序窗口,就调用了其中的函数来画这个窗口

3、Win32 API中的宽字符和多字节字符

Windows是使用C语言开发的,Win32 API同时支持宽字符与多字节字符.

(1) 字符类型 (2) 字符串指针

char CHAR PSTR(LPSTR) 指向多字节字符串

wchar_t WCHAR PWSTR(LPWSTR) 指向宽字符串

宏 TCHAR 宏 PTSTR(LPTSTR)

字符数组赋值

CHAR cha[] = “中国”;

WCHAR chw[] = L”中国”;

TCHAR cht[] = TEXT(“中国”);

为字符串指针赋值:

PSTR pszChar = “china”; //多字节字符

PWSTR pszWChar = L”china”; //宽字符

PTSTR pszTChar = TEXT(“china”); //如果项目是ASCII的 相当于”china” UNICODE 相当于L”china”

4、各种版本的MessageBox

MessageBoxA(0,”内容多字节”,”标题”,MB_OK);

MessageBoxW(0,L”内容宽字节”,L”标题”,MB_OK);

MessageBox(0,TEXT(“根据项目字符集决定”),TEXT(“标题”),MB_OK);

Windows提供的API 凡是需要传递字符串参数的函数,都会提供两个版本和一个宏.

内核对象就是创立时候会在内核里创建一个结构体的对象,看什么属于内核对象可以利用函数closehandle函数查看,或者看其创建的函数,比如第一个参数是不是安全描述符,有安全描述符参数的,一定是内核对象 很多结构体第一个参数都是当前结构体长度大小,为了以后扩容

句柄表作用是为了内核安全,实用句柄表访问内核对象,用户层不会知道内核对象实际地址,另外句柄表是进程私有的,即同一个进程,在不同句柄表的值不一样,内核对象只有进程才有句柄表,但是有个全局句柄表存储的PID,这个是全局的

CloseHandle的作用是将计数器减1,并不是直接关掉,然后为0才关闭,但是线程比较特殊,必须为0并且TerminTerminate了才关闭,所以如果只是线程只是基数为0,也没有被关闭。如果他是某进程主线程,改进程就还没死

进程的创建过程

  1. 映射EXE文件
  2. 创建内核对象EPROCESS
  3. 映射系统DLL(ntdll.dll)
  4. 创建线程内核对象ETHREAD
  5. 如果线程没有挂起为系统启动线程
    映射DLL(ntdll.LdrInitiailize Thunk)
    线程开始执行
  6. 如果线程挂起方式创建的:(fdwCreate参数是CREATE_SUSPENDED)
    恢复以后开始执行
    映射DLL(ntdll.LdrInitiailize Thunk)
    线程开始执行
1
2
3
4
5
6
7
8
9
10
11
12
BOOL CreateProcess(  
  LPCWSTR pszImageName,  
  LPCWSTR pszCmdLine,  
  LPSECURITY_ATTRIBUTES psaProcess,//安全描述符   
  LPSECURITY_ATTRIBUTES psaThread,  
  BOOL fInheritHandles, //是否允许继承    
  DWORD fdwCreate, 
  LPVOID pvEnvironment,  
  LPWSTR pszCurDir,//子进程的工作路径  
  LPSTARTUPINFOW psiStartInfo,//指向一个用于决定新进程的主窗体如何显示的STARTUPINFO结构体。  
  LPPROCESS_INFORMATION pProcInfo  //创建之后的返回的参数
);

lpApplicationName

指向一个NULL结尾的、用来指定可执行模块的字符串。
这个字符串可以是可执行模块的绝对路径,也可以是相对路径,在后一种情况下,函数使用当前驱动器和目录建立可执行模块的路径。
这个参数可以被设为NULL,在这种情况下,可执行模块的名字必须处于 lpCommandLine 参数最前面并由空格符与后面的字符分开。

lpProcessAttributes

指向一个SECURITY_ATTRIBUTES结构体,这个结构体决定是否返回的句柄可以被子进程继承。如果lpProcessAttributes参数为空(NULL),那么句柄不能被继承。

fInheritHandles

指示新进程是否从调用进程处继承了句柄。
如果参数的值为真,调用进程中的每一个可继承的打开句柄都将被子进程继承。被继承的句柄与原进程拥有完全相同的值和访问权限。

DWORD fdwCreate

指定附加的、用来控制优先类和进程的创建的标志。以下的创建标志可以以除下面列出的方式外的任何方式组合后指定。
比如挂起之类的(fdwCreate参数是CREATE_SUSPENDED

LPVOID lpEnvironment

指向一个新进程的环境块。如果此参数为空,新进程使用调用进程的环境。

LPCWSTR pszImageName

这个参数是模块路径,指向一个以NULL结尾的字符串,这个字符串用来指定子进程的工作路径。这个字符串必须是一个包含驱动器名的绝对路径。如果这个参数为空,新进程将使用与调用进程相同的驱动器和目录。这个选项是一个需要启动应用程序并指定它们的驱动器和工作目录的外壳程序的主要条件。

参数 LPSTARTUPINFOW psiStartInfo

typedef struct _STARTUPINFO {
DWORD cb;
LPTSTR lpReserved;
LPTSTR lpDesktop;
LPTSTR lpTitle;
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
WORD cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;

LPPROCESS_INFORMATION pProcInfo

最后一个参数,是个返回的参数
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION;

GetModudleFileName//得到当前模块路径

GetCurrentDirecttory//得到当前工作路径

CreateThread原型:

1
2
3
4
5
6
7
8
9
10
11
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD
SIZE_T dwStackSize,//initialstacksize 线程堆栈大小
LPTHREAD_START_ROUTINE lpStartAddress,//threadfunction
LPVOID lpParameter,//threadargument //参数
DWORD dwCreationFlags,//creationoption,创建方式,比如挂起
LPDWORD lpThreadId//threadidentifier,返回的参数,线程ID
)

线程回调是可以没有返回值参数的,格式跟要求一样就行,可以强转,还有要传入给回调参数时,比如从主线程传入的话,要保证不要让主线程都结束了,所以有时候用全局变量传参。

多个线程可以使用同一个回调函数,但是运行起来他们的栈都是不同的

挂起几次,就要回复几次

获取线程退出值,可以用

1
2
3
4
5
6
7
BOOL   GetExitCodeThread (

       HANDLE         hThread,                  // in,线程handle,也就是CreateThread()的返回值

       LPDWORD      lpExitCode               //out,存储线程结束代码,也就是线程的返回值

);

等待内核对象结束,单个和多个可用

1
2
3
4
5
6
7
8
9
10
11
12
13
DWORD WaitForSingleObject(

HANDLE hHandle,
DWORD dwMilliseconds//等待时间
);


DWORD WaitForMultipleObjects(
DWORD nCount,//等待的句柄数量
const HANDLE* lpHandles,//指向对象句柄数组的指针
BOOL bWaitAll,//true为全部等待,false为等待第一个
DWORD dwMilliseconds
);

获取线程信息

线程必须先挂起才能获取上下文,也就是上下文环境context
然后context内容太多,需要设立个Flag确定得到哪部分信息
关键代码如下

1
2
3
4
SuspendThread(线程ID);
CONTEXT context;
context.ContextFlags=CONTEXT_INTEGER;
GetThreadContext(线程ID,&context);

能获取就能设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
1、创建CRITICAL_SECTION:					
					
CRITICAL_SECTION cs;					
					
2、在使用前进行初始化					
					
InitializeCriticalSection(&cs);					
					
					
3、在函数中使用:					
					
DWORD WINAPI 线程A(PVOID pvParam) 					
{					
      EnterCriticalSection(&cs);					
					
      //对全局遍历X的操作					
					
      LeaveCriticalSection(&cs);					
   return(0);					
}					
					
					
DWORD WINAPI 线程B(PVOID pvParam) 					
{					
      EnterCriticalSection(&g_cs);					
					
      //对全局遍历X的操作					
					
      LeaveCriticalSection(&g_cs);					
   return(0);					
}					
					
4、删除CRITICAL_SECTION					
					
VOID DeleteCriticalSection(PCRITICAL_SECTION pcs);

下面是临界区的一个使用实例,生产消费问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//临界区		
CRITICAL_SECTION g_cs;		
int g_Max = 10;	//生产几个产品	
int g_Number = 0;//容器,存储产品                      		
//生产者线程函数  		
DWORD WINAPI ThreadProduct(LPVOID pM)  		
{  		
    for (int i = 0; i < g_Max; i++)		
    {  		
        //互斥的访问缓冲区  		
        EnterCriticalSection(&g_cs); 		
		g_Number = 1; 
		DWORD id = GetCurrentThreadId();
		printf("生产者%d将数据%d放入缓冲区\n",id, g_Number); 
        LeaveCriticalSection(&g_cs); 		
		
    }  		
    return 0;  		
}  		
//消费者线程函数		
DWORD WINAPI ThreadConsumer(LPVOID pM)  		
{  		
    for (int i = 0; i < g_Max; i++)		
    {  		
        //互斥的访问缓冲区  		
        EnterCriticalSection(&g_cs);  		
		g_Number = 0; 
		DWORD id = GetCurrentThreadId();
		printf("----消费者%d将数据%d放入缓冲区\n",id, g_Number); 
	LeaveCriticalSection(&g_cs); 	
    }  		
    return 0;  		
}  		
		
int main(int argc, char* argv[])		
{		
	InitializeCriticalSection(&g_cs);	
		
		
    HANDLE hThread[2]; 		
		
    hThread[0] = ::CreateThread(NULL, 0, ThreadProduct, NULL, 0, NULL); 		
	hThread[1] = ::CreateThread(NULL, 0, ThreadConsumer, NULL, 0, NULL);	
		
    WaitForMultipleObjects(2, hThread, TRUE, INFINITE);  		
    CloseHandle(hThread[0]);  		
    CloseHandle(hThread[1]);		
		
	//销毁 	
    DeleteCriticalSection(&g_cs);  		
		
		
	return 0;	
}

当线程不再试图访问共享资源时
注意:要将对全局变量所有操作都放在临界区里,才是正确的

互斥体

上文中的临界区是在全局变量,所以只能是同一个进程同步,要使多个进程同步,临界资源就得在内核中,这就是接下来说的互斥体。

1
2
3
4
5
6
  LPSECURITY_ATTRIBUTES lpMutexAttributes,
  BOOL bInitialOwner,//希望一创建就有信号,这里是false,
  //没信号互斥体就是没信号,不给信号所有线程会一直等待,
  //填True,如果是互斥体的拥有线程,也可以继续执行   
  LPCTSTR lpName//互斥体名字
);

返回值:
A handle to the mutex object indicates success. If the named mutex object existed before the function call, the function returns a handle to the existing object, and GetLastError returns ERROR_ALREADY_EXISTS. Otherwise, the caller created the mutex.

NULL indicates failure. To get extended error information, call GetLastError.

逻辑上的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
进程一:

HANDLE g_hMutex = CreateMutex(NULL,FALSE, "XYZ");


进程二:

HANDLE g_hMutex = OpenMutex(MUTEX_ALL_ACCESS,FALSE, "XYZ");

WaitForSingleObject(g_hMutex,INFINITE);

//逻辑代码

ReleaseMutex(g_hMutex);


进程三:

HANDLE g_hMutex = OpenMutex(MUTEX_ALL_ACCESS,FALSE, "XYZ");

WaitForSingleObject(g_hMutex,INFINITE);

//逻辑代码

ReleaseMutex(g_hMutex);

下面是互斥体的生产者消费者问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//互斥体		
HANDLE hMutex;		
int g_Max = 10;		
int g_Number = 0;                      		
//生产者线程函数  		
DWORD WINAPI ThreadProduct(LPVOID pM)  		
{  		
    for (int i = 0; i < g_Max; i++)		
    {  		
        //互斥的访问缓冲区  		
        WaitForSingleObject(hMutex,INFINITE);		
		g_Number = 1; 
		DWORD id = GetCurrentThreadId();
		printf("生产者%d将数据%d放入缓冲区\n",id, g_Number); 
        ReleaseMutex(hMutex);		
    }  		
    return 0;  		
}  		
//消费者线程函数		
DWORD WINAPI ThreadConsumer(LPVOID pM)  		
{  		
    for (int i = 0; i < g_Max; i++)		
    {  		
        //互斥的访问缓冲区  		
        WaitForSingleObject(hMutex,INFINITE);		
		g_Number = 0; 
		DWORD id = GetCurrentThreadId();
		printf("----消费者%d将数据%d放入缓冲区\n",id, g_Number); 
		ReleaseMutex(hMutex);
    }  		
    return 0;  		
}  		
		
int main(int argc, char* argv[])		
{		
	//创建一个互斥体	
	hMutex =  CreateMutex(NULL,FALSE,NULL);	
		
		
    HANDLE hThread[2]; 		
		
    hThread[0] = ::CreateThread(NULL, 0, ThreadProduct, NULL, 0, NULL); 		
	hThread[1] = ::CreateThread(NULL, 0, ThreadConsumer, NULL, 0, NULL);	
		
    WaitForMultipleObjects(2, hThread, TRUE, INFINITE);  		
    CloseHandle(hThread[0]);  		
    CloseHandle(hThread[1]);		
		
	//销毁 	
    CloseHandle(hMutex);		
		
		
	return 0;	
}

互斥体与临界区的区别:

1、临界区只能用于单个进程间的线程控制.

2、互斥体可以设定等待超时,但临界区不能.

3、线程意外终结时,Mutex可以避免无限等待.

4、Mutex效率没有临界区高.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
DWORD WaitForSingleObject(					
  HANDLE hHandle,        // handle to object					
  DWORD dwMilliseconds   // time-out interval					
);					
					
功能说明:					
					
等待函数可使线程自愿进入等待状态,直到一个特定的内核对象变为已通知状态为止.					
					
hHandle:					
					
内核对象句柄,可以是进程也可以是线程.					
					
dwMilliseconds:					
					
等待时间,单位是毫秒  INFINITE(-1)一直等待					
					
返回值:					
					
WAIT_OBJECT_0(0)			等待对象变为已通知		
					
WAIT_TIMEOUT(0x102)			超时		
					
					
特别说明:					
					
1、内核对象中的每种对象都可以说是处于已通知或未通知的状态之中					
					
2、这种状态的切换是由Microsoft为每个对象建立的一套规则来决定的					
					
3、当线程正在运行的时候,线程内核对象处于未通知状态					
					
4、当线程终止运行的时候,它就变为已通知状态					
					
5、在内核中就是个BOOL值,运行时FALSE 结束TRUE					
					
代码演示:					
					
DWORD WINAPI ThreadProc1(LPVOID lpParameter)					
{					
	for(int i=0;i<5;i++)				
	{				
		printf("+++++++++\n");			
		Sleep(1000);			
	}				
	return 0;				
}					
					
int main(int argc, char* argv[])					
{					
					
	//创建一个新的线程				
	HANDLE hThread1 = ::CreateThread(NULL, 0, ThreadProc1, 				
		NULL, 0, NULL);			
					
	DWORD dwCode = ::WaitForSingleObject(hThread1, INFINITE);				
					
	MessageBox(0,0,0,0);				
					
	return 0;				
}

下面是两个信号等待函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
							
DWORD WaitForMultipleObjects(							
  DWORD nCount,             // number of handles in array							
  CONST HANDLE *lpHandles,  // object-handle array							
  BOOL bWaitAll,            // wait option							
  DWORD dwMilliseconds      // time-out interval							
);							
							
功能说明:							
							
同时查看若干个内核对象的已通知状态							
							
nCount:							
							
要查看内核对象的数量							
							
lpHandles:							
							
内核对象数组							
							
bWaitAll:							
							
等到类型  TRUE 等到所有变为已通知  FALSE 只要有一个变为已通知							
							
dwMilliseconds:							
							
超时时间							
							
INFINITE一直等待							
							
返回值:							
							
bWaitAll为TRUE时,返回WAIT_OBJECT_0(0) 代码所以内核对象都变成已通知							
							
bWaitAll为FALSE时,返回最先变成已通知的内核对象在数组中的索引							
							
WAIT_TIMEOUT(0x102)			超时				
							
							
代码演示:							
							
DWORD WINAPI ThreadProc1(LPVOID lpParameter)							
{							
	for(int i=0;i<5;i++)						
	{						
		printf("+++++++++\n");					
		Sleep(1000);					
	}						
	return 0;						
}							
							
DWORD WINAPI ThreadProc2(LPVOID lpParameter)							
{							
	for(int i=0;i<3;i++)						
	{						
		printf("---------\n");					
		Sleep(1000);					
	}						
							
	return 0;						
}							
							
							
int main(int argc, char* argv[])							
{							
							
	HANDLE hArray[2];						
							
	//创建一个新的线程						
	HANDLE hThread1 = ::CreateThread(NULL, 0, ThreadProc1, 						
		NULL, 0, NULL);					
							
	//创建一个新的线程						
	HANDLE hThread2 = ::CreateThread(NULL, 0, ThreadProc2, 						
		NULL, 0, NULL);					
							
	hArray[0] = hThread1;						
	hArray[1] = hThread2;						
							
	DWORD dwCode = ::WaitForMultipleObjects(2, hArray,FALSE,INFINITE);						
							
	MessageBox(0,0,0,0);						
							
	return 0;						
}

事件

事件也是通知类型函数,下面是创建通知类型函数

1
2
3
4
5
6
7
8
9
10
11
HANDLE CreateEvent(				
  LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性 NULL时为系统默认				
  BOOL bManualReset,                      
  // 指定将事件对象创建成手动复原还是自动复原。如
  //果是TRUE,那么必须用ResetEvent函数来手工将事
  //件的状态复原到无信号状态。如果设置为FALSE,当一个等待线程被释放以后,
  //系统将会自动将事件状态复原为无信号状态。
  BOOL bInitialState,                      // TRUE 已通知状态  FALSE未通知状态,有没有信号				
  LPCTSTR lpName                           // 对象名称 以NULL结尾的字符串				
);

通知类型就是比如CreateEvent第二个项设置为true时,同步类型,只要有信号,WaitForSingleObject等到之后不会修改当前等待对象的状态,所有等待线程同时执行,false的话是互斥类型,WaitForSingleObject会修改当前等待对象的状态,有信号时只有一个能执行,剩下的是等待状态。会修改信号的状态

注意setEvent是把自己线程挂起的意思,唤醒另一个线程,把资源让出去。

后面是使用的示例

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
2、事件对象的控制				
				
BOOL SetEvent(HANDLE hEvent);				       //将对象设置为已通知
				
3、关闭时间对象句柄				
				       //关闭句柄
CloseHandle();				
				
				
4、线程控制实验:只读形式的线程控制				
				
HANDLE g_hEvent;				
				
HWND hEdit1;				
HWND hEdit2;				
HWND hEdit3;				
HWND hEdit4;				
HANDLE hThread1;				
HANDLE hThread2;				
HANDLE hThread3;				
HANDLE hThread4;				
				
DWORD WINAPI ThreadProc1(LPVOID lpParameter)				
{				
	//创建事件			
	//默认安全属性  手动设置未通知状态(TRUE)  初始状态未通知 没有名字 			
	g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);			
	HANDLE hThread[3];			
	//创建3个线程			
	hThread[0] = ::CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);			
	hThread[1] = ::CreateThread(NULL, 0, ThreadProc3, NULL, 0, NULL);			
	hThread[2] = ::CreateThread(NULL, 0, ThreadProc4, NULL, 0, NULL);			
				
	//设置文本框的值			
	SetWindowText(hEdit1,"1000");			
				
	//设置事件为已通知			
	SetEvent(g_hEvent);			
				
	//等待线程结束 销毁内核对象			
	WaitForMultipleObjects(3, hThread, TRUE, INFINITE);  			
	CloseHandle(hThread[0]);  			
	CloseHandle(hThread[1]);			
	CloseHandle(hThread[2]);			
	CloseHandle(g_hEvent);  			
				
	return 0;			
}				
				
DWORD WINAPI ThreadProc2(LPVOID lpParameter)				
{				
	TCHAR szBuffer[10] = {0};			
				
	//当事件变成已通知时 			
	WaitForSingleObject(g_hEvent, INFINITE);			
				
	//读取内容			
	GetWindowText(hEdit1,szBuffer,10);			
				
	SetWindowText(hEdit2,szBuffer);			
				
	return 0;			
}				
DWORD WINAPI ThreadProc3(LPVOID lpParameter)				
{				
	TCHAR szBuffer[10] = {0};			
				
	//当事件变成已通知时 			
	WaitForSingleObject(g_hEvent, INFINITE);			
				
	//读取内容			
	GetWindowText(hEdit1,szBuffer,10);			
				
	SetWindowText(hEdit3,szBuffer);			
				
	return 0;			
}				
DWORD WINAPI ThreadProc4(LPVOID lpParameter)				
{				
	TCHAR szBuffer[10] = {0};			
				
	//当事件变成已通知时 			
	WaitForSingleObject(g_hEvent, INFINITE);			
				
	//读取内容			
	GetWindowText(hEdit1,szBuffer,10);			
				
	SetWindowText(hEdit4,szBuffer);			
				
	return 0;			
}

 

另外通知类型还能实现线程同步。

注意:互斥里面没有有序的概念,可能某个线程多次执行,而另一个一直不执行
而互斥加有序,就是同步

1

1

下面举个示例,生产者和消费者问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//事件和临界区		
HANDLE g_hSet, g_hClear;		
int g_Max = 10;		
int g_Number = 0;		
                   		
//生产者线程函数  		
DWORD WINAPI ThreadProduct(LPVOID pM)  		
{  		
    for (int i = 0; i < g_Max; i++)		
    {  		
        WaitForSingleObject(g_hSet, INFINITE);  		
		g_Number = 1; 
		DWORD id = GetCurrentThreadId();
		printf("生产者%d将数据%d放入缓冲区\n",id, g_Number); 
        SetEvent(g_hClear);   		
    }  		
    return 0;  		
}  		
//消费者线程函数		
DWORD WINAPI ThreadConsumer(LPVOID pM)  		
{  		
    for (int i = 0; i < g_Max; i++)		
    {  		
        WaitForSingleObject(g_hClear, INFINITE);  		
		g_Number = 0; 
		DWORD id = GetCurrentThreadId();
		printf("----消费者%d将数据%d放入缓冲区\n",id, g_Number); 
        SetEvent(g_hSet);   		
    }  		
    return 0;  		
}  		
		
int main(int argc, char* argv[])		
{		
		
    HANDLE hThread[2]; 		
		
	g_hSet = CreateEvent(NULL, FALSE, TRUE, NULL);  	
    g_hClear = CreateEvent(NULL, FALSE, FALSE, NULL); 		
		
    hThread[0] = ::CreateThread(NULL, 0, ThreadProduct, NULL, 0, NULL); 		
	hThread[1] = ::CreateThread(NULL, 0, ThreadConsumer, NULL, 0, NULL);	
		
    WaitForMultipleObjects(2, hThread, TRUE, INFINITE);  		
    CloseHandle(hThread[0]);  		
    CloseHandle(hThread[1]);		
		
	//销毁 	
	CloseHandle(g_hSet);  	
    CloseHandle(g_hClear);  		
		
	return 0;	
}

总结:

事件和互斥体区别: 事件有通知类型,可以同时给很多个线程发消息。互斥体没办法线程同步,事件可以。

窗口

本质是画出来的。通过API

窗口的本质 GUI(User32,画图,windows现成的界面)GDI(gdi32 绘图,自己画的)

2

2

Handle是私有句柄表的索引,HWND是全局句柄表的索引。

GDI 图形设备接口(Graphics Device Interface)

3

3

先得到窗口句柄(设备对象),
HWND

再得到设备上下文,
HDC hdc=GetDC(hwnd)

创建画笔
hpen=Create(当前笔的分割,宽度,颜色)

关联笔和对象
selectObject(hdc,hpen)

移动初始笔的位置
MoveToEx(hdc,0,400,原来坐标)

然后开始画比如
LineTO(hdc,x坐标,y坐标)

释放资源
DeleteObjct(hpen),ReleaseDc(hwnd,hdc)

消息队列

什么是消息:
操作系统吧动作记录下来,存储到一个结构体中,这个结构体就是一个消息。

消息队列:每个线程只有一个消息队列

4

4

消息产生时,操作系统先接受,遍历所有窗口对象,找到之后,发送到该窗口对象进程的线程,然后线程把他放到消息队列里。
一个线程可以多个窗口,一个窗口只属于一个线程

第一个Windows程序

句柄所属
HWND窗口句柄
HANDLE内核对象句柄
HDC设备句柄
HINSTANCE模块句柄

真正对象在0环,这个只是个索引,一个DWORD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
LRESULT CALLBACK WindowProc(//窗口回调
  HWND hwnd, 
  UINT uMsg, 
  WPARAM wParam, 
  LPARAM lParam 
){
    return DefWindowProc(hStatusWnd, uMsg, wParam, lParam)//调用下默认处理函数
}


int CALLBACK WinMain(
  _In_  HINSTANCE hInstance,//指向一个模块的句柄,当前模块在内存中的位置
  _In_  HINSTANCE hPrevInstance,//永远空
  _In_  LPSTR lpCmdLine,//对应CreateProcess的命令行参数
  _In_  int nCmdShow//对应CreateProcess的创建属性
){
    TCHAR className[]=TEXT("chuangkouming");
    //1.第一步,定义你的窗口怎么样的
    WINCLASS wndClass={0};//初始化窗口类
    wndClass.hrBackgound=(HBRUSH)COLOR_BACKGROUND;//设置背景颜色,画刷类型
    wndClass.lpszClassName=className;//设置窗口名
    wndClass.hInstance=hInstance;//当前窗口属于哪个程序
    wndClass.lpfnWndProc=WindowProc;//设置窗口回调函数
    
    //2.注册窗口类
    RegisterClass(&wndClass)
    //3.创建窗口类
  HWND CreateWindow( 
  LPCTSTR lpClassName, //窗口类名
  LPCTSTR lpWindowName, //窗口名
  DWORD dwStyle, //窗口样式
  int x, 
  int y, 
  int nWidth, 
  int nHeight, 
  HWND hWndParent, //父窗口句柄
  HMENU hMenu, //菜单句柄
  HANDLE hInstance, //属于哪个模块
  PVOID lpParam //参数
);
ShowWindow(hwnd,SW_SHOW)
//3.接受消息并处理
MSG msg;
BOOL GetMessage(
  LPMSG lpMsg, //消息放在这
  HWND hWnd, //null所有消息
  UINT wMsgFilterMin, //过滤条件。0是全要
  UINT wMsgFilterMax 
      );
BOOL bRet;
//例子
while( (bRet = GetMessage( &msg, hWnd, 0, 0 )) != 0)
{ 
    if (bRet == -1)
    {
        // handle the error and possibly exit
    }
    else
    {
    //转换消息,把键盘虚拟码转成字符
        TranslateMessage(&msg); 
    //分发消息,
        DispatchMessage(&msg); 
    }
}

}

char szOutBuff[0x80]

窗口不能用printf,必须用OutputDebugString(szOutBuff),但是这不能打印变量,所以先得用sprint(szOutbuff,”%d”,GetLastError())格式化字符串

5

5

消息类型

typedef struct tagMSG {
HWND hwnd; //属于哪个窗口句柄
UINT message;//消息的类型
WPARAM wParam; //
LPARAM lParam; //
DWORD time; //消息什么时候产生的
POINT pt; //消息在什么类型产生的
} MSG;

子窗口

8

8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
HWND CreateWindow( 
  "Edit", //窗口类名
  "", //窗口名
  WS_CHILD|//窗口样式,子窗口必带
  WS_VISIBLE|WS_VSCROLL
, 
  int x, 
  int y, 
  int nWidth, 
  int nHeight, 
  HWND hWndParent, //父窗口句柄
  HMENU hMenu, //父窗口的话菜单句柄,子窗口的话是标识
  HANDLE hInstance, //属于哪个模块,一般用个全局变量,在父窗口那赋值
  PVOID lpParam //参数
);

子窗口通过WM_COMMAND向父窗口发消息,在父窗口回调里可以处理WM_COMMAND,如果要区分是哪个子窗口,可以通过回调函数的参数确定

 

1
2
WPARAM wParam; //
  LPARAM lParam; //

 

一个是编号(HMENU hMenu,也就是wParam的低16位LOWORD),一个是句柄,
如果想设置文本框内容可以
SetDlgText(hwnd,编号,字符串)
得到就是GetDlgText

按钮就是一个子窗口,
按钮的WNDCLASS不是我们定义的,是系统预定义好的。如果我们想知道,系统预定义的WNDCLASS都包含什么样的信息

怎么做?

TCHAR szBuffer[0x20];
GetClassName(hwndPushButton,szBuffer,0x20);

WNDCLASS wc;
GetClassInfo(hAppInstance,szBuffer,&wc);
OutputDebugStringF(“–>%s\n”,wc.lpszClassName);
OutputDebugStringF(“–>%x\n”,wc.lpfnWndProc);

总结:

1、按钮是一种特殊的窗体,并不需要提供单独的窗口回调函数.

2、当按钮有事件产生时,会给父窗口消息处理程序发送一个WM_COMMAND消息

1
2
3
graph LR
按钮-->系统提供WinProc
系统提供WinProc-->父窗口的WinProc

系统提供WinProc将消息转换成WM_COMMAND,发给父窗口

下面是视频PPT的代码

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
步骤1:创建Windows应用程序  选择空项目									
									
									
步骤2:在新建项窗口中选C++代码文件 创建一个新的cpp文件									
									
									
步骤3:在新的cpp文件中添加:#include <Windows.h>									
									
并添加入口函数:									
									
int CALLBACK WinMain(  						CALLBACK 是一个宏 			
	_In_  HINSTANCE hInstance,  					#define CALLBACK    __stdcall			
	_In_  HINSTANCE hPrevInstance,  								
	_In_  LPSTR lpCmdLine,  								
	_In_  int nCmdShow  								
	)  					所有的Win32     API函数都遵循该约定			
{  									
									
	return 0;  								
}  									
									
步骤4:设计窗口类									
									
代码:									
									
//窗口的类名									
TCHAR className[] = "My First Window"; 									
									
// 创建窗口类的对象 									
WNDCLASS wndclass = {0};						//一定要先将所有值赋值			
wndclass.hbrBackground = (HBRUSH)COLOR_MENU;						//窗口的背景色			
wndclass.lpfnWndProc = WindowProc;						//窗口过程函数			
wndclass.lpszClassName = className;						//窗口类的名字			
wndclass.hInstance = hInstance;						//定义窗口类的应用程序的实例句柄			
									
									
步骤5:注册窗口类									
									
RegisterClass(&wndclass);  									
									
									
步骤6:创建窗口									
									
// 创建窗口  									
HWND hwnd = CreateWindow(  									
	className,				//类名				
	TEXT("我的第一个窗口"),				//窗口标题				
	WS_OVERLAPPEDWINDOW,				//窗口外观样式 				
	10,				//相对于父窗口的X坐标				
	10,				//相对于父窗口的Y坐标				
	600,				//窗口的宽度  				
	300,				//窗口的高度  				
	NULL,				//父窗口句柄,为NULL  				
	NULL,				//菜单句柄,为NULL  				
	hInstance,				//当前应用程序的句柄  				
	NULL);				//附加数据一般为NULL				
									
if(hwnd == NULL)					//是否创建成功  				
	return 0;  								
									
									
步骤7:显示窗口									
									
// 显示窗口  									
ShowWindow(hwnd, SW_SHOW);  									
									
步骤8:消息循环									
									
MSG msg;  									
while(GetMessage(&msg, NULL, 0, 0))  									
{  									
	TranslateMessage(&msg);  								
	DispatchMessage(&msg);  								
}  									
									
									
步骤9:回调函数									
									
/*									
窗口消息处理程序 窗口回调函数:									
									
1、窗口回调函数处理过的消息,必须传回0.									
									
2、窗口回调不处理的消息,由DefWindowProc来处理.									
*/									
									
LRESULT CALLBACK WindowProc(  									
							IN  HWND hwnd,  		
							IN  UINT uMsg,  		
							IN  WPARAM wParam,  		
							IN  LPARAM lParam  		
							)  		
{  									
	switch(uMsg)								
	{								
		//窗口消息							
	case WM_CREATE: 								
		{							
			DbgPrintf("WM_CREATE %d %d\n",wParam,lParam);						
			CREATESTRUCT* createst = (CREATESTRUCT*)lParam;						
			DbgPrintf("CREATESTRUCT %s\n",createst->lpszClass);						
									
			return 0;						
		}							
	case WM_MOVE:								
		{							
			DbgPrintf("WM_MOVE %d %d\n",wParam,lParam);						
			POINTS points = MAKEPOINTS(lParam);						
			DbgPrintf("X Y %d %d\n",points.x,points.y);						
									
			return 0;						
		}							
	case WM_SIZE:								
		{							
			DbgPrintf("WM_SIZE %d %d\n",wParam,lParam);						
			int newWidth  = (int)(short) LOWORD(lParam);    						
			int newHeight  = (int)(short) HIWORD(lParam);   						
			DbgPrintf("WM_SIZE %d %d\n",newWidth,newHeight);						
									
			return 0;						
		}							
	case WM_DESTROY:								
		{							
			DbgPrintf("WM_DESTROY %d %d\n",wParam,lParam);						
			PostQuitMessage(0);						
									
			return 0;						
		}							
		//键盘消息							
	case WM_KEYUP:								
		{							
			DbgPrintf("WM_KEYUP %d %d\n",wParam,lParam);						
									
			return 0;						
		}							
	case WM_KEYDOWN:								
		{							
			DbgPrintf("WM_KEYDOWN %d %d\n",wParam,lParam);						
									
			return 0;						
		}							
		//鼠标消息							
	case WM_LBUTTONDOWN:								
		{							
			DbgPrintf("WM_LBUTTONDOWN %d %d\n",wParam,lParam);						
			POINTS points = MAKEPOINTS(lParam);						
			DbgPrintf("WM_LBUTTONDOWN %d %d\n",points.x,points.y);						
									
			return 0;						
		}							
	}								
	return DefWindowProc(hwnd,uMsg,wParam,lParam);								
}

 

下面是视频的关于消息的PPT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
				
Windows中的事件是一个“动作”,这个动作可能是用户操作应用程序产生的,也可能是Windows自己产生的.				
				
而消息,就是用来描述这些“动作”的,比如:				
				
这个动作是什么时候产生的?				
				
哪个应用程序产生的?				
				
在什么位置产生的?				
				
等等。。。				
				
Windows为了能够准确的描述这些信息,提供了一个结构体:MSG,该结构体里面记录的事件的详细信息.				
				
typedef struct tagMSG {				
  HWND   hwnd; 				
  UINT   message; 				
  WPARAM wParam; 				
  LPARAM lParam; 				
  DWORD  time; 				
  POINT  pt; 				
} MSG, *PMSG; 				
				
说明:				
				
1、hwnd:				
				
表示消息所属的窗口				
				
一个消息一般都是与某个窗口相关联的				
				
在Windows中 HWND类型的变量通常用来标识窗口。				
				
2、message				
				
在Windows中,消息是由一个数值来表示的				
				
但是由于数值不便于记忆,所以Windows将消息对应的数值定义为WM_XXX宏(WM == Window Message)				
				
鼠标左键按下 WM_LBUTTONDOWN				键盘按下 WM_KEYDOWN
				
3、wParam 和 lParam				
				
32位消息的特定附加信息,具体表示什么处决于message 				
				
4、time				
				
消息创建时的时间 				
				
5、消息创建时的鼠标位置

参考资料

[1]. 滴水视频

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值