文章目录
- 1.应用程序分类
- 2.开发工具和库
- 3.第一个Windows窗口
- 4.字符编码
- 5.注册窗口类
- 6.窗口创建
- 7.消息基础
- 8.常见消息
- 9.Win32窗口额外创建一个控制台窗口
- 10.消息循环原理
- 11.消息队列
- 12.键盘消息
- 13.鼠标消息
- 14.定时器消息
- 15.菜单资源
- 16.图标资源
- 17.光标资源
- 18.字符串资源
- 19.加速键资源
- 20.绘图编程
- 21.位图
- 22.文本绘制
- 23.对话框
- 24.静态库
- 25.动态库
- 26.线程
- 27、线程同步
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窗口显示方式
);
- 句柄能找到进程所在的内存,但不是指针
-
hPrevInstance
16位操作系统使用,进入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
运行vcvars32.bat
后,即使未设置环境变量可以实别cl.exe、link.exe等
该程序路径:E:\Program Files\Microsoft Visual Studio 10.0\VC\bin
,可拷贝到任意地方执行 -
编译程序
生成Hello.obj -
链接程序
生成Hello.exe
2.5.编译rc文件
- 编写资源的文件 .rc资源脚本文件
//Hello.rc
100 ICON chuhe.ico
-
编译源程序
cl.exe
生成Hello.obj -
编译rc文件
rc.exe
生成Hello.res,和Hello.obj一样都是目标文件 -
将资源链接到程序中
link.exe
生成Hello.exe,并且程序图标也成功设置
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
- 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.为什么要更改成多字符集
- 系统调用函数的参数类型
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;
}
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
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还没有运行。
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坐标位置
-
- wParam其它按键的状态,例如Ctrl、Shift等
- 一般情况鼠标按下、抬起成对出现。在鼠标移动过程中,会根据移动速度产生一系列的WM_MOUSEMOVE消息。
13.3.鼠标双击消息
- 附带信息
- wParam其它按键的状态,例如Ctrl、Shift等
- wParam=1,鼠标左键双击
- -多个按钮按下,值会累加。
- lParam:鼠标的位置,窗口客户区坐标系。
-
LOWORD
x坐标位置 -
HIWORD
y坐标位置
-
- wParam其它按键的状态,例如Ctrl、Shift等
- 消息产生顺序
- 以左键双击为例
- WM_LBUTTONDOWN
- WM_LBUTTONUP
- WM_LBUTTONDBLCLK
- WM_LBUTTONUP
- 使用时需要在注册窗口类时添加CS_DBLCLKS风格。
13.4.鼠标滚轮消息
- 附带信息
- wParam:
- LOWORD其他按钮的状态
- HIWORD滚轮的偏移量,通过正负值表示滚动方向。
- 正:向前滚动
- 负:向后滚动
- lParam:鼠标当前的位置,屏幕坐标系。
- LOWORD x坐标
- HIWORD y坐标
- wParam:
- 使用:通过偏移量,获取滚动的方向和距离。
- 鼠标偏移量一般是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,可做出分隔符
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
- wParam:
命令消息处理代码示例
#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设置光标
- 1 在注册窗口时,使用
HCURSOR SetCursor( HCURSOR hCursor // handle to cursor);
17.3.WM_SETCURSOR消息
- 消息产生时间:鼠标移动时
- 参数信息:
- wParam当前使用的光标句柄
- lParam:
- LOWORD当前区域的代码(Hit-Test code)
- HTCLIENT在客户区活动
- HTCAPTION在标题栏区域活动
- …
- HIWORD当前鼠标消息ID
- LOWORD当前区域的代码(Hit-Test code)
- 专职用法:修改光标
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
代码示例:
#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原理
- 在WM_COMMAND中相应消息,消息参数
- wParam:
- HIWORD为1表示加速键,为0表示菜单
- LOWORD为命令ID
- lParam为0
- wParam:
加速键的使用
代码示例
#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 删除画刷
- 1 创建画刷
- 透明画刷
可以使用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;
}
实心画刷效果
纹理画刷效果
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);
- 1:1成像
-
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
字体句柄 -
真正的字体名
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 应用字体到DC
SelectObject
-
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.cppint 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.线程相关操作
-
获取当前线程的ID
GetCurrentThreadId
-
获取当前线程的句柄
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个有信号,就结束等候。
- bWaitAll 等候方式
-
等待线程有信号代码示例
#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. 事件的死锁
- 下面这种情况,就会发生死锁。
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;
}