Win32 高级编程

Win32 高级编程


应用程序分类

应用程序分类

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

应用程序对比

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

开发工具和库

编译工具

  • 编译器CL.EXE:将源代码编译成目标代码 .obj
  • 链接器 LINK.EXE:将目标代码、库链接生成最终文件
  • 资源编译器 RC.EXE:(.rc)将资源编译,最终通过链接器存入最终文件

库和头文件

  • 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字符集支持

相关函数

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
  • 执行
             CL.EXE
.c/.cpp ---------------->|
                         |       LINK.EXE
					     |----------------------> .exe
          RC.EXE         |
.rc -------------------->|

第一个Windows窗口

窗口创建过程

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

注册窗口类

窗口类的概念

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

窗口类的分类

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

系统窗口类

  • 不需要注册,直接使用窗口类即可,系统已经注册好了
  • 例如:
    • 按钮 —— BUTTON
    • 编辑框 —— EDIT

全局及局部窗口类

  • 注册窗口类的函数
ATOM RegisterClass(
	CONST WNDCLASS *lpWndClass // 窗口类的数据
); // 注册成功后,返回一个数字标识
  • 注册窗口类的结构体
typeof struct _WNDCLASS {
	UNIT 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
    • 例如:
    WNDCLASS wce = {0};
    wce.style = ...|CS_GLOBALCLASS;
    
    • 应用程序窗口类:在注册窗口类时,不添加CS_GLABALCALSS风格
    • CS_HREDRAW —— 当窗口水平变化时,窗口重新绘制
    • CS_VREDRAW —— 当窗口垂直变化时,窗口重新绘制
    • CS_DBLCLKS —— 允许窗口接收鼠标双击
    • CS_NOCLOSE —— 窗口没有关闭按钮

窗口创建

消息的概念和作用

  • CreateWindow/CreateWindowEx
HWND CreateWindowEx(
	DWORD dwEsStyle, // 窗口的扩展风格
	LPCTSTR lpClassName, // 已经注册的窗口类名称
	LPCTSTR lpWindowName, // 窗口标题栏名称
	DWORD dwStyle, // 窗口的基本风格
	int x, // 窗口左上角水平坐标位置
	int y, // 窗口左上角垂直坐标位置
	int nWidth, // 窗口的宽度
	int nHeight, // 窗口的高度
	HWND hWndParant, // 窗口的父窗口句柄
	HMENU hMenu, // 窗口菜单句柄
	HINSTANCE hInstance, // 应用程序实例句柄
	LPVOID lpParam // 窗口创建时附加参数
); // 创建成狗返回窗口句柄
  • 系统根据传入的窗口类名称,在应用程序局部窗口类中查找,如果找到执行2,如果未找到执行3
  • 比较局部窗口类与创建时传入的HINSTANCE变量,如果发现相等,创建和注册的窗口类在同一模块,创建窗口返回。如果不相等,继续执行3.
  • 在应用程序全局窗口类,如果找到,执行4,如果未找到执行5
  • 使用找到的窗口类的信息,创建窗口返回
  • 在系统窗口类中查找,如果找到创建窗口返回,否则创建窗口失败

创建子窗口

  • 创建时要设置父窗口句柄
  • 创建风格要增加WS_CHILD | WS_VISIBLE

消息基础

消息的概念和作用

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

窗口处理函数

  • 每个窗口都必须具有窗口处理函数
LPESULT CALLBACK WindowProc(
	HWND hWnd, // 窗口的句柄
	UNIT uMsg, // 消息ID
	WPARAN wParam, // 消息参数
	LPARAM lParam // 消息参数
);
  • 当系统通知窗口时,会调用窗口处理函数,同时将消息ID和消息参数传递给窗口处理函数。在窗口处理函数中,不处理的消息,使用缺省窗口处理函数。例如:DefWindowProc

浅谈消息相关函数

  • GetMessage —— 获取消息
BOOL GetMessage(
	LPMSG lpMsg, // 存放获取到的消息BUFF
	HWND hWnd, // 窗口句柄
	UINT wMsgFilterMin, // 获取消息的最小ID
	UINT wMsgFilterMax // 获取消息的最大ID
);
  • lpMsg —— 当获取到消息后,将消息的参数存放到MSG结构中
  • hWnd —— 获取到hWnd所指定窗口的消息
  • wMsgFilterMinwMsgFilterMax —— 只能获取到由它们指定的消息范围内的消息,如果都为0,表示没有范围

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

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

创建消息

WM_DESTORY

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

WM_SYSCOMMAND

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

WM_CREATE

  • 产生时间:在窗口创建成功但还未显示时
  • 附带信息:
    • wParam:为0
    • lParam:为CREATESTRUCT类型的指针。通过这个指针可以获取CreateWindowEx中的全部12个参数的信息。
  • 一般用法:常用于初始化窗口的参数、资源等等,包括创建子窗口等

WM_SIZE

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

WM_QUIT

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

消息循环原理

消息循环的阻塞

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

发送消息

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

消息分类

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

消息队列

消息队列的概念

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

消息队列的分类

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

消息和消息队列的关系

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

深谈GetMessage

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

WM_PAINT消息

  • 产生时间:当窗口需要绘制的时候
  • 附带信息:
    • wParam:0
    • lParam:0
  • 专职用法:用于绘图

  • 窗口无效区域:需要重新绘制的区域
BOOL InvalidateRect(
	HWND hWnd, // 窗口句柄
	CONST RECT* lpRect, // 区域的矩形坐标
	BOOL bErase // 重绘前是否擦除
);

  • 消息处理步骤

    1. 开始绘图
    HDC BeginPaint(
    	HWND hWnd, // 绘图窗口
    	LPPAINTSTRUCT lpPaint // 绘图参数的BUFF
    ); // 返回绘图设备句柄HDC
    
    1. 正式绘图
    2. 结束绘图
    HDC EndPaint(
    	HWND hWnd, // 绘图窗口
    	CONST PAINTSTRUCT *lpPaint // 绘图参数的指针BeginPaint返回
    );
    

键盘消息

键盘消息分类

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

字符消息(WM_CHAR)

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

鼠标消息

鼠标消息分类

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

鼠标基本消息

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

鼠标双击消息

  • 附带信息:
    • wParam:其他按键的状态,例如Ctrl/Shift等
    • lParam:鼠标的位置,窗口客户区坐标系
      • LOWORD(lParam); // X坐标位置
      • HIWORD(lParam); // Y坐标位置
  • 消息产生顺序(以左键双击为例)
    • WM_LBUTTONDOWN
    • WM_LBUTTONUP
    • WM_LBUTTONDBLCLK
    • WM_LBUTTONUP
  • 使用时需要在注册窗口类的时候添加CS_DBLCLKS风格

鼠标滚轮消息

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

定时器消息

定时器消息介绍

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

创建销毁定时器

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

菜单资源

菜单分类

  • 窗口的顶层菜单
  • 弹出式菜单
  • 系统菜单
  • HMENU类型表示菜单,ID表示菜单项

资源相关

  • 资源脚本文件:*.rc
  • 编译器:RC.EXE
             CL.EXE
.c/.cpp ---------------->|
                         |       LINK.EXE
					     |----------------------> .exe
          RC.EXE         |
.rc -------------------->|

菜单资源的使用

  • 添加菜单资源
  • 加载菜单资源
    1. 注册窗口类时设置菜单
    2. 创建窗口传参设置菜单
    3. 在主窗口WM_CREATE消息中利用 SetMenu 函数设置菜单
// 加载菜单资源
HMENU LoadMenu(
	HINSTANCE hInstance, // handle to module
	LPCTSTR lpMenuName // menu name or resource identifier
);

命令消息(WM_COMMAND)处理

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

图标资源、光标资源

图标资源

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

光标资源

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

字符串资源

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

加速键资源

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

  • 使用:

    • 加载加速键表
    HACCEL LoadAccelerators(
    	HINSTANCE hInstance, // handle to module
    	LPCTSTR lpTableName // 加速键表名
    ); // 返回加速键表句柄
    
    • 翻译加速键
    int TranslateAccelerator(
    	HWND hWnd, // 处理消息的窗口句柄
    	HACCEL hAccelTable, // 加速键表句柄
    	LPMSG lpMsg // 消息
    ); // 如果是加速键,返回非零
    
  • WM_COMMAND中相应消息,消息参数

    • wParam
      • LOWORD:为1表示加速键,为0表示菜单
      • HIWORD:命令ID
    • lParam:为0

绘图编程

绘图基础

  • 绘图设备DC(Device Context),绘图上下文/绘图描述表

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

  • GDI:Windows graphics device interface(win32提供的绘图API)

  • 颜色

    • 计算机使用红、绿、蓝
      • R:0~255
      • G:0~255
      • B:0~255
    • 每一个点颜色是3个字节24位保存 0 - $$2^{24}-1$$
      • 16位:5,5,6
      • 32位:8,8,8 绘图或透明度
  • 颜色的使用

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

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

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

基本图形绘制

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

GDI绘图对象

画笔

  • 画笔的作用

    • 线的颜色、线型、线粗
    • HPEN:画笔句柄
  • 画笔的使用

    • 创建画笔
    HPEN CreatePen(
    	int fnPenStyle, // 画笔的样式
    	int nWidth, // 画笔的粗细
    	COLORREF crColor // 画笔的颜色
    ); // 创建成功返回句柄
    
    • PS_SOLID:实心线,可以支持多个像素宽,其他线型只能是一个像素宽
    • 将画笔应用到DC中
    HGDIOBJ SelectObject(
    	HDC hdc, // 绘图设备句柄
    	HGDIOBJ hgdiobj // GDI绘图对象句柄,画笔句柄
    ); // 返回原来的GDI绘图对象句柄
    
    • 绘图
    • 取出DC中的画笔
      • 将原来的画笔,使用SelectObject函数,放入到设备DC中,就会将我们创建的画笔取出
    • 释放画笔
    BOOL DeleteObject(
    	HGDIOBJ hObject // GDI绘图对象句柄,画笔句柄
    ); // 只能删除不被DC使用的画笔,所以在释放前,必须将画笔从DC中取出
    

画刷

  • 画刷相关
    • 画刷:封闭图形的填充的颜色、图案
    • HBRUSH:画刷句柄
  • 画刷的使用
    1. 创建画刷
      • CreateSolidBrush:创建实心画刷
      • CreateHatchBrush:创建纹理画刷
    2. 将画刷应用到DC中
      • SelectObject
    3. 绘图
    4. 将画刷从DC中取出
      • SelectObject
    5. 删除画刷
      • DeleteObject
  • 其他
    • 可以使用GetStockObject函数获取系统维护的笔刷、画笔等
    • 如果不使用画刷填充,需要使用NULL_BRUSH参数,获取不填充的笔刷
    • GetStockObject返回的画刷不需要DeleteObject

位图

位图绘制

  • 位图相关

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

    • 在资源中添加位图资源
    • 从资源中加载位图LoadBitmap
    • 创建一个与当前DC相匹配的DC(内存DC)
    HDC CreateCompatibleDC(
    	HDC hdc // 当前DC句柄,可以为NULL(使用屏幕DC)
    ); // 返回创建好的DC句柄
    
    • 将位图放入匹配的DC中——SelectObject
    • 成像(1:1)
    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
    );
    
    • 缩放成像
    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 destiantion redtangle
    	HDC hdxSrc, // 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 redtangle
    	DWORD dwRop // raster operation code
    );
    
    • 取出位图——SelectObject
    • 释放位图——DeleteObject
    • 释放匹配的DC——DeleteDC

