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:
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.
(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).
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With