对不起,这么长的问题。 这只是我花了好几天的时间来解决我的问题,我已经筋疲力尽了。
我试图在asynchronous模式下使用WinINet。 我必须说…这简直是疯了 。 我真的不明白这一点。 它做了很多事情,但不幸的是,它的asynchronousAPIdevise得太差,以至于不能用于具有高度稳定性要求的严重应用程序。
我的问题如下:我需要连续执行大量的HTTP / HTTPS事务,而我也需要能够根据请求立即中止它们。
我打算用下面的方式来使用WinINet:
INTERNET_FLAG_ASYNC
标志通过InternetOpen
函数初始化WInINet的使用。 InternetSetStatusCallback
)。 现在,为了执行我认为要做的事务:
InternetOpenUrl
启动交易。 在asynchronous模式下,它通常立即返回一个错误,即ERROR_IO_PENDING
。 其中一个参数是“上下文”,这个值将被传递给callback函数。 我们将它设置为指向每个事务状态结构的指针。 INTERNET_STATUS_HANDLE_CREATED
。 此时我们保存WinINet会话句柄。 INTERNET_STATUS_REQUEST_COMPLETE
调用。 这允许我们使用一些通知机制(例如设置事件)来通知发起线程事务完成。 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不佳。
我错了吗? 那是我不明白的吗? 或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
时,所有问题消失。
我没有收到请求句柄的任何通知(不要混淆“连接”句柄)。 加上没有更多的崩溃。