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
{