Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I am developing a multi-platform C++ application (mainly Windows and Linux), now I face the need to be able to limit the maximum number of instances of the application that may run at the same time (in the same machine).

I have already a shared memory module that uses:

  • Linux: System V shared memory (shmget(), shmat()...)
  • Windows: Memory mapped files (CreateFileMapping(), OpenFileMapping(), MapViewOfFile(),...)

In linux I can easily control the number of instances running with this kind of code:

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

The nice thing of this code is that when the application is killed or crashes the system takes care decrementing the number of attached clients.

Now my question is, how to implement this in Windows? (using memory mapped files). The "same" code translated to Windows memory mapped files would be (more or less):

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?
}

I have been searching for quite some time and I cannot find how to get the number of opened views.

From the 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.

From 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.

But I cannot get that shared count, and the only stackoverflow question regarding this matter (that I have found) is unanswered: Number of mapped views to a shared memory on Windows

I would really appreciate if anybody could help me with this.


Solution

(Note: Although may not be 100% reliable, see the comments section)

From the comments of RbMm and eryksun (Thanks!) I am able to solve the question with this code:

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

But to be correct I should be using the Global kernel namespace, which needs a privilege (SeCreateGlobalPrivilege). So at the end I may resort to the named pipe solution (very nice and neat).

like image 782
Marcos G. Avatar asked Apr 30 '18 21:04

Marcos G.


1 Answers

as noted eryksun, most reliable way for do this - use CreateNamedPipe function. we can use nMaxInstances parameter - The maximum number of instances that can be created for this pipe. in next way. every instances of the application on start try create pipe instance. if this is ok - we can run, otherwise the limit is reached.

code can be next:

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

and use, say for N instances

    if (!IsLimitReached(N))
    {
        MessageBoxW(0, L"running..",0,0);
    }
like image 158
RbMm Avatar answered Oct 31 '22 13:10

RbMm