Чтение онлайн

ЖАНРЫ

Программирование на Visual C++. Архив рассылки

Jenter Алекс

Шрифт:

Не стоит думать, что такая ситуация маловероятна. Представьте себе пользователя, который настроил 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-функцию, обработчик зарегистрированного сообщения… Тоже кривовато, мне не понравилось."

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

AДля начала два замечания. Во-первых, 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.

Интересующимся этой темой я настоятельно рекомендую ознакомиться со статьей 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). Как от этого избавиться?

Михаил

Это все на сегодня. Удачи вам!

Алекс Jenter jenter@mail.ru Красноярск, 2000.

Программирование на Visual C++

Выпуск №27 от 10 декабря 2000 г.

Здравствуйте, уважаемые подписчики!

Я получал достаточно много писем с просьбами рассказать о чем-то конкретном , и в этих просьбах довольно часто встречалась тема доступа к данным из программ с использованием различных технологий – ODBC, DAO, OLE DB. Конечно, тема эта очень обширна и многогранна. Но, тем не менее, программистам с ней приходится сталкиваться довольно часто, и поэтому рассмотрение ее в рассылке кажется оправданным. Я решил, что разумнее всего будет сделать серию статей на эту тему, отдельные заметки из этой серии будут по мере написания появляться в рассылке (но, заметьте, что далеко не в каждом выпуске).

Сейчас я работаю над продолжением статьи про многозадачность. Тема синхронизации потоков думаю будет особенно интересна в свете того обсуждения, которое вызвал вопрос из выпуска №25 (про активизацию уже запущенного экземпляра приложения в случае попытки запуска нового). В дальнейшем нас также ждет очень интересная тема о работе с e-mail.

Когда Александр Шаргин попросил меня перечислить вопросы, интересующие читателей рассылки, то я назвал ему и вопрос доступа к данным. К сегодняшнему дню он закончил работу над первой частью статьи про ODBC: технологии, с которой воистину все начиналось. Думаю, с нее стоит начать и нам.

СТАТЬЯ
Доступ к БД с использованием ODBC
Часть 1

Открытый интерфейс доступа к базам данных (Open Database Connectivity, ODBC) – это программный интерфейс, который позволяет приложению обращаться к различным СУБД, используя структурированный язык запросов SQL. Применяя ODBC, разработчики могут писать программы, независимые от архитектуры конкретной СУБД. Такие программы будут работать с любой реляционной базой данных (как существующей в данный момент, так и той, которая, возможно, появится в будущем), для которой написан ODBC-драйвер.

НЕМНОГО ТЕОРИИ

Структура ODBC

Архитектура ODBC имеет четыре основных компонента: пользовательское приложение, менеджер драйверов ODBC, драйвер, источник данных. Менеджер драйверов написан в виде DLL, которая загружается пользовательским приложением и перенаправляет вызовы функций ODBC API нужному драйверу. Драйвер, в свою очередь, выполняет основную работу по выполнению запросов.

Типичная схема взаимодействия приложения с базой данных состоит из трёх шагов:

Поделиться с друзьями: