CreateUnicastIpAddressEntry 函数 (netioapi.h)

CreateUnicastIpAddressEntry 函数在本地计算机上添加新的单播 IP 地址条目。

语法

IPHLPAPI_DLL_LINKAGE _NETIOAPI_SUCCESS_ NETIOAPI_API CreateUnicastIpAddressEntry(
  [in] const MIB_UNICASTIPADDRESS_ROW *Row
);

参数

[in] Row

指向单播 IP 地址条目 MIB_UNICASTIPADDRESS_ROW 结构条目的指针。

返回值

如果函数成功,则返回值NO_ERROR。

如果函数失败,则返回值为以下错误代码之一。

返回代码 说明
ERROR_ACCESS_DENIED
访问被拒绝。 此错误在以下几种情况下返回:用户在本地计算机上缺少所需的管理权限,或者应用程序没有作为内置管理员 (RunAs 管理员) 在增强的 shell 中运行。
ERROR_INVALID_PARAMETER
向该函数传递了无效参数。 如果在 Row 参数中传递 NULL 指针,Row 参数指向MIB_UNICASTIPADDRESS_ROWAddress 成员未设置为有效的单播 IPv4 或 IPv6 地址,或者未指定 Row 参数指向的MIB_UNICASTIPADDRESS_ROW的 InterfaceLuidInterfaceIndex 成员,则返回此错误。

对于为 MIB_UNICASTIPADDRESS_ROW 结构中的成员设置的值中的其他错误,也会返回此错误。 这些错误包括以下错误:如果 ValidLifetime 成员小于 PreferredLifetime 成员,如果 PrefixOrigin 成员设置为 IpPrefixOriginUnchanged,SuffixOrigin 未设置为 IpSuffixOriginUnchanged,如果 PrefixOrigin 成员未设置为 IpPrefixOriginUnchanged,并且 SuffixOrigin 设置为 IpSuffixOriginUnchanged,如果 PrefixOrigin如果 SuffixOrigin 成员未设置为 NL_SUFFIX_ORIGIN 枚举中的值,或者如果 OnLinkPrefixLength 成员设置为大于 IP 地址长度的值(以位 (32 表示单播 IPv4 地址)或 128(对于单播 IPv6 地址) ),则 member 不会设置为来自 NL_PREFIX_ORIGIN 枚举的值。

ERROR_NOT_FOUND
找不到指定的接口。 如果找不到由 Row 参数指向的MIB_UNICASTIPADDRESS_ROW的 InterfaceLuidInterfaceIndex 成员指定的网络接口,则返回此错误。
ERROR_NOT_SUPPORTED
不支持该请求。 如果本地计算机上没有 IPv4 堆栈,并且已在 Row 参数指向的MIB_UNICASTIPADDRESS_ROW的 Address 成员中指定了 IPv4 地址,则返回此错误。 如果本地计算机上没有 IPv6 堆栈,并且 地址成员中 指定了 IPv6 地址,则也会返回此错误。
ERROR_OBJECT_ALREADY_EXISTS
该对象已经存在。 如果 Row 参数指向的MIB_UNICASTIPADDRESS_ROWAddress 成员是MIB_UNICASTIPADDRESS_ROW的 InterfaceLuidInterfaceIndex 成员指定的接口上现有单播 IP 地址的副本,则返回此错误。
其他
使用 FormatMessage 获取返回错误的消息字符串。

注解

CreateUnicastIpAddressEntry 函数是在 Windows Vista 及更高版本上定义的。

CreateUnicastIpAddressEntry 函数用于在本地计算机上添加新的单播 IP 地址条目。 由 CreateUnicastIpAddressEntry 函数添加的单播 IP 地址不是永久性的。 只要适配器对象存在,IP 地址才存在。 重新启动计算机会破坏 IP 地址,手动重置网络接口卡 (NIC) 。 此外,某些 PnP 事件可能会销毁地址。

若要创建持久化 IPv4 地址,可以使用 Windows Management Instrumentation (WMI) 控件中 Win32_NetworkAdapterConfiguration 类的 EnableStatic 方法 。 netsh 命令还可用于创建永久性 IPv4 或 IPv6 地址。

有关详细信息,请参阅 Windows 套接字文档中有关 Netsh.exe 的文档。

InitializeUnicastIpAddressEntry 函数应用于使用默认值初始化MIB_UNICASTIPADDRESS_ROW结构条目的成员。 然后,应用程序可以更改要修改 的MIB_UNICASTIPADDRESS_ROW 条目中的成员,然后调用 CreateUnicastIpAddressEntry 函数。

Row 参数指向的 MIB_UNICASTIPADDRESS_ROW 结构中的 Address 成员必须初始化为有效的单播 IPv4 或 IPv6 地址。 Address 成员中SOCKADDR_INET结构的si_family成员必须初始化为 AF_INETAF_INET6,并且必须将 SOCKADDR_INET 结构的相关 Ipv4Ipv6 成员设置为有效的单播 IP 地址。 此外,必须将指向 Row 参数的 MIB_UNICASTIPADDRESS_ROW 结构中的以下成员中的至少一个初始化到接口:InterfaceLuidInterfaceIndex

字段按上面列出的顺序使用。 因此,如果指定 了 InterfaceLuid ,则此成员用于确定要添加单播 IP 地址的接口。 如果没有为 InterfaceLuid 成员设置值, (此成员的值) 设置为零,则接下来使用 InterfaceIndex 成员来确定接口。

如果 Row 参数指向的 MIB_UNICASTIPADDRESS_ROWOnLinkPrefixLength 成员设置为 255,则 CreateUnicastIpAddressEntry 将添加新的单播 IP 地址,其 OnLinkPrefixLength 成员集等于 IP 地址的长度。 因此,对于单播 IPv4 地址,对于单播 IPv6 地址, OnLinkPrefixLength 设置为 32,OnLinkPrefixLength 设置为 128。 如果这会导致 IPv4 地址的子网掩码不正确或 IPv6 地址的链接前缀不正确,则应用程序应在调用 CreateUnicastIpAddressEntry 之前将此成员设置为正确的值。

如果在未正确设置 OnLinkPrefixLength 成员的情况下创建单播 IP 地址,则可以通过调用 SetUnicastIpAddressEntry 并将 OnLinkPrefixLength 成员设置为正确值来更改 IP 地址。

调用 CreateUnicastIpAddressEntry 函数时,将忽略指向的 MIB_UNICASTIPADDRESS_ROW 结构的 DadStateScopeIdCreationTimeStamp 成员。 这些成员由网络堆栈设置。 ScopeId 成员由添加地址的接口自动确定。 从 Windows 10 开始,如果在调用 CreateUnicastIpAddressEntry 时将 dadState 设置为 MIB_UNICASTIPADDRESS_ROW 结构中的 IpDadStatePreferred,则堆栈会将地址的初始 DAD 状态设置为“首选”而不是“暂定”,并将对地址执行乐观 DAD。

如果 Row 参数指向的MIB_UNICASTIPADDRESS_ROWAddress 成员中传递的单播 IP 地址是接口上现有单播 IP 地址的副本,则 CreateUnicastIpAddressEntry 函数将失败。 请注意,环回 IP 地址只能使用 CreateUnicastIpAddressEntry 函数添加到环回接口。

Row 参数指向的 MIB_UNICASTIPADDRESS_ROW的 Address 成员中传递的单播 IP 地址不能立即使用。 重复地址检测过程成功完成后,IP 地址可用。 重复地址检测过程可能需要几秒钟才能完成,因为需要发送 IP 数据包,并且必须等待潜在的响应。 对于 IPv6,重复地址检测过程通常需要大约一秒钟的时间。 对于 IPv4,重复地址检测过程通常需要大约三秒钟。

如果应用程序在调用 CreateUnicastIpAddressEntry 函数后需要知道 IP 地址何时可用,则可以使用两种方法。 一种方法使用轮询和 GetUnicastIpAddressEntry 函数。 第二种方法调用通知函数之一 NotifyAddrChangeNotifyIpInterfaceChangeNotifyUnicastIpAddressChange ,以在地址更改时设置异步通知。

以下方法介绍如何使用 GetUnicastIpAddressEntry 和轮询。 成功返回对 CreateUnicastIpAddressEntry 函数的调用后,暂停一到三秒 (具体取决于是创建 IPv6 还是 IPv4 地址) ,以便有时间成功完成重复地址检测过程。 然后调用 GetUnicastIpAddressEntry 函数以检索更新 MIB_UNICASTIPADDRESS_ROW 结构并检查 DadState 成员的值。 如果 DadState 成员的值设置为 IpDadStatePreferred,则 IP 地址现在可用。 如果 DadState 成员的值设置为 IpDadStateTentative,则重复地址检测尚未完成。 在这种情况下,每半秒调用 一次 GetUnicastIpAddressEntry 函数,而 DadState 成员仍设置为 IpDadStateTentative。 如果 DadState 成员的值返回的某个值不是 IpDadStatePreferredIpDadStateTentative,则重复地址检测失败,IP 地址不可用。

以下方法介绍如何使用适当的通知函数。 成功返回对 CreateUnicastIpAddressEntry 函数的调用后,调用 NotifyUnicastIpAddressChange 函数以注册以收到 IPv6 或 IPv4 单播 IP 地址更改的通知,具体取决于要创建的 IP 地址的类型。 收到要创建的 IP 地址的通知时,调用 GetUnicastIpAddressEntry 函数以检索 DadState 成员。 如果 DadState 成员的值设置为 IpDadStatePreferred,则 IP 地址现在可用。 如果 DadState 成员的值设置为 IpDadStateTentative,则重复地址检测尚未完成,应用程序需要等待将来的通知。 如果 DadState 成员的值返回的某个值不是 IpDadStatePreferredIpDadStateTentative,则重复地址检测失败,IP 地址不可用。

如果在重复地址检测过程中,媒体断开连接,然后重新连接,则重复地址检测进程会重启。 因此,在完成此过程时,可能会增加超过 IPv6 的典型 1 秒值或 IPv4 的 3 秒值。

CreateUnicastIpAddressEntry 函数只能由以管理员组成员身份登录的用户调用。 如果 CreateUnicastIpAddressEntry 由非管理员组成员的用户调用,则函数调用将失败并返回ERROR_ACCESS_DENIED。 此函数也可能因为 Windows Vista 及更高版本上的用户帐户控制 (UAC) 而失败。 如果包含此函数的应用程序由以管理员组成员身份登录(而非内置管理员)的用户执行,则此调用将失败,除非应用程序已在清单文件中标记为 requestedExecutionLevel 设置为 requireAdministrator。 如果 上的应用程序缺少此清单文件,则以管理员组成员身份登录的用户(而不是内置管理员)必须在增强的 shell 中执行应用程序,因为内置管理员 (RunAs 管理员) ,此函数才能成功。

示例

以下示例演示如何使用 CreateUnicastIpAddressEntry 函数在本地计算机上添加新的单播 IP 地址条目。


#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

#include <windows.h>
#include <winsock2.h>
#include <ws2ipdef.h> 
#include <iphlpapi.h>
#include <stdio.h>
#include <stdlib.h>

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

HANDLE gCallbackComplete;
HANDLE gNotifyEvent;

void CALLBACK CallCompleted (VOID *callerContext, 
    PMIB_UNICASTIPADDRESS_ROW row, 
    MIB_NOTIFICATION_TYPE notificationType);

int main(int argc, char **argv)  {

    // Declare and initialize variables
    
    unsigned long ipAddress = INADDR_NONE;
    unsigned long ipMask = INADDR_NONE;

    DWORD dwRetVal = 0;

    DWORD dwSize = 0;
    unsigned long status = 0;

    DWORD lastError = 0;
    SOCKADDR_IN localAddress;

    NET_LUID interfaceLuid;
    PMIB_IPINTERFACE_TABLE pipTable = NULL; 
    MIB_UNICASTIPADDRESS_ROW ipRow;

    // Validate the parameters
    if (argc != 3) {
        printf("usage: %s IPv4address IPv4mask\n", argv[0]);
        exit(1);
    }

    ipAddress = inet_addr(argv[1]);
    if (ipAddress == INADDR_NONE) {
        printf("usage: %s IPv4address IPv4mask\n", argv[0]);
        exit(1);
    }

    ipMask = inet_addr(argv[2]);
    if (ipMask == INADDR_NONE) {
        printf("usage: %s IPv4address IPv4mask\n", argv[0]);
        exit(1);
    }


    status = GetIpInterfaceTable( AF_INET, &pipTable );
    if( status != NO_ERROR )
    {
        printf("GetIpInterfaceTable returned error: %ld\n", 
            status);
        exit(1);
    }

    // Use loopback interface
    interfaceLuid = pipTable->Table[0].InterfaceLuid;

    localAddress.sin_family            = AF_INET;
    localAddress.sin_addr.S_un.S_addr  = ipAddress;
    
    FreeMibTable(pipTable);
    pipTable = NULL;    

    // Initialize the row
    InitializeUnicastIpAddressEntry( &ipRow );

    ipRow.InterfaceLuid = interfaceLuid;
    ipRow.Address.Ipv4 = localAddress;

    // Create a Handle to be notified of IP address changes
    gCallbackComplete = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (gCallbackComplete == NULL) {
        printf("CreateEvent failed with error: %d\n", GetLastError() );
        exit(1);
    }    
    
    // Use NotifyUnicastIpAddressChange to determine when the address is ready
    NotifyUnicastIpAddressChange(AF_INET, &CallCompleted, NULL, FALSE, &gNotifyEvent);

    status = CreateUnicastIpAddressEntry(&ipRow);
    if(status != NO_ERROR)
    {
        CancelMibChangeNotify2(gNotifyEvent);
        switch(status)
        {
            case ERROR_INVALID_PARAMETER:
                printf("Error: CreateUnicastIpAddressEntry returned ERROR_INVALID_PARAMETER\n");
                break;
            case ERROR_NOT_FOUND:
                printf("Error: CreateUnicastIpAddressEntry returned ERROR_NOT_FOUND\n");
                break;
            case ERROR_NOT_SUPPORTED:
                printf("Error: CreateUnicastIpAddressEntry returned ERROR_NOT_SUPPORTED\n");
                break;
            case ERROR_OBJECT_ALREADY_EXISTS:
                printf("Error: CreateUnicastIpAddressEntry returned ERROR_OBJECT_ALREADY_EXISTS\n");
                break;
            default:
                //NOTE: Is this case needed? If not, we can remove the ErrorExit() function
                printf("CreateUnicastIpAddressEntry returned error: %d\n", status);
                break;
        }
        exit (status);
        
    }
    else
        printf("CreateUnicastIpAddressEntry succeeded\n");
        
    // Set timeout to 6 seconds
    status = WaitForSingleObject(gCallbackComplete, 6000);
    if(status != WAIT_OBJECT_0)
    {
        CancelMibChangeNotify2(gNotifyEvent);
        CancelMibChangeNotify2(gCallbackComplete);
        switch(status)
        {
            case WAIT_ABANDONED:
                printf("Wait on event was abandoned\n");
                break;
            case WAIT_TIMEOUT:
                printf("Wait on event timed out\n");
                break;
            default:
                printf("Wait on event exited with status %d\n", status);
                break;
        }
        return status;
    }
    printf("Task completed successfully\n");
    CancelMibChangeNotify2(gNotifyEvent);
    CancelMibChangeNotify2(gCallbackComplete);

    exit (0);
}


void CALLBACK CallCompleted(PVOID callerContext, PMIB_UNICASTIPADDRESS_ROW row, MIB_NOTIFICATION_TYPE notificationType)
{

    ADDRESS_FAMILY addressFamily; 
    SOCKADDR_IN sockv4addr;
    struct in_addr ipv4addr;
    
    // Ensure that this is the correct notification before setting gCallbackComplete
    // NOTE: Is there a stronger way to do this?
    if(notificationType == MibAddInstance) {
        printf("NotifyUnicastIpAddressChange received an Add instance\n");
        addressFamily = (ADDRESS_FAMILY) row->Address.si_family;
        switch (addressFamily) {
            case AF_INET:
                printf("\tAddressFamily: AF_INET\n");
                break;
            case AF_INET6:
                printf("\tAddressFamily: AF_INET6\n");
                break;
            default:    
                printf("\tAddressFamily: %d\n", addressFamily);
                break;
       }
       if (addressFamily == AF_INET) {
            sockv4addr = row->Address.Ipv4;
            ipv4addr = sockv4addr.sin_addr;
            printf("IPv4 address:  %s\n", inet_ntoa(ipv4addr) );
       }     
       if (callerContext != NULL)
           printf("Received a CallerContext value\n");
           
       SetEvent(gCallbackComplete);
    }    
    return;
}

要求

要求
最低受支持的客户端 Windows Vista [仅限桌面应用]
最低受支持的服务器 Windows Server 2008 [仅限桌面应用]
目标平台 Windows
标头 netioapi.h (包括 Iphlpapi.h)
Library Iphlpapi.lib
DLL Iphlpapi.dll

另请参阅

DeleteUnicastIpAddressEntry

GetUnicastIpAddressEntry

GetUnicastIpAddressTable

IP 帮助程序函数参考

InitializeUnicastIpAddressEntry

MIB_UNICASTIPADDRESS_ROW

MIB_UNICASTIPADDRESS_TABLE

Netsh.exe

NotifyAddrChange

NotifyIpInterfaceChange

NotifyUnicastIpAddressChange

SetUnicastIpAddressEntry