访问共享内存映射文件的次数(Windows)

Accessing the number of shared memory mapped file views (Windows)

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

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

在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 内存映射文件的 "same" 代码将是(或多或少):

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

Mapped views of a file mapping object maintain internal references to the object, and a file mapping object does not close until all references to it are released. Therefore, to fully close a file mapping object, an application must unmap all mapped views of the file mapping object by calling UnmapViewOfFile and close the file mapping object handle by calling CloseHandle. These functions can be called in any order.

来自UnmapViewOfFile function

Unmapping a mapped view of a file invalidates the range occupied by the view in the address space of the process and makes the range available for other allocations. It removes the working set entry for each unmapped virtual page that was part of the working set of the process and reduces the working set size of the process. It also decrements the share count of the corresponding physical page.

但我无法获得 共享计数 ,关于此事的唯一 Whosebug 问题(我发现)没有答案:Number of mapped views to a shared memory on Windows

如果有人能帮助我,我将不胜感激。


解决方案

(注:虽然可能不是100%可靠,见评论区)

根据 RbMm and eryksun 的评论(谢谢!)我可以用这段代码解决问题:

#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)。所以最后我可能会求助于命名管道解决方案(非常好和整洁)。

如 eryksun 所述,最可靠的方法是使用 CreateNamedPipe 函数。我们可以使用 nMaxInstances 参数 - The maximum number of instances that can be created for this pipe. 在接下来的方式中。启动时应用程序的每个实例都尝试创建管道实例。如果没问题 - 我们可以 运行,否则达到限制。

代码可以是下一个:

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);
    }