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

Вниз

Почему не работает следующий код...   Найти похожие ветки 

 
WED   (2004-01-20 10:14) [0]

Задача: Узнать заголовок любого видимого сворачиваемого/разворачиваемого/перемещаемого окна.

Когда этот код запускается из под Дельфи, то при попытке переместить окно, всё молча закрывается (и прога и сам Дельфи). При запуске без Дельфи - ошибка: Access violation.

вот код:

function HookProc(Code: integer; WParam: word; LParam: Longint): Longint; stdcall;
var
msg: PEVENTMSG;
buff: ARRAY [0..127] OF Char;
wnd: HWND;
begin
if Code <> HCBT_MOVESIZE then
begin
result := CallNextHookEx(HookHandle, code, WParam, LParam);
Exit;
end;

MSG := Pointer(Wparam);
WND:=Msg.hwnd;
IF (Wnd <> Application.Handle) AND
IsWindowVisible(Wnd) AND
(GetWindow(Wnd, gw_Owner) = 0) AND
(GetWindowText(Wnd, buff, sizeof(buff)) <> 0) THEN
BEGIN
GetWindowText(Wnd, buff, sizeof(buff));
end;
End;

Просветите, спецы!


 
AKul   (2004-01-20 10:37) [1]

А Hook то у тебя глобальный!
Надеюсь ты догадался, что его надо в отдельнуюю DLL выносить!?!?!


 
AKul   (2004-01-20 12:25) [2]

Судя по всему - не догадался.


 
Юрий Зотов   (2004-01-20 12:43) [3]

И не просто в отдельную DLL. В этой DLL переменную HookHandle надо еще сделать глобальной на уровне всей системы. И CallNextHookEx надо вызывать всегда, без всяких условий.

Читайте статью Алексея Павлова на этом сайте.


 
AKul   (2004-01-20 13:05) [4]

И Application.Handle тоже не подойдет......

В общем, перед тем как использовать что-то новое, следует это новое хоть немного изучить, а потом задавать вопросы, подобные " Просветите, спецы!".


 
WED   (2004-01-20 15:30) [5]

Всем спасибо, что ткнули носом ;) Статью уже нашел. Ошибку свою понял. Нашел пример глоб.хука на клавиши... Переделал под себя, вроде получилось.. - идет перехват HCBT_MINMAX, получение заголовка окна (которое изменяется) и занесение его в список. Всё работает. Только иногда ошибка вылетает из-за эксплорера. Происходит это так: запускаю свою прогу, ставлю хук (это взято из примера), разворачиваю, сворачиваю разные окна. Всё нормально - заголовки попадают в список. Потом нетрогаю комп несколько минут - и вылетает ошибка.. В заголовке ошибки эксплорер...


 
AKul   (2004-01-20 15:33) [6]

Какая инменно ошибка?


 
WED   (2004-01-20 16:00) [7]

Ошибка: память не может быть read и т.д....


 
Юрий Зотов   (2004-01-20 16:13) [8]

> WED

Похоже, что-то не то с указателями. Если Вы передаете из хука в основную программу (или наоборот) какие-то адреса, то учли, что эти адреса будут в РАЗНЫХ адресных пространствах?


 
WED   (2004-01-20 16:16) [9]

Блин, ентер нажал...
На этой ошибке жмем ОК и получаем новое окно с ошибкой там уже:
EAccess Violation....DTHook.dll....


 
AKul   (2004-01-20 16:30) [10]

Покажи код


 
WED   (2004-01-20 16:39) [11]

Сама программа:

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ImgList, ComCtrls, ToolWin, StdCtrls, ExtCtrls, Menus;

