Функция GetAddrInfoExA (ws2tcpip.h)

Функция GetAddrInfoEx предоставляет независимое от протокола разрешение имен с дополнительными параметрами, чтобы определить, какие поставщики пространств имен должны обрабатывать запрос.

Синтаксис

INT WSAAPI GetAddrInfoExA(
  [in, optional]  PCSTR                              pName,
  [in, optional]  PCSTR                              pServiceName,
  [in]            DWORD                              dwNameSpace,
  [in, optional]  LPGUID                             lpNspId,
  [in, optional]  const ADDRINFOEXA                  *hints,
  [out]           PADDRINFOEXA                       *ppResult,
  [in, optional]  timeval                            *timeout,
  [in, optional]  LPOVERLAPPED                       lpOverlapped,
  [in, optional]  LPLOOKUPSERVICE_COMPLETION_ROUTINE lpCompletionRoutine,
  [out, optional] LPHANDLE                           lpNameHandle
);

Параметры

[in, optional] pName

Указатель на строку с завершением NULL, содержащую имя узла (узла) или числовую строку адреса узла. Для интернет-протокола строка числового адреса узла представляет собой IPv4-адрес с точками или шестнадцатеричный адрес IPv6.

[in, optional] pServiceName

Указатель на необязательную строку с завершением NULL, которая содержит либо имя службы, либо номер порта, представленный в виде строки.

Имя службы — это строковый псевдоним для номера порта. Например, "http" — это псевдоним порта 80, определенный целевой группы по интернету (IETF) в качестве порта по умолчанию, используемого веб-серверами для протокола HTTP. Возможные значения параметра pServiceName , если номер порта не указан, перечислены в следующем файле:

%WINDIR%\system32\drivers\etc\services

[in] dwNameSpace

Необязательный идентификатор пространства имен, определяющий, какие поставщики пространств имен запрашиваются. Передача определенного идентификатора пространства имен приведет к тому, что будут использоваться только поставщики пространств имен, поддерживающие запросы к указанному пространству имен. При указании NS_ALL будут запрашиваться все установленные и активные поставщики пространств имен.

Параметры параметра dwNameSpace перечислены в включаемом файле Winsock2.h . В Windows Vista и более поздних версиях добавлено несколько поставщиков пространств имен. Можно установить и другие поставщики пространств имен, поэтому следующие возможные значения являются доступными только те, которые являются общедоступными. Возможны и многие другие значения.

Значение Значение
NS_ALL
0
Все установленные и активные пространства имен.
NS_DNS
12
Пространство имен системы доменных имен (DNS).
NS_NETBT
13
Пространство имен NetBIOS через TCP/IP (NETBT).
NS_WINS
14
Пространство имен службы имен Windows Internet Naming Service (NS_WINS).
NS_NLA
15
Пространство имен обнаружения сетевых расположений (NLA).

Этот идентификатор пространства имен поддерживается в Windows XP и более поздних версиях.

NS_BTH
16
Пространство имен Bluetooth.

Этот идентификатор пространства имен поддерживается в Windows Vista и более поздних версиях.

NS_NTDS
32
Пространство имен Служб каталогов Windows NT (NS_NTDS).
NS_EMAIL
37
Пространство имен электронной почты.

Этот идентификатор пространства имен поддерживается в Windows Vista и более поздних версиях.

NS_PNRPNAME
38
Одноранговое пространство имен для определенного имени однорангового узла.

Этот идентификатор пространства имен поддерживается в Windows Vista и более поздних версиях.

NS_PNRPCLOUD
39
Одноранговое пространство имен для коллекции одноранговых имен.

Этот идентификатор пространства имен поддерживается в Windows Vista и более поздних версиях.

[in, optional] lpNspId

Указатель на необязательный ИДЕНТИФИКАТОР GUID определенного поставщика пространства имен для запроса в случае, когда несколько поставщиков пространств имен зарегистрированы в одном пространстве имен, например NS_DNS. Передача GUID для конкретного поставщика пространства имен приведет к запросу только указанного поставщика пространства имен. Можно вызвать функцию WSAEnumNameSpaceProviders , чтобы получить GUID для поставщика пространства имен.

[in, optional] hints

Указатель на структуру addrinfoex , которая предоставляет указания о типе сокета, который поддерживает вызывающий объект.

Элементы ai_addrlen, ai_canonname, ai_addr и ai_next структуры addrinfoex , на которые указывает параметр pHints , должны иметь значение null или NULL. В противном случае функция GetAddrInfoEx завершится сбоем с WSANO_RECOVERY.

Дополнительные сведения см. в разделе Примечания.

[out] ppResult

Указатель на связанный список одной или нескольких структур addrinfoex , содержащих сведения об ответе на узел.

[in, optional] timeout

Необязательный параметр, указывающий время (в миллисекундах) ожидания ответа от поставщика пространства имен перед прерыванием вызова.

Этот параметр поддерживается только в том случае, если макрос ЮНИКОД или _UNICODE был определен в источниках перед вызовом функции GetAddrInfoEx . В противном случае этот параметр зарезервирован и должен иметь значение NULL , так как параметр времени ожидания не поддерживается.

[in, optional] lpOverlapped

Необязательный указатель на перекрываемую структуру, используемую для асинхронной операции.

Этот параметр поддерживается только в том случае, если макрос ЮНИКОД или _UNICODE был определен в источниках перед вызовом функции GetAddrInfoEx .

Если в Windows 8 и Windows Server 2012 не указан параметр lpCompletionRoutine , члену hEvent структуры OVERLAPPED необходимо задать событие сброса вручную для вызова после завершения асинхронного вызова. Если указана подпрограмма завершения, член hEvent должен иметь значение NULL. Если задано событие, заданное hEvent , результат операции можно получить, вызвав функцию GetAddrInfoExOverlappedResult .

В Windows 8 и Windows Server 2012 всякий раз, когда макрос ЮНИКОД или _UNICODE не определен, этот параметр зарезервирован и должен иметь значение NULL.

В Windows 7 и Windows Server 2008 R2 или более ранних версиях этот параметр зарезервирован и должен иметь значение NULL , так как асинхронные операции не поддерживаются.

[in, optional] lpCompletionRoutine

Необязательный указатель на функцию, вызываемую после успешного завершения асинхронных операций.

Этот параметр поддерживается только в том случае, если макрос ЮНИКОД или _UNICODE был определен в источниках перед вызовом функции GetAddrInfoEx .

Если указан этот параметр, он должен быть указателем на функцию со следующей сигнатурой:

typedef   
void   
(CALLBACK * LPLOOKUPSERVICE_COMPLETION_ROUTINE)(   
    __in      DWORD    dwError,   
    __in      DWORD    dwBytes,   
    __in      LPWSAOVERLAPPED lpOverlapped   
    );   

После завершения асинхронной операции будет вызвана подпрограмма завершения с параметром lpOverlapped , для которого задано значение параметра lpOverlapped, переданноев GetAddrInfoEx. Элементу Указателя структуры OVERLAPPED будет присвоено значение параметра ppResult исходного вызова. Если элемент Pointer указывает на указатель, отличный от NULL, на структуру addrinfoex , вызывающий объект отвечает за вызов FreeAddrInfoEx , чтобы освободить структуру addrinfoex . Для параметра dwError, передаваемого в подпрограмму завершения, будет задан код ошибки Winsock. Параметр dwBytes зарезервирован для использования в будущем и должен игнорироваться.

В Windows 8 и Windows Server 2012 всякий раз, когда макрос ЮНИКОД или _UNICODE не определен, этот параметр зарезервирован и должен иметь значение NULL.

В Windows 7 и Windows Server 2008 R2 или более ранних версиях этот параметр зарезервирован и должен иметь значение NULL , так как асинхронные операции не поддерживаются.

[out, optional] lpNameHandle

Необязательный указатель, используемый только для асинхронных операций.

Этот параметр поддерживается только в том случае, если макрос ЮНИКОД или _UNICODE был определен в источниках перед вызовом функции GetAddrInfoEx .

В Windows 8 и Windows Server 2012, если функция GetAddrInfoEx завершается асинхронно, указатель, возвращаемый в этом поле, может использоваться с функцией GetAddrInfoExCancel . Возвращенный дескриптор действителен, когда GetAddrInfoEx возвращается до вызова подпрограммы завершения, активации события или вызова функции GetAddrInfoExCancel с этим дескриптором.

В Windows 8 и Windows Server 2012 всякий раз, когда макрос ЮНИКОД или _UNICODE не определен, этот параметр зарезервирован и должен иметь значение NULL.

В Windows 7 и Windows Server 2008 R2 или более ранних версиях этот параметр зарезервирован и должен иметь значение NULL , так как асинхронные операции не поддерживаются.

Возвращаемое значение

При успешном выполнении GetAddrInfoEx возвращает NO_ERROR (0). Ошибка возвращает ненулевой код ошибки Сокеты Windows, как показано в коде ошибок сокетов Windows.

Большинство ненулевых кодов ошибок, возвращаемых функцией GetAddrInfoEx , сопоставляются с набором ошибок, описанным в рекомендациях Internet Engineering Task Force (IETF). В следующей таблице показаны эти коды ошибок и их эквиваленты WSA. Рекомендуется использовать коды ошибок WSA, так как они предоставляют знакомые и исчерпывающие сведения об ошибках для программистов Winsock.

Значение ошибки Эквивалент WSA Описание
EAI_AGAIN WSATRY_AGAIN Произошел временный сбой при разрешении имен.
EAI_BADFLAGS WSAEINVAL Указан недопустимый параметр. Эта ошибка возвращается, если любой из зарезервированных параметров не имеет значения NULL. Эта ошибка также возвращается, если для элемента ai_flags параметра pHints было предоставлено недопустимое значение.
EAI_FAIL WSANO_RECOVERY Произошел неустранимый сбой при разрешении имен.
EAI_FAMILY WSAEAFNOSUPPORT Элемент ai_family параметра pHints не поддерживается.
EAI_MEMORY WSA_NOT_ENOUGH_MEMORY Произошел сбой выделения памяти.
EAI_NONAME WSAHOST_NOT_FOUND Имя не разрешается для указанных параметров или параметры pName и pServiceName не указаны.
EAI_SERVICE WSATYPE_NOT_FOUND Параметр pServiceName не поддерживается для указанного элемента ai_socktype параметра pHints .
EAI_SOCKTYPE WSAESOCKTNOSUPPORT Элемент ai_socktype параметра pHints не поддерживается.
 

Используйте функцию gai_strerror для вывода сообщений об ошибках на основе кодов EAI, возвращаемых функцией GetAddrInfoEx . Функция gai_strerror предоставляется для соответствия рекомендациям IETF, но не является потокобезопасной. Поэтому рекомендуется использовать традиционные функции сокетов Windows, такие как WSAGetLastError .

Код ошибки Значение
WSA_NOT_ENOUGH_MEMORY
Недостаточно памяти для выполнения операции.
WSAEAFNOSUPPORT
Использовался адрес, несовместимый с запрошенным протоколом. Эта ошибка возвращается, если ai_family член структуры addrinfoex , на которую указывает параметр pHints , не поддерживается.
WSAEINVAL
Указан недопустимый аргумент. Эта ошибка возвращается, если для ai_flags элемента структуры addrinfoex , на которую указывает параметр pHints , было предоставлено недопустимое значение. Эта ошибка также возвращается, если параметр dwNameSpace NS_PNRPNAME или NS_PNRPCLOUD и служба одноранговых имен не работает.
WSAESOCKTNOSUPPORT
Указанный тип сокета не поддерживается в данном семействе адресов. Эта ошибка возвращается, если ai_socktype член структуры addrinfoex , на которую указывает параметр pHints , не поддерживается.
WSAHOST_NOT_FOUND
Такой узел не существует. Эта ошибка возвращается, если имя не разрешается для указанных параметров или параметры pName и pServiceName не указаны.
WSANO_DATA
Запрошенное имя является допустимым, но данные запрошенного типа не найдены.
WSANO_RECOVERY
При просмотре базы данных произошла неустранимая ошибка. Эта ошибка возвращается, если произошла неустранимая ошибка при разрешении имен.
WSANOTINITIALISED
Перед использованием этой функции должен произойти успешный вызов WSAStartup .
WSASERVICE_NOT_FOUND
Такая служба не известна. Служба не найдена в указанном пространстве имен. Эта ошибка возвращается, если параметр pName или pServiceName не найден для пространства имен, указанного в параметре dwNameSpace , или пространство имен, указанное в параметре dwNameSpace , не установлено.
WSATRY_AGAIN
Обычно это временная ошибка при разрешении имени узла, и она означает, что локальный сервер не получил ответа от заслуживающего доверия сервера. Эта ошибка возвращается при временном сбое разрешения имен.
WSATYPE_NOT_FOUND
Указанный класс не найден. Параметр pServiceName не поддерживается для указанного ai_socktype члена структуры addrinfoex , на которую указывает параметр pHints .

Комментарии

Функция GetAddrInfoEx обеспечивает не зависящий от протокола перевод с имени узла на адрес и из имени службы в номер порта. Функция GetAddrInfoEx — это расширенная версия функций getaddrinfo и GetAddrInfoW . Функция GetAddrInfoEx позволяет указать поставщика пространства имен для разрешения запроса.

Функция GetAddrInfoEx объединяет и возвращает результаты из нескольких поставщиков пространств имен, если не указан конкретный поставщик пространства имен. Для использования с протоколами IPv6 и IPv4 разрешение имен может осуществляться системой доменных имен (DNS), локальным файлом hosts , поставщиком электронной почты ( пространство имен NS_EMAIL ) или другими механизмами именования.

При определении ЮНИКОДа или _UNICODE getAddrInfoEx определяется как GetAddrInfoExW, версия Юникода этой функции. Строковые параметры определяются для типа данных PWSTR и используется структура ADDRINFOEXW . В Windows 8 и Windows Server 2012 параметры timeout, lpOverlapped, lpCompletionRoutine и lpNameHandle можно использовать для вызова функции GetAddrInfoEx , чтобы она могла выполняться асинхронно.

Если юникод или _UNICODE не определены, GetAddrInfoEx определяется как GetAddrInfoExA, версия ANSI этой функции. Строковые параметры относятся к типу данных PCSTR и используется структура ADDRINFOEXA . Параметры timeout, lpOverlapped, lpCompletionRoutine и lpNameHandle должны иметь значение NULL.

Один или оба параметра pName или pServiceName должны указывать на строку, завершаемую null. Как правило, предоставляются оба варианта.

При успешном выполнении в параметре ppResult возвращается связанный список структур addrinfoex. Список можно обработать, следуя указателю, указанному в элементе ai_next каждой возвращаемой структуры addrinfoex , пока не будет обнаружен указатель NULL . В каждой возвращаемой структуре addrinfoexчлены ai_family, ai_socktype и ai_protocol соответствуют соответствующим аргументам в вызове функции сокета или WSASocket . Кроме того, элемент ai_addr в каждой возвращаемой структуре addrinfoex указывает на заполненную структуру адресов сокета, длина которой указана в его ai_addrlen элементе.

Если параметр pName указывает на имя компьютера, возвращаются все постоянные адреса компьютера, которые можно использовать в качестве исходного адреса. В Windows Vista и более поздних версиях эти адреса будут включать все одноадресные IP-адреса, возвращаемые функциями GetUnicastIpAddressTable или GetUnicastIpAddressEntry , в которых член SkipAsSource имеет значение false в структуре MIB_UNICASTIPADDRESS_ROW .

Если параметр pName указывает на строку, равную localhost, возвращаются все адреса замыкания на себя на локальном компьютере.

Если параметр pName содержит пустую строку, возвращаются все зарегистрированные адреса на локальном компьютере.

В Windows Server 2003 и более поздних версиях, если параметр pName указывает на строку, равную ".. localmachine", возвращаются все зарегистрированные адреса на локальном компьютере.

Если параметр pName ссылается на имя виртуального сервера кластера, возвращаются только адреса виртуальных серверов. В Windows Vista и более поздних версиях эти адреса будут включать все одноадресные IP-адреса, возвращаемые функциями GetUnicastIpAddressTable или GetUnicastIpAddressEntry , в которых член SkipAsSource имеет значение true в структуре MIB_UNICASTIPADDRESS_ROW . Дополнительные сведения о кластеризации см. в разделе Кластеризация Windows .

В Windows 7 с пакетом обновления 1 (SP1) и Windows Server 2008 R2 с пакетом обновления 1 (SP1) добавлена поддержка Netsh.exe для установки атрибута SkipAsSource для IP-адреса. Это также изменяет поведение таким образом, что если член SkipAsSource в структуре MIB_UNICASTIPADDRESS_ROW имеет значение false, IP-адрес будет зарегистрирован в DNS. Если для элемента SkipAsSource задано значение true, IP-адрес не регистрируется в DNS.

Доступно исправление для Windows 7 и Windows Server 2008 R2, которое добавляет поддержку Netsh.exe для задания атрибута SkipAsSource для IP-адреса. Это исправление также изменяет поведение таким образом, что если член SkipAsSource в структуре MIB_UNICASTIPADDRESS_ROW имеет значение false, IP-адрес будет зарегистрирован в DNS. Если для элемента SkipAsSource задано значение true, IP-адрес не регистрируется в DNS. Дополнительные сведения см. в статье База знаний (KB) 2386184.

Аналогичное исправление также доступно для Windows Vista с пакетом обновления 2 (SP2) и Windows Server 2008 с пакетом обновления 2 (SP2), которое добавляет поддержку Netsh.exe для задания атрибута SkipAsSource в IP-адресе. Это исправление также изменяет поведение таким образом, что если член SkipAsSource в структуре MIB_UNICASTIPADDRESS_ROW имеет значение false, IP-адрес будет зарегистрирован в DNS. Если для элемента SkipAsSource задано значение true, IP-адрес не регистрируется в DNS.

Вызывающие функции GetAddrInfoEx могут предоставлять указания о поддерживаемом типе сокета с помощью структуры addrinfoex , на которую указывает параметр pHints . При использовании параметра pHints к связанной структуре addrinfoex применяются следующие правила:

  • Значение AF_UNSPEC для ai_family указывает, что вызывающий объект будет принимать только семейства AF_INET и AF_INET6 адресов. Обратите внимание, что AF_UNSPEC и PF_UNSPEC одинаковы.
  • Нулевое значение для ai_socktype указывает, что вызывающий объект будет принимать любой тип сокета.
  • Нулевое значение для ai_protocol указывает, что вызывающий объект будет принимать любой протокол.
  • Элемент ai_addrlen должен иметь нулевое значение.
  • Элемент ai_canonname должен иметь значение NULL.
  • Элемент ai_addr должен иметь значение NULL.
  • Элемент ai_next должен иметь значение NULL.

Другие значения в структуре addrinfoex , предоставленной в параметре pHints, указывают на конкретные требования. Например, если вызывающий объект обрабатывает только протокол IPv4 и не обрабатывает IPv6, для элемента ai_family должно быть задано значение AF_INET. Другой пример: если вызывающий объект обрабатывает только TCP и не обрабатывает UDP, для элемента ai_socktype должно быть задано значение SOCK_STREAM.

Если параметр pHints является указателем NULL , функция GetAddrInfoEx обрабатывает его так, как если бы структура addrinfoex в pHints была инициализирована с ai_family элементом, заданным AF_UNSPEC а все остальные члены — равными NULL или нулю.

При вызове GetAddrInfoEx из службы, если операция является результатом пользовательского процесса, вызывающего службу, служба должна олицетворять пользователя. Это необходимо для обеспечения правильного применения безопасности.

Функцию GetAddrInfoEx можно использовать для преобразования текстового строкового представления IP-адреса в структуру addrinfoex , содержащую структуру sockaddr для IP-адреса и других сведений. Для использования таким образом строка, на которую указывает параметр pName , должна содержать текстовое представление IP-адреса, а структура addrinfoex , на которую указывает параметр pHints , должна иметь флаг AI_NUMERICHOST, установленный в элементе ai_flags . Строка, на которую указывает параметр pName, может содержать текстовое представление адреса IPv4 или IPv6. Текстовый IP-адрес преобразуется в структуру addrinfoex , на которую указывает параметр ppResult . Возвращаемая структура addrinfoex содержит структуру sockaddr для IP-адреса, а также дополнительные сведения об IP-адресе.

На локальном компьютере для одного и того же пространства имен может быть установлено несколько поставщиков пространств имен. Например, базовое сетевое программное обеспечение Windows TCP/IP регистрируется для NS_DNS пространства имен. Microsoft Forefront Threat Management Gateway (TMG) и более старый сервер Microsoft Internet Security and Acceleration (ISA) включают клиентское программное обеспечение брандмауэра, которое также регистрируется для пространства имен NS_DNS. Если параметру dwNameSpace присвоено значение (например, NS_DNS), а параметру lpNspId присвоено значение NULL, то результаты, возвращаемые функцией GetAddrInfoEx , представляют собой объединенные результаты от всех поставщиков пространств имен, которые регистрируются для указанного пространства имен с исключенными повторяющимися результатами. Параметр lpNspId должен иметь идентификатор GUID конкретного поставщика пространства имен, если запрашивается только один поставщик пространства имен.

Если параметру pNameSpace присвоено значение NS_ALL, результаты запроса всех поставщиков пространства имен объединяются и возвращаются. В этом случае в результатах, на которые указывает параметр ppResult , могут возвращаться повторяющиеся ответы, если несколько поставщиков пространств имен возвращают одинаковые сведения.

В Windows 8 и Windows Server 2012, если функция GetAddrInfoEx завершается асинхронно, указатель, возвращаемый в параметре lpNameHandle , может использоваться с функцией GetAddrInfoExCancel . Возвращенный дескриптор действителен, когда GetAddrInfoEx возвращается до вызова подпрограммы завершения, активации события или вызова функции GetAddrInfoExCancel с этим дескриптором.

Освобождение сведений об адресе от динамического выделения

Вся информация, возвращаемая функцией GetAddrInfoEx , на которую указывает параметр ppResult , выделяется динамически, включая все структуры addrinfoex , структуры адресов сокетов и канонические строки имен узлов, на которые указывают структуры addrinfoex . Память, выделенная при успешном вызове этой функции, должна быть освобождена с последующим вызовом FreeAddrInfoEx.

Пример кода

В следующем примере показано использование функции GetAddrInfoEx .
#ifndef UNICODE
#define UNICODE
#endif

#include <winsock2.h>
#include <ws2tcpip.h>
#include <objbase.h>
#include <stdio.h>

// Need to link with Ws2_32.lib
#pragma comment(lib, "ws2_32.lib")

// Need to link with Ole32.lib to print GUID
#pragma comment(lib, "ole32.lib")

