返回

Windows编程

Windows编程基础

  • Windows系统级开发,简称Win32

应用程序分类

  • Windows这个操作系统平台上,共有三类应用程序
  • 控制台程序Console
    • Dos程序,本身没有窗口,通过Windows DOS窗口执行
  • 窗口程序
    • 拥有自己的窗口,可以与用户交互
  • 库程序
    • 存放代码、数据的程序,执行程序时可以从中取出代码执行和获取数据
    • 静态库程序:扩展名LIB,在编译链接程序时,将代码放入到执行文件中
    • 动态库程序:扩展名DLL,在程序执行时从中获取代码

应用程序对比

  • 入口函数

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

    • 控制台程序、窗口程序 - EXE文件
    • 动态库程序 - DLL文件
    • 静态库程序 - LIB文件

开发工具和库

编译工具

  • 编译器CL.EXE 将源代码编译成目标代码.obj

  • 链接器LINK.EXE 将目标代码、库链接生成最终文件

  • 资源编译器RC.EXE (.rc)将资源编译,最终通过链接器存入最终文件

  • 路径 :C:\Program Files (x86)\Microsoft Visual Studio \VC\bin

库和头文件

  • 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

相关函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
int WINAPI WinMain(
    HINSTANCE hInstance,//当前程序的实例句柄
    HINSTANCE hPrevInstance, //当前程序前一个实例句柄
    LPSTR lpCmdLine,//命令行参数字符串
    int nCmdShow //窗口的显示方式
);	
int MessageBox(
	HWND hWnd,//父窗口句柄
	LPCTSTR lpText, //显示在消息框中的文字
	LPCTSTR lpCaption,  //显示在标题栏中的文字
	UINT uType //消息框中的按钮、图标显示类型
);// 返回点击的按钮ID	
  • 句柄是用来找到内存的东西,但不是指针
  • 当前程序的实例句柄能够找到当前进程所占据的那块内存

程序编译过程

  • 编译环境准备

    • VCVARS32.BAT
  • 编译程序 - CL

    • CL.EXE –c xxx.c
  • 链接程序 - LINK

    • LINK.EXE xxx.obj xxx.lib
  • 执行

  • 编写资源的文件 -.rc资源脚本文件

  • 编译rc文件 - RC.EXE

  • 将资源链接到程序中 - LINK.EXE

第一个windows窗口

窗口创建过程

  1. 定义WinMain函数
  2. 定义窗口处理函数 (自定义,处理消息)
  3. 注册窗口类(向操作系统写入一些数据)
  4. 创建窗口( 内存中创建窗口 )
  5. 显示窗口( 绘制窗口的图像 )
  6. 消息循环(获取/翻译/派发消息)
  7. 消息处理

代码示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <windows.h>

//窗口处理函数( 自定义,处理消息)
LRESULT CALLBACK WndProc( HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam ){
	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;
}

字符编码

  • 宽字节字符
    • wchar_t 每个字符占2个字节
    • char 每个字符占1个字节
    • wchar_t 实际是 unsigned short 类型,定义时,需要增加L,通知编译器按照双字节编译字符串,采用UNICODE编码
    • 需要使用支持wchar_t函数操作宽字节字符串。例如:
1
2
wchar_t * pwszText = L"Hello wchar";
wprintf( L"%s\n", pwszText );
  • TCHAR
1
2
3
4
5
6
7
#ifdef  UNICODE 
typedef wchar_t TCHAR;
   #define __TEXT(quote) L##quote 
#else
   typedef char TCHAR;
   #define __TEXT(quote) quote   
#endif
  • UNICODE字符打印
    • wprintfUNICODE字符打印支持不完善
    • Windows下使用WriteConsoleAPI打印UNICODE字符GetStdHandle

Windows窗口

注册窗口类

窗口类的概念

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

窗口类的分类

  • 系统窗口类
    • 系统已经定义好的窗口类,所有应用程序都可以直接使用
  • 应用程序全局窗口类
    • 由用户自己定义,当前应用程序所有模块都可以使用
  • 应用程序局部窗口类
    • 由用户自己定义,当前应用程序中本模块可以使用
系统窗口类
  • 不需要注册,直接使用窗口类即可。系统已经注册好了
  • 例如:
    • 按钮 - BUTTON
    • 编辑框 - EDIT
全局及局部窗口类
  • 注册窗口类的函数
1
2
3
ATOM RegisterClass( 
  CONST WNDCLASS *lpWndClass //窗口类的数据
 ); //注册成功后,返回一个数字标识。
  • 注册窗口类的结构体
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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
  • 例如:
