一、Windows窗口的创建流程

注册一个窗口类

RegisterClass

1.1、提供一个消息处理函数(窗口回调函数)

1.2、提供一个窗口类的名字

创建一个窗口

​ GreateWindow 能够创建一个窗口,得到一个窗口句柄,是系统找到串口的一个标识

更新显示窗口

UpdateWindow(更新)

Show Window(显示)

编写一个消息循环

While(GetMessage)(…))

​ {

​ TranslateMessage();

​ DispatchMessage(…);

}

编写一个消息处理函数(窗口回调函数)

DWORD WINAPI WndProc(HWND hWnd,int nMessage,WPARAM wParam,LPARAM lParam)

{

​ Switch(message)

​ {

}

}

问题1:怎么理解这一些代码,这个过程这段代码,是定形式,是微软规定好的流程

问题2:消息是怎么获取到的

只有窗口才能够收消息,窗口收的息,需要通过 GetMessage来从程序的消息队列中获取到。你需要创建一个窗口,然后在创建窗口之后就可以使用 GetMessage获得创建的窗口收到的消息

问题3:消息队列在哪里,我为什么看不到??

因为大部分的事情都是操作系统帮你完成了比如说消息队列,比如往消息队列添加消息,从消息队列中得到消息,删除已经得到的问题4:什么是回调函数?谁调用了回调函数?

回调函数就不是你调用的函数你提供了一个函数给操作系统,操作系统在合适的时机去调用你提供的函数。因为只有操作系统才能知道什么时候得到了消息,只有我才知道我的程序怎么处理消息

问题5:为什么创建窗口要先注册窗口类?为什么创建完窗口要写更新显示窗口?为什么消息循环要写 tMessage DispatchMessage并放在一个循环里面??需要先有一个板(窗口共有一些特性,放在窗口类里面),然后创建窗口的时候根据模板去创建

刚创建出来的窗口是隐藏起来的,比如 ShowWindow给显示出来, UpdateWindow能够让窗口产生一次自绘(WMPAINT)

的作用就是从滴息队列中获取消息

DispatchMessage的作用就是调用口相对应的回调函数

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include <windows.h>
#include <stdio.h>
//用于在调试窗口输出信息
bool trace(CONST TCHAR* format, ...)//变参函数
{
TCHAR buffer[1000];
va_list argptr;
va_start(argptr,format);
//将格式化信息写入指定缓冲区
wvsprintf(buffer, format, argptr);
va_end(argptr);
//将缓冲区信息输出
OutputDebugString(buffer);
return true;
}
//必须提供窗口回调函数
LRESULT CALLBACK WndProc(
HWND hWnd, //哪个串口的消息
UINT uMsg, //消息的类型
WPARAM wParam,
LPARAM lParam //消息的附加参数
)
{
//使用分支语句处理要操作的消息
switch (uMsg)
{
case WM_CREATE://窗口创建消息,第一个被产生 的消息
{
MessageBox(hWnd, L"打开成功", L"标题", MB_OK);
break;
}
//右上角关闭按钮
case WM_CLOSE:
{
MessageBox(hWnd, L"确认是否关闭", L"提示", MB_OK);
//销毁当前窗口
DestroyWindow(hWnd);
//结束消息循环
PostQuitMessage(0);
break;
}
//窗口移动消息,移动窗口位置响应
case WM_MOVE:
{
//lParam保存左上角坐标
WORD x = LOWORD(lParam);
WORD y = HIWORD(lParam);
trace(L"POS(%d,%d)\n", x, y);
break;
}
default:
break;
}
//将不想处理的消息传递给这个函数,进行默认参数
return DefWindowProc(hWnd,uMsg,wParam,lParam);
}
void prin()
{
trace(L"一次调用");
}
int WINAPI WinMain(
_In_ HINSTANCE hInstance, //实例句柄,表示当前应用程序,用于获取资源
_In_ HINSTANCE hPrevInstance, //句柄,标识上一个打开的应用程序
_In_opt_ LPSTR lpCmdLine, //命令行参数,对应的是main函数的argv参数
_In_ int nCmdShow //显示方式,最大化最小化
)
{
prin();
//1、创建窗口类,
WNDCLASS WnsClass = {0}; //窗口结构体初始化为0
WnsClass.lpfnWndProc = WndProc; //一个回调函数的地址
WnsClass.lpszClassName = L"myclass"; //结构体名字,表示当前的窗口,由于是宽字符
WnsClass.hCursor = LoadCursor(NULL, IDC_ARROW);//使用默认光标
WnsClass.style = CS_VREDRAW | CS_HREDRAW; //窗口改变大小后重绘制
WnsClass.hInstance = hInstance; //标识当前应用程序
WnsClass.hIcon = LoadIcon(NULL,IDI_APPLICATION);//使用默认图标
WnsClass.hbrBackground = CreateSolidBrush(RGB(0, 0xff, 0));//背景颜色
WnsClass.cbWndExtra = WnsClass.cbClsExtra = 0; //扩展,几乎没有用

//2、注册已经注册好的窗口,注册后的窗口才能使用
if (0 == RegisterClass(&WnsClass)) //传入需要注册的结构体地址
{
MessageBox(NULL, L"打开失败", L"错误", MB_OK | MB_ICONERROR);
return 0;
}
//3通过设置好的窗口来创建
HWND hWnd=CreateWindow( //返回值是一个窗口句柄
L"myclass", //使用注册好的窗口类
L"第一个窗口", //窗口的显示名称
WS_OVERLAPPEDWINDOW, //窗口显示类型:重叠窗口
CW_USEDEFAULT, 0, //窗口左上角的坐标,使用CW_USEDEFAULT,可以省略y
CW_USEDEFAULT, 0, //窗口的高度和宽度,使用CW_USEDEFAULT,可以省略宽度
NULL, //当前的窗口的副窗口
NULL, //菜单句柄,标识当前使用哪个菜单
hInstance, //实例句柄,表示当前窗口属于哪个应用程序
0
);

//4、显示并更新窗口
ShowWindow(hWnd, SW_SHOWNORMAL); //默认不显示,所以,我们需要手动显示
UpdateWindow(hWnd);

//5、编写消息循环
MSG msg = { 0 }; //保存消息的结构体
while (GetMessage(
&msg, //保存信息来源的结构体
NULL, //窗口句柄,指定当前接收哪个窗口的消息,NULL表示所有
0,0)) //表示想要接收消息的范围0,表示所有
{
//将消息结构体中的前四个参数传递给对应窗口类中填写 的回调函数
DispatchMessage(&msg);

//
}
return 0;
}

二、练习题

题:钢琴

描述:使用所提供的音乐资源写出可以根据按键弹奏播放对应的曲调

进阶:本地提供一个钢琴谱,根据琴谱自动弹奏

main.cpp//主函数

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <Windows.h>
#include "Data.h"
#pragma comment(lib,"winmm.lib")
//用宏自定义消息类型
#define UM_MYMESSAGE WM_USER+1
//声明
LRESULT CALLBACK WndProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam);
void en_zch()
{
//设置英文输入法
keybd_event(VK_SHIFT, 0, 0, 0);
Sleep(100);
keybd_event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
}


int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPronstance, LPSTR lpCmdLine,int nCmdShow)
{
//注册窗口
WNDCLASS ws = {0};
ws.lpfnWndProc= WndProc;
ws.hInstance=hInstance;
ws.lpszClassName = L"piano";
RegisterClass(&ws);
//创建窗口
HWND hwnd=CreateWindow(L"piano", L"钢琴", WS_OVERLAPPEDWINDOW , 500, 500, 800, 800, NULL, NULL, hInstance, 0);
//更新显示窗口
UpdateWindow(hwnd);
ShowWindow(hwnd,SW_SHOWNORMAL);
//消息泵
MSG msg = { 0 };
while (GetMessage(&msg,0,0,0))
{
DispatchMessage(&msg);
}

return 0;
}

