Win 32 编程

程序分类

控制台应用程序(DOS 程序)

本质上是一个DOS程序,本身没有窗口,通过Windows DOS窗口执行

窗口程序

拥有自己的窗口,可以与用户交互

库程序

存放代码数据的程序,执行文件可以从中取出代码执行和获取数据

  • 静态库程序:扩展名lib,在编译链接程序时,将代码放入到执行文件中,无法执行 ( 没有入口函数 )
  • 动态库程序:扩展名dll,执行文件时从中获取代码,不能独立运行,需要依附其他程序运行 ( 有入口函数 )

编译工具

编译器cl.exe:将源代码编译成目标代码.obj
链接器link.exe:将目标代码、库链接生成最终文件
资源编译器rc.exe:( .rc ) 将资源编译,最终通过链接器存入最终文件 ( 一种脚本语言 )

库函数

kernel32.dll:提供了核心的API,例如进程、线程、内存管理等
user32.dll:提供了窗口、消息等API
gdi32.dll:绘图相关的API

头文件

windows.h:所有Windows头文件的集合
windef.hWindows数据类型 ( typdef对基本数据类型取别名实现的 )
winbase.hkernel32API
wingdi.hgdi32API
winuser.huser32API
winnt.hUnicode字符集支持

一般写一个Windows.h即可

编译过程

1
2
3
4
	    	CL.exe
.c/.cpp----------------------> .obj | LINK.exe
RC.exe | ---------------------> .exe
.rc--------------------------> .res |

宽字节字符

wchar_t每个字符占两个字节,其本质上是unsigned short类型,定义时,需要增加 “L” ,通知编译器按照双字节编译字符串,采用Unicode编码,同时对宽字节字符串操作时需要使用相关支持宽字节的函数。wprintfUnicode字符打印支持并不完善,在Windows下使用WriteConsole进行输出。

窗口类

概念

  • 窗口类包含了窗口的各种参数信息的数据结构

  • 每个窗口都具有窗口类,基于窗口类创建窗口

  • 每个窗口类都具有一个名称,使用前必须注册到系统

分类

系统窗口类:系统已经定义好的窗口类,所有应用程序都可以直接使用

应用程序全局窗口类:由用户自己定义,当前应用程序所有模块都可以直接使用

应用程序局部窗口类:由用户自己定义,当前应用程序中本模块可以使用

资源类

菜单

分类

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

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

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

加载菜单资源

1>注册窗口类时设置菜单
2>创建窗口传参设置菜单
3>在主窗口WM_CREATE消息中利用SetMenu函数设置菜单

图标

一个图标文件中可以有多个不同大小的图标

加载

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

必须放在消息处理函数中定义

1
2
3
HCURSOR SetCursor(
HCURSOR hCursor // handle to cursor
);

WM_SETCURSOR消息参数

wPARAM当前使用的光标句柄
lPARAMLOWORD当前区域的代码( Hit-Test code )
HTCLIENT( 客户区域 ) 、HTCAPTION( 标题栏区域 )…
HIWORD:当前鼠标消息ID

设置资源

在注册窗口时,设置光标( 不方便随时改光标 )
使用SetCursor设置光标( 可以随时改光标 )

字符串

方便实现中英文两版程序

添加字符串资源

添加字符串表,在表中增加字符串

字符串资源的使用

1
2
3
4
5
6
int LoadString(
HINSTANCE hInstance,// handle to resource module
UINT uID,//字符串ID
LPTSTR IpBuffer,//存放字符串BUI
int nBufferMax //字符串BUFF长度
);//成功返回字符串长度,失败0

加速键

例如快捷键,可以添加对应的ID来对应的快捷操作

加载加速键表

1
2
3
4
HACCEL LoadAccelerators(
HINSTANCE hInstance,// handle to module
LPCTSTR IpTableName// accelerator table name
);//返回加速键表句柄

翻译加速键

1
2
3
4
5
int TranslateAccelerator(
HWND hWnd,//处理消息的窗口句柄
HACCEL hAccTable,//加速键表句柄
LPMSG lpMsg //消息
);//如果是加速键,返回非零。

WM_COMMAND中相应消息,消息参数:

wPARAM : HIWORD为 1 表示加速键,为 0 表示菜单。LOWORD为命令ID

lParam :为0

消息

消息组成

窗口句柄、消息ID、消息的两个参数( 两个附带信息 ):消息产生的时间、消息产生时的鼠标位置

消息作用

当系统通知窗口工作室,就采用消息的方式派发给窗口

相关消息

消息名 产生时间 附带消息 一般用法
WM_DESTORY 窗口被销毁时的消息 wParam为 0 , lPARAM为 0 常用于在窗口被销毁之前,做对应的善后处理,例如资源、内存等。
WM_SYSCOMMAND 当点击窗口的最大化、最小化、关闭等 wParam为具体点击位置,例如关闭SC_CLOSE等 , lPARAM为鼠标光标的位置。LOWORD(lPARAM);//水平位置HIWORD(lPARAM);//垂直位置 常用在窗口关闭时,提示用户处理
WM_CREATE 窗口创建成功但是还未显示时 wParam为 0 , lPARAMCREATESTRUCT类型的指针。通过这个指针可以获取CreatWindowEX中全部的12个参数的信息 常用于初始化窗口的函数、资源等,包括创建子窗口的等
WM_SIZE 窗口的大小法神变化后,窗口刚产生时 wParam为窗口变化的原因 , lPARAM为窗口变化后的大小。LOWORD(lPARAM);//变化后的宽度HIWORD(lPARAM);//变化后的高度 常用于窗口大小变化后,调整窗口内各个部分的布局
WM_QUIT 程序员发送 wParamPostQuitMessage传递的参数 , lPARAM为 0 用于结束消息循环,当GetMessage收到这个消息后,返回FALSE,结束while处理,退出消息循环
WM_PAINT 当窗口需要绘制的时候 wParam为 0 , lPARAM为 0 用于绘图

消息的阻塞

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
)

消息循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
while(1){
if(PeekMessage(&nMsg,NULL,0,0,PM_NOREMOVE)){
//有消息
if(GetMessage(&nMsg,NULL,0,0)){
TranslateMessage(&nMsg);
DispatchMessage(&nMsg);//将消息交给窗口处理函数来处理
}
}else{
//无消息
wprintf(L"NO MESSAGE\n");
Sleep(100);
}
}

发送消息

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

消息队列

概念

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

分类

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

根据消息和消息队列之间使用关系,将消息分成两类:

  • 队列消息 – 消息的发送和获取,都是通过消息队列完成。

    队列消息-消息发送后,首先放入系统消息队列,之后分发道程序消息队列,然后通过消息循环,从队列当中获取。

    GetMessage:从消息队列中获取消息

    PostMessage:将消息投递到消息队列

    常见队列消息:WM_PAINT、键盘、鼠标、定时器。

  • 非队列消息 – 消息的发送和获取,是直接调用消息的窗口处理完成。

    非队列消息–消息发送时,首先查找消息接收窗口的窗口处理函数,直接调用处理函数,完成消息。

    SendMessage:直接将消息发送给窗口的处理函数,并等候处理结果。常见消息:WM_CREATEWM_SIZE等。

键盘消息

WM_KEYDOWN :按键被按下时产生
WM_KEYUP:按键被放开时产生
WM_SYSKEYDOWN:系统键按下时产生比如ALT、F10等
WM_SYSKEYUP:系统键放开时产生

附带信息:

WPARAM:按键建Virtual Key
LPARAM:按键的参数,例如按下次数

鼠标消息

基本鼠标消息:

WM_LBUTTONDOWN:鼠标左键按下
WM_LBUTTONUP:鼠标左键抬起
WM_RBUTTONDOWN:鼠标右键按下
WM_RBUTTONUP:鼠标右键抬起
WM_MOUSEMOVE:鼠标移动消息

双击消息:

WM_LBUTTONDBLCLK:鼠标左键双击
WM_RBUTTONDBLCLK:鼠标右键双击

滚轮消息:

WM_MOUSEWHEEL:鼠标滚轮消息

一般滚轮的位移量为120的倍数 ( 正负均可 )

附带信息∶

wPARAM:其他按键的状态,例如Ctrl/Shift等
lPARAM:鼠标的位置,窗口客户区坐标系。
LOWORD:X 坐标位置
HIWORD:Y 坐标位置

一般情况鼠标按下/抬起成对出现。在鼠标移动过程中,会根据移动速度产生一列的WM_MOUSEMOVE消息。同时使用双击消息处理时需要在注册窗口类的时候添加CS_CBLCLKS风格。

定时器消息

定时器一般用于周期性执行的操作

产生时间

在程序中创建定时器,当到达时间间隔时,GetMessage会向程序发送一个WM_TIMER消息。定时器的精度是毫秒,但是准确度很低。例如设置时间间隔为1000ms,但是会在非1000毫秒到达消息。

误差在毫秒级

1
2
3
4
5
6
UINT_PTR SetTimer(
HWND hWnd,//定时器窗口句柄
UINT_PTR nIDEvent,//定时器ID
UINT uElapse,//时间间隔(毫秒)
TIMERPROC IpTimerFunc//定时器处理函数指针(不使用则设置为NULL)
)//创建成功返回非零值

定时器的创建

1
2
3
4
UINT_PTR KillTimer(
HWND hWnd,//定时器窗口句柄
UINT_PTR nIDEvent,//定时器ID
)

销毁定时器

附带信息

wPARAM:定时器ID,用于区分时间到后是哪个定时器触发的
lPARAM:定时器处理函数的指针

绘图

绘图设备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 ( RGB )
32位:8,8,8,8 绘图或透明度 ( RGB+透明度 )

颜色的使用

COLORREF:实际DWORD ( unsigned long )
例如: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:指到窗口到目标点(从(0,0)到指定的点),设置当前点
LineTo:从窗口当前点到指定点绘制一条直线
当前点:上一次绘图时的最后一点,初始为(0,0)点。
封闭图形:能够用画刷填充的图形Rectangle ( 绘画直角矩形 ) / Ellipse ( 圆形 )

GDI 绘图对象

画笔

画笔的作用

线的颜色、线型、线粗。
HPEN:画笔句柄

画笔的创建

1
2
3
4
5
HPEN CreatePen(
int fnPenStyle,//画笔的样式
int nWidth,//画笔的粗细
COLORREF crColor//画笔的颜色
);//创建成功返回句柄

PS_SOILD:实心线,可以支持多个像素宽其他线型只能是一个像素宽。

虚线画笔像素宽只能是 1,实心画笔可以是任意像素宽

将画笔应用到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:创建纹理画刷 ( 填充纹理线 )

之后同画笔一样,需要进行交换(SelectObject)和对创建的画刷句柄进行消除。

初始画刷为白颜色,同时用直线围起来的图形即使封闭也不是封闭图形

可以使用GetStockObject函数获取系统维护的画刷、画笔等。调用的系统画刷不需要销毁,也无法使用DeleteObject进行销毁掉。

如果不使用画刷填充,需要使用NULL_BRUSH参数,获取不填充的画刷。GetStockObject返回的画刷不需要DeleteObject

位图

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

位图的使用

  • 在资源中添加位图资源
  • 从资源中加载位图LoadBitmap
  • 创建一个与当前DC相匹配的DC ( 内存 DC )
1
2
3
HDC CreateCompatibleDC(
HDC hdc //当前DC句柄,可以为NULL(使用屏幕DC)
);//返回创建好的DC句柄
  • 将位图放入匹配的DC中SelectObject
  • 成像( 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 )

  • 释放位图 ( DelectObject )

  • 释放匹配的 DC (DeleteObject)

文字的绘制

TextOut:将文字绘制在指定坐标位置 ( 只能绘制单行 )

DrawText:可以绘制矩形框的字符串

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格式的字体文件

创建字体

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 fdwltalic,//斜体 0:不为斜体 1:斜体
DWORD fdwUnderline,//字符下划线
DWORD fdwStrikeOut,//删除线
DWORD fdwCharSet,//字符集 GB312
DWORD fdwOutputPrecision,//输出精度 (已废弃,赋值为 0 即可)
DwORD fdwClipPrecision,//剪切精度 (已废弃,赋值为 0 即可)
DWORD fdwQuality,//输出质量 (已废弃,赋值为 0 即可)
DWORD fdwPitchAndFamily,//匹配字体
LPCTSTR lpszFace //字体名称
)

对话框

对话框的分类

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

对话框基本使用

  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 IpTemplate,//对话框资源ID
HWND hWndParent,//对话框父窗口
DLGPROC lpDialogFunc//自定义函数
);

DialogBox是一个阻塞函数,只有当对话框关闭后,才会返回,继续执行后续代码。

返回值是通过EndDialog设置。

1
2
3
4
5
6
HWND CreateDialog(
HINSTANCE hInstance,//应用程序实例句柄
LPCTSTR IpTemplate,//对话框资源ID
HWND hWndParent,//对话框父窗口
DLGPROC lpDialogFunc//自定义函数
);

非阻塞函数,创建成功返回窗口句柄,需要使用ShowWindow函数显示对话框

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

对话框的关闭

1
2
3
4
BOOL EndDialog(
HWND hDlg,//关闭的对话框窗口句柄
INT_PTR nResult,//关闭的返回值
)

关闭模式对话框,只能使用EndDialog,不能使用DestroyWindow等函数。nResultDialogBox函数退出时的返回值。

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

对话框的消息

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

静态库

特点

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

CWindows下直接编译不连接不需要对函数进行声明即可调用,但是C++需要对函数进行申明

C 静态库的使用

库路径设置:可以使用pragma关键字设置#pragma comment( lib,"../lib/clib.lib”)

C++ 静态库的创建

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

C++静态库的使用

库路径设置:可以使用pragma关键字设置#pragma comment( lib,"../lib/cpplib.lib”)

C++在处理函数时会对函数进行换名,因此在C++中调用C静态库会找不到,而无法使用

可以在调用的C静态库的函数声明前加一个extern "C"

动态库

特点

  1. 运行时独立存在
  2. 源码不会链接到执行程序
  3. 使用时加载(使用动态库必须使动态库执行)

与静态库的比较︰

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

创建动态库项目

添加库程序

库程序导出–提供给使用者库中的函数等信息。

  1. 声明导出︰使用_declspec(dllexport)导出函数,用该方法导出的函数名字会被替换 ( C++ )

注意:动态库编译链接后,也会有lib文件,是作为动态库函数映射使用,与静态库不完全相同。

  1. 模块定义文件.def

例如

LIBRARY DLLFunc //库
EXPORTS //库导出表
DLL_Mul @1 //导出的函数

dll文件中将函数导出后会将定义的函数的地址放在文件头,之后放定义函数的源码

动态库的使用

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

  1. 头文件和函数原型

​ 可以在函数原型的声明前,增加_declspec(dllimport))

  1. 导入动态库的LIB文件
  2. 在程序中使用函数
  3. 隐式链接的情况,dll文件可以存放的路径︰
    1. 与执行文件中同一个目录下
    2. 当前工作目录
    3. Windows 目录
    4. Windows / System32 目录
    5. Windows / System
    6. 环境变量PATH指定目录

程序通过定位到lib文件之后取其中的编号,而对引用的函数进行加载

1
2
_declspec(dllimport) XXX //定义的函数申明 ( C++ )
#pragma comment (lib,"lib文件路径")

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

  1. 定义函数指针类型typedef

  2. 加载动态库

    1
    2
    3
    HMODULE LoadLibrary(
    LPCTSTR IpFileName //动态库文件名或全路径
    );//返回DLL的实例句柄 (HINSTANCE)
  3. 获取函数地址 ( 绝对 / 真实地址 )

    1
    2
    3
    4
    FARPROC GetProcAddress(
    HMODULE hModule,//DLL句柄
    LPCSTR lpProcName //函数名称
    );//成功返回函数地址
  4. 使用函数

  5. 卸载动态库

1
2
3
BOOL FreeLibrary(
HMODULE hModule //DLL的实例句柄
)

如果出现加载的函数的地址为 0 的情况,可以在dll项目的文件中加入一个文件头,包含以下内容:

1
2
3
4
5
6
7
8
#pragma once
#ifdef __DLLEXPORT
#define __DLL_EXP _declspec(dllexport)
#else
#define __DLL_EXP _declspec(dllimport)
#endif

extern "C" __DLL_EXP 定义的函数

添加完后再次生成dll文件即可

动态库封装类

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

1
2
3
class _declspec(dllexport) CMath {
XXX
};

通常使用预编译开关切换类的导入导出定义,例如∶

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{
XXX
};

线程

Windows线程是可以执行的代码的实例。系统是以线程为单位调度程序。一序当中可以有多个线程,实现多任务的处理。

Windows 线程的特点︰

  1. 线程都具有1个ID
  2. 每个线程都具有自己的内存栈
  3. 同一进程中的线程使用同一个地址空间。

线程的调度:

将CPU的执行时间划分成时间片,依次根据时间片执行不同的线程。

线程轮询:

线程A->线程B->线程A…

创建一个线程

1
2
3
4
5
6
7
8
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES IpThreadAttributes,//安全属性(已经废弃,直接置空)
SIZE_T dwStackSize,//线程栈的大小(按一兆进行对齐)
LPTHREAD_START_ROUTINE lpStartAddress,//线程处理函数的函数地址
LPVOID IpParameter,//传递给线程处理函数的参数(void* 类型)
DWORD dwCreationFlags,//线程的创建方式 0:立刻执行 CREATE_SUSPENDED:挂起
LPDWORD IpThreadId //创建成功,返回线程的ID
);//创建成功,返回线程句柄

处理函数

线程处理函数:

1
2
3
DWORD WINAPI ThreadProc(
LPVOID lpParameter //创建线程时,传递给线程的参数
)

挂起线程:

1
SuspendThread(HANDLE hThread)//线程句柄

恢复线程:

1
ResumeThread(HANDLE hThread)//线程句柄

结束指定线程:

1
2
3
4
BOOL Terminate Thread(
HANDLE hThread,//线程句柄
DWORD dwExitCode//退出码
)

类似于"杀人",输入对应线程的句柄就可以结束对应线程

结束函数所在的线程:

1
2
3
VOID ExitThread(
DWORD dwExitCode//退出线程码
)

类似于"自杀",哪个线程调用,哪个线程就结束

获取线程的ID:

1
GetCurrentThreadId()

获取当前线程的句柄:

1
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:表示句柄中只要有一个有信号,就结束等候。

当线程执行中时,该线程无信号,当线程结束时才产生信号。

线程同步

原子锁

相关问题

多个线程对同一个数据进行原子操作,会产生结果丢失。比如执行++运算时。

错误代码分析

当线程A执行a value++时,如果线程切换时间正好是在线程A将值保存到a value之前,线程B继续执行g_value++,那么当线程A再次被切换回来之后,会将原来线程A保存的值保存到g_value上,线程B进行的加法操作被覆盖。

一般被CPU切换出该线程时,程序会将当前运行状态进行压栈保存( 每个进程都有独立的栈空间 )

以上图为例,当线程一再执行完语句一后,被CPU切换到线程二,线程一的数据进行压栈保存,线程二完整的执行完了对应的全部代码,此时切换回到线程一,栈中的数据弹出,执行语句三,而导致线程一的语句一的数据未被保存,而导致丢失了数据。

使用原子锁函数

InterlockedIncrement(long*)++操作符进行加锁

对传入的数据进行加一运算,当运行时间超过了等待时间,仍然会被操作系统切换到其他线程,当该线程无法进行加锁时会造成阻塞,只能等待切换线程。

InterlockedDecrement--操作符进行加锁
InterlockedCompareExchange 对比较进行加锁
InterlockedExchange 对交换进行加锁

原子锁的实现

直接对数据所在的内存操作,并且在任何一个瞬间只能有一个线程访问。

互斥锁

效率不如原子锁,但是原子锁能实现的互斥锁均能实现

相关的问题

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

互斥的使用
1
2
3
4
5
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES IpMutexAttributes,//安全属性
BOOL bInitialOwner,//初始的拥有者(哪个线程创建的就被哪个线程拥有) TRUE/FALSE
LPCTSTR IpName//命名
)//创建成功返回互斥句柄

在任何时间点上,只能有一个线程拥有互斥,具有独占性和排他性,当任何一个线程不拥有互斥时其拥有信号,但是当拥有互斥时其变为无信号。

WaitForSingleObject / WaitForMultipleObjects互斥的等候遵循谁先等候谁先获取。

释放互斥
1
2
3
BOOL ReleaseMutex(
HANDLE hMutex // handle to mutex
)
关闭互斥句柄
1
2
3
BOOL CloseHandle(
HANDLE hObject
)

事件

相关问题

解决程序之间的通知的问题。

事件的使用

创建事件

1
2
3
4
5
6
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,//安全属性
BOOL bManualReset,//事件重置(复位)方式,TRUE手动,FALSE自动
BOOL bInitialState,//事件初始状态,TRUE有信号
LPCTSTR lpName //事件命名
)//创建成功返回事件句柄

时间也具备有信号和无信号两个状态

等候事件

1
WaitForSingleObject / WaitForMultipleObjects

触发事件(将事件设置成有信号状态)

1
2
3
BOOL SetEvent(
HANDLE hEvent// handle to event
)

关闭事件

1
2
3
BOOL CloseHandle(
HANDLE hObject
)

注意:小心事件的死锁。

信号量

相关的问题

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

信号量的使用
1
2
3
4
5
6
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,//安全属性
LONG lInitialCount,//初始化信号量数量
LONG lMaximumCount,//信号量的最大值
LPCTSTR lpName//命名
)//创建成功返回信号量句柄
等候信号量

WaitForSingleObject / WaitForMultipleObjects每等候通过一次,信号量的信号减1,直到为 0 阻塞

给信号量指定计数值
1
2
3
4
5
BOOL ReleaseSemaphore(
HANDLE hSemaphore,//信号量句柄
LONG lReleaseCount,//释放数量
LPLONG lpPreviousCount//释放前原来信号量的数量,可以为NULL
)

关闭句柄

1
2
3
BOOL CloseHandle(
HANDLE hObject
)

相关函数

1
2
3
4
5
6
int WINAPI WinMain( 
HINSTANCE hInstance, //当前程序的实例句柄
HINSTANCE hPrevInstance, //当前程序前一个实例句柄 (已经废弃)
LPWSTR lpCmdLine, //命令行参数字符串 (char* 类型)
int nCmdShow //窗口显示方式 (最大化、最小化、原样显示)
);

句柄一个拿来找到内存的东西,但不是指针,句柄类型定义以H开头

1
2
3
4
5
6
int MessageBox(
HWND hWnd, //父窗口句柄
LPCTSTR IpText, //显示在消息框中的文字
LPCTSTR IpCaption, //显示在标题栏中的文字
UINT uType //消息框中的按钮、图标显示类型 (unsigned int)
);//返回点击的按钮ID

MessageBox是一个阻塞函数,当提示框弹出时便阻塞了,当点击了按钮后终止阻塞

uType按钮参数:

按钮参数 含义
MB_OK 默认值。有一个确认按钮在里面。
MB_YESNO 有是和否在里面。
MB_ABORTRETRYIGNORE 有Abort(放弃),Retry(重试)和Ignore(跳过)
MB_YESNOCANCEL 消息框含有三个按钮:Yes,No和Cancel
MB_RETRYCANCEL 有Retry(重试)和Cancel(取消)
MB_OKCANCEL 消息框含有两个按钮:OK和Cancel

图标:

参数 含义
MB_ICONEXCLAMATION 一个惊叹号出现在消息框
MB_ICONWARNING 一个惊叹号出现在消息框
MB_ICONINFORMATION 一个圆圈中小写字母 i 组成的图标出现在消息框
MB_ICONASTERISK 一个圆圈中小写字母 i 组成的图标出现在消息框
MB_ICONQUESTION 一个问题标记图标出现在消息框
MB_ICONSTOP 一个停止消息图标出现在消息框
MB_ICONERROR 一个停止消息图标出现在消息框
MB_ICONHAND 一个停止消息图标出现在消息框
1
2
3
ATOM RegisterClass(
CONST WNDCLASS* IpWndClass //窗口类的数据
)//注册成功后,返回一个数标识

注册窗口类的结构体

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct _WNDCLASS{
UINT style; //窗口类风格
WNDPROC IpfnWndroc; //窗口处理函数
int cbClsExtra; //窗口类的附加数据buff的大小(开缓冲区大小[字节])
int cbWndExtra; //窗口的附加数据buff的大小(开缓冲区大小[字节])
HINSTANCE hInstance; //当前模块的实例句柄
HICON hIcon; //窗口图标句柄
HCURSOR hCursor; //鼠标的句柄
HBRUSH hbrBackground; //绘制窗口背景的画刷句柄
LPCTSTR IpszMenuName; //窗口菜单的资源ID字符串
LPCTSR IpszClassName; //窗口类名称
}WNDCLASS,*PWNDCLASS;

style风格:

1
2
3
4
CS_HREDRAW - 当窗口水平变化时,窗口重新绘制
CS_VREDRAW - 当窗口垂直变化时,窗口重新绘制
CS_DCLCLKS - 允许窗口接收鼠标双击
CS_NOCLOSE - 窗口没有关闭按钮
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
2
3
4
5
6
LRESULT CALLBACK WnfProc(
HWND hWnd, //窗口句柄
UINT msgID, //消息ID
WPARAM wParam, //消息参数
LPARAM lPARAM//消息参数
)

消息处理函数,通过用户自定义来对产生的消息进行处理

1
2
3
4
5
6
BOOL GetMessage(
LPMSG IpMsg,//存放获取到的消息BUFF
HWND hWnd,//窗口句柄
UINT wMsgFilterMin,//获取消息的最小ID
UINT wMsgFilterMax//获取消息的最大ID
)

IpMsg:当获取到消息后,将消息的参数存放到MSG结构中
hWnd:获取hWnd所指定窗口的消息
wMsgFilterMaxwMsgFilterMin:只能获取到由它们指定的消息范围内的消息,如果都为0,表示没有范围。

返回值:

抓到WM_QUIT返回零值,否则返回非零值。
PostQuitMessage(0)可以发送一个WM_QUIT消息,使其返回

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

1
2
3
BOOL TranslateMessage(
CONST MSG* IpMsg //要翻译的消息地址
)

翻译消息( 对于可见字符 ),将键盘消息翻译成字符消息。
检查消息是否是按键的消息,如果不是按键消息,不做任何处理,继续执行。

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

附带信息∶

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

1
2
3
LRESULT DispatchMessage(
CONST MSG* Ipmsg//要派发的消息
)

将消息派发到该消息所属的窗口处理函数上

1
2
3
4
5
BOLL InvalidateRect(
HWND hWnd,//窗口句柄
CONST RECT* IpRect,//区域的矩形坐标
BOOL bErase//重绘前是否先擦除 TURE 表示擦除 FALSE 标识不擦除
)

申明窗口无效区域:需要重新绘制的区域


Win 32 编程
https://equinox-shame.github.io/2022/04/07/Win32 编程/
作者
梓曰
发布于
2022年4月7日
许可协议