diff --git a/Build/libHttpClient.GDK.Shared/libHttpClient.GDK.Shared.vcxitems b/Build/libHttpClient.GDK.Shared/libHttpClient.GDK.Shared.vcxitems index 9f2f9e16..a67a6db5 100644 --- a/Build/libHttpClient.GDK.Shared/libHttpClient.GDK.Shared.vcxitems +++ b/Build/libHttpClient.GDK.Shared/libHttpClient.GDK.Shared.vcxitems @@ -15,6 +15,7 @@ + @@ -25,6 +26,7 @@ + diff --git a/Build/libHttpClient.GDK.props b/Build/libHttpClient.GDK.props index 705f9f29..9dced8ab 100644 --- a/Build/libHttpClient.GDK.props +++ b/Build/libHttpClient.GDK.props @@ -65,6 +65,7 @@ false ProgramDatabase __WRL_NO_DEFAULT_LIB__;_LIB;$(libHttpClientDefine);%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories);$(GameDKLatest)GRDK\ExtensionLibraries\Xbox.XCurl.API\Include /bigobj %(AdditionalOptions) stdcpp17 diff --git a/Build/libHttpClient.GDK/libHttpClient.GDK.vcxproj b/Build/libHttpClient.GDK/libHttpClient.GDK.vcxproj index be66a8be..aa312804 100644 --- a/Build/libHttpClient.GDK/libHttpClient.GDK.vcxproj +++ b/Build/libHttpClient.GDK/libHttpClient.GDK.vcxproj @@ -3,7 +3,7 @@ {A5A6E02A-21BA-4D55-9FB9-7B24DEDD3743} DynamicLibrary - Xbox.XCurl.API + ; v140 v141 v142 diff --git a/Include/httpClient/pal.h b/Include/httpClient/pal.h index 09e4275a..824fa601 100644 --- a/Include/httpClient/pal.h +++ b/Include/httpClient/pal.h @@ -461,6 +461,7 @@ typedef struct _LIST_ENTRY { #define E_HC_NETWORK_NOT_INITIALIZED MAKE_E_HC(0x5007) // 0x89235007 #define E_HC_INTERNAL_STILLINUSE MAKE_E_HC(0x5008) // 0x89235008 #define E_HC_COMPRESSION_ENABLED MAKE_E_HC(0x5009) // 0x89235009 +#define E_HC_XCURL_REQUIRED MAKE_E_HC(0x500A) // 0x8923500A typedef uint32_t HCMemoryType; typedef struct HC_WEBSOCKET_OBSERVER* HCWebsocketHandle; diff --git a/Source/HTTP/Curl/CurlDynamicLoader.cpp b/Source/HTTP/Curl/CurlDynamicLoader.cpp new file mode 100644 index 00000000..1621e8ed --- /dev/null +++ b/Source/HTTP/Curl/CurlDynamicLoader.cpp @@ -0,0 +1,142 @@ +#include "pch.h" +#include "CurlDynamicLoader.h" + +#if HC_PLATFORM == HC_PLATFORM_GDK + +#include +#include + +namespace xbox +{ +namespace httpclient +{ + +std::mutex CurlDynamicLoader::s_initMutex; +HC_UNIQUE_PTR CurlDynamicLoader::s_instance; + +CurlDynamicLoader& CurlDynamicLoader::GetInstance() +{ + std::lock_guard lock(s_initMutex); + if (!s_instance) + { + HC_TRACE_VERBOSE(HTTPCLIENT, "Creating CurlDynamicLoader instance"); + + // Use libHttpClient custom allocator hooks while staying within class access to private ctor + http_stl_allocator a{}; + s_instance = HC_UNIQUE_PTR{ new (a.allocate(1)) CurlDynamicLoader }; + } + return *s_instance; +} + +void CurlDynamicLoader::DestroyInstance() +{ + std::lock_guard lock(s_initMutex); + if (s_instance) + { + // Unique ptr with http_alloc_deleter ensures custom free hooks are used + s_instance.reset(); + } +} + +CurlDynamicLoader::~CurlDynamicLoader() +{ + Cleanup(); +} + +bool CurlDynamicLoader::Initialize() +{ + if (m_curlLibrary != nullptr) + { + HC_TRACE_VERBOSE(HTTPCLIENT, "XCurl.dll already loaded"); + return true; // Already loaded + } + + HC_TRACE_INFORMATION(HTTPCLIENT, "Attempting to load XCurl.dll"); + + // Try to load XCurl.dll + m_curlLibrary = LoadLibraryA("XCurl.dll"); + if (m_curlLibrary == nullptr) + { + DWORD error = GetLastError(); + HC_TRACE_ERROR(HTTPCLIENT, "Failed to load XCurl.dll. Error code: %lu", error); + return false; + } + + // Load all required functions + bool success = true; + + success &= LoadFunction(reinterpret_cast(curl_global_init_fn), "curl_global_init"); + success &= LoadFunction(reinterpret_cast(curl_global_cleanup_fn), "curl_global_cleanup"); + success &= LoadFunction(reinterpret_cast(curl_easy_init_fn), "curl_easy_init"); + success &= LoadFunction(reinterpret_cast(curl_easy_cleanup_fn), "curl_easy_cleanup"); + success &= LoadFunction(reinterpret_cast(curl_easy_setopt_fn), "curl_easy_setopt"); + success &= LoadFunction(reinterpret_cast(curl_easy_getinfo_fn), "curl_easy_getinfo"); + success &= LoadFunction(reinterpret_cast(curl_easy_strerror_fn), "curl_easy_strerror"); + success &= LoadFunction(reinterpret_cast(curl_slist_append_fn), "curl_slist_append"); + success &= LoadFunction(reinterpret_cast(curl_slist_free_all_fn), "curl_slist_free_all"); + success &= LoadFunction(reinterpret_cast(curl_multi_init_fn), "curl_multi_init"); + success &= LoadFunction(reinterpret_cast(curl_multi_cleanup_fn), "curl_multi_cleanup"); + success &= LoadFunction(reinterpret_cast(curl_multi_add_handle_fn), "curl_multi_add_handle"); + success &= LoadFunction(reinterpret_cast(curl_multi_remove_handle_fn), "curl_multi_remove_handle"); + success &= LoadFunction(reinterpret_cast(curl_multi_perform_fn), "curl_multi_perform"); + success &= LoadFunction(reinterpret_cast(curl_multi_info_read_fn), "curl_multi_info_read"); + + // Note: curl_multi_poll might not be available in older versions, so we make it optional + LoadFunction(reinterpret_cast(curl_multi_poll_fn), "curl_multi_poll"); + success &= LoadFunction(reinterpret_cast(curl_multi_wait_fn), "curl_multi_wait"); + + if (!success) + { + Cleanup(); + return false; + } + + HC_TRACE_INFORMATION(HTTPCLIENT, "XCurl.dll loaded successfully"); + return true; +} + +void CurlDynamicLoader::Cleanup() +{ + if (m_curlLibrary != nullptr) + { + HC_TRACE_INFORMATION(HTTPCLIENT, "Unloading XCurl.dll"); + FreeLibrary(m_curlLibrary); + m_curlLibrary = nullptr; + } + + // Reset all function pointers + curl_global_init_fn = nullptr; + curl_global_cleanup_fn = nullptr; + curl_easy_init_fn = nullptr; + curl_easy_cleanup_fn = nullptr; + curl_easy_setopt_fn = nullptr; + curl_easy_getinfo_fn = nullptr; + curl_easy_strerror_fn = nullptr; + curl_slist_append_fn = nullptr; + curl_slist_free_all_fn = nullptr; + curl_multi_init_fn = nullptr; + curl_multi_cleanup_fn = nullptr; + curl_multi_add_handle_fn = nullptr; + curl_multi_remove_handle_fn = nullptr; + curl_multi_perform_fn = nullptr; + curl_multi_info_read_fn = nullptr; + curl_multi_poll_fn = nullptr; + curl_multi_wait_fn = nullptr; +} + +bool CurlDynamicLoader::LoadFunction(FARPROC& funcPtr, const char* functionName) +{ + funcPtr = GetProcAddress(m_curlLibrary, functionName); + if (funcPtr == nullptr) + { + DWORD error = GetLastError(); + HC_TRACE_ERROR(HTTPCLIENT, "Failed to load function: %s. Error code: %lu", functionName, error); + return false; + } + return true; +} + +} // httpclient +} // xbox + +#endif // HC_PLATFORM == HC_PLATFORM_GDK diff --git a/Source/HTTP/Curl/CurlDynamicLoader.h b/Source/HTTP/Curl/CurlDynamicLoader.h new file mode 100644 index 00000000..821cede4 --- /dev/null +++ b/Source/HTTP/Curl/CurlDynamicLoader.h @@ -0,0 +1,110 @@ +#pragma once + +// +// This header is always includable across platforms. On non-GDK platforms, +// the macros are defined as direct calls and the dynamic loader class is absent. +// On GDK, the dynamic loader class is available and macros route through it. +// + +#if HC_PLATFORM == HC_PLATFORM_GDK + +#include +#include +#include +#include + +namespace xbox +{ +namespace httpclient +{ + +// Dynamic curl function pointers +class CurlDynamicLoader +{ +public: + // Initialization/Cleanup functions + using curl_global_init_ptr = CURLcode(*)(long flags); + using curl_global_cleanup_ptr = void(*)(); + + // Easy interface functions + using curl_easy_init_ptr = CURL*(*)(); + using curl_easy_cleanup_ptr = void(*)(CURL* curl); + using curl_easy_setopt_ptr = CURLcode(*)(CURL* curl, CURLoption option, ...); + using curl_easy_getinfo_ptr = CURLcode(*)(CURL* curl, CURLINFO info, ...); + using curl_easy_strerror_ptr = const char*(*)(CURLcode code); + + // String list functions + using curl_slist_append_ptr = struct curl_slist*(*)(struct curl_slist* list, const char* string); + using curl_slist_free_all_ptr = void(*)(struct curl_slist* list); + + // Multi interface functions + using curl_multi_init_ptr = CURLM*(*)(); + using curl_multi_cleanup_ptr = CURLMcode(*)(CURLM* multi_handle); + using curl_multi_add_handle_ptr = CURLMcode(*)(CURLM* multi_handle, CURL* curl_handle); + using curl_multi_remove_handle_ptr = CURLMcode(*)(CURLM* multi_handle, CURL* curl_handle); + using curl_multi_perform_ptr = CURLMcode(*)(CURLM* multi_handle, int* running_handles); + using curl_multi_info_read_ptr = CURLMsg*(*)(CURLM* multi_handle, int* msgs_in_queue); + using curl_multi_poll_ptr = CURLMcode(*)(CURLM* multi_handle, struct curl_waitfd extra_fds[], unsigned int extra_nfds, int timeout_ms, int* ret); + using curl_multi_wait_ptr = CURLMcode(*)(CURLM* multi_handle, struct curl_waitfd extra_fds[], unsigned int extra_nfds, int timeout_ms, int* numfds); + + // Function pointers + curl_global_init_ptr curl_global_init_fn = nullptr; + curl_global_cleanup_ptr curl_global_cleanup_fn = nullptr; + curl_easy_init_ptr curl_easy_init_fn = nullptr; + curl_easy_cleanup_ptr curl_easy_cleanup_fn = nullptr; + curl_easy_setopt_ptr curl_easy_setopt_fn = nullptr; + curl_easy_getinfo_ptr curl_easy_getinfo_fn = nullptr; + curl_easy_strerror_ptr curl_easy_strerror_fn = nullptr; + curl_slist_append_ptr curl_slist_append_fn = nullptr; + curl_slist_free_all_ptr curl_slist_free_all_fn = nullptr; + curl_multi_init_ptr curl_multi_init_fn = nullptr; + curl_multi_cleanup_ptr curl_multi_cleanup_fn = nullptr; + curl_multi_add_handle_ptr curl_multi_add_handle_fn = nullptr; + curl_multi_remove_handle_ptr curl_multi_remove_handle_fn = nullptr; + curl_multi_perform_ptr curl_multi_perform_fn = nullptr; + curl_multi_info_read_ptr curl_multi_info_read_fn = nullptr; + curl_multi_poll_ptr curl_multi_poll_fn = nullptr; + curl_multi_wait_ptr curl_multi_wait_fn = nullptr; + + static CurlDynamicLoader& GetInstance(); + // Frees the singleton instance and unloads XCurl.dll (via destructor -> Cleanup) + static void DestroyInstance(); + ~CurlDynamicLoader(); + + bool Initialize(); + void Cleanup(); + bool IsLoaded() const { return m_curlLibrary != nullptr; } + +private: + CurlDynamicLoader() = default; + + bool LoadFunction(FARPROC& funcPtr, const char* functionName); + + HMODULE m_curlLibrary = nullptr; + + // Thread safety + static std::mutex s_initMutex; + static HC_UNIQUE_PTR s_instance; +}; + +} // httpclient +} // xbox + +// GDK macro variants: route through dynamic loader and provide default returns when not loaded +#define CURL_CALL(func_name) ::xbox::httpclient::CurlDynamicLoader::GetInstance().func_name##_fn +#define CURL_INVOKE_OR(defaultRet, func, ...) \ + ((::xbox::httpclient::CurlDynamicLoader::GetInstance().IsLoaded()) ? \ + (::xbox::httpclient::CurlDynamicLoader::GetInstance().func##_fn(__VA_ARGS__)) : \ + (defaultRet)) +// Convenience when defaultRet == 0 (common for void-calls or zero-initialized return types) +#define CURL_INVOKE(func, ...) CURL_INVOKE_OR(0, func, __VA_ARGS__) + +#else // non-GDK + +// Non-GDK macro variants: call directly +#define CURL_CALL(func_name) func_name +#define CURL_INVOKE_OR(defaultRet, func, ...) func(__VA_ARGS__) +// Convenience when defaultRet == 0 +#define CURL_INVOKE(func, ...) func(__VA_ARGS__) + +#endif // HC_PLATFORM == HC_PLATFORM_GDK diff --git a/Source/HTTP/Curl/CurlEasyRequest.cpp b/Source/HTTP/Curl/CurlEasyRequest.cpp index 56fa6a8d..a4a25609 100644 --- a/Source/HTTP/Curl/CurlEasyRequest.cpp +++ b/Source/HTTP/Curl/CurlEasyRequest.cpp @@ -18,13 +18,21 @@ CurlEasyRequest::CurlEasyRequest(CURL* curlEasyHandle, HCCallHandle hcCall, XAsy CurlEasyRequest::~CurlEasyRequest() { - curl_easy_cleanup(m_curlEasyHandle); - curl_slist_free_all(m_headers); + (void)CURL_INVOKE(curl_easy_cleanup, m_curlEasyHandle); + (void)CURL_INVOKE(curl_slist_free_all, m_headers); } Result> CurlEasyRequest::Initialize(HCCallHandle hcCall, XAsyncBlock* async) { - CURL* curlEasyHandle{ curl_easy_init() }; +#if HC_PLATFORM == HC_PLATFORM_GDK + // Ensure curl is loaded + if (!CurlDynamicLoader::GetInstance().IsLoaded()) + { + HC_TRACE_ERROR(HTTPCLIENT, "CurlEasyRequest::Initialize: XCurl.dll not available"); + return E_HC_XCURL_REQUIRED; + } +#endif + CURL* curlEasyHandle{ CURL_CALL(curl_easy_init)() }; if (!curlEasyHandle) { HC_TRACE_ERROR(HTTPCLIENT, "CurlEasyRequest::Initialize:: curl_easy_init failed"); @@ -175,7 +183,7 @@ void CurlEasyRequest::Complete(CURLcode result) HC_TRACE_INFORMATION(HTTPCLIENT, "CurlEasyRequest::m_errorBuffer='%s'", m_errorBuffer); long platformError = 0; - auto curle = curl_easy_getinfo(m_curlEasyHandle, CURLINFO_OS_ERRNO, &platformError); + auto curle = CURL_CALL(curl_easy_getinfo)(m_curlEasyHandle, CURLINFO_OS_ERRNO, &platformError); if (curle != CURLE_OK) { return Fail(HrFromCurle(curle)); @@ -184,13 +192,13 @@ void CurlEasyRequest::Complete(CURLcode result) HRESULT hr = HCHttpCallResponseSetNetworkErrorCode(m_hcCallHandle, E_FAIL, static_cast(platformError)); assert(SUCCEEDED(hr)); - hr = HCHttpCallResponseSetPlatformNetworkErrorMessage(m_hcCallHandle, curl_easy_strerror(result)); + hr = HCHttpCallResponseSetPlatformNetworkErrorMessage(m_hcCallHandle, CURL_CALL(curl_easy_strerror)(result)); assert(SUCCEEDED(hr)); } else { long httpStatus = 0; - auto curle = curl_easy_getinfo(m_curlEasyHandle, CURLINFO_RESPONSE_CODE, &httpStatus); + auto curle = CURL_CALL(curl_easy_getinfo)(m_curlEasyHandle, CURLINFO_RESPONSE_CODE, &httpStatus); if (curle != CURLE_OK) { return Fail(HrFromCurle(curle)); @@ -224,7 +232,7 @@ HRESULT CurlEasyRequest::AddHeader(char const* name, char const* value) noexcept assert(written == required); (void)written; - m_headers = curl_slist_append(m_headers, header.c_str()); + m_headers = CURL_CALL(curl_slist_append)(m_headers, header.c_str()); return S_OK; } @@ -368,7 +376,7 @@ size_t CurlEasyRequest::WriteHeaderCallback(char* buffer, size_t size, size_t ni size_t CurlEasyRequest::GetResponseContentLength(CURL* curlHandle) { curl_off_t contentLength = 0; - curl_easy_getinfo(curlHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &contentLength); + CURL_CALL(curl_easy_getinfo)(curlHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &contentLength); return contentLength; } diff --git a/Source/HTTP/Curl/CurlEasyRequest.h b/Source/HTTP/Curl/CurlEasyRequest.h index b5ad642a..1c1e1cb8 100644 --- a/Source/HTTP/Curl/CurlEasyRequest.h +++ b/Source/HTTP/Curl/CurlEasyRequest.h @@ -1,12 +1,9 @@ #pragma once - -#if HC_PLATFORM == HC_PLATFORM_GDK -// When developing titles for Xbox consoles, you must use WinHTTP or xCurl. -// See https://docs.microsoft.com/en-us/gaming/gdk/_content/gc/networking/overviews/web-requests/http-networking for detail -#include -#else -// This path is untested, but this http provider should work with other curl implementations as well. +// Always include CurlDynamicLoader.h for macros and (on GDK) loader type +#include "CurlDynamicLoader.h" +// Http provider should work with other curl implementations as well. // The logic in CurlMulti::Perform is optimized for XCurl, but should work on any curl implementation. +#if HC_PLATFORM != HC_PLATFORM_GDK #include #endif #include "Result.h" @@ -79,7 +76,7 @@ class CurlEasyRequest template HRESULT CurlEasyRequest::SetOpt(CURLoption option, typename OptType::type v) noexcept { - CURLcode result = curl_easy_setopt(m_curlEasyHandle, option, v); + CURLcode result = CURL_CALL(curl_easy_setopt)(m_curlEasyHandle, option, v); if (result != CURLE_OK) { HC_TRACE_ERROR(HTTPCLIENT, "curl_easy_setopt(request, %d, value) failed with %d", option, result); diff --git a/Source/HTTP/Curl/CurlMulti.cpp b/Source/HTTP/Curl/CurlMulti.cpp index 9236be72..a857a260 100644 --- a/Source/HTTP/Curl/CurlMulti.cpp +++ b/Source/HTTP/Curl/CurlMulti.cpp @@ -1,5 +1,6 @@ #include "pch.h" #include "CurlMulti.h" +#include "CurlDynamicLoader.h" #include "CurlProvider.h" namespace xbox @@ -15,10 +16,19 @@ Result> CurlMulti::Initialize(XTaskQueuePortHandle work { assert(workPort); +#if HC_PLATFORM == HC_PLATFORM_GDK + // Ensure curl is loaded + if (!CurlDynamicLoader::GetInstance().IsLoaded()) + { + HC_TRACE_ERROR(HTTPCLIENT, "CurlMulti::Initialize: XCurl.dll not available"); + return E_HC_XCURL_REQUIRED; + } +#endif + http_stl_allocator a{}; HC_UNIQUE_PTR multi{ new (a.allocate(1)) CurlMulti }; - multi->m_curlMultiHandle = curl_multi_init(); + multi->m_curlMultiHandle = CURL_CALL(curl_multi_init)(); if (!multi->m_curlMultiHandle) { HC_TRACE_ERROR(HTTPCLIENT, "CurlMulti::Initialize: curl_multi_init failed"); @@ -45,7 +55,7 @@ CurlMulti::~CurlMulti() if (m_curlMultiHandle) { - curl_multi_cleanup(m_curlMultiHandle); + (void)CURL_INVOKE(curl_multi_cleanup, m_curlMultiHandle); } } @@ -59,7 +69,7 @@ HRESULT CurlMulti::AddRequest(HC_UNIQUE_PTR easyRequest) return E_FAIL; } - auto result = curl_multi_add_handle(m_curlMultiHandle, easyRequest->Handle()); + auto result = CURL_CALL(curl_multi_add_handle)(m_curlMultiHandle, easyRequest->Handle()); if (result != CURLM_OK) { HC_TRACE_ERROR(HTTPCLIENT, "CurlMulti::AddRequest: curl_multi_add_handle failed with CURLCode=%u", result); @@ -201,7 +211,7 @@ HRESULT CurlMulti::Perform() noexcept std::unique_lock lock{ m_mutex }; int runningRequests{ 0 }; - CURLMcode result = curl_multi_perform(m_curlMultiHandle, &runningRequests); + CURLMcode result = CURL_CALL(curl_multi_perform)(m_curlMultiHandle, &runningRequests); if (result != CURLM_OK) { HC_TRACE_ERROR(HTTPCLIENT, "CurlMulti::Perform: curl_multi_perform failed with CURLCode=%u", result); @@ -211,7 +221,7 @@ HRESULT CurlMulti::Perform() noexcept int remainingMessages{ 1 }; // assume there is at least 1 message so loop is always entered while (remainingMessages) { - CURLMsg* message = curl_multi_info_read(m_curlMultiHandle, &remainingMessages); + CURLMsg* message = CURL_CALL(curl_multi_info_read)(m_curlMultiHandle, &remainingMessages); if (message) { switch (message->msg) @@ -221,7 +231,7 @@ HRESULT CurlMulti::Perform() noexcept auto requestIter = m_easyRequests.find(message->easy_handle); assert(requestIter != m_easyRequests.end()); - result = curl_multi_remove_handle(m_curlMultiHandle, message->easy_handle); + result = CURL_CALL(curl_multi_remove_handle)(m_curlMultiHandle, message->easy_handle); if (result != CURLM_OK) { HC_TRACE_ERROR(HTTPCLIENT, "CurlMulti::Perform: curl_multi_remove_handle failed with CURLCode=%u", result); @@ -247,15 +257,20 @@ HRESULT CurlMulti::Perform() noexcept { // Reschedule Perform if there are still running requests int workAvailable{ 0 }; -#if HC_PLATFORM == HC_PLATFORM_GDK || CURL_AT_LEAST_VERSION(7,66,0) - result = curl_multi_poll(m_curlMultiHandle, nullptr, 0, POLL_TIMEOUT_MS, &workAvailable); -#else - result = curl_multi_wait(m_curlMultiHandle, nullptr, 0, POLL_TIMEOUT_MS, &workAvailable); -#endif + // Try curl_multi_poll first, fall back to curl_multi_wait if not available + // For non-GDK, CURL_CALL expands directly to the symbol + if (CURL_CALL(curl_multi_poll)) + { + result = CURL_CALL(curl_multi_poll)(m_curlMultiHandle, nullptr, 0, POLL_TIMEOUT_MS, &workAvailable); + } + else + { + result = CURL_CALL(curl_multi_wait)(m_curlMultiHandle, nullptr, 0, POLL_TIMEOUT_MS, &workAvailable); + } if (result != CURLM_OK) { - HC_TRACE_ERROR(HTTPCLIENT, "CurlMulti::Perform: curl_multi_poll failed with CURLCode=%u", result); + HC_TRACE_ERROR(HTTPCLIENT, "CurlMulti::Perform: curl_multi_poll/wait failed with CURLCode=%u", result); return HrFromCurlm(result); } @@ -274,7 +289,7 @@ void CurlMulti::FailAllRequests(HRESULT hr) noexcept { for (auto& pair : m_easyRequests) { - auto result = curl_multi_remove_handle(m_curlMultiHandle, pair.first); + auto result = CURL_INVOKE_OR(CURLM_OK, curl_multi_remove_handle, m_curlMultiHandle, pair.first); if (FAILED(HrFromCurlm(result))) { HC_TRACE_ERROR(HTTPCLIENT, "CurlMulti::FailAllRequests: curl_multi_remove_handle failed with CURLCode=%u", result); diff --git a/Source/HTTP/Curl/CurlProvider.cpp b/Source/HTTP/Curl/CurlProvider.cpp index b43b4369..557a1841 100644 --- a/Source/HTTP/Curl/CurlProvider.cpp +++ b/Source/HTTP/Curl/CurlProvider.cpp @@ -1,6 +1,7 @@ #include "pch.h" #include "CurlProvider.h" #include "CurlEasyRequest.h" +#include "CurlDynamicLoader.h" namespace xbox { @@ -22,7 +23,9 @@ HRESULT HrFromCurlm(CURLMcode c) noexcept switch (c) { case CURLMcode::CURLM_OK: return S_OK; -#if HC_PLATFORM == HC_PLATFORM_GDK || CURL_AT_LEAST_VERSION(7,69,0) +#if HC_PLATFORM == HC_PLATFORM_GDK + case CURLMcode::CURLM_BAD_FUNCTION_ARGUMENT: assert(false); return E_INVALIDARG; +#elif defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7,69,0) case CURLMcode::CURLM_BAD_FUNCTION_ARGUMENT: assert(false); return E_INVALIDARG; #endif default: return E_FAIL; @@ -31,8 +34,29 @@ HRESULT HrFromCurlm(CURLMcode c) noexcept Result> CurlProvider::Initialize() { - CURLcode initRes = curl_global_init(CURL_GLOBAL_ALL); +#if HC_PLATFORM == HC_PLATFORM_GDK + // Initialize dynamic curl loader first + auto& loader = CurlDynamicLoader::GetInstance(); + if (!loader.Initialize()) + { + HC_TRACE_ERROR(HTTPCLIENT, "CurlProvider::Initialize: Failed to load XCurl.dll"); + // Ensure the loader is cleaned up if initialization fails + CurlDynamicLoader::DestroyInstance(); + return E_FAIL; + } + + CURLcode initRes = CURL_CALL(curl_global_init)(CURL_GLOBAL_ALL); + HRESULT initHr = HrFromCurle(initRes); + if (FAILED(initHr)) + { + // If curl init fails, unload XCurl and free the loader singleton + CurlDynamicLoader::DestroyInstance(); + return initHr; + } +#else + CURLcode initRes = CURL_CALL(curl_global_init)(CURL_GLOBAL_ALL); RETURN_IF_FAILED(HrFromCurle(initRes)); +#endif http_stl_allocator a{}; auto provider = HC_UNIQUE_PTR{ new (a.allocate(1)) CurlProvider }; @@ -53,11 +77,29 @@ CurlProvider::~CurlProvider() // make sure XCurlMultis are cleaned up before curl_global_cleanup m_curlMultis.clear(); - curl_global_cleanup(); +#if HC_PLATFORM == HC_PLATFORM_GDK + if (CurlDynamicLoader::GetInstance().IsLoaded()) + { + CURL_CALL(curl_global_cleanup)(); + } + // Free the dynamic loader singleton (unloads XCurl.dll via its destructor) + CurlDynamicLoader::DestroyInstance(); +#else + CURL_CALL(curl_global_cleanup)(); +#endif } HRESULT CurlProvider::PerformAsync(HCCallHandle hcCall, XAsyncBlock* async) noexcept { +#if HC_PLATFORM == HC_PLATFORM_GDK + // Check if curl is available before proceeding + if (!CurlDynamicLoader::GetInstance().IsLoaded()) + { + HC_TRACE_ERROR(HTTPCLIENT, "CurlProvider::PerformAsync: XCurl.dll not available"); + return E_HC_XCURL_REQUIRED; + } +#endif + XTaskQueuePortHandle workPort{ nullptr }; RETURN_IF_FAILED(XTaskQueueGetPort(async->queue, XTaskQueuePort::Work, &workPort)); diff --git a/Source/HTTP/Curl/CurlProvider.h b/Source/HTTP/Curl/CurlProvider.h index e4120527..438e753d 100644 --- a/Source/HTTP/Curl/CurlProvider.h +++ b/Source/HTTP/Curl/CurlProvider.h @@ -3,6 +3,16 @@ #include "Platform/IHttpProvider.h" #include "CurlMulti.h" #include "Result.h" +#if HC_PLATFORM == HC_PLATFORM_GDK +// When developing titles for Xbox consoles, you must use WinHTTP or xCurl. +// See https://docs.microsoft.com/en-us/gaming/gdk/_content/gc/networking/overviews/web-requests/http-networking for detail +#include +#include "CurlDynamicLoader.h" +#else +// This http provider should work with other curl implementations as well. +// The logic in CurlMulti::Perform is optimized for XCurl, but should work on any curl implementation. +#include +#endif namespace xbox {