Модераторы: Snowy, bartram, MetalFan, bems, Poseidon, Riply

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Как недопустить запуск второй копии программы, Нашел решение, чем оно плохое? 
V
    Опции темы
instumental
Дата 8.1.2006, 18:16 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 4
Регистрация: 11.12.2005

Репутация: нет
Всего: нет



Искал как это сделать и находил всегда длиннющие решения, в которых не мог разобраться и которые еще к тому же и не работали почему-то. Сегодня сделал так:

это код dpr.

Код

program Project1;

uses
  Forms, Windows,
  Unit1 in 'Unit1.pas' {AppId0071},

var
  PrevAppCopy: HWND;
{$R *.res}

begin
  PrevAppCopy := FindWindow('TAppId0071', nil);
  if PrevAppCopy <> 0 then
  begin
  SetForegroundWindow(PrevAppCopy);
  Application.Terminate;
  Exit;
  end;
  Application.Initialize;
  Application.Title := 'Project1';
  Application.CreateForm(TAppId0071, AppId0071);
  Application.Run;
end.


AppId0071 - это имя главной формы,
TAppId071 - это имя класса. Работает так: ищем окно с именем класса как у нашей проги и если находим то показываем его и делам Terminate текущей копии. Код этот работает, глюков не нашел. Надо только чтобы имя класса было уникальным и не совпало с каким-нибудь другим приложением.

Вопрос у меня такой. Есть ли здесь какое-нибудь слабое место. Почему всегда предлагают какие-то длинные решения этой задачи, может я что-то не то сделал.
PM MAIL   Вверх
z-END
Дата 8.1.2006, 18:26 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


прафесар™
****


Профиль
Группа: Комодератор
Сообщений: 3014
Регистрация: 13.3.2003
Где: Венья, Пиетари

Репутация: 1
Всего: 102



instumental ну как минимум:
1. может у другого приложения совпасть имя формы=)
2. возможно твое приложение в рантайме создает форму и в момент проверки оно небыло создано


--------------------
Каждый чилавек пасвоему праф...а памоему НЕТ! 

PM WWW ICQ   Вверх
Poseidon
Дата 8.1.2006, 19:07 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Delphi developer
****


Профиль
Группа: Комодератор
Сообщений: 5273
Регистрация: 4.2.2005
Где: Гомель, Беларусь

Репутация: 10
Всего: 133



Цитата(instumental @ 8.1.2006, 17:16 Найти цитируемый пост)

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

Плохо искал smile http://forum.vingrad.ru/index.php?showtopic=56734


--------------------
Если хочешь, что бы что-то работало - используй написанное, 
если хочешь что-то понять - пиши сам...
PM MAIL ICQ   Вверх
instumental
Дата 8.1.2006, 22:25 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 4
Регистрация: 11.12.2005

Репутация: нет
Всего: нет



Цитата(z-END @ 8.1.2006, 18:26 Найти цитируемый пост)

instumental ну как минимум:
1. может у другого приложения совпасть имя формы=)
2. возможно твое приложение в рантайме создает форму и в момент проверки оно небыло создано

1. Конечно может совпасть. Но вероятность этого очень мала. Надо назвать позаковыристее и все.
Неприятность я заметил одну. Делфи создает форму с тем же именем. Поэтому при работающей делфи прога не запускается и считает что она уже работает, т.к. находит форму созданную delphi.
2. Не совсем понял. Ищем же главную форму приложения.

Цитата(Poseidon @ 8.1.2006, 19:07 Найти цитируемый пост)

Цитата(instumental @ 8.1.2006, 17:16 )

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


Плохо искал  http://forum.vingrad.ru/index.php?showtopic=56734


Спасибо за ссылку. Я вообще-то не здесь искал (не на этом форуме). Находил такое что мамадарагая. Уже отчаился найти.
PM MAIL   Вверх
ivan219
Дата 8.1.2006, 23:32 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1121
Регистрация: 19.11.2005
Где: Планета земля

Репутация: нет
Всего: 12



А вот мой вареант оцените.


Код

unit Unit1;
.
.
uses.........., Unit2......;
.
.
var  A: Boolean;
  Form1: TForm1;
.
.
procedure TForm1.N7Click(Sender: TObject);
begin
  if A=false then
  begin
  Application.CreateForm(TForm2, Form2);
  Form2.Show;
  Form2.FreeOnRelease;
  A:=true;
  end
  else
  begin
  Exit;
  end;
end;



Код

unit Unit2;
.
.
implementation

uses Unit1;

{$R *.dfm}
.
.
procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
A:=false;
end;
end.


PM MAIL ICQ   Вверх
Delphins
Дата 8.1.2006, 23:45 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 93
Регистрация: 10.8.2005
Где: Россия, Краснодар

Репутация: нет
Всего: нет



Пеерменная A не распространяется на всю систему. В другой программе её не будет видно.
Самый лучший способ это послать WM сообщения всем окнам и если прийдет ответ то завершить себя. Иначе работать дальше.

PM MAIL ICQ Skype MSN   Вверх
Albinos_x
Дата 8.1.2006, 23:50 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Evil Skynet
****


Профиль
Группа: Комодератор
Сообщений: 3288
Регистрация: 28.5.2004
Где: X-6120400 Y-1 4624650

Репутация: 3
Всего: 108



Delphins
это он наверно проверку на форму2 делал...


--------------------
"Кто владеет информацией, тот владеет миром"    
Уинстон Черчилль
PM MAIL ICQ   Вверх
ivan219
Дата 8.1.2006, 23:55 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1121
Регистрация: 19.11.2005
Где: Планета земля

