CreateFile,现在说的 ReadFile 就是它的“搭档”——用来读取文件或设备的数据的 Win32 API。
它非常常见,也非常实用。下面我用通俗、生活化的方式讲清楚它。
Table of Contents
Toggle🧭 一、函数原型(简化版)
BOOL ReadFile(
HANDLE hFile, // 文件句柄
LPVOID lpBuffer, // 数据读到哪里去
DWORD nNumberOfBytesToRead, // 想读多少字节
LPDWORD lpNumberOfBytesRead, // 实际读了多少字节
LPOVERLAPPED lpOverlapped // 异步用的,一般填 NULL
);
返回值:
- 成功:返回
TRUE - 失败:返回
FALSE(可用GetLastError()查看原因)
🧩 二、参数通俗解释
| 参数 | 含义 | 通俗比喻 |
|---|---|---|
hFile |
文件句柄 | 你手上拿到的“书”或“文件”对象,之前 CreateFile() 打开的那个 |
lpBuffer |
数据缓冲区 | 你准备好的“碗”,用来装读出来的内容 |
nNumberOfBytesToRead |
计划读取的字节数 | 想读多少?(比如读 100 个字节) |
lpNumberOfBytesRead |
实际读取的字节数 | 实际读到了多少?(可能比预期少) |
lpOverlapped |
异步参数 | 一般不用,普通读文件时填 NULL |
🧠 三、简单例子(读文件)
✅ 例1:读取一个文本文件
假设磁盘上有个文件:C:\test\data.txt,内容是:
Hello World!
我们写个程序去读它👇
#include <windows.h>
#include <stdio.h>
int main() {
HANDLE hFile = CreateFile(
"C:\\test\\data.txt", // 文件路径
GENERIC_READ, // 只读
FILE_SHARE_READ, // 可共享读
NULL, // 默认安全
OPEN_EXISTING, // 文件必须存在
FILE_ATTRIBUTE_NORMAL, // 普通文件
NULL
);
if (hFile == INVALID_HANDLE_VALUE) {
printf("打开文件失败: %d\n", GetLastError());
return 1;
}
char buffer[100] = {0}; // 准备一个缓冲区
DWORD bytesRead = 0;
BOOL success = ReadFile(
hFile, // 文件句柄
buffer, // 存放读取的数据
sizeof(buffer) - 1, // 想读多少字节
&bytesRead, // 实际读了多少
NULL // 同步读取
);
if (success) {
printf("读取成功:%d 字节\n", bytesRead);
printf("内容是:%s\n", buffer);
} else {
printf("读取失败: %d\n", GetLastError());
}
CloseHandle(hFile);
return 0;
}
📘 输出结果:
读取成功:12 字节
内容是:Hello World!
🧩 四、类比理解(生活版)
可以把 CreateFile 和 ReadFile 想象成这样的生活过程:
| 动作 | 类比 |
|---|---|
CreateFile |
打开一本书 |
ReadFile |
从书上读内容到你的笔记本里 |
WriteFile |
把笔记写回书上 |
你先“打开文件”(拿到句柄),再“读内容”(复制到缓冲区),最后“关闭文件”(放下书)。
🧱 五、重要细节
-
读取位置
ReadFile每次从当前文件指针位置开始读。- 读完后,文件指针会自动往后移动。
- 可以用
SetFilePointer()改变读的位置。
-
实际读取字节数可能少于期望值
- 比如文件只剩 20 个字节,你要读 100 个,结果只读到 20 个。
-
异步读取(高级用法)
- 如果用
FILE_FLAG_OVERLAPPED打开文件,可以异步读取(不阻塞主线程)。 - 但普通文件操作中,我们通常用同步方式(
lpOverlapped = NULL)。
- 如果用
📘 六、再举几个实际用途例子
✅ 读取二进制文件(比如图片)
BYTE buffer[1024];
DWORD bytesRead;
ReadFile(hFile, buffer, sizeof(buffer), &bytesRead, NULL);
// buffer 里现在就是文件的原始字节内容
✅ 读取设备或管道
ReadFile 不仅能读文件,还能从:
- 串口 (
COM1) - 命名管道 (
\\.\pipe\mypipe) - 磁盘设备 (
\\.\PhysicalDrive0) 这些“文件型设备”里读取数据。
🧾 七、总结(口语记忆法)
| 参数 | 一句话记忆 |
|---|---|
hFile |
我要从哪个文件读? |
lpBuffer |
数据放哪? |
nNumberOfBytesToRead |
想读多少? |
lpNumberOfBytesRead |
实际读了多少? |
lpOverlapped |
要不要异步?(普通情况填 NULL) |
是否希望我帮你画一张图,展示“CreateFile → ReadFile → CloseHandle”整个读取流程?(文件 → 内存缓冲区 → 输出)
图会让你更容易记住数据是怎么流动的。

