windows/win32编程

First Post:

Last Update:

本文参考:https://www.bilibili.com/video/BV1pB4y187VB/?spm_id_from=333.337.search-card.all.click

(43条消息) 【Win32】初识Win32编程_半生瓜のblog的博客-CSDN博客

Windows编程

应用程序分类

控制台程序Console

DOS程序,本身没有窗口,通过Windows DOS窗口执行。(DOS是操作系统预留的)

窗口程序

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

库程序

存放代码、数据的程序、执行文件可以从中取出代码执行和获取数据
静态库程序:扩展名LIB,在编译链接程序时,将代码放入到执行文件中。
动态库程序:扩展名DLL,在执行文件时从中获取代码 。
静态库中的代码是直接嵌入到你的项目中,而动态库中的内容是通过地址来找到。

静态库程序无法执行,也就是说它最终生成的文件无法进入内存。
动态库程序有入口函数,可以执行。但是它不能独立运行。谁调动态库里面的东西,它就依附于谁。

应用程序对比

入口函数

  • 控制台程序-main
  • 窗口程序-WinMain
  • 动态库程序-DllMain
  • 静态库程序-无入口函数

文件存在方式

  • 控制台程序、窗口程序-EXE文件
  • 动态库程序-DLL文件
  • 静态库程序-LIB文件
1
2
3
4
5
6
7
相关函数
int WINAPI wWinMain(
HINSTANCE hInstance,//当前程序的实例句柄,找到你当前进程所占据的那块内存
HINSTANCE hPrevInstance,//当前程序前一个示例句柄,废弃了
PWSTR pCmdLine, //命令行参数字符串
int nCmdShow//窗口的显示方式
);

暂时可以将句柄理解成,句柄是用来找到内存的东西,但绝对不是指针。

1
2
3
4
5
6
7
8
int MessageBox(
[in, optional] HWND hWnd,//父窗口句柄
[in, optional] LPCTSTR lpText,//显示在消息框中的文字
[in, optional] LPCTSTR lpCaption,//显示在标题栏中的文字
[in] UINT uType//消息框中的按钮、图标显示了类型
);//返回点击的按钮ID

//能够将程序暂停在这里,说明它是个阻塞函数。它执行,可能不会立即返回。

窗口创建过程

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

第一个windows窗口

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
35
36
37
38
39
40
41
42
43
44
45
46
#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 hPerIns, 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 = "myWindow";
wc.lpszMenuName = NULL;
wc.style = CS_HREDRAW | CS_VREDRAW;
//将上面赋的这些值全部写入操作系统
RegisterClass(&wc);

//在内存中创建窗口
HWND hWnd = CreateWindow("myWindow", "menu", 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;

}

窗口有无与进程退不退没有关系。

注册窗口类

窗口类的概念

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

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

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

  • 在操作系统内核里存着就叫窗口类,在程序里存着就叫窗口类。

窗口类的分类:

系统窗口类

系统已经定义好的窗口类,所有应用程序都可以直接使用。
不需要注册,直接使用窗口类即可。系统已经注册好了。
例如:按钮-BUTTON,编辑框-EDIT

应用程序全局窗口类

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

全局及局部窗口类 :

注册窗口类的函数

1
2
3
4
5
6
7
(ATOM——unsigned short)

ATOM RegisterClass(

CONST WNDCLASS *lpWndClass//窗口类的数据);

//注册成功后 ,返回一个数字标识。(0失败,非0成功。)

style窗口类风格

应用程序全局窗口类的注册,需要在窗口类的风格中添加CS_GLOBALCLASS。

应用程序局部类窗口类注册,无需添加如上风格。

不建议使用全局窗口类——因为局部窗口类能完成全局窗口类的功能,并且全局窗口类可能会产生冗余。

1
2
3
4
CS_HREDRAW ——当窗口水平变化时,窗口重新绘制
CS_VREDRAW ——当窗口垂直变化时,窗口重新绘制
CS_DBLCLKS ——允许窗口接收鼠标双击
CS_NOCLOSE ——窗口没有关闭按钮

窗口创建

窗口创建

CreateWindow / CreateWindowEx

CreateWindow内部是如何实现的

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

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include<windows.h>

//窗口处理函数(自定义、处理消息)
LRESULT CALLBACK WndProc(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam)
{
switch (msgID)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
break;
}
return DefWindowProc(hWnd, msgID, wParam, lParam);
}

