|
一、概述
Windows的优势之一是它支持大量的设备类型,这里我们将设备笼统的定义为能够与之通信的任何东西,常见的一些设备及打开方式有:
设备
| 打开设备的函数
| 文件
| CreateFile()
| 目录
| 同上
| 逻辑磁盘驱动器
| 同上
| 物理磁盘驱动器
| 同上
| 串口
| 同上
| 并口
| 同上
| 邮件槽服务器
| CreateMailslot()
| 邮件槽客户端
| CreateFile()
| 命名管道服务器
| CreateNamedPipe()
| 命名管道客户端
| CreateFile()
| 匿名管道
| CreatePipe()
| 套接字
| Socket()
| 控制台
| CreateConsoleScreenBuffer() CreateStdHandle()
| 当然,这里只是简单地列出了各个设备打开的API,具体用法参数可以查阅MSDN或者SDK。当线程与设备发生通信时,即线程发出设备I/O请求时,一般来说有两种方式:
同步设备I/O请求:字如其名,线程发出设备I/O请求后,它会被临时挂起,直到设备完成I/O请求为止,显然这种方式严重损坏性能;
异步设备I/O请求:线程发出设备I/O请求后,不会被临时挂起而是继续执行其他任务,而设备同时继续处理I/O请求,设备处理完成后发送信号给线程,线程接着处理I/O结果。异步设备I/O可以避免线程挂起导致的CPU大量闲置,提高了利用率和吞吐量。
与设备I/O的通信交互是Windows编程中的重点,涉及到诸多的线程的同步方式,尤其是I/O完成端口的引入,使得不管是否与设备I/O关联,都提供了一种有无数种用途的绝佳的线程间通信机制。囿于学力,很多部分的理解都很有限,把自己掌握的整理出来,以便日后再学习吧。这篇笔记的结构大致如下:
Ø 打开和关闭设备
Ø 使用文件设备
Ø 异步设备I/O基础
Ø 接收I/O请求完成通知
二、打开和关闭设备
为了执行任何类型的设备I/O,我们必须先打开想要操作的设备并得到一个句柄,随后我们可以讲该句柄传给许多函数来与设备进行通信。这里同样用这个设备句柄来唯一标识我们的设备。
HANDLE CreateFile(
PCTSTR pszName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
PSECURITY_ATTRIBUTES psa,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hFileTemplate
);
从之前的表中也可以看出,CreateFile是打开许多设备句柄的关键函数,所以我们对它的参数做一个具体的说明。
l pszName:既可以标识设备的类型,也可以表示该类设备的某个实例。
l dwDesiredAccess:用来指定我们相用什么样的方式来和设备进行数据传输,常见的有四个标志,它们是
0——表示我们不希望对设备读出或写入任何数据,一般用来只想改变设备的配置(比如修改文件的时间戳)
GENERIC_READ——允许对设备进行只读访问
GENERIC_WRITE——允许对设备进行只写访问,比如备份软件或者将数据发送到打印机
GENERIC_READ | GENERIC_WRITE——允许对设备进行读写操作
l dwShareMode:用来指定设备共享特权(device-sharing privilege),但我们打开一个设备但是尚未调用CloseHandle()关闭时,该参数可以控制其他的CreateFile()调用以何种方式打开设备。常见参数有
0——要求独占对设备的访问
FILE_SHARE_READ——只允许共享设备读取方式
FILE_SHARE_WRITE——只允许共享设备写入方式
FILE_SHARE_READ | FILE_SHARE_WRITE——不解释
FILE_SHARE_DELETE——对文件操作时我们不关心文件是否被逻辑删除或者被移动,先将文件打上待删除标记,只有当该文件打开的所有句柄都被关闭的时候再将其真正删除
l Psa指向一个内核对象都具备的安全属性结构,里面可以指定安全信息以及我们是否希望CreateFile返回的句柄能被继承。通常我们传入NULL,这表示用默认的安全设定来创建文件,并且返回的句柄是不可继承的。
l dwCreationDisposition:用来表示用CreateFile打开文件时如果碰到存在的同名文件等情况如何处理,如
CREATE_NEW——若存在同名文件,则调用失败
CREATE_ALWAYS——若存在同名文件,则覆盖
OPEN_EXISTING——若打开的文件或者设备不存咋则调用失败
OPEN_ALWAYS——若打开文件不存在则只直接创建一个
当调用CreateFile打开文件之外的其他设备时,必须将OPEN_EXISTING传给dwCreationDisposition参数
l dwFlagsAndAttributes:允许我们设置一些标志来微调与设备之间的通信;其次我们还可以通过一些属性参数来设置文件属性。比如常见的告诉缓存标志:
FILE_FLAG_NO_BUFFERING告诉高速缓存管理器我们不希望它对任何数据进行缓存,,我们会自己对数据进行缓存;标志FILE_FLAG_DELETE_ON_CLOSE可以让文件系统在文件的所有句柄都被关闭后删除该文件(比如程序运行用到的临时文件,结束后删除,更加隐蔽),标志FLIE_FLAG_BACKUP_SEMANTICS用于备份和恢复软件,不要求文件的全部管理员权限,还有一个重要的标志FILE_FLAG_OVERLAPPED告诉系统我们想要以异步方式来访问设备,默认是同步I/O访问请求。我们重点来介绍一下文件属性参数,可以看到常见的Windows文件属性都有涉及
FILE_ATTRIBUTE_ARCHIVE——存档文件,默认自动设置
FILE_ATTRIBUTE_ENCRYPTED——加密文件
FILE_ATTRIBUTE_HIDDEN——隐藏文件
FILE_ATTRIBUTE_NORMAL——没有其他属性,只有单独使用时才有效
FILE_ATTRIBUTE_SYSTEM——系统文件
FILE_ATTRIBUTE_READONLY——只读文件
OK,可以看到右键文件属性的所有可能这里基本都有涉及,尽管没有向Linux那样提供了丰富的命令行选项,但是Windows在API参数中也为我们提供了尽可能多的功能选项。
三、使用文件设备
文件的使用非常普遍,因此重点来讨论下与文件设备有关的问题。首先我们看看如何取得文件的大小。
BOOL GetFileSizeEx(
HANDLE hFile,
PLARGE_INTEGER pliFileSize //联合类型指针
);
hFile当然就是目标文件的句柄了,pliFileSize是一个LARGE_INTEGER联合类型的地址,这个联合体允许我们以一个64位的有符号数的形式来引用一个64位有符号数,或者以两个32位值的形式来引用一个64位有符号数。下面是大概的定义:
Typedef union _LARGE_INTEGER {
Struct {
DWORD LowPart; //Low 32-bit unsigned value
LONG HighPart; //High 32-bit signed value
};
LONGLONG QuadPart; //Full 64-bit signed value
} LARGE_INTEGER, *PLARGE_INTEGER;
我们调用GetFileSizeEx可以得到文件的逻辑大小,使用GetCompressedFileSize可以得到文件压缩后的物理大小。
如果要对一个文件读写,其实就是向文件发送一个I/O请求,当然,这里的道理其实不仅适用于文件,对于设备同样适用,如邮件槽、管道、套接字等等。我们通常使用如下函数发送I/O请求,
BOOL ReadFile(
HANDLE hFile,
PVOID pvBuffer,
DWORD nNumBytesToRead,
PDWORD pdwNumBytes,
OVERLAPPED* pOverlapped
);
BOOL WriteFile(
HANDLE hFile,
CONST VOID *pvBuffer,
DWORD nNumBytesToWrite,
PDWORD pdwNumBytes,
OVERLAPPED* pOverlapped
);
执行同步I/O时pOverlapped设为NULL,只有在异步I/O时才有意义。
每当调用CreateFile时系统会创建一个文件内核对象来管理对文件的操作,在这个内核对象中维护这一个文件指针,它指向一个64位的偏移量,表示应该在哪里执行下一次同步读取或写入操作,我们称之为文件指针,初始化为0。需要注意的是,调用CreateFile打开同一个文件会得到多个文件内核对象,内部维护的文件指针彼此独立。我们可以使用SetFilePointerEx来设置文件指针的位置
BOOL SetFilePointerEx(
HANDLE hFile,
LARGE_INTEGER liDistanceToMove,
PLAGER_INTEGER pliNewFilePointer,
DWORD dwMoveMethod
);
hFile表示我们想要修改哪个文件内核对象的文件指针。liDistanceToMove告诉系统我们想要移动把指针在dwMoveMethod指定的位置移动多少个字节。使用负数可以将文件指针向后移动。dwMoveMehtod告诉系统如何解释liDistanceToMove,比如常用的参数值为
FILE_BEGIN——从文件头开始计算liDistanceToMove,之和为当前的文件指针
FILE_CURRENT——从当前的文件指针位置开始计算
FILE_END——在文件末尾开始计算
我们给出一个小例子
//本程序用来实验联系Windows关于文件读写操作的API
//命令行启动,参数作为写入文件的内容
#include
#include
#include
#include
using namespace std;
int main(int argc, char *argv[])
{
//操作一个文件之前必须先调用CreateFile()函数以指定的方式打开一个文件
//访问方式:GENERIC_READ |GENERIC_WRITE
//共享模式:当文件被打开时,其他程序可以对该文进进行的操作,表示不允许同时对任何操作
//创建方式:当文件存在或者不存在时的处理策略——CREATE_ALWAYS, CREATE_NEW, OPEN_ALWAYS, OPEN_EXISTING
//新创建的文件属性:ARCHIVE(存档),HIDDEN, SYSTEM, READONLY
//模板文件:没有则设为NULL
HANDLE hFile;
hFile = CreateFile(L"C:\\Documents and Settings\\admin\\桌面\\FileAPITest.txt", GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_HIDDEN, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
cout |
|
|