Алекс Jenter - Программирование на Visual C++. Архив рассылки
- Название:Программирование на Visual C++. Архив рассылки
- Автор:
- Жанр:
- Издательство:неизвестно
- Год:неизвестен
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Алекс Jenter - Программирование на Visual C++. Архив рассылки краткое содержание
РАССЫЛКА ЯВЛЯЕТСЯ ЧАСТЬЮ ПРОЕКТА RSDN, НА САЙТЕ КОТОРОГО ВСЕГДА МОЖНО НАЙТИ ВСЮ НЕОБХОДИМУЮ РАЗРАБОТЧИКУ ИНФОРМАЦИЮ, СТАТЬИ, ФОРУМЫ, РЕСУРСЫ, ПОЛНЫЙ АРХИВ ПРЕДЫДУЩИХ ВЫПУСКОВ РАССЫЛКИ И МНОГОЕ ДРУГОЕ.
Программирование на Visual C++. Архив рассылки - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Самый простой способ идентефикации объектов заключается в присвоении им строковых имен. Такой способ применяестся к объектам ядра, файловой системы, 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. Мне кажется это очень неэфективным – постоянно проверять на наличие сигнала от второй копии (как я понимаю, по этому сигналу приложение должно себя активизировать). Есть способы гораздо лучше (читайте ниже).
Но (заметьте!) мы выяснили очень важную вещь: использование объектов ядра абсолютно необходимо для определения, запущена ли копия приложения или нет.
A 2 Можно например с помощью 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-функцию, обработчик зарегистрированного сообщения… Тоже кривовато, мне не понравилось."
(Кстати, вариант второй как раз используется в статье;) А вот и сам его ответ:
A 3 Для начала два замечания. Во-первых, 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). Вероятность того, что в системе найдутся окна с таким классом, не имеющие отношения к нашей программе, представляется ничтожно малой.
Читать дальшеИнтервал:
Закладка: