5

抱歉问了这么长的问题。只是我花了几天时间试图解决我的问题,我已经筋疲力尽了。

我正在尝试在异步模式下使用 WinINet。我必须说……这简直是了。我真的无法理解这一点。它做了很多事情,但不幸的是,它的异步 API 设计得非常糟糕,以至于它不能用于具有高稳定性要求的严肃应用程序中。

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

我打算按以下方式使用 WinINet:

  1. InternetOpen通过带有INTERNET_FLAG_ASYNC标志的函数初始化 WInINet 的使用。
  2. 安装一个全局回调函数(通过InternetSetStatusCallback)。

现在,为了执行我想做的交易:

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

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

如何中止正在执行的事务?一种方法是关闭相应的 WinINet 句柄。而且由于 WinINet 没有诸如InternetAbortXXXX关闭句柄之类的功能,因此似乎是中止的唯一方法。

这确实奏效了。这样的事务立即完成并带有ERROR_INTERNET_OPERATION_CANCELLED错误代码。但是这里所有的问题都开始了......

我遇到的第一个令人不快的意外是 WinINet 有时会调用事务的回调函数,即使已经被中止。根据 MSDN,这INTERNET_STATUS_HANDLE_CLOSING是回调函数的最后一次调用。但这是一个谎言。我看到的是,有时会INTERNET_STATUS_REQUEST_COMPLETE针对同一个句柄发出相应的通知。

我还尝试在关闭它之前禁用事务句柄的回调函数,但这没有帮助。看来WinINet的回调调用机制是异步的。因此 - 即使事务句柄已关闭,它也可能调用回调函数。

这带来了一个问题:只要 WinINet可以调用回调函数——显然我无法释放事务状态结构。但是我怎么知道 WinINet 是否会这么好意地称呼它呢?从我所看到的 - 没有一致性。

尽管如此,我已经解决了这个问题。相反,我现在保留已分配事务结构的全局映射(当然受关键部分保护)。然后,在回调函数内部,我确保事务确实存在并在回调调用期间锁定它。

但是后来我发现了另一个问题,到目前为止我无法解决。当我在交易开始后不久就中止交易时,就会出现这种情况。

发生的事情是我调用InternetOpenUrl,它返回ERROR_IO_PENDING错误代码。然后我只是等待(通常很短),直到回调函数将被INTERNET_STATUS_HANDLE_CREATED通知调用。然后 - 事务句柄被保存,所以现在我们有机会在没有句柄/资源泄漏的情况下中止,我们可以继续。

我试图在这一刻之后进行中止。即在我收到后立即关闭此句柄。猜猜会发生什么?WinINet 崩溃,内存访问无效!这与我在回调函数中所做的任何事情都无关。甚至没有调用回调函数,崩溃发生在 WinINet 的深处。

另一方面,如果我等待下一个通知(例如“解析名称”)-通常它会起作用。但有时也会崩溃!Sleep如果我在获取句柄和关闭它之间做一些最小化,问题似乎就消失了。但显然这不能被接受为一个严肃的解决方案。

所有这一切让我得出结论: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));
}

通常在第一次/第二次迭代时应用程序崩溃。WinINet 创建的线程之一生成访问冲突:

Access violation reading location 0xfeeefeee.

值得注意的是,上面的地址对于用 C++(至少是 MSVC)编写的代码有特殊的意义。AFAIK,当您删除具有vtable(即-具有虚拟功能)的对象时-将其设置为上述地址。所以这是尝试调用已删除对象的虚函数。

4

2 回答 2

8

Context ctx 的声明是问题的根源,它是在 for(;;) 循环中声明的,因此它是为每个循环创建的局部变量,它将在每个循环结束时被销毁并且不再可访问。

结果,当调用回调时,ctx 已经被销毁,传递给回调的指针指向已销毁的 ctx,无效的内存指针导致崩溃。

于 2011-03-02T16:15:19.643 回答
2

特别感谢卢克。

当我明确使用 ++ 而不是 all-in-one 时,所有问题InternetConnectHttpOpenRequest消失HttpSendRequestInternetOpenUrl

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

于 2010-08-06T20:14:18.593 回答