28

I'm converting my applications to Delphi 2009 and faced an intriguing issue with some calls that need to convert a string (wide) to AnsiString.

Here's an example to demonstrate the issue I'm having:

var
  s: PAnsiChar;

...

s := PAnsiChar(Application.ExeName);

With Delphi 2007 and previous versions, s := PChar(Application.ExeName) would return the application exe path.

with Delphi 2009, s := PAnsiChar(Application.ExeName) returns only 'E'.

My guess is that's because I'm converting a unicode string to an ansi string but how can I convert it so that a PAnsiChar gets the full string?

Toon Krijthe
  • 52,876
  • 38
  • 145
  • 202
smartins
  • 3,808
  • 7
  • 42
  • 54

6 Answers6

42

I have no Delphi 2009 here, so I can't check it. But maybe you have to try:

s := PAnsiChar(AnsiString(Application.ExeName));

As gabr already pointed, this is not a very good practice, and you will only use it if you are 100% sure. The string only contains characters that have a direct mapping to the ANSI range.

That's why you should get a warning because you are converting Unicode to ANSI.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Toon Krijthe
  • 52,876
  • 38
  • 145
  • 202
  • You shouldn't because it is an explicit conversion. And, yes, it should work. – gabr Nov 12 '08 at 12:40
  • 1
    I know, but the conversion to PAnsiChar is also a bit questionable. – Toon Krijthe Nov 12 '08 at 12:49
  • 1
    It does work at the cost of the explicit conversion. Is there any other alternative? The conversion to PAnsiChar is explained on my reply below. – smartins Nov 12 '08 at 13:04
  • The problem is that you always have the chance of information loss. It is possibly better to change the PAnsiChar for a string. – Toon Krijthe Nov 12 '08 at 13:11
  • In XE5 this problem caused me to have failed calls to dll including shellexecute. simply casting PAnsiChar(myStringVar) caused non detectable and non-reproducible errors and I was frustrated until the same thing happened with an external dll and I came to this solution. works fine now. Anybody with shell-execute problem should also check this out. – user30478 Jan 07 '19 at 13:33
5

Instead of using type String, use RawByteString:

s: RawByteString;

s := LoadSomeRegularString(usually a string type);

PAnsiChar(s) <<< all fine.
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Meka
  • 51
  • 1
  • 1
  • 1
    No, don't do that. That is a total abuse of `RawByteString`. Instead of doing this, read the documentation for `RawByteString` and work out what it is really intended for. – David Heffernan Feb 12 '15 at 08:26
1

Gamecat explicit conversion works. I'm explaining the problem in more detail below so that perhaps someone can point to a better solution.

I'm using the following function to retrieve the application compilation date:

function LinkerTimeStamp(const FileName: string): TDateTime;
var
  LI: TLoadedImage;
begin
  {$IFDEF UNICODE}
  Win32Check(MapAndLoad(PAnsiChar(AnsiString(FileName)), nil, @LI, False, True));
  {$ELSE}
  Win32Check(MapAndLoad(PChar(FileName), nil, @LI, False, True));
  {$ENDIF}
  Result := LI.FileHeader.FileHeader.TimeDateStamp / SecsPerDay + UnixDateDelta;
  UnMapAndLoad(@LI);
end;

MapAndLoad requires a PAnsiChar for the ImageName Parameter so I need to convert the unicode string. Is there any other alternative as to explicitly convert to AnsiString first?

smartins
  • 3,808
  • 7
  • 42
  • 54
  • No, I don't think there is a Unicode version. CodeGear Imagehlp unit declares MapAndLoad as a LPSTR which maps to PAnsiChar. And no mention on msdn about an Unicode version. – smartins Nov 12 '08 at 13:29
  • 2
    You should probably add a comment what you changed for Unicode compatibility, and remove the IFDEF altogether - PAnsiChar and AnsiString are available at least in Delphi 4 already, and the typecasts do not hurt in Ansi programs. The simpler the code the better IMHO. – mghie Nov 13 '08 at 16:14
  • This IFDEF is quite pointless. Simply write `PAnsiChar(AnsiString(FileName))` which works everywhere. Surely though a better solution is to find an alternative to `MapAndLoad` that isn't so lame as to require ANSI input. – David Heffernan Feb 12 '15 at 08:29
  • The better solution, since you are doing this on the executing module, is as follows: `Result := PImageNtHeaders(HInstance + PImageDosHeader(HInstance)^._lfanew)^.FileHeader.TimeDateStamp / SecsPerDay + UnixDateDelta;` – David Heffernan Feb 12 '15 at 08:31
0

WideCharToMultiByte could help you.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jamie
  • 3,150
  • 2
  • 26
  • 35
0

I had the exact same problem. The PAnsiChar only points to the first character. I wrote the following function to handle the old functionality.

// This function converts a string to a PAnsiChar
// If the output is not the same, an exception is raised
// Author: nogabel@hotmail.com

function StringToPAnsiChar(stringVar : string) : PAnsiChar;
Var
  AnsString : AnsiString;
  InternalError : Boolean;
begin
  InternalError := false;
  Result := '';
  try
    if stringVar <> '' Then
    begin
       AnsString := AnsiString(StringVar);
       Result := PAnsiChar(PAnsiString(AnsString));
    end;
  Except
    InternalError := true;
  end;
  if InternalError or (String(Result) <> stringVar) then
  begin
    Raise Exception.Create('Conversion from string to PAnsiChar failed!');
  end;
end;
Kromster
  • 7,181
  • 7
  • 63
  • 111
  • 1
    `PAnsiChar(AnsiString(stringVar))` is all you need. – David Heffernan Feb 12 '15 at 08:27
  • 5
    Won't that have a problem with AnsString being a local variable and therefore Result pointing to that variable which will no longer be available after the function exits? In other words, an Access Violation in the waiting? – dummzeuch Apr 28 '17 at 09:26
-2

I think You are a bit off. Every Win32 API function has a unicode counterpart, if it is expecting a string. Try MapAndLoadW instead of MapAndLoad...

  • 2
    There's no MapAndLoadW. It was the first thing I looked at. Not all Win32 APIs have unicode counterparts, most do but some like this one do not. – smartins Feb 10 '10 at 12:40