//入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPerIns, 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 = "myWindow";
wc.lpszMenuName = NULL;
wc.style = CS_HREDRAW | CS_VREDRAW;
//将上面赋的这些值全部写入操作系统
RegisterClass(&wc);

//在内存中创建窗口
HWND hWnd = CreateWindow("myWindow", "menu", 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;

}

子窗口创建过程

创建时要设置父窗口句柄
创建风格要增加WS_CHILD | WS_VISBLE
(根据注册的窗口类,来创建多个窗口。)

1
2
3
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);

消息基础

消息的概念和作用

消息组成(windows平台下)
  • ​ 窗口句柄
  • ​ 消息ID
  • ​ 消息的两个参数(两个附带信息)
  • ​ 消息产生的时间
  • ​ 消息产生时的鼠标位置
消息的作用

当系统通知窗口工作时,就采用消息的方式(DispatchMessage)派发给(调用)窗口的窗口处理函数(将MSG的前四个信息传递给消息处理函数)。
每一个窗口都有窗口处理函数

MSG结构体接收消息

结构体定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
//对应解释同上消息组成
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
#ifdef _MAC
DWORD lPrivate;
#endif
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;

DispatchMessage如何找到窗口处理函数

1
2
3
4
5
nMsg.hwnd->保存窗口数据的内存->找到对应的窗口处理函数->WndProc

回到你自己定义的消息处理函数->传递参数->处理消息
LRESULT CALLBACK WndProc(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam)
传递这四个参数,不用关系后两个

窗口处理函数

每个窗口都必需有窗口处理函数,只要基于窗口类创建窗口,就肯定要有个窗口处理函数。

窗口处理依照如下结构定义:

1
2
3
4
5
6
LRESULT CALLBACK WindowProc(
HWND hWnd;//窗口句柄
UINT uMsg;//消息ID
WPARAM wParam;//消息参数
LPARAM lParam;//消息参数
);

当系统通知窗口时,(DispatchMessage)会调用窗口处理函数,同时将消息ID和消息参数传递给窗口处理函数。

在窗口处理函数中,不处理的消息,使用缺省窗口处理函数。

例如:DefWindowProc(可以给各种消息做默认处理)。

消息循环中的相关函数(浅谈)
GetMessage-到系统的某个地方抓本进程的消息

函数原型如下:

1
2
3
4
5
6
7
BOOL GetMessage(
LPMSG lpMsg,//存放获取到消息的BUFF,
HWND hWndp;//窗口句柄,要是定为NULL,将会抓取本进程中所有窗口中的消息
UNIT wMsgFilterMin,//获取的最小ID
UNIT wMsgFilterMax//获取消息的最大ID
//最后两个参数都为0,就是不管ID有多大,只要是本进程的消息都抓过来
);

其中后三个参数可以限制抓取消息的范围,如果设置为NULL,0,0那其实就是没有进行限制,只要是本进程的消息我都把它抓过来。

GetMessage的返回值

消息WM_QUIT会使GetMessage返回0,从而中终止消息接收。

PostQuitMessage(0);会在进程中扔出WM_QUIT这个消息,get后从而使得消息循环终止。

TranslateMessage-翻译消息——它可不是什么消息都翻译。

将按键(可见字符按键,a~z)消息翻译成字符消息。

所以进入到它的内部, 它所做的第一件事就是检查这个消息是否合法,是否是它要翻译的消息类型。

如果不是按键类型消息,不做任何处理,继续执行。

函数原型如下:

1
2
3
4
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:鼠标光标的位置(这个不重要,我们只需要知道点没点就行,具体在哪个位置其实无所谓(具体情况具体使用)),LOWORD(lParam);水平位置,HIWORD(lParam);垂直位置。(高两字节传纵坐标,低两字节传横坐标。)
一般用法:常用在窗口关闭时,提示用户处理。

WM_CREATE

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

WM_SIZE

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

WM_QUIT