Репутация: нет
Всего: 12



Этот код для отдельно взятого окна.

А если надо для других то можно повторить этуже операчию только здругими переменными и другими Unit, главное что это работае и копия окна неоткроется пока переменная А имеет значение true.

А поповоду "послать WM сообщения всем окнам" можеш поподробнее раскозать ато и вправду лучше будет.
PM MAIL ICQ   Вверх
Akina
Дата 9.1.2006, 00:00 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Советчик
****


Профиль
Группа: Модератор
Сообщений: 20581
Регистрация: 8.4.2004
Где: Зеленоград

Репутация: нет
Всего: 454



Создай мьютекс с явно уникальным именем. Если вернется ошибка already_exist - это дубликат. В отличие от атома и семафора он гарантированно умрет в момент выгрузки приложения.

Способ с сообщением вообще не универсален - приложение может быть настолько занято собой, что прозевает мессагу.


--------------------
 О(б)суждение моих действий - в соответствующей теме, пожалуйста. Или в РМ. И высшая инстанция - Администрация форума.

PM MAIL WWW ICQ Jabber   Вверх
Albinos_x
Дата 9.1.2006, 00:31 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Evil Skynet
****


Профиль
Группа: Комодератор
Сообщений: 3288
Регистрация: 28.5.2004
Где: X-6120400 Y-1 4624650

Репутация: 3
Всего: 108



Цитата(ivan219 @ 8.1.2006, 23:55 Найти цитируемый пост)

Этот код для отдельно взятого окна.

А если надо для других то можно повторить этуже операчию только здругими переменными и другими Unit, главное что это работае и копия окна неоткроется пока переменная А имеет значение true.

проще:
Код
...
procedure TForm1.N7Click(Sender: TObject);    
begin
if Form2<>nil then
   begin
   MessageDlg('Форма уже существует',mtWarning,[mbOk],0);
   end
   else
   begin
   Application.CreateForm(TForm2, Form2);    
   Form2.Show;
   Form2.Update;
   end;
end;
...
procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);    
begin    
Form2.Free;    
end;


Это сообщение отредактировал(а) Albinos_x - 9.1.2006, 00:33


--------------------
"Кто владеет информацией, тот владеет миром"    
Уинстон Черчилль
PM MAIL ICQ   Вверх
ivan219
Дата 9.1.2006, 03:14 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1121
Регистрация: 19.11.2005
Где: Планета земля

Репутация: нет
Всего: 12



Да немного проще но после закрытия её незльзя болше открыть а сообшение осталось.

Вот ещё вареант.

Первая форма

Код

...
procedure TForm1.N7Click(Sender: TObject);
begin
if not Assigned(Form2) then
 begin
 Form2 := TForm2.Create(Application);
 Form2.Show;
 end
 else
 begin
 MessageDlg('Форма уже существует',mtWarning,[mbOk],0);
 end;
end;
...



Вторая форма

Код

...
procedure TForm2.FormDestroy(Sender: TObject);
begin
Form2 := nil;
end;

procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := CaFree;
end;
...


Это сообщение отредактировал(а) ivan219 - 9.1.2006, 03:16
PM MAIL ICQ   Вверх
Poseidon
Дата 9.1.2006, 03:32 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Delphi developer
****


Профиль
Группа: Комодератор
Сообщений: 5273
Регистрация: 4.2.2005
Где: Гомель, Беларусь

Репутация: 10
Всего: 133



Я что-то не пойму, нужно запретить запуск прогаммы или формы? Если формы, то зачем такие извращения, если по Show и ShowModal больше одной копии формы и так не откроется?


--------------------
Если хочешь, что бы что-то работало - используй написанное, 
если хочешь что-то понять - пиши сам...
PM MAIL ICQ   Вверх
Guedda
Дата 9.1.2006, 10:10 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Подрывник
****


Профиль
Группа: Завсегдатай
Сообщений: 3137
Регистрация: 27.12.2005
Где: Ростов-на-Дону

Репутация: 3
Всего: 78



А что, так нельзя?:
Код

program ANV;

uses
  Forms,
  Windows,
  Dialogs,
  Main in 'Main.pas' {MainForm};

const 
  MEM_F_S = 1024;
  MEM_F_N = 'BU'; //Здесь любое имя можешь написать.

var
  MemHnd : HWND;

{$R *.res}

begin
  MemHnd := CreateFileMapping(HWND($FFFFFFFF), nil, PAGE_READWRITE, 0, MEM_FILE_S, MEM_FILE_N);
  if GetLastError <> ERROR_ALREADY_EXISTS then begin
    Application.Initialize;
    Application.Title := 'Thenoid';
    Application.CreateForm(TMainForm, MainForm);
    Application.Run;
  end else MessageDlg('Приложение уже в рантайме!!!', mtError, [mbOk], 0);
  CloseHandle(MemHnd);
end.



--------------------
Ll 2
PM MAIL WWW ICQ Skype GTalk   Вверх
Delphins
Дата 9.1.2006, 22:33 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 93
Регистрация: 10.8.2005
Где: Россия, Краснодар

Репутация: нет
Всего: нет



Если для формы то нафига столько кода? smile
Она не откроется вторая. Конечно если вы её не создаёте в RunTime.

Самый просто способ это сообщения WM.

Как для программы так и для отдельных форм.

Код

const
  wm_mymsg = WM_USER+66;
