Reputation: 310
I'm writing some functions in Delphi using Assembly. So I want to put it in a .pas file called Strings.pas
. To use in uses
of a new Delphi software. What do I need to write, to make it a valid library?
My function is like this:
function Strlen(texto : string) : integer;
begin
asm
mov esi, texto
xor ecx,ecx
cld
@here:
inc ecx
lodsb
cmp al,0
jne @here
dec ecx
mov Result,ecx
end;
end;
That counts the numbers of chars in the string. How can I make it in a lib Strings.pas
to call with uses Strings;
in my form?
Upvotes: 2
Views: 1807
Reputation: 365
The my two solutions to get the length of two types of string, as for says Peter Cordes are not both useful. Only the "PAnsiCharLen()" could be an alternative solution, but not as fast as it is StrLen() (optimized) of Amaud Bouchez, that it is about 3 times faster than mine.
10/14/2017 (mm/dd/yyy): Added one new function (Clean_Str).
However, for now, I propose three small corrections to both of them (two suggested by Peter Cordes: 1) use MovZX instead of Mov && And; 2) Use SetZ/SetE instead LAHF/ShL, use XOr EAX,EAX instead XOr AL,AL); in the future I could define the functions in assembly (now they are defined in Pascal):
unit MyStr;
{ Some strings' function }
interface
Function PAnsiCharLen(S:PAnsiChar):Integer;
{ Get the length of the PAnsiChar^ string. }
Function ShortStrLen(S:ShortString):Integer;
{ Get the length of the ShortString^ string. }
Procedure Clean_Str(Str:ShortString;Max_Len:Integer);
{ This function can be used to clear the unused space of a short string
without modifying his useful content (for example, if you save a
short-string field in a file, at parity of content the file may be
different, because the unused space is not initialized).
Clears a String Str_Ptr ^: String [], which has
Max_Len = SizeOf (String []) - 1 characters, placing # 0
all characters beyond the position of Str_Ptr ^ [Str_Ptr ^ [0]] }
implementation
Function PAnsiCharLen(S:PAnsiChar):Integer;
{ EAX EDX ECX are 1°, 2° AND 3° PARAMETERs.
Can freely modify the EAX, ECX, AND EDX REGISTERs. }
Asm
ClD {Clear string direction flag}
Push EDI {Save EDI's reg. into the STACK}
Mov EDI,S {Load S into EDI's reg.}
XOr EAX,EAX {Set AL's reg. with null terminator}
Mov ECX,-1 {Set ECX's reg. with maximum length of the string}
RepNE ScaSB {Search null and decrease ECX's reg.}
SetE AL {AL is set with FZero}
Add EAX,ECX {EAX= maximum_length_of_the_string - real_length_of_the_string}
Not EAX {EAX= real_length_of_the_string}
Pop EDI {Restore EDI's reg. from the STACK}
End;
Function ShortStrLen(S:ShortString):Integer; Assembler;
{ EAX EDX ECX are 1°, 2° AND 3° PARAMETERs.
Can freely modify the EAX, ECX, AND EDX REGISTERs. }
Asm
MovZX EAX,Byte Ptr [EAX] {Load the length of S^ into EAX's reg. (function's result)}
End;
Procedure Clean_Str(Str:ShortString;Max_Len:Integer); Assembler;
(* EAX EDX ECX are 1°, 2° AND 3° PARAMETERs.
Can freely modify the EAX, ECX, AND EDX REGISTERs. *)
Asm
ClD {Clear string direction flag}
Push EDI {Save EDI's reg. into the STACK}
Mov EDI,Str {Load input string pointer into EDI's reg.}
Mov ECX,Max_Len {Load allocated string length into ECX's reg.}
MovZX EDX,Byte Ptr [EDI] {Load real string length into EDX's reg.}
StC {Process the address of unused space of Str; ...}
AdC EDI,EDX {... skip first byte and useful Str space}
Cmp EDX,ECX {If EDX>ECX ...}
CMovGE EDX,ECX {... set EDX with ECX}
Sub ECX,EDX {ECX contains the size of unused space of Str}
XOr EAX,EAX {Clear accumulator}
Rep StoSB {Fill with 0 the unused space of Str}
Pop EDI {Restore EDI's reg. from the STACK}
End;
end.
Old (incomplete) answer:
"Some new string's functions, not presents in Delphi library, could be these:"
Type Whole=Set Of Char;
Procedure AsmKeepField (PStrIn,PStrOut:Pointer;FieldPos:Byte;
All:Boolean);
{ Given "field" as a sequence of characters that does not contain spaces
or tabs (# 32, # 9), it takes FieldPos (1..N) field
to PStrIn ^ (STRING) and copies it to PStrOut ^ (STRING).
If All = TRUE, it also takes all subsequent fields }
Function AsmUpCComp (PStr1,PStr2:Pointer):Boolean;
{ Compare a string PStr1 ^ (STRING) with a string PStr2 ^ (STRING),
considering the PStr1 alphabetic characters ^ always SHIFT }
Function UpCaseStrComp (Str1,Str2:String;Mode:Boolean):ShortInt;
{ Returns: -1 if Str1 < Str2.
0 is Str1 = Str2.
1 is Str1 > Str2.
MODE = FALSE means "case sensitive comparison" (the letters are
consider them as they are).
MODE = TRUE means that the comparison is done by considering
both strings as if they were all uppercase }
Function KeepLS (Str:String;CntX:Byte):String;
{ RETURN THE PART OF STR THAT INCLUDES THE FIRST CHARACTER
OF STR AND ALL THE FOLLOW UP TO THE POSITION CntX (0 to N-1) INCLUDED }
Function KeepRS (Str:String;CntX,CsMode:Byte):String;
{ RETURN THE PART OF STR STARTING TO POSITION CntX + 1 (0 to N-1)
UP TO END OF STR.
IF CsMode = 0 (INSERT MODE), IF CsMode = 1 (OVERWRITE-MODE):
IN THIS CASE, THE CHARACTER TO CntX + 1 POSITION IS NOT INCLUDED }
Function GetSubStr (Str:String;
Pos,Qnt:Byte;CH:Char):String;
{ RETURN Qnt STR CHARACTERS FROM POSITION Pos (1 to N) OF STR;
IF EFFECTIVE LENGTH IS LESS THAN Qnt, WILL ADDED CHARACTER = CH }
Function Keep_Right_Path_Str_W(PathName:String;FieldWidth:Byte;
FormatWithSpaces:Boolean):String;
{ RESIZE A STRING OF A FILE PATH, FROM PathName;
THE NEW STRING WILL HAVE A MAXIMUM LENGTH OF FieldWidth CHARACTERS.
REPLACE EXCEDENT CHARACTERS WITH 3 POINTS,
INSERTED AFTER DRIVE AND ROOT.
REPLACE SOME DIRECTORY WITH 3 POINTS,
ONLY WHEN IT IS NECESSARY, POSSIBLE FROM SECOND.
FORMAT RETURN WITH SPACE ONLY IF FormatWithSpaces = TRUE }
Function KeepBarStr (Percentage,Qnt:Byte;
Ch1,Ch2,Ch3:Char):String;
{ THIS IS A FUNCTION WICH MAKES A STRING WICH CONTAINS A REPRESENTATION OF STATE
OF ADVANCEMENT OF A PROCESS; IT RETURNS A CHARACTERS' SEQUENCE, CONSTITUTED BY "<Ch1>"
(LENGTH = Percentage / 100 * Qnt), WITH AN APPROXIMATION OF THE LAST CHARACTER TO
"<Ch2>" (IF "Percentage / 100 * Qnt" HAS HIS FRACTIONAL'S PART GREATER THAN 0.5),
FOLLOWED BY AN OTHER CHARACTERS' SEQUENCE, CONSTITUTED BY "<Ch3>" (LENGTH = (100 -
Percentage) / 100 * Qnt). }
Function Str2ChWhole (Str:String;Var StrIndex:Byte;
Var ChSet:Whole;
Mode:Boolean):Boolean;
{ CONVERT A PART OF Str, POINTED BY StrIndex, IN A ChSet CHARACTER SET;
IF Mode = TRUE, "StrIn" SHOULD CONTAIN ASCII CODES
OF CORRESPONDING CHARACTERS EXPRESSED IN DECIMAL SIZE;
OTHERWISE IT SHOULD CONTAIN CORRESPONDING CHARACTER SYMBOLS }
Function ChWhole2Str (ChSet:Whole;Mode:Boolean):String;
{ CONVERT A SET OF CHARACTERS IN A CORRESPONDING STRING;
IF Mode = TRUE ELEMENTS OF ChSet WILL BE CONVERTED IN ASCII CODES
EXPRESSED IN DECIMAL SIZE; OTHERWISE THE CORRESPONDING SYMBOLS
WILL BE RETURNED }
Function ConverteFSize (FSize:LongInt;
Var SizeStr:TSizeStr):Integer;
{ MAKES THE CONVERSION OF THE DIMENSION OF A FILE IN A TEXT,
LARGE TO MAXIMUM 5 CHARACTERS, AND RETURN THE COLOR OF THIS STRING }
Function UpCasePos (SubStr,Str:String):Byte;
{ Like the Pos () system function, but not "case sensitive" }
Upvotes: 2
Reputation: 43023
The correct asm version may be:
unit MyStrings; // do not overlap Strings.pas unit
interface
function StringLen(const texto : string) : integer;
implementation
function StringLen(const texto : string) : integer;
asm
test eax,eax
jz @done
mov eax,dword ptr [eax-4]
@done:
end;
end.
Note that:
MyStrings
as unit name, since it is a very bad idea to overlap the official RTL unit names, like Strings.pas
;(const texto: string)
instead of (texto: string)
, to avoid a reference count change at calling;string
type already has its length stored as integer
just before the character memory buffer;eax edx ecx
registers, and the integer result of a function is the eax
register - see this reference article - for Win32 only;texto
to be nil
(eax=0
), which stands for a void ''
string;length()
function would be faster than an asm sub-function, since it is inlined in new versions of Delphi;StrLen()
function, which expects a PChar
as input parameter - so I renamed your function as StringLen()
.Since you want to learn asm, here are some reference implementation of this function.
A fast PChar
oriented version may be :
function StrLen(S: PAnsiChar): integer;
asm
test eax,eax
mov edx,eax
jz @0
xor eax,eax
@s: cmp byte ptr [eax+edx+0],0; je @0
cmp byte ptr [eax+edx+1],0; je @1
cmp byte ptr [eax+edx+2],0; je @2
cmp byte ptr [eax+edx+3],0; je @3
add eax,4
jmp @s
@1: inc eax
@0: ret
@2: add eax,2; ret
@3: add eax,3
end;
A more optimized version:
function StrLen(S: PAnsiChar): integer;
// pure x86 function (if SSE2 not available) - faster than SysUtils' version
asm
test eax,eax
jz @@z
cmp byte ptr [eax+0],0; je @@0
cmp byte ptr [eax+1],0; je @@1
cmp byte ptr [eax+2],0; je @@2
cmp byte ptr [eax+3],0; je @@3
push eax
and eax,-4 { DWORD Align Reads }
@@Loop:
add eax,4
mov edx,[eax] { 4 Chars per Loop }
lea ecx,[edx-$01010101]
not edx
and edx,ecx
and edx,$80808080 { Set Byte to $80 at each #0 Position }
jz @@Loop { Loop until any #0 Found }
@@SetResult:
pop ecx
bsf edx,edx { Find First #0 Position }
shr edx,3 { Byte Offset of First #0 }
add eax,edx { Address of First #0 }
sub eax,ecx { Returns Length }
@@z: ret
@@0: xor eax,eax; ret
@@1: mov eax,1; ret
@@2: mov eax,2; ret
@@3: mov eax,3
end;
An SSE2 optimized version:
function StrLen(S: PAnsiChar): integer;
asm // from GPL strlen32.asm by Agner Fog - www.agner.org/optimize
or eax,eax
mov ecx,eax // copy pointer
jz @null // returns 0 if S=nil
push eax // save start address
pxor xmm0,xmm0 // set to zero
and ecx,0FH // lower 4 bits indicate misalignment
and eax,-10H // align pointer by 16
movdqa xmm1,[eax] // read from nearest preceding boundary
pcmpeqb xmm1,xmm0 // compare 16 bytes with zero
pmovmskb edx,xmm1 // get one bit for each byte result
shr edx,cl // shift out false bits
shl edx,cl // shift back again
bsf edx,edx // find first 1-bit
jnz @A200 // found
// Main loop, search 16 bytes at a time
@A100: add eax,10H // increment pointer by 16
movdqa xmm1,[eax] // read 16 bytes aligned
pcmpeqb xmm1,xmm0 // compare 16 bytes with zero
pmovmskb edx,xmm1 // get one bit for each byte result
bsf edx,edx // find first 1-bit
// (moving the bsf out of the loop and using test here would be faster
// for long strings on old processors, but we are assuming that most
// strings are short, and newer processors have higher priority)
jz @A100 // loop if not found
@A200: // Zero-byte found. Compute string length
pop ecx // restore start address
sub eax,ecx // subtract start address
add eax,edx // add byte index
@null:
end;
Or even a SSE4.2 optimized version:
function StrLen(S: PAnsiChar): integer;
asm // warning: may read up to 15 bytes beyond the string itself
or eax,eax
mov edx,eax // copy pointer
jz @null // returns 0 if S=nil
xor eax,eax
pxor xmm0,xmm0
{$ifdef HASAESNI}
pcmpistri xmm0,dqword [edx],EQUAL_EACH // comparison result in ecx
{$else}
db $66,$0F,$3A,$63,$02,EQUAL_EACH
{$endif}
jnz @loop
mov eax,ecx
@null: ret
@loop: add eax,16
{$ifdef HASAESNI}
pcmpistri xmm0,dqword [edx+eax],EQUAL_EACH // comparison result in ecx
{$else}
db $66,$0F,$3A,$63,$04,$10,EQUAL_EACH
{$endif}
jnz @loop
@ok: add eax,ecx
end;
You will find all those functions, including Win64 versions, in our very optimized SynCommons.pas unit, which is shared by almost all our Open Source projects.
Upvotes: 2
Reputation: 595897
A .pas
file is a unit, not a library. A .pas
file needs to have unit
, interface
, and implementation
statements, eg:
Strings.pas:
unit Strings;
interface
function Strlen(texto : string) : integer;
implementation
function Strlen(texto : string) : integer;
asm
// your assembly code...
// See Note below...
end;
end.
Then you can add the .pas
file to your other projects and use
the Strings
unit as needed. It will be compiled directly into each executable. You don't need to make a separate library out of it. But if you want to, you can. Create a separate Library (DLL) or Package (BPL) project, add your .pas file to it, and compile it into an executable file that you can then reference in your other projects.
In the case of a DLL library, you will not be able to use
the Strings
unit directly. You will have to export
your function(s) from the library (and string
is not a safe data type to pass over a DLL boundary between modules), eg:
Mylib.dpr:
library Mylib;
uses
Strings;
exports
Strings.Strlen;
begin
end.
And then you can have your other projects declare the function(s) using external
clause(s) that reference the DLL file, eg:
function Strlen(texto : PChar) : integer; external 'Mylib.dll';
In this case, you can make a wrapper .pas
file that declares the functions to import, add that unit to your other projects and use
it as needed, eg:
StringsLib.pas:
unit StringsLib;
interface
function Strlen(texto : PChar) : integer;
implementation
function Strlen; external 'Mylib.dll';
end.
In the case of a Package, you can use
the Strings
units directly. Simply add a reference to the package's .bpi
in your other project's Requires list in the Project Manager, and then use
the unit as needed. In this case, string
is safe to pass around.
Note: in the assembly code you showed, for the function to not cause an access violation, you need to save and restore the ESI
register. See the section on Register saving conventions in the Delphi documentation.
Upvotes: 14