Mega Code Archive

 
Categories / Delphi / Functions
 

Delphi Assembler Function Conventions

Title: Delphi Assembler Function Conventions Introduction and Purpose This is a document describing how Delphi passes procedure and function items in reference to the built-in assembler. There is no intention to teach assembler, though there will be some instructive things in the examples. However, the intention is solely to show the assembler programmer how to interact with the Delphi procedure and function headers. Code & Examples As one might be aware, there are a few methods that Delphi uses to pass procedure and function information. The default is register , which is what the examples will be using. The others use variables passed on the stack using various methods. The examples will have numerous comments both to function as the reference for this FAQ, as well as a few instructive ASM comments. The examples should not be considered as good, efficient or completely bug-free ASM as well, but only simple ASM, so the interactions between Delphi and ASM might be clearly shown. Example 1 CODE {$APPTYPE CONSOLE} program asmdoc1; uses sysutils; { Delphi assembler doc example #1 - simple parameters, simple function result things like integers, char, pointer - anything 4 bytes or less which is not an FP type } function addthreeintegers1(num1, num2, num3: integer): integer; assembler; { num1 in EAX, num2 in EDX, num3 in ECX, result in EAX } asm ADD EAX, EDX // EAX := EAX + EDX; ADD EAX, ECX // EAX := EAX + ECX; end; procedure addthreeintegers2(var num1: integer; num2, num3: integer); assembler; { num1 is a pointer to contents of data, put in EAX, num2 in EDX, num3 in ECX } asm ADD EDX, ECX // EDX := EDX + ECX; MOV ECX, [EAX] // ECX := DWord(EAX^); ADD EDX, ECX // EDX := EDX + ECX; MOV [EAX], EDX // DWord(EAX)^ := EDX; end; var a: integer; begin writeln('Tek-tips Delphi ASM FAQ example #1 - simple parameters'); writeln; a := addthreeintegers1(2, 3, 6); writeln('Answer is ', a); addthreeintegers2(a, 2, 1); writeln('Answer is ', a); write('Press ENTER to exit.'); readln; Example 2 CODE {$APPTYPE CONSOLE} program asmdoc2; uses sysutils; { Delphi assembler doc example #2 - ShortStrings } function AChangeChar(instr: ShortString): ShortString; assembler; { increments each ASCII character by 1 and returns the string EAX is pointer to string block represented by instr, output function result is in EDX as a pointer, String function results are pushed as an additional VAR parameter } asm // Delphi procedures/functions must preserve EBX, ESI, and EDI if used PUSH EBX // first byte of ShortString is a length. Copy that to result MOV CL, Byte [EAX] // CL := Byte(EAX^); MOV Byte [EDX], CL // Byte(EDX^) := CL; // now process the string @Loop1: // for i := CL downto 0 do CMP CL, 0 JE @end MOV BL, Byte [EAX+1] // BL := Byte((EAX+1)^); ADD BL, 1 // BL := BL + 1; MOV Byte [EDX+1], BL // Byte((EDX+1)^) := BL; INC EAX // EAX := EAX + 1; INC EDX // EDX := EDX + 1; DEC CL // CL := CL - 1; JMP @loop1 @end: POP EBX end; var a: ShortString; b: ShortString; begin writeln('Tek-tips Delphi ASM FAQ example #2 - ShortStrings'); writeln; a := 'ABCDEFGHIJK'; writeln('Before the string is: ', a); b := AChangeChar(A); writeln(' After the string is: ', b); // answer should be 'BCDEFGHIJKL' write('Press ENTER to exit.'); readln; end. Example #3 CODE {$APPTYPE CONSOLE} program asmdoc3; uses sysutils; { Delphi assembler doc example #3 - var AnsiStrings applies to WideStrings as well } procedure AFillCharString(var instr: AnsiString; fchar: AnsiChar); assembler; { fills string with character "fchar" instr is address in EAX fchar is in EDX (specifically DL) as pointer to the AnsiString } asm MOV EAX, [EAX] // get contents of string CMP EAX, 0 // check whether the string is nil JE @exit // exit if string is nil // AnsiStrings are passed as pointers to the DATA, so length is before MOV ECX, [EAX-4] @loop1: CMP ECX, 0 // for i := ECX downto 1 do JE @exit MOV Byte [EAX], DL // Byte(EAX^) := DL; DEC ECX // ECX := ECX - 1; INC EAX // EAX := EAX + 1; JMP @loop1 @exit: end; var a: AnsiString; begin writeln('Tek-tips Delphi ASM FAQ example #3 - AnsiStrings'); writeln; a := 'ABCDEFGHIJK'; writeln('Before the string is: ', a); AFillCharString(a, '+'); writeln(' After the string is: ', a); write('Press ENTER to exit.'); readln; end. Example #4 CODE {$APPTYPE CONSOLE} program asmdoc4; uses sysutils; { Delphi assembler doc example #4 - record types } type MyRec = record x: integer; y: char; end; procedure ARecordHandle1(var InRecord: myRec); assembler; { add 10 to InRecord.X and increment InRecord.Y by 1 Record passed in EAX as address, using record typecasting } asm MOV EDX, MyRec([EAX]).x // EDX := EAX^.x; ADD EDX, 10 // EDX := EDX + 10; MOV MyRec([EAX]).x, EDX // EAX^.X := EDX; MOV DL, MyRec([EAX]).y // DL := EAX^.y; INC DL // DL := DL + 1; MOV MyRec([EAX]).y, DL // EAX^.y := DL; end; function ARecordHandle2(InRecord: myRec): MyRec; assembler; { add 10 to InRecord.X and increment InRecord.Y by 1 Record passed in EAX as address always and can be changed always Record address for output in EDX using memory positioning, watch value alignment on records if you do it this way } asm MOV ECX, [EAX] // ECX := DWord(EAX^); ADD ECX, 10 // ECX := EDX + 10; MOV [EDX], ECX // EDX^ := ECX; MOV CL, [EAX+4] // DL := Byte((EAX+4)^); INC CL // DL := DL + 1; MOV [EDX+4], CL // Byte((EAX+4)^) := DL; end; var a, b: MyRec; begin writeln('Tek-tips Delphi ASM FAQ example #4 - record types'); writeln; A.x := 30; A.y := 'C'; ARecordHandle1(a); writeln('A.x is : ', A.x); writeln('A.y is : ', A.y); b := ARecordHandle2(a); writeln('B.x is : ', B.x); writeln('B.y is : ', B.y); write('Press ENTER to exit.'); readln; end. Example #5 CODE {$APPTYPE CONSOLE} program asmdoc5; uses sysutils; { Delphi assembler doc example #5 - FP types } function AFPAddTwoNumbers1(In1, in2: Double): Double; assembler; { add FP numbers in1 and in2, return as result FP numbers are always passed on the stack in blocks divisible by 4, so extended always takes up 12 bytes, lower FP numbers returned in ST(0) for functions } asm FLD Double(SS:[ESP+8]) // Load in2 on FP stack, can use alias FLD Double(SS:[ESP+16]) // Load in1 on FP stack, can use alias FADD ST(0), ST(1) // ST(0) := ST(0) + ST(1); end; procedure AFPAddTwoNumbers2(var In1: Double; in2: Double); assembler; { add FP numbers in1 and in2, return result in in1 var value is a pointer to memory data. } asm FLD Double(SS:[ESP+8]) // Load in2 on FP stack, can use alias FLD Double([EAX]) // Load in1 contents on FP stack FADD ST(0), ST(1) // ST(0) := ST(0) + ST(1); FSTP Double([EAX]) // Store result back to in1 contents end; var a, b, c: Double; begin writeln('Tek-tips Delphi ASM FAQ example #6'); writeln; a := 1.1; b := 2.2; c := AFPAddTwoNumbers1(a, b); writeln('C is: ', c); AFPAddTwoNumbers2(c, b); writeln('C is: ', c); write('Press ENTER to exit.'); readln; end. Example #6 CODE {$APPTYPE CONSOLE} program asmdoc6; uses sysutils; { Delphi assembler doc example #6 - Complex example #1 } function AAverageFiveInts(in1, in2, in3, in4, in5: integer): Extended; assembler; { in1 in EAX, in2 in EDX, in3 in ECX, in4 & in5 are on stack. Extended value returned on FP stack } var Res1: integer; asm ADD EAX, SS:[ESP+12] // add in4 to in1, can use alias ADD EAX, SS:[ESP+16] // add in5 to in1, can use alias ADD EAX, ECX // add in3 to in1 ADD EAX, EDX // add in2 to in1 { you can not directly interact with the FPU with the CPU registers, so the memory store is a requirement } MOV Res1, 5 // move 5 to local memory FILD Res1 // load integer from memory MOV Res1, EAX // move EAX to memory FILD Res1 // load memory to register FDIV ST(0), ST(1) // f-point division by 5 end; var a: Extended; begin writeln('Tek-tips Delphi ASM FAQ example #6 - Complex Example #1'); writeln; a := AAverageFiveInts(1, 2, 3, 4, 7); writeln('Answer is ', a:0:20); write('Press ENTER to exit.'); readln; end. Example #7 CODE {$APPTYPE CONSOLE} program asmdoc7; uses sysutils; { Delphi assembler doc example #7 - Complex example #2 } procedure ASomeRandomStuff(in1: extended; in2: integer; var in3: extended; var in4: char; in5: word); assembler; { does some random junk stuff, shows how something like this procedure header is handled in ASM 1) adds in1, in2, in3, and in5, stores result in in3. 2) increments in4 by 2. in1 = comes from stack in2 = EAX in3 = address on EDX in4 = address on ECX in5 = on stack } var Res1: integer; asm ADD EAX, DWord(in5) // in2 := in2 + in5; FLD Extended([EDX]) // load in3 FLD in1 FADD ST(0), ST(1) MOV Res1, EAX FILD Res1 FADD ST(0), ST(1) FSTP Extended([EDX]) // work on the character part MOV AL, Byte [ECX] ADD AL, 2 MOV Byte[ECX], AL end; var a: Extended; b: char; begin writeln('Tek-tips Delphi ASM FAQ example #7 - Complex Example #2'); writeln; a := 3.14; b := 'C'; ASomeRandomStuff(0.2, 20, a, b, 12); writeln('A is: ', a:0:20); writeln('B is: ', b); write('Press ENTER to exit.'); readln; end.