Программирование на Visual C++. Архив рассылки
Шрифт:
Не стоит думать, что такая ситуация маловероятна. Представьте себе пользователя, который настроил 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 значит уже есть запущенная копия приложения.
Если к ответу добавить механизм объектов ядра, то получается вариант правильный… на первый взгляд. Вот что говорит об этом способе Александр Шаргин:
"Я отказался от этого подхода, и вот почему […] Посылая сообщение с параметром HWND_BROADCAST, мы теряем доступ к возвращаемому в ответ значению. А значит, уже запущенная копия нашего приложения (если таковая есть) должна ответить также посылкой сообщения. Вопрос: кому его посылать? Главное окно во второй копии приложения ещё не создано, цикла сообщений нет… Выход один: создавать невидимое окно, и ловить в нём сообщение — кривовато…
Вариант второй: не использовать HWND_BROADCAST, а сделать EnumWindows и посылать сообщение каждому окну в отдельности. А значит писать свою CALLBACK-функцию, обработчик зарегистрированного сообщения… Тоже кривовато, мне не понравилось."
(Кстати, вариант второй как раз используется в статье;) А вот и сам его ответ:
A3 Для начала два замечания. Во-первых, CDialog таки наследует функцию PreCreateWindow от своего предка – класса CWnd. Другой вопрос, что эта функция не вызывается в процессе создания диалогового окна. Во-вторых, MFC не регистрирует класс диалогового окна, оставляя имя, предопределённое в Windows. Вместо этого MFC передаёт адрес своей собственной диалоговой функции (AfxDlgProc) при вызове CreateDialogIndirect.
Итак, мы установили, что диалоговое окно создаётся в функции CreateDialogIndirect. Мы не можем повлиять на процесс создания окна, а значит не можем и изменить имя класса. Придётся искать обходные пути. Самый простой из них, на мой взгляд – дать диалогу "во владение" невидимое окно, для которого заголовок и имя класса известны. Затем можно найти это окно с помощью FindWindow, переместиться к самому диалогу через GetWindow и сделать на него SetForegroundWindow.
Вот фрагмент функции InitInstance, который делает всё необходимое (использование статических переменных выглядит несколько коряво – я использовал их, чтобы весь код был в одном месте, но в реальной программе лучше сделать их членами класса).
Обратите внимание на использование GUIDа в качестве имени класса. Он получен с помощью утилиты Guidgen (меню Tools). Вероятность того, что в системе найдутся окна с таким
классом, не имеющие отношения к нашей программе, представляется ничтожно малой.А вот если к этому ответу добавить механизм mutex'ов, то получится действительно корректный способ.
Хочу обратить ваше внимание на один факт, присутствующий в обоих предыдущих ответах. Функция активизации уже запущенной копии целиком возлагается именно на вторую копию. Многие предлагали посылать первой копии сообщение, чтобы она воостановилась сама. Это в общем случае не работает (т.е. работает не во всех системах), из-за того, что приложение не может активизировать свое главное окно, если само не активно, и при этом не помогают ни BringWindowToTop, ни SetForegroundWindow.
Интересующимся этой темой я настоятельно рекомендую ознакомиться со статьей by Joseph M. Newcomer, где подробно разбираются достоинства и, главное, недостатки, каждого метода. А методов, помимо рассмотренных выше, очень много, напр. file mapping, shared variable и др. (я не стал публиковать эти ответы т.к. все эти объекты используются с одной целью, которая отлично решается с помощью mutex'ов).
Некоторые ссылались на статью в MSDN Q109175 – так вот: там используется некорректное решение!
Если вдруг кто-нибудь, кто прислал мне ответ, все еще считает его 100% правильным, прошу написать мне об этом – я никого обидеть не хотел, а в таком большом количестве ответов было легко что-то упустить. И еще: у кого есть какие соображения по этому поводу, замечания – пишите! Дискуссия получается на редкость интересная.
Q Есть диалог на нем Date Time Picker и есть соответствующая ему переменная m_Time типа CTime. Проблема в том, что если m_Time = 0, то в диалоге высвечивается 2:00:00!!?? Т.е. сдвиг на два часа. Причем если выставить 0:00:00, то будет "Assertion fault". Ну и соответственно, если установить 2:00:00, то после UpdateData m_Time станет = 0. Скорее всего это как-то связано с часовым поясом (у меня часовой пояс +02:00). Как от этого избавиться?
Это все на сегодня. Удачи вам!
Программирование на Visual C++
Выпуск №27 от 10 декабря 2000 г.
Здравствуйте, уважаемые подписчики!
Я получал достаточно много писем с просьбами рассказать о чем-то конкретном , и в этих просьбах довольно часто встречалась тема доступа к данным из программ с использованием различных технологий – ODBC, DAO, OLE DB. Конечно, тема эта очень обширна и многогранна. Но, тем не менее, программистам с ней приходится сталкиваться довольно часто, и поэтому рассмотрение ее в рассылке кажется оправданным. Я решил, что разумнее всего будет сделать серию статей на эту тему, отдельные заметки из этой серии будут по мере написания появляться в рассылке (но, заметьте, что далеко не в каждом выпуске).
Сейчас я работаю над продолжением статьи про многозадачность. Тема синхронизации потоков думаю будет особенно интересна в свете того обсуждения, которое вызвал вопрос из выпуска №25 (про активизацию уже запущенного экземпляра приложения в случае попытки запуска нового). В дальнейшем нас также ждет очень интересная тема о работе с e-mail.
Когда Александр Шаргин попросил меня перечислить вопросы, интересующие читателей рассылки, то я назвал ему и вопрос доступа к данным. К сегодняшнему дню он закончил работу над первой частью статьи про ODBC: технологии, с которой воистину все начиналось. Думаю, с нее стоит начать и нам.
Открытый интерфейс доступа к базам данных (Open Database Connectivity, ODBC) – это программный интерфейс, который позволяет приложению обращаться к различным СУБД, используя структурированный язык запросов SQL. Применяя ODBC, разработчики могут писать программы, независимые от архитектуры конкретной СУБД. Такие программы будут работать с любой реляционной базой данных (как существующей в данный момент, так и той, которая, возможно, появится в будущем), для которой написан ODBC-драйвер.
НЕМНОГО ТЕОРИИ
Структура ODBC
Архитектура ODBC имеет четыре основных компонента: пользовательское приложение, менеджер драйверов ODBC, драйвер, источник данных. Менеджер драйверов написан в виде DLL, которая загружается пользовательским приложением и перенаправляет вызовы функций ODBC API нужному драйверу. Драйвер, в свою очередь, выполняет основную работу по выполнению запросов.
Типичная схема взаимодействия приложения с базой данных состоит из трёх шагов: