function IntToHex (Value: Int64; Digits: Integer): AnsiString; overload;
var
result Result: AnsiString;
begin
00000000 : 55 PUSH EBP
00000001 : 8B EC MOV EBP,ESP
00000003 : 83 F8 20 CMP EAX,32
00000006 : 7E 02 JLE +2; (0xA)
00000008 : 31 C0 XOR EAX,EAX
0000000A : 56 PUSH ESI
0000000B : 89 E6 MOV ESI,ESP
0000000D : 83 EC 20 SUB ESP,32
00000010 : B9 10 00 00 00 MOV ECX,$00000010
00000015 : 52 PUSH EDX
00000016 : 89 C2 MOV EDX,EAX
00000018 : 8D 45 08 LEA EAX,DWORD PTR [EBP+8{Value}]
0000001B : E8(00 00 00 00 CALL CvtInt64{0x41A}
00000020 : 89 F2 MOV EDX,ESI
00000022 : 58 POP EAX
00000023 : E8(00 00 00 00 CALL @LStrFromPCharLen{0x18B}
00000028 : 83 C4 20 ADD ESP,32
0000002B : 5E POP ESI
0000002C : 5D POP EBP
0000002D : C2 08 00 RET NEAR,8
end;
function IntToHex(Value: Int64; Digits: Integer): string;
asm
CMP EAX, 32 // Digits < buffer length?
JLE @A1
XOR EAX, EAX
@A1: PUSH ESI
MOV ESI, ESP
SUB ESP, 32 // 32 chars
MOV ECX, 16 // base 10
PUSH EDX // result ptr
MOV EDX, EAX // zero filled field width: 0 for no leading zeros
LEA EAX, Value;
CALL CvtInt64
MOV EDX, ESI
POP EAX // result ptr
CALL System.@LStrFromPCharLen
ADD ESP, 32
POP ESI
end;
Funkci LstrFromPCharLen má v Delphi každý program, proč to tedy nepoužít, že? Ještě k řádku 18h, kde je LEA EAX, Value… Snad Vám došlo, že PTR v tom DCU dumpu měl v {závorce} hodnotu Value, tudíž takhle to jde použít. Větší problém je přeložit CvtInt64, což je funkce v unitě SysUtils, která převádí Int64 na text. Není to přesně IntToStr, ale je to kód, který „předpřevede“ integer na text (ne na string). Přeložit ji musíme :)… Dump je:procedure CvtInt64;
begin
00000000 : 08 C9 OR CL,CL
00000002 : 75 30 JNE +48; (0x34)
00000004 : B9 0A 00 00 00 MOV ECX,$0000000A
00000009 : F7 40 04 00 00 00 80 TEST DWORD PTR [EAX+4],$80000000
00000010 : 74 22 JE +34; (0x34)
00000012 : FF 70 04 PUSH DWORD PTR [EAX+4]
00000015 : FF 30 PUSH DWORD PTR [EAX]
00000017 : 89 E0 MOV EAX,ESP
00000019 : F7 1C 24 NEG DWORD PTR [ESP]
0000001C : 83 54 24 04 00 ADC DWORD PTR [ESP+4],0
00000021 : F7 5C 24 04 NEG DWORD PTR [ESP+4]
00000025 : E8(34 00 00 00 CALL +CvtInt64{0x41A}+52
0000002A : C6 46 FF 2D MOV BYTE PTR [ESI-1],$2D
0000002E : 4E DEC ESI
0000002F : 41 INC ECX
00000030 : 83 C4 08 ADD ESP,8
00000033 : C3 RET NEAR
00000034 : 56 PUSH ESI
00000035 : 83 EC 04 SUB ESP,4
00000038 : D9 7C 24 02 FSTCW [ESP+2]
0000003C : D9 3C 24 FSTCW [ESP]
0000003F : 66 81 0C 24 00 0F OR WORD PTR [ESP],$0F00
00000045 : D9 2C 24 FLDCW [ESP]
00000048 : 66 89 0C 24 MOV WORD PTR [ESP],CX
0000004C : D9 E8 FLD1
0000004E : F7 40 04 00 00 00 80 TEST DWORD PTR [EAX+4],$80000000
00000055 : 74 27 JE +39; (0x7E)
00000057 : FF 70 04 PUSH DWORD PTR [EAX+4]
0000005A : FF 30 PUSH DWORD PTR [EAX]
0000005C : 81 64 24 04 FF FF FF 7F AND DWORD PTR [ESP+4],$7FFFFFFF
00000064 : 68 FF FF FF 7F PUSH $7FFFFFFF
00000069 : 68 FF FF FF FF PUSH $FFFFFFFF
0000006E : DF 6C 24 08 FILD TWORD PTR [ESP+8]
00000072 : DF 2C 24 FILD TWORD PTR [ESP]
00000075 : D8 C2 FADD ST,ST2
00000077 : DE C1 FADDP ST1,ST
00000079 : 83 C4 10 ADD ESP,16
0000007C : EB 02 JMP +2; (0x80)
0000007E : DF 28 FILD TWORD PTR [EAX]
00000080 : DF 04 24 FILD WORD PTR [ESP]
00000083 : D9 C1 FLD ST1
00000085 : 4E DEC ESI
00000086 : D9 F8 FPREM
00000088 : DF 1C 24 FISTP WORD PTR [ESP]
0000008B : DC F9 FDIVR ST1,ST
0000008D : 8A 04 24 MOV AL,BYTE PTR [ESP]
00000090 : 04 30 ADD AL,$30
00000092 : 3C 3A CMP AL,$3A
00000094 : 72 02 JB +2; (0x98)
00000096 : 04 07 ADD AL,$07
00000098 : 88 06 MOV BYTE PTR [ESI],AL
0000009A : D9 C1 FLD ST1
0000009C : D8 D3 FCOM ST3
0000009E : 9B WAIT
0000009F : DF E0 FSTSW AX
000000A1 : 9E SAHF
000000A2 : 73 E1 JNB -31; (0x85)
000000A4 : D9 6C 24 02 FLDCW [ESP+2]
000000A8 : 83 C4 04 ADD ESP,4
000000AB : DD C3 FFREE ST3
000000AD : DD C2 FFREE ST2
000000AF : DD C1 FFREE ST1
000000B1 : DD C0 FFREE ST
000000B3 : 59 POP ECX
000000B4 : 29 F1 SUB ECX,ESI
000000B6 : 29 CA SUB EDX,ECX
000000B8 : 76 10 JBE +16; (0xCA)
000000BA : 29 D6 SUB ESI,EDX
000000BC : B0 30 MOV AL,$30
000000BE : 01 D1 ADD ECX,EDX
000000C0 : EB 03 JMP +3; (0xC5)
000000C2 : 88 04 32 MOV BYTE PTR [EDX+ESI],AL
000000C5 : 4A DEC EDX
000000C6 : 75 FA JNE -6; (0xC2)
000000C8 : 88 06 MOV BYTE PTR [ESI],AL
000000CA : C3 RET NEAR
end;
Nelekejte se, je to přece jen o trošičku delší, ale postup je úplně stejný :). Tam kde jsou jumpy, vytvářejte labely a poslední return vypusťte. Po chvilce trápení byste měli vykouzlit asi přibližně toto:procedure CvtInt64;
{ IN:
EAX: Address of the int64 value to be converted to text
ESI: Ptr to the right-hand side of the output buffer: LEA ESI, StrBuf[32]
ECX: Base for conversion: 0 for signed decimal, or 10 or 16 for unsigned
EDX: Precision: zero padded minimum field width
OUT:
ESI: Ptr to start of converted text (not start of buffer)
ECX: Byte length of converted text
}
asm
OR CL, CL
JNZ @start // CL = 0 => signed integer conversion
MOV ECX, 10
TEST [EAX + 4], $80000000
JZ @start
PUSH [EAX + 4]
PUSH [EAX]
MOV EAX, ESP
NEG [ESP] // negate the value
ADC [ESP + 4],0
NEG [ESP + 4]
CALL @start // perform unsigned conversion
MOV [ESI-1].Byte, '-' // tack on the negative sign
DEC ESI
INC ECX
ADD ESP, 8
RET
@start: // perform unsigned conversion
PUSH ESI
SUB ESP, 4
FNSTCW [ESP+2].Word // save
FNSTCW [ESP].Word // scratch
OR [ESP].Word, $0F00 // trunc toward zero, full precision
FLDCW [ESP].Word
MOV [ESP].Word, CX
FLD1
TEST [EAX + 4], $80000000 // test for negative
JZ @ld1 // FPU doesn't understand unsigned ints
PUSH [EAX + 4] // copy value before modifying
PUSH [EAX]
AND [ESP + 4], $7FFFFFFF // clear the sign bit
PUSH $7FFFFFFF
PUSH $FFFFFFFF
FILD [ESP + 8].QWord // load value
FILD [ESP].QWord
FADD ST(0), ST(2) // Add 1. Produces unsigned $80000000 in
ST(0)
FADDP ST(1), ST(0) // Add $80000000 to value to replace the sign bit
ADD ESP, 16
JMP @ld2
@ld1:
FILD [EAX].QWord // value
@ld2:
FILD [ESP].Word // base
FLD ST(1)
@loop:
DEC ESI
FPREM // accumulator mod base
FISTP [ESP].Word
FDIV ST(1), ST(0) // accumulator := acumulator / base
MOV AL, [ESP].Byte // overlap long FPU division op with int ops
ADD AL, '0'
CMP AL, '0'+10
JB @store
ADD AL, ('A'-'0')-10
@store:
MOV [ESI].Byte, AL
FLD ST(1) // copy accumulator
FCOM ST(3) // if accumulator >= 1.0 then loop
FSTSW AX
SAHF
JAE @loop
FLDCW [ESP+2].Word
ADD ESP,4
FFREE ST(3)
FFREE ST(2)
FFREE ST(1);
FFREE ST(0);
POP ECX // original ESI
SUB ECX, ESI // ECX = length of converted string
SUB EDX,ECX
JBE @done // output longer than field width = no pad
SUB ESI,EDX
MOV AL,'0'
ADD ECX,EDX
JMP @z
@zloop: MOV [ESI+EDX].Byte,AL
@z: DEC EDX
JNZ @zloop
MOV [ESI].Byte,AL
@done:
end;
unit DjH;
interface
function IntToHex (Value: Int64; Digits: Integer): String; overload;
implementation
uses Windows,Messages;
//####################################################################//
procedure CvtInt64;
asm
………
end;
//#######################INTTOHEX################################//
function IntToHex (Value: Int64; Digits: Integer): String;
asm
………
end;
Za „………“ si doplňte výše uvedený kód :). Stačí komponentu už jen nakopčit do …\Borland\DelphiX\Lib (pod názvem „DjH.pas“) nebo ji zaregistrujte přes menu „Component“ -> „Install component…“. Nyní si vytvořte aplikaci (bez VCL) a místo SysUtils napište DjH (SysUtils vymažte). Třeba si udělejte takovouto jednoduchou aplikaci:
program DjHExample;
Zkompilujte, a hle! Program má jen 14.5 kB! Pro zajímavost: Po zkomprimování Upackem jen 7.87 kB! Nyní si místo DjH v uses, napište SysUtils a zkompilujte [Ctrl + F9], a co se nestalo, aplikace má rázem 38,5 kB! Po zásahu Upacku má soubor sice míň, ale 18.2 kB je stejně „moc“. Toto (a ještě míň) my máme s komponentou DjH i bez komprimace :).
uses
Windows,DjH;
begin
Randomize;
MessageBox(0,PChar(IntToHex((Random(100000)+1),8)),'Náhodné číslo <0-100000>',0);
end.
Tak, a běžte machrovat s touto komponentou! =), cože, že je to málo? Ok, fajn, uděláme si ještě „IntToStr“, „StrToInt“ a „StrToHex“. Třeba funkci StrToHex nikde nenajdete, a ta naše funkce bude pracovat na tomto principu:
function StrToHex(S:String; Value:Integer):String;
begin
result := IntToHex(StrToInt(S), Value);
end;
Prosté… A třeba maníky v Borlandu to (asi) nenapadlo, aby nám takto usnadnili práci…
Ale nepředbíhejme, jdeme si vyštrachovat funkci IntToStr.
Našli jsme a výpis je zde:
function IntToStr (Value: Int64): AnsiString; overload;
var
result Result: AnsiString;
begin
00000000 : 55 PUSH EBP
00000001 : 8B EC MOV EBP,ESP
00000003 : 56 PUSH ESI
00000004 : 89 E6 MOV ESI,ESP
00000006 : 83 EC 20 SUB ESP,32
00000009 : 31 C9 XOR ECX,ECX
0000000B : 50 PUSH EAX
0000000C : 31 D2 XOR EDX,EDX
0000000E : 8D 45 08 LEA EAX,DWORD PTR [EBP+8{Value}]
00000011 : E8(00 00 00 00 CALL CvtInt64{0x41A}
00000016 : 89 F2 MOV EDX,ESI
00000018 : 58 POP EAX
00000019 : E8(00 00 00 00 CALL @LStrFromPCharLen{0x18B}
0000001E : 83 C4 20 ADD ESP,32
00000021 : 5E POP ESI
00000022 : 5D POP EBP
00000023 : C2 08 00 RET NEAR,8
end;
Opět je výhodnější počítat s Int64. Vypustíme první dva a poslední dva řádky a zbyde nám toto:
function IntToStr(Value: Int64): String;
Vlastně už nic neupravujeme, akorát přidáme před LstrFromPCharLen string „System.“, vymažeme úplně nesmyslný „Var“ a místo „AnsiString“ použijeme jen „String“.
begin
asm
PUSH ESI
MOV ESI, ESP
SUB ESP,32
XOR ECX,ECX
PUSH EAX
XOR EDX,EDX
LEA EAX,Value
CALL CvtInt64
MOV EDX,ESI
POP EAX
CALL System.@LStrFromPCharLen
ADD ESP,32
POP ESI
end;
end;
Hon na StrToInt:
Zde bude potřeba menší úprava…
Dump:
function StrToInt (S: AnsiString): Integer;
var
Result: Integer;
E: Integer;
begin
00000000 : 53 PUSH EBX
00000001 : 56 PUSH ESI
00000002 : 83 C4 F4 ADD ESP,-12
00000005 : 8B D8 MOV EBX,EAX
00000007 : 8B D4 MOV EDX,ESP
00000009 : 8B C3 MOV EAX,EBX
0000000B : E8(00 00 00 00 CALL @ValLong{0x1A4}
00000010 : 8B F0 MOV ESI,EAX
00000012 : 83 3C 24 00 CMP DWORD PTR [ESP],0
00000016 : 74 19 JE +25; (0x31)
00000018 : 89 5C 24 04 MOV DWORD PTR [ESP+4],EBX
0000001C : C6 44 24 08 0B MOV BYTE PTR [ESP+8],$0B
00000021 : 8D 54 24 04 LEA EDX,DWORD PTR [ESP+4]
00000025 : A1(00 00 00 00 MOV EAX,DWORD PTR [SInvalidInteger{0x4}[0]]
0000002A : 33 C9 XOR ECX,ECX
0000002C : E8(00 00 00 00 CALL ConvertErrorFmt{0x317}
00000031 : 8B C6 MOV EAX,ESI
00000033 : 83 C4 0C ADD ESP,12
00000036 : 5E POP ESI
00000037 : 5B POP EBX
00000038 : C3 RET NEAR
end;
Na adrese „12“ se kontroluje délka stringu, který se má převést na integer, pokud je délka 0, tedy string = „“, je skok na chybu, ta se ale volá na adrese „2C“ (ještě k tomu z podcallu). Můžeme si to nechat být, a celou podmínku zrušit, a při zavolání funkce s prázdným („“) stringem bude result „0“, nebo funkci JE přepsat tak, aby skákala někam na námi udělaný MessageBox, který říká, že „“ není platný integer. Naopak když se zavolá funkce s „Integerem“ kde je neplatný znak, třeba StrToInt(‚123TR54‘);, tak nám SysUtils řekne, že ‚123TR54‘ je neplatný integer. Pokud my neuděláme podmínku, převede se string na integer do prvního neplatného znaku, v tomto případě tedy „123“. Já si zvolím cestu první, tedy že i neplatný string se bude konvertovat:
function StrToInt (S: String): Integer;
Nyní už funkce StrToHex:
asm
PUSH EBX
PUSH ESI
ADD ESP,-12
MOV EBX,EAX
MOV EDX,ESP
MOV EAX,EBX
CALL System.@ValLong
ADD ESP,12
POP ESI
POP EBX
end;
function StrToHex(S:String; Value:Integer):String;
begin
result := IntToHex(StrToInt(S), Value);
end;
Funkce IntToHex, IntToStr, StrToInt, StrToHex. Myslím že naše komponenta už je alespoň k něčemu a na rozdíl od SysUtils, zabere v binárce méně než 1 kB (SysUtils asi 25 kB). Příště si do ní přidáme pár funkcí a průběžně (nepravidelně) si tuto komponentu budeme vylepšovat. Budu ji vydávat jak ve verzi „All in 1“, tak v dílech – „DjHStringUtils“. Příště se podíváme na práci jako je třeba otevírání a zavírání mechaniky a práce se soubory, bude se to jmenovat třeba „DjHDriveUtils“ a „DjHFileUtils“. V „All In 1“ komponentě budou všechny unity spojené :). Je to jen pro to, že až toho v All in 1 bude hodně, budou třeba 3 kB použity v binárce úplně na nic. Proto si jen vyberete, jaké unity chcete :)
PS: Borland nám nedal zdrojáky k SysUtils!? My se nedáme! =), zde jste mohli vidět, jak jsme trochu Borlándka setřeli… Všechno jde… Jen, když se chce…
PS2: Takovýmto způsobem si můžete vytvořit vlastní komponentu, která má jen funkce, které vážně chcete, ale pozor! Když budete chtít takovéto zdrojové kódy dávat do světa, nezapomeňte do balíku se zdrojáky, přidat i tu Vámi vyrobenou komponentu, poněvač tu si počítač nedomyslí =), tu musíte FAKT přidat :