新的 ipconfig 和 IP Helper API






4.85/5 (55投票s)
本文详细介绍了一个允许操作网络适配器的类,并使用该类为所有使用 Windows2000 或 Windows XP 的用户构建了一个图形化的 ipconfig 工具。为了实现这一点,使用了 IP 辅助 API。
引言
本文介绍的主要类实际上是我想要拥有一个 GUI 版的控制台 ipconfig 工具的替代品而产生的。很久以前,我做了很多与网络相关的工作,我厌倦了不断弹出命令提示符,而我总是毫不例外地会关闭它。经过几个不同的版本,我终于满意的一个版本诞生了。这就是 NetCfg-The Sequel。
主控制类提供了什么
- 程序化续订/释放功能
- 网络适配器枚举
- 网络适配器详细信息,如租约信息、DHCP 服务器、WINS 服务器、子网信息等。
- 适配器描述检索。
应用程序提供了什么(以上内容的体现)
- 允许查看给定系统上的多个适配器。
- 显示 IP 地址是如何获取的。
- 显示适配器信息,如 WINS、DHCP 和 DNS 服务器。
- 显示租约时间(获取和到期)
- 允许释放和续订适配器。
- 可以最小化到系统托盘,当鼠标悬停时会显示包含 IP 数据的工具提示。
网络适配器类
首先,快速说明一下:在我个人的库中,这个类是一个基于 MFC/ATL 的类,这意味着它使用了 VS.NET 中支持的许多类,可以在非 MFC 项目中使用,但在 VS6 中不支持,例如 CTime 和 CString。我还使用了 CAtlArray 等用于数组的构造,而不是项目中的所有基于 STL 的类。我在这里使用了 STL 来最大限度地减少希望在 VS6 环境开发的应用程序中使用此类的用户的移植问题。任何想要 MFC 版本(出于某种原因)的人都可以随时与我联系。
现在进入正题。
class CNetworkAdapter { public: CNetworkAdapter(); ~CNetworkAdapter(); BOOL SetupAdapterInfo( IP_ADAPTER_INFO* pAdaptInfo ); // information about the adapters name for the users // and its name to the system tstring GetAdapterName() const; tstring GetAdapterDescription() const; // dhcp lease access functions time_t GetLeaseObtained() const; time_t GetLeaseExpired() const; // access to lists of various server's ip address SIZE_T GetNumIpAddrs() const; SIZE_T GetNumDnsAddrs() const; tstring GetIpAddr( int nIp = 0 ) const; tstring GetSubnetForIpAddr( int nIp = 0 ) const; tstring GetDnsAddr( int nDns = 0 ) const; tstring GetCurrentIpAddress() const; // dhcp function BOOL IsDhcpUsed() const; tstring GetDchpAddr() const; // wins function BOOL IsWinsUsed() const; tstring GetPrimaryWinsServer() const; tstring GetSecondaryWinsServer() const; tstring GetGatewayAddr( int nGateway = DEFAULT_GATEWAY_ADDR) const; SIZE_T GetNumGatewayAddrs() const; static tstring GetAdapterTypeString( UINT nType ); UINT GetAdapterType() const; DWORD GetAdapterIndex() const; BOOL ReleaseAddress(); BOOL RenewAddress(); protected: : private: : };
除了类接口本身,为了能有效地使用这个类,还有一件事必须提到,那就是用于枚举计算机上适配器的函数。这个函数看起来像
DWORD EnumNetworkAdapters( CNetworkAdapter* lpBuffer, ULONG ulSzBuf, LPDWORD lpdwOutSzBuf );
这个函数的工作方式与许多标准的 Windows 枚举项的函数类似。您必须向其传递一个缓冲区、该缓冲区的总字节大小以及用于返回实际所需字节数的参数。如果您的缓冲区太小,则返回 `ERROR_INSUFFICIENT_BUFFER`。
操作
枚举
要使用这个类进行任何操作,您需要做的第一件事就是实际设置一个。虽然您可以自由地自己进行枚举并调用我提供的设置函数,但我提供了一个枚举函数,如果您愿意,它可以让您的生活更轻松。这正是大部分实际工作发生的地方。
DWORD EnumNetworkAdapters( CNetworkAdapter* pAdapters, ULONG ulSzBuf,LPDWORD lpdwOutSzBuf ) { IP_ADAPTER_INFO* pAdptInfo = NULL; IP_ADAPTER_INFO* pNextAd = NULL; ULONG ulLen = 0; int nCnt = 0; CWinErr erradapt; erradapt = ::GetAdaptersInfo( pAdptInfo, &ulLen ); if( erradapt == ERROR_BUFFER_OVERFLOW ) { pAdptInfo = ( IP_ADAPTER_INFO* )ALLOCATE_FROM_PROCESS_HEAP( ulLen ); erradapt = ::GetAdaptersInfo( pAdptInfo, &ulLen ); } pNextAd = pAdptInfo; while( pNextAd ) { nCnt++; pNextAd = pNextAd->Next; } *lpdwOutSzBuf = nCnt * sizeof( CNetworkAdapter ); if( ulSzBuf < *lpdwOutSzBuf ) { DEALLOCATE_FROM_PROCESS_HEAP( pAdptInfo ); return ERROR_INSUFFICIENT_BUFFER; } // this needs to be reset for future use. nCnt = 0; if( erradapt == ERROR_SUCCESS ) { // initialize the pointer we use the move through // the list. pNextAd = pAdptInfo; // loop through for all available interfaces and setup an // associated CNetworkAdapter class. while( pNextAd ) { pAdapters[ nCnt ].SetupAdapterInfo( pNextAd ); // move forward to the next adapter in the list so // that we can collect its information. pNextAd = pNextAd->Next; nCnt++; } } // free any memory we allocated from the heap before // exit. we wouldn't wanna leave memory leaks now would we? ;p DEALLOCATE_FROM_PROCESS_HEAP( pAdptInfo ); return ERROR_SUCCESS; }
为了获取适配器信息,我们首先调用 IP 函数 `GetAdaptersInfo`。一旦我们有了这个列表,我们就必须确保我们从调用者那里获得了足够的空间来存储新的适配器列表。如果我们没有足够大的缓冲区,我们会清理我们分配的内存,并将错误返回给调用者,以及完成操作所需的总空间量,以便用户可以调整其缓冲区大小并再次执行调用。如果接收到的缓冲区足够大,我们就可以开始迭代所有适配器,依次设置每个适配器。为此,会调用成员函数 `SetupAdapterInfo`。
BOOL CNetworkAdapter::SetupAdapterInfo( IP_ADAPTER_INFO* pAdaptInfo ) { BOOL bSetupPassed = FALSE; IP_ADDR_STRING* pNext = NULL; IP_PER_ADAPTER_INFO* pPerAdapt = NULL; ULONG ulLen = 0; CWinErr err; _IPINFO iphold; if( pAdaptInfo ) { #ifndef _UNICODE m_sName = pAdaptInfo->AdapterName; m_sDesc = pAdaptInfo->Description; #else USES_CONVERSION; m_sName = A2W( pAdaptInfo->AdapterName ); m_sDesc = A2W( pAdaptInfo->Description ); #endif m_sPriWins = pAdaptInfo->PrimaryWinsServer.IpAddress.String; m_sSecWins = pAdaptInfo->SecondaryWinsServer.IpAddress.String; m_dwIndex = pAdaptInfo->Index; m_nAdapterType = pAdaptInfo->Type; m_bDhcpUsed = pAdaptInfo->DhcpEnabled; m_bWinsUsed = pAdaptInfo->HaveWins; m_tLeaseObtained= pAdaptInfo->LeaseObtained; m_tLeaseExpires = pAdaptInfo->LeaseExpires; m_sDhcpAddr = pAdaptInfo->DhcpServer.IpAddress.String; if( pAdaptInfo->CurrentIpAddress ) { m_sCurIpAddr.sIp = pAdaptInfo->CurrentIpAddress->IpAddress.String; m_sCurIpAddr.sSubnet = pAdaptInfo->CurrentIpAddress->IpMask.String; }else{ m_sCurIpAddr.sIp = _T("0.0.0.0"); m_sCurIpAddr.sSubnet = _T("0.0.0.0"); } // since an adapter may have more than one ip address we need // to populate the array we have setup with all available // ip addresses. pNext = &( pAdaptInfo->IpAddressList ); while( pNext ) { iphold.sIp = pNext->IpAddress.String; iphold.sSubnet = pNext->IpMask.String; m_IpAddresses.push_back( iphold ); pNext = pNext->Next; } // an adapter usually has just one gateway however the provision // exists for more than one so to "play" as nice as possible // we allow for it here // as well. pNext = &( pAdaptInfo->GatewayList ); while( pNext ) { m_GatewayList.push_back(pNext->IpAddress.String); pNext = pNext->Next; } // we need to generate a IP_PER_ADAPTER_INFO structure in order // to get the list of dns addresses used by this adapter. err = ::GetPerAdapterInfo( m_dwIndex, pPerAdapt, &ulLen ); if( err == ERROR_BUFFER_OVERFLOW ) { pPerAdapt = (IP_PER_ADAPTER_INFO*) ALLOCATE_FROM_PROCESS_HEAP( ulLen ); err = ::GetPerAdapterInfo( m_dwIndex, pPerAdapt, &ulLen ); // if we succeed than we need to drop into our loop // and fill the dns array will all available IP // addresses. if( err == ERROR_SUCCESS ) { pNext = &(pPerAdapt->DnsServerList); while(pNext) { m_DnsAddresses.push_back( pNext->IpAddress.String ); pNext = pNext->Next; } bSetupPassed = TRUE; } // this is done outside the err == ERROR_SUCCES just in case. // the macro uses NULL pointer checking so it is ok if // pPerAdapt was never allocated. DEALLOCATE_FROM_PROCESS_HEAP(pPerAdapt); } } return bSetupPassed; }
虽然这个函数有点冗长,但它的本质非常简单。它只是将 `IP_ADAPTER_INFO` 结构中的大部分数据传输到我们的成员变量中,以便以后可以使用和检索它们。这也是我们确保处理本地 IP 地址和网关可能拥有多个 IP 地址的可能性(此类支持多个 IP 地址和网关,但在演示应用程序中未使用)。接下来,我们收集适配器的 DNS 信息,这些信息在 `IP_PER_ADAPTER_INFO` 结构中提供。为了获取这些信息,我们必须调用 `::GetPerAdapterInfo` API 函数。对我来说,DNS 信息没有像其他所有信息一样保存在适配器结构中是没有多大意义的,但不管怎样,它就是没有。最后,我们只需要清理分配的内存并退出函数。
续订和释放
虽然接口中用于释放或续订适配器的函数是分开的,但实际上我们只使用一个函数。我们可以这样做,因为续订或释放地址所需的操作是完全相同的,除了实际调用 API 来续订或释放。为了解决这个问题,我们使用一个函数,该函数接收一个指向续订/释放操作的适当 API 函数的指针。
BOOL CNetworkAdapter::DoRenewRelease(DWORD ( __stdcall *func) ( PIP_ADAPTER_INDEX_MAP AdapterInfo ) ) { IP_INTERFACE_INFO* pInfo = NULL; BOOL bDidIt = FALSE; ULONG ulLen = 0; int nNumInterfaces = 0; int nCnt = 0; CWinErr err; err = ::GetInterfaceInfo( pInfo, &ulLen ); if( err == ERROR_INSUFFICIENT_BUFFER ) { pInfo = ( IP_INTERFACE_INFO* ) ALLOCATE_FROM_PROCESS_HEAP( ulLen ); err = ::GetInterfaceInfo( pInfo, &ulLen ); if( err != NO_ERROR ) { return FALSE; } } // we can assume from here out that we have a valid array // of IP_INTERFACE_INFO structures due to the error // checking one above. nNumInterfaces = ulLen / sizeof( IP_INTERFACE_INFO ); for( nCnt = 0; nCnt < nNumInterfaces; nCnt++ ) { if( pInfo[ nCnt ].Adapter[ 0 ].Index == m_dwIndex ) { err = func( &pInfo[ nCnt ].Adapter[ 0 ] ); // free all used memory since we don't need it any more. DEALLOCATE_FROM_PROCESS_HEAP( pInfo ); bDidIt = ( err == NO_ERROR ); if( ! bDidIt ) { return FALSE; } break; } } return bDidIt; }
当我们到达这里时,第一件事是枚举系统的适配器,并遍历我们收到的列表,直到找到标识我们所代表的相同适配器的条目。一旦我们有了这个,我们就可以调用我们传递给它的任何 IP 函数,数据将被处理并返回错误代码。剩下的就是释放我们的内存并返回。
关于释放的说明:回顾一下,我猜将释放块放在循环外面会更安全,这样可以确保它被删除,但是,适配器总会找到,因为我们在枚举硬件。在循环中找不到适配器本身的唯一方法是应用程序启动,适配器被移除,然后有人尝试释放适配器。这就需要热插拔卡(因为硬件正在运行),这意味着这运行在高配置服务器上,而这并不是我的目标受众。
杂项
这里还有很多其他函数,您可以自行查看。它们非常简单,并不需要特别的关注,因为它们主要是用于检索适配器和每个适配器信息结构中的数据的访问器函数。
收尾
好吧,我希望有些人能从这段代码中学到有用的东西,对于那些学不到的人,我至少希望您现在能获得一个很好的小工具,可以替代命令行版本。这个工具的唯一遗憾之处在于它不支持 NT4,因为它需要 iphlpapi.lib。对我来说,这并不是一个真正的问题,因为我不再使用 NT4 了,但仍然希望它能支持。
演示注意事项
关于演示,我唯一真正想说的是关于拥有多个适配器的机器以及我所谓的对它的支持。对于那些没有多个适配器的人来说,您将看不到它是如何工作的,但请相信我,它就在那里。对于那些拥有多个网卡的人来说,适配器名称旁边有一个小的旋转器,可以用来在计算机上的适配器列表中移动。
未来改进
我现在要改进的这项软件的一个方面是处理 MAC 地址。我在这里提供的代码中已经做了一些准备,但尚未完成。这主要是因为这段代码对我来说已经相当老了。我只是最近才将其清理干净并在这里呈现。这是我很久以前就想做的事情之一,但一直被推迟了。事情就是这样。欢迎就此应用程序或类提出建议/评论/疑虑/请求。
历史
初始发布
贷方
Davide Calabro 的 `CXPStyleButtonST` 和相关类被用于演示应用程序。谢谢 Davide!