第17章 内存映射文件(2)_内存映射文件的3个主要用途

17.4 映射到内存的可执行文件和DLL

(1)EXE文件格式

节名

作用

.text

.exe和.dll文件的代码

.data

己经初始化的数据

.bss

未初始化的数据

.reloc

重定位表(装载进程的进程地址空间)

.rdata

运行期只读数据

.CRT

C运行期只读数据

.debug

调用试信

.xdata

异常处理表

.tls

线程本地化存储

.idata

输入文件名表

.edata

输出文件名表

.rsrc

资源表

.didata

延迟输入文件名表

(2)加载exe的过程

  ①先CreateProcess创建进程内核对象,为进程创建一个私有地址空间(页目录和页表)

  ②系统根据exe大小,在默认的基地址0x0040 0000上预订适当大小的区域(可以在链接程序时用/BASE 选项更改基地址,方法是在VC工程属性链接器高级上设置)。

  ③系统会对地址空间区域进行标注,表明该区域的后备物理存储器来自于磁盘上的exe文件,而并非来自系统的页交换文件。

  ④读取exe文件的.idata节,此节列出exe所用到的所有dll文件。然后和exe文件一样,将dll文件映射到进程空间中。如果无法映射到基地址,系统会重新定位。(详见后面的《加载Dll过程》

  ⑤ 把所有的exe文件和DLL文件都映射到进程的地址空间之后,系统开始执行exe文件的启动代码,将第一页代码加载到内存,然后更新页目和页表。将第一条指令的地址交给线程指令指针。当系统执行时,会发现代码没有在内存中,就会通过页面错误机制,将exe文件中的代码加载到内存中。

(2)加载DLL的过程

  系统通过LoadLibrary载入每个DLL,如果哪个DLL需要调用其他DLL,系统会同样地调用LoadLibrary来载入相应的DLL,其载入过程如下:

  ①预订一块足够大的地址空间来容纳DLL,并在默认的基地址(如0x10000000)预订该区域。(可以使用/BASE链接器开关来指定这个基地址)。与Windows系统的DLL都有不同的基地址,这样即使把它们载入到同一个地址空间,也不会发生重叠。

  ②如果系统无法在DLL文件指定的基地址处预订区域(可能是该区域被另一个DLL或EXE占用,或区域不够大),这时系统尝试在另一个地址来为DLL预订区域。但这时需要重定位,但重定位需要占用页交换文件中额外的存储空间,而且会增加加载DLL所需的时间。而如果DLL不包含重定位信息(使用链接器的/FIXED开关构建的DLL是不包含重定位信息的,这开关的好处是可以使DLL文件变得更小),那么将无法被载入。

  ③系统会对地址空间区域进行标注表明该区域的后备物理存储器来自磁盘上的DLL文件,而不是系统的页交换文件。如果由于Windows不能将DLL载入到指定的基地址而必须执行重定位的话,那么系统还会另外进行标注,表明DLL中有一部分物理存储器被映射到了页交换文件。

(3)第2次加载exe的过程

  ①建立进程、映射进程空间与第1次加载EXE是一样的,只是当系统发现这个EXE己经建立了内存映射文件对象时,它就直接映射到进程空间了。只是当系统分配物理页面时,根据节的保护属性赋予页面的保护属性,对于代码节赋予READ属性,全局变量节赋予COPY_ON_WRITE属性。

  ②不同的实例共享代码节和其他的节,当实例需要改变页面内容时,会拷贝页面内容到新的页面,更新页目和页表。

  ③对于不同进程实例需要共享的变量,EXE文件有一个默认的节,给这个节赋予SHARED属性,我们也可以创建自己的SHARED节(请后面相关的内容)

17.4.1 同一个可执行文件和DLL的多个实例不会共享静态数据

(1)多实例的启动

  ①如果一个应用程序己经运行,当创建该应用程序的新实例时,会根据己经创建的同一个映射文件,打开另一个内存映射视图。通过内存映射文件,同一个实例可以共享内存中的代码和数据。

  ②当第2个实例启动时,系统把包含应用程序代码和数据的虚拟内存页面映射到第2个实例的地址空间。(注意虚拟内存即为内存的一部分,被载入到虚拟内存中的数据或代码可理解为就是内存中的数据了,注意与进程的地址空间的区别

(2)“写时复制”机制——保证了多实例不会共享静态数据

 

  ①任何时候,当应用程序试图写入内存映射文件的时候,系统会截获这种尝试,接着发生“写时复制”,即为应用程序(如实例2)分配一块新的内存,然后复制“数据页面2”的内容到新的页面。并重新将“实例2”地址空间中的“数据页面2”重新映射到“新页面”

  ②这样,“实例1”和“实例2”地址空间中的“数据页面2”就分别被映射到虚拟内存中的不同页面,从而保护数据,使得数据不会被另一个实例修改。

17.4.2 在同一个可执行文件或DLL的多个实例间共享静态数据

(1)可执行文件的常用段以及段的属性

  ①常用的段:如.bss、.data、.text等,详细见前面的EXE文件格式”表格

  ②段的属性

属性

含义

READ

可以从该段读取数据

WRITE

可以向该段写入数据

EXECUTE

可以执行该段的内容

SHARED

该段的内容为多个实例所共享(本质上是关闭了写时复制机制)

(2)多实例共享数据的方法

  ①创建自己的“数据段”

#pragma data_seg(“MyShareName”)  // MyShareName为段的名字,可自定义

LONG g_lInstanceCount = 0;   //要共享的变量必须是经过初始化的

#pragma data_seg(); //告诉编译器停止把己初始化的变量放到MyShareName段中。

② 将自定义的“数据段”属性设为“共享”

 #pragma comment(linker, “/SECTION:MyShareName,RWS”) //读、写、共享

(3)allocate声明符——可将初始化或未初始化的数据放到指定的段中

//创建一个“共享段”,并将己初始化的数据放入其中
#pragma data_seg("Shared") 
int a = 0; //己初始化数据,在“Shared”中
int b = 0; //未初始化的数据,不在“Shared”段中
#pragma  data_seg() //让编译器停止将己初始化变量放入“Shared”段

//初始化,并将变量放入“Shared”段中
__declspec(allocate("Shared")) int  c = 0;

//将未始化变量放入“Shared”段中
__declspec(allocate("Shared")) int  d;

//己初始化,但不在“Shared”段中
int e = 0;

//未初始化,也不在“Shared”段中
int f;  

【AppInst程序】统计共有多少个应用程序的实例正在运行

 

//AppInst.cpp

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

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

//////////////////////////////////////////////////////////////////////////
//系统广播消息
UINT g_uMsgAppInstCountUpdate = WM_APP + 123;

//////////////////////////////////////////////////////////////////////////
//创建“共享段”
#pragma data_seg("Shared")
volatile LONG g_lApplicationInstances = 0; //正在运行的实例的个数
#pragma data_seg()

//告诉编译器,让Shared段只有可读、可读及共享属性
#pragma comment(linker,"/Section:Shared,RWS")


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

    //强制刷新,以显示正确的实例数。
    PostMessage(HWND_BROADCAST, g_uMsgAppInstCountUpdate, 0, 0);
    return TRUE;
}

//////////////////////////////////////////////////////////////////////////
void Dlg_OnCommand(HWND hWnd, int id, HWND hWndCtrl, UINT codeNotify){
    switch (id)
    {
    case IDCANCEL:
        EndDialog(hWnd, id);
        break;
    }
}

//////////////////////////////////////////////////////////////////////////
INT_PTR WINAPI Dlg_Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){

    if (uMsg == g_uMsgAppInstCountUpdate){
        SetDlgItemInt(hwnd, IDC_COUNT, g_lApplicationInstances, FALSE);
    }

    switch (uMsg)
    {
        chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Dlg_OnInitDialog);
        chHANDLE_DLGMSG(hwnd, WM_COMMAND, Dlg_OnCommand);
    }
    return FALSE;
}

