4

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

MSDN: CreateFileMapping 函数

文件映射对象的映射视图维护对该对象的内部引用,并且文件映射对象在对它的所有引用都被释放之前不会关闭。因此,要完全关闭文件映射对象,应用程序必须通过调用 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;
}
4

5 回答 5

5

来自 MSDN:

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

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

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

于 2015-04-09T16:00:57.533 回答
2

CreateFileMapping()文档说:

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

CloseHandle()文档说:

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

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

于 2015-04-09T16:07:22.857 回答
0

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

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

于 2015-04-09T16:21:14.013 回答
0

存在其他一种解决方案,这表明你都错了。它需要 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)

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

于 2015-04-10T10:30:29.583 回答
0

查看您的描述,您似乎希望您的 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!

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

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

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

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

于 2021-09-06T01:42:15.453 回答