asyn-thread: use GetAddrInfoExW on >= Windows 8
For doing async DNS resolution instead of starting a thread for each request. Fixes #12481 Closes #12482
This commit is contained in:
Родитель
a719be81e9
Коммит
a6bbc87f9e
|
@ -54,6 +54,7 @@
|
||||||
# define RESOLVER_ENOMEM ENOMEM
|
# define RESOLVER_ENOMEM ENOMEM
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "system_win32.h"
|
||||||
#include "urldata.h"
|
#include "urldata.h"
|
||||||
#include "sendf.h"
|
#include "sendf.h"
|
||||||
#include "hostip.h"
|
#include "hostip.h"
|
||||||
|
@ -144,9 +145,22 @@ static bool init_resolve_thread(struct Curl_easy *data,
|
||||||
const char *hostname, int port,
|
const char *hostname, int port,
|
||||||
const struct addrinfo *hints);
|
const struct addrinfo *hints);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
/* Thread sync data used by GetAddrInfoExW for win8+ */
|
||||||
|
struct thread_sync_data_w8
|
||||||
|
{
|
||||||
|
OVERLAPPED overlapped;
|
||||||
|
ADDRINFOEXW_ *res;
|
||||||
|
HANDLE cancel_ev;
|
||||||
|
ADDRINFOEXW_ hints;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Data for synchronization between resolver thread and its parent */
|
/* Data for synchronization between resolver thread and its parent */
|
||||||
struct thread_sync_data {
|
struct thread_sync_data {
|
||||||
|
#ifdef _WIN32
|
||||||
|
struct thread_sync_data_w8 w8;
|
||||||
|
#endif
|
||||||
curl_mutex_t *mtx;
|
curl_mutex_t *mtx;
|
||||||
int done;
|
int done;
|
||||||
int port;
|
int port;
|
||||||
|
@ -165,6 +179,9 @@ struct thread_sync_data {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct thread_data {
|
struct thread_data {
|
||||||
|
#ifdef _WIN32
|
||||||
|
HANDLE complete_ev;
|
||||||
|
#endif
|
||||||
curl_thread_t thread_hnd;
|
curl_thread_t thread_hnd;
|
||||||
unsigned int poll_interval;
|
unsigned int poll_interval;
|
||||||
timediff_t interval_end;
|
timediff_t interval_end;
|
||||||
|
@ -276,6 +293,144 @@ static CURLcode getaddrinfo_complete(struct Curl_easy *data)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
static VOID WINAPI
|
||||||
|
query_complete(DWORD err, DWORD bytes, LPWSAOVERLAPPED overlapped)
|
||||||
|
{
|
||||||
|
size_t ss_size;
|
||||||
|
const ADDRINFOEXW_ *ai;
|
||||||
|
struct Curl_addrinfo *ca;
|
||||||
|
struct Curl_addrinfo *cafirst = NULL;
|
||||||
|
struct Curl_addrinfo *calast = NULL;
|
||||||
|
struct thread_sync_data *tsd =
|
||||||
|
CONTAINING_RECORD(overlapped, struct thread_sync_data, w8.overlapped);
|
||||||
|
struct thread_data *td = tsd->td;
|
||||||
|
const ADDRINFOEXW_ *res = tsd->w8.res;
|
||||||
|
int error = (int)err;
|
||||||
|
(void)bytes;
|
||||||
|
|
||||||
|
if(error == ERROR_SUCCESS) {
|
||||||
|
/* traverse the addrinfo list */
|
||||||
|
|
||||||
|
for(ai = res; ai != NULL; ai = ai->ai_next) {
|
||||||
|
size_t namelen = ai->ai_canonname ? wcslen(ai->ai_canonname) + 1 : 0;
|
||||||
|
/* ignore elements with unsupported address family, */
|
||||||
|
/* settle family-specific sockaddr structure size. */
|
||||||
|
if(ai->ai_family == AF_INET)
|
||||||
|
ss_size = sizeof(struct sockaddr_in);
|
||||||
|
#ifdef ENABLE_IPV6
|
||||||
|
else if(ai->ai_family == AF_INET6)
|
||||||
|
ss_size = sizeof(struct sockaddr_in6);
|
||||||
|
#endif
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* ignore elements without required address info */
|
||||||
|
if(!ai->ai_addr || !(ai->ai_addrlen > 0))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* ignore elements with bogus address size */
|
||||||
|
if((size_t)ai->ai_addrlen < ss_size)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ca = malloc(sizeof(struct Curl_addrinfo) + ss_size + namelen);
|
||||||
|
if(!ca) {
|
||||||
|
error = EAI_MEMORY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* copy each structure member individually, member ordering, */
|
||||||
|
/* size, or padding might be different for each platform. */
|
||||||
|
ca->ai_flags = ai->ai_flags;
|
||||||
|
ca->ai_family = ai->ai_family;
|
||||||
|
ca->ai_socktype = ai->ai_socktype;
|
||||||
|
ca->ai_protocol = ai->ai_protocol;
|
||||||
|
ca->ai_addrlen = (curl_socklen_t)ss_size;
|
||||||
|
ca->ai_addr = NULL;
|
||||||
|
ca->ai_canonname = NULL;
|
||||||
|
ca->ai_next = NULL;
|
||||||
|
|
||||||
|
ca->ai_addr = (void *)((char *)ca + sizeof(struct Curl_addrinfo));
|
||||||
|
memcpy(ca->ai_addr, ai->ai_addr, ss_size);
|
||||||
|
|
||||||
|
if(namelen) {
|
||||||
|
size_t i;
|
||||||
|
ca->ai_canonname = (void *)((char *)ca->ai_addr + ss_size);
|
||||||
|
for(i = 0; i < namelen; ++i) /* convert wide string to ascii */
|
||||||
|
ca->ai_canonname[i] = (char)ai->ai_canonname[i];
|
||||||
|
ca->ai_canonname[namelen] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if the return list is empty, this becomes the first element */
|
||||||
|
if(!cafirst)
|
||||||
|
cafirst = ca;
|
||||||
|
|
||||||
|
/* add this element last in the return list */
|
||||||
|
if(calast)
|
||||||
|
calast->ai_next = ca;
|
||||||
|
calast = ca;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if we failed, also destroy the Curl_addrinfo list */
|
||||||
|
if(error) {
|
||||||
|
Curl_freeaddrinfo(cafirst);
|
||||||
|
cafirst = NULL;
|
||||||
|
}
|
||||||
|
else if(!cafirst) {
|
||||||
|
#ifdef EAI_NONAME
|
||||||
|
/* rfc3493 conformant */
|
||||||
|
error = EAI_NONAME;
|
||||||
|
#else
|
||||||
|
/* rfc3493 obsoleted */
|
||||||
|
error = EAI_NODATA;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_WINSOCK
|
||||||
|
SET_SOCKERRNO(error);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
tsd->res = cafirst;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tsd->w8.res) {
|
||||||
|
Curl_FreeAddrInfoExW(tsd->w8.res);
|
||||||
|
tsd->w8.res = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(error) {
|
||||||
|
tsd->sock_error = SOCKERRNO?SOCKERRNO:error;
|
||||||
|
if(tsd->sock_error == 0)
|
||||||
|
tsd->sock_error = RESOLVER_ENOMEM;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Curl_addrinfo_set_port(tsd->res, tsd->port);
|
||||||
|
}
|
||||||
|
|
||||||
|
Curl_mutex_acquire(tsd->mtx);
|
||||||
|
if(tsd->done) {
|
||||||
|
/* too late, gotta clean up the mess */
|
||||||
|
Curl_mutex_release(tsd->mtx);
|
||||||
|
destroy_thread_sync_data(tsd);
|
||||||
|
free(td);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
#ifndef CURL_DISABLE_SOCKETPAIR
|
||||||
|
char buf[1];
|
||||||
|
if(tsd->sock_pair[1] != CURL_SOCKET_BAD) {
|
||||||
|
/* DNS has been resolved, signal client task */
|
||||||
|
buf[0] = 1;
|
||||||
|
if(swrite(tsd->sock_pair[1], buf, sizeof(buf)) < 0) {
|
||||||
|
/* update sock_erro to errno */
|
||||||
|
tsd->sock_error = SOCKERRNO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
tsd->done = 1;
|
||||||
|
Curl_mutex_release(tsd->mtx);
|
||||||
|
if(td->complete_ev)
|
||||||
|
SetEvent(td->complete_ev); /* Notify caller that the query completed */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_GETADDRINFO
|
#ifdef HAVE_GETADDRINFO
|
||||||
|
|
||||||
|
@ -391,9 +546,21 @@ static void destroy_async_data(struct Curl_async *async)
|
||||||
Curl_mutex_release(td->tsd.mtx);
|
Curl_mutex_release(td->tsd.mtx);
|
||||||
|
|
||||||
if(!done) {
|
if(!done) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
if(td->complete_ev)
|
||||||
|
CloseHandle(td->complete_ev);
|
||||||
|
else
|
||||||
|
#endif
|
||||||
Curl_thread_destroy(td->thread_hnd);
|
Curl_thread_destroy(td->thread_hnd);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
#ifdef _WIN32
|
||||||
|
if(td->complete_ev) {
|
||||||
|
Curl_GetAddrInfoExCancel(&td->tsd.w8.cancel_ev);
|
||||||
|
WaitForSingleObject(td->complete_ev, INFINITE);
|
||||||
|
CloseHandle(td->complete_ev);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if(td->thread_hnd != curl_thread_t_null)
|
if(td->thread_hnd != curl_thread_t_null)
|
||||||
Curl_thread_join(&td->thread_hnd);
|
Curl_thread_join(&td->thread_hnd);
|
||||||
|
|
||||||
|
@ -439,6 +606,9 @@ static bool init_resolve_thread(struct Curl_easy *data,
|
||||||
asp->status = 0;
|
asp->status = 0;
|
||||||
asp->dns = NULL;
|
asp->dns = NULL;
|
||||||
td->thread_hnd = curl_thread_t_null;
|
td->thread_hnd = curl_thread_t_null;
|
||||||
|
#ifdef _WIN32
|
||||||
|
td->complete_ev = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
if(!init_thread_sync_data(td, hostname, port, hints)) {
|
if(!init_thread_sync_data(td, hostname, port, hints)) {
|
||||||
asp->tdata = NULL;
|
asp->tdata = NULL;
|
||||||
|
@ -454,6 +624,41 @@ static bool init_resolve_thread(struct Curl_easy *data,
|
||||||
/* The thread will set this to 1 when complete. */
|
/* The thread will set this to 1 when complete. */
|
||||||
td->tsd.done = 0;
|
td->tsd.done = 0;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
if(Curl_isWindows8OrGreater && Curl_FreeAddrInfoExW &&
|
||||||
|
Curl_GetAddrInfoExCancel && Curl_GetAddrInfoExW) {
|
||||||
|
#define MAX_NAME_LEN 256 /* max domain name is 253 chars */
|
||||||
|
#define MAX_PORT_LEN 8
|
||||||
|
WCHAR namebuf[MAX_NAME_LEN];
|
||||||
|
WCHAR portbuf[MAX_PORT_LEN];
|
||||||
|
/* calculate required length */
|
||||||
|
int w_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, hostname,
|
||||||
|
-1, NULL, 0);
|
||||||
|
if((w_len > 0) && (w_len < MAX_NAME_LEN)) {
|
||||||
|
/* do utf8 conversion */
|
||||||
|
w_len = MultiByteToWideChar(CP_UTF8, 0, hostname, -1, namebuf, w_len);
|
||||||
|
if((w_len > 0) && (w_len < MAX_NAME_LEN)) {
|
||||||
|
swprintf(portbuf, MAX_PORT_LEN, L"%d", port);
|
||||||
|
td->tsd.w8.hints.ai_family = hints->ai_family;
|
||||||
|
td->tsd.w8.hints.ai_socktype = hints->ai_socktype;
|
||||||
|
td->complete_ev = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||||
|
if(!td->complete_ev) {
|
||||||
|
/* failed to start, mark it as done here for proper cleanup. */
|
||||||
|
td->tsd.done = 1;
|
||||||
|
goto err_exit;
|
||||||
|
}
|
||||||
|
err = Curl_GetAddrInfoExW(namebuf, portbuf, NS_DNS,
|
||||||
|
NULL, &td->tsd.w8.hints, &td->tsd.w8.res,
|
||||||
|
NULL, &td->tsd.w8.overlapped,
|
||||||
|
&query_complete, &td->tsd.w8.cancel_ev);
|
||||||
|
if(err != WSA_IO_PENDING)
|
||||||
|
query_complete(err, 0, &td->tsd.w8.overlapped);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_GETADDRINFO
|
#ifdef HAVE_GETADDRINFO
|
||||||
td->thread_hnd = Curl_thread_create(getaddrinfo_thread, &td->tsd);
|
td->thread_hnd = Curl_thread_create(getaddrinfo_thread, &td->tsd);
|
||||||
#else
|
#else
|
||||||
|
@ -490,9 +695,22 @@ static CURLcode thread_wait_resolv(struct Curl_easy *data,
|
||||||
DEBUGASSERT(data);
|
DEBUGASSERT(data);
|
||||||
td = data->state.async.tdata;
|
td = data->state.async.tdata;
|
||||||
DEBUGASSERT(td);
|
DEBUGASSERT(td);
|
||||||
|
#ifdef _WIN32
|
||||||
|
DEBUGASSERT(td->complete_ev || td->thread_hnd != curl_thread_t_null);
|
||||||
|
#else
|
||||||
DEBUGASSERT(td->thread_hnd != curl_thread_t_null);
|
DEBUGASSERT(td->thread_hnd != curl_thread_t_null);
|
||||||
|
#endif
|
||||||
|
|
||||||
/* wait for the thread to resolve the name */
|
/* wait for the thread to resolve the name */
|
||||||
|
#ifdef _WIN32
|
||||||
|
if(td->complete_ev) {
|
||||||
|
WaitForSingleObject(td->complete_ev, INFINITE);
|
||||||
|
CloseHandle(td->complete_ev);
|
||||||
|
if(entry)
|
||||||
|
result = getaddrinfo_complete(data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
if(Curl_thread_join(&td->thread_hnd)) {
|
if(Curl_thread_join(&td->thread_hnd)) {
|
||||||
if(entry)
|
if(entry)
|
||||||
result = getaddrinfo_complete(data);
|
result = getaddrinfo_complete(data);
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
|
|
||||||
LARGE_INTEGER Curl_freq;
|
LARGE_INTEGER Curl_freq;
|
||||||
bool Curl_isVistaOrGreater;
|
bool Curl_isVistaOrGreater;
|
||||||
|
bool Curl_isWindows8OrGreater;
|
||||||
|
|
||||||
/* Handle of iphlpapp.dll */
|
/* Handle of iphlpapp.dll */
|
||||||
static HMODULE s_hIpHlpApiDll = NULL;
|
static HMODULE s_hIpHlpApiDll = NULL;
|
||||||
|
@ -45,9 +46,19 @@ static HMODULE s_hIpHlpApiDll = NULL;
|
||||||
/* Pointer to the if_nametoindex function */
|
/* Pointer to the if_nametoindex function */
|
||||||
IF_NAMETOINDEX_FN Curl_if_nametoindex = NULL;
|
IF_NAMETOINDEX_FN Curl_if_nametoindex = NULL;
|
||||||
|
|
||||||
|
void(WSAAPI *Curl_FreeAddrInfoExW)(ADDRINFOEXW_ *pAddrInfoEx) = NULL;
|
||||||
|
int(WSAAPI *Curl_GetAddrInfoExCancel)(LPHANDLE lpHandle) = NULL;
|
||||||
|
int(WSAAPI *Curl_GetAddrInfoExW)(PCWSTR pName, PCWSTR pServiceName,
|
||||||
|
DWORD dwNameSpace, LPGUID lpNspId, const ADDRINFOEXW_ *hints,
|
||||||
|
ADDRINFOEXW_ **ppResult, struct timeval *timeout, LPOVERLAPPED lpOverlapped,
|
||||||
|
LOOKUP_COMPLETION lpCompletionRoutine, LPHANDLE lpHandle) = NULL;
|
||||||
|
|
||||||
/* Curl_win32_init() performs win32 global initialization */
|
/* Curl_win32_init() performs win32 global initialization */
|
||||||
CURLcode Curl_win32_init(long flags)
|
CURLcode Curl_win32_init(long flags)
|
||||||
{
|
{
|
||||||
|
#ifdef USE_WINSOCK
|
||||||
|
HANDLE ws2_32Dll;
|
||||||
|
#endif
|
||||||
/* CURL_GLOBAL_WIN32 controls the *optional* part of the initialization which
|
/* CURL_GLOBAL_WIN32 controls the *optional* part of the initialization which
|
||||||
is just for Winsock at the moment. Any required win32 initialization
|
is just for Winsock at the moment. Any required win32 initialization
|
||||||
should take place after this block. */
|
should take place after this block. */
|
||||||
|
@ -104,6 +115,18 @@ CURLcode Curl_win32_init(long flags)
|
||||||
Curl_if_nametoindex = pIfNameToIndex;
|
Curl_if_nametoindex = pIfNameToIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_WINSOCK
|
||||||
|
ws2_32Dll = GetModuleHandleA("ws2_32");
|
||||||
|
if(ws2_32Dll) {
|
||||||
|
*(FARPROC*)&Curl_FreeAddrInfoExW = GetProcAddress(ws2_32Dll,
|
||||||
|
"FreeAddrInfoExW");
|
||||||
|
*(FARPROC*)&Curl_GetAddrInfoExCancel = GetProcAddress(ws2_32Dll,
|
||||||
|
"GetAddrInfoExCancel");
|
||||||
|
*(FARPROC*)&Curl_GetAddrInfoExW = GetProcAddress(ws2_32Dll,
|
||||||
|
"GetAddrInfoExW");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* curlx_verify_windows_version must be called during init at least once
|
/* curlx_verify_windows_version must be called during init at least once
|
||||||
because it has its own initialization routine. */
|
because it has its own initialization routine. */
|
||||||
if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT,
|
if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT,
|
||||||
|
@ -113,6 +136,13 @@ CURLcode Curl_win32_init(long flags)
|
||||||
else
|
else
|
||||||
Curl_isVistaOrGreater = FALSE;
|
Curl_isVistaOrGreater = FALSE;
|
||||||
|
|
||||||
|
if(curlx_verify_windows_version(6, 2, 0, PLATFORM_WINNT,
|
||||||
|
VERSION_GREATER_THAN_EQUAL)) {
|
||||||
|
Curl_isWindows8OrGreater = TRUE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Curl_isWindows8OrGreater = FALSE;
|
||||||
|
|
||||||
QueryPerformanceFrequency(&Curl_freq);
|
QueryPerformanceFrequency(&Curl_freq);
|
||||||
return CURLE_OK;
|
return CURLE_OK;
|
||||||
}
|
}
|
||||||
|
@ -120,6 +150,9 @@ CURLcode Curl_win32_init(long flags)
|
||||||
/* Curl_win32_cleanup() is the opposite of Curl_win32_init() */
|
/* Curl_win32_cleanup() is the opposite of Curl_win32_init() */
|
||||||
void Curl_win32_cleanup(long init_flags)
|
void Curl_win32_cleanup(long init_flags)
|
||||||
{
|
{
|
||||||
|
Curl_FreeAddrInfoExW = NULL;
|
||||||
|
Curl_GetAddrInfoExCancel = NULL;
|
||||||
|
Curl_GetAddrInfoExW = NULL;
|
||||||
if(s_hIpHlpApiDll) {
|
if(s_hIpHlpApiDll) {
|
||||||
FreeLibrary(s_hIpHlpApiDll);
|
FreeLibrary(s_hIpHlpApiDll);
|
||||||
s_hIpHlpApiDll = NULL;
|
s_hIpHlpApiDll = NULL;
|
||||||
|
|
|
@ -26,10 +26,11 @@
|
||||||
|
|
||||||
#include "curl_setup.h"
|
#include "curl_setup.h"
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#ifdef _WIN32
|
||||||
|
|
||||||
extern LARGE_INTEGER Curl_freq;
|
extern LARGE_INTEGER Curl_freq;
|
||||||
extern bool Curl_isVistaOrGreater;
|
extern bool Curl_isVistaOrGreater;
|
||||||
|
extern bool Curl_isWindows8OrGreater;
|
||||||
|
|
||||||
CURLcode Curl_win32_init(long flags);
|
CURLcode Curl_win32_init(long flags);
|
||||||
void Curl_win32_cleanup(long init_flags);
|
void Curl_win32_cleanup(long init_flags);
|
||||||
|
@ -40,6 +41,29 @@ typedef unsigned int(WINAPI *IF_NAMETOINDEX_FN)(const char *);
|
||||||
/* This is used instead of if_nametoindex if available on Windows */
|
/* This is used instead of if_nametoindex if available on Windows */
|
||||||
extern IF_NAMETOINDEX_FN Curl_if_nametoindex;
|
extern IF_NAMETOINDEX_FN Curl_if_nametoindex;
|
||||||
|
|
||||||
|
/* Identical copy of addrinfoexW/ADDRINFOEXW */
|
||||||
|
typedef struct addrinfoexW_
|
||||||
|
{
|
||||||
|
int ai_flags;
|
||||||
|
int ai_family;
|
||||||
|
int ai_socktype;
|
||||||
|
int ai_protocol;
|
||||||
|
size_t ai_addrlen;
|
||||||
|
PWSTR ai_canonname;
|
||||||
|
struct sockaddr *ai_addr;
|
||||||
|
void *ai_blob;
|
||||||
|
size_t ai_bloblen;
|
||||||
|
LPGUID ai_provider;
|
||||||
|
struct addrinfoexW_ *ai_next;
|
||||||
|
} ADDRINFOEXW_;
|
||||||
|
|
||||||
|
typedef void(CALLBACK *LOOKUP_COMPLETION)(DWORD, DWORD, LPWSAOVERLAPPED);
|
||||||
|
extern void(WSAAPI *Curl_FreeAddrInfoExW)(ADDRINFOEXW_*);
|
||||||
|
extern int(WSAAPI *Curl_GetAddrInfoExCancel)(LPHANDLE);
|
||||||
|
extern int(WSAAPI *Curl_GetAddrInfoExW)(PCWSTR, PCWSTR, DWORD, LPGUID,
|
||||||
|
const ADDRINFOEXW_*, ADDRINFOEXW_**, struct timeval*, LPOVERLAPPED,
|
||||||
|
LOOKUP_COMPLETION, LPHANDLE);
|
||||||
|
|
||||||
/* This is used to dynamically load DLLs */
|
/* This is used to dynamically load DLLs */
|
||||||
HMODULE Curl_load_library(LPCTSTR filename);
|
HMODULE Curl_load_library(LPCTSTR filename);
|
||||||
#else /* _WIN32 */
|
#else /* _WIN32 */
|
||||||
|
|
Загрузка…
Ссылка в новой задаче