C語言、windows程序設計、彙編筆記(很有用哦)
1. ●●●●●●●C語言程序設計●●●
● \n只代表一個字符。類似於\n的轉義字符序列爲表示無法輸入的字符或不可見
字符提供了一種通用的可擴充的機制。除此之外,C 語言提供的轉義字符序列還包括:\t 表
示製表符;\b 表示回退符;\"表示雙引號;\\表示反斜槓符本身
● 符號常量:用標示符代表一個常量。在C語言中,可以用一個標識符來表示一個常量,
稱之爲符號常量。
符號常量在使用之前必須先定義,也叫具名常量,其一般形式爲:
#define 標識符(常量名) 常量值
直接常量:直接常量是指在程序中,以直接明顯的形式給出的數據。根據使用的數據類型不同,可分爲:字符串常量、數值常量、邏輯常量和日期常量。
字符串常量:是用兩個雙引號括起來的一串字符。例如:"2345"和"Basic"等。
數值常量:數值常量就是常數,包括整數、長整數、單精度數和雙精度數。例如:2006、3.14159265、2.9D67等。
邏輯常量:邏輯常量只有True或False兩個值。
日期常量:用兩個【#】把表示日期和時間的值括起來表示日期常量。
● :數據類型[32位系統] char(1字節) bool(1字節) short(2字節) WORD(2字節) DWORD(4字節) int(4字節) LONG(4字節) float(4字節) double(8字節)
基本的數據類型默認情況下是signed"有符號的",無符號表示unsigned 數據類型
***變量使用之前必須要先定義變量。一般情況下,都會在變量定義語句之後才使用變量;若一定要在變量定義語句之前使用變量,也必須藉助關鍵字extern來聲明變量。使用extern聲明變量的形式如下:extern 數據類型名 變量名;
extern 修飾的變量,表示變量或者函數的定義在別的文件中,提示編譯器遇到此變量和函數時在其他模塊中尋找其定義,例如系統服務描述表:
首先定義這個表的結構:結構名(類型名)不一定非要和系統的一致,只要成員類型以及成員名一致即可
typedef struct SYSTEM_DESCRIPTOR_TABLE
{
PVOID ServiceTableBase;
PVOID ServiceCounterTable;
unsigned int NumberOfServices;
PVOID ParamTableBase;
}SYSTEMSERVICEDESCRIPTORTABLE,*PSYSTEMSERVICEDESCRIPTORTABLE
這個結構名是自己定義的,若要與系統的服務描述錶鏈接起來,則要用extern,系統服務描述表併爲導出,只是導出了一個指針KeServiceDescriptorTable,只要把我們定義的結構地址指向這個指針即可:
extern PSYSTEMSERVICEDESCRIPTORTABLE KeServiceDescriptorTable;
● 地址操作符只能使用變量作爲操作數,不能將其用在常量前面,要取得一個地址中的內容,可以使用指針操作符*。指針操作符的操作對象必須爲地址
● 當指針指向直接常量時, 直接常量應該用雙引號而不能用單引號,因爲單引號是一個char型的字符, 不是直接常量.所以必須取地址,賦值的時候並不是把字符串賦值給指針,而是把字符串的首地址賦給指針.
● 指針和引用的區別:1.指針佔四個字節, 引用不佔 2.引用申明時必須初始化, 指針不用
/*
*p++ 等價於 *(p++); 先算出*p, 然後p自加
*++p 等價於 *(++p); 先p自加,然後計算*p
++*p *P值自加
*/
● 當多個賦值操作符存在於同一條語句中時,按照C標準的規定,將會按照從右到左的結合方式先執行最右邊的賦值操作符,再依次向左執行其餘賦值操作符
● 當表達式不僅僅是一個變量或一個常量時,需要把表達式放在小括號內。例如:m = (int) (3.7 + 2.5)這時,程序會對3.7和2.5的和類型轉換爲int型。如果沒有括號,只是:m = (int) 3.7 + 2.5
● 數值運算中的類型轉換遵從以下規則:char型和short型數據,不管是unsigned還是usigned,在計算中都要先無條件轉換爲int型,即使是兩個char型或short型數值相加。float型計算前會無條件轉換爲double型數值,以增加精度。其餘類型的數值計算時,都要轉換爲數據類型級別較高的後再計算。
● itoa( //轉換到字符串( Integer To A String)
int nsro, //要被轉換的整數
char *buffer //轉換後存放的緩衝區
int rudix //以何種進制形式輸出
)
int atoi(const char *string) 把字符串轉換到整數型
● 3個邏輯操作符組合使用時,可以得到多個功能一致但形式不同的邏輯表達式。使用簡單的邏輯表達式來代替複雜的表達式是提高程序邏輯性的一個重要方法。下面是兩組功能一樣的邏輯表達式:第一組表達式如下:!(a || b)!a && !b第二組表達式如下:!(a && b)!a || !b使用以上功能相同的兩組邏輯表達式可以對一些複雜的、邏輯不清的表達式進行簡化。
● 前置操作符都是一元操作符,其優先級爲2,爲第二高。很容易理解將它們得結合性規定爲從右到左的原因,例如:+-+a;-++b ;!sizeof(c)按結合性從右到左,等效於:+(-(+a));-(++b);!(sizeof(c));
● switch語句是C語言中選擇結構的另一個常用的實現方式,十分適用於多路選擇的實現,break語句在switch語句中的作用十分重要。在switch語句中,遇到break便終止執行switch語句,跳出本層switch體,繼續執行後續語句,continue語句只能使用在for語句、while語句和do-while語句的循環體中。其功能爲結束本次循環,直接開始下一次循環。goto語句在C語句中可以實現無條件跳轉,通過和if語句的有效組合也可以實現循環結構。
● For語句的標準形式如下:for ( 表達式1; 表達式2; 表達式3) 表達式1用作循環結構的初始化,一般爲賦值表達式,設定循環變量及其他變量的初始值;表達式2負責循環條件的判斷,形式與if語句的控制表達式類似,一般爲關係表達式或邏輯表達式;表達式3負責改變表達式2中的循環變量的值,一般也爲有賦值作用的表達式。for ( 初始化表達式; 循環判斷表達式; 循環變量控制表達式 ));while ( 表達式 )
● 初始化數組元素的部分必須是數組中從第0個元素開始的連續序列。例如,以下初始化語句是錯誤的:int window[6] = {1, , 3, 4, 5};/* 第1個元素未賦值 */和其他類型變量一樣,數組元素也具有地址。數組在內存中是作爲一個整體分配內存的,數組元素的內存地址是連續的,其差值爲數組存儲的數據類型的字節長度值。如果存儲的是int型,那麼數組元素的地址都相差4;如果是char型,那麼數組元素的地址都相差1。比較特殊的是,數組變量的值是該數組的第0個元素的地址,即數組的首地址。C語言中的二維數組可以理解爲一種特殊的一維數組,可以看作數組元素爲一維數組的一維數組。爲了方便理解,可以把二維數組看作是一個矩陣,一維容量爲行數,二維容量爲列數。matrix[i][j]即爲matrix中第i行第j列的元素;推廣到一般情況,對於:數據類型名 array[M][N];array[i][j]在其內存中爲第(i×N+j)個元素。同樣的,二維數組初始化也可以不指定數組容量,但是隻能不指定一維數組容量(第一個數組容量),二維數組容量(第二個數組容量)必須給出。字符串常量在C語言中被處理爲一維字符數組存儲在內存中一塊連續的區域內。
● 數組的特點:
1, 連續的內存分配
2, 可以通過下標進行訪問
3, 數組的名字就是數組的首地址
4, 數組不能動態改變大小
5, 數組在中間或前端插入或刪除一個元素效率低
鏈表特點:
1, 不連續的內存分配
2, 不能通過下標進行訪問
3, 鏈表可以動態改變其長度
4, 鏈表在前面或中間插入元素, 速度快
● 函數聲明也可以稱爲函數原型,定義了函數作爲模塊化編程的基本單元的接口:函數值類型對應模塊出口,函數名對應模塊名,參數列表對應了模塊入口。函數返回值類型也稱爲函數值類型,是由函數帶回的值的類型。與變量一樣,函數的聲明和定義也是有嚴格區別的。函數聲明確定了一個函數的接口,告訴編譯器該函數的函數名、函數值類型以及形參列表中形參的個數和順序;而函數定義則確立了一個函數的功能,不僅僅包含了函數聲明所有的信息,還包含了形參的名字和函數體。關鍵字extern和static可以用來聲明函數,使用extern聲明的函數爲外部函數.C語言中規定,所有函數默認聲明爲extern類別,即外部函數。也就是說在之前的所有例子中定義的函數都是外部函數。
● C語言中定義了4個關鍵字作爲變量的存儲類別的修飾詞,分別爲:auto、static、register和extern。變量的存儲類別決定了變量在內存中的存儲區域。1. 棧是由編譯器管理的動態存儲區域,用於存儲臨時變量,即只在需要時才被分配內存,不需要時編譯器會自動回收。可存放的數據包括以下幾項。函數形參:其只在函數執行期內有效。局部變量(不包括static修飾的局部變量):只在它定義的程序塊及其下層程序塊的執行期內有效。其他臨時變量(例如,a++語句中產生的臨時變量):函數返回時產生的臨時變量。2. 堆是由程序管理的動態存儲區域,用於分配由程序使用malloc函數申請的內存空間,需要由程序自行釋放。在內存管理一章中將討論這塊存儲區域的使用。堆上分配的內存也是未經初始化的,需要程序顯式地初始化。3. 靜態存儲區用於存儲全局變量,該區域的內存在程序開始時就已固定分配完畢,直到程序結束由編譯器自動釋放,在該區域分配的內存在整個程序執行過程中都是有效的。全局變量全部存放在靜態存儲區中。靜態存儲區的內存分配時由編譯器自動初始化。auto變量的內存由編譯器自動從棧上分配,因此auto變量都是臨時變量。使用auto聲明變量的形式十分簡單,如下所示。auto 數據類型 變量名;或數據類型 auto 變量名,所有的局部變量的聲明中如果不含存儲類別,那麼都默認爲auto型變量。C語言中提供了一種存儲類別register,這種存儲類別的變量的值會被要求直接存儲在寄存器中。訪問該變量無需從內存中獲取它的值,存儲該變量時也無需再存回內存,都直接在寄存器上進行操作。由於寄存器的存取速度要遠快於內存的存取速度,因此,如果一個該變量在程序中被頻繁使用,那麼將其聲明爲register變量,將大大提高程序的執行效率。register型的變量聲明如下所示。register 數據類型名 變量名;register只能修飾函數內的變量,包括局部變量和形式參數C程序中的所有變量都有一定的生存期和作用域。生存期是指程序運行時,變量佔有內存的時間。變量作用域是指在程序中,變量可以被使用的有效代碼區域。爲了便於討論變量的作用域,按程序塊間的關係將程序塊分爲4類:本層程序塊、上層程序塊、下層程序塊和外部程序塊。如果局部變量沒有被顯式地初始化,編譯器不會自動爲其清理內存;如果全局變量沒有爲其顯式初始化,編譯器則會自動初始化,將其內存空間清除歸零。
● new 是一個運算符, 它返回一個分配在堆空間的內存地址,所以 = 左邊必須是指針類型
● 直接從空間的地址獲取該內存內容的訪問方式叫做內存的“直接訪問”。先從其他內存空間獲得要訪問的內存空間的地址,再根據該地址訪問目的空間的方法就是內存的“間接訪問”。
● 在操作系統中,數值一律用補碼來存儲。一個數值的二進制值可以稱其爲原碼,存儲時會將原碼錶示爲補碼。補碼的最高位爲符號位,數值的補碼錶示可以分爲以下兩種情況:1.非負數的補碼,非負數的補碼與原碼相同。例如,11的原碼爲00001011,其補碼也爲00001011。2.負數的補碼負數的補碼的符號位爲1,其餘位爲將該數絕對值的原碼按位取反後再加1的結果。例如,-15的補碼:因爲是負數,則符號位爲“1”,整個爲10001111;其餘7位爲-15的絕對值的原碼按位取反,即0001111取反後爲1110000,再加1爲1110001;加上符號位,最後-15的補碼是11110001。已知一個數的補碼,其求原碼的過程與已知原碼求補碼的過程完全一樣:如果補碼的符號位爲“0”,表示是一個正數,所以補碼就是該數的原碼。如果補碼的符號位爲“1”,表示是一個負數,求原碼的操作可以是:符號位爲1,其餘各位取反加1。使用補碼計算時,可以將符號位和其他位統一處理。注意:兩個用補碼錶示的數相加時,如果最高位(符號位)有進位,則進位被捨棄。減法可按加負數法來處理。C語言共提供了6個位運算操作符,包括取反操作符(~)、位或操作符(|)、位與操作符(&)、異或操作符(^)和位移操作符(>>和<<)取反操作符是一個一元操作符,其使用形式爲:~操作數;位或操作符是一個二元操作符,形式如下:數1 | 數2兩個位值進行位或運算的規則是:只要有一個數值爲1,位或的結果便爲1;如果都爲0,位或的結果爲0。位與操作符也是一個二元操作符,形式如下:數1 & 數2這兩個數可以是常量,也可以是變量。位與操作符將兩個操作數逐位進行位與運算。位與運算的規則是:只要有一個數值爲0,位與的結果便爲0;如果都爲1,位與的結果爲1。異或操作符是多元操作符,使用形式如下:數1 ^ 數2表達式將數1和數2進行異或運算,如果兩個數一樣,則值爲0;如果不一樣,值爲1。右移操作符的作用是將一個數的各二進制值全部右移若干位。其使用形式如下:數值1 >> 數值2;該表達式將數值1的二進制值右移(數值2)位,移到右端的低位被捨棄,而高位補入的數值由符號位決定。如果是無符號數,則高位補入的數爲0。左移操作符將一個數的各個二進制位值全部左移若干位。使用形式如下:數值1 << 數值2;該表達式將數值1的二進制值左移(數值2)位,移到左端的高位被捨棄,而低位補入的數值爲0。將a左移1位,即等於將a乘以2;將a左移n位,即等於將a乘以2的n次方。
● 由於結構體數據類型的名字由標識符和結構體名兩部分組成,書寫起來名字較長,因此常常使用typedef來簡化其數據類型名字,如下所示。一般的系統中,爲了尋址的方便,數據在內存中存儲時一般以其本身數據類型的字節長度爲基本單位對齊。例如,int型在內存中是以sizeof(int)個字節對齊的,char型在內存中是以sizeof(char)個字節對齊的。而結構體數據在存儲時是以其中字節長度最大的成員的字節數爲基本單位對齊的。所謂對齊,是指將內存以一個固定的字節數作爲最小單位分塊,分配內存時只能一塊一塊地分配。例如,以4字節對齊,就是將整個內存4個字節4個字節地分塊,分配內存時只能4個字節4個字節地分。可以使用#pragma pack預處理命令來改變對齊規則。由於共用體實際上只有一個有效成員,因此無法像初始化結構體那樣使用多個值的序列對共用體進行初始化,只能使用含一個值的序列對其初始化,由於共用體中的所有成員共享一塊空間,因此對任意成員的賦值都會影響其他成員的值。由於宏函數體只能是與宏函數同一行的後面的內容,因此當函數體內容較多時,如果都寫在一行中,會影響程序的可讀性。爲使程序邏輯清晰,可以使用分行符“\”將宏函數體分拆爲多行。如果一個宏函數需要返回一個值,即可能作爲一個語句的子表達式,應該將整個宏函數體放在一個括號內;如果宏函數只執行一些操作,不返回值,則應該將整個函數體放在花括號內,形成單獨一個程序塊。
● 結構體和類唯一區別, 結構體成員默認情況下是公有的(public),C語言中結構體不能寫函數
● 一般情況下,C程序的所有代碼都會被編譯器編譯。C語言提供了條件編譯使程序員能設定需要被編譯的代碼。條件編譯通過條件判斷的方法來實現編譯代碼的選擇。當滿足一些條件時,編譯纔會編譯其指定的代碼。本節將討論幾種條件編譯命令的使用。#ifdef命令的形式有三種,如下所示:1.形式一#ifdef 標識符程序段1#else程序段2#endif。#ifndef命令和#ifdef命令剛好相反。該形式的#ifndef命令實現了程序段1和程序段2的選擇編譯功能,即:如果程序未宏定義標識符,則編譯程序段1;如果該標識符已被宏定義,則對程序段2進行編譯。
2. ● ● ● ● Windows程序設計● ● ● ●
● 表頭文件
WINDOWS.H是主要的引入文件,它包含了其它Windows表頭文件,這些表頭文件的某些也包含了其它表頭文件。這些表頭文件中最重要的和最基本的是:
WINDEF.H 基本型態定義。
WINNT.H 支持Unicode的型態定義。
WINBASE.H 系統核心函數。
WINUSER.H 用戶接口函數。
WINGDI.H 圖形設備接口函數。
int WINAPI WinMain(
HINSTANCE hInstance, // 當前應用程序實例句柄,由系統提供
HINSTANCE hPrevInstance, // 上一個實例句柄 ,CreateMutex可創建實例互斥體
LPSTR lpCmdLine, // cmd命令行,可以使用 GetCommandLine獲得完整的命令行參數
int nCmdShow // 窗口顯示方式,以SW_XXXX開始,例:SW_NORMAL 默認顯示方式
);
WinMain中第三個參數在WINBASE.H中定義爲LPSTR,我將它改爲PSTR。這兩種數據型態都定義在WINNT.H中,作爲指向字符串的指針。LP前綴代表「長指針」,這是16位Windows下的產物。在WinMain聲明中改變了兩個參數的名稱。許Windows程序中的變量名使用一種稱作「匈牙利表示法」的命名系統,該系統在變量名稱前面增加了表示變量數據型態的短前綴,現在僅需記住前綴i表示int、sz表示String of Zero「以零結束的字符串」。
-----------------------------------------------------------------------------------------------
● //-Unicode的簡介
***寬字符和C語言
C中的寬字符基於wchar_t數據型態,它在幾個表頭文件包括WCHAR.H中都有定義,像這樣:typedef unsigned short wchar_t ;因此,wchar_t數據型態與無符號短整數型態相同,都是16位寬。
strlen接受的是char類型字符,如果接受Unicode字符,編譯器會把每個字符都分配爲兩個字節,如果第一個字符爲字母,則被分配爲這個字母的ASCII字符和0,如果此時再次用strlen()命令將返回爲1[第二個字符爲0則結束],並不會返回定義的Unicode字符的長度.strlen函數的寬字符版是wcslen(wide-character string length:寬字符串長度),而wcslen函數則說明如下:size_t __cdecl wcslen (const wchar_t *) ;size_t在stddef.h中定義:typedef unsigned int size_t;TCHAR.H還用一個新的數據型態TCHAR來解決兩種字符數據型態的問題.
if ANSI:typedef char CHAR ;
if UNICODE:typedef wchar_t WCHAR ;
---------------------------------------------------------------------------
#ifdef ANSI
typedef CHAR * PCHAR, * LPCH, * PCH, * NPSTR, * LPSTR, * PSTR ;
typedef CONST CHAR * LPCCH, * PCCH, * LPCSTR, * PCSTR ; ANSI 字符定義,8_bit
#endif
前綴N和L表示「near」和「long」,指的是16位Windows中兩種大小不同的指標。在Win32中near和long指標沒有區別
------------------------------------------------------------------------------------
#IFDEF UNICODE
typedef WCHAR * PWCHAR, * LPWCH, * PWCH, * NWPSTR, * LPWSTR, * PWSTR ;
typedef CONST WCHAR * LPCWCH, * PCWCH, * LPCWSTR, * PCWSTR ; UNCODE 字符定義 16_bit
#ENDIF
-----------------------------------------------------------------------------------------------
● //-窗口和消息
***Windows-Hello World函數
HICON LoadIcon(HINSTANCE hInstance,LPCTSTR lpIconName) 加載圖標供程序使用。
HCURSOR LoadCursor(HINSTANCE hInstance,LPCTSTR lpCursorName) 加載鼠標光標供程序使用。
HGDIOBJ GetStockObject(int fnObject) 取得一個圖形對象(在這個例子中,是取得繪製窗口背景的畫刷對象)。
ATOM RegisterClass (CONST WNDCLASS *lpWndClass)爲程序窗口註冊窗口類別。
int MessageBox(HWND,LPCTSTR,LPCTSTR,UINT) 顯示消息框。
HWND CreateWindow( //根據窗口類別建立一個窗口。
LPCTSTR lpClassName, // 窗口類名
LPCTSTR lpWindowName, // 窗口標題
DWORD dwStyle, // 窗口風格
int x, // X座標
int y, // Y座標
int nWidth, // 寬度
int nHeight, // 高度
HWND hWndParent, // 父窗口句柄
HMENU hMenu, // 菜單句柄
HANDLE hInstance, // 單籤實例句柄
LPVOID lpParam // 指向CREATESTRUCT結構
);
ShowWindow 在屏幕上顯示窗口,第二個參數是作爲參數傳給WinMain的iCmdShow。它確定最初如何在屏幕上顯示窗口,是一般大小、最小化還是最大化。
UpdateWindow 指示窗口自我更新。
GetMessage 從消息隊列中取得消息,第二、第三和第四個參數設定爲NULL或者0,表示程序接收它自己建立的所有窗口的所有消息。
TranslateMessage 轉譯某些鍵盤消息。
DispatchMessage 將消息發送給窗口消息處理程序。
PlaySound 播放一個聲音文件。
BeginPaint 開始繪製窗口。
GetClientRect 取得窗口顯示區域的大小。
DrawText 顯示字符串。
EndPaint 結束繪製窗口。
PostQuitMessage 在消息隊列中插入一個「退出程序」消息。
DefWindowProc 執行內定的消息處理。
窗口消息處理程序總是定義爲如下形式:LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT在winuser.h定義爲:typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
這裏typedef的用法是:把LRESULT作爲WNDPROC的函數指針
窗口消息處理程序是命名爲WndProc的函數。窗口消息處理程序可任意命名(只要求不和其它名字發生衝突)。一個Windows程序可以包含多個窗口消息處理程序。一個窗口消息處理程序總是與呼叫RegisterClass註冊的特定窗口類別相關聯
一般來說,Windows程序寫作者使用switch和case結構來確定窗口消息處理程序接收的是什麼消息,以及如何適當地處理它。窗口消息處理程序在處理消息時,必須傳回0。窗口消息處理程序不予處理的所有消息應該被傳給名爲DefWindowProc的Windows函數。從DefWindowProc傳回的值必須由窗口消息處理程序傳回。
對WM_PAINT的處理幾乎總是從一個BeginPaint呼叫開始,而以一個EndPaint呼叫結束.
***Windows中使用的值常數在表頭文件中均有相應的標識符定義。
CS 窗口類別樣式[ClassStyle]
CW 建立窗口[CreateWindow]
DT 繪製文字[DrawText]
IDI 圖示ID[IDIcon]
IDC 遊標ID[IDCursor]
MB 消息框[MessageBox]
SND 聲音[Sound]
WM 窗口消息[WindowMessage]
WS 窗口樣式[WindowStyle]
● WNDCLASS[窗口類],表頭文件WINUSER.H中定義
typedef struct _WNDCLASS {
UINT style; //顯示風格,以CS_XXXX形式
WNDPROC lpfnWndProc; //WndProc函數子程
int cbClsExtra; //初始化爲0
int cbWndExtra; //初始化爲0
HANDLE hInstance; //當前實例句柄
HICON hIcon; //圖標句柄
HCURSOR hCursor; //光標句柄
HBRUSH hbrBackground; //背景顏色,COLOR_XXXXX形式
LPCTSTR lpszMenuName; //菜單名稱,填NULL,則使用默認名稱
LPCTSTR lpszClassName; //窗口類名
} WNDCLASS;
其中的lpfn前綴代表「指向函數的指標」。(在Win32 API中,長指標和短指標(或者近程指標)沒有區別。這只是16位Windows的遺物。)cb前綴代表「字節數CountOfByte」而且通常作爲一個常數來表示一個字節的大小。h前綴是一個句柄,而hbr前綴代表「一個畫刷的代號」。lpsz前綴代表「指向以0結尾字符串的指針」。
● 普通的迭代窗口:各窗口左上角的垂直和水平距離在屏幕上按一定的大小遞增
● 消息類型
typedef struct tagMSG { // 在WinUser.h中定義
HWND hwnd; //接受消息的窗口句柄
UINT message; //消息類型,一般以WM_XXXX形式
WPARAM wParam; //消息參數值,據消息而定
LPARAM lParam; //消息參數值,據消息而定
DWORD time; //消息放入消息隊列中的時間
POINT pt; //消息發佈時,光標的座標
} MSG;
● //-輸出文字
要得到窗口顯示區域的設備內容句柄,可以呼叫 HDC GetDC(HWND hWnd)來取得句柄,在使用完後呼叫ReleaseDC
GetSystemMetrics函數以取使用者接口上各類視覺組件大小的信息,取得與系統有關的度量信息
BOOL GetTextMetrics( //取得字體大小
HDC hdc, // 指向設備上下文句柄
LPTEXTMETRIC lptm // 指向TEXTMETRIC結構
);
TEXTMETRIC結構定義如下:
typedef struct tagTEXTMETRIC { // tm
LONG tmHeight;
LONG tmAscent;
LONG tmDescent;
LONG tmInternalLeading;
LONG tmExternalLeading;
LONG tmAveCharWidth;
LONG tmMaxCharWidth;
LONG tmWeight;
LONG tmOverhang;
LONG tmDigitizedAspectX;
LONG tmDigitizedAspectY;
BCHAR tmFirstChar;
BCHAR tmLastChar;
BCHAR tmDefaultChar;
BCHAR tmBreakChar;
BYTE tmItalic;
BYTE tmUnderlined;
BYTE tmStruckOut;
BYTE tmPitchAndFamily;
BYTE tmCharSet;
} TEXTMETRIC;
● 滾動條的範圍和位置:BOOL SetScrollRange (hwnd, iBar, iMin, iMax, bRedraw) ;參數iBar爲SB_VERT或者SB_HORZ,iMin和iMax分別是範圍的最小值和最大值。如果想要Windows根據新範圍重畫滾動條,則設置bRedraw爲TRUE(如果在呼叫SetScrollRange後,呼叫了影響滾動條位置的其它函數,則應該將bRedraw設定爲FALSE以避免過多的重畫)。Windows提供了類似的函數(GetScrollRange和GetScrollPos/ShowScrollBar)來取得滾動條的目前範圍和位置以及顯示滾動條.
● //-圖形基礎
BeginPaint、GetDC和GetWindowDC獲得的設備內容都與視訊顯示器上的某個特定窗口相關。取得設備內容句柄的另一個更通用的函數是CreateDC:hdc = CreateDC (pszDriver, pszDevice, pszOutput, pData) ;可以通過下面的呼叫來取得整個屏幕的設備內容句柄:hdc = CreateDC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;
metafile是一些GDI呼叫的集合,以二進制形式編碼。您可以通過取得metafile設備內容來建立metafile,hdcMeta = CreateMetaFile (pszFilename) ;
● //-鍵盤
***按鍵消息
----------------------------------------
| | 鍵按下 | 鍵釋放 |
----------------------------------- |
|非系統鍵 | WM_KEYDOWN | WM_KEYUP |
--------------------------------------- |
|系統鍵 | WM_SYSKEYDOWN | WM_SYSKEYUP |
---------------------------------------
在四個按鍵消息(WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN和WM_SYSKEYUP)中,wParam消息參數含有上面所討論的虛擬鍵碼,而lParam消息參數則含有對了解按鍵非常有用的其它信息。lParam的32位分爲6個字段,0-15位記錄重複計數,16-23位指定OEM掃描碼(鍵盤硬件產生的代碼),第24爲保存擴充鍵旗標,第29位保存內容代碼,第30位保存此按鍵的先前狀態,第31爲保存轉換狀態.
重複計數:重複計數是該消息所表示的按鍵次數,大多數情況下,重複計數設定爲1。不過,如果按下一個鍵之後,您的窗口消息處理程序不夠快,以致不能處理自動重複速率(您可以在「控制檯」的「鍵盤」中進行設定)下的按鍵消息,Windows就把幾個WM_KEYDOWN或者WM_SYSKEYDOWN消息組合到單個消息中,並相應地增加重複計數。WM_KEYUP或WM_SYSKEYUP消息的重複計數總是爲1。因爲重複計數大於1指示按鍵速率大於您程序的處理能力,所以您也可能想在處理鍵盤消息時忽略重複計數。幾乎每個人都有文書處理或執行電子表格時畫面捲過頭的經驗,因爲多餘的按鍵堆滿了鍵盤緩衝區,所以當程序用一些時間來處理每一次按鍵時,如果忽略您程序中的重複計數,就能夠解決此問題。不過,有時可能也會用到重複計數,您應該嘗試使用兩種方法執行程序,並從中找出一種較好的方法。
OEM掃描碼:OEM掃描碼是由硬件(鍵盤)產生的代碼。這對中古時代的彙編程序寫作者來說應該很熟悉,它是從PC相容機種的ROM BIOS服務中所獲得的值(OEM指的是PC的原始設備製造商(Original Equipment Manufacturer)及其與「IBM標準」同步的內容)。在此我們不需要更多的信息。除非需要依賴實際鍵盤佈局的樣貌,不然Windows程序可以忽略掉幾乎所有的OEM掃描碼信息
擴充鍵旗標:如果按鍵結果來自IBM增強鍵盤的附加鍵之一,那麼擴充鍵旗標爲1(IBM增強型鍵盤有101或102個鍵。功能鍵在鍵盤頂端,光標移動鍵從數字鍵盤中分離出來,但在數字鍵盤上還保留有光標移動鍵的功能)。對鍵盤右端的Alt和Ctrl鍵,以及不是數字鍵盤那部分的光標移動鍵(包括Insert和Delete鍵)、數字鍵盤上的斜線(/)和Enter鍵以及Num Lock鍵等,此旗標均被設定爲1。Windows程序通常忽略擴充鍵旗標。
內容代碼:右按鍵時,假如同時壓下ALT鍵,那麼內容代碼爲1。對WM_SYSKEYUP與WM_SYSKEYDOWN而言,此位總視爲1;而對WM_SYSKEYUP與WM_KEYDOW消息而言,此位爲0。除了兩個之外:如果活動窗口最小化了,則它沒有輸入焦點。這時候所有的按鍵都會產生WM_SYSKEYUP和WM_SYSKEYDOWN消息。如果Alt鍵未被按下,則內容代碼字段被設定爲0。Windows使用WM_SYSKEYUP和WM_SYSKEYDOWN消息,從而使最小化了的活動窗口不處理這些按鍵。對於一些外國語文(非英文)鍵盤,有些字符是通過Shift、Ctrl或者Alt鍵與其它鍵相組合而產生的。這時內容代碼爲1,但是此消息並非系統按鍵消息。
鍵的先前狀態:如果在此之前鍵是釋放的,則鍵的先前狀態爲0,否則爲1。對WM_KEYUP或者WM_SYSKEYUP消息,它總是設定爲1;但是對WM_KEYDOWN或者WM_SYSKEYDOWN消息,此位可以爲0,也可以爲1。如果爲1,則表示該鍵是自動重複功能所產生的第二個或者後續消息。
轉換狀態:如果鍵正被按下,則轉換狀態爲0;如果鍵正被釋放,則轉換狀態爲1。對WM_KEYDOWN或者WM_SYSKEYDOWN消息,此字段爲0;對WM_KEYUP或者WM_SYSKEYUP消息,此字段爲1。
***虛擬鍵碼
虛擬鍵碼保存在WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN和WM_SYSKEYUP消息的wParam參數中,此代碼標識按下或釋放的鍵,以VK_XXX形式存定義在WinUser.h中,其中VK_RETURN爲Enter鍵,VK_MENU爲Alt鍵,VK_PRIOR爲Page Up鍵,VK_NEXT爲Page Down鍵,VK_SNAPSHOT爲Print Screen鍵,VK_CLEAR爲Num Lock關閉時的數字鍵盤5
***位移狀態
在處理按鍵消息時,您可能需要知道是否按下了位移鍵(Shift、Ctrl和Alt)或開關鍵(Caps Lock、Num Lock和Scroll Lock)。通過呼叫GetKeyState函數,您就能獲得此信息。例如:iState = GetKeyState (VK_SHIFT) ;如果按下了Shift,則iState值爲負(即設定了最高位置位),SHORT GetKeyState(int nVirtKey)若返回1,則表示此按鍵被按下,若是其他,則是沒有被按下
SHORT GetAsyncKeyState(
int vKey // virtual-key code
);//判斷鍵盤上任何按鍵的狀態
BOOL GetKeyboardState(
PBYTE lpKeyState // 指定256字節數組接受鍵狀態
); //獲取全部按鍵狀態
***字符消息
---------------------------------------------
| | 字符 | 死字符 |
--------------------------------------------|
|非系統字符| WM_CHAR | WM_DEADCHAR |
------------------------------------------ |
|系統字符 | WM_SYSCHAR | WM_SYSDEADCHAR |
--------------------------------------- ----
參數wParam是ANSI或Unicode字符代碼,lParam參數與按鍵消息的lParam參數相同
int GetKeyNameText(
LONG lParam, // 消息的第二個參數
LPTSTR lpString, // 指定一個緩衝區,存放字符串
int nSize // 鍵名字的最大長度
);
***消息順序
因爲TranslateMessage函數從WM_KEYDOWN和WM_SYSKEYDOWN消息產生了字符消息,所以字符消息是夾在按鍵消息之間傳遞給窗口消息處理程序的,WM_KEYDOWN->WM_CHAR->WM_KEYUP.如果您按下Shift鍵,再按下A鍵,然後釋放A鍵,再釋放Shift鍵,就會輸入大寫的A,而窗口消息處理程序會接收到五個消息,依次:WM_KEYDOWN[虛擬鍵碼VK_SHIFT (0x10)]->WM_KEYDOWN[「A」的虛擬鍵碼(0x41)]->WM_CHAR[「A」的字符代碼(0x41)]->WM_KEYUP[「A」的虛擬鍵碼(0x41)]->WM_KEYUP[虛擬鍵碼VK_SHIFT(0x10)],Shift鍵本身不產生字符消息。
***處理控制字符
處理按鍵和字符消息的基本規則是:如果需要讀取輸入到窗口的鍵盤字符,那麼您可以處理WM_CHAR消息。如果需要讀取光標鍵、功能鍵、Delete、Insert、Shift、Ctrl以及Alt鍵,那麼您可以處理WM_KEYDOWN消息。
***死字符消息
在某些非U.S.英語鍵盤上,有些鍵用於給字母加上音調。因爲它們本身不產生字符,所以稱之爲「死鍵」。例如,使用德語鍵盤時,對於U.S.鍵盤上的+/=鍵,德語鍵盤的對應位置就是一個死鍵,未按下Shift鍵時它用於標識銳音,按下Shift鍵時則用於標識抑音.
***插入符號函數
CreateCaret 建立與窗口有關的插入符
SetCaretPos 在窗口中設定插入符的位置
ShowCaret 顯示插入符
HideCaret 隱藏插入符
DestroyCaret 撤消插入符
GetCaretPos 取得插入符目前位置
GetCaretBlinkTime 取得插入符閃爍時間 //Blink 閃爍
SetCaretBlinkTime 設定插入符閃爍時間
------------------------------------------------------------------------------------------------
● //-鼠標
***鼠標基礎
理論上,可以用GetSystemMetrics函數來確認鼠標是否存在:fMouse = GetSystemMetrics (SM_MOUSEPRESENT) ;如果已經安裝了鼠標,fMouse將傳回TRUE(非0);如果沒有安裝,則傳回0。然而,在Windows 98中,不論鼠標是否安裝,此函數都將傳回TRUE 。在Microsoft Windows NT中,它可以正常工作。要確定所安裝鼠標其上按鍵的個數,可使用cButtons = GetSystemMetrics (SM_CMOUSEBUTTONS) ; 如果沒有安裝鼠標,那麼函數將傳回0。
對於所有鼠標消息,wParam的值指示鼠標按鍵以及Shift和Ctrl鍵的狀態,lParam值均含有鼠標的位置:低字組爲x座標,高字組爲y座標,這兩個座標是相對於窗口顯示區域左上角的位置。
***非顯示區域鼠標消息
可以用兩個Windows函數將屏幕座標轉換爲顯示區域座標或者反之:ScreenToClient (hwnd, &pt);ClientToScreen (hwnd, &pt) ;
ShowCursor (TRUE) 顯示光標 同理的↓
HideCuesor() 隱藏光標
***攔截鼠標
只要呼叫:SetCapture (hwnd) ;在這個函數呼叫之後,Windows將所有鼠標消息發給窗口句柄爲hwnd的窗口消息處理程序。之後收到鼠標消息都是以顯示區域消息的型態出現,即使鼠標正在窗口的非顯示區域。lParam參數將指示鼠標在顯示區域座標中的位置。不過,當鼠標位於顯示區域的左邊或者上方時,這些x和y座標可以是負的。當您想釋放鼠標時,呼叫:ReleaseCapture () ;
------------------------------------------------------------------------------------------------
● //-定時器
呼叫SetTimer(HWND hWnd , UINT nIDEvent, UINT uElapse, TIMERPROC lpTimerFunc),第一個參數是其窗口消息處理程序將接收WM_TIMER消息的窗口句柄。第二個參數是定時器ID,它是一個非0數值,在整個例子中假定爲1。第三個參數是一個32位無正負號整數,以毫秒爲單位指定一個時間間隔,一個60,000的值將使Windows每分鐘發送一次WM_TIMER消息。函數爲您的Windows程序分配一個定時器。SetTimer有一個時間間隔範圍爲1毫秒4,294,967,295毫秒(將近50天)的整數型態參數,這個值指示Windows每隔多久時間給您的程序發送WM_TIMER消息。KillTimer(HWND,nIDEvent),撤銷定時器,第一個參數爲窗口的句柄,第二個爲時鐘的ID.
***時間日期結構SYSTEMTIME
typedef struct _SYSTEMTIME {
WORD wYear; //年
WORD wMonth; //月
WORD wDayOfWeek; //星期,從0開始
WORD wDay; //天
WORD wHour; //小時
WORD wMinute; //分鐘
WORD wSecond; //秒
WORD wMilliseconds; //微秒
} SYSTEMTIME;
SYSTEMTIME主要用於GetLocalTime和GetSystemTime函數。GetSystemTime函數傳回目前的世界時間(Coordinated Universal Time,UTC),大概與英國格林威治時間相同。GetLocalTime函數傳回當地時間以及GetCurrentTime返回當前時間,依據計算機所在的時區。這些值的精確度完全決定於使用者所調整的時間精確度以及是否指定了正確的時區。
------------------------------------------------------------------------------------------------
● //-子窗口控件
GetWindowLong該函數獲得有關指定窗口的信息,函數也獲得在額外窗口內存中指定偏移位地址的32位度整型值。
LONG GetWindowLong(
HWND hWnd, // 窗口句柄
int nIndex // 欲取回的信息,以GWL_XXX或者DWL_XXX形式,在WinUser.h中,有以下數值
GWL=GetWindowLong DWL=
GWL_EXSTYLE= (-20) 擴展窗口樣式
GWL_STYLE=(-16) 窗口樣式
GWL_WNDPROC= (-4) 該窗口的窗口函數的地址
GWL_HINSTANCE= (-6) 擁有窗口的實例的句柄
GWL_HWNDPARENT= (-8) 該窗口之父的句柄。不要用SetWindowWord來改變這個值
GWL_ID= (-12) 對話框中一個子窗口的標識符
GWL_USERDATA = (-21) 含義由應用程序規定
DWL_DLGPROC = 4 這個窗口的對話框函數地址
DWL_MSGRESULT = 0 在對話框函數中處理的一條消息返回的值
DWL_USER = 8 含義由應用程序規定
);
BOOL IsWindowVisible( //窗口是否可見
HWND hWnd // handle to window
);
HBRUSH CreateSolidBrush( //創建一個具有指定顏色的邏輯刷子
COLORREF crColor // brush color value Specifies the color of the brush. To create a COLORREF color value, use the RGB macro
);
------------------------------------------------------------------------------------------------
● //-菜單及其他資源
HMENU GetSystemMenu( //得到系統菜單句柄
HWND hWnd, // handle to window
BOOL bRevert // reset option 如果參數bRevert爲FALSE,返回值是窗口菜單的拷貝的句柄:如果參數bRevert爲TRUE,返回值是NULL。
);
***改變菜單
AppendMenu 在菜單尾部添加一個新的菜單項目
DeleteMenu 刪除菜單中一個現有的菜單項並清除該項目
InsertMenu 在菜單中插入一個新項目
ModifyMenu 修改一個現有的菜單項目
RemoveMenu 從菜單中移走某一項目
------------------------------------------------------------------------------------------------
● //-多任務和多線程
建立新的線程的API函數是 HANDLE CreateThread( //創建線程
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 指向SECURITY_ATTRIBUTES結構的指針
DWORD dwStackSize, // 初始線程堆棧大小,默認0
LPTHREAD_START_ROUTINE lpStartAddress, // 線程函數
LPVOID lpParameter, // 指向線程的參數值
DWORD dwCreationFlags, // 額外標誌,通常0
[out]LPDWORD lpThreadId // 返回線程句柄
);
HANDLE CreateEvent( //創建事件對象
LPSECURITY_ATTRIBUTES lpEventAttributes, // 指向SECURITY_ATTRIBUTES結構的指針
BOOL bManualReset, // 復位方式
BOOL bInitialState, // 初始狀態的事件對象
LPCTSTR lpName // 事件對象名稱
);
要設立一個現存的事件對象,呼叫SetEvent (hEvent) ;要重置一個事件對象,呼叫ResetEvent (hEvent) ;一個程序通常呼叫:DWORD WaitForSingleObject(
HANDLE hHandle, // 事件句柄
DWORD dwMilliseconds // 時間間隔
);
------------------------------------------------------------------------------------------------
● //動態鏈接庫
儘管一個動態鏈接庫模塊可能有其它擴展名(如.EXE或.FON),但標準擴展名是.DLL。只有帶.DLL擴展名的動態鏈接庫才能被Windows自動加載。如果文件有其它擴展名,則程序必須另外使用LoadLibrary或者LoadLibraryEx函數加載該模塊.動態鏈接庫有着令人困惑的印象,部分原因是由於「鏈接庫」這個詞被放在幾種不同的用語之後。除了動態鏈接庫之外,我們也用它來稱呼「目的碼鏈接庫」或「引用鏈接庫」。目的碼鏈接庫是帶.LIB擴展名的文件。在使用連結程序進行靜態連結時,它的程序代碼就會加到程序的.EXE文件中。
DLL入口函數:int WINAPI DllMain ( //函數入口
HINSTANCE hInstance, //模塊實例句柄
DWORD fdwReason, //呼叫函數的理由
PVOID pvReserved //DLL的初始化
);
HMODULE LoadLibrary( //加載函數庫
LPCTSTR lpFileName // 文件名,返回一個模塊實例句柄
);
BOOL FreeLibrary( //卸載函數庫
HMODULE hModule // 模塊句柄,邏輯型
);
HMODULE GetModuleHandle( //同樣返回一個模塊的實例句柄
LPCTSTR lpModuleName // 模塊名字
);
3. ● ●●●●●●彙編語言程序設計●●●●
● 80386處理器有3種工作模式:實模式、保護模式和虛擬86模式。實模式和虛擬86模式是爲了和8086處理器兼容而設置的。在實模式下,80386處理器就相當於一個快速的8086處理器。保護模式是80386處理器的主要工作模式。在此方式下,80386可以尋址4 GB的地址空間,同時,保護模式提供了80386先進的多任務、內存分頁管理和優先級保護等機制。80386處理器被複位或加電的時候以實模式啓動。虛擬86模式以保護模式爲基礎,它的工作方式實際上是實模式和保護模式的混合。從實模式切換到保護模式是通過修改控制寄存器CR0的控制位PE(位0)來實現的PE爲1則進入保護模式,分頁機制是通過PG位來實現PG爲1則啓用分頁,只有在PE位是1的時候,PG位纔有可能是1,分頁機制把線性地址空間和物理地址空間分別劃分爲大小相同的塊。這樣的塊稱爲頁。通過在線性地址空間的頁與物理地址空間的頁之間建立映射,分頁機制可以實現線性地址到物理地址的轉換。
● 保護模式下的分段模式:
在32位機器中,CPU中CS,DS,ES,SS,4個16位的段寄存器存放的的是選擇符,共16*4=64位
各項任務共享的內存空間由全局選擇符索引,而某個獨立使用的內存空間由局部選擇符來索引,以選擇符的高13位左右偏移量,再以CPU內部事先初始好的GDTR(全局描述符表寄存器)中的32位作爲基地址
,可以獲得相應的描述符,由描述符中的線性地址決定段的基地址,再利用指令(或其他方式)給出的偏移量,便可得到線性地址.物理地址=線性基地址+偏移量
4KB頁面的線性地址,如何映射到物理內存:
4KB=1024個頁映射表 (每個映射表4字節)
4字節=4*8位=32位線性地址
線性地址的高10位->(物理頁中的頁目錄表)1024個頁目錄項 (每個頁目錄項4字節*1024=4096字節=4KB)
線性地址的低22位->高10位+低12位
高10位->(物理頁中頁表)1024個頁表項 (每個頁表項4字節*1024=4096字節=4KB)
低12位->存放諸如“頁是否存在於內存”或“頁的權限”等信息
舉例說:00402080,首先轉換爲32位地址=0000 0000 0100 0000 0010 0000 1000 0000,那麼高10位0010000000,這個是頁目錄,系統根據CR3寄存器顯示的頁目錄表的基址,找到頁目錄,然後找索引爲10000000的目錄,每個頁目錄對應1024個頁表,找到後,在根據地址中間10位1000,從頁表中找到索引爲1000的地址,這樣就得到了目標頁面的物理地址,最後加上低12位1,就得到數據存放的真正物理地址
一個線性地址大小爲4個字節(32bit),包含着找到物理地址的信息,分爲3個部分:第22位到第31位這10位(最高10位)是頁目錄中的索引,第12位到第21位這10位是頁表中的索引,第0位到第11位這12位(低12位)是頁內偏移。在把一個線性地址轉換成物理地址時,CPU首先根據CR3中的值,找到頁目錄所在的物理頁。然後根據線性地址的第22位到第31位這10位(最高的10bit)的值作爲索引,找到相應的PDE,其中含有這個虛擬地址所對應頁表的物理地址。有了頁表的物理地址,再把虛擬地址的第12位到第21位這10位的值作爲索引,找到該頁表中相應的PTE,其中就有這個虛擬地址所對應物理頁的物理地址。最後用線性地址的最低12位,也就是頁內偏移,加上這個物理頁的物理地址,就得到了該線性地址所對應的物理地址。
控制寄存器 :CR0
屏蔽中斷 :cli
中斷返回指令: iret
● 實模式下,一個完整的地址由段地址和偏移地址兩部分組成。段地址放在16位的段寄存器中,然後在指令中用16位的偏移地址尋址。處理器換算時先將段地址乘以10h,得到段在物理內存中的起始地址;然後加上16位的偏移地址得到實際的物理地址。如xxxx:yyyy格式的虛擬地址在內存中的實際位置是xxxx×10h+yyyy。
● 80386所有的通用寄存器都是32位的,2的32次方相當於4G,所以用任何一個通用寄存器來間接尋址,不必分段就已經可以訪問到所有的內存地址。雖然在尋址上不再有分段的限制問題,但在保護模式下,一個地址空間是否可以被寫入,可以被多少優先級的代碼寫入,是不是允許執行等涉及保護的問題就出來了。要解決這些問題,必須對一個地址空間定義一些安全上的屬性。段寄存器這時就派上了用途。但是涉及屬性和保護模式下段的其他參數,要表示的信息太多了,要用64位長的數據才能表示。我們把這64位的屬性數據叫做段描述符(Segment Descriptor).
● 80386的段寄存器是16位的,無法放下保護模式下64位的段描述符。如何解決這個新的問題呢?解決辦法是把所有段的段描述符順序放在內存中的指定位置,組成一個段描述符表(Descriptor Table);而段寄存器中的16位用來做索引信息,指定這個段的屬性用段描述符表中的第幾個描述符來表示。這時,段寄存器中的信息不再是段地址了,而是段選擇器(Segment Selector)。
● 80386中引入了兩個新的寄存器來管理段描述符表。一個是48位的全局描述符表寄存器GDTR,一個是16位的局部描述符表寄存器LDTR。GDTR指向的描述符表爲全局描述符表GDT(Global Descriptor Table)。它包含系統中所有任務都可用的段描述符,通常包含描述操作系統所使用的代碼段、數據段和堆棧段的描述符及各任務的LDT段等;全局描述符表只有一個。
LDTR則指向局部描述符表LDT(Local Descriptor Table)。80386處理器設計成每個任務都有一個獨立的LDT。它包含有每個任務私有的代碼段、數據段和堆棧段的描述符,也包含該任務所使用的一些門描述符,如任務門和調用門描述符等。
● 80386中可以使用下述指令進行數組訪問:mov cx,[eax + ebx * 2 + 數組基地址],這相當於把數組中下標爲eax和ebx的項目放入cx中;ebx * 2中的2可以是1,2,4或8,這樣就可以支持8位到64位的數組。
● 在保護地址模式下,經常遇到三種地址:邏輯地址(ogica Address)、線性地址(inear Address)和物理地址(Physica Address)。CPU通過分段機制將邏輯地址轉換爲線性地址,再通過分頁機制將線性地址轉換爲物理地址。(1)邏輯地址這是內存地址的精確描述,通常表示爲十六進制:xxxx:YYYYYYYY,這裏xxxx爲seector(選擇器),而YYYYYYYY是針對seector所選擇的段地址的線性偏移量。除了指定xxxx的具體數值外,還可使用具體的段寄存器的名字來替代,如CS(代碼段),DS(數據段),ES(擴展段),FS(附加數據段#1),GS(附加數據段#2)和SS(堆棧段)。這些符號都來自舊的“段:偏移量”風格,在 8086 實模式下使用此種方式來指定“far pointers”(遠指針)。(2)線性地址線性地址是邏輯地址到物理地址變換之間的中間層,是處理器可尋址的內存空間(稱爲線性地址空間)中的地址。程序代碼會產生邏輯地址,或者說是段中的偏移地址,加上相應段的基地址就生成了一個線性地址。如果啓用了分頁機制,那麼線性地址可以再經變換以產生一個物理地址。若沒有啓用分頁機制,那麼線性地址直接就是物理地址。不過,在開啓分頁功能之後,一個線性地址可能沒有相對映的物理地址,因爲它所對應的內存可能被交換到硬盤中。32位線性地址可用於定位4GB存儲單元。(3)物理地址所謂物理地址,就是指系統內存的真正地址。對於32 位的操作系統,它的範圍爲0x00000000~0xFFFFFFFF,共有4GB。只有當CPU工作於分頁模式時,此種類型的地址纔會變得非常“有趣”。
● 80386處理器把4 KB大小的一塊內存當做一“頁”內存,每頁物理內存可以根據“頁目錄”和“頁表”,隨意映射到不同的線性地址上。頁表規定的不僅是地址的映射,同時還規定了頁的訪問屬性,如是否可寫、可讀和可執行等。是否啓用內存分頁機制是由80386處理器新增的CR0寄存器中的位31(PG位)決定的。如果PG=0,則分頁機制不啓用,這時所有指令尋址的地址(線性地址)就是系統中實際的物理地址;當PG=1的時候,80386處理器進入內存分頁管理模式,所有的線性地址要經過頁表的映射纔得到最後的物理地址。在實模式下尋址的時候,段寄存器+偏移地址經過轉換計算以後得到的地址是“物理地址”,而保護模式下,段選擇器+偏移地址轉換後的地址被稱爲“線性地址”而不是“物理地址”。
● 每個應用程序都有自己的4 GB的尋址空間。該空間可存放操作系統、系統DLL和用戶DLL的代碼,它們之中有各種函數供應用程序調用。再除去其他的一些空間,餘下的是應用程序的代碼、數據和可以分配的地址空間。
● 不同應用程序的線性地址空間是隔離的。雖然它們在物理內存中同時存在,但在某個程序所屬的時間片中,其他應用程序的代碼和數據沒有被映射到可尋址的線性地址中,所以是不可訪問的。從編程的角度看,程序可以使用4 GB的尋址空間,而且這個空間是“私有”的。
● DLL程序沒有自己“私有”的空間。它們總是被映射到其他應用程序的地址空間中,當做其他應用程的一部分運行。原因很簡單,如果它不和其他程序同屬一個地址空間,應用程序該如何調用它呢?
● 並不是Win32彙編源代碼用不到段寄存器,而是用戶在使用中不必去關心段寄存器!
● 爲了使高優先級的代碼能夠安全地被低優先級的代碼調用,保護模式下增加了“門”的概念。“門”指向某個優先級高的程序所規定的入口點,所有優先級低的程序調用優先級高的程序只能通過門重定向,進入門所規定的入口點。這樣可以避免低級別的程序代碼從任意位置進入優先級高的程序的問題。保護模式下的中斷和異常等服務程序也要從“門”進入,80386的門分爲中斷門、自陷門和任務門幾種。
● 在保護模式下要表示一箇中斷或異常服務程序的信息需要用8個字節,包括門的種類以及xxxx:yyyyyyyy格式的入口地址等。這組信息叫做“中斷描述符”。保護模式下把所有的中斷描述符放在一起組成“中斷描述符表”IDT(Interrupt Descriptor Table)。
● 實模式下的中斷和異常服務程序地址存放在中斷向量表中。中斷向量表位於物理內存00000h開始的400h字節中,共支持100h箇中斷向量;每個中斷向量是一個xxxx:yyyy格式的地址,佔用4字節。當發生n號異常或n號中斷,或者執行到int n指令的時候,CPU首先到內存n×4的地方取出服務程序的地址aaaa:bbbb;然後將標誌寄存器、中斷時的CS和IP壓入堆棧,接着轉移到aaaa:bbbb處執行;在服務程序最後遇到iret的時候,CPU從堆棧中恢復標誌寄存器,然後取出CS和IP並返回。
● 在Windows中,操作系統使用動態鏈接庫來代替中斷服務程序提供系統功能,所以 Win32彙編中int指令也就失去了存在的意義。這就是在Win32彙編源代碼中看不到int指令的原因。其實那些調用API的指令原本是用int指令實現的。
● 16位的段選擇器中只有高13位表示索引值。剩下的3個數據位中,第0,1位表示程序的當前優先級RPL;第2位TI位用來表示在段描述符的位置;TI=0表示在GDT中,TI=1表示在LDT中。
● 保護機制主要由下列幾方面組成:
段的類型檢查——段的類型是由段描述符指定的,主要屬性有是否可執行,是否可讀和是否可寫等。而CS,DS和SS等段選擇器是否能裝入某種類型的段描述符是有限制的。如不可執行的段不能裝入CS;不可讀的段不能裝入DS與ES等數據段寄存器;不可寫的段不能裝入SS等。如果段類型檢查通不過,則處理器會產生一般性保護異常或堆棧異常。
● 頁的類型檢查——除了可以在段級別上指定整個段是否可讀寫外,在頁表中也可以爲每個頁指定是否可寫。對於特權級下的執行代碼,所有的頁都是可寫的。但對於1,2和3級的代碼,還要根據頁表中的R/W項決定是否可寫,企圖對只讀的頁進行寫操作會產生頁異常。
● 訪問數據時的級別檢查——優先級低的代碼不能訪問優先級高的數據段。80386的段描述符中有一個DPL域(描述符優先級),表示這個段可以被訪問的最低優先級。而段選擇器中含有RPL域(請求優先級),表示當前執行代碼的優先級。只有DPL在數值上大於或等於RPL值的時候,該段纔是可以訪問的,否則會產生一般性保護異常。
● 控制轉移的檢查——在處理器中,有很多指令可以實現控制轉移,如jmp,call,ret,int和iret等指令。但優先級低的代碼不能隨意轉移到優先級高的代碼中,所以遇到這些指令的時候,處理器要檢查轉移的目的位置是否合法。
● 指令集的檢查——有兩類指令可以影響保護機制。第一類是改變GDT,LDT,IDT以及控制寄存器等關鍵寄存器的指令,稱爲特權指令;第二類是操作I/O端口的指令以及cli和sti等改變中斷允許的指令,稱爲敏感指令。試想一下,如果用戶級程序可以用sti禁止一切中斷(包括時鐘中斷),那麼整個系統就無法正常運行,所以這些指令的運行要受到限制。特權指令只能在優先級0上才能運行,而敏感指令取決於eflags寄存器中的IOPL位。只有IOPL位表示的優先級高於等於當前代碼段的優先級時,指令才能執行。
● I/O操作的保護——I/O地址也是受保護的對象。因爲通過I/O操作可以繞過系統對很多硬件進行控制。80386可以單獨爲I/O空間提供保護,每個任務有個TSS(任務狀態段)來記錄任務切換的信息。TSS中有個I/O允許位圖,用來表示對應的I/O端口是否可以操作。某個I/O地址在位圖中的對應數據位爲0則表示可以操作;如果爲1則還要看eflags中的IPOL位,這時只有IOPL位表示的優先級高於等於當前代碼段的優先級,才允許訪問該I/O端口。
●●●●●●●●PE文件格式學習●●●●●●●●●
● 在PE文件中,代碼、已初始化的數據、資源和重定位信息等數據被按照屬性分類放到不同的節(Section)中,而每個節的屬性和位置等信息用一個IMAGE_SECTION_HEADER結構來描述,所有的IMAGE_SECTION_HEADER結構組成一個節表(Section Table),節表數據在PE文件中被放在所有節數據的前面。
●IMAGE_SECTION_HEADER結構在winnt.h定義如下:
typedef struct _IMAGE_SECTION_HEADER
{
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];//節表名稱,如“.text”IMAGE_SIZEOF_SHORT_NAME=8
union
{
DWORD PhysicalAddress;//物理地址
DWORD VirtualSize;//真實長度,可以使用其中的任何一個,一般是節的數據大小
} Misc;
DWORD VirtualAddress;//相對虛擬地址
DWORD SizeOfRawData;//物理長度
DWORD PointerToRawData;//節基於文件的偏移量
DWORD PointerToRelocations;//重定位的偏移
DWORD PointerToLinenumbers;//行號表的偏移
WORD NumberOfRelocations;//重定位項數目
WORD NumberOfLinenumbers;//行號表的數目
DWORD Characteristics;//節屬性 如可讀,可寫,可執行等
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
●IMAGE_IMPORT_DESCRIPTOR是導入表結構數組,數組以全0作爲結束標記,該結構定義如下:
typedef struct _IMAGE_IMPORT_DESCRIPTOR
{
union
{
DWORD Characteristics; //0
DWORD OriginalFirstThunk;// 指向一個 IMAGE_THUNK_DATA 結構數組的RVA
}
DWORD TimeDateStamp;// 文件生成的時間
DWORD ForwarderChain;// 這個數據一般爲0,可以不關心
DWORD Name1; // RVA,指向DLL名字的指針,ASCII字符串
DWORD FirstThunk; //指向一個IMAGE_THUNK_DATA 結構數組的RVA,這個數據與IAT所指向的地址一致
}IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR
●IMAGE_EXPORT_DIRECTORY是導出表結構數組,數組以全0作爲結束標記,該結構定義如下:
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
●IMAGE_THUNK_DATA 這是一個DWORD類型的集合。通常我們將其解釋爲指向一個IMAGE_IMPORT_BY_NAME 結構的指針,其定義如下:
IMAGE_THUNK_DATA
{
union
{
PBYTE ForwarderString;
PDWORD Function;
DWORD Ordinal;//判定當前結構數據是不是以序號爲輸出的,如果是的話該值爲0x800000000,此時 ....PIMAGE_IMPORT_BY_NAME不可做爲名稱使用
PIMAGE_IMPORT_BY_NAME AddressOfData;
}u1;
} IMAGE_THUNK_DATA,*PIMAGE_THUNK_DATA;
●typedef struct _IMAGE_IMPORT_BY_NAME
{
WORD Hint;// 函數輸出序號
BYTE Name1[1];//輸出函數名稱
} IMAGE_IMPORT_BY_NAME,*PIMAGE_IMPORT_BY_NAME
我們知道,Win32中可以對每個內存頁分別指定可執行、可讀寫等屬性,PE文件將同樣屬性的數據分類放在一起是爲了統一描述這些數據裝入內存後的頁面屬性。由於數據是按照屬性在節中放置的,不同用途但是屬性相同的數據(如導入表、導出表以及.const段指定的只讀數據)可能被放在同一個節中,所以PE文件中還用一系列的數據目錄結構IMAGE_DATA_DIRECTORY來分別指明這些數據的位置,數據目錄表和其他描述文件屬性的數據合在一起稱爲PE文件頭,PE文件頭被放置在節和節表的前面。
● 操作系統識別可執行文件的方法是按照文件格式而不是按照擴展名,如果文件頭中的數據格式不符合任何已經定義的格式,那麼系統按照COM文件的格式裝入文件,也就是說將整個文件的數據全部當做代碼裝入執行。這個規則說明了爲什麼很多非.exe擴展名的可執行文件(如LE格式的VxD文件、PE格式的.dll,.scr文件等等)也能夠被裝入並正確運行,也說明了爲什麼把可執行文件的擴展名隨意修改爲.exe、.com或者.bat(甚至是.pif,.scr或者.bat),系統也能正確識別並執行的原因。
● PE文件中的DOS部分由MZ格式的文件頭和可執行代碼部分組成,可執行代碼被稱爲“DOS塊”(DOS stub)。MZ格式的文件頭由IMAGE_DOS_HEADER結構定義:以下是cmd.exe文件解析來的
IMAGE_DOS_HEADER STRUCT
{
WORD e_magic Magic DOS signature MZ(4Dh 5Ah) //DOS可執行文件標記
WORD e_cblp Bytes on last page of file
WORD e_cp Pages in file
WORD e_crlc Relocations
WORD e_cparhdr Size of header in paragraphs
WORD e_minalloc Minimun extra paragraphs needs
WORD e_maxalloc Maximun extra paragraphs needs
WORD e_ss intial(relative)SS value //DOS代碼的初始化堆棧SS
WORD e_sp intial SP value //DOS代碼的初始化堆棧指針SP
WORD e_csum Checksum
WORD e_ip intial IP value //DOS代碼的初始化指令入口[指針IP]
WORD e_cs intial(relative)CS value // DOS代碼的初始堆棧入口
WORD e_lfarlc File Address of relocation table
WORD e_ovno Overlay number
WORD e_res[4] Reserved words
WORD e_oemid OEM identifier(for e_oeminfo)
WORD e_oeminfo OEM information;e_oemid specific 0000
WORD e_res2[10] Reserved words
DWORD e_lfanew Offset to start of PE header //指向真正PE文件頭
}IMAGE_DOS_HEADER ENDS
● PE文件頭(NT文件頭)
從DOS文件頭的e_lfanew字段(文件頭偏移003ch)得到真正的PE文件頭位置後,現在來看看它的定義,PE文件頭是由IMAGE_NT_HEADERS結構定義的:
●typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; //00004550h
IMAGE_FILE_HEADER FileHeader; //結構體
IMAGE_OPTIONAL_HEADER32 OptionalHeader;//結構體
}
● PE文件頭的第一個雙字是一個標誌,它被定義爲00004550h,也就是字符“P”,“E”加上兩個0,這也是“PE”這個稱呼的由來,大部分的文件屬性由標誌後面的IMAGE_FILE_HEADER和IMAGE_OPTIONAL_HEADER32結構來定義,從名稱看,似乎後面的這個PE文件表頭結構是可選的(Optional),但實際上這個名稱是名不符實的,因爲它總是存在於每個PE文件中。
IMAGE_FILE_HEADER結構的定義如下所示,字段後面的註釋中標出了字段相對於PE文件頭的偏移量,以供讀者快速參考:
●typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //運行平臺 intel i386一般爲14Ch
WORD NumberOfSections; //塊(section)數目
DWORD TimeDateStamp; //時間日期標記
DWORD PointerToSymbolTable; //COFF符號指針,這是程序調試信息
DWORD NumberOfSymbols; //符號數
WORD SizeOfOptionalHeader; //可選部首長度,是IMAGE_OPTIONAL_HEADER的長度
WORD Characteristics; //文件屬性
}
● 定義IMAGE_OPTIONAL_HEADER32結構的本意在於讓不同的開發者能夠在PE文件頭中使用自定義的數據,這就是結構名稱中“Optional”一詞的由來,但實際上IMAGE_FILE_HEADER結構不足以用來定義PE文件的屬性,反而在這個“可選”的部分中有着更多的定義數據,對於讀者來說,可以完全不必考慮這兩個結構的區別在哪裏,只要把它們當成是連在一起的“PE文件頭結構”就可以了。IMAGE_OPTIONAL_HEADER32結構的定義如下,同樣,字段後面的註釋中標出了字段本身相對於PE文件頭的偏移量:
IMAGE_OPTIONAL_HEADER32 STRUCT
typedef struct _IMAGE_OPTIONAL_HEADER
{
WORD Magic; //幻數,一般爲10BH
BYTE MajorLinkerVersion; //鏈接程序的主版本號
BYTE MinorLinkerVersion; //鏈接程序的次版本號
DWORD SizeOfCode; //代碼段大小
DWORD SizeOfInitializedData; //已初始化數據塊的大小
DWORD SizeOfUninitializedData; //未初始化數據庫的大小
DWORD AddressOfEntryPoint; //程序開始執行的入口地址,這是一個RVA(相對虛擬地址)
DWORD BaseOfCode; //代碼段的起始RVA
DWORD BaseOfData; //數據段的起始RVA
DWORD ImageBase; //可執行文件默認裝入的基地址
DWORD SectionAlignment; //內存中塊的對齊值(默認的塊對齊值爲1000H,4KB個字節)
DWORD FileAlignment;//文件中塊的對齊值(默認值爲200H字節,爲了保證塊總是從磁盤的扇區開始的)
WORD MajorOperatingSystemVersion;//要求操作系統的最低版本號的主版本號
WORD MinorOperatingSystemVersion;//要求操作系統的最低版本號的次版本號
WORD MajorImageVersion;//該可執行文件的主版本號
WORD MinorImageVersion;//該可執行文件的次版本號
WORD MajorSubsystemVersion;//要求最低之子系統版本的主版本號
WORD MinorSubsystemVersion;//要求最低之子系統版本的次版本號
DWORD Win32VersionValue;//保留字
DWORD SizeOfImage;//映像裝入內存後的總尺寸
DWORD SizeOfHeaders;//部首及塊表的大小
DWORD CheckSum;//CRC檢驗和
WORD Subsystem;//程序使用的用戶接口子系統
WORD DllCharacteristics;//DLLmain函數何時被調用,默認爲0
DWORD SizeOfStackReserve;//初始化時堆棧大小
DWORD SizeOfStackCommit;//初始化時實際提交的堆棧大小
DWORD SizeOfHeapReserve;//初始化時保留的堆大小
DWORD SizeOfHeapCommit;//初始化時實際提交的對大小
DWORD LoaderFlags;//與調試有關,默認爲0
DWORD NumberOfRvaAndSizes;//數據目錄結構的數目
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//數據目錄表
}
●IMAGE_DATA_DIRECTORY數據目錄表,結構定義如下:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;//數據塊的起始RVA
DWORD Size;//數據塊的長度
}
●IMAGE_RESOURCE_DIRECTORY自願結構表,結構定義如下:
typedef struct _IMAGE_RESOURCE_DIRECTORY {
DWORD Characteristics;//理論上爲資源的屬性,不過事實上總是0
DWORD TimeDateStamp;//資源的產生時刻
WORD MajorVersion;//理論上爲資源的版本,不過事實上總是0
WORD MinorVersion;
WORD NumberOfNamedEntries;//以名稱命名的入口數量
WORD NumberOfIdEntries;//以ID命名的入口數量
// IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[];
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
●IMAGE_BASE_RELOCATION重定位表,結構:
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;//重定位數據RVA
DWORD SizeOfBlock;//重定位數據大小
WORD TypeOffset[1];//重定位數據項數則
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
●IMAGE_BOUND_IMPORT_DESCRIPTOR綁定輸入表表,結構:
typedef struct _IMAGE_BOUND_IMPORT_DESCRIPTOR {
DWORD TimeDateStamp;
WORD OffsetModuleName;
WORD NumberOfModuleForwarderRefs;
// Array of zero or more IMAGE_BOUND_FORWARDER_REF follows
} IMAGE_BOUND_IMPORT_DESCRIPTOR, *PIMAGE_BOUND_IMPORT_DESCRIPTOR;
●獲取PE文件的結構信息有兩種方法,①通過打開文件直接讀取②是通過文件映射讀取
①:首先通過CreateFile()來打開一個文件,獲得文件的句柄hFile
然後用
BOOL ReadFile(
HANDLE hFile, // handle of file to read
LPVOID lpBuffer, // pointer to buffer that receives data
DWORD nNumberOfBytesToRead, // number of bytes to read
LPDWORD lpNumberOfBytesRead, // pointer to number of bytes read
LPOVERLAPPED lpOverlapped // pointer to structure for data
); 第二個參數指向一個緩衝區,接收文件的指針
最後把這個指針賦值給IMAGE_DOS_HEADER,IMAGE_DOS_HEADER是文件的DOS頭,這一步是把DOS頭結構的地址與文件的地址關聯,之後就可以對PE中的數據進行訪問
②:首先
HANDLE CreateFile(
LPCTSTR lpFileName, // pointer to name of the file
DWORD dwDesiredAccess, // access (read-write) mode
DWORD dwShareMode, // share mode
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
// pointer to security attributes
DWORD dwCreationDisposition, // how to create
DWORD dwFlagsAndAttributes, // file attributes
HANDLE hTemplateFile // handle to file with attributes to
// copy
);返回文件的的句柄hFile
然後依據此句柄創建文件映射內核對象
HANDLE CreateFileMapping(
HANDLE hFile, // handle to file to map
LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
// optional security attributes
DWORD flProtect, // protection for mapping object
DWORD dwMaximumSizeHigh, // high-order 32 bits of object size
DWORD dwMaximumSizeLow, // low-order 32 bits of object size
LPCTSTR lpName // name of file-mapping object
);創建一個文件映射內核對象,返回文件映射句柄hMapping
之後
LPVOID MapViewOfFile(
HANDLE hFileMappingObject, // file-mapping object to map into
// address space
DWORD dwDesiredAccess, // access mode
DWORD dwFileOffsetHigh, // high-order 32 bits of file offset
DWORD dwFileOffsetLow, // low-order 32 bits of file offset
DWORD dwNumberOfBytesToMap // number of bytes to map
);把文件裝入內存,返回得到文件起始地址,一個指針
最後就是把這個指針轉換成IMAGE_DOS_HEADER類型,賦值給IMAGE_DOS_HEADER首址
●●●●●●●●●●●●●●●●●●●●●●●
EAX :累加器,用於算術,邏輯運算以及外設傳送信息
EBX :基址寄存器,存放存儲器的地址
ECX :計數器,做循環或者串指令運算中的隱含計數器
EDX :數據寄存器,存放數據的高位,或者存放外設端口地址
ESI :源變址寄存器,若在串指令中有特殊用法
EDI :目的變址寄存器,若在串指令中有特殊用法
ESP :堆棧指針寄存器,指示棧頂的便宜地址,不能再用於其它用途
EBP :基址指針寄存器,說明數據在堆棧段的基地址
CF (carry flag)進位標誌
ZF (zero flag)零標誌
SF (sign flag)
能夠改變ECS,EIP的指令統稱爲跳轉指令,若要單一修改EIP的值,可以通過jmp 合法寄存器 來實現,通用寄存器的值都可以用mov指令改寫
● 有符號數比較大小的結果指令依次爲:G[大於] E[等於] L[小於]
● 無符號數比較大小的結果指令依次爲:A[大於] E[等於] B[小於]
立即數不能直接送段地址
1. 通用數據傳送指令.
MOV 傳送字或字節.
MOVSX 先符號擴展,再傳送.
MOVZX 先零擴展,再傳送.
PUSH 把字壓入堆棧.
POP 把字彈出堆棧.
PUSHA 把AX,CX,DX,BX,SP,BP,SI,DI依次壓入堆棧.
POPA 把DI,SI,BP,SP,BX,DX,CX,AX依次彈出堆棧.
PUSHAD 把EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI依次壓入堆棧.
POPAD 把EDI,ESI,EBP,ESP,EBX,EDX,ECX,EAX依次彈出堆棧.
BSWAP 交換32位寄存器裏字節的順序
XCHG 交換字或字節.( 至少有一個操作數爲寄存器,段寄存器不可作爲操作數)
CMPXCHG 比較並交換操作數.( 第二個操作數必須爲累加器AL/AX/EAX )
XADD 先交換再累加.( 結果在第一個操作數裏 )
XLAT 字節查錶轉換.
── BX 指向一張 256 字節的表的起點, AL 爲表的索引值 (0-255,即
0-FFH); 返回 AL 爲查表結果. ( [BX+AL]->AL )
2. 輸入輸出端口傳送指令.
IN I/O端口輸入. ( 語法: IN 累加器, {端口號│DX} )
OUT I/O端口輸出. ( 語法: OUT {端口號│DX},累加器 )
輸入輸出端口由立即方式指定時, 其範圍是 0-255; 由寄存器 DX 指定時,
其範圍是 0-65535.
3. 目的地址傳送指令.
LEA 裝入有效地址.
例: LEA DX,string ;把偏移地址存到DX.
LDS 傳送目標指針,把指針內容裝入DS.
例: LDS SI,string ;把段地址:偏移地址存到DS:SI.
LES 傳送目標指針,把指針內容裝入ES.
例: LES DI,string ;把段地址:偏移地址存到ES:DI.
LFS 傳送目標指針,把指針內容裝入FS.
例: LFS DI,string ;把段地址:偏移地址存到FS:DI.
LGS 傳送目標指針,把指針內容裝入GS.
例: LGS DI,string ;把段地址:偏移地址存到GS:DI.
LSS 傳送目標指針,把指針內容裝入SS.
例: LSS DI,string ;把段地址:偏移地址存到SS:DI.
4. 標誌傳送指令.
LAHF 標誌寄存器傳送,把標誌裝入AH.
SAHF 標誌寄存器傳送,把AH內容裝入標誌寄存器.
PUSHF 標誌入棧.
POPF 標誌出棧.
PUSHD 32位標誌入棧.
POPD 32位標誌出棧.
二、算術運算指令
───────────────────────────────────────
ADD 加法.
ADC 帶進位加法.
INC 加 1.
AAA 加法的ASCII碼調整.
DAA 加法的十進制調整.
SUB 減法.
SBB 帶借位減法.
DEC 減 1.
NEC 求反(以 0 減之).
CMP 比較.(兩操作數作減法,僅修改標誌位,不回送結果).
AAS 減法的ASCII碼調整.
DAS 減法的十進制調整.
MUL 無符號乘法.
IMUL 整數乘法.
以上兩條,結果回送AH和AL(字節運算),或DX和AX(字運算),
AAM 乘法的ASCII碼調整.
DIV 無符號除法.
IDIV 整數除法.
以上兩條,結果回送:
商回送AL,餘數回送AH, (字節運算);
或 商回送AX,餘數回送DX, (字運算).
AAD 除法的ASCII碼調整.
CBW 字節轉換爲字. (把AL中字節的符號擴展到AH中去)
CWD 字轉換爲雙字. (把AX中的字的符號擴展到DX中去)
CWDE 字轉換爲雙字. (把AX中的字符號擴展到EAX中去)
CDQ 雙字擴展. (把EAX中的字的符號擴展到EDX中去)
三、邏輯運算指令
───────────────────────────────────────
AND 與運算.
OR 或運算.
XOR 異或運算.
NOT 取反.
TEST 測試.(兩操作數作與運算,僅修改標誌位,不回送結果).
SHL 邏輯左移.
SAL 算術左移.(=SHL)
SHR 邏輯右移.
SAR 算術右移.(=SHR)
ROL 循環左移.
ROR 循環右移.
RCL 通過進位的循環左移.
RCR 通過進位的循環右移.
以上八種移位指令,其移位次數可達255次.
移位一次時, 可直接用操作碼. 如 SHL AX,1.
移位>1次時, 則由寄存器CL給出移位次數.
如 MOV CL,04
SHL AX,CL
四、串指令
───────────────────────────────────────
DS:SI 源串段寄存器 :源串變址.
ES:DI 目標串段寄存器:目標串變址.
CX 重複次數計數器.
AL/AX 掃描值.
D標誌 0表示重複操作中SI和DI應自動增量; 1表示應自動減量.
Z標誌 用來控制掃描或比較操作的結束.
MOVS 串傳送.
( MOVSB 傳送字符. MOVSW 傳送字. MOVSD 傳送雙字. )
CMPS 串比較.
( CMPSB 比較字符. CMPSW 比較字. )
SCAS 串掃描.
把AL或AX的內容與目標串作比較,比較結果反映在標誌位.
LODS 裝入串.
把源串中的元素(字或字節)逐一裝入AL或AX中.
( LODSB 傳送字符. LODSW 傳送字. LODSD 傳送雙字. )
STOS 保存串.
是LODS的逆過程.
REP 當CX/ECX<>0時重複.
REPE/REPZ 當ZF=1或比較結果相等,且CX/ECX<>0時重複.
REPNE/REPNZ 當ZF=0或比較結果不相等,且CX/ECX<>0時重複.
REPC 當CF=1且CX/ECX<>0時重複.
REPNC 當CF=0且CX/ECX<>0時重複.
五、程序轉移指令
───────────────────────────────────────
1>無條件轉移指令 (長轉移)
JMP 無條件轉移指令
CALL 過程調用
RET/RETF過程返回.
2>條件轉移指令 (短轉移,-128到+127的距離內)
( 當且僅當(SF XOR OF)=1時,OP1<OP2 )
JA/JNBE 不小於或不等於時轉移.
JAE/JNB 大於或等於轉移.
JB/JNAE 小於轉移.
JBE/JNA 小於或等於轉移.
以上四條,測試無符號整數運算的結果(標誌C和Z).
JG/JNLE 大於轉移.
JGE/JNL 大於或等於轉移.
JL/JNGE 小於轉移.
JLE/JNG 小於或等於轉移.
以上四條,測試帶符號整數運算的結果(標誌S,O和Z).
JE/JZ 等於轉移.
JNE/JNZ 不等於時轉移.
JC 有進位時轉移.
JNC 無進位時轉移.
JNO 不溢出時轉移.
JNP/JPO 奇偶性爲奇數時轉移.
JNS 符號位爲 "0" 時轉移.
JO 溢出轉移.
JP/JPE 奇偶性爲偶數時轉移.
JS 符號位爲 "1" 時轉移.
3>循環控制指令(短轉移)
LOOP CX不爲零時循環.
LOOPE/LOOPZ CX不爲零且標誌Z=1時循環.
LOOPNE/LOOPNZ CX不爲零且標誌Z=0時循環.
JCXZ CX爲零時轉移.
JECXZ ECX爲零時轉移.
4>中斷指令
INT 中斷指令
INTO 溢出中斷
IRET 中斷返回
5>處理器控制指令
HLT 處理器暫停, 直到出現中斷或復位信號才繼續.
WAIT 當芯片引線TEST爲高電平時使CPU進入等待狀態.
ESC 轉換到外處理器.
LOCK 封鎖總線.
NOP 空操作.
STC 置進位標誌位.
CLC 清進位標誌位.
CMC 進位標誌取反.
STD 置方向標誌位.
CLD 清方向標誌位.
STI 置中斷允許位.
CLI 清中斷允許位.
六、僞指令
───────────────────────────────────────
DB 定義字節.
DW 定義字(2字節).
PROC 定義過程.
ENDP 過程結束.
SEGMENT 定義段.
ASSUME 建立段寄存器尋址.
ENDS 段結束.
END 程序結束.
● \n只代表一個字符。類似於\n的轉義字符序列爲表示無法輸入的字符或不可見
字符提供了一種通用的可擴充的機制。除此之外,C 語言提供的轉義字符序列還包括:\t 表
示製表符;\b 表示回退符;\"表示雙引號;\\表示反斜槓符本身
● 符號常量:用標示符代表一個常量。在C語言中,可以用一個標識符來表示一個常量,
稱之爲符號常量。
符號常量在使用之前必須先定義,也叫具名常量,其一般形式爲:
#define 標識符(常量名) 常量值
直接常量:直接常量是指在程序中,以直接明顯的形式給出的數據。根據使用的數據類型不同,可分爲:字符串常量、數值常量、邏輯常量和日期常量。
字符串常量:是用兩個雙引號括起來的一串字符。例如:"2345"和"Basic"等。
數值常量:數值常量就是常數,包括整數、長整數、單精度數和雙精度數。例如:2006、3.14159265、2.9D67等。
邏輯常量:邏輯常量只有True或False兩個值。
日期常量:用兩個【#】把表示日期和時間的值括起來表示日期常量。
● :數據類型[32位系統] char(1字節) bool(1字節) short(2字節) WORD(2字節) DWORD(4字節) int(4字節) LONG(4字節) float(4字節) double(8字節)
基本的數據類型默認情況下是signed"有符號的",無符號表示unsigned 數據類型
***變量使用之前必須要先定義變量。一般情況下,都會在變量定義語句之後才使用變量;若一定要在變量定義語句之前使用變量,也必須藉助關鍵字extern來聲明變量。使用extern聲明變量的形式如下:extern 數據類型名 變量名;
extern 修飾的變量,表示變量或者函數的定義在別的文件中,提示編譯器遇到此變量和函數時在其他模塊中尋找其定義,例如系統服務描述表:
首先定義這個表的結構:結構名(類型名)不一定非要和系統的一致,只要成員類型以及成員名一致即可
typedef struct SYSTEM_DESCRIPTOR_TABLE
{
PVOID ServiceTableBase;
PVOID ServiceCounterTable;
unsigned int NumberOfServices;
PVOID ParamTableBase;
}SYSTEMSERVICEDESCRIPTORTABLE,*PSYSTEMSERVICEDESCRIPTORTABLE
這個結構名是自己定義的,若要與系統的服務描述錶鏈接起來,則要用extern,系統服務描述表併爲導出,只是導出了一個指針KeServiceDescriptorTable,只要把我們定義的結構地址指向這個指針即可:
extern PSYSTEMSERVICEDESCRIPTORTABLE KeServiceDescriptorTable;
● 地址操作符只能使用變量作爲操作數,不能將其用在常量前面,要取得一個地址中的內容,可以使用指針操作符*。指針操作符的操作對象必須爲地址
● 當指針指向直接常量時, 直接常量應該用雙引號而不能用單引號,因爲單引號是一個char型的字符, 不是直接常量.所以必須取地址,賦值的時候並不是把字符串賦值給指針,而是把字符串的首地址賦給指針.
● 指針和引用的區別:1.指針佔四個字節, 引用不佔 2.引用申明時必須初始化, 指針不用
/*
*p++ 等價於 *(p++); 先算出*p, 然後p自加
*++p 等價於 *(++p); 先p自加,然後計算*p
++*p *P值自加
*/
● 當多個賦值操作符存在於同一條語句中時,按照C標準的規定,將會按照從右到左的結合方式先執行最右邊的賦值操作符,再依次向左執行其餘賦值操作符
● 當表達式不僅僅是一個變量或一個常量時,需要把表達式放在小括號內。例如:m = (int) (3.7 + 2.5)這時,程序會對3.7和2.5的和類型轉換爲int型。如果沒有括號,只是:m = (int) 3.7 + 2.5
● 數值運算中的類型轉換遵從以下規則:char型和short型數據,不管是unsigned還是usigned,在計算中都要先無條件轉換爲int型,即使是兩個char型或short型數值相加。float型計算前會無條件轉換爲double型數值,以增加精度。其餘類型的數值計算時,都要轉換爲數據類型級別較高的後再計算。
● itoa( //轉換到字符串( Integer To A String)
int nsro, //要被轉換的整數
char *buffer //轉換後存放的緩衝區
int rudix //以何種進制形式輸出
)
int atoi(const char *string) 把字符串轉換到整數型
● 3個邏輯操作符組合使用時,可以得到多個功能一致但形式不同的邏輯表達式。使用簡單的邏輯表達式來代替複雜的表達式是提高程序邏輯性的一個重要方法。下面是兩組功能一樣的邏輯表達式:第一組表達式如下:!(a || b)!a && !b第二組表達式如下:!(a && b)!a || !b使用以上功能相同的兩組邏輯表達式可以對一些複雜的、邏輯不清的表達式進行簡化。
● 前置操作符都是一元操作符,其優先級爲2,爲第二高。很容易理解將它們得結合性規定爲從右到左的原因,例如:+-+a;-++b ;!sizeof(c)按結合性從右到左,等效於:+(-(+a));-(++b);!(sizeof(c));
● switch語句是C語言中選擇結構的另一個常用的實現方式,十分適用於多路選擇的實現,break語句在switch語句中的作用十分重要。在switch語句中,遇到break便終止執行switch語句,跳出本層switch體,繼續執行後續語句,continue語句只能使用在for語句、while語句和do-while語句的循環體中。其功能爲結束本次循環,直接開始下一次循環。goto語句在C語句中可以實現無條件跳轉,通過和if語句的有效組合也可以實現循環結構。
● For語句的標準形式如下:for ( 表達式1; 表達式2; 表達式3) 表達式1用作循環結構的初始化,一般爲賦值表達式,設定循環變量及其他變量的初始值;表達式2負責循環條件的判斷,形式與if語句的控制表達式類似,一般爲關係表達式或邏輯表達式;表達式3負責改變表達式2中的循環變量的值,一般也爲有賦值作用的表達式。for ( 初始化表達式; 循環判斷表達式; 循環變量控制表達式 ));while ( 表達式 )
● 初始化數組元素的部分必須是數組中從第0個元素開始的連續序列。例如,以下初始化語句是錯誤的:int window[6] = {1, , 3, 4, 5};/* 第1個元素未賦值 */和其他類型變量一樣,數組元素也具有地址。數組在內存中是作爲一個整體分配內存的,數組元素的內存地址是連續的,其差值爲數組存儲的數據類型的字節長度值。如果存儲的是int型,那麼數組元素的地址都相差4;如果是char型,那麼數組元素的地址都相差1。比較特殊的是,數組變量的值是該數組的第0個元素的地址,即數組的首地址。C語言中的二維數組可以理解爲一種特殊的一維數組,可以看作數組元素爲一維數組的一維數組。爲了方便理解,可以把二維數組看作是一個矩陣,一維容量爲行數,二維容量爲列數。matrix[i][j]即爲matrix中第i行第j列的元素;推廣到一般情況,對於:數據類型名 array[M][N];array[i][j]在其內存中爲第(i×N+j)個元素。同樣的,二維數組初始化也可以不指定數組容量,但是隻能不指定一維數組容量(第一個數組容量),二維數組容量(第二個數組容量)必須給出。字符串常量在C語言中被處理爲一維字符數組存儲在內存中一塊連續的區域內。
● 數組的特點:
1, 連續的內存分配
2, 可以通過下標進行訪問
3, 數組的名字就是數組的首地址
4, 數組不能動態改變大小
5, 數組在中間或前端插入或刪除一個元素效率低
鏈表特點:
1, 不連續的內存分配
2, 不能通過下標進行訪問
3, 鏈表可以動態改變其長度
4, 鏈表在前面或中間插入元素, 速度快
● 函數聲明也可以稱爲函數原型,定義了函數作爲模塊化編程的基本單元的接口:函數值類型對應模塊出口,函數名對應模塊名,參數列表對應了模塊入口。函數返回值類型也稱爲函數值類型,是由函數帶回的值的類型。與變量一樣,函數的聲明和定義也是有嚴格區別的。函數聲明確定了一個函數的接口,告訴編譯器該函數的函數名、函數值類型以及形參列表中形參的個數和順序;而函數定義則確立了一個函數的功能,不僅僅包含了函數聲明所有的信息,還包含了形參的名字和函數體。關鍵字extern和static可以用來聲明函數,使用extern聲明的函數爲外部函數.C語言中規定,所有函數默認聲明爲extern類別,即外部函數。也就是說在之前的所有例子中定義的函數都是外部函數。
● C語言中定義了4個關鍵字作爲變量的存儲類別的修飾詞,分別爲:auto、static、register和extern。變量的存儲類別決定了變量在內存中的存儲區域。1. 棧是由編譯器管理的動態存儲區域,用於存儲臨時變量,即只在需要時才被分配內存,不需要時編譯器會自動回收。可存放的數據包括以下幾項。函數形參:其只在函數執行期內有效。局部變量(不包括static修飾的局部變量):只在它定義的程序塊及其下層程序塊的執行期內有效。其他臨時變量(例如,a++語句中產生的臨時變量):函數返回時產生的臨時變量。2. 堆是由程序管理的動態存儲區域,用於分配由程序使用malloc函數申請的內存空間,需要由程序自行釋放。在內存管理一章中將討論這塊存儲區域的使用。堆上分配的內存也是未經初始化的,需要程序顯式地初始化。3. 靜態存儲區用於存儲全局變量,該區域的內存在程序開始時就已固定分配完畢,直到程序結束由編譯器自動釋放,在該區域分配的內存在整個程序執行過程中都是有效的。全局變量全部存放在靜態存儲區中。靜態存儲區的內存分配時由編譯器自動初始化。auto變量的內存由編譯器自動從棧上分配,因此auto變量都是臨時變量。使用auto聲明變量的形式十分簡單,如下所示。auto 數據類型 變量名;或數據類型 auto 變量名,所有的局部變量的聲明中如果不含存儲類別,那麼都默認爲auto型變量。C語言中提供了一種存儲類別register,這種存儲類別的變量的值會被要求直接存儲在寄存器中。訪問該變量無需從內存中獲取它的值,存儲該變量時也無需再存回內存,都直接在寄存器上進行操作。由於寄存器的存取速度要遠快於內存的存取速度,因此,如果一個該變量在程序中被頻繁使用,那麼將其聲明爲register變量,將大大提高程序的執行效率。register型的變量聲明如下所示。register 數據類型名 變量名;register只能修飾函數內的變量,包括局部變量和形式參數C程序中的所有變量都有一定的生存期和作用域。生存期是指程序運行時,變量佔有內存的時間。變量作用域是指在程序中,變量可以被使用的有效代碼區域。爲了便於討論變量的作用域,按程序塊間的關係將程序塊分爲4類:本層程序塊、上層程序塊、下層程序塊和外部程序塊。如果局部變量沒有被顯式地初始化,編譯器不會自動爲其清理內存;如果全局變量沒有爲其顯式初始化,編譯器則會自動初始化,將其內存空間清除歸零。
● new 是一個運算符, 它返回一個分配在堆空間的內存地址,所以 = 左邊必須是指針類型
● 直接從空間的地址獲取該內存內容的訪問方式叫做內存的“直接訪問”。先從其他內存空間獲得要訪問的內存空間的地址,再根據該地址訪問目的空間的方法就是內存的“間接訪問”。
● 在操作系統中,數值一律用補碼來存儲。一個數值的二進制值可以稱其爲原碼,存儲時會將原碼錶示爲補碼。補碼的最高位爲符號位,數值的補碼錶示可以分爲以下兩種情況:1.非負數的補碼,非負數的補碼與原碼相同。例如,11的原碼爲00001011,其補碼也爲00001011。2.負數的補碼負數的補碼的符號位爲1,其餘位爲將該數絕對值的原碼按位取反後再加1的結果。例如,-15的補碼:因爲是負數,則符號位爲“1”,整個爲10001111;其餘7位爲-15的絕對值的原碼按位取反,即0001111取反後爲1110000,再加1爲1110001;加上符號位,最後-15的補碼是11110001。已知一個數的補碼,其求原碼的過程與已知原碼求補碼的過程完全一樣:如果補碼的符號位爲“0”,表示是一個正數,所以補碼就是該數的原碼。如果補碼的符號位爲“1”,表示是一個負數,求原碼的操作可以是:符號位爲1,其餘各位取反加1。使用補碼計算時,可以將符號位和其他位統一處理。注意:兩個用補碼錶示的數相加時,如果最高位(符號位)有進位,則進位被捨棄。減法可按加負數法來處理。C語言共提供了6個位運算操作符,包括取反操作符(~)、位或操作符(|)、位與操作符(&)、異或操作符(^)和位移操作符(>>和<<)取反操作符是一個一元操作符,其使用形式爲:~操作數;位或操作符是一個二元操作符,形式如下:數1 | 數2兩個位值進行位或運算的規則是:只要有一個數值爲1,位或的結果便爲1;如果都爲0,位或的結果爲0。位與操作符也是一個二元操作符,形式如下:數1 & 數2這兩個數可以是常量,也可以是變量。位與操作符將兩個操作數逐位進行位與運算。位與運算的規則是:只要有一個數值爲0,位與的結果便爲0;如果都爲1,位與的結果爲1。異或操作符是多元操作符,使用形式如下:數1 ^ 數2表達式將數1和數2進行異或運算,如果兩個數一樣,則值爲0;如果不一樣,值爲1。右移操作符的作用是將一個數的各二進制值全部右移若干位。其使用形式如下:數值1 >> 數值2;該表達式將數值1的二進制值右移(數值2)位,移到右端的低位被捨棄,而高位補入的數值由符號位決定。如果是無符號數,則高位補入的數爲0。左移操作符將一個數的各個二進制位值全部左移若干位。使用形式如下:數值1 << 數值2;該表達式將數值1的二進制值左移(數值2)位,移到左端的高位被捨棄,而低位補入的數值爲0。將a左移1位,即等於將a乘以2;將a左移n位,即等於將a乘以2的n次方。
● 由於結構體數據類型的名字由標識符和結構體名兩部分組成,書寫起來名字較長,因此常常使用typedef來簡化其數據類型名字,如下所示。一般的系統中,爲了尋址的方便,數據在內存中存儲時一般以其本身數據類型的字節長度爲基本單位對齊。例如,int型在內存中是以sizeof(int)個字節對齊的,char型在內存中是以sizeof(char)個字節對齊的。而結構體數據在存儲時是以其中字節長度最大的成員的字節數爲基本單位對齊的。所謂對齊,是指將內存以一個固定的字節數作爲最小單位分塊,分配內存時只能一塊一塊地分配。例如,以4字節對齊,就是將整個內存4個字節4個字節地分塊,分配內存時只能4個字節4個字節地分。可以使用#pragma pack預處理命令來改變對齊規則。由於共用體實際上只有一個有效成員,因此無法像初始化結構體那樣使用多個值的序列對共用體進行初始化,只能使用含一個值的序列對其初始化,由於共用體中的所有成員共享一塊空間,因此對任意成員的賦值都會影響其他成員的值。由於宏函數體只能是與宏函數同一行的後面的內容,因此當函數體內容較多時,如果都寫在一行中,會影響程序的可讀性。爲使程序邏輯清晰,可以使用分行符“\”將宏函數體分拆爲多行。如果一個宏函數需要返回一個值,即可能作爲一個語句的子表達式,應該將整個宏函數體放在一個括號內;如果宏函數只執行一些操作,不返回值,則應該將整個函數體放在花括號內,形成單獨一個程序塊。
● 結構體和類唯一區別, 結構體成員默認情況下是公有的(public),C語言中結構體不能寫函數
● 一般情況下,C程序的所有代碼都會被編譯器編譯。C語言提供了條件編譯使程序員能設定需要被編譯的代碼。條件編譯通過條件判斷的方法來實現編譯代碼的選擇。當滿足一些條件時,編譯纔會編譯其指定的代碼。本節將討論幾種條件編譯命令的使用。#ifdef命令的形式有三種,如下所示:1.形式一#ifdef 標識符程序段1#else程序段2#endif。#ifndef命令和#ifdef命令剛好相反。該形式的#ifndef命令實現了程序段1和程序段2的選擇編譯功能,即:如果程序未宏定義標識符,則編譯程序段1;如果該標識符已被宏定義,則對程序段2進行編譯。
2. ● ● ● ● Windows程序設計● ● ● ●
● 表頭文件
WINDOWS.H是主要的引入文件,它包含了其它Windows表頭文件,這些表頭文件的某些也包含了其它表頭文件。這些表頭文件中最重要的和最基本的是:
WINDEF.H 基本型態定義。
WINNT.H 支持Unicode的型態定義。
WINBASE.H 系統核心函數。
WINUSER.H 用戶接口函數。
WINGDI.H 圖形設備接口函數。
int WINAPI WinMain(
HINSTANCE hInstance, // 當前應用程序實例句柄,由系統提供
HINSTANCE hPrevInstance, // 上一個實例句柄 ,CreateMutex可創建實例互斥體
LPSTR lpCmdLine, // cmd命令行,可以使用 GetCommandLine獲得完整的命令行參數
int nCmdShow // 窗口顯示方式,以SW_XXXX開始,例:SW_NORMAL 默認顯示方式
);
WinMain中第三個參數在WINBASE.H中定義爲LPSTR,我將它改爲PSTR。這兩種數據型態都定義在WINNT.H中,作爲指向字符串的指針。LP前綴代表「長指針」,這是16位Windows下的產物。在WinMain聲明中改變了兩個參數的名稱。許Windows程序中的變量名使用一種稱作「匈牙利表示法」的命名系統,該系統在變量名稱前面增加了表示變量數據型態的短前綴,現在僅需記住前綴i表示int、sz表示String of Zero「以零結束的字符串」。
-----------------------------------------------------------------------------------------------
● //-Unicode的簡介
***寬字符和C語言
C中的寬字符基於wchar_t數據型態,它在幾個表頭文件包括WCHAR.H中都有定義,像這樣:typedef unsigned short wchar_t ;因此,wchar_t數據型態與無符號短整數型態相同,都是16位寬。
strlen接受的是char類型字符,如果接受Unicode字符,編譯器會把每個字符都分配爲兩個字節,如果第一個字符爲字母,則被分配爲這個字母的ASCII字符和0,如果此時再次用strlen()命令將返回爲1[第二個字符爲0則結束],並不會返回定義的Unicode字符的長度.strlen函數的寬字符版是wcslen(wide-character string length:寬字符串長度),而wcslen函數則說明如下:size_t __cdecl wcslen (const wchar_t *) ;size_t在stddef.h中定義:typedef unsigned int size_t;TCHAR.H還用一個新的數據型態TCHAR來解決兩種字符數據型態的問題.
if ANSI:typedef char CHAR ;
if UNICODE:typedef wchar_t WCHAR ;
---------------------------------------------------------------------------
#ifdef ANSI
typedef CHAR * PCHAR, * LPCH, * PCH, * NPSTR, * LPSTR, * PSTR ;
typedef CONST CHAR * LPCCH, * PCCH, * LPCSTR, * PCSTR ; ANSI 字符定義,8_bit
#endif
前綴N和L表示「near」和「long」,指的是16位Windows中兩種大小不同的指標。在Win32中near和long指標沒有區別
------------------------------------------------------------------------------------
#IFDEF UNICODE
typedef WCHAR * PWCHAR, * LPWCH, * PWCH, * NWPSTR, * LPWSTR, * PWSTR ;
typedef CONST WCHAR * LPCWCH, * PCWCH, * LPCWSTR, * PCWSTR ; UNCODE 字符定義 16_bit
#ENDIF
-----------------------------------------------------------------------------------------------
● //-窗口和消息
***Windows-Hello World函數
HICON LoadIcon(HINSTANCE hInstance,LPCTSTR lpIconName) 加載圖標供程序使用。
HCURSOR LoadCursor(HINSTANCE hInstance,LPCTSTR lpCursorName) 加載鼠標光標供程序使用。
HGDIOBJ GetStockObject(int fnObject) 取得一個圖形對象(在這個例子中,是取得繪製窗口背景的畫刷對象)。
ATOM RegisterClass (CONST WNDCLASS *lpWndClass)爲程序窗口註冊窗口類別。
int MessageBox(HWND,LPCTSTR,LPCTSTR,UINT) 顯示消息框。
HWND CreateWindow( //根據窗口類別建立一個窗口。
LPCTSTR lpClassName, // 窗口類名
LPCTSTR lpWindowName, // 窗口標題
DWORD dwStyle, // 窗口風格
int x, // X座標
int y, // Y座標
int nWidth, // 寬度
int nHeight, // 高度
HWND hWndParent, // 父窗口句柄
HMENU hMenu, // 菜單句柄
HANDLE hInstance, // 單籤實例句柄
LPVOID lpParam // 指向CREATESTRUCT結構
);
ShowWindow 在屏幕上顯示窗口,第二個參數是作爲參數傳給WinMain的iCmdShow。它確定最初如何在屏幕上顯示窗口,是一般大小、最小化還是最大化。
UpdateWindow 指示窗口自我更新。
GetMessage 從消息隊列中取得消息,第二、第三和第四個參數設定爲NULL或者0,表示程序接收它自己建立的所有窗口的所有消息。
TranslateMessage 轉譯某些鍵盤消息。
DispatchMessage 將消息發送給窗口消息處理程序。
PlaySound 播放一個聲音文件。
BeginPaint 開始繪製窗口。
GetClientRect 取得窗口顯示區域的大小。
DrawText 顯示字符串。
EndPaint 結束繪製窗口。
PostQuitMessage 在消息隊列中插入一個「退出程序」消息。
DefWindowProc 執行內定的消息處理。
窗口消息處理程序總是定義爲如下形式:LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT在winuser.h定義爲:typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
這裏typedef的用法是:把LRESULT作爲WNDPROC的函數指針
窗口消息處理程序是命名爲WndProc的函數。窗口消息處理程序可任意命名(只要求不和其它名字發生衝突)。一個Windows程序可以包含多個窗口消息處理程序。一個窗口消息處理程序總是與呼叫RegisterClass註冊的特定窗口類別相關聯
一般來說,Windows程序寫作者使用switch和case結構來確定窗口消息處理程序接收的是什麼消息,以及如何適當地處理它。窗口消息處理程序在處理消息時,必須傳回0。窗口消息處理程序不予處理的所有消息應該被傳給名爲DefWindowProc的Windows函數。從DefWindowProc傳回的值必須由窗口消息處理程序傳回。
對WM_PAINT的處理幾乎總是從一個BeginPaint呼叫開始,而以一個EndPaint呼叫結束.
***Windows中使用的值常數在表頭文件中均有相應的標識符定義。
CS 窗口類別樣式[ClassStyle]
CW 建立窗口[CreateWindow]
DT 繪製文字[DrawText]
IDI 圖示ID[IDIcon]
IDC 遊標ID[IDCursor]
MB 消息框[MessageBox]
SND 聲音[Sound]
WM 窗口消息[WindowMessage]
WS 窗口樣式[WindowStyle]
● WNDCLASS[窗口類],表頭文件WINUSER.H中定義
typedef struct _WNDCLASS {
UINT style; //顯示風格,以CS_XXXX形式
WNDPROC lpfnWndProc; //WndProc函數子程
int cbClsExtra; //初始化爲0
int cbWndExtra; //初始化爲0
HANDLE hInstance; //當前實例句柄
HICON hIcon; //圖標句柄
HCURSOR hCursor; //光標句柄
HBRUSH hbrBackground; //背景顏色,COLOR_XXXXX形式
LPCTSTR lpszMenuName; //菜單名稱,填NULL,則使用默認名稱
LPCTSTR lpszClassName; //窗口類名
} WNDCLASS;
其中的lpfn前綴代表「指向函數的指標」。(在Win32 API中,長指標和短指標(或者近程指標)沒有區別。這只是16位Windows的遺物。)cb前綴代表「字節數CountOfByte」而且通常作爲一個常數來表示一個字節的大小。h前綴是一個句柄,而hbr前綴代表「一個畫刷的代號」。lpsz前綴代表「指向以0結尾字符串的指針」。
● 普通的迭代窗口:各窗口左上角的垂直和水平距離在屏幕上按一定的大小遞增
● 消息類型
typedef struct tagMSG { // 在WinUser.h中定義
HWND hwnd; //接受消息的窗口句柄
UINT message; //消息類型,一般以WM_XXXX形式
WPARAM wParam; //消息參數值,據消息而定
LPARAM lParam; //消息參數值,據消息而定
DWORD time; //消息放入消息隊列中的時間
POINT pt; //消息發佈時,光標的座標
} MSG;
● //-輸出文字
要得到窗口顯示區域的設備內容句柄,可以呼叫 HDC GetDC(HWND hWnd)來取得句柄,在使用完後呼叫ReleaseDC
GetSystemMetrics函數以取使用者接口上各類視覺組件大小的信息,取得與系統有關的度量信息
BOOL GetTextMetrics( //取得字體大小
HDC hdc, // 指向設備上下文句柄
LPTEXTMETRIC lptm // 指向TEXTMETRIC結構
);
TEXTMETRIC結構定義如下:
typedef struct tagTEXTMETRIC { // tm
LONG tmHeight;
LONG tmAscent;
LONG tmDescent;
LONG tmInternalLeading;
LONG tmExternalLeading;
LONG tmAveCharWidth;
LONG tmMaxCharWidth;
LONG tmWeight;
LONG tmOverhang;
LONG tmDigitizedAspectX;
LONG tmDigitizedAspectY;
BCHAR tmFirstChar;
BCHAR tmLastChar;
BCHAR tmDefaultChar;
BCHAR tmBreakChar;
BYTE tmItalic;
BYTE tmUnderlined;
BYTE tmStruckOut;
BYTE tmPitchAndFamily;
BYTE tmCharSet;
} TEXTMETRIC;
● 滾動條的範圍和位置:BOOL SetScrollRange (hwnd, iBar, iMin, iMax, bRedraw) ;參數iBar爲SB_VERT或者SB_HORZ,iMin和iMax分別是範圍的最小值和最大值。如果想要Windows根據新範圍重畫滾動條,則設置bRedraw爲TRUE(如果在呼叫SetScrollRange後,呼叫了影響滾動條位置的其它函數,則應該將bRedraw設定爲FALSE以避免過多的重畫)。Windows提供了類似的函數(GetScrollRange和GetScrollPos/ShowScrollBar)來取得滾動條的目前範圍和位置以及顯示滾動條.
● //-圖形基礎
BeginPaint、GetDC和GetWindowDC獲得的設備內容都與視訊顯示器上的某個特定窗口相關。取得設備內容句柄的另一個更通用的函數是CreateDC:hdc = CreateDC (pszDriver, pszDevice, pszOutput, pData) ;可以通過下面的呼叫來取得整個屏幕的設備內容句柄:hdc = CreateDC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;
metafile是一些GDI呼叫的集合,以二進制形式編碼。您可以通過取得metafile設備內容來建立metafile,hdcMeta = CreateMetaFile (pszFilename) ;
● //-鍵盤
***按鍵消息
----------------------------------------
| | 鍵按下 | 鍵釋放 |
----------------------------------- |
|非系統鍵 | WM_KEYDOWN | WM_KEYUP |
--------------------------------------- |
|系統鍵 | WM_SYSKEYDOWN | WM_SYSKEYUP |
---------------------------------------
在四個按鍵消息(WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN和WM_SYSKEYUP)中,wParam消息參數含有上面所討論的虛擬鍵碼,而lParam消息參數則含有對了解按鍵非常有用的其它信息。lParam的32位分爲6個字段,0-15位記錄重複計數,16-23位指定OEM掃描碼(鍵盤硬件產生的代碼),第24爲保存擴充鍵旗標,第29位保存內容代碼,第30位保存此按鍵的先前狀態,第31爲保存轉換狀態.
重複計數:重複計數是該消息所表示的按鍵次數,大多數情況下,重複計數設定爲1。不過,如果按下一個鍵之後,您的窗口消息處理程序不夠快,以致不能處理自動重複速率(您可以在「控制檯」的「鍵盤」中進行設定)下的按鍵消息,Windows就把幾個WM_KEYDOWN或者WM_SYSKEYDOWN消息組合到單個消息中,並相應地增加重複計數。WM_KEYUP或WM_SYSKEYUP消息的重複計數總是爲1。因爲重複計數大於1指示按鍵速率大於您程序的處理能力,所以您也可能想在處理鍵盤消息時忽略重複計數。幾乎每個人都有文書處理或執行電子表格時畫面捲過頭的經驗,因爲多餘的按鍵堆滿了鍵盤緩衝區,所以當程序用一些時間來處理每一次按鍵時,如果忽略您程序中的重複計數,就能夠解決此問題。不過,有時可能也會用到重複計數,您應該嘗試使用兩種方法執行程序,並從中找出一種較好的方法。
OEM掃描碼:OEM掃描碼是由硬件(鍵盤)產生的代碼。這對中古時代的彙編程序寫作者來說應該很熟悉,它是從PC相容機種的ROM BIOS服務中所獲得的值(OEM指的是PC的原始設備製造商(Original Equipment Manufacturer)及其與「IBM標準」同步的內容)。在此我們不需要更多的信息。除非需要依賴實際鍵盤佈局的樣貌,不然Windows程序可以忽略掉幾乎所有的OEM掃描碼信息
擴充鍵旗標:如果按鍵結果來自IBM增強鍵盤的附加鍵之一,那麼擴充鍵旗標爲1(IBM增強型鍵盤有101或102個鍵。功能鍵在鍵盤頂端,光標移動鍵從數字鍵盤中分離出來,但在數字鍵盤上還保留有光標移動鍵的功能)。對鍵盤右端的Alt和Ctrl鍵,以及不是數字鍵盤那部分的光標移動鍵(包括Insert和Delete鍵)、數字鍵盤上的斜線(/)和Enter鍵以及Num Lock鍵等,此旗標均被設定爲1。Windows程序通常忽略擴充鍵旗標。
內容代碼:右按鍵時,假如同時壓下ALT鍵,那麼內容代碼爲1。對WM_SYSKEYUP與WM_SYSKEYDOWN而言,此位總視爲1;而對WM_SYSKEYUP與WM_KEYDOW消息而言,此位爲0。除了兩個之外:如果活動窗口最小化了,則它沒有輸入焦點。這時候所有的按鍵都會產生WM_SYSKEYUP和WM_SYSKEYDOWN消息。如果Alt鍵未被按下,則內容代碼字段被設定爲0。Windows使用WM_SYSKEYUP和WM_SYSKEYDOWN消息,從而使最小化了的活動窗口不處理這些按鍵。對於一些外國語文(非英文)鍵盤,有些字符是通過Shift、Ctrl或者Alt鍵與其它鍵相組合而產生的。這時內容代碼爲1,但是此消息並非系統按鍵消息。
鍵的先前狀態:如果在此之前鍵是釋放的,則鍵的先前狀態爲0,否則爲1。對WM_KEYUP或者WM_SYSKEYUP消息,它總是設定爲1;但是對WM_KEYDOWN或者WM_SYSKEYDOWN消息,此位可以爲0,也可以爲1。如果爲1,則表示該鍵是自動重複功能所產生的第二個或者後續消息。
轉換狀態:如果鍵正被按下,則轉換狀態爲0;如果鍵正被釋放,則轉換狀態爲1。對WM_KEYDOWN或者WM_SYSKEYDOWN消息,此字段爲0;對WM_KEYUP或者WM_SYSKEYUP消息,此字段爲1。
***虛擬鍵碼
虛擬鍵碼保存在WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN和WM_SYSKEYUP消息的wParam參數中,此代碼標識按下或釋放的鍵,以VK_XXX形式存定義在WinUser.h中,其中VK_RETURN爲Enter鍵,VK_MENU爲Alt鍵,VK_PRIOR爲Page Up鍵,VK_NEXT爲Page Down鍵,VK_SNAPSHOT爲Print Screen鍵,VK_CLEAR爲Num Lock關閉時的數字鍵盤5
***位移狀態
在處理按鍵消息時,您可能需要知道是否按下了位移鍵(Shift、Ctrl和Alt)或開關鍵(Caps Lock、Num Lock和Scroll Lock)。通過呼叫GetKeyState函數,您就能獲得此信息。例如:iState = GetKeyState (VK_SHIFT) ;如果按下了Shift,則iState值爲負(即設定了最高位置位),SHORT GetKeyState(int nVirtKey)若返回1,則表示此按鍵被按下,若是其他,則是沒有被按下
SHORT GetAsyncKeyState(
int vKey // virtual-key code
);//判斷鍵盤上任何按鍵的狀態
BOOL GetKeyboardState(
PBYTE lpKeyState // 指定256字節數組接受鍵狀態
); //獲取全部按鍵狀態
***字符消息
---------------------------------------------
| | 字符 | 死字符 |
--------------------------------------------|
|非系統字符| WM_CHAR | WM_DEADCHAR |
------------------------------------------ |
|系統字符 | WM_SYSCHAR | WM_SYSDEADCHAR |
--------------------------------------- ----
參數wParam是ANSI或Unicode字符代碼,lParam參數與按鍵消息的lParam參數相同
int GetKeyNameText(
LONG lParam, // 消息的第二個參數
LPTSTR lpString, // 指定一個緩衝區,存放字符串
int nSize // 鍵名字的最大長度
);
***消息順序
因爲TranslateMessage函數從WM_KEYDOWN和WM_SYSKEYDOWN消息產生了字符消息,所以字符消息是夾在按鍵消息之間傳遞給窗口消息處理程序的,WM_KEYDOWN->WM_CHAR->WM_KEYUP.如果您按下Shift鍵,再按下A鍵,然後釋放A鍵,再釋放Shift鍵,就會輸入大寫的A,而窗口消息處理程序會接收到五個消息,依次:WM_KEYDOWN[虛擬鍵碼VK_SHIFT (0x10)]->WM_KEYDOWN[「A」的虛擬鍵碼(0x41)]->WM_CHAR[「A」的字符代碼(0x41)]->WM_KEYUP[「A」的虛擬鍵碼(0x41)]->WM_KEYUP[虛擬鍵碼VK_SHIFT(0x10)],Shift鍵本身不產生字符消息。
***處理控制字符
處理按鍵和字符消息的基本規則是:如果需要讀取輸入到窗口的鍵盤字符,那麼您可以處理WM_CHAR消息。如果需要讀取光標鍵、功能鍵、Delete、Insert、Shift、Ctrl以及Alt鍵,那麼您可以處理WM_KEYDOWN消息。
***死字符消息
在某些非U.S.英語鍵盤上,有些鍵用於給字母加上音調。因爲它們本身不產生字符,所以稱之爲「死鍵」。例如,使用德語鍵盤時,對於U.S.鍵盤上的+/=鍵,德語鍵盤的對應位置就是一個死鍵,未按下Shift鍵時它用於標識銳音,按下Shift鍵時則用於標識抑音.
***插入符號函數
CreateCaret 建立與窗口有關的插入符
SetCaretPos 在窗口中設定插入符的位置
ShowCaret 顯示插入符
HideCaret 隱藏插入符
DestroyCaret 撤消插入符
GetCaretPos 取得插入符目前位置
GetCaretBlinkTime 取得插入符閃爍時間 //Blink 閃爍
SetCaretBlinkTime 設定插入符閃爍時間
------------------------------------------------------------------------------------------------
● //-鼠標
***鼠標基礎
理論上,可以用GetSystemMetrics函數來確認鼠標是否存在:fMouse = GetSystemMetrics (SM_MOUSEPRESENT) ;如果已經安裝了鼠標,fMouse將傳回TRUE(非0);如果沒有安裝,則傳回0。然而,在Windows 98中,不論鼠標是否安裝,此函數都將傳回TRUE 。在Microsoft Windows NT中,它可以正常工作。要確定所安裝鼠標其上按鍵的個數,可使用cButtons = GetSystemMetrics (SM_CMOUSEBUTTONS) ; 如果沒有安裝鼠標,那麼函數將傳回0。
對於所有鼠標消息,wParam的值指示鼠標按鍵以及Shift和Ctrl鍵的狀態,lParam值均含有鼠標的位置:低字組爲x座標,高字組爲y座標,這兩個座標是相對於窗口顯示區域左上角的位置。
***非顯示區域鼠標消息
可以用兩個Windows函數將屏幕座標轉換爲顯示區域座標或者反之:ScreenToClient (hwnd, &pt);ClientToScreen (hwnd, &pt) ;
ShowCursor (TRUE) 顯示光標 同理的↓
HideCuesor() 隱藏光標
***攔截鼠標
只要呼叫:SetCapture (hwnd) ;在這個函數呼叫之後,Windows將所有鼠標消息發給窗口句柄爲hwnd的窗口消息處理程序。之後收到鼠標消息都是以顯示區域消息的型態出現,即使鼠標正在窗口的非顯示區域。lParam參數將指示鼠標在顯示區域座標中的位置。不過,當鼠標位於顯示區域的左邊或者上方時,這些x和y座標可以是負的。當您想釋放鼠標時,呼叫:ReleaseCapture () ;
------------------------------------------------------------------------------------------------
● //-定時器
呼叫SetTimer(HWND hWnd , UINT nIDEvent, UINT uElapse, TIMERPROC lpTimerFunc),第一個參數是其窗口消息處理程序將接收WM_TIMER消息的窗口句柄。第二個參數是定時器ID,它是一個非0數值,在整個例子中假定爲1。第三個參數是一個32位無正負號整數,以毫秒爲單位指定一個時間間隔,一個60,000的值將使Windows每分鐘發送一次WM_TIMER消息。函數爲您的Windows程序分配一個定時器。SetTimer有一個時間間隔範圍爲1毫秒4,294,967,295毫秒(將近50天)的整數型態參數,這個值指示Windows每隔多久時間給您的程序發送WM_TIMER消息。KillTimer(HWND,nIDEvent),撤銷定時器,第一個參數爲窗口的句柄,第二個爲時鐘的ID.
***時間日期結構SYSTEMTIME
typedef struct _SYSTEMTIME {
WORD wYear; //年
WORD wMonth; //月
WORD wDayOfWeek; //星期,從0開始
WORD wDay; //天
WORD wHour; //小時
WORD wMinute; //分鐘
WORD wSecond; //秒
WORD wMilliseconds; //微秒
} SYSTEMTIME;
SYSTEMTIME主要用於GetLocalTime和GetSystemTime函數。GetSystemTime函數傳回目前的世界時間(Coordinated Universal Time,UTC),大概與英國格林威治時間相同。GetLocalTime函數傳回當地時間以及GetCurrentTime返回當前時間,依據計算機所在的時區。這些值的精確度完全決定於使用者所調整的時間精確度以及是否指定了正確的時區。
------------------------------------------------------------------------------------------------
● //-子窗口控件
GetWindowLong該函數獲得有關指定窗口的信息,函數也獲得在額外窗口內存中指定偏移位地址的32位度整型值。
LONG GetWindowLong(
HWND hWnd, // 窗口句柄
int nIndex // 欲取回的信息,以GWL_XXX或者DWL_XXX形式,在WinUser.h中,有以下數值
GWL=GetWindowLong DWL=
GWL_EXSTYLE= (-20) 擴展窗口樣式
GWL_STYLE=(-16) 窗口樣式
GWL_WNDPROC= (-4) 該窗口的窗口函數的地址
GWL_HINSTANCE= (-6) 擁有窗口的實例的句柄
GWL_HWNDPARENT= (-8) 該窗口之父的句柄。不要用SetWindowWord來改變這個值
GWL_ID= (-12) 對話框中一個子窗口的標識符
GWL_USERDATA = (-21) 含義由應用程序規定
DWL_DLGPROC = 4 這個窗口的對話框函數地址
DWL_MSGRESULT = 0 在對話框函數中處理的一條消息返回的值
DWL_USER = 8 含義由應用程序規定
);
BOOL IsWindowVisible( //窗口是否可見
HWND hWnd // handle to window
);
HBRUSH CreateSolidBrush( //創建一個具有指定顏色的邏輯刷子
COLORREF crColor // brush color value Specifies the color of the brush. To create a COLORREF color value, use the RGB macro
);
------------------------------------------------------------------------------------------------
● //-菜單及其他資源
HMENU GetSystemMenu( //得到系統菜單句柄
HWND hWnd, // handle to window
BOOL bRevert // reset option 如果參數bRevert爲FALSE,返回值是窗口菜單的拷貝的句柄:如果參數bRevert爲TRUE,返回值是NULL。
);
***改變菜單
AppendMenu 在菜單尾部添加一個新的菜單項目
DeleteMenu 刪除菜單中一個現有的菜單項並清除該項目
InsertMenu 在菜單中插入一個新項目
ModifyMenu 修改一個現有的菜單項目
RemoveMenu 從菜單中移走某一項目
------------------------------------------------------------------------------------------------
● //-多任務和多線程
建立新的線程的API函數是 HANDLE CreateThread( //創建線程
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 指向SECURITY_ATTRIBUTES結構的指針
DWORD dwStackSize, // 初始線程堆棧大小,默認0
LPTHREAD_START_ROUTINE lpStartAddress, // 線程函數
LPVOID lpParameter, // 指向線程的參數值
DWORD dwCreationFlags, // 額外標誌,通常0
[out]LPDWORD lpThreadId // 返回線程句柄
);
HANDLE CreateEvent( //創建事件對象
LPSECURITY_ATTRIBUTES lpEventAttributes, // 指向SECURITY_ATTRIBUTES結構的指針
BOOL bManualReset, // 復位方式
BOOL bInitialState, // 初始狀態的事件對象
LPCTSTR lpName // 事件對象名稱
);
要設立一個現存的事件對象,呼叫SetEvent (hEvent) ;要重置一個事件對象,呼叫ResetEvent (hEvent) ;一個程序通常呼叫:DWORD WaitForSingleObject(
HANDLE hHandle, // 事件句柄
DWORD dwMilliseconds // 時間間隔
);
------------------------------------------------------------------------------------------------
● //動態鏈接庫
儘管一個動態鏈接庫模塊可能有其它擴展名(如.EXE或.FON),但標準擴展名是.DLL。只有帶.DLL擴展名的動態鏈接庫才能被Windows自動加載。如果文件有其它擴展名,則程序必須另外使用LoadLibrary或者LoadLibraryEx函數加載該模塊.動態鏈接庫有着令人困惑的印象,部分原因是由於「鏈接庫」這個詞被放在幾種不同的用語之後。除了動態鏈接庫之外,我們也用它來稱呼「目的碼鏈接庫」或「引用鏈接庫」。目的碼鏈接庫是帶.LIB擴展名的文件。在使用連結程序進行靜態連結時,它的程序代碼就會加到程序的.EXE文件中。
DLL入口函數:int WINAPI DllMain ( //函數入口
HINSTANCE hInstance, //模塊實例句柄
DWORD fdwReason, //呼叫函數的理由
PVOID pvReserved //DLL的初始化
);
HMODULE LoadLibrary( //加載函數庫
LPCTSTR lpFileName // 文件名,返回一個模塊實例句柄
);
BOOL FreeLibrary( //卸載函數庫
HMODULE hModule // 模塊句柄,邏輯型
);
HMODULE GetModuleHandle( //同樣返回一個模塊的實例句柄
LPCTSTR lpModuleName // 模塊名字
);
3. ● ●●●●●●彙編語言程序設計●●●●
● 80386處理器有3種工作模式:實模式、保護模式和虛擬86模式。實模式和虛擬86模式是爲了和8086處理器兼容而設置的。在實模式下,80386處理器就相當於一個快速的8086處理器。保護模式是80386處理器的主要工作模式。在此方式下,80386可以尋址4 GB的地址空間,同時,保護模式提供了80386先進的多任務、內存分頁管理和優先級保護等機制。80386處理器被複位或加電的時候以實模式啓動。虛擬86模式以保護模式爲基礎,它的工作方式實際上是實模式和保護模式的混合。從實模式切換到保護模式是通過修改控制寄存器CR0的控制位PE(位0)來實現的PE爲1則進入保護模式,分頁機制是通過PG位來實現PG爲1則啓用分頁,只有在PE位是1的時候,PG位纔有可能是1,分頁機制把線性地址空間和物理地址空間分別劃分爲大小相同的塊。這樣的塊稱爲頁。通過在線性地址空間的頁與物理地址空間的頁之間建立映射,分頁機制可以實現線性地址到物理地址的轉換。
● 保護模式下的分段模式:
在32位機器中,CPU中CS,DS,ES,SS,4個16位的段寄存器存放的的是選擇符,共16*4=64位
各項任務共享的內存空間由全局選擇符索引,而某個獨立使用的內存空間由局部選擇符來索引,以選擇符的高13位左右偏移量,再以CPU內部事先初始好的GDTR(全局描述符表寄存器)中的32位作爲基地址
,可以獲得相應的描述符,由描述符中的線性地址決定段的基地址,再利用指令(或其他方式)給出的偏移量,便可得到線性地址.物理地址=線性基地址+偏移量
4KB頁面的線性地址,如何映射到物理內存:
4KB=1024個頁映射表 (每個映射表4字節)
4字節=4*8位=32位線性地址
線性地址的高10位->(物理頁中的頁目錄表)1024個頁目錄項 (每個頁目錄項4字節*1024=4096字節=4KB)
線性地址的低22位->高10位+低12位
高10位->(物理頁中頁表)1024個頁表項 (每個頁表項4字節*1024=4096字節=4KB)
低12位->存放諸如“頁是否存在於內存”或“頁的權限”等信息
舉例說:00402080,首先轉換爲32位地址=0000 0000 0100 0000 0010 0000 1000 0000,那麼高10位0010000000,這個是頁目錄,系統根據CR3寄存器顯示的頁目錄表的基址,找到頁目錄,然後找索引爲10000000的目錄,每個頁目錄對應1024個頁表,找到後,在根據地址中間10位1000,從頁表中找到索引爲1000的地址,這樣就得到了目標頁面的物理地址,最後加上低12位1,就得到數據存放的真正物理地址
一個線性地址大小爲4個字節(32bit),包含着找到物理地址的信息,分爲3個部分:第22位到第31位這10位(最高10位)是頁目錄中的索引,第12位到第21位這10位是頁表中的索引,第0位到第11位這12位(低12位)是頁內偏移。在把一個線性地址轉換成物理地址時,CPU首先根據CR3中的值,找到頁目錄所在的物理頁。然後根據線性地址的第22位到第31位這10位(最高的10bit)的值作爲索引,找到相應的PDE,其中含有這個虛擬地址所對應頁表的物理地址。有了頁表的物理地址,再把虛擬地址的第12位到第21位這10位的值作爲索引,找到該頁表中相應的PTE,其中就有這個虛擬地址所對應物理頁的物理地址。最後用線性地址的最低12位,也就是頁內偏移,加上這個物理頁的物理地址,就得到了該線性地址所對應的物理地址。
控制寄存器 :CR0
屏蔽中斷 :cli
中斷返回指令: iret
● 實模式下,一個完整的地址由段地址和偏移地址兩部分組成。段地址放在16位的段寄存器中,然後在指令中用16位的偏移地址尋址。處理器換算時先將段地址乘以10h,得到段在物理內存中的起始地址;然後加上16位的偏移地址得到實際的物理地址。如xxxx:yyyy格式的虛擬地址在內存中的實際位置是xxxx×10h+yyyy。
● 80386所有的通用寄存器都是32位的,2的32次方相當於4G,所以用任何一個通用寄存器來間接尋址,不必分段就已經可以訪問到所有的內存地址。雖然在尋址上不再有分段的限制問題,但在保護模式下,一個地址空間是否可以被寫入,可以被多少優先級的代碼寫入,是不是允許執行等涉及保護的問題就出來了。要解決這些問題,必須對一個地址空間定義一些安全上的屬性。段寄存器這時就派上了用途。但是涉及屬性和保護模式下段的其他參數,要表示的信息太多了,要用64位長的數據才能表示。我們把這64位的屬性數據叫做段描述符(Segment Descriptor).
● 80386的段寄存器是16位的,無法放下保護模式下64位的段描述符。如何解決這個新的問題呢?解決辦法是把所有段的段描述符順序放在內存中的指定位置,組成一個段描述符表(Descriptor Table);而段寄存器中的16位用來做索引信息,指定這個段的屬性用段描述符表中的第幾個描述符來表示。這時,段寄存器中的信息不再是段地址了,而是段選擇器(Segment Selector)。
● 80386中引入了兩個新的寄存器來管理段描述符表。一個是48位的全局描述符表寄存器GDTR,一個是16位的局部描述符表寄存器LDTR。GDTR指向的描述符表爲全局描述符表GDT(Global Descriptor Table)。它包含系統中所有任務都可用的段描述符,通常包含描述操作系統所使用的代碼段、數據段和堆棧段的描述符及各任務的LDT段等;全局描述符表只有一個。
LDTR則指向局部描述符表LDT(Local Descriptor Table)。80386處理器設計成每個任務都有一個獨立的LDT。它包含有每個任務私有的代碼段、數據段和堆棧段的描述符,也包含該任務所使用的一些門描述符,如任務門和調用門描述符等。
● 80386中可以使用下述指令進行數組訪問:mov cx,[eax + ebx * 2 + 數組基地址],這相當於把數組中下標爲eax和ebx的項目放入cx中;ebx * 2中的2可以是1,2,4或8,這樣就可以支持8位到64位的數組。
● 在保護地址模式下,經常遇到三種地址:邏輯地址(ogica Address)、線性地址(inear Address)和物理地址(Physica Address)。CPU通過分段機制將邏輯地址轉換爲線性地址,再通過分頁機制將線性地址轉換爲物理地址。(1)邏輯地址這是內存地址的精確描述,通常表示爲十六進制:xxxx:YYYYYYYY,這裏xxxx爲seector(選擇器),而YYYYYYYY是針對seector所選擇的段地址的線性偏移量。除了指定xxxx的具體數值外,還可使用具體的段寄存器的名字來替代,如CS(代碼段),DS(數據段),ES(擴展段),FS(附加數據段#1),GS(附加數據段#2)和SS(堆棧段)。這些符號都來自舊的“段:偏移量”風格,在 8086 實模式下使用此種方式來指定“far pointers”(遠指針)。(2)線性地址線性地址是邏輯地址到物理地址變換之間的中間層,是處理器可尋址的內存空間(稱爲線性地址空間)中的地址。程序代碼會產生邏輯地址,或者說是段中的偏移地址,加上相應段的基地址就生成了一個線性地址。如果啓用了分頁機制,那麼線性地址可以再經變換以產生一個物理地址。若沒有啓用分頁機制,那麼線性地址直接就是物理地址。不過,在開啓分頁功能之後,一個線性地址可能沒有相對映的物理地址,因爲它所對應的內存可能被交換到硬盤中。32位線性地址可用於定位4GB存儲單元。(3)物理地址所謂物理地址,就是指系統內存的真正地址。對於32 位的操作系統,它的範圍爲0x00000000~0xFFFFFFFF,共有4GB。只有當CPU工作於分頁模式時,此種類型的地址纔會變得非常“有趣”。
● 80386處理器把4 KB大小的一塊內存當做一“頁”內存,每頁物理內存可以根據“頁目錄”和“頁表”,隨意映射到不同的線性地址上。頁表規定的不僅是地址的映射,同時還規定了頁的訪問屬性,如是否可寫、可讀和可執行等。是否啓用內存分頁機制是由80386處理器新增的CR0寄存器中的位31(PG位)決定的。如果PG=0,則分頁機制不啓用,這時所有指令尋址的地址(線性地址)就是系統中實際的物理地址;當PG=1的時候,80386處理器進入內存分頁管理模式,所有的線性地址要經過頁表的映射纔得到最後的物理地址。在實模式下尋址的時候,段寄存器+偏移地址經過轉換計算以後得到的地址是“物理地址”,而保護模式下,段選擇器+偏移地址轉換後的地址被稱爲“線性地址”而不是“物理地址”。
● 每個應用程序都有自己的4 GB的尋址空間。該空間可存放操作系統、系統DLL和用戶DLL的代碼,它們之中有各種函數供應用程序調用。再除去其他的一些空間,餘下的是應用程序的代碼、數據和可以分配的地址空間。
● 不同應用程序的線性地址空間是隔離的。雖然它們在物理內存中同時存在,但在某個程序所屬的時間片中,其他應用程序的代碼和數據沒有被映射到可尋址的線性地址中,所以是不可訪問的。從編程的角度看,程序可以使用4 GB的尋址空間,而且這個空間是“私有”的。
● DLL程序沒有自己“私有”的空間。它們總是被映射到其他應用程序的地址空間中,當做其他應用程的一部分運行。原因很簡單,如果它不和其他程序同屬一個地址空間,應用程序該如何調用它呢?
● 並不是Win32彙編源代碼用不到段寄存器,而是用戶在使用中不必去關心段寄存器!
● 爲了使高優先級的代碼能夠安全地被低優先級的代碼調用,保護模式下增加了“門”的概念。“門”指向某個優先級高的程序所規定的入口點,所有優先級低的程序調用優先級高的程序只能通過門重定向,進入門所規定的入口點。這樣可以避免低級別的程序代碼從任意位置進入優先級高的程序的問題。保護模式下的中斷和異常等服務程序也要從“門”進入,80386的門分爲中斷門、自陷門和任務門幾種。
● 在保護模式下要表示一箇中斷或異常服務程序的信息需要用8個字節,包括門的種類以及xxxx:yyyyyyyy格式的入口地址等。這組信息叫做“中斷描述符”。保護模式下把所有的中斷描述符放在一起組成“中斷描述符表”IDT(Interrupt Descriptor Table)。
● 實模式下的中斷和異常服務程序地址存放在中斷向量表中。中斷向量表位於物理內存00000h開始的400h字節中,共支持100h箇中斷向量;每個中斷向量是一個xxxx:yyyy格式的地址,佔用4字節。當發生n號異常或n號中斷,或者執行到int n指令的時候,CPU首先到內存n×4的地方取出服務程序的地址aaaa:bbbb;然後將標誌寄存器、中斷時的CS和IP壓入堆棧,接着轉移到aaaa:bbbb處執行;在服務程序最後遇到iret的時候,CPU從堆棧中恢復標誌寄存器,然後取出CS和IP並返回。
● 在Windows中,操作系統使用動態鏈接庫來代替中斷服務程序提供系統功能,所以 Win32彙編中int指令也就失去了存在的意義。這就是在Win32彙編源代碼中看不到int指令的原因。其實那些調用API的指令原本是用int指令實現的。
● 16位的段選擇器中只有高13位表示索引值。剩下的3個數據位中,第0,1位表示程序的當前優先級RPL;第2位TI位用來表示在段描述符的位置;TI=0表示在GDT中,TI=1表示在LDT中。
● 保護機制主要由下列幾方面組成:
段的類型檢查——段的類型是由段描述符指定的,主要屬性有是否可執行,是否可讀和是否可寫等。而CS,DS和SS等段選擇器是否能裝入某種類型的段描述符是有限制的。如不可執行的段不能裝入CS;不可讀的段不能裝入DS與ES等數據段寄存器;不可寫的段不能裝入SS等。如果段類型檢查通不過,則處理器會產生一般性保護異常或堆棧異常。
● 頁的類型檢查——除了可以在段級別上指定整個段是否可讀寫外,在頁表中也可以爲每個頁指定是否可寫。對於特權級下的執行代碼,所有的頁都是可寫的。但對於1,2和3級的代碼,還要根據頁表中的R/W項決定是否可寫,企圖對只讀的頁進行寫操作會產生頁異常。
● 訪問數據時的級別檢查——優先級低的代碼不能訪問優先級高的數據段。80386的段描述符中有一個DPL域(描述符優先級),表示這個段可以被訪問的最低優先級。而段選擇器中含有RPL域(請求優先級),表示當前執行代碼的優先級。只有DPL在數值上大於或等於RPL值的時候,該段纔是可以訪問的,否則會產生一般性保護異常。
● 控制轉移的檢查——在處理器中,有很多指令可以實現控制轉移,如jmp,call,ret,int和iret等指令。但優先級低的代碼不能隨意轉移到優先級高的代碼中,所以遇到這些指令的時候,處理器要檢查轉移的目的位置是否合法。
● 指令集的檢查——有兩類指令可以影響保護機制。第一類是改變GDT,LDT,IDT以及控制寄存器等關鍵寄存器的指令,稱爲特權指令;第二類是操作I/O端口的指令以及cli和sti等改變中斷允許的指令,稱爲敏感指令。試想一下,如果用戶級程序可以用sti禁止一切中斷(包括時鐘中斷),那麼整個系統就無法正常運行,所以這些指令的運行要受到限制。特權指令只能在優先級0上才能運行,而敏感指令取決於eflags寄存器中的IOPL位。只有IOPL位表示的優先級高於等於當前代碼段的優先級時,指令才能執行。
● I/O操作的保護——I/O地址也是受保護的對象。因爲通過I/O操作可以繞過系統對很多硬件進行控制。80386可以單獨爲I/O空間提供保護,每個任務有個TSS(任務狀態段)來記錄任務切換的信息。TSS中有個I/O允許位圖,用來表示對應的I/O端口是否可以操作。某個I/O地址在位圖中的對應數據位爲0則表示可以操作;如果爲1則還要看eflags中的IPOL位,這時只有IOPL位表示的優先級高於等於當前代碼段的優先級,才允許訪問該I/O端口。
●●●●●●●●PE文件格式學習●●●●●●●●●
● 在PE文件中,代碼、已初始化的數據、資源和重定位信息等數據被按照屬性分類放到不同的節(Section)中,而每個節的屬性和位置等信息用一個IMAGE_SECTION_HEADER結構來描述,所有的IMAGE_SECTION_HEADER結構組成一個節表(Section Table),節表數據在PE文件中被放在所有節數據的前面。
●IMAGE_SECTION_HEADER結構在winnt.h定義如下:
typedef struct _IMAGE_SECTION_HEADER
{
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];//節表名稱,如“.text”IMAGE_SIZEOF_SHORT_NAME=8
union
{
DWORD PhysicalAddress;//物理地址
DWORD VirtualSize;//真實長度,可以使用其中的任何一個,一般是節的數據大小
} Misc;
DWORD VirtualAddress;//相對虛擬地址
DWORD SizeOfRawData;//物理長度
DWORD PointerToRawData;//節基於文件的偏移量
DWORD PointerToRelocations;//重定位的偏移
DWORD PointerToLinenumbers;//行號表的偏移
WORD NumberOfRelocations;//重定位項數目
WORD NumberOfLinenumbers;//行號表的數目
DWORD Characteristics;//節屬性 如可讀,可寫,可執行等
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
●IMAGE_IMPORT_DESCRIPTOR是導入表結構數組,數組以全0作爲結束標記,該結構定義如下:
typedef struct _IMAGE_IMPORT_DESCRIPTOR
{
union
{
DWORD Characteristics; //0
DWORD OriginalFirstThunk;// 指向一個 IMAGE_THUNK_DATA 結構數組的RVA
}
DWORD TimeDateStamp;// 文件生成的時間
DWORD ForwarderChain;// 這個數據一般爲0,可以不關心
DWORD Name1; // RVA,指向DLL名字的指針,ASCII字符串
DWORD FirstThunk; //指向一個IMAGE_THUNK_DATA 結構數組的RVA,這個數據與IAT所指向的地址一致
}IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR
●IMAGE_EXPORT_DIRECTORY是導出表結構數組,數組以全0作爲結束標記,該結構定義如下:
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
●IMAGE_THUNK_DATA 這是一個DWORD類型的集合。通常我們將其解釋爲指向一個IMAGE_IMPORT_BY_NAME 結構的指針,其定義如下:
IMAGE_THUNK_DATA
{
union
{
PBYTE ForwarderString;
PDWORD Function;
DWORD Ordinal;//判定當前結構數據是不是以序號爲輸出的,如果是的話該值爲0x800000000,此時 ....PIMAGE_IMPORT_BY_NAME不可做爲名稱使用
PIMAGE_IMPORT_BY_NAME AddressOfData;
}u1;
} IMAGE_THUNK_DATA,*PIMAGE_THUNK_DATA;
●typedef struct _IMAGE_IMPORT_BY_NAME
{
WORD Hint;// 函數輸出序號
BYTE Name1[1];//輸出函數名稱
} IMAGE_IMPORT_BY_NAME,*PIMAGE_IMPORT_BY_NAME
我們知道,Win32中可以對每個內存頁分別指定可執行、可讀寫等屬性,PE文件將同樣屬性的數據分類放在一起是爲了統一描述這些數據裝入內存後的頁面屬性。由於數據是按照屬性在節中放置的,不同用途但是屬性相同的數據(如導入表、導出表以及.const段指定的只讀數據)可能被放在同一個節中,所以PE文件中還用一系列的數據目錄結構IMAGE_DATA_DIRECTORY來分別指明這些數據的位置,數據目錄表和其他描述文件屬性的數據合在一起稱爲PE文件頭,PE文件頭被放置在節和節表的前面。
● 操作系統識別可執行文件的方法是按照文件格式而不是按照擴展名,如果文件頭中的數據格式不符合任何已經定義的格式,那麼系統按照COM文件的格式裝入文件,也就是說將整個文件的數據全部當做代碼裝入執行。這個規則說明了爲什麼很多非.exe擴展名的可執行文件(如LE格式的VxD文件、PE格式的.dll,.scr文件等等)也能夠被裝入並正確運行,也說明了爲什麼把可執行文件的擴展名隨意修改爲.exe、.com或者.bat(甚至是.pif,.scr或者.bat),系統也能正確識別並執行的原因。
● PE文件中的DOS部分由MZ格式的文件頭和可執行代碼部分組成,可執行代碼被稱爲“DOS塊”(DOS stub)。MZ格式的文件頭由IMAGE_DOS_HEADER結構定義:以下是cmd.exe文件解析來的
IMAGE_DOS_HEADER STRUCT
{
WORD e_magic Magic DOS signature MZ(4Dh 5Ah) //DOS可執行文件標記
WORD e_cblp Bytes on last page of file
WORD e_cp Pages in file
WORD e_crlc Relocations
WORD e_cparhdr Size of header in paragraphs
WORD e_minalloc Minimun extra paragraphs needs
WORD e_maxalloc Maximun extra paragraphs needs
WORD e_ss intial(relative)SS value //DOS代碼的初始化堆棧SS
WORD e_sp intial SP value //DOS代碼的初始化堆棧指針SP
WORD e_csum Checksum
WORD e_ip intial IP value //DOS代碼的初始化指令入口[指針IP]
WORD e_cs intial(relative)CS value // DOS代碼的初始堆棧入口
WORD e_lfarlc File Address of relocation table
WORD e_ovno Overlay number
WORD e_res[4] Reserved words
WORD e_oemid OEM identifier(for e_oeminfo)
WORD e_oeminfo OEM information;e_oemid specific 0000
WORD e_res2[10] Reserved words
DWORD e_lfanew Offset to start of PE header //指向真正PE文件頭
}IMAGE_DOS_HEADER ENDS
● PE文件頭(NT文件頭)
從DOS文件頭的e_lfanew字段(文件頭偏移003ch)得到真正的PE文件頭位置後,現在來看看它的定義,PE文件頭是由IMAGE_NT_HEADERS結構定義的:
●typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; //00004550h
IMAGE_FILE_HEADER FileHeader; //結構體
IMAGE_OPTIONAL_HEADER32 OptionalHeader;//結構體
}
● PE文件頭的第一個雙字是一個標誌,它被定義爲00004550h,也就是字符“P”,“E”加上兩個0,這也是“PE”這個稱呼的由來,大部分的文件屬性由標誌後面的IMAGE_FILE_HEADER和IMAGE_OPTIONAL_HEADER32結構來定義,從名稱看,似乎後面的這個PE文件表頭結構是可選的(Optional),但實際上這個名稱是名不符實的,因爲它總是存在於每個PE文件中。
IMAGE_FILE_HEADER結構的定義如下所示,字段後面的註釋中標出了字段相對於PE文件頭的偏移量,以供讀者快速參考:
●typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //運行平臺 intel i386一般爲14Ch
WORD NumberOfSections; //塊(section)數目
DWORD TimeDateStamp; //時間日期標記
DWORD PointerToSymbolTable; //COFF符號指針,這是程序調試信息
DWORD NumberOfSymbols; //符號數
WORD SizeOfOptionalHeader; //可選部首長度,是IMAGE_OPTIONAL_HEADER的長度
WORD Characteristics; //文件屬性
}
● 定義IMAGE_OPTIONAL_HEADER32結構的本意在於讓不同的開發者能夠在PE文件頭中使用自定義的數據,這就是結構名稱中“Optional”一詞的由來,但實際上IMAGE_FILE_HEADER結構不足以用來定義PE文件的屬性,反而在這個“可選”的部分中有着更多的定義數據,對於讀者來說,可以完全不必考慮這兩個結構的區別在哪裏,只要把它們當成是連在一起的“PE文件頭結構”就可以了。IMAGE_OPTIONAL_HEADER32結構的定義如下,同樣,字段後面的註釋中標出了字段本身相對於PE文件頭的偏移量:
IMAGE_OPTIONAL_HEADER32 STRUCT
typedef struct _IMAGE_OPTIONAL_HEADER
{
WORD Magic; //幻數,一般爲10BH
BYTE MajorLinkerVersion; //鏈接程序的主版本號
BYTE MinorLinkerVersion; //鏈接程序的次版本號
DWORD SizeOfCode; //代碼段大小
DWORD SizeOfInitializedData; //已初始化數據塊的大小
DWORD SizeOfUninitializedData; //未初始化數據庫的大小
DWORD AddressOfEntryPoint; //程序開始執行的入口地址,這是一個RVA(相對虛擬地址)
DWORD BaseOfCode; //代碼段的起始RVA
DWORD BaseOfData; //數據段的起始RVA
DWORD ImageBase; //可執行文件默認裝入的基地址
DWORD SectionAlignment; //內存中塊的對齊值(默認的塊對齊值爲1000H,4KB個字節)
DWORD FileAlignment;//文件中塊的對齊值(默認值爲200H字節,爲了保證塊總是從磁盤的扇區開始的)
WORD MajorOperatingSystemVersion;//要求操作系統的最低版本號的主版本號
WORD MinorOperatingSystemVersion;//要求操作系統的最低版本號的次版本號
WORD MajorImageVersion;//該可執行文件的主版本號
WORD MinorImageVersion;//該可執行文件的次版本號
WORD MajorSubsystemVersion;//要求最低之子系統版本的主版本號
WORD MinorSubsystemVersion;//要求最低之子系統版本的次版本號
DWORD Win32VersionValue;//保留字
DWORD SizeOfImage;//映像裝入內存後的總尺寸
DWORD SizeOfHeaders;//部首及塊表的大小
DWORD CheckSum;//CRC檢驗和
WORD Subsystem;//程序使用的用戶接口子系統
WORD DllCharacteristics;//DLLmain函數何時被調用,默認爲0
DWORD SizeOfStackReserve;//初始化時堆棧大小
DWORD SizeOfStackCommit;//初始化時實際提交的堆棧大小
DWORD SizeOfHeapReserve;//初始化時保留的堆大小
DWORD SizeOfHeapCommit;//初始化時實際提交的對大小
DWORD LoaderFlags;//與調試有關,默認爲0
DWORD NumberOfRvaAndSizes;//數據目錄結構的數目
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//數據目錄表
}
●IMAGE_DATA_DIRECTORY數據目錄表,結構定義如下:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;//數據塊的起始RVA
DWORD Size;//數據塊的長度
}
●IMAGE_RESOURCE_DIRECTORY自願結構表,結構定義如下:
typedef struct _IMAGE_RESOURCE_DIRECTORY {
DWORD Characteristics;//理論上爲資源的屬性,不過事實上總是0
DWORD TimeDateStamp;//資源的產生時刻
WORD MajorVersion;//理論上爲資源的版本,不過事實上總是0
WORD MinorVersion;
WORD NumberOfNamedEntries;//以名稱命名的入口數量
WORD NumberOfIdEntries;//以ID命名的入口數量
// IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[];
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
●IMAGE_BASE_RELOCATION重定位表,結構:
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;//重定位數據RVA
DWORD SizeOfBlock;//重定位數據大小
WORD TypeOffset[1];//重定位數據項數則
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
●IMAGE_BOUND_IMPORT_DESCRIPTOR綁定輸入表表,結構:
typedef struct _IMAGE_BOUND_IMPORT_DESCRIPTOR {
DWORD TimeDateStamp;
WORD OffsetModuleName;
WORD NumberOfModuleForwarderRefs;
// Array of zero or more IMAGE_BOUND_FORWARDER_REF follows
} IMAGE_BOUND_IMPORT_DESCRIPTOR, *PIMAGE_BOUND_IMPORT_DESCRIPTOR;
●獲取PE文件的結構信息有兩種方法,①通過打開文件直接讀取②是通過文件映射讀取
①:首先通過CreateFile()來打開一個文件,獲得文件的句柄hFile
然後用
BOOL ReadFile(
HANDLE hFile, // handle of file to read
LPVOID lpBuffer, // pointer to buffer that receives data
DWORD nNumberOfBytesToRead, // number of bytes to read
LPDWORD lpNumberOfBytesRead, // pointer to number of bytes read
LPOVERLAPPED lpOverlapped // pointer to structure for data
); 第二個參數指向一個緩衝區,接收文件的指針
最後把這個指針賦值給IMAGE_DOS_HEADER,IMAGE_DOS_HEADER是文件的DOS頭,這一步是把DOS頭結構的地址與文件的地址關聯,之後就可以對PE中的數據進行訪問
②:首先
HANDLE CreateFile(
LPCTSTR lpFileName, // pointer to name of the file
DWORD dwDesiredAccess, // access (read-write) mode
DWORD dwShareMode, // share mode
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
// pointer to security attributes
DWORD dwCreationDisposition, // how to create
DWORD dwFlagsAndAttributes, // file attributes
HANDLE hTemplateFile // handle to file with attributes to
// copy
);返回文件的的句柄hFile
然後依據此句柄創建文件映射內核對象
HANDLE CreateFileMapping(
HANDLE hFile, // handle to file to map
LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
// optional security attributes
DWORD flProtect, // protection for mapping object
DWORD dwMaximumSizeHigh, // high-order 32 bits of object size
DWORD dwMaximumSizeLow, // low-order 32 bits of object size
LPCTSTR lpName // name of file-mapping object
);創建一個文件映射內核對象,返回文件映射句柄hMapping
之後
LPVOID MapViewOfFile(
HANDLE hFileMappingObject, // file-mapping object to map into
// address space
DWORD dwDesiredAccess, // access mode
DWORD dwFileOffsetHigh, // high-order 32 bits of file offset
DWORD dwFileOffsetLow, // low-order 32 bits of file offset
DWORD dwNumberOfBytesToMap // number of bytes to map
);把文件裝入內存,返回得到文件起始地址,一個指針
最後就是把這個指針轉換成IMAGE_DOS_HEADER類型,賦值給IMAGE_DOS_HEADER首址
●●●●●●●●●●●●●●●●●●●●●●●
EAX :累加器,用於算術,邏輯運算以及外設傳送信息
EBX :基址寄存器,存放存儲器的地址
ECX :計數器,做循環或者串指令運算中的隱含計數器
EDX :數據寄存器,存放數據的高位,或者存放外設端口地址
ESI :源變址寄存器,若在串指令中有特殊用法
EDI :目的變址寄存器,若在串指令中有特殊用法
ESP :堆棧指針寄存器,指示棧頂的便宜地址,不能再用於其它用途
EBP :基址指針寄存器,說明數據在堆棧段的基地址
CF (carry flag)進位標誌
ZF (zero flag)零標誌
SF (sign flag)
能夠改變ECS,EIP的指令統稱爲跳轉指令,若要單一修改EIP的值,可以通過jmp 合法寄存器 來實現,通用寄存器的值都可以用mov指令改寫
● 有符號數比較大小的結果指令依次爲:G[大於] E[等於] L[小於]
● 無符號數比較大小的結果指令依次爲:A[大於] E[等於] B[小於]
立即數不能直接送段地址
1. 通用數據傳送指令.
MOV 傳送字或字節.
MOVSX 先符號擴展,再傳送.
MOVZX 先零擴展,再傳送.
PUSH 把字壓入堆棧.
POP 把字彈出堆棧.
PUSHA 把AX,CX,DX,BX,SP,BP,SI,DI依次壓入堆棧.
POPA 把DI,SI,BP,SP,BX,DX,CX,AX依次彈出堆棧.
PUSHAD 把EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI依次壓入堆棧.
POPAD 把EDI,ESI,EBP,ESP,EBX,EDX,ECX,EAX依次彈出堆棧.
BSWAP 交換32位寄存器裏字節的順序
XCHG 交換字或字節.( 至少有一個操作數爲寄存器,段寄存器不可作爲操作數)
CMPXCHG 比較並交換操作數.( 第二個操作數必須爲累加器AL/AX/EAX )
XADD 先交換再累加.( 結果在第一個操作數裏 )
XLAT 字節查錶轉換.
── BX 指向一張 256 字節的表的起點, AL 爲表的索引值 (0-255,即
0-FFH); 返回 AL 爲查表結果. ( [BX+AL]->AL )
2. 輸入輸出端口傳送指令.
IN I/O端口輸入. ( 語法: IN 累加器, {端口號│DX} )
OUT I/O端口輸出. ( 語法: OUT {端口號│DX},累加器 )
輸入輸出端口由立即方式指定時, 其範圍是 0-255; 由寄存器 DX 指定時,
其範圍是 0-65535.
3. 目的地址傳送指令.
LEA 裝入有效地址.
例: LEA DX,string ;把偏移地址存到DX.
LDS 傳送目標指針,把指針內容裝入DS.
例: LDS SI,string ;把段地址:偏移地址存到DS:SI.
LES 傳送目標指針,把指針內容裝入ES.
例: LES DI,string ;把段地址:偏移地址存到ES:DI.
LFS 傳送目標指針,把指針內容裝入FS.
例: LFS DI,string ;把段地址:偏移地址存到FS:DI.
LGS 傳送目標指針,把指針內容裝入GS.
例: LGS DI,string ;把段地址:偏移地址存到GS:DI.
LSS 傳送目標指針,把指針內容裝入SS.
例: LSS DI,string ;把段地址:偏移地址存到SS:DI.
4. 標誌傳送指令.
LAHF 標誌寄存器傳送,把標誌裝入AH.
SAHF 標誌寄存器傳送,把AH內容裝入標誌寄存器.
PUSHF 標誌入棧.
POPF 標誌出棧.
PUSHD 32位標誌入棧.
POPD 32位標誌出棧.
二、算術運算指令
───────────────────────────────────────
ADD 加法.
ADC 帶進位加法.
INC 加 1.
AAA 加法的ASCII碼調整.
DAA 加法的十進制調整.
SUB 減法.
SBB 帶借位減法.
DEC 減 1.
NEC 求反(以 0 減之).
CMP 比較.(兩操作數作減法,僅修改標誌位,不回送結果).
AAS 減法的ASCII碼調整.
DAS 減法的十進制調整.
MUL 無符號乘法.
IMUL 整數乘法.
以上兩條,結果回送AH和AL(字節運算),或DX和AX(字運算),
AAM 乘法的ASCII碼調整.
DIV 無符號除法.
IDIV 整數除法.
以上兩條,結果回送:
商回送AL,餘數回送AH, (字節運算);
或 商回送AX,餘數回送DX, (字運算).
AAD 除法的ASCII碼調整.
CBW 字節轉換爲字. (把AL中字節的符號擴展到AH中去)
CWD 字轉換爲雙字. (把AX中的字的符號擴展到DX中去)
CWDE 字轉換爲雙字. (把AX中的字符號擴展到EAX中去)
CDQ 雙字擴展. (把EAX中的字的符號擴展到EDX中去)
三、邏輯運算指令
───────────────────────────────────────
AND 與運算.
OR 或運算.
XOR 異或運算.
NOT 取反.
TEST 測試.(兩操作數作與運算,僅修改標誌位,不回送結果).
SHL 邏輯左移.
SAL 算術左移.(=SHL)
SHR 邏輯右移.
SAR 算術右移.(=SHR)
ROL 循環左移.
ROR 循環右移.
RCL 通過進位的循環左移.
RCR 通過進位的循環右移.
以上八種移位指令,其移位次數可達255次.
移位一次時, 可直接用操作碼. 如 SHL AX,1.
移位>1次時, 則由寄存器CL給出移位次數.
如 MOV CL,04
SHL AX,CL
四、串指令
───────────────────────────────────────
DS:SI 源串段寄存器 :源串變址.
ES:DI 目標串段寄存器:目標串變址.
CX 重複次數計數器.
AL/AX 掃描值.
D標誌 0表示重複操作中SI和DI應自動增量; 1表示應自動減量.
Z標誌 用來控制掃描或比較操作的結束.
MOVS 串傳送.
( MOVSB 傳送字符. MOVSW 傳送字. MOVSD 傳送雙字. )
CMPS 串比較.
( CMPSB 比較字符. CMPSW 比較字. )
SCAS 串掃描.
把AL或AX的內容與目標串作比較,比較結果反映在標誌位.
LODS 裝入串.
把源串中的元素(字或字節)逐一裝入AL或AX中.
( LODSB 傳送字符. LODSW 傳送字. LODSD 傳送雙字. )
STOS 保存串.
是LODS的逆過程.
REP 當CX/ECX<>0時重複.
REPE/REPZ 當ZF=1或比較結果相等,且CX/ECX<>0時重複.
REPNE/REPNZ 當ZF=0或比較結果不相等,且CX/ECX<>0時重複.
REPC 當CF=1且CX/ECX<>0時重複.
REPNC 當CF=0且CX/ECX<>0時重複.
五、程序轉移指令
───────────────────────────────────────
1>無條件轉移指令 (長轉移)
JMP 無條件轉移指令
CALL 過程調用
RET/RETF過程返回.
2>條件轉移指令 (短轉移,-128到+127的距離內)
( 當且僅當(SF XOR OF)=1時,OP1<OP2 )
JA/JNBE 不小於或不等於時轉移.
JAE/JNB 大於或等於轉移.
JB/JNAE 小於轉移.
JBE/JNA 小於或等於轉移.
以上四條,測試無符號整數運算的結果(標誌C和Z).
JG/JNLE 大於轉移.
JGE/JNL 大於或等於轉移.
JL/JNGE 小於轉移.
JLE/JNG 小於或等於轉移.
以上四條,測試帶符號整數運算的結果(標誌S,O和Z).
JE/JZ 等於轉移.
JNE/JNZ 不等於時轉移.
JC 有進位時轉移.
JNC 無進位時轉移.
JNO 不溢出時轉移.
JNP/JPO 奇偶性爲奇數時轉移.
JNS 符號位爲 "0" 時轉移.
JO 溢出轉移.
JP/JPE 奇偶性爲偶數時轉移.
JS 符號位爲 "1" 時轉移.
3>循環控制指令(短轉移)
LOOP CX不爲零時循環.
LOOPE/LOOPZ CX不爲零且標誌Z=1時循環.
LOOPNE/LOOPNZ CX不爲零且標誌Z=0時循環.
JCXZ CX爲零時轉移.
JECXZ ECX爲零時轉移.
4>中斷指令
INT 中斷指令
INTO 溢出中斷
IRET 中斷返回
5>處理器控制指令
HLT 處理器暫停, 直到出現中斷或復位信號才繼續.
WAIT 當芯片引線TEST爲高電平時使CPU進入等待狀態.
ESC 轉換到外處理器.
LOCK 封鎖總線.
NOP 空操作.
STC 置進位標誌位.
CLC 清進位標誌位.
CMC 進位標誌取反.
STD 置方向標誌位.
CLD 清方向標誌位.
STI 置中斷允許位.
CLI 清中斷允許位.
六、僞指令
───────────────────────────────────────
DB 定義字節.
DW 定義字(2字節).
PROC 定義過程.
ENDP 過程結束.
SEGMENT 定義段.
ASSUME 建立段寄存器尋址.
ENDS 段結束.
END 程序結束.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.