Win32从入门到放弃

文章目录


1.应用程序分类

1.1.分类

  • 控制台程序
    DOS程序,本身没有窗口,通过Windows DOS窗口执行。
  • 窗口程序
  • 库程序:
    存放代码、数据的程序,执行文件可以从中取出代码执行和获取数据。
    • 静态库程序:扩展名lib,在编译链接程序时,将代码放入到执行文件中。
    • 动态库程序:扩展名dll,在执行文件执行时从中获取代码。

1.2.应用程序对比

  • 入口函数
    • 控制台程序 main
    • 窗口程序 Winmain
    • 动态库程序 DllMain
    • 静态库程序 无入口函数
  • 文件存在方式
    • 控制台程序exe文件
    • 窗口程序exe文件
    • 动态库程序DLL文件
    • 静态库程序LIB文件

控制台应用程序

#include <stdio.h>
int main(){
	printf("hello world\n");

	return 0;
}

2.开发工具和库

2.1.编译工具

  • 编译器CL.exe将源代码编译成目标代码.obj
  • 链接吕link.exe将目标代码、库链接生成最终文件
  • 资源编译器rc.exe(.rc)将资源编译,最终通过链接器存入最终文件。
  • 路径:E:\Program Files\Microsoft Visual Studio 10.0\VC\bin

2.2.库和头文件

  • Windows库
    • kernel32.dll 提供了核心的API,例如进程、线程、内存管理等。
    • user32.dll 提供了窗口、消息等API
    • gdi32.dll 绘图相关的API
    • 路径:C:\Windows\System32
  • 头文件
    • windows.h 所有windows头文件的集合
    • windef.h windows数据类型
    • winbase.h kernel32的API
    • wingdi.h gdi32的API
    • winuser.h user32的API
    • winnt.h UNICODE字符集支持
    • 路径:C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Include

2.3.相关函数

2.3.1.WinMain

int WINAPI WinMain(  HINSTANCE hInstance,      // handle to current instance当前程序的实例句柄
  HINSTANCE hPrevInstance,  // handle to previous instance当前程序前一个实例句柄
  LPSTR lpCmdLine,          // command line命令行参数字符串
  int nCmdShow              // show state窗口显示方式
  );
  • 句柄能找到进程所在的内存,但不是指针
  • hPrevInstance16位操作系统使用,进入32位系统以后(windows95开始),不再使用
  • nCmdShow,窗口的显示方式分三种,最大化、最小化、既不最大化也不最小化(原样显示)

2.3.2.MessageBox

int MessageBox(  HWND hWnd,          // handle to owner window父窗口句柄
  LPCTSTR lpText,     // text in message box显示在消息框中的文字
  LPCTSTR lpCaption,  // message box title显示在标题栏的文字
  UINT uType          // message box style消息框中的按钮、图标显示类型
  );//返回点击的按钮ID

2.4.程序编译过程

  • 源程序
#include <windows.h>
int WinMain(HINSTANCE hIns,
			HINSTANCE hPreIns,
			LPSTR lpCmdLine,
			int nCmdShow){
	MessageBox(NULL,"hello world","Information",MB_YESNOCANCEL|MB_ICONERROR);
	return 0;
}
  • 编译环境准备vcvars32.bat
    Win32从入门到放弃
    运行vcvars32.bat后,即使未设置环境变量可以实别cl.exe、link.exe等
    该程序路径:E:\Program Files\Microsoft Visual Studio 10.0\VC\bin,可拷贝到任意地方执行

  • 编译程序
    Win32从入门到放弃
    生成Hello.obj
    Win32从入门到放弃

  • 链接程序
    Win32从入门到放弃
    生成Hello.exe
    Win32从入门到放弃

2.5.编译rc文件

  • 编写资源的文件 .rc资源脚本文件
//Hello.rc
100 ICON chuhe.ico

Win32从入门到放弃

  • 编译源程序 cl.exe
    Win32从入门到放弃
    生成Hello.obj
    Win32从入门到放弃

  • 编译rc文件 rc.exe
    Win32从入门到放弃
    生成Hello.res,和Hello.obj一样都是目标文件
    Win32从入门到放弃

  • 将资源链接到程序中 link.exe
    Win32从入门到放弃
    生成Hello.exe,并且程序图标也成功设置
    Win32从入门到放弃

3.第一个Windows窗口

3.1.窗口创建过程

  • 定义WinMain函数
  • 定义窗口处理函数(自定义,处理消息)
  • 注册窗口类(向操作系统写入一些数据)
  • 创建窗口
  • 显示窗口
  • 消息循环(获取/翻译/派发消息)
  • 消息处理

3.2.代码示例

#include <windows.h>

LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam){

	switch(msgID){
	case WM_DESTROY:
		PostQuitMessage(0);//可以使GetMessage函数返回0
		break;
	}
	return DefWindowProc(hWnd,msgID,wParam,lParam);
}

int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){
	//注册窗口类
	WNDCLASS wc={0};
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hIns;
	wc.lpfnWndProc=WndProc;
	wc.lpszClassName="Main";
	wc.lpszMenuName=NULL;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	RegisterClass(&wc);
	//创建窗口
	HWND hWnd=CreateWindow("Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);
	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	//消息循环
	MSG nMsg={0};
	while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg);
		DispatchMessage(&nMsg);//将消息交给窗口函数处理
	}
	return 0;
}

4.字符编码

4.1.编码历史背景

  • ASC:7位代表一个字符。
  • ASCII:American Standard Code for Information Interchange,美国信息互换标准代码,ASCⅡ8位代表一个字符。
  • DBCS:单双字节混合编码。
  • UNICODE:万国码。

4.2.DBCS和UNICODE

Win32从入门到放弃

  • Windows中UNICODE编码一般是UTF-16。在处理单字节字符时被0
  • Linux中UNICODE编码一般是UTF-8

4.3.宽字节字符

  • wchar_t每个字符占2个字节
    char每个字符占1个字节
    wchar_t实际上是unsigned short类型,定义时,需要增加“L”,通知编译器按照双字节编译字符串,采用UNICODE编码。
  • 需要使用支持wchar_t函数操作宽字节字符串。例如:
    wchar_t* pwszText=L"Hello wchar";
    wprintf(L"%s\n",pwszText);
    
#include <Windows.h>
#include <stdio.h>
void C_char(){
	char* pszText="Hello char";
	int len=strlen(pszText);
	printf("%s %d\n",pszText,len);
}
void W_char(){
	wchar_t* pszText=L"Hello wchar";
	int len=wcslen(pszText);
	wprintf(L"%s %d\n",pszText,len);
}


int main(){
	C_char();
	W_char();	
	getchar();
	return 0;
}

4.4.TCHAR

  • WinNT.h中

    #ifdef UNICODE
     	typedef wchar_t TCHAR;
     	#define __TEXT(quote) L##quote
    #else
    	typedef char TCHAR;
    	#define __TEXT(quote) quote
    #endif
    
    #define UNICODE
    #include <Windows.h>
    #include <stdio.h>
    
    void T_char(){
    	/*
    	char* pszText="Hello txt";
    	wchar_t* pszText=L"Hello txt";
    	*/
    	TCHAR* pszText=__TEXT("Hello txt");
    #ifdef UNICODE
    	wprintf(L"%s\n",pszText);
    #else
    	printf("单字节%s\n",pszText);
    #endif
    }
    
    int main(){
    
    	T_char();
    	getchar();
    	return 0;
    }
    

4.5.打印UNICODE

  • UNICODE字符打印
    • wprintf对UNICODE字符打印支持不完善。
    • 在Windows下使用WriteConsole这个 API打印UNICODE字符
    • WriteConsole 不公能打印UNICODE字符,也能打印char*字符
      GetStdHandle(STD_OUTPUT_HANDLE);//标准输出句柄
      GetStdHandle(STD_ERROR_HANDLE);
      GetStdHandle(STD_INTPUT_HANDLE);//标准输入句柄
      
#define UNICODE
#include <Windows.h>
#include <stdio.h>

void PrintUnicode(){
	wchar_t* pszText=L"锄禾日当午\n";
	//char* pszText="锄禾日当午\n";	
	HANDLE hOut=GetStdHandle(STD_OUTPUT_HANDLE);//标准输出句柄
	WriteConsole(hOut,pszText,wcslen(pszText),NULL,NULL);
	
	
}
int main(){
	PrintUnicode();
	getchar();
	return 0;
}

4.6.为什么要更改成多字符集

Win32从入门到放弃

  • 系统调用函数的参数类型
LPSTR==char* LPSTR==const char*
LPWSTR==wchar_t* LPCWSTR==const wchar_t*
LPTSTR==TCHAR* LPCTSTR==const TCHAR*
  • 系统中大量使用了LPTSTR和LPCTSTR
    4.4.TCHAR小节所述,使用UNICODE字符集时,TCHAR将按宽字节处理,传参时必须为L"Hello"这种格式。改成多字符集,就不需要这样传参了。

5.注册窗口类

5.1.窗口类的概念

  • 窗口类包含了窗口的各种参数信息的数据结构。
  • 每个窗口都具有窗口类,基于窗口类创建窗口。
  • 每个窗口类都具有一个名称,使用前必须注册到系统。

5.2.窗口类的分类

  • 系统窗口类
    系统已经定义好的窗口类,所有应用程序都可以直接使用。
  • 应用程序全局窗口类
    由用户自己定义,当前应用程序所有模块都可以使用。
  • 应用程序局部窗口类
    由用户自己定义,当前应用程序中本模块可以使用。

5.3.系统窗口类

  • 不需要注册,直接使用窗口类即可。系统已经注册好了。
    例如:
    按钮 BUTTON
    编辑框 EDIT
#include <windows.h>



int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){

	//创建窗口
	HWND hWnd=CreateWindow("Button","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);
	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	//消息循环
	MSG nMsg={0};
	while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg);
		DispatchMessage(&nMsg);//将消息交给窗口函数处理
	}
	return 0;
}

5.4.全局及局部窗口类

  • 注册窗口类的函数

    ATOM RegisterClass(
    			CONST WNDCLASS* lpWndClass//窗口类的数据
    );//注册成功后,返回一个数字标识。失败返回非0。
    
  • 注册窗口类的结构体

    typedef struct _WNDCLASS { 
        UINT       style; //窗口的风格
        WNDPROC    lpfnWndProc; //窗口处理函数
        int        cbClsExtra; //窗口类的附加数据buff的大小
        int        cbWndExtra; //窗口的附加数据buff的大小
        HINSTANCE  hInstance; //当前模块的实例句柄
        HICON      hIcon; //窗口图标句柄
        HCURSOR    hCursor; //鼠标的句柄
        HBRUSH     hbrBackground; //绘制窗口背景的画刷句柄
        LPCTSTR    lpszMenuName; //窗口菜单的资源ID字符串
        LPCTSTR    lpszClassName; //窗口类的名称
    } WNDCLASS, *PWNDCLASS; 
    
  • style窗口类风格

    • 应用程序全局窗口类的注册,需要在窗口类的风格中增加CS_GLOBALCLASS
    • 应用程序局部窗口类,在注册窗口类时,不添加CS_GLOBALCLASS风格。
    • 尽量不使用全局窗口类的注册,全局窗口类的功能,局部窗口类都能完成
    • 全局窗口类易造成程序的冗余,微软也不推荐。
  • CS_HREDRAW当窗口水平变化时,窗口重新绘制
    CS_VREDRAW当窗口垂直变化时,窗口重新绘制
    CS_DBLCLKS允许窗口接收鼠标双击(无此风格,双击再快也是单击)。
    CS_NOCLOSE 窗口没有关闭按钮

6.窗口创建

6.1.窗口创建

HWND CreateWindowEx( 
				  DWORD dwExStyle,      // extended window style窗口的扩展风格
				  LPCTSTR lpClassName,  // registered class name已经注册的窗口类名称
				  LPCTSTR lpWindowName, // window name 窗口标题栏的名字 
				  DWORD dwStyle,        // window style窗口的基本风格
				  int x,                // horizontal position of window窗口左上角水平坐标位置
				  int y,                // vertical position of window窗口左上角垂直坐标位置
				  int nWidth,           // window width  窗口宽度
				  int nHeight,          // window height窗口高度
				  HWND hWndParent,      // handle to parent or owner window窗口父窗口句柄
				  HMENU hMenu,          // menu handle or child identifier窗口菜单句柄
				  HINSTANCE hInstance,  // handle to application instance应用程序实例句柄
				  LPVOID lpParam        // window-creation data窗口创建时附加参数
				  );//创建成功返回窗口句柄

6.2.窗口创建执行过程

  • 1 CreateWindowEx内部根据传入的窗口类名称,在应用程序局部窗口类中查找,如果找到执行2,如果未找到执行3。
  • 2 比较局部窗口类与创建窗口时传入的HINSTANCE变量。如果发现相等,创建和注册的窗口类在同一模块,创建窗口返回。如果不相等,继续执行3。
  • 3 在应用程序全局窗口类,如果找到,执行4,如果未找到执行5。
  • 4 使用找到的窗口类信息,创建窗口返回。
  • 5 在系统窗口类中查找,如果找到创建窗口返回,否则创建窗口失败。

6.3.子窗口创建

  • 创建时要设置父窗口句柄
  • 创建风格要增加WS_CHILD|WS_VISIBLE
#include <windows.h>

LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lPARAM){

	switch(msgID){
	case WM_DESTROY:
		PostQuitMessage(0);//可以使GetMessage函数返回0,相当于抛了一个WM_QUIT
		break;
	}
	return DefWindowProc(hWnd,msgID,wParam,lPARAM);
}

int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){
	//注册窗口类
	WNDCLASS wc={0};
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hIns;
	wc.lpfnWndProc=WndProc;
	wc.lpszClassName="Main";
	wc.lpszMenuName=NULL;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	RegisterClass(&wc);
	//创建窗口
	HWND hWnd=CreateWindowEx(0,"Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);

	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hIns;
	wc.lpfnWndProc=DefWindowProc;
	wc.lpszClassName="Child";
	wc.lpszMenuName=NULL;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	RegisterClass(&wc);
	HWND hChild1=CreateWindowEx(0,"Child","c1",WS_CHILD|WS_VISIBLE|WS_OVERLAPPEDWINDOW,0,0,200,200,hWnd,NULL,hIns,NULL);
	HWND hChild2=CreateWindowEx(0,"Child","c2",WS_CHILD|WS_VISIBLE|WS_OVERLAPPEDWINDOW,200,0,200,200,hWnd,NULL,hIns,NULL);


	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	//消息循环
	MSG nMsg={0};
	while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg);
		DispatchMessage(&nMsg);//将消息交给窗口函数处理
	}
	return 0;
}

Win32从入门到放弃

7.消息基础

7.1.消息的概念和作用

  • 消息组成(windows平台下)
    • 窗口句柄
    • 消息ID
    • 消息的两个参数(两个附带信息)
    • 消息产生的时间
    • 消息产生时鼠标的位置
  • 消息的作用
    • 当系统通知窗口工作时,就采用消息的方式派发给窗口(的处理函数)。

7.2.窗口处理函数

  • 每个窗口都必须具有窗口处理函数
    LRESULT CALLBACK WindowProc(  
    		  HWND hwnd,      // handle to window窗口句柄
    		  UINT uMsg,      // message identifier消息ID
    		  WPARAM wParam,  // first message parameter消息参数
    		  LPARAM lParam   // second message parameter消息参数
     );
    
  • 当系统通知窗口时,会调用窗口处理函数,同时将消息ID和消息参数传递给窗口处理函数。在窗口处理函数中,不处理的消息,使用缺省窗口处理函数。例如:DefWindowProc

7.3.浅谈消息相关函数

  • GetMessage 获取消息。

    BOOL GetMessage( 
    		 LPMSG lpMsg,         // message information//存放获取到的消息BUFF
    		  HWND hWnd,           // handle to window窗口句柄
    		  UINT wMsgFilterMin,  // first message 获取消息的最小ID
    		  UINT wMsgFilterMax   // last message获取消息的最大ID
      );
    
    • lpMsg 当获取到消息后,将消息的参数存放到MSG结构中。
    • hWnd 获取到hWnd所指定窗口的消息。为NULL时抓取当前进程所有窗口的消息。
    • wMsgFilteMin和wMsgFilterMax只能获取到由它们指定的消息范围内的消息。如果都为0,表示没有范围,当前进程中所有的消息都抓取。
  • TranslateMessage 翻译消息。将按键消息,翻译成字符消息。

    BOOL TranslateMessage(  
    	CONST MSG *lpMsg   // message information要翻译的消息地址
    );
    
    • 检查消息是否是按键的消息,如果不是按键消息,不做任何处理,继续执行。
  • DispatchMessage 派发消息

    LRESULT DispatchMessage(  
    		CONST MSG *lpmsg   // message information要派发的消息
    );
    
    • 将消息派发到该消息所属的窗口处理函数上。

8.常见消息

8.1.WM_DESTROY

  • 生产时间:窗口被销毁时的消息。
  • 附带信息:
    • wParam为0
    • lParam为0
  • 一般用法:常用于在窗口被销毁之前,做相应的善后处理,例如资源、内存等。

8.2.WM_SYSCOMMAND

  • 生产时间:当点击窗口的最大化、最小化、关闭等。
  • 附带信息:
    • wParam:具体点击的位置,例如关闭SC_CLOSE等
    • lParam:鼠标光标的位置。
      • LOWORD(lParam);水平位置
      • HIWORD(lParam);垂直位置
  • 一般用法:常用在窗口关闭时,提示用户处理。

8.3.WM_CREATE

  • 生产时间:在窗口创建成功但还未显示时。

  • 附带信息:

    • wParam为0

    • lParam为CREATESTRUCT类型的指针
      通过这个指可以获取CreateWindowEx中的全部12个参数信息。

      CREATESTRUCT* pcs=(CREATESTRUCT*)lParam;
      
  • 一般用法:常用于初始化窗口的参数、资源等等,包括创建子窗口等。

8.4.WM_SIZE

  • 生产时间:在窗口的大小发生变化后。
  • 附带信息:
    • wParam窗口大小变化的原因
    • lParam窗口变化后的大小
      • LOWORD(lParam)//变化后的宽度
      • HIWORD(lParam)//变化后的高度
  • 一般用法:常用于窗口大小变化后,调整窗口各个部分的布局。

8.5.WM_QUIT

  • 生产时间:程序员发送。
  • 附带信息:
    • wParam:PostQuitMessage函数传递的参数。
    • lParam:0
  • 一般用法:用于结束消息循环,当GetMessage收到这个消息后,会返回FALSE,结束while处理,退出消息循环。

8.6.WM_PAINT

详见WM_PAINT

8.7.WM_COMMAND

点击菜单触发的WM_COMMAND
点击加速键触发的WM_COMMAND

8.8.WM_INITDIALOG

详见对话框的消息

9.Win32窗口额外创建一个控制台窗口

  • 1、定义全局变量,标准输出句柄HANDLE g_hOutput=0;
  • 2、入口函数增加控制台窗口AllocConsole();
  • 3、WriteConsole往控制台写入要显示的内容。
#include <windows.h>
#include <stdio.h>
HANDLE g_hOutput=0;//1、定义全局变量,标准输出句柄
void OnSize(HWND hWnd,LPARAM lParam){
	short nWidth=LOWORD(lParam);
	short nHeight=HIWORD(lParam);
	char szText[256]={0};
	sprintf(szText,"WM_SIZE:宽:%d,高:%d\n",nWidth,nHeight);
	WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);//3、往控制台窗口写入信息
}

LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lPARAM){

	switch(msgID){
	case WM_DESTROY:		
		PostQuitMessage(0);
		break;
	case WM_SYSCOMMAND:		
		break;
	case WM_SIZE:
		OnSize(hWnd,lPARAM);
		break;
	}
	return DefWindowProc(hWnd,msgID,wParam,lPARAM);
}

int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){
	AllocConsole();//2、增加dos窗口
	g_hOutput=GetStdHandle(STD_OUTPUT_HANDLE);
	//注册窗口类
	WNDCLASS wc={0};
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=NULL;
	wc.hIcon=NULL; 
	wc.hInstance=hIns;
	wc.lpfnWndProc=WndProc;
	wc.lpszClassName="Main";
	wc.lpszMenuName=NULL;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	RegisterClass(&wc);
	//创建窗口
	HWND hWnd=CreateWindowEx(0,"Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);

	
	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	//消息循环
	MSG nMsg={0};
	while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg);
		DispatchMessage(&nMsg);//将消息交给窗口函数处理
	}
	return 0;
}

10.消息循环原理

10.1.消息循环的阻塞

  • GetMessage从系统获取消息,将消息从系统中移除,阻塞函数。当系统无消息时,会等候下一条消息。

  • PeekMessage以查看的方式从系统获取消息,可以不将消息从系统移除,非阻塞函数。当系统无消息时,返回FALSE,继续执行后续代码。

    BOOL PeekMessage( 
    		 LPMSG lpMsg,         // message information
    		  HWND hWnd,           // handle to window
    		  UINT wMsgFilterMin,  // first message  
    		  UINT wMsgFilterMax,  // last message
    		  UINT wRemoveMsg      // removal options移除标识,PM_REMOVE/PM_NOREMOVE
    		  
      );
    
    • 前四个参数跟GetMessage完全一致,可以参照GetMessage
    • wRemoveMsg
      • 一般为PM_NOREMOVE
      • PM_REMOVE PeekMessage处理后,消息不会从队列中删除。
      • PM_NOREMOVE经过PeekMessage处理后,消息将从队列中删除。
#include <windows.h>
#include <stdio.h>
HANDLE g_hOutput=0;//1、全局变量,定义标准输出句柄
void OnSize(HWND hWnd,LPARAM lParam){
	short nWidth=LOWORD(lParam);
	short nHeight=HIWORD(lParam);
	char szText[256]={0};
	sprintf(szText,"WM_SIZE:宽:%d,高:%d\n",nWidth,nHeight);
	WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);//3、往控制台窗口写入信息
}

LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lPARAM){

	switch(msgID){
	case WM_DESTROY:		
		PostQuitMessage(0);
		break;
	case WM_SYSCOMMAND:		
		break;
	case WM_SIZE:
		OnSize(hWnd,lPARAM);
		break;
	}
	return DefWindowProc(hWnd,msgID,wParam,lPARAM);
}

int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){
	AllocConsole();//2、增加dos窗口
	g_hOutput=GetStdHandle(STD_OUTPUT_HANDLE);
	//注册窗口类
	WNDCLASS wc={0};
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=NULL;
	wc.hIcon=NULL; 
	wc.hInstance=hIns;
	wc.lpfnWndProc=WndProc;
	wc.lpszClassName="Main";
	wc.lpszMenuName=NULL;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	RegisterClass(&wc);
	//创建窗口
	HWND hWnd=CreateWindowEx(0,"Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);

	
	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	//消息循环
	MSG nMsg={0};
	//while(GetMessage(&nMsg,NULL,0,0)){
	//	TranslateMessage(&nMsg);
	//	DispatchMessage(&nMsg);//将消息交给窗口函数处理
	//}
	while(1){
		if(PeekMessage(&nMsg,NULL,0,0,PM_NOREMOVE)){
			//有消息
			if(GetMessage(&nMsg,NULL,0,0)){
				TranslateMessage(&nMsg);
				DispatchMessage(&nMsg);//将消息交给窗口函数处理
			}else{
				return 0;
			}
		}else{
			//无消息,空闲处理
			WriteConsole(g_hOutput,"OnIdle",strlen("OnIdle"),NULL,NULL);

		}
	}
	return 0;
}

10.2.发送消息

  • SendMessage 发送消息,会等候消息处理的结果。

    LRESULT SendMessage(  
    		  HWND hWnd,      // handle to destination window消息发送的目的窗口
    		  UINT Msg,       // message 消息ID 
    		  WPARAM wParam,  // first message parameter消息参数
    		  LPARAM lParam   // second message parameter消息参数
      );
    
  • PostMessage投递消息,消息发出后立刻返回,不等候消息执行结果。

    BOOL PostMessage(  
    		 HWND hWnd,      // handle to destination window消息发送的目的窗口
    		  UINT Msg,       // message  消息ID
    		  WPARAM wParam,  // first message parameter消息参数
    		  LPARAM lParam   // second message parameter消息参数
      );
    
  • PostQuitMessage(0);内部调用了PostMessage(hWnd,WM_QUIT,0,0);,如下

    #include <windows.h>
    #include <stdio.h>
    HANDLE g_hOutput=0;//1、全局变量,定义标准输出句柄
    void OnSize(HWND hWnd,LPARAM lParam){
    	short nWidth=LOWORD(lParam);
    	short nHeight=HIWORD(lParam);
    	char szText[256]={0};
    	sprintf(szText,"WM_SIZE:宽:%d,高:%d\n",nWidth,nHeight);
    	WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);//3、往控制台窗口写入信息
    }
    
    LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lPARAM){
    
    	switch(msgID){
    	case WM_DESTROY:		
    		//PostQuitMessage(0);可以使GetMessage函数返回0,抛出了一个WM_QUIT消息
    		PostMessage(hWnd,WM_QUIT,0,0);
    		break;
    	case WM_SYSCOMMAND:		
    		break;
    	case WM_SIZE:
    		OnSize(hWnd,lPARAM);
    		break;
    	}
    	return DefWindowProc(hWnd,msgID,wParam,lPARAM);
    }
    
    int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){
    	AllocConsole();//2、增加dos窗口
    	g_hOutput=GetStdHandle(STD_OUTPUT_HANDLE);
    	//注册窗口类
    	WNDCLASS wc={0};
    	wc.cbClsExtra=0;
    	wc.cbWndExtra=0;
    	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
    	wc.hCursor=NULL;
    	wc.hIcon=NULL; 
    	wc.hInstance=hIns;
    	wc.lpfnWndProc=WndProc;
    	wc.lpszClassName="Main";
    	wc.lpszMenuName=NULL;
    	wc.style=CS_HREDRAW|CS_VREDRAW;
    	RegisterClass(&wc);
    	//创建窗口
    	HWND hWnd=CreateWindowEx(0,"Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);
    
    	
    	//显示窗口
    	ShowWindow(hWnd,SW_SHOW);
    	UpdateWindow(hWnd);
    	//消息循环
    	MSG nMsg={0};
    	//while(GetMessage(&nMsg,NULL,0,0)){
    	//	TranslateMessage(&nMsg);
    	//	DispatchMessage(&nMsg);//将消息交给窗口函数处理
    	//}
    	while(1){
    		if(PeekMessage(&nMsg,NULL,0,0,PM_NOREMOVE)){
    			//有消息
    			if(GetMessage(&nMsg,NULL,0,0)){
    				TranslateMessage(&nMsg);
    				DispatchMessage(&nMsg);//将消息交给窗口函数处理
    			}else{
    				return 0;
    			}
    		}else{
    			//无消息,空闲处理
    			WriteConsole(g_hOutput,"OnIdle",strlen("OnIdle"),NULL,NULL);
    
    		}
    	}
    	return 0;
    }
    

10.1.消息分类

  • 系统消息,ID范围0-0x03FF(1024)
    由系统定义好的消息,可以在程序中直接使用
  • 用户自定义消息,ID范围0x0400-0x7FFF(31743)
    由用户自己定义,满足用户自己的需求。由用户自己发出消息,并响应处理。
    自定义消息宏:WN_USER
#include <windows.h>
#include <stdio.h>
#define WM_MYMESSAGE WM_USER+1001	//即0x400+1001
HANDLE g_hOutput=0;
void OnSize(HWND hWnd,LPARAM lParam){
	short nWidth=LOWORD(lParam);
	short nHeight=HIWORD(lParam);
	char szText[256]={0};
	sprintf(szText,"WM_SIZE:宽:%d,高:%d\n",nWidth,nHeight);
	PostMessage(hWnd,WM_MYMESSAGE,1,2);//发送自己定义的消息
	SendMessage(hWnd,WM_MYMESSAGE,1,2);//发送自己定义的消息
	WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);
}
void OnMyMessage(HWND hWnd,WPARAM wParam,LPARAM lParam){
	char szText[256]={0};
	sprintf(szText,"自定义消息被处理:wParam=%d,lParam=%d\n",wParam,lParam);
	MessageBox(hWnd,szText,"Infor",MB_OK);
}

LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam){

	switch(msgID){
	case WM_MYMESSAGE:
		OnMyMessage(hWnd,wParam,lParam);
		break;
	case WM_DESTROY:		
		//PostQuitMessage(0);可以使GetMessage函数返回0,抛出了一个WM_QUIT消息
		PostMessage(hWnd,WM_QUIT,0,0);
		break;
	case WM_SYSCOMMAND:		
		break;
	case WM_SIZE:
		OnSize(hWnd,lParam);
		break;
	}
	return DefWindowProc(hWnd,msgID,wParam,lParam);
}

int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){
	AllocConsole();
	g_hOutput=GetStdHandle(STD_OUTPUT_HANDLE);
	//注册窗口类
	WNDCLASS wc={0};
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=NULL;
	wc.hIcon=NULL; 
	wc.hInstance=hIns;
	wc.lpfnWndProc=WndProc;
	wc.lpszClassName="Main";
	wc.lpszMenuName=NULL;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	RegisterClass(&wc);
	//创建窗口
	HWND hWnd=CreateWindowEx(0,"Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);

	
	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	//消息循环
	MSG nMsg={0};
	//while(GetMessage(&nMsg,NULL,0,0)){
	//	TranslateMessage(&nMsg);
	//	DispatchMessage(&nMsg);//将消息交给窗口函数处理
	//}
	while(1){
		if(PeekMessage(&nMsg,NULL,0,0,PM_NOREMOVE)){
			//有消息
			if(GetMessage(&nMsg,NULL,0,0)){
				TranslateMessage(&nMsg);
				DispatchMessage(&nMsg);//将消息交给窗口函数处理
			}else{
				return 0;
			}
		}else{
			//无消息,空闲处理
			WriteConsole(g_hOutput,"OnIdle",strlen("OnIdle"),NULL,NULL);

		}
	}
	return 0;
}

11.消息队列

11.1.消息队列的概念

  • 消息队列是用入存放消息的队列。
  • 消息在队列中先入先出。
  • 所有窗口程序都具有消息队列。
  • 程序可以从队列中获取消息。

11.2.消息队列分类

  • 系统消息队列,由系统维护的消息队列。存放系统产生的消息,如鼠标、键盘等。
  • 程序消息队列,属于每一个应用程序(线程)的消息队列。由应用程序(线程)维护。
  • 系统每隔一段一间,会将系统队列里的消息转发到对应进程的消息队列 。
  • 每个进程GetMessage只负责抓取本进程消息队列的消息。

11.3.消息和队列关系

  • 消息和消息队列的关系
    • 1 当鼠标、键盘产生消息时,会将消息存放到系统消息队列
    • 2 系统会根据存放的消息,找到对应程序的消息队列。
    • 3 将消息投递到程序的消息队列中
  • 根据消息和消息队列之间使用关系,将消息分成两类:
    • 队列消息,消息的发送和获取,都是通过消息队列完成。
    • 非队列消息,消息的发送和获取,是直接调用消息的窗口处理完成。
  • 队列消息,发送后,首先放队系统队列,然后通过消息循环,从队列中获取。
    • GetMessage从消息队列中获取消息。
    • PostMessage将消息投递到消息队列。
    • 常见队列消息:WM_PAINT、WM_QUIT、键盘、鼠标、定时器。
    • WM_QUIT必须进队列,否则GetMessage永远不为0,消息循环无法退出。
  • 非队列消息,发送时,首先查找消息接收窗口的窗口处理函数,直接调用处理函数,完成消息。
    • SendMessage直接将消息发送给窗口的处理函数,并等候处理结果。
    • 常见消息:WM_CREATE、WM_SIZE等。
    • WM_CREATE必须不能进队列,因为WM_CREATE消息产生时窗口还未创建,GetMessage还没有运行。
      Win32从入门到放弃

11.4.深淡GetMessage原理

  • 在程序(线程)消息队列查找消息,如果队列有消息,检查消息是否满足指定条件(HWND,ID范围),不满足条件就不会取走消息,否则从队列取出消息返回。
  • 如果程序(线程)消息队列没有消息,向系统消息队列获取属于本程序的消息。如果系统队列的当前消息属于本程序,系统会将消息转发到程序消息队列。
  • 如果系统消息队列也没有消息,检查当前进程的所有窗口的需要重新绘制的区域,如果发一有需要绘制的区域,产生WM_PAINT消息,取得消息返回处理。
  • 如果没有重新绘制区域,检查定时器。如果有到时的定时器,产生WM_TIMER,返回处理执行。
  • 如果没有到时的定时器,整理程序的资源、内存等等。
  • GetMessage会继续等候下一条消息。PeekMessage会返回FALSE ,交出程序的控制权。
  • 注意:GetMessaage如果获取到WM_QUIT,函数会返回FALSE 。

11.5.WM_PAINT消息

  • 产生时间:当窗口需要绘制的时候。且消息队列中也没有消息时,即有其它消息就不会产生WM_PAINT消息。

  • 附带信息:

    • wParam为0
    • lParam为0
  • 专职用法:用于绘图。

    • 第一个WM_PAINT是由ShowWindow产生的。
    • 产生WM_SIZE消息时,也会产生WM_PAINT。
    • 调用InvalidateRect时,GetMessaage会产生WM_PAINT。
    BOOL InvalidateRect(  
    	  HWND hWnd,           // handle to window窗口句柄
    	  CONST RECT* lpRect,  // rectangle coordinates区域矩形坐标
    	  BOOL bErase          // erase state重绘前是否先擦除
      );
    
  • 消息处理步骤

  • 1 开始绘图

    HDC BeginPaint(  
    		  HWND hwnd,            // handle to window绘图窗口
    		  LPPAINTSTRUCT lpPaint // paint information绘图参数的BUFF
      );//返回绘图设备句柄
    
  • 2 正式绘图

  • 3 结束绘图

    BOOL EndPaint(  
    		 HWND hWnd,                  // handle to window绘图窗口
    		  CONST PAINTSTRUCT *lpPaint  // paint data绘图参数的指指BeginPaint返回
      );
    

处理WM_PAINT消息例子,如下:

#include <windows.h>
HANDLE g_hOutput=0;
void OnPaint(HWND hWnd){
	char* pszText="WM_PAINT\n";
	WriteConsole(g_hOutput,pszText,strlen(pszText),NULL,NULL);

	PAINTSTRUCT ps={0};
	HDC hdc=BeginPaint(hWnd,&ps);
	TextOutA(hdc,100,100,"hello",5);
	EndPaint(hWnd,&ps);
	//以上绘图代码,必须放在处理WM_PAINT消息时调用 


}

LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lPARAM){

	switch(msgID){
	case WM_PAINT:
		OnPaint(hWnd);
		break;
	case WM_LBUTTONDOWN:
		InvalidateRect(hWnd,NULL,TRUE);//鼠标点击时,会调用InvalidateRect函数,从而产生WM_PAINT消息。
		break;
	case WM_DESTROY:
		PostQuitMessage(0);//可以使GetMessage函数返回0
		break;
	}
	return DefWindowProc(hWnd,msgID,wParam,lPARAM);
}

int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){
	AllocConsole();
	g_hOutput=GetStdHandle(STD_OUTPUT_HANDLE);
	//注册窗口类
	WNDCLASS wc={0};
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hIns;
	wc.lpfnWndProc=WndProc;
	wc.lpszClassName="Main";
	wc.lpszMenuName=NULL;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	RegisterClass(&wc);
	//创建窗口
	HWND hWnd=CreateWindow("Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);
	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	//消息循环
	MSG nMsg={0};
	while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg);
		DispatchMessage(&nMsg);//将消息交给窗口函数处理
	}
	return 0;
}

12.键盘消息

12.1.键盘消息分类

  • WM_KEYDOWN按键被按下时产生
  • WM_KEYUP按键被放开时产生
  • WM_SYSKEYDOWN系统键按下时产生,比如ALT、F10
  • WM_SYSKEYUP系统键放开时产生
  • 附带信息:
    • WPARAM 按键的Virtual Key
    • LPARAM 按键的参数,例如按下次数

12.2.字符消息WM_CHAR

  • TranslateMessage在转换WM_KEYDOWN消息时,对于可见字符可以产生WM_CHAR,不可见字符无此消息。
  • 附带信息
    • WPARAM 输入的字符的ASCII字符编码值
    • LPARAM 按按键的相关参数

12.3.字盘消息和WM_CHAR消息举例

#include <windows.h>
#include <stdio.h>
HANDLE h_gOutput=0;
void OnKeyDown(HWND hWnd,WPARAM wParam,LPARAM lParam){
	char szText[256]={0};
	sprintf(szText,"WM_KEYDOWN:键码值=%d\n",wParam);
	WriteConsole(h_gOutput,szText,strlen(szText),NULL,NULL);

}
void OnKeyUp(HWND hWnd,WPARAM wParam,LPARAM lParam){
	char szText[256]={0};
	sprintf(szText,"WM_KEYUP:键码值=%d\n",wParam);
	WriteConsole(h_gOutput,szText,strlen(szText),NULL,NULL);
}
void OnChar(HWND hWnd,WPARAM wParam){
	char szText[256]={0};
	sprintf(szText,"WM_CHAR:wParam=%d\n",wParam);
	WriteConsole(h_gOutput,szText,strlen(szText),NULL,NULL);
}
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam){

	switch(msgID){
	case WM_CHAR:
		OnChar(hWnd,wParam);
		break;
	case WM_KEYDOWN:
		OnKeyDown(hWnd,wParam,lParam);
		break;
	case WM_KEYUP:
		OnKeyUp(hWnd,wParam,lParam);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);//可以使GetMessage函数返回0
		break;
	}
	return DefWindowProc(hWnd,msgID,wParam,lParam);
}

int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){
	AllocConsole();
	h_gOutput=GetStdHandle(STD_OUTPUT_HANDLE);
	//注册窗口类
	WNDCLASS wc={0};
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hIns;
	wc.lpfnWndProc=WndProc;
	wc.lpszClassName="Main";
	wc.lpszMenuName=NULL;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	RegisterClass(&wc);
	//创建窗口
	HWND hWnd=CreateWindow("Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);
	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	//消息循环
	MSG nMsg={0};
	while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg);
		DispatchMessage(&nMsg);//将消息交给窗口函数处理
	}
	return 0;
}

13.鼠标消息

13.1.鼠标消息分类

  • 鼠标基本消息
    • WM_LBUTTONDOWN鼠标左键按下
    • WM_LBUTTONUP鼠标左键抬起
    • WM_RBUTTONDOWN鼠标右键按下
    • WM_RBUTTONUP鼠标右键抬起
    • WM_MOUSEMOVE 鼠标移动消息
  • 双击消息
    • WM_LBUTTONDBLCLK鼠标左键双击
    • WM_RBUTTONDBLCLK鼠标右键双击
  • 滚轮消息
    • WM_MOUSEWHEEL 鼠标滚轮消息

13.2.鼠标基本消息

  • 附带信息
    • wParam其它按键的状态,例如Ctrl、Shift等
      • wParam=0,无按键按下
      • wParam=1,鼠标左键按下
      • wParam=2 ,鼠标右键按下
      • wParam=8,Ctrl按下
      • wParam=4,Shift按下
      • 多个按钮按下,值会累加。比如:Ctrl+鼠标左键按下,wParam=9。
    • lParam:鼠标的位置,窗口客户区坐标系。
      • LOWORD x坐标位置
      • HIWORD y坐标位置
  • 一般情况鼠标按下、抬起成对出现。在鼠标移动过程中,会根据移动速度产生一系列的WM_MOUSEMOVE消息。

13.3.鼠标双击消息

  • 附带信息
    • wParam其它按键的状态,例如Ctrl、Shift等
      • wParam=1,鼠标左键双击
      • -多个按钮按下,值会累加。
    • lParam:鼠标的位置,窗口客户区坐标系。
      • LOWORD x坐标位置
      • HIWORD y坐标位置
  • 消息产生顺序
    • 以左键双击为例
    • WM_LBUTTONDOWN
    • WM_LBUTTONUP
    • WM_LBUTTONDBLCLK
    • WM_LBUTTONUP
  • 使用时需要在注册窗口类时添加CS_DBLCLKS风格。

13.4.鼠标滚轮消息

  • 附带信息
    • wParam:
      • LOWORD其他按钮的状态
      • HIWORD滚轮的偏移量,通过正负值表示滚动方向。
        • 正:向前滚动
        • 负:向后滚动
    • lParam:鼠标当前的位置,屏幕坐标系。
      • LOWORD x坐标
      • HIWORD y坐标
  • 使用:通过偏移量,获取滚动的方向和距离。
  • 鼠标偏移量一般是120的倍数。

13.5.鼠标消息的举例代码

#include <windows.h>
#include <stdio.h>
HANDLE h_gOutput=0;
void OnLButtonDown(HWND hWnd,WPARAM wParam,LPARAM lParam){
	char szText[256]={0};	
	sprintf(szText,"WM_LBUTTONDOWN:按键状态wParam=%d,x=%d,y=%d\n",wParam,LOWORD(lParam),HIWORD(lParam));
	WriteConsole(h_gOutput,szText,strlen(szText),NULL,NULL);
}
void OnLButtonUp(HWND hWnd,WPARAM wParam,LPARAM lParam){
	char szText[256]={0};
	sprintf(szText,"WM_LBUTTONUP:按键状态wParam=%d,x=%d,y=%d\n",wParam,LOWORD(lParam),HIWORD(lParam));
	WriteConsole(h_gOutput,szText,strlen(szText),NULL,NULL);
}
void onm ouseMove(HWND hWnd,WPARAM wParam,LPARAM lParam){
	char szText[256]={0};
	sprintf(szText,"WM_MOUSEMOVE:按键状态wParam=%d,x=%d,y=%d\n",wParam,LOWORD(lParam),HIWORD(lParam));
	WriteConsole(h_gOutput,szText,strlen(szText),NULL,NULL);
	
}
void OnLButtonDblClk(HWND hWnd,WPARAM wParam,LPARAM lParam){
	char szText[256]={0};	
	sprintf(szText,"WM_LBUTTONDBLCLK:按键状态wParam=%d,x=%d,y=%d\n",wParam,LOWORD(lParam),HIWORD(lParam));
	WriteConsole(h_gOutput,szText,strlen(szText),NULL,NULL);
}
void onm ouseWheel(HWND hWnd,WPARAM wParam,LPARAM lParam){
	char szText[256]={0};
	short nDelta=HIWORD(wParam);//偏移量
	sprintf(szText,"WM_MOUSEWHEEL:按钮状态%d,偏移量%d,滚动方向:%s,x=%d,y=%d\n",LOWORD(wParam),nDelta,nDelta>0?"前":"后",LOWORD(lParam),HIWORD(lParam));
	WriteConsole(h_gOutput,szText,strlen(szText),NULL,NULL);
}
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam){

	switch(msgID){
	case WM_MOUSEWHEEL:
		OnMouseWheel(hWnd,wParam,lParam);
		break;
	case WM_LBUTTONDBLCLK:
		OnLButtonDblClk(hWnd,wParam,lParam);
		break;
	case WM_MOUSEMOVE:
		OnMouseMove(hWnd,wParam,lParam);
		break;
	case WM_LBUTTONDOWN:
		OnLButtonDown(hWnd,wParam,lParam);
		break;
	case WM_LBUTTONUP:
		OnLButtonUp(hWnd,wParam,lParam);
		break;

	case WM_DESTROY:
		PostQuitMessage(0);//可以使GetMessage函数返回0
		break;
	}
	return DefWindowProc(hWnd,msgID,wParam,lParam);
}

int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){
	AllocConsole();
	h_gOutput=GetStdHandle(STD_OUTPUT_HANDLE);
	//注册窗口类
	WNDCLASS wc={0};
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hIns;
	wc.lpfnWndProc=WndProc;
	wc.lpszClassName="Main";
	wc.lpszMenuName=NULL;
	wc.style=CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS;
	RegisterClass(&wc);
	//创建窗口
	HWND hWnd=CreateWindow("Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);
	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	//消息循环
	MSG nMsg={0};
	while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg);
		DispatchMessage(&nMsg);//将消息交给窗口函数处理
	}
	return 0;
}

14.定时器消息

14.1.定时器消息介绍

  • 产生时间:
    在程序中创建定时器,当达到时间间隔时,定时器(实际上是GetMessae)会向程序发送一个WM_TIMER消息。定时器的精准度是毫秒,但是准确度很低。例如设置时间间隔为1000ms,但是会在非1000毫秒到达消息(有误差不是说会达到秒级别,误差在几毫秒左右)。
  • 附带信息
    • wParam:定时器ID
    • lParam:定时器处理函数的指针

14.2.创建、销毁定时器

  • 创建定时器

    UINT_PTR SetTimer( 
    	  HWND hWnd,              // handle to window定时器窗口句柄(即哪个窗口处理)
    	  UINT_PTR nIDEvent,      // timer identifier定时器ID
    	  UINT uElapse,           // time-out value时间间隔,毫秒为单位
    	  TIMERPROC lpTimerFunc   // timer procedure定时器处理函数指针(一般不使用,为NULL)
      );//创建成功,返回非0
    
  • 关闭定时器

    BOOL KillTimer(  
    		  HWND hWnd,          // handle to window定时器窗口句柄
    		  UINT_PTR uIDEvent   // timer identifier定时器ID
      );
    

14.3.定时器代码示例

#include <windows.h>
#include <stdio.h>

HANDLE h_gOutput=0;
void OnTimer(HWND hWnd,WPARAM wParam){
	char szText[256]={0};
	
	switch(wParam){
	case 1:
		sprintf(szText,"触发第1个定时器,ID=%d\n",wParam);
		WriteConsole(h_gOutput,szText,strlen(szText),NULL,NULL);
		break;
	case 2:
		sprintf(szText,"触发第2个定时器,ID=%d\n",wParam);
		WriteConsole(h_gOutput,szText,strlen(szText),NULL,NULL);
		break;
	}
}


LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam){

	switch(msgID){
	case WM_TIMER:
		OnTimer(hWnd,wParam);
		break;
	case WM_CREATE:
		SetTimer(hWnd,1,1000,NULL);
		SetTimer(hWnd,2,2000,NULL);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);//可以使GetMessage函数返回0
		break;
	}
	return DefWindowProc(hWnd,msgID,wParam,lParam);
}

int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){
	AllocConsole();
	h_gOutput=GetStdHandle(STD_OUTPUT_HANDLE);
	//注册窗口类
	WNDCLASS wc={0};
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hIns;
	wc.lpfnWndProc=WndProc;
	wc.lpszClassName="Main";
	wc.lpszMenuName=NULL;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	RegisterClass(&wc);
	//创建窗口
	HWND hWnd=CreateWindow("Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);
	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	//消息循环
	MSG nMsg={0};
	while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg);
		DispatchMessage(&nMsg);//将消息交给窗口函数处理
	}
	return 0;
}

15.菜单资源

15.1.菜单分类

  • 窗口的顶层菜单
  • 弹出式菜单
  • 系统菜单

HMENU类型表示菜单(菜单句柄),ID表示菜单项。

15.2.资源相关

  • 资源脚本文件:*.rc文件
  • 编译器:rc.exe

15.3.菜单资源使用

  • 添加菜单资源
  • 加载菜单资源有三种方法:
    • 1 注册窗口类时设置菜单
    • 2 创建窗口传参设置菜单
    • 3 在主窗口WM_CREATE消息中利用SetMenu函数设置菜单
  • 加载菜单资源
    HMENU LoadMenu(  
    	  HINSTANCE hInstance,  // handle to module
    	  LPCTSTR lpMenuName    // menu name or resource identifier
      );
    
  • 设置菜单项名称为空格,更改菜单项属性Separator=True,可做出分隔符
    Win32从入门到放弃Win32从入门到放弃

15.4.加载菜单的三种方法

15.4.1. 注册窗口类时设置菜单

#include <windows.h>
#include "resource.h"

LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam){

	switch(msgID){	
	case WM_DESTROY:
		PostQuitMessage(0);//可以使GetMessage函数返回0
		break;
	}
	return DefWindowProc(hWnd,msgID,wParam,lParam);
}

int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){
	g_hInstance=hIns;
	//注册窗口类
	WNDCLASS wc={0};
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hIns;
	wc.lpfnWndProc=WndProc;
	wc.lpszClassName="Main";	
	wc.lpszMenuName=(char*)IDR_MENU1;//注册窗口类时设置菜单
	wc.style=CS_HREDRAW|CS_VREDRAW;
	RegisterClass(&wc);
	//创建窗口
	HWND hWnd=CreateWindow("Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);
	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	//消息循环
	MSG nMsg={0};
	while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg);
		DispatchMessage(&nMsg);//将消息交给窗口函数处理
	}
	return 0;
}

15.4.2. 创建窗口传参设置菜单

#include <windows.h>
#include "resource.h"
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam){

	switch(msgID){
	case WM_DESTROY:
		PostQuitMessage(0);//可以使GetMessage函数返回0
		break;
	}
	return DefWindowProc(hWnd,msgID,wParam,lParam);
}

int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){
	g_hInstance=hIns;
	//注册窗口类
	WNDCLASS wc={0};
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hIns;
	wc.lpfnWndProc=WndProc;
	wc.lpszClassName="Main";
	wc.lpszMenuName=NULL;	
	wc.style=CS_HREDRAW|CS_VREDRAW;
	RegisterClass(&wc);
	//创建窗口
	//***************************创建窗口传参设置菜单
	HMENU hMenu=LoadMenu(hIns,(char*)IDR_MENU1);
	HWND hWnd=CreateWindow("Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,hMenu,hIns,NULL);

	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	//消息循环
	MSG nMsg={0};
	while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg);
		DispatchMessage(&nMsg);//将消息交给窗口函数处理
	}
	return 0;
}

15.4.3. 在主窗口WM_CREATE消息中利用SetMenu函数设置菜单

#include <windows.h>
#include "resource.h"
HINSTANCE g_hInstance=0;
void OnCreate(HWND hWnd){
	HMENU hMenu=LoadMenu(g_hInstance,(char*)IDR_MENU1);
	SetMenu(hWnd,hMenu);
}
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam){

	switch(msgID){
	case WM_CREATE:
		OnCreate(hWnd);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);//可以使GetMessage函数返回0
		break;
	}
	return DefWindowProc(hWnd,msgID,wParam,lParam);
}

int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){
	g_hInstance=hIns;
	//注册窗口类
	WNDCLASS wc={0};
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hIns;
	wc.lpfnWndProc=WndProc;
	wc.lpszClassName="Main";
	wc.lpszMenuName=NULL;	
	wc.style=CS_HREDRAW|CS_VREDRAW;
	RegisterClass(&wc);
	//创建窗口
	HWND hWnd=CreateWindow("Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);
	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	//消息循环
	MSG nMsg={0};
	while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg);
		DispatchMessage(&nMsg);//将消息交给窗口函数处理
	}
	return 0;
}

15.5.命令消息处理WM_COMMAND

  • 附带消息:
    • wParam:
      • LOWORD 菜单项的ID
      • HIWORD 对于菜单为0
    • lParam 对于菜单为0

命令消息处理代码示例

#include <windows.h>
#include "resource.h"
HINSTANCE g_hInstance=0;
void OnCreate(HWND hWnd){
	HMENU hMenu=LoadMenu(g_hInstance,(char*)IDR_MENU1);
	SetMenu(hWnd,hMenu);
}
void OnCommand(HWND hWnd,WPARAM wParam){
	switch(LOWORD(wParam)){
	case ID_NEW:
		MessageBox(hWnd,"新建被点击","Info",MB_OK);
		break;
	case ID_EXIT:
		MessageBox(hWnd,"退出被点击","Info",MB_OK);
		break;
	case ID_ABOUT:
		MessageBox(hWnd,"关于被点击","Info",MB_OK);
		break;
	}
}
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam){

	switch(msgID){
	case WM_COMMAND:
		OnCommand(hWnd,wParam);
		break;
	case WM_CREATE:
		OnCreate(hWnd);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);//可以使GetMessage函数返回0
		break;
	}
	return DefWindowProc(hWnd,msgID,wParam,lParam);
}

int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){
	g_hInstance=hIns;
	//注册窗口类
	WNDCLASS wc={0};
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hIns;
	wc.lpfnWndProc=WndProc;
	wc.lpszClassName="Main";
	wc.lpszMenuName=NULL;
	//wc.lpszMenuName=(char*)IDR_MENU1;//菜单
	wc.style=CS_HREDRAW|CS_VREDRAW;
	RegisterClass(&wc);
	//创建窗口
	/*HMENU hMenu=LoadMenu(hIns,(char*)IDR_MENU1);
	HWND hWnd=CreateWindow("Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,hMenu,hIns,NULL);*/
	HWND hWnd=CreateWindow("Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);
	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	//消息循环
	MSG nMsg={0};
	while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg);
		DispatchMessage(&nMsg);//将消息交给窗口函数处理
	}
	return 0;
}

15.6.菜单项状态

15.7.上下文菜单

16.图标资源

  • 添加资源:注意图标的大小,一个图标文件中,可以有不同大小的图标。

  • 加载:

    HICON LoadIcon(  
    			HINSTANCE hInstance, // handle to application instance
    		  	LPCTSTR lpIconName   // name string or resource identifier
      );//成功返回HICON句柄
    
  • 设置:注册窗口类

