Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Начинающим";
Текущий архив: 2011.01.23;
Скачать: [xml.tar.bz2];

Вниз

Запрет запуска второго экземпляра приложения   Найти похожие ветки 

 
Германн ©   (2010-10-30 01:39) [0]

с переключением на первый экземпляр.
Обнаружил что старый добрый SetForegroundWindow нынче не работает. Пошерстил в поисковиках, нашел кучу вариантов. Но в самых свежих сообщениях, которые я нашел, все эти варианты называются "хаками" и не дающими гарантии. Насколько смог уразуметь сейчас самым прямым путем является использование функции AllowSetForegroundWindow. Вот сейчас сижу и пытаюсь найти наиболее красивое решение. Этой функции нужен ProcessID. Искать его перебором как-то не красиво. Процесс то мой собственный. Кроме того функции SetForegroundWindow нужен Handle окна того процесса. Опять искать?
А что если использовать для данной задачи CreateFileMapping? И в этом файле передавать и PID и Handle?
P.S. Давно уже так близко кWinAPI не подходил. Дрожат коленки. :(
P.P.S. Пока сам попробовать не могу. Вчерашние эксперименты привели к необходимости переустановки Д2007.


 
Германн ©   (2010-10-30 01:51) [1]

Хм. AllowSetForegroundWindow оказывается в Д2007 не описана.
В хелпе присутствует, в исходниках отсутствует. Значит нужно её ещё и ручками вытаскивать из user32.dll. Ну совсем засада! :(


 
0x00FF00 ©   (2010-10-30 16:07) [2]

> Германн ©   (30.10.10 01:39)

http://www.wasm.ru/article.php?article=1005015
Вот здесь весьма оригинальный подход к проблеме — разделяемый сегмент памяти.
Держим в нём две переменные, при инициализации выставляемые в нуль.
При запуске проверяем — действительно ли нули? Если да, значит ура, мы первые. Если нет — читаем, выставляем в AllowSetЧегоТоТам, завершаемся.
Дополнительного кода, как с мапированными файлами, не нужно, просто проверка на равенство 0.


 
0x00FF00 ©   (2010-10-30 16:37) [3]

https://forums.embarcadero.com/message.jspa?messageID=181022#181022
Вот так должно на Delphi выглядеть создание SHARED-сегмента, по мнению экспертов Embarcadero (в самом начале, где #pragma).
Далее, с этими переменными можно работать, как с обычными.


 
0x00FF00 ©   (2010-10-30 17:01) [4]

И, да: PID не нужно передавать, если уже есть Handle окна.
PID можно узнать, имея лишь Handle, с помощью GetWindowThreadProcessId:
http://msdn.microsoft.com/en-us/library/ms633522(VS.85).aspx

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


 
Германн ©   (2010-10-30 22:43) [5]


> 0x00FF00 ©

Спасибо! Буду иметь в виду такой вариант.


 
DVM ©   (2010-10-30 23:09) [6]


> Германн ©

я всегда пользуюсь таким примерно кодом:


function ForceForegroundWindow(hWnd: HWND): BOOL;
const
 SPI_GETFOREGROUNDLOCKTIMEOUT = $2000;
 SPI_SETFOREGROUNDLOCKTIMEOUT = $2001;
var
 OsVerInfo: TOSVersionInfo;
 Win32MajorVersion: Integer;
 Win32MinorVersion: Integer;
 Win32Platform: Integer;
 ForegroundThreadID: DWORD;
 ThisThreadID: DWORD;
 Timeout: DWORD;
begin
 OsVerInfo.dwOSVersionInfoSize := SizeOf(TOSVersionInfo);
 GetVersionEx(osVerInfo);
 Win32MajorVersion := OsVerInfo.dwMajorVersion;
 Win32MinorVersion := OsVerInfo.dwMinorVersion;
 Win32Platform := OsVerInfo.dwPlatformId;
 if IsIconic(hWnd) then ShowWindow(hWnd, SW_RESTORE);
 if GetForegroundWindow = hWnd then Result := True
 else
 begin
   if ((Win32Platform = VER_PLATFORM_WIN32_NT) and (Win32MajorVersion > 4)) or
      ((Win32Platform = VER_PLATFORM_WIN32_WINDOWS) and ((Win32MajorVersion > 4) or
      ((Win32MajorVersion = 4) and (Win32MinorVersion > 0)))) then
   begin
     Result := False;
     ForegroundThreadID := GetWindowThreadProcessID(GetForegroundWindow, nil);
     ThisThreadID := GetWindowThreadPRocessId(hWnd, nil);
     if AttachThreadInput(ThisThreadID, ForegroundThreadID, True) then
     begin
       BringWindowToTop(hWnd);
       SetForegroundWindow(hWnd);
       AttachThreadInput(ThisThreadID, ForegroundThreadID, False);
       Result := (GetForegroundWindow = hWnd);
     end;
     if not Result then
     begin
       SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, @Timeout, 0);
       SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, nil, SPIF_SENDCHANGE);
       BringWindowToTop(hWnd);
       SetForegroundWindow(hWnd);
       SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, @Timeout, SPIF_SENDCHANGE);
     end;
   end
   else
   begin
     BringWindowToTop(hWnd);
     SetForegroundWindow(hWnd);
   end;
   Result := (GetForegroundWindow = hWnd);
 end;
end; // End of function ForceForegroundWindow


 
DVM ©   (2010-10-30 23:11) [7]

Еще есть недокументированная SwitchToThisWindow живет в user32.dll ей таскбар пользуется для переключения окон, правда давно не проверял, работает ли еще она.


 
Германн ©   (2010-10-31 01:25) [8]


> DVM ©   (30.10.10 23:09) [6]
>
>
> > Германн ©
>
> я всегда пользуюсь таким примерно кодом:
>

Вот тут http://codeguru.ru/windows/windows/common/%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%89%D0%B5%D0%BD%D0%B8%D0%B5-%D0%BE%D0%BA%D0%BD%D0%B0-%D0%BD%D0%B0-%D0%BF%D0%B5%D1%80%D0%B5%D0%B4%D0%BD%D0%B8%D0%B9-%D0%BF%D0%BB%D0%B0%D0%BD.html
примерно такое же решение. Но тоже "хакерское" и вероятно тоже если не кривое, то и не прямое.


 
DVM ©   (2010-10-31 01:30) [9]


> Германн ©   (31.10.10 01:25) [8]

А чего в нем кривого? Все честно законно, документировано.

И оно будет работать по всей видимости всегда. Есть причины.

Как бы там ни было, у MS всегда должна быть в загашнике функция для переключения окон таскбаром, пока есть таскбар.


 
Германн ©   (2010-10-31 01:31) [10]

Пробую написать своё решение с использованием функции AllowSetForegroundWindow. И возник вопрос:
Как грамотно использовать функцию GetWindowThreadProcessId?
Ни в SDK, ни в msdn не сказано что делать в случае какой-либо ошибки.
Такое впечатление, что эту функцию писала "другая группа" (с) DVM ©
:)


 
Германн ©   (2010-10-31 01:33) [11]


> DVM ©   (31.10.10 01:30) [9]
>
>
> > Германн ©   (31.10.10 01:25) [8]
>
> А чего в нем кривого? Все честно законно, документировано.
>
>

Приведу еще одну ссылку
http://blogs.msdn.com/b/oldnewthing/archive/2009/02/20/9435239.aspx


 
Германн ©   (2010-10-31 01:44) [12]


>  А чего в нем кривого?

1. AttachThreadInput
 Используя эту функцию в данном примере мы пытаемся обмануть ОС говоря, что это нам очень нужно. Обманув мы возвращаем всё в исходное состояние.
2. SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT...
 Ради личной цели меняем общие настройки ОС.


 
DVM ©   (2010-10-31 01:49) [13]

ну второе необязательно, можно выкинуть вообще, первое и так делает то что надо


 
DVM ©   (2010-10-31 01:51) [14]


> Германн ©

а ты вообще уверен, что тебе нужно именно Foreground окно? Т.е с фокусом ввода? Просто вылезти наверх недостаточно?


 
Германн ©   (2010-10-31 02:08) [15]


