April 29, 2013

Delphi XE4 and AnsiString

I already ported a lot of my applications to Delphi XE4. This represent hundreds of thousands lines of code. All in all this was very easy, coming from XE3.

There is only one changed feature that forced me to slightly update my source code: in XE4, all AnsiString routines have been moved to a new unit named System.AnsiStrings. I had to add this unit where ever I used on of those routines. If you don't, XE4 gives a warning about deprecated function:

Given the code:

procedure TForm1.Button1Click(Sender: TObject);
var
    S : AnsiString;
    L : Integer;
begin
    S := 'Hello world!';
    L := StrLen(PAnsiChar(S));
end;

You get the warning:
    [dcc32 Warning] Unit1.pas(32): W1000 Symbol 'StrLen' is deprecated: 
 'Moved to the AnsiStrings unit'

Sadly, if you add System.AnsiStrings to the uses clause and recompile the code, you get the warning:
[dcc32 Error] Unit1.pas(33): E2251 Ambiguous overloaded call to 'StrLen'
  System.SysUtils.pas(10369): Related method: 
       function StrLen(const PAnsiChar): Cardinal;
  System.AnsiStrings.pas(3166): Related method: 
       function StrLen(const PAnsiChar): Cardinal;

I don’t clearly understand why the compiler emit this warning because as you see, the two overloaded versions of StrLen are the same!

To avoid this issue, you have to fully qualify StrLen like this:
procedure TForm1.Button1Click(Sender: TObject);
var
    S : AnsiString;
    L : Integer;
begin
    S := 'Hello world!';
    L := System.AnsiStrings.StrLen(PAnsiChar(S));
end;
Unfortunately, this makes the code not compilable anymore with previous Delphi versions. XE3 has an AnsiStrings unit but without StrLen.

I have to produce code working for several Delphi versions. So I designed a little workaround so that change in my existing source code is mimimal. I wrote a new _StrLen function which has the required conditional compilation to make it works with all Delphi versions. And everywhere I call StrLen, I replaced it by _StrLen. This is easy to find since the compiler complain at each instance.
function _StrLen(const S : PAnsiChar): Cardinal;
begin
{$IFDEF VER250}
    Result := System.AnsiStrings.StrLen(S);
{$ELSE}
    Result := StrLen(S);
{$ENDIF}
end;
In the uses clause, I have this:
uses
  Windows, Messages,
{$IFDEF VER250}
  System.AnsiStrings,
{$ENDIF}
  SysUtils, Variants, Classes, Graphics,  Controls, Forms, Dialogs, StdCtrls;
Please note that in order to say compatible with very old Delphi versions such as Delphi 7, I don’t use prefixes and instead I use the “unit scope name” in the project options so that the compiler properly resolve “SysUtils” to “System.SysUtils” and the likes for compilers which supports unit scope naming. Anyway, almost all my source code was already written that way because most of it exists long before Delphi supported that feature.

What I explained here about StrLen occurs with many other ANSI strings routines. Same solution applies to all.

This article is available from:
  http://francois-piette.blogspot.be/2013/04/delphi-xe4-and-ansistring.html

Follow me on Twitter
Follow me on LinkedIn
Follow me on Google+
Visit my website: http://www.overbyte.be

2 comments:

dummzeuch said...

I experienced the same problem in GExperts and solved it by by adding my own inlined function StrLen at the top of the unit like this:

{$ifdef DelphiXE4}
function StrLen(a: PChar): integer; inline;
begin
Result := System.AnsiStrings.StrLen(a);
end;
{$endif}

This saves all those untidy ifdefs scattered around the code.

Cesar Vega said...

Thanks for such an elegant solution!