Does anyone know any API or undocumented function to calculate null-terminated string length for cross-platform in delphi ?

Does anyone know any API or undocumented function to calculate null-terminated string length for cross-platform in delphi ?

Here is what I implemented:

function CString2TBytes(ptr :{$IFDEF NEXTGEN} MarshaledAString {$ELSE} PAnsiChar {$ENDIF}) :TBytes;
var
  by :Byte;
  len :Integer;
begin
  len := 0;
  by := Byte(ptr^);
  while by<>0 do
  begin
    Inc(len);
    by := Byte((ptr+len)^);
  end;
  if len>0 then
  begin
    SetLength(Result,len);
    len := 0;
    by := Byte(ptr^);
    while by<>0 do
    begin
      Result[len] := by;
      Inc(len);
      by := Byte((ptr+len)^);
    end;
  end;

end;

The purpose is to solve the question in stackoverflow which Remy said that just typecast pointer to tbyts is buggy ( yes, I admit it).

BTW, I'm not good at Delphi, so if anything just done wrong, please kindly tell me. Thanks.

http://stackoverflow.com/questions/27857479/whats-the-correct-way-to-assign-pansichar-to-a-unicode-string/27873918#27873918
http://stackoverflow.com/questions/27857479/whats-the-correct-way-to-assign-pansichar-to-a-unicode-string/27873918#27873918

Comments

  1. A few observations:

    1. You aren't copying the null terminator to the TBytes instance. Is this intentional? Maybe it is, but it means you can't now safely cast back from the TBytes to a PAnsiChar/'MarshaledAString'.

    2. You don't really need the IFDEF in the function signature - perhaps this is just a matter of style, but I'd prefer using MarshaledAString always, short of using Andreas Hausladen's compiler patch to get PAnsiChar up and running in the 'mobile' compilers.

    3. Your solution is rather verbose - the following should be enough, though like your code however, it doesn't copy across the null terminator:

    function CStrToBytes(const Chars: MarshaledAString): TBytes;
    var
      SeekPtr: PByte;
    begin
      SeekPtr := PByte(Chars);
      while SeekPtr^ <> 0 do Inc(SeekPtr);
      SetLength(Result, SeekPtr - Chars);
      if Result <> nil then Move(Chars^, Result[0], Length(Result));
    end;

    ReplyDelete
  2. Sam Shaw In your solution you have a bug. You don't always set the result variable! Unfortunately the compiler doesn't complain about this when the result is a managed type. However the result variable may contain the contents of the previous call, and since you don't always set it, that's what you return in some cases.

    So, do "result := nil;" at the start, or do what Chris Rolliston does and always call SetLength.

    Not copying the zero terminator is correct if the result is to be used with TEncoding, which I presume it is given the context.

    ReplyDelete
  3. Chris Rolliston Thanks for comment. About your observations:
    1. Yes, I intent to not copy null terminator and let the Lenght reflect the "meaningful" content.

    2. I used that because I considered let function works on none-mobile support versions which are D2009+ to XE2?

    3.Your code is neat and this is what asking for  ;-)  If the code is c++, I will use the way you wrritten.  I'm just a little afraid of using delphi arrry of x.

    ReplyDelete
  4. Asbjørn Heid haha~ I did consider that might be a issue when I wrote that function.  However, I used a little unit test for giving it an empty string. It works. 

    I don't want it be nil if string is an empty string because I don't want to check if it is nil when function returned.  I considered to set lenght to 0 from begining, but I was wonder it's nesseary to do that?

    Because it is a managed type for delphi, I assume that it will always create an "empty" instance of that type, which lenght is 0.   Is this incorrect ?

    Thank you.

    ReplyDelete
  5. Sam Shaw Yes that is incorrect. I also assumed what you did but that's not what happens.

    Functions returning managed types essentially behave like procedures where the result variable is passed as a "var" parameter. This means that it's possible for the result variable to contain something at the start of the function. For example check this out:

    function Foo(const x: integer): string;
    begin
      if x <> 0 then
        result := IntToStr(x);
    end;
    procedure Test;
    var
      s: string;
    begin
      s := Foo(42);
      WriteLn(s);
      s := Foo(0);
      WriteLn(s);
    end;
    begin
      Test;
      ReadLn;
    end.

    It will print 42 twice. If you instead call WriteLn(Foo(...)) directly it will just print 42 once and then an empty line. This is because the variable that's passed is a separate temporary for each call, and the temporaries are initialized of course.

    ReplyDelete
  6. Asbjørn Heid  Thanks so much for explaination!  Learned.  :)

    ReplyDelete
  7. Sam Shaw If you want to support earlier versions, then an alternative to the IFDEF in the function signature is a conditionally-defined type alias. The following will compile in Delphi 6 (released in 2001) or above:

    {$IFDEF NEXTGEN}
    {$LEGACYIFEND ON} //mobile compilers want ENDIF
    {$ENDIF}

    {$IF NOT DECLARED(MarshaledAString)}
    type
      MarshaledAString = PAnsiChar;
    {$IFEND}

    This way would definitely be preferable if you have several routines that take a MarshaledAString/PAnsiChar,.

    ReplyDelete

Post a Comment