Вспоминается одна смешная история. Во времена Word 6.0, работая как-то в одной солидной организации, трепетно относившейся к своим коммерческим тайнам, был приглашен возмущенным соседом по комнате к его компьютеру. На экране красовалось нечто вроде: "Вы не можете открыть этот очень конфиденциальный документ, так как его уже открыл пользователь имярек". Имяреком был назван начальник отдела эксплуатации, сидевший в другом конце здания. Параноидальное сознание сразу стало выстраивать сложную схему шпионажа через локальную сеть, который развернул этот самый начальник. Другая часть мозга, пользуясь невытесняющей многозадачностью, принялась прикидывать размер вознаграждения за его поимку. Короче, помчались разбираться. Через пять минут половина отдела эксплуатации во главе с недоумевающим начальником толклась около компьютера и выдвигала идеи. В итоге оказалось, что никакого шпионажа нет, а ...
  ... а во всем виноваты Word 6.0, невнимательный пользователь и пиратство. Пиратство виновато потому, что так тогда делали все. Word 6.0 инсталлировался с дискет, которые добрый начальник отдела эксплуатации по очереди раздавал всем желающим, так что под его именем рожала документы вся организация. Невнимательный пользователь виноват потому, что редактировал-редактировал документ в одном экземпляре Word'а, а потом взял, да и попытался открыть его из файл-менеджера еще раз. А Word виноват потому, что уж так он тогда обеспечивал взаимодействие нескольких экземпляров приложения (сейчас гораздо лучше).
Больше всех жалко начальника отдела эксплуатации, которому пришлось прилично понервничать. Пользователь-то, которому его заложил глупый Word, был сотрудником отдела безопасности!
  Многозадачность Windows, являясь безусловным благом, не подлежащим ныне обсуждению и сомнению, является вместе с тем источником многих вызовов, бросаемых программисту. Некоторые из них весьма изощренны и ответ на них требует глубокого понимания принципов организации рабочей среды.
Например, при разработке приложения MyCall пришлось столкнуться с такой проблемой. Как известно, Remote Access Service (RAS), работающий в асинхронном режиме, запускается в отдельном потоке (нити), отличном от основного потока приложения. Также не менее хорошо известно, что для контроля процесса установления соединения в RAS используется callback-функция (например, RasDialFunc), которую работающий RAS вызывает при необходимости сообщить приложению о своем состоянии. Так вот, если у программиста имеется потребность передать из RasDialFunc какое-либо пользовательское сообщение окну приложения, то он может побояться воспользоваться для этой цели функцией SendMessage, полагая, что два потока, одновременно запускающие одну и ту же оконную процедуру в одном контексте - это пролог к катастрофе. Между тем такое действие вполне допустимо и безопасно, так как умная Windows, прежде чем передать управление оконной процедуре, проверяет, в каком потоке создавалось соответствующее окно, и при необходимости переключает потоки. При этом передающий поток замораживается на время обработки сообщения принимающим потоком.
  Другие же проблемы многозадачности более тривиальны, даже рутинны. Одна из них встает всякий раз, когда программист начинает работу над новым проектом: что делать в случае попытки запуска приложения, если оно уже запущено?
  В самом деле. Сама по себе Windows не ограничивает возможности пользователя запускать какое-либо приложение любое разумное количество раз. Выполнив два двойных щелчка по одной и той же иконке, пользователь может получить два экземпляра одного и того же приложения, и каждый из них будет функционировать в собственном процессе и собственном контексте так, как будто другого не существует.
Правда, только до той поры, пока они не попытаются разделить между собой какой-нибудь ресурс (в широком смысле), который разделять невозможно или нельзя. Например, невозможно разделять коммуникационный порт. И нельзя разделять общий файл данных, если оба экземпляра приложения пишут в него противоречащие друг другу данные. Разнообразие возникающих в этом случае эффектов может порадовать самого взыскательного искателя развлечений.
  Принятие решения о том, как поступать со вторым и последующими экземплярами приложения, целиком возлагается на программиста. Можно придумать много разных вариантов стратегии поведения приложения, в зависимости от назначения приложения. Среди них такие:
