Win32设备I/O详解

news2024/11/28 17:41:30

Windows设备

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

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

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

打开和关闭设备

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

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

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

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

CreateFile函数

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

函数声明如下

 1 WINBASEAPI
 2 HANDLE
 3 WINAPI
 4 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.coloradmin.cn/o/2099615.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机床采集网关在汽车智能工厂中的应用及成效-天拓四方

随着工业4.0的浪潮席卷全球,智能化、数字化成为了制造业转型升级的关键词。在这一背景下,机床采集网关以其强大的数据采集、传输和处理能力,为企业的数字化转型提供了强有力的支持。本文将通过一个实际案例,详细介绍机床采集网关在…

数据结构:(LeetCode144)二叉树的前序遍历

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。 示例 1: 输入:root [1,null,2,3] 输出:[1,2,3] 解释: 示例 2: 输入:root [1,2,3,4,5,null,8,null,null,6,7,9] 输出:…

CUDA与TensorRT学习二:CUDA编程入门

文章目录 一、理解CUDA的grid和Block1)第一个cuda项目 二、理解.cu和.cpp的相互引用及Makefile三、利用CUDA矩阵乘法(matmul)计算、Error Handle 及硬件信息获取1)矩阵乘法2)Error Handle3)硬件信息获取 四、安装Nsight system an…

【APP自动化】Appium 环境搭建

1 基础环境 安装 node.js (1) 安装node.js 安装的是10版本,node-v10.16.0-x64,node.js安装比较简单,直接采用默认选项即可,路径的话,可以自己更改下。 (2) 添加Path环境变量 (3) 验证node.js是否安装成功 可以在CMD…

STM32 IIC

第一块:介绍协议规则,然后用软件模拟的形式来实现协议, 第二块:介绍STM32的IIC外设,然后用硬件来实现协议 因为IIC是同步时序的额,软件模拟协议也非常方便,像我们单片机一样,外挂芯…

零基础入门转录组数据分析——基因Wilcoxon秩和检验

零基础入门转录组数据分析——基因Wilcoxon秩和检验 目录 零基础入门转录组数据分析——基因Wilcoxon秩和检验1. 单基因Wilcoxon秩和检验的基础知识2. 基因Wilcoxon秩和检验(Rstudio)——代码实操2. 1 数据处理2. 2 基因Wilcoxon秩和检验2. 3 Wilcoxon秩…

FreeRTOS学习笔记—①堆与栈

在嵌入式系统中,堆与栈通常表示操作系统对进程占用的两种管理方式,而RTOS中栈更为重要,每一个链路都要有自己的栈。因此对堆和栈的概念进行了些区分和了解。以下是自己学习总结的一些,如有不对的地方请指正: &#xf…

算法打卡 Day25(二叉树)-修剪二叉搜索树 + 将有序数组转换为二叉搜索树 + 把二叉搜索树转换为累加树

文章目录 Leetcode 669-修剪二叉搜索树题目描述解题思路 Leetcode 108-将有序数组转换为二叉搜索树题目描述解题思路 Leetcode 538-把二叉搜索树转换为累加树题目描述解题思路 Leetcode 669-修剪二叉搜索树 题目描述 https://leetcode.cn/problems/trim-a-binary-search-tree…

elementUI——checkbox复选框监听不到change事件,通过watch监听来解决——基础积累

今天在写后台管理系统的时候,遇到一个需求,就是要求监听复选框的change事件,场景就是:两个复选框互斥,且可以取消勾选。 就是这两个复选框可以同时都不勾选,如果勾选的话,另一个一定要取消勾选。…

​如何通过Kimi强化论文写作中的数据分析?

在学术研究领域,数据分析是验证假设、发现新知识和撰写高质量论文的关键环节。Kimi,作为一款先进的人工智能助手,能够在整个论文写作过程中提供支持,从文献综述到数据分析,再到最终的论文修订。本文将详细介绍如何将Ki…

OceanBase 的ODP OBproxy 的记录

OceanBase 的ODP的路由说明一、简述为什么使用ODP的原因 (强一致性情况下) 1.分布式数据库在SQL解析这块存在本地执行计划,远程执行计划,分布式执行计划。 本地执行计划:整个SQL的表都在session所在的Observer 节点上。…

ABAP 结构体变量的嵌套INCLUDE TYPE 和 INCLUDE STRUCTURE

文章目录 创建程序语法格式程序测试AS SPFLI_NAME2 RENAMING WITH SUFFIX _NAME2 后缀变量的结构程序结构类型嵌套表和结构字段类型TYPES嵌套类型程序 创建程序 语法格式 程序测试 AS SPFLI_NAME2 RENAMING WITH SUFFIX _NAME2 后缀 变量的结构 程序 *&------------------…

Java进阶13讲__第六讲

算法: 冒泡排序 选择排序 二分查找 1. 冒泡排序 1.1 定义 1.2 代码示例 Java业务逻辑-1(冒泡排序)-CSDN博客https://blog.csdn.net/XiaomeiGuiSnJs/article/details/140880229 2. 选择排序 2.1 定义 2.2 代码示例 package cn.hdc.itWork.d5.d2;import java.uti…

【C语言】详解数组

文章目录 前言一、数组的概念二、一维数组1.一维数组的创建2.一维数组的初始化3. 一维数组的使用4.一维数组在内存中的存储 三、二维数组1.二维数组的创建2. 二维数组的初始化3. 二维数组的使用4.二维数组在内存中的存储 前言 一、数组的概念(数组是一组相同类型元素…

精准设计与高效开发:用六西格玛设计DFSS实现新能源汽车开发突破

快速变化的市场需求和激烈的竞争迫使制造企业不得不持续创新和优化产品开发流程。如何在保证产品质量的前提下,加快产品开发周期,成为许多企业亟待解决的问题。六西格玛中的DFSS(Design for Six Sigma)模型提供了一种系统的方法&a…

维信小程序禁止截屏/录屏

一、维信小程序禁止截屏/录屏 //录屏截屏,禁用wx.setVisualEffectOnCapture({visualEffect:hidden});wx.setVisualEffectOnCapture(Object object) 测试安卓手机: 用户截屏,被禁用 用户录屏,录制的是空白内容/黑色内容的视频。 二、微信小…

RS-FS-N01风速变送器简明教程(485通信类型变送器)

该文章仅供参考,编写人不对任何实验设备、人员及测量结果负责!!! 文章主要介绍变送器的硬件连接、软件配置、数据读写以温湿度计算。 1 硬件连接 2 软件配置 将变送器硬件部分正确连接后 打开“485 参数配置工具.exe” 对风速…

hello树先生——红黑树

红黑树 一.什么是红黑树二.红黑树的实现1.创建树节点结构2.插入功能的实现 三.提供一些常见二叉树接口四.进行平衡测试 一.什么是红黑树 红黑树是一种自平衡的二叉搜索树,具有以下特性: 节点颜色:每个节点要么是红色,要么是黑色。…

从模型到实践:新时代【数学建模竞赛论文】的结构、规范与创新解析

目录 1. 数学建模竞赛论文的重要作用 1.1 论文是竞赛成果的书面形式 1.2 论文是评判参赛成绩的唯一依据 1.3 论文写作是科技论文写作的基本训练 1.4 数学建模竞赛论文的综合性 1.5 数学建模竞赛论文与学术研究的联系 1.6 数学建模竞赛论文的重要性在评委眼中 1.7 数学建…

Leetcode3248. 矩阵中的蛇

Every day a Leetcode 题目来源:3248. 矩阵中的蛇 解法1:模拟 遍历字符串数组 commands,模拟🐍的移动过程。 如果最后🐍的位置为 (i, j),则编号为 (i * n) j。 代码: /** lc appleetcode…