天天看点

SetupDiCallClassInstaller处理流程

    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).      

继续阅读