C++ WinINet异步模式灾难
很抱歉问这么长的问题。只是我花了几天的时间试图解决我的问题,我已经筋疲力尽了 我正在尝试以异步模式使用WinINet。我必须说。。。这简直是疯了。我真的不明白。它做了很多事情,但不幸的是,它的异步API设计得太差,无法用于具有高稳定性要求的严肃应用程序中 我的问题如下:我需要串行地执行大量HTTP/HTTPS事务,而我还需要能够在请求时立即中止它们 我打算以以下方式使用WinINet:C++ WinINet异步模式灾难,c++,c,windows,networking,wininet,C++,C,Windows,Networking,Wininet,很抱歉问这么长的问题。只是我花了几天的时间试图解决我的问题,我已经筋疲力尽了 我正在尝试以异步模式使用WinINet。我必须说。。。这简直是疯了。我真的不明白。它做了很多事情,但不幸的是,它的异步API设计得太差,无法用于具有高稳定性要求的严肃应用程序中 我的问题如下:我需要串行地执行大量HTTP/HTTPS事务,而我还需要能够在请求时立即中止它们 我打算以以下方式使用WinINet: 通过带有internetflag\u ASYNC标志的InternetOpen函数初始化WInINet的使用
internetflag\u ASYNC
标志的InternetOpen
函数初始化WInINet的使用InternetSetStatusCallback
)InternetOpenUrl
启动事务。在异步模式下,它通常会立即返回一个错误,即error\u IO\u PENDING
。它的一个参数是“context”,该值将传递给回调函数。我们将其设置为指向每个事务状态结构的指针INTERNET\u status\u HANDLE\u CREATED
。此时,我们保存WinINet会话句柄INTERNET\u STATUS\u REQUEST\u COMPLETE
调用回调函数。这允许我们使用一些通知机制(如设置事件)通知发起线程事务已完成InternetCloseHandle
),并删除状态结构InternetAbortXXXX
之类的函数,因此关闭句柄似乎是中止的唯一方法
这确实奏效了。这样的交易会立即完成,同时错误\u互联网操作\u被取消
错误代码。
但是这里所有的问题都开始了
我遇到的第一个令人不快的惊喜是,WinINet有时倾向于调用事务的回调函数,即使在事务已经中止之后。
根据MSDN,INTERNET\u STATUS\u HANDLE\u CLOSING
是回调函数的最后一次调用。但是这是一个谎言。我看到的是,对于同一个句柄,有时会出现后续的internetstatus\u REQUEST\u COMPLETE
通知
在关闭事务句柄之前,我还尝试禁用它的回调函数,但这没有帮助。WinINet的回调调用机制似乎是异步的。因此-即使在事务句柄关闭后,它也可以调用回调函数
这就带来了一个问题:只要WinINet可以调用回调函数,显然我无法释放事务状态结构。但我怎么知道WinINet会不会这么好心的称呼它呢?从我所看到的,没有一致性
尽管如此,我一直在努力解决这个问题。相反,我现在保留分配的事务结构的全局映射(当然受关键部分的保护)。然后,在回调函数中,我确保事务确实存在,并在回调调用期间锁定它
但后来我发现了另一个问题,到目前为止我还无法解决。当我在事务启动后很快中止事务时,它就会出现
我调用了InternetOpenUrl
,它返回ERROR\u IO\u PENDING
错误代码。然后我只需等待(通常非常短),直到回调函数将被调用,并带有INTERNET\u STATUS\u HANDLE\u CREATED
通知。然后-保存事务句柄,这样我们就有机会在没有句柄/资源泄漏的情况下中止,我们可以继续
我试着在这一刻之后做流产。也就是说,在我收到该手柄后,立即关闭该手柄。
猜猜会发生什么WinINet崩溃,内存访问无效!这与我在回调函数中所做的一切无关。回调函数甚至没有被调用,崩溃发生在WinINet的深处
另一方面,如果我等待下一个通知(例如“解析名称”),它通常会工作。但有时也会崩溃!
如果我在获取句柄和关闭句柄之间放置一些最小的睡眠
,问题似乎就消失了。但显然,这不能被视为一个严肃的解决方案
所有这些都让我得出结论:WinINet的设计很糟糕。
- 对于特定会话(事务)的回调函数调用范围没有严格的定义
- 对于允许我关闭WinINet句柄的时间没有严格的定义
- 谁知道还有什么
HINTERNET g_hINetGlobal;
struct Context
{
HINTERNET m_hSession;
HANDLE m_hEvent;
};
void CALLBACK INetCallback(HINTERNET hInternet, DWORD_PTR dwCtx, DWORD dwStatus, PVOID pInfo, DWORD dwInfo)
{
if (INTERNET_STATUS_HANDLE_CREATED == dwStatus)
{
Context* pCtx = (Context*) dwCtx;
ASSERT(pCtx && !pCtx->m_hSession);
INTERNET_ASYNC_RESULT* pRes = (INTERNET_ASYNC_RESULT*) pInfo;
ASSERT(pRes);
pCtx->m_hSession = (HINTERNET) pRes->dwResult;
VERIFY(SetEvent(pCtx->m_hEvent));
}
}
void FlirtWInet()
{
g_hINetGlobal = InternetOpen(NULL, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, INTERNET_FLAG_ASYNC);
ASSERT(g_hINetGlobal);
InternetSetStatusCallback(g_hINetGlobal, INetCallback);
for (int i = 0; i < 100; i++)
{
Context ctx;
ctx.m_hSession = NULL;
VERIFY(ctx.m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL));
HINTERNET hSession = InternetOpenUrl(
g_hINetGlobal,
_T("http://ww.google.com"),
NULL, 0,
INTERNET_FLAG_NO_UI | INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_RELOAD,
DWORD_PTR(&ctx));
if (hSession)
ctx.m_hSession = hSession;
else
{
ASSERT(ERROR_IO_PENDING == GetLastError());
WaitForSingleObject(ctx.m_hEvent, INFINITE);
ASSERT(ctx.m_hSession);
}
VERIFY(InternetCloseHandle(ctx.m_hSession));
VERIFY(CloseHandle(ctx.m_hEvent));
}
VERIFY(InternetCloseHandle(g_hINetGlobal));
}
值得注意的是,上面的地址对C++编写的代码(至少MSVC)有特殊的意义。
AFAIK当您删除具有vtable
(即-具有虚拟功能)的对象时,它将设置为上述地址。
s
Access violation reading location 0xfeeefeee.