//---------
procedure WM_MYAPPEXIST( {Че тут не помню... :(( wmparam1 и wmparam2 чтоли.} ); message wm_mymsg;


а потом процдуру обрабатывать. Сообщение обработается всегда. Кроме случаев когда оно зависло.
Сообщения ставятся в очеред на обработку.
Отсылааются они вроде как через SendMessage.
Неогу сказать точно потому, что Delphi перестал запускаться.
PM MAIL ICQ Skype MSN   Вверх
Romero
Дата 8.3.2006, 16:03 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 129
Регистрация: 17.6.2005

Репутация: 1
Всего: 1



Я в одной из своих прог использовал следующий механизм:
При запуске программа пишет куда-либо (в файл или реестр) свой PID. Перед записью из того же места считывается PID предыдущей копии программы и выполняется TerminateProcess.
2 нюанса:
- мне нужно было именно перезапускать прогу (убивать старую версию) в случае повторного запуска;
- есть небольшой шанс, что убьешь левый процесс (в случае, если предыдущая версия проги завершилась и был запущен процесс с тем же PID).
PM   Вверх
Демо
Дата 9.3.2006, 09:43 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1278
Регистрация: 3.11.2005

Репутация: 6
Всего: 50



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

Спасибо.


--------------------
    
PM MAIL ICQ Skype   Вверх
Girder
Дата 9.3.2006, 09:54 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Лентяй 2
***


Профиль
Группа: Участник Клуба
Сообщений: 1993
Регистрация: 12.5.2004

Репутация: 25
Всего: 155



Демо, раз не пользуемся поиском... и не видем что данная тема разжеванна и на Vingrad-е(и не однакратно!)... то какой смысл оставлять ссылку... на конкурирующий ресурс? Реклама?

Без "правильного и корректного" решения остануться только те, кто поленился сделать пару кликов... - енто раз! А второе... автор темы спрашивает не решение... а о правильности уже имеющегося решения!

PS: Тема закрыта!

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

Это сообщение отредактировал(а) Girder - 9.3.2006, 13:25


--------------------
Как слышим, так и пишим.
Истина где-то там...
PM   Вверх
Alex
Дата 9.3.2006, 13:31 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 4147
Регистрация: 25.3.2002
Где: Москва

Репутация: 6
Всего: 162



Тема действительно не однократно, обсуждалась как у нас так и на многих форумах, сказано по ней много, примеров реализации не меньше, какие-то из них перестали работать в Win NT, какие-то продолжают работать по сей день, многие из них не универсальны, срабатывают не всегда или по просту не экономичны по отношению к ресурсам с точки зрения программирования под Delphi. Хотя кажется, что тут сложного, взять и где-то запомнить уникальный идентификатор приложения и при старте проверять, то место куда мы его запоминаем, если там есть данные, то значит приложение уже запущено. На помощь в этом деле к нам приходят возможность хранения файла данных в памяти процесса.
Вот модуль, который сделает все, что нужно:
Код

unit Not2Run;

interface

uses
    Windows
  , SysUtils
  , Forms
  ;

procedure CheckOneInstance(const ApplicationName: String);
 // Проверяет что программа с идентификатором ApplicationName уже запущена.
 // Если это так, то предыдущий экземпляр "выталкивается" на поверхность и
 // выполняется Halt(1)

type
{ WIN32 Helper Classes }

{ tHandledObject }

  tHandledObject = class(tObject)
  protected
    fName  : String;
    fHandle: tHandle;
    fCreated: Boolean;
    procedure SetHandle(const aName :String; aHandle: tHandle);
    procedure ErrorCreate;
  public
    destructor Destroy; override;
    property Name: string read fName;
    property Handle: tHandle read fHandle;
    property Created: Boolean read fCreated;
  end;

{ tSharedMem }

  tSharedMem = class(tHandledObject)
  private
    fSize: Integer;
    fDataPtr: Pointer;
  public
    constructor Create(const aName: string; aSize: Integer);
    destructor Destroy; override;
    property Size: Integer read fSize;
    property DataPtr: Pointer read fDataPtr;
  end;

type
  eSharedResources = class(Exception);

implementation

{ tHandledObject }

destructor tHandledObject.Destroy;
begin
  if fHandle <> 0 then CloseHandle(fHandle);
end;

procedure tHandledObject.ErrorCreate;
begin
  raise eSharedResources.Create(Format('Ошибка создания %s(%s):'^M^J'%s',[ClassName,fName,SysErrorMessage(GetLastError)]));
end;

procedure tHandledObject.SetHandle(const aName :String; aHandle: tHandle);
begin
  fName:= aName;
  if aHandle = 0 then ErrorCreate;
  fHandle:= aHandle;
  fCreated:= GetLastError = 0;
end;

{ tSharedMem }

constructor tSharedMem.Create(const aName: string; aSize: Integer);
begin
  fSize:= aSize;
  SetHandle(aName,CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, aSize, PChar(aName)));
  fDataPtr := MapViewOfFile(fHandle, FILE_MAP_WRITE, 0, 0, aSize);
  if fDataPtr = nil then ErrorCreate;
end;

destructor TSharedMem.Destroy;
begin
  if fDataPtr <> nil then UnmapViewOfFile(fDataPtr);
  inherited Destroy;
end;

var
  FirstInstance :tSharedMem;

procedure CheckOneInstance(const ApplicationName: String);
 // Проверяет что программа с идентификатором ApplicationName уже запущена.
 // Если это так, то предыдущий экземпляр "выталкивается" на поверхность и
 // выполняется Halt(1)
var HPtr: pHandle;
begin
  FirstInstance:= tSharedMem.Create('Alex&Co_FirstInstace_'
       +AnsiUpperCase(StringReplace(ApplicationName,'\','/',[rfReplaceAll])),
                                         SizeOf(Application.Handle));
  HPtr:= pHandle(FirstInstance.DataPtr);
  if HPtr^ = 0  then  // это первый экземпляр программы в памяти?
    HPtr^:= Application.Handle  //да
  else begin  // нет, не первый. Вытащим первый на поверхность
    if IsIconic(HPtr^) then ShowWindow(HPtr^, SW_RESTORE);
    SetForegroundWindow(HPtr^);
    Halt(1); // и отваливаем
  end;
end;

initialization

finalization
  if FirstInstance <> nil then FirstInstance.Free;
end.

Пример использования:
Код

program Project1;

uses
  Forms,
  Not2Run,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

begin
  // Запущена или нет уже программа
  CheckOneInstance('Name_Program');
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.



Это сообщение отредактировал(а) Alex - 9.3.2006, 13:32


--------------------
Написать можно все - главное четко представлять, что ты хочешь получить в конце. 
PM Skype   Вверх
Демо
Дата 9.3.2006, 13:47 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1278
Регистрация: 3.11.2005

Репутация: 6
Всего: 50



Цитата(Alex @ 9.3.2006, 13:31 Найти цитируемый пост)
Вот модуль, который сделает все, что нужно:

код Pascal/Delphi


Не совсем всё.

В приведенном классе не реализовано выдвижение уже запущенного приложения на передний план для ОС W2000 и выше.

Код

SetForegroundWindow(HPtr^);


будет работать неправильно.
Ссылка на MSDN: http://msdn.microsoft.com/library/default....roundwindow.asp
Добавлено @ 13:58
Вот код, который выполняет восстановление приложения:

Код

function TForm1.ApplicationMessage(var Message: TMessage): Boolean;
var
  hWnd, hCurWnd, dwThreadID, dwCurThreadID: THandle;
  OldTimeOut: Cardinal;
  AResult: Boolean;
begin
   Result := False;
   if Message.Msg = RestoreOldInstance then
   begin
     Application.Restore;
     hWnd := Application.Handle;
     SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, @OldTimeOut, 0);
     SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, Pointer(0), 0);
     SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE);
     hCurWnd := GetForegroundWindow;
     AResult := False;
     while not AResult do
     begin
        dwThreadID := GetCurrentThreadId;
        dwCurThreadID := GetWindowThreadProcessId(hCurWnd);
        AttachThreadInput(dwThreadID, dwCurThreadID, True);
        AResult := SetForegroundWindow(hWnd);
        AttachThreadInput(dwThreadID, dwCurThreadID, False);
     end;
     SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE);
     SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, Pointer(OldTimeOut), 0);
   end;
   inherited;
