1. ホーム
  2. c++

[解決済み] WinInetとInternetOpen

2022-02-16 12:49:11

質問

ドキュメントには、InternetOpenは問題なく複数回呼び出すことができると記載されています。しかし、私の質問は、私はそれが返されたハンドルにInternetCloseHandleを複数回呼び出す必要がありますか?

例えば、私のクラスは CAPIRequestContext このハンドルは InternetOpen によって返されます。私のリクエストはそれぞれそれ自身のコピーを持っています。現在、デストラクタで InternetCloseHandle を呼び出しているので、複数回呼び出されています。

次のようなことが問題になるのではないかと思っています。 スレッド A が CAPIRequestObject InternetOpenを呼び出し、ハンドルを保存します。スレッド B も同じことをしますが、スレッド A が終了する前にスコープ外に出てしまうので、スレッド B は自身の CAPIRequestObject を呼び出し、その結果 InternetCloseHandle を、InternetOpen が返すハンドル上で実行します。

私のクラスのデストラクタで 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;

}

解決方法は?

<ブロッククオート

ドキュメントには、InternetOpenは問題なく複数回呼び出すことができると記載されています。しかし、私の質問は、私はそれが返されたハンドルにInternetCloseHandleを複数回呼び出す必要がありますか?

はい。 InternetOpen() のドキュメントを参照してください。

呼び出し側のアプリケーションが HINTERNET が返すハンドル InternetOpen , を使用して閉じなければなりません。 InternetCloseHandle 機能 .

例えば、私は以下のようなクラスを持っています。 CAPIRequestContext で返されるハンドルを持っています。 InternetOpen . 私のリクエストはそれぞれそれ自身のコピーを持っています。今、私は InternetCloseHandle をデストラクタで呼び出すと、複数回呼び出されることになります。

これは正しい実装で、もしクラスの各インスタンスが InternetOpen() の所有権を得るか、あるいは一意の HINTERNET .

しかし を実装する必要があることに注意してください。 ルールオブスリー . 基本的に、リソースを解放するためのデストラクタを提供しなければならない場合、同様にコピーコンストラクタとコピーアサインメント演算子を提供する必要があります。

しかし InternetCloseHandle() を複数回実行すると、同じ HINTERNET ということで、複数の CAPIRequestContext を使用して、同じ HINTERNET を呼び出し、それらすべてが InternetCloseHandle() 1 . ですから、コピーコンストラクタとコピー代入演算子はどちらかでなければなりません。

  1. の所有権を取得します。 HINTERNET ソースから CAPIRequestContext をコピーしています。

  2. コピーできないように完全に無効にする CAPIRequestContext を別のものに置き換えます。

あなたの場合、私なら2を選びますね。

1 : どのインスタンスが呼び出せて、どのインスタンスが呼び出せないかを示す、インスタンスごとのフラグが必要でしょう。しかし、これは良いクラス設計とは言えません。 もし HINTERNET が提供するような参照カウントのセマンティクスを実装する必要があります。 std::shared_ptr .

以下のようなことが問題になるのではないかと思っています。スレッドAは、CAPIRequestObjectを作成し、InternetOpenを呼び出してハンドルを保存します。スレッド B も同じことをしますが、スレッド A が終了する前にスコープ外に出るので、スレッド B は、それ自身の CAPIRequestObject でデストラクタを呼び出し、 InternetOpen が返したハンドルに対して InternetCloseHandle を呼びます。

これは完全に安全であり、各 CAPIRequestObject は独自の HINTERNET .

クラスのデストラクタでInternetCloseHandleの呼び出しを削除した方が良いですか?

いいえ、各クラスのインスタンスが一意の HINTERNET .

<ブロッククオート

HttpOpenRequestから返されたハンドルに対してInternetCloseHandleを呼び出す必要があると推測されます。

はい、に記載されているように HttpOpenRequest() のドキュメントを参照してください。

呼び出し側のアプリケーションが HINTERNET が返すハンドル HttpOpenRequest , を使用して閉じなければなりません。 InternetCloseHandle 機能 .

InternetConnectから返されるハンドルに関しても同様の質問があります。これらのハンドルは共有されているのでしょうか?

それぞれの HINTERNET は個別に閉じる必要があります。