//必须提供窗口回调函数
LRESULT CALLBACK WndProc(
HWND hWnd, //哪个串口的消息
UINT uMsg, //消息的类型
WPARAM wParam, //虚拟键值
LPARAM lParam //消息的附加参数
)
{
//创建Data对象
Data obj;
//使用分支语句处理要操作的消息
switch (uMsg)
{
case WM_CREATE:
{//开场音乐
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\LoopyMusic.wav", NULL, SND_ASYNC | SND_NODEFAULT);

if (obj.ReadData(hWnd))
{
MessageBox(hWnd, L"检测到你有琴谱,是否播放", L"提示", MB_OK|MB_OKCANCEL);
PostMessage(hWnd, UM_MYMESSAGE,0,0);//给自定义消息发送
}
break;
}
//自定义响应消息
case UM_MYMESSAGE:
{
for (int i = 0; i < sizeof(Data::Souce); i++)
{
obj.If_Case(Data::Souce[i]);
}
break;
}
//右上角关闭按钮
case WM_CLOSE:
{
MessageBox(hWnd, L"是否退出钢琴", L"提示", MB_OK);
//销毁当前窗口
DestroyWindow(hWnd);
//结束消息循环
PostQuitMessage(0);
break;
}
//按键按下响应
case WM_KEYDOWN:
{
obj.If_Case(wParam);
}
default:
break;
}
//将不想处理的消息传递给这个函数,进行默认参数
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

Data.h//数据类头文件,

用于本地数据的读取及函数的封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#pragma once
#include <Windows.h>
#include <iostream>
#include <tchar.h>
class Data
{
public:
//接收从本地读取到的字符
char static Souce[50];

public:
//判断传入的字符对应的播放音乐
void If_Case(char c_char);
//读取本地歌谱
BOOL ReadData(HWND hwnd);
};

Data.cpp//数据类函数定义

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#include "Data.h"

char Data::Souce[50] = {};
//从本地读取歌谱
BOOL Data::ReadData(HWND hwnd)
{
FILE* file;
//如果打开成则为0
int Errno = fopen_s(&file, "D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound.txt", "r+");
if (Errno != 0)
{
MessageBox(hwnd, L"错误", 0, 0);
return false;
}
else
{
fgets(Souce,50,file);
//关闭文件
fclose(file);
return true;
}
}
//判断字符对应所播放的音乐
void Data::If_Case(char c_char)
{
switch (c_char)
{
case 'Q':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_Q.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'W':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_W.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'E':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_E.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'R':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_R.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'T':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_T.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'Y':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_Y.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'U':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_U.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'I':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_I.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'O':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_O.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'P':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_P.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'A':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_A.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'S':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_S.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'D':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_D.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'F':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_F.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'G':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_G.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'H':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_H.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'J':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_J.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'K':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_K.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'L':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_L.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'Z':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_Z.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'X':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_X.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'C':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_C.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'V':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_V.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'B':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_B.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'N':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_N.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case 'M':
PlaySoundA("D:\\Project\\WIN_MAIN\\PIANO\\Sound\\Sound_M.wav", NULL, SND_ASYNC | SND_NODEFAULT);
Sleep(800);
break;
case ' ':
Sleep(800);
break;
default:
break;
}
}

题:计算器

描述:自己编写一个计算器能够控制windows系统中的计算器进行操作

main.cpp//主函数

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include <Windows.h>
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
WNDCLASS wc = {0};
wc.lpszClassName = L"my_calc";
wc.lpfnWndProc = WndProc;
wc.hbrBackground =(HBRUSH)COLOR_APPWORKSPACE;

RegisterClass(&wc);

//创建窗口
HWND hwnd=CreateWindow(L"my_calc",L"计算器",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,0,390,600,NULL,NULL,hInstance,NULL);

ShowWindow(hwnd, SW_NORMAL);
UpdateWindow(hwnd);

//消息循环
MSG msg = {0};
while (GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);

DispatchMessage(&msg);
}
return 0;
}
//回调函数消息处理
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
HWND calc = FindWindow(L"CalcFrame", L"计算器");
static HINSTANCE hInstance = (HINSTANCE)GetModuleHandle(NULL);
//HWND calc=FindWindow(NULL,L"计算器" );
//HWND calc = (HWND)0x00140596;
switch (msg)
{
case WM_CREATE:
{
//SendMessage(calc, WM_COMMAND, MAKEWPARAM(0x83, BN_CLICKED), NULL);
//对话框
CreateWindow(L"edit", L" ", WS_CHILD | WS_VISIBLE | WS_BORDER, 10, 10, 350, 180, hwnd, (HMENU)0x1011, hInstance, NULL);
//按键
CreateWindow(L"button", L"清空", WS_CHILD | WS_VISIBLE, 10, 200, 170, 50, hwnd, (HMENU)0x1000, hInstance, NULL);
CreateWindow(L"button", L"退格", WS_CHILD | WS_VISIBLE, 190, 200, 170, 50, hwnd, (HMENU)0x1017, hInstance, NULL);

CreateWindow(L"button", L"1", WS_CHILD | WS_VISIBLE, 10, 260, 80, 50, hwnd, (HMENU)0x1001, hInstance, NULL);
CreateWindow(L"button", L"2", WS_CHILD | WS_VISIBLE, 10, 320, 80, 50, hwnd, (HMENU)0x1002, hInstance, NULL);
CreateWindow(L"button", L"3", WS_CHILD | WS_VISIBLE, 10, 380, 80, 50, hwnd, (HMENU)0x1003, hInstance, NULL);
CreateWindow(L"button", L"4", WS_CHILD | WS_VISIBLE, 100, 260, 80, 50, hwnd, (HMENU)0x1004, hInstance, NULL);
CreateWindow(L"button", L"5", WS_CHILD | WS_VISIBLE, 100, 320, 80, 50, hwnd, (HMENU)0x1005, hInstance, NULL);
CreateWindow(L"button", L"6", WS_CHILD | WS_VISIBLE, 100, 380, 80, 50, hwnd, (HMENU)0x1006, hInstance, NULL);
CreateWindow(L"button", L"7", WS_CHILD | WS_VISIBLE, 190, 260, 80, 50, hwnd, (HMENU)0x1007, hInstance, NULL);
CreateWindow(L"button", L"8", WS_CHILD | WS_VISIBLE, 190, 320, 80, 50, hwnd, (HMENU)0x1008, hInstance, NULL);
CreateWindow(L"button", L"9", WS_CHILD | WS_VISIBLE, 190, 380, 80, 50, hwnd, (HMENU)0x1009, hInstance, NULL);
CreateWindow(L"button", L"0", WS_CHILD | WS_VISIBLE, 10, 440, 170, 50, hwnd, (HMENU)0x0011, hInstance, NULL);
CreateWindow(L"button", L".", WS_CHILD | WS_VISIBLE, 190, 440, 80, 50, hwnd, (HMENU)0x10012, hInstance, NULL);

CreateWindow(L"button", L"+", WS_CHILD | WS_VISIBLE, 280, 260, 80, 50, hwnd, (HMENU)0x10013, hInstance, NULL);
CreateWindow(L"button", L"-", WS_CHILD | WS_VISIBLE, 280, 320, 80, 50, hwnd, (HMENU)0x10014, hInstance, NULL);
CreateWindow(L"button", L"*", WS_CHILD | WS_VISIBLE, 280, 380, 80, 50, hwnd, (HMENU)0x10015, hInstance, NULL);
CreateWindow(L"button", L"/", WS_CHILD | WS_VISIBLE, 280, 440, 80, 50, hwnd, (HMENU)0x10016, hInstance, NULL);
CreateWindow(L"button", L"等于", WS_CHILD | WS_VISIBLE, 10, 500, 350, 50, hwnd, (HMENU)0x10018, hInstance, NULL);
break;
}
case WM_CLOSE:
{
DestroyWindow(hwnd);

PostQuitMessage(0);
break;
}
case WM_COMMAND:
{
switch (LOWORD(wparam))
{
case 0x1011://对话框
break;
case 0x1000://清空 //0x51是控件ID
SendMessage(calc, WM_COMMAND, MAKEWPARAM(0x51, BN_CLICKED), NULL);
break;
case 0x1017://清空
SendMessage(calc, WM_COMMAND, MAKEWPARAM(0x53, BN_CLICKED), NULL);
break;
case 0x1001://数字1
SendMessage(calc, WM_COMMAND, MAKEWPARAM(0x83, BN_CLICKED), NULL);
break;
case 0x1002://数字2
SendMessage(calc, WM_COMMAND, MAKEWPARAM(0x84, BN_CLICKED), NULL);
break;
case 0x1003://数字3
SendMessage(calc, WM_COMMAND, MAKEWPARAM(0x85, BN_CLICKED), NULL);
break;
case 0x1004://数字4
SendMessage(calc, WM_COMMAND, MAKEWPARAM(0x86, BN_CLICKED), NULL);
break;
case 0x1005://数字5
SendMessage(calc, WM_COMMAND, MAKEWPARAM(0x87, BN_CLICKED), NULL);
break;
case 0x1006://数字6
SendMessage(calc, WM_COMMAND, MAKEWPARAM(0x88, BN_CLICKED), NULL);
break;
case 0x1007://数字7
SendMessage(calc, WM_COMMAND, MAKEWPARAM(0x89, BN_CLICKED), NULL);
break;
case 0x1008://数字8
SendMessage(calc, WM_COMMAND, MAKEWPARAM(0x8A, BN_CLICKED), NULL);
break;
case 0x1009://数字9
SendMessage(calc, WM_COMMAND, MAKEWPARAM(0x8B, BN_CLICKED), NULL);
break;
case 0x0011://数字0
SendMessage(calc, WM_COMMAND, MAKEWPARAM(0x82, BN_CLICKED), NULL);
break;
case 0x0012://小数点
SendMessage(calc, WM_COMMAND, MAKEWPARAM(0x54, BN_CLICKED), NULL);
break;
case 0x0013://运算符 +
SendMessage(calc, WM_COMMAND, MAKEWPARAM(0x5D, BN_CLICKED), NULL);
break;
case 0x0014://运算符 -
SendMessage(calc, WM_COMMAND, MAKEWPARAM(0x5E, BN_CLICKED), NULL);
break;
case 0x0015://运算符 *
SendMessage(calc, WM_COMMAND, MAKEWPARAM(0x5C, BN_CLICKED), NULL);
break;
case 0x0016://运算符 /
SendMessage(calc, WM_COMMAND, MAKEWPARAM(0x5B, BN_CLICKED), NULL);
break;
case 0x0018://运算符 =
SendMessage(calc, WM_COMMAND, MAKEWPARAM(0x79, BN_CLICKED), NULL);
break;
default:
break;
}
}
default:
break;
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}

题:拦截控件消息

控件的窗口类,是系统自己注册的,所以,控件的消息处理函数,也是系统自己的。我们如果想要得到控件消息,就需要截获到控件的消息处理函数。

GetClassLong 能够获取一个已经注册的窗口的类中的属性

SetClassLong 能够设置一个窗口,对应的窗口类中的属性

示例:我们使用SetClassLong实现修改按钮的光标

//修改按钮的cursor属性

1
2
HCURSOR hCursor = LoadCursor(g_hInstance, MAKEINTRESOURCE(IDC_CURSOR1));
SetClassLong(hButton4, GCL_HCURSOR, (LONG)hCursor);

//修改控件回调

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include <windows.h>
// 旧的窗口回调,
WNDPROC g_oldProc = NULL;
LRESULT CALLBACK ButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_LBUTTONDOWN:
MessageBox(0, L"你被hook了", L"", 0);
break;
default:
break;
}
// 调用默认的处理函数
return CallWindowProc(DefWindowProc, hWnd, uMsg, wParam, lParam);
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
CreateWindow(L"button", L"按钮1", WS_CHILD | WS_VISIBLE,
10, 10, 50, 20, hWnd, (HMENU)0x1001, GetModuleHandle(NULL), NULL);
CreateWindow(L"button", L"hook", WS_CHILD | WS_VISIBLE,
10, 40, 50, 20, hWnd, (HMENU)0x1002, GetModuleHandle(NULL), NULL);
CreateWindow(L"button", L"unhook", WS_CHILD | WS_VISIBLE,
10, 70, 50, 20, hWnd, (HMENU)0x1003, GetModuleHandle(NULL), NULL);
break;
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case 0x1001:
MessageBox(0, L"我是按钮", L"", 0);
break;
case 0x1002:
{
// 获取按钮1 的句柄
HWND hButton1 = GetDlgItem(hWnd, 0x1001);
// 替换原本的消息回调
g_oldProc =
(WNDPROC)SetWindowLong(hButton1, GWL_WNDPROC, (LONG)ButtonProc);
break;
}
case 0x1003:
{
// 获取按钮1 的句柄
HWND hButton1 = GetDlgItem(hWnd, 0x1001);
// 替换原本的消息回调
SetWindowLong(hButton1, GWL_WNDPROC, (LONG)g_oldProc);
break;
}
default:
break;
}
}
default:
break;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{
// 1.注册窗口类
WNDCLASS wc = {};
// 窗口回调
wc.lpfnWndProc = WndProc;
// 窗口类名
wc.lpszClassName = L"myClass";
RegisterClass(&wc);

// 2.创建窗口
HWND hWnd = CreateWindow(L"myClass", L"我的窗口", WS_OVERLAPPEDWINDOW,
40, 40, 300, 400, NULL, NULL, hInstance, NULL);

// 3.更新显示窗口
UpdateWindow(hWnd);
ShowWindow(hWnd, SW_SHOW);

// 4.建立消息循环
MSG msg = {};
while (GetMessage(&msg, 0, 0, 0))
{
DispatchMessage(&msg);
}
}

题:校园学生登录

里面有图片控件是直接在可视化界面导入的

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#include <Windows.h>
#include <tchar.h>
#include "resource.h"
#include <comdef.h>
#include <Commctrl.h>//通用控件必备头
//全局的实例句柄
static HINSTANCE g_hInstance;
//全局的父窗口句柄
HWND g_hDlg;
//非模态回调
INT_PTR CALLBACK DlgProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam);
//手动回调
LRESULT CALLBACK WndPorc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE lProInstance, LPSTR lpCmdLine, int nCmdShow)
{
g_hInstance = hInstance;
HWND hDlg=CreateDialog(hInstance,MAKEINTRESOURCE(IDD_DIALOG1),NULL, DlgProc);
g_hDlg = hDlg;
ShowWindow(hDlg,SW_SHOW);
MSG msg = {};
while (GetMessage(&msg,0,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

//回调
INT_PTR CALLBACK DlgProc(HWND hWnd,UINT Message,WPARAM wParam,LPARAM lParam)
{
switch (Message)
{
case WM_CLOSE:
{
PostQuitMessage(0);
}
break;
case WM_CREATE:
{
}
break;
case WM_COMMAND:
{

switch (LOWORD(wParam))
{
case IDOK2://登录控件
{
HWND hBt = GetDlgItem(hWnd, IDOK2);
//SendMessage(hBt, BM_GETCHECK,0,0);
//MessageBox(hBt,L"无法连接服务器",L"提示",1);
//获取文本框句柄
HWND hedit_user = GetDlgItem(hWnd, IDC_EDIT3);
HWND hedit_passwd = GetDlgItem(hWnd, IDC_EDIT2);
//定义文本框获取缓冲区
WCHAR c_User[20] = {0};
WCHAR c_Passwd[20] = {0};
//获取编辑框中是文本
GetWindowText(hedit_user, c_User,20);
GetWindowText(hedit_passwd, c_Passwd, 20);
WCHAR C_MAX[100] = {0};
//将用户名转换成char*用于使用strcmp函数的比较
_bstr_t c_UTemp(c_User);//将WHCHAR* 转换位char*
_bstr_t c_PTemp(c_Passwd);//将WHCHAR* 转换位char*
//设置一个可以登录的用户名或密码
const char* u_temp = "admin";
const char* p_temp = "123456";
//int i = strcmp(c_UTemp, u_temp);
if ((strcmp(c_UTemp, u_temp)==0 && strcmp(c_PTemp, p_temp)==0))//等于0说明账户名和密码都相等,验证成功
{
ShowWindow(hWnd,SW_HIDE);//隐藏主窗口

HWND hMain = CreateDialog(g_hInstance,MAKEINTRESOURCE(IDD_DIALOG2) ,g_hDlg, DlgProc);
//初始化界面
//1,初始化图片
HBITMAP hBit_map = LoadBitmap(g_hInstance, MAKEINTRESOURCE(IDB_BITMAP1));
HWND hPic=GetDlgItem(hMain, IDC_STATIC1);//获取图片框资源句柄
SendMessage(hPic, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBit_map);
//2,初始化列表
//////////////////////////////////////////////////////////////////
//1. 获取列表框的句柄
HWND hListCtrl = GetDlgItem(hMain, IDC_LIST3);
ListView_SetExtendedListViewStyle(hListCtrl, LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
//添加第一列
LVCOLUMN lv = {};
lv.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
lv.cx = 112;//112正好对应该窗口的宽度为4列
lv.pszText = (TCHAR*)_T("年份");
lv.fmt = LVCFMT_CENTER;
ListView_InsertColumn(hListCtrl, 0, &lv);
//添加第二列
lv.pszText = (TCHAR*)_T("时间");
ListView_InsertColumn(hListCtrl, 1, &lv);
//添加第三列
lv.pszText = (TCHAR*)_T("经历");
ListView_InsertColumn(hListCtrl, 2, &lv);
//添加第四列
lv.pszText = (TCHAR*)_T("备注");
ListView_InsertColumn(hListCtrl, 3, &lv);

LVITEM li = {};
li.mask = LVIF_TEXT;
li.iItem = 0;
li.pszText = (TCHAR*)_T("2009");
//添加一行,但是没有设置本行单元格的内容
ListView_InsertItem(hListCtrl, &li);
//要设置单元格的内容
ListView_SetItemText(hListCtrl, 0, 1, (TCHAR*)_T("9月5日"));
ListView_SetItemText(hListCtrl, 0, 2, (TCHAR*)_T("清华大学附中就读"));
ListView_SetItemText(hListCtrl, 0, 3, (TCHAR*)_T("班长一职"));
//添加第二行
li.mask = LVIF_TEXT;
li.iItem = 1;
li.pszText = (TCHAR*)_T("2001");
//添加一行,但是没有设置本行单元格的内容
ListView_InsertItem(hListCtrl, &li);
//要设置单元格的内容
ListView_SetItemText(hListCtrl, 1, 1, (TCHAR*)_T("9月5日"));
ListView_SetItemText(hListCtrl, 1, 2, (TCHAR*)_T("清华大学"));
ListView_SetItemText(hListCtrl, 1, 3, (TCHAR*)_T("学生会主席"));
////////////////////////////////////////////////////////////////


ShowWindow(hMain, SW_SHOW);

}
else
{
_stprintf_s(C_MAX, _T("账号或密码错误,请确认!\n\t账号:%s\n\t密码:%s"), c_User, c_Passwd);
MessageBox(hWnd, C_MAX, L"提示", 1);
}
}
break;

default:
break;
}
}
break;
case WM_NOTIFY:
{
//1. 先获取小结构体中的信息,做大致判断
LPNMHDR pNm = (LPNMHDR)lParam;
if (pNm->idFrom == IDC_LIST3 && pNm->code == NM_CLICK)
{

}
}
break;
case WM_RBUTTONDOWN:
{

}
default:
break;
}
return 0;
}
//手动回调
LRESULT CALLBACK WndPorc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
//将不想处理的消息传递给这个函数,进行默认参数
return DefWindowProc(hWnd, Message, wParam, lParam);
}

三、SDK详解

常用字符串处理函数

ASCLL版 UNICODE版 T版
获取长度 strlen wcsnlen _tcslen
字符串拷贝 strcpy_s wcscpy_s _tcscpy_s
字符转数字 atoi _wtoi _tstoi
字符转数字 sscanf_s swscanf_s _stscanf_s
数字转字符 sprintf_s swprintf_s _stprintf_s

调试输出

//变参函数,用于在输出款输出信息

1
2
3
4
5
6
7
8
9
10
11
12
bool _trace(const TCHAR* format, ...) 
{
TCHAR buffer[1000];
va_list argprt;
va_start(argprt, format);
//将格式化信息写入指定缓冲区
wvsprintf(buffer, format, argprt);
va_end(argprt);
//将缓冲区信息输出
OutputDebugString(buffer);
return true;
}

//第一天所用例题

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <Windows.h>
#include <tchar.h>



bool _trace(const TCHAR* format, ...) //变参函数
{
TCHAR buffer[1000];
va_list argptr;
va_start(argptr, format);
//将格式化信息写入指定的缓冲区
wvsprintf(buffer, format, argptr);
va_end(argptr);
//将缓冲区信息输出
OutputDebugString(buffer);
return true;
}

bool MyMessageBox(const TCHAR* format, ...) //变参函数
{
TCHAR buffer[1000];
va_list argptr;
va_start(argptr, format);
//将格式化信息写入指定的缓冲区
wvsprintf(buffer, format, argptr);
va_end(argptr);
//将缓冲区信息输出
MessageBox(0,buffer,_T("提示"),1);
return true;
}
void MyGetErrorInfo(LPCTSTR lpErrInfo,
UINT unErrCode,
UINT unLine) // unLine=__LINE__
{
LPTSTR lpMsgBuf = nullptr;
WCHAR szMessage[128] = { 0 };
WCHAR szCaption[32] = { 0 };

FormatMessage(0x1300, NULL, unErrCode,
0x400, (LPTSTR)&lpMsgBuf, 64, NULL);

swprintf_s(szMessage, 128,
L"Error_0x%08X:%s", unErrCode, lpMsgBuf);

swprintf_s(szCaption, 32,
L"%s (Error Line:%05d)", lpErrInfo, unLine);
MessageBox(NULL, szMessage, szCaption, MB_OK);
}

int APIENTRY WinMain(
HINSTANCE hInstance,
HINSTANCE hPreInstance,
LPSTR lpCmdLine,
int nShow
)
{
OutputDebugString(_T("你好 呵呵"));

_trace(_T("有两个数,分别是%d和%s"), 100, L"你好");

MyMessageBox(_T("有两个数,分别是%d和%s"), 100, L"你好");

//GetStdHandle(STD_OUTPUT_HANDLE);
HANDLE hOut = GetStdHandle(100);
int nError = GetLastError();
nError = 10;
MyGetErrorInfo(L"十五派友情提示", nError, __LINE__);
return 0;
}

数据类型

常用数据类型

Windows 数据类型 描述信息
UINT 无符号32位整数
DWORD 整数
PDWORD 指向32位值的指针
BOOL 布尔(true/false)
SHORT 带符号16位整数
LPSTR 字符串指针
LPCSTR 字符串常量指针
WPARAM 32位的消息参数
LPARAM 32位的消息参数
LRESULT 32位函数返回值
HWND 窗口句柄

常用控件

标准控件

响应消息:WM_COMMAND

控件时常见的窗口上的交互元素

当控件的特定功能被触发后,会主动发消息通知父窗口

父窗口可以通过发消息给控件

例如:控件的创建(button时按钮控件)

1
CreateWindow(L"button", L"控件名", WS_CHILD | WS_VISIBLE, 280, 440, 80, 50, hwnd, (HMENU)0x10016, hInstance, NULL);

标准控件时windows提供的基本控件,使用方式也比较简单,一下时常用的标准控件

窗口类名 控件名 英文
“button” 按钮 Button
“button” 复选框 CheckBox
“button” 单选框 RadioButton
“static” 静态文本 Static Text
“static” 图片 Picture Control
“combobox” 复合框 ComBox
“edit” 编辑 Edit
“listbox” 列表框 ListBox

滚动条 Slider Control控件

1
2
3
4
5
6
7
8
9
10
11
case WM_HSCROLL://当水平的滑块滑动时,会产生这个消息
{
int nPos = 0;
//1. 先获取滑块的句柄
HWND hSlider = GetDlgItem(hWnd, IDC_SLIDER1);
//2. 给滑块发消息 接收滑块移动的位置
nPos = SendMessage(hSlider, TBM_GETPOS, 0, 0);
}

//给滑块发消息,滑块句柄, 消息类型, 重绘,进度位置
//SendMessage(hSlider, TBM_SETPOS, true, 0);

进度条

响应的还是WM_COMMAND消息,这个消息的WPARAM的低位时控件ID

1
2
3
4
5
6
7
8
9
10
11
static int nPos = 0;
//1. 获取进度条控件的句柄
HWND hProcess = GetDlgItem(hWnd, IDC_PROGRESS1);

//2. 给进度条控件发消息,设置进度
SendMessage(hProcess, PBM_SETPOS, nPos, 0);
nPos += 20;
if (nPos>100)
{
nPos = 0;
}

通用控件

窗口类名 控件
WC_LISTVIEW 列表框控件
WC_TREEVIEW 树控件
WC_TABCONTROL Tab控件
HOTLEY_CLASS 热键控件

列表框控件的创建

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
//根据ID获取列表框控件的句柄
HWND hListCtrl = GetDlgItem(hMain, IDC_LIST3);
//更改列表框的风格
ListView_SetExtendedListViewStyle(hListCtrl, LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
//添加第一列
LVCOLUMN lv = {};
lv.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
lv.cx = 112;//112正好对应该窗口的宽度为4列
lv.pszText = (TCHAR*)_T("年份");
lv.fmt = LVCFMT_CENTER;
ListView_InsertColumn(hListCtrl, 0, &lv);
//添加第二列
lv.pszText = (TCHAR*)_T("时间");
ListView_InsertColumn(hListCtrl, 1, &lv);
//添加第三列
lv.pszText = (TCHAR*)_T("经历");
ListView_InsertColumn(hListCtrl, 2, &lv);
//添加第四列
lv.pszText = (TCHAR*)_T("备注");
ListView_InsertColumn(hListCtrl, 3, &lv);

LVITEM li = {};
li.mask = LVIF_TEXT;
li.iItem = 0;
li.pszText = (TCHAR*)_T("2009");
//添加一行,但是没有设置本行单元格的内容
ListView_InsertItem(hListCtrl, &li);
//要设置单元格的内容
ListView_SetItemText(hListCtrl, 0, 1, (TCHAR*)_T("9月5日"));
ListView_SetItemText(hListCtrl, 0, 2, (TCHAR*)_T("清华大学附中就读"));
ListView_SetItemText(hListCtrl, 0, 3, (TCHAR*)_T("班长一职"));
//添加第二行
li.mask = LVIF_TEXT;
li.iItem = 1;
li.pszText = (TCHAR*)_T("2001");
//添加一行,但是没有设置本行单元格的内容
ListView_InsertItem(hListCtrl, &li);
//要设置单元格的内容
ListView_SetItemText(hListCtrl, 1, 1, (TCHAR*)_T("9月5日"));
ListView_SetItemText(hListCtrl, 1, 2, (TCHAR*)_T("清华大学"));
ListView_SetItemText(hListCtrl, 1, 3, (TCHAR*)_T("学生会主席"));

窗口风格

响应消息:WM_NOTIFY

窗口标准风格之

三大窗口风格

重叠窗口 弹出窗口 子窗口
WS_OVERLAPPEDWINODW WS_POPUPWINDOW WS_CHILDWINDOW

常用函数

函数名 说明
CreateWindow 使用窗口类,创建窗口,创建控件
RegisterClass 注册窗口类
DialogBox 使用资源创建一个模态对话框
MoveWindow 移动窗口到指定位置
ShowWIndow 隐藏(SW_HWID)或显示(SW_SHOW)窗口
GetWindowText 获取窗口标题
SetWindowText 设置窗口标题
SetParent 更改指定窗口的父窗口
TrackPopupMeun 弹出菜单(坐标需要转换,需要获取子菜单的句柄)
GetSubMenu 获取指定子菜单的句柄
GetDlgItem 根据控件的ID获取子控件句柄
GetParent 获取父窗口句柄
FindWindow 找到一个窗口,获取其句柄

常用资源

想要操作资源需先获得资源的句柄

图标 Icon

  • 需要有一个图标资源

  • LoadIcon根据资源ID得到句柄

  • 将句柄填充到窗口类上

    1
    2
    3
    4

    HBITMAP hBit_map = LoadBitmap(g_hInstance, MAKEINTRESOURCE(IDB_BITMAP1));
    HWND hPic=GetDlgItem(hMain, IDC_STATIC1);//获取图片框资源句柄
    SendMessage(hPic, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBit_map);

光标 Cursor

  • 需要有一个光标资源
  • LoadCursor根据ID得到句柄
  • 将句柄填充到窗口类上

位图

  • 配合picture控件使用

菜单

  • 下拉菜单

    • 有一个菜单资源
      • 填充到窗口类中 直接填
      • 填充到创建窗口的参数上,需要LoadMenu获取到句柄
  • 弹出菜单

    • TrackPopupMenu 提供坐标以及菜单的句柄

      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
      case WM_RBUTTONDOWN:
      {
      //这里得到的是窗口坐标
      DWORD dwX = LOWORD(lParam);
      DWORD dwY = HIWORD(lParam);
      //我们需要将窗口坐标转换为屏幕坐标
      POINT pt = { dwX ,dwY };
      ClientToScreen(hWnd, &pt);

      HMENU hMenu = LoadMenu(g_hInstance, MAKEINTRESOURCE(IDR_MENU2));

      //获取子菜单的句柄
      HMENU hSubMenu = GetSubMenu(
      hMenu, //父级菜单句柄
      1);

      TrackPopupMenu(
      hSubMenu,
      TPM_LEFTALIGN,
      pt.x,
      pt.y,
      0,
      hWnd,
      NULL
      );
      }
      break;

对话框资源

可视化编程

  • DialogBox 模态对话框 会阻塞住父窗口
  • CreateDialog 非模态对话框 不会阻塞父窗口

模态对话框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<Windows.h>
#include <tchar.h>
#include "resource.h"
INT_PTR CALLBACK DlgProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
return 0;
}
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPreInstance,
LPSTR lpCmdLine,
int nCmdShow
)
{
DialogBox(
hInstance,
MAKEINTRESOURCE(IDD_DIALOG1),
NULL,
DlgProc
);
return 0;
}

非模态对话框

CreateDialog

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
#include<Windows.h>
#include <tchar.h>
#include "resource.h"
INT_PTR CALLBACK DlgProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
return 0;
}
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPreInstance,
LPSTR lpCmdLine,
int nCmdShow
)
{
HWND hDlg = CreateDialog(
hInstance,
MAKEINTRESOURCE(IDD_DIALOG1),
NULL,
DlgProc
);
//显示窗口
ShowWindow(hDlg, SW_SHOW);
MSG msg = {};
// 消息循环
while (GetMessage(
&msg, //获取到消息的结构体
0, //获取哪一个窗口的消息,为0就是所有窗口
0,
0)
)
{
// 5.2翻译消息
TranslateMessage(&msg);
// 5.3转发到消息回调函数
DispatchMessage(&msg);
}
return 0;
}

