Modern Delphi to C strings for passing to C DLLs - c

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.

Related

delphi x32 and x64, (typecast?) array of bytes into (wide)string

I receive from an external function(dll) widestring in array of bytes.
to convert the bytes to string, I use the following simple code:
mystrvar := widestring(buffer);
where buffer is the byte array.
when compile for 32bits, it works great, but when compile this for 64bits code returns empty string while the buffer(byte array) is the same in both cases.
the same happens when use
mystrvar := string(buffer);
while pchar(buffer) or pwchar(buffer) works.
Reason why I do not use pwchar is;
pwchar(buffer) breaks by 00 while widestring(buffer) does not. This buffer(bytearray) contains stringlist which is delimited by (00).
btw, excuse me for bad english.
Use
SetString(mystrvar,buffer,LENGTH(buffer) DIV SizeOf(WideChar));
assuming that
VAR
mystrvar : WideString;
buffer: ARRAY OF BYTE;
and that "buffer" does not contain a trailing zero-terminating set of bytes. Also note that "buffer" is an array of BYTEs and thus the length of the buffer is twice that of the length of the resulting string.
Assuming your array is double Null Terminating you can use this:
while Buffer^ <> WideNull do
begin
value := PWChar(Buffer);
CommaText := CommaText + value + ',';
Inc(Buffer, (Length(value) + 1));
end;

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 can I assign a value to a Char array?

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';

Delphi TBytes - how to copy?

Point is opimization here.
Now:
type TSomeClass=class(TObject)
private
DataWrite: TBytes;
...
end;
Function TSomeClass.GetPacket: TBytes;
begin
SetLength(Result, Length(DataWrite));
Move(DataWrite[0],Result[0],Length(DataWrite));
end;
What I want to achieve:
Function TSomeClass.GetPacket: TBytes;
begin
Result := DataWrite;
end;
Because Arrays in Delphi are pointers to first element, the latter only and only writes 4 bytes so it is MUCH faster. Is this correct?
The one thing you need to be aware of is that different from strings, dynamic arrays are not "copy-on-write".
If you assign a string, or a dynamic array, only the pointer to the data on the heap is copied and the reference count is incremented.
But with a string, if you then write into a string (e.g. s[1] := 'a') which has a reference count > 1, the compiler will emit code which makes sure that the string is copied first. This is not the case with dynamic arrays:
var
s, t: string;
a, b: TBytes;
begin
s := 'abc';
t := s;
t[2] := 'X';
WriteLn(s); //still abc
a := TBytes.Create(1, 2, 3);
b := a;
b[1] := 0;
WriteLn(a[1]); // is now 0 not 2!
So in case of your code, if you change the contents of DataWrite after GetPacket was called, the change will be visible in the TBytes that GetPacket returned.
For the code where you actually make a copy of the array, instead of calling SetLength And Move, you can use:
function TSomeClass.GetPacket: TBytes;
begin
Result := Copy(DataWrite, 0, High(Integer));
end;
That will work but note that you are now working on the same byte array in client code that calls GetPacket. This might be a bad idea. Consider some network library that does some additional compression or encryption on the byte array. This creates a lot of possibilites to interact with your class without using the exposed interface - which is bad. Thus IMHO copying is the better option here.
BTW: How big are the arrays we are talking about here?

Resources