5.1.2 DriverEntry函数的具体实现
DriverEntry函数的实现步骤如图5-3所示。
图 5-3 DriverEntry函数的实现步骤
此函数首先会分配一个NDIS_PROTOCOL_CHARACTERISTICS的结构体;然后执行初始化置零操作;其次用协议数据(版本、名称等)与驱动程序所需的回调函数地址设置该结构体;接着调用NdisRegisterProtocol函数把NPF注册为一个NDIS协议驱动程序;再设置驱动程序的卸载函数和IRP派遣函数,获得适配器信息,并给每个网络适配器创建一个设备对象。
DriverEntry函数的主要代码如下:
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath)
{
NDIS_PROTOCOL_CHARACTERISTICS ProtocolChar;
……
/根据操作系统的版本,定义跳过环回数据包的正确标识/
//获得操作系统的版本
PsGetVersion(&OsMajorVersion,&OsMinorVersion,NULL,NULL);
//根据不同的操作系统,定义跳过环回数据包的正确标识
if((OsMajorVersion==5)&&(OsMinorVersion==0))
{//Windows 2000需要NDIS_FLAGS_DONT_LOOPBACK与
//NDIS_FLAGS_SKIP_LOOPBACK两个标识
g_SendPacketFlags=NDIS_FLAGS_DONT_LOOPBACK|
NDIS_FLAGS_SKIP_LOOPBACK_W2K;
}
else
{//Windows XP、Windows 2003与后续的操作系统
//只需要NDIS_FLAGS_DONT_LOOPBACK标识
g_SendPacketFlags=NDIS_FLAGS_DONT_LOOPBACK;
}
/初始化设备名称的前缀/
NdisInitUnicodeString(&g_NPF_Prefx,g_NPF_PrefxBuffer);
/获得CPU的个数/
g_NCpu=NdisSystemProcessorCount();
/ProtocolChar结构体初始化置零/
RtlZeroMemory(&ProtocolChar,sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
/向NDIS库注册协议驱动程序/
//用协议数据(版本、名称等)与回调函数地址设置ProtocolChar
ifdef NDIS50
ProtocolChar.MajorNdisVersion=5;
else
ProtocolChar.MajorNdisVersion=3;
endif
ProtocolChar.MinorNdisVersion=0;
ProtocolChar.Reserved=0;
ProtocolChar.OpenAdapterCompleteHandler=NPF_OpenAdapterComplete;
ProtocolChar.CloseAdapterCompleteHandler=NPF_CloseAdapterComplete;
ProtocolChar.SendCompleteHandler=NPF_SendComplete;
ProtocolChar.TransferDataCompleteHandler=NPF_TransferDataComplete;
ProtocolChar.ResetCompleteHandler=NPF_ResetComplete;
ProtocolChar.RequestCompleteHandler=NPF_RequestComplete;
ProtocolChar.ReceiveHandler=NPF_tap;
ProtocolChar.ReceiveCompleteHandler=NPF_ReceiveComplete;
ProtocolChar.StatusHandler=NPF_Status;
ProtocolChar.StatusCompleteHandler=NPF_StatusComplete;
ifdef NDIS50
ProtocolChar.BindAdapterHandler=NPF_BindAdapter;
ProtocolChar.UnbindAdapterHandler=NPF_UnbindAdapter;
ProtocolChar.PnPEventHandler=NPF_PowerChange;
ProtocolChar.ReceivePacketHandler=NULL;
endif
ProtocolChar.Name=ProtoName;
//把NPF注册为一个NDIS协议驱动程序
NdisRegisterProtocol(
&Status,
&g_NdisProtocolHandle,
&ProtocolChar,
sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
if(Status!=NDIS_STATUS_SUCCESS)
{
//注册失败,函数返回
return Status;
}
/设置IRP派遣函数和卸载函数/
DriverObject->MajorFunction[IRP_MJ_CREATE]=NPF_Open;
DriverObject->MajorFunction[IRP_MJ_CLOSE]=NPF_Close;
DriverObject->MajorFunction[IRP_MJ_CLEANUP]=NPF_Cleanup;
DriverObject->MajorFunction[IRP_MJ_READ]=NPF_Read;
DriverObject->MajorFunction[IRP_MJ_WRITE]=NPF_Write;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=NPF_IoControl;
DriverObject->DriverUnload=NPF_Unload;//卸载函数
/获取系统中可用网络适配器的信息/
bindP=getAdaptersList();
if(bindP==NULL)
{//没有找到适配器,试图获取TCP/IP绑定的适配器
tcpBindingsP=getTcpBindings();
if(tcpBindingsP==NULL)
{//没找到TCP/IP绑定的适配器,函数退出
goto RegistryError;
}
bindP=(WCHAR*)tcpBindingsP;
bindT=(WCHAR*)(tcpBindingsP->Data);
}
else
{
bindT=bindP;
}
/给每个网络适配器创建一个设备对象/
for(;*bindT!=UNICODE_NULL;
bindT+=
(macName.Length+sizeof(UNICODE_NULL))/sizeof(WCHAR))
{
RtlInitUnicodeString(&macName,bindT);
//给一个适配器创建一个设备对象
NPF_CreateDevice(DriverObject,&macName);
}
return STATUS_SUCCESS;
/处理函数错误/
RegistryError:
NdisDeregisterProtocol(&Status,g_NdisProtocolHandle);
Status=STATUS_UNSUCCESSFUL;
return(Status);
}
驱动程序在DriverEntry函数中,通过调用NdisRegisterProtocol函数向NDIS库注册ProtocolXXX函数,NdisRegisterProtocol函数的原型如下:
VOID NdisRegisterProtocol(
OUT PNDIS_STATUS Status,
OUT PNDIS_HANDLE NdisProtocolHandler,
IN NDIS_PROTOCOL_CHARACTERISTICS ProtocolCharacteristics,
IN UINT CharacteristicsLength
);
上述函数中,参数Status是一个为指向函数调用者而提供的状态变量指针,该函数可以返回下列值:
❑NDIS_STATUS_SUCCESS:NDIS库把调用者注册为一个协议驱动程序。
❑NDIS_STATUS_BAD_CHARACTERISTICS:对于参数ProtocolCharacteristics中MajorNdisVersion所描述的版本而言CharacteristicsLength太短。
❑NDIS_STATUS_BAD_VERSION:参数ProtocolCharacteristics中MajorNdisVersion描述的版本不可用。
❑NDIS_STATUS_RESOURCES:资源不够,原因可能是内存不够,从而阻止NDIS库对调用者进行注册。
参数NdisProtocolHandler指向调用者提供的一个变量,函数将通过它返回一个描述已注册驱动的句柄。
参数ProtocolCharacteristics指向一个NDIS_PROTOCOL_CHARACTERISTICS结构体,由调用者设置。
参数CharacteristicsLength描述ProtocolCharacteristics所指结构体的大小。
函数返回句柄NdisProtocolHandler对协议驱动程序是透明的,协议驱动程序必须保存该句柄,并在将来对NDIS库函数的调用中将其作为输入参数传递。例如,打开底层适配器(调用NdisOpenAdapter函数)时就得使用该句柄。
在调用NdisRegisterProtocol之前,DriverEntry必须完成以下操作:
❑对NDIS_PROTOCOL_CHARACTERISTICS结构体进行初始化置零。可调用NdisZeroMemory函数实现置零。这将确保那些可选的且不使用的成员被设置为NULL。如果没置零,在调用NdisRegisterProtocol之前,任何不使用的成员必须置为NULL。
❑在NDIS_PROTOCOL_CHARACTERISTICS结构体中指定协议兼容的NDIS版本。
❑在NDIS_PROTOCOL_CHARACTERISTICS结构体中设置协议驱动程序所导出的各ProtocolXXX回调函数指针的地址。
下面为NPF中注册协议驱动程序对应的代码:
/声明NDIS_PROTOCOL_CHARACTERISTICS结构的实例ProtocolChar/
NDIS_PROTOCOL_CHARACTERISTICS ProtocolChar;
/协议名称为PacketDriver/
NDIS_STRING ProtoName=NDIS_STRING_CONST("PacketDriver");
/对ProtocolChar的内存空间清零/
RtlZeroMemory(&ProtocolChar,sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
/用协议数据(版本、名称等)与回调函数地址设置ProtocolChar/
ifdef NDIS50
ProtocolChar.MajorNdisVersion=5;
else
ProtocolChar.MajorNdisVersion=3;
endif
ProtocolChar.MinorNdisVersion=0;
ProtocolChar.Reserved=0;
ProtocolChar.OpenAdapterCompleteHandler=NPF_OpenAdapterComplete;
……
ProtocolChar.Name=ProtoName;
/把NPF注册为一个NDIS协议驱动程序/
NdisRegisterProtocol(
&Status,
&g_NdisProtocolHandle,
&ProtocolChar,
sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
if(Status!=NDIS_STATUS_SUCCESS)
{
//注册协议驱动程序到NDIS库失败,程序结束
return Status;
}
一般会在DriverEntry函数中对驱动对象设置卸载函数和IRP派遣函数。设备对象中的MajorFunction成员是一个函数指针数组,IRP_MJ_CREATE、IRP_MJ_CLOSE、IRP_MJ_WRITE等代表数组的第几个元素。下列内容为DriverEntry函数中的具体设置。
/设置IRP派遣函数/
DriverObject->MajorFunction[IRP_MJ_CREATE]=NPF_Open;
DriverObject->MajorFunction[IRP_MJ_CLOSE]=NPF_Close;
DriverObject->MajorFunction[IRP_MJ_CLEANUP]=NPF_Cleanup;DriverObject->MajorFunction[IRP_MJ_READ]=NPF_Read;
DriverObject->MajorFunction[IRP_MJ_WRITE]=NPF_Write;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=NPF_IoControl;
/设置卸载函数/
DriverObject->DriverUnload=NPF_Unload;
getAdaptersList函数用于获取系统中可用网络适配器的信息,若该函数操作成功,则返回一个包含系统中可用网络适配器列表的字符串。如果getAdaptersList函数操作失败,NPF会再调用getTcpBindings函数试图获得TCP/IP协议所绑定的设备信息。
下面为DriverEntry函数中的相关代码。
/获得适配器信息/
bindP=getAdaptersList();
if(bindP==NULL)
{
//在注册表中没找到适配器,试图获得TCP/IP协议所绑定的适配器
tcpBindingsP=getTcpBindings();
if(tcpBindingsP==NULL)
{
//没有找到TCP/IP协议所绑定的适配器,程序执行错误处理
goto RegistryError;
}
bindP=(WCHAR*)tcpBindingsP;
bindT=(WCHAR*)(tcpBindingsP->Data);
}
else
{
bindT=bindP;
}
/给每个网络适配器创建一个设备对象/
for(;*bindT!=UNICODE_NULL;bindT+=
(macName.Length+sizeof(UNICODE_NULL))/sizeof(WCHAR))
{
RtlInitUnicodeString(&macName,bindT);
//给网络适配器创建一个设备对象
NPF_CreateDevice(DriverObject,&macName);
}
NPF_CreateDevice函数会为一个可用的网络适配器创建一个设备对象。
下面详细介绍DriverEntry函数中调用的一些重要函数的实现。
1.getAdaptersList函数
getAdaptersList函数用于返回系统中可用网络适配器的列表,其原型如下:
PWCHAR getAdaptersList(VOID);
此函数会返回一个包含网络适配器列表的字符串,该适配器列表是从注册表SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}的注册项中获取的。获取过程为:getAdaptersList函数首先遍历该注册表项,获取子项信息,再打开子项的"Linkage"子项,在"Linkage"子项下查找"Export"键名的键值(例如在图5-4中,键值为"\Device\NdisWanIp"),并把对应的键值存储到内存中,形成一个列表,最后函数返回该列表。NPF试图从这个列表创建它所绑定的网络适配器。通过这种方式,可以动态地加载或卸载设备对象,而不用通过控制面板操作。
图 5-4 注册表项0006\Linkage
getAdaptersList函数的主要实现步骤如图5-5所示。
图 5-5 getAdaptersList函数的主要实现步骤
getAdaptersList函数的主要实现代码如下:
PWCHAR getAdaptersList(void)
{
……
/分配DeviceNames所需的内存空间/
PWCHAR DeviceNames=
(PWCHAR)ExAllocatePoolWithTag(PagedPool,BufLen,'0PWA');
if(DeviceNames==NULL)
{
//分配失败,函数返回
return NULL;
}
/查找注册表,形成返回列表/
//设置一个OBJECT_ATTRIBUTES类型的参数objAttrs,为了便于后续调用,
//其中NDIS_STRING AdapterListKey=
//NDIS_STRING_CONST("\Registry\Machine\System\
//CurrentControlSet\Control\Class\
//{4D36E972-E325-11CE-BFC1-08002BE10318}");
InitializeObjectAttributes(&objAttrs,&AdapterListKey,
OBJ_CASE_INSENSITIVE,NULL,NULL);
//打开注册表表项,返回objAttrs所描述的注册表表项的句柄
status=ZwOpenKey(&keyHandle,KEY_READ,&objAttrs);
if(!NT_SUCCESS(status)){
//打开失败
}
else{//打开成功
……
//获取一个已打开注册表项子项的信息
while((status=ZwEnumerateKey(keyHandle,i,
KeyBasicInformation,AdapInfo,sizeof(AdapInfo),
&resultLength))==STATUS_SUCCESS)
{
WCHAR ExportKeyName[512];
//所打开的注册表项
PWCHAR ExportKeyPrefx=
L"\Registry\Machine\System\
CurrentControlSet\Control\Class\
{4D36E972-E325-11CE-BFC1-08002BE10318}\";
UINT ExportKeyPrefxSize=
sizeof(L"\Registry\Machine\System\
CurrentControlSet\Control\Class\
{4D36E972-E325-11CE-BFC1-08002BE10318}");
//需要打开的子项"Linkage"
PWCHAR LinkageKeyPrefx=L"\Linkage";
UINT LinkageKeyPrefxSize=sizeof(L"\Linkage");
//所查找的键名为"Export"
NDIS_STRING FinalExportKey=NDIS_STRING_CONST("Export");
PKEY_BASIC_INFORMATION tInfo=(PKEY_BASIC_INFORMATION)AdapInfo;
UNICODE_STRING AdapterKeyName;
HANDLE ExportKeyHandle;
//合成要打开的注册表子项,此处以图5-4所示为例
//“\Registry\Machine\System\CurrentControlSet
//\Control\Class\
//{4D36E972-E325-11CE-BFC1-08002BE10318}
//\0006\Linkage”
RtlCopyMemory(ExportKeyName,
ExportKeyPrefx,ExportKeyPrefxSize);
RtlCopyMemory((PCHAR)ExportKeyName+ExportKeyPrefxSize,
tInfo->Name,tInfo->NameLength+2);
RtlCopyMemory((PCHAR)ExportKeyName+
ExportKeyPrefxSize+tInfo->NameLength,
LinkageKeyPrefx,LinkageKeyPrefxSize);
//把合成的注册表子项赋给一个Unicode字符串
RtlInitUnicodeString(&AdapterKeyName,ExportKeyName);
//设置参数objAttrs,便于后续调用
InitializeObjectAttributes(&objAttrs,
&AdapterKeyName,OBJ_CASE_INSENSITIVE,NULL,NULL);
//打开注册表表项,返回objAttrs中所描述的注册表表项的句柄
status=ZwOpenKey(&ExportKeyHandle,KEY_READ,&objAttrs);
if(!NT_SUCCESS(status))
{//打开失败,跳出本次循环
continue;
}
//查找"Export"键名的键值信息
status=ZwQueryValueKey(ExportKeyHandle,
&FinalExportKey,KeyValuePartialInformation,
&valueInfo,sizeof(valueInfo),&resultLength);
if(!NT_SUCCESS(status)&&(status!=STATUS_BUFFER_OVERFLOW))
{
//查询失败
}
else{//查询成功
//计算所需的内存大小
ULONG valueInfoLength=
valueInfo.DataLength+
FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION,
Data[0]);
//分配内存,用于查询
PKEY_VALUE_PARTIAL_INFORMATION valueInfoP=
(PKEY_VALUE_PARTIAL_INFORMATION)
ExAllocatePoolWithTag(PagedPool,
valueInfoLength,'1PWA');
if(valueInfoP!=NULL)
{
status=ZwQueryValueKey(ExportKeyHandle,
&FinalExportKey,
KeyValuePartialInformation,
valueInfoP,
valueInfoLength,&resultLength);
if(!NT_SUCCESS(status)){
//查询失败
}
else{//查询成功
if(BufPos+valueInfoP->DataLength>BufLen)
{//如果DeviceNames内存空间不够,成倍增长空间
PWCHAR DeviceNames2=
(PWCHAR)ExAllocatePoolWithTag
(PagedPool,BufLen<<1,'0PWA');
if(DeviceNames2)
{//分配成功,数据转存
RtlCopyMemory((PCHAR)DeviceNames2,
(PCHAR)DeviceNames,BufLen);
BufLen<<=1;//内存空间翻倍
ExFreePool(DeviceNames);
DeviceNames=DeviceNames2;
}
}
if(BufPos+valueInfoP->DataLength<
BufLen)
{//复制Export键名的键值到DeviceNames中
RtlCopyMemory(
(PCHAR)DeviceNames+BufPos,
valueInfoP->Data,
valueInfoP->DataLength);
BufPos+=valueInfoP->DataLength-2;
}
}
ExFreePool(valueInfoP);
}
else{
//用于查询的内存分配失败
}
}//一次查找Export键名的键值信息结束,设置结束符
DeviceNames[BufPos/2]=0;
DeviceNames[BufPos/2+1]=0;
//关闭注册表子项
ZwClose(ExportKeyHandle);
i++;
}//结束遍历设备链表的while语句
//关闭注册表项
ZwClose(keyHandle);
}
/设置函数返回内容/
if(BufPos==0)
{
ExFreePool(DeviceNames);
return NULL;
}
return DeviceNames;
}
2.getTcpBindings函数
如果getAdaptersList函数执行失败,NPF就会试图通过getTcpBindings函数获得与TCP/IP协议绑定的网络适配器名称。getTcpBindings函数的原型如下:
PKEY_VALUE_PARTIAL_INFORMATION getTcpBindings(VOID);
函数查询注册表项HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Linkage下的Bind键名,返回指向该键名键值的指针,该注册表键值包含TCP/IP协议所绑定的适配器名称。对注册表的操作过程如图5-6与图5-7所示。
图 5-6 注册表项Tcpip\Linkage
图 5-7 Bind键名的键值
getTcpBindings函数的主要实现代码如下:
PKEY_VALUE_PARTIAL_INFORMATION getTcpBindings(void)
{
……
/查找注册表,获得适配器名称/
//设置一个OBJECT_ATTRIBUTES类型的参数objAttrs,便于后续调用
//其中NDIS_STRING tcpLinkageKeyName=
//NDIS_STRING_CONST(\Registry\Machine\System
//L"\CurrentControlSet\Services\Tcpip\Linkage");
InitializeObjectAttributes(&objAttrs,&tcpLinkageKeyName,
OBJ_CASE_INSENSITIVE,NULL,NULL);
/打开注册表表项,返回objAttrs中所描述的注册表表项的句柄/
status=ZwOpenKey(&keyHandle,KEY_READ,&objAttrs);
if(!NT_SUCCESS(status))
{//打开失败
}
else
{//成功打开
ULONG resultLength;
KEY_VALUE_PARTIAL_INFORMATION valueInfo;
//对注册表表项进行查询,其中bindValueName的定义为
//NDIS_STRING bindValueName=NDIS_STRING_CONST("Bind");
status=ZwQueryValueKey(keyHandle,&bindValueName,
KeyValuePartialInformation,&valueInfo,
sizeof(valueInfo),&resultLength);
if(!NT_SUCCESS(status)&&(status!=STATUS_BUFFER_OVERFLOW))
{//查询失败
}
else
{//计算所需的内存大小,并分配内存,用于查询
ULONG valueInfoLength=valueInfo.DataLength+
FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION,Data[0]);
PKEY_VALUE_PARTIAL_INFORMATION valueInfoP=
(PKEY_VALUE_PARTIAL_INFORMATION)
ExAllocatePoolWithTag(PagedPool,valueInfoLength,'2PWA');
if(valueInfoP!=NULL)
{
//对注册表表项进行查询,获取键名为Bind的键值信息
status=ZwQueryValueKey(
keyHandle,&bindValueName,
KeyValuePartialInformation,
valueInfoP,
valueInfoLength,&resultLength);
if(!NT_SUCCESS(status))
{//查询失败
ExFreePool(valueInfoP);
}
else
{
if(valueInfoLength!=resultLength)
{//失败,查询结果的长度前后不一致
ExFreePool(valueInfoP);
}
else
{
if(valueInfoP->Type!=REG_MULTI_SZ)
{//失败,键名Bind的键值类型不是REG_MULTI_SZ
ExFreePool(valueInfoP);
}
else
{//所有的操作正确
result=valueInfoP;
}
}
}
}
}
//关闭注册表
ZwClose(keyHandle);
}
/返回查询的数据/
return result;
}
3.NPF_CreateDevice函数
NPF_CreateDevice函数主要是为给定的网络设备创建一个设备对象,其原型如下:
BOOLEAN NPF_CreateDevice(IN OUT PDRIVER_OBJECT adriverObjectP,
IN PUNICODE_STRING amacNameP);
上述函数中,参数adriverObjectP是用来与设备相关联的驱动对象,比如NPF的一个驱动对象实例;参数amacNameP是网络适配器的名称,NPF_CreateDevice函数为该网络适配器创建设备对象。
如果NPF_CreateDevice函数执行成功,则返回非0值。
NPF对每个可用的网络适配器只创建一个设备对象,该设备对象指向该NPF驱动对象,但仍然包含关于原始设备的信息。通过这种方式,当用户打开新设备对象时,NPF能够确定使用正确的网络适配器。NPF_CreateDevice函数的主要实现步骤如图5-8所示。
图 5-8 NPF_CreateDevice函数的主要步骤
NPF_CreateDevice函数的主要实现代码如下:
BOOLEAN NPF_CreateDevice(IN OUT PDRIVER_OBJECT adriverObjectP,
IN PUNICODE_STRING amacNameP)
{
……
/检查amacNameP->Buffer是否包含合法的"\Device\"子字符串/
if(RtlCompareMemory(amacNameP->Buffer,
devicePrefx.Buffer,devicePrefx.Length)<
devicePrefx.Length)
{
return FALSE;
}
/分配设置设备对象名称的内存空间/
deviceName.Length=0;
deviceName.MaximumLength=(USHORT)(amacNameP->Length+
g_NPF_Prefx.Length+sizeof(UNICODE_NULL));
deviceName.Buffer=ExAllocatePoolWithTag(PagedPool,
deviceName.MaximumLength,'3PWA');
if(deviceName.Buffer==NULL)
return FALSE;
/分配设置用户可见设备名称的内存空间/
deviceSymLink.Length=0;
deviceSymLink.MaximumLength=
(USHORT)(amacNameP->Length-devicePrefx.Length
+symbolicLinkPrefx.Length+g_NPF_Prefx.Length
+sizeof(UNICODE_NULL));
deviceSymLink.Buffer=ExAllocatePoolWithTag(NonPagedPool,
deviceSymLink.MaximumLength,'3PWA');
if(deviceSymLink.Buffer==NULL)
{
ExFreePool(deviceName.Buffer);
return FALSE;
}
/生成设置设备对象的名称/
RtlAppendUnicodeStringToString(&deviceName,&devicePrefx);
RtlAppendUnicodeStringToString(&deviceName,&g_NPF_Prefx);
RtlAppendUnicodeToString(&deviceName,amacNameP->Buffer+
devicePrefx.Length/sizeof(WCHAR));
/生成用户可见的设备名称/
RtlAppendUnicodeStringToString(&deviceSymLink,&symbolicLinkPrefx);
RtlAppendUnicodeStringToString(&deviceSymLink,&g_NPF_Prefx);
RtlAppendUnicodeToString(&deviceSymLink,
amacNameP->Buffer+devicePrefx.Length/sizeof(WCHAR));
/为驱动程序的使用者分配内存,并初始化一个设备对象/
status=IoCreateDevice(adriverObjectP,
sizeof(DEVICE_EXTENSION),
&deviceName,
FILE_DEVICE_TRANSPORT,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&devObjP);
/在一个设备对象名称与一个用户可见的名称之间建立一个符号连接/
if(NT_SUCCESS(status))
{
PDEVICE_EXTENSION devExtP=
(PDEVICE_EXTENSION)devObjP->DeviceExtension;
devObjP->Flags|=DO_DIRECT_IO;
//设置适配器的名称
RtlInitUnicodeString(&devExtP->AdapterName,
amacNameP->Buffer);
//在一个设备对象名称与一个用户可见的名称之间建立一个符号连接
if(IoCreateSymbolicLink(&deviceSymLink,&deviceName)!=
STATUS_SUCCESS)
{//创建连接失败,函数返回
ExFreePool(deviceName.Buffer);
ExFreePool(deviceSymLink.Buffer);
devExtP->ExportString=NULL;
return FALSE;
}
//设置应用程序可见的设备名称
devExtP->ExportString=deviceSymLink.Buffer;
ExFreePool(deviceName.Buffer);
return TRUE;
}
else
{//IoCreateDevice调用失败,函数返回
ExFreePool(deviceName.Buffer);
ExFreePool(deviceSymLink.Buffer);
return FALSE;
}
}
上述代码中,NPF_CreateDevice函数主要使用IoCreateDevice与IoCreateSymbolicLink函数实现,下面分别简要介绍这两个函数。
IoCreateDevice函数为驱动程序的使用者分配内存,并初始化一个设备对象,其原型如下:
NTSTATUS IoCreateDevice(
IN PDRIVER_OBJECT DriverObject,
IN ULONG DeviceExtensionSize,
IN PUNICODE_STRING DeviceName OPTIONAL,
IN DEVICE_TYPE DeviceType,
IN ULONG DeviceCharacteristics,
IN BOOLEAN Exclusive,
OUT PDEVICE_OBJECT*DeviceObject
);
在上述函数中,参数DriverObject为指向驱动对象的指针。每个驱动程序有唯一的驱动对象与之相对应,但每个驱动对象会有若干个设备对象。
参数DeviceExtensionSize为输入参数,它指定设备扩展的大小,I/O管理器会根据这个大小在内存中创建设备对象的设备扩展。驱动程序使用该设备扩展来维护DeviceObject所描述设备的I/O操作的上下文区块。设备扩展的内部结构由驱动程序自定义。
参数DeviceName为可选的输入参数,用于设置设备对象的名称,使其指向一个以0结尾的Unicode字符串,同时字符串必须为完整的路径。可利用该字符串对设备对象进行命名。
参数DeviceType为输入参数,描述一个系统定义的FILE_DEVICE_XXX常量,它表示设备的类型(如FILE_DEVICE_DISK、FILE_DEVICE_KEYBOARD等)或一个厂商定义的新类型设备。NPF属于网络传输设备类型,所以设置为FILE_DEVICE_TRANSPORT值。
参数DeviceCharacteristics为输入参数,它描述一个或多个系统定义的常量,各常量可进行位或操作,该参数提供了有关驱动设备的额外信息。如果设备没有任何相关的特性,则设为0。NPF中设置的值为FILE_DEVICE_SECURE_OPEN。
参数Exclusive为输入参数,指示设备对象是否表现为一个互斥设备。也就是说,在同一时刻只有一个句柄能发送I/O请求,同一进程的多线程能够通过一个单独的句柄发送请求。
参数DeviceObject为输出参数,如果函数调用成功,则指向新创建的设备对象。一个设备对象代表驱动程序所支持的一个物理的、虚拟的或逻辑的设备。
在NPF中IoCreateDevice函数返回STATUS_SUCCESS则认为函数调用成功,此时函数会创建一个设备对象并返回一个指向该对象的指针,否则为失败。当不再需要该对象时,调用者负责调用IoDeleteDevice函数释放该对象。NPF在卸载驱动函数NPF_Unload中释放生成的设备对象。
即使指定了设备名称,也只能被内核模式下的其他驱动程序所识别,在用户模式下的应用程序则无法识别这个设备。可以通过符号连接找到该设备,使得用户模式下的应用程序能识别该设备。符号连接可以理解为为设备对象起的一个别名。别名可以被用户模式下的应用程序识别。可采用IoCreateSymbolicLink函数在一个设备对象名称与一个用户可见的名称之间创建一个符号连接,其原型如下:
NTSTATUS IoCreateSymbolicLink(
IN PUNICODE_STRING SymbolicLinkName,
IN PUNICODE_STRING DeviceName
);
上述函数中,参数SymbolicLinkName为输入参数,指向一个Unicode字符串,该字符串为用户可见的名称;参数DeviceName也为输入参数,也指向一个Unicode字符串,但该字符串为驱动程序所创建设备对象的名称。
如果符号连接创建成功,IoCreateSymbolicLink函数返回STATUS_SUCCESS。当不再需要该符号连接时,可调用IoDeleteSymbolicLink函数删除该符号连接,NPF在函数NPF_Unload中调用该函数删除所建立的符号连接。