Skip to content

Commit 9a4372b

Browse files
authored
Making XCurl be delay-loaded (#898)
* Making XCurl be delay-loaded * PR feedback * PR feedback
1 parent b769a36 commit 9a4372b

11 files changed

Lines changed: 361 additions & 33 deletions

File tree

Build/libHttpClient.GDK.Shared/libHttpClient.GDK.Shared.vcxitems

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
</ItemGroup>
1616
<ItemGroup>
1717
<ClCompile Include="$(MSBuildThisFileDirectory)..\..\Source\Common\Win\utils_win.cpp" />
18+
<ClCompile Include="$(MSBuildThisFileDirectory)..\..\Source\HTTP\Curl\CurlDynamicLoader.cpp" />
1819
<ClCompile Include="$(MSBuildThisFileDirectory)..\..\Source\HTTP\Curl\CurlEasyRequest.cpp" />
1920
<ClCompile Include="$(MSBuildThisFileDirectory)..\..\Source\HTTP\Curl\CurlMulti.cpp" />
2021
<ClCompile Include="$(MSBuildThisFileDirectory)..\..\Source\HTTP\Curl\CurlProvider.cpp" />
@@ -25,6 +26,7 @@
2526
</ItemGroup>
2627
<ItemGroup>
2728
<ClInclude Include="$(MSBuildThisFileDirectory)..\..\Source\Common\Win\utils_win.h" />
29+
<ClInclude Include="$(MSBuildThisFileDirectory)..\..\Source\HTTP\Curl\CurlDynamicLoader.h" />
2830
<ClInclude Include="$(MSBuildThisFileDirectory)..\..\Source\HTTP\Curl\CurlEasyRequest.h" />
2931
<ClInclude Include="$(MSBuildThisFileDirectory)..\..\Source\HTTP\Curl\CurlMulti.h" />
3032
<ClInclude Include="$(MSBuildThisFileDirectory)..\..\Source\HTTP\Curl\CurlProvider.h" />

Build/libHttpClient.GDK.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
<SupportJustMyCode>false</SupportJustMyCode>
6666
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
6767
<PreprocessorDefinitions>__WRL_NO_DEFAULT_LIB__;_LIB;$(libHttpClientDefine);%(PreprocessorDefinitions)</PreprocessorDefinitions>
68+
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(GameDKLatest)GRDK\ExtensionLibraries\Xbox.XCurl.API\Include</AdditionalIncludeDirectories>
6869
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
6970
<LanguageStandard>stdcpp17</LanguageStandard>
7071
</ClCompile>

Build/libHttpClient.GDK/libHttpClient.GDK.vcxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<PropertyGroup Label="Globals">
44
<ProjectGuid>{A5A6E02A-21BA-4D55-9FB9-7B24DEDD3743}</ProjectGuid>
55
<ConfigurationType>DynamicLibrary</ConfigurationType>
6-
<GDKExtLibNames>Xbox.XCurl.API</GDKExtLibNames>
6+
<GDKExtLibNames>;</GDKExtLibNames>
77
<PlatformToolset Condition="$(VisualStudioVersion)==14">v140</PlatformToolset>
88
<PlatformToolset Condition="$(VisualStudioVersion)==15">v141</PlatformToolset>
99
<PlatformToolset Condition="$(VisualStudioVersion)==16">v142</PlatformToolset>

Include/httpClient/pal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@ typedef struct _LIST_ENTRY {
461461
#define E_HC_NETWORK_NOT_INITIALIZED MAKE_E_HC(0x5007) // 0x89235007
462462
#define E_HC_INTERNAL_STILLINUSE MAKE_E_HC(0x5008) // 0x89235008
463463
#define E_HC_COMPRESSION_ENABLED MAKE_E_HC(0x5009) // 0x89235009
464+
#define E_HC_XCURL_REQUIRED MAKE_E_HC(0x500A) // 0x8923500A
464465

465466
typedef uint32_t HCMemoryType;
466467
typedef struct HC_WEBSOCKET_OBSERVER* HCWebsocketHandle;
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
#include "pch.h"
2+
#include "CurlDynamicLoader.h"
3+
4+
#if HC_PLATFORM == HC_PLATFORM_GDK
5+
6+
#include <memory>
7+
#include <mutex>
8+
9+
namespace xbox
10+
{
11+
namespace httpclient
12+
{
13+
14+
std::mutex CurlDynamicLoader::s_initMutex;
15+
HC_UNIQUE_PTR<CurlDynamicLoader> CurlDynamicLoader::s_instance;
16+
17+
CurlDynamicLoader& CurlDynamicLoader::GetInstance()
18+
{
19+
std::lock_guard<std::mutex> lock(s_initMutex);
20+
if (!s_instance)
21+
{
22+
HC_TRACE_VERBOSE(HTTPCLIENT, "Creating CurlDynamicLoader instance");
23+
24+
// Use libHttpClient custom allocator hooks while staying within class access to private ctor
25+
http_stl_allocator<CurlDynamicLoader> a{};
26+
s_instance = HC_UNIQUE_PTR<CurlDynamicLoader>{ new (a.allocate(1)) CurlDynamicLoader };
27+
}
28+
return *s_instance;
29+
}
30+
31+
void CurlDynamicLoader::DestroyInstance()
32+
{
33+
std::lock_guard<std::mutex> lock(s_initMutex);
34+
if (s_instance)
35+
{
36+
// Unique ptr with http_alloc_deleter ensures custom free hooks are used
37+
s_instance.reset();
38+
}
39+
}
40+
41+
CurlDynamicLoader::~CurlDynamicLoader()
42+
{
43+
Cleanup();
44+
}
45+
46+
bool CurlDynamicLoader::Initialize()
47+
{
48+
if (m_curlLibrary != nullptr)
49+
{
50+
HC_TRACE_VERBOSE(HTTPCLIENT, "XCurl.dll already loaded");
51+
return true; // Already loaded
52+
}
53+
54+
HC_TRACE_INFORMATION(HTTPCLIENT, "Attempting to load XCurl.dll");
55+
56+
// Try to load XCurl.dll
57+
m_curlLibrary = LoadLibraryA("XCurl.dll");
58+
if (m_curlLibrary == nullptr)
59+
{
60+
DWORD error = GetLastError();
61+
HC_TRACE_ERROR(HTTPCLIENT, "Failed to load XCurl.dll. Error code: %lu", error);
62+
return false;
63+
}
64+
65+
// Load all required functions
66+
bool success = true;
67+
68+
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_global_init_fn), "curl_global_init");
69+
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_global_cleanup_fn), "curl_global_cleanup");
70+
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_easy_init_fn), "curl_easy_init");
71+
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_easy_cleanup_fn), "curl_easy_cleanup");
72+
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_easy_setopt_fn), "curl_easy_setopt");
73+
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_easy_getinfo_fn), "curl_easy_getinfo");
74+
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_easy_strerror_fn), "curl_easy_strerror");
75+
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_slist_append_fn), "curl_slist_append");
76+
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_slist_free_all_fn), "curl_slist_free_all");
77+
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_multi_init_fn), "curl_multi_init");
78+
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_multi_cleanup_fn), "curl_multi_cleanup");
79+
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_multi_add_handle_fn), "curl_multi_add_handle");
80+
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_multi_remove_handle_fn), "curl_multi_remove_handle");
81+
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_multi_perform_fn), "curl_multi_perform");
82+
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_multi_info_read_fn), "curl_multi_info_read");
83+
84+
// Note: curl_multi_poll might not be available in older versions, so we make it optional
85+
LoadFunction(reinterpret_cast<FARPROC&>(curl_multi_poll_fn), "curl_multi_poll");
86+
success &= LoadFunction(reinterpret_cast<FARPROC&>(curl_multi_wait_fn), "curl_multi_wait");
87+
88+
if (!success)
89+
{
90+
Cleanup();
91+
return false;
92+
}
93+
94+
HC_TRACE_INFORMATION(HTTPCLIENT, "XCurl.dll loaded successfully");
95+
return true;
96+
}
97+
98+
void CurlDynamicLoader::Cleanup()
99+
{
100+
if (m_curlLibrary != nullptr)
101+
{
102+
HC_TRACE_INFORMATION(HTTPCLIENT, "Unloading XCurl.dll");
103+
FreeLibrary(m_curlLibrary);
104+
m_curlLibrary = nullptr;
105+
}
106+
107+
// Reset all function pointers
108+
curl_global_init_fn = nullptr;
109+
curl_global_cleanup_fn = nullptr;
110+
curl_easy_init_fn = nullptr;
111+
curl_easy_cleanup_fn = nullptr;
112+
curl_easy_setopt_fn = nullptr;
113+
curl_easy_getinfo_fn = nullptr;
114+
curl_easy_strerror_fn = nullptr;
115+
curl_slist_append_fn = nullptr;
116+
curl_slist_free_all_fn = nullptr;
117+
curl_multi_init_fn = nullptr;
118+
curl_multi_cleanup_fn = nullptr;
119+
curl_multi_add_handle_fn = nullptr;
120+
curl_multi_remove_handle_fn = nullptr;
121+
curl_multi_perform_fn = nullptr;
122+
curl_multi_info_read_fn = nullptr;
123+
curl_multi_poll_fn = nullptr;
124+
curl_multi_wait_fn = nullptr;
125+
}
126+
127+
bool CurlDynamicLoader::LoadFunction(FARPROC& funcPtr, const char* functionName)
128+
{
129+
funcPtr = GetProcAddress(m_curlLibrary, functionName);
130+
if (funcPtr == nullptr)
131+
{
132+
DWORD error = GetLastError();
133+
HC_TRACE_ERROR(HTTPCLIENT, "Failed to load function: %s. Error code: %lu", functionName, error);
134+
return false;
135+
}
136+
return true;
137+
}
138+
139+
} // httpclient
140+
} // xbox
141+
142+
#endif // HC_PLATFORM == HC_PLATFORM_GDK
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#pragma once
2+
3+
//
4+
// This header is always includable across platforms. On non-GDK platforms,
5+
// the macros are defined as direct calls and the dynamic loader class is absent.
6+
// On GDK, the dynamic loader class is available and macros route through it.
7+
//
8+
9+
#if HC_PLATFORM == HC_PLATFORM_GDK
10+
11+
#include <windows.h>
12+
#include <memory>
13+
#include <mutex>
14+
#include <XCurl.h>
15+
16+
namespace xbox
17+
{
18+
namespace httpclient
19+
{
20+
21+
// Dynamic curl function pointers
22+
class CurlDynamicLoader
23+
{
24+
public:
25+
// Initialization/Cleanup functions
26+
using curl_global_init_ptr = CURLcode(*)(long flags);
27+
using curl_global_cleanup_ptr = void(*)();
28+
29+
// Easy interface functions
30+
using curl_easy_init_ptr = CURL*(*)();
31+
using curl_easy_cleanup_ptr = void(*)(CURL* curl);
32+
using curl_easy_setopt_ptr = CURLcode(*)(CURL* curl, CURLoption option, ...);
33+
using curl_easy_getinfo_ptr = CURLcode(*)(CURL* curl, CURLINFO info, ...);
34+
using curl_easy_strerror_ptr = const char*(*)(CURLcode code);
35+
36+
// String list functions
37+
using curl_slist_append_ptr = struct curl_slist*(*)(struct curl_slist* list, const char* string);
38+
using curl_slist_free_all_ptr = void(*)(struct curl_slist* list);
39+
40+
// Multi interface functions
41+
using curl_multi_init_ptr = CURLM*(*)();
42+
using curl_multi_cleanup_ptr = CURLMcode(*)(CURLM* multi_handle);
43+
using curl_multi_add_handle_ptr = CURLMcode(*)(CURLM* multi_handle, CURL* curl_handle);
44+
using curl_multi_remove_handle_ptr = CURLMcode(*)(CURLM* multi_handle, CURL* curl_handle);
45+
using curl_multi_perform_ptr = CURLMcode(*)(CURLM* multi_handle, int* running_handles);
46+
using curl_multi_info_read_ptr = CURLMsg*(*)(CURLM* multi_handle, int* msgs_in_queue);
47+
using curl_multi_poll_ptr = CURLMcode(*)(CURLM* multi_handle, struct curl_waitfd extra_fds[], unsigned int extra_nfds, int timeout_ms, int* ret);
48+
using curl_multi_wait_ptr = CURLMcode(*)(CURLM* multi_handle, struct curl_waitfd extra_fds[], unsigned int extra_nfds, int timeout_ms, int* numfds);
49+
50+
// Function pointers
51+
curl_global_init_ptr curl_global_init_fn = nullptr;
52+
curl_global_cleanup_ptr curl_global_cleanup_fn = nullptr;
53+
curl_easy_init_ptr curl_easy_init_fn = nullptr;
54+
curl_easy_cleanup_ptr curl_easy_cleanup_fn = nullptr;
55+
curl_easy_setopt_ptr curl_easy_setopt_fn = nullptr;
56+
curl_easy_getinfo_ptr curl_easy_getinfo_fn = nullptr;
57+
curl_easy_strerror_ptr curl_easy_strerror_fn = nullptr;
58+
curl_slist_append_ptr curl_slist_append_fn = nullptr;
59+
curl_slist_free_all_ptr curl_slist_free_all_fn = nullptr;
60+
curl_multi_init_ptr curl_multi_init_fn = nullptr;
61+
curl_multi_cleanup_ptr curl_multi_cleanup_fn = nullptr;
62+
curl_multi_add_handle_ptr curl_multi_add_handle_fn = nullptr;
63+
curl_multi_remove_handle_ptr curl_multi_remove_handle_fn = nullptr;
64+
curl_multi_perform_ptr curl_multi_perform_fn = nullptr;
65+
curl_multi_info_read_ptr curl_multi_info_read_fn = nullptr;
66+
curl_multi_poll_ptr curl_multi_poll_fn = nullptr;
67+
curl_multi_wait_ptr curl_multi_wait_fn = nullptr;
68+
69+
static CurlDynamicLoader& GetInstance();
70+
// Frees the singleton instance and unloads XCurl.dll (via destructor -> Cleanup)
71+
static void DestroyInstance();
72+
~CurlDynamicLoader();
73+
74+
bool Initialize();
75+
void Cleanup();
76+
bool IsLoaded() const { return m_curlLibrary != nullptr; }
77+
78+
private:
79+
CurlDynamicLoader() = default;
80+
81+
bool LoadFunction(FARPROC& funcPtr, const char* functionName);
82+
83+
HMODULE m_curlLibrary = nullptr;
84+
85+
// Thread safety
86+
static std::mutex s_initMutex;
87+
static HC_UNIQUE_PTR<CurlDynamicLoader> s_instance;
88+
};
89+
90+
} // httpclient
91+
} // xbox
92+
93+
// GDK macro variants: route through dynamic loader and provide default returns when not loaded
94+
#define CURL_CALL(func_name) ::xbox::httpclient::CurlDynamicLoader::GetInstance().func_name##_fn
95+
#define CURL_INVOKE_OR(defaultRet, func, ...) \
96+
((::xbox::httpclient::CurlDynamicLoader::GetInstance().IsLoaded()) ? \
97+
(::xbox::httpclient::CurlDynamicLoader::GetInstance().func##_fn(__VA_ARGS__)) : \
98+
(defaultRet))
99+
// Convenience when defaultRet == 0 (common for void-calls or zero-initialized return types)
100+
#define CURL_INVOKE(func, ...) CURL_INVOKE_OR(0, func, __VA_ARGS__)
101+
102+
#else // non-GDK
103+
104+
// Non-GDK macro variants: call directly
105+
#define CURL_CALL(func_name) func_name
106+
#define CURL_INVOKE_OR(defaultRet, func, ...) func(__VA_ARGS__)
107+
// Convenience when defaultRet == 0
108+
#define CURL_INVOKE(func, ...) func(__VA_ARGS__)
109+
110+
#endif // HC_PLATFORM == HC_PLATFORM_GDK

Source/HTTP/Curl/CurlEasyRequest.cpp

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,21 @@ CurlEasyRequest::CurlEasyRequest(CURL* curlEasyHandle, HCCallHandle hcCall, XAsy
1818

1919
CurlEasyRequest::~CurlEasyRequest()
2020
{
21-
curl_easy_cleanup(m_curlEasyHandle);
22-
curl_slist_free_all(m_headers);
21+
(void)CURL_INVOKE(curl_easy_cleanup, m_curlEasyHandle);
22+
(void)CURL_INVOKE(curl_slist_free_all, m_headers);
2323
}
2424

2525
Result<HC_UNIQUE_PTR<CurlEasyRequest>> CurlEasyRequest::Initialize(HCCallHandle hcCall, XAsyncBlock* async)
2626
{
27-
CURL* curlEasyHandle{ curl_easy_init() };
27+
#if HC_PLATFORM == HC_PLATFORM_GDK
28+
// Ensure curl is loaded
29+
if (!CurlDynamicLoader::GetInstance().IsLoaded())
30+
{
31+
HC_TRACE_ERROR(HTTPCLIENT, "CurlEasyRequest::Initialize: XCurl.dll not available");
32+
return E_HC_XCURL_REQUIRED;
33+
}
34+
#endif
35+
CURL* curlEasyHandle{ CURL_CALL(curl_easy_init)() };
2836
if (!curlEasyHandle)
2937
{
3038
HC_TRACE_ERROR(HTTPCLIENT, "CurlEasyRequest::Initialize:: curl_easy_init failed");
@@ -175,7 +183,7 @@ void CurlEasyRequest::Complete(CURLcode result)
175183
HC_TRACE_INFORMATION(HTTPCLIENT, "CurlEasyRequest::m_errorBuffer='%s'", m_errorBuffer);
176184

177185
long platformError = 0;
178-
auto curle = curl_easy_getinfo(m_curlEasyHandle, CURLINFO_OS_ERRNO, &platformError);
186+
auto curle = CURL_CALL(curl_easy_getinfo)(m_curlEasyHandle, CURLINFO_OS_ERRNO, &platformError);
179187
if (curle != CURLE_OK)
180188
{
181189
return Fail(HrFromCurle(curle));
@@ -184,13 +192,13 @@ void CurlEasyRequest::Complete(CURLcode result)
184192
HRESULT hr = HCHttpCallResponseSetNetworkErrorCode(m_hcCallHandle, E_FAIL, static_cast<uint32_t>(platformError));
185193
assert(SUCCEEDED(hr));
186194

187-
hr = HCHttpCallResponseSetPlatformNetworkErrorMessage(m_hcCallHandle, curl_easy_strerror(result));
195+
hr = HCHttpCallResponseSetPlatformNetworkErrorMessage(m_hcCallHandle, CURL_CALL(curl_easy_strerror)(result));
188196
assert(SUCCEEDED(hr));
189197
}
190198
else
191199
{
192200
long httpStatus = 0;
193-
auto curle = curl_easy_getinfo(m_curlEasyHandle, CURLINFO_RESPONSE_CODE, &httpStatus);
201+
auto curle = CURL_CALL(curl_easy_getinfo)(m_curlEasyHandle, CURLINFO_RESPONSE_CODE, &httpStatus);
194202
if (curle != CURLE_OK)
195203
{
196204
return Fail(HrFromCurle(curle));
@@ -224,7 +232,7 @@ HRESULT CurlEasyRequest::AddHeader(char const* name, char const* value) noexcept
224232
assert(written == required);
225233
(void)written;
226234

227-
m_headers = curl_slist_append(m_headers, header.c_str());
235+
m_headers = CURL_CALL(curl_slist_append)(m_headers, header.c_str());
228236

229237
return S_OK;
230238
}
@@ -368,7 +376,7 @@ size_t CurlEasyRequest::WriteHeaderCallback(char* buffer, size_t size, size_t ni
368376
size_t CurlEasyRequest::GetResponseContentLength(CURL* curlHandle)
369377
{
370378
curl_off_t contentLength = 0;
371-
curl_easy_getinfo(curlHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &contentLength);
379+
CURL_CALL(curl_easy_getinfo)(curlHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &contentLength);
372380
return contentLength;
373381
}
374382

Source/HTTP/Curl/CurlEasyRequest.h

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
#pragma once
2-
3-
#if HC_PLATFORM == HC_PLATFORM_GDK
4-
// When developing titles for Xbox consoles, you must use WinHTTP or xCurl.
5-
// See https://docs.microsoft.com/en-us/gaming/gdk/_content/gc/networking/overviews/web-requests/http-networking for detail
6-
#include <XCurl.h>
7-
#else
8-
// This path is untested, but this http provider should work with other curl implementations as well.
2+
// Always include CurlDynamicLoader.h for macros and (on GDK) loader type
3+
#include "CurlDynamicLoader.h"
4+
// Http provider should work with other curl implementations as well.
95
// The logic in CurlMulti::Perform is optimized for XCurl, but should work on any curl implementation.
6+
#if HC_PLATFORM != HC_PLATFORM_GDK
107
#include <curl/curl.h>
118
#endif
129
#include "Result.h"
@@ -79,7 +76,7 @@ class CurlEasyRequest
7976
template<typename T>
8077
HRESULT CurlEasyRequest::SetOpt(CURLoption option, typename OptType<T>::type v) noexcept
8178
{
82-
CURLcode result = curl_easy_setopt(m_curlEasyHandle, option, v);
79+
CURLcode result = CURL_CALL(curl_easy_setopt)(m_curlEasyHandle, option, v);
8380
if (result != CURLE_OK)
8481
{
8582
HC_TRACE_ERROR(HTTPCLIENT, "curl_easy_setopt(request, %d, value) failed with %d", option, result);

0 commit comments

Comments
 (0)