第15章 在应用程序中使用虚拟内存(2)

15.6 改变保护属性

(1)VritualProtect函数

参数

描述

PVOID pvAddress

指向要修改属性的内存基地址

SIZE_T dwSize

区域的大小,以字节为单位

DWORD flNewProtect

PAGE_*(除PAGE_WRITECOPY、PAGE_EXCUTE_WRITECOPY外)

PDWORD pflOldProtect

返回原来的保护属性,有时虽然不需要返回这个信息,但必须传入一个有效的pflOldProtect参数

(2)注意点

  ①保护属性是与整个物理存储页相关联的,不能给一个字节指定保护属性。

  ②当若干连续的物理存储页跨越不同区域时,则VirtualProtect不能改变它们的保护属性。如果有相邻区域,又想改变跨区域的连续页面的保护属性,则必须多次调用该函数。

【VirtualProtect程序】

#include <windows.h>
#include <tchar.h>
#include <time.h>
#include <locale.h>

#define MEMSIZE   (1024*1024)

int _tmain(){
    _tsetlocale(LC_ALL, _T("chs"));
    srand((unsigned)time(NULL));

    //1.保留并提交内存(1MB)
    VOID* pRecv = VirtualAlloc(NULL, MEMSIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    
    if (pRecv !=NULL){
        _tprintf(_T("分配%dKB内存成功!
"), MEMSIZE / 1024);
    } else{
        _tprintf(_T("申请%dKB内存失败!(错误代码:%d)
"), MEMSIZE / 1024,GetLastError());
        return 0;
    }
    
    //2.内存写入操作
    float* pfArray = (float*)pRecv;
    for (int i = 0; i < MEMSIZE / sizeof(float); i++){
        pfArray[i] = 1.0f*(rand() % 10);
    }
    _tprintf(_T("从[0x%X]处开始,成功写入%d个Float型的数据!
"), pRecv,MEMSIZE / sizeof(float));
//3.更改保护属性为只读 DWORD dwOldProtect = 0; BOOL bOk = VirtualProtect(pRecv, MEMSIZE, PAGE_READONLY, &dwOldProtect); if (bOk){ _tprintf(_T("成功修改申请的内存空间为只读属性! ")); } else{ _tprintf(_T("试图修改申请的内存空间为只读属性失败!(错误代码:%d) "),GetLastError()); return 0; }
//4.读取所有值进行求和 float fSum = 0.0f; for (int i = 0; i < MEMSIZE / sizeof(float);i++){ fSum += pfArray[i]; } _tprintf(_T("%d个随机数总和为%f "), MEMSIZE / sizeof(float),fSum); //5.试图写入第10个元素,这将引起异常 __try{ pfArray[9] = 0.0f; }__except (EXCEPTION_EXECUTE_HANDLER){ _tprintf(_T("非法访问内存,试图在只读内存中写入数据! ")); } //6.直接释放 bOk = VirtualFree(pRecv, 0, MEM_RELEASE); _tprintf(_T("释放内存%s! "),bOk? _T("成功"):_T("失败")); return 0; }

 15.7 重置物理存储器的内容

(1)进程页表中的项(页表项,PTE)结构

 

(2)MEM_RESET标志:当内存中某页面内容被修改时,该页面的“脏”位置1,表示“己修改”。以后如果要从exe、DLL或页交换文件中载入新的页面到内存里。系统会在内存中查找可用的页面,如果找到的是己被修改过的页面,那么系统将把它们换出到页交换文件。但可以通过修改该页面的这个标志(即复位,即把“脏”位置0,表示没有修改过),此时该页面的内容将被当作垃圾而废弃,所以也就不会被写入页交换文件。这对一部分并不重要的数据来说,是可行的,而且这样做也有利于提高系统的性能。

(3)调用VirtualAlloc并传入MEM_RESET时,可能发生的两种情况

    ①要重置的页面还没被映射到物理内存中,这时系统将抛弃这些页面,当下次被映射到物理内存时,会使用新的、全部清零的内存页。

②重置页面己被映射进内存中,系统会将他们这些页面标志为没被修改过(即重置),从而这些页面的内容被当作垃圾,也就不会被写入页交换文件中。这些页面会在下次

(4)重置内存页面的注意事项

  ①当调用VirtualAlloc预订或提交时基地址能常会被向下取整到页面大小整数倍(即向地址小的方向)。大小则会被向上取整到页面大小的整数倍(即大的方向)。但重置存储器时,VirtualAlloc会从相反的方向进行取整!其目的是确保基地址之前的同一页面还有其他重要数据的情况下,不会被抛弃。同理大小向下取整也是出于同样的目的

  ②MEM_RESET只能单独使用,不能将其与其他标志按位或起来。

  ③传入MEM_RESET时,需要传一个有效的保护属性(如PAGE_READWITE),即使实际上并没有用到这个值。

【MemReset程序】重置存储器示例程序

   

//MemReset.cpp

/************************************************************************
Module: MemReset.cpp
Notices:Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
************************************************************************/

#include "../../CommonFiles/CmnHdr.h"
#include <tchar.h>

//////////////////////////////////////////////////////////////////////////
int WINAPI  _tWinMain(HINSTANCE, HINSTANCE, PTSTR, int){
    TCHAR szAppName[] = TEXT("MEM_RESET 测试");
    TCHAR szTestData[] = TEXT("Some text data");

    //提交一页并修改他的内容;1024会被向上取整到一个页面的大小(4KB)
    PTSTR pszData = (PTSTR)VirtualAlloc(NULL, 1024, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    _tcscpy_s(pszData, 1024, szTestData);

    //如果不访问数据,就把pszData里的当成垃圾看待
    if (IDNO ==MessageBox(NULL,TEXT("是否要保存数据,以便稍后来访问呢?"),szAppName,MB_YESNO)){
        //告诉系统将pszData所指空间当作垃圾,这样该内存中的数据就不会被写入页交换文件中

        //注意:传入MEM_RESET给VVirtualAlloc函数,该函数会将基地址和大小设置到一个安全的
        //范围,比如:
        //    VirtualAlloc(pvData,5000,MEM_RESET,PAGE_READWRITE);
        //当CPU的分配粒度为4KB时,将被重置1个页面。如果大于4KB时,被重置0个页面。
        MEMORY_BASIC_INFORMATION mbi;
        //以下调用总是会成功,先获取区域大小并将让重置的区域大小等于该值。(4KB的整数倍)
        VirtualQuery(pszData, &mbi, sizeof(mbi));
        VirtualAlloc(pszData, mbi.RegionSize, MEM_RESET, PAGE_READWRITE); 
    }

    //为了演示页面被重置过,这里可以给系统内存增加一些压力,如
    //提交跟整个物理内存大小一样大的区域(注意,虽然是预订和提交这么大的
    //地址空间,但系统并不会真正为其分配物理内存,只是提交到页交换文件中
    MEMORYSTATUS  mst;
    GlobalMemoryStatus(&mst);
    SIZE_T dwSize = mst.dwTotalPhys>mst.dwTotalVirtual ? mst.dwAvailPhys: mst.dwTotalPhys;

    PVOID pvDummy = VirtualAlloc(NULL, dwSize,
                                 MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    //在刚申请的整个空间中写入数据,这会给系统的内存造成很大的压力,
    //并导致原来内存中的一些的页面被写入到页交换文件(被修改过的页面,如pszData
    //所指的页面,当然如果后来又有被重置的话,是不会被写入的)
    if (pvDummy == NULL){
        MessageBox(NULL, TEXT("试图提交所有可用物理内存大小的空间失败!"), szAppName, MB_OK);
        VirtualFree(pszData, 0, MEM_RELEASE);
        return 0;
    }
    
    ZeroMemory(pvDummy, dwSize); //因写入整个物理内存,这些导致
                                  //原来的pszData页面被换出。

    //比较原始的数据,与当前pszData里数据是否相同
    if (_tcscmp(pszData, szTestData) == 0)
    {
        //pszData的数据与原始数据一样,因为ZeroMemory会迫使页面被写入页交换文件
        MessageBox(NULL, TEXT("修改的数据己被保存!"), szAppName, MB_OK);
    } else{
        //pszData的数据与原始数据不同,ZeroMemory并没有引起我们的页面被写入页交换文件
        MessageBox(NULL, TEXT("修改的数据未被保存!"), szAppName, MB_OK);
    }

    //释放地址空间
    VirtualFree(pvDummy, 0, MEM_RELEASE);
    VirtualFree(pszData, 0, MEM_RELEASE);
    return 0;
}

//resouce.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 15_MemReset.rc 使用
//
#define IDI_MEMRESET                       101

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1001
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

//MemReset.rc

// Microsoft Visual C++ generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// 中文(简体,中国) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE 
BEGIN
    "resource.h"
END

2 TEXTINCLUDE 
BEGIN
    "#include ""winres.h""
"
    ""
END

3 TEXTINCLUDE 
BEGIN
    "
"
    ""
END

#endif    // APSTUDIO_INVOKED


/////////////////////////////////////////////////////////////////////////////
//
// Icon
//

// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_MEMRESET               ICON                    "MemReset.ico"
#endif    // 中文(简体,中国) resources
/////////////////////////////////////////////////////////////////////////////



#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//


/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED

15.8 地址窗口扩展(Address Windowing Extension,AWE)

(1)AWE的作用

  ①允许应用程序以一种特殊的方式分配内存,操作系统保证不会将以这种方式分配的内存换出到磁盘上

  ②允许应用程序访问比进程地址空间还要多的内存

  ③使用AWE时,所有物理页面的交换控制(如映射)由应用程序自己控制,但要注意AWE操作需要“锁定内存页”权限。

(2)使用AWE的基本步骤

  ①在进址的地址空间中“开窗”(VirtualAlloc+MEM_PHYSICAL)

  ②申请分配物理内存(AllocateUserPhysicalPages):会拿出物理页的编号(放数组中)

  ③将物理内存映射到“窗口”中(MapUserPhysicalPages)

  ④通过“窗口”读写内存

  ⑤释放物理内存页面(FreeUserPhysicalPages)

  ⑥清理预订的地址空间:VirtualFree,并传入MEM_RELEASE标志。

(3)相关函数介绍

  ①“开窗”操作:VirtualAlloc(NULL,ulRAMBytes,MEM_RESERVE|MEM_PHYSICAL,PAGE_READWRITE);

  【注意】MEM_PHYSICAL表示该区域最终会以物理内存作为后备,AWE要求所有映射到地址窗口的存储器必须是可读写的,即PAGE_READWRITE是传给VirtualAlloc的唯一有效保护属性,且不能用VirtualProtect函数来改变保护属性。

  ②AllocateUserPhysicalPages函数:申请物理内存

参数

描述

HANDLE hProcess

进程句柄,注意只能是当前进程,不会跨进程使用!

PULONG_PTR pulRAMPages

传入时,表示请求分配的内存页数量,传出时表示实际分配到的页面数量。

PULONG_PTR aRAMPages

每个内存页的页框号保存在该指针指向的数组中。这些页框号是供系统使用的,我们的应用程序不用关心这些数值本身。

  ③MapUserPhysicalPages函数:把内存块指定给地址窗口

参数

描述

PVOID pvAddressWindow

要将内存块映射到哪个地址窗口

ULONG_PTR ulRAMPages

通过该地址窗口能看到多少个页面的内存

PULONG_PTR aRAMPages

通过该地址窗口能看到哪些页面的内存

注意:A.当给aRAMPages传入NULL时,用来撤消映射。一旦映射成功,就可以通过这个地址窗口来读写真正的物理内存了。

  ④FreeUserPhysicalPages:释放之前申请的物理内存.

    FreeUserPhysicalPages(hProcess,pulRAMPages,aRAMPages);

    【注意】:

  A.第2个参数表示要释放多少个页面。第3个参数表示要释放哪些页面的页框号

  B.如果内存块己经被映射到地址窗口,那么系统会取消映射并释放内存块

【AWE程序】演示“地址窗口扩展”的应用——该程序需要“锁定内存页”的权限

//AWE.cpp

/************************************************************************
Module: AWE.cpp
Notices:Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
************************************************************************/
#include "../../CommonFiles/CmnHdr.h"
#include "AddrWindow.h"
#include "resource.h"
#include <tchar.h>
#include <strsafe.h>

//////////////////////////////////////////////////////////////////////////
CAddrWindow  g_aw[2]; //2个内存地址窗口
CAddrWindowStorage g_aws[2]; //2个内存块
const ULONG_PTR g_nChars = 1024; //1024个字符缓冲区
const DWORD g_cbBufferSize = g_nChars*sizeof(TCHAR);

//////////////////////////////////////////////////////////////////////////
BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam){
    chSETDLGICONS(hwnd, IDI_AWE);

    //创建2个地址窗口
    chVERIFY(g_aw[0].Create(g_cbBufferSize));
    chVERIFY(g_aw[1].Create(g_cbBufferSize));

    //创建2个内存块
    if (!g_aws[0].Allocate(g_cbBufferSize)){
        chFAIL("分配RAM失败!
可能的原因:当前用户没有“锁定内存页”的权限");
    }
    chVERIFY(g_aws[1].Allocate(g_cbBufferSize));

    //在第1个内存块中放入一些默认的文本信息
    g_aws[0].MapStorage(g_aw[0]);
    _tcscpy_s((PTSTR)(PVOID)g_aw[0], g_cbBufferSize, TEXT("内存块0中的文本!"));

    //在第2个内存块中放入一些默认的文本信息
    g_aws[1].MapStorage(g_aw[0]);
    _tcscpy_s((PTSTR)(PVOID)g_aw[0], g_cbBufferSize, TEXT("内存块1中的文本!"));

    //填写列表框控件
    for (int n = 0; n <= 1;n++){
        int id = ((n == 0) ? IDC_WINDOW0STORAGE : IDC_WINDOW1STORAGE);
        HWND hWndCB = GetDlgItem(hwnd, id);
        ComboBox_AddString(hWndCB, TEXT("无内存块"));
        ComboBox_AddString(hWndCB, TEXT("内存块 0"));
        ComboBox_AddString(hWndCB, TEXT("内存块 1"));
        
        //地址窗口0显示内存块0,窗口1显示内存块1
        ComboBox_SetCurSel(hWndCB, n + 1);
        FORWARD_WM_COMMAND(hwnd, id, hWndCB, CBN_SELCHANGE, SendMessage);
        Edit_LimitText(GetDlgItem(hwnd, 
            (n==0)?IDC_WINDOW0TEXT:IDC_WINDOW1TEXT),g_nChars);
    }

    return TRUE;
}
//////////////////////////////////////////////////////////////////////////
void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtrl, UINT codeNotity){
    switch (id)
    {
    case IDCANCEL:
        EndDialog(hwnd, id);
        break;

    case IDC_WINDOW0STORAGE:
    case IDC_WINDOW1STORAGE:
        if (codeNotity == CBN_SELCHANGE){
            //显示地址窗口所指定的内存块内容
            int nWindow = ((id == IDC_WINDOW0STORAGE) ? 0 : 1);
            int nStorage = ComboBox_GetCurSel(hwndCtrl) - 1;
            if (nStorage == -1){ //撤消地址窗口的映射
                chVERIFY(g_aw[nWindow].UnmapStorage());//
            } else{
                if (!g_aws[nStorage].MapStorage(g_aw[nWindow])){
                    //无法将指定的内存中映射到地址窗口
                    ComboBox_SetCurSel(hwndCtrl, 0);//强制为“无内存”
                    chMB("该内存块只能被映射一次!");
                }
            }
            //更新地址窗口的显示文本
            HWND hwndText = GetDlgItem(hwnd, 
                        ((nWindow == 0)?IDC_WINDOW0TEXT:IDC_WINDOW1TEXT));
            MEMORY_BASIC_INFORMATION mbi;
            VirtualQuery(g_aw[nWindow], &mbi, sizeof(mbi));
            //注意:如果地址窗口没被映射,则mbi.State == MEM_RESERVE
            EnableWindow(hwndText, (mbi.State == MEM_COMMIT));
            Edit_SetText(hwndText,
                    IsWindowEnabled(hwndText) ?(PCTSTR)(PVOID)g_aw[nWindow]:TEXT("无内存块"));
        }
        break;

    case IDC_WINDOW0TEXT:
    case IDC_WINDOW1TEXT:
        if (codeNotity == EN_CHANGE){
            //更新地址窗口指定的内存块的内容
            int nWindow = ((id == IDC_WINDOW0TEXT) ? 0 : 1);
            //注意,是通过地址窗口来访问相应的内存块的
            Edit_GetText(hwndCtrl, (PTSTR)(PVOID)g_aw[nWindow], g_nChars);
        }
        break;
    }
}

//////////////////////////////////////////////////////////////////////////
INT_PTR  WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
    switch (uMsg){
        chHANDLE_DLGMSG(hWnd, WM_INITDIALOG, Dlg_OnInitDialog);
        chHANDLE_DLGMSG(hWnd, WM_COMMAND, Dlg_OnCommand);
    }
    return FALSE;
}

//////////////////////////////////////////////////////////////////////////
int WINAPI _tWinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPTSTR lpCmdLine,int nShowCmd){
    DialogBox(hInstance, MAKEINTRESOURCE(IDD_AWE), NULL, Dlg_Proc);
    return 0;
}

//AddrWindow.h

/************************************************************************
Module:  AddrWindow.h
Notices: Copyright(c) 2008 Jeffrey Richter & Christophe Nasrre
************************************************************************/
#pragma  once

//////////////////////////////////////////////////////////////////////////
#include "../../CommonFiles/CmnHdr.h"
#include <tchar.h>

//////////////////////////////////////////////////////////////////////////
//CSystemInfo类:对GetSystemInfo函数的简单封装,要在后面两个类中用到
class CSystemInfo :public SYSTEM_INFO{
public:
    CSystemInfo(){ GetSystemInfo(this);}
};

//////////////////////////////////////////////////////////////////////////
//CAddrWindow类:封装了地址窗口
class CAddrWindow{
public:
    CAddrWindow();
    ~CAddrWindow();

    //预订(保留)地址窗口
    BOOL Create(SIZE_T dwBytes, PVOID pvPreferredWindowBase = NULL);

    //销毁地址窗口
    BOOL  Destroy();

    //撤消映射
    BOOL UnmapStorage();

    //通过operator 重载隐式转换,返回地址窗口的虚拟地址
    //类型转换操作符(type conversion operator)是一种特殊的类成员函数,
    //它定义将类类型值转变为其他类型值的转换。转换操作符在类定义体内声明,
    //在保留字 operator 之后跟着转换的目标类型(即PVOID)
    operator PVOID();
private:
    PVOID m_pvWindow; //地址窗口区域的虚拟地址
    static CSystemInfo sm_sinf;
};
//////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////
//CAddrWindowStorage:对内存块进行封装
class CAddrWindowStorage{
public:
    CAddrWindowStorage();
    ~CAddrWindowStorage();
    
    //Allocate:分配内存(须有锁定面用户权限)
    BOOL Allocate(ULONG_PTR ulBytes);

    //Free:释放内存块
    BOOL Free();

    //HowManyPagesAllocated:返回己成功分配的页面数量
    ULONG_PTR HowManyPagesAllocated();

    //MapStorage:把内存块映射到一个地址窗口对象(CAddrWindow)
    BOOL MapStorage(CAddrWindow& aw);

    //UnmapStorage:撤消映射
    BOOL UnmapStorage(CAddrWindow& aw);

private:
    static BOOL EnablePrivilege(PCTSTR pszPrivName, BOOL bEnable = TRUE);

private:
    ULONG_PTR  m_ulPages;   //物理页面数
    PULONG_PTR m_pulUserPfnArray; //数组,用来接收分配到的物理页的帧号
    static CSystemInfo sm_sinf;
};

//////////////////////////////// End of File //////////////////////////////////

//AddrWindow.cpp

/************************************************************************
Module:  AddrWindow.cpp
Notices: Copyright(c) 2008 Jeffrey Richter & Christophe Nasrre
************************************************************************/
#pragma  once

//////////////////////////////////////////////////////////////////////////
#include "AddrWindow.h"

//////////////////////////////////////////////////////////////////////////
//CAddrWindow类:封装了地址窗口
CAddrWindow::CAddrWindow(){ m_pvWindow = NULL; }

CAddrWindow::~CAddrWindow(){ Destroy(); }

//预订(保留)地址窗口
BOOL CAddrWindow::Create(SIZE_T dwBytes, PVOID pvPreferredWindowBase){
    //预订地址窗口区域
    m_pvWindow = VirtualAlloc(pvPreferredWindowBase, dwBytes,
                              MEM_RESERVE | MEM_PHYSICAL, PAGE_READWRITE);
    return (m_pvWindow != NULL);
}

//销毁地址窗口
BOOL  CAddrWindow::Destroy(){
    BOOL bOk = TRUE;
    if (m_pvWindow != NULL){
        //销毁地址窗口区域
        bOk = VirtualFree(m_pvWindow, 0, MEM_RELEASE);
        m_pvWindow = NULL;
    }
    return bOk;
}

//撤消映射
BOOL  CAddrWindow::UnmapStorage(){
    MEMORY_BASIC_INFORMATION mbi;
    VirtualQuery(m_pvWindow, &mbi, sizeof(mbi));

    return (MapUserPhysicalPages(m_pvWindow,
        mbi.RegionSize / sm_sinf.dwPageSize, NULL));
}

//通过operator 重载隐式转换,返回地址窗口的虚拟地址
//类型转换操作符(type conversion operator)是一种特殊的类成员函数,
//它定义将类类型值转变为其他类型值的转换。转换操作符在类定义体内声明,
//在保留字 operator 之后跟着转换的目标类型(即PVOID)
CAddrWindow::operator PVOID(){ return (m_pvWindow); }

//////////////////////////////////////////////////////////////////////////

CSystemInfo  CAddrWindow::sm_sinf; //给静态变量初始化

//////////////////////////////////////////////////////////////////////////
CAddrWindowStorage::CAddrWindowStorage(){ m_ulPages = 0; m_pulUserPfnArray = NULL; }
CAddrWindowStorage::~CAddrWindowStorage(){ Free(); }

//Allocate:分配内存(须有锁定面用户权限)
BOOL CAddrWindowStorage::Allocate(ULONG_PTR ulBytes){
    Free(); //清除己经存在的地址窗口

    //计算指定的空间大小需要多少个物理页面
    m_ulPages = (ulBytes + sm_sinf.dwPageSize - 1) / sm_sinf.dwPageSize;

    //分配存放物理页编号的数组空间
    m_pulUserPfnArray = (PULONG_PTR)HeapAlloc(GetProcessHeap(), 0,
                                              m_ulPages*sizeof(ULONG_PTR));

    BOOL bOk = (m_pulUserPfnArray != NULL);
    if (bOk){
        //“内存锁定页面”权限提升
        EnablePrivilege(SE_LOCK_MEMORY_NAME, TRUE);
        bOk = AllocateUserPhysicalPages(GetCurrentProcess(),
                                        &m_ulPages, m_pulUserPfnArray);
        EnablePrivilege(SE_LOCK_MEMORY_NAME, FALSE);
    }
    return bOk;
}

//Free:释放内存块
BOOL CAddrWindowStorage::Free(){
    BOOL bOk = TRUE;
    if (m_pulUserPfnArray != NULL){
        bOk = FreeUserPhysicalPages(GetCurrentProcess(),
                                    &m_ulPages, m_pulUserPfnArray);
        if (bOk){
            //释放存放物理页编号的数组
            HeapFree(GetProcessHeap(), 0, m_pulUserPfnArray);
            m_ulPages = 0;
            m_pulUserPfnArray = NULL;
        }
    }
    return bOk;
}

//HowManyPagesAllocated:返回己成功分配的页面数量
ULONG_PTR  CAddrWindowStorage::HowManyPagesAllocated(){ return m_ulPages; }

//MapStorage:把内存块映射到一个地址窗口对象(CAddrWindow)
BOOL  CAddrWindowStorage::MapStorage(CAddrWindow& aw){
    return (MapUserPhysicalPages(aw, HowManyPagesAllocated(), m_pulUserPfnArray));
}

//UnmapStorage:撤消映射
BOOL  CAddrWindowStorage::UnmapStorage(CAddrWindow& aw){
    return (MapUserPhysicalPages(aw, HowManyPagesAllocated(), NULL));
}

BOOL CAddrWindowStorage::EnablePrivilege(PCTSTR pszPrivName, BOOL bEnable)
{
    BOOL bOk = FALSE; //假设函数调用失败
    HANDLE hToken;

    //尝试打开进程令牌
    if (OpenProcessToken(GetCurrentProcess(),
        TOKEN_ADJUST_PRIVILEGES, &hToken)){

        //启用“内存锁定页面”权限
        TOKEN_PRIVILEGES tp = { 1 };
        LookupPrivilegeValue(NULL, pszPrivName, &tp.Privileges[0].Luid);
        tp.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0;
        AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
        bOk = (GetLastError() == ERROR_SUCCESS);
        CloseHandle(hToken);
    }
    return bOk;
}
//////////////////////////////////////////////////////////////////////////
CSystemInfo CAddrWindowStorage::sm_sinf;
//////////////////////////////////////////////////////////////////////////

//////////////////////////////// End of File //////////////////////////////////

//resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 15_AWE.rc 使用
//
#define IDD_AWE                         101
#define IDI_AWE                         102
#define IDC_WINDOW0TEXT                 1006
#define IDC_WINDOW0STORAGE              1007
#define IDC_WINDOW1STORAGE              1008
#define IDC_WINDOW1TEXT                 1009

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        103
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1001
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

//AWE.rc

// Microsoft Visual C++ generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// 中文(简体,中国) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE 
BEGIN
    "resource.h"
END

2 TEXTINCLUDE 
BEGIN
    "#include ""winres.h""
"
    ""
END

3 TEXTINCLUDE 
BEGIN
    "
"
    ""
END

#endif    // APSTUDIO_INVOKED


/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//

IDD_AWE DIALOGEX 0, 0, 279, 44
STYLE DS_SETFONT | DS_SETFOREGROUND | DS_3DLOOK | DS_CENTER | WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "地址窗口扩展(Address Windowing Extensions)"
FONT 10, "宋体", 400, 0, 0x0
BEGIN
    LTEXT           "窗口0:",IDC_STATIC,9,6,23,8
    COMBOBOX        IDC_WINDOW0STORAGE,35,4,80,58,CBS_DROPDOWNLIST | WS_TABSTOP
    EDITTEXT        IDC_WINDOW0TEXT,123,4,152,14,ES_AUTOHSCROLL
    LTEXT           "窗口1:",IDC_STATIC,9,28,23,8
    COMBOBOX        IDC_WINDOW1STORAGE,35,25,80,58,CBS_DROPDOWNLIST | WS_TABSTOP
    EDITTEXT        IDC_WINDOW1TEXT,123,25,152,14,ES_AUTOHSCROLL
END


/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//

#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
    IDD_AWE, DIALOG
    BEGIN
        LEFTMARGIN, 7
        TOPMARGIN, 7
    END
END
#endif    // APSTUDIO_INVOKED


/////////////////////////////////////////////////////////////////////////////
//
// Icon
//

// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_AWE                 ICON                    "AWE.ico"
#endif    // 中文(简体,中国) resources
/////////////////////////////////////////////////////////////////////////////



#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//


/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED
原文地址:https://www.cnblogs.com/5iedu/p/4859637.html