《WINDOWS 環境下32位匯編語言程序設計》第15章 注冊表和INI文件

15.1 注冊表和INI文件簡介

在一個操作系統中,無論是操作系統本身還是運行于其中的大部分應用程序,都需要使用某種方式保存配置信息。在DOS系統中,配置信息往往是軟件的開發者根據自己的喜好用各種途徑加以保存的,比如在磁盤上面寫一個二進制的 .dat文件,或者寫一個文本文件等,這些配置文件中數據的格式也是各不相同的。

到了Windows 3.x系統中,這種情況發生了變化,雖然軟件的開發者還可以沿用這種自成體系的方法,但是系統也提供了一種標準的初始化文件格式(Initialization File)來保存配置信息。初始化文件是一種以INI為擴展名的文本文件,操作系統提供了一套專用的函數對INI文件進行操作。Windows 3.x不僅鼓勵程序員使用INI文件,操作系統本身也使用INI文件來保存配置信息,比如,Windows安裝目錄下的Win.ini文件中保存了桌面設置和與應用程序運行有關的信息;System.ini文件中保存了與硬件配置有關的信息,另外,Control.ini與Program.ini等文件也是很重要的配置文件。

INI文件在使用中存在諸多缺陷。由于INI文件是文本文件,使用任何文本編輯器都可以隨意對它進行修改,所以安全性不是很好。另外,INI文件的結構比較簡單,無法保存格式復雜的數據,如很長的二進制數據或帶回車的字符串等。最主要的缺點還是單個INI文件的大小不能超過64 KB。如果不同的應用程序都將自己的配置信息保存于Win.ini或者System.ini中,那么這些文件的規模很快就會超過限制的長度。如果不同應用程序都使用自己的INI的話,那么集中管理又成了一個問題。

在Windows 9x和Windows NT系列操作系統中,改用了一種全新的方法來管理配置信息,那就是使用集中管理的注冊表(Registry)。注冊表并不像它的中文名稱所稱的那樣是“表”,而是一種格式由系統定義的數據庫,它存放于某些二進制文件中。不同操作系統中對應的文件名可能有所不同,比如,Windows 9x系統中的注冊表文件由位于Windows安裝目錄中的System.dat和User.dat兩個文件組成,而NT系統的注冊表往往由位于Windows安裝目錄下的System32\Config目錄中的多個文件構成。操作系統將這些不同的文件集中“虛擬”成整個注冊表供系統自身及應用程序使用。

與INI文件對于Windows 3.x系統的重要性相比,注冊表對于Windows 9x和NT系統來說顯得更加重要。因為絕大多數系統使用的配置信息都存放于此,如系統的硬件配置、安裝的驅動程序列表、文件的關聯信息、系統的網絡配置和權限配置等,這些配置信息直接關系到Windows系統的啟動和初始化過程。如果注冊表受到了破壞,那么輕者Windows的啟動過程出現異常,重者可能會使整個系統無法啟動。另外,大部分應用程序的配置信息也存放于此,如果注冊表受到了破壞,即使操作系統能正常啟動,應用程序的運行也可能會受影響。

正是因為注冊表結構的封閉性和重要性,我們無法再使用像編輯INI文件那樣的簡單方法來編輯注冊表文件。實際上,Windows系統對注冊表文件的保護很嚴格,當系統在運行的時候,注冊表文件是被操作系統以獨占方式打開的,其他應用程序即使是用最基本的讀權限也無法打開它們,更不用說對它們進行寫操作了。

要對注冊表進行操作,必須使用系統提供的接口。Windows為此提供了一系列的注冊表操作函數,應用程序可以通過它們來完成注冊表編輯器(Regedit程序)能完成的全部功能,甚至包括遠程操作注冊表以及對.reg文件進行導入和導出等操作。

為了提供向下兼容性,Windows 9x和NT系統在支持注冊表操作的同時也支持INI文件的操作。實際上對于某些“Copy and play”的小程序來說,需要保存的配置信息并不復雜,使用INI文件可能更加簡單實用,而且保存于注冊表中的配置信息是無法隨文件拷貝到其他計算機中的。如果某些應用程序希望拷貝程序的同時可以拷貝配置信息,那么最好還是使用INI文件,所以在Windows 9x和NT系統中,INI文件的使用還是相當廣泛的。

本章用兩個單獨的例子,詳細介紹INI文件和注冊表的使用方法。

15.2 INI文件的操作

15.2.1 INI文件的結構

INI文件是一種文本格式的文件,其中的數據組織格式為:

;注釋
[Section1 Name]
KeyName1=value1
;注釋
KeyName2=value2
...
[Section2 Name]
KeyName1=value1
KeyName2=value2
...

INI文件中可以存在多個小節(Section),每個小節的開始用包括在一對方括號中的小節名稱指定,不同的小節不能重名,一個小節的內容從小節名稱的下一行開始,直到下一個小節開始為止。用戶程序可以按照自己的需求建立多個小節。

在每個小節中可以定義多個鍵(Key),每一個鍵由一個“鍵名=鍵值”格式的字符串組成,并獨自占用一行。在同一個小節中不能存在同名的鍵,但是在不同的小節中可以存在同名的鍵。

如果需要在INI文件的某些地方加注釋,可以將注釋放在單獨的一行中,行首以分號開始,注釋行出現的地方并沒有什么限制,既可以出現在文件的最前面,也可以出現在文件的任何一行中,如上面的例子中注釋出現在小節名稱的前面以及兩個鍵的中間。

一般來說,如果在自己開發的應用程序中使用系統定義的INI文件,如Win.ini等,由于文件中已經存在多個小節,那么自己建立一個獨立的小節比較合適,然后在這個小節中定義不同的鍵值,比如,下面是筆者的計算機上Win.ini文件的片斷:

...
[MCI Extensions]
asf=MPEGVideo
asx=MPEGVideo
m3u=MPEGVideo
mp2v=MPEGVideo
mp3=MPEGVideo
mpv2=MPEGVideo
wma=MPEGVideo
wmv=MPEGVideo
[Hex Workshop]
Path=C:\PROGRA~1\BREAKP~1\HEXWOR~1.1\hworks32.exe
CurrentVersion=3.11
...

其中的“MCI Extensions”小節是Windows系統自身使用的小節,Windows在這里定義了一些媒體文件的關聯方式,而“Hex Workshop”小節是安裝了十六進制編輯器HexWorkshop后由這個軟件創建并使用的,該軟件在小節中用“Path”鍵定義了軟件的安裝目錄、在“CurrentVersion”鍵中定義了軟件的版本號。

如果覺得往系統INI文件中寫數據顯得不是那么“綠色環保”,那么應用程序可以建立一個獨立的INI文件。如本節的例子文件就在自己運行的目錄中建立了一個Option.ini文件,并在“Windows Position”小節的“X”,“Y”鍵中保存窗口的位置,以便在下一次運行的時候將窗口移動到上一次退出時所處的位置,內容如下:

[Windows Position]
X=194
Y=162
...

Windows系統提供了一系列函數對INI文件進行操作,其中包括讀取和設置鍵值,獲取小節名稱列表及獲取和刪除整個小節內容等函數。下面的例子演示了這些功能的使用方法。

15.2.2 管理鍵值

本節的例子程序存放在所附光盤的Chapter15\ini目錄中,運行后的界面如圖15.1所示。例子程序在運行目錄下創建了一個Option.ini文件,程序中的所有操作都是針對這個文件進行的。當用戶在“Section”一欄中輸入小節名稱、在“Key”一欄中輸入鍵名后,如果INI文件中對應的小節和鍵定義是已經存在的,那么按下“讀取Key”按鈕后就會將鍵值讀取到“Value”一欄中;而按下“刪除Key”按鈕的時候,可以將這個鍵刪除。

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖15.1 INI文件操作例子的運行界面

在輸入小節和鍵名后繼續在“Value”一欄中輸入一個字符串,并按下“保存Key”按鈕,如果指定鍵已經存在,那么程序用新的鍵值替換原來的鍵值;如果鍵名不存在,則程序創建這個鍵;如果創建鍵的時候小節名是不存在的,那么程序在創建鍵值之前會自動創建小節;在最極端的情況下,當INI文件也不存在的時候,那么程序也會創建INI文件。

當用戶按下“刪除Key”按鈕將一個小節中的鍵逐一刪除直到全部鍵都被刪除的時候,小節名稱并不會被刪除,INI文件中還會留有一個空的小節名稱,按下“刪除Section”按鈕后可以將小節名稱刪除。如果在小節中尚包含有鍵的時候按下“刪除Section”按鈕,那么小節名稱包括小節中的全部鍵都會被刪除。

每次進行操作后,程序自動將INI文件中的所有小節和鍵值枚舉一遍并將內容顯示在圖15.1下面的編輯框中,以便觀察操作的結果。下面通過分析這個程序來了解這些功能的實現方法。

源文件目錄中的Ini.rc文件定義了如圖15.1所示的對話框,代碼如下:

#include                    <resource.h>
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#define  ICO_MAIN         1000
#define  DLG_MAIN         1000
#define  IDC_SEC          1001
#define  IDC_KEY          1002
#define  IDC_VALUE       1003
#define  IDC_INI          1004
#define  IDC_DEL_SEC     1005
#define  IDC_DEL_KEY     1006
#define  IDC_GET_KEY     1007
#define  IDC_SET_KEY     1008
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN          ICON     "Main.ico"
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DLG_MAIN DIALOG 205, 128, 245, 168
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "INI文件操作"
FONT 9, "宋體"
{
RTEXT "Section", -1, 4, 7, 30, 8
EDITTEXT IDC_SEC, 39, 5, 78, 12, ES_AUTOHSCROLL | WS_BORDER | WS_TABSTOP
RTEXT "Key", -1, 4, 23, 30, 8
EDITTEXT IDC_KEY, 39, 21, 78, 12, ES_AUTOHSCROLL | WS_BORDER | WS_TABSTOP
RTEXT "Value", -1, 4, 39, 30, 8
EDITTEXT IDC_VALUE, 39, 37, 78, 12, ES_AUTOHSCROLL | WS_BORDER | WS_TABSTOP
LTEXT "當前INI文件內容:", -1, 8, 57, 141, 8
EDITTEXT IDC_INI, 6, 71, 232, 91, ES_MULTILINE | ES_AUTOVSCROLL |
ES_AUTOHSCROLL | ES_READONLY | WS_BORDER | WS_VSCROLL | WS_TABSTOP
PUSHBUTTON "刪除 &Section", IDC_DEL_SEC, 122, 4, 57, 14
PUSHBUTTON "刪除K&ey", IDC_DEL_KEY, 183, 4, 57, 14
PUSHBUTTON "讀取 &Key", IDC_GET_KEY, 122, 20, 57, 14
PUSHBUTTON "保存Ke&y", IDC_SET_KEY, 183, 20, 57, 14
}
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

匯編源文件Ini.asm的內容如下:

