2025-11-27 16:46:48 +09:00

673 lines
21 KiB
C++

// ==========================================================================
// Class Implementation : COXInstanceManager
// ==========================================================================
// Source file : OXInstanceManager.cpp
// Version: 9.3
// This software along with its related components, documentation and files ("The Libraries")
// is © 1994-2007 The Code Project (1612916 Ontario Limited) and use of The Libraries is
// governed by a software license agreement ("Agreement"). Copies of the Agreement are
// available at The Code Project (www.codeproject.com), as part of the package you downloaded
// to obtain this file, or directly from our office. For a copy of the license governing
// this software, you may contact us at legalaffairs@codeproject.com, or by calling 416-849-8900.
// //////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "OXInstanceManager.h"
#include "UTB64bit.h"
#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif
IMPLEMENT_DYNAMIC(COXInstanceManager, CObject)
#define new DEBUG_NEW
static const TCHAR szInstanceListMutexPrefix[] = _T("COXInstanceManager_InstanceList_Mutex_");
static const TCHAR szAdditionalDataMutexPrefix[] = _T("COXInstanceManager_Additional_Mutex_");
static const TCHAR szFileMapPrefix[] = _T("COXInstanceManager_FileMap_");
/////////////////////////////////////////////////////////////////////////////
// Definition of static members
const LPCTSTR COXInstanceManager::m_pszInstanceListMutexPrefix = szInstanceListMutexPrefix;
const LPCTSTR COXInstanceManager::m_pszAdditionalDataMutexPrefix = szAdditionalDataMutexPrefix;
const LPCTSTR COXInstanceManager::m_pszFileMapPrefix = szFileMapPrefix;
// Data members -------------------------------------------------------------
// protected:
// CString m_sApplicationName;
// --- A unique name identifying all the instances of an application
// DWORD m_nInstanceListSize;
// --- The complete size used for the CInstanceList object.
// This number is rounded to the allocation granularity
// DWORD m_nAdditionalDataSize;
// --- The complete size used for the additional data
// This number is rounded to the allocation granularity
// CMutex m_instanceListMutex;
// --- The mutexused to synchronize access to the list of instances
// CMutex m_additionalDataMutex;
// --- The mutex used to synchronize access to the additional data
// HANDLE m_hFileMap;
// --- The file mapping object
// LPVOID m_pInstanceListView;
// --- The view on the file mapping used to access the shared instance list
// LPVOID m_pAdditionalDataView;
// --- The view on the file mapping used to access the additional data
// private:
// Member functions ---------------------------------------------------------
// public:
// ... To make sure inly one object is created per instance
static BOOL bInstanceManagerCreated = FALSE;
COXInstanceManager::COXInstanceManager(LPCTSTR pszApplicationName, DWORD nMinAdditionalDataSize /* = 0 */)
:
m_sApplicationName(pszApplicationName),
m_nAdditionalDataSize(nMinAdditionalDataSize),
m_instanceListMutex(FALSE, m_pszInstanceListMutexPrefix + m_sApplicationName),
m_additionalDataMutex(FALSE, m_pszAdditionalDataMutexPrefix + m_sApplicationName),
m_hFileMap(NULL),
m_pInstanceListView(NULL),
m_pAdditionalDataView(NULL)
{
// ... You should supply a unique name for the application
ASSERT((pszApplicationName != NULL) && (*pszApplicationName != _T('\0')));
#ifdef _DEBUG
// ... Only one object of the COXInstanceManager class may be created per instance
ASSERT(bInstanceManagerCreated == FALSE);
bInstanceManagerCreated = TRUE;
#endif // _DEBUG
ASSERT_VALID(this);
// Compute the size of the instance list
m_nInstanceListSize = sizeof(CInstanceList);
// ... Round the size to a multiple of the page size
SYSTEM_INFO systemInfo;
::ZeroMemory(&systemInfo, sizeof(systemInfo));
::GetSystemInfo(&systemInfo);
if (m_nInstanceListSize % systemInfo.dwAllocationGranularity != 0)
m_nInstanceListSize = m_nInstanceListSize / systemInfo.dwAllocationGranularity +
systemInfo.dwAllocationGranularity;
else
m_nInstanceListSize = m_nInstanceListSize / systemInfo.dwAllocationGranularity;
if (m_nAdditionalDataSize % systemInfo.dwAllocationGranularity != 0)
m_nAdditionalDataSize = m_nAdditionalDataSize / systemInfo.dwAllocationGranularity +
systemInfo.dwAllocationGranularity;
else
m_nAdditionalDataSize = m_nAdditionalDataSize / systemInfo.dwAllocationGranularity;
// Create file map in memory
DWORD nTotalSize = m_nInstanceListSize + m_nAdditionalDataSize;
// ... Total size must also be a multiple of the page size
ASSERT(nTotalSize % systemInfo.dwAllocationGranularity == 0);
CString sFileMapName = m_pszFileMapPrefix + m_sApplicationName;
m_hFileMap = ::CreateFileMapping((HANDLE)(INT_PTR)0xFFFFFFFF, NULL,
PAGE_READWRITE | SEC_COMMIT, 0, nTotalSize, sFileMapName);
BOOL bAlreadyExists = (::GetLastError() == ERROR_ALREADY_EXISTS);
#ifdef _DEBUG
if (m_hFileMap == NULL)
TRACE1("COXInstanceManager::COXInstanceManager : No file map could be created (error %i)\n",
::GetLastError());
#endif // _DEBUG
if (!bAlreadyExists)
InitializeInstanceList();
// Add this instance to the list
AddInstanceToList(GetCurrentInstanceID());
}
BOOL COXInstanceManager::HasPreviousInstance()
{
return (1 < GetNumberOfInstances());
}
DWORD COXInstanceManager::GetNumberOfInstances()
{
// Get a pointer to the list of instances
if (!GetInstanceList())
return FALSE;
// ... Lock the list
CSingleLock listLock(&m_instanceListMutex, TRUE);
// Get the total number of instances in the list
CInstanceList* pInstanceList = (CInstanceList*)m_pInstanceListView;
ASSERT(pInstanceList->m_nCurrentNumInstances <= OX_MAX_NUM_INSTANCES_IN_LIST);
DWORD nCurrentNumInstances = pInstanceList->m_nCurrentNumInstances;
#ifdef _DEBUG
if (pInstanceList->m_nCurrentNumInstances == OX_MAX_NUM_INSTANCES_IN_LIST)
TRACE1("COXInstanceManager::GetNumberOfInstances : Max number reached (%i), extra may be ignored\n",
OX_MAX_NUM_INSTANCES_IN_LIST);
#endif
// Immediately unmap the view to protect against dangerous pointers
ReleaseInstanceList();
return nCurrentNumInstances;
}
DWORD COXInstanceManager::GetMaxAllowedInstances()
{
// Get a pointer to the list of instances
if (!GetInstanceList())
return 0;
// ... Lock the list
CSingleLock listLock(&m_instanceListMutex, TRUE);
// Get the total number of instances in the list
CInstanceList* pInstanceList = (CInstanceList*)m_pInstanceListView;
ASSERT(pInstanceList->m_nCurrentNumInstances <= OX_MAX_NUM_INSTANCES_IN_LIST);
DWORD nMaxAllowedInstances = pInstanceList->m_nMaxAllowedInstances;
// Immediately unmap the view to protect against dangerous pointers
ReleaseInstanceList();
return nMaxAllowedInstances;
}
BOOL COXInstanceManager::SetMaxAllowedInstances(DWORD nMaxAllowedInstances,
BOOL bCloseExtra /* = TRUE */)
{
// Get a pointer to the list of instances
if (!GetInstanceList())
return FALSE;
#ifdef _DEBUG
if (OX_MAX_NUM_INSTANCES_IN_LIST < nMaxAllowedInstances)
TRACE2("COXInstanceManager::SetMaxAllowedInstances : Trying to set max allowable number (%i)higher than process list size (%i), extra may be ignored\n",
nMaxAllowedInstances, OX_MAX_NUM_INSTANCES_IN_LIST);
#endif
// ... Lock the list
CSingleLock listLock(&m_instanceListMutex, TRUE);
// Get the total number of instances in the list
CInstanceList* pInstanceList = (CInstanceList*)m_pInstanceListView;
ASSERT(pInstanceList->m_nCurrentNumInstances <= OX_MAX_NUM_INSTANCES_IN_LIST);
pInstanceList->m_nMaxAllowedInstances = nMaxAllowedInstances;
// Get a copy off the data before we release the instancelist
CDWordArray instanceColl;
DWORD nCurrentNumInstances = pInstanceList->m_nCurrentNumInstances;
instanceColl.SetSize(pInstanceList->m_nCurrentNumInstances);
::CopyMemory(instanceColl.GetData(), pInstanceList->m_rgPID, pInstanceList->m_nCurrentNumInstances * sizeof(DWORD));
// Immediately unmap the view to protect against dangerous pointers
ReleaseInstanceList();
listLock.Unlock();
// If their are currently more instances running than allowed, we will close some
int nInstancesToKill = nCurrentNumInstances - nMaxAllowedInstances;
if (bCloseExtra && (0 < nInstancesToKill))
{
// ... Iterate the collection backwards (because we are deleting items)
DWORD nThisPID = GetCurrentInstanceID();
DWORD nInstanceID = 0;
int nInstanceIndex = PtrToInt(instanceColl.GetSize() - 1);
while ((0 <= nInstanceIndex) && (0 < nInstancesToKill))
{
nInstanceID = instanceColl.GetAt(nInstanceIndex);
// ... Kill the instance if it is not this instance
if ((nInstanceID != nThisPID) && (CloseInstance(nInstanceID)) )
nInstancesToKill--;
nInstanceIndex--;
}
if (0 < nInstancesToKill)
{
// ... We we still have not killed enough instances, try to kill this instance
if (CloseInstance(nThisPID))
nInstancesToKill--;
}
#ifdef _DEBUG
if (0 < nInstancesToKill)
TRACE0("COXInstanceManager::SetMaxAllowedInstances : Not enough instnaces could be closed\n");
#endif
}
return TRUE;
}
BOOL COXInstanceManager::GetInstanceCollection(CDWordArray& instanceColl)
{
// Get a pointer to the list of instances
if (!GetInstanceList())
return FALSE;
// ... Lock the list
CSingleLock listLock(&m_instanceListMutex, TRUE);
// Get the total number of instances in the list
// ... First DWORD in list is the number of items, followed by the PIDs
CInstanceList* pInstanceList = (CInstanceList*)m_pInstanceListView;
ASSERT(pInstanceList->m_nCurrentNumInstances <= OX_MAX_NUM_INSTANCES_IN_LIST);
// Fill the result collection
instanceColl.SetSize(pInstanceList->m_nCurrentNumInstances);
::CopyMemory(instanceColl.GetData(), pInstanceList->m_rgPID, pInstanceList->m_nCurrentNumInstances * sizeof(DWORD));
// Immediately unmap the view to protect against dangerous pointers
ReleaseInstanceList();
return TRUE;
}
BOOL COXInstanceManager::CheckMaxAllowedInstances(BOOL bActivatePrevious /* = TRUE */)
{
BOOL bMaxExceeded = FALSE;
// Get a pointer to the list of instances
if (!GetInstanceList())
return FALSE;
// ... Lock the list
CSingleLock listLock(&m_instanceListMutex, TRUE);
// Get the total number of instances in the list
CInstanceList* pInstanceList = (CInstanceList*)m_pInstanceListView;
ASSERT(pInstanceList->m_nCurrentNumInstances <= OX_MAX_NUM_INSTANCES_IN_LIST);
bMaxExceeded = (pInstanceList->m_nMaxAllowedInstances < pInstanceList->m_nCurrentNumInstances);
if (bMaxExceeded && bActivatePrevious)
{
// Activate a previous instance
DWORD nThisPID = GetCurrentInstanceID();
DWORD nPID = 0;
if (0 < pInstanceList->m_nCurrentNumInstances)
nPID = pInstanceList->m_rgPID[0];
if (nPID == nThisPID)
{
nPID = 0;
if (1 < pInstanceList->m_nCurrentNumInstances)
nPID = pInstanceList->m_rgPID[1];
ASSERT(nPID != nThisPID);
}
if (nPID != 0)
{
// Another instance was found : activate it
HWND hTopLevelWnd = GetMainWindow(nPID);
if (hTopLevelWnd != NULL)
{
::SetForegroundWindow(hTopLevelWnd);
if(CWnd::FromHandle(hTopLevelWnd)->GetStyle() & WS_MINIMIZE)
{
::ShowWindow(hTopLevelWnd,SW_RESTORE);
}
}
}
}
// Immediately unmap the view to protect against dangerous pointers
ReleaseInstanceList();
return bMaxExceeded;
}
HWND COXInstanceManager::GetMainWindow(DWORD_PTR nPID)
{
// First find a top level window of the specified instance
CInstanceWindow instanceWindow;
instanceWindow.m_nPID = nPID;
instanceWindow.m_hMainWnd = NULL;
::EnumWindows(&EnumMainWindows, (LPARAM)&instanceWindow);
#ifdef _DEBUG
if (instanceWindow.m_hMainWnd == NULL)
TRACE1("COXInstanceManager::GetMainWindow : No main window found for instance %i\n", nPID);
#endif // _DEBUG
return instanceWindow.m_hMainWnd;
}
BOOL COXInstanceManager::CloseInstance(DWORD_PTR nPID)
{
// ... Assume failure
BOOL bSuccess = FALSE;
HWND hMainWnd = GetMainWindow(nPID);
if (hMainWnd != NULL)
{
// ... Only try to close enabled windows
// A window will be disabled e.g. when it owns an open modal dialog
// Closing the main window at this point may be dangerous
// For more info see MSDN :
// * Terminating Windows-Based Application from Another App (PSS ID Number: Q92528)
// * Dialog Box Default Message Instanceing : WM_CLOSE
if (::IsWindowEnabled(hMainWnd))
{
::PostMessage(hMainWnd, WM_CLOSE, 0, 0);
bSuccess = TRUE;
}
}
#ifdef _DEBUG
if (::IsWindow(hMainWnd) && !::IsWindowEnabled(hMainWnd))
TRACE1("COXInstanceManager::CloseInstance : Main window of instance %i is disabled, failing\n", nPID);
#endif // _DEBUG
return bSuccess;
}
DWORD COXInstanceManager::GetCurrentInstanceID()
{
return ::GetCurrentProcessId();
}
DWORD COXInstanceManager::GetAdditionalDataSize() const
{
return m_nAdditionalDataSize;
}
LPVOID COXInstanceManager::GetAdditionalData()
{
if (m_pAdditionalDataView != NULL)
return m_pAdditionalDataView;
if (m_hFileMap == NULL)
{
TRACE0("COXInstanceManager::GetAdditionalData : No file mapping could be created, returning NULL\n");
return NULL;
}
// Create new view of file mapping (additional data is located after the instance list)
m_pAdditionalDataView = ::MapViewOfFile(m_hFileMap, FILE_MAP_READ | FILE_MAP_WRITE,
0, m_nInstanceListSize, m_nAdditionalDataSize);
#ifdef _DEBUG
if (m_pAdditionalDataView != NULL)
ASSERT(AfxIsValidAddress(m_pAdditionalDataView, m_nAdditionalDataSize));
else
TRACE1("COXInstanceManager::GetAdditionalData : No view of file map could be created (error %i), returning NULL\n",
::GetLastError());
#endif // _DEBUG
return m_pAdditionalDataView;
}
void COXInstanceManager::ReleaseAdditionalData()
{
if (m_pAdditionalDataView != NULL)
{
VERIFY(::UnmapViewOfFile(m_pAdditionalDataView));
m_pAdditionalDataView = NULL;
}
}
CMutex& COXInstanceManager::GetAdditionalDataMutex()
{
return m_additionalDataMutex;
}
#ifdef _DEBUG
void COXInstanceManager::AssertValid() const
{
CObject::AssertValid();
}
void COXInstanceManager::Dump(CDumpContext& dc) const
{
CObject::Dump(dc);
}
#endif //_DEBUG
COXInstanceManager::~COXInstanceManager()
{
// Remove this instance from the list
RemoveInstanceFromList(GetCurrentInstanceID());
// ... View of instance list data should have been closed after use
ASSERT(m_pInstanceListView == NULL);
// Close view of additional data if not done yet
ReleaseAdditionalData();
// Close the file map
if (m_hFileMap != NULL)
{
VERIFY(::CloseHandle(m_hFileMap));
m_hFileMap = NULL;
}
bInstanceManagerCreated = FALSE;
}
// protected:
BOOL COXInstanceManager::GetInstanceList()
// --- In :
// --- Out :
// --- Returns : Whether it succeeded or not
// --- Effect : This functions gets a pointer to the instance list and stores it
// in the data member m_pInstanceListView
// The pointer is valid until the next call to ReleaseInstanceList()
{
// ... Should have been unmapped after its last use
ASSERT(m_pInstanceListView == NULL);
if (m_hFileMap == NULL)
{
TRACE0("COXInstanceManager::GetInstanceListData : No file mapping could be created, returning NULL\n");
return NULL;
}
// Create new view of file mapping (additional data is located after the instance list)
m_pInstanceListView = ::MapViewOfFile(m_hFileMap, FILE_MAP_READ | FILE_MAP_WRITE,
0, 0, m_nInstanceListSize);
#ifdef _DEBUG
if (m_pInstanceListView != NULL)
ASSERT(AfxIsValidAddress(m_pInstanceListView, m_nInstanceListSize));
else
TRACE1("COXInstanceManager::GetInstanceListData : No view of file map could be created (error %i), returning NULL\n",
::GetLastError());
#endif // _DEBUG
return (m_pInstanceListView != NULL);
}
void COXInstanceManager::ReleaseInstanceList()
// --- In :
// --- Out :
// --- Returns :
// --- Effect : Releases the pointer to the instance list
// The value of the data member m_pInstanceListView is no longer valid
{
if (m_pInstanceListView != NULL)
{
VERIFY(::UnmapViewOfFile(m_pInstanceListView));
m_pInstanceListView = NULL;
}
}
BOOL COXInstanceManager::InitializeInstanceList()
// --- In :
// --- Out :
// --- Returns : Whether it succeeded or not
// --- Effect : Initializes the shared data of the instance list
{
BOOL bSuccess = FALSE;
// Get a pointer to the list of instances
if (!GetInstanceList())
return FALSE;
// ... Lock the list
CSingleLock listLock(&m_instanceListMutex, TRUE);
// Initailze the instance list
CInstanceList* pInstanceList = (CInstanceList*)m_pInstanceListView;
// ... Default max allowed instances to 1
pInstanceList->m_nMaxAllowedInstances = 1;
pInstanceList->m_nCurrentNumInstances = 0;
::ZeroMemory(pInstanceList->m_rgPID, sizeof(pInstanceList->m_rgPID));
// Immediately unmap the view to protect against dangerous pointers
ReleaseInstanceList();
return bSuccess;
}
BOOL COXInstanceManager::AddInstanceToList(DWORD nPID)
// --- In : nPID : The instance (process) ID
// --- Out :
// --- Returns : Whether it succeeded or not
// --- Effect : Adds the specified instance ID to the list
{
// ... Assume failure
BOOL bSuccess = FALSE;
// Get a pointer to the list of instances
if (!GetInstanceList())
return FALSE;
// ... Lock the list
CSingleLock listLock(&m_instanceListMutex, TRUE);
// Add PID to the end of the list
CInstanceList* pInstanceList = (CInstanceList*)m_pInstanceListView;
ASSERT(pInstanceList->m_nCurrentNumInstances <= OX_MAX_NUM_INSTANCES_IN_LIST);
// ... Only add when there is space left
if (pInstanceList->m_nCurrentNumInstances < OX_MAX_NUM_INSTANCES_IN_LIST)
{
for(int nIndex=0; nIndex<(int)pInstanceList->m_nCurrentNumInstances; nIndex++)
{
::PostMessage(GetMainWindow(pInstanceList->m_rgPID[nIndex]),
WM_OX_INSTANCE_CREATED,(WPARAM)NULL,(LPARAM)nPID);
}
pInstanceList->m_rgPID[pInstanceList->m_nCurrentNumInstances] = nPID;
pInstanceList->m_nCurrentNumInstances++;
bSuccess = TRUE;
}
else
{
TRACE1("COXInstanceManager::AddInstanceToList : Max number of trackable instances (%i) reached, extra ignored\n",
OX_MAX_NUM_INSTANCES_IN_LIST);
}
ASSERT(pInstanceList->m_nCurrentNumInstances <= OX_MAX_NUM_INSTANCES_IN_LIST);
// Immediately unmap the view to protect against dangerous pointers
ReleaseInstanceList();
return bSuccess;
}
BOOL COXInstanceManager::RemoveInstanceFromList(DWORD nPID)
// --- In : nPID : The instance (process) ID
// --- Out :
// --- Returns : Whether it succeeded or not
// --- Effect : Removes the specified instance ID from the list
{
BOOL bSuccess = FALSE;
// Get a pointer to the list of instances
if (!GetInstanceList())
return FALSE;
// ... Lock the list
CSingleLock listLock(&m_instanceListMutex, TRUE);
// Search for the specified instance ID
CInstanceList* pInstanceList = (CInstanceList*)m_pInstanceListView;
ASSERT(pInstanceList->m_nCurrentNumInstances <= OX_MAX_NUM_INSTANCES_IN_LIST);
DWORD nInstanceIndex = 0;
while ((nInstanceIndex < pInstanceList->m_nCurrentNumInstances) &&
(pInstanceList->m_rgPID[nInstanceIndex] != nPID))
{
nInstanceIndex++;
}
if (nInstanceIndex < pInstanceList->m_nCurrentNumInstances)
{
DWORD dwRemovedPID=pInstanceList->m_rgPID[nInstanceIndex];
// Move all the following ID one position to the beginning and set
// the last one to zero
DWORD* pFoundInstance = &pInstanceList->m_rgPID[nInstanceIndex];
// ... Move 1 to the top
::MoveMemory(pFoundInstance, pFoundInstance+1,
(pInstanceList->m_nCurrentNumInstances-nInstanceIndex-1)*sizeof(DWORD));
// .. Add a new 0
pInstanceList->m_rgPID[pInstanceList->m_nCurrentNumInstances]=0;
ASSERT(1 <= pInstanceList->m_nCurrentNumInstances);
// ...Decrement the current number
pInstanceList->m_nCurrentNumInstances--;
bSuccess = TRUE;
for(int nIndex=0; nIndex<(int)pInstanceList->m_nCurrentNumInstances; nIndex++)
{
::PostMessage(GetMainWindow(pInstanceList->m_rgPID[nIndex]),
WM_OX_INSTANCE_DESTROYED,(WPARAM)NULL,(LPARAM)dwRemovedPID);
}
}
else
TRACE1("COXInstanceManager::RemoveInstanceFromList : Instances (%i) not found, ignored\n", nPID);
ASSERT(pInstanceList->m_nCurrentNumInstances <= OX_MAX_NUM_INSTANCES_IN_LIST);
// Immediately unmap the view to protect against dangerous pointers
ReleaseInstanceList();
return bSuccess;
}
BOOL CALLBACK COXInstanceManager::EnumMainWindows(HWND hWnd, LPARAM lParam)
// --- In : hWnd : A top-level window
// lParam : Additioanl data (pointer to a CInstanceWindow object)
// --- Out :
// --- Returns : Whether to continue iterating
// --- Effect : This function is called for every top-level window
{
#if defined (_WINDLL)
#if defined (_AFXDLL)
AFX_MANAGE_STATE(AfxGetAppModuleState());
#else
AFX_MANAGE_STATE(AfxGetStaticModuleState());
#endif
#endif
CInstanceWindow* pInstanceWindow = (CInstanceWindow*)lParam;
ASSERT(AfxIsValidAddress(pInstanceWindow, sizeof(CInstanceWindow)));
// We will search for a toplevel window of the specified instance
// Thiw window should be not-owned (to exclude dialogs)
DWORD nWindowPID = 0;
::GetWindowThreadProcessId(hWnd, &nWindowPID);
if ((nWindowPID==pInstanceWindow->m_nPID) &&
(::GetWindow(hWnd,GW_OWNER)==NULL) &&
(::GetWindowLongPtr(hWnd,GWL_EXSTYLE) & WS_EX_TOOLWINDOW)==0 &&
(::GetWindowLongPtr(hWnd,GWL_STYLE) & WS_VISIBLE)==WS_VISIBLE)
{
pInstanceWindow->m_hMainWnd = hWnd;
// ... Found, stop looking
return FALSE;
}
// ... Not found, keep on looking
return TRUE;
}
// private:
// ==========================================================================