封装重叠 I/O 基本功能的类






4.50/5 (9投票s)
重叠 I/O - 实践
引言
我在 CodeProject 上的第一篇文章讨论了您可能希望使用 重叠 I/O[^ ] 的一个充分理由。在那篇文章中,我使用了伪代码概述了使用重叠 I/O 所需的步骤,以说明您选择使用它的原因。这次,我将提供一个封装重叠 I/O 基本功能的可用类。以下内容假定您已阅读了我之前的文章。
COverlappedIO
看起来是这样的。class COverlappedIO
{
public:
COverlappedIO(void);
virtual ~COverlappedIO(void);
void Attach(HANDLE hIO);
OVERLAPPED *operator&() { return &m_op; }
virtual bool Read(BYTE *pbData, DWORD dwBufferSize,
LPDWORD pdwBytesRead,
HANDLE hStopEvent = INVALID_HANDLE_VALUE);
virtual bool Write(BYTE *pbData, DWORD dwBufferSize,
HANDLE hStopEvent = INVALID_HANDLE_VALUE);
virtual bool Wait(LPDWORD pdwBytesTransferred,
HANDLE hStopEvent = INVALID_HANDLE_VALUE);
private:
HANDLE m_hIO;
OVERLAPPED m_op;
};
该类使用创建/附加(create/attach)模式。您创建一个 COverlappedIO
实例,可以作为类成员、局部变量或堆对象,然后将一个打开的句柄附加到一个可以写入或读取的对象上。这可以是一个文件句柄、一个命名管道或匿名管道的句柄、一个通信端口的句柄或一个邮箱(maislot)的句柄。
完成 Attach()
后,根据需要通过对象进行 Read()
或 Write()
。Write()
方法接受一个指向要写入的缓冲区的指针和缓冲区的长度(以字节为单位)。Read()
方法接受相同的参数,外加一个指向 DWORD
变量的指针,该变量将接收实际读取的字节数。这两个方法还接受一个可选的事件对象 HANDLE
。如果在调用中未指定句柄,则默认为 INVALID_HANDLE_VALUE
。如果使用默认值,Read()
或 Write()
方法将立即返回,并已启动一个重叠 I/O 操作。如果传递一个句柄,方法将等待直到 I/O 操作完成或直到该句柄被触发。请注意,这两种方法都无法实际验证您传递的是否是有效的事件句柄;它们只能检查您传递的句柄是否是默认的 INVALID_HANDLE_VALUE
。
该类也不知道您要对附加的句柄执行的操作是否是该句柄的有效操作。如果您想从句柄读取,您需要确保该句柄已以读取权限打开;写入也一样。这种“不知道”不是因为疏忽;据我所知,没有任何 API 可以调用来确定句柄是否支持您将要对其执行的操作,因此该类别只能信任您。
读取
看起来是这样的。Write()
也类似。
bool COverlappedIO::Read(BYTE *pbData, DWORD dwBufferSize,
LPDWORD pdwBytesRead, HANDLE hStopEvent)
{
assert(pbData);
assert(dwBufferSize);
assert(pdwBytesRead);
if (m_hIO != INVALID_HANDLE_VALUE)
{
::ReadFile(m_hIO, pbData, dwBufferSize, pdwBytesRead, &m_op);
if (hStopEvent != INVALID_HANDLE_VALUE)
return Wait(pdwBytesRead, hStopEvent);
else
return true;
}
// Something went wrong
return false;
}
我们对参数进行了一堆 assert
并继续。我们检查是否已调用 Attach()
来附加一个有效的句柄。如果是,我们发起一个 ReadFile()
调用,使用重叠 I/O,如果成功,我们将返回 Wait()
的结果或 true
。返回哪个取决于是否传递了有效的停止句柄。
如果您在没有指定有效停止句柄的情况下调用 Read()
或 Write()
,该调用将在设置 I/O 操作后返回,但可能在操作完成之前。这对于重叠 I/O 是正常的(它之所以称为重叠,是有原因的)。您发起一个 I/O 操作,然后继续您的正常工作。过一段时间后,您需要知道 I/O 是否已完成,以便知道可以安全地继续处理。这就是 Wait()
方法的作用。与 Read()
和 Write()
方法一样,Wait()
方法接受一个可选的停止事件句柄。Wait()
方法看起来是这样的。
bool COverlappedIO::Wait(LPDWORD pdwBytesTransferred, HANDLE hStopEvent)
{
HANDLE hOverlapped[2] = { m_op.hEvent, hStopEvent };
if (m_hIO != INVALID_HANDLE_VALUE)
{
switch (WaitForMultipleObjects(
hStopEvent == INVALID_HANDLE_VALUE ? 1 : 2,
hOverlapped, FALSE, INFINITE))
{
case WAIT_OBJECT_0:
// Signalled on the overlapped event handle, check the result
if (GetOverlappedResult(m_hIO, &m_op, pdwBytesTransferred, FALSE))
return true;
case WAIT_OBJECT_0 + 1:
// Signalled to stop, just stop...
return false;
}
}
return false;
}
那里没有什么惊天动地的。我们创建一个包含重叠事件句柄(重叠 I/O 操作完成时触发该句柄)和停止句柄的 2 个成员数组。然后我们检查该对象是否实际上附加了一个 I/O 句柄,如果是,我们就进入 WaitForMultipleObjects()
的调用,将我们之前创建的 2 个成员数组和告诉它要实际监控的句柄数量的句柄计数传递给它。如果我们传递的停止句柄是默认值(INVALID_HANDLE_VALUE
),那么我们将传递 1 给 WaitForMultipleOjects()
,否则传递 2。无论哪种情况,WaitForMultipleObjects()
都会等待直到发生某事。如果发生的事情是重叠事件句柄被触发,那么我们就调用 GetOverlappedResult()
来确定 I/O 操作是否成功,并返回适当的状态。如果发生其他任何事情,我们就返回 false
。
清理。
该类会自行清理它创建的任何对象。它不会(也不应该)调用 CloseHandle()
来关闭您附加到它的句柄。这就是为什么有一个 Attach()
方法,但没有 Detach()
方法的原因。
历史
2004 年 8 月 21 日 - 初始版本。
2004 年 9 月 3 日 - 对类接口进行了少量修改。