Win32设备I/O详解

Windows设备

在Windows平台下,设备被定义为能够与之进行通信的任何东西。最常见的 I/O 设备包括:文件、文件流、目录、物理磁盘、卷、控制台缓冲区、磁带驱动器、通信资源、mailslot 和管道等。

平常我们使用的文件,目录都可以称之为设备。

本文是介绍设备的通用操作,以文件操作进行演示。

打开和关闭设备

在Windows中,常见的设备及用途如下:

 在前面的文章中,介绍了mailslot的使用,它就是属于设备的一种,并且它的读取和写入跟文件读取写入用的是一样的函数。

很多设备的数据读取和数据写入都是以相同的方式来实现,具体的方式我们可以看到下表:

 不同的设备有不同的设置函数,以串口为例,设置串口参数使用的是setCommConfig函数,这里不作具体介绍,实际使用时,可以查阅MSDN文档。

CreateFile函数

在上表中我们可以看到,设备的打开大部分使用的是CreateFile函数,打开文件也是这个函数,而文件是我们平常使用得比较多的,所以这里重点介绍CreateFile函数。

函数声明如下

 1 WINBASEAPI2 HANDLE3 WINAPI4 CreateFileW(5     _In_ LPCWSTR lpFileName,6     _In_ DWORD dwDesiredAccess,7     _In_ DWORD dwShareMode,8     _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,9     _In_ DWORD dwCreationDisposition,
10     _In_ DWORD dwFlagsAndAttributes,
11     _In_opt_ HANDLE hTemplateFile
12     );

参数说明:

lpFileName:

指定用于创建或打开的对象的名称。

dwDesiredAccess:

请求对文件或设备的访问权限,通常可以取以下值,或GENERIC_READ|GENERIC_WRITE的组合值。

dwShareMode:

指定共享方式。如果将此参数设置为0,那么对象不能被共享,后续对该对象进行打开操作将会失败,直到关闭句柄为止。为达到对象共享效果,可以使用

下面的一个或多个值来指定共享方式

含义

0

0x00000000

阻止其他进程在请求删除、读取或写入访问权限时打开文件或设备。

FILE_SHARE_DELETE

0x00000004

启用文件或设备上的后续打开操作以请求删除访问权限。

如果未指定此标志,但已打开文件或设备进行删除访问,则函数将失败。

注意 删除访问权限允许删除和重命名操作。

FILE_SHARE_READ

0x00000001

启用文件或设备上的后续打开操作以请求读取访问权限。

否则,如果其他进程请求读取访问权限,则无法打开文件或设备。

如果未指定此标志,但已打开文件或设备进行读取访问,则函数将失败。

FILE_SHARE_WRITE

0x00000002

启用文件或设备上的后续打开操作以请求写入访问权限。

否则,如果其他进程请求写入访问权限,则无法打开文件或设备。

如果未指定此标志,但文件或设备已打开进行写入访问,或者具有具有写入访问权限的文件映射,则函数将失败。

lpSecurityAttributes:

指向 SECURITY_ATTRIBUTES 结构的指针,该结构包含两个独立但相关的数据成员:可选的安全描述符,以及一个布尔值,用于确定是否可由子进程继承返回的句柄。

此参数可以为 NULL。

如果此参数为 NULL,则应用程序可能创建的任何子进程都不能继承 CreateFile 返回的句柄,并且与返回的句柄关联的文件或设备将获取默认安全描述符。

结构的 lpSecurityDescriptor 成员指定文件或设备的 SECURITY_DESCRIPTOR 。 如果此成员为 NULL,则会为与返回的句柄关联的文件或设备分配默认安全描述符。

CreateFile 在打开现有文件或设备时忽略 lpSecurityDescriptor 成员,但仍继续使用 bInheritHandle 成员。

结构的 bInheritHandle 成员指定是否可以继承返回的句柄。

dwCreationDisposition:

要对存在或不存在的文件或设备执行的操作。

对于文件以外的设备,此参数通常设置为 OPEN_EXISTING。

此参数必须是下列值之一,这些值不能组合使用:

含义

CREATE_ALWAYS

2

始终创建新文件。

如果指定的文件不存在并且是有效路径,则创建新文件,函数成功,最后一个错误代码设置为零。

有关详细信息,请参阅本主题的“备注”部分。

CREATE_NEW

1

仅当文件尚不存在时,才创建新文件。

如果指定的文件存在,则函数将失败,最后一个错误代码设置为 ERROR_FILE_EXISTS (80) 。

如果指定的文件不存在,并且是可写位置的有效路径,则会创建一个新文件。

OPEN_ALWAYS

4

始终打开文件。

如果指定的文件存在,则函数成功,并将最后一个错误代码设置为 ERROR_ALREADY_EXISTS (183) 。

如果指定的文件不存在,并且是可写位置的有效路径,则函数将创建一个文件,并将最后一个错误代码设置为零。

OPEN_EXISTING

3

仅当文件或设备存在时才打开它。

如果指定的文件或设备不存在,则函数将失败,并将最后一个错误代码设置为 ERROR_FILE_NOT_FOUND (2) 。

有关设备的详细信息,请参阅“备注”部分。

TRUNCATE_EXISTING

5

打开一个文件并截断它,使其大小为零字节,仅当它存在时。

如果指定的文件不存在,则函数将失败,最后一个错误代码设置为 ERROR_FILE_NOT_FOUND (2) 。

调用进程必须打开文件,并将 GENERIC_WRITE 位设置为 dwDesiredAccess 参数的一部分。

dwFlagsAndAttributes:

文件或设备属性和标志 

dwFlagsAndAttributes参数有两个用途:

一,它允许我们设置一些标志来微调与设备之间的通信;

二,如果设备是一个文件,我们还能够设置文件的属性。这些通信标志中的大多数都是一些信号,用来告诉系统我们打算以何种方式来访问设备。这样系统就可以对缓存算法进行优化,来帮助我们提高应用程序的效率。

FILE_ATTRIBUTE_NORMAL 是文件最常见的默认值。

以下某些文件属性和标志可能仅适用于文件,不一定适用于 CreateFile 可以打开的所有其他类型的设备。

Attribute含义

FILE_ATTRIBUTE_ARCHIVE

32 (0x20)

文件应存档。 应用程序使用此属性来标记要备份或删除的文件。

FILE_ATTRIBUTE_ENCRYPTED

16384 (0x4000)

此文件或目录已加密。 对于文件来说,表示文件中的所有数据都是加密的。 对于目录,这意味着加密是新创建的文件和子目录的默认设置。 有关详细信息,请参阅 文件加密。

家庭版、家庭高级版、初学者版或 ARM 版本的 Windows 不支持此标志。

FILE_ATTRIBUTE_HIDDEN

2 (0x2)

文件被隐藏。 不要将其包含在普通目录列表中。

FILE_ATTRIBUTE_NORMAL

128 (0x80)

该文件未设置其他属性。 此属性仅在单独使用时有效。

FILE_ATTRIBUTE_OFFLINE

4096 (0x1000)

文件的数据不会立即可用。 此属性指示文件数据以物理方式移动到脱机存储。 远程存储(分层存储管理软件)使用此属性。 应用程序不应随意更改此属性。

FILE_ATTRIBUTE_READONLY

1 (0x1)

文件为只读文件。 应用程序可以读取文件,但不能写入或删除它。

FILE_ATTRIBUTE_SYSTEM

4 (0x4)

该文件是操作系统的一部分或由操作系统独占使用。

FILE_ATTRIBUTE_TEMPORARY

256 (0x100)

该文件用于临时存储。

有关详细信息,请参阅本主题的 缓存行为 部分。

hTemplateFile

这个参数既可以标识一个已经打开的文件的句柄,也可以是 NULL。如果hFileTemplate标识一个文件句柄,那么CreateFile会完全忽略dwFlagsAndAttributes参数,并转而使用hFileTemplate所标识的文件的属性。

为了能够让函数以这种方式工作,hFileTemplate标识的文件必须是一个已经用GENERIC_READ标志打开的文件。如果CreateFile要打开已有的文件(而不是创建新文件),那么它会忽略hFileTemplate参数。

返回值:

如果CreateFile成功地创建或打开了文件或设备,那么它会返回文件或设备句柄。

如果CreateFile 失败了,那么它会返回INVALID_HANDLE_VALUE。

注意:这里不能通过NULL来判断

设置文件指针的位置

使用CreateFile打开文件时,文件指针的读取位置会被默认设置为0,如果调用ReadFile函数从文件读取了10个字节到内存中,那么系统会更新文件指针。这样下一次调用ReadFile时,会从偏移量为10的地方开始读取文件。

1 BYTE buff[10];
2 DWORD dwReadBytes;
3 HANDLE hFile = CreateFile(L"tt.tx",....); //文件指针设置为0
4 ReadFile(hFile,buff,10,&dwReadBytes,NULL); //读取0-9字节
5 ReadFile(hFile,buff,10,&dwReadBytes,NULL); //读取10-19字节

每个文件内核对象都有自己的文件指针,所以如果两次打开同一个文件,都会从0开始读取。

如果使用DuplicateHandle函数复制句柄,则会使用同一个内核对象的文件指针。

1 BYTE buffer[10];
2 DWORD dwReadBytes ;
3 HANDLE hFile1 = CreateFile(L"tt.txt",...); //文件指针设置为0 ;
4 HANDLE hFile2;
5 DuplicateHandle(GetcurrentProcess ( ) , hFile1,GetCurrentProcess()  , &hFile2,FALSE,DUPLICATE_SAME_ACCESS);
6 ReadFile (hFile1, buffer,10,&dwReadBytes, NULL); //读取0-9字节
7 ReadFile (hFile2, buffer,10,&dwReadBytes,NULL);  //读取10-19字节

可以通过SetFilePointerEx函数来设置文件指针的读取位置,SetFilePointerEx函数声明如下:

1 WINBASEAPI
2 BOOL
3 WINAPI
4 SetFilePointerEx(
5     _In_ HANDLE hFile,
6     _In_ LARGE_INTEGER liDistanceToMove,
7     _Out_opt_ PLARGE_INTEGER lpNewFilePointer,
8     _In_ DWORD dwMoveMethod
9     );

参数说明:

hFile:文件句柄

liDistanceToMove:设置指针移动多少字节,正值向前移动,负值向后移动

lpNewFilePointer:指向LARGE_INTEGER结构中返回文件指针的新值,如果不需要使用,可以设置为NULL

dwMoveMethod:文件指针移动的起点。 此参数的取值可为下列值之一:

含义

FILE_BEGIN

0

起始点为零或文件的开头。 如果指定了此标志,则 liDistanceToMove 参数将解释为无符号值。

FILE_CURRENT

1

起点是文件指针的当前值。

FILE_END

2

起点是当前文件结束位置。

需要注意的地方:

1、将文件指针的值设为超过文件当前的大小是允许的 ,不会引发异常。

2、如果SetFilePointerEx 操作的文件是用FILE_FLAG_NO_BUFFERING标志打开的,那么文件指针只能被设置为扇区大小的整数倍。

3、Windows没有提供一个GetFilePointerEx函数,可以调用SetFilePointerEx将文件指针移动0个字节,通过这种方式来达到相同的效果。
 

设置文件尾

在关闭文件的时候,系统会负责设置文件尾。但是,有时我们可能想要强制使文件变得更小或变得更大。在这些情况下,可以使用SetEndOfFile函数来强制使用文件变大或变小。

SetEndOfFile函数声明如下:

1 WINBASEAPI
2 BOOL
3 WINAPI
4 SetEndOfFile(
5     _In_ HANDLE hFile
6     );

参数说明:

hFile:文件句柄

SetEndOfFile函数会根据文件对象的文件指针当前所在的位置来截断文件的大小或增大文件的大小。

例如,如果想将文件的大小强制设为1024,可以像下面这样使用SetEndOfFile:

1 HANDLE hFile = CreateFile(L"1024.txt", GENERIC_WRITE|GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
2 LARGE_INTEGER liDistanceToMove;
3 liDistanceToMove.QuadPart = 1024;
4 SetFilePointerEx(hFile, liDistanceToMove, NULL, FILE_BEGIN);
5 SetEndOfFile(hFile);
6 CloseHandle(hFile);

同步设备I/O

在前面的文章中有使用过ReadFile函数来读取mailslot消息。

设备既可以是文件,也可以是邮件槽(mailslot)、管道、套接字等等。不管使用的是哪种设备,都是使用ReadFile(读取)和WriteFIle(写入)函数来进行I/O操作。

ReadFile函数声明如下:

1 BOOL
2 WINAPI
3 ReadFile(
4     _In_ HANDLE hFile,
5     _Out_writes_bytes_to_opt_(nNumberOfBytesToRead, *lpNumberOfBytesRead) __out_data_source(FILE) LPVOID lpBuffer,
6     _In_ DWORD nNumberOfBytesToRead,
7     _Out_opt_ LPDWORD lpNumberOfBytesRead,
8     _Inout_opt_ LPOVERLAPPED lpOverlapped
9     );

参数说明:

hFile:标识要访问的设备的句柄

pvBuffer:指向一个缓存,函数会把设备数据读取到该缓存中

nNumberOfBytesToRead:指定从设备中读取多少个字节

lpNumberOfBytesRead:接收 实际 读取的字节数

lpOverlapped:在执行同步I/O时,这个参数应该设置为NULL,如果要执行异步I/O,在打开文件时,要指定FILE_FLAG_OVERLAPPED标志。

注意:ReadFile只能用于指定了GENERIC_READ标志打开的设备

使用示例如下:

 1 TCHAR szFile[MAX_PATH]{};2 TCHAR szText[128]{};3 DWORD dwNumberOfBytesToRead = 0;4 5 //从对话框获取文件路径6 ::GetDlgItemText(GetSafeHwnd(), IDC_EDIT1, szFile, MAX_PATH);7 8 //打开文件9 auto hFile = CreateFile(szFile, GENERIC_READ, FILE_SHARE_READ, NULL,OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
10 
11 if (hFile == INVALID_HANDLE_VALUE)
12 {
13     return;
14 }
15 
16 //读取文件 从0-127个字节
17 if (TRUE == ReadFile(hFile, szText, 128, &dwNumberOfBytesToRead, NULL))
18 {
19     AfxMessageBox(szText);
20 }
21 
22 CloseHandle(hFile);

WriteFile函数声明如下:

1 BOOL
2 WINAPI
3 WriteFile(
4     _In_ HANDLE hFile,
5     _In_reads_bytes_opt_(nNumberOfBytesToWrite) LPCVOID lpBuffer,
6     _In_ DWORD nNumberOfBytesToWrite,
7     _Out_opt_ LPDWORD lpNumberOfBytesWritten,
8     _Inout_opt_ LPOVERLAPPED lpOverlapped
9     );

参数说明:

WriteFile函数的参数和ReadFile基本一致,只是一个是读取,一个是写入。这里不详细介绍参数了。

使用示例如下:

 1 HANDLE hFile = CreateFile(L"1024.txt", GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);2 3 if (INVALID_HANDLE_VALUE == hFile)4     return;5 6 LPCTSTR buffer = _tcsdup(L"HelloWorld");7 DWORD nSizeToWrite = lstrlen(buffer) * sizeof(TCHAR);8 DWORD dwSizeWritten = 0;9 if (WriteFile(hFile, buffer, nSizeToWrite, &dwSizeWritten, NULL))
10 {
11     AfxMessageBox(L"写入成功");
12 }
13 
14 free((void*)buffer);

将数据刷新至设备

在前面介绍 CreateFile 函数的时候,提到过可以传一些标志来改变系统对文件数据进行缓存的方式。其他一些设备,比如串口、邮件槽以及管道,也会对数据进行缓存。如果我们想要强制系统将缓存数据写入到设备,那么可以调用FlushFileBuffers函数,声明如下:

BOOL FlushFileBuffers(HANDLE hFile);

FlushFileBuffers函数会强制将与hFile参数所标识的设备相关联的所有缓存数据写入设备。设备必须是通过GENERIC_WRITE标志打开的,这样FlushFileBuffers才能够正常工作。如果调用成功,那么函数会返回TRUE。

示例代码

WindowsProgramming/IO at master · zhaotianff/WindowsProgramming · GitHub

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/415639.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MES系统:现代工厂生产车间的科技与管理创新

一、现代工厂生产车间:高度集成、自动化与智能化 在当今这个日新月异的工业时代,工厂生产车间已经远远超越了传统意义上的简单生产线概念,它们正逐步演变为一个高度集成、自动化与智能化的生态系统。这一变革不仅极大地提升了生产效率&#…

文件包含PHP伪协议利用方法

1、file 使用file协议读取Windows系统文件 2、php:filter协议 使用php://filter协议可以将php代码进行base64编码后显示出来 3、php://input 使用php://input协议可以将post请求的数据作为php代码运行 访问该协议并进行抓包 修改请求方式为POST 在下方输入php代码 点击forw…

【GIT】idea中实用的git操作,撤回commit,撤回push、暂存区使用

IDEA中最常见的UI操作:【GIT】Idea中的git命令使用-全网最新详细(包括现象含义) 文章目录 问题一: idea撤回仅commit错误的代码(仅本地仓库,因为还没推送到远程)问题二: idea撤回Com…

c++--智能指针(RAII)

智能指针可以帮助我们管理动态空间,即自动释放动态空间。 --------------------------------------------------------------------------------------------------------------------------------- 简单原理 事实上,智能指针的原理就是将指向动态空间…

浅谈人工智能之Windows:基于ollama进行本地化大模型部署

浅谈人工智能之Windows:基于ollama进行本地化大模型部署 引言 随着人工智能技术的飞速发展,大型语言模型(LLMs)已经成为推动自然语言处理领域进步的关键力量。然而,传统的云部署方式可能带来数据隐私、成本以及访问速…

【滑动窗口】将 x 减到 0 的最小操作数

将 x 减到 0 的最小操作数 将 x 减到 0 的最小操作数题目思路讲解代码书写 将 x 减到 0 的最小操作数 题目 题目链接: 将 x 减到 0 的最小操作数 思路讲解 按照题目的思路去做这一题是非常恶心的, 因此我们采用正难则反思路. 将问题转换为: 求中间某一个最长的数组长度, 使…

hyperf json-rpc

安装 安装docker hyperf 安装 hyperf-rpc-server-v8 (服务端) docker run --name hyperf-rpc-server-v8 \ -v /www/docker/hyperf-rpc-server:/data/project \ -w /data/project \ -p 9508:9501 -it \ --privileged -u root \ --entrypoint /bin/sh \…

Unity学习路线

目录 一、Unity官方推荐路线二、AI总结的学习路线1、Unity学习路线图(文言一心)一、基础入门(初级)二、进阶提升(中级)三、高级深入(高级)四、专家级探索 注意事项 2、Unity学习路线…

【2024 CCF编程能力等级认证(GESP)C++ 】 计算机基础知识

目录 1. 引言2. 计算机系统结构2.1 中央处理器(CPU - Central Processing Unit)2.1.1 运算器 2.1.2 控制器2.1.3 性能指标2.2 存储器2.3 输入设备2.4 输出设备 3. 计算机系统层次结构4. 操作系统4.1 操作系统分类4.2 操作系统常见操作4.2.1 基本开关机操…

Mqtt消费端实现的几种方式

此处测试的mqtt的Broker是使用的EMQX 5.7.1&#xff0c;可移步至https://blog.csdn.net/tiantang_1986/article/details/140443513查看详细介绍 一、方式1 添加必要的依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spr…

直播相关概念

文章目录 1、腾讯云直播2、直播&#xff1a;视频直播3、常用的直播组合&#xff1a;4、推流&#xff1a;主播通过推流地址进行视频的推送5、拉流&#xff1a;观众通过拉流地址进行视频的播放6、准备工作6.1、进入腾讯云直播 1、腾讯云直播 直播即时聊天&#xff1a;打赏 文字 …

Linux运维--iptables防火墙命令以及端口号等详解(全)

Linux之iptable防火墙命令以及端口号等详解&#xff08;全&#xff09; 在Linux系统中&#xff0c;你可以使用firewalld和iptables来管理和设置防火墙规则。Firewalld是一个动态管理防火墙的工具&#xff0c;而iptables是一个更底层的工具&#xff0c;可以直接配置Linux内核的…

【重学 MySQL】一、数据库概述

【重学 MySQL】一、数据库概述 为什么要使用数据库数据库与数据库管理系统数据库&#xff08;Database&#xff09;数据库管理系统&#xff08;DBMS&#xff09;数据库与数据库管理系统的关系数据库是数据存储的容器数据库管理系统是数据库的管理者相互依存的关系数据库系统的组…

【网络安全】服务基础第一阶段——第六节:Windows系统管理基础---- DNS部署与安全

计算机智能识别并用IP地址定位&#xff0c;例如我们想要访问一个网页&#xff0c;其实是只能使用这个网页的IP地址&#xff0c;即四位的0&#xff5e;255来访问&#xff0c;但这一串数字难以记忆&#xff0c;于是就有了DNS&#xff0c;将难以记忆的数字转化为容易记忆的域名&am…

Elasticsearch 介绍

1、课程介绍 1.1 ES 8.x 演化进程 版本号发布日期多少个次要版本迭代历时8.02022年2月11日&#xff1f;至今7.02019年4月11日17个次要版本34个月6.02017年11月15日8个次要版本17个月5.02016年10月27日6个次要版本13个月 2、Elasticsearch 是什么 2.1 概念 2.1.1 标准定义 …

文件上传的学习

文件上传漏洞 文件上传漏洞是指由于程序员在对用户文件上传部分的控制不足或者处理缺陷&#xff0c;而导致的用户可以越过其本身权限向服务器上上传可执行的动态脚本文件。这里上传的文件可以是木马&#xff0c;病毒&#xff0c;恶意脚本或者WebShell等。“文件上传”本身没有…

计算机二级 C程序设计(2020B场)全解

A选项&#xff1a;C语言中&#xff0c;一共有3种结构。分别是顺序结构、选择结构&#xff08;else-if语句&#xff09;、循环结构&#xff08;for、while语句&#xff09;。因此&#xff0c;C语言具有结构化特征。 B选项&#xff1a;不仅能解决简单问题&#xff0c;3种基本结构…

WPF MVVM如何在ViewModel直接操作控件对象

早些年在WPF中使用COM组件时&#xff0c;需要在ViewModel中操作COM组件中的控件对象&#xff0c;但是这个控件对象又不支持绑定&#xff0c; 后面的解决办法是在窗口加载时&#xff0c;将控件对象以参数传递到Loaded事件的处理命令中&#xff0c;然后将这个对象记录下来&#…

Ubuntu 18.04升级gclibc为2.28版本

一、查看系统支持的 GLIBC 版本号 ​strings /lib/x86_64-linux-gnu/libc.so.6 | grep GLIBC_出现以下&#xff0c;说明到2.27版本&#xff0c;没有2.28版本&#xff0c;所以我们需要手动安装 GLIBC_2.2.5 GLIBC_2.2.6 GLIBC_2.3 GLIBC_2.3.2 GLIBC_2.3.3 GLIBC_2.3.4 GLIBC_…

Docker入门笔记

Docker 文章目录 Docker1. 下载 &#xff08;centos&#xff09;2. 部署 MySQL3. 常用命令4. 数据卷5. 自定义镜像6. Java 项目部署 1. 下载 &#xff08;centos&#xff09; 卸载旧版 yum remove docker \docker-client \docker-client-latest \docker-common \docker-lates…