Яркий пример третьей стратегии реализован в Word. Если экземпляр приложения уже запущен, и пользователь выполняет двойной щелчок по какому-нибудь файлу .doc в Проводнике, то запускаемый при этом второй экземпляр Word передает реквизиты загружаемого файла первому экземпляру и завершается, а первый экземпляр загружает файл в новое окно документа.
  С первой стратегией все просто. При запуске приложения ничего делать не надо - и тогда вы сможете наблюдать на экране столько экземпляров вашего приложения, сколько нужно, чтобы получить моральное удовлетворение. Главное - позаботиться, чтобы экземпляры приложения никогда не пытались разделять ресурсы, которые невозможно или нельзя разделять.
  Вторая стратегия несколько сложнее. Очевидно, для ее реализации необходимо, чтобы второй экземпляр приложения каким-то образом узнал о том, что существует первый экземпляр, и завершился, не успев принести какого-нибудь вреда. К сожалению, никакого специального средства для этого не существует.
Поговаривают, что не всегда все было так трагично. Во времена наших предков, когда "Докторская" стоила 2.20, а Билл был еще совсем бедным со своими двумя десятками гигабаксов, заработанных на DOSе, это делалось очень просто. В win16 имел смысл параметр hPrevInstance функции WinMain. Достаточно было убедиться, что он ненулевой - и приложение знало, что оно не одиноко на этом свете. Но когда разрабатывалась win32, кто-то из главных системщиков Microsoft лопухнулся, а потом, когда все всплыло, на специальном совещании было принято решение прикрыть провинившегося и объяснить все произошедшее какими-нибудь благообразными словами, вроде: "Мы так и хотели с самого начала, а hPrevInstance сохранили для совместимости снизу вверх." Так или иначе - но hPrevInstance теперь бесполезен. А следует пользоваться средствами межпроцессного взаимодействия (Interprocess Communications), которые, к чести Microsoft, реализованы в Windows великолепно.
  Способов дать знать второму экземпляру о существовании первого можно придумать много. Один из них прямо дается в MSDN в статье WinMain:
hPrevInstance
Identifies the previous instance of the application. For a Win32-based application, this parameter is always NULL. If you need to detect whether another instance already exists, create a named mutex using the CreateMutex function. If the GetLastError function returns ERROR_ALREADY_EXISTS, another instance of your application exists (it created the mutex).
Идентифицирует предыдущий экземпляр приложения. Для Win32-приложений этот параметр всегда равен NULL. Если вам необходимо определить, существует ли уже экземпляр приложения, создайте именованный объект mutex, используя функцию CreateMutex function. Если функция GetLastError вернет ERROR_ALREADY_EXISTS, другой экземпляр вашего приложения существует (так как он уже создал mutex).
  Вот этой самой идеей и воспользуемся, слегка модифицировав ее:
.386
.Model flat,stdcall
;///////////////////////////////////////////////////////////// windows.inc
OpenMutexA PROTO :DWORD,:DWORD,:DWORD ;прототипы функций API
CreateMutexA PROTO :DWORD,:DWORD,:DWORD
ExitProcess PROTO :DWORD
SYNCHRONIZE =00100000h ;константы типов доступа
STANDARD_RIGHTS_REQUIRED =000f0000h
MUTANT_QUERY_STATE =0001h
MUTANT_ALL_ACCESS =STANDARD_RIGHTS_REQUIRED OR SYNCHRONIZE
OR MUTANT_QUERY_STATE
MUTEX_ALL_ACCESS =MUTANT_ALL_ACCESS
NULL =0 ;разные константы
FALSE =0
TRUE =1
;///////////////////////////////////////////////////////////// WinMain
EXIT_OK =0 ;коды выхода
EXIT_ALREADY_EXIST =1
.const
check_inst_mutex_name db "Check Instant Mutex 0001",0 ;имя mutex
.data?
check_inst_mutex dd ? ;дескриптор mutex
.code
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
WinMain PROC PUBLIC hinst,prev_hinst,command_line,cmd_show
;................................................. проверка повторного запуска
mov check_inst_mutex,0
invoke OpenMutexA,MUTEX_ALL_ACCESS,FALSE,offset check_inst_mutex_name
.if(!eax) ;если mutex не существует - создать его и запустить приложение
invoke CreateMutexA,NULL,TRUE,offset(check_inst_mutex_name)
mov check_inst_mutex,eax
.else ;если mutex существует - прервать работу приложения
invoke ExitProcess,EXIT_ALREADY_EXIST
.endif
;......................................................... тело приложения
;...
;... ;весь остальной текст приложения
;...
;......................................................... завершение работы
.if(check_inst_mutex!=0) ;уничтожить объект mutex
invoke ReleaseMutex,check_inst_mutex
.endif
invoke ExitProcess,EXIT_OK
WinMain ENDP
;#############################################################
end
  Как видим, логика работы очень проста. Первым делом пытаемся открыть mutex с заданным именем.
Если вы - автор многих разных приложений, имейте в виду, что одинаковое имя mutex'а позволит запускаться только одному из этих приложений. Если ваше чувство юмора не столь развито, не забывайте модифицировать имя mutex'а в разных приложениях.
  Если mutex открыть не удается, значит запускаемый нами экземпляр приложения уникален, и мы смело можем продолжат его работу. Только перед этим создадим mutex, чтобы не дать запускаться последующим экземплярам.
  Ну и, конечно, при завершении работы приложения следует не забыть вызвать функцию ReleaseMutex.
В принципе, это не обязательно, так как если не освободить mutex с помощью этой функции, по завершении работы приложения он переводится в состояние WAIT_ABANDONED, которое в данном случае не мешает достижению стоящей перед нами задачи. Однако хороший стиль программирования предполагает явное освобождение занимаемых приложением ресурсов.
А вот Сергей AKA The Byte Reaper (http://neptunix.narod.ru) совершенно справедливо обратил наше внимане на то, что приведенный выше пример реализации второй стратегии сильно перегружен формальностями и реверансами в сторону MSDN. Полностью сохраняет ту же функциональность, но гораздо приятнее взору настоящего ассемблерщика предложенный Сергеем вот какой вариант:Идея проста предельно. Если создаваемый mutex уже присутствует в системе, то функция GetLastError вовращает код ERROR_ALREADY_EXISTS. Кстати, функция CreateMutex возвращает при этом вполне валидный дескриптор существующего mutex'а, но нас это совершенно не должно интересовать. Мы просто заканчиваем работу приложения - и все.;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= invoke CreateMutexA,NULL,0,offset(check_inst_mutex_name) invoke GetLastError .if(eax==ERROR_ALREADY_EXISTS) invoke ExitProcess,EXIT_ALREADY_EXIST .endif ;=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  Третья стратегия существенно сложнее в реализации, а гамма возможных решений намного больше. Очевидно, ее основой должно быть не только взаимное оповещение экземплярами приложения о существовании друг друга, но и обмен между ними какой-то информацией. Все зависит от того, какой объем информации потребуется. Если это одно-два двойных слова, то может оказаться достаточным использовать систему сообщений. В других случаях имеет смысл, например, организовать разделяемую память посредством File Mapping.
  Пример реализации третьей стратегии, использующий только систему оконных сообщений, можно найти в приложении MyCall. Там используется регистрируемое глобальное сообщение, которое приложение передает в широковещательном режиме в надежде, что уже запущенный экземпляр ответит ему тем же сообщением. Если такой ответ получен, то делается вывод, что один экземпляр приложения уже запущен, и запускаемый экземпляр завершает работу. Но перед этим он извлекает из ответного сообщения дескриптор главного окна работающего экземпляра и выводит это окно на первый план, чтобы удовлетворить потребность пользователя в доступе к приложению.
  Здесь мы приведем версию, выполняющую ту же задачу, но несколько отличную в реализации. По сути, она является гибридом из описанной выше и примененной в MyCall: для определения факта повторного запуска приложения используется mutex, а для передачи дескриптора окна работающего приложения вновь запускаемому - глобальное сообщение.
  Это полностью работающее приложение. В его основу положен шаблон оконного приложения. Содержимое файла windows.inc не приводится (то, что он должен собой представлять, показано в предыдущем примере. Недостающие элементы следует добавить самостоятельно, по аналогии, пользуясь заголовочными файлами для C/C++).
.386
.Model flat,stdcall
include windows.inc ;заголовочный файл API
;///////////////////////////////////////////////////////////// WinMain
EXIT_OK =0 ;коды выхода
EXIT_ERROR =1
EXIT_ALREADY_EXIST =2
QUESTION_PRIME_HWND =1 ;запрос идентификатора окна
ANSWER_PRIME_HWND =2 ;ответ идентификатора окна
.data?
win_class WNDCLASSEXA{} ;структура класса главного окна
main_window dd ? ;дескриптор главного окна
check_inst_mutex dd ? ;дескриптор mutex'а
second_instance dd ? ;признак второго экземпляра
check_inst_message dd ? ;идентификатор регистрируемого сообщения
loop_message MSG{} ;сообщение для цикла обработки
.const
app_name db "Single Instance Application",0
class_name db "Single Instance Application Class",0
check_inst_mutex_name db "Check Instant Mutex 0001",0
check_inst_message_name db "Check Instant Message 0001",0
.code
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
WinMain PROC PUBLIC hinst,prev_hinst,command_line,cmd_show
;................................ регистрация класса окна
mov win_class.cbSize,sizeof(WNDCLASSEXA)
mov win_class.style,CS_HREDRAW OR CS_VREDRAW
mov win_class.lpfnWndProc,offset win_procedure
mov win_class.cbClsExtra,0
mov win_class.cbWndExtra,0
invoke GetModuleHandleA,NULL
mov win_class.hInstance,eax
invoke LoadIconA,NULL,IDI_APPLICATION
mov win_class.hIcon,eax
mov win_class.hIconSm,eax
invoke LoadCursorA,NULL,IDC_ARROW
mov win_class.hCursor,NULL
mov win_class.hbrBackground,COLOR_WINDOWFRAME
mov win_class.lpszMenuName,NULL
mov win_class.lpszClassName,offset class_name
invoke RegisterClassExA,offset win_class
.if(!eax)
jmp abort
.endif
;....................... .......... создание окна
invoke CreateWindowExA,NULL,\
offset class_name,\
offset app_name,\
WS_OVERLAPPEDWINDOW,\
100,100,300,200,\
NULL,\
NULL,\
win_class.hInstance,\
NULL
.if(!eax)
jmp abort
.endif
mov main_window,eax
;................................. зарегистрировать глобальное сообщение
mov check_inst_message,0
invoke RegisterWindowMessageA,offset check_inst_message_name
.if(eax)
mov check_inst_message,eax
.endif
;.................................. проверить повторный запуск
mov check_inst_mutex,0
invoke OpenMutexA,MUTEX_ALL_ACCESS,FALSE,offset check_inst_mutex_name
.if(!eax)
mov second_instance,FALSE
invoke CreateMutexA,NULL,TRUE,offset check_inst_mutex_name
mov check_inst_mutex,eax
.else
mov second_instance,TRUE
.if(check_inst_message)
invoke SendMessageA,HWND_BROADCAST,check_inst_message,
QUESTION_PRIME_HWND,NULL
.endif
invoke ExitProcess,EXIT_ALREADY_EXIST
.endif
;.................................. вывод окна на экран
invoke ShowWindow,main_window,SW_SHOW
;................................... инициализация
;здесь можно вставить все необходимые действия для первичной инициализации
;приложения (например, загрузку и разбор файла настроек)
;......................................... цикл опроса очереди сообщений
msg_loop:
invoke GetMessageA,offset loop_message,NULL,0,0
.if(!eax) ;завершение работы
.if(check_inst_mutex!=0) ;освободить mutex
invoke ReleaseMutex,check_inst_mutex
.endif
invoke ExitProcess,loop_message.wParam
.endif
invoke TranslateMessage,offset loop_message
invoke DispatchMessageA,offset loop_message
jmp msg_loop
abort:
invoke ExitProcess,EXIT_ERROR
WinMain ENDP
;/////////////////////////////////////////////////////////// Оконная процедура
win_procedure PROC hwnd,message,w_param,l_param
mov eax,message
;................................. обработка регистрируемого сообщения
.if(eax==check_inst_message) ;если получено зарегистрированное сообщение
.if(check_inst_message)
.if(second_instance) ;если это второй экземпляр
.if(w_param==ANSWER_PRIME_HWND) ;если в сообщении получен дескриптор окна
invoke SetForegroundWindow,l_param ;вывести это окно на первый план
jmp worked
.endif
.else ;если это первый экземпляр
.if(w_param==QUESTION_PRIME_HWND) ;если запрашивается дескриптор окна
mov eax,hwnd ;послать ему дескриптор главного окна
.if(eax==main_window)
invoke SendMessageA,HWND_BROADCAST,check_inst_message,
ANSWER_PRIME_HWND,main_window
jmp worked
.endif
.endif
.endif
.endif
jmp noworked
;............................................ закрытие приложения
.elseif(eax==WM_DESTROY)
invoke PostQuitMessage,EXIT_OK
jmp worked
;............................................ обработка остальных сообщений
; .elseif(message==...)
; ...
; .elseif(message==...)
; ...
; .elseif(message==...)
; ...
;.............................. ............. сообщение не обработано
.else
noworked:
invoke DefWindowProcA,hwnd,message,w_param,l_param
ret
;............................................ сообщение обработано
.endif
worked:
xor eax,eax
ret
win_procedure ENDP
;#############################################################
end
  Общая идея такова:
  См. также интересный, эффективный и простой вариант решения рассматриваемой задачи, предложенный Геннадием Майко.
[C] Svet(R)off