I/O设备处理必然让主程序停下来干等I/O的完成,解决这个问题,可以使用OVERLAPPED。
OVERLAPPED I/O是WIN32的一项技术, 你可以要求操作系统为你传送数据,并且在传送完毕时通知你。这项技术使你的程序在I/O进行过程中仍然能够继续处理事务。事实上,操作系统内部正是以线程来I/O完成OVERLAPPED I/O。你可以获得线程的所有利益,而不需付出什么痛苦的代价。也就是说,OVERLAPPED主要是设置异步I/O操作,异步I/O操作是指应用程序可以在后台读或者写数据,而在前台做其他事情。
Allen denver在他的《Serial Communication in Win32》中是这样解释OVERLAPPED I/O的:我个人认为还是比较准确的。
利用win32所谓的overlapped I/O特征,可以并行处理I/O操作,并且当任何一个I/O完成时,你的程序会收到一个通告。其它操作系统把这个特征称为nonblockeingI/O或者asynchronous I/O。
Overlapped I/O是win32的一项技术,你可以要求操作系统为你传送数据,并且在传送完毕时通知你。这项技术使你的程序在I/O进行中仍然能够继续处理事物。Overlapped I/O的基本形式是以ReadFile和WriteFile函数完成的。
首先以FILE_FLAG_OVERLAPPED告诉Win32说不要使用默认的同步I/O(在CreataFile函数中设定OVERLAPPED参数)。设立一个OVERLAPPED结构时,设定“I/O请求”的必要参数。接下来,调用ReadFile()并以OVERLAPPED结构的地址作为最后一个参数。这个时候,理论上,win32会在后台处理I/O操作。你的程序可以继续处理其他事情。
OVERLAPPED结构
typedef struct _OVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset; //文件开始读写的偏移位置,该偏移位置从文件头开始算起
DWORD OffsetHigh; //64位的文件偏移位置中较高的32位
HANDLE hEvent; //event对象,当Overlapped I/O完成时即被激发。
} OVERLAPPED,*LPOVERLAPPED;
OVERLAPPED结构的生命期超过ReadFile()和WriteFile()函数,所以应把这个结构放在一个安全的地方,局部变量不是一个安全的地方,她会很快就过了生存范围。最安全的地方就是heap。
应用:
1, 激发的文件handle
2, 激发的Event对象
3, 异步过程调用(Asynchronous Precudure Call,APC)
4, I/O Completion Ports
Win32文件操作函数
Win32之中有三个基本的函数用来执行I/O:
CreateFile()
ReadFile();
WriteFile();
没有哪一个函数用来关闭文件,只要调用CloseHandle()即可。
CreateFile()可以用来打开各种资源,包括(但不限制于)文件(硬盘、软盘、光盘或者其它)、串行口和并行口(serial and parallel ports)、Named pipes
createFile函数
HANDLE CreateFile(
LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
);
参数:
lpFileName:文件名
dwDesiredAccess:访问模式(写/读),可取值如下:
GENERIC_READ:允许对设备进行读访问;
GENERIC_WRITE:允许对设备进行写访问(可组合使用);
0 :只允许获取与一个设备有关的信息
dwShareMode:共享模式,可取值如下:
FILE_SHARE_READF:允许对文件进行共享读操作;
LE_SHARE_WRITE:允许对文件进行共享写操作(可组合使用);
0:不共享;
lpSecurityAttributes: 指向一个SECURITY_ATTRIBUTES结构的指针,定义了文件的安全特性
dwCreationDisposition:如何创建,可取值如下:
CREATE_NEW:创建文件;如文件存在则会出错
CREATE_ALWAYS: 创建文件,会改写前一个文件
OPEN_EXISTING: 文件必须已经存在。由设备提出要求
OPEN_ALWAYS: 如文件不存在则创建它
TRUNCATE_EXISTING 将现有文件缩短为零长度
dwFlagsAndAttributes:文件属性,一个或多个下述常数
FILE_ATTRIBUTE_ARCHIVE标记归档属性
FILE_ATTRIBUTE_COMPRESSED 将文件标记为已压缩,或者标记为文件在目录中的默认压缩方式
FILE_ATTRIBUTE_NORMAL: 默认属性
FILE_ATTRIBUTE_HIDDEN :隐藏文件或目录
FILE_ATTRIBUTE_READONLY: 文件为只读
FILE_ATTRIBUTE_SYSTEM :文件为系统文件
FILE_FLAG_WRITE_THROUGH: 操作系统不得推迟对文件的写操作
FILE_FLAG_OVERLAPPED: 对文件进行重叠(Overlapped)操作
FILE_FLAG_NO_BUFFERING: 禁止对文件进行缓冲处理。文件只能写入磁盘卷的扇区块
FILE_FLAG_RANDOM_ACCESS: 针对随机访问对文件缓冲进行优化
FILE_FLAG_SEQUENTIAL_SCAN :针对连续访问对文件缓冲进行优化
FILE_FLAG_DELETE_ON_CLOSE :关闭了上一次打开的句柄后,将文件删除。特别适合临时文件
也可在Windows NT下组合使用下述常数标记:
SECURITY_ANONYMOUS, SECURITY_IDENTIFICATION, SECURITY_IMPERSONATION,SECURITY_DELEGATION, SECURITY_CONTEXT_TRACKING,SECURITY_EFFECTIVE_ONLY
hTemplateFile:一个临时文件,将拥有全部的属性拷贝,可以设为0。
返回值
如执行成功,则返回文件句柄。
函数说明:
INVALID_HANDLE_VALUE表示出错,会设置GetLastError。即使函数成功,但若文件存在,且指定了CREATE_ALWAYS或 OPEN_ALWAYS,GetLastError也会设为ERROR_ALREADY_EXISTS。
第6个参数dwFlagsAndAttributes,指定使用传统调用或者Overlapped调用,但不能两个都指定。设定FILE_FLAG_OVERLAPPED值,那对该文件的每一个操作都将是Overlapped操作。
ReadFile函数
BOOL ReadFile(
HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead,
LPOVERLAPPED lpOverlapped
);
参数:
hFile:文件的句柄
lpBuffer:用于保存读入数据的一个缓冲区
nNumberOfBytesToRead:要读入的字符数
lpNumberOfBytesRead:指向实际读取字节数的指针
lpOverlapped:如文件打开时指定了FILE_FLAG_OVERLAPPED,那么这个参数必须引用一个OVERLAPPED结构。否则,应将这个参数设为NULL
返回值:
调用成功,返回非0
调用不成功,返回0
函数说明:
从文件指针指向的位置开始将数据读出到一个文件中,且支持同步和异步操作,
如果文件打开方式没有指明FILE_FLAG_OVERLAPPED的话,当程序调用成功时,它将实际读出文件的字节数保存到lpNumberOfBytesRead指明的地址空间中;
如果文件打开方式指明FILE_FLAG_OVERLAPPED的话,函数将按照Overlapped结构中指定的位置开始读取数据。
WriteFile函数
BOOL WriteFile(
HANDLE hFile,
LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped
);
参数:
hFile:文件的句柄
lpBuffer:用于保存写入数据的一个缓冲区
nNumberOfBytesToRead:欲写入的字节数
lpNumberOfBytesRead:指向实际写入字节数的指针
lpOverlapped:如文件打开时指定了FILE_FLAG_OVERLAPPED,那么这个参数必须引用一个OVERLAPPED结构。否则,应将这个参数设为NULL
返回值:
调用成功,返回非0;调用不成功,返回0
函数说明:
从文件指针指向的位置开始将数据写入到一个文件中,且支持同步和异步操作, 如果文件打开方式没有指明FILE_FLAG_OVERLAPPED的话,当程序调用成功时,它将实际写入文件的字节数保存到lpNumberOfBytesRead指明的地址空间中;
如果文件打开方式指明FILE_FLAG_OVERLAPPED的话,函数将按照OVERLAPPED结构中指定的位置开始写入数据。
Setfilepointer函数
DWORD SetFilePointer(
HANDLE hFile,
LONG lDistanceToMove,
PLONG lpDistanceToMoveHigh,
DWORD dwMoveMethod
);
参数:
hFile:文件句柄
lDistanceToMove:移动的字节数低DWORD
lpDistanceToMoveHigh:移动的字节数高DWORD,为了支持64位长度的大文件,而 用来指定64字节的高32位,如果文件大小只需要32位则设置为NULL
ldwMoveMethod:移动方法,可取值如下:
FILE_BEGIN从文件开始处开始移动
FILE_CURRENT 从文件开始除开始移动
FILE_END 从文件末尾开始移动
返回值:
函数成功,返回文件操作指针的位置;函数失败返回INVALID_SET_FILE_POINTER
函数说明:
移动文件指针到相关位置(和文件读写不同,这个函数没有异步版本)
被激发的File handles
首先在CreataFile函数中设定FILE_FLAG_OVERLAPPED参数;再设立一个OVERLAPPED结构时,设定“I/O请求”的必要参数。接下来,调用ReadFile()并以OVERLAPPED结构的地址作为最后一个参数。这个时候, win32会在后台处理I/O操作。你的程序可以继续处理其他事情。
如果需等待Overlapped I/O的执行结果,可以调用wait…函数,并设定文件handle为其参数。一旦Overlapped I/O操作完成,文件handle即被激发。
当你完成操作,请调用GetOverlappedResult函数以确定结果。
GetOverlappedResult函数
BOOL GetOverlappedResult(
HANDLE hFile,
LPOVERLAPPED lpOverlapped,
LPDWORD lpNumberOfBytesTransferred,
BOOL bWait
);
参数:
hFile:文件或设备的句柄
lpOverlapped:一个指向OVERLAPPED结构的指针
lpNumberOfBytesTransferred:真正被传输的字节数
bWait:是否要等待操作完成,true表示要等待,利用这个特点可以利用它来等待异步文件操作的结束
返回值:
如果Overlapped操作成功,则返回ture;否则返回false。
如果bWait设置为false,而Overlapped还没有完成,GetLastError会返回ERROR_IO_INCOMPLETE.
实例
int ReadSomething()
{
bool rc;
HANDLE hFile;
DWORD numread;
OVERLAPPED overlap;
char buf[512];
hFile = CreateFile("C://WINDOWS//WINFILE.EXE",
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
return -1;
}
memset(&overlap, 0, sizeof(overlap));
overlap.Offset = 1500;//从文件的第1500位置读取数据
rc = ReadFile(hFile, buf, 300, &numread, &overlap);//读取300个字节
if (rc)
{
//the data was read successfully
}
else
{
if (GetLastError() == ERROR_IO_PENDING)
{
WaitForSingleObject(hFile,INFINITE);
rc = GetOverlappedResult(hFile, &overlap, &numread, FALSE);
}
else
{
//something went wrong
}
}
CloseHandle(hFile);
return TRUE;
}
代码说明:
虽然你要求一个overlaopped操作,但它并不一定就是overlapped。因为如果操作系统认为它可以快速的取得那份数据,那么文件操作就会在ReadFile()返回之前完成,而ReadFile()将返回true。
操作系统把这个overlapped操作请求放到队列中等待执行,那么ReadFile()和WriteFile()都会返回false。你必须调用GetLastError(),如果它传回ERROR_IO_PENDING,那意味着overlapped操作被放到队列中等待执行。
被激发的Event对象
以文件handle作为激发机制,有一个明显的限制,那就是没办法说出到底是哪一个Overlapped操作完成了。调用GetOverlappedResult并不是很好的做法,win32提供了一个比较好的做法。
OVERLAPPED结构中的最后一个参数是一个Event句柄,如果使用文件handle作为激发对象,可以设为null;如果设定这个参数为一个Event句柄时,系统会在Overlapped操作完成时,自动将此Event激发。由于每一个Overlapped都有它自己的OVERLAPPED结构,所以每一个结构都有一个Event对象,用来代表该操作。
你所使用的Event对象必须是手动重置而非自动重置。因为系统核心可能在你等待该Event对象之前,先激发它,这样你的wait..函数永远都不会返回了。
使用Event对象搭配Overlapped,你就可以对同一个文件发出多个读写操作,每一个操作有自己的Event对象,然后再调用waitformultipleObject函数来等待其中之一或全部完成。
异步过程调用(APC)
使用Overlapped+Event,会产生两个基础问题。一:使用WaitForMultipleObjects函数,你只能够等待最多MAXIMUM_WAIT_OBJECTS个对象;二:你必须不断的根据哪一个handle被激发,而计算如何反映。
这两个问题可以靠一个所谓的异步过程调用(Asynchronous Procedure Call,APC)解决。使用ReadFileEx()和WriteFileEx()就可以调用这个机制。这两个函数允许你指定一个额外的参数,是一个callback函数地址。当一个Overlapped I/O 完成时,系统会调用callback函数。这个callback函数被称为I/O Completion routine。
WriteFileEx函数
BOOL WriteFileEx(
HANDLE hFile,
LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPOVERLAPPED lpOverlapped,
LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
参数:
hFile:使用FILE_FLAG_OVERLAPPED标识创建的file句柄。
lpBuffer:指向数据的指针
nNumberOfBytesToWrite:需要写入的字节数。
lpOverlapped:OVERLAPPED变量的指针。
lpCompletionRoutine:函数指针。函数原型为
VOID CALLBACK FileIOCompletionRoutine(
DWORD dwErrorCode,
DWORD dwNumberOfBytesTransfered,
LPOVERLAPPED lpOverlapped
);
返回值:
函数说明:
将数据写入文件。该函数只能用于异步I/O操作,即overlapped I/O。 注:WriteFile()可以用于同步和异步I/O操作。指定的OVERLAPPED中的hEvent栏位不需要放置一个event handle。可以自由运用。一般将hEvent栏位指向一个结构指针。
ReadFileEx函数
BOOL ReadFileEx(
HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
LPOVERLAPPED lpOverlapped,
LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
说明:读入数据。参数与上同。