[WTL] CSingleInstance<>
От: Aera Беларусь  
Дата: 21.11.05 13:15
Оценка: 16 (2)
Здравствуйте,

Вспомогательный класс, позволяющий автоматически гарантировать существование единственного экземпляра программы.
Написано на основе статьи: 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_;
};
--
RedApe
Re: [WTL] CSingleInstance<>
От: Аноним  
Дата: 19.01.06 09:59
Оценка:
Здравствуйте, Aera

Каким образом можно использовать Ваш код для dialog-based приложений? Генерить ручками WM_CREATE?
Re[2]: Re: [WTL] CSingleInstance<>
От: Aera Беларусь  
Дата: 19.01.06 10:39
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Каким образом можно использовать Ваш код для 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;
  }
   ...
};
--
RedApe
Re[3]: [WTL] CSingleInstance<>
От: Aera Беларусь  
Дата: 19.01.06 10:46
Оценка:
Здравствуйте, Aera, Вы писали:

Полный текст скачать здесь
--
RedApe
Re[4]: [WTL] CSingleInstance<>
От: Аноним  
Дата: 20.01.06 08:11
Оценка:
Здравствуйте, Aera, Вы писали:

A>Здравствуйте, Aera, Вы писали:


A>Полный текст скачать здесь


Спасибо. А кто-нибудь знает как определить модальность диалога, если он является главным окном приложения?
Re[4]: [WTL] CSingleInstance<>
От: Аноним  
Дата: 12.02.06 19:40
Оценка:
Здравствуйте, Aera, Вы писали:

A>Здравствуйте, Aera, Вы писали:


A>Полный текст скачать здесь


Зачем???
static_cast<T*>(this)->PostCloseMessage( GetOtherInstanceHWND() );

PostCloseMessage — статическая функция.
Re[5]: [WTL] CSingleInstance<>
От: Аноним  
Дата: 12.02.06 20:06
Оценка:
Здравствуйте, 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
Re[6]: [WTL] CSingleInstance<>
От: Aera Беларусь  
Дата: 13.02.06 06:50
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, 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];

эта строка пытается скомпилироваться только если вы не использовали макрос, и, таким образом, вскрывает ошибку.
--
RedApe
Re[5]: [WTL] CSingleInstance<>
От: Aera Беларусь  
Дата: 13.02.06 06:54
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, Aera, Вы писали:


A>>Здравствуйте, Aera, Вы писали:


A>>Полный текст скачать здесь


А>Зачем???

А>
А>static_cast<T*>(this)->PostCloseMessage( GetOtherInstanceHWND() );
А>

А>PostCloseMessage — статическая функция.

Здесь я Подстроил под свои нужды. Функцию PostCloseMessage можно перекрыть в своем классе и реализовать закрытие окна по-другому. Например, так:
void PostCloseMessage(HWND hWnd)
{
  ::SendMessage(hWnd, WM_COMMAND, ID_APP_EXIT, 0);
}
--
RedApe
Re[7]: [WTL] CSingleInstance<>
От: Аноним  
Дата: 16.02.06 04:22
Оценка:
Здравствуйте, 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>эта строка пытается скомпилироваться только если вы не использовали макрос, и, таким образом, вскрывает ошибку.

Весь прикол в том, что я именно так и сделал. Но ошибку полуючаю все-равно
Re[8]: [WTL] CSingleInstance<>
От: Aera Беларусь  
Дата: 16.02.06 07:02
Оценка:
А>>>И это не компилируется
А>Весь прикол в том, что я именно так и сделал. Но ошибку полуючаю все-равно

Вот пример который у меня работает на 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;
}
--
RedApe
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.