how to convert string to PwideChar in Android Platform using Delphi ? in windows apps its done using..
var
PW: PWideChar;
begin
PW := pwidechar(widestring(String));
PW := pwidechar(widestring(Reply));
A := ExistWordInString(PW,String,[soWholeWord,soDown]); //A : Boolean
....
end;
the problems is Undeclared identifier: 'WideString' , how to work around this ?
Delphi 10 Berlin , Firemonkey, Android
UPDATE
well, according to http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Migrating_Delphi_Code_to_Mobile_from_Desktop ,, we cant use widestring, i cant think of another way to use string this function :
function ExistWordInString(aString:PWideChar;aSearchString:string;aSearchOptions: TStringSearchOptions): Boolean;
var
Size : Integer;
Begin
Size:=StrLen(aString);
Result := SearchBuf(aString, Size, 0, 0, aSearchString, aSearchOptions)<>nil;
Your code is not strictly correct in Windows. Yes you can convert string (an alias for UnicodeString) to the COM WideString, but this is a waste of time and resources. The correct code is:
var
P: PWideChar;
S: string;
....
P := PWideChar(S);
In fact, since you are using a Unicode version of Delphi, it is probably idiomatic to use PChar (an alias for PWideChar), to fit alongside string.
So I would write:
var
P: PChar;
S: string;
....
P := PChar(S);
Now, this code, as well as being the correct way to do this on Windows, works equally on all platforms.
Related
I'm developing an Android app in Delphi 10.4. My client communicates with the server through web services.
I use a search-by-name service with the GET method to take a list of names depending on what letter, English or Greek, I type in a TEdit. The line of code looks like this:
mydata := IdHTTP1.GET('http://.../Names/search/'+Edit1.Text);
The request is called every time the user types a letter in the TEdit, and returns the names that start with the first letter(s) of the text that the user typed.
When I use English letters, the search works fine, but when I use Greek letters, it doesn't work properly. Instead, it returns all the list of names 1.
I try the path in a browser using Greek letters, like this: http://.../Names/search/Αντ, and it works, it returns the names starting with Αντ. But in the app, it doesn't work.
Is it possible that the encoding of the TEdit or TIdHTTP component are wrong?
It's like it doesn't read the Greek letters and sends an empty string.
1 Because if the path is: http://.../Names/search/, it returns all the list of names.
My code looks like this:
procedure TForm1.Edit1ChangeTracking(Sender: TObject);
var
mydata : string;
jsv,js : TJSONValue;
originalObj,jso : TJSONObject;
jsa : TJSONArray;
i: Integer;
begin
Memo1.Lines.Clear;
try
IdHTTP1.Request.ContentType := 'application/json';
IdHTTP1.Request.CharSet := 'utf-8';
IdHTTP1.Request.AcceptLanguage := 'gr';
IdHTTP1.Request.ContentEncoding := 'UTF-8';
mydata := IdHTTP1.Get('http://.../Names/search/'+Edit1.Text);
except
on E: Exception do
begin
ShowMessage(E.Message);
end;
end;
try
jsv := TJSONObject.ParseJSONValue(TEncoding.UTF8.GetBytes(mydata),0) as TJSONValue;
try
jsa := jsv as TJSONArray;
for i := 0 to jsa.Size-1 do
begin
jso := jsa.Get(I) as TJSONObject;
js := jso.Get('Name').JsonValue;
Memo1.Lines.Add(js.Value);
if i=4 then // show the 5 first names of the search
break;
end;
finally
jsv.Free();
end;
except
on E: exception do
begin
ShowMessage(E.Message);
end;
end;
end;
URLs can't contain unencoded non-ASCII characters. You can't just append the TEdit text as-is, you need to url-encode any non-ASCII characters, as well as characters reserved by the URI specs.
If you use your browser's built-in debugger, you will see that it is actually doing this encoding when transmitting the request to the server. For example, a URL like: http://.../Names/search/Αντ sends a request like this:
GET /Names/search/%CE%91%CE%BD%CF%84 HTTP/1.1
Host: ...
...
Notice Αντ => %CE%91%CE%BD%CF%84
In your code, you can use Indy's TIdURI class for this purpose, eg:
uses
..., IdURI;
mydata := IdHTTP1.GET('http://.../Names/search/'+TIdURI.PathEncode(Edit1.Text));
On a side note:
Since you are sending a GET request, you do not need to set the Request.ContentType, Request.CharSet, or Request.ContentEncoding properties (besides, 'UTF-8' is not valid for ContentEncoding anyway).
Also, ParseJSONValue() has an overload that takes a string, so you don't need to use TEncoding.UTF8.GetBytes().
AS. since closing related questions - more examples added below.
The below simple code (which finds a top-level Ie window and enumerates its children) works Ok with a '32-bit Windows' target platform. There's no problem with earlier versions of Delphi as well:
procedure TForm1.Button1Click(Sender: TObject);
function EnumChildren(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
const
Server = 'Internet Explorer_Server';
var
ClassName: array[0..24] of Char;
begin
Assert(IsWindow(hwnd)); // <- Assertion fails with 64-bit
GetClassName(hwnd, ClassName, Length(ClassName));
Result := ClassName <> Server;
if not Result then
PUINT_PTR(lParam)^ := hwnd;
end;
var
Wnd, WndChild: HWND;
begin
Wnd := FindWindow('IEFrame', nil); // top level IE
if Wnd <> 0 then begin
WndChild := 0;
EnumChildWindows(Wnd, #EnumChildren, UINT_PTR(#WndChild));
if WndChild <> 0 then
..
end;
I've inserted an Assert to indicate where it fails with a '64-bit Windows' target platform. There's no problem with the code if I un-nest the callback.
I'm not sure if the erroneous values passed with the parameters are just garbage or are due to some mis-placed memory addresses (calling convention?). Is nesting callbacks infact something that I should never do in the first place? Or is this just a defect that I have to live with?
edit:
In response to David's answer, the same code having EnumChildWindows declared with a typed callback. Works fine with 32-bit:
(edit: The below does not really test what David says since I still used the '#' operator. It works fine with the operator, but if I remove it, it indeed does not compile unless I un-nest the callback)
type
TFNEnumChild = function(hwnd: HWND; lParam: LPARAM): Bool; stdcall;
function TypedEnumChildWindows(hWndParent: HWND; lpEnumFunc: TFNEnumChild;
lParam: LPARAM): BOOL; stdcall; external user32 name 'EnumChildWindows';
procedure TForm1.Button1Click(Sender: TObject);
function EnumChildren(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
const
Server = 'Internet Explorer_Server';
var
ClassName: array[0..24] of Char;
begin
Assert(IsWindow(hwnd)); // <- Assertion fails with 64-bit
GetClassName(hwnd, ClassName, Length(ClassName));
Result := ClassName <> Server;
if not Result then
PUINT_PTR(lParam)^ := hwnd;
end;
var
Wnd, WndChild: HWND;
begin
Wnd := FindWindow('IEFrame', nil); // top level IE
if Wnd <> 0 then begin
WndChild := 0;
TypedEnumChildWindows(Wnd, #EnumChildren, UINT_PTR(#WndChild));
if WndChild <> 0 then
..
end;
Actually this limitation is not specific to a Windows API callbacks, but the same problem happens when taking address of that function into a variable of procedural type and passing it, for example, as a custom comparator to TList.Sort.
http://docwiki.embarcadero.com/RADStudio/Rio/en/Procedural_Types
procedure TForm2.btn1Click(Sender: TObject);
var s : TStringList;
function compare(s : TStringList; i1, i2 : integer) : integer;
begin
result := CompareText(s[i1], s[i2]);
end;
begin
s := TStringList.Create;
try
s.add('s1');
s.add('s2');
s.add('s3');
s.CustomSort(#compare);
finally
s.free;
end;
end;
It works as expected when compiled as 32-bit, but fails with Access Violation when compiled for Win64. For 64-bit version in function compare, s = nil and i2 = some random value;
It also works as expected even for Win64 target, if one extracts compare function outside of btn1Click function.
This trick was never officially supported by the language and you have been getting away with it to date due to the implementation specifics of the 32 bit compiler. The documentation is clear:
Nested procedures and functions (routines declared within other routines) cannot be used as procedural values.
If I recall correctly, an extra, hidden, parameter is passed to nested functions with the pointer to the enclosing stack frame. This is omitted in 32 bit code if no reference is made to the enclosing environment. In 64 bit code the extra parameter is always passed.
Of course a big part of the problem is that the Windows unit uses untyped procedure types for its callback parameters. If typed procedures were used the compiler could reject your code. In fact I view this as justification for the belief that the trick you used was never legal. With typed callbacks a nested procedure can never be used, even in the 32 bit compiler.
Anyway, the bottom line is that you cannot pass a nested function as parameter to another function in the 64 bit compiler.
This code is working in a Firemonkey Windows app, but doesn't work in Android app, i get Goodbye instead of Welcome, what is wrong?
Edit8 Text : 162496 //Computer unique code
Edit9 Text : 1564224593 //serial #
procedure TForm2.Button5Click(Sender: TObject);
var
f2,f1:textfile;
i,j:byte;
s1,s2,s3,c:string;
F: TextFile;
begin
j:=0;
s2 := Edit8.Text;
for i:=1 to Length(s2) do
if (s2[i]>='0') and (s2[i]<='9') then
s3:=s3+s2[i];
for i:=1 to Length(s3)-1 do
if (edit9.Text[i*2-1]<>s3[i]) or (abs(strtoint(s3[i+1])-strtoint(s3[i]))<> strtoint(edit9.Text[i*2])) then
inc(j);
if j=0 then
ShowMessage('Welcome')
else
ShowMessage('Goodbye');
end;
Delphi mobile compilers use zero-based strings.
You have three choices:
As #Günter_the_Beautiful points out, your best choice is to rewrite your code to use string helpers (which are always 0-based)
Rewrite your code to use 0-based indexing: for I := 0 to ...
If you need a quick fix, turn it off locally for your code snippet using the {$ZEROBASEDSTRINGS OFF} directive (and revert it back with {$ZEROBASEDSTRINGS ON} again).
For options 2. and 3., if you need your code to be cross-platform, consider using appropriate platform conditional defines. This is what makes option 1. compelling: no need to clutter your code with conditional defines.
I am using these two helper routines:
FUNCTION GetChar(CONST S : STRING ; OneBasedIndex : LongWord) : CHAR;
BEGIN
{$IF CompilerVersion>=24 }
Result:=S[SUCC(OneBasedIndex-LOW(S))]
{$ELSE }
Result:=S[OneBasedIndex]
{$IFEND }
END;
PROCEDURE SetChar(VAR S : STRING ; OneBasedIndex : LongWord ; NewChar : CHAR);
BEGIN
{$IF CompilerVersion>=24 }
S[SUCC(OneBasedIndex-LOW(S))]:=NewChar
{$ELSE }
S[OneBasedIndex]:=NewChar
{$IFEND }
END;
This way, you can continue working with strings as 1-based (which is the logical choice :-)) as long as you always access the strings as characters using these two functions.
I was able to find two Bcrypt libraries that can be compiled for windows but I am struggling to compile them for Android in Delphi XE8.
The first one is https://github.com/chinshou/bcrypt-for-delphi which doesn't require any modifications to be compiled for Windows.
For the second one https://github.com/PonyPC/BCrypt-for-delphi-lazarus-fpc I had to make some minor adjustments in the checkPassword function to get the same result since it was FreePascal specific:
function checkPassword(const Str: string; const Hash: ansistring): boolean;
var
RegexObj: TRegEx;
match : TMatch;
Salt : String;
begin
RegexObj := TRegEx.Create('^\$2a\$10\$([\./0-9A-Za-z]{22})',[roIgnoreCase]);
match := RegexObj.Match(Hash);
if match.Success then
begin
Salt := Copy(match.Value,8,22);
Result := HashPassword(Str, Salt) = Hash;
end
else
begin
Result := False;
end;
end;
After changing the platform from Win to Android the first one shows a lot of errors since it depends on ComObj, Windows and ActiveX. The second one after replacing RegExpr with RegularExpressions and Types shows only conflicts that results from changes in the String variable. The code uses AnsiString, AnsiChar which I cannot just replace with String and Char since it affects the hashing function.
What am I missing? what other modifications should I do to replace the obsolete AnsiString and AnsiChar declarations, allowing the code to be compiled for Android?
The String declaration problem with the second library was caused by the move command in the HashPassword function
Move(password[1], key[0], Length(password));
since the size of the password variable changed after replacing the declaration from AnsiString to String. Replacing this with a simple for loop and Ord function fixes the problem although there is probably a more elegant way of doing it.
function HashPassword(const Str: string; const salt: string): string;
var
password: String ;
key, saltBytes, Hash: TBytes;
i: Integer;
begin
password := AnsiToUtf8(str);
SetLength(key, Length(password) + 1);
for i := 0 to length(password)-1 do
key[i]:=ord(password[i+1]);
key[high(key)] := 0;
saltBytes := BsdBase64Decode(salt);
Hash := CryptRaw(key, saltBytes);
Result := FormatPasswordHashForBsd(saltBytes, Hash);
end;
To summarize, the conversion of the second library to an Android compatible code requires the following changes:
modifying the regular expression code in the checkPassword function
according to the code posted in the question
altering the uses section by replacing "RegExpr" with "RegularExpressions, Types"
replacing all declarations from AnsiString to String and AnsiChar to Char
modifying the HashPassword function as shown above
The following code works on Win32, anyway it is trowing exception if run on Android or iOS. The exception is : "No mapping for the Unicode character exists in the target multi-byte code page"
function GetURLAsString(aURL: string): string;
var
lHTTP: TIdHTTP;
lStream: TStringstream;
begin
lHTTP := TIdHTTP.Create(nil);
lStream := TStringstream.Create(result);//create('',EEncoding.utf8),not work
try
lHTTP.Get(aURL, lStream);
lStream.Position := 0;
result := lStream.readstring(lStream.Size);//error here
finally
FreeAndNil(lHTTP);
FreeAndNil(lStream);
end;
end;
TStringStream is not a suitable class for this situation. It requires you to specify an encoding in its constructor. If you do not, it uses the OS default encoding instead. On Windows, that default is locale-specific to the user account that is running your app. On mobile, that default is UTF-8 instead.
HTTP can transmit text using any number of charsets. If the data does not match the encoding used by the TStringStream, you are going to run into decoding problems.
TIdHTTP knows the charset of the received data and can decode it into a Delphi string for you, eg:
function GetURLAsString(aURL: string): string;
var
lHTTP: TIdHTTP;
begin
lHTTP := TIdHTTP.Create(nil);
try
Result := lHTTP.Get(aURL);
finally
FreeAndNil(lHTTP);
end;
end;