对话框控件的使用

单选框

有一个重要操作,需要记住:分组

ctrl+D之后将同一组的radio都编为连续的序号。

将每组的第一个radio设置group为true

静态文本

一个提示语直接拖拽即可

静态图片

能够显示一个bitmap的图片

需要一个picture的控件,然后去设置两个属性

一个bmp格式的图片资源

1
2
3
4
//1,初始化图片
HBITMAP hBit_map = LoadBitmap(g_hInstance, MAKEINTRESOURCE(IDB_BITMAP1));
HWND hPic=GetDlgItem(hMain, IDC_STATIC1);//获取图片框资源句柄
SendMessage(hPic, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBit_map);

四、SDK问题

问答1

1、什么是windows 错误码?应该在什么时候获取? 使用什么函数获取? 获取到之后如何
查看其所代表的内容?
答:windows 错误码是不同的API有不同类型的错误返回值,在API结束之后获取,可以使用SetLastError函数获取,使用错误查找工具或者在VS监视栏中输入“err,hr”查看。
2、如何为自己的函数定义windows 错误码,使用哪一个函数?
答:SetLastError函数。
3、在Virtual Stodio 中,在监视栏中输入什么字符,能够方便的查看错误码?
答:err,hr字符。
4、什么是句柄? 几个字节? 有什么用?
答:在程序设计中,句柄是一种特殊的智能指针。当一个应用程序要引用其他系统(如数据库、操作系统)所管理的内存块或对象时,就要使用句柄。在windows编程中,一个句柄是指使用的一个唯一的整数值,即一个4字节(64位程序中为8字节)长的数值,来标识应用程序中的不同对象和同类中的不同的实例,诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等。
5、什么是实例? WinMain 函数的四个参数分别代表什么意思?
答:实例诸如一个窗口,按钮,图标,滚动条等,hInstance是程序实例的句柄,它是程序的加载地址,hPrevInstance这个参数已经废弃,IpCmdLine是由调用者传入的命令,nCmdShow是由调用者传入的int型参数。
6、请问TCHAR,_tcslen 本质是什么?由谁控制? 使用通用字符需要包含什么头文件?
答:T版的数据或者函数,本质上还是ascii编码或者unicode的编码,主要靠#indefine和typedef实现的,它只由编译环境自动选择,tchar.h头文件。
7、窗口回调函数的原型是什么?
答:原型如下:
LRESULT CALLBACK WindowProc(
HWND hwnd, //窗口句柄
UINT uMsg, //消息ID
WPARAM wParam, //消息参数1
LPARAM lParam //消息参数2
);
8、使用SPY++查看QQ 登录框类名是什么? 聊天窗口类名是什么?
答: TXGuiFoundation TXGuiFoundation
9、窗口类结构体中hbrBackground 有什么用? 请尽量详细的说明。
答:hbrBackground 可以刷新背景所用的画刷的句柄。Windows定义六种现有画刷:WHITE_BRUSH、LTGRAY_BRUSH、GRAY_BRUSH、DKGRAY_BRUSH、BLACK_BRUSH和NULL_BRUSH (也叫HOLLOW_BRUSH)。您可以将任何一种现有画刷选入您的装置内容中,就和您选择一种画笔一样。Windbws将HBRUSH定义为画刷的代号,所以可以先定义一个画刷代号变数:
HBRUSH hBrush ;
您可以通过呼叫GetStockObject来取得GRAY_BRUSH的代号:
hBrush = GetStockObject (GRAY_BRUSH) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (GRAY_BRUSH);//设置窗口背景画刷的句柄

10、什么是队列消息? 什么是非队列消息? 请各举出三个例子。
答:队列消息包括系统和线程的消息队列,队列送到系统消息队列,然后送到线程消息队列,而非队列消息是直接送给目的窗口的过程。队列消息如WM_MOUSERMOVE,WM_CHAR,WM_PAINT。非队列消息有WM_ACTIVATE,应用系统发送WM_WINDOWPOSCHANGED,SendMessage函数也会发送非队列消息。

11、PostMessage 和SendMessage 有什么区别?
答:PostMessage只是把消息放入队列,不管其他程序是否处理都返回,然后继续执行;而SendMessage必须等待其他程序处理消息后才返回,继续执行。PostMessage的返回值表示PostMessage函数执行是否正确;而SendMessage的返回值表示其他程序处理消息后的返回值。 PostMessage会造成消息的滞后性,而SendMessage则不会,但如果SendMessage消息处理失败,则会造成程序停止。

12、下面这个结构体是窗口类结构体,请分别说明,各个字段是什么含义。
答:各字段含义见注释。
typedef struct tagWNDCLASSW {
UINT style;//窗口类的风格(无符号32位整数)
WNDPROC lpfnWndProc;//指向窗口过程的指针
int cbClsExtra;//分派给窗口类的扩展的字节数
int cbWndExtra;//分派给窗口实例的扩展的字节数
HINSTANCE hInstance;.//实例句柄
HICON hIcon;//类图标的句柄
HCURSOR hCursor;//类鼠标指针的句柄
HBRUSH hbrBackground;//刷新背景所用的画刷的句柄
LPCWSTR lpszMenuName;//窗口类包含的菜单的名称
LPCWSTR lpszClassName;//窗口类名
}
WNDCLASSW

13、以下消息分别在什么时候触发
WM_LBUTTONDOWN 点击鼠标左键
WM_PAINT 绘制对象时
WM_CLOSE 点击关闭程序按钮

==================

1、窗口风格有三大风格,分别是什么? 各有什么特点?
答:窗口三大风格是重叠窗口(Overlapped Window),弹出窗口(Popup Window),子窗口(Child Window),重叠窗口是顶级窗口,是缺省类型,它有边框,标题栏,客户区等,还有其他组件;弹出窗口也是顶级窗口,通常用于对话框或者Message对话框,它具有WS_POPUP窗口风格,隐含带有WS_CLIPSIBLINGS窗口风格。

2、WM_COMMAND 作为控件的通知消息,wParam 与lParam 分别有什么含义?
答:wParam是指32位的消息参数,是一个消息有关的常量值,也可能是窗口或控件的句柄, lParam 也是指32位的消息参数,通常是一个指向内存中数据的指针。

3、什么是模态对话框? 什么是非模态对话框? 用什么创建?,
答:模态对话框创建后一定要在用户关闭对话框后,其父窗口才能响应用户操作,否则父窗口便无法响应任何用户的操作,模态对话框函数自带消息循环;非模态对话框则是创建完后其父窗口不需要像模态窗口那样等到对话框关闭才能响应用户的操作,一样可以在对话框未关闭前响应用户操作,非模态对话框函数需要自己写消息循环。模态对话框使用DialogBox创建,非模态对话框使用CreateDialog完成。

4、控件既可以用代码创建,也可以在对话框资源上拖拽,创建控件的函数是什么?。
答:创建控件的函数是CreateWindow。

5、WM_NOTIFY 和WM_COMMAND 的区别。
答:WM_COMMAND和WM_NOTIFY都是控件通知消息,WM_NOTIFY和WM_COMMAND相比,是一种更灵活的消息格式,lParam中放的是一个称为NMHDR结构的指针。在wParam中放的则是控件的ID。最初Windows 3.x就有的控件,如Edit,Combo,List,Button等,发送的控件通知消息的格式是WM_COMMAND;而后期的Win32通用控件,如List View,Image List,IP Address,Tree View,Toolbar等,发送的都是WM_NOTIFY控件通知消息。

6、使用通用(扩展)控件的准备工作都有哪些?
答:在15版本以下的VS使用通用控件需要先包含<CommCtrl.h>,引入 #pragma comment(lib,”comct132.lib”),调用InitCommonControls初始化通用控件,使用CreateWindowEx函数创建通用控件。

