6.2.2 Packet.dll库中获得网络适配器列表的实现
1.关键结构体
结构体_ADAPTER_INFO包含了系统上所安装的网络适配器可理解的信息,以及用户需要的所有附加信息。该结构体的定义如下:
typedef struct_ADAPTER_INFO
{
//指向列表中的下一个结点
struct_ADAPTER_INFO*Next;
//描述该适配器的名字
CHAR Name[ADAPTER_NAME_LENGTH+1];
//适配器可理解的描述
CHAR Description[ADAPTER_DESC_LENGTH+1];
//数据链路层地址的长度
UINT MacAddressLen;
//数据链路层地址
UCHAR MacAddress[MAX_MAC_ADDR_LENGTH];
//适配器的物理特性,该NetType结构体包含了该适配器的链路类型与速度
NetType LinkLayer;
//指向一个网络地址列表,每一个结点描述该适配器的所有网络地址
PNPF_IF_ADDRESS_ITEM pNetworkAddresses;
//适配器标识
UINT Flags;
}ADAPTER_INFO,*PADAPTER_INFO;
在packetNtx\Dll\AdInfo.c文件中声明了下面两个全局变量:
PADAPTER_INFO g_AdaptersInfoList=NULL;
HANDLE g_AdaptersInfoMutex=NULL;
g_AdaptersInfoList为全局网络适配器的列表,当应用程序连接Packet.dll库时创建该列表。g_AdaptersInfoMutex是保护g_AdaptersInfoList列表的互斥信号。注意,把ADAPTER_INFO作为一个参数的API,都假定是获取了该互斥信号后再进行访问的。换句话说,如果哪个API没有把ADAPTER_INFO作为一个参数,那么对g_AdaptersInfoList的访问就需要先获得该互斥信号,然后再进行访问。实例代码如下所示:
void PacketPopulateAdaptersInfoList()
{
……
/*
*调用者不使用g_AdaptersInfoList作为参数,
*应该获得g_AdaptersInfoMutex互斥信号
*/
WaitForSingleObject(g_AdaptersInfoMutex,INFINITE);
/访问g_AdaptersInfoList参数/
if(g_AdaptersInfoList)
{
……
}
……
/释放g_AdaptersInfoMutex互斥信号/
ReleaseMutex(g_AdaptersInfoMutex);
……
}
ADAPTER_INFO结构体中的成员结构体_NPF_IF_ADDRESS_ITEM包含一个适配器中所有网络地址结点的列表,该结构体的定义如下:
typedef struct_NPF_IF_ADDRESS_ITEM
{
npf_if_addr Addr;//网络地址
struct_NPF_IF_ADDRESS_ITEM*Next;//指向列表的下一个结点
}
NPF_IF_ADDRESS_ITEM,*PNPF_IF_ADDRESS_ITEM;
结构体_NPF_IF_ADDRESS_ITEM中的成员npf_if_addr存储着网络适配器的地址。PacketGetNetInfoEx函数使用该结构体,以返回适配器的网络地址。该结构体定义如下:
typedef struct npf_if_addr{
struct sockaddr_storage IPAddress;//IP地址
struct sockaddr_storage SubnetMask;//网络掩码地址
struct sockaddr_storage Broadcast;//广播地址
}npf_if_addr;
2.PacketGetAdapterNames函数
PacketGetAdapterNames函数用于获取可用网络适配器的列表,以及它们对应的描述,其原型如下:
BOOLEAN PacketGetAdapterNames(PTSTR pStr,PULONG BufferSize)
上述函数中,参数pStr是用户分配的字符串,用来存储适配器的名称;参数BufferSize是pStr所指缓冲区的长度。如果函数执行失败,参数BufferSize返回存储适配器列表所需的字节数。如果函数执行成功,返回非0值。如果返回值为0,则表示参数BufferSize返回的是存储适配器列表所需的字节数。
PacketGetAdapterNames函数的主要实现步骤如图6-6所示。
图 6-6 PacketGetAdapterNames函数的主要实现步骤
通常,PacketGetAdapterNames函数是第一个与驱动程序通信的函数。它返回系统上所安装的WinPcap能支持的适配器名称。在适配器的名称之后,pStr包含一个字符串,它用来对每一个适配器进行描述。
在调用PacketGetAdapterNames函数后,pStr字符串的格式如下所示:
❑一些数量的ASCII码字符串之间由一个“\0”分开,每个字符串表示一个适配器的名称。
❑上述字符串后紧接着后续两个“\0”。
❑一些数量的ASCII码字符串之间由一个“\0”分开,每个字符串描述一个对应的适配器。其描述的个数与适配器名称的个数一样,第一个描述对应于第一个适配器名称,后面的则以此类推。
❑上述字符串后紧接着后续两个“\0”。
PacketGetAdapterNames函数的实现代码如下:
BOOLEAN PacketGetAdapterNames(PTSTR pStr,PULONG BufferSize)
{
……
/创建适配器列表/
PacketPopulateAdaptersInfoList();
/获得g_AdaptersInfoMutex互斥信号/
WaitForSingleObject(g_AdaptersInfoMutex,INFINITE);
if(!g_AdaptersInfoList)
{//失败,系统中没有找到适配器,函数返回
……
}
/填充pStr中适配器名称与描述这两个列表/
//第一次遍历g_AdaptersInfoList,
//目的是计算第二个列表开始的偏移量与检查缓冲区的大小
for(TAdInfo=g_AdaptersInfoList;TAdInfo!=NULL;
TAdInfo=TAdInfo->Next)
{
if(TAdInfo->Flags!=INFO_FLAG_DONT_EXPORT)
{
//更新有关大小的变量值
SizeNeeded+=(ULONG)strlen(TAdInfo->Name)+
(ULONG)strlen(TAdInfo->Description)+2;
SizeNames+=(ULONG)strlen(TAdInfo->Name)+1;
}
}
//检查缓冲区大小,以防缓冲区溢出
//注意:需要两个额外的分隔符(两个列表间一个“\0”,
//第二个列表结束时的一个“\0”)
if(SizeNeeded+2>*BufferSize||pStr==NULL)
{
ReleaseMutex(g_AdaptersInfoMutex);
*BufferSize=SizeNeeded+2;//报告所需的缓冲区大小
SetLastError(ERROR_INSUFFICIENT_BUFFER);
return FALSE;
}
OffDescriptions=SizeNames+1;//获得第二个列表开始的偏移
//第二次遍历g_AdaptersInfoList,目的是复制适配器的信息
for(TAdInfo=g_AdaptersInfoList,SizeNames=0,
SizeDesc=0;TAdInfo!=NULL;
TAdInfo=TAdInfo->Next)
{
if(TAdInfo->Flags!=INFO_FLAG_DONT_EXPORT)
{
//复制数据
StringCchCopyA(((PCHAR)pStr)+SizeNames,
*BufferSize-SizeNames,TAdInfo->Name);
StringCchCopyA(((PCHAR)pStr)+OffDescriptions+
SizeDesc,
*BufferSize-OffDescriptions-SizeDesc,
TAdInfo->Description);
//更新有关大小的变量值
SizeNames+=(ULONG)strlen(TAdInfo->Name)+1;
SizeDesc+=(ULONG)strlen(TAdInfo->Description)+1;
}
}
//在两个列表之间添加“\0”分隔符
((PCHAR)pStr)[SizeNames]=0;
//添加一个“\0”,结束列表
((PCHAR)pStr)[SizeNeeded+1]=0;
//释放g_AdaptersInfoMutex互斥信号
ReleaseMutex(g_AdaptersInfoMutex);
return TRUE;
}
PacketGetAdapterNames函数首先调用PacketPopulateAdaptersInfoList函数创建适配器列表g_AdaptersInfoList。接着开始向pStr中填充适配器的名称与描述这两个列表,在填充的过程中,需要遍历g_AdaptersInfoList两次,第一次遍历是为了计算第二个列表开始的偏移与检查缓冲区的大小,第二次遍历是为了复制适配器的名称与描述信息。复制数据结束后,在两个列表之间加入分隔符“\0”,并在第二个列表之后添加一个“\0”分隔符结束列表。
(1)PacketPopulateAdaptersInfoList函数
PacketPopulateAdaptersInfoList函数创建适配器列表g_AdaptersInfoList。该函数先释放掉g_AdaptersInfoList中原有的内容,然后调用PacketGetAdaptersNPF函数以便用新的信息填充该列表。
PacketPopulateAdaptersInfoList函数主要实现代码如下所示:
void PacketPopulateAdaptersInfoList()
{
……
/获得g_AdaptersInfoMutex互斥信号/
WaitForSingleObject(g_AdaptersInfoMutex,INFINITE);
/释放g_AdaptersInfoList中原有的内容/
if(g_AdaptersInfoList)
{
TAdInfo=g_AdaptersInfoList;
while(TAdInfo!=NULL)//遍历列表
{
PNPF_IF_ADDRESS_ITEM pItem,pCursor;
Mem2=TAdInfo;
pCursor=TAdInfo->pNetworkAddresses;
TAdInfo=TAdInfo->Next;
while(pCursor!=NULL)
{
pItem=pCursor->Next;
GlobalFreePtr(pCursor);//释放内存
pCursor=pItem;
}
GlobalFreePtr(Mem2);//释放内存
}
g_AdaptersInfoList=NULL;
}
/用新的信息填充列表/
if(!PacketGetAdaptersNPF())
{//失败
……
}
……
//释放g_AdaptersInfoMutex互斥信号
ReleaseMutex(g_AdaptersInfoMutex);
}
(2)PacketGetAdaptersNPF函数
可通过查询注册表的SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}条目获得适配器的名称,并调用PacketAddAdapterNPF函数来更新适配器列表g_AdaptersInfoList的内容。如果函数成功则返回非0值。
PacketGetAdaptersNPF函数主要实现代码如下:
static BOOLEAN PacketGetAdaptersNPF()
{
……
/打开注册表/
Status=RegOpenKeyEx(HKEY_LOCAL_MACHINE,
TEXT("SYSTEM\CurrentControlSet\Control\Class
\{4D36E972-E325-11CE-BFC1-08002BE10318}"),
0,
KEY_READ,
&AdapKey);
if(Status!=ERROR_SUCCESS)
{//打开注册表失败,跳转到tcpip_linkage
goto tcpip_linkage;
}
i=0;
/遍历{4D36E972-E325-11CE-BFC1-08002BE10318}键,获取适配器的名称/
while((Result=RegEnumKey(AdapKey,i,AdapName,
sizeof(AdapName)/2))==ERROR_SUCCESS)
{
i++;
FireWireFlag=0;
//从注册表键中获取适配器的名称
Status=RegOpenKeyEx(AdapKey,AdapName,0,KEY_READ,
&OneAdapKey);
if(Status!=ERROR_SUCCESS)
{//失败
continue;
}
//检查该适配器是否为火线(FireWire)适配器,
//通过在ComponentId中查找“1394”字符串判断。防止列出火线适配器,
//因为WinPcap能够打开它们,但是它们与操作系统的接口被断开了,
//故打开它们会导致蓝屏
dim=sizeof(TName);
Status=RegQueryValueEx(OneAdapKey,
TEXT("ComponentId"),
NULL,
NULL,
(PBYTE)TName,
&dim);
if(Status==ERROR_SUCCESS)
{
if(IsFireWire(TName))
{//设置不导出标志
FireWireFlag=INFO_FLAG_DONT_EXPORT;
}
}
Status=RegOpenKeyEx(OneAdapKey,TEXT("Linkage"),
0,KEY_READ,&LinkageKey);
if(Status!=ERROR_SUCCESS)
{//失败
RegCloseKey(OneAdapKey);
continue;
}
dim=sizeof(DeviceGuidName);
Status=RegQueryValueExA(LinkageKey,
"Export",
NULL,
NULL,
(PBYTE)DeviceGuidName,
&dim);
if(Status!=ERROR_SUCCESS)
{
RegCloseKey(OneAdapKey);
RegCloseKey(LinkageKey);
continue;
}
if(strlen(DeviceGuidName)>=strlen("\Device\"))
{
//把\Device\NPF_字符串放置到名称的开始
StringCchPrintfA(TAName,sizeof(TAName),"%s%s",
npfCompleteDriverPrefx,
DeviceGuidName+
strlen("\Device\"));
}
else
continue;
//为了以防万一,将TAName最后一个字符设为结束符'\0'
TAName[sizeof(TAName)-1]='\0';
//成功获取适配器的信息,把给适配器的信息添加到全局适配器列表
PacketAddAdapterNPF(TAName,FireWireFlag);
RegCloseKey(OneAdapKey);
RegCloseKey(LinkageKey);
}
RegCloseKey(AdapKey);
/*
*如果在{4D36E972-E325-11CE-BFC1-08002BE10318}中没找到适配器,
*这在Windows NT4下可能性很大,可试图查找tcpip绑定来获得适配器的信息
*/
tcpip_linkage:
……
return TRUE;
}
(3)PacketAddAdapterNPF函数
PacketAddAdapterNPF函数会向适配器列表g_AdaptersInfoList中添加一个结点。参数AdName则是要添加的列表结点的适配器名称。如果函数执行成功则返回非0值。
PacketAddAdapterNPF函数的主要实现步骤如图6-7所示。
图 6-7 PacketAddAdapterNPF函数的主要实现步骤
PacketAddAdapterNPF函数主要实现代码如下所示:
static BOOLEAN PacketAddAdapterNPF(PCHAR AdName,UINT fags)
{
……
/获得g_AdaptersInfoMutex互斥信号/
WaitForSingleObject(g_AdaptersInfoMutex,INFINITE);
/查看适配器的名称是否已在适配器列表中,如果是,则函数返回/
for(TAdInfo=g_AdaptersInfoList;TAdInfo!=NULL;
TAdInfo=TAdInfo->Next)
{
if(strcmp(AdName,TAdInfo->Name)==0)
{//AdName已存在列表中,函数返回
ReleaseMutex(g_AdaptersInfoMutex);
return TRUE;
}
}
//此处不能释放g_AdaptersInfoMutex互斥信号
//如果此时有两个线程试图添加同一个适配器,列表中将存在一个适配器的副本
/试图打开NPF适配器,查看它是否可用/
if(fags!=INFO_FLAG_DONT_EXPORT)
{//试图打开适配器
adapter=PacketOpenAdapterNPF(AdName);
if(adapter!=NULL)
{
//分配一个缓冲区,从驱动程序获得厂商的描述
OidData=GlobalAllocPtr(
GMEM_MOVEABLE|GMEM_ZEROINIT,512);
if(OidData==NULL)
{
//分配失败
PacketCloseAdapter(adapter);
ReleaseMutex(g_AdaptersInfoMutex);
TRACE_EXIT("PacketAddAdapterNPF");
return FALSE;
}
}
else
{
//NPF适配器不可用,不添加到列表中
ReleaseMutex(g_AdaptersInfoMutex);
TRACE_EXIT("AddAdapter");
return FALSE;
}
}
/获得该适配器的PADAPTER_INFO结构体中各成员的值/
//PacketOpenAdapter函数成功,认为这是一个可用的适配器,
//为它在适配器列表中分配一个条目
TmpAdInfo=GlobalAllocPtr(GMEM_MOVEABLE|GMEM_ZEROINIT,
sizeof(ADAPTER_INFO));
if(TmpAdInfo==NULL)
{//内存分配失败,函数返回
……
}
//复制适配器名称AdName到TmpAdInfo->Name中
strncpy(TmpAdInfo->Name,AdName,
sizeof(TmpAdInfo->Name)/sizeof(TmpAdInfo->Name[0])-1);
//无须给TmpAdInfo->Name添加结束符,
//因为在尾部预留了一个字节,并且分配TmpAdInfo内存时已设为0
if(fags!=INFO_FLAG_DONT_EXPORT)
{
PNPF_IF_ADDRESS_ITEM pAddressesFromRegistry;
//对NIC驱动进行查询,获得适配器的描述
OidData->Oid=OID_GEN_VENDOR_DESCRIPTION;
OidData->Length=256;
ZeroMemory(OidData->Data,256);
Status=PacketRequest(adapter,FALSE,OidData);
if(Status==0||((char*)OidData->Data)[0]==0)
{
//不能从NIC驱动程序获得适配器的描述信息
}
//复制设备的描述
strncpy(TmpAdInfo->Description,(PCHAR)OidData->Data,
sizeof(TmpAdInfo->Description)/
sizeof(TmpAdInfo->Description[0])-1);
//无须给TmpAdInfo->Description添加结束符,
//因为在尾部预留了一个字节,并且分配TmpAdInfo内存时已设为0
//从注册表获得一个适配器的NetType信息,该结构体包含适配器的链接类型与速度
Status=PacketGetLinkLayerFromRegistry(adapter,
&(TmpAdInfo->LinkLayer));
if(Status==FALSE)
{//失败,函数返回
……
}
//查询NIC驱动程序,获取适配器的MAC地址,现在只支持以太网
OidData->Oid=OID_802_3_CURRENT_ADDRESS;
OidData->Length=256;
ZeroMemory(OidData->Data,256);
Status=PacketRequest(adapter,FALSE,OidData);
if(Status)
{//成功获取MAC地址,地址长度设为6
memcpy(TmpAdInfo->MacAddress,OidData->Data,6);
TmpAdInfo->MacAddressLen=6;
}
else
{//获取MAC地址失败,设为00:00:00:00:00:00,长度设为0
memset(TmpAdInfo->MacAddress,0,6);
TmpAdInfo->MacAddressLen=0;
}
//获取网络地址项PNPF_IF_ADDRESS_ITEM
TmpAdInfo->pNetworkAddresses=NULL;
//从注册表中获取一个适配器的网络地址项
if(!PacketGetAddressesFromRegistry(TmpAdInfo->Name,
&pAddressesFromRegistry))
{
}
else
{
PNPF_IF_ADDRESS_ITEM pCursor;
//把pAddressesFromRegistry追加到列表
//TmpAdInfo->pNetworkAddresses的尾部
if(TmpAdInfo->pNetworkAddresses==NULL)
{
TmpAdInfo->pNetworkAddresses=
pAddressesFromRegistry;
}
else
{
pCursor=TmpAdInfo->pNetworkAddresses;
while(pCursor->Next!=NULL)
pCursor=pCursor->Next;
pCursor->Next=pAddressesFromRegistry;
}
}
TmpAdInfo->Flags=INFO_FLAG_NDIS_ADAPTER;
//NdisWan适配器不会被NPF驱动程序导出,因此此处不可能见到它们
//释放内存
PacketCloseAdapter(adapter);
GlobalFreePtr(OidData);
}
else
{
//是火线适配器,设置该标识,使后续调用将阻止它列出
TmpAdInfo->Flags=INFO_FLAG_DONT_EXPORT;
}
/更新g_AdaptersInfoList列表/
TmpAdInfo->Next=g_AdaptersInfoList;
g_AdaptersInfoList=TmpAdInfo;
/释放g_AdaptersInfoMutex互斥信号/
ReleaseMutex(g_AdaptersInfoMutex);
return TRUE;
}
PacketAddAdapterNPF函数首先会检查ADAPTER_INFO::Name成员是否有足够的空间存储适配器名称,然后获取g_AdaptersInfoMutex互斥信号。
接着函数查看要添加的适配器名称是否已存在于适配器列表中,如果是,则函数返回。否则就试图打开该适配器,查看它是否可用。如果可用则将分配一个缓冲区,以便从驱动程序获得厂商的描述信息。
然后,函数获得描述该适配器的PADAPTER_INFO结构体中各成员的值。依次复制适配器名称、设备的描述,并从注册表获得适配器的NetType结构体,同时查询NIC驱动程序,获取适配器的MAC地址,以及网络地址项PNPF_IF_ADDRESS_ITEM等。
最后函数更新g_AdaptersInfoList列表,释放g_AdaptersInfoMutex互斥信号,函数成功返回。
函数调用的PacketCloseAdapter函数与PacketOpenAdapterNPF函数在第7章中将详细分析。同时该函数还调用了PacketRequest、PacketGetLinkLayerFromRegistry与PacketGetAddressesFromRegistry函数。这些函数的原型与作用分别描述如下。
PacketRequest函数在驱动程序上执行一个参数的查询/设置操作,其原型如下:
BOOLEAN PacketRequest(LPADAPTER AdapterObject,
BOOLEAN Set,PPACKET_OID_DATA OidData)
上述函数中,参数AdapterObject指向一个_ADAPTER结构体;参数Set决定是参数的设置(Set=TRUE)还是查询(Set=FALSE)操作;参数OidData是一个指向_PACKET_OID_DATA结构体的指针,该结构体包含了所传递的数据。
如果函数执行成功,则返回非0值。
注意,并不是所有的网络适配器都实现了所有的参数查询/设置功能。
PacketAddAdapterNPF函数主要依赖于DeviceIoControl系统函数的实现,调用如下:
Result=(BOOLEAN)DeviceIoControl(
AdapterObject->hFile,
(DWORD)Set?(DWORD)BIOCSETOID:(DWORD)BIOCQUERYOID,
OidData,
sizeof(PACKET_OID_DATA)-1+OidData->Length,OidData,
sizeof(PACKET_OID_DATA)-1+OidData->Length,&BytesReturned,NULL
);
DeviceIoControl系统函数会对一个驱动程序发送一个IOCTL命令码,这样对应的设备就会执行相应的操作,其原型如下:
BOOL DeviceIoControl(
HANDLE hDevice,
DWORD dwIoControlCode,
LPVOID lpInBuffer,
DWORD nInBufferSize,
LPVOID lpOutBuffer,
DWORD nOutBufferSize,
LPDWORD lpBytesReturned,
LPOVERLAPPED lpOverlapped
);
上述函数中,各参数的含义如下:
❑hDevice为输入参数,它是执行操作的设备句柄。
❑dwIoControlCode为输入参数,它是命令码。
❑lpInBuffer为输入参数,指向包含操作所需输入参数的内存区。
❑nInBufferSize为输入参数,输入参数内存区的字节数。
❑lpOutBuffer为输出参数,指向接收操作返回数据的输出内存区。
❑nOutBufferSize为输入参数,输出参数内存区的字节数。
❑lpBytesReturned为输出参数,存储在输出参数内存区中的数据字节数。
❑lpOverlapped为输入参数,指向一个OVERLAPPED结构体的指针。如果hDevice没有使用FILE_FLAG_OVERLAPPED表示打开,则无须理会lpOverlapped。
如果DeviceIoControl函数执行成功,则返回非0值,否则返回0。
PacketGetLinkLayerFromRegistry函数用于获得一个已打开适配器的NetType结构体,该结构体包含适配器链路层的类型与速度(单位为bps),其原型如下:
static BOOLEAN PacketGetLinkLayerFromRegistry(
LPADAPTER AdapterObject,NetType*type)
上述函数中,参数AdapterObject为一个已打开适配器的句柄;参数type是一个指向NetType结构体的指针,函数用数据链路层的类型与速度值填充它。参数type的LinkType成员可以为下列值之一:
❑NdisMedium802_3:Ethernet(802.3)
❑NdisMediumWan:WAN
❑NdisMedium802_5:Token Ring(802.5)
❑NdisMediumFddi:FDDI
❑NdisMediumAtm:ATM
❑NdisMediumArcnet878_2:ARCNET(878.2)
如果PacketGetLinkLayerFromRegistry函数执行成功,则返回非0值,否则返回0值。
该函数主要依赖于PacketRequest函数的实现,其主要实现代码如下:
static BOOLEAN PacketGetLinkLayerFromRegistry(
LPADAPTER AdapterObject,NetType*type)
{
……
//获得数据链路层的类型
OidData->Oid=OID_GEN_MEDIA_IN_USE;
OidData->Length=sizeof(ULONG);
Status=PacketRequest(AdapterObject,FALSE,OidData);
type->LinkType=((UINT)OidData->Data);
//获得数据链路层的速度
OidData->Oid=OID_GEN_LINK_SPEED;
OidData->Length=sizeof(ULONG);
Status=PacketRequest(AdapterObject,FALSE,OidData);
……
}
PacketGetAddressesFromRegistry函数用于从注册表中获取一个适配器的网络地址项,其原型如下:
static BOOLEAN PacketGetAddressesFromRegistry(
LPCSTR AdapterNameA,
PNPF_IF_ADDRESS_ITEM*ppItems)
上述函数中,参数AdapterName是存储适配器名称的字符串;参数ppItems是调用者分配的指向一个地址项指针的指针。函数将会把该指针设置为从注册表中获得的地址。
如果PacketGetAddressesFromRegistry函数执行成功,则返回非0值,否则返回0值。
该函数会从注册表中获得一个接口的信息,如IP地址、网络掩码地址与广播地址。用户传递进来的缓冲区将用npf_if_addr结构体填充,每一个结构体都包含一个单独地址的数据。如果缓冲区已满,余下的地址信息将会被丢弃,因此如果只需要第一个地址,把它的大小设为sizeof(npf_if_addr)即可。
3.PacketGetNetInfoEx函数
PacketGetNetInfoEx函数用于获得一个适配器的所有地址信息,诸如IP地址、网络掩码地址与广播地址等,其原型如下:
BOOLEAN PacketGetNetInfoEx(PCHAR AdapterName,
npf_if_addr*buffer,PLONG NEntries)
上述函数中,参数AdapterName字符串是适配器的名称;参数buffer是一个用户分配的npf_if_addr结构体类型的数组,该函数将以npf_if_addr结构体填充该数组,每个结构体都含有一个单独地址的数据。如果buffer满了,余下的地址信息将会被丢弃,因此如果只需第一个网络地址,把buffer的大小设为sizeof(npf_if_addr)即可;参数NEntries是数组的大小(以npf_if_addr类型计算大小),也就是所存储npf_if_addr结构体的个数。
如果函数执行成功,则返回非0值,否则返回0值。
PacketGetNetInfoEx函数首先会对适配器的名称AdapterName考虑兼容性,必要时提供Unicode字符串到ASCII码字符串的转换。接着在一个类型为ADAPTER_INFO的全局列表g_AdaptersInfoList中更新该适配器的信息。然后在该列表中查找与该适配器相关联的PADAPTER_INFO结构体,如果查找成功,则给buffer存储npf_if_addr类型的数据,并返回。
PacketGetNetInfoEx函数的主要实现代码如下:
BOOLEAN PacketGetNetInfoEx(PCHAR AdapterName,
npf_if_addr*buffer,PLONG NEntries)
{
PADAPTER_INFO TAdInfo;
PCHAR Tname;
BOOLEAN Res,FreeBuff;
/*提供字符串转换,以便后向兼容。
*WChar2SChar函数把一个Unicode字符串转换为了一个ASCII码字符串,
*WChar2SChar函数为执行转换分配了新的内存空间,内存需要释放
*/
if(AdapterName[1]!=0)
{//ASCII字符串
Tname=AdapterName;
FreeBuff=FALSE;
}
else
{
Tname=WChar2SChar((PWCHAR)AdapterName);
FreeBuff=TRUE;//内存需要释放
}
/*
*更新该适配器的信息
*PacketUpdateAdInfo函数在一个全局列表g_AdaptersInfoList中
*更新一个适配器的信息
*/
if(!PacketUpdateAdInfo(Tname))
{
//更新失败,函数返回
if(FreeBuff)
GlobalFreePtr(Tname);
return FALSE;
}
/获得g_AdaptersInfoMutex互斥信号/
WaitForSingleObject(g_AdaptersInfoMutex,INFINITE);
/在全局的ADAPTER_INFO列表中查找与适配器相关联的PADAPTER_INFO结构体/
TAdInfo=PacketFindAdInfo(Tname);
if(TAdInfo!=NULL)
{//查找成功
LONG numEntries=0,i;
PNPF_IF_ADDRESS_ITEM pCursor;
//获取该适配器的npf_if_addr类型的地址列表
pCursor=TAdInfo->pNetworkAddresses;
//计算地址列表的元素个数numEntries
while(pCursor!=NULL)
{
numEntries++;
pCursor=pCursor->Next;
}
//且NEntries=min(numEntries,NEntries)
if(numEntries<*NEntries)
{
*NEntries=numEntries;
}
//给buffer存储npf_if_addr类型的数据
pCursor=TAdInfo->pNetworkAddresses;
for(i=0;(i<*NEntries)&&(pCursor!=NULL);i++)
{
buffer[i]=pCursor->Addr;
pCursor=pCursor->Next;
}
Res=TRUE;
}
else
{//查找失败
TRACE_PRINT("PacketGetNetInfoEx:Adapter not found");
Res=FALSE;
}
/释放g_AdaptersInfoMutex互斥信号/
ReleaseMutex(g_AdaptersInfoMutex);
if(FreeBuff)
GlobalFreePtr(Tname);
return Res;
}
PacketGetNetInfoEx函数主要调用了PacketUpdateAdInfo函数与PacketFindAdInfo函数,下面分别说明这两个函数。
(1)PacketUpdateAdInfo函数
PacketUpdateAdInfo函数会在一个全局列表g_AdaptersInfoList中更新一个适配器的信息,其原型如下:
BOOLEAN PacketUpdateAdInfo(PCHAR AdapterName);
上述函数中,参数AdapterName是所需更新信息的适配器名称。
如果函数执行成功,则返回TRUE。如果返回FALSE,则表示适配器不可用或没有被连接。
PacketUpdateAdInfo函数主要的实现代码如下:
BOOLEAN PacketUpdateAdInfo(PCHAR AdapterName)
{
PADAPTER_INFO TAdInfo,PrevAdInfo;
/获得g_AdaptersInfoMutex互斥信号/
WaitForSingleObject(g_AdaptersInfoMutex,INFINITE);
PrevAdInfo=TAdInfo=g_AdaptersInfoList;
/如果适配器AdapterNam结点在g_AdaptersInfoList列表中存在,则销毁该结点/
while(TAdInfo!=NULL)
{
if(strcmp(TAdInfo->Name,AdapterName)==0)
{
if(TAdInfo==g_AdaptersInfoList)
{
g_AdaptersInfoList=TAdInfo->Next;
}
else
{
PrevAdInfo->Next=TAdInfo->Next;
}
if(TAdInfo->pNetworkAddresses!=NULL)
{
PNPF_IF_ADDRESS_ITEM pItem,pNext;
pItem=TAdInfo->pNetworkAddresses;
//释放网络地址列表
while(pItem!=NULL)
{
pNext=pItem->Next;
GlobalFreePtr(pItem);
pItem=pNext;
}
}
GlobalFreePtr(TAdInfo);//释放该适配器信息结点
break;
}
PrevAdInfo=TAdInfo;
TAdInfo=TAdInfo->Next;
}
/释放g_AdaptersInfoMutex互斥信号/
ReleaseMutex(g_AdaptersInfoMutex);
/*
*获得适配器AdapterName的信息,
*并把该结点信息添加到g_AdaptersInfoList适配器列表中
*/
if(PacketAddAdapterNPF(AdapterName,0)==TRUE)
{
//信息更新成功
return TRUE;
}
return TRUE;
}
PacketUpdateAdInfo函数首先会检查适配器AdapterNam在g_AdaptersInfoList列表中是否存在,如果存在则销毁该结点。然后获得适配器AdapterName的信息,并把该结点信息更新到g_AdaptersInfoList适配器列表中。
(2)PacketFindAdInfo函数
PacketFindAdInfo函数用于查找g_AdaptersInfoList全局列表,以便找到适配器的相关信息,其原型如下:
PADAPTER_INFO PacketFindAdInfo(PCHAR AdapterName)
上述函数中,参数AdapterName是需获取信息的适配器名称。
如果函数执行成功,则返回一个指向适配器信息的PADAPTER_INFO结构体指针;如果执行失败,则返回NULL值。
该函数的主要实现代码如下:
PADAPTER_INFO PacketFindAdInfo(PCHAR AdapterName)
{
PADAPTER_INFO TAdInfo;
……
TAdInfo=g_AdaptersInfoList;
/查找适配器AdapterName/
while(TAdInfo!=NULL)
{
if(strcmp(TAdInfo->Name,AdapterName)==0)
{
//成功找到适配器AdapterName
break;
}
TAdInfo=TAdInfo->Next;
}
……
return TAdInfo;
}
该函数需要返回一个PADAPTER_INF类型的指针,所以不需要获取g_AdaptersInfoMutex互斥信号,在PacketGetNetInfoEx函数中调用该函数时会提供互斥操作的保护。