How to convert from PAnsichar to PWidechar? - delphi

I am implementing Ping function using windows API in delphi-xe3 from here
(http://delphi.about.com/od/internetintranet/l/aa081503a.htm).
I am having problem with the following function.It displays error incompatible type Pansichar and Pwidechar.I replaced Pchar with PAnsichar now it displays exception
'Error getting IP from HostName'.
I am testing it with localhost.
Please guide whats the proper conversion.
const ADP_IP = '127.0.0.1';
procedure TranslateStringToTInAddr(AIP: string; var AInAddr);
var
phe: PHostEnt;
pac: PChar;
GInitData: TWSAData;
begin
WSAStartup($101, GInitData);
try
phe := GetHostByName(PChar(AIP));
if Assigned(phe) then
begin
pac := phe^.h_addr_list^;
if Assigned(pac) then
begin
with TIPAddr(AInAddr).S_un_b do begin
s_b1 := Byte(pac[0]);
s_b2 := Byte(pac[1]);
s_b3 := Byte(pac[2]);
s_b4 := Byte(pac[3]);
end;
end
else
begin
raise Exception.Create('Error getting IP from HostName');
end;
end
else
begin
raise Exception.Create('Error getting HostName');
end;
except
FillChar(AInAddr, SizeOf(AInAddr), #0);
end;
WSACleanup;
end;

You don't want to convert from PAnsiChar to PWideChar. On your Unicode Delphi your PChar maps to PWideChar. But gethostbyname receives PAnsiChar. You need to convert from Unicode to ANSI.
Code it like this:
phe := gethostbyname(PAnsiChar(AnsiString(AIP)));
In other words, convert your string to AnsiString, and then cast as PAnsiChar. Personally I'd declare the AIP parameter to be AnsiString.
procedure TranslateStringToTInAddr(const AIP: AnsiString; var AInAddr);
And then write the call to gethostbyname like so:
phe := gethostbyname(PAnsiChar(AIP));
That untyped var parameter looks dubious to me. I see no compelling reason for its use. What's wrong with declaring it to be of type TIPAddr? Your FillChar is somewhat dubious. How can you use SizeOf on an untyped parameter?

Related

Other Ways to Check if String is a Regular Expression

I have this function to check if a string is a regular expression and it works fine :
function IsValidRegEx(aString: string): Boolean;
var
aReg : TRegEx;
begin
Result := False;
if Trim(aString) = '' then
begin
Exit;
end;
try
aReg := TRegEx.Create(aString);
if aReg.IsMatch('asdf') then
begin
end;
Result := True;
except
end;
end;
the problem is it always raise a debugger exception notification if string value is false. I want to eliminate that notification. There is an option to ignore that exception in the notification itself but I don't want it. As much as possible it would be the codes that will adjust.
If you want to use this approach, then you can't avoid exceptions being raised by the Delphi regex library. You'd need to dig down to the PCRE library that Delphi uses to implement its regex library. For instance:
{$APPTYPE CONSOLE}
uses
System.RegularExpressionsAPI;
function IsValidRegEx(const Value: UTF8String): Boolean;
var
CharTable: Pointer;
Options: Integer;
Pattern: Pointer;
Error: PAnsiChar;
ErrorOffset: Integer;
begin
CharTable := pcre_maketables;
Options := PCRE_UTF8 or PCRE_NEWLINE_ANY;
Pattern := pcre_compile(PAnsiChar(Value), Options, #Error, #ErrorOffset, CharTable);
Result := Assigned(Pattern);
pcre_dispose(Pattern, nil, CharTable);
end;
begin
Writeln(IsValidRegEx('*'));
Writeln(IsValidRegEx('.*'));
Readln;
end.
Note that I have written this with Delphi XE7, as I don't have access to XE2. If this code doesn't compile, then it should not be too hard to study the source code for the Delphi regex library to work out how to achieve the same in XE2.

Delphi XE8: HTTPEncode convert string error

I use the HTTPEncode() function in Delphi XE8 to encode Japanese text. Some characters can encode correctly, but some cannot. Below is an example:
aStr := HTTPEncode('萩原小学校');
I expected this:
aStr = '%E8%90%A9%E5%8E%9F%E5%B0%8F%E5%AD%A6%E6%A0%A1'
But I got this:
aStr = '%E8%90%A9%E5%8E%9F%E5%B0%8F%3F%E6%A0%A1'
Can someone help me to encode '萩原小学校' as '%E8%90%A9%E5%8E%9F%E5%B0%8F%E5%AD%A6%E6%A0%A1'?
I'm not sure what this HTTPEncode function is. There is a function in Web.HTTPApp of that name. Perhaps that is what you refer to. If so, it is clearly marked as deprecated. Assuming you have enabled compiler warnings, the compiler will be telling you this, and telling you also to use TNetEncoding.UTL.Encode instead.
Let's try that:
{$APPTYPE CONSOLE}
uses
System.NetEncoding;
begin
Writeln(TNetEncoding.URL.Encode('萩原小学校'));
end.
Output
%E8%90%A9%E5%8E%9F%E5%B0%8F%E5%AD%A6%E6%A0%A1
# David Heffernan, # Remy Lebeau, thank you so much for your time to help me. your answer make me understand why i cannot convert my string with HTTPEncode.
I have tried many times myself till i found this: Delphi: Convert from windows-1251 to Shift-JIS
function MyEncode(const S: string; const CodePage: Integer): string;
var
Encoding: TEncoding;
Bytes: TBytes;
b: Byte;
sb: TStringBuilder;
begin
Encoding := TEncoding.GetEncoding(CodePage);
try
Bytes := Encoding.GetBytes(S);
finally
Encoding.Free;
end;
sb := TStringBuilder.Create;
try
for b in Bytes do begin
sb.Append('%');
sb.Append(IntToHex(b, 2));
end;
Result := sb.ToString;
finally
sb.Free;
end;
end;
MyEncode('萩原小学校', 65001);
Output = %E8%90%A9%E5%8E%9F%E5%B0%8F%E5%AD%A6%E6%A0%A1

StrAlloc not working after migrating to Delphi XE7

I am working on an application which was recently upgraded from Delphi 2007 to XE7. There is one particular scenario where the conversion of TMemoryStream to PChar is failing. Here is the code:
procedure TCReport.CopyToClipboard;
var
CTextStream: TMemoryStream;
PValue: PChar;
begin
CTextStream := TMemoryStream.Create;
//Assume that this code is saving a report column to CTextStream
//Verified that the value in CTextStream is correct
Self.SaveToTextStream(CTextStream);
//The value stored in PValue below is corrupt
PValue := StrAlloc(CTextStream.Size + 1);
CTextStream.Read(PValue^, CTextStream.Size + 1);
PValue[CTextStream.Size] := #0;
{ Copy text stream to clipboard }
Clipboard.Clear;
Clipboard.SetTextBuf(PValue);
CTextStream.Free;
StrDispose(PValue);
end;
Adding the code for SaveToTextStream:
procedure TCReport.SaveToTextStream(CTextStream: TStream);
var
CBinaryMemoryStream: TMemoryStream;
CWriter: TWriter;
begin
CBinaryMemoryStream := TMemoryStream.Create;
CWriter := TWriter.Create(CBinaryMemoryStream, 24);
try
CWriter.Ancestor := nil;
CWriter.WriteRootComponent(Self);
CWriter.Free;
CBinaryMemoryStream.Position := 0;
{ Convert Binary 'WriteComponent' stream to text}
ObjectBinaryToText(CBinaryMemoryStream, CTextStream);
CTextStream.Position := 0;
finally
CBinaryMemoryStream.Free;
end;
end;
I observed that the StrLen(PChar) is also coming out to be half the size of TMemoryStream. But in Delphi 2007 it was coming out to be same as the size of TMemoryStream.
I know that the above code is assuming the size of a char to be 1 byte, and that could be a problem. But I tried multiple approaches, and nothing works.
Could you suggest a better way to go about this conversion?
Yet again, this is the issue of Delphi 2009 and later using Unicode text. In Delphi 2007 and earlier:
Char is an alias to AnsiChar.
PChar is an alias to PAnsiChar.
string is an alias to AnsiString.
In Delphi 2009 and later:
Char is an alias to WideChar.
PChar is an alias to PWideChar.
string is an alias to UnicodeString.
Your code is written assuming that PChar is PAnsiChar. Hence your problems. You need to stop using StrAlloc anyway. You are making life hard for yourself by manually allocating heap memory here. Let the compiler do the work.
You need to obtain your text in a string variable, and then simply do:
Clipboard.AsText := MyStrVariable;
Exactly how best to obtain the string depends on the facilities that TCReport offers. I expect that it will yield a string directly in which case you'll write something like this:
procedure TCReport.CopyToClipboard;
begin
Clipboard.AsText := Self.ReportAsText;
end;
I'm guessing as to what your functionality your TCReport offers, but I'm sure you know.
By reffering to what hvd and David Heffernan wrote above, one possible way is to change CTextStream on CopyToClipboard to TStringStream as follow :
procedure TCReport.CopyToClipboard;
var
CTextStream: TStringStream;
begin
CTextStream := TStringStream.Create;
try
//Assume no error with Self.SaveToTextStream
Self.SaveToTextStream(CTextStream);
{ Copy text stream to clipboard }
Clipboard.AsText := CTextStream.DataString;
finally
CTextStream.Free;
end;
end;
But you should make sure that SaveToTextStream function provides CTextStream with the exact encoding text data.

How to access characters of a WideString by index?

i have the following code snippit that won't compile:
procedure Frob(const Grob: WideString);
var
s: WideString;
begin
s :=
Grob[7]+Grob[8]+Grob[5]+Grob[6]+Grob[3]+Grob[4]+Grob[1]+Grob[2];
...
end;
Delphi5 complains Incompatible types.
i tried simplifying it down to:
s := Grob[7];
which works, and:
s := Grob[7]+Grob[8];
which does not.
i can only assume that WideString[index] does not return a WideChar.
i tried forcing things to be WideChars:
s := WideChar(Grob[7])+WideChar(Grob[8]);
But that also fails:
Incompatible types
Footnotes
5: Delphi 5
The easier, and faster, in your case, is the following code:
procedure Frob(const Grob: WideString);
var
s: WideString;
begin
SetLength(s,8);
s[1] := Grob[7];
s[2] := Grob[8];
s[3] := Grob[5];
s[4] := Grob[6];
s[5] := Grob[3];
s[6] := Grob[4];
s[7] := Grob[1];
s[8] := Grob[2];
...
end;
Using a WideString(Grob[7])+WideString(Grob[8]) expression will work (it circumvent the Delphi 5 bug by which you can't make a WideString from a concatenation of WideChars), but is much slower.
Creation of a WideString is very slow: it does not use the Delphi memory allocator, but the BSTR memory allocator supplied by Windows (for OLE), which is damn slow.
Grob[7] is a WideChar; that's not the issue.
The issue seems to be that the + operator cannot act on wide chars. But it can act on wide strings, and any wide char can be cast to a wide string:
S := WideString(Grob[7]) + WideString(Grob[8]);
As Geoff pointed out my other question dealing with WideString weirdness in Delphi, i randomly tried my solution from there:
procedure Frob(const Grob: WideString);
var
s: WideString;
const
n: WideString = ''; //n=nothing
begin
s :=
n+Grob[7]+Grob[8]+Grob[5]+Grob[6]+Grob[3]+Grob[4]+Grob[1]+Grob[2];
end;
And it works. Delphi is confused about what type a WideString[index] in, so i have to beat it over the head.

AnsiString To Stream

I created the following code:
Function AnsiStringToStream(Const AString: AnsiString): TStream;
Begin
Result := TStringStream.Create(AString, TEncoding.ANSI);
End;
But I'm "W1057 Implicit string cast from 'AnsiString' to 'string'"
There is something wrong with him?
Thank you.
The TStringStream constructor expects a string as its parameter. When you give it an an AnsiString instead, the compiler has to insert conversion code, and the fact that you've specified the TEncoding.ANSI doesn't change that.
Try it like this instead:
Function AnsiStringToStream(Const AString: AnsiString): TStream;
Begin
Result := TStringStream.Create(string(AString));
End;
This uses an explicit conversion, and leaves the encoding-related work up to the compiler, which already knows how to take care of it.
In D2009+, TStringStream expects a UnicodeString, not an AnsiString. If you just want to write the contents of the AnsiString as-is without having to convert the data to Unicode and then back to Ansi, use TMemoryStream instead:
function AnsiStringToStream(const AString: AnsiString): TStream;
begin
Result := TMemoryStream.Create;
Result.Write(PAnsiChar(AString)^, Length(AString));
Result.Position := 0;
end;
Since AnsiString is codepage-aware in D2009+, ANY string that is passed to your function will be forced to the OS default Ansi encoding. If you want to be able to pass any 8-bit string type, such as UTF8String, without converting the data at all, use RawByteString instead of AnsiString:
function AnsiStringToStream(const AString: RawByteString): TStream;
begin
Result := TMemoryStream.Create;
Result.Write(PAnsiChar(AString)^, Length(AString));
Result.Position := 0;
end;

Resources