==================
1、什么是API? 什么是动态链接库? 写出常见的动态链接库及其大体功能。
答:API是指API(Application Programming Interface,应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组历程的能力,而又无需访问源码,或理解内部工作机制的细节。
动态链接库的英文名称叫DynamicLinkableLibrary,它是microsoft在windows操作系统中实现共享函数库概念的一种实现方式。大多数与windows相关的磁盘文件如果不是程序模块,就是动态链接程序。
常见的动态链接库及其大体功能:
kernel32.dll Windows9x/Me中非常重要的32位动态链接库文件,属于内核级文件。它控制着系统的内存管理、数据的输入输出操作和中断处理
user32.dll Windows用户界面相关应用程序接口,用于包括Windows处理,基本用户界面等特性,如创建窗口和发送消息。
gdi32.dll Windows GDI图形用户界面相关程序,包含的函数用来绘制图像和显示文字
winspool.dll 一款文本编辑软件的库文件
comdlg32.dll Windows应用程序公用对话框模块,用于例如打开文件对话框

都包括什么?
2、你所知道的文件操作
答:文件操作包括新建打开文件CreateFile,删除指定文件DeleteFile,复制文件CopyFile,ReadFile读取文件,WriteFile写文件,GetFileAttribute获取文件属性,SetFileAttribute设置文件或目录的属性。

3、什么是进程? 什么是线程?
答:进程是资源的分配和调度的一个独立单元,而线程是CPU调度的基本单元,同一个进程中可以包括多个线程,并且线程共享整个进程的资源(寄存器、堆栈、上下文),一个进程至少包括一个线程。

4、Dll 可以导出什么?
答:Dll可以导出类和函数。

5、在编写dll 时,当我们想要导出一个东西时,有哪些方式? 分别如何实现?
答:一、可以使用_declspec(dllexport),在函数声明前加_declspec(dllexport)。二、使用模块定义文件(扩展名为.def)导出,文件里面写
LIBRARY
DESCRIPTION “DLL”
EXPORTS
函数名

6、我们使用dll 的时候,通常有两种方式。分别如何实现?
答:见上一题。

7、什么叫做对象库? 什么叫做导入库?
答;对象库是一个扩展名为.LIB的文件,在这个文件的代码在运行链接器进行静态链接时被添加到程序的.EXE文件中。导入库是一种特殊的形式的对象库文件,后缀名和对象库一样,链接器用它来解析源代码中的函数调用。但是导入库不包含任何代码,它们只给链接器提供信息来建立EXE文件中用于动态链接的重定位表格。

8、什么叫做名称粉碎? 有什么用处?
答:名称粉碎是函数名称相同的函数在C中出现重定义,然而在C++中用于函数重载,相同的名称会粉碎成不同的函数内存地址,从而区别调用的是什么函数,由此可以实现多态。

问答2

  1. 怎么将”123”转换成123? 反过来呢?

    使用sscanf_s函数将字符转换位数字,宽版使用stscanf_s

    反过来就是先转换为数字后,反向遍历

    (老师批注:stscanf_s是通用版本,根据项目默认编码方式自动匹配,wsprintf是宽版,用到通用版本才需要tchar,单纯用宽版函数是不需要tchar.h的)

  2. “哈哈”和L”哈哈”一样吗? 哪里不一样? 怎么转换?

    答:不一样,窄版和宽版,如果需要使用宽板只需在字符前加上L即可,但需包含头文件tchar.h

  3. 创建窗口的流程是什么?

    1,使用WNDCLASS结构体定义窗口

    2,将定义的窗口注册

    3,将注册的窗口创建出来

    4,更新并显示窗口

    5,创建消息循环

    6,回调

  4. 怎么响应窗口消息?

    答:不同的消息有不同的响应方式,例如:当窗口创建的时候会产生WM_CREATE消息,点击按钮(button)时会产生WM_COMMAND消息,

  5. wParam和lParam保存了什么信息?

    答:如在响应WM_COMMAND消息时WPARAM的高位是通知码,低位是ID,LPARAM保存的是句柄

  6. 怎么响应控件的单击消息?

    答:单击控件时会产生WM_COMMAND消息,在这个消息中他的WPARAM的低位是被单机控件的ID,把这个ID找出来后,通过这个ID使用GetDlgItem这个函数获取该控件的句柄从而进行想要的消息响应

  7. 如何创建单选框?

    在工具栏中拖拽,然后通过WPARAM的低位ID响应,然后通过WPARAM的高位通知码获取单选框是否被点击

  8. 怎么将单选框设置为选中状态?

    答:给单选框发送一个SendMessage消息第二参数BM_SETCHECK第三个参数就是设置选中的状态码1

  9. 如何获取复选框是否被选中?

    使用BM_GETCHECK参数给复选框发送SendMessage消息,用BOOL值接收

  10. 如何给窗口添加主菜单?

    答:资源创建,通过ID获取句柄,创建窗口时加上这个句柄

  11. 怎么在程序中加载图片资源?

    答:

    //加载图片

    HBITMAP hBit_map = LoadBitmap(g_hInstance, MAKEINTRESOURCE(IDB_BITMAP1));

    //获取图片框资源句柄

    HWND hPic=GetDlgItem(hMain, IDC_STATIC1);

    //设置图片

    SendMessage(hPic, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBit_map);

  12. 怎么在窗口中弹出右键菜单

    在WM_RBUTTONDOWN消息中响应右击,在这个消息中获取右击的坐标,使用这个坐标弹出窗口(TrackPopupMeun)

  13. 怎么设置和获取编辑框的文本

    获取编辑框句柄GetDlgItem

    创建接收文本的缓冲区WCHAR 数组

    获取编辑款文本GetWindowText

  14. 下拉框怎么添加文本?

    获取句柄在使用ComboBox_AddString函数添加文本

  15. 怎么结束一个对话框?

    EndDiaLog

  16. 怎么获取或修改窗口的窗口风格?

    获取需要修改控件的句柄,然后使用SetClassLong修改

  17. 怎么替换一个控件的消息回调函数?

    ​ 控件句柄 修改的属性 回调函数

    SetWindowLong(hButton1, GWL_WNDPROC, (LONG)ButtonProc);

  18. 怎么创建DLL工程?

    在新建的时候选择dll,

  19. 怎么导出函数?

    两种导出方法

    在头文件中声明导出_declspec(dllexport)

    模块定义导出,添加一个def文件,在文件中写入EXPORTS(后跟需要导出的函数,且函数后标号)

    之后再属性配置里–>连接器–>输入,中有一个模块定义文件,把这个选项值写成.def文件全称(xxx.def)

  20. 怎么调用从DLL导出的函数?

    两种调用方式

    隐式调用

    包含头文件,头文件中含有导出函数的声明。

       载入lib文件  #pragma  comment(lib,“”);
       直接调用函数即可

    显示调用

    使用LoadLibrary()将目标dll强行加载到进程中。
    在 GetProcAddress()获得函数的地址。然后使用返回的函数指针调用

    (老师批注:dll显式链接方式不要忘了FreeLibrary)

  21. 怎么调试DLL

    (老师批注:可以通过编写调用dll函数的程序进行调试)

  22. exe加载DLL时(使用相对路径),被加载的DLL可以放在任何目录吗,如果不能,那么都能放到哪些目录?

    只能放在相对exe的相对路径中

  23. 怎么使用静态库? 和动态库相比有和区别?

    使用方法是一样的,只是使用静态库编译后的程序会比动态库的程序占用空间大,因为静态库是直接把库文件载入到了程序中,而不是需要从外部调用

五、磁盘信息

//获取盘符的字符串,

GetLogicalDriveStrings( 100,buf);

// 把语言设置为中文

setlocale(LC_ALL, “chs”);

// 获取驱动器类型

,由于这里需要的是一个指针,所以需要在定义一个TCHAR*指针类型指向数组(缓冲区)

用整形接收返回值,

​ 2,代表可移动设备 DRIVE_REMOVABLE

​ 3,硬盘 DRIVE_FIXED

​ 4,远程设备DRIVE_REMOTE

​ 5,光驱 DRIVE_CDROM

GetDriveType(pTemp);

//计算磁盘空间

1
2
3
4
5
GetDiskFreeSpace(pTemp,//盘符
&每簇的扇区数量,
&每个扇区的容量,
&空闲簇的总量,
&全部簇的总量

//查看磁盘的实例代码

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
56
// 02_获取磁盘信息.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。

#include <iostream>
#include <Windows.h>
#include <tchar.h>
int main()
{
//1. 获取驱动器名称
TCHAR buf[100] = {};
TCHAR* pTemp = buf;
GetLogicalDriveStrings( 100,buf);
// 把语言设置为中文
setlocale(LC_ALL, "chs");
while (pTemp[0]!=0)
{

wprintf(_T("%s "), pTemp);
//2. 获取驱动器类型
DWORD dwType = GetDriveType(pTemp);
switch (dwType)
{
case DRIVE_REMOVABLE:
wprintf(_T("可移动设备 "));
break;
case DRIVE_FIXED:
wprintf(_T("硬盘 "));
break;
case DRIVE_REMOTE:
wprintf(_T("远程设备 "));
break;
case DRIVE_CDROM:
wprintf(_T("光驱 "));
break;
default:
break;
}
//3. 驱动器的空间信息
DWORD 每簇的扇区数量 = 0;
DWORD 每个扇区的容量 = 0;
DWORD 空闲簇的总量 = 0;
DWORD 全部簇的总量 = 0;
GetDiskFreeSpace(pTemp,
&每簇的扇区数量,
&每个扇区的容量,
&空闲簇的总量,
&全部簇的总量
);

printf("总容量为%.2lf,空闲容量为%.2lf",
(((全部簇的总量/1024.0) * 每簇的扇区数量 * 每个扇区的容量)/1024)/1024,
(((空闲簇的总量/1024.0) * 每簇的扇区数量 * 每个扇区的容量) / 1024) / 1024);
printf("\n");
pTemp += wcslen(buf)+1;
/*wcslen遇到0会被截断,缓冲区存储的时“C:\0D:\”,所以wcslen(buf)的长度等于3,pTemp+=wcslen(buf)+1之后就是下一个盘符的开始*/
}
}

六、动态链接库

定义 DLL 应用程序的入口点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// dllmain.cpp : 
#include "framework.h"
//dll文件是给他人提供函数所用,不会作为一个程序单独运行,也就没有第一行执行代码的概念
//通常不需要理会DllMain,不会再里面写什么代码
//
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call,LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH: //进程加载
case DLL_THREAD_ATTACH: //线程加载
case DLL_THREAD_DETACH: //线程卸载
case DLL_PROCESS_DETACH: //进程卸载
break;
}
return TRUE;
}

声明导出

在需要导出函数的头文件中声明

extern “C” _declspec(dllexport) void pr();//需要声明的函数即返回类型

注意一定要在cpp文件中包含头文件,不然找不到该函数的定义

模块定义文件导出

添加一个.def文件 ,

1
2
3
EXPORTS
Fun1 @1
Fun2 @2

Fun1和Fun2是函数名,@1表示导出函数的序号

在属性配置里–>连接器–>输入,中有一个模块定义文件,把这个选项值写成.def文件全称(xxx.def)

调用dll中函数的方式,有两种:

隐式调用

​ 包含头文件,头文件中含有导出函数的声明。
​ 载入lib文件 #pragma comment(lib,“”);
​ 直接调用函数即可

1
2
3
4
5
6
7
8
#include "..\Dll1\Dll1.h"
#pragma comment(lib,"..\\Debug\\Dll1.lib")
int main()
{
fun1();
fun2();
system("pause");
}

显示调用

使用显示调用不需要头文件,但是你需要知道函数的原型(返回值类型,各个参数的类型)。
LoadLibrary()将目标dll强行加载到本进程中。
GetProcAddress()获得函数的地址。
自然就可以调用此函数。

1
2
3
4
5
6
7
8
9
10
typedef  void(*FUN)();

FUN g_Fun;
int main()
{
HMODULE hModule = LoadLibrary(L"Dll1.dll");
g_Fun = (FUN)GetProcAddress(hModule, "fun1");
g_Fun();
system("pause");
}

lib文件的差别:

随着dll一起生成的lib文件,称之为导入库,里面是没有代码和数据,是链接信息,供编译器再exe文件与dll文件做链接的。
还有一种是静态对象库,编译的时候会将静态库中的使用到的代码数据编译到exe中,exe发布就不需要携带很多文件。

七、进程

1 内核对象的概念

2 进程,模块,线程

概念

基本操作:

进程 模块 线程的遍历

进程的终止 强制结束 进程间的通讯

线程挂起 恢复 终止 强制结束

3 文件操作

难点:递归遍历文件

4 线程同步:原子操作 临界区 互斥体 事件 信号量

5 同步IO和异步IO

6 基本的网络编程 TCP UDP

7 IOCP模型

概念

一、 如何理解内核对象的概念?

a. windows操作系统的设计是面向对象的,很多的组件都是以对象的方式去管理的。将整个操作系统的内核看成是一个封装体,API函数就是对外的接口。然后通过给API传递句柄去实现操作具体的某一个对象,句柄就是对象的标识。windows管理的对象也有很多类型,不同类型的对象的管理方式是不同的。

b. 所有的API函数都是隶属于不同的动态链接库(DLL文件)的。有三大DLL文件:

User32.dll GDI32.dll kernel32.dll

其中User32.dll和窗口,控件等有关,GDI32和绘制图形有关,Kernel32和操作系统的界面无关的很多机制有关。

这三种DLL创建出来的对象就可以分为3大类。管理方式就不一样。那么我们的内核对象有自己的管理方式。

c. 一个进程,是一个运行中的程序。

一个进程包含了一个运行中的程序所用到的所有资源:一个内存空间 内存空间中有很多的模块 内存空间被分成了两大部分—用户层和内核层

我们正常的普通程序只能访问用户层空间(000000007FFFFFFF)。大部分的对象(结构体变量)都是放在内核空间的(80000000FFFFFFFF)。

所以我们自然不能直接访问到对象,而是通过句柄。

所有的进程内核层是共享的,不同的进程用户层空间是不一样。

2 内核对象有什么特点??

特点1—内核对象是跨进程的:所有的内核对象存在于内核层的。既然内核层是大家共享的,所以内核对象也是可以跨进程的。

特点2—跨进程机制怎么管理呢??:通过引用计数和内核对象句柄表管理的。

进程A的句柄表

1 内核对象A的地址
2 内核对象B的地址

进程B的句柄表

1 内核对象B的地址

进程C的句柄表

1 内核对象M的地址
2 内核对象N的地址
3 内核对象L的地址
4 内核对象B的地址

如果一个内核对象被n个进程使用,引用计数就是n。没有被进程使用,引用计数就是0,没被任何进程使用,内核对象就会自动销毁。

特点3:每一个内核对象都有安全描述属性,我们通过创建对象的API是否包含这样一个参数,就能确定它是不是内核对象

3 通过我们怎么获得内核对象的句柄的

a. 自己创建的

b. 自己打开的(这个对象是已经创建好的)

c. 从父进程继承过来的。

d. 别的进程复制过来的。DuplicateHandle

进程概念:

进程,简单来说,就是一个运行中的程序,包含了:

a. 一个虚拟的内存空间 所有程序的代码和数据都在这片内存空间中。

b. 内存空间中排布了很多的模块

c. 至少有一个线程

在进程的虚拟内存中,一般会加载一个exe,很多的dll。他们都称之为模块。

进程本身是一个综合了各种资源的东西,是不能执行代码,能够执行代码的是归属于进程的线程。

每一个线程都是一个独立的执行单元:

1 每一个线程有自己的一块堆栈。

2 每一个线程有自己的执行环境。

CPU在执行代码的时候,主要是依赖于一套寄存器:

通用寄存器:eax ebx ecx edx esi edi esp ebp

指令指针寄存器:eip 存储着下一条要执行的指令

段寄存器:cs ss ds es fs gs

…..

所有的线程都是操作系统统一去管理调度的,每一个线程都有自己的优先级。根据优先级决定先调用谁后调用谁。

线程发生切换,实际就是切换线程的执行环境,比如现在有A,B,C三个线程,此时线程A在执行,线程A的时间片用完了,就保存A的执行环境,看B和C的优先级谁高,假如是C高,那么就把C的线程环境加载到CPU中。

基本操作:

进程 模块 线程的遍历(非常重要的操作)

方法有很多 我们这里使用的是创建快照的方式:CreateToolHelp32Snapshot。

需要知道的几点:

a. 进程是操作系统管理的,遍历的时候,能够遍历出系统中的所有进程的信息:进程名,路径,进程ID

​ 遍历进程的用处:通常来说我们都是知道进程名,然后去找到ID(ID每一次程序运行的时候都是不一样的),我们如果要操作进程,就需要使用OpenProcess函数得到它的句柄,OpenProcess这个函数,就是根据进程ID得到句柄的。

b. 模块是属于某一个进程的,所以我们遍历模块的时候,需要指定遍历的是哪一个进程的模块。

​ 能够遍历出模块的信息为:模块名,模块的起始虚拟地址(加载基址)

​ 遍历模块的用处:a 可以知道一个程序都加载了哪些DLL,监测DLL注入 b 分析DLL中的PE文件信息,可以为我们分析一个程序提供依据。

c. 线程虽然也是属于某一个进程的,但是其管理是操作系统统一管理的,所以我们遍历的时候,也是遍历出操作系统中的所有线程。需要自己去过滤然后,得到某一个进程的线程。