end;


(с) Rouse_

Для этого приложение, которое должно быть восстановлено, должно получить сообщение RestoreOldInstance.
Добавлено @ 14:00
Girder,
Существует ли где-либо на винграде такой код?;)
Добавлено @ 14:00
Или есть другие варианты? С удовольствием посмотрю и попробую

Это сообщение отредактировал(а) Демо - 9.3.2006, 14:02


--------------------
    
PM MAIL ICQ Skype   Вверх
Girder
Дата 9.3.2006, 14:57 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Лентяй 2
***


Профиль
Группа: Участник Клуба
Сообщений: 1993
Регистрация: 12.5.2004

Репутация: 25
Всего: 155



Цитата(Демо @ 9.3.2006, 13:47 Найти цитируемый пост)
Существует ли где-либо на винграде такой код?;)
Ссылку я тебе давал http://forum.vingrad.ru/index.php?showtopi...st&p=450839

Приспособить его... енто дело пяти секунд smile

PS: Причем дата ентого поста(на винграде)... горазда раньше... чем на исходниках. Так что делай выводы smile один уже нашел енти пять секунд smile ... шучю smile


--------------------
Как слышим, так и пишим.
Истина где-то там...
PM   Вверх
Демо
Дата 9.3.2006, 15:19 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1278
Регистрация: 3.11.2005

Репутация: 6
Всего: 50



Girder,
Поискал в форумах, в FAQ по ключевому слову SystemParametersInfo.

ничего не нашел;)

А именно изменения таймаута - один из ключевых моментов.

Цитата(Girder @ 9.3.2006, 14:57 Найти цитируемый пост)
Приспособить его... енто дело пяти секунд

Приспособить не только можно. Но и нужно. smile
Вообще, видимо придется с нуля реализовать - сделать полный пример.



--------------------
    
PM MAIL ICQ Skype   Вверх
Girder
Дата 9.3.2006, 15:29 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Лентяй 2
***


Профиль
Группа: Участник Клуба
Сообщений: 1993
Регистрация: 12.5.2004

Репутация: 25
Всего: 155



Цитата(Демо @ 9.3.2006, 15:19 Найти цитируемый пост)
А именно изменения таймаута - один из ключевых моментов.
Приведи пример... где без них не будет работать.


--------------------
Как слышим, так и пишим.
Истина где-то там...
PM   Вверх
Демо
Дата 9.3.2006, 15:36 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1278
Регистрация: 3.11.2005

Репутация: 6
Всего: 50



Цитата(Girder @ 9.3.2006, 15:29 Найти цитируемый пост)
Приведи пример... где без них не будет работать.


Да какой тут пример. Попробуй пару вариантов:

1. Сверни приложение, на передний план выдвинь другое. Стартуй второую копию.
2. Не сворачивая приложение все то же самое, что и в п.1.

После этих манипуляций и выполнения кода по восстановлению результат должен быть таким:
Первая копия приложения должна развернуться на передний план на экране и получить фокус ввода.

В разных версиях Windows будет отличаться результат.


--------------------
    
PM MAIL ICQ Skype   Вверх
Демо
Дата 9.3.2006, 15:54 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1278
Регистрация: 3.11.2005

Репутация: 6
Всего: 50



Girder,

Для чистоты эксперимента все равно придется реализовать полный цикл :
1. Опреджеление уже запущенной копии программы.
2. Извещение запущенной копии каким-либо образом о том, что сделана попытка запуска еще копии.
3. Поднятие первой копии наверх.


--------------------
    
PM MAIL ICQ Skype   Вверх
Alex
Дата 9.3.2006, 17:01 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 4147
Регистрация: 25.3.2002
Где: Москва

Репутация: 6
Всего: 162



Вот, модифицировал модуль:
Код

unit Not2Run;

interface

uses
    Windows
  , SysUtils
  , Messages
  , Forms
  ;

procedure CheckOneInstance(const ApplicationName: String);
 // Проверяет что программа с идентификатором ApplicationName уже запущена.
 // Если это так, то предыдущий экземпляр "выталкивается" на поверхность и
 // выполняется Halt(1)

type
{ WIN32 Helper Classes }

{ tHandledObject }

  tHandledObject = class(tObject)
  protected
    fName  : String;
    fHandle: tHandle;
    fCreated: Boolean;
    procedure SetHandle(const aName :String; aHandle: tHandle);
    procedure ErrorCreate;
  public
    destructor Destroy; override;
    property Name: string read fName;
    property Handle: tHandle read fHandle;
    property Created: Boolean read fCreated;
  end;

{ tSharedMem }

  tSharedMem = class(tHandledObject)
  private
    fSize: Integer;
    fDataPtr: Pointer;
  public
    constructor Create(const aName: string; aSize: Integer);
    destructor Destroy; override;
    property Size: Integer read fSize;
    property DataPtr: Pointer read fDataPtr;
  end;

type
  eSharedResources = class(Exception);

implementation

{ tHandledObject }

destructor tHandledObject.Destroy;
begin
  if fHandle <> 0 then CloseHandle(fHandle);
end;

procedure tHandledObject.ErrorCreate;
begin
  raise eSharedResources.Create(Format('Ошибка создания %s(%s):'^M^J'%s',[ClassName,fName,SysErrorMessage(GetLastError)]));
end;

procedure tHandledObject.SetHandle(const aName :String; aHandle: tHandle);
begin
  fName:= aName;
  if aHandle = 0 then ErrorCreate;
  fHandle:= aHandle;
  fCreated:= GetLastError = 0;
end;

{ tSharedMem }

constructor tSharedMem.Create(const aName: string; aSize: Integer);
begin
  fSize:= aSize;
  SetHandle(aName,CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, aSize, PChar(aName)));
  fDataPtr := MapViewOfFile(fHandle, FILE_MAP_WRITE, 0, 0, aSize);
  if fDataPtr = nil then ErrorCreate;
end;

destructor TSharedMem.Destroy;
begin
  if fDataPtr <> nil then UnmapViewOfFile(fDataPtr);
  inherited Destroy;
end;

var
  FirstInstance :tSharedMem;

procedure CheckOneInstance(const ApplicationName: String);
 // Проверяет что программа с идентификатором ApplicationName уже запущена.
 // Если это так, то предыдущий экземпляр "выталкивается" на поверхность и
 // выполняется Halt(1)

  function ForceForeground(const Wnd: HWND): boolean;
  var
    ForeThreadID, NewThreadID: DWORD;
  begin
    if GetForegroundWindow <> Wnd then
    begin
      ForeThreadID:= GetWindowThreadProcessId(GetForegroundWindow, nil);
      NewThreadID:= GetWindowThreadProcessId(Wnd, nil);
      if ForeThreadID <> NewThreadID then
      begin
        AttachThreadInput(ForeThreadID, NewThreadID, True);
        Result:= SetForegroundWindow(Wnd);
        AttachThreadInput(ForeThreadID, NewThreadID, False);
        if Result then
          Result:= SetForegroundWindow(Wnd);
      end
      else
        Result:= SetForegroundWindow(Wnd);
    end
    else
      Result:= True;
  end;

var HPtr: pHandle;
begin
  FirstInstance:= tSharedMem.Create('Alex&Co_FirstInstace_'
       +AnsiUpperCase(StringReplace(ApplicationName,'\','/',[rfReplaceAll])),
                                         SizeOf(Application.Handle));
  HPtr:= pHandle(FirstInstance.DataPtr);
  if HPtr^ = 0  then  // это первый экземпляр программы в памяти?
    HPtr^:= Application.Handle  //да
  else begin  // нет, не первый. Вытащим первый на поверхность
    if IsIconic(HPtr^) then SendMessage(HPtr^, WM_SYSCOMMAND, SC_RESTORE, 0);
    ForceForeground(HPtr^);
    Halt(1); // и отваливаем
  end;
end;

initialization

finalization
  if FirstInstance <> nil then FirstInstance.Free;
end.



--------------------
Написать можно все - главное четко представлять, что ты хочешь получить в конце. 
PM Skype   Вверх
Демо
Дата 9.3.2006, 17:41 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1278
Регистрация: 3.11.2005

Репутация: 6
Всего: 50



Alex,

Вроде бы все отлично на W2000 Server!
Надо протестировать на W98, XP SP1, XP SP2 еще.



--------------------
    
PM MAIL ICQ Skype   Вверх
Alex
Дата 9.3.2006, 17:44 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 4147
Регистрация: 25.3.2002
Где: Москва

Репутация: 6
Всего: 162



Я тестировал, на SP2 и 98

Это сообщение отредактировал(а) Alex - 9.3.2006, 18:00


--------------------
Написать можно все - главное четко представлять, что ты хочешь получить в конце. 
PM Skype   Вверх
Петрович
Дата 10.3.2006, 00:40 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Участник Клуба
Сообщений: 1000
Регистрация: 2.12.2003
Где: Москва

Репутация: 15
Всего: 55



Господа.
О чем базар!
Демо, ты первый вариант Alex'а точно проверял? Или только предполагаешь что SetForegroundWindow(HPtr^) будет работать неправильно?

Я вот например таким вот способом пользуюсь уже не первый год. До сих пор проблем не было.

Более того, раз уж отсылаешь к MSDN, то будь добр, поясни тогда мне бестолковому, почему в данном случае, SetForegroundWindow(HPtr^) не должна сработать?
Только подчеркиваю, именно в данном случае, а не в том в котором его используешь ты!

Вот при твоем подходе да, тут мне ясно почему ты не получишь от SetForegroundWindow нужного тебе эффекта.

Короче, хотелось-бы получить толковые разяснения, либо забери свои слова обратно smile .



--------------------
Все знать невозможно, но хочется
PM ICQ   Вверх
Демо
Дата 10.3.2006, 01:33 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1278
Регистрация: 3.11.2005

Репутация: 6
Всего: 50



Цитата(Петрович @ 10.3.2006, 00:40 Найти цитируемый пост)
ты первый вариант Alex'а точно проверял?


Первый - это какой? Дата 9.3.2006, 13:31?

Так мне не нужно его проверять, так как с этим вариантом в свое время повозился сколько-то.
Вот последний работает однозначно.
Только еще бы на XP SP1 проверить.
Добавлено @ 01:35
Цитата(Петрович @ 10.3.2006, 00:40 Найти цитируемый пост)
почему в данном случае, SetForegroundWindow(HPtr^) не должна сработать?


Я же привел ссылку из MSDN, где описана ситуация?

Вот точная цитата из MSDN:

Windows 98, Windows 2000: The system restricts which processes can set the foreground window. A process can set the foreground window only if one of the following conditions is true:

- The process is the foreground process.
- The process was started by the foreground process.
- The process received the last input event.
- There is no foreground process.
- The foreground process is being debugged.
- The foreground is not locked (see LockSetForegroundWindow).
- The foreground lock time-out has expired (see SPI_GETFOREGROUNDLOCKTIMEOUT in SystemParametersInfo).
- Windows 2000: No menus are active.


Цитата(Петрович @ 10.3.2006, 00:40 Найти цитируемый пост)
Только подчеркиваю, именно в данном случае,


Чем данный случай отличается от общего?

Цитата(Петрович @ 10.3.2006, 00:40 Найти цитируемый пост)
Вот при твоем подходе да, тут мне ясно почему ты не получишь от SetForegroundWindow нужного тебе эффекта.


Почему?;)

Это сообщение отредактировал(а) Демо - 10.3.2006, 01:52


--------------------
    
PM MAIL ICQ Skype   Вверх
Петрович
Дата 10.3.2006, 02:33 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Участник Клуба
Сообщений: 1000
Регистрация: 2.12.2003
Где: Москва

Репутация: 15
Всего: 55



Ну вот, ты так и не удосужился разобраться.

Цитата(Демо @ 10.3.2006, 02:33 Найти цитируемый пост)
Первый - это какой? Дата 9.3.2006, 13:31?

Именно он.


Цитата(Демо @ 10.3.2006, 02:33 Найти цитируемый пост)
Так мне не нужно его проверять, так как с этим вариантом в свое время повозился сколько-то.
Вот последний работает однозначно.


Вот именно, что последний, функцианально мало чем отличается от первого. А если быть более точным, то в плане работы SetForegroundWindow(HPtr^) - вообще НИЧЕМ!.
Т.е., первый вариант работает так-же как и второй.


Цитата(Демо @ 10.3.2006, 02:33 Найти цитируемый пост)
Я же привел ссылку из MSDN, где описана ситуация?

Вот точная цитата из MSDN:

Дык читать я и сам умею.

Ты мне докажи что ни одно из преречисленных условий не выполняется для примера Alex'а !

Лично я считаю что для него выполняется условие номер 1. Поэтому, SetForegroundWindow(HPtr^) прекрастно выполнит свое дело.

Цитата(Демо @ 10.3.2006, 02:33 Найти цитируемый пост)
Чем данный случай отличается от общего?

А что в твоем понимания есть "общий" случай? Я лично вижу в данном топике два случая, твой (точнее Rose) и Alex'а. А вот они имеют одно очень существенное в данном случае отличие:
У Alex'а, вызов SetForegroundWindow делается из второй копии приложения, а в тебя из первой. Вот в этом и есть главное отличие!
Именно поэтому, у тебя не выполняется первое условие. Вот и приходится извращаться с AttachThreadInput и SystemParametersInfo, дабы играя на побочных эффектах достич выполнения третьего условия.

В общем, надо с большим вниманием читать приводимые примеры и сопоставлять их с документацией.

Это сообщение отредактировал(а) Петрович - 10.3.2006, 02:55


--------------------
Все знать невозможно, но хочется
PM ICQ   Вверх
Петрович
Дата 10.3.2006, 02:52 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Участник Клуба
Сообщений: 1000
Регистрация: 2.12.2003
Где: Москва

Репутация: 15
Всего: 55



Правда, справедливости ради, надо отметить что мое утверждение:
Цитата(Петрович @ 10.3.2006, 03:33 Найти цитируемый пост)
Лично я считаю что для него выполняется условие номер 1. Поэтому, SetForegroundWindow(HPtr^) прекрастно выполнит свое дело.

тоже не совсем верно. Точнее не для всех случаев.
А если еще точнее, то оно не верно именно для тех случаев, для которых и придумано такое, на первый взгляд странное, поведение SetForegroundWindow.
На самом деле, такое поведение SetForegroundWindow весьма осмысленно. А именно, опишу такой случай, слегка надуманный, но у меня встречается нечто подобное.
Итак:
Имеется программа которая при запуске выполняет некоторую фоновую работу (ну например закачка чего-то из инета). Показ состояния и управление этой работой производится через ее форму.
По ряду причин допустима работа только одной копии программы. По звершению работы, программа автоматически завершается.

Теперь, мне надо заставить выполняться это действо каждый час. Я "заряжаю" эту программу в шедулер. И вот, в один прекрасный час, сталкиваюсь с ситуацией, когда программа еще не завершилось от прошлого часа. Казалось-бы ничего страшного, вторая копия подняла на поверхность первую и сама ушла - именно так и задуманно.
Но, беда в том, что именно в это время, у меня активным было окно эксплорера, в котором я с остервенением (в слепую) набивал ответ в эту тему smile. И что получилось - активным стало окно первой копии, и весь мой последующий ввод попер в него, вместо эксплорера. Ну естественно, это было воспринято как комманда, причем, по случайному стечению обстоятельств я как раз набирал текст format c: <Enter> smile.
Потом были Y и пр. В общем, когда спохватился, спасать было нечего. smile

Таков был-бы сценарий если-бы SetForegroundWindow работал всегда одинаково - то чего добиваешся ты.

Но, слава программерам Microsoft, они, начиная с Windows 98 (наконец то) изменили ее поведение.
Теперь, в описанной ситуации, SetForegroundWindow будет почти проигнорированна! И вместо "всплытия", просто начнет мигать кнопка приложения в таск-баре.

Вот так.


--------------------
Все знать невозможно, но хочется
PM ICQ   Вверх
Girder
Дата 10.3.2006, 09:59 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Лентяй 2
***


Профиль
Группа: Участник Клуба
Сообщений: 1993
Регистрация: 12.5.2004

Репутация: 25
Всего: 155



Но все енто... справедливо только в пределах одного десктопа.

Т.е. если я запускаю копию в другом десктопе... то первое приложение не всплывет smile так что "правильное и корректное решения задачи" всегда надо писать в ковычках smile


--------------------
Как слышим, так и пишим.
Истина где-то там...
PM   Вверх
Демо
Дата 10.3.2006, 10:01 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1278
Регистрация: 3.11.2005

Репутация: 6
Всего: 50



Цитата(Петрович @ 10.3.2006, 02:33 Найти цитируемый пост)
Вот именно, что последний, функцианально мало чем отличается от первого. А если быть более точным, то в плане работы SetForegroundWindow(HPtr^) - вообще НИЧЕМ!.


Хм. В плане работы SetForeground да. За одним ма-а-аленьким исключением - работает совместно с функцией AttachThreadInput, что в корне меняет ситуацию.

Цитата(Петрович @ 10.3.2006, 02:33 Найти цитируемый пост)
Т.е., первый вариант работает так-же как и второй.


См. пред. предложение. Условия, в которых выполняется SetForegroundWindow, изменились кардинально.

Цитата(Петрович @ 10.3.2006, 02:33 Найти цитируемый пост)
Ты мне докажи что ни одно из преречисленных условий не выполняется для примера Alex'а !
Лично я считаю что для него выполняется условие номер 1. Поэтому, SetForegroundWindow(HPtr^) прекрастно выполнит свое дело.


А каким образом выполняется первое условие? Приложение, которое надо выдвинуть на передний план, может быть даже не на втором плане, а далее.

Для того, чтобы ни одно из перечисленных условий не выполнялось, достаточно, чтобы выполнялось более одного приложения с GUI и приложение, которое должно быть активировано, было на заднем плане(при условии активности других приложений, конечно).

Цитата(Петрович @ 10.3.2006, 02:33 Найти цитируемый пост)
У Alex'а, вызов SetForegroundWindow делается из второй копии приложения, а в тебя из первой. Вот в этом и есть главное отличие!
Именно поэтому, у тебя не выполняется первое условие.


Нет, не поэтому. Почему? - Условие в пред. предложении.

Цитата(Петрович @ 10.3.2006, 02:33 Найти цитируемый пост)
В общем, надо с большим вниманием читать приводимые примеры и сопоставлять их с документацией.


Очень верное замечание!

Что касается постинга Дата 10.3.2006, 02:52, то пример, конечно же, надуман. Всё, что касается проблем, связанных с SetForeground, касается только лишь тех задач, которые инициированы пользователем.
Т.е. пользователь желает запустить приложение (именно пользователь). В этот момент ему именно это и нужно(а не системе со своими задачами по расписанию).
В том случае, если приложение уже запущено, но скрыто под другими 20-ю приложениями, и нужно активировать предыдущую копию, выдвинув ее на передний план без дополнительных телодвижений(клик на панели задач).
Заметь, что пользователю именно это и нужно!


--------------------
    
PM MAIL ICQ Skype   Вверх
Петрович
Дата 10.3.2006, 10:01 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Участник Клуба
Сообщений: 1000
Регистрация: 2.12.2003
Где: Москва

Репутация: 15
Всего: 55



Согласен.
Все течет все изменяется. Некоторые решения устаревают и становятся "некорректными" сами собой - просто меняются технологии. Вот и с десктопами так. smile


--------------------
Все знать невозможно, но хочется
PM ICQ   Вверх
Демо
Дата 10.3.2006, 10:07 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1278
Регистрация: 3.11.2005

Репутация: 6
Всего: 50



Цитата(Girder @ 10.3.2006, 09:59 Найти цитируемый пост)
Но все енто... справедливо только в пределах одного десктопа.


Конечно. С несколькими рабочими столами не приходилось работать, но думаю, что и в такой ситуации есть какое-нибудь решение.

Это сообщение отредактировал(а) Демо - 10.3.2006, 10:08


--------------------
    
PM MAIL ICQ Skype   Вверх
Петрович
Дата 10.3.2006, 10:08 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Участник Клуба
Сообщений: 1000
Регистрация: 2.12.2003
Где: Москва

Репутация: 15
Всего: 55



Демо, Мне лень с тобой спорить. Но, последнее что скажу, так это то что ты так и не проверил первый вариант Alex'а. И даже не удосужился понять его суть.
Цитата(Демо @ 10.3.2006, 11:01 Найти цитируемый пост)
А каким образом выполняется первое условие? Приложение, которое надо выдвинуть на передний план, может быть даже не на втором плане, а далее.

То которое надо "поднять" да. Но, приведенный перечень условий относится к приложению вызывающему SetForegroundWindow. А вторая копия, из которой вызывается SetForegroundWindow у Alex'а, как раз и есть то приложение которое было "только что" запущено.



--------------------
Все знать невозможно, но хочется
PM ICQ   Вверх
Демо
Дата 10.3.2006, 10:22 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1278
Регистрация: 3.11.2005

Репутация: 6
Всего: 50



Петрович,
Girder,
Alex,

Всё, признаю ошибку.
Похоже, Петрович прав, и в MSDN именно и написано о процессе, вызывающем SetForegroundWindow.
Первый вариант от Alex у меня срабатывает.
Попрошу знакомых протестировать на разных системах.

PS.

А английский, конечно, хотелось бы знать лучше;)

