Вход/Регистрация
Программирование на Visual C++. Архив рассылки
вернуться

Jenter Алекс

Шрифт:

На этот вопрос пришло просто огромное количество ответов, и разных. Отдельное спасибо хочу сказать Андрею Твердохлебову, который прислал ссылку на действительно замечательную статью на эту тему, которая позволила многое прояснить. Именно в ней приводится по-настоящему правильный и надежный способ.

К сожалению, действительно хороших ответов, несмотря на большое общее количество, получилось довольно мало. Многие удовольствовались в ответе решением задачи ограничения числа запущенных экземпляров до одного, совершенно забывая о том, что задача еще и активизировать уже запущенный экземпляр.

A1 Относительно вопроса, заданного в №25 данной рассылки хотелось бы сразу высказать сильное сомнение по поводу возможности его решения при помощи использования имен оконных классов. Мне кажется, что каждый экземпляр приложения в операционной системе Windows имеет, как это не пародоксально звучит, свой набор зарегистрированных оконных классов. Общность стандартных (системных) оконных классов поддерживается автоматической загрузкой в адресное пространство системных библиотек, которые, в момент своей инициализации, регистрируют свои оконные классы. Список DLL (только они позволяют делать общедоступными определенные виды окон), подгружаемых автоматом каждому приложению, хранится где-то в реестре (не помню точно где). Еще одним доводом в пользу предположения об уникальности списка зарегистрированных оконных классов каждого приложения (экземпляра приложения) служит сама процедура RegisterClass(Ex). В качестве аргумента данной процедуры выступает указатель на структуру, одним из элементов которой является указатель (адрес) на оконную процедуру. Нет 100% гарантии того, что разные DLL проекцируются в одно и то же адресное пространство всех приложений без исключений. Следовательно, адрес оконной процедуры перестает нести смысловую нагрузку. Из изложенного выше, позвольте сделать вывод: если в результате регистрации приложением оконного класса произошла ошибка типа "Оконный класс с указанным именем уже существует" это означает лишь то, что именно ЭТО приложение уже зарегистрировало подобный класс. И наоборот, если регистрация прошла успешно, то это не значит, что нет такого приложения (экземпляра приложения) которое не зарегистрировало бы оконный класс с подобным именем. Следовательно подобный подход при решении подобной проблемы невозможен.

Решить указанную проблему можно лишь при помощи объектов ядра операционной системы, объектов файловой системы (так, по моему, решаются подобные задачи в OS типа Linux/Unix) и некоторых других объектов (типа mailslot, TCP ports и наверное можно придумать что либо еще). Важно выполнение следующих условий:

1. Объекты должны быть доступны из различных приложений

2. Объекты должны быть одинаковым образом идентефицированы для всех приложений (идет отказ от дескрипторов объектов, которые актуальны только в рамках одного процесса).

3. Желательно (а может обязательно?), что бы OS поддерживала синхронизацию доступа к данным объектам.

Самый простой способ идентефикации объектов заключается в присвоении им строковых имен. Такой способ применяестся к объектам ядра, файловой системы, mailslot. TCP способ использует разименовку по номеру порта. И имя и номер имеют одно и то же значение для всех приложений. Для функции RegisterClass(Ex), похоже, не выполняется первое условие. Способ определения повторного запуска экземпляра приложения давно известен и использует объект ядра системы типа "mutex". В задаче, кроме того, требуется подать сигнал активизации первому экземпляру приложения. Пришлось модифицировать известный способ, попутно решив эту проблему для себя, и использовать объект ядра типа "event". В общем, принцип работы схемы выглядит так:

1. Попытка получить доступ к объекту ядра по имени

1.1. Доступ получить не удалось из-за отстутствия объекта с указанным именем – данный экземпляр приложения первый. Переходим к п. 2

1.2. Доступ получен – большая вероятность того что данное приложение запущено во второй раз. Почему не 100% уверенность? Делаем скидку на то, что кто то другой мог выбрать для своих нужд именно этот тип объектов и именно с этим именем :). Переходим на п. 3

2. Инициализация и активация главного окна

2.1. Создаем объект ядра с именем, использованным в п. 1

2.2. Инициализируем и запускаем приложение

2.3. Время от времени проверяем поступление сигнала о запуске второй копии приложения

3. Передача сигнала первому приложению о запуске второй копии и выход из программы.

Владимир Голенкевич

Очень подробный и интересный ответ, но к сожалению не совсем корректный. Насчет классов – действительно, они доступны только внутри зарегистрировавшего их процесса (в MSDN есть хорошая статья "Window Classes in Win32" by Kyle Marsh). Однако я не совсем согласен с логическими построениями автора (или понял их неправильно). Т.к. имя класса по идее уникально, то естественно, что ИМЕННО ЭТО приложение зарегистрировало класс. А как иначе?..

Вопросы вызывают еще два момента. Во первых, при создании объектов ядра НИ В КОЕМ СЛУЧАЕ не нужно сначала проверять, существует ли такой объект (п.1). Иначе можно нарваться на т.н. race conditions, описанные в вышеупомянутой статье – ситуация, когда два экземпляра стартуют почти одновременно. Получается, что одна копия проверяет, что объект не существует, и создает его. Но прежде чем она его создаст, вторая копия тоже убеждается в том, что объекта еще нет, и тоже собирается со спокойной совестью его создать. В результате первая копия успевает создать объект, а второй копии создать объект так и не удается, но это уже не важно, т.к. вторая копия все равно запускается.

Не стоит думать, что такая ситуация маловероятна. Представьте себе пользователя, который настроил Windows запускать программы по одному щелчку на ярлыке, но по привычке делает double-click…

Весь смысл объектов ядра как раз в том, что при их создании ГАРАНТИРУЕТСЯ, что никто другой в это же время не сможет создать такой же объект. Нужно сразу пытаться СОЗДАТЬ объект – и если эта операция не удается (возвращается ERROR_ALREADY_EXISTS или ERROR_ACCESS_DENIED) – вот тогда можно с уверенностью говорить о том, что запущена еще одна копия.

Во-вторых, мне не совсем понятны пункты 2.3 и 3. Мне кажется это очень неэфективным – постоянно проверять на наличие сигнала от второй копии (как я понимаю, по этому сигналу приложение должно себя активизировать). Есть способы гораздо лучше (читайте ниже).

Но (заметьте!) мы выяснили очень важную вещь: использование объектов ядра абсолютно необходимо для определения, запущена ли копия приложения или нет.

A2 Можно например с помощью RegisterWindowMessage.

В двух словах:

1. Регистрируем сообщение.

2. Отправляем его на HWND_BROADCAST с каким нибудь кодом в wParam, (например 1) и своим hWnd в lParam (чтобы получатель знал, куда отправлять ответ)

3. Пишем обработчик нашего зарегистрированного сообщения. Он анализирует wParam, если там 1 и lParam не равен собственному hWnd, то он отсылает в ответ такое же сообщение но с кодом 2 например.(отправителя мы получили через lParam)

4. Если мы получили сообщение с кодом 2 в wParam значит уже есть запущенная копия приложения.

Pavlik Yatsuk

Если к ответу добавить механизм объектов ядра, то получается вариант правильный… на первый взгляд. Вот что говорит об этом способе Александр Шаргин:

"Я отказался от этого подхода, и вот почему […] Посылая сообщение с параметром HWND_BROADCAST, мы теряем доступ к возвращаемому в ответ значению. А значит, уже запущенная копия нашего приложения (если таковая есть) должна ответить также посылкой сообщения. Вопрос: кому его посылать? Главное окно во второй копии приложения ещё не создано, цикла сообщений нет… Выход один: создавать невидимое окно, и ловить в нём сообщение — кривовато…

Вариант второй: не использовать HWND_BROADCAST, а сделать EnumWindows и посылать сообщение каждому окну в отдельности. А значит писать свою CALLBACK-функцию, обработчик зарегистрированного сообщения… Тоже кривовато, мне не понравилось."

(Кстати, вариант второй как раз используется в статье;) А вот и сам его ответ:

A3 Для начала два замечания. Во-первых, CDialog таки наследует функцию PreCreateWindow от своего предка – класса CWnd. Другой вопрос, что эта функция не вызывается в процессе создания диалогового окна. Во-вторых, MFC не регистрирует класс диалогового окна, оставляя имя, предопределённое в Windows. Вместо этого MFC передаёт адрес своей собственной диалоговой функции (AfxDlgProc) при вызове CreateDialogIndirect.

Итак, мы установили, что диалоговое окно создаётся в функции CreateDialogIndirect. Мы не можем повлиять на процесс создания окна, а значит не можем и изменить имя класса. Придётся искать обходные пути. Самый простой из них, на мой взгляд – дать диалогу "во владение" невидимое окно, для которого заголовок и имя класса известны. Затем можно найти это окно с помощью FindWindow, переместиться к самому диалогу через GetWindow и сделать на него SetForegroundWindow.

Вот фрагмент функции InitInstance, который делает всё необходимое (использование статических переменных выглядит несколько коряво – я использовал их, чтобы весь код был в одном месте, но в реальной программе лучше сделать их членами класса).

BOOL CMyApp::InitInstance {

 …

 HWND hWnd = FindWindow("{4C1D4220-C3E5-11d4-93A8-B5D00D46136A}", NULL);

 if (hWnd != NULL) {

hWnd = GetWindow(hWnd, GW_OWNER);

SetForegroundWindow(hWnd);

return FALSE;

 }

 WNDCLASS wc;

 ZeroMemory(&wc, sizeof(wc));

 wc.hInstance = AfxGetInstanceHandle;

 wc.lpfnWndProc = DefWindowProc;

 wc.lpszClassName = "{4C1D4220-C3E5-11d4-93A8-B5D00D46136A}";

 RegisterClass(&wc);

 static CMyDlg dlg;

 m_pMainWnd = &dlg;

 dlg.Create(IDD_MY_DIALOG, NULL);

 static CWnd wndDummy;

 wndDummy.CreateEx(0, "{4C1D4220-C3E5-11d4-93A8-B5D00D46136A}", "", 0, CRect(0,0,0,0), &dlg, 0);

 …

 return TRUE;

}

Обратите внимание на использование GUIDа в качестве имени класса. Он получен с помощью утилиты Guidgen (меню Tools). Вероятность того, что в системе найдутся окна с таким классом, не имеющие отношения к нашей программе, представляется ничтожно малой.

Александр Шаргин

А вот если к этому ответу добавить механизм mutex'ов, то получится действительно корректный способ.

Хочу обратить ваше внимание на один факт, присутствующий в обоих предыдущих ответах. Функция активизации уже запущенной копии целиком возлагается именно на вторую копию. Многие предлагали посылать первой копии сообщение, чтобы она воостановилась сама. Это в общем случае не работает (т.е. работает не во всех системах), из-за того, что приложение не может активизировать свое главное окно, если само не активно, и при этом не помогают ни BringWindowToTop, ни SetForegroundWindow.

  • Читать дальше
  • 1
  • ...
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • ...

Ебукер (ebooker) – онлайн-библиотека на русском языке. Книги доступны онлайн, без утомительной регистрации. Огромный выбор и удобный дизайн, позволяющий читать без проблем. Добавляйте сайт в закладки! Все произведения загружаются пользователями: если считаете, что ваши авторские права нарушены – используйте форму обратной связи.

Полезные ссылки

  • Моя полка

Контакты

  • chitat.ebooker@gmail.com

Подпишитесь на рассылку: