在我的io完成端口实现中,我是通过从OVERLAPPED结构继承的结构中的操作码上的操作码上的switch语句传递通知的,期望OVERLAPPED基距所导出的偏移量为0。
后来,我在派生的结构中使用了虚拟方法,而不是使用带开关的操作码,这使设计更容易,更清晰。我仍然认为OVERLAPPED结构的偏移量为0,所以我只是使用类似的方法来执行io操作:
WSASend(ns, &wsbuf, 1, &sent, 0, (LPWSAOVERLAPPED)(std::addressof(CTX)), nullptr);
其中CTX
是BaseIoCtx
类型的结构,即:
BaseIoCtx : public OVERLAPPED
{
void *fd;
virtual void HandleIocpPacket(io::iocp::CompletionResult& IocpPacket) = 0;
virtual ~BaseIoCtx() = default;
};
,然后在io循环中回退,我使用了以下代码:
// BaseIoCtx inherits from OVERLAPPED
// result is an OVERLAPPED_ENTRY
BaseIoCtx *ctx = reinterpret_cast<BaseIoCtx*>(result.lpOverlapped);
ctx->HandleIocpPacket(result); // the virtual function that delivers the notification
我认为这应该运作良好,并且运作了一个多月!
昨天进行了一些测试之后,我在这一行发生了奇怪的崩溃:
ctx->HandleIocpPacket(result); // the virtual function that delivers the notification
调试器说:read access violation ... ctx-> was (nullptr, 0XFFFFFFFF, any strange address)
无论如何,我将ctx本身放在Visual Studio调试器中的有效地址上,并在OVERLAPPED基数中包含正确的传输长度,但是__vfptr
是nullptr
或一个非常奇怪的地址,例如0xFFFFFFF或0xCCCCCCCCCCC因此,我在提交io请求之前在行上设置了一个断点,并看到__vfptr指向有效的vtable
并包含BaseIoCtx
的两个虚函数,所以我发现在io请求期间Windows内部函数在__vfptr
处写入并破坏了它。
我想知道是因为我虽然基本结构不是在派生类的开头,但不是__vfptr
,但是在对offsetof进行了一些实验之后,我发现__vfptr
的位置是偏移量0!
但是我然后将BaseIoCtx
更改为:
struct BaseIoCtx : public IoContext
{
OVERLAPPED ov = { 0 };
static BaseIoCtx * from_ov_ptr(OVERLAPPED *ov_ptr) noexcept
{
constexpr std::size_t ov_offset = offsetof(BaseIoCtx, ov);
return reinterpret_cast<BaseIoCtx*>(reinterpret_cast<char*>(ov_ptr) - ov_offset);
}
void *fd;
virtual void HandleIocpPacket(io::iocp::CompletionResult& IocpPacket) = 0;
virtual ~BaseIoCtx() = default;
};
以及我使用的提交中:
WSASend(ns, &wsbuf, 1, &sent, 0, (LPWSAOVERLAPPED)(std::addressof(CTX.ov)), nullptr);
然后在io循环中,我将结构取回:
BaseIoCtx * ctx = BaseIoCtx::from_ov_ptr(result.lpOverlapped);
,并且效果很好。现在的问题是:第一个实现如何在同一个编译器上长期运行!新方法是否被认为是C ++中的一种可移植方式,而不是hack方法?如果是hack方法,还有更好的方法吗?