Это сообщение отредактировал(а) Демо - 10.3.2006, 10:23


--------------------
    
PM MAIL ICQ Skype   Вверх
Rouse_
Дата 10.3.2006, 10:36 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 469
Регистрация: 23.4.2005

Репутация: 18
Всего: 29



Зачем попусту спорить? Это же и так очевидно что SetForegroundWindow нужно делать из активного приложения, а мой код был написан для других целей, чтобы показать как это сделать из приложения не являющегося активным, что он собственно и делает smile

ЗЫ: Любой овощь хорошь только будучи употреблен в нужное время smile © ИШ


--------------------
 Vae Victis
(Горе побежденным (лат.))
Демо с открытым кодом: http://rouse.drkb.ru 
PM MAIL WWW ICQ   Вверх
Alex
Дата 10.3.2006, 10:46 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 4147
Регистрация: 25.3.2002
Где: Москва

Репутация: 6
Всего: 162



Цитата(Rouse_ @ 10.3.2006, 10:36 Найти цитируемый пост)
Любой овощь хорошь только будучи употреблен в нужное время

smile


--------------------
Написать можно все - главное четко представлять, что ты хочешь получить в конце. 
PM Skype   Вверх
Петрович
Дата 10.3.2006, 11:20 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Участник Клуба
Сообщений: 1000
Регистрация: 2.12.2003
Где: Москва