; Ini.asm   ---------- Ini 文件操作的例子
; ------------------------------------------------------------------
; 使用 nmake 或下列命令進行編譯和鏈接:
; ml /c /coff Ini.asm
; rc Ini.rc
; Link  /subsystem:windows Ini.obj Ini.res
.386
.model flat,stdcall 
option casemap:none ; include 文件定義
include 	c:/masm32/include/windows.inc 
include 	c:/masm32/include/user32.inc 
include 	c:/masm32/include/kernel32.inc 
includelib 	c:/masm32/lib/user32.lib 
includelib 	c:/masm32/lib/kernel32.lib ; equ 等值定義
ICO_MAIN 	equ 1000
DLG_MAIN 	equ 1000
IDC_SEC 	equ 1001
IDC_KEY 	equ 1002 
IDC_VALUE 	equ 1003 
IDC_INI 	equ 1004 
IDC_DEL_SEC equ 1005 
IDC_DEL_KEY equ 1006 
IDC_GET_KEY equ 1007 
IDC_SET_KEY equ 1008; 數據段
.data?
hInstance 	dword ?
hWinMain 	dword ?
szProfileName	dword MAX_PATH dup(?)
szBuffer1 	byte 32760 dup(?)
szBuffer2	byte 32760 dup(?)
.const 
szFileName 	byte '\Option.ini',0
szSecPos 	byte 'Windows Position',0
szKeyX 		byte 'X',0
szKeyY 		byte 'Y',0
szFmt1 		byte '%d',0
szFmtSection byte '[%s]'
szCrLf 		byte 0dh,0ah,0; 代碼段
.code 
; 枚舉全部 Section 和全部 Key
_EnumINI proc local @szBuffer[256]:byte invoke SetDlgItemText, hWinMain, IDC_INI, NULL ;讀取 Section 列表并循環處理invoke GetPrivateProfileSectionNames, addr szBuffer1, \sizeof szBuffer1, addr szProfileName mov esi, offset szBuffer1 .while byte ptr[esi]invoke wsprintf, addr @szBuffer, addr szFmtSection, esi invoke SendDlgItemMessage, hWinMain, IDC_INI, EM_REPLACESEL, FALSE, addr @szBuffer ;讀取 Key 列表并循環顯示invoke GetPrivateProfileSection, esi, addr szBuffer2, \sizeof szBuffer2,addr szProfileNamemov edi, offset szBuffer2 .while byte ptr [edi]invoke SendDlgItemMessage, hWinMain, IDC_INI, EM_REPLACESEL, FALSE, edi invoke SendDlgItemMessage, hWinMain, IDC_INI, EM_REPLACESEL, FALSE, addr szCrLf invoke lstrlen, edi add edi, eax inc edi .endw invoke lstrlen, esi add esi, eax inc esi .endw ret 
_EnumINI endp _GetPosition proc local @szBuffer[512]:byte ;將當前路徑和 ini 文件名組合起來invoke GetCurrentDirectory, MAX_PATH, addr szProfileName mov esi, offset szProfileName invoke lstrlen, esi mov ecx, offset szFileName .if byte ptr [esi+eax-1] == '\'inc ecx .endif invoke lstrcat, esi, ecx ;讀存放在 ini 文件中的數據invoke GetPrivateProfileInt, addr szSecPos, \addr szKeyX, 50, addr szProfileName push eax invoke GetPrivateProfileInt, addr szSecPos, \addr szKeyY, 50, addr szProfileName pop ecx invoke SetWindowPos, hWinMain, HWND_TOP, ecx, eax, 0, 0, SWP_NOSIZE ret 
_GetPosition endp _SavePosition proc local @szBuffer[512]:byte, @szRect:RECT invoke GetWindowRect, hWinMain, addr @szRect invoke wsprintf, addr @szBuffer, addr szFmt1, @szRect.left invoke WritePrivateProfileString, addr szSecPos, addr szKeyX, \addr @szBuffer, addr szProfileName invoke wsprintf, addr @szBuffer, addr szFmt1, @szRect.top invoke WritePrivateProfileString, addr szSecPos, addr szKeyY, \addr @szBuffer, addr szProfileName ret 
_SavePosition endp _ProcDlgMain proc uses ebx edi esi hWnd, wMsg, wParam, lParam local @szSection[256]:byte local @szKey[256]:byte local @szValue[256]:byte local @szBuffer[256]:byte mov eax, wMsg .if eax == WM_CLOSE invoke _SavePosition invoke EndDialog, hWnd, NULL .elseif eax == WM_INITDIALOG push hWnd pop hWinMain invoke LoadIcon, hInstance, ICO_MAIN invoke SendMessage, hWnd, WM_SETICON, ICON_BIG, eax invoke _GetPosition invoke _EnumINI .elseif eax == WM_COMMAND invoke GetDlgItemText, hWnd, IDC_SEC, addr @szSection, sizeof @szSection invoke GetDlgItemText, hWnd, IDC_KEY, addr @szKey, sizeof @szKey invoke GetDlgItemText, hWnd, IDC_VALUE, addr @szValue, sizeof @szValue mov eax, wParam .if ax >= IDC_SEC && ax <= IDC_INI mov eax, TRUE ret .elseif ax == IDC_DEL_SEC invoke WritePrivateProfileString, addr @szSection, \NULL, NULL, addr szProfileName .elseif ax == IDC_DEL_KEY invoke WritePrivateProfileString, addr @szSection, \addr @szKey, NULL, addr szProfileName .elseif ax == IDC_SET_KEY invoke WritePrivateProfileString, addr @szSection, \addr @szKey, addr @szValue, addr szProfileName .elseif ax == IDC_GET_KEY invoke GetPrivateProfileString, addr @szSection, \addr @szKey, NULL, addr @szBuffer, \sizeof @szBuffer, addr szProfileName invoke SetDlgItemText, hWnd, IDC_VALUE, addr @szBuffer .endif invoke _EnumINI .else mov eax, FALSE ret .endif mov eax, TRUE ret 
_ProcDlgMain endp main proc invoke GetModuleHandle, NULL mov hInstance, eax invoke DialogBoxParam, eax, DLG_MAIN, NULL, offset _ProcDlgMain, NULL invoke ExitProcess, 0
main endp 
end main 

在對話框關閉的時候,程序在WM_CLOSE函數中調用_SavePosition子程序將對話框窗口當前的位置保存到INI文件中,以便在下一次運行的時候恢復原來的窗口位置。程序在WM_INITDIALOG中調用_GetPosition子程序以恢復上一次保存的位置,這樣,程序就可以“記住”窗口的擺放位置。

枚舉小節和鍵值的功能是在_EnumINI子程序中完成的。程序在一開始執行的時候或者在每次收到WM_COMMAND消息的時候都調用這個函數,所以每次用戶有所操作,INI文件的變化就會馬上在窗口中的編輯框中反映出來。

1.鍵值的創建和刪除

當按下“保存Key”按鈕時,例子程序使用WritePrivateProfileString函數保存鍵值,這個函數可以往指定的INI文件中寫入鍵值,函數的用法是:

invoke WritePrivateProfileString,lpAppName,lpKeyName,lpString,lpFileName

在函數的參數中,lpAppName參數指向包含Section名稱的字符串,lpKeyName參數指向包含鍵名稱的字符串,lpString參數指向鍵值字符串,最后一個參數指向INI文件名字符串。這些字符串都是以0字符結束的。

當這些參數全部指定為字符串的時候,函數將在指定INI文件的指定小節中寫入“鍵名=鍵值”格式的行;當指定的INI文件、文件中的小節和小節中的鍵名都已經存在的時候,函數用新鍵值替換原來的鍵值;當指定的INI文件存在而小節不存在的時候,函數自動創建小節并將鍵寫入;如果連指定的INI文件也不存在的話,函數會自動創建文件。總之,程序不必考慮INI文件是否存在,小節是否存在或鍵值定義是否存在等情況,只要調用WritePrivateProfileString函數就可以保證配置信息被正確保存。

WritePrivateProfileString函數也可以用來刪除鍵或者小節,當lpAppName和lpKeyName參數指定了小節名稱和鍵名,而lpString參數指定為NULL的時候,函數將指定的鍵刪除,如例子文件中對“刪除Key”按鈕的操作就是這樣的:

    .elseif ax ==    IDC_DEL_KEYinvoke  WritePrivateProfileString,addr @szSection,\addr @szKey,NULL,addr szProfileName

但是使用這種方法逐一將某個小節中的鍵全部刪除時,空白小節的定義字符串“[SectionName]”還保留在INI文件中。如果想要將小節的定義字符串連同小節的全部鍵定義全部刪除的話,可以將lpKeyName和lpString參數全部指定為NULL,而lpAppName參數指定要刪除的小節,如例子文件中對“刪除Section”按鈕的處理代碼:

    .elseif ax ==    IDC_DEL_SECinvoke  WritePrivateProfileString,addr @szSection,\NULL,NULL,addr szProfileName

如果函數執行成功,將返回一個非0的值,如果執行失敗將返回0。在定義鍵名的時候,注意不用在名稱字符串中包括“=”號,因為等號被用來分隔鍵名和鍵值,鍵名也不能以注釋字符“;”開始。在定義鍵值的時候可以使用等號和分號,但注意不要將鍵值定義為多行的文本。如果在字符串中包含換行和回車,比如將鍵值字符串指向下列所示的一個字符串:

"hello,world!",0dh,0ah,"this is the second line",0

那么函數會成功地被調用,但是最后的INI文件中會出現這樣的內容:

[Section]
Key=hello,world!
this is the second line

顯然,函數不加判斷地將換行和回車也寫到了INI文件中,但是當取回鍵值的時候,只有第一行能被正確取回,而底下的行將當做格式錯誤的“垃圾”留在INI文件中。

由于INI文件是以文本方式保存的,所以實際上鍵值也只能用字符串方式表示,如果需要保存一個數值類型的值,那么程序需要自己使用wsprintf函數將數值轉換成字符串后再保存。比如例子程序在退出時為保存窗口位置,就是在_SavePosition子程序中首先用GetWindowRect函數獲取窗口位置,然后使用wsprintf函數轉換后再保存的。

2.獲取鍵值

獲取鍵值的操作比較方便,因為這時既可以用GetPrivateProfileString函數獲取鍵值字符串,也可以使用GetPrivateProfileInt函數讓Windows將鍵值字符串轉換成數值后再返回,就像使用GetDlgItemInt函數轉換對話框子窗口中的字符串一樣。(比較奇怪的是保存鍵值的時候并沒有一個WritePrivateProfileInt函數,結果每次還要首先使用wsprintf函數!)

GetPrivateProfileString函數的用法是:

invoke GetPrivateProfileString,lpAppName,lpKeyName,lpDefault, \lpReturnedString,nSize,lpFileName

該函數的幾個參數與WritePrivateProfileString的參數類似,也是使用lpAppName,lpKeyName和lpFileName參數分別指定小節名稱、鍵名和INI文件名,但是其余幾個參數則有所不同:

lpReturnedString參數指向一個緩沖區,函數在這里返回獲取的鍵值字符串,緩沖區的長度用nSize參數指定,當緩沖區的長度太小以至于無法容納返回的字符串時,字符串會被截止到nSize?1的長度后返回,余下的一個字節用來存放一個0字符用做結尾。

lpDefault參數指向一個默認字符串,當指定的鍵無法找到的時候,函數將這個字符串拷貝到返回緩沖區中。

GetPrivateProfileString還有兩種特殊用法:首先,當lpAppName參數指定為NULL的時候,函數在緩沖區中返回的是全部小節名稱的列表,每個小節名以0結尾,全部的名稱列表再以一個附加的0結束,返回到緩沖區中的數據格式如下所示:

小節名稱1,0,小節名稱2,0,…,小節名稱n,0,0

另外,當lpAppName參數指定了小節名稱,而lpKeyName參數指定為NULL的時候,函數在緩沖區中返回該小節的全部鍵名列表,每個鍵名以0結尾,全部列表后面再以一個附加的0結束,如下所示:

鍵名1,0,鍵名2,0,…,鍵名n,0,0

所以用這兩種方法調用GetPrivateProfileString函數可以實現枚舉小節名稱和枚舉鍵名的功能。

不管用何種方式使用GetPrivateProfileString函數,函數的返回值是返回到緩沖區中的字符串長度(長度中并不包括結尾的0字符)。

如果保存的鍵值是全部由數字字符組成的話(比如例子程序的_SavePosition子程序中用wsprintf函數從窗口位置的坐標值轉換過來的字符串),那么可以使用GetPrivateProfileInt函數直接將字符串轉換成數值后再返回:

invoke  GetPrivateProfileInt,lpAppName,lpKeyName,nDefault,lpFileName

其中lpAppName,lpKeyName和lpFileName參數的使用方法同上,函數將指定的鍵值字符串轉換成數值類型以后返回,但是函數不支持負數,如果鍵值字符串是“?1234”格式的負數,那么函數的返回值是0。nDefault指定一個默認數值,如果指定的鍵名不存在的話,函數返回nDefault指定的數值。

