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

Вниз

ПЕрехват АПИ   Найти похожие ветки 

 
juiceman   (2004-06-27 23:20) [0]

Перехват АПИ-вызовов на Delphi. Существует ли исходник кроме как Ketmara ? На сях ведь их куча, а на паскале вообще никогда не видел. Delphi-программисты более ленивые ? )))


 
Игорь Шевченко ©   (2004-06-27 23:44) [1]


> Существует ли исходник кроме как Ketmara ?


Существует. Об исходнике Ketmar"а слышу впервые. Есть у Digitman"а, есть у меня.


 
Ihor Osov'yak ©   (2004-06-28 00:12) [2]

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


 
KilkennyCat ©   (2004-06-28 00:31) [3]


> Ihor Osov"yak ©   (28.06.04 00:12) [2]


Вау, китайские иероглифы в комментариях! :)


 
juiceman   (2004-06-28 00:35) [4]

2Игорь Шевченко ©
У меня тут все проблема с самими перехватом, внедрение - пройденый этап. Прочитал у Рихтера что заменяя байты на входе функции могут возникнуть проблемы, через отладку (это и есть как я выразился исходник Ketmara : "Использование Debug API: пример перехвата вызовов функций Win32 API  ") тоже нет желания делать, зачем загонять процесс под отладчик? Так вот, Рихтер говорит что наилучший способ это через таблицу импорта. Не получается.
А каким способом Вы эту часть реализовали?


 
Ihor Osov'yak ©   (2004-06-28 01:08) [5]

2 [3] KilkennyCat ©   (28.06.04 00:31)

да нет, это только сайт китайский, исходник - непонятно чей..

2 [4] juiceman   (28.06.04 00:35)

Извинит меня Игорь наверное, что я перед отца в пекло - примерно так и делалось.  Бросаю два ключевых модуля, как есть, наверное можно разобраться, за качество кода сорри, чем багаты - тем и рады (первый модулек - декларации - практически в первозданном виде), второй -я там основательно погулял. Также смутно припоминаю, что права на запись памяти -
GetRight - не очень то удачно сделано, но сейчас уже вспоминать неохота.
Во втором модуля оставлен перехват только нескольких функций, остальные - вырезаны, а то код слишком большой. Модуль после вырезания - компилируется.

WrTm - отладочный вывод, glob_e0capt - модуль, в котором декларировано несколько несущественніх глобальных переменных..

Внедрение - при помощи хука, это за пределами этих модульков

unit PEStuff;

interface
uses Windows;