//////////////////////////////////////////////////////////////////////////
int WINAPI _tWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, _In_ int nShowCmd)
{
    //注删用于广播的消息,并何存ID,该消息用来通知实例个数发生变化
    g_uMsgAppInstCountUpdate = RegisterWindowMessage(TEXT("MsgAppInstCountUpdate"));

    //新的应用程序实例正在运行
    InterlockedExchangeAdd(&g_lApplicationInstances, 1);

    DialogBox(hInstance, MAKEINTRESOURCE(IDD_APPINST), NULL, Dlg_Proc);

    //应用程序的结束运行
    InterlockedExchangeAdd(&g_lApplicationInstances, -1);

    //通知其他实例更新显示
    PostMessage(HWND_BROADCAST, g_uMsgAppInstCountUpdate, 0, 0);

    return 0;
}

//resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 17_AppInst.rc 使用
//
#define IDD_APPINST                     1
#define IDC_COUNT                       100
#define IDI_APPINST                     101
#define IDI_ICON1                       102

// 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

//AppInst.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_APPINST DIALOGEX 0, 0, 161, 21
STYLE DS_SETFONT | DS_CENTER | WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "多个实例的应用程序"
FONT 10, "宋体", 400, 0, 0x0
BEGIN
    LTEXT           "正在运行的实例数:",IDC_STATIC,25,6,93,8,SS_NOPREFIX
    RTEXT           "#",IDC_COUNT,113,6,16,12,SS_NOPREFIX
