MS将于今年10月推出win10 RS3 RTM版,并要求OEM厂商的驱动程序必须支持新的D/C/H/U驱动框架(微软爸爸一声令下,苦了我们)。其中的"C"项要求过滤驱动的inf文件必须以扩展INF(Extension inf的概念在Win10上首次提出)的方式安装。经过一番痛苦的摸索,终于把公司大部分的过滤驱动的inf文件转为扩展INF。你以为我会写怎么支持DCHU?呵呵,参考今年WinHEC的资料吧~我写这篇文章是为了总结在支持Extension INF的过程中,观察Setupapi.dev.log日志得到的感悟。在开始之前,先概括的说一下安装设备的流程:首先由"Device installation Application"(设备安装程序)直接间接的调用SetupDiCallClassInstaller发出"DIF_CODE"(DIF码),DIF码依次经过"Class co-installer"/"Device co-installer",最后到达"Class installer",类安装器经过处理后,这个DIF码最终被"default handler"(系统默认的处理器)处理,这些默认处理器就是各种SetupDixxx API,他们处理由SetupDiCallClassInstaller发出的DIF码。整个处理流程简单的说就是SetupDiCallClassInstaller在设备安装的不同阶段会发出各种类型的DIF码,Pnp管理器查找注册表,找到对应的Co-Installer/Class Installer,并最终调用DIF码对应Default Handler进行操作。
可以把这个流程看做一种进程间的通信:设备安装器模块(进程A)为了安装设备,向系统设备安装模块(进程B,应该是Pnp管理器)发出请求,中间可能通过其他系统自带的/用户自定义的模块,各个模块通力合作最终完成安装。
1.Device Installation Application
MSDN上多次提到"Device Install application"(下文简称设备安装程序)这个单词,经过我反复琢磨,觉得这个单词就是指:设备管理器/devcon.exe/pnputil.exe/dpinst.exe等MS提供的设备安装/管理工具以及由OEM厂商针对自己的产品所提供的,调用SetupDixxx API安装功能驱动/过滤驱动的可执行程序。这些可执行程序的一个特点就是在安装过程中可能会调用SetupDiCallClassInstaller函数安装某个驱动包,以devcon.exe安装驱动为例:
int cmdInstall(__in LPCTSTR BaseName, __in LPCTSTR Machine, __in DWORD Flags, __in int argc, __in_ecount(argc) TCHAR* argv[])
{
HDEVINFO DeviceInfoSet = INVALID_HANDLE_VALUE;
SP_DEVINFO_DATA DeviceInfoData;
GUID ClassGUID;
TCHAR ClassName[MAX_CLASS_NAME_LEN];
...
if (!SetupDiGetINFClass(InfPath,&ClassGUID,ClassName,sizeof(ClassName)/sizeof(ClassName[0]),0));
DeviceInfoSet = SetupDiCreateDeviceInfoList(&ClassGUID,0);
DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
if (!SetupDiCreateDeviceInfo(DeviceInfoSet,
ClassName,
&ClassGUID,
NULL,
0,
DICD_GENERATE_ID,
&DeviceInfoData));
if(!SetupDiSetDeviceRegistryProperty(DeviceInfoSet,
&DeviceInfoData,
SPDRP_HARDWAREID,
(LPBYTE)hwIdList,
(lstrlen(hwIdList)+1+1)*sizeof(TCHAR)));
if (!SetupDiCallClassInstaller(DIF_REGISTERDEVICE,
DeviceInfoSet,
&DeviceInfoData));
...
在cmdInstall函数的结尾,程序以DIF_REGISTERDEVICE为参数调用SetupDiCallClassInstaller函数。从函数名隐约可以看出,设备安装程序在安装设备时,会调用ClassInstaller----类安装器。在文章的开头部分已经提过设备安装的流程,纵观整个流程,是DIF码把这些零散的组件联系在一起。当然了设备安装程序就是整个流程的源头,是它发出了向ClassInstaller发出了DIF码。
2.Co-Installer
值得注意的是它和下面即将提到的Class Installer合并到一起,以Installer的名字在MSDN中出现。初次阅读时,总会误以为Installer是第三方提供的设备安装程序(Device Installer Application)。
MS提倡使用系统提供的device setup class(设备安装类),因此,我们鲜有机会自己设计设备安装类(Toaster除外,它是个demo程序)。我们能做的是提供各种协安装器(这名字让我想到皇协军,性质也挺像----辅助安装设备驱动),它作为处理DIF码的第二个环节而存在。Co-Installer除了接受DIF码以外,还接收到来自SetupDiCallClassInstaller的其他参数:句柄DeviceInfoSet/DeviceInfoData等。可能是我浅薄,很少看到在Co-Installer中对SetupDiCallClassInstaller传入的参数进行修改。
3.Class Installer
MS提供的设备类安装器,位于处理DIF码流程中的第三个环节。按微软的说法,它收到DIF码后,进行文件拷贝/修改注册表/创建服务等操作。
4.Default DIF Code Handler
它位于整个流程的最后一步(其实还有call post-process),所有从SetupDiCallClassInstaller发出的DIF码都会在此调用系统定义的Default Handler进行处理。请不要对Handler这个词抱有恐惧感,其实它是一个个SetupDixxx函数。比如DIF_REMOVE码对应的Default Handler是SetupDiRemoveDevice。如果不明白这个函数的处理流程可以对照MSDN文档。
5.应用
上面说的,都可以在MSDN上找到,就总结而言,可能还不如<竹林蹊径>来的详细。为了使结尾升华,我们以在设备管理器中卸载WinDDK提供的Toast总线驱动root\BusEnum为例,看下各类DIF码的行为,内容摘自c:\windows\inf\setupapi.dev.log:
>>> [Device Uninstall (Device Manager) - ROOT\SYSTEM\0002]
>>> Section start 2017/08/24 21:49:01.644
cmd: "C:\Windows\system32\mmc.exe" /s C:\Windows\system32\compmgmt.msc
inf: {SetupUninstallOEMInf: oem44.inf}
inf: Driver Store location: C:\Windows\System32\DriverStore\FileRepository\bus.inf_x86_neutral_13a2bbb5fc11723b\bus.inf
sto: {Delete Driver Package: C:\Windows\System32\DriverStore\FileRepository\bus.inf_x86_neutral_13a2bbb5fc11723b\bus.inf} 21:49:01.693
sto: Deleting driver package from Driver Store:
sto: Driver Store = C:\Windows\System32\DriverStore (Online | 6.1.7601)
sto: Driver Package = C:\Windows\System32\DriverStore\FileRepository\bus.inf_x86_neutral_13a2bbb5fc11723b\bus.inf
sto: Flags = 0x00000000
pol: {Driver package policy check} 21:49:01.946
pol: {Driver package policy check - exit(0x00000000)} 21:49:01.948
sto: {Unstage Driver Package: C:\Windows\System32\DriverStore\FileRepository\bus.inf_x86_neutral_13a2bbb5fc11723b\bus.inf} 21:49:01.951
idb: Published INF 'oem44.inf' deleted
idb: Unpublished driver store entry 'bus.inf_x86_neutral_13a2bbb5fc11723b'.
sto: Published driver package INF 'oem44.inf' was deleted.
idb: Unregistered driver store entry 'bus.inf_x86_neutral_13a2bbb5fc11723b'.
sto: {Delete Directory: C:\Windows\System32\DriverStore\FileRepository\bus.inf_x86_neutral_13a2bbb5fc11723b} 21:49:02.161
sto: {Delete Directory: exit(0x00000000)} 21:49:02.170
sto: {Unstage Driver Package: exit(0x00000000)} 21:49:02.179
sto: Deleted driver package from Driver Store. Time = 484 ms
sto: {Delete Driver Package: exit(0x00000000)} 21:49:02.192
inf: Uninstalling catalog: C:\Windows\INF\oem44.CAT
inf: {SetupUninstallOEMInf exit (0x00000000)}
dmg: Successfully uninstalled oem44.inf.
dvi: {DIF_REMOVE} 21:49:02.333
dvi: No class installer for 'Toaster Bus Enumerator'
dvi: Using exported function 'CriticalDeviceCoInstaller' in module 'C:\Windows\system32\SysClass.Dll'.
dvi: CoInstaller 1 == SysClass.Dll,CriticalDeviceCoInstaller
dvi: CoInstaller 1: Enter 21:49:02.344
dvi: CoInstaller 1: Exit
dvi: Default installer: Enter 21:49:02.349
dvi: {Remove DEVICE}
dvi: InstanceID = 'ROOT\SYSTEM\0002'
dvi: Devnode Status = 0x0180200b
dvi: CM_Query_And_Remove_Subtree_Ex returns 0x00000000
dvi: Devnode Status after CM_Query_And_Remove_Subtree_Ex = 0x01802401
dvi: Query-and-Remove succeeded
dvi: {Delete DEVICE}
dvi: Device Instance uninstalled.
dvi: {Delete DEVICE exit (0x00000000)}
dvi: {Remove DEVICE exit (0x00000000)}
dvi: Default installer: Exit
dvi: {DIF_REMOVE - exit(0x00000000)} 21:49:05.749
<<< Section end 2017/08/24 21:49:05.765
<<< [Exit status: SUCCESS]
在>>>>和<<<<之间的是日志体,其中"inf:"指inf文件操作;"sto:"指driver store操作;"dvi:"指设备安装操作。在上面的日志中,我们可以看到字段"{DIF_REMOVE}",这是由设备管理器发出的DIF码;为了处理这个DIF码,Pnp管理器搜索类安装器并打印:"No class installer for 'Toaster Bus Enumerator';搜索协安装器:SysClass.dll;并发送Query_Remove请求删除设备,这段和MSDN上的描述很相似:
DIF_REMOVE:
Default DIF Code Handler:
SetupDiRemoveDevice
Installer Operation:
...
Installers should not delete files when handling this DIF request, in case the files are in use by another device.
Windows sends this DIF request before it initiates PnP query-remove and remove processing.
最后删除设备{Delete DEVICE},而删除设备的操作又和SetupDiRemoveDevice的描述很像:
SetupDiRemoveDevice:
SetupDiRemoveDevice removes the device from the system.
It deletes the device's hardware and software registry keys and any hardware-profile-specific registry keys (configuration-specific registry keys).