type
TForm1 = class(TForm)
StatusBar1: TStatusBar;
Bevel1: TBevel;
GroupBox1: TGroupBox;
Button2: TButton;
Button1: TButton;
Memo1: TMemo;
ImageList1: TImageList;
MainMenu1: TMainMenu;
File1: TMenuItem;
Save1: TMenuItem;
N1: TMenuItem;
Exit1: TMenuItem;
N2: TMenuItem;
Startloging1: TMenuItem;
StopLoging1: TMenuItem;
H1: TMenuItem;
Programmhelp1: TMenuItem;
N3: TMenuItem;
About1: TMenuItem;
SaveDialog1: TSaveDialog;
Button3: TButton;
procedure Exit1Click(Sender: TObject);
procedure ToolButton1Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Startloging1Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure StopLoging1Click(Sender: TObject);
procedure Save1Click(Sender: TObject);
procedure FormShow(Sender: TObject);
private
{перезаписываем процедуру окна - подробнее см. ниже}
procedure WndProc(var Msg: TMessage); override;
public
{ Public declarations }
end;

var
Form1: TForm1;
WndFlag: HWND; // дескриптор последнего окна
hDLL: THandle; // дескриптор загружаемой библиотеки
WM_MYWINHOOK: Cardinal; // моё сообщение

implementation

{$R *.dfm}

function GetWndText(WndH: HWND): string;
var
s: string;
Len: integer;
begin
Len:= GetWindowTextLength(WndH)+1; // получаю размер текста
if Len > 1 then
begin
SetLength(s, Len);
GetWindowText(WndH, @s[1], Len); // получаю сам текст, который записывается в s
Result:= s;
end
else
Result:= "caption not detected";
end;

procedure TForm1.Exit1Click(Sender: TObject);
begin
Close;
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
Close;
end;

procedure TForm1.Startloging1Click(Sender: TObject);
begin
Button1.Click;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
Hook: procedure (switch : Boolean; hMainProg: HWND) stdcall;
begin
@hook:= nil; // инициализируем переменную hook
hDLL:= LoadLibrary(PChar("DThook.dll")); { загрузка DLL }
if hDLL > HINSTANCE_ERROR then
begin { если всё без ошибок, то }
@hook:=GetProcAddress(Hdll, "hook"); { получаем указатель на необходимую процедуру}
Button2.Enabled:=True;
Button1.Enabled:=False;
StatusBar1.SimpleText:= "Status: DLL loaded...";
hook(true, Form1.Handle);
StatusBar1.SimpleText:= "Status: loging in progress...";
end
else
begin
ShowMessage("Ошибка при загрузке DLL !");
Exit;
end;

end;

procedure TForm1.Button2Click(Sender: TObject);
var
Hook: procedure (switch : Boolean; hMainProg: HWND) stdcall;
begin
@hook:= nil; // инициализируем переменную hook
if hDLL > HINSTANCE_ERROR then
begin { если всё без ошибок, то }
@hook:=GetProcAddress(Hdll, "hook"); { получаем указатель на необходимую процедуру}
Button1.Enabled:=True;
Button2.Enabled:=False;
hook(false, Form1.Handle);
if FreeLibrary(hDLL) then
begin
StatusBar1.SimpleText:= "Status: DLL unloaded.";
sleep(1000)
end
else
begin
StatusBar1.SimpleText:= "Status: ERROR while unloading DLL";
Exit;
end;
StatusBar1.SimpleText:= "Status: Stoped";
end;

end;

procedure TForm1.StopLoging1Click(Sender: TObject);
begin
Button2.Click;
end;

procedure TForm1.Save1Click(Sender: TObject);
begin
ToolButton1.Click;
end;

{
подмена процедуры окна - необходимо для обработки сообщений, поступивших из
DLL (см. исходный код DLL)
}
procedure TForm1.WndProc(var Msg: TMessage);
begin
inherited ; // выполняем всё то, что должно происходить при поступлении сообщеня окну
{Но если пришло моё сообщение - выполняем следующий код}
if Msg.Msg = WM_MYWINHOOK then
begin
Memo1.Lines.Add(GetWndText(Msg.lParam));
WndFlag:= HWND(Msg.lParam)
end;
end;

procedure TForm1.FormShow(Sender: TObject);
begin
if OpenMutex(MUTEX_ALL_ACCESS, TRUE, "SmallDeskTop") <> 0 then
begin
ShowMessage("Only one instance can be run !");
Close;
end;
CreateMutex(0, TRUE, "SmallDeskTop");
end;

initialization