END


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

// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_APPINST             ICON                    "AppInst.Ico"

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

#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
    IDD_APPINST, DIALOG
    BEGIN
    END
END
#endif    // APSTUDIO_INVOKED

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



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


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

17.5 映射到内存的数据文件

17.5.1 用内存映射文件来处理大文件

(1)处理大文件的思路

  ①把文件头的部分映射到视图中,完成对文件的第1个视图的访问后

  ②撤消对前一部分的映射,然后把文件的另一部分映射到视图,重复此过程,直到完成对整个文件的访问。

【Count0程序】演示一个32位地址空间中使用8GB文件的例子,用来统计一个二进制文件中所有值为0的字节数。

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

__int64 Count0s(PCTSTR pFileName)
{
    //视图的地址必须是分配粒度的整数倍
    SYSTEM_INFO sinf;
    GetSystemInfo(&sinf);

    //打开数据文件
    HANDLE hFile = CreateFile(pFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
                              FILE_FLAG_SEQUENTIAL_SCAN, NULL);

    //创建文件映像
    HANDLE hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);

    DWORD dwFileSizeHigh;
    __int64 qwFileSize = GetFileSize(hFile, &dwFileSizeHigh);
    qwFileSize += ((__int64)dwFileSizeHigh<<32);

    CloseHandle(hFile);//不必再使用hFile句柄

    __int64 qwFileOffset = 0, qwNumOf0s = 0;

    while (qwFileSize > 0){

        //计算块的大小
        DWORD dwBytesInBlock = sinf.dwAllocationGranularity;
        if (qwFileSize < sinf.dwAllocationGranularity)
            dwBytesInBlock = (DWORD)qwFileSize;

        PBYTE pbFile = (PBYTE)MapViewOfFile(hFileMapping,
                            FILE_MAP_READ, //access mode
                            (DWORD)(qwFileOffset>>32), //high-order DWORD of Offset开始地址
                            (DWORD)(qwFileOffset & 0xFFFFFFFF),//low-order DWORD of offset
                            dwBytesInBlock);//number of bytes to map

        //计算块中0的数量
        for (DWORD dwByte = 0; dwByte < dwBytesInBlock;dwByte++){
            if (pbFile[dwByte]==0)
                qwNumOf0s++;
        }

        //撤消视图,在地址空间中并不需要多视图
        UnmapViewOfFile(pbFile);

        //跳到下一个块
        qwFileOffset += dwBytesInBlock;
        qwFileSize -= dwBytesInBlock;
    }

    CloseHandle(hFileMapping);
    return (qwNumOf0s);
}

