The Delphi NextGen compiler does not support AnsiString. Much of Innova Solutions library and Object Database code planned for porting to mobile devices makes heavy use of the very flexible AnsiString implementation of a string consisting of simple (8bit) bytes. An AnsiString implementation was critical to any attempt to port the code.
Using Class Operators to Implement AnsiString with NextGen
For our purposes automatic conversions between the various forms of the Delphi String implementations was a problem not a “feature”. Not requiring string conversions made our “AnsiString” types much simpler.
By using records with class operators we have been able to reconstruct AnsiString, AnsiChar and PAnsiChar.
Example Definition of AnsiString Record
AnsiString = Record private FData: TISBytesArray; function GetData(a: Integer): AnsiChar; procedure SetData(a: Integer; const Value: AnsiChar); Procedure SetStrLength(aLen: Integer); //Function AnsiAsUnicode: String; //Achieved by class operator implicit Public class operator Add(a, b: AnsiString): AnsiString; class operator Add(a: AnsiString; b: Byte): AnsiString; class operator Add(a: AnsiString; b: AnsiChar): AnsiString; class operator Add(a: String; b: AnsiString): AnsiString; class operator Add(a: AnsiString; b: String): AnsiString; class operator Subtract(a, b: AnsiString): AnsiString; class operator Implicit(a: String): AnsiString; class operator Implicit(a: Char): AnsiString; class operator Implicit(a: AnsiChar): AnsiString; class operator Implicit(a: Byte): AnsiString; class operator Implicit(a: AnsiString): String; class operator Implicit(a: AnsiString): Pointer; // class operator Explicit(a: AnsiString): Pointer; // class operator Explicit(a: AnsiString): String; class operator Equal(a, b: AnsiString): Boolean; class operator NotEqual(a, b: AnsiString): Boolean; class operator GreaterThan(a, b: AnsiString): Boolean; class operator GreaterThanOrEqual(a, b: AnsiString): Boolean; class operator LessThan(a, b: AnsiString): Boolean; class operator LessThanOrEqual(a, b: AnsiString): Boolean; Procedure Delete(Index: Integer; Count: Integer); Procedure UnicodeAsAnsi(UString: String); // For std AnsiChars every second byte is a null // Bypasses Conversion Routines Function RecoverFullUnicode: String; // Reinstates String following UnicodeAsAnsi // Bypasses Conversion Routines Procedure CompressedUnicode(const AUCode: UnicodeString); //Contains Ascii version of Unicode but '' if 2 byte characters found // Returns '' if Chars above 255 // Function DeCompressUnicode: UnicodeString; Function ReadBytesFrmStrm(AStm: TStream; ABytes: Integer): Int64; Procedure ReadOneLineFrmStrm(AStm: TStream); function WriteBytesToStrm(AStm: TStream; ABytes: Integer = 0; AOffset: Integer = 0): Integer; { Offest zero ref } function WriteLineToStream(AStm: TStream): Integer; Procedure CopyBytesFromMemory(ABuffer: Pointer; ACount: Integer); Function CopyBytesToMemory(ABuffer: Pointer; ABytes: Integer = -1; AOffset: Integer = 0): Integer; { Offest zero ref } Procedure UniqueString; Function AsStringValues: String; // 123[0D](^M)5647 Function GetLength: Integer; Function IsBlank: Boolean; // #2+#5+#13+' ' is blank Function High: Integer; Function Low: Integer; Function AnsiLastChar: Pointer; // Returns a pointer to the last full character in the AnsiStringBase. Property Length: Integer Read GetLength write SetStrLength; Property ASString: String read UnCompressedToUnicode write CompressedUnicode; //Useful for seeing value in Debug Property Data[a: Integer]: AnsiChar Read GetData Write SetData; Default; end;
I originally defined the record interface and then thought about the class operator and method implementations. I then developed DUnit tests for these records before attempting to port my libraries
(which also had their own set of tests). The porting and testing identified additional requirements. New operators and methods were included so that, for my code, these records relatively transparently replaced the old AnsiStrings.
The actual implementation of each method is typically surprisingly simple and often builds one upon the other.
For Example
class operator AnsiString.Add(a: AnsiString; b: Byte): AnsiString; Var isrc, idest: Integer; begin Result.Length := a.Length + 1; idest := 0; for isrc := 0 to System.High(a.FData) - 1 do begin Result.FData[idest] := a.FData[isrc]; Inc(idest); end; Result.FData[idest] := b; Inc(idest); end; class operator AnsiString.Add(a: AnsiString; b: AnsiChar): AnsiString; begin Result := a + Byte(b); end;
It was also useful provide overload implementations for a number of string based functions to handle the AnsiString records.
Function StrScan(const Str: PAnsiChar; Chr: Byte): PAnsiChar; overload; Procedure UniqueString(Var AStg: AnsiString); overload; function ByteType(const S: AnsiString; Index: Integer): TMbcsByteType; overload; Function Pos(ASubStr, AStr: AnsiString): Integer; Overload; function StrPos(const Str1, Str2: PAnsiChar): PAnsiChar; overload; Function Copy(AStr: AnsiString; AOffset: Integer { First leeter =1 } = 1; aLen: Integer = 2000): AnsiString; overload; function StrCopy(Dest: PAnsiChar; const Source: PAnsiChar): PAnsiChar; function StrLCopy(Dest: PAnsiChar; const Source: PAnsiChar; MaxLen: Cardinal) : PAnsiChar; overload; function StrLCopy(Dest: Pointer; const Source: PAnsiChar; MaxLen: Cardinal) : PAnsiChar; overload; Procedure SetPosFirst;
By adding this code to the uses clause inside an IFDEF NextGen block
Uses SomeLib, {$IFDEF NextGen} IsAnsiStringModule, {$ENDIF} otherLib;
Ansistring should compile happily in both compilers with minimal adjustments.
Limitations
I did have to make some adjustment however.
- The Record based AnsiString can not be used as a constant.
- In Procedure Calls you can not default these record types.
procedure PopStream(ObjStrm: TMemoryStream; APopData: ansistring = ''; ADoEncryption: Boolean = True); Becomes procedure PopStream(ObjStrm: TMemoryStream; APopData: ansistring; ADoEncryption: Boolean = True);
- In some places where a buffer is needed you may need to use the functions supplied within the records.
{$IFDEF NextGen} Result.CopyBytesFromMemory(@ABuffer[AStart], ActCount); {$ELSE} SetLength(Result, ActCount); CopyMemory(@Result[1], @ABuffer[AStart], ActCount); {$ENDIF} And {$IFDEF NEXTGEN} s.WriteLineToStream(FLogFileStm); {$ELSE} FLogFileStm.Write(s[1], Length(s)); FLogFileStm.Write(CRLF, 2); {$ENDIF}
- Your are not able to use Length(A) on these ansistring records but the later versions of the old compiler supports A.Length for strings.
{$IFDEF NEXTGEN} sl := s.Length; {$ELSE} sl := Length(s); {$ENDIF}
Have fun and let me know how you go.
Download
The full source file is available from
https://github.com/rogerinnova/Delphi-NextGen-Compiler-Utilities/tree/master/ANSI%20String%20Code