WndFlag:=0;
{ регистрирую своё сообщение в системе - точно так же надо сделать и в теле DLL
что бы DLL могла посылать главному приложению это сообщение.
}
WM_MYWINHOOK:= RegisterWindowMessage("WM_MYWINHOOK");
end.


 
WED   (2004-01-20 16:39) [12]

Это DLL:

library DTHook;

uses
SysUtils,
Windows,
Messages,
Forms;

const
MMFName: PChar = "DesktopMMF"; // имя объекта файлового отображения

{структура, поля которой будут отображены в файл подкачки}
type
PGlobalDLLData = ^TGlobalDLLData;
TGlobalDLLData = packed record
SysHook: HWND; // дескриптор установленной ловушки
MyAppWnd: HWND; // дескриптор нашего приложения
end;

var
GlobalData: PGlobalDLLData;
MMFHandle: THandle;
WM_MYWINHOOK: Cardinal;

function WinHookProc(code : integer; wParam : word; lParam : longint) : longint; stdcall;
var
AppWnd: HWND; // дескриптор приложения, окно которго изменили
begin

if code <> HCBT_MINMAX then //если это не свернуть/развернуть
begin
Result:= CallNextHookEx(GlobalData^.SysHook, Code, wParam, lParam); //стандартный обработчик
Exit;
end;

AppWnd:= GetForegroundWindow(); //получим хэндл верхнего окна
If AppWnd<>0 Then //если хэндл есть
begin
If IsWindowVisible(AppWnd) {AND //если окно видимо
{(AppWnd.Parent=0)} Then //и окно родительское
SendMessage(GlobalData^.MyAppWnd, WM_MYWINHOOK, wParam, AppWnd); //отправим мессадж нашей проге
end;

CallNextHookEx(GlobalData^.SysHook, Code, wParam, lParam); //и стандартный обработчик
Result:= 0;
end;

{Процедура установки HOOK-а}
procedure hook(switch : Boolean; hMainProg: HWND) export; stdcall;
begin
if switch=true then
begin
{Устанавливаю HOOK, если он не установлен (switch=true). }
GlobalData^.SysHook := SetWindowsHookEx(WH_CBT, @WinHookProc, HInstance, 0);
GlobalData^.MyAppWnd:= hMainProg;
if GlobalData^.SysHook <> 0 then
MessageBox(0, "KEYBOARD HOOK установлен !", "Message from DTHook.dll", 0)
else
MessageBox(0, "HOOK установить не удалось !", "Message from DTHook.dll", 0);

end
else
begin

{Удаляю функцию-фильтр, если она установлена (т.е. switch=false). }
if NOT UnhookWindowsHookEx(GlobalData^.SysHook) then MessageBox(0, "HOOK снять не удалось !", "Message from TDhook.dll", 0);

end;
end;

procedure OpenGlobalData();
begin
{регестрируем свой тип сообщения в системе}
WM_MYWINHOOK:= RegisterWindowMessage("WM_MYWINHOOK");

{получаем объект файлового отображения}
// MMFHandle:= CreateFileMapping(DWord(-1), nil, PAGE_READWRITE, 0, SizeOf(TGlobalDLLData), MMFName); // можно так, но лучше: см. след. строку
MMFHandle:= CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, SizeOf(TGlobalDLLData), MMFName);

if MMFHandle = 0 then
begin
MessageBox(0, "Can""t create FileMapping", "Message from DTHook.dll", 0);
Exit;
end;

{отображаем глобальные данные на АП вызывающего процесса и получаем указатель
на начало выделенного пространства}
GlobalData:= MapViewOfFile(MMFHandle, FILE_MAP_ALL_ACCESS, 0, 0, SizeOf(TGlobalDLLData));
if GlobalData = nil then
begin
CloseHandle(MMFHandle);
MessageBox(0, "Can""t make MapViewOfFile", "Message from DTHook.dll", 0);
Exit;
end;

end;

procedure CloseGlobalData();
begin
UnmapViewOfFile(GlobalData);
CloseHandle(MMFHandle);
end;

procedure DLLEntryPoint(dwReason: DWord); stdcall;
begin
case dwReason of
DLL_PROCESS_ATTACH: OpenGlobalData;
DLL_PROCESS_DETACH: CloseGlobalData;
end;
end;

exports hook;

begin
{назначим поцедуру переменной DLLProc}
DLLProc:= @DLLEntryPoint;
{вызываем назначенную процедуру для отражения факта присоединения данной
библиотеки к процессу}
DLLEntryPoint(DLL_PROCESS_ATTACH);
end.


 
WED   (2004-01-20 16:40) [13]

Вообще это взято из примера в одной статье. Там был клавиатурный хук. Я просто его урезал и слегка переделал. И хук не на клавиши, а на окна сделал. А в общем всё осталось как было.


 
Юрий Зотов   (2004-01-20 17:00) [14]

С виду, вроде, нормально. Можно попробовать прикрепить DLL к EXE и погонять под отладчиком ПРИ АКТИВНОМ EXE.


 
WED   (2004-01-20 17:02) [15]

Если ты еще объяснишь как это и что это....


 
WED   (2004-01-20 17:10) [16]

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


 
WED   (2004-01-20 17:44) [17]

эй! спецы! вы где?


 
Юрий Зотов   (2004-01-20 17:58) [18]

> WED © (20.01.04 17:02) [15]

Sorry, это очень долго. Чтобы разобрать этот код в разговоре нужен примерно час (да и то при условии, что имеется нужный уровень подготовки), а в форуме - слишком много писать придется.

> WED © (20.01.04 17:10) [16]

Обратил внимание, что у Вас wParam - word. Для Win32 это неверно и могут быть чудеса. Попробуйте вот такой код ловушки:

function WinHookProc(code, wParam, lParam: longint): longint; stdcall;
begin
if (code = HCBT_MINMAX) and (GetParent(wParam) = 0) then
SendMessage(GlobalData^.MyAppWnd, WM_MYWINHOOK, wParam, lParam and $FFFF);
// WParam - хэндл окна, lParam - константа SW_xxx
Result := CallNextHookEx(GlobalData^.SysHook, Code, wParam, lParam)
end;


 
AKul   (2004-01-20 17:59) [19]

Тебе же сказали, что на первый взгляд все нормально.
Кроме того, проверил твой код (только основные фрагменты) у себя на машине - никаких глюков не обнаружил (долго не тестировал).
Попробуй выловить свой глюк под SoftIce"ом.
Насчет остальных вопросов: В Help заглянуть слабо? или ты хочешь, чтобы мы это сделали?


 
WED   (2004-01-21 09:46) [20]

2 Akul: Да нет, не надо за меня делать... Достаточно было бы просто ткунть носом какой оператор использовать... Пробовал GetParent(AppWND) - не работает :(


 
pasha_golub   (2004-01-21 09:49) [21]

2WED
GetWindowLong+F1


 
pasha_golub   (2004-01-21 09:59) [22]


var
WinStyle : LongInt;
begin
WinStyle := GetWindowLong(WindowHandle, GWL_STYLE);
if (WinStyle AND WS_VISIBLE) <> 0) //видимое
AND (WinStyle AND WS_CHILD = 0) //не дочернее
then
...


 
WED   (2004-01-21 10:06) [23]

Понял.
Спасибо, Паша.


 
pasha_golub   (2004-01-21 10:14) [24]

2WED
Еще в догонку


var ExStyle: longint;
begin
...
ExStyle := GetWindowLong(WindowHandle, GWL_EXSTYLE);
if ((ExStyle AND WS_EX_TOOLWINDOW) = 0)
..


 
WED   (2004-01-21 10:19) [25]

Гм. А не работает...
Сделал так:

function CheckStyle(AppWnd: HWND): Boolean;
var
WinStyle : LongInt;
begin
Result:=False;
WinStyle := GetWindowLong(AppWnd, GWL_STYLE);
if (WinStyle AND WS_VISIBLE) <> 0) AND ((WinStyle AND WS_CHILD) <> 0) then Result:=True;
end;