17.光标资源

17.1.光标资源的介绍

  • 添加光标的资源
    光标的大小默认是32*32像素,每个光标有HotSpot,是当前鼠标的热点

  • 加载资源

    HCURSOR LoadCursor(  
    		HINSTANCE hInstance,  // handle to application instance
      		LPCTSTR lpCursorName  // name or resource identifier
      );
    
    • hInstance可以为NULL,获取系统默认的Cursor

17.2.设置光标资源的两种方法

  • 设置资源有两种方法:
    • 1 在注册窗口时,使用LoadCursor设置光标(只能在客户区显示设置的光标)
    • 2 使用SetCursor设置光标
HCURSOR SetCursor(  HCURSOR hCursor   // handle to cursor);

17.3.WM_SETCURSOR消息

  • 消息产生时间:鼠标移动时
  • 参数信息:
    • wParam当前使用的光标句柄
    • lParam:
      • LOWORD当前区域的代码(Hit-Test code)
        • HTCLIENT在客户区活动
        • HTCAPTION在标题栏区域活动
      • HIWORD当前鼠标消息ID
  • 专职用法:修改光标

17.4.示例代码

#include <windows.h>
#include "resource.h"
HINSTANCE g_hInstancee=0;

LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam){

	switch(msgID){
	case WM_SETCURSOR:
		if(LOWORD(lParam)==HTCLIENT){
			HCURSOR hCur=LoadCursor(g_hInstancee,(char*)IDC_CURSOR2);
			SetCursor(hCur);
			return 0;//不加,执行return DefWindowProc(hWnd,msgID,wParam,lParam);时,会修改成系统注册窗口时的光标
		}else{
			//非客户区,不处理。系统默认处理函数会处理
		}				
		break;
	case WM_DESTROY:
		PostQuitMessage(0);//可以使GetMessage函数返回0
		break;
	}
	return DefWindowProc(hWnd,msgID,wParam,lParam);
}

int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){
	g_hInstancee=hIns;
	//注册窗口类
	WNDCLASS wc={0};
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=LoadCursor(hIns,(char*)IDC_CURSOR1);//加载光标,只能在客户区显示该光标
	wc.hIcon=LoadIcon(hIns,(char*)IDI_ICON1);//加载图标资源
	wc.hInstance=hIns;
	wc.lpfnWndProc=WndProc;
	wc.lpszClassName="Main";
	wc.lpszMenuName=NULL;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	RegisterClass(&wc);
	//创建窗口
	HWND hWnd=CreateWindow("Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);
	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	//消息循环
	MSG nMsg={0};
	while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg);
		DispatchMessage(&nMsg);//将消息交给窗口函数处理
	}
	return 0;
}

18.字符串资源

  • 添加字符串资源,添加字符串表,在表中增加字符串

  • 字符串资源的使用

    int LoadString(  
    			HINSTANCE hInstance,  // handle to resource module
    		  	UINT uID,             // resource identifier字符串ID
    		  	LPTSTR lpBuffer,      // resource buffer存放字符串BUFF
    		  	int nBufferMax        // size of buffer字符串BUFF长度
      );//成功返回字符串长度,失败0
    

代码示例:
Win32从入门到放弃

#include <windows.h>
#include "resource.h"

LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam){

	switch(msgID){
	case WM_DESTROY:
		PostQuitMessage(0);//可以使GetMessage函数返回0
		break;
	}
	return DefWindowProc(hWnd,msgID,wParam,lParam);
}

int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){

	//注册窗口类
	WNDCLASS wc={0};
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hIns;
	wc.lpfnWndProc=WndProc;
	wc.lpszClassName="Main";
	wc.lpszMenuName=NULL;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	RegisterClass(&wc);
	//创建窗口
	char szText[256]={0};
	LoadString(hIns,IDS_WND,szText,256);
	HWND hWnd=CreateWindow("Main",szText,WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);
	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	//消息循环
	MSG nMsg={0};
	while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg);
		DispatchMessage(&nMsg);//将消息交给窗口函数处理
	}
	return 0;
}

19.加速键资源

  • 添加 资源添加加速键表,增加命令ID对应的加速键。

  • 使用

    • 加载加速键表

      HACCEL LoadAccelerators(  
      		HINSTANCE hInstance,  // handle to module
       		LPCTSTR lpTableName   // accelerator table name
        );//返回加速键表句柄
        
      
      • 翻译加速键
      int TranslateAccelerator( 
      		 HWND hWnd,         // handle to destination window处理消息的窗口句柄
       		 HACCEL hAccTable,  // handle to accelerator table加速键表句柄
        		LPMSG lpMsg        // message information消息
        		);//如果是加速键,返回非零
      
  • TranslateAccelerator原理
    Win32从入门到放弃

  • 在WM_COMMAND中相应消息,消息参数
    • wParam:
      • HIWORD为1表示加速键,为0表示菜单
      • LOWORD为命令ID
    • lParam为0

加速键的使用
Win32从入门到放弃
Win32从入门到放弃
代码示例

#include <windows.h>
#include "resource.h"
void OnCommand(HWND hWnd,WPARAM wParam){
	switch(LOWORD(wParam)){
	case ID_NEW:
		if(HIWORD(wParam)==0)
			MessageBox(hWnd,"新建菜单项被点击","Info",MB_OK);
		else if(HIWORD(wParam)==1)
			MessageBox(hWnd,"CTRL+M被点击","Info",MB_OK);
		break;
	}
}

LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam){

	switch(msgID){
	case WM_COMMAND:
		OnCommand(hWnd,wParam);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);//可以使GetMessage函数返回0
		break;
	}
	return DefWindowProc(hWnd,msgID,wParam,lParam);
}

int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){

	//注册窗口类
	WNDCLASS wc={0};
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hIns;
	wc.lpfnWndProc=WndProc;
	wc.lpszClassName="Main";
	wc.lpszMenuName=(char*)IDR_MENU1;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	RegisterClass(&wc);
	//创建窗口
	HWND hWnd=CreateWindow("Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);
	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	HACCEL hAccel=LoadAccelerators(hIns,(char*)IDR_ACCELERATOR1);
	//消息循环
	MSG nMsg={0};
	while(GetMessage(&nMsg,NULL,0,0)){
		if(!TranslateAccelerator(hWnd,hAccel,&nMsg)){
			//如果不是加速键
			TranslateMessage(&nMsg);
			DispatchMessage(&nMsg);//将消息交给窗口函数处理
		}

	}
	return 0;
}

20.绘图编程

20.1.绘图基础

  • 绘图设备DC(Device Context),绘图上下文/绘图描述表。
  • HDC,DC句柄,表示绘图设备。
  • GDI,Windows graphich device interface(Win32提供的绘图API)
  • 颜色
    • 计算机使用红、绿、蓝,
      • R 0~255
      • G 0~255
      • B 0~255
  • 每一个点颜色是3个字节24位保存0~224-1
    • 16位:5,5,6
    • 32位:8,8,8,8(最后8位是绘图或透明)
  • 颜色的使用
    • COLORREF实际是DWORD
    • 例如:COLORREF nColor=0;
  • 赋值使用RGB宏
    • 例如:nColor=RGB(0,0,255);
  • 获取RGB值
    • GetRValue
    • GetGValue
    • GetBValue
    • 例如:BYTE nRed=GetRValue(nColor);

20.2.基本图形绘制

  • SetPixel设置指定点的颜色

    COLORREF SetPixel(  
    		HDC hdc,           // handle to DC 
    		  int X,             // x-coordinate of pixel
    		  int Y,             // y-coordinate of pixel  
    		  COLORREF crColor   // pixel color
    );//返回原来的颜色
    
  • 线的使用(直线、弧线)
    MoveToEx 指名窗口当前点
    LineTo从窗口当前点到指定点绘制一条直线
    当前点:上一次绘图的最后一点,初始为(0,0)点。

  • 封闭图形:能够用画刷填充的图形

    • Rectangle 矩形
    • Ellipse 圆

代码示例

#include <windows.h>

void OnPaint(HWND hWnd){
	PAINTSTRUCT ps={0};
	HDC hdc=BeginPaint(hWnd,&ps);
	SetPixel(hdc,100,100,RGB(255,0,0));//绘制点

	LineTo(hdc,100,0);
	MoveToEx(hdc,0,0,NULL);
	LineTo(hdc,0,100);

	Rectangle(hdc,100,100,300,300);
	Ellipse(hdc,100,100,300,300);
	EndPaint(hWnd,&ps);
}
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam){

	switch(msgID){
	case WM_PAINT:
		OnPaint(hWnd);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);//可以使GetMessage函数返回0
		break;
	}
	return DefWindowProc(hWnd,msgID,wParam,lParam);
}

int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){
	//注册窗口类
	WNDCLASS wc={0};
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hIns;
	wc.lpfnWndProc=WndProc;
	wc.lpszClassName="Main";
	wc.lpszMenuName=NULL;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	RegisterClass(&wc);
	//创建窗口
	HWND hWnd=CreateWindow("Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);
	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	//消息循环
	MSG nMsg={0};
	while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg);
		DispatchMessage(&nMsg);//将消息交给窗口函数处理
	}
	return 0;
}

20.3.GDI绘图对象

20.3.1.画笔

  • 画笔的作用
    线的颜色、线型、粗细
    HEPN 画笔句柄
  • 画笔的使用
    • 1 创建画笔

      HPEN CreatePen(  
      		int fnPenStyle,    // pen style
      	 	int nWidth,        // pen width  
      	  	COLORREF crColor   // pen color
        );//创建成功返回句柄
      
      • PS_SOILD实必线,可支持多个像素宽其他线型只能是一个像素宽。
    • 2 将画笔应用到DC中

      HGDIOBJ SelectObject(  
      			HDC hdc,          // handle to DC
      		  	HGDIOBJ hgdiobj   // handle to object //GDI绘图对象句柄
        );//返回原来的GDI绘图对象句柄
        
      

      注意保存原来DC当中的画笔。

    • 3、绘图

    • 4、取出DC中的画笔
      将原来的画笔,使用SelectObject函数,放入到设备DC中,就会将我们创建的画笔取出。

    • 5 释放画笔

      BOOL DeleteObject(  
      		HGDIOBJ hObject   // handle to graphic object//GDI绘图对象句柄
      );
      

只能删除不被DC使用的画笔,所在在释放前,必须将画笔从DC中取出。

画笔示例代码:

#include <windows.h>

void OnPaint(HWND hWnd){
	PAINTSTRUCT ps={0};
	HDC hdc=BeginPaint(hWnd,&ps);
	HPEN hPen=CreatePen(PS_SOLID,5,RGB(255,0,0));//创建画笔
	HGDIOBJ nOldPen=SelectObject(hdc,hPen);	

	Rectangle(hdc,100,100,300,300);
	Ellipse(hdc,100,100,300,300);

	SelectObject(hdc,nOldPen);
	DeleteObject(hPen);
	EndPaint(hWnd,&ps);
}
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam){

	switch(msgID){
	case WM_PAINT:
		OnPaint(hWnd);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);//可以使GetMessage函数返回0
		break;
	}
	return DefWindowProc(hWnd,msgID,wParam,lParam);
}

int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){
	//注册窗口类
	WNDCLASS wc={0};
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hIns;
	wc.lpfnWndProc=WndProc;
	wc.lpszClassName="Main";
	wc.lpszMenuName=NULL;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	RegisterClass(&wc);
	//创建窗口
	HWND hWnd=CreateWindow("Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);
	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	//消息循环
	MSG nMsg={0};
	while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg);
		DispatchMessage(&nMsg);//将消息交给窗口函数处理
	}
	return 0;
}

20.3.2.画刷

  • 画刷相关
    • 画刷 ,封闭图形填充颜色、图案
    • HBRUSH 画刷句柄
  • 画刷的使用
    • 1 创建画刷
      • CreateSolidBrush创建实心画刷
      • CreateHatchBrush创建纹理画刷
    • 2 将画刷应用到DC中
      • SelectObject
    • 3 绘图
    • 4 将画刷从DC中取出
      • SelectObject
    • 5 删除画刷
  • 透明画刷
    可以使用GetStockObject函数获取系统维护的画刷、画笔、字体等。
    如果不使用画刷填充,需要使用NULL_BRUSH参数,获取不填充的画刷(即透明画刷)。
    注意系统最原始的画刷是白色不是透明。
    GetStockObject返回的画刷不需要DeleteObject。

示例代码:

#include <windows.h>

void OnPaint(HWND hWnd){
	PAINTSTRUCT ps={0};
	HDC hdc=BeginPaint(hWnd,&ps);
	HPEN hPen=CreatePen(PS_SOLID,5,RGB(255,0,0));//创建画笔
	HGDIOBJ nOldPen=SelectObject(hdc,hPen);	//选中画笔

	HBRUSH hBrush=CreateSolidBrush(RGB(0,255,0));//创建实心画刷
//  HBRUSH hBrush=CreateHatchBrush(HS_CROSS,RGB(0,255,0));//创建纹理画刷
//	HGDIOBJ hBrush=GetStockObject(NULL_BRUSH);//由系统维护的透明画刷
	HGDIOBJ nOldBrush=SelectObject(hdc,hBrush);//选中画刷

	Rectangle(hdc,100,100,300,300);
	Ellipse(hdc,100,100,300,300);

	SelectObject(hdc,nOldPen);//恢复原来的画笔
	SelectObject(hdc,nOldBrush);//恢复原来的画刷
	DeleteObject(hPen);//删除画笔
	DeleteObject(hBrush);//删除画刷
	EndPaint(hWnd,&ps);
}
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam){

	switch(msgID){
	case WM_PAINT:
		OnPaint(hWnd);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);//可以使GetMessage函数返回0
		break;
	}
	return DefWindowProc(hWnd,msgID,wParam,lParam);
}