文本绘制

  • 文字的绘制
    • TextOut:将文字绘制在指定坐标位置
int DrawText(
	HDC hdc, // DC句柄
	LPCTSTR lpString, //字符串
	int nCount, // 字符数量
	LPRECT lpRect, // 绘制文字的矩形框
	UINT uFormat // 绘制的方式
); // DT_VCENTR/DT_BOTTOM只适用于DT_SINGLELINE,和DT_WORDBREAK冲突
  • 文字颜色和背景
    • 文字颜色:SetTextColor
    • 文字背景色:SetBkColor (只适用于OPAQUE模式(不透明模式))
    • 文字背景模式:SetBkModeOPAQUE/TRANSPARENT

字体

  • 字体相关

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

    1. 创建字体
    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 // 字体名称
    );
    
    1. 应用字体到DC——SelectObject
    2. 绘制文字——DrawText/TextOut
    3. 取出字体——SelectObject
    4. 删除字体——DeleteObject

对话框

对话框原理

  • 对话框的分类

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

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

    INT CALLBACK DialogProc(
    	HWND hwndDlg, // 窗口句柄
    	UINT uMsg, // 消息ID
    	WPARAM wParam, // 消息参数
    	LPARAM lParam // 消息参数
    );
    
    • 返回TRUE:缺省处理函数不需要处理
    • 返回FALSE:交给缺省处理函数处理
    • 不需要调用缺省对话框窗口处理函数

