CreateFileMapping,MapViewOfFile,处理泄漏 C++

Posted

技术标签:

【中文标题】CreateFileMapping,MapViewOfFile,处理泄漏 C++【英文标题】:CreateFileMapping, MapViewOfFile, handle leaking c++ 【发布时间】:2015-06-15 01:18:52 【问题描述】:

背景:我正在尝试创建一个可由多个进程访问的内存映射文件。在下面的代码中,我只输入了与我目前必须使事情变得更简单的问题有关的代码。根据 msdn,我应该能够创建文件映射,映射文件视图并关闭从 CreateFileMapping 收到的句柄,并且 MapViewOfFile 将使我的 FileMap 保持活动状态。 FileMap 应该仍然可以访问,直到我 UnmapViewOfFile。

MSDN: CreateFileMapping function

文件映射对象的映射视图维护对该对象的内部引用,并且文件映射对象在对它的所有引用都被释放之前不会关闭。因此,要完全关闭文件映射对象,应用程序必须通过调用 UnmapViewOfFile 取消映射文件映射对象的所有映射视图,并通过调用 CloseHandle 关闭文件映射对象句柄。这些函数可以按任意顺序调用。

问题: 成功映射文件视图然后关闭 CreateFileMapping 接收到的句柄后,FileMap 不再存在(它应该仍然存在)并且我的 MemMapFileReader 能够创建一个新映射错误为 0。(当它应该收到错误 183 '已经存在')

错误的解决方案:不关闭句柄允许 MemMapFileReader 程序访问它,但会导致 MemMapFileCreator 中的句柄泄漏,因为在进程关闭之前,句柄永远不会关闭。

问题:我错过了什么或做错了什么?

MemMapFileCreator

#include "stdafx.h"


#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>
#include <iostream>
#define BUF_SIZE 256
TCHAR szName[] = TEXT("MyFileMappingObject");
TCHAR szMsg[] = TEXT("Message from first process.");

int _tmain()

HANDLE hMapFile;
LPCTSTR pBuf;

hMapFile = CreateFileMapping(
    INVALID_HANDLE_VALUE,    // use paging file
    NULL,                    // default security
    PAGE_READWRITE,          // read/write access
    0,                       // maximum object size (high-order DWORD)
    BUF_SIZE,                // maximum object size (low-order DWORD)
    szName);                 // name of mapping object

DWORD lastError = GetLastError();
if (hMapFile == NULL)

    _tprintf(TEXT("Could not create file mapping object (%d).\n"),
        GetLastError());
    std::cin.get();
    return 1;

pBuf = (LPTSTR)MapViewOfFile(hMapFile,   // handle to map object
    FILE_MAP_ALL_ACCESS, // read/write permission
    0,
    0,
    BUF_SIZE);

if (pBuf == NULL)

    _tprintf(TEXT("Could not map view of file (%d).\n"),
        GetLastError());

    CloseHandle(hMapFile);

    std::cin.get();
    return 1;



CopyMemory((PVOID)pBuf, szMsg, (_tcslen(szMsg) * sizeof(TCHAR)));

CloseHandle(hMapFile);

_getch();


UnmapViewOfFile(pBuf);
return 0;

MemMapFileReader

#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>
#include <iostream>
#pragma comment(lib, "user32.lib")

#define BUF_SIZE 256
TCHAR szName[] = TEXT("MyFileMappingObject");

int _tmain()

HANDLE hMapFile;
LPCTSTR pBuf;

hMapFile = CreateFileMapping(
    INVALID_HANDLE_VALUE,
    NULL,
    PAGE_READWRITE,   // read/write access
    0,
    BUF_SIZE,
    szName);               // name of mapping object
DWORD lastError = GetLastError();
if (hMapFile == NULL)

    _tprintf(TEXT("Could not open file mapping object (%d).\n"),
        GetLastError());
    std::cin.get();
    return 1;


pBuf = (LPTSTR)MapViewOfFile(hMapFile, // handle to map object
    FILE_MAP_ALL_ACCESS,  // read/write permission
    0,
    0,
    BUF_SIZE);

if (pBuf == NULL)

    _tprintf(TEXT("Could not map view of file (%d).\n"),
        GetLastError());

    CloseHandle(hMapFile);

    std::cin.get();
    return 1;


MessageBox(NULL, pBuf, TEXT("Process2"), MB_OK);

UnmapViewOfFile(pBuf);

CloseHandle(hMapFile);

std::cin.get();
return 0;

【问题讨论】:

能否引用或链接到您所指的 MSDN 部分? "然后关闭 CreateFileMapping 接收到的句柄,FileMap 不再存在(它应该仍然存在)" - 它仍然存在。但它的名字被毁了。关闭句柄后 - 您最初命名的部分变得未命名。结果,您无法按名称打开它。但部分仍然存在且功能齐全 【参考方案1】:

来自 MSDN:

文件映射对象的映射视图维护对该对象的内部引用,并且文件映射对象在对它的所有引用都被释放之前不会关闭。因此,要完全关闭文件映射对象,应用程序必须通过调用 UnmapViewOfFile 取消映射文件映射对象的所有映射视图,并通过调用 CloseHandle 关闭文件映射对象句柄。这些函数可以按任意顺序调用。

CreateFileMapping 文档说,要完全关闭文件,必须关闭所有句柄,顺序无关紧要。此逻辑不可逆:您不能关闭句柄并期望使用其他句柄,就好像文件映射没有“关闭”一样。

换句话说,这意味着要清理文件映射,您需要以任意顺序关闭所有句柄。但是,您不能关闭底层文件映射对象并仍然使用依赖它的视图。

【讨论】:

那么我想我可以假设 Compact 2013 的运行方式不同?在 Compact 2013 中,文件映射对象即使在关闭其句柄(如果其视图已被映射)后仍可访问,直到调用 unmapviewofile 实现细节当然会有所不同,但从文档中你不能依赖这种行为。 好点。如果它在析构函数中关闭,那么握住手柄也没有什么坏处。 你不明白问题的根源——对象名称的消失。你写的一切都无关紧要 @sutol:一点也不。该名称恰好消失了因为对象没有更多句柄了。【参考方案2】:

CreateFileMapping() 文档说:

文件映射对象的映射视图维护对象的内部引用,并且文件映射对象在所有对它的引用都被释放之前不会关闭。

CloseHandle() 文档说:

通常,CloseHandle 使指定的对象句柄无效,减少对象的句柄计数,并执行对象保留检查。在对象的最后一个句柄关闭后,该对象将从系统中删除。

映射视图只是将映射对象的引用计数保持在零以上,直到它们被取消映射,但它们不会保持底层文件/映射本身打开。

【讨论】:

你不明白问题的根源——对象名称的消失。你写的一切都无关紧要 我完全理解,我发布的内容相关的。 “CloseHandle 使指定的对象句柄无效”表示调用CloseHandle() 时映射对象已消失。视图仍然被映射的事实并不能阻止映射对象及其名称消失。您可以使用 SysInternals Process Explorer 之类的工具来验证这一点。【参考方案3】:

在创建者进程中关闭部分句柄视图后不会消失,直到取消映射它的名称 - NT 命名空间中的“MyFileMappingObject”被破坏。结果下一次调用 CreateFileMapping - 找不到命名对象“MyFileMappingObject”并创建新的(当你的逻辑打开现有的桅杆时)。再次 - 部分没有被破坏,但它的名字被破坏了。 你的名字 - 糟糕的解决方案 - 真的不错 - 这绝对是正常的 - 你没有关闭部分的句柄。这不是句柄泄漏 - 只会在您的过程中永久打开句柄。这种情况很正常

您使用的是 NAMED 部分。关闭它后处理 - 部分没有被破坏 - 因为也存在部分视图,它持有它。但部分的名称已被破坏。以及创建部分的新调用 - 不是打开现有的而是创建新的。

【讨论】:

没错,只要我稍后关闭手柄就可以了。 只是不关闭部分句柄。这都是需要的 @Remy Lebeau:你什么都不懂。绝对。“表示调用 CloseHandle() 时映射对象已消失。” - 谵妄。调用 CloseHandle() 时,部分(映射对象)不会消失。它的NAME onlt 不见了。【参考方案4】:

存在另一种解决方案,这表明你都错了。 它需要 SE_CREATE_PERMANENT_PRIVILEGE,但对于演示这是正常的

STATIC_OBJECT_ATTRIBUTES_EX(g_oa, "\\BaseNamedObjects\\MyFileMappingObject", OBJ_CASE_INSENSITIVE|OBJ_PERMANENT, 0, 0);

NTSTATUS CreateAndWrite()

    NTSTATUS status;
    HANDLE hSection;
    LARGE_INTEGER Size =  PAGE_SIZE ;
    if (0 <= (status = ZwCreateSection(&hSection, SECTION_MAP_READ|SECTION_MAP_WRITE, &g_oa, &Size, PAGE_READWRITE, SEC_COMMIT, 0)))
    
        PVOID BaseAddress = 0;
        SIZE_T ViewSize = 0;
        if (0 <= (status = ZwMapViewOfSection(hSection, NtCurrentProcess(), &BaseAddress, 0, 0, 0, &ViewSize, ViewUnmap, 0, PAGE_READWRITE)))
        
            STATIC_WSTRING(szMsg, "Message from first process.");
            memcpy(BaseAddress, szMsg, sizeof(szMsg));
            ZwUnmapViewOfSection(NtCurrentProcess(), BaseAddress);
        
        ZwClose(hSection);
    

    return status;


NTSTATUS OpenReadAndDestroy()

    NTSTATUS status;
    HANDLE hSection;
    if (0 <= (status = ZwOpenSection(&hSection, SECTION_MAP_READ|DELETE, &g_oa)))
    
        PVOID BaseAddress = 0;
        SIZE_T ViewSize = 0;
        if (0 <= (status = ZwMapViewOfSection(hSection, NtCurrentProcess(), &BaseAddress, 0, 0, 0, &ViewSize, ViewUnmap, 0, PAGE_READONLY)))
        
            MessageBox(0, (PCWSTR)BaseAddress, 0, 0);
            ZwUnmapViewOfSection(NtCurrentProcess(), BaseAddress);
        

        ZwMakeTemporaryObject(hSection);

        ZwClose(hSection);
    

    return status;


        if (0 <= GotPermanentPrivilege())
        
      if (0 <= CreateAndWrite())
      
        // at this point - no one handles for "MyFileMappingObject" exist
        // we close all and even unmap view
        // but 1 reference to section object exist (by OBJ_PERMANENT flag)
        // and NAME is still exist
        // as result we can open,map and read data :)
        OpenReadAndDestroy();
      
        

您对此有何评论? 部分被破坏,毕竟手柄关闭了??不能使用视图? D)

附言 SE_CREATE_PERMANENT_PRIVILEGE - 通常只有 LocalSystem,但我们可以得到它,如果有 SE_DEBUG_PRIVILEGE+SE_IMPERSONATE_PRIVILEGE - 打开“系统”线程并模拟它

【讨论】:

【参考方案5】:

查看您的描述,您似乎希望您的 Creator 应用在​​关闭之前/不管任何客户。 这将有以下内容: 一旦您的创建者应用关闭,Windows 将自动关闭该进程打开的所有句柄,因为这是一种资源泄漏预防机制。 这意味着,如果没有客户端附加到刚刚创建的文件映射对象创建者是唯一引用它的应用程序。 当此应用程序关闭时,所有引用文件映射对象的句柄都将关闭,这反过来将触发 Windows 规则“当文件映射对象的所有句柄都关闭时,对象将被自动释放”,从而导致您的对象被删除! 这就是您的客户端应用程序出现错误 0 的原因。

方法一:

采用这种方法的动机: 您想编写一个应用程序,同一应用程序的多个实例将通过共享内存进行通信。

在此类涉及共享内存进行进程间通信的应用程序中,我们开发的两个应用程序之间的唯一区别归结为“谁创建对象”,其余操作都是使用共享内存,并且对于两个应用程序。所以我们可以避免开发两个应用程序而只编写一个。

这种方法的另一个动机是,在客户端-服务器 LIKE 架构中,服务器必须在客户端之前启动。 如果你不想有这个限制,那么这种方法会有所帮助。 注意,这意味着您需要考虑更改架构,这对于现有应用程序来说可能是一项安静的任务!

实施:

// CreateFileMapping object
...
// Check the error value using GetLastError.
If (GetLastError() == ERROR_ALREADY_EXISTS)

    // I am a client

else

    // I am creator/server

在您的情况下,由于您允许您的创建者应用程序在客户端之前关闭,这意味着创建者不会像服务器那样承担太多重大责任。 如果是这种情况,那么您可以使用上述方法。 说不定你就不用再维护一个APP了!

方法 2: 让您的应用程序保持活跃,直到至少有任何一个客户端连接。 但同样,这将有点难以实施!

关于 UnmapViewOfFile 此功能是关闭/释放系统资源的正确方法。 但这并不意味着,关闭应用程序而不调用它会使您的资源保持活力! 不叫它意味着两种情况:

    应用程序崩溃。 开发者忘记调用了!

这会导致资源泄露! Windows 容错机制通过在进程正常或突然结束时关闭进程打开的所有句柄来缓解此问题。 因此,我们可以将 UnmapViewOfFile 视为适当释放资源的函数。

【讨论】:

以上是关于CreateFileMapping,MapViewOfFile,处理泄漏 C++的主要内容,如果未能解决你的问题,请参考以下文章

CreateFileMapping,MapViewOfFile,如何避免占用系统内存

CreateFileMapping() 用于写入文件长度未知的文本

无法释放 C++ 中 CreateFileMapping 和 MapViewOfFile 创建的共享内存

CreateFileMapping MapViewOfFile UnmapViewOfFile函数用法及示例

尝试使用 CreateFileMapping 和自定义 DACL 创建只读共享内存区域在 OpenFileMapping 中失败

Windows API一日一练(59)CreateFileMapping和MapViewOfFile函数