> DVM ©   (31.10.10 01:51) [14]
>
>
> > Германн ©
>
> а ты вообще уверен, что тебе нужно именно Foreground окно?
>  Т.е с фокусом ввода? Просто вылезти наверх недостаточно?
>
>

Достаточно. Но я тебя не совсем понимаю. Есть другой способ при попытке запустить программу из меню Пуск, из ярлыка на рабочем столе и т.д. не запускать вторую копию, а "показать пользователю окно уже работающего экземпляра программы"?


 
DVM ©   (2010-10-31 02:15) [16]


> Германн ©   (31.10.10 02:08) [15]

Я вот так делаю, проблем вроде нет:


unit uSingleRun;

interface

uses
 Windows, Messages, SysUtils, Forms;

type

 TInstanceDesc = record
   App: HWND;
   MainWin: HWND;
 end;
 PInstanceDesc = ^TInstanceDesc;

 TSingleRun = class
 private
   FInstanceHandle: THandle;
   FInstanceName: string;
   FRegistered: boolean;
 public
   constructor Create(const AInstanceName: string);
   destructor Destroy; override;
   function ActivatePrevInstance: Boolean;
   procedure RegisterInstance(AWnd: HWND);
   procedure UnregisterInstance;
 end;

var
 SingleRun: TSingleRun;

implementation

//------------------------------------------------------------------------------

constructor TSingleRun.Create(const AInstanceName: String);
begin
 inherited Create;
 FInstanceName := AInstanceName;
end;

//------------------------------------------------------------------------------

destructor TSingleRun.Destroy;
begin
 UnregisterInstance;
 inherited Destroy;
end;

//------------------------------------------------------------------------------

function TSingleRun.ActivatePrevInstance: boolean;
var
 p: Pointer;
 h: THandle;
 hApp: HWND;
 hMainWin: HWND;
begin
 result := False;
 h := OpenFileMapping(FILE_MAP_READ, False, PChar(FInstanceName));
 if h <> 0 then
   begin
     p := MapViewOfFile(h, FILE_MAP_READ, 0, 0, 0);
     if Assigned(p) then
       begin
         hApp := PInstanceDesc(p)^.App;
         hMainWin := PInstanceDesc(p)^.MainWin;
         if IsWindow(hApp) and IsWindow(hMainWin) then
           begin
             if IsIconic(hApp) then
               ShowWindow(hApp, SW_SHOW);
             if IsIconic(hMainWin) then
               ShowWindow(hMainWin, SW_SHOW);
             BringWindowToTop(hMainWin);
             SetForegroundWindow(hMainWin);
             result := True;
           end;
         UnmapViewOfFile(p);
       end;
     CloseHandle(h);
   end;
end;

//------------------------------------------------------------------------------

procedure TSingleRun.RegisterInstance(AWnd: HWND);
var
 p: Pointer;
begin
 if FRegistered then
   Exit;
 FInstanceHandle := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0,
                                  SizeOf(TInstanceDesc), PChar(FInstanceName));
 if FInstanceHandle <> 0 then
   begin
     p := MapViewOfFile(FInstanceHandle, FILE_MAP_WRITE, 0, 0, 0);
     if Assigned(p) then
       begin
         PInstanceDesc(p)^.App := Application.Handle;
         PInstanceDesc(p)^.MainWin := AWnd;
         UnmapViewOfFile(p);
         FRegistered := True;
       end
     else
       UnregisterInstance;
   end;
end;

//------------------------------------------------------------------------------

procedure TSingleRun.UnregisterInstance;
begin
 if FInstanceHandle <> 0 then
   begin
     CloseHandle(FInstanceHandle);
     FInstanceHandle := 0;
     FRegistered  := False;
   end;
end;

//------------------------------------------------------------------------------

initialization

 SingleRun := TSingleRun.Create(ExtractFileName(ParamStr(0)));

finalization

 SingleRun.Free;
 
end.


 
DVM ©   (2010-10-31 02:18) [17]

в dpr файле в самом начале вставить:

 if SingleRun.ActivatePrevInstance then exit;