​ 线程遍历,能够得到的信息有:线程ID,所属进程的ID。

​ 遍历线程的用处:可以得到进程中每一个线程的信息。可以操作这些线程,比如挂起,终止等等。

进程控制

WinExec 可以创建进程
ShellExecute 可以创建一个进程,打印一个文件,浏览一个文件夹
system 也能够以控制台命令的方式打开一个进程
CreateProcess 创建进程
OpenProcess 打开进程
ExitProcess 退出本进程
TerminateProcess 结束其它进程

进程的创建

1使用宏加载想要的进程#define PATH2 L”D:\Program Files (x86)\Tencent\QQ\Bin\QQScLauncher.exe”

2使用结构体PROCESS_INFORMATION定义加载进程信息的缓冲区

3使用CreateProcess函数创建进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <Windows.h>
#define PATH2 L"D:\\Program Files (x86)\\Tencent\\QQ\\Bin\\QQScLauncher.exe"
int main()
{
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};
CreateProcess(
PATH2, //路径
NULL, //命令行参数
NULL, //进程安全属性
NULL, //线程安全属性
FALSE, //是否继承句柄
NULL, //创建方式
NULL, //环境
NULL, //当前的运行目录
&si, //启动信息
&pi //进程信息
);
return 0;
}

结束一个进程

使用该进程PID打开进程从而获取到进程句柄

使用TerminateProcess函数结束进程

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <Windows.h>
int main()
{
//1. 打开进程,得到句柄
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, 5312);
//2. 终结进程
TerminateProcess(hProcess, 0);

return 0;
}

进程与模块

CreateToolhelp32Snapshot 可以分别创建进程,线程,进程模块进程堆的快照
Process32First 用来首次调用,获得第一个进程信息
Process32Next 以后的调用由它来完成,不断的获取进程信息
Module32First 用来首次调用,获得第一个模块信息
Module32Next 以后的调用由它来完成,不断的获取模块信息
Thread32First 用来首次调用,获得第一个线程信息
Thread32Next 以后的调用由它来完成,不断地获取线程信息

创建快照

WIndows提供了一组快照的API,使用前需要包含TIHelp32.h头文件,这一组API

​ 能够给当前系统中所有进程拍一个快照,能够获取所有进程的一些基本信息

​ 能够当前系统中的线程拍一个快照

​ 能够给某一个进程拍模块快照

​ 能够给某一个进程拍堆快照

进程的遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>

int main()
{
//1. 得到快照的句柄
HANDLE hToolHelp = CreateToolhelp32Snapshot(
TH32CS_SNAPPROCESS,
0
);
//2. 获取第一个结点的信息
PROCESSENTRY32 pe = {sizeof(PROCESSENTRY32)};
if (Process32First(hToolHelp, &pe))
{
//pe.szExeFile;//进程名
//pe.th32ProcessID;//ID
do
{
printf("%d:%S\n", pe.th32ProcessID, pe.szExeFile);
} while (Process32Next(hToolHelp, &pe));
}
return 0;
}

模块的遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>

int main()
{
//1. 得到快照的句柄
HANDLE hToolHelp = CreateToolhelp32Snapshot(
TH32CS_SNAPMODULE,
25076
);
//2. 获取第一个结点的信息
MODULEENTRY32 me = { sizeof(MODULEENTRY32) };
if (Module32First(hToolHelp, &me))
{
//pe.szExeFile;//进程名
//pe.th32ProcessID;//ID
do
{
printf("模块名:%S 加载基址:%p\n", me.szModule, me.modBaseAddr);
} while (Module32Next(hToolHelp, &me));
}
return 0;
}

进程间通讯

发送消息

COPYDATASTRUCT发送

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <Windows.h>
int main()
{
HWND hWnd = (HWND)0x0003037E;

COPYDATASTRUCT cds = {0};
char buf[100] = { "hahahahahahahahlou world" };

cds.dwData = 200;//随便写,写什么发什么
cds.lpData = buf;
cds.cbData = 100;
SendMessage(hWnd, WM_COPYDATA, 0, (LPARAM)&cds);

}

邮槽发送

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <Windows.h>
int main()
{
// 1. 打开邮槽对象
HANDLE hFile = CreateFile(
L"\\\\.\\mailslot\\Sample", // 邮槽名称
GENERIC_WRITE, // 读写属性
FILE_SHARE_READ, // 共享属性
NULL, // 安全属性
OPEN_EXISTING, // 打开方式
FILE_ATTRIBUTE_NORMAL, // 标志位
NULL); // 文件模板(默认留空)
// 2. 向mailslot写入
DWORD dwWritten;
LPSTR lpMessage = (LPSTR)"邮槽测试消息!";
DWORD dwMegLen = strlen(lpMessage) + sizeof(CHAR);
WriteFile(hFile, lpMessage, dwMegLen, &dwWritten, NULL);
// 3. 结束
printf("已经向邮槽写入信息!\n");
CloseHandle(hFile);
return 0;
}

接收消息

COPYDATA接收只需在回调函数的WM_COPYDATA消息响应定义接收的结构体

1
2
3
4
5
6
case WM_COPYDATA:
{
PCOPYDATASTRUCT pcds = (PCOPYDATASTRUCT)lParam;
MessageBoxA(0, (char*)(pcds->lpData), "提示", 0);
}
break;
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
#include <iostream>
#include <Windows.h>
int main()
{
// 1. 创建邮槽对象
HANDLE hSlot = CreateMailslot(
L"\\\\.\\mailslot\\Sample", // 邮槽名
0, // 不限制消息大小
MAILSLOT_WAIT_FOREVER, // 无超时
(LPSECURITY_ATTRIBUTES)NULL // 安全属性
);
// 2. 循环读取邮槽信息
while (true) {
// 2.1 获取邮槽消息数量
DWORD dwMsgCount = 0, dwMsgSize = 0;
GetMailslotInfo( /* 获取邮槽信息 */
hSlot, // 邮槽句柄
(LPDWORD)NULL,// 无最大消息限制
&dwMsgSize, // 下一条消息的大小
&dwMsgCount, // 消息的数量
(LPDWORD)NULL);// 无时限
if (dwMsgSize == MAILSLOT_NO_MESSAGE) {
Sleep(2000);
continue;
}
// 2.2 循环获取全部消息(有可能不只一条)
// 2.2 循环获取全部消息(有可能不只一条)
while (dwMsgCount)
{
PBYTE lpBuffer;
lpBuffer = new BYTE[dwMsgSize + 0x10];
// 读取邮槽中的信息
DWORD dwRet;
ZeroMemory(lpBuffer, dwMsgSize);
if (!ReadFile(hSlot, lpBuffer, dwMsgSize, &dwRet, NULL))
{
printf("ReadFile函数执行失败,错误码:%d.\n", GetLastError());
delete[] lpBuffer;
return 0;
}
// 显示信息
printf("邮槽的内容: %s\n", lpBuffer);
// 计算剩余的消息数
GetMailslotInfo(hSlot, (LPDWORD)NULL, &dwMsgSize, &dwMsgCount, nullptr);
delete[] lpBuffer;
}
}
}

八,线程

线程创建

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
#include <iostream>
#include <Windows.h>
DWORD WINAPI ThreadProc(
LPVOID lpThreadParameter
)
{
while (true)
{
printf("我是子线程,%s\n", lpThreadParameter);
}
return 0;
}

int main()
{
//1 创建线程
HANDLE hThread = CreateThread(
NULL,//安全属性
NULL,//栈大小
ThreadProc,//线程回调函数
(LPVOID)"15PB", //会将此参数传递给回调函数
0,
0 //获取线程ID,每一个线程都有一个唯一标识,就是线程ID
);
//2 循环
while (true)
{
printf("我是主线程\n");
}
return 0;
}

线程信号

WaitForSingleObject(hThread,-1 ); //该函数表示信号,参数一为被创建的线程名,参数二为一直等到有信号的状态,意思是等待被创建线程结束

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
// 02_关于线程的信号.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <Windows.h>

#include <Windows.h>
DWORD WINAPI ThreadProc(
LPVOID lpThreadParameter
)
{
int n = 0;
while (n<100000)
{
printf("%d\n",n);
n++;
}
return 0;
}


int main()
{
//1 创建线程
HANDLE hThread = CreateThread(
NULL,//安全属性
NULL,//栈大小
ThreadProc,//线程回调函数
(LPVOID)"15PB", //会将此参数传递给回调函数
0,
0 //获取线程ID,每一个线程都有一个唯一标识,就是线程ID
);

Sleep(1000);
//2 等到线程结束
WaitForSingleObject(
hThread,
-1 //一直等到有信号的状态
);
return 0;
}

线程遍历

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
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
VOID ListProcessThreads(DWORD dwPID) {
HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
THREADENTRY32 te32;

// 创建快照
hThreadSnap = //参数一为一个线程的宏,遍历进程就写进程的宏,模块就写模块的宏
CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
// 设置输入参数,结构的大小
te32.dwSize = sizeof(THREADENTRY32);
// 开始获取信息
Thread32First(hThreadSnap, &te32);
do {
if (te32.th32OwnerProcessID == dwPID)
{
printf("线程ID:%d\n", te32.th32ThreadID);
//1. 打开线程,得到句柄
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
//2. 暂停
//SuspendThread(hThread);
//ResumeThread(hThread);
TerminateThread(hThread, 0);
}
// 显示相关信息
} while (Thread32Next(hThreadSnap, &te32));
CloseHandle(hThreadSnap);
}

int main()
{
ListProcessThreads(4068);
return 0;
}

理解伪句柄

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
56
57
58
59
60
61
62
63
64
65
66
#include <iostream>
#include <Windows.h>

DWORD WINAPI ThreadProc(
LPVOID lpThreadParameter
)
{
//1 得到句柄
HANDLE hThread = (HANDLE)lpThreadParameter;
//2 获取创建时间
FILETIME stcCreationTime, stcExitTime;
FILETIME stcKernelTime, stcUserTime;
GetThreadTimes(hThread, &stcCreationTime,
&stcExitTime, &stcKernelTime, &stcUserTime);
FILETIME fi = { 0 };
SYSTEMTIME st = { 0 };
//将标准时间转换为本地时间
FileTimeToLocalFileTime(&stcCreationTime, &fi);
//将时间戳转为能够看懂的时间
FileTimeToSystemTime(&fi, &st);

return 0;
}

int main()
{
//1 获取当前线程句柄
HANDLE hThread = GetCurrentThread();
HANDLE hProcess = GetCurrentProcess();
//2 获取当前线程的创建时间
FILETIME stcCreationTime, stcExitTime;
FILETIME stcKernelTime, stcUserTime;
GetThreadTimes(hThread, &stcCreationTime,
&stcExitTime, &stcKernelTime, &stcUserTime);
FILETIME fi = { 0 };
SYSTEMTIME st = { 0 };
//将标准时间转换为本地时间
FileTimeToLocalFileTime(&stcCreationTime, &fi);
//将时间戳转为能够看懂的时间
FileTimeToSystemTime(&fi, &st);
Sleep(10000);
//2.X 将伪句柄变成一个真句柄
HANDLE hThreadParent = 0;
DuplicateHandle(
GetCurrentProcess(), // 拥有源句柄的进程句柄
GetCurrentThread(), // 指定对象的现有句柄(伪句柄)
GetCurrentProcess(), // 拥有新对象句柄的进程句柄
&hThreadParent, // 用于保存新句柄
0, // 安全访问级别
false, // 是否可以被子进程继承
DUPLICATE_SAME_ACCESS); // 转换选项

//3 创建一个子线程
HANDLE hChildThread = CreateThread(
NULL,//安全属性
NULL,//栈大小
ThreadProc,//线程回调函数
(LPVOID)hThreadParent, //会将此参数传递给回调函数
0,
0 //获取线程ID,每一个线程都有一个唯一标识,就是线程ID
);
WaitForSingleObject(hChildThread, -1);
CloseHandle(hChildThread); //关闭句柄
CloseHandle(hThreadParent);//关闭句柄
return 0;
}

获取线程环境

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
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
VOID ListProcessThreads(DWORD dwPID) {
HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
THREADENTRY32 te32;

// 创建快照
hThreadSnap =
CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
// 设置输入参数,结构的大小
te32.dwSize = sizeof(THREADENTRY32);
// 开始获取信息
Thread32First(hThreadSnap, &te32);
do {
if (te32.th32OwnerProcessID == dwPID)
{
printf("线程ID:%d\n", te32.th32ThreadID);
//1. 打开线程,得到句柄
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
//2. 暂停
//SuspendThread(hThread);
//3. 获取上下文
CONTEXT context = { CONTEXT_ALL };
GetThreadContext(hThread, &context);
//context.;
//SetThreadContext(hThread, &context); //获取之后,可以修改

ResumeThread(hThread);
//TerminateThread(hThread, 0);
}
// 显示相关信息
} while (Thread32Next(hThreadSnap, &te32));
CloseHandle(hThreadSnap);
}

int main()
{

ListProcessThreads(25088);

return 0;
}

线程同步

概念

1 线程同步是一个什么样的问题???

如果你编写的是多线程程序,那么多个线程是并发执行,可以认为他们是同时在执行代码。但是线程和线程之间并非是完全的没有关系。很多时候会有以下两种关系:

第一种情况,线程A的继续执行,要以线程B完成了某一个操作之后为前提。 这种需求称之为同步

第二种情况,多个线程在争抢一个资源,比如:全局变量,可以是文件,可以是一个数据结构,可以是一个对象。 这种需求称之为同步互斥。

2 以下的这些机制怎么解决的这个问题??各自又什么区别,分别用于什么样的场景呢???

a. 以下这些机制都能够比较方便的解决互斥问题。

原子操作:

​ 原子操作适合去解决共享资源是全局变量的互斥问题。

​ 作用就是对于一个变量的基本算术运算保证是原子性的。

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
#include <iostream>
#include <Windows.h>
long g_n = 0;
DWORD WINAPI ThreadPro1(LPVOID lpThreadParameter) {
for (int i = 0; i < 100000; i++)
{
InterlockedIncrement(&g_n);
//写入文件
}
return 0;
}
DWORD WINAPI ThreadPro2(LPVOID lpThreadParameter) {
for (int i = 0; i < 100000; i++)
{
InterlockedIncrement(&g_n);
//读取文件
}

return 0;
}
int main() {
HANDLE hThread1 = 0, hThread2 = 0;
hThread1 = CreateThread(NULL, NULL, ThreadPro1, NULL, NULL, NULL);
hThread2 = CreateThread(NULL, NULL, ThreadPro2, NULL, NULL, NULL);
WaitForSingleObject(hThread1, -1);
WaitForSingleObject(hThread2, -1);
printf("%d", g_n);
return 0;
}

临界区解决互斥问题:

​ 被保护的代码(代码访问了共享资源)放置在

​ EnterCriticalSection

​ LeaveCriticalSection 之间即可

​ 临界区具有线程所有权这个概念,必须进入临界区的线程,调用离开临界区,临界区才会被打开。假如加锁的线程崩溃了,其他线程就锁死了。

介绍另外3种机制前,先说两个重要概念

1:

激发态(有信号) 非激发态(没有信号)

WaitForSignaleObject(内核对象,时间);函数的作用,当内核对象处于非激发态的时候,就阻塞住,内核对象处于激发态了,就立即返回。

2:WaitForSignaleObject的副作用,WaitForSignaleObject对于被等待的内核对象有副作用。

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
#include <iostream>
#include <Windows.h>
int g_n = 0;
//定义临界区结构体
CRITICAL_SECTION g_cs = {};

DWORD WINAPI ThreadPro1(LPVOID lpThreadParameter) {
for (int i = 0; i < 100000; i++)
{
EnterCriticalSection(&g_cs);
g_n++;
LeaveCriticalSection(&g_cs);
}

return 0;
}
DWORD WINAPI ThreadPro2(LPVOID lpThreadParameter) {
for (int i = 0; i < 100000; i++)
{
EnterCriticalSection(&g_cs);
g_n++;
LeaveCriticalSection(&g_cs);
}
return 0;
}
int main() {
//初始化临界区对象
InitializeCriticalSection(&g_cs);
HANDLE hThread1 = 0, hThread2 = 0;
hThread1 = CreateThread(NULL, NULL, ThreadPro1, NULL, NULL, NULL);
hThread2 = CreateThread(NULL, NULL, ThreadPro2, NULL, NULL, NULL);
WaitForSingleObject(hThread1, -1);
WaitForSingleObject(hThread2, -1);
printf("%d", g_n);
return 0;
}

知道了以上两个概念后,我们看其他3个机制

互斥体解决互斥问题:

​ 被保护的代码(代码访问了共享资源)放置在

​ WaitForSignalObject

​ ReleaseMutex

​ 互斥体也具有线程所有权的概念,得到互斥体的线程,需要自己去释放互斥体。谁加锁,谁开锁。如果得到互斥体的线程崩溃了,互斥体会立即变为激发态。所有等待互斥体的线程中会立即有线程得到互斥体。不会造成死锁的问题。

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
#include <iostream>
#include <Windows.h>
int g_n = 0;
HANDLE g_hMutex = 0;
DWORD WINAPI ThreadPro1(LPVOID lpThreadParameter) {
for (int i = 0; i < 100000; i++)
{
WaitForSingleObject(g_hMutex, -1);
g_n++;
ReleaseMutex(g_hMutex);
}

return 0;
}
DWORD WINAPI ThreadPro2(LPVOID lpThreadParameter) {
for (int i = 0; i < 100000; i++)
{
WaitForSingleObject(g_hMutex, -1);
g_n++;
ReleaseMutex(g_hMutex);
}
return 0;
}
int main() {
HANDLE hThread1 = 0, hThread2 = 0;
g_hMutex = CreateMutex(
NULL,
FALSE,//创建的线程,是不是第一个拥有者
NULL
);
hThread1 = CreateThread(NULL, NULL, ThreadPro1, NULL, NULL, NULL);
hThread2 = CreateThread(NULL, NULL, ThreadPro2, NULL, NULL, NULL);
WaitForSingleObject(hThread1, -1);
WaitForSingleObject(hThread2, -1);
printf("%d", g_n);
return 0;
}

事件解决互斥问题:

需要是自动模式的事件

​ 被保护的代码(代码访问了共享资源)放置在

​ WaitForSignalObject

​ SetEvent

​ 事件,没有线程所有权的概念,任何线程都可以释放事件。

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
#include <iostream>
#include <Windows.h>
int g_n = 0;
HANDLE g_hEvent = 0;
DWORD WINAPI ThreadPro1(LPVOID lpThreadParameter) {
for (int i = 0; i < 100000; i++)
{
WaitForSingleObject(g_hEvent, -1);
g_n++;
SetEvent(g_hEvent);
}

return 0;
}
DWORD WINAPI ThreadPro2(LPVOID lpThreadParameter) {
for (int i = 0; i < 100000; i++)
{
WaitForSingleObject(g_hEvent, -1);
g_n++;
SetEvent(g_hEvent);
}
return 0;
}
int main() {

HANDLE hThread1 = 0, hThread2 = 0;
g_hEvent = CreateEvent(
NULL, //安全属性
FALSE,//TRUE:手工重置 FALSE:自动重置
TRUE, //TRUE:初始为激发态, FALSE:初始就是非激发态
NULL
);
hThread1 = CreateThread(NULL, NULL, ThreadPro1, NULL, NULL, NULL);
hThread2 = CreateThread(NULL, NULL, ThreadPro2, NULL, NULL, NULL);
WaitForSingleObject(hThread1, -1);
WaitForSingleObject(hThread2, -1);
printf("%d", g_n);
return 0;
}

事件决定顺序

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
#include <iostream>
#include <Windows.h>
HANDLE g_hEvent = 0;
DWORD WINAPI WifeProc(LPVOID lpThreadParameter) {
printf("下班了,开始做饭吧");
Sleep(1000);
SetEvent(g_hEvent);
return 0;
}
DWORD WINAPI HusProc(LPVOID lpThreadParameter) {
WaitForSingleObject(g_hEvent, -1);
printf("下班了,开始吃饭吧");
return 0;
}

int main()
{
HANDLE hThread1 = 0, hThread2 = 0;
g_hEvent = g_hEvent = CreateEvent(
NULL, //安全属性
FALSE,//TRUE:手工重置 FALSE:自动重置
FALSE, //TRUE:初始为激发态, FALSE:初始就是非激发态
NULL
);
hThread1 = CreateThread(NULL, NULL, WifeProc, NULL, NULL, NULL);
hThread2 = CreateThread(NULL, NULL, HusProc, NULL, NULL, NULL);
WaitForSingleObject(hThread1, -1);
WaitForSingleObject(hThread2, -1);
}

信号量:

​ 有信号数这么一个概念,只要信号数不为0,那么就处于激发态。WaitForSignaleObject函数对它的副作用是将信号数减1。

​ 最大信号数为1 的信号量,可以认为是一个事件,可以解决互斥问题。

​ 被保护的代码(代码访问了共享资源)放置在

​ WaitForSignalObject

​ ReleaseSemaphore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // SD
LONG lInitialCount, // initial count
LONG lMaximumCount, // maximum count
LPCTSTR lpName // object name
)
此函数可用来创建或打开一个信号量,先看参数说明:
lpSemaphoreAttributes:为信号量的属性,一般可以设置为NULL
lInitialCount:信号量初始值,必须大于等于0,而且小于等于 lpMaximumCount,如果lInitialCount 的初始值为0,则该信号量默认为unsignal状态,如果lInitialCount的初始值大于0,则该信号量默认为signal状态,
lMaximumCount: 此值为设置信号量的最大值,必须大于0
lpName:信号量的名字,长度不能超出MAX_PATH ,可设置为NULL,表示无名的信号量。当lpName不为空时,可创建有名的信号量,若当前信号量名与已存在的信号量的名字相同时,则该函数表示打开该信号量,这时参数lInitialCount 和
lMaximumCount 将被忽略。
函数调用成功返回信号量句柄。
释放信号量函数:

b. 对于共享资源有序的访问,也可以更关注于要有序。

​ 事件和信号量更适合解决有序的问题。因为他们不要求谁上锁,谁开锁。

用代码实现一个读文件线程,一个写文件线程,请实现先写后读,两个线程都结束之后,主线程结束。

这种没有过多线程同时访问的有顺序的问题,比较适合用事件来解决。

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
#include <iostream>
#include <Windows.h>
int g_n = 0;
HANDLE g_hSemaphore = 0;
DWORD WINAPI ThreadPro1(LPVOID lpThreadParameter) {
for (int i = 0; i < 100000; i++)
{
WaitForSingleObject(g_hSemaphore, -1);
g_n++;
ReleaseSemaphore(g_hSemaphore, 1, NULL);
}

return 0;
}
DWORD WINAPI ThreadPro2(LPVOID lpThreadParameter) {
for (int i = 0; i < 100000; i++)
{
WaitForSingleObject(g_hSemaphore, -1);
g_n++;
ReleaseSemaphore(g_hSemaphore, 1, NULL);
}

return 0;
}
int main() {
HANDLE hThread1 = 0, hThread2 = 0;

g_hSemaphore = CreateSemaphore(
NULL,
1,
1,
NULL
);

hThread1 = CreateThread(NULL, NULL, ThreadPro1, NULL, NULL, NULL);
hThread2 = CreateThread(NULL, NULL, ThreadPro2, NULL, NULL, NULL);
WaitForSingleObject(hThread1, -1);
WaitForSingleObject(hThread2, -1);
printf("%d", g_n);
return 0;
}

多个信号数的信号量,比较适合解决多个线程之间有顺序需要协调的问题,最为经典的就是生产者消费者问题。

关键点有两个:

​ 1 必须有一个队列,可以有数量限制,也可以没有数量限制。我们考虑的是有数量限制的问题

​ 2 每一个生产者是一个线程,每一个消费者也是一个线程。队列满了,生产者需要等待。队列空了,消费者需要等待。

整个的问题是多线程并发时的协调问题。

生产者消费者问题,有两个操作

P:生产