1
2
WNDCLASS wce = {0};
wce.style = .|CS_GLOBALCLASS;
  • 应用程序局部窗口类:在注册窗口类时,不添加CS_GLOBALCLASS风格(推荐使用局部窗口类)
    • CS_HREDRAW - 当窗口水平变化时,窗口重新绘制
    • CS_VREDRAW - 当窗口垂直变化时,窗口重新绘制
    • CS_DBLCLKS - 允许窗口接收鼠标双击
    • CS_NOCLOSE - 窗口没有关闭按钮

窗口创建

创建窗口的函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
CreateWindow/CreateWindowEx
	HWND CreateWindowEx(
		DWORD dwExStyle, //窗口的扩展风格
		LPCTSTR lpClassName,  //已经注册的窗口类名称
		LPCTSTR lpWindowName, //窗口标题栏的名字
		DWORD dwStyle, //窗口的基本风格
		int x, //窗口左上角水平坐标位置
		int y, //窗口左上角垂直坐标位置
		int nWidth, //窗口的宽度
		int nHeight,//窗口的高度
		HWND hWndParent,//窗口的父窗口句柄
		HMENU hMenu,//窗口菜单句柄
		HINSTANCE hInstance, //应用程序实例句柄
		LPVOID lpParam //窗口创建时附加参数   
); 创建成功返回窗口句柄

窗口创建原理

  1. 系统(CreateWindow内部)根据传入的窗口类名称,在应用程序局部窗口类中查找,如果找到执行2,如果未找到执行3

  2. 比较局部窗口类与创建窗口时传入的HINSTANCE变量。如果发现相等,创建和注册的窗口类在同一模块,创建窗口返回。如果不相等,继续执行3

  3. 在应用程序全局窗口类,如果找到,执行4,如果未找到执行5

  4. 使用找到的窗口类的信息,创建窗口返回

  5. 在系统窗口类中查找,如果找到创建窗口返回,否则创建窗口失败

子窗口创建

  • 创建时要设置父窗口句柄

  • 创建风格要增加WS_CHILD|WS_VISIBLE

消息基础

消息的概念和作用

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

窗口处理函数

  • 每个窗口都必须具有窗口处理函数
1
2
3
4
5
6
LRESULT CALLBACK WindowProc(
	HWND hwnd, //窗口句柄
	UINT uMsg, //消息ID
	WPARAM wParam, //消息参数
	LPARAM lParam  //消息参数
);
  • 当系统通知窗口时,会调用窗口处理函数,同时将消息ID和消息参数传递给窗口处理函数
  • 在窗口处理函数中,不想处理的消息,使用缺省窗口处理函数,例如:DefWindowProc

浅谈消息相关函数

  • GetMessage - 获取消息(本进程)
1
2
3
4
5
6
BOOL GetMessage(
	LPMSG lpMsg, //存放获取到的消息BUFF
	HWND hWnd, //窗口句柄
	UINT wMsgFilterMin,//获取消息的最小ID  
	UINT wMsgFilterMax   //获取消息的最大ID 
);
  • lpMsg - 当获取到消息后,将消息的参数存放到MSG结构中

  • hWnd - 获取到hWnd所指定窗口的消息

  • wMsgFilterMinwMsgFilterMax -只能获取到由它们指定的消息范围内的消息,如果都为0,表示没有范围

  • TranslateMessage - 翻译消息。将按键消息,翻译成字符消息

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

  • DispatchMessage - 派发消息

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

常见消息

WM_DESTORY

  • 产生时间:窗口被销毁时的消息

  • 附带信息:

    • wParam:为0
    • lParam:为0
  • 一般用法:常用于在窗口被销毁之前,做相应的善后处理,例如资源、内存等

WM_SYSCOMMAND

  • 产生时间:当点击窗口的最大化、最小化、关闭等

  • 附带信息:

    • wParam : 具体点击的位置,例如关闭是SC_CLOSE
    • lParam : 鼠标光标的位置
1
2
LOWORD(lParam);//水平位置
HIWORD(lParam);//垂直位置
  • 一般用法:常用在窗口关闭时,提示用户处理

WM_CREATE

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

  • 附带信息:

    • wParam : 为0
    • lParam : 为CREATESTRUCT类型的指针
    • 通过这个指针可以获取CreatWindowEx中的全部12个参数的信息
  • 一般用法:常用于初始化窗口的参数、资源等等,包括创建子窗口等

WM_SIZE

  • 产生时间:在窗口的大小发生变化后

  • 附带信息:

    • wParam : 窗口大小变化的原因
    • lParam : 窗口变化后的大小
1
2
LOWORD(lParam) //变化后的宽度
HIWORD(lParam) //变化后的高度		
  • 一般用法:常用于窗口大小变化后,调整窗口内各个部分的布局

WM_QUIT

  • 产生时间:程序员发送

  • 附带信息:

    • wParam : PostQuitMessage 函数传递的参数
    • lParam : 0
    • 一般用法:用于结束消息循环,当GetMessage收到这个消息后,会返回FALSE,结束while处理,退出消息循环

消息循环的原理

消息循环的阻塞

  • GetMessage - 从系统获取消息,将消息从系统中移除,阻塞函数。当系统无消息时,会等候下一条消息
  • PeekMessage - 以查看的方式从系统获取消息,可以不将消息从系统移除,非阻塞函数。当系统无消息时,返回FALSE,继续执行后续代码
1
2
3
4
5
6
7
8
BOOL PeekMessage(
	LPMSG lpMsg,         // message information
	HWND hWnd,           // handle to window
	UINT wMsgFilterMin,  // first message
	UINT wMsgFilterMax,  // last message
	UINT wRemoveMsg //移除标识  
	PM_REMOVE / PM_NOREMOVE
);

发送消息

  • SendMessage - 发送消息,会等候消息处理的结果
  • PostMessage - 投递消息,消息发出后立刻返回,不等候消息执行结果
1
2
3
4
5
6
BOOL SendMessage/PostMessage(
	HWND hWnd,//消息发送的目的窗口
	UINT Msg, //消息ID
	WPARAM wParam, //消息参数
	LPARAM lParam  //消息参数          
);

消息分类

  • 系统消息 - ID范围 0 - 0x03FF
    • 由系统定义好的消息,可以在程序中直接使用。
  • 用户自定义消息 - ID范围 0x0400 - 0x7FFF(31743)
    • 由用户自己定义,满足用户自己的需求。由用户自己发出消息,并响应处理。
    • 自定义消息宏:WM_USER

Windows消息

消息队列

消息队列概念

  • 消息队列是用于存放消息的队列

  • 消息在队列中先入先出

  • 所有窗口程序都具有消息队列

  • 程序(GetMessage)可以从队列中获取消息

消息队列分类

  • 系统消息队列-由系统维护的消息队列。存放系统产生的消息,例如鼠标、键盘等
  • 程序消息队列-属于每一个应用程序(线程)的消息队列。由应用程序(线程)维护

消息和队列关系

  • 消息和消息队列的关系
    • 当鼠标、键盘产生消息时,会将消息存放到系统消息队列
    • 系统会根据存放的消息,找到对应的程序消息队列
    • 将消息投递到对应的程序消息队列
  • 根据消息和消息队列之间使用关系,将消息分成两类:
    • 队列消息 - 消息的发送和获取,都是通过消息队列完成
    • 非队列消息 - 消息的发送和获取,是直接调用消息的窗口处理函数完成
  • 队列消息-消息发送后,首先放入队列,然后通过消息循环,从队列当中获取
    • GetMessage - 从消息队列中获取消息
    • PostMessage - 将消息投递到消息队列
    • 常见队列消息:WM_PAINT、键盘、鼠标、定时器
  • 非队列消息-消息发送时,首先查找消息接收窗口的窗口处理函数,直接调用窗口处理函数,完成消息
    • SendMessage - 直接将消息发送给窗口的处理函数,并等候处理结果
    • 常见消息:WM_CREATEWM_SIZE

深谈GetMessage原理

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

WM_PAINT消息

  • 产生时间:当窗口需要绘制的时候
  • 附带信息:
    • wParam : 0
    • lParam : 0
  • 专职用法:用于绘图
  • 窗口无效区域 :需要重新绘制的区域
1
2
3
4
5
BOOL InvalidateRect(
  	    HWND hWnd,  //窗口句柄
  	    CONST RECT* lpRect,  //区域的矩形坐标
  	    BOOL bErase  //重绘前是否先擦除
);

消息处理步骤

  • 开始绘图
1
2
3
4
HDC BeginPaint(
	HWND hwnd, //绘图窗口
	LPPAINTSTRUCT lpPaint //绘图参数的BUFF
); //返回绘图设备句柄HDC
  • 正式绘图

  • 结束绘图

1
2
3
4
BOOL EndPaint(
	HWND hWnd, //绘图窗口
	CONST PAINTSTRUCT *lpPaint  //绘图参数的指针BeginPaint返回
);

键盘消息

键盘消息分类

  • WM_KEYDOWN - 按键被按下时产生
  • WM_KEYUP - 按键被放开时产生
  • WM_SYSKEYDOWN - 系统键按下时产生 比如ALTF10
  • WM_SYSKEYUP - 系统键放开时产生

附带信息:

  • WPARAM - 按键的Virtual Key
  • LPARAM - 按键的参数,例如按下次数

字符消息(WM_CHAR)

  • TranslateMessage在转换WM_KEYDOWN消息时,对于可见字符可以产生WM_CHAR,不可见字符无此消息

附带信息:

  • WPARAM - 输入的字符的ASCII字符编码值
  • LPARAM - 按键的相关参数

  • WM_KEYDOWN不能区分大小写;WM_CHAR能够区分大小写

鼠标消息

鼠标消息分类

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

鼠标基本消息

  • 附带信息:
    • wPARAM : 其他按键的状态,例如Ctrl/Shift
    • lPARAM : 鼠标的位置,窗口客户区坐标系
      • LOWORDX坐标位置
      • HIWORDY坐标位置
  • 一般情况鼠标按下/抬起成对出现。在鼠标移动过程中,会根据移动速度产生一系列的WM_MOUSEMOVE消息

鼠标双击消息

  • 附带信息:

    • wPARAM - 其他按键的状态,例如Ctrl/Shift

    • lPARAM - 鼠标的位置,窗口客户区坐标系

      • LOWORD(lParam)X坐标位置
      • HIWORD(lParam)Y坐标位置
  • 消息产生顺序

    • 以左键双击为例:

      • WM_LBUTTONDOWN
      • WM_LBUTTONUP
      • WM_LBUTTONDBLCLK
      • WM_LBUTTONUP
    • 使用时需要在注册窗口类的时候添加CS_DBLCLKS风格

鼠标滚轮消息

  • 附带信息:
    • wPARAM
      • LOWORD - 其他按键的状态
      • HIWORD - 滚轮的偏移量,通过正负值表示表示滚动方向
      • 正:向前滚动 负:向后滚动
    • lPARAM:鼠标当前的位置,屏幕坐标系
      • LOWORD - X坐标
      • HIWORD - Y坐标
  • 使用:
    • 通过偏移量,获取滚动的方向和距离

定时器消息

定时器消息介绍

  • 产生时间:
    • 在程序中创建定时器,当到达时间间隔时,定时器(GetMessage)会向程序发送一个WM_TIMER消息
    • 定时器的精度是毫秒,但是准确度很低。例如设置时间间隔为1000ms,但是会在非1000毫秒到达消息
  • 附带信息:
    • wPARAM : 定时器ID
    • lPARAM : 定时器处理函数的指针

创建销毁定时器

  • 创建定时器
1
2
3
4
5
6
UINT_PTR SetTimer(
	HWND hWnd,//定时器窗口句柄
	UINT_PTR nIDEvent, //定时器ID
	UINT uElapse,//时间间隔
	TIMERPROC lpTimerFunc //定时器处理函数指针( 一般不使用,NULL)
);//创建成功,返回非0
  • 关闭定时器
1
2
3
4
BOOL KillTimer(
	HWND hWnd,//定时器窗口句柄
	UINT_PTR uIDEvent //定时器ID
);

菜单资源

菜单分类

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

HMENU:菜单句柄,类型表示菜单,ID表示菜单项

资源相关

菜单资源使用

  • 添加菜单资源

  • 加载菜单资源的三种方法

    • 注册窗口类时设置菜单
    • 创建窗口传参设置菜单
    • 在主窗口WM_CREATE消息中利用 SetMenu函数设置菜单
  • 加载菜单资源

1
2
3
4
HMENU LoadMenu(
	HINSTANCE hInstance,  // handle to module
	LPCTSTR lpMenuName    // menu name or resource identifier
);		

命令消息处理

  • 附带信息:
    • wPARAM
      • HIWORD - 对于菜单为0
      • LOWORD - 菜单项的ID
    • lPARAM - 对于菜单为0

上下文菜单

  • 显示上下文菜单
1
2
3
4
5
6
7
8
9
BOOL TrackPopupMenu(
	HMENU hMenu, //菜单句柄
	UINT uFlags, //显示方式
	int x,//水平位置,屏幕坐标系
	int y,//垂直位置,屏幕坐标系
	int nReserved, //保留,必须0
	HWND hWnd, //处理菜单消息的窗口句柄
	CONST RECT *prcRect //NULL,忽略
); //TrackPopupMenu是阻塞函数
  • WM_RBUTTONUP
    • 鼠标右键弹起消息为窗口坐标系坐标,要使用需将其转换成屏幕坐标系坐标 ClientToScreen
  • WM_CONTEXTMENU
    • wParam:右键点击的窗口句柄
    • lPARAM
      • LOWORDX坐标,屏幕坐标系
      • HIWORDY坐标,屏幕坐标系
    • WM_CONTEXTMENU消息是WM_RBUTTONUP消息之后产生

Windows资源

图标资源、光标资源、字符串资源

图标资源

  • 添加资源
    • 注意图标的大小,一个图标文件中,可以有多个不同大小的图标
  • 加载
1
2
3
4
HICON LoadIcon(
	HINSTANCE hInstance, // handle to application instance
	LPCTSTR lpIconName   // name string or resource identifier
); //成功返回HICON句柄
  • 设置
    • 注册窗口类

光标资源

  • 添加光标的资源
    • 光标的大小默认是32X32像素,每个光标有HotSpot,是当前鼠标的热点
  • 加载资源
1
2
3
4
HCURSOR LoadCursor(
	HINSTANCE hInstance,  // handle to application instance
	LPCTSTR lpCursorName  // name or resource identifier
); //hInstance - 可以为NULL,获取系统默认的Cursor
  • 设置资源
    • 在注册窗口时,设置光标使用SetCursor函数设置光标
1
2
3
HCURSOR SetCursor(
  	HCURSOR hCursor   // handle to cursor
);
  • WM_SETCURSOR 消息参数
    • wPARAM - 当前使用的光标句柄
    • lPARAM
      • LOWORD 当前区域的代码(Hit-Test code )
      • HTCLIENT / HTCAPTION…
      • HIWORD - 当前鼠标消息ID

字符串资源

  • 添加字符串资源
    • 添加字符串表,在表中增加字符串
  • 字符串资源的使用
1
2
3
4
5
6
int LoadString(
	HINSTANCE hInstance,  // handle to resource module
	UINT uID, //字符串ID
	LPTSTR lpBuffer, //存放字符串BUFF
	int nBufferMax // 字符串BUFF长度
); //成功返回字符串长度,失败0	

加速键资源

  • 添加
    • 资源添加加速键表,增加命令ID对应的加速键。
  • 使用
    • 加载加速键表
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
HACCEL LoadAccelerators(
	HINSTANCE hInstance,  // handle to module
	LPCTSTR lpTableName   // accelerator table name
); //返回加速键表句柄
//翻译加速键
int TranslateAccelerator(
	HWND hWnd,//处理消息的窗口句柄
	HACCEL hAccTable,  //加速键句柄
	LPMSG lpMsg //消息
); //如果是加速键,返回非零。
  • WM_COMMAND中相应消息,消息参数
  • wPARAM
    • HIWORD1表示加速键,为0表示菜单
    • LOWORD 为命令ID。
  • lParam:为0

绘图编程

绘图基础

  • 绘图设备 DCDevice Context),绘图上下文/绘图描述表

  • HDC - DC句柄,表示绘图设备

  • GDI - Windows graphics device interfaceWin32提供的绘图API

  • 颜色

    • 计算机使用红、绿、蓝,
      • R - 0~255
      • G - 0~255
      • B - 0~255
  • 每一个点颜色是3个字节24位保存 0-2^24-1

    • 16位:5,5,6
    • 32位:8,8,8, 8绘图或透明度
  • 颜色的使用

    • COLORREF - 实际DWORD
    • 例如:COLORREF nColor = 0;
  • 赋值使用RGB宏

    • 例如 : nColor = RGB( 0, 0, 255);
  • 获取RGB值,

    • GetRValue/GetGValue/GetBValue
    • 例如 :BYTE nRed = GetRValue( nColor );

基本图形绘制

  • SetPixel 设置指定点的颜色
1
2
3
4
5
6
COLORREF SetPixel(
		HDC hdc,//DC句柄
		int X,//X坐标
		int Y,//Y坐标
		COLORREF crColor //设置的颜色
); //返回点原来的颜色
  • 线的使用(直线、弧线)
    • MoveToEx – 指名窗口当前点
    • LineTo - 从窗口当前点到指定点绘制一条直线
    • 当前点:上一次绘图时的最后一点,初始为(0,0)
  • 封闭图形:能够用画刷填充的图形
    • Rectangle / Ellipse

GDI绘图对象

画笔

  • 画笔的作用
    • 线的颜色、线型、线粗
    • HPEN - 画笔句柄
  • 画笔的使用
    • 创建画笔
1
2
3
4
5
HPEN CreatePen(
	int fnPenStyle, //画笔的样式
	int nWidth, //画笔的粗细
	COLORREF crColor //画笔的颜色
);//创建成功返回句柄
  • PS_SOILD - 实心线,可以支持多个像素宽其他线型只能是一个像素宽
  • 将画笔应用到DC中
1
2
3
4
HGDIOBJ SelectObject(
		HDC hdc,//绘图设备句柄
		HGDIOBJ hgdiobj //GDI绘图对象句柄,画笔句柄
);//返回原来的GDI绘图对象句柄	
  • 注意保存原来DC当中画笔

  • 绘图

  • 取出DC中的画笔

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

1
2
3
BOOL DeleteObject(
	HGDIOBJ hObject   //GDI绘图对象句柄,画笔句柄
);
  • 只能删除不被DC使用的画笔,所以在释放前,必须将画笔从DC中取出

画刷

  • 画刷相关
    • 画刷 - 封闭图形的填充的颜色、图案
    • HBRUSH - 画刷句柄
  • 画刷的使用
    • 创建画刷
      • CreateSolidBrush - 创建实心画刷
      • CreateHatchBrush - 创建纹理画刷
    • 将画刷应用到DC中
      • SelectObject
    • 绘图
    • 将画刷从DC中取出
      • SelectObject
    • 删除画刷
      • DeleteObject

位图绘制

  • 位图相关
    • 光栅图形 - 记录图像中每一点的颜色等信息
    • 矢量图形 - 记录图像算法、绘图指令等
    • HBITMAP - 位图句柄
  • 位图的使用
    • 在资源中添加位图资源
    • 从资源中加载位图LoadBitmap
    • 创建一个与当前DC相匹配的DC(内存DC
1
2
3
HDC CreateCompatibleDC(
	HDC hdc   //当前DC句柄,可以为NULL(使用屏幕DC)
); //返回创建好的DC句柄
  • 将位图放入匹配的DCSelectObject
  • 成像(1:1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
BOOL BitBlt(
	HDC hdcDest, //目的DC
    int nXDest,  // 目的左上X坐标
	int nYDest,  // 目的左上Y坐标
	int nWidth,  // 目的宽度
	int nHeight, // 目的高度
	HDC hdcSrc,  //源DC
	int nXSrc,   // 源左上X坐标
	int nYSrc,   // 源左上Y坐标
	DWORD dwRop  //成像方法  SRCCOPY
);
  • 缩放成像
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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,    // 源DC宽
	int nHeightSrc,   // 源DC高
	DWORD dwRop       // raster operation code
);
  • 取出位图
    • SelectObject
  • 释放位图
    • DeleteObject
  • 释放匹配的DC
    • DeleteDC

文本绘制

绘制字符串

  • 文字的绘制
    • TextOut - 将文字绘制在指定坐标位置。
1
2
3
4
5
6
7
int DrawText(
	HDC hDC,          //DC句柄
	LPCTSTR lpString, //字符串
	int nCount,       //字符数量
	LPRECT lpRect,    //绘制文字的矩形框
	UINT uFormat      //绘制的方式
);
  • 文字颜色和背景
    • 文字颜色 :SetTextColor
    • 文字背景色:SetBkColor
    • 文字背景模式:SetBkMode (OPAQUE / TRANSPARENT)

字体

  • 字体相关

    • Windows常用的字体为TrueType格式的字体文件
      • 字体名 - 标识字体类型
      • HFONT - 字体句柄
  • 字体的使用

    • 创建字体
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
HFONT CreateFont(
	int nHeight, //字体高度
	int nWidth,  //字体宽度
	int nEscapement, //字符串倾斜角度
	int nOrientation,//字符旋转角度
	int fnWeight, //字体的粗细
	DWORD fdwItalic, //斜体
	DWORD fdwUnderline, //字符下划线
	DWORD fdwStrikeOut, //删除线
	DWORD fdwCharSet, //字符集  
	DWORD fdwOutputPrecision,//输出精度
	DWORD fdwClipPrecision,//剪切精度
	DWORD fdwQuality,//输出质量
	DWORD fdwPitchAndFamily,//匹配字体
	LPCTSTR lpszFace //字体名称
);	
  • 应用字体到DC
    • SelectObject
  • 绘制文字
    • DrawText/TextOut
  • 取出字体
    • SelectObject
  • 删除字体
    • DeleteObject

Windows内核开发

对话框

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

对话框原理

  • 对话框的分类
    • 模式对话框 - 当对话框显示时,会禁止其他窗口和用户交互操作
    • 无模式对话框 - 在对话框显示后,其他窗口仍然可以和用户交互操作
  • 对话框基本使用
    1. 对话框窗口处理函数
    2. 注册窗口类(不使用)
    3. 创建对话框
    4. 对话框的关闭
  • 对话框窗口处理函数(并非真正的对话框窗口处理函数)
1
2
3
4
5
6
INT CALLBACK DialogProc(
	HWND hwndDlg,  //窗口句柄
	UINT uMsg,     //消息ID
	WPARAM wParam, //消息参数
	LPARAM lParam  //消息参数
);
  • 返回TRUE - 缺省处理函数不需要处理
  • 返回FALSE- 交给缺省处理函数处理
  • 不需要调用缺省对话框窗口处理函数

模式对话框

  • 创建对话框
1
2
3
4
5
6
INT DialogBox(
	HINSTANCE hInstance,//应用程序实例句柄
	LPCTSTR lpTemplate, //对话框资源ID
	HWND hWndParent, //对话框父窗口
	DLGPROC lpDialogFunc //自定义函数
);		
  • DialogBox是一个阻塞函数,只有当对话框关闭后,才会返回,继续执行后续代码
  • 返回值是通过EndDialog设置
  • 对话框的关闭
1
2
3
4
BOOL EndDialog(
	HWND hDlg,//关闭的对话框窗口句柄
	INT_PTR nResult //关闭的返回值
);
  • 关闭模式对话框,只能使用EndDialog,不能使用DestroyWindow等函数
  • nResultDialogBox函数退出时的返回值
  • 对话框的消息
    • WM_INITDIALOG - 对话框创建之后显示之前,通知对话框窗口处理函数,可以完成自己的初始化相关的操作

无模式对话框

  • 创建对话框
1
2
3
4
5
6
HWND CreateDialog(
	HINSTANCE Create Dialog,  //应用程序实例句柄
	LPCTSTR lpTemplate,   //模板资源ID
	HWND hWndParent,      //父窗口
	DLGPROC lpDialogFunc  //自定义函数
);
  • 非阻塞函数,创建成功返回窗口句柄,需要使用ShowWindow函数显示对话框
  • 对话框的关闭
    • 关闭时使用DestroyWindow销毁窗口,不能使用EndDialog关闭对话框
  • 对话框的消息
    • WM_INITDIALOG - 对话框创建之后显示之前,通知对话框窗口处理函数,可以完成自己的初始化相关的操作

静态库

静态库特点

  • 运行不存在

  • 静态库源码被链接到调用程序中

  • 目标程序的归档

C语言静态库

  • C静态库的创建
    • 创建一个静态库项目
    • 添加库程序,源文件使用C文件
  • C静态库的使用
    • 库路径设置:可以使用pragma 关键字设置
    • #pragma comment( lib, “../lib/clib.lib”)

C++语言静态库

  • C++静态库的创建
    • 创建一个静态库项目。
    • 添加库程序,源文件使用CPP文件。
  • C++静态库的使用
    • 库路径设置:可以使用pragma关键字设置
    • #pragma comment( lib, “../lib/cpplib.lib”)

动态库

动态库特点

  • 动态库特点

    • 运行时独立存在
    • 源码不会链接到执行程序
    • 使用时加载(使用动态库必须使动态库执行)
  • 与静态库的比较:

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

动态库创建

  • 创建动态库项目
  • 添加库程序
  • 库程序导出 - 提供给使用者库中的函数等信息
    • 声明导出 : 使用 _declspec(dllexport) 导出函数
      • 注意:动态库编译链接后,也会有LIB文件,是作为动态库函数映射使用,与静态库不完全相同
    • 模块定义文件.def
      • 例如:
1
2
3
LIBRARY DLLFunc //库
EXPORTS         //库导出表
DLL_Mul	@1	//导出的函数

动态库的使用

隐式链接
  • 隐式链接(操作系统负责使动态库执行)
    • 头文件和函数原型
      • 可以在函数原型的声明前,增加_declspec(dllimport)
    • 导入动态库的LIB文件
    • 在程序中使用函数
    • 隐式链接的情况,dll文件可以存放的路径:
      1. 与执行文件中同一个目录下(推荐)
      2. 当前工作目录
      3. Windows目录
      4. Windows/System32目录
      5. Windows/System
      6. 环境变量PATH指定目录
显式链接
  • 显式链接(程序员自己负责使动态库执行)
  • 定义函数指针类型typedef
  • 加载动态库
1
2
3
HMODULE LoadLibrary(
	LPCTSTR lpFileName  //动态库文件名或全路径
); //返回DLL的实例句柄(HINSTANCE)
  • 获取函数地址
1
2
3
4
FARPROC GetProcAddress(
	HMODULE hModule,    //DLL句柄
	LPCSTR lpProcName   //函数名称
); //成功返回函数地址
  • 使用函数
  • 卸载动态库
1
2
3
BOOL FreeLibrary(
	HMODULE Free Library   //DLL的实例句柄
);

动态库中封装类

  • 在类名称前增加_declspec(dllexport)定义,例如:
1
2
3
class _declspec(dllexport) CMath {
	...
};
  • 通常使用预编译开关切换类的导入导出定义,例如:
1
2
3
4
5
6
7
8
#ifdef DLLCLASS_EXPORTS
	#define EXT_CLASS _declspec(dllexport)//DLL
	#else
	#define EXT_CLASS _declspec(dllimport)//使用者
	#endif
	class EXT_CLASS CMath{
	...
	};
  • 使用dll中封装的类
    1. 导入DLLLIb
    2. 类的定义
    3. 使用类

Windows线程开发

线程

线程基础

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

创建线程

  • 创建线程
1
2
3
4
5
6
7
8
HANDLE CreateThread(
	LPSECURITY_ATTRIBUTES lpThreadAttributes,//安全属性
	SIZE_T dwStackSize,                       //线程栈的大小
	LPTHREAD_START_ROUTINE lpStartAddress,    //线程处理函数的函数地址
	LPVOID lpParameter,                       //传递给线程处理函数的参数
	DWORD dwCreationFlags,					//线程的创建方式,
	LPDWORD lpThreadId                       //创建成功,返回线程的ID
); //创建成功,返回线程句柄
  • 定义线程处理函数
1
2
3
DWORD WINAPI ThreadProc(
	LPVOID lpParameter //创建线程时,传递给线程的参数
);

线程挂起/销毁

  • 挂起
1
2
3
DWORD SuspendThread(
	HANDLE hThread   // handle to thread
);
  • 唤醒
1
2
3
DWORD ResumeThread(
	HANDLE hThread   // handle to thread
);
  • 结束指定线程
1
2
3
4
BOOL TerminateThread(
	HANDLE hThread,    // handle to thread
	DWORD dwExitCode   // exit code
);
  • 结束函数所在的线程
1
2
3
VOID ExitThread(
	DWORD dwExitCode   // exit code for this thread
);

线程相关操作

  • 获取当前线程的ID
    • GetCurrentThreadId
  • 获取当前线程的句柄
    • GetCurrentThread
  • 等候单个句柄有信号
1
2
3
4
VOID WaitForSingleObject(
	HANDLE handle,  //句柄BUFF的地址
	DWORD dwMilliseconds      // 等候时间 INFINITE
);
  • 同时等候多个句柄有信号
1
2
3
4
5
6
DWORD WaitForMultipleObjects(
	DWORD nCount, //句柄数量
	CONST HANDLE *lpHandles,  //句柄BUFF的地址
	BOOL bWaitAll,//等候方式
	DWORD dwMilliseconds      // 等候时间 INFINITE
);
  • bWaitAll - 等候方式
  • TRUE - 表示所有句柄都有信号,才结束等候
  • FASLE- 表示句柄中只要有1个有信号,就结束等候

线程同步

原子锁

  • 相关问题
    • 多个线程对同一个数据进行原子操作,会产生结果丢失。比如执行++运算时
  • 错误代码分析:
    • 当线程A执行g_value++时,如果线程切换时间正好是在线程A将值保存到g_value之前,线程B继续执行g_value++,那么当线程A再次被切换回来之后,会将原来线程A保存的值保存到g_value上,线程B进行的加法操作被覆盖
  • 使用原子锁函数
1
2
3
4
5
InterlockedIncrement
	InterlockedDecrement
	InterlockedCompareExchange
	InterlockedExchange
	...
  • 原子锁的实现:直接对数据所在的内存操作,并且在任何一个瞬间只能有一个线程访问

互斥

  • 相关的问题
    • 多线程下代码或资源的共享使用。
  • 互斥的使用
    • 创建互斥
1
2
3
4
5
HANDLE CreateMutex(
		 LPSECURITY_ATTRIBUTES lpMutexAttributes, //安全属性
		BOOL bInitialOwner,//初始的拥有者 TRUE/FALSE
		 LPCTSTR lpName    //命名
	); //创建成功返回互斥句柄
  • 等候互斥
    • WaitFor.... 互斥的等候遵循谁先等候谁先获取
  • 释放互斥
1
2
3
BOOL ReleaseMutex(
	HANDLE hMutex   // handle to mutex
);
  • 关闭互斥句柄
    • CloseHandle

事件

  • 相关问题
    • 程序之间的通知的问题。
  • 事件的使用
    • 创建事件
1
2
3
4
5
6
7
HANDLE CreateEvent(
	LPSECURITY_ATTRIBUTES lpEventAttributes, //安全属性
	BOOL bManualReset,                       
	//事件重置(复位)方式,TRUE手动,FALSE自动
	BOOL bInitialState,       //事件初始状态,TRUE有信号
	LPCTSTR lpName  //事件命名
); //创建成功返回 事件句柄
  • 等候事件
    • WaitForSingleObject / WaitForMultipleObjects
  • 触发事件(将事件设置成有信号状态)
1
2
3
BOOL SetEvent(
	HANDLE hEvent   // handle to event
);
  • 复位事件(将事件设置成无信号状态)
1
2
3
BOOL ResetEvent(
	HANDLE hEvent   // handle to event
);
  • 关闭事件 CloseHandle
  • 小心事件的死锁

信号量

  • 相关的问题
    • 类似于事件,解决通知的相关问题。但提供一个计数器,可以设置次数。
  • 信号量的使用
    • 创建信号量
1
2
3
4
5
6
HANDLE CreateSemaphore(
	LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, //安全属性
	LONG lInitialCount,        //初始化信号量数量 
	LONG lMaximumCount, //信号量的最大值 
	LPCTSTR lpName           //命名
); //创建成功返回信号量句柄
  • 等候信号量
    • WaitFor... 每等候通过一次,信号量的信号减1,直到为0阻塞
  • 给信号量指定计数值
1
2
3
4
5
6
BOOL ReleaseSemaphore(
	HANDLE hSemaphore, //信号量句柄
	LONG lReleaseCount, //释放数量
	LPLONG lpPreviousCount   
	//释放前原来信号量的数量,可以为NULL
);
  • 关闭句柄 CloseHandle
最后更新于 Oct 21, 2022 20:46 UTC
Built with Hugo
Theme Stack designed by Jimmy