How can I assign a value to a Char array? - arrays

Say I have the variable:
Var question : array[1..50] of char;
When I do:
question := 't'; //What is the correct way to change the value?
It returns an error:
Incompatible types: 'array[1..50] of Char' and 'Char'
Note: I want to have a max string size of 50 chars, not 50 different chars.
The reason for this question is that I have a record in another unit(This is just a basic example, not what I actually have written above) In that unit I have a Record, which I can't use the string data type in(Or is there a way? please explain if there is). I just need to know how to give an array of chars a value.

While Delphi strings and arrays of char are related, they are not the same.
Delphi overloads assignment of strings and literals (char and string) to array of chars, but only when the lower array bound is zero.
The following code works for me in D2007 and Delphi XE:
var x : array[0..49] of char;
begin
x:='b'; // char literal
x:='bb'; // string literal
end.
If I change the [0 to [1 it fails. This limitation probably simplifies the language helper that takes care of this, and probably the feature is only meant for dealing with converted C structs where arrays always have lower bound 0.

Are you sure that you can't use string data type in a record?
Anyways...
type
TCharArray = array[Char] of Char;
function StringToArray(Str: string): TCharArray;
begin
FillChar(Result, High(Byte), #0);
Move(Str[1], Result, length(Str));
end;
procedure TestCharArray;
var
question: TCharArray;
begin
question := StringToArray('123');
ShowMessage(PChar(#question));
end;
Also take a look at StrPCopy function.

If you don't need unicode characters, you should just define your string like string[50].
After that you don't need any functions or conversions to work with that string, and it'll be just as easy to read and write it to a file.
Hscores = record
var
_topscore : integer;
_topname : string[50];
end;

I'm pretty sure you can use strings in record types.
This blog entry shows an example: http://delphi.about.com/od/beginners/a/record_type.htm
In order to assign a value to the Char array, you have to index it, like any other array:
question[1] := 't';

Related

Array of strings in Ada

So, I'm coding in Ada for the first time and have to create this pre-populated array of strings, so I can use a random method and call a different value each time.
poolOfWords: constant array(1..50) of string := ("gum","sin","for","cry","lug","bye","fly",
"ugly", "each","from","work","talk","with","self", "pizza","thing","feign","friend",
"elbow","fault", "dirty","budget","spirit","quaint","maiden","escort","pickax",
"example","tension","quinine","kidney","replica","sleeper", "triangle","kangaroo",
"mahogany","sergeant","sequence","moustache","dangerous","scientist",
"different","quiescent", "magistrate","erroneously","loudspeaker",
"phytotoxic","matrimonial","parasympathomimetic","thigmotropism");
But when I try to compile I get this error: "unconstrained element type in array declaration".
I Googled all over internet and couldn't find a solution to that. I even tried to use unbounded_string, but didn't work as well. It works fine when I pre-populate an array of integers, floats, but when it come to strings it's not working.
Can someone help me and explain what's going on and how to solve it? I really don't want to declare the array and then populate each of the 50 indexes one by one.
Your problem stems from the fact that String in Ada is an unconstrained type. This means, roughly, that you need to specify "something" in order to have a usable type. In the case of String you need to specify the string length, since a String is just an array of Character. If you say something like
X : String;
the compiler complains since it does not know the length of X and it does not know how much memory to reserve for X. You need to say something like
X : String(1..6);
or something like
X : String := "foobar";
Note that in the second case you do not need to specify the index range since the compiler is able to deduce it from the fact that you use a 6-Character string to initialize X.
This explains why it is illegal to declare an array of just String. When you declare an array the compiler needs to know the size of every entry (all the entries must share the same size, this makes access to a random entry of the array very efficient). If you declare an array of just String, the compiler would not know how much memory to allocate to every entry since the length is unspecified.
Note that this instead would work (disclaimer: I have not an Ada compiler at hand now, so I cannot check the code)
subtype ZIP_Code is String (1..5);
type ZIP_Code_Array is array(Positive range <>) of ZIP_Code;
since now the compiler knows the size of every entry of a ZIP_Code_Array (5 Character).
In your case, however, the strings have all different sizes. I can see two possible solutions
If the maximum length is known in advance you can use the subtype approach shown above and pad the strings with spaces. It depends on your context if this is a suitable solution or not
The approach that I typically use is to create an array of Unconstrained_String that I initialize as follows
function "+"(X: String) return Unbounded_String
renames To_Unbounded_String;
poolOfWords: constant array(1..50) of Unbounded_String :=
(+"gum",
+"sin",
+"for",
-- and so on...
);
Note the renames that defines the unary + as an alias for To_Unbounded_String. This is just syntactic sugar that allows you to write +"gum" instead of To_Unbounded_String("gum"). Someone cringes at this type of syntactic sugar (and I am not so keen either), but in this case I think it is worth since it makes the code "lighter." It would be nice to have the definition of unary + near its use, just for clarity.
An alternative to the use of Unbounded_String is to use the generic package Bounded_Strings and define poolOfWords as an array of Bounded_String. A Bounded_String can hold strings with variable length, as long as the length is smaller than the bound specified when instantiating the Bounded_Strings package. In order to convert back and forth String you can use the same "rename trick."
My personal preference goes to the Unbounded_String, unless I have some special reason that forces me to use the Bounded_String.
Another option is to use Ada's standard containers. For example:
Package String_Holder is new Ada.Containers.Indefinite_Holders(
"=" => Ada.Strings.Equal_Case_Insensitive,
Element_Type => String
);
Function "+"( Right : String_Holder.Holder ) return String
renames String_Holder.Element;
Function "+"( Right : String ) return String_Holder.Holder
renames String_Holder.To_Holder;
Allows you to store in the holder varying sized values of type String, so you could forego any dependence on Ada.Strings.Unbounded_String and use something like:
poolOfWords: constant array(Positive range <>) of String_Holder.Holder :=
+"gum", +"sin", +"for", +"cry", -- ...
+"Stuff"
);
Function Get_Word(Index : Positive) return String is
(+poolOfWords(Index)) with Pre => Index in poolOfWords'Range;
Alternatively, you could use Ada.Containers.Indefinite_Vectors like so:
Package String_Vector is new Ada.Containers.Indefinite_Vectors(
"=" => Ada.Strings.Equal_Case_Insensitive,
Index_Type => Positive,
Element_Type => String
);
though there is one problem with this in Ada 2012: You can't initialize such a vector directly within the declaration, but can initialize it via function-call:
Function Populate return String_Vector.Vector is
Begin
Return Result : String_Vector.Vector do
Result.Append( "some" );
Result.Append( "data" );
Result.Append( "HERE" );
End Return;
End Populate;
Being able to say X : String_Vectors.Vector := ("this", "that", "the other") is in Ada 2020 .

Modern Delphi to C strings for passing to C DLLs

I've got some old C DLLs that serve as intermediaries between my Delphi app and MATLAB compiled DLLs. These were originally developed with Delphi 7, and worked fine. Now, however, I've upgraded first to XE7 and now to 10.2.3, and I'm having problems. Basically, I have to pass a pointer to a record to the DLL; the structure includes fields that are pointers to old-fashioned C-style strings (i.e. an array of chars, where each char is a byte, and terminated by a 0 byte).
So consider this record:
MyRec = record
id: Integer;
name: PByte;
end;
I wrote a function like this:
function MakeCString(S: String): TBytes;
var
I: Integer;
len: Integer;
begin
len := Length(S);
SetLength(Result, len + 1);
if len > 0 then
begin
for I := 0 to (len - 1) do
begin
Result[i] := Byte(S[I + 1]);
end;
end;
Result[len] := 0;
end;
Then, my intent is to use this like this:
var r: MyRec;
r.id := 27;
r.name := MakeCString('Jimbo');
callMatlabWrapperDll(#r);
I know this is ugly and prone to memory loss - if it even works reliably at all! It seems to sort of work, but now on a different Windows 10 machine from where it worked (maybe), on my dev machine (also Windows 10), I get errors.
I've banged my head on this long enough. What I need is a way to create a C-style string, pass its address to a DLL, and clean up afterwards. (Note: This is all Win32, running on Win64).
Thanks.
Your MakeCString() is implemented wrong. It is not converting Unicode characters to ANSI, it is just truncating the characters as-is from 16-bit to 8-bit, which is not the same thing.
For that matter, you don't actually need MakeCString() at all. You can use TEncoding.GetBytes() instead.
Also, you are assigning the result of your "conversion" directly to your record's PByte field. You should first assign it to a local TBytes variable, and then get a pointer to its data.
Try this instead:
type
MyRec = record
id: Integer;
name: PByte;
end;
procedure callMatlab;
var
r: MyRec;
name: TBytes;
begin
name := TEncoding.Default.GetBytes('Jimbo'#0);
r.id := 27;
r.name := PByte(name);
callMatlabWrapperDll(#r);
end;
Alternatively, you can (and should) use PAnsiChar instead, which is what C-style APIs generally use for 8-bit strings, eg:
type
MyRec = record
id: Integer;
name: PAnsiChar;
end;
procedure callMatlab;
var
r: MyRec;
name: AnsiString;
begin
name := AnsiString('Jimbo');
r.id := 27;
r.name := PAnsiChar(name);
callMatlabWrapperDll(#r);
end;
This would be more inline with your Delphi 7 code, when String was still AnsiString.
Two things are important to note:
Since Delphi 2009, string is Unicode (that is, two bytes per character).
Delphi strings are essentially C-style strings with an additional header.
The first point means that you must convert the strings from Unicode to ANSI. The second point means that you don't need to write a function that creates a C-style string from a Delphi string -- the Delphi string already is C-style if you just disregard its header. Simply casting a Delphi string to PChar gives you a pointer to the C-style string, because the header is at a negative offset from the string pointer's value.
If MyDelphiString is your Delphi string, this can be converted to an old AnsiString by a simple cast: AnsiString(MyDelphiString). This will create a new string on the heap, with the same content but now in legacy ANSI format (1 byte per char). Of course you will lose "special" characters like ⌬∮☃电脑.
Let us save this to a local variable so we know that its lifetime is (at least) as long as this variable is in scope (thanks Remy Lebeau):
var
MyAnsiString: AnsiString;
begin
MyAnsiString := AnsiString(MyDelphiString)
And to get a pointer to the C-style string which is a subset of this string object, just write PAnsiChar(MyAnsiString). If you insist on using a PByte type in the record, you can be explicit about this reinterpretation as well:
r.Name = PByte(PAnsiChar(MyAnsiString))
But it would be nicer if the record member's type was PAnsiChar instead of PByte.

array of Byte to string and backwards conversion

Some time ago I needed to convert Array of Byte type to TBytes, which was done with help of delphi gurus here on SO;
Specifically, I needed to convert data so that I could extract what UDPServer gave me on ServerUDPRead in Indy 10.
This function was made by #David Heffernan, and is posted on this topic: Delphi XE3 indy compatibility issue between tbytes and tidbytes
So, I'm using
function CopyBytes(const Bytes: array of Byte): TBytes;
var
Count: Integer;
begin
Count := Length(Bytes);
SetLength(Result, Count);
if Count > 0 then
Move(Bytes[0], Result[0], Length(Bytes));
end;
to convert this to TBytes and as one I can then send this type via Client UDP SendBuffer.
However, I need to make some modifications to data between they are forwarded;
I read first line of array of byte which the ServerUDPRead delivers, to a string with this:
var FirstString: string;
FirstString := PAnsiChar(#AData[0]);
Where AData is array of byte;
Now, how could I do reverse conversion, so that I can put my own string to this AData array of byte instead of the one that is currently there, but without modifying any other data inside of the array, and then convert it to TBytes?
Is there a way to put something like AData[0]:=PAnsiChar(mystring); (this one is wrong, of course...) and then convert it with CopyBytes to TBytes, or maybe convert first to TBytes and then replace it there...?
Either way would be useful.
Indy has BytesToString() and ToBytes() functions for converting between String and TIdBytes. And if you are using Indy 10.6+, the IIdTextEncoding interface has GetBytes() and GetString() overloads that take PByte parameters, so you can use TBytes and array of Byte arrays without having to copy to/from TIdBytes.

How to pass array of shortstring to a method

I would like to make a procedure that take array of shortstring as argument
procedure f(const a, b: Array of shortstring);
I would like to call this with arrays of known length and shortstrings of known length e.g.
var
A, B: array[1..2] of string[5];
C, D: array[1..40] of string[12];
begin
f(A,B);
f(C,D);
end;
This result in an compiler error E2008 Incompatible types.
Why is that? Can I write a procedure that can take arrays of shortstring (any length of arrays/strings)?
Why use shortstring?
The shortstings are fields in an existing record. There are alot of these record with thousand of shortstrings. In an effort to migrate data from turbo power B-Tree Filer to SQL databases one step is to convert the record to a dataset, and the back to a record, to confirm all fields are converted correctly both directions. I have been using CompareMem on the records to check this, but it does not provide enough information as to which field a conversion error is in. Thus a small program was created, which from the record definition can generate code to compare the two records. It was for this code generator I needed a function to compare shortstrings. It ended up using CompareMem on the shortstrings.
A ShortString is 0 to 255 characters long. The length of a ShortString can change dynamically, but memory is a statically allocated 256 bytes, the first byte stores the length of the string, and the remaining 255 bytes are available for characters, whilist string[5] declared in this way allocate only as much memory as the type requires (5 byte + 1 byte for length).
you could use type
type
MyString = string[5];
...
procedure f(const a, b: Array of MyString);
...
var
A, B: array[1..2] of MyString;
begin
f(A,B);
end;
In a similar situation I've used the following:
type
TOpenArrayOfOpenString = record
strict private
FSizeOfString: Integer;
FpStart: PChar;
FArrayLength: Integer;
function GetItemPtr(AIndex: Integer): PShortString;
public
constructor Init(var AFirstString: Openstring; AArrayLength: Integer);
function Equals(const AArray: TOpenArrayOfOpenString): Boolean;
property SizeOfString: Integer read FSizeOfString;
property pStart: PChar read FpStart;
property ArrayLength: Integer read FArrayLength;
property ItemPtrs[AIndex: Integer]: PShortString read GetItemPtr; default;
end;
{ TOpenArrayOfOpenString }
constructor TOpenArrayOfOpenString.Init(var AFirstString: Openstring; AArrayLength: Integer);
begin
FSizeOfString := SizeOf(AFirstString);
FpStart := #AFirstString[0]; // incl. length byte!
FArrayLength := AArrayLength;
end;
function TOpenArrayOfOpenString.Equals(const AArray: TOpenArrayOfOpenString): Boolean;
begin
Result := CompareMem(pStart, AArray.pStart, SizeOfString * ArrayLength);
end;
function TOpenArrayOfOpenString.GetItemPtr(AIndex: Integer): PShortString;
begin
Result := PShortString(pStart + AIndex * SizeOfString);
end;
You could use it like this:
procedure f(const a: TOpenArrayOfOpenString);
var
i: Integer;
begin
for i := 0 to Pred(a.ArrayLength) do
Writeln(a[i]^);
end;
procedure Test;
var
A: array[1..2] of string[5];
C: array[1..40] of string[12];
begin
f(TOpenArrayOfOpenString.Init(A[1], Length(A)));
f(TOpenArrayOfOpenString.Init(C[1], Length(C)));
end;
It's not as elegant as a solution built into the language could be and it is a bit hacky as it relies on the fact/hope/... that the strings in the array are laid out contiguously. But it worked for me for some time now.
type
shortStrings =array[1..2] of string[5];
...
a,b : shortString;
..
procedure rock(a,b : shortStrings);
..
You are combining two different kinds of open array.
First, there is the classic Turbo Pascal "string" (also called "openstring" in Delphi IIRC) which is essentially string[255]. As string[255] is a superset of all shortstrings, the open array aspect simply converts all shortstring types to it.
The "array of xx" syntax is the Delphi (4+?) open array. It is an open array of any type, not just strings, and the syntax to call it is f(nonarrayparam,[arrayelement0,arrayelement1]);
Somehow you seem to mix both syntaxes, and even aggrevate it by adding CONST which sollicits pass by reference and excludes conversions.
I think you assume shortstring has an performance advantage. It has, in some cases. Open array is not one of those cases. Not even in TP :-)

How do I check if an array contains particular index in Pascal?

E.g. I have this array:
type
OptionRange = array[ 1..9 ] of integer;
How do I check if array[x] exists?
Actually, I want to limit user input with the array index. Am I doing the wrong thing? Is there a better practical solution?
In Free Pascal and the Borland dialects (and perhaps elsewhere as well), you can use the Low and High functions on the array type or a variable of the array type. I see this used most often to determine the bounds for for loops:
var
range: OptionRange;
i: Integer;
begin
for i := Low(range) to High(range) do begin
range[i] := GetOptionRangeElement(i);
end;
end;
You can also define a subrange type and then use it to define both the array and the index variables you use on the array:
type
OptionRangeIndex = 1..9;
OptionRange = array[OptionRangeIndex] of Integer;
var
range: OptionRange;
i: OptionRangeIndex;
Then, when you have range checking enabled (assuming your compiler offers such a feature) and you use a value that's outside the range for OptionRange indices, you'll get a run-time error that you can catch and handle however you want.
I'm not really sure what an option range is or why an array of nine integers would be used to represent one, but I figure that's a name-selection issue.

Resources