windows 4
在使用RegisterClass注冊窗口類成功之后,即可以使用該窗口類創(chuàng)建并顯示應(yīng)用程序的窗口。
這個過程如下面的代碼所示:
//?創(chuàng)建應(yīng)用程序主窗口
hWnd=CreateWindow?("SdkDemo1",?//?窗口類名
"第一個Win32?SDK應(yīng)用程序",?//?窗口標(biāo)題
WS_OVERLAPPEDWINDOW,?//?窗口樣式
CW_USEDEFAULT,?//?初始化?x?坐標(biāo)
CW_USEDEFAULT,?//?初始化?y?坐標(biāo)
CW_USEDEFAULT,?//?初始化窗口寬度
CW_USEDEFAULT,?//?初始化窗口高度
NULL,?//?父窗口句柄
NULL,?//?窗口菜單句柄
hInstance,?//?程序?qū)嵗浔?/p>
NULL);?//?創(chuàng)建參數(shù)
//?顯示窗口
ShowWindow(hWnd,SW_SHOW);
//?更新主窗口客戶區(qū)
UpdateWindow(hWnd);
CreateWindow函數(shù)的原型是這樣的:
HWND?CreateWindow(LPCTSTR?lpClassName,?//?指向已注冊的類名
LPCTSTR?lpWindowName,?//?指向窗口名稱
DWORD?dwStyle,?//?窗口樣式
int?x,?//?窗口的水平位置
int?y,?//?窗口的垂直位置
int?nWidth,?//?窗口寬度
int?nHeight,?//?窗口高度
HWND?hWndParent,?//?父窗口或所有者窗口句柄
HMENU?hMenu,?//?菜單句柄或子窗口標(biāo)識符
HANDLE?hInstance,?//?應(yīng)用程序?qū)嵗浔?/p>
LPVOID?lpParam,?//?指向窗口創(chuàng)建數(shù)據(jù)的指針
);
在前面的示例中,我們對x、y、nWidth和nHeight參數(shù)都傳遞了同一個值CW_USEDEFAULT,表示使用系統(tǒng)默認(rèn)的窗口位置和大小,該常量僅對于重疊式窗口(即在dwStype樣式中指定了WS_OVERLAPPEDWINDOW,另一個常量WS_TILEDWINDOW有著相同的值)有效。
●?注意:
●?盡管Windows?95是一個32位的操作系統(tǒng),但是,其中也保留了很多16位的特征,比如說,在Windows?95環(huán)境下,系統(tǒng)最多只可以有16384個窗口句柄。而在Windows?NT下則無此限。然而,事實上,對于一般的桌面?zhèn)€人機(jī)系統(tǒng)來說,我們幾乎不可能超過這個限制。
創(chuàng)建窗口完成之后,ShowWindows顯示該窗口,第二個參數(shù)SW_SHOW表示在當(dāng)前位置以當(dāng)前大小激活并顯示由第一個參數(shù)標(biāo)識的窗口。然后,函數(shù)UpdateWindows向窗口發(fā)送一條WM_PAINT消息,以通知窗口更新其客戶區(qū)。需要注意的是,由UpdateWindows發(fā)送的WM_PAINT消息將直接發(fā)送到窗口過程(在上面的例子中是WndProc函數(shù)),而不是發(fā)送到進(jìn)程的消息隊列,因此,盡管這時應(yīng)用程序的主消息循環(huán)尚未啟動,但是窗口過程仍可接收到該WM_PAINT消息并更新其用戶區(qū)。
在完成上面的步驟之后,進(jìn)入應(yīng)用程序的主消息循環(huán)。
一般情況下,主消息循環(huán)具有下面的格式:
while?(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
主消息循環(huán)由對三個API函數(shù)的調(diào)用和一個while結(jié)構(gòu)組成。其中GetMessage從調(diào)用線程的消息隊列中獲取消息,并將消息放到由第一個參數(shù)指定的消息結(jié)構(gòu)中。如果指定了第二個參數(shù),則GetMessage獲取屬于該參數(shù)指定的窗口句柄所標(biāo)識的窗口的消息,如果該參數(shù)為NULL,則GetMessage獲取屬于調(diào)用線程及屬于該線程的所有窗口的消息。最后兩個參數(shù)指定了GetMessage所獲取消息的范圍,如果兩個參數(shù)均為0,則GetMessage檢索并獲取所有可以得到的消息。
在上面的代碼中,變量msg是一個類型為MSG的結(jié)構(gòu)對象,該結(jié)構(gòu)體的定義如下:
typedef?struct?tagMSG?{?//?msg
HWND?hwnd;
UINT?message;
WPARAM?wParam;
LPARAM?lParam;
DWORD?time;
POINT?pt;?}?MSG;
下面解釋各成員的含義:
hwnd:?標(biāo)識獲得該消息的窗口進(jìn)程的窗口句柄。
message:?指定消息值。
wParam:?其含義特定于具體的消息類型。
lParam:?其含義特定于具體的消息類型。
time:?指定消息發(fā)送時的時間。
pt:?以屏幕坐標(biāo)表示的消息發(fā)送時的鼠標(biāo)指針的位置。
在while循環(huán)體中的TranslateMessage函數(shù)將虛擬按鍵消息翻譯為字符消息,然后將消息發(fā)送到調(diào)用線程的消息隊列,在下一次調(diào)用GetMessage函數(shù)或PeekMessage函數(shù)時,該字符消息將被獲取。
TranslateMessage函數(shù)將WM_KEYDOWN和WM_KEYUP虛擬按鍵組合翻譯為WM_CHAR和WM_DEADCHAR,將WM_SYSKEYDOWN和WM_SYSKEYUP虛擬按鍵組合翻譯為WM_SYSCHAR和WM_SYSREADCHAR。需要注意的一點(diǎn)是,僅當(dāng)相應(yīng)的虛擬按鍵組合能夠被翻譯為所對應(yīng)的ASCII字符時,TranslateMessage才發(fā)送相應(yīng)的WM_CHAR消息。
如果一個字符消息被發(fā)送到調(diào)用線程的消息隊列,則TranlateMessage返回非零值,否則返回零值。
●?注意:
●?與在Windows?95操作系統(tǒng)下不同,在Windows?NT下,TranslateMessage對于功能鍵和光標(biāo)箭頭鍵也返回一個非零值。
然后,函數(shù)DispatchMessage將屬于某一窗口的消息發(fā)送該窗口的窗口過程。這個窗口由MSG結(jié)構(gòu)中的hwnd成員所標(biāo)識的。函數(shù)的返回值為窗口過程的返回值,但是,我們一般不使用這個返回值。這里要注意的是,并不一定是所有屬于某一個窗口的消息都發(fā)送給窗口的窗口過程,比如對于WM_TIMER消息,如果其lParam參數(shù)不為NULL的話,由該參數(shù)所指定的函數(shù)將被調(diào)用,而不是窗口過程。
如果GetMessage從消息隊列中得到一個WM_QUIT消息,則它將返回一個假值,從而退出消息循環(huán),WM_QUIT消息的wParam參數(shù)指定了由PostQuitMessage函數(shù)給出的退出碼,一般情況下,WinMain函數(shù)返回同一值。
下面我們來看一下程序主窗口的窗口過程WndProc。
窗口過程名是可以由用戶自行定義,然后在注冊窗口類時在WNDCLASS結(jié)構(gòu)中指定。但是,一般來說,程序都把窗口過程命令為WndProc來類似的名稱,如MainWndProc等,并不是一定要這樣做,但是這樣明顯的有利于閱讀,因此也是我們推薦的做法。
窗口過程具有如下的原型:
LRESULT?WINAPI?WndProc(HWND,UINT,WPARAM,LPARAM);
或
LRESULT?CALLBACK?WndProc(HWND,UINT,WPARAM,LPARAM);
對于編譯器而言,兩種書寫形式都是一樣的,它們都等價于
long?__stdcall?WndProc(void?*,unsigned?int,unsigned?int,long)
窗口過程使用了四個參數(shù),在它被調(diào)用時(再強(qiáng)調(diào)一點(diǎn),一般情況下,窗口過程是由操作系統(tǒng)調(diào)用,而不是由應(yīng)用程序調(diào)用的,這就是我們?yōu)槭裁磳⑺鼈兎Q為回調(diào)函數(shù)的道理),這四個參數(shù)對應(yīng)于所發(fā)送消息結(jié)構(gòu)的前四個成員。
下面給出了一個窗口過程的例子:
//?WndProc?主窗口過程
LRESULT?WINAPI?WndProc?(HWND?hWnd,
UINT?msg,
WPARAM?wParam,
LPARAM?lParam)
{
HDC?hdc;
RECT?rc;
HPEN?hPen,hPenOld;
HBRUSH?hBrush,hBrushOld;
switch?(msg)
{
case?WM_PAINT:
hdc=GetDC(hWnd);
GetClientRect(hWnd,&rc);
hPen=CreatePen(PS_SOLID,0,RGB(0,0,0));
hBrush=CreateHatchBrush(HS_DIAGCROSS,RGB(0,0,0));
hPenOld=SelectObject(hdc,hPen);
hBrushOld=SelectObject(hdc,hBrush);
Ellipse(hdc,rc.left,rc.top,rc.right,rc.bottom);
SelectObject(hdc,hPenOld);
SelectObject(hdc,hBrushOld);
ReleaseDC(hWnd,hdc);
break;
case?WM_DESTROY:
PostQuitMessage(0);
break;
default:
break;
}
return?DefWindowProc(hWnd,msg,wParam,lParam);
}
在該窗口過程中,我們處理了最基本兩條消息。
第一條消息是WM_PAINT,當(dāng)窗口客戶區(qū)的全部或一部分需要重繪時,系統(tǒng)向該窗口發(fā)送該消息。在前面的過程中我們已經(jīng)提到過,在使用ShowWindow函數(shù)顯示窗口之后,通常隨即調(diào)用函數(shù)UpdateWindow,該函數(shù)直接向窗口過程發(fā)送一個WM_PAINT消息,以通知窗口繪制其客戶區(qū)。在該消息的處理函數(shù)中,我們先使用GetDC獲得窗口的設(shè)備句柄,它是用來調(diào)用各種繪圖方法的。然后調(diào)用GetClientRect獲得當(dāng)前窗口的客戶區(qū)矩形。接著調(diào)用CreatePen創(chuàng)建一個黑色畫筆,調(diào)用CreateHatchBrush創(chuàng)建一個45度交叉線的填充畫刷,并且SelectObject函數(shù)將它們選入設(shè)備描述表中,原有的畫筆和畫刷被保存到hPenOld和hBrushOld中,以便以后恢復(fù)。完成以上步驟之后,調(diào)用Ellipse函數(shù)以當(dāng)前客戶區(qū)大小繪制一個橢圓。最后,再一次調(diào)用SelectObject函數(shù)恢復(fù)原有的畫筆和畫刷,并調(diào)用ReleaseDC釋放設(shè)備描述表句柄。
對于窗口來說,除了客戶區(qū)以外的其它內(nèi)容將由系統(tǒng)進(jìn)行重繪,這些內(nèi)容包括窗口標(biāo)題條、邊框、菜單條、工具條以及其它控件,如果包含了它們的話。這種重繪往往發(fā)生在覆蓋于窗口上方的其它窗口被移走,或者是窗口被移動或改變大小時。因此,對于大多數(shù)窗口過程來說,WM_PAINT消息是必須處理的。
另一個對于絕大多數(shù)窗口過程都必須處理的消息是WM_DESTROY,當(dāng)窗口被撤消時(比如用戶從窗口的系統(tǒng)菜單中選擇了“關(guān)閉”,或者單擊了右邊的小叉,對于這些事件,Windows的默認(rèn)處理是調(diào)用DestroyWindow函數(shù)撤銷相應(yīng)的窗口),將會接收到該消息。由于本程序僅在一個窗口,因此在這種情況下應(yīng)該終止應(yīng)用程序的執(zhí)行,因此我們調(diào)用了PostQuitMessage函數(shù),該函數(shù)向線程的消息隊列中放入一個WM_QUIT消息,傳遞給PostQuitMessage函數(shù)的參數(shù)將成為WM_QUIT消息的wParam參數(shù),在上面的例子中,該值為0。
對于其它情況,?Windows專門為此提供了一個默認(rèn)的窗口過程,稱為DefWindowProc,我們只需要以WndProc的參數(shù)原封不動的調(diào)用默認(rèn)窗口過程DefWindowProc,并將其返回值作為WndProc的返回值即可。
將上面講述的所有內(nèi)容綜合起來,我們就已經(jīng)使用Win32?SDK完成了一個功能簡單,但是結(jié)構(gòu)完整的Win32應(yīng)用程序了。
對于使用Win32?SDK編寫的實用的Win32應(yīng)用程序,它們的結(jié)構(gòu)與此相比要復(fù)雜得多,在這些情況下,應(yīng)用程序也許不僅僅包括一個窗口,而對應(yīng)的窗口過程中的switch
結(jié)構(gòu)一般也會是一個異常膨脹的嵌套式switch結(jié)構(gòu)。如此龐大的消息處理過程大大增加了程序調(diào)試和維護(hù)的難度,使用MFC則有可能在很多程度上減輕這種負(fù)擔(dān),這便是MFC為廣大程序員所樂于接受,以至今天成為實際上的工業(yè)標(biāo)準(zhǔn)的原因。但是,不管它如何復(fù)雜,歸根到底,一般情況下,它仍然具有和我們的這個功能簡單的Win32應(yīng)用程序一樣或類似的結(jié)構(gòu)。
為了讀者閱讀和分析方便,我們把這個程序的完整代碼給出如下:
#include?<windows.h>
//?函數(shù)原型
int?WINAPI?WinMain(HINSTANCE,HINSTANCE,LPSTR,int);
LRESULT?WINAPI?WndProc(HWND,UINT,WPARAM,LPARAM);
//?WinMain?函數(shù)
int?WINAPI?WinMain?(HINSTANCE?hInstance,
HINSTANCE?hPrevInstance,
LPSTR?lpCmdLine,
int?nCmdShow)
{
HWND?hWnd;?//?主窗口句柄
MSG?msg;?//?窗口消息
WNDCLASS?wc;?//?窗口類
if?(!hPrevInstance)
{
//?填充窗口類信息
wc.style=CS_HREDRAW|CS_VREDRAW;
wc.lpfnWndProc=WndProc;
wc.cbClsExtra=0;
wc.cbWndExtra=0;
wc.hInstance=hInstance;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);
wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=GetStockObject(WHITE_BRUSH);
wc.lpszMenuName=NULL;
wc.lpszClassName="SdkDemo1";
//?注冊窗口類
RegisterClass(&wc);
}
//?創(chuàng)建應(yīng)用程序主窗口
hWnd=CreateWindow?("SdkDemo1",?//?窗口類名
"第一個Win32?SDK應(yīng)用程序",?//?窗口標(biāo)題
WS_OVERLAPPEDWINDOW,?//?窗口樣式
CW_USEDEFAULT,?//?初始化?x?坐標(biāo)
CW_USEDEFAULT,?//?初始化?y?坐標(biāo)
CW_USEDEFAULT,?//?初始化窗口寬度
CW_USEDEFAULT,?//?初始化窗口高度
NULL,?//?父窗口句柄
NULL,?//?窗口菜單句柄
hInstance,?//?程序?qū)嵗浔?/p>
NULL);?//?創(chuàng)建參數(shù)
//?顯示窗口
ShowWindow(hWnd,SW_SHOW);
//?更新主窗口客戶區(qū)
UpdateWindow(hWnd);
//?開始消息循環(huán)
while?(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return?msg.wParam;
}
//?WndProc?主窗口過程
LRESULT?WINAPI?WndProc?(HWND?hWnd,
UINT?msg,
WPARAM?wParam,
LPARAM?lParam)
{
HDC?hdc;
RECT?rc;
HPEN?hPen,hPenOld;
HBRUSH?hBrush,hBrushOld;
switch?(msg)
{
case?WM_PAINT:
hdc=GetDC(hWnd);
GetClientRect(hWnd,&rc);
hPen=CreatePen(PS_SOLID,0,RGB(0,0,0));
hBrush=CreateHatchBrush(HS_DIAGCROSS,RGB(0,0,0));
hPenOld=SelectObject(hdc,hPen);
hBrushOld=SelectObject(hdc,hBrush);
Ellipse(hdc,rc.left,rc.top,rc.right,rc.bottom);
SelectObject(hdc,hPenOld);
SelectObject(hdc,hBrushOld);
ReleaseDC(hWnd,hdc);
break;
case?WM_DESTROY:
PostQuitMessage(0);
break;
default:
break;
}
return?DefWindowProc(hWnd,msg,wParam,lParam);
}
這里我們簡單的說一下如何在Microsoft?Developer?Studio中編譯該示例程序。請按下面的步驟進(jìn)行:
1.?選擇File菜單下的New命令,新建一個Win32?Application工程,這里我們假設(shè)對該工程命名為SdkDemo1。
2.?選擇Project菜單下的Add?To?Project|New...命令,向工程中添加一個C++?Source?File?(C++源文件),可以將該文件命名為winmain.cpp,不需要鍵入擴(kuò)展名,Microsoft?Developer?Studio在創(chuàng)建文件時會自動加上.cpp的后綴名。
然后在Wordspace窗口的FileView中雙擊文件名winmain.cpp?(在依賴于你在前面過程中的設(shè)定),輸入下面的源代碼即可。
如果已將源代碼輸入為C++源文件(以.cpp為后綴名的文件),則可以使用Project|Add?To?Project|Files...將其添加到工程中。
圖3.2?示例程序SdkDemo1的運(yùn)行結(jié)果
3.?單擊Build菜單下的Build?SdkDemo1.exe或Build?All或按下快捷鍵F7或單擊Build或Build?Minibar工具條上的按鈕,編譯并創(chuàng)建可執(zhí)行文件SdkDemo1.exe,運(yùn)行該可執(zhí)行文件(從Developer?Studio中或資源管理器均可),將得到如圖3.2所示的結(jié)果。
32位編程的特點(diǎn)
本節(jié)假定用戶是剛接觸32位Windows編程的新手,那么,有必要將一些相關(guān)的概念術(shù)語弄清楚,
同時,也要把Windows?95、Windows?NT和16位的Windows?3.x相區(qū)別開來。
這些最重要的概念包括進(jìn)程和線程的管理以及新的32位平坦內(nèi)存模式。
進(jìn)程是裝入內(nèi)存中正在執(zhí)行的應(yīng)用程序,進(jìn)程包括私有的虛擬地址空間、代碼、數(shù)據(jù)及其它操作系統(tǒng)資源,如文件、管道以及對該進(jìn)程可見的同步對象等。
進(jìn)程包括了一個或多個在進(jìn)程上下文內(nèi)運(yùn)行的線程。
線程是操作系統(tǒng)分配CPU時間的基本實體。線程可以執(zhí)行應(yīng)用程序代碼的任何部分,包括當(dāng)前正在被其它線程執(zhí)行的那些。同一進(jìn)程的所有線程共享同樣的虛擬地址空間、全局變量和操作系統(tǒng)資源。
在一個應(yīng)用程序中,可以包括一個或多個進(jìn)程,每個進(jìn)程由一個或多個線程構(gòu)成。
線程通過“休眠”(sleeping,暫停所有執(zhí)行并等待)的方法,來做到與進(jìn)程中的其它線程所同步。
在線程休眠前,必須告訴Windows,該線程將等待某一事件的發(fā)生。當(dāng)該事件發(fā)生時,Windows發(fā)給線程一個喚醒調(diào)用,線程繼續(xù)執(zhí)行。也就是說,線程與事件一起被同步,除此之外,也可以由特殊的同步對象來進(jìn)行線程的同步。這些同步對象包括:
●?互斥?不受控制的線程訪問在多線程應(yīng)用程序中可能會引起很大的問題。這里所說的互斥是一小段代碼,它時刻采取對共享數(shù)據(jù)的獨(dú)占控制以執(zhí)行代碼。
互斥常被應(yīng)用于多進(jìn)程的同步數(shù)據(jù)存取。
●?信號量?信號量與互斥相似,但是互斥只允許在同一時刻一個線程訪問它的數(shù)據(jù),而信號量允許多個線程在同一時刻訪問它的數(shù)據(jù)。Win32不知道哪一個線程擁有信號量,它只保證信號量使用的資源量。
●?臨界區(qū)?和互斥相似,但它僅被屬于單個進(jìn)程的線程使用。臨界區(qū)對象提供非常有效的同步模式,每次在同一時間內(nèi)只有一個線程可以訪問臨界區(qū)對象。
●?事件?事件對象用于許多實例中去通知休眠的線程所等待的事件已經(jīng)發(fā)生,事件告訴線程何時去執(zhí)行某一個給定的任務(wù),并可以使多線程流平滑。
將所有的這些同步對象應(yīng)用于控制數(shù)據(jù)訪問使得線程同步成為可能,否則,如果一個線程改變了另一個線程正在讀的數(shù)據(jù),將有可能導(dǎo)致很大的麻煩。
在Win32環(huán)境下,每個運(yùn)行的在進(jìn)程內(nèi)的線程還可以為它自己的特定線程數(shù)據(jù)分配內(nèi)存,通過Win32提供的線程本地存儲(TLS)API,應(yīng)用程序可以建立動態(tài)的特定線程數(shù)據(jù),在運(yùn)行時這些數(shù)據(jù)聯(lián)系在一起。
下面我們來看在32位應(yīng)用程序地址空間中的內(nèi)存分配和內(nèi)存管理。
常見的內(nèi)存分配可以劃分為兩類:幀分配(frame?allocation)和堆分配(heap?allocation)。
兩者的主要區(qū)別在于幀分配通常和實際的內(nèi)存塊打交道,而堆分配在一般情況下則使用指向內(nèi)存塊的指針,并且,幀對象在超過其作用域時會被自動的刪除,而程序員必須顯式的刪除在堆上分配的對象。
在幀上分配內(nèi)存的這種說法來源于“堆棧幀”(stack?frame)這個名詞,堆棧幀在每當(dāng)函數(shù)被調(diào)用時創(chuàng)建,它是一塊用來暫時保存函數(shù)參數(shù)以及在函數(shù)中定義的局部變量的內(nèi)存區(qū)域。
幀變量通常被稱作自動變量,這是因為編譯器自動為它們分配所需的內(nèi)存。
幀分配有兩個主要特征,首先,當(dāng)我們定義一個局部變量時,編譯器將在堆棧幀上分配足夠的空間來保存整個變量,對于很大的數(shù)組和其它數(shù)據(jù)結(jié)構(gòu)也是這樣;其次,當(dāng)超過其作用域時,幀變量將被自動的刪除。
下面舉一個幀分配的例子:
int?Func(int?Argu1,int?Argu2)?//?編譯器將在堆棧幀上為函數(shù)參數(shù)變量分配空間
{
//?在堆棧上創(chuàng)建局部對象
char?szDatum[256][256];
...
//?超過作用域時將自動刪除在堆棧上分配的對象
}
對于局部函數(shù)變量,其作用域轉(zhuǎn)變在函數(shù)退出時發(fā)生,但如果使用了嵌套的花括號,則幀變量的作用域?qū)⒂锌赡鼙群瘮?shù)作用域小。自動刪除這些幀變量非常之重要。對于簡單的基本數(shù)據(jù)類型?(如整型或字節(jié)變量)、數(shù)組或數(shù)據(jù)結(jié)構(gòu),自動刪除只是簡單的回收被這些這是所占用的內(nèi)存。由于這些變量已超出其作用域,它們將再也不可以被訪問。對于C++對象,自動刪除的過程要稍稍復(fù)雜一些。當(dāng)一個對象被定義為一個幀變量時,其構(gòu)造函數(shù)在定義對象變量時被自動的調(diào)用,當(dāng)對象超出其作用域時,在對象所占用的內(nèi)存被釋放前,其析構(gòu)函數(shù)先被自動的調(diào)用。這種對構(gòu)造函數(shù)和析構(gòu)函數(shù)的調(diào)用看起來非常的簡便,但我們必須對它們倍加小心,尤其是對析構(gòu)函數(shù),不正確的構(gòu)造和釋放對象將可能對內(nèi)存管理帶來嚴(yán)重的問題。
在幀上分配對象的最大的優(yōu)越性在于這些對象將會被自動的刪除,也就是說,當(dāng)你在幀上分配對象之后,不必?fù)?dān)心它們會導(dǎo)致內(nèi)存漏損(memory?leak)。
但是,幀分配也有其不方便之處,首先,在幀上分配的變量不可以超出其作用域,其中,幀空間往往是有限的,因此,在很多情況下,我們更傾向于使用堆分配來代替這里所講述的幀分配來為那些龐大的數(shù)據(jù)結(jié)構(gòu)或?qū)ο蠓峙鋬?nèi)存。
堆是為程序所保留的用于內(nèi)存分配的區(qū)域,它與程序代碼和堆棧相隔離。在通常情況下,C程序使用函數(shù)malloc和free來分配和釋放堆內(nèi)存。調(diào)試版本(Debug?version)的MFC提供了改良版本的C++內(nèi)建運(yùn)算符new和delete用于在堆內(nèi)存中分配和釋放對象。
與幀分配不同,在堆上可分配的對象所占用的內(nèi)存的總量只受限于系統(tǒng)可有的所有虛擬內(nèi)存空間。
以下的示例代碼對比了上面討論的內(nèi)存分配方法在為數(shù)組、數(shù)據(jù)結(jié)構(gòu)和對象分配內(nèi)存時的用法:
使用幀分配為數(shù)組分配內(nèi)存:
{
const?int?BUFF_SIZE?=?128;
//?在幀上分配數(shù)組空間
char?myCharArray[BUFF_SIZE];
int?myIntArray[BUFF_SIZE];
//?所分配的空間在超出作用域時自動回收
}
使用堆分配為數(shù)組分配內(nèi)存:
const?int?BUFF_SIZE?=?128;
//?在堆上分配數(shù)組空間
char*?myCharArray?=?new?char[BUFF_SIZE];
int*?myIntArray?=?new?int[BUFF_SIZE];
...
delete?[]?myCharArray;
delete?[]?myIntArray;
使用幀分配為結(jié)構(gòu)分配內(nèi)存:
struct?MyStructType?{?int?topScore;};
void?SomeFunc(void)
{
//?幀分配
MyStructType?myStruct;
//?使用該結(jié)構(gòu)
myStruct.topScore?=?297;
//?在超出作用域時結(jié)構(gòu)所占用的內(nèi)存被自動回收
}
使用堆分配為結(jié)構(gòu)分配內(nèi)存:
//?堆分配
MyStructType*?myStruct?=?new?MyStructType;
//?通過指針使用該結(jié)構(gòu)
myStruct->topScore?=?297;
delete?myStruct;
使用幀分配為對象分配內(nèi)存:
{
CMyClass?myClass;?//?構(gòu)造函數(shù)被自動調(diào)用
myClass.SomeMemberFunction();?//?使用該對象
}
使用堆分配為對象分配內(nèi)存:
//?自動調(diào)用構(gòu)造函數(shù)
CMyClass?*myClass=new?CMyClass;
myClass->SomeMemberFunction();?//?使用該對象
delete?myClass;?//?在使用delete的過程中調(diào)用析構(gòu)函數(shù)
●?注意:
●在堆上分配的內(nèi)存一定要記得釋放,對于使用運(yùn)算符new分配的內(nèi)存,應(yīng)當(dāng)使用delete運(yùn)算符來釋放;而使用malloc函數(shù)分配的內(nèi)存應(yīng)當(dāng)使用free函數(shù)來釋放。
不應(yīng)當(dāng)對同一內(nèi)存塊交叉使用運(yùn)算符new、delete和函數(shù)malloc、free?(即使用delete運(yùn)算符釋放由malloc函數(shù)分配的內(nèi)存,或使用free函數(shù)釋放由new運(yùn)算符根本的內(nèi)存),否則在MFC的調(diào)試版本下將會導(dǎo)致內(nèi)存沖突。
對固定大小的內(nèi)存塊,使用運(yùn)算符new和delete要比使用標(biāo)準(zhǔn)C庫函數(shù)malloc和free方便。
但有時候我們需要使用可變大小的內(nèi)存塊,這時,我們必須使用標(biāo)準(zhǔn)的C庫函數(shù)malloc、realloc和free。
最后重新強(qiáng)調(diào)一點(diǎn),即不要使用realloc改變由new運(yùn)算符分配的內(nèi)存塊的大小。
下面我們簡單的介紹Win32內(nèi)存管理模式。
在Microsoft?Win32應(yīng)用程序編程接口中,每一個進(jìn)程都有自己多達(dá)4GB的虛擬地址空間。內(nèi)存中低位的2GB?(從0x00到0x7FFFFFFF)可以為用戶所用,高位的2GB?(從0x80000000到0xFFFFFFFFF)為內(nèi)核所保留。進(jìn)程所使用的虛擬地址并不代碼對象在內(nèi)存中實際的物理地址。事實上,內(nèi)核為每一個進(jìn)程維護(hù)了一個頁映射,頁映射是一個用于將虛擬地址轉(zhuǎn)換為對應(yīng)的物理地址的內(nèi)部數(shù)據(jù)結(jié)構(gòu)。
每一個進(jìn)程的虛擬地址空間都要比所有進(jìn)程可用的物理內(nèi)存RAM?(隨機(jī)存取存儲器)的總和大得多。為了增加物理存儲的大小,內(nèi)核使用磁盤作為額外的存儲空間。對于所有正在執(zhí)行的進(jìn)程來說,總的存儲空間的量是物理內(nèi)存RAM和磁盤上可以為頁面文件所用的自由空間的總合,這里頁面文件指用來增加物理存儲空間的磁盤文件。每個進(jìn)程物理存儲空間和虛擬地址(也稱為邏輯地址)空間以頁的形式來組織,頁是一種內(nèi)存單元,其大小依賴于宿主計算機(jī)的類型。對于x86計算機(jī)來說,宿主頁大小為4KB,但我們不能假定對有所有運(yùn)行Windows操作系統(tǒng)的計算機(jī),其頁大小均為4KB。
為了使內(nèi)存管理具有最大的靈活性,內(nèi)核可以將物理內(nèi)存中的頁移入或移出磁盤上的頁面文件。
當(dāng)一個頁被移入物理內(nèi)存時,內(nèi)核更新受到影響的進(jìn)程的頁映射。將內(nèi)核需要物理內(nèi)存空間時,它將物理內(nèi)存中最近最少使用的頁移入頁面文件。對于應(yīng)用程序來說,內(nèi)核對物理內(nèi)存的管理是完全透明的,應(yīng)用程序只對它自己的虛擬地址空間進(jìn)行操作。
在進(jìn)程虛擬地址空間中的頁可以具在表所列的狀態(tài)之一:
狀態(tài)說明
空閑(Free)?空閑頁是當(dāng)前不可用,但可以被占用或保留的頁。
保留(Reserved)?保留頁是進(jìn)程的虛擬地址空間中為將來使用所保留的頁。進(jìn)程不可以存取保留頁,
并且也沒有為保留頁分配物理存儲。保留頁保留虛擬地址中的一段以使得它們不可以被隨后的其它分配操作(如malloc和LocalAlloc之類的函數(shù)等)所使用。一個進(jìn)程可以使用VirtualAlloc函數(shù)在其地址空間中保留頁面,然后再占用這些保留頁。最后使用VirtualFree函數(shù)釋放它們。
占用(Committed)?已被占用的頁是那些已分配了物理存儲(在內(nèi)存中或磁盤上)的頁。占用頁可以被禁
止進(jìn)行存取,或允許只讀存取,或允許讀寫存取。進(jìn)程可以使用VirtualAlloc函數(shù)分
配占用頁。GlobalAlloc和LocalAlloc函數(shù)分配允許讀寫存取的占用頁。占用頁可以使
用VirtualFree函數(shù)進(jìn)行釋放,函數(shù)VirtualFree釋放頁的存儲空間,并將其狀態(tài)改變?yōu)?/p>
保留。
進(jìn)程可以使用函數(shù)GlobalAlloc和LocalAlloc來分配內(nèi)存。在Win32?API的32位線性環(huán)境中,本地堆和
全局堆并沒有區(qū)別,因此,使用這兩個函數(shù)來分配內(nèi)存對象也沒有任何區(qū)別。
由GlobalAlloc和LocalAlloc函數(shù)分配的內(nèi)存對象位于私有的占用頁中,這些頁允許進(jìn)行讀寫存取。
私有內(nèi)存不可以為其它進(jìn)程所訪問。與在Windows?3.x中不同,使用帶有GMEM_DDESHARE標(biāo)志
的GlobalAlloc函數(shù)分配的內(nèi)存事實上并沒有被全局共享。保留該標(biāo)志位僅是為了向前兼容和為一
些應(yīng)用程序增強(qiáng)動態(tài)數(shù)據(jù)交換(DDE,dynamic?data?exchange)的性能而使用。應(yīng)用程序如果因其它
目的需要共享內(nèi)存,那么必須使用文件映射對象。多個進(jìn)程可以通過映射同一個文件映射對象的
視來提供命名共享內(nèi)存。我們在這里將不討論文件映射和共享內(nèi)存的問題。
通過使用函數(shù)GlobalAlloc和LocalAlloc,可以分配能夠表示為32位的任意大小的內(nèi)存塊,所受的唯
一限制是可用的物理內(nèi)存,包括在磁盤上的頁面文件中的存儲空間。這些函數(shù),和其它操作全局
和本局內(nèi)存對象的全局和本地函數(shù)一起被包含在Win32?API中,以和Windows的16位版本相兼容。
但是,從16位分段內(nèi)存模式到32位虛擬內(nèi)存模式的轉(zhuǎn)變將使得一些函數(shù)和一些選項變得不必要甚
至沒有意義。比如說,現(xiàn)在不再和近指針和遠(yuǎn)指針的區(qū)別,因為無論在本地還是在全局進(jìn)行分配
都將返回32位虛擬地址。
函數(shù)GlobalAlloc和LocalAlloc都可以分配固定或可移動的內(nèi)存對象。可移動對象也可以被標(biāo)記為可
丟棄的(discardable)。在早期的Windows版本中,可移動的內(nèi)存對象對于內(nèi)存管理非常之重要,它
們允許系統(tǒng)在必要時壓縮堆以為其它內(nèi)存分配提供可用空間。通過使用虛擬內(nèi)存,系統(tǒng)能夠通過
移動物理內(nèi)存頁來管理內(nèi)存,而不影響使用這些頁的進(jìn)程的虛擬地址。當(dāng)系統(tǒng)移動一個物理內(nèi)存
頁時,它簡單的將進(jìn)程的虛擬頁映射到新的物理頁的位置。可移動內(nèi)存在分配可丟棄內(nèi)存仍然有
用。當(dāng)系統(tǒng)需要額外的物理存儲時,它使用一種稱作“最近最少使用”的算法來釋放非鎖定的可
丟棄內(nèi)存。可丟棄內(nèi)存可以用于那些不是經(jīng)常需要和易于重新創(chuàng)建的數(shù)據(jù)。
當(dāng)分配固定內(nèi)存對象時,GlobalAlloc和LocalAlloc返回32位指針,調(diào)用線程可以立即使用該指針來
進(jìn)行內(nèi)存存取。對于可移動內(nèi)存,返回值為一個句柄。為了得到一個指向可移動內(nèi)存的指針,調(diào)
用線程可以使用GlobalLock和LocalLock函數(shù)。這些函數(shù)鎖定內(nèi)存使得它不能夠被移動或丟棄,除
非使用函數(shù)GlobalReAlloc或LocalReAlloc對內(nèi)存對象進(jìn)行重新分配。已鎖定內(nèi)存對象的內(nèi)存塊保持
鎖定狀態(tài),直至鎖定計數(shù)減到0,這時該內(nèi)存塊可以被移動或丟棄。
由GlobalAlloc和LocalAlloc所分配的內(nèi)存的實際大小可能大于所要求的大小。為了得到已分配的實
際內(nèi)存數(shù),可以使用函數(shù)GlobalSize和LocalSize。如果總分配量大于所要求的量,進(jìn)程則可以使用
所有的這些量。]
函數(shù)GlobalReAlloc和LocalReAlloc以字節(jié)為單位改變由GlobalAlloc和LocalAlloc函數(shù)分配內(nèi)存對象的
大小或其屬性。內(nèi)存對象的大小可以增大,也可以減小。
函數(shù)GlobalFree和LocalFree用于釋放由GlobalAlloc、LocalAlloc、GlobalReAlloc或LocalReAlloc分配的
內(nèi)存。
其它的全局和本地函數(shù)包括GlobalDiscard、LocalDiscard、GlobalFlags、LocalFlags、GlobalHandle和
LocalHandle。GlobalDiscard和LocalDiscard用于丟棄指定的可丟棄內(nèi)存對象,但不使其句柄無效。
該句柄可能通過函數(shù)GlobalReAlloc或LocalReAlloc與新分配的內(nèi)存塊相關(guān)聯(lián)。函數(shù)GlobalFlags或
LocalFlags返回關(guān)于指定內(nèi)存對象的信息。這些住處包括對象的鎖定計數(shù)以及對象是否可丟棄或是
否已被丟棄。函數(shù)GlobalHandle或LocalHandle返回與指定指針相關(guān)聯(lián)的內(nèi)存對象的句柄。
Win32進(jìn)程可以完全的使用標(biāo)準(zhǔn)的C庫函數(shù)malloc、free等來操作內(nèi)存。在Windows的早期版本中使
用這些函數(shù),將可能帶來問題隱患,但是使用Win32?API的應(yīng)用程序中則不會。舉例來說,使用
malloc分配固定指針將不能使用可移動內(nèi)存的優(yōu)點(diǎn)。由于系統(tǒng)可以通過移動物理內(nèi)存頁來自由的
管理內(nèi)存,而不影響虛擬地址,因此內(nèi)存管理將不再成為問題。類似的,遠(yuǎn)指針和近指針之間不
再有差別。因此,除非你希望使用可丟棄內(nèi)存,否則完全可以將標(biāo)準(zhǔn)的C庫函數(shù)用于內(nèi)存管理。
Win32?API提供了一系列的虛擬內(nèi)存函數(shù)來操作或決定虛擬地址空間中的頁的狀態(tài)。許多應(yīng)用程
序使用標(biāo)準(zhǔn)的分配函數(shù)GlobalAlloc、LocalAlloc、malloc等就可以滿足其需要。然而,虛擬內(nèi)存函
數(shù)提供了一些這些標(biāo)準(zhǔn)分配函數(shù)所不具有的功能,它們可以進(jìn)行下面的這些操作:
●?保留進(jìn)程虛擬地址空間中的一段?保留地址空間并不為它們分配物理存儲,而只是防止其它
分配操作使用這段空間。它并不影響其它進(jìn)程的虛擬地址空間。保留頁防止了對物理存儲
的不必要的浪費(fèi),然而它允許進(jìn)程為可能增長的動態(tài)數(shù)據(jù)結(jié)構(gòu)保留一段地址空間,進(jìn)程可
以在需要的時候為這些空間分配物理存儲。
●?占用進(jìn)程虛擬地址空間中的保留頁的一部分,以使得物理存儲(無論是RAM還是磁盤空間)
只對正在進(jìn)行分配的進(jìn)程可用。
●?指定允許讀寫存取、只讀存取或不允許存取的占用頁區(qū)域。這和標(biāo)準(zhǔn)的分配函數(shù)總是分配
允許讀寫存取的頁不同。
●?釋放一段保留頁,使得調(diào)用線程在隨后的分配操作中可以使用這段虛擬地址。
●?取消對一段頁的占用,釋放它們的物理存儲,使它們可以其它進(jìn)程在隨后的分配中使用。
●?在物理內(nèi)存RAM中鎖定一個或多個占用頁,以免系統(tǒng)將這些頁交換到頁面文件中。
●?獲得關(guān)于調(diào)用線程或指定線程的虛擬地址空間中的一段頁的信息。
●?改變調(diào)用線程或指定線程的虛擬地址空間中指定占用頁段的存取保護(hù)。
虛擬內(nèi)存函數(shù)對內(nèi)存頁進(jìn)行操作。函數(shù)使用當(dāng)前計算機(jī)的頁大小來對指定的大小和地址進(jìn)行舍
入。
可以使用函數(shù)GetSystemInfo來獲得當(dāng)前計算機(jī)的頁大小。
函數(shù)VirtualAlloc完成以下操作:
●?保留一個或多個自由頁。
●?占用一個或多個保留頁。
●?保留并占用一個或多個自由頁。
你可以指針?biāo)A艋蛘加玫捻摰钠鹗嫉刂?#xff0c;或者讓系統(tǒng)來決定。函數(shù)將指定的地址舍入到合適的
頁邊界。保留頁是不可訪問的,但占用頁可以使用標(biāo)志位PAGE_READWRITE、
PAGE_READONLY和PAGE_NOACCESS來分配。當(dāng)頁被占用時,從頁面文件中分配存儲空間,
每個頁僅在第一次試圖對其進(jìn)行讀寫操作時被初始化并加載到物理內(nèi)存中。可以用一般的指針引
用來訪問由VirtualAlloc函數(shù)占用的頁。
函數(shù)VirtualFree完成下面的操作:
●?解除對一個或多個頁的占用,改變其狀態(tài)為保留。解除對頁的戰(zhàn)勝釋放與之相關(guān)的物理存
儲,使其為其它進(jìn)程可用。任何占用頁塊都可以被解除占用。
●?釋放一個或多個保留頁塊,改變其狀態(tài)為自由。釋放頁塊使這段保留空間可以為進(jìn)程分配
空間使用。保留頁只能通過釋放由函數(shù)VirtualAlloc最初保留的整個塊來釋放。
●?同時解除對一個或多個占用頁的占用并釋放它們,將其狀態(tài)改變?yōu)樽杂伞V付ǖ膲K必須包
括由VirtualAlloc最初保留的整個塊,而且這些頁的當(dāng)前狀態(tài)必須為占用。
函數(shù)VirtualLock允許進(jìn)程將一個或多個占用頁鎖定在物理內(nèi)存RAM中,防止系統(tǒng)將它們交換到頁
面文件中。這保證了一些要求苛刻的數(shù)據(jù)可以不通過磁盤訪問來存取。將一個頁鎖定入內(nèi)存是很
危險的,因為它限制了系統(tǒng)管理內(nèi)存的能力。由于可能會將可執(zhí)行代碼交換到頁面文件中,可執(zhí)
行程序使用VirtualLock將有可能降低系統(tǒng)性能。函數(shù)VirtualUnlock解除VirtualLock對內(nèi)存的鎖
定。
函數(shù)VirtualQuery和VirtualQueryEx返回關(guān)于以進(jìn)程地址空間中某一指定地址開始的一段連續(xù)內(nèi)存
區(qū)域的信息。VirtualQuery返回關(guān)于調(diào)用線程內(nèi)存的信息。VirtualQueryEx返回指定進(jìn)程內(nèi)存的信
息,這通常用來支持調(diào)試程序,這些程序常常需要知道關(guān)于被調(diào)試進(jìn)程的信息。頁區(qū)域以相對于
指定地址最接近的頁邊界為界。一般來說,它通過具有下述屬性的后續(xù)頁來進(jìn)行擴(kuò)展:
所有頁具有相同的狀態(tài),或為占用,或為保留,或為自由。
如果初始頁不為自由,區(qū)域中的所有頁都屬于通過調(diào)用VirtualAlloc保留的同一個最初頁分配。
所有頁的存取保護(hù)相同,或為PAGE_READONLY,或為PAGE_READWRITE,或為
PAGE_NOACCESS。
函數(shù)VirtualProtect允許進(jìn)程修改進(jìn)程地址空間中任意占用頁的存取保護(hù)。舉例來說,一個進(jìn)程可
以分配讀寫頁來保存易受影響的數(shù)據(jù),然后將存取改變?yōu)橹蛔x或禁止訪問,以避免無意中被重
寫。典型的,VirtualProtect用于使用VirtualAlloc分配的頁,但事實下,它也可以用于通過其它分
配函數(shù)占用的頁。然而,VirtualProtect改變整個頁的保護(hù)狀態(tài),而由其它函數(shù)返回的指針并非總
是指向頁邊界。函數(shù)VirtualProtectEx類似于VirtualProtect,但函數(shù)VirtualProtectEx可以改變指定進(jìn)
程的內(nèi)存的保護(hù)狀態(tài)。改變這些內(nèi)存的保護(hù)狀態(tài)在調(diào)試程序訪問被調(diào)試進(jìn)程的內(nèi)存的非常有用。__
轉(zhuǎn)載于:https://www.cnblogs.com/nealgavin/p/3206083.html
總結(jié)
- 上一篇: 【转】 java自定义注解
- 下一篇: HL7 ADT Message Samp