C++ WinINet异步模式灾难

C++ WinINet异步模式灾难,c++,c,windows,networking,wininet,C++,C,Windows,Networking,Wininet,很抱歉问这么长的问题。只是我花了几天的时间试图解决我的问题,我已经筋疲力尽了 我正在尝试以异步模式使用WinINet。我必须说。。。这简直是疯了。我真的不明白。它做了很多事情,但不幸的是,它的异步API设计得太差,无法用于具有高稳定性要求的严肃应用程序中 我的问题如下:我需要串行地执行大量HTTP/HTTPS事务,而我还需要能够在请求时立即中止它们 我打算以以下方式使用WinINet: 通过带有internetflag\u ASYNC标志的InternetOpen函数初始化WInINet的使用

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

我正在尝试以异步模式使用WinINet。我必须说。。。这简直是疯了。我真的不明白。它做了很多事情,但不幸的是,它的异步API设计得太差,无法用于具有高稳定性要求的严肃应用程序中

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

我打算以以下方式使用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
    调用回调函数。这允许我们使用一些通知机制(如设置事件)通知发起线程事务已完成
  • 发出事务的线程意识到事务已经完成。然后执行清理:关闭WinINet会话句柄(通过
    InternetCloseHandle
    ),并删除状态结构
  • 到目前为止似乎没有问题

    如何中止执行中的事务?一种方法是关闭相应的WinINet句柄。由于WinINet没有诸如
    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句柄的时间没有严格的定义
    • 谁知道还有什么
    我错了吗?这是我不明白的吗?或者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.