int __cdecl wmain(int argc, wchar_t ** argv)
{

    //-----------------------------------------
    // Declare and initialize variables
    WSADATA wsaData;
    int iResult;

    DWORD dwRetval;

    int i = 1;

    DWORD dwNamespace = NS_ALL;
    LPGUID lpNspid = NULL;

    ADDRINFOEX *result = NULL;
    ADDRINFOEX *ptr = NULL;
    ADDRINFOEX hints;

    // LPSOCKADDR sockaddr_ip;
    struct sockaddr_in *sockaddr_ipv4;
    struct sockaddr_in6 *sockaddr_ipv6;

    // DWORD ipbufferlength = 46;
    wchar_t ipstringbuffer[46];

    // variables needed to print namespace provider GUID
    int iRet = 0;
    WCHAR GuidString[40] = { 0 };

    // Validate the parameters
    if (argc != 4) {
        wprintf(L"usage: %ws <hostname> <servicename> <namespace>\n", argv[0]);
        wprintf(L"getaddrinfoex provides protocol-independent translation\n");
        wprintf(L"   from a host name to an IP address\n");
        wprintf(L"%ws example usage\n", argv[0]);
        wprintf(L"   %ws www.contoso.com 0 12\n", argv[0]);
        wprintf(L"   looks up the www.contoso.com in the NS_DNS namespace\n",
                argv[0]);
        return 1;
    }
    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        wprintf(L"WSAStartup failed: %d\n", iResult);
        return 1;
    }
    //--------------------------------
    // Setup the hints address info structure
    // which is passed to the getaddrinfo() function
    ZeroMemory(&hints, sizeof (hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    dwNamespace = (DWORD) _wtoi(argv[3]);

    wprintf(L"Calling GetAddrInfoEx with following parameters:\n");
    wprintf(L"\tName = %ws\n", argv[1]);
    wprintf(L"\tServiceName (or port) = %ws\n", argv[2]);
    wprintf(L"\tNamespace = %s (", argv[3]);
    switch (dwNamespace) {
    case NS_ALL:
        wprintf(L"(NS_ALL)\n");
        break;
    case NS_DNS:
        wprintf(L"(NS_DNS)\n");
        break;
    case NS_NETBT:
        wprintf(L"NS_NETBT");
        break;
    case NS_WINS:
        wprintf(L"NS_WINS");
        break;
    case NS_NLA:
        wprintf(L"NS_NLA");
        break;
    case NS_BTH:
        wprintf(L"NS_BTH");
        break;
    case NS_NTDS:
        wprintf(L"NS_NTDS");
        break;
    case NS_EMAIL:
        wprintf(L"NS_EMAIL");
        break;
    case NS_PNRPNAME:
        wprintf(L"NS_PNRPNAME");
        break;
    case NS_PNRPCLOUD:
        wprintf(L"NS_PNRPCLOUD");
        break;
    default:
        wprintf(L"Other");
        break;
    }
    wprintf(L")\n\n");

//--------------------------------
// Call getaddrinfoex(). If the call succeeds,
// the result variable will hold a linked list
// of addrinfo structures containing response
// information
    dwRetval =
        GetAddrInfoEx(argv[1], argv[2], dwNamespace, lpNspid, &hints, &result,
                      NULL, NULL, NULL, NULL);
    if (dwRetval != 0) {
        wprintf(L"GetAddrInfoEx failed with error: %d\n", dwRetval);
        WSACleanup();
        return 1;
    }

    wprintf(L"GetAddrInfoEx returned success\n");

    // Retrieve each address and print out the hex bytes
    for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {

        wprintf(L"GetAddrInfoEx response %d\n", i++);
        wprintf(L"\tFlags: 0x%x\n", ptr->ai_flags);
        wprintf(L"\tFamily: ");
        switch (ptr->ai_family) {
        case AF_UNSPEC:
            wprintf(L"Unspecified\n");
            break;
        case AF_INET:
            wprintf(L"AF_INET (IPv4)\n");
            // the InetNtop function is available on Windows Vista and later
            sockaddr_ipv4 = (struct sockaddr_in *) ptr->ai_addr;
            wprintf(L"\tIPv4 address %ws\n",
                    InetNtop(AF_INET, &sockaddr_ipv4->sin_addr, ipstringbuffer,
                             46));

            // We could also use the WSAAddressToString function
            // sockaddr_ip = (LPSOCKADDR) ptr->ai_addr;
            // The buffer length is changed by each call to WSAAddresstoString
            // So we need to set it for each iteration through the loop for safety
            // ipbufferlength = 46;
            // iRetval = WSAAddressToString(sockaddr_ip, (DWORD) ptr->ai_addrlen, NULL, 
            //    ipstringbuffer, &ipbufferlength );
            // if (iRetval)
            //    wprintf(L"WSAAddressToString failed with %u\n", WSAGetLastError() );
            // else    
            //    wprintf(L"\tIPv4 address %ws\n", ipstringbuffer);
            break;
        case AF_INET6:
            wprintf(L"AF_INET6 (IPv6)\n");
            // the InetNtop function is available on Windows Vista and later
            sockaddr_ipv6 = (struct sockaddr_in6 *) ptr->ai_addr;
            wprintf(L"\tIPv6 address %ws\n",
                    InetNtop(AF_INET6, &sockaddr_ipv6->sin6_addr,
                             ipstringbuffer, 46));

            // We could also use WSAAddressToString which also returns the scope ID
            // sockaddr_ip = (LPSOCKADDR) ptr->ai_addr;
            // The buffer length is changed by each call to WSAAddresstoString
            // So we need to set it for each iteration through the loop for safety
            // ipbufferlength = 46;
            //iRetval = WSAAddressToString(sockaddr_ip, (DWORD) ptr->ai_addrlen, NULL, 
            //    ipstringbuffer, &ipbufferlength );
            //if (iRetval)
            //    wprintf(L"WSAAddressToString failed with %u\n", WSAGetLastError() );
            //else    
            //    wprintf(L"\tIPv6 address %ws\n", ipstringbuffer);
            break;
        default:
            wprintf(L"Other %ld\n", ptr->ai_family);
            break;
        }
        wprintf(L"\tSocket type: ");
        switch (ptr->ai_socktype) {
        case 0:
            wprintf(L"Unspecified\n");
            break;
        case SOCK_STREAM:
            wprintf(L"SOCK_STREAM (stream)\n");
            break;
        case SOCK_DGRAM:
            wprintf(L"SOCK_DGRAM (datagram) \n");
            break;
        case SOCK_RAW:
            wprintf(L"SOCK_RAW (raw) \n");
            break;
        case SOCK_RDM:
            wprintf(L"SOCK_RDM (reliable message datagram)\n");
            break;
        case SOCK_SEQPACKET:
            wprintf(L"SOCK_SEQPACKET (pseudo-stream packet)\n");
            break;
        default:
            wprintf(L"Other %ld\n", ptr->ai_socktype);
            break;
        }
        wprintf(L"\tProtocol: ");
        switch (ptr->ai_protocol) {
        case 0:
            wprintf(L"Unspecified\n");
            break;
        case IPPROTO_TCP:
            wprintf(L"IPPROTO_TCP (TCP)\n");
            break;
        case IPPROTO_UDP:
            wprintf(L"IPPROTO_UDP (UDP) \n");
            break;
        default:
            wprintf(L"Other %ld\n", ptr->ai_protocol);
            break;
        }
        wprintf(L"\tLength of this sockaddr: %d\n", ptr->ai_addrlen);
        wprintf(L"\tCanonical name: %s\n", ptr->ai_canonname);

        if (ptr->ai_blob == NULL)
            wprintf(L"\tBlob: (null)\n");
        else    
            wprintf(L"\tLength of the blob: %u\n",
                    (DWORD) ptr->ai_bloblen);

        if (ptr->ai_provider == NULL)
            wprintf(L"\tNamespace provider GUID: (null)\n");
        else {
            iRet =
                StringFromGUID2(*(ptr->ai_provider), (LPOLESTR) & GuidString,
                                39);
            // For c rather than C++ source code, the above line needs to be
            // iRet = StringFromGUID2(&ptr.ai_provider, (LPOLESTR) &GuidString, 39); 
            if (iRet == 0)
                wprintf(L"StringFromGUID2 failed\n");
            else {
                wprintf(L"\tNamespace provider: %ws\n", GuidString);
            }
        }
    }

    FreeAddrInfoEx(result);
    WSACleanup();

    return 0;
}


В следующем примере показано, как использовать асинхронную функцию GetAddrInfoEx для разрешения имени в IP-адрес.

//
//    This sample demonstrates how to use asynchronous GetAddrInfoEx to
//    resolve a name to an IP address.
//
//    ResolveName <QueryName>
//

#ifndef UNICODE
#define UNICODE
#endif

#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>

// Need to link with Ws2_32.lib
#pragma comment(lib, "ws2_32.lib")

#define MAX_ADDRESS_STRING_LENGTH   64

//
//  Asynchronous query context structure.
//

typedef struct _QueryContext
{
    OVERLAPPED      QueryOverlapped;
    PADDRINFOEX     QueryResults;
    HANDLE          CompleteEvent;
}QUERY_CONTEXT, *PQUERY_CONTEXT;

VOID
WINAPI
QueryCompleteCallback(
    _In_ DWORD Error,
    _In_ DWORD Bytes,
    _In_ LPOVERLAPPED Overlapped
    );

int
__cdecl
wmain(
    _In_ int Argc, PWCHAR Argv[]
    )
{
    INT                 Error = ERROR_SUCCESS;
    WSADATA             wsaData;
    BOOL                IsWSAStartupCalled = FALSE;
    ADDRINFOEX          Hints;
    QUERY_CONTEXT       QueryContext;
    HANDLE              CancelHandle = NULL;
    DWORD               QueryTimeout = 5 * 1000; // 5 seconds

    ZeroMemory(&QueryContext, sizeof(QueryContext));

    //
    //  Validate the parameters
    //

    if (Argc != 2)
    {
        wprintf(L"Usage: ResolveName <QueryName>\n");
        goto exit;
    }

    //
    //  All Winsock functions require WSAStartup() to be called first
    //

    Error = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (Error != 0)
    {
        wprintf(L"WSAStartup failed with %d\n", Error);
        goto exit;
    }

    IsWSAStartupCalled = TRUE;

    ZeroMemory(&Hints, sizeof(Hints));
    Hints.ai_family = AF_UNSPEC;

    //
    //  Note that this is a simple sample that waits/cancels a single
    //  asynchronous query. The reader may extend this to support
    //  multiple asynchronous queries.
    //

    QueryContext.CompleteEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    if (QueryContext.CompleteEvent == NULL)
    {
        Error = GetLastError();
        wprintf(L"Failed to create completion event: Error %d\n",  Error);
        goto exit;
    }

    //
    //  Initiate asynchronous GetAddrInfoExW.
    //
    //  Note GetAddrInfoEx can also be invoked asynchronously using an event
    //  in the overlapped object (Just set hEvent in the Overlapped object
    //  and set NULL as completion callback.)
    //
    //  This sample uses the completion callback method.
    //

    Error = GetAddrInfoExW(Argv[1],
                           NULL,
                           NS_DNS,
                           NULL,
                           &Hints,
                           &QueryContext.QueryResults,
                           NULL,
                           &QueryContext.QueryOverlapped,
                           QueryCompleteCallback,
                           &CancelHandle);

    //
    //  If GetAddrInfoExW() returns  WSA_IO_PENDING, GetAddrInfoExW will invoke
    //  the completion routine. If GetAddrInfoExW returned anything else we must
    //  invoke the completion directly.
    //

    if (Error != WSA_IO_PENDING)
    {
        QueryCompleteCallback(Error, 0, &QueryContext.QueryOverlapped);
        goto exit;
    }

    //
    //  Wait for query completion for 5 seconds and cancel the query if it has
    //  not yet completed.
    //

    if (WaitForSingleObject(QueryContext.CompleteEvent,
                            QueryTimeout)  == WAIT_TIMEOUT )
    {

        //
        //  Cancel the query: Note that the GetAddrInfoExCancelcancel call does
        //  not block, so we must wait for the completion routine to be invoked.
        //  If we fail to wait, WSACleanup() could be called while an
        //  asynchronous query is still in progress, possibly causing a crash.
        //

        wprintf(L"The query took longer than %d seconds to complete; "
                L"cancelling the query...\n", QueryTimeout/1000);

        GetAddrInfoExCancel(&CancelHandle);

        WaitForSingleObject(QueryContext.CompleteEvent,
                            INFINITE);
    }

exit:

    if (IsWSAStartupCalled)
    {
        WSACleanup();
    }

    if (QueryContext.CompleteEvent)
    {
        CloseHandle(QueryContext.CompleteEvent);
    }

    return Error;
}

//
// Callback function called by Winsock as part of asynchronous query complete
//

VOID
WINAPI
QueryCompleteCallback(
    _In_ DWORD Error,
    _In_ DWORD Bytes,
    _In_ LPOVERLAPPED Overlapped
    )
{
    PQUERY_CONTEXT  QueryContext = NULL;
    PADDRINFOEX     QueryResults = NULL;
    WCHAR           AddrString[MAX_ADDRESS_STRING_LENGTH];
    DWORD           AddressStringLength;

    UNREFERENCED_PARAMETER(Bytes);

    QueryContext = CONTAINING_RECORD(Overlapped,
                                     QUERY_CONTEXT,
                                     QueryOverlapped);

    if (Error != ERROR_SUCCESS)
    {
        wprintf(L"ResolveName failed with %d\n", Error);
        goto exit;
    }

    wprintf(L"ResolveName succeeded. Query Results:\n");

    QueryResults = QueryContext->QueryResults;

    while(QueryResults)
    {
        AddressStringLength = MAX_ADDRESS_STRING_LENGTH;

        WSAAddressToString(QueryResults->ai_addr,
                           (DWORD)QueryResults->ai_addrlen,
                           NULL,
                           AddrString,
                           &AddressStringLength);

        wprintf(L"Ip Address: %s\n", AddrString);
        QueryResults = QueryResults->ai_next;
    }

exit:

    if (QueryContext->QueryResults)
    {
        FreeAddrInfoEx(QueryContext->QueryResults);
    }

    //
    //  Notify caller that the query completed
    //

    SetEvent(QueryContext->CompleteEvent);
    return;
}

Примечание Убедитесь, что среда разработки предназначена для последней версии Ws2tcpip.h , которая включает определения структуры и функций для addrinfoex и GetAddrInfoEx соответственно.
 

Международные доменные имена

Имена узлов Интернета обычно состоят из очень ограниченного набора символов:
  • буквы верхнего и нижнего регистра английского алфавита в кодировке ASCII;
  • цифры от 0 до 9;
  • Символы дефиса ASCII.

С ростом интернета возрастает потребность в определении имен узлов в Интернете для других языков, не представленных набором символов ASCII. Идентификаторы, которые упрощают эту потребность и позволяют представить символы, отличные от ASCII (Юникод), в виде специальных строк символов ASCII, называются интернационализированными доменными именами (IDN). Для стандартной обработки IDN используется механизм интернационализации доменных имен в приложениях (IDNA). Спецификации idn и IDNA задокументированы в документах RFC 3490, RTF 5890 и RFC 6365 , опубликованных Целевой службой разработки интернета (IETF).

В Windows 8 и Windows Server 2012 функция GetAddrInfoEx обеспечивает поддержку синтаксического анализа международного доменного имени (IDN), применяемого к имени, переданного в параметре pName . Winsock выполняет кодирование и преобразование punycode/IDN. Это поведение можно отключить с помощью флага AI_DISABLE_IDN_ENCODING , описанного ниже.

В Windows 7 и Windows Server 2008 R2 или более ранних версиях функция GetAddrInfoEx в настоящее время не поддерживает синтаксический анализ IDN, применяемый к имени, переданного в параметре pName . Версия расширенных символов функции GetAddrInfoEx не использует Punycode для преобразования формата punycode IDN в формате RFC 3490. Версия расширенных символов функции GetAddrInfoEx при запросе DNS кодирует имя Юникода в формате UTF-8, который используется DNS-серверами Майкрософт в корпоративной среде.

Некоторые функции в Windows Vista и более поздних версиях поддерживают преобразование меток Юникода в idN в их эквиваленты ASCII. Итоговое представление каждой метки Юникода содержит только символы ASCII и начинается с префикса xn--, если метка Юникода содержит символы, отличные от ASCII. Причина заключается в поддержке существующих DNS-серверов в Интернете, так как некоторые средства и серверы DNS поддерживают только символы ASCII (см. RFC 3490).

Функция IdnToAscii использует Punycode для преобразования IDN в представление ASCII исходной строки Юникода с помощью стандартного алгоритма, определенного в RFC 3490. Функция IdnToUnicode преобразует asCII-форму IDN в обычный синтаксис кодировки Юникод UTF-16. Дополнительные сведения и ссылки на соответствующие проекты стандартов см. в разделе Обработка международных доменных имен (IDN).

Функцию IdnToAscii можно использовать для преобразования имени IDN в форму ASCII, которую затем можно передать в параметре pName в функцию GetAddrInfoEx при использовании версии ASCII этой функции (если не определены ЮНИКОД и _UNICODE). Чтобы передать это имя IDN в функцию GetAddrInfoEx при использовании версии расширенных символов этой функции (при определении ЮНИКОДа или _UNICODE), можно использовать функцию MultiByteToWideChar для преобразования строки CHAR в строку WCHAR .

Использование ai_flags в параметре hints

Флаги в ai_flags член необязательной структуры addrinfoex , предоставленной в параметре hints, изменяют поведение функции.

Эти биты флагов определены в файле заголовка Ws2def.h в пакете средств разработки программного обеспечения Microsoft Windows (SDK) для Windows 7. Эти биты флагов определены в файле заголовка Ws2tcpip.h в windows SDK для Windows Server 2008 и Windows Vista. Эти биты флагов определены в файле заголовка Ws2tcpip.h в пакете SDK для Windows Server 2003 и Windows XP.

Биты флага могут быть комбинацией следующих:

Биты флага Описание
AI_PASSIVE Установка флага AI_PASSIVE указывает, что вызывающий объект намерен использовать возвращенную структуру адреса сокета при вызове функции bind . Если флаг AI_PASSIVE задан, а pName является указателем NULL , для части IP-адреса структуры адресов сокета устанавливается значение INADDR_ANY для IPv4-адресов и IN6ADDR_ANY_INIT для адресов IPv6.

Если флаг AI_PASSIVE не установлен, то возвращаемая структура адресов сокета готова к вызову функции connect для протокола, ориентированного на подключение, или к вызову функций connect, sendto или send для протокола без подключения. Если параметр pName в данном случае является указателем NULL , для части IP-адреса структуры адресов сокета устанавливается адрес замыкания на себя.

AI_CANONNAME Если ни AI_CANONNAME , ни AI_NUMERICHOST не используются, функция GetAddrInfoEx пытается разрешить. Если передается строка литерала , getAddrInfoEx пытается преобразовать строку, а если передается имя узла, функция GetAddrInfoEx пытается разрешить имя в адресе или нескольких адресах.

Если задан бит AI_CANONNAME , параметр pName не может иметь значение NULL. В противном случае функция GetAddrInfoEx завершится сбоем с WSANO_RECOVERY.

Если задан бит AI_CANONNAME и функция GetAddrInfoEx возвращает успешное выполнение, элемент ai_canonname в параметре ppResult указывает на строку, завершающуюся null, которая содержит каноническое имя указанного узла.

Примечание Функция GetAddrInfoEx может возвращать успешное выполнение, если установлен флаг AI_CANONNAME , но элемент ai_canonname в связанной структуре addrinfo имеет значение NULL. Поэтому рекомендуемое использование флага AI_CANONNAME включает проверку того, имеет ли член ai_canonname в связанной структуре addrinfoexзначение NULL.
 
AI_NUMERICHOST Если задан бит AI_NUMERICHOST , параметр pName должен содержать строку ненулевого числового адреса узла, в противном случае возвращается ошибка EAI_NONAME . Этот флаг предотвращает вызов службы разрешения имен.
AI_NUMERICSERV Если задан бит AI_NUMERICSERV , параметр pServiceName должен содержать числовой номер порта, отличный от NULL , в противном случае возвращается ошибка EAI_NONAME . Этот флаг предотвращает вызов службы разрешения имен.

Флаг AI_NUMERICSERV определен в Windows SDK для Windows Vista и более поздних версий. Флаг AI_NUMERICSERV не поддерживается поставщиками Майкрософт.

AI_ALL Если задан бит AI_ALL , выполняется запрос на IPv6-адреса и IPv4-адреса с AI_V4MAPPED.

Флаг AI_ALL определен в Windows SDK для Windows Vista и более поздних версий. Флаг AI_ALL поддерживается в Windows Vista и более поздних версиях.

AI_ADDRCONFIG Если задан бит AI_ADDRCONFIG , Метод GetAddrInfoEx будет разрешаться только в том случае, если настроен глобальный адрес. Если указан флаг AI_ADDRCONFIG , IPv4-адреса должны возвращаться только в том случае, если адрес IPv4 настроен в локальной системе, а адреса IPv6 должны возвращаться только в том случае, если адрес IPv6 настроен в локальной системе. Адрес замыкания на себя IPv4 или IPv6 не считается допустимым глобальным адресом.

Флаг AI_ADDRCONFIG определен в Windows SDK для Windows Vista и более поздних версий. Флаг AI_ADDRCONFIG поддерживается в Windows Vista и более поздних версиях.

AI_V4MAPPED Если задан бит AI_V4MAPPED и запрос IPv6-адресов завершается сбоем, выполняется запрос службы имен для IPv4-адресов, и эти адреса преобразуются в формат IPv4-адресов IPv6.

Флаг AI_V4MAPPED определен в Windows SDK для Windows Vista и более поздних версий. Флаг AI_V4MAPPED поддерживается в Windows Vista и более поздних версиях.

AI_NON_AUTHORITATIVE Если задан бит AI_NON_AUTHORITATIVE , поставщик пространства имен NS_EMAIL возвращает как достоверные, так и не заслуживающие доверия результаты. Если бит AI_NON_AUTHORITATIVE не задан, поставщик пространства имен NS_EMAIL возвращает только достоверные результаты.

Флаг AI_NON_AUTHORITATIVE определен в Windows SDK для Windows Vista и более поздних версий. Флаг AI_NON_AUTHORITATIVE поддерживается в Windows Vista и более поздних версиях и применяется только к пространству имен NS_EMAIL .

AI_SECURE Если задан бит AI_SECURE , поставщик пространства имен NS_EMAIL вернет результаты, полученные с повышенной безопасностью, чтобы свести к минимуму возможные спуфинговы.

Флаг AI_SECURE определен в Windows SDK для Windows Vista и более поздних версий. Флаг AI_SECURE поддерживается в Windows Vista и более поздних версиях и применяется только к пространству имен NS_EMAIL .

AI_RETURN_PREFERRED_NAMES Если задано AI_RETURN_PREFERRED_NAMES , в параметре pName не должно быть указано имя. Поставщик пространства имен NS_EMAIL вернет предпочтительные имена для публикации.

Флаг AI_RETURN_PREFERRED_NAMES определен в Windows SDK для Windows Vista и более поздних версий. Флаг AI_RETURN_PREFERRED_NAMES поддерживается в Windows Vista и более поздних версиях и применяется только к пространству имен NS_EMAIL .

AI_FQDN Если задано AI_FQDN и указано неструктурированное имя (одна метка), GetAddrInfoEx вернет полное доменное имя, в которое в конечном итоге будет разрешено имя. Полное доменное имя возвращается в элементе ai_canonname в связанной структуре addrinfoex . Это отличается от AI_CANONNAME битового флага, возвращающего каноническое имя, зарегистрированное в DNS, которое может отличаться от полного доменного имени, в которое разрешается неструктурированное имя.

Если задан бит AI_FQDN , параметр pName не может иметь значение NULL. В противном случае функция GetAddrInfoEx завершится сбоем с WSANO_RECOVERY.

В Windows 8 и Windows Server 2012 можно задать биты AI_FQDN и AI_CANONNAME . Если функция GetAddrInfoEx вызывается как с AI_FQDN , так и с AI_CANONNAME битами, параметр ppResult возвращает указатель на структуру addrinfoex2 , а не на структуру addrinfoex .

В Windows 7 и Windows Server 2008 R2 можно задать только один из AI_FQDN и AI_CANONNAME битов. Функция GetAddrInfoEx завершится ошибкой, если оба флага присутствуют с EAI_BADFLAGS.

Windows 7: Флаг AI_FQDN определен в Windows SDK для Windows 7 и более поздних версий. Флаг AI_FQDN поддерживается в Windows 7 и более поздних версиях.

AI_FILESERVER Если задано AI_FILESERVER , поставщику пространства имен будет указано, что запрашиваемое имя узла используется в сценарии общей папки. Поставщик пространства имен может игнорировать это указание.

Windows 7: Флаг AI_FILESERVER определен в Windows SDK для Windows 7 и более поздних версий. Флаг AI_FILESERVER поддерживается в Windows 7 и более поздних версиях.

AI_DISABLE_IDN_ENCODING Если задано AI_DISABLE_IDN_ENCODING , это отключает автоматическое кодирование международного доменного имени с помощью Punycode в функциях разрешения имен, вызываемых функцией GetAddrInfoEx .

Windows 8: Флаг AI_DISABLE_IDN_ENCODING определен в Windows SDK для Windows 8 и более поздних версий. Флаг AI_DISABLE_IDN_ENCODING поддерживается в Windows 8 и более поздних версиях.

 

Примечание

Заголовок ws2tcpip.h определяет GetAddrInfoEx как псевдоним, который автоматически выбирает версию ANSI или Юникод этой функции на основе определения константы препроцессора UNICODE. Сочетание использования псевдонима, не зависящий от кодировки, с кодом, не зависящим от кодировки, может привести к несоответствиям, которые приводят к ошибкам компиляции или среды выполнения. Дополнительные сведения см. в разделе Соглашения для прототипов функций.

Требования

Требование Значение
Минимальная версия клиента Windows XP [классические приложения | Приложения UWP]
Минимальная версия сервера Windows Server 2008 [классические приложения | Приложения UWP]
Целевая платформа Windows
Header ws2tcpip.h
Библиотека Ws2_32.lib
DLL Ws2_32.dll

См. также раздел

FreeAddrInfoEx

GetAddrInfoExCancel

GetAddrInfoExOverlappedResult

GetAddrInfoW

IdnToAscii

IdnToUnicode

WSAEnumNameSpaceProviders

WSAGetLastError

Коды ошибок сокетов Windows

addrinfoex

addrinfoex2

gai_strerror

getaddrinfo