WinInet和InternetOpen

时间:2016-06-06 20:49:13

标签: c++ wininet

文档说明可以多次调用InternetOpen而不会出现任何问题。我的问题是,我应该多次在它返回的句柄上调用InternetCloseHandle吗?

例如,我有一个我调用CAPIRequestContext的类,它有一个由InternetOpen返回的句柄。我的每个请求都有它自己的副本。现在,我在析构函数中调用了InternetCloseHandle,因此它被多次调用。

我想知道以下是否会导致问题: 线程A创建一个CAPIRequestObject,它调用InternetOpen并存储句柄。线程B执行相同操作,但在线程A退出之前超出范围,因此线程B调用它自己的CAPIRequestObject中的析构函数,该函数在InternetOpen返回的句柄上调用InternetCloseHandle

我应该在班级的析构函数中删除对InternetCloseHandle的调用吗?至少对于InternetHandle?我假设我应该为HttpOpenRequest返回的句柄调用InternetCloseHandle。

我对InternetConnect返回的句柄有类似的疑问。这些句柄是否共享?

以下是一些示例代码,减去了一些我认为与此问题无关的外部代码:

class CAPIClient;
class CAPIRequest
{
public:
     CAPIRequestContext()
     {
         m_hConn = NULL;
         m_hInternet = NULL;
         m_hRequest = NULL;
     }

     ~CAPIRequestContext()
     {
         if (m_hRequest) InternetCloseHandle(m_hRequest);
         if (m_hConn) InternetCloseHandle(m_hConn);
         if (m_hInternet) InternetCloseHandle(m_hInternet);
     }

     bool Init(const CAPIClient &client, const std::string &uri, const std::string &method)
     {
          ATLASSERT(!(m_hInternet || m_hConn || m_hRequest));
          if (m_hInternet || m_hConn || m_hRequest) throw std::exception("Cannot init request more than once.");

          bool success = false;
          m_AuthToken = *client.m_pAuthToken;
          URI = uri;
          m_hInternet = InternetOpen("MyApp", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
DWORD requestTimeout = 60 * 1000; // Set timeout to 60 seconds instead of 30

          if (m_hInternet)
          {  
                InternetSetOption(m_hInternet, INTERNET_OPTION_RECEIVE_TIMEOUT, &requestTimeout, sizeof(requestTimeout));
                m_hConn = InternetConnect(m_hInternet, (LPSTR)client.m_host.c_str(), client.m_port, NULL, NULL, INTERNET_SERVICE_HTTP, 0, (DWORD_PTR)this);
                if (m_hConn) {
                      m_hRequest = HttpOpenRequest(m_hConn, method.c_str(), uri.c_str(), "HTTP/1.1", NULL, NULL, client.GetOpenRequestFlags(), 0);
                }
                if (m_hRequest)
                {
                    success = true;
                }
          }
       return success;
     }      
}

    // There are additional calls like 
    // SendRequest
    // GetData
    // GetFullResponse

private:
     // Added these methods to make sure I'm not copying the handles
enter code here
     CAPIRequestContext(const CAPIRequestContext &other) = delete;
     CAPIRequestContext& operator=(const CAPIRequestContext& other) = delete;

private:
      HINTERNET m_hInternet;
      HINTERNET m_hConn;
      HINTERNET m_hRequest;

}

1 个答案:

答案 0 :(得分:1)

  

文档说明可以多次调用InternetOpen而不会出现任何问题。我的问题是,我应该多次在它返回的句柄上调用InternetCloseHandle吗?

是的,如InternetOpen()文档中所述:

  

在调用应用程序使用InternetOpen返回的HINTERNET句柄完成后,必须使用InternetCloseHandle函数关闭它。

  

例如,我有一个我调用CAPIRequestContext的类,它有一个由InternetOpen返回的句柄。我的每个请求都有它自己的副本。现在,我在析构函数中调用InternetCloseHandle,因此多次调用它。

如果该类的每个实例都调用InternetOpen(),或者获得唯一HINTERNET的所有权,那么这将是一个正确的实现。

HOWEVER ,请注意此类需要实现Rule of Three。基本上,如果您必须提供析构函数来释放资源,您还需要提供一个复制构造函数和复制赋值运算符。

但是,您无法在同一InternetCloseHandle()上多次致电HINTERNET,因此您无法使用同一CAPIRequestContext多个HINTERNET他们都叫InternetCloseHandle() 1 。因此,您的复制构造函数和复制赋值运算符必须:

  1. 取得正在复制的来源HINTERNET的{​​{1}}的所有权。

  2. 完全禁用,以防止将CAPIRequestContext复制到另一个。{/ p>

  3. 在你的情况下,我会选择#2。

    1 :您需要一个每实例标志,指示哪个实例可以调用它,哪些实例不能调用它。但这不是好的课堂设计。如果您需要共享CAPIRequestContext,则应该实现引用计数语义,例如由HINTERNET提供。

      

    我想知道以下是否会导致问题:线程A创建一个CAPIRequestObject,它调用InternetOpen并存储句柄。线程B执行相同操作,但在线程A退出之前超出范围,因此线程B调用其自身的CAPIRequestObject中的析构函数,该函数调用InternetOpen返回的句柄上的InternetCloseHandle。

    这是完全安全的,前提是每个std::shared_ptr都有自己的CAPIRequestObject

      

    我应该在班级的析构函数中删除对InternetCloseHandle的调用吗?

    不,如果每个类实例都包含唯一的HINTERNET

      

    我假设我应该为HttpOpenRequest返回的句柄调用InternetCloseHandle。

    是的,正如HttpOpenRequest()文档中所述:

      

    在调用应用程序使用HINTERNET返回的HINTERNET句柄完成后,必须使用InternetCloseHandle函数关闭它。

      

    我对InternetConnect返回的句柄有类似的疑问。这些句柄是否共享?

    每个HttpOpenRequest必须单独关闭。