IP Helper API 使用方法
作者:刘巍 Wednesday, August 7 2002 12:46 PM
IP Helper API 简介
IP Helper 是一套用于管理本地网络设置的API(应用程序编程接口)它的功能十分强大,通过使用这一套API,你可以方便的改变计算机的网络设置或者提取有关的信息。而且它还提供了一种消息机制,能够在本地计算机的网络设置发生改变时通知你的应用程序。也就是说以前设置IP,掩码等等另人难以入眠的种种烦琐的工作现在都可以轻松搞定了。而且实际上,它不仅仅能够提取本机的网络设置信息,还能够获得网络上其它计算机的IP使用情况和MAC地址。这正是后面另一篇文章的例子程序中要使用的功能。
哪儿可以搞到IP Helper API?
Windows 98以上的所有操作系统在系统目录的system32下都带有iphlpapi.dll这个库文件,而且对于NT 4.0在加装了Service Pack 2以后也就有了这个库文件。这只能说明我们的程序可以在这些系统上运行而不需要额外的库文件,但是对于C程序员来说还必须有相应的头文件等等东西。这个东东网上不太多,在华中理工和清华9#的FTP上有它的头文件,文件很小,只有几K(头文件而已,大不到哪儿去)。当然,你也可以问微软要,下载SDK或者是定购SDK光盘(这是我梦想的事情)。最后是对MSDN的一个勘误:MSDN所声称的静态链接库iphlpapi.lib是不存在的(至少我没有找到),还是老老实实的使用动态库吧。
从最简单的开始
最简单的当然是看看我们计算机上的网络设置是什么而不要动手修改。附表中列出了一个简单的网络设置查看程序,在这个程序中我分类使用了主要的API函数,为了交代问题而不让你陷入到MFC的汪洋大海中去,我把它做成了控制台界面的,而且做了详尽的注释,你可以把其中的代码剪切粘贴下来在任何地方使用。让我来解释一下这些代码:
提取网卡信息
hInst=LoadLibrary("iphlpapi.dll");
if(!hInst)
cout<<"iphlpapi.dll not supported in this platform!\n";
这三行代码的作用是加载iphlpapi库文件,在极个别的情况下,你使用了win95以下的操作系统或者不小心删除了iphlpapi.dll,第三行的语句才会被打印出来,这也是程序失败的唯一原因。
希望你还记得如何使用.dll。长久以来我们一直都在幸福的使用着VC的静态链接库,在没有静态链接库的情况下我们还有傻瓜化的控件。下面的几行代码显示了调用DLL中函数的过程:
pGAInfo=(PGAINFO)GetProcAddress(hInst,"GetAdaptersInfo");
ULONG ulSize=0;
pGAInfo(pInfo,&ulSize);
pInfo=(PIP_ADAPTER_INFO)new(char[ulSize]);
pGAInfo(pInfo,&ulSize);
第一行代码是获得函数GetAdaptersInfo的入口地址,以便我们在后面通过指针调用函数。这个函数的功能是提取网卡的信息,并接收两个参数,第一个参数是用来保存网卡信息的内存缓冲的首地址,而第二个参数是这个缓冲的大小。但上面的代码看起来有些奇怪对么?由于我们事先不知道本地机器上有多少张网卡,所以也就没法知道应该分配多大的缓存。好在GetAdaptersInfo函数在缓冲的大小不够时会在第二个参数也就是ulSize中填入应该分配的缓冲的大小。这样,我们就可以调用两次GetAdaptersInfo,第一次是获取缓冲区的大小,然后分配这个缓冲以后再次调用它以获得实际的网卡信息。
令人不解的是,GetAdaptersInfo通过pInfo返回的信息竟然是以静态链表的方式组织的,下面就是访问链表的代码:
while(pInfo)
{
。。。。。。。。。。。。。。。。。。。。//访问网卡数据
//将当前指针移向下一个结点
pInfo=pInfo->Next;
}
为了说明问题,我再一次省略了其中的代码。下面看看GetAdaptersInfo都返回了些什么样的信息,下面是对pInfo所指向的数据结构的解释:
typedef struct _IP_ADAPTER_INFO {
struct _IP_ADAPTER_INFO* Next;
//链表指针域,我们通过这个来遍历静态键表
DWORD ComboIndex;//保留未用
char AdapterName[MAX_ADAPTER_NAME_LENGTH + 4];
//网卡名
char Description[MAX_ADAPTER_DESCRIPTION_LENGTH + 4];
//对网卡的描述,实际上好象是//驱动程序的名字
UINT AddressLength;
//物理地址的长度,通过这个我们才能正确的显示下面数组中的物理地
//址
BYTE Address[MAX_ADAPTER_ADDRESS_LENGTH];
//物理地址,每个字节存放一个十六进制的数
//值,我们配合上一个数据域在printf中用/x格式把每个字节输出。
DWORD Index;//网卡索引号
UINT Type;//网卡类型
UINT DhcpEnabled;//是否启用了DHCP动态IP分配?
PIP_ADDR_STRING CurrentIpAddress;//当前使用的IP地址
IP_ADDR_STRING IpAddressList;
//绑定到此网卡的IP地址链表,重要项目
IP_ADDR_STRING GatewayList;
//网关地址链表,重要项目
IP_ADDR_STRING DhcpServer;
//DHCP服务器地址,只有在DhcpEnabled==TRUE的情况下才有
//效
BOOL HaveWins;//是否启用了WINS?
IP_ADDR_STRING PrimaryWinsServer;
//主WINS地址
IP_ADDR_STRING SecondaryWinsServer;
//辅WINS地址
time_t LeaseObtained;
//当前DHCP租借获取的时间
time_t LeaseExpires;
//当前DHCP租借失效时间。这两个数据结构只有在启用了DHCP时才
//有用。
} IP_ADAPTER_INFO, *PIP_ADAPTER_INFO;
现在应该可以看懂这个程序了吧。还必须告诉你的是这个数据结构中的几个IP地址字符串IpAddressList、GatewayList等等都是以链表的方式组织的(微软偏好链表?)。这就是为什么我的程序里面充满了循环。实际上你看到的所有数据都可以在控制面板|网络|属性|TCP/IP的属性页的高级选项里看见
提取网络接口信息
其中最主要的两个函数是GetNumberOfInterfaces和GetInterfaceInfo,前者指出网络接口的个数,后者提取网络接口的信息。对于第一个函数要说明的一点是它好象并没有返回正确的值,因为据MSDN描述:一个网络接口是网卡的逻辑抽象,它们是一对一的关系。而实际情况是我的机器上只有一张网卡,这个函数却返回了2。实际上,因为每个系统都附加有一个调试用的网络接口,这个接口的IP地址是127.0.0.1子网掩码是255.0.0.0。这个结果可以从程序的输出看出来。由GetInterfaceInfo返回的IP_INTERFACE_INFO结构中也有一个NumAdapters整型的数据域记录了正确的网卡。然后对于GetInterfaceInfo要注意的是它也必须被调用两次,第一次获取缓冲大小,第二次才是取值。然后再次让我感到其怪的是GetInterfaceInfo返回的IP_INTERFACE_INFO不象上面的结构是用链表,而是用的动态数组的方法(到现在什么线性结构都用上了),所以遍历其中每一个元素的代码变成:
for(int i=0;iNumAdapters;i++)
{
cout<<"Adapter index:"<Adapter[i].Index<Adapter[i].Name<
IP_INTERFACE_INFO结构的解释如下:
typedef struct _IP_INTERFACE_INFO {
LONG NumAdapters;
// 动态数组中网络接口元素的个数,通过它来遍历数
//组
IP_ADAPTER_INDEX_MAP Adapter[1];
// 网络接口数据数组
} IP_INTERFACE_INFO,*PIP_INTERFACE_INFO;
其中的IP_ADAPTER_INDEX_MAP结构如下:
typedef struct _IP_ADAPTER_INDEX_MAP {
ULONG Index;
// 网卡索引
WCHAR Name[MAX_ADAPTER_NAME];
// 网卡名
} IP_ADAPTER_INDEX_MAP, * PIP_ADAPTER_INDEX_MAP;
提取IP信息
这部分显然和提取网络接口信息部分是相同的。
设置本地网络
设置的过程与提取过程其实是换汤不换药。我不想在这里演示每一个函数的用法,所以只使用了一个(我认为)最常用的函数AddIPAddress。这个函数能够设置本地网络的IP。但不幸的是,这个IP是临时的,当系统重新启动或者发生其它的PNP事件的时候这个IP就不存在了。那么有人会问这有什么用呢?实际上,每个网络接口卡都可以绑定多个IP,所以在网络环境恶劣的情况下(如校园网)同时预备多个IP以防断线是有必要的。必须指出的是这种临时的IP在网络通讯时可能导致的问题现在还没有测试过,诸如CODEGURU,CSDN,MSDN,或者TECHREPUBLIC对这套API也没有详细的解释(虽然它很有用)。
其它的API函数
这些函数能够让你察看或者设置网络数据报文方面的信息。比方GetIpStatistics、GetIcmpStatistics函数能够让你查看当前IP数据报和ICMP数据报的流量,以及废弃的数据报数量等等。使用这些函数你可以构建自己的网络监控程序检察网络中的故障。你也可以使用SetIpStatistics 函数来设置相应的IP协议栈属性,缩短或者延长IP数据报的缺省TTL值。然后你也可以使用GetIpForwardTable、CreateIpForwardEntry 、DeleteIpForwardEntry、SetIpForwardEntry来分别获取IP路由表的信息,创建路由表项,删除路由表项和修改路由表项。也可以用GetBestRoute、 GetBestInterface获得到达指定IP的最好的路由点和网络接口。
事实上,通过这些函数我们可以得到许多MIB变量(《使用TCP/IP协议实现网际互联》第二卷),通过这些MIB变量,我们可以非常快速的制作一个网络管理软件。
未涉及的部分
我们没有涉及IP Helper API中SendARP函数的使用。实际上,这个函数是我使用这套API的主要原因,它能够简单的发送ARP数据包并返回目标机器的MAC地址。我使用这个函数制作了另一个简单的IP查看程序,它能够查看局域网上哪些IP正在使用,并能够显示它们的MAC地址。如果把这些信息记录在文件中,我就可以统计出一天中哪些家伙的上网时间最长以及其上网的习性并能够知道哪些人更换了他们的网卡,也可以分析出这个网络的使用情况(我在学校的高峰时段统计出一个网段的254个IP中竟有236个正在使用,可见增加网段的必要了)。我将在后面的文章中说明这个程序的制作。我们也没有涉及这套API提供的当网络设置改变时向应用程序发出消息的异步通知功能。因为它们非常简单,与WINSOCK中WSAAsyncSelect的使用方法是一样的,就不再说明了。
后话
讨论如何使用API函数也许会被一批"纯粹"的程序员所鄙视。但每次看到有人在论坛上急切的询问如何取得本机网关,IP地址,路由之类的信息的时候,我就觉得非常有必要介绍这一套API函数,它能够(至少在我看来)满足我们的大部分要求