Репутация: 15
Всего: 55



Цитата(Rouse_ @ 10.3.2006, 11:36 Найти цитируемый пост)
ЗЫ: Любой овощь хорошь только будучи употреблен в нужное время  © ИШ

Более чем точно.


--------------------
Все знать невозможно, но хочется
PM ICQ   Вверх
Страницы: (3) [Все] 1 2 3 
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Delphi: WinAPI и системное программирование"
Snowybartram
MetalFanbems
PoseidonRrader
Riply

Запрещено:

1. Публиковать ссылки на вскрытые компоненты

2. Обсуждать взлом компонентов и делиться вскрытыми компонентами

  • Литературу по Delphi обсуждаем здесь
  • Действия модераторов можно обсудить здесь
  • С просьбами о написании курсовой, реферата и т.п. обращаться сюда
  • Вопросы по реализации алгоритмов рассматриваются здесь
  • 90% ответов на свои вопросы можно найти в DRKB (Delphi Russian Knowledge Base) - крупнейшем в рунете сборнике материалов по Дельфи
  • 99% ответов по WinAPI можно найти в MSDN Library, оставшиеся 1% здесь

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Snowy, bartram, MetalFan, bems, Poseidon, Rrader, Riply.

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Delphi: WinAPI и системное программирование | Следующая тема »


 




[ Время генерации скрипта: 0.1696 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.