編寫協同安裝程序
協同安裝程序是微軟公司開發的win32的DLL,它是用來幫助在Windows 2000系統上進行設備安裝。它被Setup API調用作為類安裝程序的“助手”。例如,供應商可以提供協同安裝程序將特定設備信息寫入INF文件無法處理的注冊表中。
本章內容:
l 4.1 協同安裝程序總覽
l 4.2 協同安裝程序界面
l 4.3 協同安裝程序操作
l 4.4 注冊協同安裝程序
1.1 協同安裝程序總覽
由Setup API調用的協同安裝程序如圖4-1所示。
圖4-1 協同安裝程序在設備安裝中的分工
帶有陰影的方框表示可由IHV和OEM提供的組件,其他組件則由OS提供。參見第1章“設備安裝總覽”可以了解更多的有關安裝組件的信息。
協同安裝程序可以是設備專用或類專用的。Setup API只在安裝協同安裝程序為其注冊的設備時才調用一個設備專用的協同安裝程序。操作系統(OS)及供應商可以為一個設備注冊零個或多個設備專用的協同安裝程序。在為協同安裝程序注冊安裝設備設置類的任何設備時,Setup API調用類協同安裝程序。操作系統及供應商可以為一個設備設置類注冊一個或多個類協同安裝程序。除此之外,類協同安裝程序還可以為一個或多個設置類注冊。
GUI模式設置、新設備DLL以及定制設置應用程序通過調用帶有設備安裝函數代碼(DIF代碼)的SetupDiCallClassInstaller來安裝設備。對于每一個DIF請求,SetupDiCallClassInstaller調用為設備設置類注冊的任何類協同安裝程序,調用為特定設備注冊的任何設備協同安裝程序,以及由系統提供用于設備設置類的類安裝程序(如果有的話)。
定制設置應用程序須調用SetupDiCallClassInstaller而不是直接調用協同安裝程序或類安裝程序。這個函數可以保證所有注冊的協同安裝程序都能被正確調用。
類協同安裝程序一般都在設備安裝之前注冊,而設備專用的協同安裝程序則是作為設備安裝的一部分被注冊的。因此類協同安裝程序總是在第一次構建時就被添加到協同安裝程序列表之中,并在設備安裝時被所有DIF請求調用。特定設備協同安裝程序則是在為該設備完成DIF_REGISTER_COINSTALLERS請求之后被添加到重安裝列表之中的(或是在調用了SetupDiRegisterCoDeviceInstallers之后)。特定設備協同安裝程序并不參與以下DIF請求:DIF_ALLOWINSTALL、DIF_INSTALLDEVICEFILES及DIF_SELECTBESTCOMPATDRV。
如果協同安裝程序需要響應以下任何一個DIF請求,它就必須是一個類協同安裝程序(而不是設備專用協同安裝程序):
l DIF_FIRSTTIMESETUP,DIF_DETECT*
l DIF_NEWDEVICEWIZARD_PRESELECT
l DIF_NEWDEVICEWIZARD_SELECT
l DIF_NEWDEVICEWIZARD_PREANALYZE
l DIF_NEWDEVICEWIZARD_POSTANALYZE
設備協同安裝程序并不適用于這樣的上下文,這是因為并沒有標識出某個特定的設備,或是因為在安裝的這個初始階段還沒有注冊過設備安裝程序。
圖4-2顯示了在注冊了任意一個特定設備的協同安裝程序之后,SetupDiCallClassInstaller調用協同安裝程序及類安裝程序的順序。
圖4-2 為DIF請求調用協同安裝程序的處理及后處理示例
在圖4-2所演示的示例中,為該設備的設置類注冊了兩個類協同安裝程序以及一個特定設備協同安裝程序。以下步驟對應于圖4-2中的帶圓圈的數字標號:
1. SetupDiCallClassInstaller調用第一個類協同安裝程序,同時指定一個表示安裝請求正在處理中的DIF代碼(在本例中是DIF_INSTALLDEVICE)。協同安裝程序在安裝請求中有參與的選擇權。本例中,第一個注冊的類協同安裝程序返回NO_ERROR。
2. 接下來,SetupDiCallClassInstaller調用任意額外注冊的類協同安裝程序。在本例中,第二個類安裝程序返回了ERROR_DI_POSTPROCESSING_REQUIRED,它要求在后處理之后再調用協同安裝程序。
3. SetupDiCallClassInstaller調用任意注冊的設備專用協同安裝程序。
4. 在調用了所有的注冊過的協同安裝程序后,如果設備的設置類有一個系統提供的類安裝程序,SetupDiCallClassInstaller就調用它。在本例中,類安裝程序返回ERROR_DI_DO_DEFAULT,這是類安裝程序的一個典型返回值。
5. 如果有一個缺省的設備處理驅動程序,SetupDiCallClassInstaller就為安裝請求調用它。DIF_INSTALLDEVICE有一個缺省的設備處理驅動程序SetupDiInstallDevice,它是Setup API的一部分。
6. SetupDiCallClassInstaller調用任何要求后處理的協同安裝程序。在本例中,第二個類協同安裝程序要求了后處理。
除了協同安裝程序在它的單個入口點被再一次調用外,協同安裝程序的后處理與驅動程序的IoCompletion例程相似。當SetupDiCallClassInstaller為后處理調用協同安裝程序時,它將PostProcessing設為TRUE,并將InstallResult設為Context參數中的恰當值。在本例中,Context.InstallResult是NO_ERROR,這是因為成功地執行了缺省的設備處理驅動程序。
在后處理中,SetupDicallClassInstaller反向調用了協同安裝程序。如果圖4-2中的所有協同安裝程序都已返回了ERROR_DI_POSTPROCESSING_REQUIRED,那么SetupDiCallClassInstaller就會為后處理先調用Device_Coinstaller_l,之后再是Class_Coinstaller_2,和Class_Coinstaller_1。類安裝程序并不要求后處理,只有協同安裝程序才要求。
即使先前的協同安裝程序在安裝請求中失敗,要求后處理的協同安裝程序也會被調用。
1.2 安裝程序界面
協同安裝程序具有以下原型:
typedef
DWORD
( CALLBACK* COINSTALLER_PROC) (
IN DI_FUNCTION InstallFunction,
IN HDEVINFO DeviceInfoSet,
IN PSP_DEVINFO_DADA DeviceInfoData OPTIONAL,
IN OUT PCOINSTALLER_CONTEXT_DATA Context
);
InstallFunction指定了正被處理的設備安裝請求,在其中協同安裝程序具有參與的選擇權。例如,DIF_INSTALLDEVICE。參見《Windows 2000 Driver Development Reference》一書的第一卷第3部分的第5章“安裝功能代碼”有關“在DIF代碼上的文檔處理”內容。
DeviceInfoSet提供了一個設備信息集的標識值。
DeviceInfoData有選擇性地標識作為設備安裝請求的目標設備。如果這個參數是非NULL的,它就在設備信息集中標識一個元素。當SetupDiCallClassInstaller調用一個特定設備協同安裝程序時,DeviceInfoData為非NULL。特定類協同安裝程序可以同一個具有NULL DeviceInfoData的DIF請求(如DIF_DETECT或DIF_FIRSTTIMESETUP)一起被調用。
Context指向該安裝請求的特定協同安裝程序上下文結構。這個上下文信息的格式如下:
Typedef struct
_COINSTALLER_CONTEXT_DATA {
BOOLEAN PostProcessing;
DWORD InstallResult;
PVOID PrivateData;
} COINSTALLER_CONTEXT_DATA, *PCOINSTALLER_CONTEXT_DATA;
當在恰當的類安裝程序(如有的話)處理了InstallFunction之后再調用協同安裝程序時,PostProcessing為TRUE。PostProcessing對協同安裝程序是只讀的。
如果PostProcessing為FALSE,則InstallResult不相關。如果PostProcessing為TRUE,InstallResult就是安裝請求的當前狀態。該值為NO_ERROR或是一個由為該安裝請求調用的先前部分返回的錯誤狀態。協同安裝程序可以為它的函數返回通過返回該值傳送狀態,或者可以返回其他狀態。InstallResult對協同安裝程序是只讀的。
PrivateData指向一個被分配的協同安裝程序緩沖。如果協同安裝程序設置該指針并要求后處理,那么當SetupDiCallClassInstaller為后處理調用協同安裝程序時,將該指針傳給這個協同安裝程序。
設備協同安裝程序返回以下一個值:
l NO_ERROR
協同安裝程序對特定的InstallFunction執行恰當的動作,或協同安裝程序決定它無需為該請求執行任何操作。
l ERROR_DI_POSTPROCESSING_REQUIRED
協同安裝程序對特定的InstallFunction執行任何恰當的操作,同時在類安裝已處理了該請求之后并要求被再次調用。
l A Win32 error
協同安裝程序遇到一個錯誤。
協同安裝程序不會設置ERROR_DI_DO_DEFAULT的返回狀態。這個狀態只能由類安裝程序使用。如果一個協同安裝程序返回了這樣的狀態,那么SetupDiCallClassInstaller將不能正確地處理DIF_Xxx請求。協同安裝程序也可能在后處理傳送中傳輸ERROR_DE_DO_DEFAULT的一個返回狀態,但是它永遠不會設置這個值。
1.3 協同安裝程序操作
協同安裝程序是用戶模式的Win32 DLL,它為注冊編寫額外的配置信息或執行要求動態信息的其他安裝任務,而該動態信息無法通過編寫INF來得到。
協同安裝程序可以完成以下一些或所有的任務:
l 打開InstallFunction來處理僅一個或少量的DIF_Xxx請求。
l 根據它是否被后處理調用來執行不同的操作(也就是Contex->PostProcessing是否為TRUE?)
l 當為后處理調用時,檢查Context->InstallResult。如果它不是NO_ERROR,就進行任何必需的清除并返回InstallResult。
協同安裝程序必須不能給最終用戶顯示任何的UI。協同安裝程序應該為設備提供恰當的缺省值。如果它沒有缺省值并要求用戶輸入,那么其他的設備支持組件就應提示用戶稍后的所需輸入。例如,若調制解調器沒有正確的撥號屬性設置,則需在用戶使用調制解調器而不是在設備設置時提示他們。
1.3.1 處理DIF代碼
每個DIF代碼的參考頁都繼續了以下一些或全部的部分:
何時發送
描述Setup應用程序發送該DIF請求的典型時間及原因。
由誰處理
指出允許哪些安裝程序處理該請求。該安裝程序包括了類安裝程序、類協同安裝程序(設置-類范圍的協同安裝程序),以及設備協同安裝程序(特定設備協同安裝程序)。
輸入
SetupDiCallClassInstaller通過在它的主入口點調用安裝程序給一個安裝程序發送一個DIF請求。除了DIF代碼之外,這個功能提供與某請求相關的額外信息。參見每個DIF代碼的參考頁可得到與每個請求一起提供的信息細節。以下列表包括了額外輸入的一般描述,還列出了用于處理參數的SetupDiXxx函數:
l DeviceInfoSet
為設備信息集提供一個標識值。
該標識值是不透明的。利用這個標識值,例如,在調用中將設備信息集標識到SetupDiXxx函數。
DeviceInfoSet可能具有相聯的設備設置類。如果是這樣的,則調用SetupDiGetDeviceInfoListClass以得到類GUID。
l DeviceInfoData
有選擇性地給一個SP_DEVINFO_DATA結構提供一個指針,該結構在設備信息集中標識了一個設備。
l Device Installation Parameters
這些非直接的參數為SP_DEVINSTALL_PARAMS結構中的設備安裝提供了信息。如果DeviceInfoData是非NULL,就有與DeviceInfoData相關的設備安裝參數。如果DeviceInfoData為NULL,則設備安裝參數就與DeviceInfoSet相關的設備安裝參數。
調用SetupDiGetDeviceInstallParams以得到設備安裝參數。
l Class Installation Parameters
將可選的非直接參數指定給某個DIF請求。它們尤其是“DIF請求參數”。例如,一個DIF_REMOVE安裝請求的類安裝參數被包含在一個SP_REMOVEDEVICE_PARAMS結構中。
每個SP_XXX_PARAMS結構開始于一個固定大小的SP_CLASSINSTALL-HEADER。
調用SetupDiGetClassInstallParams以得到類安裝參數。
如果DIF請求具有類安裝參數,就有與DeviceInfoSet相關的參數集,及與DeviceInfoData相關的另一個參數集(如果DIF請求指定了DeviceInfoData)。SetupDiGetClassInstallParams返回了可得到的最特定參數。
l 上下文 (Context)
協同安裝程序具有一個可選的上下文參數。
l 輸出 (Output)
描述這個DIF代碼所需的輸出。
如果安裝程序修改了設備安裝參數,那么在返回之前安裝程序必須調用SetupDiSetDeviceInstallParams來應用改變。類似地,如果安裝程序修改DIF代碼的類安裝參數,安裝程序必須調用SetupDiSetClassInstallParams。
l 返回值 (Return Value)
指定DIF代碼的恰當返回值。參見圖4-3中有關返回值的更多信息。
l 缺省處理程序 (Default Handler)
指定SetupDi函數,它執行DIF代碼的系統定義操作。并非所有的DIF代碼都具有缺省處理程序。除非協同安裝程序或類安裝程序采取步驟阻礙調用缺省處理程序,SetupDiCallClassInstaller才會在它調用類安裝程序之后再調用DIF代碼的缺省處理程序(但卻是在它調用任何為后處理注冊的協同安裝程序之前)。
操作 (Operation)
描述安裝程序可能用來處理DIF請求的典型任務。
其他 (See Also)
相關信息源的列表。
圖4-3中是SetupDiCallClassInstaller中處理DIF代碼的事件序列。
操作系統執行每個DIF代碼的一些操作。由供應商提供的協同安裝程序及類安裝程序可以參與安裝行為。請注意即使DIF代碼失敗了,SetupDiCallClassInstaller也調用了為后處理注冊的協同安裝程序。
1.4 注冊協同安裝程序
協同安裝程序可以為某個設置類的單個或全部設備注冊。當特定設備中的一個已被安裝時,這些設備的協同安裝程序就通過INF文件動態注冊。類協同安裝程序被手工注冊或由定制的設置應用程序及一個INF注冊。
如要了解更多的信息,可參見《Registering a Device-Specific Coinstaller》及《Registering a Class Coinstaller》。
如要更新協同安裝程序,DLL的每個新版本都需有一個新的文件名,這是因為當用戶在設備屬性頁上點擊Update Driver按紐時,尤其要用到DLL。
圖4-3 在SetupDiCallClassInstaller中的DIF代碼處理流程圖
1.4.1 注冊設備專用的協同安裝程序
為了注冊設備專用的協同安裝程序,將以下部分添加到設備的INF文件中:
; :
; :
[DestinationDirs]
XxxCopyFilesSection = 11 \\DIRID_SYSTEM
\\ Xxx = driver or dev . prefix
; :
; :
[XxxInstall . OS-platporm.CoInstallers] \\ OS-platform is optional
CopyFiles = XxxCopyFilesSection
AddReg = Xxx.OS-platform. CoInstallers_AddReg
[XxxCopyFilesSection]
XxxCoInstall.dll
{Xxx. OS-platform.CoInstallers_AddReg}
HKR,,CoInstallers32.0x00010000.”XxxCoInstall.dll. \
XxxCoInstallEntryPoint”
DestinationDirs部分中的項說明XxxCopyFiles部分中列出的文件將被復制到系統目錄下。Xxx前綴標識出驅動程序、設備或設備組(如cdrom_CopyFilesSection)。Xxx前綴應是唯一的。
協同安裝程序安裝節的名稱可以用可選的操作系統/架構擴展(如cdrom_install.NTx86.CoInstallers)來修飾。
Xxx_AddReg部分中的項在設備驅動程序密鑰中建立一個CoInstallers32值項。該項包含了協同安裝程序DLL,而且可選地還可有一個特定入口點。如果忽略這個輸入點,則缺省為CoDeviceInstall。十六進制標志參數(0x00010000)指明這是REG_MULTI_SZ值項。
為了給設備注冊多于一個的特定設備協同安裝程序,復制每個協同安裝程序的文件并在注冊項中包含至少一個信息串。例如,為了注冊兩個協同安裝程序,建立如下部分:
; :
; :
[DestinationDirs]
XxxCopyFilesSection = 11 \\DIRID_SYSTEM
\\ Xxx = driver or dev . prefix
; :
; :
[XxxInstall . OS-platporm.CoInstallers] \\ OS-platform is optional
CopyFiles = XxxCopyFilesSection
AddReg = Xxx.OS-platform. CoInstallers_AddReg
[XxxCopyFilesSection]
XxxCoInstall.dll \\ copy 1st coinst. file
YyyCoinstall.dll \\ copy 2nd coinst. file
[Xxx. OS-platform.CoInstallers_AddReg]
HKR..CoInstallers32.0x00010000. \
“XxxCoInstall.dll. XxxCoInstallEntryPoint”. \
“YyyCoInstall.dll. YyyCoInstallEntryPoint”
\\ add both to registry
當執行設備專用的協同安裝程序INF部分時,該協同安裝程序是在安裝一個設備的過程中被注冊的。接著Setup API在安裝過程中的每個隨后步驟上調用協同安裝程序。如果為一個設備注冊多個協同安裝程序,那么Setup API按其在注冊中所列順序調用它們。
1.4.2 注冊類協同安裝程序
如要為某個設置類的每個設備都注冊一個協同安裝程序,可按以下所列建立一個注冊表項
HKLM\System\CurrentControlSet\Control\CoDeviceInstallers subkey:
{setup-class-GUID}: REG_MULTI_SZ : “XyzCoInstall.dll. XyzCoInstallEntryPoint\0\0”
該系統建立了CoDeviceInstallers密鑰。Setup-class-GUID為設備設置類指定GUID。如果協同安裝程序提供設備的多個類,它就建立每個設置類的單個值項。
我們不能覆蓋先前寫給setup-class-GUID密鑰的其他協同安裝程序。讀取這個密鑰,將自己的協同安裝程序信息串附加到REG_MULTI_SZ列表中,并將該密鑰寫回到注冊表中。
如果忽略CoInstallEntryPoint,則缺省為CoDeviceInstall。
協同安裝程序DLL必須也被復制到系統目錄下。
一旦復制了文件且做出了注冊表項,類協同安裝程序就可被用來調用相關設備和服務。
不用手工建立注冊項來注冊一個類協同安裝程序,就可以利用INF文件注冊它,如下所示。
[version]
signature = “$Windows NT$”
[DestinationDirs]
DefaultDestDir = 11 / / DIRID_SYSTEM
[DefaultInstall]
CopyFiles = @classXcoinst.dll
AddReg = CoInstaller_AddReg
[CoInstaller_AddReg]
HKLM.System\CurrentControlSet\Control\CoDeviceInstallers, \
{setup-class-GUID}, 0x00010008, “classXcoinst.dll,classXCoInstaller’
; above line uses the line continuation character (\)
這個例子INF將文件classXcoinst.dll復制到系統目錄下并在CoDeviceInstallers密鑰下建立了一個setup-class-GUID類的項。Xxx-AddReg部分的項指示兩個標志:”00010000”標志表示這個項是REG_MULTI_SZ,而”00000008”標志表示新值將被附加到任何已有的值上(如果新值并未存在于信息串中)。
這樣一個注冊表類協同安裝程序的INF可由右點擊安裝或通過SetupInstallFromInfSection應用程序激活。
貼段偽代碼
HRESULT
CoInstaller(
IN DI_FUNCTION InstallFunction,
IN HDEVINFO DeviceInfoSet,
IN PSP_DEVINFO_DATA DeviceInfoData, OPTIONAL
IN OUT PCOINSTALLER_CONTEXT_DATA Context
)
{
switch(InstallFunction)
{
case DIF_SELECTBESTCOMPATDRV:
if(!Context->PostProcessing)
{
DbgOut("DIF_SELECTBESTCOMPATDRV");
return ERROR_DI_POSTPROCESSING_REQUIRED;
}
else //post processing
{
DbgOut("DIF_SELECTBESTCOMPATDRV PostProcessing");
//We will do something here
SP_DRVINFO_DATA DriverInfoData;
SP_DRVINSTALL_PARAMS DriverInstallParams;
DriverInfoData.cbSize = sizeof(SP_DRVINFO_DATA);
DriverInstallParams.cbSize = sizeof(SP_DRVINSTALL_PARAMS);
if(SetupDiEnumDriverInfo(DeviceInfoSet,DeviceInfoData,SPDIT_CLASSDRIVER ,0,&DriverInfoData))
if(SetupDiGetDriverInstallParams(DeviceInfoSet,DeviceInfoData,&DriverInfoData,&DriverInstallParams))
if(DriverInstallParams.Rank != 0)
DriverInstallParams.Rank = 0;
if(!SetupDiGetDriverInstallParams(DeviceInfoSet,DeviceInfoData,&DriverInfoData,&DriverInstallParams))
{
DbgOut("SetupDiGetDriverInstallParams");
}
}
break;
case DIF_REMOVE:
DbgOut("DIF_REMOVE");
break;
default:
break;
}
return NO_ERROR;
}