例子程序在初始化的時候,在_GetPosition子程序中使用GetPrivateProfileInt函數讀出上次退出時保存的窗口位置,并使用SetWindowPos函數將窗口移動到這個位置上。

15.2.3 管理小節

在鍵名已知的情況下固然可以使用GetPrivateProfileString等函數獲取鍵值,但在某些情況下并不是所有的鍵名都是已知的,比如一個編輯文件需要保存近來編輯過的文件名列表,它可以建立一個小節如下:

[History]
file1=C:\My documents\Readme.txt
file2=D:\MyApp\Help.txt
file3=C:\download\win32asm.txt
...

這時小節中鍵的數量和名稱就是不定的。另外,也有小節的數量和名稱不定的情況,這時就需要對小節或鍵進行枚舉。上一節中已經介紹了在GetPrivateProfileString函數中通過將lpAppName或lpKeyName參數設置為NULL來獲取小節名稱列表和鍵名列表的方法。實際上,Windows中還有專門用來實現此功能的函數,這些函數不僅可用來枚舉小節和鍵,也可以用來一次性修改整個小節的內容。

GetPrivateProfileSectionNames函數可以用來返回全部小節名稱的列表:

invoke GetPrivateProfileSectionNames,lpBuffer,nSize,lpFileName

lpFileName參數指向INI文件名,lpBuffer參數是一個指針,指向用來返回小節名稱列表的緩沖區,nSize參數指定緩沖區的長度。返回到緩沖區中的數據格式也是:“小節名稱1,0,小節名稱2,0,…,小節名稱n,0,0”的格式。當緩沖區太小以至于不能容納全部數據的時候,后面的數據被丟棄,全部數據被截尾到nSize?2的長度,剩下的兩個字節用來保存兩個表示結束的0字符。函數的返回值是返回到緩沖區中的數據長度(不包括結尾的0字符)。

GetPrivateProfileSection函數則可以用來返回整個小節的鍵定義,與上一節介紹的調用GetPrivateProfileString函數時將lpKeyName參數設置為NULL以獲取鍵名列表不同,GetPrivateProfileSection函數返回的是鍵定義列表。函數的用法是:

invoke GetPrivateProfileSection,lpAppName,lpBuffer,nSize,lpFileName

函數的lpAppName指向一個包含小節名稱的字符串,返回到緩沖區中的數據格式為:

鍵名1=鍵值1,0,鍵名2=鍵值2,0,…,鍵名n=鍵值n,0,0

所以,使用這個函數可以同時完成枚舉鍵名和鍵值的功能。例子程序中就是使用它來枚舉鍵值的,但使用中如果覺得自己處理“鍵名=鍵值”字符串來分解鍵名和鍵值比較麻煩的話,可以用GetPrivateProfileString函數枚舉鍵名并再次調用它獲取指定鍵的鍵值。

在Windows 9x中,緩沖區最大不能超過32767 B,而在Windows NT中則沒有限制,函數的返回值是返回到緩存區中的數據長度(不包括結尾的0字符)。

WritePrivateProfileSection函數則將“鍵名1=鍵值1,0,鍵名2=鍵值2,0,…,鍵名n=鍵值n,0,0”格式的小節數據一次性全部寫入。函數的用法是:

invoke  WritePrivateProfileSection,lpAppName,lpString,lpFileName

lpString參數指向包含鍵值定義列表的緩沖區,函數執行后,指定小節原來的鍵定義被全部刪除,然后加入新的鍵定義。如果執行成功,函數返回非0值,否則返回0。

15.2.4 使用不同的INI文件

在前面介紹的這些INI文件函數中,當lpFileName參數指定的文件名字符串中不包括路徑時,系統將認為文件位于Windows安裝目錄下,這樣當函數創建INI文件的時候,就會把文件創建于Windows安裝目錄下。但是大部分情況下,希望INI文件位于程序的運行目錄下,這樣拷貝文件的時候可以連同INI文件一起拷貝,另外,在卸載或刪除程序的時候可以避免在Windows目錄中留下一個“垃圾”INI文件。

如果希望在程序的運行目錄而非Windows目錄中建立INI文件,那么最簡單的方法就是在INI文件名前面加上“.\”路徑,也就是說把文件名寫成“.\MyIniFile.ini”的格式,這樣系統會在當前目錄下建立INI文件。這種方法的缺陷是如果程序運行中需要不斷切換當前目錄的話,系統就會在不同的地方亂建INI文件。一個常見的情況就是使用“打開文件”通用對話框的時候,系統會將當前目錄切換到對話框中瀏覽的那個目錄,這樣寫INI文件時就會造成INI文件的位置根本無法確定。一個相對保險的辦法就是像例子程序中所示的那樣,在程序一開始運行的時候就使用GetCurrentDirectory函數獲取當前目錄,然后將INI文件名添加到目錄后組成一個全路徑的文件名,以后在所有的操作中都使用這個文件名。而最保險的辦法就是首先用GetModuleFileName獲取包含全路徑的當前執行文件名,再從這個文件名中分離出路徑,最后添加上INI文件名

如果要操作的是Windows安裝目錄下的Win.ini文件而非其他INI文件時,那么既可以使用上面這些函數,也可以使用另一組專門用于操作Win.ini文件的函數,這組函數是:

invoke  GetProfileString,lpAppName,lpKeyName,lpDefault,lpBuffer,nSize
invoke  GetProfileInt,lpAppName,lpKeyName,nDefault
invoke  WriteProfileString,lpAppName,lpKeyName,lpString
invoke  GetProfileSection,lpAppName,lpBuffer,nSize
invoke  WriteProfileSection,lpAppName,lpString

可以看出,與操作通用INI文件的函數相比,這組函數的函數名中少了“Private”單詞,參數中少了lpFileName參數(因為操作的INI文件名就是Win.ini,并不需要單獨指定),所有其他參數的用法都是相同的。

讀者可以注意到這些函數中似乎少了一個GetProfileSectionNames函數,實際上并沒有這個函數,如果要枚舉Win.ini文件中的小節列表的話,只需把GetPrivateProfileSectionNames函數的lpFileName參數指定為NULL即可,并不需要單獨設置一個類似于GetProfileSectionNames的函數。

15.3 對注冊表的操作

注冊表在Windows 9x及NT系統中是很重要的,不談操作系統本身對注冊表依賴性的大小,僅從應用程序的角度來說,除了用來代替INI文件用做保存配置信息以外,有些功能是必須通過操作注冊表來完成的,比如,要讓一個程序在Windows啟動的時候自動運行,那就必須在注冊表的HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion \Run子鍵下面添加一個鍵值定義;如果要讓應用程序和某種數據文件相關聯,就必須在注冊表的HKEY_CLASSES_ROOT鍵下面為相應的數據文件擴展名設置關聯信息;再比如,編寫好一個COM組件以后,要為組件的DLL文件在注冊表中添加注冊信息,組件才能被其他程序使用。另外,操作系統本身將絕大多數的配置信息保存于注冊表中,應用程序在運行中往往需要查詢這些系統信息來決定運行的方式。

要自如地使用注冊表,必須解決兩方面的問題,第一是如何在程序中對注冊表進行讀寫與枚舉等操作;第二就是要搜集整理關于注冊表鍵值定義的資料。因為注冊表是Windows中出了名的“數據迷宮”,很多鍵的定義只有Microsoft自己知道,公開的鍵定義也足夠編成一本手冊了,僅知道如何讀寫注冊表卻不知道該往哪里讀寫是沒用的。由于本節并不是當做注冊表的定義手冊來寫的,所以內容僅涉及第一個方面的問題。本節討論的內容是:注冊表的數據組織方式,以及如何用Win32匯編來讀寫和枚舉注冊表的內容。

15.3.1 注冊表的結構

1.注冊表的數據組織方式

INI文件中的數據是按照兩層組織的——只能通過一些在結構上“平行”的小節來歸類不同的鍵,這就像一個驅動器中只能建立一層目錄來管理文件一樣非常不便,與此相比,注冊表的結構有很大改進。

注冊表中的數據是分多個層次來組織的,組織的方式類似于磁盤目錄的多層組織方式。與文件系統中根目錄、子目錄和文件這樣的層次劃分類似,注冊表中的數據層次分為根鍵、鍵和鍵值項,其中根鍵就相當于文件系統中的根目錄,鍵相當于子目錄,鍵值項相當于文件。根鍵和子鍵是為了將不同的鍵值項分類組織而定義的,只有鍵值項中才包含真正的配置數據。

在Windows自帶的注冊表編輯器Regedit中就可以看出注冊表的結構來。如圖15.2所示,注冊表中的根鍵有6個,其名稱是Windows規定的,并且是固定不變的,它們分別是HKEY_CLASSES_ROOT,HKEY_CURRENT_USER,HKEY_LOCAL_MACHINE,HKEY_CURRENT_CONFIG,HKEY_DYN_DATA和HKEY_USERS。在每個根鍵下可以建立不同的鍵,以HKEY_LOCAL_MACHINE根鍵為例,下面有HARDWARE,SAM,SECURITY和SOFTWARE等子鍵,而SOFTWARE鍵下面又由各種應用程序創建了一些子鍵,如ACD Systems,Acer和Adobe等,鍵和子鍵的層次關系是相對的,就像一個目錄既可以是其上層目錄的子目錄,又可以是其下層目錄的父目錄一樣。

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖15.2 注冊表的結構

在一個鍵中既可以繼續建立多個子鍵,也可以同時建立多個鍵值項,就像一個目錄中既可以建立多個子目錄,同時也可以存放多個文件一樣。圖15.2右邊窗口中列出的就是ACDSee鍵中定義的鍵值項。

每個鍵值項由鍵值名稱和鍵值數據組成(就像是文件名和文件中的數據的關系),如上圖中鍵值名稱AppMode中的數據是“00 00 00 00”、鍵值名稱UserName中的數據是“aaa”。不像INI文件中的鍵值只能定義為字符串,注冊表鍵值的數據類型要豐富得多,全部可用的鍵值類型如表15.1所示。

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?表15.1 注冊表的鍵值類型

圖15.2中的AppMode和UsageCount鍵值項是REG_BINARY類型的,WindowsPosition鍵值項是REG_DWORD類型的,其余的鍵值項是REG_SZ類型的,這3種類型的鍵值項在注冊表中是最常見的。每個鍵下面還可以有一個沒有名稱的鍵值項,稱為默認鍵,默認鍵必須是REG_SZ或REG_EXPAND_SZ類型的。

2.注冊表中的根鍵

注冊表的結構中大量采用“映射”關系,系統定義的6種根鍵其實存放在不同的文件中。在Windows 9x系統中,HKEY_LOCAL_MACHINE根鍵的內容存放在System.dat文件中,HKEY_USERS根鍵的內容存放在User.dat文件中。而在Windows NT系統中,注冊表的內容存放得更分散,連HKEY_LOCAL_MACHINE根鍵中的不同子鍵SOFTWARE,SAM,SECURITY和SYSTEM等都分開存放在Windows\system32\config目錄下的不同文件中。

HKEY_LOCAL_MACHINE和HKEY_USERS根鍵是注冊表中的兩大根鍵,其余的根鍵都是它們的派生,實際上它們都是這兩大根鍵下面某些子鍵的映射,如HKEY_CLASSES_ROOT根鍵是HKEY_LOCAL_MACHINE根鍵下SOFTWARE\Classes子鍵的映射,HKEY_CURRENT_CONFIG根鍵是HKEY_LOCAL_MACHINE根鍵下Config子鍵的映射。

HKEY_USERS根鍵中的內容是用戶配置信息,其內容取決于計算機是否激活了用戶配置文件。若未激活用戶配置文件,則里面只有名為.DEFAULT的單一子鍵,該子鍵包括與所有用戶相關的各種設置。若激活了用戶配置文件并且正確地執行了登錄操作,則根鍵下還會有代表當前登錄用戶的子鍵,這時候HKEY_CURRENT_USER根鍵就是這個子鍵的映射。

由于HKEY_DYN_DATA保存了系統運行時的動態數據,它反映出系統的當前狀態,所以它的數據在每次運行時都是不一樣的。應用程序一般不使用這個根鍵。

對這些實際上是其他數據的映射的根鍵來說,操作根鍵上的數據和操作未經映射前的數據產生的效果是一樣的,系統建立映射可以讓鍵值數據的組織更清晰,操作起來更加快捷、方便。

15.3.2 管理子鍵

本節用一個例子來演示對注冊表的操作方法,例子代碼在所附光盤的Chapter15\Reg目錄中,運行后的界面如圖15.3所示。例子程序演示了子鍵的創建和刪除,鍵值項的創建、讀取和刪除,以及枚舉子鍵和鍵值項的功能。

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖15.3 注冊表操作

例子的運行界面例子程序對HKEY_LOCAL_MACHINE根鍵進行操作,當在“鍵名”一欄中輸入子鍵名稱字符串,并在“鍵值名”一欄輸入鍵值名稱后,按下“讀取鍵值”按鈕,如果指定子鍵中存在這個鍵值項,程序會讀出鍵值數據并顯示在“鍵值”一欄中;如果按下“刪除鍵值”按鈕,那么對應鍵值項被刪除;讀者也可以在“鍵值”一欄中輸入其他數據并選定“類型”,然后按下“保存鍵值”按鈕將新的鍵值數據設置到注冊表中。

例子程序也可以對子鍵進行操作。在對話框最下面的“子鍵名”一欄中輸入子鍵名稱,按下“創建子鍵”的話,函數會在“鍵名”一欄指定的鍵下面創建子鍵,如果按下“刪除子鍵”按鈕,那么“鍵名”指定的鍵下面由“子鍵名”指定的子鍵會被刪除。

每次進行操作以后,程序將“鍵名”指定的鍵下面的全部子鍵和鍵值項列在編輯框中,以便讓用戶看到操作的結果。

這里列出了資源腳本文件和源代碼。Reg.rc文件定義了如圖15.3所示的對話框,文件內容如下:

#include                    <c:/masm32/include/resource.h>#define ICO_MAIN                    1000
#define DLG_MAIN                    1000
#define IDC_KEY                     1001
#define IDC_VALUENAME               1002
#define IDC_VALUE                   1003
#define IDC_TYPE                    1004
#define IDC_KEYLIST                 1005
#define IDC_SUBKEY                  1006
#define IDC_REMOVE_VALUE            1007
#define IDC_GET_VALUE               1008
#define IDC_SET_VALUE               1009
#define IDC_CREATE_SUBKEY           1010
#define IDC_REMOVE_SUBKEY           1011
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN          ICON                    "Main.ico"
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DLG_MAIN DIALOG 205, 107, 245, 206
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "注冊表操作"
FONT 9, "宋體"
{
RTEXT "鍵名HKEY_LOCAL_MACHINE\", -1, 9, 9, 105, 8
EDITTEXT IDC_KEY, 118, 7, 121, 12, ES_AUTOHSCROLL | WS_BORDER | WS_TABSTOP
RTEXT "鍵值名", -1, 4, 26, 30, 8
EDITTEXT  IDC_VALUENAME,  39,  24,  139,  12,  ES_AUTOHSCROLL  |  WS_BORDER  |
WS_TABSTOP
RTEXT "鍵值", -1, 4, 43, 30, 8
EDITTEXT IDC_VALUE, 39, 41, 201, 12, ES_AUTOHSCROLL | WS_BORDER | WS_TABSTOP
RTEXT "類型", -1, 4, 59, 30, 8
COMBOBOX IDC_TYPE, 39, 58, 78, 77, CBS_DROPDOWNLIST | WS_TABSTOP
LTEXT "當前鍵下的所有的子鍵名:", -1, 8, 78, 141, 8
EDITTEXT IDC_KEYLIST, 6, 92, 232, 91, ES_MULTILINE | ES_AUTOVSCROLL |
ES_AUTOHSCROLL | ES_READONLY | WS_BORDER | WS_VSCROLL | WS_TABSTOP
RTEXT "子鍵名", -1, 7, 191, 30, 8
EDITTEXT IDC_SUBKEY, 42, 189, 73, 12, ES_AUTOHSCROLL | WS_BORDER | WS_TABSTOP
PUSHBUTTON "刪除鍵值", IDC_REMOVE_VALUE, 183, 23, 57, 14
PUSHBUTTON "讀取鍵值", IDC_GET_VALUE, 122, 57, 57, 14
PUSHBUTTON "保存鍵值", IDC_SET_VALUE, 183, 57, 57, 14
PUSHBUTTON "創建子鍵", IDC_CREATE_SUBKEY, 121, 188, 57, 14
PUSHBUTTON "刪除子鍵", IDC_REMOVE_SUBKEY, 182, 188, 57, 14
}

匯編源代碼Reg.asm的內容如下:

; Reg.asm     -------------- 注冊表操作的例子
; -------------------------------------------------------------------
; 使用 nmake 或下列命令進行編譯和鏈接:
; ml /c /coff Reg.asm
; rc Reg.rc
; Link  /subsystem:windows Reg.obj Reg.res
.386
.model flat,stdcall 
option casemap:none ; include 文件定義
include 	c:/masm32/include/windows.inc 
include 	c:/masm32/include/user32.inc 
includelib 	c:/masm32/lib/user32.lib 
include 	c:/masm32/include/kernel32.inc 
includelib 	c:/masm32/lib/kernel32.lib 
include 	c:/masm32/include/Advapi32.inc 
includelib 	c:/masm32/lib/Advapi32.lib ; equ 等值定義
ICO_MAIN 		equ 1000 
DLG_MAIN 		equ 1000 
IDC_KEY 		equ 1001 
IDC_VALUENAME 	equ 1002 
IDC_VALUE 		equ 1003 
IDC_TYPE 		equ 1004 
IDC_KEYLIST 	equ 1005 
IDC_SUBKEY 		equ 1006 
IDC_REMOVE_VALUE equ 1007 
IDC_GET_VALUE 	equ 1008
IDC_SET_VALUE 	equ 1009 
IDC_CREATE_SUBKEY equ 1010
IDC_REMOVE_SUBKEY equ 1011 ; 數據段
.data?
hInstance 	dword ?
hWinMain 	dword ?
.const 
szTypeSz 	byte 'REG_SZ',0
szTypeDw 	byte 'REG_DWORD',0
szFmtSubkey byte '【子鍵】%s',0dh,0ah,0
szFmtSz 	byte '【鍵值】%s=%s (REG_SZ 類型)',0dh,0ah,0
szFmtDw 	byte '【鍵值】%s=%08X (REG_DWORD 類型)',0dh,0ah,0
szFmtValue 	byte '【鍵值】%s (其它類型)',0dh,0ah,0
szNotSupport byte '程序暫不顯示其它類型的鍵值!',0; 代碼段
.code 
include _Reg.asm _EnumKey proc _lpKey local @hKey, @dwIndex, @dwLastTime:FILETIME local @szBuffer1[512]:byte local @szBuffer[256]:byte local @szValue[256]:byte local @dwSize, @dwSize1, @dwType mov @dwIndex, 0 invoke SetDlgItemText, hWinMain, IDC_KEYLIST, NULL ;枚舉子鍵invoke RegOpenKeyEx, HKEY_LOCAL_MACHINE, _lpKey, NULL, \KEY_ENUMERATE_SUB_KEYS, addr @hKey .if eax == ERROR_SUCCESS .while TRUE mov @dwSize, sizeof @szBuffer invoke RegEnumKeyEx, @hKey, @dwIndex, addr @szBuffer, addr @dwSize, \NULL, NULL, NULL, NULL.break .if eax == ERROR_NO_MORE_ITEMS invoke wsprintf, addr @szBuffer1, addr szFmtSubkey, addr @szBuffer invoke SendDlgItemMessage, hWinMain, IDC_KEYLIST, EM_REPLACESEL, 0, addr @szBuffer1 inc @dwIndex .endw invoke RegCloseKey, @hKey .endif ;枚舉鍵值mov @dwIndex, 0invoke RegOpenKeyEx, HKEY_LOCAL_MACHINE, _lpKey, NULL, \KEY_QUERY_VALUE, addr @hKey .if eax == ERROR_SUCCESS .while TRUE mov @dwSize, sizeof @szBuffer mov @dwSize1, sizeof @szValue invoke RegEnumValue, @hKey, @dwIndex, addr @szBuffer, addr @dwSize, \NULL, addr @dwType, addr @szValue, addr @dwSize1 .break .if eax == ERROR_NO_MORE_ITEMS mov eax, @dwType .if eax == REG_SZ invoke wsprintf, addr @szBuffer1, addr szFmtSz, addr @szBuffer, addr @szValue .elseif eax == REG_DWORD invoke wsprintf, addr @szBuffer1, addr szFmtDw, addr @szBuffer, dword ptr @szValue .else invoke wsprintf, addr @szBuffer1, addr szFmtValue, addr @szBuffer .endif invoke SendDlgItemMessage, hWinMain, IDC_KEYLIST, EM_REPLACESEL, 0, addr @szBuffer1 inc @dwIndex .endw invoke RegCloseKey, @hKey .endif ret 
_EnumKey endp _ProcDlgMain proc uses ebx edi esi hWnd, wMsg, wParam, lParam local @szKey[256]:byte, @szSubkey[256]:byte local @szValueName[256]:byte, @szValue[256]:byte local @dwType, @dwSize mov eax, wMsg .if eax == WM_CLOSE invoke EndDialog, hWnd, NULL .elseif eax == WM_INITDIALOG push hWnd pop hWinMain invoke LoadIcon, hInstance, ICO_MAIN invoke SendMessage, hWnd, WM_SETICON, ICON_BIG, eax invoke SendDlgItemMessage, hWnd, IDC_TYPE, CB_ADDSTRING, 0, addr szTypeSz invoke SendDlgItemMessage, hWnd, IDC_TYPE, CB_ADDSTRING, 0, addr szTypeDw invoke SendDlgItemMessage, hWnd, IDC_TYPE, CB_SETCURSEL, 0, 0invoke _EnumKey, NULL .elseif eax == WM_COMMAND invoke GetDlgItemText, hWnd, IDC_KEY, addr @szKey, 256invoke GetDlgItemText, hWnd, IDC_SUBKEY, addr @szSubkey, 256 invoke GetDlgItemText, hWnd, IDC_VALUENAME, addr @szValueName, 256 mov eax, wParam .if ax >= IDC_KEY && ax <= IDC_SUBKEY mov eax, TRUE ret .elseif ax == IDC_REMOVE_VALUE invoke _RegDelValue, addr @szKey, addr @szValueName .elseif ax == IDC_GET_VALUE ;讀取鍵值mov @dwSize, sizeof @szValue invoke RtlZeroMemory, addr @szValue, @dwSize invoke _RegQueryValue, addr @szKey, addr @szValueName, \addr @szValue, addr @dwSize, addr @dwType .if eax == ERROR_SUCCESS mov eax, @dwType .if eax == REG_SZ invoke SetDlgItemText, hWnd, IDC_VALUE, addr @szValue invoke SendDlgItemMessage, hWnd, IDC_TYPE, CB_SETCURSEL, 0, 0 .elseif eax == REG_DWORD invoke SendDlgItemMessage, hWnd, IDC_TYPE, CB_SETCURSEL, 1, 0invoke SetDlgItemInt, hWnd, IDC_VALUE, dword ptr @szValue, FALSE .else invoke SetDlgItemText, hWnd, IDC_VALUE, addr szNotSupport.endif .else invoke SetDlgItemText, hWnd, IDC_VALUE, NULL .endif ;設置鍵值.elseif ax == IDC_SET_VALUE invoke SendDlgItemMessage, hWnd, IDC_TYPE, CB_GETCURSEL, 0, 0.if !eax invoke GetDlgItemText, hWnd, IDC_VALUE, addr @szValue, 256 invoke lstrlen, addr @szValue inc eax invoke _RegSetValue, addr @szKey, addr @szValueName, \addr @szValue, REG_SZ, eax .else invoke GetDlgItemInt, hWnd, IDC_VALUE, NULL, FALSE mov dword ptr @szValue, eax invoke _RegSetValue, addr @szKey, addr @szValueName, \addr @szValue, REG_DWORD, 4.endif .elseif ax == IDC_CREATE_SUBKEY invoke _RegCreateKey, addr @szKey, addr @szSubkey .elseif ax == IDC_REMOVE_SUBKEY invoke _RegDelSubKey, addr @szKey, addr @szSubkey .endif invoke _EnumKey, addr @szKey .elsemov eax, FALSE ret .endif mov eax, TRUE ret 
_ProcDlgMain endp main proc invoke GetModuleHandle, NULL mov hInstance, eax invoke DialogBoxParam, eax, DLG_MAIN, NULL, offset _ProcDlgMain, NULL invoke ExitProcess, 0 
main endp 
end main 

