Copying string content to char array - delphi

I want to copy the content in the string to char array.
Can I use this code StrLCopy(C, pChar(#S[1]), high(C));
I am currently using Delphi 2006. Will there be any problems if I upgrade my Delphi version because of Unicode support provided in newer versions?
If not, what can be the code for this conversion?

When you're copying a string into an array, prefer StrPLCopy.
StrPLCopy(C, S, High(C));
That will work in all versions of Delphi, even when Unicode is in effect. The character types of C and S should be the same; don't try to use that function to convert between Ansi and Unicode characters.
But StrLCopy is fine, too. You don't need to have so much pointer code, though. Delphi already knows how to convert a string into a PChar:
StrLCopy(C, PChar(S), High(C));

This works, in a quick test:
var
ch: array[0..10] of Char;
c: Char;
x: Integer;
st: string;
begin
s := 'Testing';
StrLCopy(PChar(#ch[0]), PChar(s), High(ch));
x := 100;
for c in ch do
begin
Canvas.TextOut(x, 100, c);
Inc(c, Canvas.TextWidth(c) + 3);
end;
end;

Related

Can not assign char array to string in Delphi 10.2

I have the following variable declarations:
arrChar_1: array[0..2] of Char;
arrChar_2: array[0..2] of Char;
str: string;
Then I made the assignment:
str := arrChar_1 + arrChar_2;
This assignment works normally on Delphi 6. But error occurs when I compile it on Delphi 10.2:
[dcc32 Error] MigrateConcatenateCharArray.dpr(26): E2008 Incompatible types
I'm solving this problem in the following way:
str := Copy(first_arrChar, 0, StrLen(first_arrChar));
str := str + Copy(second_arrChar, 0, StrLen(second_arrChar));
Is there any other good solution to this problem? (1)
In Delphi 6:
String = AnsiString
Char = AnsiChar
In Delphi 10.2:
String = UnicodeString
Char = WideChar
Can tell me what caused the incompatibility issue to occur? (2)
I'm understanding that widechar is a multi-byte character type. Unicode is the way that characters are encoded. But I'm confused about them.
The following compiles in all versions of Delphi:
procedure Main;
var
arrChar_1: array[0..2] of AnsiChar;
arrChar_2: array[0..2] of AnsiChar;
str: AnsiString;
begin
str := arrChar_1 + arrChar_2;
end;
The following code does not compile in Unicode versions of Delphi:
procedure Main;
var
arrChar_1: array[0..2] of WideChar;
arrChar_2: array[0..2] of WideChar;
str: UnicodeString;
begin
str := arrChar_1 + arrChar_2;
end;
This seems a little odd to me. Why should the concatenation operator be supported for AnsiChar arrays but not WideChar arrays?
If you examine how the concatenation operator is implemented for AnsiChar arrays that begins to shed some light. The generated code first converts the arrays into ShortString instances. These are then converted into Delphi AnsiString instances. Finally the two AnsiString instances are concatenated.
Now, this would explain why the code fails for WideChar arrays. The ShortString type only supports AnsiChar elements and so a different path through the string support routines would have been needed. One can assume that the Embarcadero designers chose, for whatever reason, not to support this form of concatenation when implementing Unicode support.
To back this idea up, consider the following:
procedure Main;
var
arrChar_1: array[0..254] of AnsiChar;
arrChar_2: array[0..254] of AnsiChar;
str: AnsiString;
begin
str := arrChar_1 + arrChar_2;
end;
This compiles. But change either of the 254 upper bounds to 255 and the code fails to compile (in all versions of Delphi) reporting E2008 Incompatible types. That is because the array now exceeds the maximum length of a ShortString object.
As for how to migrate your code to Unicode Delphi, I suggest that you simply cast the character arrays to string:
str := string(arrChar_1) + string(arrChar_2);

How should I adapt my code for compatibility between TBytes and TIdBytes?

I am having the same problem as mentioned in "Delphi XE4 Indy compatibility issue between TBytes and TidBytes ", i.e. compatibility issues between TBytes(Delphi RTL) and TIdBytes(Indy) datatypes when compiling with the Delphi XE4. The source of my problem is that the code is not exactly according to Indy's interface and some of functions use TBytes, instead of TIdBytes, when calling native Indy IO procedures.
So I was wondering what will the best fix be?
As I see it there are two approaches:
Refactor all functions in the project to use TIdBytes rather than TBytes.
Implement a TBytesToTidBytes conversion procedure (converts the TBytes to TIdBytes) and call that procedure prior to making the mentioned native Indy calls.
Which of the approaches is better/best? Do you have any other ideas on how I can do that?
FYI: The project I am trying to configure with the XE4 is available online on sourceforge : http://sourceforge.net/projects/indy10clieservr/?source=directory
The suggested conversion procedure should be something like:
procedure TBytesToTIdBytes(const Input:TBytes, var Output: TIdBytes)
var
i,L : Integer;
allocate : Boolean;
begin
L := Length(Input);
if(Length(Output) <> L) then
begin
SetLength(Output,L);
end;
if(L > 0) then
move(Pointer(Input)^,Pointer(Output)^,L);
end;
TBytes and TIdBytes are both implemented as dynamic arrays, they are simply declared differently. The "politically correct" solution is to make a copy of the bytes. But that can waste memory for large arrays. A simpler solution is to use a typecast so you can utilize the array's internal reference count, eg:
type
PIdBytes = ^TIdBytes;
var
B1: TBytes;
B2: TIdBytes;
begin
B1 := ...;
B2 := PIdBytes(#B1)^;
end;
Or simply:
var
B1: TBytes;
B2: TIdBytes;
begin
B1 := ...;
B2 := TIdBytes(B1);
end;
Both types are not the same at implementation level, in newer Delphi versions (TBytes is a simple alias for TArray<Byte> in recent Delphi releases).
So I guess you can use such a procedure:
procedure TBytesToTIdBytes(const Input: TBytes; var Output: TIdBytes);
var L: integer;
begin
L := Length(Input);
SetLength(Output,L);
move(Input[0],Output[0],L);
end;
Here move() is faster than a loop.

How to convert AnsiChar to UnicodeChar with specific CodePage?

I'm generating texture atlases for rendering Unicode texts in my app. Source texts are stored in ANSI codepages (1250, 1251, 1254, 1257, etc). I want to be able to generate all the symbols from each ANSI codepage.
Here is the outline of the code I would expect to have:
for I := 0 to 255 do
begin
anChar := AnsiChar(I); //obtain AnsiChar
//Apply codepage without converting the chars
//<<--- this part does not work, showing:
//"E2033 Types of actual and formal var parameters must be identical"
SetCodePage(anChar, aCodepages[K], False);
//Assign AnsiChar to UnicodeChar (automatic conversion)
uniChar := anChar;
//Here we get Unicode character index
uniCode := Ord(uniChar);
end;
The code above does not works (E2033) and I'm not sure it is a proper solution at all. Perhaps there's much shorter version.
What is the proper way of converting AnsiChar into Unicode with specific codepage in mind?
I would do it like this:
function AnsiCharToWideChar(ac: AnsiChar; CodePage: UINT): WideChar;
begin
if MultiByteToWideChar(CodePage, 0, #ac, 1, #Result, 1) <> 1 then
RaiseLastOSError;
end;
I think you should avoid using strings for what is in essence a character operation. If you know up front which code pages you need to support then you can hard code the conversions into a lookup table expressed as an array constant.
Note that all the characters that are defined in the ANSI code pages map to Unicode characters from the Basic Multilingual Plane and so are represented by a single UTF-16 character. Hence the size assumptions of the code above.
However, the assumption that you are making, and that this answer persists, is that a single byte represents a character in an ANSI character set. That's a valid assumption for many character sets, for example the single byte western character sets like 1252. But there are character sets like 932 (Japanese), 949 (Koren) etc. that are double byte character sets. Your entire approach breaks down for those code pages. My guess is that only wish to support single byte character sets.
If you are writing cross-platform code then you can replace MultiByteToWideChar with UnicodeFromLocaleChars.
You can also do it in one step for all characters. Here is an example for codepage 1250:
var
encoding: TEncoding;
bytes: TBytes;
unicode: TArray<Word>;
I: Integer;
S: string;
begin
SetLength(bytes, 256);
for I := 0 to 255 do
bytes[I] := I;
SetLength(unicode, 256);
encoding := TEncoding.GetEncoding(1250); // change codepage as needed
try
S := encoding.GetString(bytes);
for I := 0 to 255 do
unicode[I] := Word(S[I+1]); // as long as strings are 1-based
finally
encoding.Free;
end;
end;
Here is the code I have found to be working well:
var
I: Byte;
anChar: AnsiString;
Tmp: RawByteString;
uniChar: Char;
uniCode: Word;
begin
for I := 0 to 255 do
begin
anChar := AnsiChar(I);
Tmp := anChar;
SetCodePage(Tmp, aCodepages[K], False);
uniChar := UnicodeString(Tmp)[1];
uniCode := Word(uniChar);
<...snip...>
end;

Assign String to Array of Characters

Question One
I have
var example : array[0..15] of char;
I want to assign the value from an input to that variable
example := inputbox('Enter Name', 'Name', '');
In the highscores unit I have record and array
type
points = record
var
_MemoryName : array[0..15] of char;
_MemoryScore : integer;
end;
var
rank : array[1..3] of points;
var s: string;
a: packed array[0..15] of char;
highscoresdata.position[1]._MemoryName := StrPLCopy(a, s, Length(a)) ;
returns -> (186): E2010 Incompatible types: 'array[0..15] of Char' and 'PWideChar'
var s: string;
a: packed array[0..15] of char;
s := InputBox('caption', 'Caption', 'Caption');
FillChar(a[0], length(a) * sizeof(char), #0);
Move(s[1], a[0], length(a) * sizeof(char));
scores.rank[1]._MemoryName := <<tried both s and a>> ;
returns (189): E2008 Incompatible types
Question One
There are many ways. One is:
procedure TForm1.FormCreate(Sender: TObject);
var
s: string;
a: packed array[0..15] of char;
begin
s := InputBox(Caption, Caption, Caption);
assert(length(s) <= 16);
FillChar(a[0], length(a) * sizeof(char), #0);
Move(s[1], a[0], length(s) * sizeof(char));
end;
But there might be a more elegant solution to your original problem, I suspect.
Question Two
Every time you wish a function/procedure didn't have a particular argument, you should realize that there might be a problem with the design of the project. Nevertheless, it isn't uncommon that Sender parameters are superfluous, because they are almost omnipresent because of the design of the VCL (in particular, the TNotifyEvent). If you know that the receiving procedure doesn't care about the Sender parameter, simply give it anything, like Self or nil.
Question Three
Consider this code:
procedure TForm4.FormCreate(Sender: TObject);
var
a: packed array[0..15] of char;
b: packed array[0..15] of char;
begin
a := b;
end;
This doesn't work. You cannot treat arrays like strings; in particular, you cannot assign static arrays like this (a := b).
Instead, you have to do something like...
Move(b[0], a[0], length(a) * sizeof(char));
...or simply loop and copy one value at a time. But the above simple assignment (a := b) does work if you declare a static array type:
type
TChrArr = packed array[0..15] of char;
procedure TForm4.FormCreate(Sender: TObject);
var
a: TChrArr;
b: TChrArr;
begin
b := a;
end;
Andreas has you covered for question 1.
Question 2
I would arrange that your event handler called another method:
procedure TForm5.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
RespondToEditControlKeyPress;
end;
That way you can just call RespondToEditControlKeyPress directly.
I'd guess that you want to call it with no parameters because you want code to run when the edit control's text is modified. You could perhaps use the OnChange event instead. And it may be that OnChange is more appropriate because pressing a key is not the only way to get text into an edit control.
By the way, it's better to ask one question at a time here on Stack Overflow.
For a quick way to copy string-type values into array-of-character type values. I suggest a small helper function like this:
procedure StrToCharArray( inputStr:String; var output; maxlen:Integer);
type
ArrayChar = Array[0..1] of Char;
begin
StrLCopy( PChar(#ArrayChar(output)[0]),PChar(inputStr),maxlen);
end;
Each time you call it, pass in the maximum length to be copied. Remember that if the buffer length is 15, you should pass in 14 as the maxlen, so that you leave room for the terminating nul character, if you intend to always terminate your strings:
StrToCharArray( UserInputStr, MyRecord.MyField, 14 );
This function will ensure that the data you copy into the record is null terminated, assuming that's what you wanted. Remember that in a fixed length character array it's up to you to decide what the rules are. Null terminated? Fully padded with spaces or null characters.... Strings and arrays-of-characters are so different, that there exist multiple possible ways of converting between the two.
If you don't intend to terminate your strings with nul, then you should use the FillChar+Move combination shown in someone else's answer.
The obvious answer is of course.
Don't use a packed array of char.
Use a string instead.
If you use ansistring, 1 char will always take 1 byte.
If you use shortstring ditto.
Ansistring is compatible with Pchar which is a pointer to a packed array of char.
So you can write
function inputbox(a,b,c: ansistring): pchar;
begin
Result:= a+b+c;
end;
var s: ansistring;
begin
s:= inputbox('a','b','c');
end;
Some advice
It looks like your are translating code from c to Delphi.
a packed array of char is exactly the same as the old (1995) shortstring minus the length byte at the beginning of shortstring.
The only reason I can think of to use packed array of char is when you are reading data to and from disk, and you have legacy code that you don't want to change.
I would keep the legacy code to read and write from disk and then transfer the data into an ansistring and from there on only use ansistring.
It's soooooooo much easier, Delphi does everything for you.
And... ansistring is much faster, gets automatically created and destroyed, can have any length (up to 2GB), uses less memory --because identical strings only get stored once (which means stringa:= stringb where a string is 20 chars is at least 5x faster using ansistrings than array's of char).
And of course best of all, buffer overflow errors are impossible with ansistring.
What about unicodestring?
Unicodestring is fine to use, but sometimes translation of chars happens when converting between packed array of char and unicodestring, therefore I recommend using ansistring in this context.
What you try to do is impossible, indeed:
highscoresdata.position[1]._MemoryName := StrPLCopy(a, s, Length(a));
That tries to assign a pointer (the result of StrPLCopy, a PWideChar in the last few versions of Delphi) to an array, which is indeed impossible. You can't copy an array like that. I would do:
StrLCopy(highscoresdata.position[1]._MemoryName, PChar(s),
Length(highscoresdata.position[1]._MemoryName));
That should work, and is IMO the simplest solution to copy a string to an array of characters. There is no need to use a as some kind of intermediate, and using Move is, IMO, rather low level and therefore a little tricky (it is easy to forget to multiply by the size of a character, it is unchecked, it does not add a #0, etc.), especially if you don't know what exactly you are doing.
This solution should even work for versions of Delphi before Delphi 2009, as it does not rely on the size of the character.
FWIW, I would not use packed arrays. Packed doesn't have a meaning in current Delphi, but could confuse the compiler and make the types incompatible.

Variable might not have been initialized. Can I switch this warning on for a string?

When I compile this code
{$WARNINGS ON}
function Test(s: string): string;
var
t: string;
d: double;
begin
if s = '' then begin
t := 'abc';
d := 1;
end;
Result := t + FloatToStr(d);
end;
I get the warning "Variable 'd' might not have been initialized", but I do not get the same warning for variable 't'. This seems inconsistent. This code is only a simple example to show the compiler warnings, but I have just found a bug in my live code which would have been caught by a compile-time warning for uninitialised string variables. Can I switch this warning on somehow in Delphi 6? Or in a newer version of Delphi?
Nope, there is no switch for this. The warning doesn't occur because a string is a compiler managed type and is always initialized by the compiler.
Yes :-)
Use shortstrings or pChars
{$WARNINGS ON}
function Test: String;
var
p: pChar;
d: double;
begin
Result := p + FloatToStr(d);
end;
//This code will give a warning.
Seriously
No, the normal Delphi strings and shortstrings are automatically initialized to '' (empty string). Shortstrings live on the stack and don't need cleanup. Other strings are so called 'managed' types and automatically deleted when they are no longer used using reference counting.
PChars, the good news
pChars are just pointers. Delphi does not manage them.
However Delphi does automatically convert them to strings and visa versa.
pChars the bad news
If you convert a pChar to a string Delphi copies the contents of the pChar into the string and you are still responsible for destroying the pChar.
Also note that this copying takes time and if you do it a lot will slow your code down.
If you convert a string to a pChar Delphi will give you a pointer to the address the string lives in. And !! Delphi will stop managing the string. You can still assign values to the string, but it will no longer automatically grow.
From: http://www.marcocantu.com/epascal/English/ch07str.htm
The following code will not work as expected:
procedure TForm1.Button2Click(Sender: TObject);
var
S1: String;
begin
SetLength (S1, 100);
GetWindowText (Handle, PChar (S1), Length (S1));
S1 := S1 + ' is the title'; // this won't work
Button1.Caption := S1;
end;
This program compiles, but when you run it, you are in for a surprise: The Caption of the button will have the original text of the window title, without the text of the constant string you have added to it. The problem is that when Windows writes to the string (within the GetWindowText API call), it doesn't set the length of the long Pascal string properly. Delphi still can use this string for output and can figure out when it ends by looking for the null terminator, but if you append further characters after the null terminator, they will be skipped altogether.
How can we fix this problem? The solution is to tell the system to convert the string returned by the GetWindowText API call back to a Pascal string. However, if you write the following code:
S1 := String (S1);
the system will ignore it, because converting a data type back into itself is a useless operation. To obtain the proper long Pascal string, you need to recast the string to a PChar and let Delphi convert it back again properly to a string:
S1 := String (PChar (S1));
Actually, you can skip the string conversion, because PChar-to-string conversions are automatic in Delphi. Here is the final code:
procedure TForm1.Button3Click(Sender: TObject);
var
S1: String;
begin
SetLength (S1, 100);
GetWindowText (Handle, PChar (S1), Length (S1));
S1 := String (PChar (S1));
S1 := S1 + ' is the title';
Button3.Caption := S1;
end;
An alternative is to reset the length of the Delphi string, using the length of the PChar string, by writing:
SetLength (S1, StrLen (PChar (S1)));

Resources