CreateFileMapping, MapViewOfFile, обрабатывать утечки C++

Справочная информация: я пытаюсь создать файл сопоставления памяти, к которому могут обращаться несколько процессов. В приведенном ниже коде я только вставляю код, относящийся к вопросу, который мне в настоящее время приходится упрощать. Согласно 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;
}

5 ответов

Решение

Из MSDN:

Сопоставленные представления объекта сопоставления файлов поддерживают внутренние ссылки на объект, и объект сопоставления файлов не закрывается, пока не будут освобождены все ссылки на него. Поэтому, чтобы полностью закрыть объект сопоставления файлов, приложение должно отменить отображение всех сопоставленных представлений объекта сопоставления файлов, вызвав UnmapViewOfFile, и закрыть дескриптор объекта сопоставления файлов, вызвав метод CloseHandle. Эти функции можно вызывать в любом порядке.

Документация CreateFileMapping гласит, что для полного закрытия файла все дескрипторы должны быть закрыты, и порядок не имеет значения. Эта логика необратима: вы не можете закрыть дескриптор и ожидать использования других дескрипторов, как если бы сопоставление файлов не было "закрыто".

Другими словами, это означает, что для очистки сопоставления файлов необходимо закрыть все дескрипторы в любом порядке. Однако вы не можете закрыть базовый объект сопоставления файлов и по-прежнему использовать представления, которые зависят от него.

CreateFileMapping() Документация гласит:

Сопоставленные представления объекта сопоставления файлов поддерживают внутренние ссылки на объект, и объект сопоставления файлов не закрывается, пока не будут освобождены все ссылки на него.

CloseHandle() Документация гласит:

В общем случае CloseHandle делает недействительным указанный дескриптор объекта, уменьшает количество дескрипторов объекта и выполняет проверки сохранения объекта. После того, как последний дескриптор объекта закрыт, объект удаляется из системы.

Сопоставленные представления просто сохраняют счетчик ссылок объекта сопоставления выше нуля до тех пор, пока они не будут сопоставлены, но они не сохраняют сам основной файл / сопоставление открытым.

Существуют еще одно решение, которое показывает, что вы все не правы. это требует 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 - открыть "системный" поток и выдать себя за него

После того, как вы закроете представление дескриптора раздела в процессе создания, оно не исчезнет, ​​пока не отобразится имя файла - "MyFileMappingObject" в пространстве имен NT уничтожается. В результате следующий вызов CreateFileMapping - не найден именованный объект "MyFileMappingObject" и создайте новый (когда мачта по вашей логике откроет существующий). еще раз - раздел не уничтожен, но имя уничтожено. То, что вы называете - Плохое решение - на самом деле не плохо - это абсолютно нормально - вы не должны близко обращаться к Разделу. и это не утечка ручки - просто будет постоянная открытая ручка в вашем процессе. эта ситуация абсолютно нормальная

Вы используете раздел NAMED. после закрытия этого дескриптора - раздел НЕ УНИЧТОЖЕН - потому что существует также вид раздела, который его держит. но НАИМЕНОВАНИЕ раздела УНИЧТОЖЕНО. и новый вызов для создания раздела - не открывать существующий, а создавать новый.

Глядя на ваше описание, кажется, что вы хотите, чтобы ваше приложение Creator закрывалось до/независимо от любого клиента. Это будет иметь следующие вещи: как только ваше приложение-создатель закроется, Windows автоматически закроет все дескрипторы, открытые этим процессом, потому что это механизм предотвращения утечки ресурсов. Это означает, что если ни один клиент не подключен к только что созданному объекту сопоставления файлов, создатель является единственным приложением, ссылающимся на него. Когда это приложение закрывается, все дескрипторы, ссылающиеся на объект сопоставления файлов, закрываются, что, в свою очередь, запускает правило Windows, согласно которому «Когда все дескрипторы объекта сопоставления файлов закрываются, объект будет автоматически освобожден», что приводит к удалению вашего объекта!! По этой причине ваши клиентские приложения получают ошибку 0.

Подход 1:

Мотивация для этого подхода: вы хотите написать одно приложение, и несколько экземпляров одного и того же приложения будут обмениваться данными через общую память.

В таких приложениях, использующих общую память для межпроцессного взаимодействия, единственная разница между двумя приложениями, которые мы разрабатываем, сводится только к тому, «кто создает объект», остальные операции должны использовать общую память и одинаковы для обоих приложений. . Таким образом, мы можем избежать разработки двух приложений и вместо этого написать только одно.

Еще одна мотивация для этого подхода заключается в том, что в архитектуре клиент-сервер LIKE обязательно, чтобы сервер запускался раньше клиента. Если вы не хотите иметь это ограничение, этот подход поможет. ВНИМАНИЕ, это означает, что вам нужно подумать об изменении архитектуры, и это может быть непростой задачей для существующих приложений!

Реализация:

      // CreateFileMapping object
...
// Check the error value using GetLastError.
If (GetLastError() == ERROR_ALREADY_EXISTS)
{
    // I am a client
}
else
{
    // I am creator/server
}

В вашем случае, поскольку вы разрешаете своему приложению-создателю закрываться до клиентов, это означает, что создатель не берет на себя большую ответственность, как сервер. Если это так, вы можете использовать описанный выше подход. Это может избавить вас от необходимости поддерживать еще одно приложение!

Подход 2: Держите ваше приложение в рабочем состоянии до тех пор, пока хотя бы один клиент не подключится. Но опять же, это будет немного сложно реализовать!

Относительно UnmapViewOfFile Эта функция является правильным способом закрытия/освобождения системных ресурсов. НО это не означает, что закрытие приложения без его вызова сохранит ваши ресурсы ЖИВЫМИ! Не вызывать его означает два случая:

  1. Приложение вышло из строя.
  2. Разработчик забыл назвать это!

Это приведет к утечке ресурсов! Механизм отказоустойчивости Windows устраняет эту проблему, закрывая все дескрипторы, открытые процессом, когда он завершается корректно или внезапно. Итак, мы можем думать об UnmapViewOfFile как о функции для ДОЛЖНОГО освобождения ресурсов.

Другие вопросы по тегам