注冊表函數是一組函數名以Reg開頭的函數,全部函數包含在Advapi32.dll文件中,所以在源程序的一開始要使用include和includelib語句將Advapi32.inc文件和Advapi32.lib文件包含進來。

例子程序中將把注冊表的各種操作分別寫成子程序,用_RegQueryValue,_RegSetValue和_RegDelValue子程序來查詢、設置和刪除鍵值,用_RegCreateKey和_RegDelSubKey完成子鍵的創建和刪除工作,并在WM_COMMAND消息中根據不同的按鈕消息調用相應的子程序。為了讓讀者不經修改就可以將這些子程序用在其他程序中,將這些子程序放在一個單獨的_Reg.asm文件中并在主程序中使用include語句包含進來,文件內容如下:

; _Reg.asm          ------幾個注冊表操作的公用子程序; 查詢鍵值
_RegQueryValue	proc	_lpszKey,_lpszValueName,_lpszValue,_lpdwSize,_lpdwTypelocal	@hKey,@dwReturnmov	@dwReturn,-1invoke	RegOpenKeyEx,HKEY_LOCAL_MACHINE,_lpszKey,NULL,\KEY_QUERY_VALUE,addr @hKey.if	eax == ERROR_SUCCESSinvoke	RegQueryValueEx,@hKey,_lpszValueName,NULL,_lpdwType,\_lpszValue,_lpdwSizemov	@dwReturn,eaxinvoke	RegCloseKey,@hKey.endifmov	eax,@dwReturnret_RegQueryValue	endp; 設置鍵值
_RegSetValue	proc	_lpszKey,_lpszValueName,_lpszValue,_dwValueType,_dwSizelocal	@hKeyinvoke	RegCreateKey,HKEY_LOCAL_MACHINE,_lpszKey,addr @hKey.if	eax == ERROR_SUCCESSinvoke	RegSetValueEx,@hKey,_lpszValueName,NULL,\_dwValueType,_lpszValue,_dwSizeinvoke	RegCloseKey,@hKey.endifret_RegSetValue	endp; 創建子鍵
_RegCreateKey	proc	_lpszKey,_lpszSubKeyNamelocal	@hKey,@hSubkey,@dwDispinvoke	RegOpenKeyEx,HKEY_LOCAL_MACHINE,_lpszKey,NULL,\KEY_CREATE_SUB_KEY,addr @hKey.if	eax == ERROR_SUCCESSinvoke	RegCreateKeyEx,@hKey,_lpszSubKeyName,NULL,NULL,\NULL,NULL,NULL,addr @hSubkey,addr @dwDispinvoke	RegCloseKey,@hKeyinvoke	RegCloseKey,@hSubkey.endifret_RegCreateKey	endp; 刪除鍵值
_RegDelValue	proc	_lpszKey,_lpszValueNamelocal	@hKeyinvoke	RegOpenKeyEx,HKEY_LOCAL_MACHINE,_lpszKey,NULL,\KEY_WRITE,addr @hKey.if	eax == ERROR_SUCCESSinvoke	RegDeleteValue,@hKey,_lpszValueNameinvoke	RegCloseKey,@hKey.endifret_RegDelValue	endp; 刪除子鍵
_RegDelSubKey	proc	_lpszKey,_lpszSubKeyNamelocal	@hKeyinvoke	RegOpenKeyEx,HKEY_LOCAL_MACHINE,_lpszKey,NULL,\KEY_WRITE,addr @hKey.if	eax == ERROR_SUCCESSinvoke	RegDeleteKey,@hKey,_lpszSubKeyNameinvoke	RegCloseKey,@hKey.endifret_RegDelSubKey	endp

1.打開和關閉子鍵

注冊表函數對注冊表的操作是通過句柄來完成的,與文件操作一樣,在對某個鍵下的子鍵或者鍵值項進行操作之前,需要先將這個鍵打開,然后使用鍵句柄來引用這個鍵,在操作完畢以后再將鍵句柄關閉。注冊表的根鍵不需要打開,它們的句柄是固定不變的,要使用根鍵的時候只要把這些句柄直接拿來用就是了,Windows.inc中已經預定義了它們的數值:

    HKEY_CLASSES_ROOT                         equ 80000000hHKEY_CURRENT_USER                         equ 80000001hHKEY_LOCAL_MACHINE                        equ 80000002hHKEY_USERS                                equ 80000003hHKEY_PERFORMANCE_DATA                     equ 80000004hHKEY_CURRENT_CONFIG                       equ 80000005hHKEY_DYN_DATA                             equ 80000006h

在程序中可以隨時將這些助記符當做句柄來引用對應的根鍵。在程序結束的時候,不需要關閉這些根鍵句柄。

打開子鍵使用RegOpenKeyEx函數,在Win16中還存在一個RegOpenKey函數,雖然在Win32中這個函數仍然存在,但這僅是為了兼容的目的而設置的。API手冊中推薦使用RegOpenKeyEx函數:

invoke  RegOpenKeyEx,hKey,lpSubKey,dwOptions,samDesired,phkResult

函數的hKey參數指定父鍵句柄,lpSubKey指向一個字符串,用來表示要打開的子鍵名稱,在系統中一個子鍵的全稱是以“根鍵\第1層子鍵\第2層子鍵\第n層子鍵”類型的字符串表示的,中間用“\”隔開,字符串的最后以0字符結束,這和目錄名的表示方法是很像的。

既然子鍵的全稱是這樣表示的,那么要打開一個子鍵的時候,下面的兩種表示方法有什么不同呢?

(1)父鍵=HKEY_LOCAL_MACHINE,子鍵=Software\RegTest\MySubkey

(2)父鍵=HKEY_LOCAL_MACHINE\Software,子鍵=RegTest\MySubkey

答案是:這兩種表示方法是完全相同的。在使用RegOpenKeyEx函數打開子鍵的時候,既可以將hKey參數設置為HKEY_LOCAL_MACHINE根鍵的句柄,并將lpSubKey參數指向“Software\RegTest\MySubkey”字符串;也可以將hKey參數設置為“HKEY_LOCAL_MACHINE\Software”的句柄,將lpSubKey參數指向“RegTest\MySubkey”字符串,得到的結果是一樣的。但是,使用第一種方法時,hKey參數可以直接使用助記符HKEY_LOCAL_MACHINE來表示,因為根鍵的句柄是固定的,不需要打開;而使用第二種方法時,還需要先打開“HKEY_LOCAL_MACHINE\Software”鍵來獲取它的句柄,所以具體使用哪種方法還要根據具體情況靈活選用。

函數的其他幾個參數的含義如下。

● dwOptions參數——系統保留參數,必須指定為0。

● samDesired參數——子鍵的打開方式,根據使用子鍵的方式,可以設置為下列取值的組合,只有指定了打開的方式,才能在打開子鍵后進行相應的操作:

■ KEY_ALL_ACCESS——允許所有的存取。

■ KEY_CREATE_LINK——允許建立符號列表。

■ KEY_CREATE_SUB_KEY——允許建立下一層子鍵。

■ KEY_ENUMERATE_SUB_KEYS——允許枚舉下一層子鍵。

■ KEY_EXECUTE——允許讀操作。

■ KEY_QUERY_VALUE——允許查詢鍵值數據。

■ KEY_READ—KEY_QUERY_VALUE,KEY_ENUMERATE_SUB_KEYS和KEY_NOTIFY的組合。

■ KEY_SET_VALUE——允許修改或創建鍵值數據。

■ KEY_WRITE——KEY_SET_VALUE和KEY_CREATE_SUB_KEY的組合。

● phkResult參數——指向一個雙字變量,函數在這里返回打開的子鍵句柄。

如果函數執行成功,返回值是ERROR_SUCCESS,并且函數在phkResult參數指向的變量中返回子鍵句柄。

當不再需要繼續使用鍵句柄的時候,可以使用RegCloseKey函數將它關閉:

invoke RegCloseKey, hKey

如果句柄被成功關閉,函數返回ERROR_SUCCESS。

2.創建和刪除子鍵

創建一個子鍵可以使用RegCreateKeyEx函數:

invoke RegCreateKeyEx,hKey,lpSubKey,Reserved,lpClass,dwOptions,\samDesired,lpSecurityAttributes,phkResult,lpdwDisposition

函數中與RegOpenKeyEx函數中同名參數的含義和用法是相同的,hKey也是用來指定父鍵句柄,lpSubKey指向要創建的子鍵名稱字符串,samDesired參數指明子鍵建立后的操作方式,phkResult指向用來返回鍵句柄的雙字變量。

其余一些參數的含義如下:

● Reserved參數——保留參數,必須設置為0。

● lpClass參數——為創建的子鍵定義一個類名,這個參數一般設置為NULL。

● dwOptions參數——創建子鍵時的選項,它可以是以下取值之一:

■ REG_OPTION_NON_VOLATILE——默認值,子鍵被創建到注冊表文件中。

■ REG_OPTION_VOLATILE——創建易失性的子鍵,子鍵被保存在內存中,當系統重新啟動的時候,子鍵消失。這個選項僅對Windows NT系統有效,在9x系統中被忽略。

● lpSecurityAttributes參數——指向一個SECURITY_ATTRIBUTES結構,用來指定鍵句柄的繼承性,如果句柄不需要被繼承,可以使用NULL。

● lpdwDisposition參數——這個參數一般使用NULL。

當需要創建的子鍵已經存在的時候,函數僅起到RegOpenKeyEx函數的作用;如果子鍵不存在,那么函數將創建子鍵。如果函數執行成功,返回值是ERROR_SUCCESS。

如果要創建“HKEY_LOCAL_MACHINE\Key1\Key2\Key3”子鍵,既可以將hKey參數設置為HKEY_LOCAL_MACHINE,將lpSubKey參數指向“Key1\Key2\Key3”字符串;也可以先打開“HKEY_LOCAL_MACHINE\Key1”鍵,將hKey設置為打開的鍵句柄,然后將lpSubKey參數指向“Key2\Key3”字符串,這與RegOpenKeyEx函數中的用法是類似的。在第二種用法中,打開父鍵的時候注意要指定KEY_CREATE_SUB_KEY方式。

當被創建子鍵的上層鍵不存在的時候,函數連同上層的子鍵一起創建。如上面的例子中,假如Key2也不存在,那么函數先在“HKEY_LOCAL_MACHINE\Key1”下創建Key2,然后在Key2下繼續創建Key3。

_Reg.asm文件中的大部分子程序首先用RegOpenKeyEx函數打開子鍵,以便進行下一步操作,但是保存鍵值用的_RegSetValue子程序中使用的是RegCreateKeyEx函數,這樣當子鍵已經存在的時候,函數僅打開它,如果子鍵不存在的話則創建子鍵。

刪除子鍵使用RegDeleteKey函數:

invoke  RegDeleteKey,hKey,lpSubKey

hKey參數為父鍵句柄,lpSubKey參數指向要刪除的子鍵名稱字符串。函數僅刪除最后一 層 子 鍵,以 及 下 面 的 全 部 鍵 值 項。比 如,在“HKEY_LOCAL_MACHINE\Key1\Key2\Key3”子鍵存在的情況下,當hKey指定為HKEY_LOCAL_MACHINE,lpSubKey指向“Key1\Key2\Key3”的時候,函數僅刪除Key3子鍵,不會連同Key2,Key1全部刪除。但如果Key3子鍵下有鍵值項的話,這些鍵值項會被一起刪除。

如果要刪除的子鍵下還存在下一層子鍵,比如,上例中的Key3子鍵下還存在Key4子鍵,那么對Key3子鍵進行刪除時,Windows 9x和Windows NT系統的做法是不同的:在Windows 9x中,Key3子鍵本身、Key3子鍵下所有的鍵值項和下層子鍵(包括上面舉例的Key4)會被全部刪除;而在Windows NT中,只有在不存在下層子鍵的情況下刪除才能成功,如果Key3子鍵下還存在Key4子鍵,那么對Key3子鍵的刪除是不會成功的。

應用程序不能直接在HKEY_LOCAL_MACHINE根鍵下面創建和刪除子鍵,只能在下一層由系統定義的子鍵下進行操作,如果要保存配置信息的話,用戶應用程序一般在HKEY_LOCAL_MACHINE\SOFTWARE子鍵下再創建自己的子鍵,然后將鍵值項保存在自己的子鍵中。

15.3.3 管理鍵值

配置信息是存放在鍵值項中的,打開或者創建一個鍵的最終目的都是為了在鍵下面存取鍵值項,這就像磁盤上的目錄是用來合理組織和管理文件用的,數據還是存放在文件中的。當使用打開或者創建鍵的函數得到鍵句柄后,就可以通過它來存取鍵值項了。

1.設置鍵值項

在一個鍵下面設置和創建鍵值項使用RegSetValueEx函數:

invoke RegSetValueEx,hKey,lpValueName,Reserved,dwType,lpData,cbData

● hKey參數指定一個鍵句柄,鍵值項將保存在這個鍵下,lpValueName參數指向定義鍵值項名稱的字符串。假如lpValueName參數指向一個空串或者設置為NULL,并且設置的鍵值類型是REG_SZ的話,那么函數設置的是鍵的默認值(圖15.2中所示的“默認”項)。

● Reserved參數是保留的,必須設置為0。

● dwType參數指出了要設置的鍵值數據的類型,可以使用的類型如表15.1所示。

● lpData參數是一個指針,指向包含鍵值數據的緩沖區,cbData參數為要保存的數據長度。緩沖區中的數據格式,以及cbData參數指定的數據長度需要和dwType參數指出的鍵值類型相對應,比如,要設置REG_SZ類型的鍵值項,就要將cbData參數設置為字符串的長度+1(加上尾部的0);同樣對于REG_MULTI_SZ類型的鍵值項來說,最后的兩個0的長度都必須包括到cbData參數中;對于REG_DWORD類型的鍵值項,需要將雙字數據放在緩沖區中并將cbData參數設置為4(不像其他函數一樣當參數是雙字的時候一般將雙字在參數中直接傳遞)。

當子鍵中的鍵值項不存在的時候,函數新建鍵值項;當鍵值項已經存在的時候,函數將新的鍵值數據寫入。如果鍵值數據保存成功,函數返回ERROR_SUCCESS。

雖然鍵值數據的最大長度沒有規定,其大小僅受限于可用的內存大小,應用程序甚至可以使用REG_BINARY格式的鍵值項將整個文件都保存到注冊表中,但在實際的使用中還是建議不要將大于2 KB的數據放到注冊表中,因為這將影響注冊表的使用效率。

要在一個鍵中創建或修改鍵值項,鍵的打開方式中必須包括KEY_SET_VALUE方式。

2.查詢鍵值數據

讀取鍵值項中的數據或者查詢鍵值項的屬性使用RegQueryValueEx函數,用法如下:

invoke RegQueryValueEx,hKey,lpValueName,lpReserved,\lpType,lpData,lpcbData

參數hKey和lpValueName用來指定要讀取的鍵值項所處的子鍵句柄和鍵值項的名稱,lpReserved參數是保留參數,必須使用0。lpData參數指向一個緩沖區,用來接收返回的鍵值數據。

函數的其余幾個參數使用時必須注意的是它們都是指向雙字變量的指針,這一點和使用RegSetValueEx函數時是不同的:

● lpType參數——函數在這個參數指向的雙字變量中返回讀取的鍵值類型,如果不需要返回鍵值項的類型,可以將這個參數設置為NULL。

● lpcbData參數——在調用的時候,程序必須在這個參數指向的雙字變量中放置緩沖區的長度(并不是直接用lpcbData參數指出緩沖區長度)。當函數返回的時候,雙字變量被函數改為返回到緩沖區中的數據的實際長度。

當函數執行成功的時候,函數的返回值是ERROR_SUCCESS。當程序指定的緩沖區長度不足以容納返回的數據的時候,函數的返回值是ERROR_MORE_DATA,這時lpcbData參數指向的雙字變量中返回需要的長度。

如果僅需要查詢鍵值長度而不需要返回實際的數據,可以將lpData參數設置為NULL,但是lpcbData參數不能為NULL,這時函數會在lpcbData參數指向的雙字變量中返回鍵值數據的長度。如果僅想查詢鍵值項的類型,也可以同時將lpcbData和lpData參數設置為NULL。在這些情況下如果函數查詢成功,返回值也是ERROR_SUCCESS。

如果要在一個鍵中查詢鍵值數據的話,鍵的打開方式中必須包括KEY_QUERY_VALUE方式。

3.刪除鍵值項

刪除一個鍵值項的操作則比較簡單,使用RegDeleteValue函數就可以了:

invoke  RegDeleteValue,hKey,lpValueName

hKey參數和lpValueName指定父鍵句柄和被刪除鍵值項的名稱。唯一需要注意的是父鍵句柄的打開方式必須包括KEY_SET_VALUE。如果鍵值項被成功刪除,則函數返回ERROR_SUCCESS。

15.3.4 子鍵和鍵值的枚舉

在實際的應用中往往需要對一個鍵下的子鍵或者鍵值項進行列表操作,就像在DOS系統下常用Dir命令一樣,這就要用到子鍵和鍵值的枚舉函數。在注冊表函數中,枚舉子鍵和枚舉鍵值項使用的函數是不一樣的,不像FindFirstFile等文件列表函數那樣將文件連同子目錄混在一起列出來。下面分別介紹這兩種函數。

1.枚舉子鍵

例子程序中枚舉子鍵和鍵值項的操作是在_EnumKey子程序中完成的,讀者可以參考一下相應的代碼,在這個子程序中,程序首先使用RegEnumKeyEx函數來枚舉子鍵:

invoke RegEnumKeyEx,hKey,dwIndex,lpName,lpcbName,lpReserved,\lpClass,lpcbClass,lpftLastWriteTime

● hKey參數指定被枚舉的鍵句柄,dwIndex參數指定需要返回信息的子鍵索引編號,lpName指向一個緩沖區,函數在這里返回子鍵名稱,lpClass指向用于返回子鍵類名的緩沖區,lpftLastWriteTime指向一個FILETIME結構,函數在這里返回子鍵上一次被寫入的時間。lpReserved參數是保留參數,必須設置為0。

要注意的是:lpcbName和lpcbClass指向兩個雙字變量,調用函數前,這兩個雙字變量中必須放入lpName和lpClass指定的緩沖區的長度,當函數返回的時候,函數在里面返回實際返回到緩沖區中的字符串的長度。如果函數執行成功,返回值是ERROR_SUCCESS。

因為RegEnumKeyEx函數每次返回一個子鍵的名稱信息,所以要枚舉全部子鍵的話,必須用循環多次調用這個函數,并且每次將dwIndex參數指定的子鍵索引號遞增,當子鍵全部被枚舉后,繼續調用函數將得到一個ERROR_NO_MORE_ITEMS返回值,這時就可以結束循環了。下面是循環的典型寫法:

.data
dwIndex          dd       ?
dwSize            dd       ?
szBuffer         db       256 dup (?)
.code... ...mov dwIndex,0.while TRUEmov dwSize,sizeof szBufferinvoke RegEnumKeyEx,hKey,dwIndex,addr szBuffer,\addr dwSize,NULL,NULL,NULL,NULL.break .if eax == ERROR_NO_MORE_ITEMS;處理獲取的子鍵inc dwIndex.endw

在循環開始前,程序初始化當做索引用的dwIndex變量,每次調用RegEnumKeyEx后將索引加1,當檢測到函數的返回值是ERROR_NO_MORE_ITEMS的時候,使用 .break語句退出循環。程序不使用 .break .if eax != ERROR_SUCCESS語句當做結束循環的條件是因為:當出現緩沖區不夠長等意外情況時,函數的調用可能失敗,但是這時子鍵可能還沒有全部被枚舉,所以只有判斷返回值是ERROR_NO_MORE_ITEMS才能保證全部子鍵被枚舉。

每次調用函數之前,程序必須重新將dwSize變量的值設置為szBuffer緩沖區的大小,這一點非常重要,筆者有很多次聽朋友說他的程序只能枚舉一部分子鍵,最后查出的原因就是忘了這一步。這是因為每次函數返回時,dwSize中會變成返回的子鍵名稱字符串的長度,如果不重新設置,下一次調用時函數就會將這個長度認為是緩存區的長度。隨著循環的進行,這個值會變得越來越小。

當進行枚舉子鍵操作時,父鍵的打開方式中必須包括KEY_ENUMERATE_SUB_KEYS方式(KEY_READ方式中已經包括KEY_ENUMERATE_SUB_KEYS)。

2.枚舉鍵值

RegEnumKeyEx函數僅枚舉一個鍵下面的全部子鍵,對鍵下面的鍵值項則不會去理會。如果要枚舉一個鍵下面的鍵值項,那么必須使用RegEnumValue函數:

invoke  RegEnumValue,hKey,dwIndex,lpValueName,lpcbValueName,\lpReserved,lpType,lpData,lpcbData

函數的hKey,dwIndex和lpReserved參數的使用同RegEnumKeyEx函數中的同名參數。其余的一些參數中,lpValueName和lpData參數指向兩個緩存區,函數在里面分別返回鍵值項的名稱和數據。lpcbValueName和lpcbData參數指向兩個雙字變量,調用函數前里面必須放入鍵值項名稱緩沖區和鍵值數據緩沖區的長度,函數返回后這兩個變量的值被改為返回到緩沖區中的數據長度。lpType參數則指向一個用于返回鍵值數據類型的雙字變量。

如果不需要返回鍵值數據,lpData和lpcbData參數可以設置為NULL,如果不需要返回鍵值數據類型,lpType參數也可以設置為NULL。

要進行枚舉鍵值項的操作,父鍵的打開方式中必須包括KEY_QUERY_VALUE方式。

下面是一段典型的用于枚舉鍵值項的循環代碼:

.data
dwIndex           dd       ?
dwType            dd       ?
dwNameSize        dd       ?
szName            db       256 dup (?)
dwDataSize        dd       ?
szData            db       256 dup (?)
.code...mov dwIndex,0.while    TRUEmov      dwNameSize,sizeof szNamemov      dwDataSize,sizeof szDatainvoke   RegEnumValue,hKey,dwIndex,addr szName,\addr dwNameSize,NULL,addr dwType,\addr szData,addr dwDataSize.break   .if eax == ERROR_NO_MORE_ITEMS;處理獲取的鍵值項inc      dwIndex.endw

這個循環的結構和使用RegEnumKeyEx函數的循環是大同小異的。同樣要注意的是,在循環中每次要重新設置dwNameSize和dwDataSize變量的值,因為它們在RegEnumValue函數執行后也會被改寫。

3.查詢鍵屬性

在枚舉子鍵和鍵值項的時候往往會遇到這樣一個問題:注冊表函數對鍵值數據的長度并沒有限制,在預留緩沖區的時候如果申請太大的內存比較浪費,申請太小的內存則無法枚舉成功,對于返回的子鍵名稱和鍵值項名稱也是如此。那么,究竟該留多大的緩沖區呢?其實在枚舉之前可以先用RegQueryInfoKey函數查看一下鍵的統計信息。

