AnsiString on an Android Device

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.

  1. The Record based AnsiString can not be used as a constant.
  2. 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);
    
  3. 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}
    
  4. 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

Leave a Reply

Your email address will not be published. Required fields are marked *