int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){
	//注册窗口类
	WNDCLASS wc={0};
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hIns;
	wc.lpfnWndProc=WndProc;
	wc.lpszClassName="Main";
	wc.lpszMenuName=NULL;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	RegisterClass(&wc);
	//创建窗口
	HWND hWnd=CreateWindow("Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);
	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	//消息循环
	MSG nMsg={0};
	while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg);
		DispatchMessage(&nMsg);//将消息交给窗口函数处理
	}
	return 0;
}

实心画刷效果
Win32从入门到放弃

纹理画刷效果
Win32从入门到放弃

21.位图

21.1.位图绘制

21.1.1. 位图相关

  • 光栅图形,记录图像中每一点的颜色等信息。
  • 矢量图形,记录图像算法、绘图指令等。
  • HBITMAP位图句柄

21.1.2. 位图的使用

  • 1 在资源中添加位图资源

  • 2 从资源中加载位图LoadBitmap

  • 3 创建一个与当前DC相匹配的DC(内存DC)

    HDC CreateCompatibleDC(  
    	HDC hdc   // handle to DC当前DC句柄,可以为NULL(使用屏幕DC)
    	);//返回创建好的DC句柄
    
  • 4 将位图放入匹配的DC中 SelectObject

  • 5 成像

    • 1:1成像
      BOOL BitBlt(
        HDC hdcDest, // handle to destination DC目的DC
        int nXDest,  // x-coord of destination upper-left corne目的左上x坐标
        int nYDest,  // y-coord of destination upper-left corner目的左上y坐标
        int nWidth,  // width of destination rectangle目的宽
        int nHeight, // height of destination rectangle目的高
        HDC hdcSrc,  // handle to source DC源DC
        int nXSrc,   // x-coordinate of source upper-left corner源左上X坐标
        int nYSrc,   // y-coordinate of source upper-left corner源左上y坐标
        DWORD dwRop  // raster operation code成像方法SRCCOPY
      );
      
      
    • 缩放成像
      BOOL StretchBlt(  HDC hdcDest,      // handle to destination DC
        int nXOriginDest, // x-coord of destination upper-left corner
        int nYOriginDest, // y-coord of destination upper-left corner
        int nWidthDest,   // width of destination rectangle
        int nHeightDest,  // height of destination rectangle
        HDC hdcSrc,       // handle to source DC
        int nXOriginSrc,  // x-coord of source upper-left corner
        int nYOriginSrc,  // y-coord of source upper-left corner
        int nWidthSrc,    // width of source rectangle
        int nHeightSrc,   // height of source rectangle
        DWORD dwRop       // raster operation code);
      
  • 6 取出位图 SelectObject

  • 7 释放位图 DeleteObject

  • 8 释放匹配的DC DeleteDC

21.1.3. 代码示例

#include <windows.h>
#include "resource.h"
HINSTANCE g_hInstance=0;
void BrawBmp(HDC hdc){
	HBITMAP hBmp=LoadBitmap(g_hInstance,(CHAR*)IDB_BITMAP1);
	//创建一个内存DC,并构建一个虚拟区域,并且内存DC在虚拟区域中绘图
	HDC hMemdc=CreateCompatibleDC(hdc);
	//将位图数据送给内存DC,内存DC在虚拟区域中将位图绘制出来。
	HGDIOBJ nOldBmp=SelectObject(hMemdc,hBmp);
	//1:1成像,将虚拟区域中绘制好的图像成像到窗口中
	BitBlt(hdc,100,100,48,48,hMemdc,0,0,SRCCOPY);
	//缩放成像,缩小成24*24
	StretchBlt(hdc,200,200,24,24,hMemdc,0,0,48,48,SRCCOPY);
	//缩放成像,放大成96*96
	StretchBlt(hdc,300,300,96,96,hMemdc,0,0,48,48,SRCCOPY);
	SelectObject(hMemdc,nOldBmp);
	DeleteObject(hBmp);
	DeleteObject(hMemdc);
}

void OnPaint(HWND hWnd){
	PAINTSTRUCT ps={0};
	HDC hdc=BeginPaint(hWnd,&ps);
	BrawBmp(hdc);
	
	EndPaint(hWnd,&ps);
}
LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam){

	switch(msgID){
	case WM_PAINT:
		OnPaint(hWnd);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);//可以使GetMessage函数返回0
		break;
	}
	return DefWindowProc(hWnd,msgID,wParam,lParam);
}

int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){
	g_hInstance=hIns;
	//注册窗口类
	WNDCLASS wc={0};
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hIns;
	wc.lpfnWndProc=WndProc;
	wc.lpszClassName="Main";
	wc.lpszMenuName=NULL;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	RegisterClass(&wc);
	//创建窗口
	HWND hWnd=CreateWindow("Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);
	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	//消息循环
	MSG nMsg={0};
	while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg);
		DispatchMessage(&nMsg);//将消息交给窗口函数处理
	}
	return 0;
}

22.文本绘制

22.1.文本绘制

  • TextOut

  • DrawText

    int DrawText(  
    		HDC hDC,          // handle to DC
      		LPCTSTR lpString, // text to draw 字符串
      		int nCount,       // text length
     		 LPRECT lpRect,    // formatting dimensions绘制文字的矩形框
      		UINT uFormat      // text-drawing options绘制的方式
      );
    
    • DT_LEFT
    • DT_TOP
    • DT_WORDBREAK 多行
    • DT_NOCLIP
    • DT_SINGLELINE:单行显示文本,回车和换行符都不断行。
    • DT_CENTER:指定文本水平居中显示。
    • DT_VCENTER:指定文本垂直居中显示。该标记只在单行文本输出时有效,所以它必须与DT_SINGLELINE结合使用。
    • DT_BOTTOM
      DT_BOTTOM、DT_VCENTER只适用于DT_SINGLELINE
      和DT_WORDBREAK冲突
    • DT_CALCRECT:可以使DrawText函数计算出输出文本的尺寸。如果输出文本有多行,DrawText函数使用lpRect定义的矩形的宽度,并扩展矩形的底部以容纳输出文本的最后一行。如果输出文本只有一行,则DrawText函数改变矩形的右边界,以容纳下正文行的最后一个字符。出现上述任何一种情况,DrawText函数将返回格式化文本的高度,而不是绘制文本。

22.2.文字颜色和背景

  • 文字颜色:SetTextColor
  • 文字背景色:SetBkColor
  • 文字背景模式:SetBkMode
    • QPAQUE,默认,背景不透明。
    • TRANSPARENT 背景透明。

22.3.字体

22.3.1.字体介绍

  • 字体相关
    Window常用的字体为TrueType格式的字体文件
    字体名:标识字体类型
    HFONT字体句柄

  • 真正的字体名
    Win32从入门到放弃

22.3.2.字体的使用

  • 1 创建字体

    HFONT CreateFont(  
    		  int nHeight,               // height of font字体高度
    		  int nWidth,                // average character width字体宽度
    		  int nEscapement,           // angle of escapement字符串倾斜角度,以0.1度为单位
    		  int nOrientation,          // base-line orientation angle字符串旋转角度,以x轴为准线旋转,适用于三维
    		  int fnWeight,              // font weight字体粗细
    		  DWORD fdwItalic,           // italic attribute option斜体
    		  DWORD fdwUnderline,        // underline attribute option下划线
    		  DWORD fdwStrikeOut,        // strikeout attribute option删除线
    		  DWORD fdwCharSet,          // character set identifier字符集,GB2312_CHARSET
    		  DWORD fdwOutputPrecision,  // output precision输出精度
    		  DWORD fdwClipPrecision,    // clipping precision剪切精度
    		  DWORD fdwQuality,          // output quality输出质量
    		  DWORD fdwPitchAndFamily,   // pitch and family匹配字体
    		  LPCTSTR lpszFace           // typeface name字体名称
      );
    
  • 2 应用字体到DCSelectObject

  • 3 绘制文字

    • DrawText
    • TextOut
  • 4 取出字体SelectObject

  • 5 删除字体DeleteObject

22.3.3. 代码示例

#include <windows.h>
#include <stdio.h>
void OnPaint(HWND hWnd){
	PAINTSTRUCT ps={0};
	HDC hdc=BeginPaint(hWnd,&ps);	

	SetTextColor(hdc,RGB(255,0,0));//设置字体颜色
	SetBkColor(hdc,RGB(0,255,0));//设置背景字体颜色,只适于OPAQUE模式
	SetBkMode(hdc,TRANSPARENT);//设置背景字体透明

	HFONT hFont=CreateFont(30,0,45,0,900,1,1,1,GB2312_CHARSET,0,0,0,0,"微软雅黑");
	HGDIOBJ nOldFont=SelectObject(hdc,hFont);


	char szText[256]="Hello word!";	
	TextOut(hdc,100,100,szText,strlen(szText));

	RECT rt;
	rt.left=100;
	rt.top=150;
	rt.right=200;
	rt.bottom=200;
	//Rectangle(hdc,100,150,200,200);
	DrawText(hdc,szText,strlen(szText),&rt,DT_LEFT|DT_TOP|DT_WORDBREAK|DT_NOCLIP);
	SelectObject(hdc,nOldFont);
	DeleteObject(hFont);
	EndPaint(hWnd,&ps);

}

LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam){

	switch(msgID){
	case WM_PAINT:
		OnPaint(hWnd);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);//可以使GetMessage函数返回0
		break;
	}
	return DefWindowProc(hWnd,msgID,wParam,lParam);
}

int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){
	//注册窗口类
	WNDCLASS wc={0};
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hIns;
	wc.lpfnWndProc=WndProc;
	wc.lpszClassName="Main";
	wc.lpszMenuName=NULL;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	RegisterClass(&wc);
	//创建窗口
	HWND hWnd=CreateWindow("Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);
	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	//消息循环
	MSG nMsg={0};
	while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg);
		DispatchMessage(&nMsg);//将消息交给窗口函数处理
	}
	return 0;
}

23.对话框

  • 普通窗口:自定义函数调用缺省函数

    WndProc(...){
    	... ...
    	DefWindowProc(...);
    }
    
  • 对话框窗口:缺省函数调用自定义函数

    缺省函数(...){
    	...
    	自定义函数(...)
    }
    

23.1.对话框原理

  • 对话框分类

    • 模式对话框 ,当对话框显示时,会禁止与其它窗口和用户交互操作。
    • 无模式对话框,在对话框显示后,其他窗口仍然可以和用户交互操作。
  • 对话框基本使用

    • 1 对话框窗口处理函数
    • 2 注册窗口类(不使用)
    • 3 创建对话框
    • 4 对话框的关闭
  • 对话框窗口处理函数(并非真正的对话框窗口处理函数)

    INT_PTR CALLBACK DialogProc(  
    		  HWND hwndDlg,  // handle to dialog box
    		  UINT uMsg,     // message  
    		  WPARAM wParam, // first message parameter
    		  LPARAM lParam  // second message parameter
      );
    
    • 返回TRUE,缺省处理函数不需要处理。
    • 返回FALSE,交给缺省处理函数处理。
    • 不需要调用缺省对话框窗口处理函数。

23.2.模式对话框

23.2.1. 创建对话框

INT_PTR DialogBox(  
	  HINSTANCE hInstance,  // handle to module
	  LPCTSTR lpTemplate,   // dialog box template对话框资源ID
	  HWND hWndParent,      // handle to owner window
	  DLGPROC lpDialogFunc  // dialog box procedure自定义函数
  );
  • DialogBox是一个阻塞函数,只有当对话框关闭后,才会返回,执行后续代码。
  • 返回值通过EndDialog设置。

23.2.2. 对话框的关闭

BOOL EndDialog(  
	  HWND hDlg,        // handle to dialog box
	  INT_PTR nResult   // value to return
  );
  • 关闭模式对话框,只能使用EndDialog,不能使用DestroyWindow等函数。
  • DestroyWindow只能实现销毁窗口,EndDialog除了销毁窗口还有一个功能,可以解除DialogBox的阻塞。
  • nResult是DialogBox函数退出时的返回值。

23.2.3. 对话框的消息

  • WM_INITDIALOG,对话框创建之后显示之前,通知对话框窗口处理函数,可以完成自己的初始化相关操作。
  • 对话框创建不会产生WM_CREATE消息。

23.2.4. 代码示例

#include <windows.h>
#include "resource.h"
HINSTANCE g_hIns=0;
INT_PTR CALLBACK DialogProc(  
	HWND hwndDlg,  // handle to dialog box
	UINT uMsg,     // message  
	WPARAM wParam, // first message parameter
	LPARAM lParam  // second message parameter
	){
		switch(uMsg){
		case WM_INITDIALOG:
			MessageBox(hwndDlg,"WM_INITDIALOG","Info",MB_OK);
			break;
		case WM_SYSCOMMAND:
			if(wParam==SC_CLOSE){
				//销毁模式对话框
				EndDialog(hwndDlg,100);
			}
			break;
		} 
		return FALSE;
}
void OnCommand(HWND hWnd,WPARAM wParam){
	switch(LOWORD(wParam)){
	case ID_MODEL:
		int nRet=DialogBox(g_hIns,(char*)IDD_DIALOG1,hWnd,DialogProc);
		if(nRet==100)
			MessageBox(hWnd,"successful","Info",MB_OK);
		break;
	}
}

LRESULT CALLBACK WndProc(HWND hWnd,UINT msgID,WPARAM wParam,LPARAM lParam){

	switch(msgID){
	case WM_COMMAND:
		OnCommand(hWnd,wParam);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);//可以使GetMessage函数返回0
		break;
	}
	return DefWindowProc(hWnd,msgID,wParam,lParam);
}

int CALLBACK WinMain(HINSTANCE hIns,HINSTANCE hPreIns,LPSTR lpCmdLine,int nCmdShow){
	g_hIns=hIns;
	//注册窗口类
	WNDCLASS wc={0};
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hIns;
	wc.lpfnWndProc=WndProc;
	wc.lpszClassName="Main";
	wc.lpszMenuName=(CHAR*)IDR_MENU1;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	RegisterClass(&wc);
	//创建窗口
	HWND hWnd=CreateWindow("Main","Window",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hIns,NULL);
	//显示窗口
	ShowWindow(hWnd,SW_SHOW);
	UpdateWindow(hWnd);
	//消息循环
	MSG nMsg={0};
	while(GetMessage(&nMsg,NULL,0,0)){
		TranslateMessage(&nMsg);
		DispatchMessage(&nMsg);//将消息交给窗口函数处理
	}
	return 0;
}

23.3.无模式对话框

23.3.1.创建对话框

HWND CreateDialog(  
	  HINSTANCE hInstance,  // handle to module
	  LPCTSTR lpTemplate,   // dialog box template name模板资源ID
	  HWND hWndParent,      // handle to owner window
	  DLGPROC lpDialogFunc  // dialog box procedure自定义函数
  );
  • 非阻塞函数,创建成功返回窗口句柄,需要使用ShowWindow函数显示对话框。

23.3.2. 对话框的关闭

  • 关闭时使用DestroyWindow销毁窗口,不能使用EndDialog关闭对话框。

24.静态库

24.1.静态库特点

  • 运行不存在。
  • 静态库源码被链接到调用程序中。
  • 目标程序的归档。

24.2.C语言静态库

  • C静态库的创建

    • 1 创建一个静态库项目。
    • 2 添加库程序,源文件使用C文件。
  • C静态库的使用

    • 库路径设置:可以使用pragma关键字设置。
    • #pragma comment(lib,"../lib/clib.lib")
  • 代码示例

    Clib.c

    
    int Clib_add(int a,int b){
    	return a+b;
    }
    
    int Clib_sub(int a,int b){
    	return a-b;
    }
    

    UserClib.c

    #pragma comment(lib,"../Debug/Clib.lib")
    int main(){
    	int sum=0,sub=0;
    	sum=Clib_add(1,2);
    	sub=Clib_sub(3,5);
    	printf("sum=%d,sub=%d\n",sum,sub);
    	getchar();
    	return 0;
    }
    

24.3.C++语言静态库

  • C++静态库的创建

    • 1 创建一个静态库项目
    • 2 添加库程序,源文件使用CPP文件。
  • C++静态库的使用

    • 库路径设置:可以使用pragma关键字设置。
    • #pragma comment(lib,"../lib/clib.lib")
  • 代码示例
    CPPlib.cpp

    
    int CPPlib_add(int a,int b){
    	return a+b;
    }
    
    int CPPlib_sub(int a,int b){
    	return a-b;
    }
    

    UserCPPlib.cpp

    //给编译器看的
    #include <iostream>
    using namespace std;
    int CPPlib_add(int a,int b);
    int CPPlib_sub(int a,int b);
    
    
    //给链接器看的
    #pragma comment(lib,"../Debug/CPPlib.lib")
    
    int main(){
    	int sum=0,sub=0;
    	sum=CPPlib_add(1,2);
    	sub=CPPlib_sub(3,5);
    	cout<<"sum="<<sum<<",sub"<<sub<<endl;
    	getchar();
    	return 0;
    }
    
  • C++调用C语言静态库

//给编译器看的
#include <iostream>
using namespace std;
int CPPlib_add(int a,int b);
int CPPlib_sub(int a,int b);
//调用C语言静态库Clib.lib中的函数
extern "C" int Clib_add(int a,int b);
extern "C" int Clib_sub(int a,int b);

//给链接器看的
#pragma comment(lib,"../Debug/CPPlib.lib")//?CPPlib_sub@@YAHHH@Z、?CPPlib_add@@YAHHH@Z
#pragma comment(lib,"../Debug/Clib.lib")//Clib_add、Clib_sub
int main(){
	int sum=0,sub=0;
	sum=CPPlib_add(1,2);//?CPPlib_sub@@YAHHH@Z
	sub=CPPlib_sub(3,5);//?CPPlib_add@@YAHHH@Z
	cout<<"sum="<<sum<<",sub"<<sub<<endl;
	sum=Clib_add(1,2);//?Clib_sub@@YAHHH@Z
	sub=Clib_sub(2,3);//?Clib_add@@YAHHH@Z
	cout<<"sum="<<sum<<",sub"<<sub<<endl;
	getchar();
	return 0;
}

25.动态库

25.1.动态库特点

  • 动态库特点
    • 1 运行时独立存大
    • 2 源码不会链接到执行程序
    • 3 使用时加载(使用动态库必须使动态库执行)
  • 与静态库的比较
    • 1 由于静态库是将代码嵌入到使用程序中,多个程序使用时,会有多份代码,所以代码体积会增大。动态库的代码只需要存在一份,其他程序通过函数地址使用,所以代码体积小。
    • 2 静态库发生变化后,新的代码需要重新链接嵌入到执行程序中。动态库发生变化后,如果库中函数的定义(或地址)未变化,其它使用DLL的程序不需要重新链接。

25.1.动态库创建

  • 创建动态库项目
  • 添加库程序
  • 库程序导出(两种方法)- 提供给使用者库中的函数等信息。

25.2.动态库导出的两种方法

25.2.1.声明导出

  • 声明导出:使用_declspec(dllexport)导出函数
    注意:动态库编译链接后,也会有lib文件,是作为动态库函数映射使用,与静态库不完全相同。

CPPdll.cpp

_declspec(dllexport)int CPPdll_add(int a,int b){
	return a+b;
}

_declspec(dllexport)int CPPdll_sub(int a,int b){
	return a-b;
}

_declspec(dllexport)int CPPdll_mul(int a,int b){
	return a*b;
}

25.2.2. 模块定义文件.def

  • 例如:
LIBRARY CPPdll  //库
EXPORTS			//库导出表
	CPPdll_add @1//导出的函数
	CPPdll_sub @2//导出的函数

CPPdll.dll

int CPPdll_add(int a,int b){
	return a+b;
}

int CPPdll_sub(int a,int b){
	return a-b;
}

_declspec(dllexport)int CPPdll_mul(int a,int b){
	return a*b;
}
  • 编译器在生成dll文件时会改变函数名,如CPPdll_mul会变换成?CPPdll_mul@@YAHHH@Z写入dll文件。

CPPdll.def

LIBRARY CPPdll
EXPORTS
	CPPdll_add @1
	CPPdll_sub @2

UseShow.cpp

#include <windows.h>
#include <iostream>
using namespace std;
typedef int(*ADD)(int a,int b);
typedef int(*SUB)(int a,int b);
typedef int(*MUL)(int a,int b);

int main(){
	HINSTANCE hDll=LoadLibrary("CPPdll.dll");		
	cout<<"hDll:"<<hDll<<endl;
	ADD myAdd=(ADD)GetProcAddress(hDll,"CPPdll_add");//?CPPdll_add@@YAHHH@Z
	cout<<"myAdd:"<<myAdd<<endl;
	int sum=myAdd(1,2);
	cout<<"sum="<<sum<<endl;
	SUB mySub=(SUB)GetProcAddress(hDll,"CPPdll_sub");
	cout<<"mySub:"<<mySub<<endl;
	int sub=mySub(2,3);
	cout<<"sub="<<sub<<endl;
	MUL myMul=(MUL)GetProcAddress(hDll,"?CPPdll_mul@@YAHHH@Z");
	cout<<"myMul:"<<myMul<<endl;
	int mul=myMul(3,4);
	cout<<"mul="<<mul<<endl;
	FreeLibrary(hDll);
	getchar();
	return 0;
	
}

25.1.动态库的使用

25.1.1.隐式链接(操作系统负责使动态库执行)

  • 1 头文件和函数原型
    可以在函数原型的声明前,增加_declspec(dllimport)
  • 2 导入动态库的lib文件
  • 3 在程序中使用函数
  • 4 隐式链接的情况,dll文件可以存放的路径:
    • 1 与执行文件中同一个目录下
    • 2 当前工作目录
    • 3 Windows目录
    • 4 Windows/System32目录
    • 5 WIndows/System目录
    • 6 环境变量PATH指定目录

代码示例

#include <iostream>
using namespace std;
_declspec(dllimport)int CPPdll_add(int a,int b);
_declspec(dllimport)int CPPdll_sub(int a,int b);
_declspec(dllimport)int CPPdll_mul(int a,int b);
#pragma comment(lib,"../Debug/CPPdll.lib")//通知链接器抓取编号和DLL文件名
int main(){
	int sum=CPPdll_add(1,2);
	int sub=CPPdll_sub(2,3);
	int mul=CPPdll_mul(3,4);
	cout<<"sum="<<sum<<",sub="<<sub<<".mul="<<mul<<endl;
	return 0;
}

25.1.2.显示链接(程序员自己负责动态库执行)

  • 1 定义函数指针类型typedef

  • 2 加载动态库

    HMODULE LoadLibrary(  
    	LPCTSTR lpFileName   // file name of module动态库文件名或全路径
    );//返回DLL的实例句柄(HINSTANCE)
    
    
  • 3 获取函数绝对地址

    FARPROC GetProcAddress(  
    		  HMODULE hModule,    // handle to DLL module//DLL句柄
    		  LPCSTR lpProcName   // function name函数名称
      );//成功返回函数地址
    
  • 4 使用函数

  • 5 卸载动态库

    BOOL FreeLibrary(  
    	HMODULE hModule   // handle to DLL module//DLL的实例句柄
    );
    

代码示例详见

25.1.动态库中封装类

  • 在类名称前增加_declspec(dllexport)定义,例如:

    class _declspec(dllexport) CMath{
    	...
    };
    
  • 通常使用预编译开关切换类的导入导出定义,例如:

    #ifdef DLLCLASS_EXPORTS
    #define EXT_CLASS _declspec(dllexport)//DLL
    #else
    #define EXT_CLASS _declspec(dllimport)//使用者
    #endif
    class EXT_CLASS CMath{
    	...
    };
    

代码示例:

  • 制作dll封装类

    ClassDll.h

    
    #ifndef	_DLLCLASS_H
    #define _DLLCLASS_H
    
    #ifdef DLLCLASS_EXPORTS
    #define EXT_CLASS _declspec(dllexport)//DLL
    #else
    #define EXT_CLASS _declspec(dllimport)//使用者
    #endif
    
    class EXT_CLASS CMath{
    public:
    	int Add(int a,int b);
    	int Sub(int a,int b);
    };
    
    #endif
    

    ClassDll.cpp

    #define DLLCLASS_EXPORTS
    #include "ClassDll.h"
    int CMath::Add(int a,int b){
    	return a+b;
    }
    
    int CMath::Sub(int a,int b){
    	return a-b;
    }
    
  • 使用ClassDll.dll

#include <iostream>
using namespace std;
#include "../ClassDll/ClassDll.h"
#pragma  comment(lib,"../Debug/ClassDll.lib")
int main(){
	CMath math;
	int add=math.Add(1,2);
	int sub=math.Sub(2,3);
	cout<<"sum="<<add<<",sub="<<sub<<endl;
	getchar();
	return 0;
}

26.线程

26.1.线程基础

  • Windows线程是可以执行的代码的实例。系统是以线程为单位调度程序。一个程序当中可以有多个线程(主线程只有一个),实现多任务的处理。
  • Windows线程的特点:
    • 1 线程都具有一个ID
    • 2 每个线程都 具有自己的内存栈。
    • 3 同一进程中的线程使用同一个地址空间。
  • 线程的调度:
    操作时间将CPU的执行时间划分成时间片,依次根据时间片执行不同的线程。
    线程轮询:线程A–>线程B–>线程A…

26.2.创建线程

  • 创建线程

    HANDLE CreateThread(  
    		  LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD安全属性(这是一个废弃的参数直接NULL)
    		  SIZE_T dwStackSize,                       // initial stack size线程栈的大小(永远按1M对齐)
    		  LPTHREAD_START_ROUTINE lpStartAddress,    // thread function线程处理函数的函数地址
    		  LPVOID lpParameter,                       // thread argument传递给线程处理函数的参数
    		  DWORD dwCreationFlags,                    // creation option线程的创建方式。有两种方式,立即执行;挂起方式
    		  LPDWORD lpThreadId                        // thread identifier创建成功返回线程的ID
      );//创建成功,返回线程句柄
    
  • 定义线程处理函数

    DWORD WINAPI ThreadProc(  
    	LPVOID lpParameter   // thread data创建线程时,传递给线程的参数
    );
    
  • 代码示例

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

DWORD WINAPI TestProc(  LPVOID lpParameter){
	char* pszText=(char*)lpParameter;
	while(1){
		printf("%s\n",pszText);
		Sleep(1000);
	}
	return 0;

}