V:消耗

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <vector>
HANDLE hSemaphoreFull = 0;
HANDLE hSemaphoreEmpty = 0;
HANDLE hMutex = 0;
HANDLE hMutexNum = 0;
int g_nNum = 0;
class 盖饭
{
public:
盖饭(const char *szName, int nNum) :m_Id(nNum)
{
int nSize = strlen(szName) + 1;
m_szName = new char[nSize];
memset(m_szName, 0, nSize);
strcpy_s(m_szName, nSize, szName);
}
int GetId()
{
return m_Id;
}
private:
char * m_szName;
int m_Id;
};
std::vector<盖饭*> g_盖饭表;
int Continue = 1;
//生产者回调函数
DWORD WINAPI Creater(
LPVOID lpThreadParameter
)
{
srand(time(NULL));
while (Continue)
{
int nTime = rand() % 100;
printf("%d号生产者开始做饭,预计%dms\n", (int)lpThreadParameter, nTime);
WaitForSingleObject(hMutexNum, -1);
g_nNum++;
盖饭* p = new 盖饭("鱼香肉丝", g_nNum);
ReleaseMutex(hMutexNum);
Sleep(nTime * 10);
printf("%d号生产者传菜,菜号为%d\n", (int)lpThreadParameter, p->GetId());
WaitForSingleObject(hSemaphoreEmpty, -1);
WaitForSingleObject(hMutex, -1);
g_盖饭表.push_back(p);
ReleaseMutex(hMutex);
ReleaseSemaphore(hSemaphoreFull, 1, NULL);
}
return 0;
}
//消费者回调函数
DWORD WINAPI User(LPVOID lpThreadParameter)
{
srand(time(NULL));
while (Continue)
{
WaitForSingleObject(hSemaphoreFull, -1);
int nTime = rand() % 100;
Sleep(nTime * 10);
WaitForSingleObject(hMutex, -1);
printf(" %d号消费者取走了%d号饭,耗时%dms\n", (int)lpThreadParameter, g_盖饭表[0]->GetId(), nTime);
g_盖饭表.erase(g_盖饭表.begin());
ReleaseMutex(hMutex);
ReleaseSemaphore(hSemaphoreEmpty, 1, NULL);
}
return 0;
}
int main()
{
hSemaphoreFull = CreateSemaphore(NULL, 0, 3, NULL);
hSemaphoreEmpty = CreateSemaphore(NULL, 3, 3, NULL);
hMutex = CreateMutex(NULL, FALSE, NULL);
hMutexNum = CreateMutex(NULL, FALSE, NULL);
HANDLE hThread[20] = {};
//9个生产者
hThread[0] = CreateThread(NULL, 0, Creater, (LPVOID)1, 0, 0);
hThread[1] = CreateThread(NULL, 0, Creater, (LPVOID)2, 0, 0);
hThread[2] = CreateThread(NULL, 0, Creater, (LPVOID)3, 0, 0);
hThread[3] = CreateThread(NULL, 0, Creater, (LPVOID)4, 0, 0);
hThread[4] = CreateThread(NULL, 0, Creater, (LPVOID)5, 0, 0);
hThread[5] = CreateThread(NULL, 0, Creater, (LPVOID)6, 0, 0);
hThread[6] = CreateThread(NULL, 0, Creater, (LPVOID)7, 0, 0);
hThread[7] = CreateThread(NULL, 0, Creater, (LPVOID)8, 0, 0);
hThread[8] = CreateThread(NULL, 0, Creater, (LPVOID)9, 0, 0);
hThread[9] = CreateThread(NULL, 0, Creater, (LPVOID)10, 0, 0);
//4个消费者
hThread[10] = CreateThread(NULL, 0, User, (LPVOID)1, 0, 0);
hThread[11] = CreateThread(NULL, 0, User, (LPVOID)2, 0, 0);
hThread[12] = CreateThread(NULL, 0, User, (LPVOID)3, 0, 0);
hThread[13] = CreateThread(NULL, 0, User, (LPVOID)4, 0, 0);
WaitForMultipleObjects(14, hThread, TRUE, -1);
}

总结:

原子操作,只能保证对于基本算数操作是原子性的。

临界区和互斥体从词语的含义上看,他们主要就是为了解决互斥问题。

临界区的优点是快。互斥体的优点是能够跨进程访问,崩溃不死锁。

事件 从词语的含义上看,更适合做通知(产生了一个事件)。比较适合解决有先后顺序的多线程问题。

事件和互斥体的最大区别,就是线程所有权。互斥体谁上锁,谁开锁。事件没有这个要求。

信号量,由于存在信号数的问题,比较适合解决多线程的协调问题。典型问题,就是生产者消费者问题。

常用函数

原子操作:

函数 作用 备注
InterlockedIncrement 自增 InterlockedIncrement(&g_count)
InterlockedDecrement 自减 InterlockedDecrement(&g_count);
InterlockedExchangeAdd 加法/减法 InterlockedExchangeAdd(&g_count, 256L);
InterlockedExchange 赋值 InterlockedExchange(&g_count, 256L);

临界区

函数 作用 备注
InitializeCriticalSection 初始化
DeleteCriticalSection 销毁
EnterCriticalSection 进入临界区
LeaveCriticalSection 离开临界区

互斥体

函数 作用 备注
CreateMutex 创建互斥体 可以给互斥体起名字
OpenMutex 打开互斥体,得到句柄 根据名字才能打开互斥体
ReleaseMutex 释放互斥体 会使得互斥体处于激发态
CloseHandle 关闭句柄 使用完后关闭
WaitForSignalObject 等待互斥体处于激发态 等到激发态后,会使得互斥体再次处于非激发态

事件

函数 作用 备注
CreateEvent 创建事件 可以给事件起名字 可以设置两种模式:手工 自动
OpenEvent 打开事件,得到句柄 根据名字才能打开事件
SetEvent 释放事件 会使得事件处于激发态
ResetEvent 重置事件 会使得事件处于非激发态,对手工模式的事件有效
WaitForSignalObject 等待事件处于激发态 等到激发态后,对于自动模式的事件会使其再次处于非激发态

信号量

函数 作用 备注
CreateSemaphore 创建信号量 可以给信号量起名字 可以指定最大信号数和当前信号数
OpenSemaphore 打开信号量 根据名字才能打开信号量
ReleaseSemaphore 释放信号量 会增加信号量的信号数,但是不会超过最大信号数
WaitForSignalObject 等待信号量处于激发态 若处于激发态,则会减少1个信号数,信号数位0,将其置为非激发态

九、文件操作

DeleteFile 删除文件
CopyFile 拷贝文件
MoveFile 移动文件
CreateFile 打开文件
GetFileSize 获取文件大小
ReadFile 读取文件
CloseHandle 关闭文件
FindFirstFile 查找第一个文件
FindNextFile 查找下一个文件
WriteFile 写入数据

文件属性

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <iostream>
#include <Windows.h>
#include <tchar.h>

void MyEnumFile(const TCHAR * szPath)
{
WIN32_FIND_DATA wfd = {};

HANDLE hFind = FindFirstFile(szPath, &wfd);

if (hFind!=INVALID_HANDLE_VALUE)
{
do
{
if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
printf("文件夹:%S\n", wfd.cFileName);

//MyEnumFile();
}
else
{
printf("文件:%S\n", wfd.cFileName);
}
} while (FindNextFile(hFind, &wfd));
}
}
int main()
{
//1. 删除文件
//DeleteFile(_T("D:\\hehe\\haha.txt"));
//2. 拷贝文件
//CopyFile(_T("D:\\hehe\\123\\haha.txt"), _T("D:\\hehe\\123.txt"),TRUE);
//3. 移动文件
//MoveFile(_T("D:\\hehe\\123\\haha.txt"), _T("D:\\hehe\\nihao.txt"));

//4. 打开文件 进行读写 fread fwrite
HANDLE hFile = CreateFile(
_T("D:/hehe/123/haha.txt"), //路径
GENERIC_READ | GENERIC_WRITE, //打开的权限
NULL, //共享模式
NULL, //安全属性
OPEN_EXISTING, //
FILE_ATTRIBUTE_NORMAL, //普通属性
NULL
);
if (hFile != INVALID_HANDLE_VALUE)
{
DWORD dwRealSize = 0;
//获取文件大小,申请相应的缓冲区空间
DWORD dwSize = GetFileSize(hFile, NULL);
char* pbuf = new char[dwSize] {0};
//读取文件
ReadFile(hFile, pbuf, dwSize, &dwRealSize, NULL);
CloseHandle(hFile);
}
//5. 查看文件的属性
//5.1 基本属性
DWORD dwAttribute = GetFileAttributes(_T("D:\\hehe\\123\\haha.txt"));
if (dwAttribute& FILE_ATTRIBUTE_HIDDEN)
{
printf("这个文件是隐藏的");
}
if (dwAttribute & FILE_ATTRIBUTE_READONLY)
{
//printf("这个文件是只读的");
}
WIN32_FILE_ATTRIBUTE_DATA wfad = {};
GetFileAttributesEx(_T("D:\\hehe\\123\\haha.txt"), GetFileExInfoStandard, &wfad);
//5.2 扩展属性
wfad.ftCreationTime;//这里获取到的是时间戳
FILETIME fi = { 0 };
SYSTEMTIME st = { 0 };
//将标准时间转换为本地时间
FileTimeToLocalFileTime(&wfad.ftCreationTime, &fi);
//将时间戳转为能够看懂的时间
FileTimeToSystemTime(&fi, &st);

//6. 枚举某一个文件夹下的文件
MyEnumFile(_T("D:\\*"));
return 0;
}

遍历盘符

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
56
57
58
59
	//1. 获取驱动器名称
TCHAR buf[100] = {};
TCHAR* pTemp = buf;

DWORD 总容量;
DWORD 空闲容量;
GetLogicalDriveStrings(100, buf);
// 把语言设置为中文
setlocale(LC_ALL, "chs");
int Flag = 0;
//wprintf(_T("%s "), pTemp);


while (pTemp[0] != 0)
{

LVITEM li = {};
li.mask = LVIF_TEXT;
li.iItem = Flag;
li.pszText = pTemp;
//添加一行,但是没有设置本行单元格的内容
ListView_InsertItem(hListCtrl, &li);

//2. 获取驱动器类型
DWORD dwType = GetDriveType(pTemp);
switch (dwType)
{
case DRIVE_REMOVABLE:
ListView_SetItemText(hListCtrl, Flag, 1, (TCHAR*)_T("可移动设备"));
break;
case DRIVE_FIXED:
ListView_SetItemText(hListCtrl, Flag, 1, (TCHAR*)_T("硬盘"));
break;
case DRIVE_REMOTE:
ListView_SetItemText(hListCtrl, Flag, 1, (TCHAR*)_T("远程设备"));
break;
case DRIVE_CDROM:
ListView_SetItemText(hListCtrl, Flag, 1, (TCHAR*)_T("光驱"));
break;
default:
break;
}
//3. 驱动器的空间信息
DWORD 每簇的扇区数量 = 0;
DWORD 每个扇区的容量 = 0;
DWORD 空闲簇的总量 = 0;
DWORD 全部簇的总量 = 0;
GetDiskFreeSpace(pTemp,
&每簇的扇区数量,
&每个扇区的容量,
&空闲簇的总量,
&全部簇的总量
);

Flag++;
pTemp += wcslen(buf)+1 ;

}
}

文件遍历

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
56
57
58
59
60
61
62
63
64
65
66
67
68
//文件遍历
void CMyDlg2::EnumFile(CString filePath)
{//每次遍历都得置0
i = 0;
j = 0;
dwConstSize1 = 0;
dwConstSize2 = 0;
CString cs;
// 拼接完整路径
CString fullPath = filePath + L"\\*";
// 查找第一个文件
WIN32_FIND_DATA fileData = {};
HANDLE hFile = FindFirstFile(fullPath, &fileData);
if (hFile != INVALID_HANDLE_VALUE)
{
do
{
// 输出文件的信息,
// 过滤两个文件夹……
if (wcscmp(fileData.cFileName, L".") == 0 ||
wcscmp(fileData.cFileName, L"..") == 0)
continue;

// 如果找到的是目录,递归遍历目录中的其他文件
if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
EnumFile(filePath + L"\\" + fileData.cFileName);
continue;
}
m_FileList.InsertItem(i, filePath + L"\\" + fileData.cFileName);
// 获取文件大小
DWORD dwSize = (fileData.nFileSizeHigh * (MAXDWORD + 1))
+ fileData.nFileSizeLow;
// 转换成字符串格式

cs.Format(L"%d kb", dwSize/1024);
m_FileList.SetItemText(i, 1, cs);
// 筛选处指定后缀名的文件
if (wcscmp(L".exe", PathFindExtension(fileData.cFileName)) == 0 ||
wcscmp(L".obj", PathFindExtension(fileData.cFileName)) == 0 ||
wcscmp(L".tlog", PathFindExtension(fileData.cFileName)) == 0 ||
wcscmp(L".idb", PathFindExtension(fileData.cFileName)) == 0 ||
wcscmp(L".pdb", PathFindExtension(fileData.cFileName)) == 0 ||
wcscmp(L".pch", PathFindExtension(fileData.cFileName)) == 0 ||
wcscmp(L".res", PathFindExtension(fileData.cFileName)) == 0 ||
wcscmp(L".ilk", PathFindExtension(fileData.cFileName)) == 0 ||
wcscmp(L".sdf", PathFindExtension(fileData.cFileName)) == 0 ||
wcscmp(L".ipch", PathFindExtension(fileData.cFileName)) == 0 ||
wcscmp(L".log", PathFindExtension(fileData.cFileName)) == 0 ||
wcscmp(L".lastbuildstate", PathFindExtension(fileData.cFileName)) == 0
)
{
CString csPath;//保存路径到vector中
csPath.Format(L"%s\\%s" , filePath,fileData.cFileName);

path.push_back(csPath);//将路径加入vector
m_FileList2.InsertItem(i, filePath + L"\\" + fileData.cFileName);
m_FileList2.SetItemText(i, 1, cs);

dwConstSize2 += dwSize / 1024;
j ++;
}
dwConstSize1 += dwSize / 1024;
i++;
// 继续遍历下一个文件
} while (FindNextFile(hFile, &fileData));
}
}