RegQueryInfoKey函數返回的信息有:一個鍵下面子鍵的數量、鍵值項的數量、子鍵名稱和鍵值名稱字符串的最大長度及鍵值數據的最大長度等。根據這些信息,就能方便地申請足夠大的緩沖區來保證枚舉成功。函數還能返回創建子鍵時指定的類名和最后一次寫入子鍵的時間等信息。

RegQueryInfoKey函數的用法如下:

invoke RegQueryInfoKey,hKey,lpClass,lpcbClass,lpReserved,\lpcSubKeys,lpcbMaxSubKeyLen,lpcbMaxClassLen,\lpcValues,lpcbMaxValueNameLen,lpcbMaxValueLen,\lpcbSecurityDescriptor,lpftLastWriteTime

函數的參數比較多,但并不復雜,各參數的含義是:

● hKey — 指 定 要 獲 取 信 息 的 鍵 句 柄,鍵 的 打 開 方 式 中 必 須 包 括KEY_QUERY_VALUE。

● lpClass——指向一個緩沖區,用來返回創建鍵時指定的Class字符串。

● lpcbClass——指向一個雙字變量,調用函數時變量中必須放入lpClass指定的緩沖區的長度,函數返回時在這里放入返回到緩沖區中的字符串長度。

● lpReserved——保留參數,必須設置為0。

● lpcSubKeys——指向一個雙字,用來返回鍵中的子鍵數量。

● lpcbMaxSubKeyLen——指向一個雙字,用來返回所有子鍵中最長的名稱字符串長度,返回的長度不包括字符串結尾的0字符。

● lpcbMaxClassLen——指向一個雙字,用來返回所有子鍵中最長的Class字符串長度,返回的長度不包括字符串結尾的0字符。

● lpcValues——指向一個雙字,用來返回鍵下面的鍵值項數量。

● lpcbMaxValueNameLen——指向一個雙字,用來返回所有鍵值項中最長的名稱字符串長度,返回的長度不包括字符串結尾的0字符。

● lpcbMaxValueLen——指向一個雙字,用來返回所有鍵值數據的最大長度。

● lpcbSecurityDescriptor——指向一個雙字,用來返回安全描述符的長度。

● lpftLastWriteTime——指向一個FILETIME結構,用來返回最后一次修改鍵的時間。

可以看到,除hKey外其他的參數都是指針,指向用來返回數據的變量或結構,如果不需要返回某種信息的話,可以將對應的指針參數設置為NULL。另外,所有返回的最長名稱字符串長度中都不包括結尾的0字符。

除了前面介紹的函數外,系統中還存在一些不常用的注冊表函數,比如,可以用RegLoadKey和RegReplaceKey函數從指定的文件中恢復注冊表的子鍵信息,也可以通過RegSaveKey函數將鍵信息保存到指定的文件中。另外,可以通過RegConnectRegistry等函數操作遠程注冊表。對于這些函數,本節不再詳細介紹。

15.3.5 注冊表應用舉例

曾經有一段時間,注冊表修改和優化的工具層出不窮,如魔法兔子、俠客系統修改器和Windows優化大師等都是這方面的典型軟件。看了前面的注冊表函數后,讀者現在一定知道,編寫這些軟件的大部分時間是花在界面設計和收集注冊表的說明資料上,在這些工作的背后,使用的就是這么幾個注冊表函數。

本節用幾個簡單的例子來演示一些涉及注冊表操作的常見應用,讀者在收集到足夠的注冊表說明資料后,參考這些例子就可以寫出類似的應用程序來。

1.設置開機自動運行

Windows在啟動并執行登錄操作后,會將HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run子鍵下的所有鍵值項枚舉一遍,并將所有REG_SZ類型的鍵值數據字符串當做一個文件名自動執行,所以在這個子鍵下設置一個鍵值項,讓它的鍵值數據是某個文件名字符串(也可以是數據文件名,Windows會自動打開關聯的可執行文件),就可以讓這個文件在Windows啟動后自動運行。

由于Windows只關心鍵值數據,并不關心鍵值名稱,所以在設置的時候只要保證鍵值名稱是唯一的就可以了。下面的_SetAutoRun子程序就可以用來將程序設置為自動運行:

.const
szKeyAutoRun   db 'Software\Microsoft\Windows\CurrentVersion\Run',0
szValueAutoRun db 'AutoRun Test',0   ;在不同程序中使用時修改此字符串!.code... ...
_SetAutoRun proc _dwFlaglocal  @szFileName[MAX_PATH]:byte.if _dwFlaginvoke  GetModuleFileName,NULL,addr @szFileName,MAX_PATHinc      eaxinvoke  _RegSetValue,addr szKeyAutoRun,addr szValueAutoRun,\addr @szFileName,REG_SZ,eax.elseinvoke  _RegDelValue,addr szKeyAutoRun,addr szValueAutoRun.endifret
_SetAutoRun endp

當用TRUE參數調用_SetAutoRun子程序時,程序將被設置為自動運行,這時子程序先用GetModuleFileName函數獲取當前執行文件的文件名,然后用.const段中定義的szValueAutoRun字符串當做鍵值項的名稱,用文件名當做鍵值數據在Run鍵下設置一個鍵值項。當用FALSE參數調用的時候,程序將取消自動運行,這時子程序僅簡單地將前面設置的鍵值項刪除而已。

代碼中用到的幾個子程序在15.3.2小節中列出的_Reg.asm文件中。

2.設置文件關聯

如果將一個數據文件與一個可執行文件關聯,那么就可以通過雙擊數據文件來直接執行可執行文件,比如,雙擊以txt為擴展名的文本文件,系統就會自動執行Notepad.exe文件來編輯它,這就是因為txt文件是與Notepad.exe文件關聯的。

文件關聯是在注冊表的HKEY_CLASSES_ROOT根鍵中設置的。要為某種擴展名設置關聯,需要在HKEY_CLASSES_ROOT根鍵下設置兩個子鍵,第一個子鍵的名稱是“.擴展名”(和數據文件的擴展名相對應),這個子鍵需要設置默認值,默認值的數據為HKEY_CLASSES_ROOT根鍵下另一個子鍵的名稱,在這個子鍵下可以繼續設置與這種數據文件關聯的可執行文件名。

如果關聯的操作方式是“打開”,那么在第二個子鍵中,可以繼續創建“shell\open\command”子鍵,并把command子鍵的默認值設為可執行文件名,這樣雙擊數據文件,就會執行這個可執行文件;如果關聯的操作方式是“打印”,那么可以在第二個子鍵中繼續創建“shell\print\command”子鍵,同樣將command子鍵的默認值設為執行打印操作的可執行文件名。當然,也可以只設置“open”操作的關聯。經過這兩個步驟,文件關聯就設置好了。

以“*.test”數據文件為例,要在HKEY_CLASSES_ROOT根鍵下創建一個“.test”子鍵,把它的默認值設置為“testfile”,然后再創建一個“testfile\shell\open\command”子鍵,并把command鍵的默認值設置為指定的可執行文件名。

下面這段代碼就是將“*.test”數據文件與當前可執行文件關聯的子程序,調用_SetExt子程序就可以完成這個功能:

.const
szKeyEnter       db       'testfile',0
szKeyExt1        db       '.test',0
szKeyExt2        db       'testfile\shell\open\command',0
szParam          db       ' "%1"',0.data... ..._SetExt proclocal   @hKeylocal   @szFileName[MAX_PATH]:byteinvoke   RegCreateKey,HKEY_CLASSES_ROOT,addr szKeyExt1,addr @hKey.if eax == ERROR_SUCCESSinvoke  RegSetValueEx,@hKey,NULL,NULL,\REG_SZ,addr szKeyEnter,sizeof szKeyEnterinvoke  RegCloseKey,@hKey.endifinvoke RegCreateKey, HKEY_CLASSES_ROOT, addr szKeyExt2, addr @hKey.if eax == ERROR_SUCCESSinvoke GetModuleFileName,NULL,addr @szFileName,MAX_PATHinvoke lstrcat,addr @szFileName,addr szParaminvoke lstrlen,addr @szFileNameinc eaxinvoke RegSetValueEx,@hKey,NULL,NULL,\REG_EXPAND_SZ,addr @szFileName,eaxinvoke RegCloseKey,@hKey.endifret
_SetExt endp

子程序的第一部分創建了“.test”子鍵,第二部分創建了“testfile\shell\open\command”子鍵,注意在可執行文件名字符串后要加上“%1”字符串,這樣系統才會將數據文件的文件名放在命令行參數中傳給可執行文件。由于系統必須將“%1”字符串替換成數據文件名,所以command子鍵默認值的數據類型被定義為REG_EXPAND_SZ類型。

由于在兩次RegSetValueEx函數的調用中設置的都是子鍵的默認值,所以函數的lpValueName參數都被指定為NULL。

在所附光盤的Chapter15\Associate目錄中有一個將“*.test”文件與Associate.exe文件關聯的例子,這個例子是在第13章中的Cmdline例子中添加_SetExt子程序代碼形成的。讀者可以仔細分析源程序并執行一下,執行文件以后再雙擊目錄中的Hello.test文件,就會發現Associate.exe文件被執行了,而且文件顯示的第二個參數為“Hello.test”,這表示對“*.test”文件的關聯生效了!

要撤銷關聯,只要進行逆向操作—將“.test”子鍵和“testfile\shell\open\command”子鍵刪除即可。

【完整代碼筆記】

; Associate.asm
; 設置注冊表將 *.test 文件管理到本程序
; 程序從 Cmdline.asm 修改而來
; -------------------------------------------------------------------
; 使用 nmake 或下列命令進行編譯和鏈接:
; ml /c /coff Associate.asm
; Link /subsystem:windows Associate.obj
.386
.model flat,stdcall 
option casemap:none ; include 文件定義
include 	c:/masm32/include/windows.inc 
include 	c:/masm32/include/user32.inc 
include 	c:/masm32/include/kernel32.inc 
include 	c:/masm32/include/Advapi32.inc 
includelib 	c:/masm32/lib/user32.lib 
includelib 	c:/masm32/lib/kernel32.lib 
includelib 	c:/masm32/lib/Advapi32.lib ; 數據段
.data?
szBuffer1 	byte 4096 dup(?)
szBuffer2 	byte 4096 dup(?)
szOutput 	byte 8192 dup(?).const 
szCaption 	byte '命令行參數',0
szFormat1 	byte '*.test 文件的關聯被設置到本程序',0dh,0ahbyte '請雙擊目錄中的Hello.test文件進行測試! 并注意下面的參數[1]',0dh,0ah,0ahbyte '可執行文件名稱:',0dh,0ah,'%s',0dh,0ah,0ahbyte '參數總數:%d',0dh,0ah,0
szFormat2 	byte '參數[%d]:%s',0dh,0ah,0
szKeyEnter	byte 'testfile',0
szKeyExt1	byte '.test',0
szKeyExt2 	byte 'testfile\shell\open\command',0
szParam 	byte ' "%1"',0; 代碼段
.code include _Cmdline.asm_SetExt proc local @hKey local @szFileName[MAX_PATH]:byte invoke RegCreateKey, HKEY_CLASSES_ROOT, addr szKeyExt1, addr @hKey .if eax == ERROR_SUCCESS invoke RegSetValueEx, @hKey, NULL, NULL, \REG_SZ, addr szKeyEnter, sizeof szKeyEnter invoke RegCloseKey, @hKey .endif invoke RegCreateKey, HKEY_CLASSES_ROOT, addr szKeyExt2, addr @hKey .if eax == ERROR_SUCCESS invoke GetModuleFileName, NULL, addr @szFileName, MAX_PATH invoke lstrcat, addr @szFileName, addr szParam invoke lstrlen, addr @szFileName inc eax invoke RegSetValueEx, @hKey, NULL, NULL, \REG_EXPAND_SZ, addr @szFileName, eax invoke RegCloseKey, @hKey .endif ret 
_SetExt endp main proc invoke _SetExt invoke GetModuleFileName, NULL, offset szBuffer1, sizeof szBuffer1 invoke _argc mov ebx, eax invoke wsprintf, addr szOutput, addr szFormat1, addr szBuffer1, eax xor esi, esi .while esi < ebx invoke _argv, esi, addr szBuffer2, sizeof szBuffer2 invoke wsprintf, addr szBuffer1, addr szFormat2, esi, addr szBuffer2 invoke lstrcat, addr szOutput, addr szBuffer1 inc esi .endw invoke MessageBox, NULL, addr szOutput, addr szCaption, MB_OK invoke ExitProcess, 0
main endp 
end main 