非常好 👍,你问的这个 lpBuffer 是 ReadFile 函数中最关键的参数之一。
它决定了读取到的数据要放到哪里。
下面我来通俗讲解,并配上例子让你立刻明白。
🧭 一、lpBuffer 是什么?
lpBuffer 全称是 “Long Pointer to Buffer”,意思是:
👉 指向一个**内存区域(缓冲区)**的指针,
ReadFile()读到的数据就会放到这里。
简单说:
- 文件 → 数据读出来 → 放到你准备的
lpBuffer这块内存里。
就像你要打水,lpBuffer 就是你拿的“桶”,ReadFile 是“打水的人”,文件是“水源”。
🧩 二、lpBuffer 的常见定义方式
一般是一个 数组 或 内存指针。常见写法如下👇
char buffer[100]; // 定义一个 100 字节的缓冲区
ReadFile(hFile, buffer, 100, &bytesRead, NULL);
或者你也可以用动态内存分配:
char* buffer = (char*)malloc(100);
ReadFile(hFile, buffer, 100, &bytesRead, NULL);
🧠 三、举几个通俗例子
✅ 例1:读取文本文件
char buffer[128] = {0}; // 准备一个128字节的缓冲区
DWORD bytesRead;
ReadFile(hFile, buffer, sizeof(buffer) - 1, &bytesRead, NULL);
printf("文件内容是:%s\n", buffer);
📘 输出:
文件内容是:Hello World!
👉 buffer 就是 lpBufferReadFile 把文件中的 “Hello World!” 拷贝进了 buffer 里。
✅ 例2:读取二进制文件(如图片)
BYTE buffer[1024];
DWORD bytesRead;
ReadFile(hFile, buffer, sizeof(buffer), &bytesRead, NULL);
printf("前4个字节是:%02X %02X %02X %02X\n", buffer[0], buffer[1], buffer[2], buffer[3]);
📘 输出:
前4个字节是:89 50 4E 47
👉 这四个字节其实是 PNG 图片文件头的标志(89 50 4E 47 = “.PNG”)。
✅ 例3:读取串口数据(设备)
char recvData[50];
DWORD bytesRead;
ReadFile(hSerial, recvData, sizeof(recvData), &bytesRead, NULL);
printf("收到的数据:%.*s\n", bytesRead, recvData);
👉 这时 lpBuffer(即 recvData)装的不是文件内容,而是串口传来的原始数据。
📘 四、注意事项
| 注意点 | 说明 |
|---|---|
| 必须分配足够内存 | 否则可能导致越界或程序崩溃 |
lpBuffer 必须可写 |
因为 ReadFile 要往里写数据 |
| 不会自动添加字符串结束符 | 读文本时记得自己加上 '\0',否则打印会乱码 |
二进制数据不要用 %s 打印 |
因为里面可能没有 \0,会乱输出 |
🧩 五、图示理解
[文件内容] → "Hello World!"
ReadFile(
hFile,
buffer, ← lpBuffer (存放数据的“碗”)
100,
&bytesRead,
NULL
);
执行后:
buffer = "Hello World!"
bytesRead = 12
🧾 六、总结一句话记忆
lpBuffer= 我准备好的“内存碗”,ReadFile= “把文件内容舀进碗里”的动作。
是否希望我帮你画一个示意图,展示 ReadFile 从文件到 lpBuffer 的数据流?
(那张图会像是“文件 → 内核 → 内存 → 变量”的过程,非常直观。)

在 x64dbg 中调试 ReadFile 时,lpBuffer 是一个指针,它指向的内存区域会存放从文件或设备读取出来的数据。
简单又形象的说法:
lpBuffer 是“碗”,真正的数据装在它指向的那块“内存碗”里。
x64dbg 显示的是这个指针和它指向的内容。
✅ x64dbg 中如何查看 lpBuffer 内容?
假设代码中有:
char buffer[100] = {0};
ReadFile(hFile, buffer, 100, &bytesRead, NULL);
当程序运行到 ReadFile 调用之后:
-
查看寄存器里传参
在 32 位下调用约定为:- 第 2 个参数
lpBuffer通常在ESP+8 - 或者在 x64 中第 2 个参数在寄存器
RDX
- 第 2 个参数
-
跳转到这个指针指向的内存
在反汇编界面右键 → Follow in Dump → Memory Address
就能看到内存中的真实数据内容
🧩 举例:读取文件 “Hello World!”
文件内容:
Hello World!
ReadFile 执行完后,在 x64dbg Dump 窗口会看到类似:
| 地址 | Hex 内容 | ASCII 显示 |
|---|---|---|
| 0x00xxxxxx | 48 65 6C 6C 6F 20 57 6F 72 6C 64 21 00 … | Hello World! |
说明:
48= ‘H’65= ‘e’6C 6C= ‘ll’- 看到右边 ASCII 中直观显示文件内容
👉 这块内存就是 lpBuffer 指向的地方
👉 x64dbg 通过 Dump 界面展示它的内容
🧠 再举例:读二进制文件(如 PNG)
读某张图片前 8 字节
x64dbg 中 Dump:
| Hex | ASCII |
|---|---|
| 89 50 4E 47 0D 0A 1A 0A | .PNG…. |
此时不用 %s 打印,因为没有字符串结束符
x64dbg 可以正确显示二进制原始内容
✨ 重点总结
| 项 | 说明 |
|---|---|
| lpBuffer 是什么 | 一个内存地址,用于存放读出的数据 |
| 在 x64dbg 查看什么 | 它指向的那段内存的 Hex 和 ASCII 内容 |
| 什么时候有数据 | ReadFile 调用完成之后 |
一句话背下来:
lpBuffer 在 x64dbg 里显示的不是变量本身,而是它背后的“内存碗”里的真实数据内容。
如果你愿意,我可以带你操作一个完整示例:
断在 ReadFile 前后 → 查看 lpBuffer 内容变化 → 识别 ASCII/二进制信息。
只要你说一声“来个实操”。 😄

非常好的问题 👍,这个细节很多人一开始都会弄混。
🧭 简答一句话:
lpBuffer存放的是内存起始地址(开头的地方),不是结束的地方。
📘 详细解释:
lpBuffer 是一个指针,它指向一块内存区域(缓冲区)的开始位置。
当 ReadFile 把文件数据读进来时,是从这个地址开始写入。
也就是说:
lpBuffer→ 缓冲区的第一个字节的地址(起点)lpBuffer + n→ 后续第 n 个字节的位置(偏移量)lpBuffer + bytesRead - 1→ 最后一个有效字节的位置(结束点)
🧩 举个形象例子:
假设:
char buffer[10];
ReadFile(hFile, buffer, 10, &bytesRead, NULL);
假设 buffer 的地址是 0x1000,文件内容是 "ABCDEF"(6 字节)。
执行完后内存中是这样的👇:
| 地址 | 内容 | 说明 |
|---|---|---|
0x1000 |
41 (‘A’) | lpBuffer 起始位置 |
0x1001 |
42 (‘B’) | 第二个字节 |
0x1002 |
43 (‘C’) | 第三个字节 |
0x1003 |
44 (‘D’) | 第四个字节 |
0x1004 |
45 (‘E’) | 第五个字节 |
0x1005 |
46 (‘F’) | 第六个字节 |
0x1006 |
?? | 未使用 |
| … | … | … |
所以:
lpBuffer = 0x1000(指向开头)- 结束位置 =
0x1000 + bytesRead - 1 = 0x1005
🧠 在 x64dbg 中观察:
- 在寄存器窗口中找到
lpBuffer(如 RDX 或栈参数)
假设值是0x1000 - 右键 → “Follow in Dump → Memory address”
- 在 Dump 窗口中你看到的第一行就是从
lpBuffer(开头)开始的内容
所以在 x64dbg 里看到的内存数据第一字节,就是从
lpBuffer开始的!
✅ 总结:
| 问题 | 答案 |
|---|---|
lpBuffer 是开头还是结尾? |
✅ 开头地址 |
| 数据存放顺序 | 从 lpBuffer 开始,往后依次写入 |
| 如何找结束地址? | lpBuffer + bytesRead - 1 |
| x64dbg Dump 里第一行是什么? | 从 lpBuffer 开始的内容 |
一句话记忆:
lpBuffer指向“碗的口”,不是“碗的底”。
数据是从这个口往后装进去的。