模式对话框

  • 创建模式对话框

    INT DialogBox(
    	HINSTANCE hInstance, // 应用程序实例句柄
    	LPCTSTR lpTemplate, // 对话框资源ID
    	HWND hWndParent, // 对话框父窗口
    	DLGPROC lpDialogFunc // 自定义函数
    );
    
    • DialogBox是一个阻塞函数,只有当对话框关闭后,才会返回,继续执行后续代码
    • 返回值通过EndDialog设置
  • 对话框的关闭

    BOOL EndDialog(
    	HWND hDlg, // 关闭的对话框窗口句柄
    	INT_PTR nResult // 关闭的返回值
    );
    
    • 关闭模式对话框,只能使用EndDialog,不能使用DestoryWindow等函数
    • nResultDialogBox函数退出时的返回值
  • 对话框的消息

    • WM_INITDIALOG:对话框创建之后显示之前,通知对话框窗口处理函数,可以完成自己的初始化相关操作

无模式对话框

  • 创建对话框

    HWND CreateDialog(
    	HINSTANCE hInstance, // 应用程序实例句柄
    	LPCTSTR lpTemplate, // 模板资源ID
    	HWND hWndParent, // 父窗口
    	DLGPROC lpDialogFunc // 自定义函数
    );
    
    • 非阻塞函数,创建成功后返回窗口句柄,需要使用ShowWindow函数显示对话框
  • 对话框的关闭

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