命令行參數分析的通用子程序

; _CmdLine.asm
; 命令行參數分析的通用子程序
; 功能:
; _argc ---> 對命令行參數進行數量統計
; _argv ---> 取某個命令行參數
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
CHAR_BLANK	equ	20h	;定義空格
CHAR_DELI	equ	'"'	;定義分隔符
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 取命令行參數個數 (arg count)
; 參數個數必定大于等于 1, 參數 1 為當前執行文件名
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_argc		proclocal	@dwArgcpushadmov	@dwArgc,0invoke	GetCommandLinemov	esi,eaxcld
_argc_loop:
;********************************************************************
; 忽略參數之間的空格
;********************************************************************lodsbor	al,aljz	_argc_endcmp	al,CHAR_BLANKjz	_argc_loop
;********************************************************************
; 一個參數開始
;********************************************************************dec	esiinc	@dwArgc
_argc_loop1:lodsbor	al,aljz	_argc_endcmp	al,CHAR_BLANKjz	_argc_loop		;參數結束cmp	al,CHAR_DELIjnz	_argc_loop1		;繼續處理參數內容
;********************************************************************
; 如果一個參數中的一部分有空格,則用 " " 包括
;********************************************************************@@:lodsbor	al,aljz	_argc_endcmp	al,CHAR_DELIjnz	@Bjmp	_argc_loop1
_argc_end:popadmov	eax,@dwArgcret_argc		endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 取指定位置的命令行參數
;  argv 0 = 執行文件名
;  argv 1 = 參數1 ...
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_argv		proc	_dwArgv,_lpReturn,_dwSizelocal	@dwArgv,@dwFlagpushadinc	_dwArgvmov	@dwArgv,0mov	edi,_lpReturninvoke	GetCommandLinemov	esi,eaxcld
_argv_loop:
;********************************************************************
; 忽略參數之間的空格
;********************************************************************lodsbor	al,aljz	_argv_endcmp	al,CHAR_BLANKjz	_argv_loop
;********************************************************************
; 一個參數開始
; 如果和要求的參數符合,則開始復制到返回緩沖區
;********************************************************************dec	esiinc	@dwArgvmov	@dwFlag,FALSEmov	eax,_dwArgvcmp	eax,@dwArgvjnz	@Fmov	@dwFlag,TRUE@@:
_argv_loop1:lodsbor	al,aljz	_argv_endcmp	al,CHAR_BLANKjz	_argv_loop		;參數結束cmp	al,CHAR_DELIjz	_argv_loop2cmp	_dwSize,1jle	@Fcmp	@dwFlag,TRUEjne	@Fstosbdec	_dwSize@@:jmp	_argv_loop1		;繼續處理參數內容_argv_loop2:lodsbor	al,aljz	_argv_endcmp	al,CHAR_DELIjz	_argv_loop1cmp	_dwSize,1jle	@Fcmp	@dwFlag,TRUEjne	@Fstosbdec	_dwSize@@:jmp	_argv_loop2
_argv_end:xor	al,alstosbpopadret_argv		endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

運行結果:

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/96464.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/96464.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/96464.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

JDK 17、OpenJDK 17、Oracle JDK 17 的說明

Java生態系統的核心概念&#xff1a;簡單來說&#xff1a;JDK 17 是一個標準規范&#xff0c;定義了Java開發工具包第17個長期支持版應該包含什么功能。openjdk-17-jdk 是一個具體的實現&#xff0c;是遵循上述規范、由OpenJDK社區提供的開源軟件包。下面我們通過一個表格和詳細…

手寫MyBatis第58彈:如何優雅輸出可執行的SQL語句--深入理解MyBatis日志機制:

&#x1f942;(???)您的點贊&#x1f44d;?評論&#x1f4dd;?收藏?是作者創作的最大動力&#x1f91e; &#x1f496;&#x1f4d5;&#x1f389;&#x1f525; 支持我&#xff1a;點贊&#x1f44d;收藏??留言&#x1f4dd;歡迎留言討論 &#x1f525;&#x1f525;&…

Spring Boot 監控實戰:集成 Prometheus 與 Grafana,打造全方位監控體系

前言 在當今微服務架構盛行的時代&#xff0c;應用程序的監控變得尤為重要。Spring Boot 作為廣泛使用的微服務框架&#xff0c;其監控需求也日益增加。Prometheus 和 Grafana 作為開源監控領域的佼佼者&#xff0c;為 Spring Boot 應用提供了強大的監控能力。本文將詳細介紹如…

JS中的多線程——Web Worker

眾所周知&#xff0c;JavaScript 是單線程運行的&#xff08;至于為什么是單線程可以看一下這篇文章——事件循環機制&#xff09;&#xff0c;當瀏覽器主線程被大量計算任務阻塞時&#xff0c;頁面就會出現明顯的卡頓現象。Web Worker 提供了在獨立線程中運行 JavaScript 的能…

【SQL注入】延時盲注

sleep(n)??: 核心延時函數。使數據庫程序暫停 n秒。??if(condition, true_expr, false_expr)??: 條件判斷函數。如果 condition為真&#xff0c;執行 true_expr&#xff0c;否則執行 false_expr。??用于將延時與判斷條件綁定??。??mid(a, b, c)??: 字符串截取函數…

IntelliJ IDEA 2025.1 Java Stream Debugger 快速使用指南

1. 功能概覽 Java Stream Debugger 提供 Trace Current Stream Chain 功能&#xff0c;用來在調試時分析和可視化 Stream 操作鏈。 主要用途&#xff1a; 在運行時查看流操作鏈的每一步輸出找出 map/filter 等操作的問題避免手動加 peek() 打印調試2. 使用入口 在 IDEA 2025.1 …

ARM-指令集全解析:從基礎到高階應用

一、ARM 指令集體系結構版本ARM 公司定義了多個指令集版本&#xff1a;ARMv1&#xff1a;原型機 ARM1&#xff0c;沒有用于商業產品。ARMv2&#xff1a;擴展 V1&#xff0c;包含 32 位乘法指令和協處理器指令。ARMv3&#xff1a;第一個微處理器 ARM6 核心&#xff0c;支持 Cach…

第3講 機器學習入門指南

近年來&#xff0c;隨著企業和個人生成的數據量呈指數級增長&#xff0c;機器學習已成為日益重要的技術領域。從自動駕駛汽車到流媒體平臺的個性化推薦&#xff0c;機器學習算法已廣泛應用于各個場景。讓我們深入解析機器學習的核心要義。3.1 機器學習定義機器學習是人工智能的…

深入理解跳表:多層索引加速查找的經典實現

跳表&#xff08;Skip List&#xff09;是一種多層有序鏈表結構&#xff0c;通過引入多級索引加速查找&#xff0c;其核心設計類似于“立體高速公路系統”&#xff0c;底層是原始鏈表&#xff0c;上面有各種高度的"高架橋"。 高層道路跨度大&#xff0c;連接遠方節點…

Flutter 視頻播放器——flick_video_player 介紹與使用

在移動端應用中&#xff0c;視頻播放是一個常見的功能場景&#xff0c;例如短視頻、直播、課程、廣告展示等。 Flutter 本身并沒有直接提供視頻播放器組件&#xff0c;而是依賴第三方庫來實現。 今天要介紹的庫是 flick_video_player&#xff0c;它基于 video_player 封裝&…

編寫cmakelists文件常用語句

cmake_minimum_required (VERSION 3.10) 指定最小版本project(XXXX) 指定項目名字 ---------------set(MAIN_EXEC_NAME dwarf_parser) 定義變量${ MAIN_EXEC_NAME } 變量取值set(CMAKE_CXX_STANDARD 14) 指定c14標準&#xff0c;還有11、17、20等標準…

麒麟桌面系統找不到mbr啟動,并重新安裝grub

根據你提供的情況,“麒麟桌面系統找不到MBR啟動”,這通常是由于GRUB引導損壞、MBR記錄丟失或分區表異常導致的。你可以按照以下步驟重新安裝GRUB并修復MBR啟動: ? 步驟一:準備工具 使用銀河麒麟LiveCD或U盤啟動盤(可用Ventoy制作); 啟動電腦,選擇從U盤或光盤進入Live環…

【音頻字幕】構建一個離線視頻字幕生成系統:使用 WhisperX 和 Faster-Whisper 的 Python 實現

一、背景介紹 對于一端沒有字幕外國視頻、字幕&#xff0c;在不懂外語的情況下&#xff0c;怎么獲取相關內容&#xff1f;作為技術宅&#xff0c;怎么自建搭建一個語音轉文字的環境當前AI技術這么發達&#xff1f; 試試 二、系統設計 音頻提取(僅僅是視頻需要該邏輯、本身就是音…

Linux ALSA架構:PCM_OPEN流程 (二)

一 應用端源碼路徑: external\tinyalsa\pcm.c external\tinyalsa\pcm_hw.cstruct pcm *pcm_open(unsigned int card, unsigned int device,unsigned int flags, struct pcm_config *config) {...pcm->ops &hw_ops;pcm->fd pcm->ops->open(card, device,…

tp5的tbmember表閉包查詢 openid=‘abc‘ 并且(wx_unionid=null或者wx_unionid=‘‘)

閉包查詢 tbmember表閉包查詢查詢 openid‘abc并且islose0并且islogout0并且&#xff08;wx_unionidnull或者wx_unionid’&#xff09; Db::table(tbmember)->where([openid>abc,islose>0,islogout>0])->where(function ($query){$query->where(wx_unioni…

邪修實戰系列(3)

1、第一階段邪修實戰總覽&#xff08;9.1-9.30&#xff09; 把第一階段&#xff08;基礎夯實期&#xff09;的學習計劃拆解成極具操作性的每日行動方案。這個計劃充分利用我“在職學習”的特殊優勢&#xff0c;強調“用輸出倒逼輸入”&#xff0c;確保每一分鐘的學習都直接服務…

【GD32】ROM Bootloader、自定義Bootloader區別

Bootloader是應用程序跑起來之前&#xff0c;用于初始化的一段程序&#xff0c;它分為兩種&#xff0c;ROM Bootloader、自定義Bootloader。GD32芯片出廠時預燒錄在ROM中的Bootloader&#xff08;以下簡稱ROM Bootloader&#xff09;和自己編寫的Bootloader&#xff08;以下簡稱…

Linux防火墻-Firewalld

一、 概述 按表現形式劃分&#xff1a; 軟件防火墻&#xff1a; 集成在系統內部&#xff0c;Linux系統&#xff1a; iptables、firewalld、ufw&#xff1b; windows系統下&#xff1a; windows defender 硬件防火墻&#xff1a; 華為防火墻、思科防火墻、奇安信防火墻、深信服防…

【Qt】PyQt、原生QT、PySide6三者的多方面比較

目錄 引言 一、基本定義 二、核心對比維度 1. 編程語言與開發效率 2. 功能與 API 兼容性 3. 性能表現 4. 許可證與商業使用 5. 社區與文檔支持 三、遷移與兼容性 四、適用場景推薦 五、總結對比表 總結 引言 PySide6、PyQt&#xff08;通常指 PyQt5/PyQt6&#xf…

JavaWeb站內信系統 - 技術設計文檔

1. 系統概述1.1 項目背景本系統旨在為企業或社區平臺提供一套完整的站內信解決方案&#xff0c;支持用戶之間的消息發送、接收、管理等功能&#xff0c;提升用戶間的溝通效率。1.2 設計目標實現用戶間消息發送和接收支持一對一和一對多消息發送提供消息狀態跟蹤&#xff08;已讀…