в OnCreate главной формы

SingleRun.RegisterInstance(Handle);


 
DVM ©   (2010-10-31 02:21) [18]

ну еще

var
SingleRun: TSingleRun;

можно защитить, обернув в функцию и убрать с глаз долой в implementation

но это уже неважно


 
Германн ©   (2010-10-31 02:22) [19]


> DVM ©   (31.10.10 02:15) [16]
>
>
> > Германн ©   (31.10.10 02:08) [15]
>
> Я вот так делаю, проблем вроде нет:
>
>

И этот юнит ты помещаешь в начало списка uses.
Это тоже уже описано.


 
Германн ©   (2010-10-31 02:33) [20]

В любом случае всё упирается в http://msdn.microsoft.com/en-us/library/ms633539%28VS.85%29.aspx
Здесь четко указано какой процесс имеет право поднять своё окно "наверх".


 
Германн ©   (2010-10-31 02:54) [21]

Подниму наверх свой вопрос Германн ©   (31.10.10 01:31) [10]
Как грамотно использовать функцию GetWindowThreadProcessId?
Эта функция возвращает два значения.
1. identifier of the thread that created the window.
2. lpdwProcessId
[out]Pointer to a variable that receives the process identifier. If this parameter is not NULL, GetWindowThreadProcessId copies the identifier of the process to the variable

MSDN ничего не говорит о том, что делать если вызов сей функции был "неудачен".


 
0x00FF00 ©   (2010-10-31 06:30) [22]

Придумал ещё один способ.

Он не включает в себя создание расшаренных файлов или сегментов.
Основан он на посылке обычного Broadcast-сообщения WM_NULL, которое все окна в системе обычно игнорируют.
Все, кроме нашего.

Ключевыми являются обработчики сообщений WM_NULL (который для всех окон задан по умолчанию) и WM_RESPOND.

При создании нового экземпляра приложения, WM_NULL посылается широковещательно всем окнам системы. Откликнется же на нулевое сообщение только наше окно. Если оно видит, что ему пришло WM_NULL с ненулевым WParam — то оно отсылает окну-отправителю WM_RESPOND (после чего отправитель закрывается), и само выводит себя на передний план.

Впрочем, если считаете, что использовать WM_NULL "не по назначению" — дурной тон, то вполне можно применить RegisterWindowMessage для создания спецсообщения (увы, без предварительной регистрации Broadcast не сработает).

Вот минимальный рабочий пример:

program Test;

uses
 Windows, Messages;

const
 WM_RESPOND = WM_USER + 1000;
 BSF_ALLOWSFW = $80;
 CN = "TEST_WND";
 WN = "Test";
 WW = 400;
 WH = 300;

var
 M : MSG;
 W : HWND;
 WC : WNDCLASS;

function WindowProc(hWnd: HWND; Msg, WParam, LParam: LongInt): LongInt; stdcall;
begin
 Result:=0;
 case Msg of
   WM_CREATE: BroadcastSystemMessage(BSF_ALLOWSFW or BSF_NOTIMEOUTIFNOTHUNG, nil, WM_NULL, hWnd, 0);

   WM_NULL:
     if (WParam<>0)and(WParam<>hWnd) then begin
       SetForegroundWindow(hWnd);
       SendMessage(WParam, WM_RESPOND, 0, 0);
     end;

   WM_RESPOND: SendMessage(hWnd, WM_CLOSE, 0, 0);

   WM_CLOSE: DestroyWindow(hWnd);

   WM_DESTROY: PostQuitMessage(0);
   
   else Result:=DefWindowProc(hWnd, Msg, WParam, LParam);
 end;
end;

