// ========================================================================== // 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: // ==========================================================================