产生时间:程序员发送。
附带信息:
wPram:PostQuitmessage函数传递的参数。
lParam:0。
一般用法:用于结束消息循环,当GetMessage收到这个消息后,会返回FALSE,结束while处理,退出消息循环。
这个消息不用我们去处理,进不去我们定义的窗口处理函数,GetMessage()返回了0,无法进入循环获取消息。

消息循环的原理

消息循环的阻塞

GetMessage-从系统获取消息,将消息从系统中移除,阻塞函数。当系统无消息时,会等候下一条消息。
对人来说消息是一直存在的,但是对于CPU来说(速度接近光速),消息不是经常有的,所以会经常发生阻塞。这样程序的效率就不高,从而引出下面这个函数。

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

(前四个参数同GetMessage)

最后一个参数是,是否赋予它抓取消息的能力,一般是不给它的,也就是填写

1
2
3
4
5
6
7
BOOL PeekMessageA(
[out] LPMSG lpMsg,
[in, optional] HWND hWnd,
[in] UINT wMsgFilterMin,
[in] UINT wMsgFilterMax,
[in] UINT wRemoveMsg
);

也就是说,更好的流程是,先派PeekMessage去侦查是否有消息,有就告诉GetMessage让它来处 理。没有就不要派Get去了,因为它会一直在那里等着消息的出现。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
while (1)
{
if (PeekMessage(&nMsg, NULL, 0, 0, PM_NOREMOVE))
{
//有消息-判断是否是WM_QUIT
if (GetMessage(&nMsg, NULL, 0, 0))
{
TranslateMessage(&nMsg);
DispatchMessage(&nMsg);
}
else
{
return 0;
}
}
else
{
//没有消息——空闲处理
WriteConsole(g_HOUTPUT, "空闲ing\n", strlen("空闲ing"), NULL, NULL);
}
}

发送消息

Windows平台上的消息,都是它们两个造出来的。

SendMessage-发送消息,会等候消息处理的结果。
PostMessage-投递消息,消息发出后立刻返回,不等候消息执行结果。
函数原型如下:

1
2
3
4
5
6
LRESULT SendMessage(
[in] HWND hWnd,//消息发送的目的创建
[in] UINT Msg,//消息ID
[in] WPARAM wParam,//消息参数
[in] LPARAM lParam//消息参数
);

这四个参数就是一个消息的前四个参数,剩下的两个参数函数内部以某种手段自加来获取。

消息分类

系统消息-ID范围0~0x03FF
由系统定义好的消息,可以在程序中直接使用。
程序员只负责一头,要么发送不用处理,要么处理不用发送。
用户自定义的消息-ID范围0x0400(WM_USER) - 0x7FFF(31743)
由用户自己定义,满足用户自己的需求。由用户自己发出消息,并响应处理。
由程序员,自己定制,自己发送,自己处理。
自定义消息宏:WM_USER(叫什么都行)
例如:

定义消息名称

1
#define WM_MYMESSAGE WM_USER+1001

发送,在哪发都可以,附加消息,你自己的,附加什么都行。

PostMessage/SendMessage(hWnd, WM_MYMESSAGE, 1, 2);

消息队列

消息队列的概念

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

静态库

Windows上静态库和Linux上的静态库在原理上没有任何区别,都是封装一堆东西等着别人去掉。

静态库的特点

运行不存在。
没有如何,不能执行,生成的文件无法形成静态影像,无法进内存。
静态库源码被链接到调用程序中。
目标程序的归档。

C语言静态库

C静态库的创建

创建一个静态库程序。
添加库程序,源文件使用C文件。

C静态库的使用

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

C++静态库

C++静态库的创建

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

C++静态库的使用

库路径设置:可以使用pragma关键字设置
#pragma comment(lib,“…/xx/xxx.lib”)
示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<iostream>
using namespace std;
//给编译器看
int CLIB_add(int add1, int add2);
int CLIB_sub(int add1, int add2);
//给链接器看
#pragma comment(lib,"../Debug/CPPLIB.lib")


int main(void)
{
cout << CLIB_add(5, 2) << endl;
cout << CLIB_sub(5, 2) << endl;
return 0;
}

动态库