type
 PImageDosHeader = ^TImageDosHeader;
 _IMAGE_DOS_HEADER = packed record { DOS .EXE  header           }
   e_magic: Word; { Magic umber  }
   e_cblp: Word; { Bytes on last page of file      }
   e_cp: Word; { Pages in file                   }
   e_crlc: Word; { Relocations                      }
   e_cparhdr: Word; { Size of header in paragraphs     }
   e_minalloc: Word; { Minimum extra paragraphs needed  }
   e_maxalloc: Word; { Maximum extra paragraphs needed  }
   e_ss: Word; { Initial (relative) SS value      }
   e_sp: Word; { Initial SP value                 }
   e_csum: Word; { Checksum                         }
   e_ip: Word; { Initial IP value                 }
   e_cs: Word; { Initial (relative) CS value      }
   e_lfarlc: Word; { File address of relocation table }
   e_ovno: Word; { Overlay number                   }
   e_res: array[0..3] of Word; { Reserved words                   }
   e_oemid: Word; { OEM identifier (for e_oeminfo)   }
   e_oeminfo: Word; { OEM information; e_oemid specific}
   e_res2: array[0..9] of Word; { Reserved words                   }
   _lfanew: LongInt; { File address of new exe header   }
 end;
 TImageDosHeader = _IMAGE_DOS_HEADER;

 PIMAGE_FILE_HEADER = ^IMAGE_FILE_HEADER;
 IMAGE_FILE_HEADER = packed record
   Machine: WORD;
   NumberOfSections: WORD;
   TimeDateStamp: DWORD;
   PointerToSymbolTable: DWORD;
   NumberOfSymbols: DWORD;
   SizeOfOptionalHeader: WORD;
   Characteristics: WORD;
 end;

 PIMAGE_DATA_DIRECTORY = ^IMAGE_DATA_DIRECTORY;
 IMAGE_DATA_DIRECTORY = packed record
   VirtualAddress: DWORD;
   Size: DWORD;
 end;

 PIMAGE_SECTION_HEADER = ^IMAGE_SECTION_HEADER;
 IMAGE_SECTION_HEADER = packed record
   Name: packed array[0..IMAGE_SIZEOF_SHORT_NAME - 1] of
   Char;
   VirtualSize: DWORD; // or VirtualSize (union);
   VirtualAddress: DWORD;
   SizeOfRawData: DWORD;
   PointerToRawData: DWORD;
   PointerToRelocations: DWORD;
   PointerToLinenumbers: DWORD;
   NumberOfRelocations: WORD;
   NumberOfLinenumbers: WORD;
   Characteristics: DWORD;
 end;

 PIMAGE_OPTIONAL_HEADER = ^IMAGE_OPTIONAL_HEADER;
 IMAGE_OPTIONAL_HEADER = packed record
   { Standard fields. }
   Magic: WORD;
   MajorLinkerVersion: Byte;
   MinorLinkerVersion: Byte;
   SizeOfCode: DWORD;
   SizeOfInitializedData: DWORD;
   SizeOfUninitializedData: DWORD;
   AddressOfEntryPoint: DWORD;
   BaseOfCode: DWORD;
   BaseOfData: DWORD;
   { NT additional fields. }
   ImageBase: DWORD;
   SectionAlignment: DWORD;
   FileAlignment: DWORD;
   MajorOperatingSystemVersion: WORD;
   MinorOperatingSystemVersion: WORD;
   MajorImageVersion: WORD;
   MinorImageVersion: WORD;
   MajorSubsystemVersion: WORD;
   MinorSubsystemVersion: WORD;
   Reserved1: DWORD;
   SizeOfImage: DWORD;
   SizeOfHeaders: DWORD;
   CheckSum: DWORD;
   Subsystem: WORD;
   DllCharacteristics: WORD;
   SizeOfStackReserve: DWORD;
   SizeOfStackCommit: DWORD;
   SizeOfHeapReserve: DWORD;
   SizeOfHeapCommit: DWORD;
   LoaderFlags: DWORD;
   NumberOfRvaAndSizes: DWORD;
   DataDirectory: packed array
   [0..IMAGE_NUMBEROF_DIRECTORY_ENTRIES - 1] of IMAGE_DATA_DIRECTORY;
   Sections: packed array[0..9999] of IMAGE_SECTION_HEADER;
 end;

 PIMAGE_NT_HEADERS = ^IMAGE_NT_HEADERS;
 IMAGE_NT_HEADERS = packed record
   Signature: DWORD;
   FileHeader: IMAGE_FILE_HEADER;
   OptionalHeader: IMAGE_OPTIONAL_HEADER;
 end;
 PImageNtHeaders = PIMAGE_NT_HEADERS;
 TImageNtHeaders = IMAGE_NT_HEADERS;

 {  PIMAGE_IMPORT_DESCRIPTOR = ^IMAGE_IMPORT_DESCRIPTOR;
   IMAGE_IMPORT_DESCRIPTOR = packed record
     Characteristics: DWORD; // or original first thunk
                             // 0 for terminating null import descriptor
                             // RVA to original unbound IAT  TimeDateStamp: DWORD;
                             // 0 if not bound,
                             // -1 if bound, and real date\time stamp
                             //     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
                             // O.W. date/time stamp of DLL bound to (Old BIND)
     Name: DWORD;
     FirstThunk: DWORD;      // PIMAGE_THUNK_DATA // RVA to IAT (if bound this IAT has actual addresses)
     ForwarderChain: DWORD;  // -1 if no forwarders
   end;
   TImageImportDescriptor = IMAGE_IMPORT_DESCRIPTOR;
   PImageImportDescriptor = PIMAGE_IMPORT_DESCRIPTOR;}

 PIMAGE_IMPORT_BY_NAME = ^IMAGE_IMPORT_BY_NAME;
 IMAGE_IMPORT_BY_NAME = record
   Hint: Word;
   Name: array[0..0] of Char;
 end;

 PIMAGE_THUNK_DATA = ^IMAGE_THUNK_DATA;
 IMAGE_THUNK_DATA = record
   Whatever: DWORD;
 end;

 PImage_Import_Entry = ^Image_Import_Entry;
 Image_Import_Entry = record
   Characteristics: DWORD;
   TimeDateStamp: DWORD;
   MajorVersion: Word;
   MinorVersion: Word;
   Name: DWORD;
   LookupTable: DWORD;
 end;

const
 IMAGE_DOS_SIGNATURE = $5A4D; // MZ
 IMAGE_OS2_SIGNATURE = $454E; // NE
 IMAGE_OS2_SIGNATURE_LE = $454C; // LE
 IMAGE_VXD_SIGNATURE = $454C; // LE
 IMAGE_NT_SIGNATURE = $00004550; // PE00

implementation

end.


 
Ihor Osov'yak ©   (2004-06-28 01:12) [6]

unit HookTextUnit;

interface
uses Windows, SysUtils, Classes, PEStuff;

type
 TConvertTextFunction = function(text: string): string;
 TTextOutA = function(hdc: HDC; x, y: Integer; text: PAnsiChar; len: Integer): BOOL; stdcall;
 TTextOutW = function(hdc: HDC; x, y: Integer; text: PWideChar; len: Integer): BOOL; stdcall;
 TExtTextOutA = function(hdc: HDC; x, y: Integer; Options: DWORD; Clip:
   PRect; text: PAnsiChar; len: Integer; dx: PInteger): BOOL; stdcall;
 TExtTextOutW = function(hdc: HDC; x, y: Integer; Options: DWORD;
   Clip: PRect; text: PWideChar; len: Integer; dx: PInteger): BOOL; stdcall;
 TDrawTextA = function(hdc: HDC; text: PAnsiChar; len: Integer; rect: PRect; Format: DWORD): Integer; stdcall;
 TDrawTextW = function(hdc: HDC; text: PWideChar; len: Integer; rect: PRect; Format: DWORD): Integer; stdcall;
 TDrawTextExA = function(hdc: HDC; text: PAnsiChar; len: Integer; rect: PRect; Format: DWORD;
   DTParams: PDrawTextParams): Integer; stdcall;
 TDrawTextExW = function(hdc: HDC; text: PWideChar; len: Integer; rect: PRect;
   Format: DWORD; DTParams: PDrawTextParams): Integer; stdcall;

 PPointer = ^Pointer;

 TImportCode = packed record
   JumpInstruction: Word; // should be $25FF
   AddressOfPointerToFunction: PPointer;
 end;
 PImportCode = ^TImportCode;

procedure HookTextOut(aConvertFunction: TConvertTextFunction);
procedure UnhookTextOut;

implementation

uses glob_e0capt, messages;

var
 ConvertTextFunction: TConvertTextFunction = nil;
 OldTextOutA: TTextOutA = nil;
 OldTextOutW: TTextOutW = nil;
 OldExtTextOutA: TExtTextOutA = nil;
 OldExtTextOutW: TExtTextOutW = nil;
 OldDrawTextA: TDrawTextA = nil;
 OldDrawTextW: TDrawTextW = nil;
 OldDrawTextExA: TDrawTextExA = nil;
 OldDrawTextExW: TDrawTextExW = nil;

function StrLenW(s: PWideChar): Integer;
var
 i: Integer;
begin
 if s = nil then
 begin
   Result := 0;
   exit;
 end;
 i := 0;
 try
   while (s[i] <> #0) do
     inc(i);
 except
 end;
 Result := i;
end;

var
 sumStr: string;
 countStep: integer;

procedure SendCapturedText(Y: integer; aStr: string);
var
 cd: TCopyDataStruct;

begin
 if sumStr <> "" then
   sumStr := sumStr + ";";
 inc(countStep);
 sumStr := sumStr + aStr;
 if countStep < 4 then
   exit;
 cd.dwData := Y;
 cd.cbData := Length(sumStr) + 1;
 cd.lpData := PChar(sumStr);
 SendMessage(toolsHandle, WM_COPYDATA,
   0,
   LParam(@cd));
 countStep := 0;
 sumStr := "";

end;

function NewTextOutA(hdc: HDC; x, y: Integer; text: PAnsiChar; len:
 Integer): BOOL; stdcall;
var
 s: string;
begin
 if enCapture then
   if WindowFromDC(hdc) = handleForCapture then
   begin
     try
       if len <= 0 then
         s := text
       else
       begin
         SetLength(s, len);
         FillChar(s[1], len + 1, 0);
         Move(text^, s[1], len);
       end;
       if x = 0 then
         SendCapturedText(Y, s);
       WrTm("hdc= " + IntToHex(hdc, 8) + "  x= " + IntToStr(x) + " y= " + IntToStr(y) + " text = " + s);
       SetLength(s, 0);
     except

     end;
   end;

 if @OldTextOutA <> nil then
   Result := OldTextOutA(hdc, x, y, text, len)
 else
   Result := false;

end;


далее пропускаю код остальных перехватчиков и далее снова содержательная часть


 
Ihor Osov'yak ©   (2004-06-28 01:13) [7]

function NewDrawTextExW(hdc: HDC; text: PWideChar; len: Integer; rect:
 PRect;
 Format: DWORD; DTParams: PDrawTextParams): Integer; stdcall;
var
 s: WideString;
begin
 Result := 0;
 exit;

 try
   if Len < 0 then
     Len := strlenW(text);
   if Len > 0 then
   begin
     SetLength(s, len);
     FillChar(s[1], len * 2 + 2, 0);
     Move(text^, s[1], len * 2);
     if @ConvertTextFunction <> nil then
       s := ConvertTextFunction(s);
     if @OldDrawTextExW <> nil then

       Result := OldDrawTextExW(hdc, PWideChar(s), length(s), rect, Format, DTParams)
     else
       Result := 0;
   end
   else
     Result := OldDrawTextExW(hdc, text, 0, rect, Format, DTParams);
 except
   Result := 0;
 end;
end;

function PointerToFunctionAddress(Code: Pointer): PPointer;
var
 func: PImportCode;
begin

 Result := nil;
 if Code = nil then
   exit;
 try
   func := code;
   if (func.JumpInstruction = $25FF) then
   begin
     Result := func.AddressOfPointerToFunction;
   end;
 except
   Result := nil;
 end;
end;

function FinalFunctionAddress(Code: Pointer): Pointer;
var
 func: PImportCode;
begin

 Result := Code;
 if Code = nil then
   exit;
 try
   func := code;
   if (func.JumpInstruction = $25FF) then
   begin
     Result := func.AddressOfPointerToFunction^;
   end;
 except
   Result := nil;
 end;
end;

procedure GetRight;
var
 si: TSYSTEMINFO;
 mi: TMEMORYBASICINFORMATION;
 p: cardinal;
 dw: dword;
 //cb:Tcb;
begin

 GetSystemInfo(si);
 p := cardinal(si.lpMinimumApplicationAddress);

 while (p < cardinal(si.lpMaximumApplicationAddress)) (*and (HeapsCount<MAX_HEAPS)*) do
 begin
   VirtualQueryEx(GetCurrentProcess, Pointer(p), mi, sizeof(mi));
   //Heaps[HeapsCount].Offset := p;
   //Heaps[HeapsCount].Size := mi.RegionSize;
   //Heaps[HeapsCount].Valid := (mi.State = MEM_COMMIT);
   //Data:=Pointer(HeapsCount);
   VirtualProtectEx(GetCurrentProcess, pointer(p), mi.RegionSize,
     PAGE_EXECUTE_READWRITE, @dw);

   Inc(p, mi.RegionSize);
   //inc(HeapsCount);
 end;
end;
var
 level: integer;

function PatchAddress(OldFunc, NewFunc: Pointer): Integer;
var
 BeenDone: TList;

 function PatchAddressInModule(hModule: THandle;
   OldFunc, NewFunc: Pointer): Integer;
 var
   Dos: PImageDosHeader;
   NT: PImageNTHeaders;
   ImportDesc: PImage_Import_Entry;
   rva: DWORD;
   Func: PPointer;
   DLL: string;
   f: Pointer;
   written: DWORD;
   sb: string;
   i: integer;
 begin
   inc(level);
   sb := " ";
   for i := 1 to level * 3 do
     sb := sb + " ";
   Result := 0;
   Dos := Pointer(hModule);

   //Wr("");
   //WrTm(sb+"cp745: start tru");

   if BeenDone.IndexOf(Dos) >= 0 then
   begin
     dec(level);
     exit;
   end;
   //WrTm(sb+"cp761");
   BeenDone.Add(Dos);
   OldFunc := FinalFunctionAddress(OldFunc);
   if IsBadReadPtr(Dos, SizeOf(TImageDosHeader)) then
   begin
     dec(level);
     exit;
   end;
   //WrTm(sb+"cp768");
   if Dos.e_magic <> IMAGE_DOS_SIGNATURE then
   begin
     dec(level);
     exit;
   end;
   //WrTm(sb+"cp773");
   NT := Pointer(Integer(Dos) + dos._lfanew);
   //  if IsBadReadPtr(NT,SizeOf(TImageNtHeaders)) then exit;

   RVA := NT^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;

   if RVA = 0 then
     exit;
   //WrTm(sb+"cp780 before loop1");
   ImportDesc := pointer(dword(Dos) + RVA);
   while (ImportDesc^.Name <> 0) do
   begin
     DLL := PChar(dword(Dos) + ImportDesc^.Name);
     //WrTm(Dll);
     //WrTm(sb+"cp785 tru pach in "+Dll);

     PatchAddressInModule(GetModuleHandle(PChar(DLL)), OldFunc, NewFunc);
     //WrTm(sb+"cp788 after tru "+Dll);
     //WrTm(sb+"cp789 before loop2");
     Func := Pointer(dword(DOS) + ImportDesc.LookupTable);
     while Func^ <> nil do
     begin
       //WrTm(sb+"cp792 Func="+IntToHex(integer(Func),8)+" "+IntToHex(integer(Func^),8));
       f := FinalFunctionAddress(Func^);
       //WrTm(sb+"cp794 f   ="+IntToHex(integer(f),8));
       //WrTm(sb+"cp795 oldf   ="+IntToHex(integer(OldFunc),8));
       if f = OldFunc then
       begin
         //WrTm(sb+"cp794");
         //WrTm(sb+Dll);
         WriteProcessMemory(GetCurrentProcess, Func, @NewFunc, 4, written);
         //Move(NewFunc,Func^,4);
         //asm
         //  int 3
         //end;
         //integer(Func^):=integer(NewFunc);
         if Written > 0 then
         begin
           Inc(Result);
           //WrTm(sb+"res<>0");
         end
         else
         begin
           //i:=Getlasterror;
           //WrTm(sb+"res=0 err="+IntToHex(i,8));
         end
       end;
       Inc(Func);
     end;
     //WrTm(sb+"cp805  after loop2");
     Inc(ImportDesc);
   end;
   //WrTm(sb+"cp808  after loop1");
   //WrTm(sb+"cp808: end tru");
   dec(level);
 end;

begin //PatchAddress
 result := 0;
 if NewFunc = nil then
   exit;
 if OldFunc = nil then
   exit;
 if NewFunc = OldFunc then
   exit;
 BeenDone := TList.Create;
 try
   //Wr("");
   //enWr2 := ParamStr(0)="D:\!prj\SmallPrj\ScraperDB\test_v\test\Release\test.exe";
   //WrTm("inp   : "+IntToStr(GetModuleHandle(nil)));
   //WRTm("param0: "+ParamStr(0));
   Result := PatchAddressInModule(GetModuleHandle(nil), OldFunc, NewFunc);
 finally
   BeenDone.Free;
 end;
end; //PatchAddress

procedure HookTextOut(aConvertFunction: TConvertTextFunction);
begin
 //Wr("");

 if @OldTextOutA = nil then
   @OldTextOutA := FinalFunctionAddress(@TextOutA);
 if @OldTextOutW = nil then
   @OldTextOutW := FinalFunctionAddress(@TextOutW);

 if @OldExtTextOutA = nil then
   @OldExtTextOutA := FinalFunctionAddress(@ExtTextOutA);
 if @OldExtTextOutW = nil then
   @OldExtTextOutW := FinalFunctionAddress(@ExtTextOutW);

 if @OldDrawTextA = nil then
   @OldDrawTextA := FinalFunctionAddress(@DrawTextA);
 if @OldDrawTextW = nil then
   @OldDrawTextW := FinalFunctionAddress(@DrawTextW);

 if @OldDrawTextExA = nil then
   @OldDrawTextExA := FinalFunctionAddress(@DrawTextExA);
 if @OldDrawTextExW = nil then
   @OldDrawTextExW := FinalFunctionAddress(@DrawTextExW);

 @ConvertTextFunction := @aConvertFunction;

 GetRight;

 PatchAddress(@OldTextOutA, @NewTextOutA);
 PatchAddress(@OldTextOutW, @NewTextOutW);

end;

procedure UnhookTextOut;
begin
 //If @OldTextOutA<>nil then begin
 PatchAddress(@NewTextOutA, @OldTextOutA);
 PatchAddress(@NewTextOutW, @OldTextOutW);
 PatchAddress(@NewExtTextOutA, @OldExtTextOutA);
 PatchAddress(@NewExtTextOutW, @OldExtTextOutW);
 PatchAddress(@NewDrawTextA, @OldDrawTextA);
 PatchAddress(@NewDrawTextW, @OldDrawTextW);
 PatchAddress(@NewDrawTextExA, @OldDrawTextExA);
 PatchAddress(@NewDrawTextExW, @OldDrawTextExW);

 // end;
end;

initialization
finalization

 UnhookTextOut;
end.


 
juiceman   (2004-06-28 01:39) [8]

Ух, ну Вы мне приятный сюрприз преподнесли, буду копаться %) Огромное спасибо!!!


 
Ihor Osov'yak ©   (2004-06-28 01:56) [9]

2 [8] juiceman   (28.06.04 01:39)

Копайся, копайся. Это заведомо в лучшем состоянии, чем я получил тогда.. За код сильно не ругать, я в общем-то тогда системными вещами только начинал заниматься, так что в коде могут быть ляпы, но проект работоспособный.


 
Digitman ©   (2004-06-28 08:15) [10]


> juiceman


при реализации перехвата требуется правка не только таблицы импорта, но и таблицы экспорта.. последнее необходимо для случаев динамической загрузки библиотеки, эксп.ф-ция которой перехватывается ... если не править таблицу экспорта, то GetProcAddr() вернет старую (оригинальную) точку входа

таблица же импорта правится только для случая стат.загрузки


 
Игорь Шевченко ©   (2004-06-28 10:25) [11]

Неплохая методика перехвата была описана у Гэри Неббета, в его книжке "Справочник по функциям базового API для Windows NT", с примером. Но не на Delphi, разумеется


 
Ihor Osov'yak ©   (2004-06-28 12:40) [12]

2 [10] Digitman ©   (28.06.04 08:15)

> если не править таблицу экспорта, то GetProcAddr() вернет старую (оригинальную) точку входа

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

> [11] Игорь Шевченко ©   (28.06.04 10:25)

Стоит на полке. Руки не доходят серьезно заняться. И что за жизнь такая - раньше доступа к информации не было, теперь информация есть, времени нету ее обработать :-(


 
Игорь Шевченко ©   (2004-06-28 13:38) [13]

Ihor Osov"yak ©   (28.06.04 12:40)

Хорошая книжка :)


> И что за жизнь такая - раньше доступа к информации не было,
> теперь информация есть, времени нету ее обработать


И не говори, аналогичная ситуация :)



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

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

Наверх




Память: 0.53 MB
Время: 0.042 c
14-1090216479
serg128
2004-07-19 09:54
2004.08.08
Подскажите хорошую статью по написанию служб в C++ Builder Delphi


14-1090582484
Fishka
2004-07-23 15:34
2004.08.08
Тесты на знание MS SQL и Delphi на русском языке


1-1090519806
Боян Георгиев
2004-07-22 22:10
2004.08.08
Scroll в конце TMemo


4-1088413866
Buster
2004-06-28 13:11
2004.08.08
Как определить время создания файла и директории?


3-1089963918
stud
2004-07-16 11:45
2004.08.08
сортировка dbgrideh





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