静态库

静态库特点

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

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
    • 例如:
    LIBRARY DLLFunc //库
    EXPORTS // 库导出表
    DLL_Mul   @1  // 导出的函数
    

动态库的使用

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

  • 显式链接(程序自己负责使动态库执行)

    • 定义函数指针类型 typedef
    • 加载动态库
    HMODULE LoadLibrary(
    	LPCTSTR lpFileName // 动态库文件名或全路径
    ); // 返回DLL的实例句柄
    
    • 获取函数地址
    FARPROC GetProcAddress(
    	HMODULE hModule, // DLL句柄
    	LPCSTR lpProcName // 函数名称
    ); // 成功返回函数地址
    
    • 使用函数
    • 卸载动态库
    BOOL FreeLibrary(
    	HMODULE hModule // DLL的实例句柄
    );
    

动态库中封装类

  • 在类名称前增加_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 CMatn{

}

线程

线程基础

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

创建线程

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

线程挂起/销毁

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

线程相关操作

  • 获取当前线程的ID
GetCurrentThreadId();
  • 获取当前线程的句柄
GetCurrentThread();
  • 等候单个句柄有信号
VOID WaitForSingleObject(
	HANDLE handle, // 句柄BUFF的地址
	DWORD dwMilliseconds // 等候时间 INFINITE
);
  • 同时等候多个句柄有信号

    DWORD WaitForMultipleObjects(
    	DWORD nCount, // 句柄数量
    	CONST HANDLE *lpHandles, // 句柄BUFF的地址
    	BOOL bWaitAll, // 等候方式
    	DWORD dwMilliseconds // 等候时间 INFINITE
    );
    
    • bWaitAll:等候方式
      • TRUE:表示所有句柄都有信号,才结束等候
      • FALSE:表示句柄中只要有一个有信号,就结束等候

线程同步

原子锁

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

互斥

  • 相关的问题

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

    • 创建互斥
    HANDLE CreateMutex(
    	LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性
    	BOOL bInitialOwner, // 初始的拥有者 TRUE/FALSE
    	LPCTSTR lpName // 命名
    ); // 创建成功返回互斥句柄
    
    • 等候互斥
    WaitFor... // 互斥的等候遵循谁先等候谁先获取
    
    • 释放互斥
    BOOL ReleaseMutex(
    	HANDLE hMutex // 互斥句柄
    );
    
    • 关闭互斥句柄
    CloseHandle();
    

事件

  • 相关问题

    • 程序之间的通知的问题
  • 事件的使用

    • 创建事件
    HANDLE CreateEvent(
    	LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性
    	BOOL bManualReset, // 事件重置(复位)方式,TRUE手动,FALSE自动
    	BOOL bInitialState, // 事件初始状态,TRUE有信号
    	LPCTSTR lpName // 事件命名
    ); // 创建成功返回事件句柄
    
    • 等候事件——WaitForSingleObject/WaitForMultipleObjects
    • 触发事件(将事件设置成有信号状态)
    BOOL SetEvent(
    	HANDLE hEvent // 事件句柄
    );
    
    • 复位事件(将事件设置成无信号状态)
    BOOL ResetEvent(
    	HANDLE hEvent // 事件句柄
    );
    
    • 关闭事件——CloseHandle();
  • 小心事件的死锁

信号量

  • 相关的问题

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

    • 创建信号量
    HANDLE CreateSemaphore(
    	LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全属性
    	LONG lInitialCount, // 初始化信号量数量
    	LONG lMaximumCount, // 信号量的最大量
    	LPCTSTR lpName // 命名
    ); // 创建成功返回信号量句柄
    
    • 等候信号量
      • WaitFor... 每等候通过一次,信号量的信号减1,直到为0阻塞
    • 给信号量指定计数值
    BOOL ReleaseSemaphore(
    	HANDLE hSemaphore, // 信号量句柄
    	LONG lReleaseCount, // 释放数量
    	LPLONG lpPreviousCount // 返回释放前原来信号量的数量,可以为NULL
    );
    
    • 关闭句柄——CloseHandle();
posted @ 2023-10-03 19:24  天空之城00  阅读(94)  评论(0)    收藏  举报