WinINetasynchronous模式灾难

对不起,这么长的问题。 这只是我花了好几天的时间来解决我的问题,我已经筋疲力尽了。

我试图在asynchronous模式下使用WinINet。 我必须说…这简直是疯了 。 我真的不明白这一点。 它做了很多事情,但不幸的是,它的asynchronousAPIdevise得太差,以至于不能用于具有高度稳定性要求的严重应用程序。

我的问题如下:我需要连续执行大量的HTTP / HTTPS事务,而我也需要能够根据请求立即中止它们。

我打算用下面的方式来使用WinINet:

  1. 通过INTERNET_FLAG_ASYNC标志通过InternetOpen函数初始化WInINet的使用。
  2. 安装全局callback函数(通过InternetSetStatusCallback )。

现在,为了执行我认为要做的事务:

  1. 用描述事务状态的各种成员分配每个事务结构。
  2. 调用InternetOpenUrl启动交易。 在asynchronous模式下,它通常立即返回一个错误,即ERROR_IO_PENDING 。 其中一个参数是“上下文”,这个值将被传递给callback函数。 我们将它设置为指向每个事务状态结构的指针。
  3. 在此之后不久,全局callback函数被调用(来自另一个线程)状态为INTERNET_STATUS_HANDLE_CREATED 。 此时我们保存WinINet会话句柄。
  4. 事务完成后,最终callback函数将通过INTERNET_STATUS_REQUEST_COMPLETE调用。 这允许我们使用一些通知机制(例如设置事件)来通知发起线程事务完成。
  5. 发出交易的线程认识到它是完整的。 然后它进行清理:closuresWinINet会话句柄(通过InternetCloseHandle ),并删除状态结构。

到目前为止,似乎没有问题。

如何中止正在执行的交易? 一种方法是closures相应的WinINet句柄。 而且由于WinINet没有像InternetAbortXXXX这样的函数 – closures句柄似乎是中止的唯一方法。

事实上,这工作。 这样的事务立即用ERROR_INTERNET_OPERATION_CANCELLED错误代码完成。 但是,这里所有的问题开始…

我遇到的第一个不愉快的惊喜是,WinINet有时会调用事务的callback函数,即使它已经被中止了。 根据MSDN, INTERNET_STATUS_HANDLE_CLOSING是callback函数的最后一次调用。 但是这是一个谎言 。 我所看到的是, 有时会出现相同句柄的INTERNET_STATUS_REQUEST_COMPLETE通知。

我也试图在closures之前closures事务句柄的callback函数,但是这并没有帮助。 似乎WinINet的callback调用机制是asynchronous的。 因此 – 即使事务句柄已经closures,它也可能调用callback函数。

这就产生了一个问题:只要WinINet 可以调用callback函数 – 显然我不能释放事务状态结构。 但是我怎么知道WinINet会不会这样称呼呢? 从我所看到的 – 没有一致性。

不过,我一直在这个工作。 相反,我现在保留一个分配的交易结构的全球地图(受关键部分的保护)。 然后,在callback函数中,确保事务确实存在,并在callback调用期间locking它。

但后来我发现了另外一个问题,至今我都解决不了。 它是在我开始不久之后中止交易时产生的。

发生什么事是我打电话InternetOpenUrl ,它返回ERROR_IO_PENDING错误代码。 然后我等待(通常很短),直到使用INTERNET_STATUS_HANDLE_CREATED通知调用callback函数。 然后 – 事务句柄被保存,所以现在我们有机会中止没有处理/资源泄漏,我们可以继续。

这一刻之后,我试图彻底放弃。 也就是说,我收到后立即closures这个句柄。 猜猜会发生什么? WinINet崩溃 ,无效的内存访问! 这和我在callback函数中做的事无关。 callback函数甚至没有调用,崩溃是在WinINet的深处。

另一方面,如果我等待下一个通知(例如“parsing名称”) – 通常是有效的。 但有时也会崩溃! 这个问题似乎消失了,如果我把一些最小的Sleep之间获得处理和closures它。 但显然这不能被认为是一个严肃的解决办法。

所有这一切使我得出结论: WinINetdevise不佳。

  • 关于特定会话(事务)的callback函数调用的范围没有严格的定义。
  • 关于允许我们closuresWinINet句柄的时刻没有严格的定义。
  • 谁知道还有什么?

我错了吗? 那是我不明白的吗? 或WinINet不能安全使用?

编辑:

这是演示第二个问题的最小代码块:崩溃。 我已经删除了所有的error handling等

 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)); } 

通常在第一次/第二次迭代中,应用程序崩溃。 由WinINet创build的一个线程生成访问冲突:

 Access violation reading location 0xfeeefeee. 

值得注意的是,上述地址对用C ++编写的代码(至less是MSVC)有特殊的意义。 AFAIK当你删除一个具有虚拟vtable的对象(即 – 具有虚拟function) – 它被设置为上述地址。 所以这是一个尝试调用一个已经被删除的对象的虚函数。

Context的声明ctx是问题的来源,它是在for(;;)循环中声明的,所以它是为每个循环创建的局部变量,它会被销毁,并且不能在每个循环结束时被访问。

因此,当一个回调被调用时,ctx已经被销毁,指针被传递到一个被销毁的ctx的回调点,无效的内存指针导致崩溃。

特别感谢卢克。

当我明确使用InternetConnect + HttpOpenRequest + HttpSendRequest而不是所有功能于一身的InternetOpenUrl时,所有问题消失。

我没有收到请求句柄的任何通知(不要混淆“连接”句柄)。 加上没有更多的崩溃。