WinInet 和 InternetOpen

WinInet and InternetOpen

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

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

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

我是否应该在 class 的析构函数中删除对 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;

}

The documentation states that InternetOpen can be called multiple times without any issues. My question though is should I be calling InternetCloseHandle on handle returned by it multiple times?

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

After the calling application has finished using the HINTERNET handle returned by InternetOpen, it must be closed using the InternetCloseHandle function.

For example, I have a class I call CAPIRequestContext, which has a handle which is returned by InternetOpen. Each one of my requests has it's own copy. Right now, I call InternetCloseHandle in the destructor, so it gets called multiple times.

如果 class 的每个实例调用 InternetOpen(),或者以其他方式获得唯一 HINTERNET.

的所有权,那将是一个正确的实现

但是,请注意这样的 class 需要实现 Rule of Three。基本上,如果你必须提供一个析构函数来释放资源,你还需要提供一个复制构造函数和复制赋值运算符。

但是,你不能在同一个 HINTERNET 上多次调用 InternetCloseHandle(),所以你不能有多个 CAPIRequestContext 使用同一个 HINTERNET 和所有其中调用 InternetCloseHandle()1。因此,您的复制构造函数和复制赋值运算符必须:

  1. 从正在复制的来源 CAPIRequestContext 获取 HINTERNET 的所有权。

  2. 完全禁用以防止将一个 CAPIRequestContext 复制到另一个。

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

1:您需要一个实例标志,指示哪个实例可以调用它,哪些不能。但是这样class设计的不好。如果您需要共享一个 HINTERNET,您应该改为实现引用计数语义,例如 std::shared_ptr.

提供的

I'm wondering if the following could cause issues: Thread A creates a CAPIRequestObject which calls InternetOpen and stores the handle. Thread B does the same, but then goes out of scope before Thread A exits, so Thread B calls the destructor in it's own CAPIRequestObject, which calls InternetCloseHandle on the handle returned by InternetOpen.

这是绝对安全的,前提是每个 CAPIRequestObject 都有自己的 HINTERNET

Should I remove the call to InternetCloseHandle in the destructors of my class?

否,如果每个 class 实例都拥有唯一的 HINTERNET

I assume I should call InternetCloseHandle for the handle returned by HttpOpenRequest.

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

After the calling application has finished using the HINTERNET handle returned by HttpOpenRequest, it must be closed using the InternetCloseHandle function.

I have similar questions regarding the handle returned by InternetConnect. Are these handles shared?

每个 HINTERNET 必须单独关闭。