int _tmain(){
    _tsetlocale(LC_ALL, _T("chs"));

    TCHAR szFileName[] = _T("aaa.mp4");
    __int64 qwNumOf0s = 0;
    qwNumOf0s = Count0s(szFileName);
    _tprintf(_T("%s文件的字节流中,“0”的个数共有%I64d个"),szFileName,qwNumOf0s);
    return 0;
}

17.5.2 内存映射文件和一致性

(1)同一个文件映射对象被映射成多个视图,则系统会确保各视图中的数据是一致的。即使是多个进程把同一个数据文件映射到多个视图中,数据也仍然会保持一致。(因为数据在每个页面在内存中只有一份,只是这些内存页被映射到多个进程的地址空间。

(2)同一数据文件创建多个文件映射对象,这些不同的文件映射对象的各个视图,系统并不保证数据是一致的系统只保证同一文件映射对象的多个视图间保持一致。

(3)只读文件不存在一致性问题,经常用于内存映射文件。

(4)跨网络共享可写文件时,系统无法保证数据视图的一致性。

17.5.3 给内存映射文件指定基地址

(1)MapViewOfFileEx函数:可以把文件映射到指定的地址(类似VirtualAlloc可在指定基地址预订地址空间)

参数

含义

HANDLE hFileMappingObject

这些参数的含义与MapViewOfFile函数一样

DWORD dwDesiredAccess

DWORD dwFileOffsetHigh

DWORD dwFileOffsetLow

SIZE_T dwNumerOfBytesToMap

PVOID pvBaseAddress

①为要映射的文件指定一个目标地址。该地址必须是分配粒度(64K)的整数倍。否则函数会返回NULL,表示有错误发生,GetLastError得到ERROR_MAPPED_ALIGNMENT。

②如果指定的NULL,则函数的行为与MapViewOfFile完全相同。

备注:①如果系统无法将文件映射至指定地址(如文件太大,导致与其他己预订的地址空间发生重叠,函数不会尝试去找另一个能够容纳文件的地址空间,而是返回NULL。

②指定的地址必须是在进程的用户模式分区中,否则函数返回NULL

(2)MapViewOfFileEx函数在跨进程共享数据的时候很有用。如共享链表时,每个元素的保存是下一个元素的内存地址。这时为防止这个内存地址在两个进程中指向的内容不同,可以通过内存映射文件,将映射到两个进程中的同一个基地址去。

17.5.4 内存映射文件的实现细节

(1)Win98下内存映射文件的细节

  ①Win98下视图总是被映射到0x80000000至0xBFFFFFFF范围内。注意,这范围的地址是被所有进程共享的。如果另一个进程也对同一个文件映射对象调用MapViewOfFile函数。则Windows会将第1个内存地址返回给第2个进程,即两个进程返回的内存地址是相同的。所以他们访问相同的数据,并且它们的视图具有相关性。

  ②当文件映射对象的视图被映射时,系统会为整个文件映射对象保留足够的地址空间,哪怕我们在MapViewOfFile函数中指定的只是其中的一小部分。因此,如果两个调用MapViewOfFile时,如果第1次映射的是整个文件,第2次映射的是文件从64KB处开始的位置,则两次调用时函数返回的地址会相差64KB。

(2)Win2000以上系统映射的细节

  ①不同进程对同一个文件映射对象调用MapViewOfFile返回的内存地址可能不同。文件映射对象是个内核对象,每次调用MapViewOfFile都会增加使用计数。

  ②如果某个进程两次调用MapViewOfFile,第1次映射整个文件,第2次从文件64KB偏移处开始映射,则首先两个文件映射大小是不同的,第一个区域的大小为整个文件映射对象的大小,第二个区域为文件映射对象大小减去64KB。其次,函数返回的地址相差也未必是64KB,因为每次调用MapViewOfFile时,文件被映射到不同的地址上去。

  ③尽管区域不同(包括大小和地址),但这两个视图是来自同一个文件映射对象,所以他们的数据仍然是相关的。

【TwoViews程序】演示两个MapViewOfFile得到的内存地址的不同(可以Win98与WinVista下做实验比较)

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

//TwoView
int TwoView(PCTSTR pszFileName){

    //打开一个己经存在的文件,但必须大于64KB
    HANDLE hFile = CreateFile(pszFileName, GENERIC_READ | GENERIC_WRITE,
                              0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    //创建一个文件映射对象
    HANDLE hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);

    //创建整个文件的视图
    PBYTE pbFile = (PBYTE)MapViewOfFile(hFileMapping, FILE_MAP_WRITE, 0, 0, 0);

    //创建视图2(从64KB偏移处开始)
    PBYTE pbFile2 = (PBYTE)MapViewOfFile(hFileMapping, FILE_MAP_WRITE, 0, 64 * 1024, 0);

    //文件大小
    DWORD dwFileSizeHigh;
    __int64 qwFileSize = GetFileSize(hFile, &dwFileSizeHigh);

    //在Win98下视图被映射到0x80000000至0xBFFFFFFF的范围内,这个视图被所有进程共享
    //两次MapViewOfFile时,指向同一个视图。所以,pbFile2-pbFile相差正好是64KB。
    //但Win2000以上,即使是两次相同的MapViewOfFile映射时,其返回内存地址也是不同的。
    //所以pbFile2-pbFile未必等于64KB。
    int iDifference = int(pbFile2 - pbFile);
    _tprintf(_T("指针pbFile=0x%08X,pbFile2=0x%08X,
两者相差=%d KB(%s64KB)
"), pbFile2, pbFile, iDifference / 1024,
             iDifference == 64 * 1024 ? TEXT("等于") : TEXT("不等于"));

    UnmapViewOfFile(pbFile2);
    UnmapViewOfFile(pbFile);
    CloseHandle(hFileMapping);
    CloseHandle(hFile);

    return 0;
}

int _tmain(){
    _tsetlocale(LC_ALL, _T("chs"));

    TCHAR szFileName[] = _T("aaa.mp4");

    TwoView(szFileName);
    return 0;
} 

17.7 以页交换文件为后备存储器的内存映射文件

(1)以页交换文件而不是磁盘文件来作为后备存储器。(因不必创建或打开磁盘文件,所以不需要调用CreateFile)。

(2)调用CreateFileMapping时将hFile设为INVALID_HANDLE_VALUE这等于告诉系统我们希望从页交换文件,而不是磁盘上的文件来作为文件映射对象的物理存储器)。所需的大小由dwMaximumSizeHigh和dwMaximumSizeLow参数决定。(注意,如果是以磁盘文件为后备存储器时,当CreateFile的返回值是INVLID_HANDLE_VALUE,系统则会创建一个以页交换文件为后备存储器的文件映射对象,这点一定要特别注意因此,我们建议要经常对CreateFile返回值进行检查!)

(3)将视图映射到进程的地址空间:MapViewOfFile。

(4)CloseHandle(hFileMapping)关闭文件映射对象,并从页交换文件中回收所有己调拨的存储器。

【MMFShare程序】演示如何用内存映射文件在多个进程中传输数据

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

#define MMF_NAME  TEXT("MMFSharedData")
//////////////////////////////////////////////////////////////////////////

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

    //初始化编辑框
    Edit_SetText(GetDlgItem(hwnd, IDC_DATA), TEXT("Some test data"));

    //禁用关闭按钮
    Button_Enable(GetDlgItem(hwnd, IDC_CLOSEFILE), FALSE);
    return TRUE;
}

//////////////////////////////////////////////////////////////////////////
void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify){
    
    static HANDLE s_hFileMap = NULL;//文件映射对象句柄

    switch (id)
    {
    case IDCANCEL:
        EndDialog(hwnd, id);
        break;

    case IDC_CREATEFILE:
        if (codeNotify != BN_CLICKED)
            break;

        //创建以页交换文件为后备存储器的文件映射对象
        s_hFileMap = CreateFileMapping(
                        INVALID_HANDLE_VALUE,//以页交换文件为后备存储器
                        NULL,//默认安全属性
                        PAGE_READWRITE, //可读可写
                        0,
                        4*1024, //所需的空间为4KB
                        MMF_NAME); //名称为:MMFSharedData


        if (s_hFileMap != NULL){
            if (GetLastError() == ERROR_ALREADY_EXISTS){
                chMB("文件映射对象己经存在!");
                CloseHandle(s_hFileMap);
            } else{
                //成功创建内存映射文件

                //视图映射到进程地址空间
                PVOID pView = MapViewOfFile(s_hFileMap,
                                            FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
                if (pView != NULL){
                    //将编辑框的数据写入MMF
                    Edit_GetText(GetDlgItem(hwnd, IDC_DATA), (PTSTR)pView, 4 * 1024);

                    //撤销映射
                    UnmapViewOfFile(pView);

                    //为防止用户多次单击该按钮,禁用这个按钮
                    Button_Enable(hwndCtl, FALSE);

                    //启用关闭文件按钮
                    Button_Enable(GetDlgItem(hwnd, IDC_CLOSEFILE), TRUE);
                }
            }

        } else {
            chMB("创建文件映射对象失败!");
        }
        break;

    case IDC_CLOSEFILE:
        if (codeNotify != BN_CLICKED)
            break;

        if (CloseHandle(s_hFileMap)){
            //将按钮还原成默认的状态
            Button_Enable(hwndCtl, FALSE); //关闭按钮
            Button_Enable(GetDlgItem(hwnd, IDC_CREATEFILE), TRUE); //创建按钮
        }
        break;

    case IDC_OPENFILE:
        if (codeNotify != BN_CLICKED)
            break;

        //判断是否存在一个名为“MMFSharedData”的文件映射对象
        HANDLE hFileMapT = OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE,
                                           FALSE, MMF_NAME);
        if (hFileMapT != NULL){
            //映射到进程的地址空间
            PVOID pView = MapViewOfFile(hFileMapT, 
                                        FILE_MAP_READ | FILE_MAP_WRITE,
                                        0, 0, 0);
            if (pView != NULL){
                //将MMF里的内容显示在编辑框中
                Edit_SetText(GetDlgItem(hwnd, IDC_DATA), (PTSTR)pView);
                UnmapViewOfFile(pView);
            } else{
                chMB("无法映射视图");
            }

        } else{
            chMB("无法打开文件映射对象");
        }
        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(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, _In_ int nShowCmd)
{
    DialogBox(hInstance, MAKEINTRESOURCE(IDD_MMFSHARE), NULL, Dlg_Proc);
    return 0;
}

//resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 17_MMFShare.rc 使用
//
#define IDD_MMFSHARE                    1
#define IDC_DATA                        100
#define IDC_CREATEFILE                  101
#define IDC_OPENFILE                    102
#define IDI_MMFSHARE                    102
#define IDC_CLOSEFILE                   103

// 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

//MMFShare.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_MMFSHARE            ICON                    "MMFShare.ico"

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

IDD_MMFSHARE DIALOGEX 38, 36, 186, 61
STYLE DS_SETFONT | DS_CENTER |WS_MINIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "共享数据(内存映射文件)"
FONT 10, "宋体", 400, 0, 0x0
BEGIN
    PUSHBUTTON      "&创建映射数据",IDC_CREATEFILE,4,4,84,14,WS_GROUP
    PUSHBUTTON      "关闭映射数据",IDC_CLOSEFILE,96,4,84,14
    LTEXT           "数据:",IDC_STATIC,4,26,24,8
    EDITTEXT        IDC_DATA,28,24,153,12
    PUSHBUTTON      "打开映射并获取数据",IDC_OPENFILE,40,44,104,14,WS_GROUP
END


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

#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
    IDD_MMFSHARE, DIALOG
    BEGIN
    END
END
#endif    // APSTUDIO_INVOKED

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



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


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