begin
 fillChar(WC, SIZEOF(WNDCLASS), 0);
 with WC do begin
   Style:=CS_HREDRAW or CS_VREDRAW;
   lpfnWndProc:=@WindowProc;
   hInstance:=SysInit.HInstance;
   hCursor:=LoadCursor(0, IDC_ARROW);
   hbrBackground:=GetSysColorBrush(COLOR_BTNFACE);
   lpszClassName:=CN;
 end;
 RegisterClass(WC);
 W := CreateWindowEx(0, CN, WN, WS_VISIBLE or WS_SYSMENU,
 round((GetSystemMetrics(SM_CXSCREEN)-WW)/2),
 round((GetSystemMetrics(SM_CYSCREEN)-WH)/2),
 WW, WH, 0, 0, HInstance, nil);
 ShowWindow(W, SW_SHOWNORMAL);
 UpdateWindow(W);

 while GetMessage(M, 0, 0, 0) do begin
   TranslateMessage(M);
   DispatchMessage(M);
 end;
 
 UnregisterClass(CN, HInstance);
 Halt(M.wParam);
end.


P.S. Звиняюсь, если слишком назойлив :-[


 
0x00FF00 ©   (2010-10-31 06:31) [23]

Ой.
WM_RESPOND здесь вообще зря.
Можно обойтись обычным WM_CLOSE, чтобы не плодить сообщения.


 
DVM ©   (2010-10-31 10:19) [24]


> Германн ©   (31.10.10 02:54) [21]


> MSDN ничего не говорит о том, что делать если вызов сей
> функции был "неудачен".

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


 
Leonid Troyanovsky ©   (2010-10-31 11:13) [25]


> Германн ©   (31.10.10 02:08) [15]

> Достаточно. Но я тебя не совсем понимаю. Есть другой способ
> при попытке запустить программу из меню Пуск, из ярлыка
> на рабочем столе и т.д. не запускать вторую копию

Например, из проводника
http://groups.google.com/group/fido7.ru.delphi.chainik/msg/278f89887ab26b31

--
Regards, LVT.


 
Leonid Troyanovsky ©   (2010-10-31 11:25) [26]


> Германн ©   (31.10.10 02:33) [20]

> Здесь четко указано какой процесс имеет право поднять своё
> окно "наверх".

Если юзер пускает процесс из проводника, комстроки или ярлыком,
то запускаемый процесс может стать foreground получая юзеровский
ввод либо как запускаемый foreground процессом.

Т.е., если специально не мешать ходу процесса, то ничего
осбенного (в т.ч. AllowSetForegroundWindow) для назначения
foreground предыдущей копии делать не надо.
Во всяком случае для XP, w2k3 было так.

--
Regards, LVT.


 
Leonid Troyanovsky ©   (2010-10-31 11:32) [27]


> 0x00FF00 ©   (31.10.10 06:30) [22]

> WM_NULL с ненулевым WParam — то оно отсылает окну-отправителю
> WM_RESPOND (после чего отправитель закрывается), и само
> выводит себя на передний план.

Сам себя foreground  процесс сделать не может.
See SetForeGroundWindow restrictions.

--
Regards, LVT.


 
Leonid Troyanovsky ©   (2010-10-31 11:44) [28]


> DVM ©   (31.10.10 02:15) [16]

>          PInstanceDesc(p)^.App := Application.Handle;
>          PInstanceDesc(p)^.MainWin := AWnd;

Первое хранить необязательно, бо можно найти GWL_HWNDPARENT.
А вот будет ли валиден AWnd - это еще вопрос.
Наверное, нужно хранить ThreadId, затем делать EnumThreadWindows,
убедиться, что это искомое приложение и установить на поток хук,
чтобы не пропустить WM_QUIT. А может потребуется и OpenThread,
чтобы не пропустить Terminate*.

--
Regards, LVT.



Страницы: 1 вся ветка

Форум: "Начинающим";
Текущий архив: 2011.01.23;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.55 MB
Время: 0.005 c
15-1286742549
Юрий
2010-10-11 00:29
2011.01.23
С днем рождения ! 11 октября 2010 понедельник


2-1289104970
Zalm
2010-11-07 07:42
2011.01.23
TIdPOP3 &amp; TIdMessage.Flags


2-1288448825
M-games
2010-10-30 18:27
2011.01.23
Очень нужна помощь


2-1288543652
mefodiy
2010-10-31 19:47
2011.01.23
Юникод в DBGrid


2-1288744271
Германн
2010-11-03 03:31
2011.01.23
TApplication событие OnHint





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский