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.2.2 Packet.dll库中获得网络适配器列表的实现 - 图1

图 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.2.2 Packet.dll库中获得网络适配器列表的实现 - 图2

图 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函数中调用该函数时会提供互斥操作的保护。