from http://www.cic.tsinghua.edu.cn/jdx/book4/dlz.htm
6.1.2.2 加锁与解锁
上面程序段分配的固定局部内存对象可以由应用程序直接存取,但是,Windows并不鼓励使用固定内存对象。因此,在使用可移动和可删除内存对象时,就要经常用到对内存对象的加锁与解锁。
不管是可移动对象还是可删除对象,在它分配后其内存句柄是不变的,它是内存对象的恒定引用。但是,应用程序无法通过内存句柄直接存取内存对象,应用程序要存取内存对象还必须获得它的近地址,这通过调用LocalLock函数实现。LocalLock函数将局部内存对象暂时固定在局部堆的某一位置,并返回该地址的近地址值,此地址可供应用程序存取内存对象使用,它在应用程序调用 LocalUnlock函数解锁此内存对象之前有效。怎样加锁与解锁可移动内存对象,请看如下代码:
HLOCAL hLocalObject;
char NEAR *pcLocalObject;
if (hLocalObject = LocalAlloc(LMEM_MOVEABLE, 32))
{
if
(pcLocalObject = LocalLock(hLocalObject)) {
/*Use
pcLocalObject as the near address of the locally allocated object */
.…..
LocalUnlock(hLocalObject);
}
else
{
/*
The lock failed. React accordingly. */
}
}
else {
/*
The 32 bytes cannot be allocated. React accordingly. */
}
应用程序在使用完内存对象后,要尽可能早地为它解锁,这是因为Windows无法移动被锁住了的内存对象。当应用程序要分配其它内存时,Windows不能利用被锁住对象的区域,只能在它周围寻找,这会降低Windows内存管理的效率。
6.1.2.3 改变局部内存对象
局部内存对象分配之后,还可以调用LocalReAlloc函数进行修改。LocalReAlloc函数可以改变局部内存对象的大小而不破坏其内容:如果比原来的空间小,则Windows将对象截断;如果比原来大,则Windows将增加区域填0(使用LMEM_ZEROINIT选项),或者不定义该区域内容。另外,LocalReAlloc函数还可以改变对象的属性,如将属性从LMEM_MOVEABLE改为LMEM_DISCARDABLE,或反过来,此时必须同时指定LMEM_MODIFY选项。但是,LocalReAlloc函数不能同时改变内存对象的大小和属性,也不能改变具有LMEM_FIXED属性的内存对象和把其它属性的内存对象改为LMEM_FIXED属性。如何将一个可移动内存对象改为可删除的,请看下面的例子:
hLocalObject = LocalAlloc(32, LMEM_MOVEABLE);
.…..
hLocalObject = LocalReAlloc(hLocalObject, 32,
LMEM_MODIFY| LMEM_KISCARDABLE);
6.1.2.4 释放与删除
分配了的局部内存对象可以使用LocalDiscard和LocalFree函数来删除和释放,删除和释放只有在内存对象未锁住时才有效。
LocalFree函数用来释放局部内存对象,当一个局部内存对象被释放时,其内容从局部堆移走,并且其句柄也从有效的局部内存表中移走,原来的内存句柄变为不可用。LocalDiscard 函数用来删除局部内存对象,它只移走对象的内容,而保持其句柄有效,用户在需要时,还可以使用此内存句柄用LocalReAlloc函数重新分配一块内存。
另外,Windows还提供了函数LocalSize用于检测对象所占空间;函数LocalFlags用于检测内存对象是否可删除,是否已删除,及其锁计数值;函数LocalCompact用于确定局部堆的可用内存。
全局内存对象在全局堆中分配,全局堆包括所有的系统内存。一般来说,应用程序在全局堆中进行大型内存分配(约大于1KB),在全局堆还可以分配大于64K的巨型内存,这将在后面介绍。
6.1.3.1 分配全局内存对象
全局内存对象使用GlobalAlloc函数分配,它和使用LocalAlloc分配局部内存对象很相似。使用GlobalAlloc的例子我们将和GlobalLock一起给出。
6.1.3.2 加锁与解锁
全局内存对象使用GlobalLock函数加锁,所有全局内存对象在存取前都必须加锁。GlobalLock将对象锁定在内存固定位置,并返回一个远指针,此指针在调用GlobalUnlock之前保持有效。
GlobalLock和LocalLock稍有不同,因为全局内存对象可能被多个任务使用,因此在使用GlobalLock加锁某全局内存对象时,对象可能已被锁住,为了处理这种情况,Windows增加了一个锁计数器。当使用GlobalLock加锁全局内存对象时,锁计数器加1;使用GlobalUnlock解锁对象时,锁计数器减1,只有当锁计数器为0时,Windows才真正解锁此对象。GlobalAlloc和GlobalLock的使用见如下的例子:
HGLOBAL hGlobalObject;
char FAR * lpGlobalObject;
if (hGlobalObject = GlobalAlloc(GMEM_MOVEABLE,
1024)) {
if
(lpGlobalObject = GlobalLock(hGlobalObject)) {
/*
Use lpGlobalObject as the far address of the globally allocated object. */
.…..
GlobalUnlock
(hGlobalObject);
}
else
{
/*
The lock failed .React accordingly. */
}
}
else {
/*
The 1024 bytes cannot be allocated. React accordingly. */
}
6.1.3.3 修改全局内存对象
修改全局内存对象使用GlobalReAlloc函数,它和LocalReAlloc函数很类似,这里不再赘述。修改全局内存对象的特殊之处在于巨型对象的修改上,这一点我们将在后面讲述。
6.1.3.4 内存释放及其它操作
全局内存对象使用GlobalFree函数和GlobalDiscard来释放与删除,其作用与LocalFree和LocalDiscard类似。GlobalSize函数可以检测内存对象大小;GlobalFlags函数用来检索对象是否可删除,是否已删除等信息;GlobalCompact函数可以检测全局堆可用内存大小。
6.1.3.5 巨型内存对象
如果全局内存对象的大小为64KB或更大,那它就是一个巨型内存对象,使用GlobalLock函数加锁巨型内存对象将返回一个巨型指针。分配一个128KB的巨型内存对象,使用下面的代码段:
HGLOBAL hGlobalObject;
char huge * hpGlobalObject;
if (hGlobalObject = GlobalAlloc(GMEM_MOVEABLE,
0x20000L)) {
if
(hpGlobalObject = (char huge *)GlobalLock(hGlobalObject)) {
/*
Use hpGlobalObject as the far address of the globally allocated object. */
...
GlobalUnlock
(hGlobalObject);
}
else
{
/*
The lock failed. React accordingly. */
}
}
else {
/*
The 128K cannot be allocated. React accordingly. */
}
巨型内存对象的修改有一点特殊性,当对象大小增加并超过64K的倍数时,Windows可能要为重新分配的内存对象返回一个新的全局句柄,因此,巨型内存对象的修改应采用下面的形式:
if (hTempHugeObject =
GlobalReAlloc(hHugeObject,0x20000L,GMEM_MOVEABLE)){
hHugeObject
= hTempObject;
}
else {
/*
The object could not be Reallocated. React accordingly. */
}
Windows采用段的概念来管理应用程序的内存,段有代码段和数据段两种,一个应用程序可有多个代码段和数据段。代码段和数据段的数量决定了应用程序的内存模式,图6.2说明了内存模式与应用程序代码段和数据段的关系。
|
|
代码段数
|
|
单段
|
多段
|
|
数据段数
|
单段
|
小内存模式
|
中内存模式
|
|
多段
|
压缩内存模式
|
大内存模式
|
图6.2 内存模式图
段的管理和全局内存对象的管理很类似,段可以是固定的,可移动的和可删除的,其属性在应用程序的模块定义文件中指定。段在全局内存中分配空间,Windows鼓励使用可移动的代码段和数据段,这样可以提高其内存利用效率。使用可删除的代码段可以进一步减小应用程序对内存的影响,如果代码段是可删除的,在必要时Windows将其删除以满足对全局内存的请求。被删除的段由Windows监控,当应用程序利用该代码段时,Windows自动地将它们重新装入。
6.1.4.1 代码段
代码段是不超过64K字节的机器指令,它代表全部或部分应用程序指令。代码段中的数据是只读的,对代码段执行写操作将引起通用保护(GP)错误。
每个应用程序都至少有一个代码段,例如我们前面几章的例子都只有一个代码段。用户也可以生成有多个代码段的应用。实际上,多数Windows应用程序都有多个代码段。通过使用多代码段,用户可以把任何给定代码段的大小减少到完成某些任务所必须的几条指令。这样,可通过使某些段可删除,来优化应用程序对内存的使用。
中模式和大模式的应用程序都使用多代码段,这些应用程序的每一个段都有一个或几个源文件。对于多个源文件,将它们分开各自编译,为编译过的代码所属的每个段命名,然后连接。段的属性在模块定义文件中定义,Windows使用SEGMENTS语句来完成此任务,如下面的代码定义了四个段的属性:
SEGMENTS
MEMORY_MAIN PRELOAD MOVEABLE
MEMORY_INIT LOADONCALL
MOVEABLE DISCARDABLE
MEMORY_WNDPROC PRELOAD MOVEABLE
MEMORY_ABOUT LOADONCALL
MOVEABLE DISCARDABLE
用户也可以在模块定义文件中用CODE语句为所有未显式定义过的代码段定义缺省属性。例如,要将未列在SEGMENTS语句中的所有段定义为可删除的,可用下面的语句:
CODE MOVEABLE DISCARDABLE。
6.1.4.2 数据段
每个应用程序都有一个数据段,数据段包含应用程序的堆栈、局部堆、静态数据和全局数据。一个数据段的长度也不能超过64K。数据段可以是固定的或可移动的,但不能是可删除的。如果数据段是可移动的,Windows在将控制转向应用程序前自动为其加锁,当应用程序分配全局内存,或试图在局部堆中分配超过当前可分的内存时,可移动数据段可能被移动,因此在数据段中不要保留指向变量的长指针,当数据段移动时,此长指针将失效。
在模块定义文件中用DATA语句定义数据段的属性,属性的缺省值为MOVEABLE和MULTIPLE。MULTIPLE属性使Windows为应用程序的每一个实例拷贝一个应用程序数据段,这就是说每个应用程序实例中数据段的内容都是不同的。
6.1.5 内存管理程序示例Memory
应用程序Memory示例了部分内存管理,它是一个使用了可删除代码段的中模式Windows应用程序。Memory程序有四个C语言源程序,在模块定义文件中显示定义了四个代码段,相应地模块定义文件和makefile文件有地些修改,读者可通过比较Memory程序和5.1.2节的例子来体会它们之间的不同。另外,读者在编译和连接应用程序Memory后,可用Visual C++提供的Windows Heap Walker
(HEAPWALK.EXE)来观察Memory运行时的各个段。
//模块1:MEMORY_MAIN
#include "windows.h"
#include "memory.h"
HANDLE hInst;
/****************************************************************************
MODULE: memory1.c
FUNCTION: WinMain(HANDLE, HANDLE, LPSTR, int)
PURPOSE: calls initialization function, processes message loop
****************************************************************************/
int PASCAL WinMain(hInstance, hPrevInstance,
lpCmdLine, nCmdShow)
HANDLE hInstance;
HANDLE hPrevInstance;
LPSTR lpCmdLine;
int nCmdShow;
{
MSG msg;
if (!hPrevInstance)
if
(!InitApplication(hInstance))
return (FALSE);
if (!InitInstance(hInstance, nCmdShow))
return (FALSE);
while (GetMessage(&msg, NULL, NULL, NULL)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (msg.wParam);
}
//模块2:MEMORY_INIT
#include "windows.h"
#include "memory.h"
/****************************************************************************
MODULE: memory2.c
FUNCTION: InitApplication(HANDLE)
PURPOSE: Initializes window data and registers window class
****************************************************************************/
BOOL InitApplication(hInstance)
HANDLE hInstance;
{
WNDCLASS wc;
wc.style = NULL;
wc.lpfnWndProc = MainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = COLOR_WINDOW+1;
wc.lpszMenuName =
"MemoryMenu";
wc.lpszClassName = "MemoryWClass";
return (RegisterClass(&wc));
}
/****************************************************************************
MODULE: memory2.c
FUNCTION:
InitInstance(HANDLE, int)
PURPOSE: Saves instance
handle and creates main window
****************************************************************************/
BOOL InitInstance(hInstance, nCmdShow)
HANDLE hInstance;
int
nCmdShow;
{
HWND
hWnd;
hInst = hInstance;
hWnd = CreateWindow("MemoryWClass", "Memory Sample
Application",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL );
if
(!hWnd)
return (FALSE);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return (TRUE);
}
//模块3:MEMORY_WNDPROC
#include "windows.h"
#include "memory.h"
/****************************************************************************
MODULE: memory3.c
FUNCTION: MainWndProc(HWND, UINT, WPARAM, LPARAM)
PURPOSE: Processes messages
MESSAGES:
WM_COMMAND - application menu (About
dialog box)
WM_DESTROY - destroy window
****************************************************************************/
long FAR PASCAL __export MainWndProc(hWnd,
message, wParam, lParam)
HWND hWnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
{
FARPROC lpProcAbout;
switch (message) {
case
WM_COMMAND:
if (wParam == IDM_ABOUT) {
lpProcAbout
= MakeProcInstance(About, hInst);
DialogBox(hInst,
"AboutBox", hWnd, lpProcAbout);
FreeProcInstance(lpProcAbout);
break;
}
else
return
(DefWindowProc(hWnd, message, wParam, lParam));
case
WM_DESTROY:
PostQuitMessage(0);
break;
default:
return (DefWindowProc(hWnd,
message, wParam, lParam));
}
return (NULL);
}
//模块4:MEMORY_ABOUT
#include "windows.h"
#include "memory.h"
/****************************************************************************
MODULE: memory4.c
FUNCTION: About(HWND, unsigned, WORD, LONG)
PURPOSE: Processes messages
for "About" dialog box
MESSAGES:
WM_INITDIALOG
- initialize dialog box
WM_COMMAND - Input received
****************************************************************************/
BOOL FAR PASCAL __export About(hDlg, message,
wParam, lParam)
HWND hDlg;
unsigned message;
WORD wParam;
LONG lParam;
{
switch (message) {
case
WM_INITDIALOG:
return (TRUE);
case
WM_COMMAND:
if (wParam == IDOK || wParam == IDCANCEL) {
EndDialog(hDlg,
TRUE);
return
(TRUE);
}
break;
}
return (FALSE);
}
下面是模块定义文件中的一小段,它在编译每个模块时,使用/NT选项为每个段进行命名。
MEMORY1.OBJ: MEMORY1.C
$(MEMORY1_DEP)
$(CC)
$(CFLAGS) $(CCREATEPCHFLAG) /c /NT MEMORY_MAIN MEMORY1.C
MEMORY2.OBJ: MEMORY2.C
$(MEMORY2_DEP)
$(CC)
$(CFLAGS) $(CUSEPCHFLAG) /c /NT MEMORY_INIT MEMORY2.C
MEMORY3.OBJ: MEMORY3.C
$(MEMORY3_DEP)
$(CC)
$(CFLAGS) $(CUSEPCHFLAG) /c /NT MEMORY_WNDPROC MEMORY3.C
MEMORY4.OBJ: MEMORY4.C
$(MEMORY4_DEP)
$(CC)
$(CFLAGS) $(CUSEPCHFLAG) /c /NT MEMORY_ABOUT MEMORY4.C