Так вот: видимость отлавилвается нормально, а дочернее или нет абсолютно пофиг. Проверял на нескольких программах... На том же Дельфи самом с окном кода программы.... Ощущение, что WS_CHILD у всех окон = 0. :(


 
WED   (2004-01-21 10:42) [26]

2 Юрий Зотов

Попробовал такой код ловушки... Тоже самое... У меня есть подозрение, что происходи конфликт двух (а может больше ловушек)... т.е. моей и еще каких-то программ... Например тот же PuntoSwitcher тоже ведь хук использует.... но только для клавиш... наверное... мда.. блин.


 
AKul   (2004-01-21 10:47) [27]


> WED © (21.01.04 10:42) [26]

Ты писал, что происходит ошибка
EAccess Violation....DTHook.dll....,
т.е. адресс кода в твоей DLL, где происходит ошибка, тебе выдается.
Найди этот адрес в твоей DLL и сопоставь с исходным кодом.


 
WED   (2004-01-21 10:53) [28]

Еще прикол: попробовал на двух других компах (тоже WinXP):
1. Два раза сернуть+развернуть окно проводника и система висит несколько секунд. Рабочий стол (Explorer) после этого падает и перезагружается.
2. Без проблем сворачивание и разворачивание сколько угодно раз окна проводника, но при попытке закрыть окно - сообщение об ошибке...
Кому-нть такие симптомы что-нть говорят? Лично мне, что проблемы с памятью... а вот что именно :(

2AKul - Щас буду пробовать...


 
WED   (2004-01-21 11:03) [29]

2AKul: Не вышло нифига :( Виснет зараза... Так что не получается узнать, да и адрес каждый раз разный.... один раз был FF63F23F,а второй FF63F15B... Отладчик дельфовский запустился, там пытаюсь перейти на этот адрес, в ответ получаю мат. :(


 
AKul   (2004-01-21 11:58) [30]


> WED © (21.01.04 11:03) [29]
> адрес каждый раз разный.... один раз был FF63F23F,а
> второй FF63F15B...

Это не адреса в DLL. Эти адреса, если мне не изменяет память, находятся в диапазоне системных данных (не вытесняемая память).
Проанализируй внимательнее сообщение, скорее всего, по этим адресам происходит запись, а тебе нужен адрес кода, в котором возникла ошибка (если конечно она возникает в теле твоей DLL).
Sorry, если что-то подзабыл.


 
WED   (2004-01-21 12:06) [31]

Других адресов там нет...
Если интересно, то вот http://siam-cat.chat.ru/img/error.JPG
скриншот ошибки.

Еще момент: заметил, что ошибку вызывает не та программа окно которой сворачиваю/разворачиваю, а другая.


 
WED   (2004-01-21 12:15) [32]

Вобщем научился я вызывать ошибку. Открываю проводник и меняю путь. Всё. Ошибка обеспечена.

Сначала вылетает это окно
http://siam-cat.chat.ru/img/error2.JPG
, а следом
http://siam-cat.chat.ru/img/error.JPG


 
AKul   (2004-01-21 12:36) [33]

Что-то мне помнится, что по умолчанию во все процессы меппируется только секция кода DLL.
У тебя же в хуке есть обращение к переменной, расположенной в секции данных
Result:= CallNextHookEx( GlobalData^.SysHook, Code, wParam, lParam); //стандартный обработчик
Следовательно, переменная GlobalData "правильная" только в адресном пространстве процесса, который загрузил эту DLL.
Тебе нужно сделать так, чтобы секция данных тоже меппировалась во все процессы. Тогда отпадет необходимость меппирования файла.
Это все относится к NT.
Кстати, почему ты именно использовал меппирование файла? Ведь под NT меппированные файлы находятся уже не в разделяемой памяти.
Хотя я могу ошибаться!


 
WED   (2004-01-21 12:52) [34]

Почему использовал? Потому что в примере так было.... Я ж говорил: всё содрано из примера, просто сам хук переделан.

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


 
Юрий Зотов   (2004-01-21 13:20) [35]

> AKul © (21.01.04 12:36) [33]

> Следовательно, переменная GlobalData "правильная" только в
> адресном пространстве процесса, который загрузил эту DLL.

Нет, не так. Во всех "экземплярах" данных DLL эта переменная инициализируется по DLL_PROCESS_ATTACH одним и тем же адресом из страничной памяти системы (то есть, памяти разделяемой на уровне всей системы). Обратите внимание на первый параметр CreateFileMapping.

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

Компилятор Delphi не поддерживает описателей а-ля "pragma public", поэтому сделать данные разделяемыми так легко не получится. Именно поэтому здесь и нужем механизм File Mapping"а - как раз он и обеспечивает глобальность данных. Этот споособ не раз проверен и работает он вполне нормально (да и с чего бы ему не работать, если он вполне легален и документирован).


 
WED   (2004-01-21 13:48) [36]

2 Юрий Зотов, AKul:
Уважаемые! Вы меня уже запутали. ;)
просьба возмите приведенный код. Протестируйте. Быстро ошибку получить можно либо открыв проводник и поменять текущую папку.


 
AKul   (2004-01-21 14:02) [37]


> Юрий Зотов © (21.01.04 13:20) [35]

> Во всех "экземплярах" данных DLL эта переменная
> инициализируется по DLL_PROCESS_ATTACH одним и тем же адресом
> из страничной памяти системы

Так и есть, но я имел в виду другое:
Переменная GlobalData есть переменная, которая описана в разделе глобальных переменных и в EXE файле она будет содержаться в секции неинициализируемых данных (bss кажется), которая неразделяемая между процессами (непромеппирована в них).
А теперь представьте, что она находиться по адресу address (именно переменная, а не ее значение) и инициализируется действительно правильным адресом для любого процесса. Что по Вашему произойдет, когда Hook вызовется в адресном пространстве другого процесса? где в таком случае будет находится сама переменная GlobalData (не ее значение), если секция данных непромеппирована в этот процесс? Ведь компилятор заменит строку GlobalDFata^.SysHook на что-то типа
mov eax,[address]; Правилен ли здесь адрес переменной
; GlobalDataдля другого процесса?
mov eax,[eax+смещение поля SysHook]

Зачем здесь нужем механизм File Mapping"а я знаю.
Этим вопросом я пытался подчеркнуть то, что если сама переменная GlobalData (именно переменная, ее адрес, а не значение!!!) доступна во всех процессах (без меппирования секции данных), почему бы тогда вместо этого указателя не использвать саму запись?


 
AKul   (2004-01-21 14:15) [38]


> Юрий Зотов © (21.01.04 13:20) [35]


> Компилятор Delphi не поддерживает описателей а-ля "pragma
> public", поэтому сделать данные разделяемыми так легко не
> получится.


А что мешает модифицировать описание секций в EXE-файле?


 
pasha_golub   (2004-01-21 15:14) [39]


function CheckStyle(AppWnd: HWND): Boolean;
var
WinStyle, ExStyle : LongInt;
WindowOwner : HWND;
begin
WinStyle := GetWindowLong(WindowHandle, GWL_STYLE);
ExStyle := GetWindowLong(WindowHandle, GWL_EXSTYLE);
WindowOwner := GetWindow(WindowHandle, GW_OWNER);
Result:= (WinStyle AND WS_VISIBLE) <> 0)
AND ((WinStyle AND WS_CHILD) <> 0)
AND (WindowOwner = 0)
end;

Но в данном варианте, мы получим TRUE только для тех окон, которые есть на Taskbar"е. Например, у IDE Делфи 6 4 окна. 1 - окно application, второе - главное с меню, третье окно - юнита с кодом (Имеется в виду что наша программа запущена.), а получим true только для одного, для первого. Потому что все остальные есть суть дети.


 
pasha_golub   (2004-01-21 15:18) [40]

В догонку, 4 окно - это окно формы в режиме дизайна.



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

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

Наверх





Память: 0.57 MB
Время: 0.032 c
3-16150
Veetyok
2004-01-14 00:49
2004.02.06
INSERT INTO


14-16743
NsiSoft
2004-01-15 18:45
2004.02.06
ЧАТ!


9-16028
greenrul
2003-07-21 23:06
2004.02.06
Волновой алгоритм поиска пути -


3-16110
Flagman
2004-01-15 12:51
2004.02.06
Refresh при изменении записи


3-16157
Layner
2004-01-14 09:53
2004.02.06
Знатоки, как определить, какая версия ADO стоит на машине юзера?





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
Английский Французский Немецкий Итальянский Португальский Русский Испанский