Здравствуйте,
Вспомогательный класс, позволяющий автоматически гарантировать существование единственного экземпляра программы.
Написано на основе статьи:
http://rsdn.ru/article/baseserv/avins.xmlАвтор(ы): Dr. Joseph M. Newcomer
Дата: 17.02.2001
В статье подробно рассматриваются различные способы ограничения числа запущенных копий приложения одной. Указываются недостатки и даже ошибки во многих известных методах решения задачи, которые постоянно фигурируют в конференциях и в Сети. Кроме того, указывает на различные трактовки понятия "одна копия приложения" и демонстрирует правильное решение для каждого из случаев.
Пример использования:
class CMainFrame
: public CFrameWindowImpl<CMainFrame>
, public CUpdateUI<CMainFrame>
, public CMessageFilter
, public CIdleHandler
, public CSingleInstance<CMainFrame>
{
public:
DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME)
DECLARE_UNIQUE_INSTANCE(UNIQUE_TO_TRUSTEE, "MyOwndGUID-{4B54CB01-BB74-42f7-B5B1-88486561617F}");
BEGIN_MSG_MAP_EX(CMainFrame)
CHAIN_MSG_MAP(CSingleInstance<CMainFrame>)
// дальше как обычно
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit)
COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)
END_MSG_MAP()
// при желании
int OnInstanceFound()
{
if (__argc>1 && __argv[1]==std::string("-unload"))
{
return doCloseAll;
}
return doKeepOlder;
}
};
Реализация
enum {
UNIQUE_TO_SYSTEM,
UNIQUE_TO_SESSION,
UNIQUE_TO_DESKTOP,
UNIQUE_TO_TRUSTEE,
};
template<typename T>
class CSingleInstance
{
public:
#define DECLARE_UNIQUE_INSTANCE(type, str) \
static const char* GetUniqueInstanceString()\
{ \
return str; \
} \
static int GetUniqueInstanceType()\
{\
return type;\
}
static int GetUniqueInstanceType()
{
return UNIQUE_TO_SYSTEM;
}
static const char* GetUniqueInstanceString()
{
// must return unique string using DECLARE_UNIQUE_INSTANCE
const char buffer[-1];
return buffer;
}
enum {
doKeepOlder,
doKeepYounger,
doCloseAll,
};
int OnInstanceFound(HWND other)
{
return doKeepOlder;
}
BEGIN_MSG_MAP(CSingleInstance<T>)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(GetMessageUINT(), OnPrivateMessage)
MESSAGE_HANDLER(WM_COPYDATA, OnCopyData);
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
END_MSG_MAP()
CSingleInstance()
: locked_(false)
{
hMutex_ = ::CreateMutex(0, 0, GetMutexName());
}
~CSingleInstance()
{
if (locked_) UnLock();
::CloseHandle(hMutex_);
}
static CString GetMutexName()
{
CString mutex_name=T::GetUniqueInstanceString();
int unique_type=T::GetUniqueInstanceType();
switch(unique_type)
{
case UNIQUE_TO_SYSTEM:
break;
case UNIQUE_TO_DESKTOP:
{
DWORD len;
HDESK desktop = ::GetThreadDesktop(GetCurrentThreadId());
BOOL result = ::GetUserObjectInformation(desktop, UOI_NAME, NULL, 0, &len);
DWORD err = ::GetLastError();
if(!result && err == ERROR_INSUFFICIENT_BUFFER)
{ /* NT/2000 */
LPBYTE data = new BYTE[len];
result = GetUserObjectInformation(desktop, UOI_NAME, data, len, &len);
mutex_name += _T("-");
mutex_name += (LPCTSTR)data;
delete[] data;
} /* NT/2000 */
else
{ /* Win9x */
mutex_name += _T("-Win9x");
} /* Win9x */
} break;
case UNIQUE_TO_SESSION:
{ /* сессия */
HANDLE token;
DWORD len;
BOOL result = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token);
if(result)
{ /* NT */
::GetTokenInformation(token, TokenStatistics, NULL, 0, &len);
LPBYTE data = new BYTE[len];
GetTokenInformation(token, TokenStatistics, data, len, &len);
LUID uid = ((PTOKEN_STATISTICS)data)->AuthenticationId;
delete[] data;
CString t;
t.Format(_T("-%08x%08x"), uid.HighPart, uid.LowPart);
mutex_name += t;
}
} break;
case UNIQUE_TO_TRUSTEE:
{ /* пользователь */
#define NAMELENGTH 64
TCHAR userName[NAMELENGTH];
DWORD userNameLength = NAMELENGTH;
TCHAR domainName[NAMELENGTH];
DWORD domainNameLength = NAMELENGTH;
if(GetUserName(userName, &userNameLength))
{ /* получаем сетевое имя */
// Вызовы NetApi очень прожорливы по времени
// Этот метод получает доменное имя из
// переменных окружения
domainNameLength = ExpandEnvironmentStrings(_T("%USERDOMAIN%"),
domainName,
NAMELENGTH);
CString t;
t.Format(_T("-%s-%s"), domainName, userName);
mutex_name += t;
} break;
#undef NAMELENGTH
}
}
return mutex_name;
}
HWND GetOtherInstanceHWND()
{
HWND hOther = static_cast<T*>(this)->m_hWnd;
EnumWindows(DoSearch, (LPARAM)&hOther);
return static_cast<T*>(this)->m_hWnd == hOther ? NULL : hOther;
}
private:
bool Lock(int tmx=0)
{
assert(!locked_);
int result=::WaitForSingleObject(hMutex_, tmx);
return result!=WAIT_TIMEOUT;
}
void UnLock()
{
assert(locked_);
::ReleaseMutex(hMutex_);
}
//////////////////////////////////////////////////////////////////////////
LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL& handled)
{
if (Lock(100))
{
// single instance
int result=static_cast<T*>(this)->OnInstanceFound(0);
if (result==doCloseAll)
{
handled=true;
return -1;
}
}
else
{
HWND hWnd = GetOtherInstanceHWND();
int result=static_cast<T*>(this)->OnInstanceFound(hWnd);
if (result==doKeepOlder)
{
// Restore older
if (::IsIconic(hWnd)) ::ShowWindow(hWnd, SW_RESTORE);
::SetForegroundWindow(hWnd);
handled=true;
return -1;
} else
{
// close older
::PostMessage( GetOtherInstanceHWND(), WM_CLOSE, 0, 0 );
if (result==doCloseAll || !Lock(5000))
{
// didn't close, what to do?
handled=true;
return -1;
}
}
}
handled=false;
return 0;
}
LRESULT OnDestroy(UINT, WPARAM, LPARAM, BOOL& handled)
{
if(locked_) UnLock();
handled=false;
return 0;
}
LRESULT OnPrivateMessage(UINT msg, WPARAM, LPARAM, BOOL&)
{
return msg;
}
LRESULT OnCopyData(UINT, WPARAM, LPARAM lParam, BOOL&)
{
COPYDATASTRUCT* cds=reinterpret_cast<COPYDATASTRUCT*>(lParam);
CString name((char const*)cds->lpData, cds->cbData);
return name==GetMutexName();
}
static UINT GetMessageUINT()
{
return ::RegisterWindowMessage(T::GetUniqueInstanceString());
}
static BOOL CALLBACK DoSearch(HWND hWnd, LPARAM lParam)
{
DWORD result;
HWND *target = (HWND*)lParam;
LRESULT ok = ::SendMessageTimeout(
hWnd, GetMessageUINT(),0,0,
SMTO_BLOCK | SMTO_ABORTIFHUNG, 200,
&result);
if(ok == 0 || hWnd == *target) // ignore
return TRUE;
if(result == GetMessageUINT())
{
// может легально существовать несколько экземпляров.
// надо убедиться, что этот экземпляр использует
// именно наш мьютекс
CString name = GetMutexName();
COPYDATASTRUCT cds = { 0, name.GetLength()+1, name.GetBuffer(1) };
DWORD result;
LRESULT ok = ::SendMessageTimeout(
hWnd, WM_COPYDATA, (WPARAM)0, (LPARAM)(LPVOID)&cds,
SMTO_BLOCK | SMTO_ABORTIFHUNG, 200,
&result);
name.ReleaseBuffer();
if (ok && result)
{
*target = hWnd;
return FALSE;
}
}
return TRUE;
}
private:
HANDLE hMutex_;
bool locked_;
};
Здравствуйте, Аноним, Вы писали:
А>Каким образом можно использовать Ваш код для dialog-based приложений? Генерить ручками WM_CREATE?
Можно добавить вот это:
template<typename T>
class CSingleInstance
{
...
bool IsModalDialog()
{
// m_bModal определяется в CDialogImpl
return static_cast<T*>(this)->m_bModal;
}
...
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
...
LRESULT OnInitDialog(UINT, WPARAM, LPARAM, BOOL& handled)
{
LRESULT res = OnCreate(0, 0, 0, handled);
if (res==-1)
{
// закрываем окно
// не знаю способа отличить модальный диалог от немодального
// поэтому делаю так
if (static_cast<T*>(this)->IsModalDialog())
{
::EndDialog(static_cast<T*>(this)->m_hWnd, 0);
} else {
::DestroyWindow(static_cast<T*>(this)->m_hWnd);
::PostQuitMessage(0);
}
return 0;
}
// продолжаем запуск
handled=false;
return 0;
}
...
};
Здравствуйте, Aera, Вы писали:
A>Здравствуйте, Aera, Вы писали:
A>Полный текст скачать здесь
Спасибо. А кто-нибудь знает как определить модальность диалога, если он является главным окном приложения?
Здравствуйте, Aera, Вы писали:
A>Здравствуйте, Aera, Вы писали:
A>Полный текст скачать здесь
Зачем???
static_cast<T*>(this)->PostCloseMessage( GetOtherInstanceHWND() );
PostCloseMessage — статическая функция.
Здравствуйте, Aera, Вы писали:
A>Полный текст скачать здесь
И это не компилируется
static const char* GetUniqueInstanceString()
{
// must return unique string using DECLARE_UNIQUE_INSTANCE
const char buffer[-1];
return buffer;
}
error C2118: negative subscript
error C2734: 'buffer' : const object must be initialized if not extern
warning C4172: returning address of local variable or temporary
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, Aera, Вы писали:
A>>Полный текст скачать здесь
А>И это не компилируется
А>А> static const char* GetUniqueInstanceString()
А> {
А> // must return unique string using DECLARE_UNIQUE_INSTANCE
А> const char buffer[-1];
А> return buffer;
А> }
А>
Так ведь почти англиским языком написано: нужно в своем классе при помощи макроса DECLARE_UNIQUE_INSTANCE определить строку идентифицирующую ваше приложение, вот таким образом
class MyClass : public CSingleInstance<MyClass>
{
DECLARE_UNIQUE_INSTANCE(UNIQUE_TO_SYSTEM, "{ваш собственный guid}");
}
const char buffer[-1];
эта строка пытается скомпилироваться только если вы не использовали макрос, и, таким образом, вскрывает ошибку.
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, Aera, Вы писали:
A>>Здравствуйте, Aera, Вы писали:
A>>Полный текст скачать здесь
А>Зачем???
А>А>static_cast<T*>(this)->PostCloseMessage( GetOtherInstanceHWND() );
А>
А>PostCloseMessage — статическая функция.
Здесь я Подстроил под свои нужды. Функцию PostCloseMessage можно перекрыть в своем классе и реализовать закрытие окна по-другому. Например, так:
void PostCloseMessage(HWND hWnd)
{
::SendMessage(hWnd, WM_COMMAND, ID_APP_EXIT, 0);
}
Здравствуйте, Aera, Вы писали:
A>Здравствуйте, Аноним, Вы писали:
А>>Здравствуйте, Aera, Вы писали:
A>>>Полный текст скачать здесь
А>>И это не компилируется
А>>А>> static const char* GetUniqueInstanceString()
А>> {
А>> // must return unique string using DECLARE_UNIQUE_INSTANCE
А>> const char buffer[-1];
А>> return buffer;
А>> }
А>>
A>Так ведь почти англиским языком написано: нужно в своем классе при помощи макроса DECLARE_UNIQUE_INSTANCE определить строку идентифицирующую ваше приложение, вот таким образом
A>A>class MyClass : public CSingleInstance<MyClass>
A>{
A> DECLARE_UNIQUE_INSTANCE(UNIQUE_TO_SYSTEM, "{ваш собственный guid}");
A>}
A>
A>A> const char buffer[-1];
A>
A>эта строка пытается скомпилироваться только если вы не использовали макрос, и, таким образом, вскрывает ошибку.
Весь прикол в том, что я именно так и сделал. Но ошибку полуючаю все-равно
А>>>И это не компилируется
А>Весь прикол в том, что я именно так и сделал. Но ошибку полуючаю все-равно
Вот пример который у меня работает на VC6.0. Это у тебя компилируется?
#include <atlbase.h>
#include <atlapp.h>
CAppModule _Module;
#include <atlwin.h>
#include <atlcrack.h>
#include <atlmisc.h>
#include <atlctrls.h>
#include <atlframe.h>
#include <atlctrlw.h>
#include "instance.h"
class MyClass
: public CSingleInstance<MyClass>
{
public:
DECLARE_UNIQUE_INSTANCE(UNIQUE_TO_SYSTEM, "{MY-GUID}");
};
int main()
{
MyClass instance;
instance;
return 0;
}