访问共享内存映射文件视图的数量(Windows)

时间:2018-04-30 21:13:33

标签: c++ c windows memory-mapped-files

我正在开发一个多平台的C ++应用程序(主要是Windows和Linux),现在我需要能够限制可能同时运行的应用程序的最大实例数(在同一台机器上)

我已经使用了一个共享内存模块:

  • Linux:System V共享内存(shmget(),shmat()...)
  • Windows:内存映射文件(CreateFileMapping(),OpenFileMapping(),MapViewOfFile(),...)

在linux中,我可以轻松控制使用这种代码运行的实例数量:

#include <stdio.h>
#include <sys/shm.h>
#include <unistd.h>
#include <stdlib.h>

int main(void)
{
    struct shmid_ds shm;
    int shmId;
    key_t shmKey = 123456; // A unique key...

    // Allocating 1 byte shared memory segment
    // open it if already existent and rw user permission
    shmId = shmget(shmKey, 1, IPC_CREAT|0x0180);

    // Attach to the shared memory segment
    shmat(shmId, (char *) 0, SHM_RDONLY);

    // Get the number of attached "clients"
    shmctl(shmId, IPC_STAT, &shm);

    // Check limit
    if (shm.shm_nattch > 4) {
        printf("Limit exceeded: %ld > 4\n", shm.shm_nattch);
        exit(1);
    }

    //...
    sleep(30);
}

这段代码的好处是,当应用程序被终止或崩溃时,系统会小心减少连接客户端的数量。

现在我的问题是,如何在Windows中实现它?(使用内存映射文件)。转换为Windows内存映射文件的&#34;相同&#34; 代码将(或多或少):

void functionName(void)
{
    // Create the memory mapped file (in system pagefile)
    HANDLE hMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE|SEC_COMMIT,
                  0, 1, "Global\\UniqueShareName");

    // Map the previous memory mapped file into the address space
    char *addr = (char*)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);

    // How can I check now the number of views mapped?
}

我一直在寻找相当长的时间,但我找不到如何获得打开的观看次数

来自CreateFileMapping function

  

文件映射对象的映射视图维护内部引用   对象和文件映射对象直到全部关闭   对它的引用已经发布。因此,要完全关闭文件   映射对象,应用程序必须取消映射文件的所有映射视图   通过调用UnmapViewOfFile映射对象并关闭文件映射   通过调用CloseHandle来处理对象句柄。可以调用这些函数   任何订单。

来自UnmapViewOfFile function

  

取消映射文件的映射视图会使占用的范围无效   进程的地址空间中的视图并生成范围   可用于其他分配。它删除了工作集条目   每个未映射的虚拟页面,它是工作集的一部分   处理并减少过程的工作集大小。 它也   减少相应物理页面的共享计数

但是我无法获得共享计数,并且唯一有关此问题的堆栈溢出问题(我发现)无法回答:Number of mapped views to a shared memory on Windows

如果有人能帮助我,我真的很感激。

解决方案

注意:虽然可能不是100%可靠,但请参阅评论部分)

来自RbMmeryksun的评论(谢谢!)我可以使用此代码解决问题:

#include <stdio.h>
#include <windows.h>
#include <winternl.h>

typedef NTSTATUS (__stdcall *NtQueryObjectFuncPointer) (
            HANDLE                   Handle,
            OBJECT_INFORMATION_CLASS ObjectInformationClass,
            PVOID                    ObjectInformation,
            ULONG                    ObjectInformationLength,
            PULONG                   ReturnLength);

int main(void)
{
    _PUBLIC_OBJECT_BASIC_INFORMATION pobi;
    ULONG rLen;

    // Create the memory mapped file (in system pagefile) (better in global namespace
    // but needs SeCreateGlobalPrivilege privilege)
    HANDLE hMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE|SEC_COMMIT,
                  0, 1, "Local\\UniqueShareName");

    // Get the NtQUeryObject function pointer and then the handle basic information
    NtQueryObjectFuncPointer _NtQueryObject = (NtQueryObjectFuncPointer)GetProcAddress(
            GetModuleHandle("ntdll.dll"), "NtQueryObject");

    _NtQueryObject(hMap, ObjectBasicInformation, (PVOID)&pobi, (ULONG)sizeof(pobi), &rLen);

    // Check limit
    if (pobi.HandleCount > 4) {
        printf("Limit exceeded: %ld > 4\n", pobi.HandleCount);
        exit(1);
    }
    //...
    Sleep(30000);
}

但是为了正确我应该使用全局内核命名空间,它需要一个特权(SeCreateGlobalPrivilege)。所以最后我可以采用命名的管道解决方案(非常漂亮和整洁)。

1 个答案:

答案 0 :(得分:4)

正如eryksun所说,最可靠的方法是使用CreateNamedPipe功能。我们可以使用nMaxInstances参数 - 可以为此管道创建的最大实例数。以下一种方式。启动时应用程序的每个实例都尝试创建管道实例。如果这样可以 - 我们可以运行,否则达到限制。

代码可以是下一个:

BOOL IsLimitReached(ULONG MaxCount)
{
    SECURITY_DESCRIPTOR sd;
    InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
    SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE);

    SECURITY_ATTRIBUTES sa = { sizeof(sa), &sd, FALSE };

    HANDLE hPipe = CreateNamedPipe(L"\\\\.\\pipe\\<some pipe>", 
        PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE|PIPE_READMODE_BYTE, MaxCount, 0, 0, 0, &sa);

    if (hPipe == INVALID_HANDLE_VALUE)
    {
        ULONG dwError = GetLastError();

        if (dwError != ERROR_PIPE_BUSY)
        {
            // handle error 
        }
        return TRUE;
    }

    return FALSE;
}

并使用,例如N个实例

    if (!IsLimitReached(N))
    {
        MessageBoxW(0, L"running..",0,0);
    }