DWORD WINAPI TestProc2(LPVOID lpParameter){
	char* pszText=(char*)lpParameter;
	while(1){
		printf("%s\n",pszText);
		Sleep(1000);
	}
	return 0;
}
int main(){
	DWORD nId=0;
	char* pszText="********";
	HANDLE hThread=CreateThread(NULL,0,TestProc,pszText,0,&nId);
	char* pszText2="--------";
	HANDLE hThread2=CreateThread(NULL,0,TestProc2,pszText2,0,&nId);
	getchar();
	return 0;
}

26.3.线程挂起和毁

  • 挂起

    DWORD SuspendThread(
    	HANDLE hThread   // handle to thread
    );
    
  • 唤醒

    DWORD ResumeThread(
    	HANDLE hThread   // handle to thread
    );
    
  • 挂起、唤醒代码示例

    #include <Windows.h>
    #include <stdio.h>
    
    DWORD WINAPI TestProc(  LPVOID lpParameter){
    	char* pszText=(char*)lpParameter;
    	while(1){
    		printf("%s\n",pszText);
    		Sleep(1000);
    	}
    	return 0;
    
    }
    
    DWORD WINAPI TestProc2(LPVOID lpParameter){
    	char* pszText=(char*)lpParameter;
    	while(1){
    		printf("%s\n",pszText);
    		Sleep(1000);
    	}
    	return 0;
    }
    int main(){
    	DWORD nId=0;
    	char* pszText="********";
    	HANDLE hThread=CreateThread(NULL,0,TestProc,pszText,0,&nId);//线程创建成功,立刻执行
    	char* pszText2="--------";
    	HANDLE hThread2=CreateThread(NULL,0,TestProc2,pszText2,CREATE_SUSPENDED,&nId);//线程创建成功后挂起
    	getchar();
    	SuspendThread(hThread);//挂起线程
    	ResumeThread(hThread2);//唤醒线程
    	getchar();
    	return 0;
    }
    
  • 结束指定线程

    BOOL TerminateThread(  
    		HANDLE hThread,    // handle to thread
      		DWORD dwExitCode   // exit code
      );
    
  • 结束函数所在的线程

    VOID ExitThread(  
    		DWORD dwExitCode   // exit code for this thread
    );
    

26.4.线程相关操作

  • 获取当前线程的IDGetCurrentThreadId

  • 获取当前线程的句柄GetCurrentThread

  • 等候单个句柄有信号

    DWORD WaitForSingleObject(
    		HANDLE hHandle,        // handle to object句柄BUFF的地址
      		DWORD dwMilliseconds   // time-out interval最大等候时间,单位为毫秒;如果参数为INFINITE,则等候时间无限长
      );//阻塞函数
    
    • 一个可等待的句柄必须具备:有信号、无信号两种状态。
    • 线程执行过程中为无信号,执行结束为有信号。
  • 同时等候多个句柄有信号

    DWORD WaitForMultipleObjects(
    		  DWORD nCount,             // number of handles in array句柄数量
    		  CONST HANDLE *lpHandles,  // object-handle array句柄BUFF的地址(即数组名字)
    		  BOOL bWaitAll,            // wait option等候方式
    		  DWORD dwMilliseconds      // time-out interval等候时间
      );
    
    • bWaitAll 等候方式
      • TRUE 表示所有句柄都有信号,才结束等候。
      • FLASE 表示句柄中只要有1个有信号,就结束等候。
  • 等待线程有信号代码示例

    #include <Windows.h>
    #include <stdio.h>
    
    DWORD WINAPI TestProc(  LPVOID lpParameter){
    	char* pszText=(char*)lpParameter;
    	while(1){
    		printf("%s\n",pszText);
    		Sleep(1000);
    	}
    	return 0;
    
    }
    
    DWORD WINAPI TestProc2(LPVOID lpParameter){
    	char* pszText=(char*)lpParameter;
    	while(1){
    		printf("%s\n",pszText);
    		Sleep(1000);
    	}
    	return 0;
    }
    int main(){
    	DWORD nId=0;
    	char* pszText="********";
    	HANDLE hThread=CreateThread(NULL,0,TestProc,pszText,0,&nId);//线程创建成功,立刻执行
    	WaitForSingleObject(hThread,INFINITE);//阻塞函数,hThread有信号才会解除阻塞往下执行
    	char* pszText2="--------";
    	HANDLE hThread2=CreateThread(NULL,0,TestProc2,pszText2,0,&nId);//线程创建成功,立刻执行
    	getchar();
    	SuspendThread(hThread);//挂起线程
    	ResumeThread(hThread2);//唤醒线程
    	getchar();
    	return 0;
    }
    

27、线程同步

27.1. 原子锁

27.1.1. 原子锁解决的问题

  • 多个线程对同一个数据进行原子操作,会产生结果丢失。比如执行++运算时。
  • 当线程A执行g_value++时,如果线程切换时间正好是在线程A将值保存到g_value之前,线程B继续执行g_value++,那么当线程A再次被切换回来之后,会将原来线程A保存的值保存到g_value上,线程B进行的加法操作被覆盖。

27.1.2.使用原子锁

InterlockedIncrement
InterlockedDecrement
InterlockedCompareExchange
InterlockedExchange

  • 只能对运算符进行原子锁。
  • 每种函数只能对一种运算符进行原子锁,需要记忆大量的函数。
  • 效率相对于其它锁较高。
  • 原子锁的实现:直接对数据所在的内存操作,并且在任何一个瞬间只能有一个线程访问。

27.1.3.原子锁代码示例

#include <Windows.h>
#include <stdio.h>
long g_value=0;
DWORD WINAPI TestProc1(LPVOID pParam){
	for(int i=0;i<100000;i++){
		//g_value++;
		InterlockedIncrement(&g_value);
	}
	return 0;
}

DWORD WINAPI TestProc2(LPVOID pParam){
	for(int i=0;i<100000;i++){
		//g_value++;
		InterlockedIncrement(&g_value);
	}
	return 0;
}
int main(){
	DWORD nID=0;
	HANDLE hThread[2];
	hThread[0]=CreateThread(NULL,0,TestProc1,NULL,0,&nID);
	hThread[1]=CreateThread(NULL,0,TestProc2,NULL,0,&nID);
	WaitForMultipleObjects(2,hThread,TRUE,INFINITE);
	printf("%d\n",g_value);
	return 0;
}

27.2. 互斥

27.2.1. 互斥解决的问题

  • 多线程下代码或资源的共享使用。

27.2.2. 互斥的使用

  • 1 创建互斥

    HANDLE CreateMutex(  
    		LPSECURITY_ATTRIBUTES lpMutexAttributes,  // SD安全属性(设为NULL,已废弃)
    	 	BOOL bInitialOwner,                       // initial owner初始的拥有者
    	  	LPCTSTR lpName                            // object name命名,也可以置空
      );//创建成功返回互斥句柄
    
    • 可等候句柄
      • 任何一个时间点上只能有一个线程拥有互斥,其它线程等待。直到拥有互斥的线程释放互斥。
      • 互斥的等候遵循谁先等候谁先获取
      • 任何一个线程都不拥有互斥时,互斥句柄有信号。
      • 某一线程拥有互斥,互斥句柄无信号。
    • bInitialOwner
      • TRUE 创建互斥的线程拥有互斥
      • FALSE 创建互斥的线程也不拥有互斥
  • 2 等候互斥

    • WaitForSingleObject
    • WaitForMultipleObjects
    • 互斥的等候遵循谁先等候谁先获取。
  • 3 释放互斥

BOOL ReleaseMutex(  
	HANDLE hMutex   // handle to mutex
);
  • 4 关闭互斥句柄
BOOL CloseHandle( 
	 HANDLE hObject   // handle to object
);

27.2.3. 互斥代码示例

#include <Windows.h>
#include <stdio.h>
HANDLE g_hMutex=0;//接收互斥句柄
DWORD WINAPI TestProc(  LPVOID lpParameter){
	char* pszText=(char*)lpParameter;
	while(1){
		WaitForSingleObject(g_hMutex,INFINITE);//等候互斥有信号
		for(int i=0;i<strlen(pszText);i++){
			printf("%c",pszText[i]);
			Sleep(125);
		}
		printf("\n");
		ReleaseMutex(g_hMutex);//释放互斥
	}
	return 0;

}

DWORD WINAPI TestProc2(LPVOID lpParameter){
	char* pszText=(char*)lpParameter;
	while(1){
		WaitForSingleObject(g_hMutex,INFINITE);//等候互斥有信号
		for(int i=0;i<strlen(pszText);i++){
			printf("%c",pszText[i]);
			Sleep(125);
		}
		printf("\n");
		ReleaseMutex(g_hMutex);//释放互斥
	}
	return 0;
}
int main(){
	g_hMutex=CreateMutex(NULL,FALSE,NULL);//主线程不拥有互斥,我们要子线程与子线程互斥	
	DWORD nId=0;
	char* pszText="********";
	HANDLE hThread=CreateThread(NULL,0,TestProc,pszText,0,&nId);//线程创建成功,立刻执行
	
	char* pszText2="--------";
	HANDLE hThread2=CreateThread(NULL,0,TestProc2,pszText2,0,&nId);//线程创建成功,立刻执行

	
	getchar();
	CloseHandle(g_hMutex);
	return 0;
}

27.2.4. 原子锁与互斥

  • 原子锁能解决的问题互斥都可以解决。
  • 原子锁比互斥的效率高。

27.3. 事件

27.3.1. 事件解决的问题

  • 程序(线程)之间通知的问题。

27.3.2. 事件的使用

  • 1 创建事件

    HANDLE CreateEvent(  
    		  LPSECURITY_ATTRIBUTES lpEventAttributes, // SD安全属性,为NULL,已废弃
    		  BOOL bManualReset,                       // reset type事件重置(复位)方式,TRUE手动,FALSE自动
    		  BOOL bInitialState,                      // initial state事件初始状态,TRUE创建之初有信号
    		  LPCTSTR lpName                           // object name事件命名
      );//创建成功返回事件句柄
    
    • 复位:有信号变成无信号。
    • 触发:无信号变成有信号。
  • 2 等候事件

    • WaitForSingleObject
    • WaitForMultipleObjects
  • 3 触发事件(将事件设置成有信号状态)

BOOL SetEvent(  
		HANDLE hEvent   // handle to event
);
  • 4 复位事件(将事件设置成无信号状态)
BOOL ResetEvent(  
	HANDLE hEvent   // handle to event
);
  • 5 关闭事件 CloseHandle

27.3.3. 事件的死锁

  • 下面这种情况,就会发生死锁。
    Win32从入门到放弃

27.3.4. 事件的代码示例

#include <Windows.h>
#include <stdio.h>
HANDLE g_hEvent=0;
DWORD WINAPI PrintProc(LPVOID pParam){
	while(1){
		WaitForSingleObject(g_hEvent,INFINITE);//等待事件有信号
		ResetEvent(g_hEvent);
		printf("*********\n");
		
	}
	return 0;
}

DWORD WINAPI CtrlProc(LPVOID pParam){
	while(1){
		Sleep(1000);
		SetEvent(g_hEvent);
	}
	return 0;
}

int main(){
	g_hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);//初始事件句柄无信号,需手动复位
	DWORD nID=0;
	HANDLE hThread[2]={0};
	hThread[0]=CreateThread(NULL,0,PrintProc,NULL,0,&nID);
	hThread[1]=CreateThread(NULL,0,CtrlProc,NULL,0,&nID);
	WaitForMultipleObjects(2,hThread,TRUE,INFINITE);
	CloseHandle(g_hEvent);
	return 0;
}

27.4. 信号量

27.4. 1.信号量解决的问题

  • 类似于事件,解决通知的相关问题。但提供一个计数器,可以设置次数。

27.4. 2.信号量的使用

  • 1 创建信号量

    HANDLE CreateSemaphore(  
    			 LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // SD安全属性,NULL,已经废弃
    		 	 LONG lInitialCount,                          // initial count初始化信号量数量
    		  	LONG lMaximumCount,                          // maximum count信号量的最大值
    		  	LPCTSTR lpName                               // object name命名,可以为NULL
      );//创建成功返回信号量句柄
    
    • 信号量lInitialCount不为0,有信号。
    • 信号量lInitialCount为0,无信号 。
  • 2 等候信号量

    • WaitForSingleObject
    • WaitForMultipleObjects
    • 每等候通过一次,信号量的信号减1,直到为0阻塞。
  • 3 给信号量指定计数值

BOOL ReleaseSemaphore(  
		  HANDLE hSemaphore,       // handle to semaphore信号量句柄
		  LONG lReleaseCount,      // count increment amount释放数量
		  LPLONG lpPreviousCount   // previous count释放前原来信号量的数量,可以为NULL
  );
  • 4 关闭句柄 CloseHandle

27.4. 3.信号量代码示例

#include <Windows.h>
#include <stdio.h>
HANDLE g_hSema=0;
DWORD WINAPI TestProc(LPVOID pParam){
	while(1){
		WaitForSingleObject(g_hSema,INFINITE);
		printf("***********\n");
	}
}

int main(){
	g_hSema=CreateSemaphore(NULL,3,10,NULL);
	DWORD nID=0;
	HANDLE hThread=CreateThread(NULL,0,TestProc,NULL,0,&nID);
	getchar();
	ReleaseSemaphore(g_hSema,5,NULL);//不想接收剩余的信号量,可以设为NULL
	WaitForSingleObject(hThread,INFINITE);
	CloseHandle(g_hSema);
	return 0;
}
上一篇:微信网页开发--地理位置定位出现偏差


下一篇:迅为IMX8MM开发板Yocto系统设置开机自启动