Drag&Drop, перетаскивание строк в компоненте DBGrid

Не мало статей уже написано про то как перетаскивать различные объекты, по форме используя функцию Drag&Drop, но все-таки в очередной раз хочу вернуться к этой теме и рассказать вам как можно используя Drag&Drop легко организовать перетаскивание строк в компоненте DBGrid. Не буду вас долго томить с введением, и поэтому давайте начинать...

Открываем Delphi и создаем новый проект. На форме нам понадобиться один компонент Memo с закладки Standard (именно в него мы будем перетаскивать строки), а также непосредсвенно сам компонент DbGrid с закладки DataControl.

Delphi Drag&Drop

Ну что я надеюсь, вы уже справились и кинули Memo и DbGrid на форму, да вот еще, в этом уроке я не буду рассказывать вам о том как подключиться к базе данных и как вывести таблицу из БД в компонент DBgrid, я предполагаю что вы это умеете делать.

Что теперь, выделяем DbGrid и в Object Inspector'e на вкладке Events создаем событие OnCellClick (кликаем 2 раза)
Теперь, когда Delphi создал для нас заготовку под будующую процедуру напишем между begin и end вот такой код:


DBGrid1.BeginDrag(True);  

Далее, нам нужно сказать компоненту Memo откуда ему можно принимать данные. Поэтому создаем обработчик событий OnDragOver на компоненте Memo и опять же между begin .. end прописываем вот такой код:


Accept:= Source IS TDBGrid; 

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


procedure TForm1.Memo1DragDrop(Sender, Source: TObject; X, Y: Integer);  
var i : integer;  
begin  
Memo1.Clear;  
for i:= 0 to -1 + DBGrid1.FieldCount do  
begin  
Memo1.Lines.Add(DBGrid1.Fields[i].AsString);  
end;  
end;   

Вот и все как видите ничего сложного, запускаем проект и переносим строчку из DbGrid в Memo.

Добавлено: 31 Июля 2018 08:01:42 Добавил: Андрей Ковальчук

Хранение данных в EXE-файле

Вы можете включить любой тип данных как RCDATA или пользовательских тип ресурса. Это очень просто. Данный совет покажет вам общую технику создания такого ресурса.


Type  
  TStrItem = String[39];  { 39 символов + байт длины -> 40 байтов }  
  TDataArray = Array [0..7, 0..24] of TStrItem;  
  
Const  
  Data: TDataArray = (  
  ('..', ...., '..' ),  { 25 строк на строку }  
  ...                   { 8 таких строк }  
  ('..', ...., '..' )); { 25 строк на строку }  

Данные размещаются в вашем сегменте данных и занимают в нем 8K. Если это слишком много для вашего приложения, поместите реальные данные в ресурс RCDATA. Следующие шаги демонстрируют данный подход. Создайте небольшую безоконную программку, объявляющую типизированную константу как показано выше, и запишите результат в файл на локальный диск:


program MakeData;  
type  
  TStrItem = string[39]; { 39 символов + байт длины -> 40 байтов }  
  TDataArray = array[0..7, 0..24] of TStrItem;  
  
const  
  Data: TDataArray = (  
    ('..', ...., '..'), { 25 строк на строку }  
    ... { 8 таких строк }  
    ('..', ...., '..')); { 25 строк на строку }  
  
var  
  F: file of TDataArray;  
begin  
  Assign(F, 'data.dat');  
  Rewrite(F);  
  Write(F, Data);  
  Close(F);  
end.  

Теперь подготовьте файл ресурса и назовите его DATA.RC. Он должен содержать только следующую строчку:


DATAARRAY RCDATA "data.dat"  

Сохраните это, откройте сессию DOS, перейдите в каталог где вы сохранили data.rc (там же, где и data.dat!) и выполните следующую команду:


brcc data.rc (brcc32 для Delphi 2.0)
Теперь вы имеете файл data.res, который можете подключить к своему Delphi-проекту. Во время выполнения приложения вы можете генерировать указатель на данные этого ресурса и иметь к ним доступ, что и требовалось.


{ в секции interface модуля  }  
type  
  TStrItem = string[39]; { 39 символов + байт длины -> 40 байтов }  
  TDataArray = array[0..7, 0..24] of TStrItem;  
  PDataArray = ^TDataArray;  
const  
  pData: PDataArray = nil; { в Delphi 2.0 используем Var }  
  
implementation  
{$R DATA.RES}  
  
procedure LoadDataResource;  
var  
  dHandle: THandle;  
begin  
  { pData := Nil; если pData - Var }  
  dHandle := FindResource(hInstance, 'DATAARRAY', RT_RCDATA);  
  if dHandle <> 0 then  
  begin  
    dhandle := LoadResource(hInstance, dHandle);  
    if dHandle <> 0 then  
      pData := LockResource(dHandle);  
  end;  
  if pData = nil then  
    { неудача, получаем сообщение об ошибке с помощью 
    WinProcs.MessageBox, без помощи VCL, поскольку здесь код 
    выполняется как часть инициализации программы и VCL 
    возможно еще не инициализирован! }  
end;  
  
initialization  
  LoadDataResource;  
end.  

Теперь вы можете ссылаться на элементы массива с помощью синтаксиса pData^[i,j].

Автор: Peter Below

Добавлено: 31 Июля 2018 07:59:42 Добавил: Андрей Ковальчук

Файловые функции DELPHI. Файловые операции средствами ShellApi

В Delphi существует понятие - подпрограммы управления файлами (category File management routines). Процедуры и функции входящие в эту категорию находятся в модулях System, SysUtils (каталог Source\Rtl\Sys) и FileCtrl (каталог Source\Vcl). Модуль FileCtrl содержит только две функции из категории подпрограмм управления файлами - это DirectoryExists и ForceDirectories. Местонахождение остальных процедур и функций определяется следующим образом. Если в подпрограмме используется файловая переменная, то она входит в модуль System. Если дескриптор или имя файла в виде строки, то в модуль SysUtils. Правда есть исключения (интуитивно понятные) ChDir входит в System. Также в System входят MkDir, RmDir из категории ввода/вывода (I/O routines). Надо отметить, что все подпрограммы, отнесенные к категориям ввода/вывода и текстовых файлов (Text file routines) находятся в модуле System (исключая процедуру AssignPrn входящую в модуль Printers каталог Source\Vcl). Вот список подпрограмм отсортирован по категориям и по алфавиту.

File management routines - подпрограммы управления файлами

Модуль Подпрограмма

System procedure AssignFile(var F; FileName: string); // Связывает файловую переменную с именем файла
System procedure ChDir(S: string); // Изменяет текущий каталог
System procedure CloseFile(var F); // Закрывает файл по файловой переменной
SysUtils function CreateDir(const Dir: string): Boolean; // Создает новый каталог
SysUtils function DeleteFile(const FileName: string): Boolean; // Удаляет файл
FileCtrl function DirectoryExists(Name: string): Boolean; // Проверяет наличие каталога
SysUtils function DiskFree(Drive: Byte): Int64; // Определяет свободное пространство на диске
SysUtils function DiskSize(Drive: Byte): Int64; // Определяет полный размер диска
SysUtils function FileAge(const FileName: string): Integer; // Определяет время последнего обновления
SysUtils procedure FileClose(Handle: Integer); // Закрывает файл по дескриптору
SysUtils function FileDateToDateTime(FileDate: Integer): TDateTime; // Преобразует DOS-дату в Delphi-дату
SysUtils function FileExists(const FileName: string): Boolean; // Проверяет наличие файла
SysUtils function FileGetAttr(const FileName: string): Integer; // Определяет атрибуты файла
SysUtils function FileGetDate(Handle: Integer): Integer; // Определяет время последнего обновления
SysUtils function FileOpen(const FileName: string; Mode: LongWord): Integer; // Открывает существующий файл
SysUtils function FileRead(Handle: Integer; var Buffer; Count: Integer): Integer; // Читает из файла
SysUtils function FileSearch(const Name, DirList: string): string; // Ищет файл в списке каталогов
SysUtils function FileSeek(Handle, Offset, Origin: Integer): Integer; // Меняет позицию указателя
SysUtils function FileSetAttr(const FileName: string; Attr: Integer): Integer; // Устанавливает атрибуты файла
SysUtils function FileSetDate(Handle: Integer; Age: Integer): Integer; // Устанавливает время последнего обновления
SysUtils function FileWrite(Handle: Integer; const Buffer; Count: Integer): Integer; // Записывает в файл
SysUtils procedure FindClose(var F: TSearchRec); // Прекращает поиск файлов
SysUtils function FindFirst(const Path: string; Attr: Integer; var F: TSearchRec): Integer; // Начинает поиск файлов
SysUtils function FindNext(var F: TSearchRec): Integer; // Продолжает поиск файлов
FileCtrl function ForceDirectories(Dir: string): Boolean; // Создает все каталоги пути
SysUtils function GetCurrentDir: string; // Определяет текущий каталог
System procedure GetDir(D: Byte; var S: string); // Определяет текущий каталог
SysUtils function RemoveDir(const Dir: string): Boolean; // Удаляет каталог
SysUtils function RenameFile(const OldName, NewName: string): Boolean; // Переименовывает файл
SysUtils function SetCurrentDir(const Dir: string): Boolean; // Устанавливает текущий каталог


I/O routines - подпрограммы ввода/вывода

Модуль Подпрограмма
System procedure Append(var F: Text); Добавляет текст в конец файла
System procedure BlockRead(var F: File; var Buf; Count: Integer [; var AmtTransferred: Integer]); Читает блок из файла
System procedure BlockWrite(var f: File; var Buf; Count: Integer [; var AmtTransferred: Integer]); Записывает блок в файл
System function Eof(var F): Boolean; Определяет конец файла
System function FilePos(var F): Longint; Определяет позицию указателя
System function FileSize(var F): Integer; Определяет размер файла
System function IOResult: Integer; Определяет ошибки предыдущего ввода/вывода
System procedure MkDir(S: string); Создает каталог
System procedure Rename(var F; Newname:string); Переименовывает файл
System procedure Reset(var F [: File; RecSize: Word ] ); Открывает файл
System procedure Rewrite(var F: File [; Recsize: Word ] ); Создает и открывает новый файл
System procedure RmDir(S: string); Удаляет каталог
System procedure Seek(var F; N: Longint); Устанавливает позицию указателя
System procedure Truncate(var F); Усекает файл до текущей позиции указателя


Text file routines - подпрограммы текстовых файлов

Модуль Подпрограмма

Printers procedure AssignPrn(var F: Text); Связывает файловую переменную с принтером
System function Eoln [(var F: Text) ]: Boolean; Определяет конец строки
System procedure Erase(var F); Удаляет файл
System procedure Flush(var F: Text); Переписывает данные в файл из его буфера
System procedure Read(F , V1 [, V2,...,Vn ] ); Читает из файла
System procedure Readln([ var F: Text; ] V1 [, V2, ...,Vn ]); Читает из файла до конца строки
System function SeekEof [ (var F: Text) ]: Boolean; Определяет конец файла
System function SeekEoln [ (var F: Text) ]: Boolean; Определяет конец строки
System procedure SetTextBuf(var F: Text; var Buf [ ; Size: Integer] ); Устанавливает новый буфер
System procedure Write(F, V1 [, V2,...,Vn ] ); Записывает в файл
System procedure Writeln([ var F: Text; ] V1 [, V2, ...,Vn ] ); Записывает в файл с концом строки


Примеры:

Проверяем наличие файла и записываем его


type  
TFileData=record  
Name:String[10];  
ExtDat:Extended;  
end;  
var  
Cals: File of TFileData;  
CalsData: TFileData;  
  
procedure NAME;  
//Описание процедуры  
var p: Real;  
u: Byte;  
begin  
Road:='{файл}.dat';  
Dest:='{каталог}'+Road;  
try  
AssignFile(Cals,Dest);  
// Если файл существует открываем на чтение, иначе создаем новый  
If FileExists(Cals) then Reset(cals) else Rewrite(cals);  
// установим позицию чтения в конец файла  
seek (cals,filesize(cals));  
CalsData.Name := 'название параметра';  
CalsData.ExtDat := {сами данные};  
Write(Cals,CalsData);  
except  
on E: EInOutError do  
ShowMessage('При выполнении файловой операции возникла ошибка'+  
' № '+ IntToStr(E. ErrorCode)+': '+SysErrorMessage(GetLastError));  
on E: EAccessViolation do  
ShowMessage('Ошибка!: '+SysErrorMessage(GetLastError));  
end;  
CloseFile(cals); //Независимо от того что произошло выше закрываем открытый файл  
end;  

Перепишем файл a.dat в файл b.dat, удалив признаки конца файла:


Proedure MyWrite;  
var  
f1,f2 :file of Byte;  
a :Byte;  
i :Longint;  
begin  
{$I-}  
AssignFile(f1, 'a.dat');  
AssignFile(f2, 'b.dat');  
Reset(f1);  
Rewrite(f2);  
for i := 1 to FileSize(f1) do  
begin  
Read(f1, a);  
if a <> 26 then Write(f2, a);  
end;  
CloseFile(f1);  
CloseFile(f2);  
end.  

Файл записей. Пишем и читаем любую:


Procedure MyBook;  
type TR=Record  
Name:string[100];  
Age:Byte;  
Income:Real;  
end;  
var f:file of TR;  
r:TR;  
begin  
//assign file  
assignFile(f, 'MyFileName');  
//open file  
if FileExists('MyFileName') then  
reset(f)  
else  
rewrite(f);  
//чтение 10й записи  
seek(f,10);  
read(f,r);  
//запись 20й записи  
seek(f, 20);  
write(f,r);  
closefile(f);  
end;  

Файловые операции средствами ShellAPI.
Автор: Владимир Татарчевский
Рассмотрим применение функции SHFileOperation.
function SHFileOperation(const lpFileOp: TSHFileOpStruct): Integer; stdcall;

Функция позволяет производить копирование, перемещение, переименование и удаление (в том числе и в Recycle Bin) объектов файловой системы.
Функция возвращает 0, если операция выполнена успешно, и ненулевое значение в противном случае.

Функция имеет единственный аргумент - структуру типа TSHFileOpStruct, в которой и передаются все необходимые данные. Эта структура выглядит следующим образом:


_SHFILEOPSTRUCTA = packed record  
Wnd: HWND;  
wFunc: UINT;  
pFrom: PAnsiChar;  
pTo: PAnsiChar;  
fFlags: FILEOP_FLAGS;  
fAnyOperationsAborted: BOOL;  
hNameMappings: Pointer;  
lpszProgressTitle: PAnsiChar; { используется только при установленном флаге FOF_SIMPLEPROGRESS }  
end;  

Поля структуры имеют следующее назначение:
hwnd Хэндл окна, на которое будут выводиться диалоговые окна о ходе операции.
wFunc Требуемая операция. Может принимать одно из значений:

FO_COPY Копирует файлы, указанные в pFrom в папку, указанную в pTo.
FO_DELETE Удаляет файлы, указанные pFrom (pTo игнорируется).
FO_MOVE Перемещает файлы, указанные в pFrom в папку, указанную в pTo.
FO_RENAME Переименовывает файлы, указанные в pFrom.
pFrom - указатель на буфер, содержащий пути к одному или нескольким файлам. Если файлов несколько, между путями ставится нулевой байт. Список должен
заканчиваться двумя нулевыми байтами.

pTo - аналогично pFrom, но содержит путь к директории - адресату, в которую производится копирование или перемещение файлов. Также может
содержать несколько путей. При этом нужно установить флаг FOF_MULTIDESTFILES.

fFlags - управляющие флаги.
FOF_ALLOWUNDO Если возможно, сохраняет информацию для возможности UnDo.
FOF_CONFIRMMOUSE Не реализовано.
FOF_FILESONLY Если в поле pFrom установлено *.*, то операция будет производиться только с файлами.
FOF_MULTIDESTFILES Указывает, что для каждого исходного файла в поле pFrom указана своя директория - адресат.
FOF_NOCONFIRMATION Отвечает "yes to all" на все запросы в ходе опеации.
FOF_NOCONFIRMMKDIR Не подтверждает создание нового каталога, если операция требует, чтобы он был создан.
FOF_RENAMEONCOLLISION В случае, если уже существует файл с данным именем, создается файл с именем "Copy #N of..."
FOF_SILENT Не показывать диалог с индикатором прогресса.
FOF_SIMPLEPROGRESS Показывать диалог с индикатором прогресса, но не показывать имен файлов.
FOF_WANTMAPPINGHANDLE Вносит hNameMappings элемент. Дескриптор должен быть освобожден функцией SHFreeNameMappings.
fAnyOperationsAborted
Принимает значение TRUE если пользователь прервал любую файловую операцию до ее завершения и FALSE в ином случае.

hNameMappings - дескриптор объекта отображения имени файла, который содержит массив структур SHNAMEMAPPING. Каждая структура содержит старые и новые имена пути для каждого файла, который перемещался, был скопирован, или переименован. Этот элемент используется только, если установлен флаг FOF_WANTMAPPINGHANDLE.

lpszProgressTitle - указатель на строку, используемую как заголовок для диалогового окна прогресса. Этот элемент используется только, если установлен флаг FOF_SIMPLEPROGRESS.

Примечание. Если pFrom или pTo не указаны, берутся файлы из текущей директории. Текущую директорию можно установить с помощью функции
SetCurrentDirectory и получить функцией GetCurrentDirectory.

Примеры:

Добавьте в секцию uses модуль ShellAPI, в котором определена функция SHFileOperation.

Удаление файлов.:


procedure TForm1.Button1Click(Sender: TObject);  
var  
SHFileOpStruct : TSHFileOpStruct;  
From : array [0..255] of Char;  
begin  
SetCurrentDirectory( PChar( 'C:\' ) );  
From := 'Test1.tst' + #0 + 'Test2.tst' + #0 + #0;  
with SHFileOpStruct do  
begin  
Wnd := Handle;  
wFunc := FO_DELETE;  
pFrom := @From;  
pTo := nil;  
fFlags := 0;  
fAnyOperationsAborted := False;  
hNameMappings := nil;  
lpszProgressTitle := nil;  
end;  
SHFileOperation( SHFileOpStruct );  
end;  

Ни один из флагов не установлен. Если вы хотите не просто удалить файлы, а переместить их в корзину, должен быть
установлен флаг FOF_ALLOWUNDO.

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


type TBuffer = array of Char;  
  
procedure CreateBuffer( Names : array of string; var P : TBuffer );  
var I, J, L : Integer;  
begin  
for I := Low( Names ) to High( Names ) do  
begin  
L := Length( P );  
SetLength( P, L + Length( Names[ I ] ) + 1 );  
for J := 0 to Length( Names[ I ] ) - 1 do  
P[ L + J ] := Names[ I, J + 1 ];  
P[ L + J ] := #0;  
end;  
SetLength( P, Length( P ) + 1 );  
P[ Length( P ) ] := #0;  
end;  

Функция, удаляющая файлы, переданные ей в списке Names. Параметр ToRecycle определяет, будут ли файлы перемещены в корзину или удалены. Функция возвращает 0, если операция выполнена успешно, и ненулевое значение, если функции переданы имена несуществующих файлов.


function DeleteFiles( Handle : HWnd; Names : array of string; ToRecycle : Boolean ) : Integer;  
var  
SHFileOpStruct : TSHFileOpStruct;  
Src : TBuffer;  
begin  
CreateBuffer( Names, Src );  
with SHFileOpStruct do  
begin  
Wnd := Handle;  
wFunc := FO_DELETE;  
pFrom := Pointer( Src );  
pTo := nil;  
fFlags := 0;  
if ToRecycle then fFlags := FOF_ALLOWUNDO;  
fAnyOperationsAborted := False;  
hNameMappings := nil;  
lpszProgressTitle := nil;  
end;  
Result := SHFileOperation( SHFileOpStruct );  
Src := nil;  
end;  

Освобождаем буфер Src простым присваиванием значения nil. Потери памяти при этом не происходит, происходит корректное уничтожение динамического массива.

Проверяем :


procedure TForm1.Button1Click(Sender: TObject);  
begin  
DeleteFiles( Handle, [ 'C:\Test1', 'C:\Test2' ], True );  
end;  

Файлы 'Test1' и 'Test2' удаляются совсем, без помещения в корзину, несмотря на установленный флаг FOF_ALLOWUNDO. При использовании функции SHFileOperation используйте полные пути, когда это возможно.
Копирование и перемещение.

Функция перемещает файлы указанные в списке Src в директорию Dest. Параметр Move определяет, будут ли файлы перемещаться или копироваться. Параметр AutoRename указывает, переименовывать ли файлы в случае конфликта имен.


function CopyFiles( Handle : Hwnd; Src : array of string; Dest : string; Move : Boolean; AutoRename : Boolean ) : Integer;  
var  
SHFileOpStruct : TSHFileOpStruct;  
SrcBuf : TBuffer;  
begin  
CreateBuffer( Src, SrcBuf );  
with SHFileOpStruct do  
begin  
Wnd := Handle;  
wFunc := FO_COPY;  
if Move then wFunc := FO_MOVE;  
pFrom := Pointer( SrcBuf );  
pTo := PChar( Dest );  
fFlags := 0;  
if AutoRename then fFlags := FOF_RENAMEONCOLLISION;  
fAnyOperationsAborted := False;  
hNameMappings := nil;  
lpszProgressTitle := nil;  
end;  
Result := SHFileOperation( SHFileOpStruct );  
SrcBuf := nil;  
end;  

Выполнение:


procedure TForm1.Button1Click(Sender: TObject);  
begin  
CopyFiles( Handle, [ 'C:\Test1', 'C:\Test2' ], 'C:\Temp', True, True );  
end;  

Переименование.


function RenameFiles( Handle : HWnd; Src : string; New : string; AutoRename : Boolean ) : Integer;  
var SHFileOpStruct : TSHFileOpStruct;  
begin  
with SHFileOpStruct do  
begin  
Wnd := Handle;  
wFunc := FO_RENAME;  
pFrom := PChar( Src );  
pTo := PChar( New );  
fFlags := 0;  
if AutoRename then fFlags := FOF_RENAMEONCOLLISION;  
fAnyOperationsAborted := False;  
hNameMappings := nil;  
lpszProgressTitle := nil;  
end;  
Result := SHFileOperation( SHFileOpStruct );  
end;  

Выполнение


procedure TForm1.Button1Click(Sender: TObject);  
begin  
RenameFiles( Handle, 'C:\Test1' , 'C:\Test3' , False );  
end;  

Добавлено: 31 Июля 2018 07:58:37 Добавил: Андрей Ковальчук

Файловые операции

В следующем примере используется функция SHFileOperation для копирования группы файлов и показа анимированного диалога. Вы можете использовать также следующие флаги для копирования, удаления, переноса и переименования файлов. TO_COPY, FO_DELETE, FO_MOVE, FO_RENAME

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


uses ShellAPI;  
  
procedure TForm1.Button1Click(Sender: TObject);   
var   
  Fo      : TSHFileOpStruct;   
  buffer  : array[0..4096] of char;   
  p       : pchar;   
begin   
  FillChar(Buffer, sizeof(Buffer), #0);   
  p := @buffer;   
  p := StrECopy(p, 'C:\DownLoad\1.ZIP') + 1;   
  p := StrECopy(p, 'C:\DownLoad\2.ZIP') + 1;   
  p := StrECopy(p, 'C:\DownLoad\3.ZIP') + 1;   
  StrECopy(p, 'C:\DownLoad\4.ZIP');   
  FillChar(Fo, sizeof(Fo), #0);   
  Fo.Wnd    := Handle;   
  Fo.wFunc  := FO_COPY;   
  Fo.pFrom  := @Buffer;   
  Fo.pTo    := 'D:\';   
  Fo.fFlags := 0;   
  if ((SHFileOperation(Fo) <> 0) or  
    (Fo.fAnyOperationsAborted <> false)) then  
    ShowMessage('Cancelled')   
end;  

Добавлено: 31 Июля 2018 07:56:23 Добавил: Андрей Ковальчук

Упрощаем работу с потоками (TStream)

Работа программиста невозможна без работы с данными, которые хранятся в файлах или в памяти. В Delphi введен механизм потокового ввода-вывода, значительно упрощающий наш нелегкий труд. Однако структура данных может быть достаточно сложна. К тому же, в разных проектах она наверняка будет различна. Все это заставляет нас снова и снова писать сотни строчек однообразного кода записи/чтения данных. Утомляет. В этой я покажу, как я решил эту проблему для себя.

Совсем немного теории
Для тех, кто знает, что скрывается за страшной аббревиатурой RTTI, рекомендую пропустить этот раздел, ничего нового Вы здесь не найдете. А остальных попытаюсь немного ввести в курс дела.

RTTI (Run-time type information) - как видно из названия, это механизм, позволяющий определить тип данных во время выполнения. Суть его в том, что компилятор генерирует расширенную информацию для почти всех классов, используемых в вашей программе. Я сказал почти? Да, только для классов, объявленных с директивой {$M+} и их потомков, а таким классом, в частности является TPersistent. Потомками этого класса являются все компоненты, графические классы (TFont, TBitmap, TIcon и т.д.) и многие другие. Так вот, я отвлекся, эта информация активно используется самой средой разработки (инспектор объектов, редакторы свойств) и может быть использована программистом. Необходимые средства для работы с RTTI находятся в модуле TypInfo.pas. Проблема лишь в том, что по неизвестным мне причинам, Borland решила не документировать эти возможности (в справке по Delphi7, не нашел ничего связанного с RTTI, кроме упомянутой ранее директивы {$M+/-}, метода TObject.ClassInfo и операторов is и as).

И еще: RTTI позволяет получить информацию о свойствах и методах, объявленных ТОЛЬКО в разделе published. Зачем нам это нужно и как это нам поможет - увидите далее.

Ставим задачу
Поставим себе такую задачу: создать класс, который будет искать все свои published-свойства и сохранять их в поток (в файл, в частности). Программисту нужно только ОДИН раз написать код, который реализует сказанное выше, создать потомка этого класса, объявить в нем все необходимые свойства, и вызвать метод SaveToStream (его не надо будет перекрывать для каждого потомка) для сохранения самого себя в поток. Аналогично, метод LoadFromStream прочитает все свойства из потока. Ну-с, приступим-с.

Реализация
Класс TRttiObject


// Сохраняет и читает из потока все Published свойства  
TRttiObject = class(TInterfacedPersistent, IStreamPersist)  
public  
  procedure SaveToStream(Stream: TStream);  
  procedure LoadFromStream(Stream: TStream);  
  constructor Create; virtual;  
  
end;  

Тут нужно немного пояснить. Класс будет записывать/читать все свойства, тип которых целый (в том числе логический), перечислимый, вещественный, символьный, строковый, а также некоторые классы, которые поддерживают работу с потоками. Отличить эти классы от других, можно запросив у них интерфейс IStreamPersist, который объявлен в Classes.pas так:


IStreamPersist = interface  
  ['{B8CD12A3-267A-11D4-83DA-00C04F60B2DD}']  
  procedure LoadFromStream(Stream: TStream);  
  procedure SaveToStream(Stream: TStream);  
  
end;  

Этот класс реализуют, например, все потомки TGraphic, такие как TBitmap, TIcon, TMetafile, а также наш класс TRttiObject.

Почему в качестве предка выбран TInterfacedPersistent, а не TObject или TInterfacedObject. Тут несколько причин: во-первых, он является потомком TPersistent, который объявлен с директивой {$M+} (правда ничего не мешало бы сделать это самим), а во-вторых, в нем наиболее удачно для нас реализованы методы интерфейса IInterface (подсчет ссылок, реализованный в TInterfacedObject, нам ни к чему, а если взять TObject, то эти методы нужно будет реализовать самому).


procedure TRttiObject.SaveToStream(Stream: TStream);  
var  
  TypeData: PTypeData;  
  PropList: PPropList;  
  Count,i: Integer;  
  
  // Локальные процедуры  
  procedure WriteOrdProp;  // Запись целых и перечислимых данных  
  var  
    Value: Integer;  
  begin  
    Value:=GetOrdProp(self,PropList[i]);  
    Stream.Write(Value,SizeOf(Value));  
  end;  
  
  procedure WriteFloatProp;  // Запись вещественных данных  
  var  
    Value: Extended;  
  begin  
    Value:=GetFloatProp(self,PropList[i]);  
    Stream.Write(Value,SizeOf(Value));  
  end;  
  
  procedure WriteStringProp;  // Запись строки  
  var  
    Value: String;  
    L: Integer;  
  begin  
    Value:=GetStrProp(self,PropList[i]);  
    L:=Length(Value);  
    Stream.Write(L,SizeOf(L));  
    Stream.Write(PChar(Value)^,Length(Value));  
  end;  
  
  procedure WriteClassProp;  // Запись класса  
  var  
    Obj: TObject;  
    SaveLoader: IStreamPersist;  
    IsEmpty: Boolean;  
  begin  
    Obj:=GetObjectProp(self,PropList[i]);  
    if (Obj is TGraphic) then begin  
      IsEmpty:=TGraphic(Obj).Empty;  
      Stream.Write(IsEmpty,SizeOf(Boolean));  
    end;  
    if Supports(Obj,IStreamPersist,SaveLoader) then begin  
      SaveLoader.SaveToStream(Stream);  
    end;  
  end;  
  
// Собственно сама процедура поиска свойств и записи  
begin  
  TypeData:=GetTypeData(ClassInfo);  // Получаем указатель на информацию  
  Count:=TypeData.PropCount;  // Получаем количество свойств  
  if Count>0 then begin  
    // Выделяем память для списка свойств  
    GetMem(PropList,SizeOf(PPropInfo)*Count);  
    Try  
      // Получаем список свойств  
      GetPropInfos(ClassInfo,PropList);  
      // Перебираем все свойства из списка и сохраняем их  
      // в поток в соответствии с их типом  
      for i:=0 to Count - 1 do begin  
        case PropList[i].PropType^.Kind of  
          tkEnumeration, tkInteger, tkChar, tkWChar: WriteOrdProp;  
          tkFloat: WriteFloatProp;  
          tkString, tkLString: WriteStringProp;  
          tkClass: WriteClassProp;  
        end;  
      end;  
    finally  
      // Освобождаем память  
      FreeMem(PropList,SizeOf(PPropInfo)*Count);  
    end;  
  end;  
end;  

Я не буду подробно комментировать каждую функцию из TypInfo.pas, по комментариям сами разберетесь, прошу только обратить внимание на локальную процедуру записи класса. Сначала мы получаем экземпляр самого объекта. Потом проверяем, не является ли он потомком TGraphic. Далее записываем в поток, является ли графический объект пустым. Дело в том, что если объект (например Bitmap) пустой, то вызов SaveToStream не запишет в поток ничего. При чтении объект не сможет узнать о том, что он должен быть пустым, и будет, как ни в чем не бывало читать следующие по очереди данные из потока. Само собой это вызовет ошибку. Честно говоря, мне не очень нравится, как я решил эту проблему. Если у Вас есть идеи получше - пишите в обсуждении статьи.

И в конце процедуры WriteClassProp самое главное. Запрашиваем интерфейс IStreamPersist и заодно проверяем, поддерживает ли вообще его объект. Если да, то вызываем метод интерфейса SaveToStream.

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

Что дальше?
Вы думаете это все? Кроме класса TRttiObject в прилагаемом файле Вы найдете следующие классы:


TRttiList = class(TObjectList, IStreamPersist)  

Предназначен для составления и управления списками объектов типа TRttiObject. Кроме того, поддерживает интерфейс IStreamPersist и может сам являться свойством объекта TRttiObject. Таким образом, вы можете составлять из этих классов сколь угодно сложные структуры, массивы, деревья и т.д.


TAsyncRttiList = class(TRttiList)  

Тоже самое, но процесс сохранения и загрузки позволяет выполнять асинхронно, т.е. сразу возвращает управление, а когда процессы завершены, оповещает главный поток (Thread).

Вот теперь все. Надеюсь, эти материалы будут для Вас полезны и возможно у Вас появятся какие-либо идей, как исправить/дополнить их. Буду очень признателен!

Исходные коды (zip-архив, 2.3 K) (обновление от 3/27/2006 2:10:00 AM)

Автор: Юрий Спектор

Добавлено: 31 Июля 2018 07:55:48 Добавил: Андрей Ковальчук

Сохранение и выдёргивание ресурсов в DLL или EXE

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

Далее рассмотрим, как создать файл ресурсов, содержащий корию какого-либо файла. После создания такого файла его можно легко прицепить к Вашему проекту директивой {$R}. Файл ресурсов, который мы будем создавать имеет следующий формат:

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

Заголовок ресурса выглядит следующим образом:


TResHeader = record  
  DataSize: DWORD;        // размер данных  
  HeaderSize: DWORD;      // размер этой записи  
  ResType: DWORD;         // нижнее слово = $FFFF => ordinal  
  ResId: DWORD;           // нижнее слово = $FFFF => ordinal  
  DataVersion: DWORD;     // *  
  MemoryFlags: WORD;  
  LanguageId: WORD;       // *  
  Version: DWORD;         // *  
  Characteristics: DWORD; // *  
end;  

Поля помеченны звёздочкой Мы не будем использовать.

Приведённый код создаёт файл ресурсов и копирует его в данный файл:


procedure CreateResourceFile(  
  DataFile, ResFile: string; // имена файлов  
  ResID: Integer // id ресурсов  
  );  
var  
  FS, RS: TFileStream;  
  FileHeader, ResHeader: TResHeader;  
  Padding: array [0..SizeOf(DWORD)-1] of Byte;  
begin  
  
  { Open input file and create resource file }  
  FS := TFileStream.Create( // для чтения данных из файла  
  DataFile, fmOpenRead);  
  RS := TFileStream.Create( // для записи файла ресурсов  
  ResFile, fmCreate);  
  
  { Создаём заголовок файла ресурсов - все нули, за исключением 
  HeaderSize, ResType и ResID }  
  FillChar(FileHeader, SizeOf(FileHeader), #0);  
  FileHeader.HeaderSize := SizeOf(FileHeader);  
  FileHeader.ResId := $0000FFFF;  
  FileHeader.ResType := $0000FFFF;  
  
  { Создаём заголовок данных для RC_DATA файла 
  Внимание: для создания более одного ресурса необходимо 
  повторить следующий процесс, используя каждый раз различные 
  ID ресурсов }  
  FillChar(ResHeader, SizeOf(ResHeader), #0);  
  ResHeader.HeaderSize := SizeOf(ResHeader);  
  // id ресурса - FFFF означает "не строка!"  
  ResHeader.ResId := $0000FFFF or (ResId shl 16);  
  // тип ресурса - RT_RCDATA (from Windows unit)  
  ResHeader.ResType := $0000FFFF  
  or (WORD(RT_RCDATA) shl 16);  
  // размер данных - есть размер файла  
  ResHeader.DataSize := FS.Size;  
  // Устанавливаем необходимые флаги памяти  
  ResHeader.MemoryFlags := $0030;  
  
  { Записываем заголовки в файл ресурсов }  
  RS.WriteBuffer(FileHeader, sizeof(FileHeader));  
  RS.WriteBuffer(ResHeader, sizeof(ResHeader));  
  
  { Копируем файл в ресурс }  
  RS.CopyFrom(FS, FS.Size);  
  
  { Pad data out to DWORD boundary - any old 
  rubbish will do!}  
  if FS.Size mod SizeOf(DWORD) <> 0 then  
    RS.WriteBuffer(Padding, SizeOf(DWORD) -  
    FS.Size mod SizeOf(DWORD));  
  
  { закрываем файлы }   
  FS.Free;   
  RS.Free;  
end;  

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

Извлечение ресурсов из EXE

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

Вся процедура заключается в создании потока ресурса, создании файлового потока и копировании из потока ресурса в поток файла.


procedure ExtractToFile(Instance:THandle; ResID:Integer; ResType, FileName:string);  
var  
  ResStream: TResourceStream;  
  FileStream: TFileStream;  
begin  
  try  
    ResStream := TResourceStream.CreateFromID(Instance, ResID, pChar(ResType));  
    try  
      //if FileExists(FileName) then  
      //DeleteFile(pChar(FileName));  
      FileStream := TFileStream.Create(FileName, fmCreate);  
      try  
        FileStream.CopyFrom(ResStream, 0);  
      finally  
        FileStream.Free;  
      end;  
    finally  
      ResStream.Free;  
    end;  
  except  
    on E:Exception do  
    begin  
      DeleteFile(FileName);  
      raise;  
    end;  
  end;  
end;  

Всё, что требуется, это получить Instance exe-шника или dll (у Вашего приложения это Application.Instance или Application.Handle, для dll Вам придётся получить его самостоятельно :)

ResID
тот же самый ID , который был присвоен ресурсу
ResType: WAVEFILE, BITMAP, CURSOR, CUSTOM
это типы ресурсов, с которыми возможно работать, но у меня получилось успешно проделать процедуру только с CUSTOM
FileName
это имя файла, который мы хотим создать из ресурса

Добавлено: 31 Июля 2018 07:54:52 Добавил: Андрей Ковальчук

Реестр и как им пользоваться

Реестр - важная штука в Win32 и Win64.
Я называю реестр - хранилище информации. Он хранит важные данные о настройках Windows и программ.
Реестр разбит на категории >

Корневой ключ > Ключ > Подключ > Данные

Корневые ключи везде одинаковые

HKEY_CLASSES_ROOT (HKCR)
в этом ключе хранится информация о зарегистрированных типах файлов.
HKEY_CURRENT_USER (HKCU)
здесь находятся данные о настройках Windows и программах текущего пользователя
HKEY_LOCAL_MACHINE (HKLM)
а тут есть данные о настройках Windows и программах всего компа
HKEY_USERS (HKU)
информация о пользователях
HKEY_CURRENT_CONFIG (HKCC)
название говорит само о себе (CURRENT_CONFIG)

Чтобы использовать реестр в практике, нужно добавить в поле uses модуль registry .
(var reg:tregistry;)

А здесь написаны нужные процедурки и свойства объекта reg


reg.RootKey := HKEY // содержит корневой ключ (например HKEY_CURRENT_USER)  
reg.OpenKey := key // coдержит ключ и подключ  
reg.readString(readbool, readinteger) // прочитать свойство  
reg.writeString(writebool, writeinteger) // записать свойство  
reg.closekey //закрыть ключ  
reg.free // освободить реестр от tregistry.create (см.внизу)

Для старта проиниализируйте его. Сделайте это так:


var reg:tregistry;  
begin  
reg := tregistry.create;  
// эта процедура инициализирует реестр и как root key  
//делает HKCU  
end;  

И напоследок, несколько примеров:


begin  
reg.rootkey := HKEY_LOCAL_MACHINE;  
reg.openkey('\Sofware\Microsoft\Windows\CurrentVersion\Run',false); //первое свойство - ключ, второе  
// - Автосоздание ключа если его нет в реестре  
reg.WriteString('Notepad','notepad.exe');// первое- название свойства, второе - свойство  
reg.closekey  
reg.free  
{ Здесь был показан пример добавление Блокнота в автозагрузку всех пользователей компа }  
end;  

И об основах реестра собственно все.

Добавлено: 31 Июля 2018 07:53:46 Добавил: Андрей Ковальчук

Редактор диска своими руками

Многие помнят легендарный Norton DiskEditor - утилиту, дающую огромный простор для исследовательской и прочей деятельности. И сейчас есть множество аналогов. WinHex, например.

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

Для начала разберемся как происходит само чтение диска. Проще всего это делать в Windows 2000/XP (с правами администратора, конечно). Работа с жестким диском в этих операционных системах производится путем открытия диска как файла с помощью функции CreateFile и указания диска или раздела по схеме Device Namespace (открывается физический диск - '\.PHYSICALDRIVE<n>'), полученный хэндл в дальнейшем используется для работы с диском с помощью функций ReadFile, WriteFile и DeviceIoControl.


// Drive - номер диска (нумерация с нуля).  
   
hFile := CreateFile(PChar('\.PhysicalDrive'+IntToStr(Drive)),  
  GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE,nil,OPEN_EXISTING,0,0);  
if hFile = INVALID_HANDLE_VALUE then Exit;  

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


const  
  IOCTL_DISK_GET_DRIVE_GEOMETRY = $70000;  
   
type  
  TDiskGeometry = packed record  
    Cylinders: Int64;           // количество цилиндров  
  
    MediaType: DWORD;           // тип носителя  
    TracksPerCylinder: DWORD;   // дорожек на цилиндре  
    SectorsPerTrack: DWORD;     // секторов на дорожке  
    BytesPerSector: DWORD;      // байт в секторе  
  end;  
   
Result := DeviceIoControl(hFile,IOCTL_DISK_GET_DRIVE_GEOMETRY,nil,0,  
  @DiskGeometry,SizeOf(TDiskGeometry),junk,nil) and (junk = SizeOf(TDiskGeometry)); 

Функция возвращает True, если операция прошла успешно, и False в противном случае.

Теперь уже можно приступить к определению местоположения логических дисков на винчестере. Начать это нужно с чтения нулевого сектора физического диска. Он содержит MBR (Master Boot Record), а также Partition Table. Кстати, думаю, будет интересно сохранить содержимое MBR в файл и посмотреть программу загрузки каким-нибудь дизасмом. Но в данный момент нас интересует только Partition Table.

Эта таблица располагается в секторе по смещению $1be и состоит из четырех одинаковых элементов, каждый из которых описывает один раздел:


TPartitionTableEntry = packed record  
  BootIndicator: Byte;          // $80, если активный (загрузочный) раздел  
  StartingHead: Byte;  
  StartingCylAndSect: Word;  
  SystemIndicator: Byte;  
  EndingHead: Byte;  
  EndingCylAndSect: Word;  
  StartingSector: DWORD;        // начальный сектор  
  
  NumberOfSects: DWORD;         // количество секторов  
end;  

Соответственно, саму Partition Table можно представить как массив:


TPartitionTable = packed array [0..3] of TPartitionTableEntry;
Подробнее остановлюсь на этой структуре. Как видно из описания структур, Partition Table может содержать только четыре раздела. А так как, возможно, пользователю необходимо большее количество, было введено понятие "Extended Partition" (таким образом, разделы бывают Primary и Extended). Extended Partition - это раздел, который имеет свою собственную Partition Table (и, соответственно может содержать в себе еще четыре раздела). Extended Partition содержит логические диски. Тип раздела определяется полем SystemIndicator. Оно содержит информацию о файловой системе логического диска, либо 5 (или $f), если это Extended Partition.

Примеры значений поля SystemIndicator:


01 - FAT12  
04 - FAT16  
05 - EXTENDED      
06 - FAT16      
07 - NTFS  
0B - FAT32  
0F - EXTENDED  

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


// так как диск для нас - это единый файл, то для перемещения по нему  
// с помощью SetFilePointer понадобится 64хразрядная арифметика  
  
function __Mul(a,b: DWORD; var HiDWORD: DWORD): DWORD; // Result = LoDWORD  
asm  
  
  mul edx  
  mov [ecx],edx  
end;  
  
function ReadSectors(DriveNumber: Byte; StartingSector, SectorCount: DWORD;  
  Buffer: Pointer; BytesPerSector: DWORD = 512): DWORD;  
var  
  hFile: THandle;  
  br,TmpLo,TmpHi: DWORD;  
begin  
  Result := 0;  
  hFile := CreateFile(PChar('\.PhysicalDrive'+IntToStr(DriveNumber)),  
    GENERIC_READ,FILE_SHARE_READ,nil,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);  
  if hFile = INVALID_HANDLE_VALUE then Exit;  
  TmpLo := __Mul(StartingSector,BytesPerSector,TmpHi);  
  if SetFilePointer(hFile,TmpLo,@TmpHi,FILE_BEGIN) = TmpLo then  
  
  begin  
    SectorCount := SectorCount*BytesPerSector;  
    if ReadFile(hFile,Buffer^,SectorCount,br,nil) then Result := br;  
  end;  
  CloseHandle(hFile);  
end;  

И, заодно, функция для записи:


function WriteSectors(DriveNumber: Byte; StartingSector, SectorCount: DWORD;  
  Buffer: Pointer; BytesPerSector: DWORD = 512): DWORD;  
var  
  hFile: THandle;  
  bw,TmpLo,TmpHi: DWORD;  
begin  
  Result := 0;  
  hFile := CreateFile(PChar('\.PhysicalDrive'+IntToStr(DriveNumber)),  
    GENERIC_WRITE,FILE_SHARE_READ,nil,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);  
  if hFile = INVALID_HANDLE_VALUE then Exit;  
  TmpLo := __Mul(StartingSector,BytesPerSector,TmpHi);  
  if SetFilePointer(hFile,TmpLo,@TmpHi,FILE_BEGIN) = TmpLo then  
  
  begin  
    SectorCount := SectorCount*BytesPerSector;  
    if WriteFile(hFile,Buffer^,SectorCount,bw,nil) then Result := bw;  
  end;  
  CloseHandle(hFile);  
end;  

Функции возвращает количество прочитаных (или записаных) байт. Для хранения информации о разделах объявим дополнительную структуру:


PDriveInfo = ^TDriveInfo;  
TDriveInfo = record  
  PartitionTable: TPartitionTable;  
  LogicalDrives: array [0..3] of PDriveInfo;  
end;  

Ну а теперь сам код разбора структуры диска:


const  
  
  PartitionTableOffset = $1be;  
  ExtendedPartitions = [5,$f];  
  
var  
  MainExPartOffset: DWORD = 0;  
  
function GetDriveInfo(DriveNumber: Byte; DriveInfo: PDriveInfo;  
  StartingSector: DWORD; BytesPerSector: DWORD = 512): Boolean;  
var  
  buf: array of Byte;  
  CurExPartOffset: DWORD;  
  i: Integer;  
begin  
  
  SetLength(buf,BytesPerSector);  
  // читаем сектор в буфер  
  if ReadSectors(DriveNumber,MainExPartOffset+StartingSector,1,@buf[0]) = 0 then  
  begin  
    Result := False;  
    Exit;  
  end;  
  // заполняем структуру DriveInfo.PartitionTable  
  
  Move(buf[PartitionTableOffset],DriveInfo.PartitionTable,SizeOf(TPartitionTable));  
  Finalize(buf); // буфер больше не нужен  
  
  Result := True;  
  for i := 0 to 3 do // для каждой записи в Partition Table  
  
    if DriveInfo.PartitionTable[i].SystemIndicator in ExtendedPartitions then  
    begin  
      New(DriveInfo.LogicalDrives[I]);  
      if MainExPartOffset = 0 then  
  
      begin  
        MainExPartOffset := DriveInfo.PartitionTable[I].StartingSector;  
        CurExPartOffset := 0;  
      end else CurExPartOffset := DriveInfo.PartitionTable[I].StartingSector;  
      Result := Result and GetDriveInfo(DriveNumber,DriveInfo.LogicalDrives[I],  
        CurExPartOffset);  
    end else DriveInfo.LogicalDrives[I] := nil;  
end;  

Функция заполняет структуру DriveInfo и возвращает True, если операция прошла успешно, или False в противном случае.

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

В нулевом секторе каждого основного раздела находится BIOS Parameter Block, содержаший такую информацию как название файловой системы, количество секторов в кластере и т.д. А также программа-загрузчик (сохраняем сектор в файл и смотрим дизасмом).

Теперь, когда мы закончили с теоретической частью, можно приступить к реализации редактора.

С чтением и записью информации мы уже разобрались. Теперь займемся ее отображением. Отображать содержимое выбранного сектора удобнее всего в компоненте TStringGrid.

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

К счастью, в Delphi они уже есть (IntToHex и StrToInt) и остается их только правильно использовать. StrToInt можно использовать для преобразования строки, содержащей шестнадцатиричное число в Integer, если дописать впереди символ $.

Например, StrToInt('$FF');

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

P.S.
Для получения доступа к физическому диску мы открывали устройство \.PHYSICALDRIVE<n>, далее разбирали его структуру. Можно было поступить проще - открывать сразу логические диски (\.C:,\.D: и т.д.), но при таком варианте мы бы упустили из виду некоторые области диска, такие как, MBR и неразмеченные области. Какой вариант предпочтительнее, зависит от задачи.

Автор: Kerk

Добавлено: 31 Июля 2018 07:53:00 Добавил: Андрей Ковальчук

Размещение значка приложения на System Tray

Часто программисту приходится сталкиваться с задачей написания приложения, работающего в фоновом режиме и не нуждающегося в месте на Панели задач. Если вы посмотрите на правый нижний угол рабочего стола Windows, то наверняка найдете там приложения, для которых эта проблема решена: часы, переключатель раскладок клавиатуры, регулятор громкости и т. п. Ясно, что, как бы вы не увеличивали и не уменьшали формы своего приложения, попасть туда обычным путем не удастся. Способ для этого предоставляет Shell API.

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

Весь API System Tray состоит из 1 (одной) функции:


function Shell_NotifyIcon(dwMessage: DWORD;   
IpData: PNotifylconData): BOOL; PNotifylconData = TNotifylconData; TNotifylconData = record  
cbSize: DWORD;  
Wnd: HWND;  
uID: UINT;  
uFlags: UINT;  
uCallbackMessage: UINT;  
hlcon: HICON;  
szTip: array [0..63] of AnsiChar;   
end;  

Параметр dwMessage определяет одну из операций: NIM_ADD означает добавление значка в область, NIM_DELETE — удаление, NIM_MODIFY — изменение.

Ход операции зависит от того, какие поля структуры TNotifyiconData будут заполнены.

Обязательным для заполнения является поле cbsize — там содержится размер структуры. Поле wnd должно содержать дескриптор окна, которое будет оповещаться о событиях, связанных со значком. Идентификатор сообщения Windows, которое вы хотите получать от системы о перемещениях мыши над значком, запишите в поле uCallbackMessage. Если вы хотите, чтобы при этих перемещениях над вашим значком показывалась подсказка, то задайте ее текст в поле szTip. В поле UID задается номер значка — каждое приложение может поместить на System Tray сколько угодно значков. Дальнейшие операции вы будете производить, задавая этот номер. Дескриптор помещаемого значка должен быть задан в поле hIcon. Здесь вы можете задать значок, связанный с вашим приложением, или загрузить свой — из ресурсов.

Примечание

Изменить главный значок приложения можно в диалоговом окне Project/ Options на странице Application. Он будет доступен через свойство Application.Icon. Тут же можно отредактировать и строку для подсказки — свойство Application.Title.

Наконец, в поле uFlags вы должны сообщить системе, что именно вы от нее хотите, или, другими словами, какие из полей hicon, uCaiibackMessage и szTip вы на самом деле заполнили. В этом поле предусмотрена комбинация трех флагов: NIF_ICON, NIF_MESSAGE и NIF_TIP. Вы можете заполнить, скажем, поле szTip, но если вы при этом не установили флаг NIF_TIP, созданный вами значок не будет иметь строки с подсказкой.

Два приведенных ниже метода иллюстрируют сказанное. Первый из них создает значок на System Tray, а второй — уничтожает его.


const WM_MYTRAYNOTIFY = WMJJSER + 123;  
procedure TForml.CreateTraylcon(n:Integer);   
var nidata : TNotifyiconData;  
begin  
with nidata do   
begin  
cbSize := SizeOf{TNotifyiconData) ;  
Wnd := Self.Handle;  
uID := n;  
uFiags := NIF_ICON or NIF_MESSAGE or NIFJTIP;  
uCallBackMessage := WM_MYTRAYNOTIFY;  
hicon := Application.Icon.Handle;  
szTip := 'THis is Traylcon Example';   
end;  
Shell_NotifyIcon(NIM_ADD, @nidata);   
end;  
procedure TForml.DeleteTraylcon(n:Integer);   
var nidata : TNotifylconData; begin  
with nidata do  
begin  
cbSize := SizeOf(TNotifylconData);  
 Wnd := Self.Handle; uID := n; end;  
Shell_NotifyIcon(NIM_DELETE, @nidata);  
end;  

Примечание

He забывайте уничтожать созданные вами значки на System Tray. Это не делается автоматически даже при закрытии приложения. Значок будет удален только после перезагрузки системы.

Внешний вид значка, помещенного нами на System Tray, ничем не отличается от значков других приложений (рис. 31.1).

Рис. 31.1. Над значком, помещенным на панель System Tray, видна строка подсказки

Сообщение, задаваемое в поле uCallbackMessage, по сути дела является единственной ниточкой, связывающей вас со значком после его создания. Оно объединяет в себе несколько сообщений. Когда к вам пришло такое сообщение (в примере, рассмотренном выше, оно имеет идентификатор WM_MYTRAYNOTIFY), поля в переданной в обработчик структуре типа TMessage распределены так. Параметр wParam содержит номер значка (тот самый, что задавался в поле uID при его создании), а параметр LParam — идентификатор сообщения от мыши, вроде WM_MOUSEMOVE, WM_LBUTTONDOWN и т. п. К сожалению, остальная информация из этих сообщений теряется. Координаты мыши в момент события придется узнать, вызвав функцию API GetCursorPos:


procedure TForml.WMICON(var msg: TMessage);   
var P : TPoint; begin case msg.LParam of  
WM_LBUTTONDOWN:   
begin  
GetCursorPos(p);  
SetForegroundWindow(Application.MainForm.Handle); PopupMenul.Popup(P.X, P.Y);  
end;  
WM_LBUTTONUP :   
end;  
end;  

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

Теперь решим еще две задачи. Во-первых, как сделать, чтобы приложение минимизировалось не на Панель задач (TaskBar), а на System Tray? И более того — как сразу запустить его в минимизированном виде, а показывать главную форму только по наступлении определенного события (приходу почты, наступлению определенного времени и т. п.).

Ответ на первый вопрос очевиден. Если минимизировать не только окно главной формы приложения (Application.MainForm.Handle), но и окно приложения (Application.Handle), то приложение полностью исчезнет "с экранов радаров". В этот самый момент нужно создать значок на панели System Tray. В его всплывающем меню должен быть пункт, при выборе которого оба окна восстанавливаются, а значок удаляется.

Чтобы приложение запустилось сразу в минимизированном виде и без главной формы, следует к вышесказанному добавить установку свойства Application.showMainForm в значение False. Здесь возникает одна сложность — если главная форма создавалась в невидимом состоянии, ее компоненты будут также созданы невидимыми. Поэтому при первом ее показе установим их свойство visible в значение True. Чтобы не повторять это дважды, установим флаг — глобальную переменную shownonce:


procedure TForml.HideMainForm;  
 begin  
Appiication.showMainForm := False;  
ShowWindow(Application.Handle, SW_HIDE);  
ShowWindow(Application.MainForm.Handle, SW_HIDE);  
 end;  
procedure TForml.RestoreMainForm;  
var i,j : Integer;  
begin  
Appiication.showMainForm := True;  
ShowWindow(Application.Handle, SW_RESTORE); ShowWindow(Application.MainForm.Handle, SW_RESTORE);  
if not ShownOnce then begin  
for I := 0 to Application.MainForm.ComponentCount -1 do if Application.MainForm.Components[I] is TWinControl then with Application.MainForm.Components[I] as TWinControl do if Visible then  
 begin  
ShowWindow(Handle, SW_SHOWDEFAULT);   
for J := 0 to ComponentCount -1 do if Components[J] is TWinControl then  
ShowWindow((Components[J] as TWinControl).Handle, SW_SHOWDEFAULT);  
end;  
ShownOnce := True;   
end;  
 end;  
procedure TForml.WMSYSCOMMAND(var msg: TMessage);  
 begin inherited;  
if (Msg.wParam=SC_MINIMIZE) then   
begin  
HideMainForm; CreateTraylcon(l) ;  
 end;  
 end;  
procedure TForml.FileOpenltemlClick(Sender: TObject); begin  
RestoreMainForm;  
DeleteTraylcon(l);  
end;  

Теперь у вас в руках полноценный набор средств для работы с панелью System Tray. В заключение необходимо добавить, что все описанное реализуется не в операционной системе, а в оболочке ОС — Проводнике (Explorer). В принципе, и Windows NT 4/2000, и Windows 95/98 допускают замену оболочки ОС на другие, например DashBoard или LightStep. Там функции панели System Tray могут быть не реализованы или реализованы через другие API. Впрочем, случаи замены оболочки достаточно редки.

Добавлено: 31 Июля 2018 07:51:24 Добавил: Андрей Ковальчук

Работа с инифайлами. Объект Inifiles

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

Одно из главных преимуществ инифайлов заключается в том, что эти файлы подерживают переменные разных типов (String, Integer, Boolean). В этих файлах очень удобно хранить различные настройки, например параметры шрифта, цвет фона, какие checkbox'ы выбрал пользователь и многое другое.

Теперь начнем разбираться с этими инифайлами. Для начала создайте новое приложение. Добавьте в секцию uses слово inifiles. Сохраните и откомпилируйте ваше приложение. Теперь сделаем, чтобы при каждом открытии программы форма имела такие размеры, какие установил пользователь последний раз. Для начала нам надо создать объект типа Inifile. Создается он методом Create(Filename:string); причем если в переменной Filename не указан путь к фалу, то он создаться в директории Windows, что не очень-то удобно. Поэтому мы создадим этот файл в директории нашей программы. Напишем это в обработчик события OnDestroy для формы:


procedure TForm1.FormDestroy(Sender: TObject);  
var Ini: Tinifile; //необходимо создать объект, чтоб потом с ним работать  
begin  
Ini:=TiniFile.Create(extractfilepath(paramstr(0))+'MyIni.ini'); //создали файл в директории программы  
Ini.WriteInteger('Size','Width',form1.width);   
Ini.WriteInteger('Size','Height',form1.height);  
Ini.WriteInteger('Position','X',form1.left);  
Ini.WriteInteger('Position','Y',form1.top);  
Ini.Free;  
end;  

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

Вот файл MyIni.ini после завершения работы программы (у вас естественно значения будут другими):


[Size]  
Width=188  
Height=144  
  
[Position]  
X=14  
Y=427  

Теперь подробно разберемся как записывать информацию в инифайлы:
После того, как вы создали инифайл, в него можно записывать три вида переменных: Integer, String, Boolean, это осуществляется соответствующими процедурами: WriteInteger, WriteString, WriteBool. У всех этих процедур одинаковые параметры. В общем объявление этих процедур выглядит так:


Ini.WriteInteger(const Section: string, const Ident:string, Value: Integer);
Здесь Section -это имя секции, куда будут помещены параметры и значения. В файле имена секций заключены в квадратные скобки. Обычно в секции объединяют схожие параметры.

Ident - это название параметра, которому будет присваиваться какое-нибудь значение.

Value - это собственно значение, которое будет присвоено параметру. В файле оно стоит после знака равно.

Теперь напишем обработчик события OnCreate для формы, в котором будем считывать значения из файла и изменять размеры формы в соответствии с полученными значениями. Код должен иметь такой вид:


procedure TForm1.FormCreate(Sender: TObject);  
var Ini: Tinifile;  
begin  
Ini:=TiniFile.Create(extractfilepath(paramstr(0))+'MyIni.ini'); //открываем файл  
Form1.Width:=Ini.ReadInteger('Size','Width',100); //последнее значение (100) это значение по умолчанию (default)  
Form1.Height:=Ini.ReadInteger('Size','Height',100);  
Form1.Left:=Ini.ReadInteger('Position','X',10);  
Form1.Top:=Ini.WriteInteger('Position','Y',10);  
Ini.Free;  
end;  

В этом коде все просто: открыли файл, прочитали из соответствующих секций необходимые параметры и присвоили их форме. Чтение значений из инифайла по сути ничем не отличается от записи в них. Указываете секцию, где хранится необходимый параметр, указываете параметр и читаете его значение. Как вы видите все просто!

Теперь я отвечу еще на один вопрос, который может появиться - почему не обычные текстовые файлы и не реестр? Отвечаю: из текстового файла очень сложно получить и обработать необходимую информацию. Многие рекомендуют для Win95/98/2000/Me, короче для всех 32-разрядных ОС использовать именно реестр, но лично я считаю, что инифайлы удобнее, так как при при переносе программы на другой компьютер, нужно перенести только один инифайл, а во-вторых, если вы что-нибудь в реестре случайно удалите, то может случиться каюк.

Автор: Михаил Христосенко

Добавлено: 31 Июля 2018 07:50:27 Добавил: Андрей Ковальчук

Работа с директориями в Delphi

В этой статье я постараюсь познакомить Вас с некоторыми стандартными функциями для работы с директориями. И еще приведу несколько пользовательских функций и примеры их использования. Также рассмотрен вопрос вызова диалога выбора директории.

Для начала начнем с простой функции для создания новой папки. Общий вид функции такой:


function CreateDir(const Dir: string): Boolean;  

То есть если папка успешно создана функция возвращает true. Сразу же простой пример ее использования:


procedure TForm1.Button1Click(Sender: TObject);  
begin  
if createdir('c:\TestDir') = true then  
showmessage('Директория успешно создана')  
else  
showmessage('При создании директории произошла ошибка');  
end; 

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

Объявления


createdir(edit1.text);  

и


createdir(extractfilepath(paramstr(0))+edit1.text);    

приведут к одному и тому же результату.

Теперь рассмотрим функцию для удаления папок. Ее объявление выглядит так:


function RemoveDir(const Dir: string): Boolean; 

Сразу же хочу предупредить, что данная функция способна удалять только пустые папки, и если там что-нибудь будет, то произойдет ошибка! Но выход есть!!! Здесь нам на помощь придет пользовательская функция с простым названием MyRemoveDir. Вот описание функции:


Function MyRemoveDir(sDir : String) : Boolean;   
var   
iIndex : Integer;   
SearchRec : TSearchRec;   
sFileName : String;   
begin   
Result := False;   
sDir := sDir + '\*.*';   
iIndex := FindFirst(sDir, faAnyFile, SearchRec);   
  
while iIndex = 0 do begin   
sFileName := ExtractFileDir(sDir)+'\'+SearchRec.Name;   
if SearchRec.Attr = faDirectory then begin   
if (SearchRec.Name <> '' ) and   
(SearchRec.Name <> '.') and   
(SearchRec.Name <> '..') then   
MyRemoveDir(sFileName);   
end else begin   
if SearchRec.Attr <> faArchive then   
FileSetAttr(sFileName, faArchive);   
if NOT DeleteFile(sFileName) then   
ShowMessage('Could NOT delete ' + sFileName);   
end;   
iIndex := FindNext(SearchRec);   
end;   
  
FindClose(SearchRec);   
  
RemoveDir(ExtractFileDir(sDir));   
Result := True  
end;  

Копируете это все в Вашу программу, а затем эту функцию можно вызвать например так:


if NOT MyRemoveDir('C:\TestDir') then  
ShowMessage('Не могу удалить эту директорию');  

Теперь маленько отстранимся от непосредственной работы с папками и рассмотрим волнующий многих вопрос. Как вызвать диалог выбора папки (как при установке программ)?? ПРОСТО!!!

Подключаем в uses модуль Filectrl.pas (то есть uses FileCtrl;). Теперь ставим на форму еще кнопочку (чтобы не путаться :) и пишем такой код:


procedure TForm1.Button3Click(Sender: TObject);  
const  
SELDIRHELP = 1000;  
var  
Dir: string;  
begin  
Dir := 'C:\windows';  
if SelectDirectory(Dir, [sdAllowCreate, sdPerformCreate, sdPrompt],SELDIRHELP) then  
Caption := Dir;  
end;  

При выборе директории в заголовке формы отобразиться ее название!

Теперь рассмотрим следующую процедуру. К примеру Вам надо создать папку Dir1 по адресу: C:\MyDir\Test\Dir1, но при этом папок MyDir и Test на Вашем компьютере не существует. Функция CreateDir здесь не сработает, поэтому воспользуемся процедурой ForceDirectories. Ее общий вид таков:


procedure ForceDirectories(Dir: string);  

Пример ее использования (как всегда я поставил на форму новую кнопку, а там написал)


procedure TForm1.Button4Click(Sender: TObject);  
var  
Dir: string;  
begin  
Dir := 'C:\MyDir\Test\Dir1';  
ForceDirectories(Dir);  
end;  

Если директория указанная в параметре Name существует - то функция возвратит true.

Надеюсь, что помог Вам описанием данных функций и процедур. Сразу хочется дать совет: почаще заглядывайте в HELP, там много интересной и полезной информации!

Автор: Михаил Христосенко

Добавлено: 31 Июля 2018 07:49:42 Добавил: Андрей Ковальчук

Работа с Com портом под Windows

Введение
Однажды в студеную зимнюю пору… Итак в отличие от DOS Win 9x,NT имеет другую идеологию работы с аппаратурой. Если в нашем уважаемом старичке DOS драйвер мог быть написан на asm с прямым доступом к портам, то в Win все немного сложнее. Почему ? Win, в отличие от DOS многозадазадачна, поэтому позволять каждому приложению напрямую менять настройки аппаратуры нельзя, т.к при этом одна задача может не знать об изменении состояния аппаратуры другой задачей. В принципе в Win 9x можно пользоваться in in – out 378h, однако по вышеизложенной причине это нежелательно. Для написания программ, работающих с аппаратурой в Win используется API(интерфейс прикладных программ). Данный интерфейс позволяет использовать системные сервисы Win из прикладухи. Реализация API при этом возлагается на драйверы. Для написания драйверов используется Win Driver Developer Kit (DDK)(для каждой Win95, 98,NT есть свой DDK). Кроме непосредственно API можно использовать IOCTL коды (этот способ получил распространение еще в DOS), однако это выходит за рамки данной статьи.

Работа с аппаратурой под Win.
Win API стандартизирует работу с оборудованием. Для получения доступа к аппаратуре используется следующая последовательность шагов:'

1. Получить Handler устройства вызовом CreateFile с именем устройства. Более подробно см Windows SDK Help.

2. Для управления устройством вызывать функции API для данного устройства, либо посылать IOCTL(input - otput control) последнее через DeviceIOCtl(подробно см Windows SDK Help).

3. Закрыть устройство CloseHandle(Handler);

Последовательный порт под Win
Открытие порта:

Var   
  FHandle: Thandle;  
  
FHandle := CreateFile(  
  PChar(ComString),  
  GENERIC_READ or GENERIC_WRITE,  
  0,  
  nil,  
  OPEN_EXISTING,  
  FILE_FLAG_OVERLAPPED,  
  0);  

Параметр 1: Имя порта – ‘COM1’, итд

Параметр 2: режим открытия GENERIC_READ – чтение, GENERIC_WRITE – запись

Параметр 3: режим разделения ресуртса. Примечание: 0 – неразделяемый (именно так описано открытие последовательного порта в WIN SDK, другие режимы не проверял).

Параметр 4: Режим безопасности. Имеет смысл в Windows NT, Windows 9x игнорирует его.

Параметр 5: Способ открытия. Для порта - OPEN_EXISTING – открыть, когда устройство реально существует.

Параметр6: режим наложения операций - FILE_FLAG_OVERLAPPED – разрешение таких операций. При этом операции чтения – записи, требующие значительного времени, выполняются фоново по отношению к основному потоку программы.

Параметр7: шаблон файла, для последовательного порта – всегда 0.

В случае нормального открытия порта FHandle – дескриптор порта, при неудаче содержит значение INVALID_HANDLE_VALUE.

Закрытие порта:
Закрытие порта выполняется вызовом CloseHandle(FHandle).

Настройка параметров передачи (скорость, кол-во бит, стоп биты)
Структура данных о настройках порта (device control block) DCB содержит информацию о настройках порта. Поля структуры:


DWORD DCBlength;           // sizeof(DCB)  
DWORD BaudRate             // Скорость передачи (baud rate). Есть стандартный набор  
 // скоростей: все константы скоростей выглядят как CBR_<число>.  
 //Пример CBR_9600, CBR_115200.  
  
Flags  
DWORD fBinary: // режим проверки символа Eof – включение данного режима Windows  
               // не поддерживает ( по крайней мере сейчас). Маска $01  
DWORD fParity: //Контроль четности Маска $02 – включение контроля четности  
DWORD fOutxCtsFlow:  // Маска $04 – Включение контроля сигнала CTS при выводе байтов.  
DWORD fOutxDsrFlow:  // Маска $08 – Включение контроля сигнала DSR при выводе байтов.  
DWORD fDtrControl:   // Маска $30 – Тип контроля сигнала DTR: значения  
   DTR_CONTROL_DISABLE    деактивация сигнала.  
   DTR_CONTROL_ENABLE     конкретное значение сигнала можно задавать через   
                          вызов EscapeCommFunction.  
   DTR_CONTROL_HANDSHAKE  Автоматическое управление сигналом.  
DWORD fDsrSensitivity: // Маска $40 - Включение контроля сигнала DSR.  
DWORD fTXContinueOnXoff:1; // XOFF continues Tx  
DWORD fOutX: // Маска $100. Включение режима работы по XON XOFF при передаче  
DWORD fInX:  // Маска $200 -//- при приеме  
DWORD fErrorChar: // Маска $400. Разрешение замещения при ошибочном приеме  
             // (несовпадение четности) принятого байта на член структуры ErrorChar.  
DWORD fNull: // Маска $800 enable null stripping – пропускать при приеме символы NULL  
DWORD fRtsControl: // Маска $3000. Тип контроля:  
   RTS_CONTROL_DISABLE  
   RTS_CONTROL_ENABLE  
   RTS_CONTROL_HANDSHAKE    Аналогично сигналу DTR  
   RTS_CONTROL_TOGGLE       – Высокий уровень пока, есть данные для передачи.  
  
DWORD fAbortOnError // Маска $4000. Прекращение операций  
                    // чтения – записи при возникновении ошибок  
DWORD fDummy2:17;   // Не используются  
  
  
Другие данные структуры  
  
WORD wReserved;     // Не используется  
WORD XonLim;        // минимальное число байт в приемном буфере до отправки символа XON  
WORD XoffLim;       // максимальное число байт в приемном буфере до отправки символа XOFF  
BYTE ByteSize;      // количество бит в байте от 4 до 8  
BYTE Parity;        // 0-4=no,odd,even,mark,space бит паритета,  
BYTE StopBits;      // 0,1,2 = 1, 1.5, 2 – стоп биты,   
                    // 1,5 используются только при 5 битах в посылке для мелкосхемы 8250.  
  
ONESTOPBIT 1 stop bit  
ONE5STOPBITS            1.5 stop bits  
TWOSTOPBITS             2 stop bits  
  
char XonChar;       // Tx and Rx XON символ  
char XoffChar;      // Tx and Rx XOFF символ  
char ErrorChar;     // Символ, которым заменяется ошибочно принятый байт  
  
char EofChar;       // end of input character  
char EvtChar;       // received event character  
WORD wReserved1;    // Не используется  

Delphi имеет оболочку для DCB – TDCB.

Получить текущую конфигурацию порта можно функцией GetCommState(Fhandle:Handle; fDCB:TDCB).

Установить соответственно SetCommDCB.

После установки параметров порта. Читать и писать можно через ReadFile и WriteFile.
Заключение
В данной заметке приведена лишь небольшая часть сведений о работе с последовательным портом. Если хоть кому-нибудь это интересно и нужно напишите мне на mgoblin@mail.ru, я попробую вдохновиться на дальнейший труд.

Добавлено: 31 Июля 2018 07:48:44 Добавил: Андрей Ковальчук

Программный поиск файлов

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

Многим известны программы, где можно искать файлы, правила поиска файла. Файлы можно искать как с файловых командирах (нортон, волков, дос навигатор, фар), так в любой операционной системе. В операционной системе windows диалоговое окно поиска файла вызывается "Пуск" - "Поиск" - "Файлы и папки". В открывшимся окне необходимо задать условие искомого файла (название, маска) и путь начального поиска (каталог). На других вкладках этого диалогового окна можно расширить возможности поиска по дате изменения, по содержащемуся тексту, по размеру.

Вспомним правила поиска файлов. Вы можете задать как имя искомого файла, так и его маску, если название неизвестно или необходимо найти несколько. Т.е. применяя специальный шаблон поиска, вы можете организовать условия выборки найденных файлов. Сразу оговорюсь, что поиск можно применять как к файлам, так и к каталогам. Будем их называть элементами файловой системы. В шаблон маски искомых элементов может входить:

Буквы и цифры в названии и расширении.
Символ * (звездочка, математический знак "умножить"), заменяющий любое количество всевозможных букв и цифр в названии или расширении.
Символ ? (знак вопроса), заменяющий одну букву или цифру в названии или расширении искомого элемента.
Например, вы ищите все текстовые файлы с расширением TXT. В поле имени искомого файла вам нужно ввести "*.TXT" (пишется без кавычек) и система найдет все такие файлы в указанном диске или каталоге. Если вам надо найти все файлы с названием semen, то в поле поиска файла нужно ввести "semen.*". Если вам нужно найти элементы с третьей буквой k и с первой буквой t в расширении, то вводите "??k*.t*". Здесь знак вопроса указывает на любой символ, третьим символом по порядку идет буква k, далее название файла (каталога) может состоять из любого количества букв и цифр, указываем звездочку. В расширении первая буква t, дальше следует любое расширение.

Примечание: файлы и каталоги в операционной системе windows ищутся без учета регистра, т.е. строчние и прописные буквы не различаются.

Теперь рассмотрим программный поиск файлов с помощью языка программирования object pascal.

Вся организация цикла поиска, а именно это и есть цикл с продолжением поиска, сводится к:

Задание условий поиска. Это каталог и маска искомого элемента или элементов, атрибуты элемента(ов). При задании условий поиска сразу происходит поиск первого подходящего под условие. Это функция FindFirst.
Продолжение поиска следующего элемента по заданным в первом пункте условиям. Это функция FindNext и она может вызываться сколько угодно раз, пока все файлы и каталоги, удовлетворяющие условию, не будут найдены.
Закрытие поиска и освобождение памяти, выделяемую системой под поиск. Команда FindClose.
Функция FindFirst.
Синтаксис:


FindFirst  
(КАТАЛОГ_ПОИСКА_И_МАСКА_ФАЙЛА,  
АТРИБУТЫ_ИСКОМОГО_ФАЙЛА , ПОИСКОВОЯ_ПЕРЕМЕННАЯ); 

где: Каталог для поиска и маска искомого элемента - строковая величина, имеющая тип String, может, например, содержать 'c:\*.*' - все элементы в корне диска С. Обратите внимание, что указывается полный путь для поиска.

Атрибуты искомого элемента это пользовательские или системные атрибуты, которые может иметь файл (каталог, метка диска). Вот их перечень:

faReadOnly - Файлы "только чтение". Такой атрибут устанавливается на файлы, которые не рекомендовано изменять, удалять. Такой атрибут имеют файлы, например, записанные на компакт-дисках.
faHidden - Скрытые файлы. При обычных установках браузера и командира эти файлы невидимы.
faSysFile - Системные файлы.
faVolumeID - Файл метки диска. Такой элемент в своем имени имеет название диска (максимум 11 символов).
faDirectory - Атрибут признака каталога.
faArchive - Обычный файл. По умолчанию устанавливается на заново создаваемых файлах.
faAnyFile - Если установить в качестве атрибута искомых элементов, то будет произведен поиск по всем вышесказанным атрибутам.
Эти вам нужно искать только элементы, имеющие атрибут "каталог" и "скрытый", то можно применить знак математического сложения, например faDirectory + faHidden.

Поисковая переменная имеет тип TSearchRec. В нее, при успешном результате поиска, будет занесены все необходимые данные о найденном файловом элементе.

Поскольку FindFirst является функцией, то она должна сама возвращать некоторое значение. Это значение имеет тип Integer и означает результат поиска файла (код ошибки поиска). Если файл найден, то принимает нулевое значение.

Функция FindNext.

FindNext ( ПОИСКОВАЯ_ПЕРЕМЕННАЯ );
Эта функция продолжает поиск, заданный в функции FindNext. Возвращает значение результата поиска (нулевое в случае успешного поиска).

Процедура FindClose.

FindClose ( ПОИСКОВАЯ_ПЕРЕМЕННАЯ );
Закрывает поиск и освобождает память, выделенную системой под поиск.

Теперь рассмотрим пример. Допустим, нам надо найти все файлы и каталоги в каталоге DELPHI, находящийся на диске C:. В дальнейшем, вы можете самостоятельно, изменяя маску, менять условия поиска. Для формы с компонентом ListBox1 и кнопкой Button1 реакция на OnClick по кнопке:


procedure TForm1.Button1Click(Sender: TObject);  
Var SR:TSearchRec; // поисковая переменная  
    FindRes:Integer; // переменная для записи результата поиска  
begin  
ListBox1.Clear; // очистка компонента ListBox1 перед занесением в него списка файлов  
  
// задание условий поиска и начало поиска  
FindRes:=FindFirst('c:\delphi\*.*',faAnyFile,SR);   
  
While FindRes=0 do // пока мы находим файлы (каталоги), то выполнять цикл  
   begin  
      ListBox1.Items.Add(SR.Name); // добавление в список название найденного элемента  
      FindRes:=FindNext(SR); // продолжение поиска по заданным условиям  
   end;  
FindClose(SR); // закрываем поиск  
end;  

Представленный пример кода, в принципе, является основой для организации более углубленного поиска, поиска файлов по времени создания, по содержащимся словам. Если вы запустите эту программу на выполнение, то при нажатии на кнопку Button1 вы увидите в списке в первой и второй строке элементы "." и "..". Это элементы, имеющие атрибут "каталог". Первый содержит связь с корневым каталогом диска, второй содержит связь к каталогом верхнего уровня. Со вторым вы встречаетесь в дисковых командных оболочках, например нортон, когда выбираете каталог ".." и нажимаете на "ввод". Тем самым вы попадаете в каталог на уровень выше. Естественно, в нашей поисковой программе такие элементы не надо вносить в список, поэтому мы игнорируем их нахождение. Исправляем процедуру нажатия на кнопку Button1:


procedure TForm1.Button1Click(Sender: TObject);  
Var SR:TSearchRec;  
    FindRes:Integer;  
begin  
ListBox1.Clear;  
  
FindRes:=FindFirst('c:\delphi\*.*',faAnyFile,SR);  
While FindRes=0 do  
   begin  
      // если найденный элемент каталог и  
      if ((SR.Attr and faDirectory)=faDirectory) and   
      // он имеет название "." или "..", тогда:  
      ((SR.Name='.')or(SR.Name='..')) then   
         begin  
            FindRes:=FindNext(SR); // продолжить поиск  
            Continue; // продолжить цикл  
         end;  
  
      ListBox1.Items.Add(SR.Name);  
      FindRes:=FindNext(SR);  
   end;  
FindClose(SR);  
end;  

В этом случае, при нахождении каталога с именем "." или с именем ".." программа продолжит обработку цикла поиска без вывода найденного имени элемента в компонент списка ListBox1.

Теперь рассмотрим тип TSearchRec. Он имеет в себе несколько полезных свойств:

Name - название найденного каталога (файла);
Size - размер файла в байтах;
Attr - атрибуты каталога (файла);
Time - упакованное значение времени и даты создания каталога (файла).
Все вышеперечисленные свойства мы уже рассмотрели или они понятны сразу, за исключением свойства Time. Оно имеет тип Integer и содержит в себе упакованное значение даты и времени создания файла. Распаковка производится с помощью функции FileDateToDateTime, которая в результате возвращает значение даты и времени.

Теперь добавим в нашу форму компонент DateTimePicher1 (страница Win32) и допишем несколько строк.


procedure TForm1.Button1Click(Sender: TObject);  
Var SR:TSearchRec;  
    FindRes:Integer;  
begin  
ListBox1.Clear;  
  
FindRes:=FindFirst('c:\delphi\*.*',faAnyFile,SR);  
While FindRes=0 do  
   begin  
      if ((SR.Attr and faDirectory)=faDirectory) and  
      ((SR.Name='.')or(SR.Name='..')) then  
         begin  
            FindRes:=FindNext(SR);  
            Continue;  
         end;  
      // если у файла (каталога) дата создания меньше,  
      // чем установлено в DateTimePicker1, то  
      if FileDateToDateTime(SR.Time)<DateTimePicker1.Date then   
         begin  
            FindRes:=FindNext(SR); // продолжить поиск  
            Continue; // продолжить цикл  
         end;  
  
      ListBox1.Items.Add(SR.Name);  
      FindRes:=FindNext(SR);  
   end;  
FindClose(SR);  
end;  

Как вы уже заметили, мы отбираем файлы и каталоги по дате создания, начиная с указанной в компоненте DateTimePicker1.

Теперь попробуем организовать поиск файлов во всех вложенных каталогах. Это не так просто, как может показаться на первый взгляд. Нам придется вручную организовывать весь цикл входа-выхода из каталога, перебор файлов. Немного сложноватый материал, но возможно те из вас, кто уже работал с языком программирования pascal или другим, знакомы с технологией многократности и многовложенности использования одного и того же программного кода. Коротко объясню алгоритм работы такой программы.

Задание начальных условий поиска, поиск первого элемента.
Если найден файл, то выводим его и соответственно обрабатываем (выводим в список, открываем, удаляем и т.п.).
Если найден каталог, то начинаем новую процедуру поиска. Но программный код остается прежним. Мы просто заново вызываем и входим в эту же процедуру поиска.
Обрабатываем таким же образом все вложенные в этот каталог файлы и каталоги (начинаем новый поиск в обнаруженном каталоге).
Если элементов во вложенном каталоге больше нет, то обработка процедуры поиска в нем завершается, и мы выходим из нее. При этом мы оказываемся в том же месте, откуда и вызвали эту процедуру. Но она была вызвана из этой же процедуры. Поэтому программа продолжает свое выполнение дальше с момента возврата.
Таким образом, сколько витков программа наматывает на так называемый клубок, столько витков она и размотает. Программа на выполнении проходит все дерево вложенных каталогов, выполняя один и тот же кусок программного кода! И при этом данные условий поиска не перепутываются, и для каждой уникальной процедуры они сохраняются.

Рассмотрим пример. Создайте новый проект. Для создания отдельной процедуры поиска нам нужно объявить ее в соответствующем разделе (создаем ее вручную, поэтому и самостоятельно объявляем).

В разделе public пишем строку:


procedure FindFile(Dir:String); 

А в разделе кода программы, до слова "end." вставляем пустой каркас процедуры


procedure TForm1.FindFile(Dir:String);  
begin  
  
end; 

На форму вставляем компонент списка ListBox1, Button1, Edit1. Для компонента Edit1 свойство Text устанавливаем в "c:\delphi\". Обратите внимание на последний символ, знак "\", присутствие которого в начальном пути поиска обязательно. Дальше процедура OnClick для кнопки Button1 выглядит следующим образом:


procedure TForm1.Button1Click(Sender: TObject);  
begin  
ListBox1.Clear; // очистка списка файлов  
FindFile(Edit1.Text); // поиск файлов с начальными условиями, заданных в Edit1  
end;  

Созданная нами вручную процедура поиска:


procedure TForm1.FindFile(Dir:String);  
Var SR:TSearchRec;  
    FindRes:Integer;  
begin  
FindRes:=FindFirst(Dir+'*.*',faAnyFile,SR);  
While FindRes=0 do  
   begin  
      if ((SR.Attr and faDirectory)=faDirectory) and  
      ((SR.Name='.')or(SR.Name='..')) then  
         begin  
            FindRes:=FindNext(SR);  
            Continue;  
         end;  
  
      // если найден каталог, то  
      if ((SR.Attr and faDirectory)=faDirectory) then   
         begin  
            // входим в процедуру поиска с параметрами текущего каталога +  
            // каталог, что мы нашли  
            FindFile(Dir+SR.Name+'\');   
            FindRes:=FindNext(SR);  
            // после осмотра вложенного каталога мы продолжаем поиск  
            // в этом каталоге  
            Continue; // продолжить цикл  
         end;  
  
      ListBox1.Items.Add(SR.Name);  
      FindRes:=FindNext(SR);  
   end;  
FindClose(SR);  
end;  

Если вы в компоненте Edit1 в качестве начального условия поиска файлов зададите корневую папку диска, например "С:\", то вы получите полный перечень всех файлов на данном диске. Обратите внимание на скорость поиска файлов и скорость работы вашей программы.

Добавлено: 31 Июля 2018 07:47:32 Добавил: Андрей Ковальчук

Потоки и DLL

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

Техническая сторона вопроса будет сфокусирована на потоках и функции DllEntryPoint. Функция DllEntryPoint не должна объявляться в ваших Delphi DLL. Фактически, большую часть, если не всю, Delphi DLL будет правильно работать и без вашего явного объявления DllEntryPoint. Тем не менее, я включил данный совет для тех Win32-программистов, которые понимают эту функцию и хотят связать с ней свое функциональное назначение, чтобы оно являлось частью DLL. Чтобы быть более конкрентым, это будет интересно тем программистам, которые хотят вызывать одну и ту же DLL из многочисленных потоков одной программы.

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


library MyDll;  
  
// Здесь код  
// экспорта  
  
begin  
// Расположенный здесь код выполняется в первую  
// очередь при каждом вызове DLL любым exe-файлом.  
end.  

Как вы можете здесь увидеть, здесь нет традиционного DLLEntryPoint, имеющегося в стандартных C/C++ DLL. Для тех, кто только начал изучать Win32, я сообщу, что DLLEntryPoint берет начало от функций LibMain и WEP, работающих в Windows 3.1. LibMain и WEP теперь считаются устаревшими, вместо них необходимо использовать DLLEntryPoint.

Для явной установки DLLEntryPoint в Delphi, используйте следующий код-скелет, имеющий преимущество перед переменной DLLProc, объявленной глобально в SYSTEM.PAS:


library DllEntry;  
  
procedure DLLEntryPoint(Reason: DWORD);  
begin  
// Здесь организуется блок Case для Dll_Process_Attach, и др.  
end;  
  
// Здесь реализация экспортируемых функций  
  
// экспорт  
  
begin  
if DllProc = nil then begin  
DllProc := @DLLEntryPoint;  
DllEntryPoint(Dll_Process_Attach);  
end;  
end.  

Данный код назначает объявленный пользователей метод с именем DLLEntryPoint объявленной глобально переменной Delphi с именем DllProc, в свою очередь объявленой в SYSTEM.PAS следующим образом:


var  
DllProc: Pointer; { Вызывается каждый раз при вызове точки входа DLL }  

Вы можете имитировать стандартную функциональность DLLEntryPoint, вызывая объявленный к тому времени локально DLLEntryPoint, и передавая ему Dll_Process_Attach в качестве переменной. В C/C++ DLL эта переменная должна передаваться определенной пользователем функции с именем DllEntryPoint автоматически при первом доступе к DLL из первой обратившейся к ней программы. В Delphi первый вызов этой функции может быть произведен вручную пользователем, но последующие вызовы происходят автоматически до тех пор, пока вы не назначите первый раз функцию переменной DllProc. Другими словами, вы можете форсировать первый вызов DllEntryPoint как показано выше, но последующие вызовы будут сделаны системой автоматически.

Dll_Process_Attach - одна из четырех возможных констант, которые система можете передавать функции DllEntryPoint. Эти константы объявлены в WINDOWS.PAS следующим образом:


DLL_PROCESS_ATTACH = 1; // Программа подключается к DLL  
DLL_THREAD_ATTACH = 2;  // Поток программы подключается к DLL  
DLL_THREAD_DETACH = 3;  // Поток "оставляет" DLL  
DLL_PROCESS_DETACH = 0; // Exe "отсоединяется" от DLL  

Более детальная скелетная конструкция DllEntryPoint с использованием приведенных констант:


procedure DLLEntryPoint(Reason: DWORD);  
begin  
case Reason of  
Dll_Process_Attach:  
MessageBox(DLLHandle, 'Подключение процесса', 'Инфо', mb_Ok);  
Dll_Thread_Attach:  
MessageBox(DLLHandle, 'Подключение потока', 'Инфо', mb_Ok);  
Dll_Thread_Detach:  
MessageBox(DLLHandle, 'Отключение потока', 'Инфо', mb_Ok);  
Dll_Process_Detach:  
MessageBox(DLLHandle, 'Отключение процесса', 'Инфо', mb_Ok);  
end; // case  
end;  

В приведенном примере я просто вызываю диалог MessageBox в ответ на возможные параметры, передаваемые DLLEntryPoint. Тем не менее, вы могли бы найти более достойное применение данным константам или вовсе игнорировать их.

Работа с потоками

Приведенный ниже небольшой фрагмент кода достоин занять место в программе, вызывающей DLL. Он показывает как можно объявить функцию, экспортируемую из DLL, и как вызвать эту функцию из потока. Конечно, обычно нет необходимости вызывать функцию DLL из потока, я делаю это просто для того, чтобы показать функциональное назначение, связанное с обсуждаемыми выше константами Dll_Thread_Attach и Dll_Thread_Detach.


function MyFunc: ShortString;  
external 'DLLENTRY1' name 'MyFunc';  
  
procedure ThreadFunc(P: Pointer); stdcall;  
var  
S: array[0..255] of Char;  
begin  
StrPCopy(S, MyFunc);  
MessageBox(Form1.Handle, S, 'Инфо', mb_Ok);  
end;  
  
procedure TForm1.UseThreadClick(Sender: TObject);  
var  
ThreadID: DWORD;  
HThread: THandle;  
begin  
HThread := CreateThread(nil, 0, @ThreadFunc,   
nil, 0, ThreadID);  
if HThread = 0 then ShowMessage('Нет потоков');  
end;  

Приведенный здесь код делится на три секции. В первой декларируется MyFunc, являющаяся простой реализацией функции в DLL. ThreadFunc сама располагается в отдельном потоке, создаваемом программой. Процедура UseThreadClick создает поток. Сразу после создания потока система вызывет процедуру ThreadFunc.

Вот декларация CreateThread:


var  
  
DWORD = Integer;  
  
function CreateThread(  
lpThreadAttributes: Pointer; // атрибуты безопасности потока  
dwStackSize: DWORD;          // размер стека для потока  
lpStartAddress: TFNThreadStartRoutine; // функция потока  
lpParameter: Pointer;        // аргумент для нового потока   
dwCreationFlags: DWORD;      // флаги создания  
var lpThreadId: DWORD):      // Возвращаемый идентификатор потока  
THandle;                     // Возвращаемый дескриптор потока  

В нормальной ситуации большинство параметров, передаваемых CreateThread, могут быть установлены в 0 или nil. Показан типичный пример вызова данной функции, но во многих случаях использование lpParameter неоправданно тяжело. Разумеется, любые переменные, установленные в данном параметре, передаются ThreadFunc в виде единственного аргумента.

Фактически, реализация функции потока очень проста, происходит вызов DLL и показывается информационный диалог, демонстрирующий строку, возвращаемую DLL.

Если вы создали программу с потоковой функцией как было показано выше, и создали DLL с функцией DLLEntryPoint, тоже показанной выше, то можно получить визуальное подтверждение того, как работает функция DLLEntryPoint. Поясняю: когда ваша программа загружается в память, DLL также должна быть автоматически загружена, тем самым вызывая MessageBox с текстом `Процесс подключен'. Диалоги появляются в зависимости от причины (Reason) вызова функции DllEntryPoint:


procedure DLLEntryPoint(Reason: DWORD);  
begin  
case Reason of  
Dll_Process_Attach:  
MessageBox(DLLHandle, 'Процесс подключен', 'Инфо', mb_Ok);  
Dll_Thread_Attach:  
MessageBox(DLLHandle, 'Поток подключен', 'Инфо', mb_Ok);  
Dll_Thread_Detach:  
MessageBox(DLLHandle, 'Поток отключен', 'Инфо', mb_Ok);  
Dll_Process_Detach:  
MessageBox(DLLHandle, 'Процесс отключен', 'Инфо', mb_Ok);  
end; // case  
end;  

Если вы создали процедуру ThreadFunc, показанную выше, то должно появиться диалоговое окно (MessageBox) с надписью "Поток подключен". При завершении работы подпрограммы ThreadFunc появится окошко с надписью "Поток отключен". Наконец, при закрытии программы должна появиться надпись "Процесс отключен". Пример, демонстрирующий процесс, доступен в сети.

Довольно сложно иллюстрировать технические возможности Delphi. Не все программисты Delphi захотят так глубоко вникать в дебри Windows API. Тем не менее, те, которые хотят воспользоваться мощью Windows 95 и Windows NT на полную катушку, могут видеть, что все современные технологии доступны всем без исключения программистам на Delphi. Приведенный выше пример доступен в Compuserve в виде файла DLLENT.ZIP и также размещен на Интернет-сервере Borland по адресу www.borland.com..

Автор: Charles Calvert

Добавлено: 31 Июля 2018 07:45:43 Добавил: Андрей Ковальчук

Получение списка DLL загруженных приложением

Иногда бывает полезно знать какими DLL-ками пользуется Ваше приложение. Давайте посмотрим как это можно сделать в Win NT/2000.

Пример функции


unit ModuleProcs;  
  
interface  
  
uses Windows, Classes;  
type  
  TModuleArray = array[0..400] of HMODULE;  
  TModuleOption = (moRemovePath, moIncludeHandle);  
  TModuleOptions = set of TModuleOption;  
  
function GetLoadedDLLList(sl: TStrings;  
Options: TModuleOptions = [moRemovePath]): Boolean;  
implementation  
  
uses SysUtils;  
  
function GetLoadedDLLList(sl: TStrings;  
Options: TModuleOptions = [moRemovePath]): Boolean;  
type  
EnumModType = function (hProcess: Longint; lphModule: TModuleArray;  
cb: DWord; var lpcbNeeded: Longint): Boolean; stdcall;  
var  
psapilib: HModule;  
EnumProc: Pointer;  
ma: TModuleArray;  
I: Longint;  
FileName: array[0..MAX_PATH] of Char;  
S: string;  
begin  
Result := False;  
(* Данная функция запускается только для Widnows NT *)  
if Win32Platform <> VER_PLATFORM_WIN32_NT then  
Exit;  
psapilib := LoadLibrary('psapi.dll');  
if psapilib = 0 then  
Exit;  
try  
EnumProc := GetProcAddress(psapilib, 'EnumProcessModules');  
if not Assigned(EnumProc) then  
Exit;  
sl.Clear;  
FillChar(ma, SizeOF(TModuleArray), 0);  
if EnumModType(EnumProc)(GetCurrentProcess, ma, 400, I) then  
begin  
for I := 0 to 400 do  
if ma[i] <> 0 then  
begin  
FillChar(FileName, MAX_PATH, 0);  
GetModuleFileName(ma[i], FileName, MAX_PATH);  
if CompareText(ExtractFileExt(FileName), '.dll') = 0 then  
begin  
S := FileName;  
if moRemovePath in Options then  
S := ExtractFileName(S);  
if moIncludeHandle in Options then  
sl.AddObject(S, TObject(ma[I]))  
else  
sl.Add(S);  
end;  
end;  
end;  
Result := True;  
finally  
FreeLibrary(psapilib);  
end;  
end;  
end.  

Для вызова приведённой функции надо сделать следующее:

Добавить listbox на форму (Listbox1)
Добавить кнопку на форму (Button1)
Обработчик события OnClick для кнопки будет выглядеть следующим образом

procedure TForm1.Button1Click(Sender: TObject);  
begin  
GetLoadedDLLList(ListBox1.Items, [moIncludeHandle, moRemovePath]);  
end;   

Автор: Simon Carter

Добавлено: 31 Июля 2018 07:44:27 Добавил: Андрей Ковальчук