Pointer of ^Pchar to array of PChar - delphi

when i migrate from Delphi 6 to Delphi 10.2 Tokyo
i get error when i try to casting pointer of ^PChar to array of PChar
type
PServEnt = ^TServEnt;
TServEnt = packed record
s_name: PChar; // official service name
s_aliases: ^PChar; // alias list
s_port: Smallint; // protocol to use
s_proto: PChar; // port #
end;
function TIdStackWindows.WSGetServByPort(
const APortNumber: Integer): TIdStrings;
var
ps: PServEnt;
i: integer;
p: array of PChar;
begin
Result := TIdStringList.Create;
p := nil;
try
ps := GetServByPort(HToNs(APortNumber), nil);
if ps <> nil then
begin
Result.Add(ps^.s_name);
i := 0;
p := Pointer(ps^.s_aliases); // get error Incompatible types: 'Dynamic array' and 'Pointer'
while p[i] <> nil do
begin
Result.Add(PChar(p[i]));
inc(i);
end;
end;
except
Result.Free;
end;
end;
this code working well at Delphi 2010 ,how to make it correct at Delphi 10.2 Tokyo

The error message is correct, and if the code compiled in earlier versions of Delphi then that was because those earlier versions of the compiler were deficient.
A dynamic array is more than just a pointer to the first element. It also encapsulates the meta data which stores the length of the array, and the reference count. Your cast is therefore not valid. You got away with this invalid code because you did not attempt to access this meta data, but that's as much by chance as through intention.
Don't attempt to cast to a dynamic array. Instead use pointer arithmetic. For instance:
function TIdStackWindows.WSGetServByPort(
const APortNumber: Integer): TIdStrings;
var
ps: PServEnt;
p: PPChar;
begin
Result := TIdStringList.Create;
try
ps := GetServByPort(HToNs(APortNumber), nil);
if ps <> nil then
begin
Result.Add(ps^.s_name);
p := PPChar(ps^.s_aliases); // cast needed due to Indy record type's use of un-nameable type
while p^ <> nil do
begin
Result.Add(p^);
inc(p);
end;
end;
except
Result.Free;
raise;
end;
end;
I changed the type declaration of the alias list to PPChar to avoid incompatible type errors when assigning to the local variable of that type.
Note also that I have corrected your exception handling which was previously swallowing exceptions and returning an invalid object reference.

Related

Delphi. filewrite, error - E2036 Variable required

I didn't use filewrite a lot of time.
I made this procedure and received the next error: E2036 Variable required (on flen variable in filewrite).
procedure TForm1.WriteFN(const PIN: integer);
var
lFile: integer;
flen : integer;
begin
flen := 2;
lFile := FileOpen('/sys/pins', fmOpenWrite);
try
if filewrite(lFile, PChar(IntToStr(PIN)), flen) = -1 then
raise Exception.CreateFmt('Cannot export PIN%d', [PIN]);
finally
fileclose(lFile);
end;
end;
How to solve this problem?
Delphi Rio, Win10.
The second parameter of FileWrite() is an untyped const. Whatever you pass to it gets passed by reference, and as such you have to give it a real variable to refer to. In this case, you can simply dereference the PChar pointer, that will let the parameter reference the 1st Char in the temporary String you are creating, eg:
FileWrite(lFile, PChar(IntToStr(PIN))^, flen)
Note, however, that FileWrite() operates on raw bytes, not on string characters. You are telling FileWrite() to write exactly 2 bytes, which may or may not work properly, depending on which version of Delphi you are using, and what value the PIN contains.
Try this instead:
procedure TForm1.WriteFN(const PIN: integer);
var
lFile: integer;
flen : integer;
s: AnsiString;
begin
s := AnsiString(IntToStr(PIN));
flen := Length(s);
lFile := FileOpen('/sys/pins', fmOpenWrite);
if lFile = -1 then
raise Exception.CreateFmt('Cannot create file to export PIN%d', [PIN]);
try
if FileWrite(lFile, PAnsiChar(s)^, flen) = -1 then
raise Exception.CreateFmt('Cannot write to file to export PIN%d', [PIN]);
finally
FileClose(lFile);
end;
end;
If you are using a modern version of Delphi, consider using TFile.WriteAllText() instead:
uses
..., System.IOUtils;
procedure TForm1.WriteFN(const PIN: integer);
begin
try
TFile.WriteAllText('/sys/pins', IntToStr(PIN));
except
Exception.RaiseOuterException(
Exception.CreateFmt('Cannot export PIN%d', [PIN])
);
end;
end;

Using Delphi 'with' keyword in brackets

I need to add some initialization without adding a variable declaration to the var section, so I try to do that by doing this:
sql.columns.Add(with TColumn.Create do
begin
ColName := 'Price';
As_ := 'MaxPrice';
end);
but Delphi raises an error while compiling.
Any ideas?
TList<T>.Add() expects a fully constructed object as input. The with keyword does not provide you access to the object it is operating on. You need to use a variable, whether you like it or not:
var
col: TColumn;
col := TColumn.Create;
col.ColName := 'Price';
col.As_ := 'MaxPrice';
sql.columns.Add(col);
The alternative is to write a function instead, and use its special Resultvariable:
function MakeColumn(const AName, AAs: string): TColumn;
begin
Result := TColumn.Create;
Result.ColName := AName;
Result.As_ := AAs;
end;
sql.columns.Add(MakeColumn('Price', 'MaxPrice'));

E2251 Ambiguous overloaded call to 'TextToFloat'

My code:
function Str2Dbl(const str: string; var v: double): boolean;
var
dp: integer;
cstr: string;
xv: extended;
begin
if FormatSettings.DecimalSeparator <> '.' then
begin
dp := pos('.', str);
if dp <> 0 then
begin
cstr := str;
cstr[dp] := FormatSettings.DecimalSeparator;
end
else
cstr := str;
end
else
cstr := str;
if cstr <> '' then
result := TextToFloat(#cstr[1], xv, fvExtended, FormatSettings)
else
result := false;
if result then
v := xv;
end;
In Delphi 10.2 it gives an error:
[dcc32 Error] commutil.pas(1005): E2251 Ambiguous overloaded call to 'TextToFloat'
System.SysUtils.pas(18332): Related method: function TextToFloat(PWideChar; var; TFloatValue; const TFormatSettings): Boolean;
System.SysUtils.pas(18515): Related method: function TextToFloat(PAnsiChar; var; TFloatValue; const TFormatSettings): Boolean;
I do not understand how to fix this error!!!
The error is because #cstr[1] has type Pointer and the overload resolution does not know which overload (PAnsiChar or PWideChar) you want.
In any case, using #cstr[1] is wrong in general, and will fail with a runtime error if cstr is empty. Use PChar(cstr) instead. This will also allow the overload resolution to work.
I appreciate that you test whether or not cstr is empty, but that test is not necessary if you use the magic of the PChar(...) cast. Even when the string is empty, PChar(...) gives a valid pointer to a null-terminated character array.
The documentation is worth consulting on this subject. The key statement is:
PChar(S) always returns a pointer to a memory block; if S is empty, a pointer to #0 is returned.
So, you will be able to replace:
if cstr <> '' then
result := TextToFloat(#cstr[1], xv, fvExtended, FormatSettings)
else
result := false;
which did not compile anyway, with:
result := TextToFloat(PChar(cstr), xv, fvExtended, FormatSettings)
which does compile and avoids that if statement boiler-plate.
Aside
I initially expected that enabling typed address operator with {$T+} would make #cstr[1] be a typed pointer and help the overload resolution. However, that is not the case. It was surprising to me that this program compiles:
{$T+}
var
PA: PAnsiChar;
PW: PWideChar;
s: string;
begin
PA := #s[1];
PW := #s[1];
end.
The linked documentation says:
When # is applied to a variable reference in the {$T+} state, the type of the result is ^T, where T is compatible only with pointers to the type of the variable.
This seems to be contradicted by

Has function initialization code changed from Seattle to Tokyo?

I am in the process of upgrading code from Delphi 10 Seattle to Delphi 10.2 Tokyo and get a lot of H2077 hints Value assigned to ... never used on assignments.
(Even in places where these were explicitly added in the past to get rid of 'may not have a value' warnings).
These are all function initialized like:
Result := 0;
...
Or:
Result := ftType1; // where ftType1 is an enumerated type
...
Did the compiler get smarter in detecting these or has something changed regarding the initial return values of functions?
We have always had these hints 'on', and I always build (not compile).
Example function (1) that builds without hints in Seattle,
but gives the hint H2077 Value assigned to 'GetDatabaseDialect' not used on the first Result := 0 line in Tokyo.
function GetDatabaseDialect(DBName, User, Pswd: string) : integer;
var
status: array[1..19] of longint;
szDbName, szDbParam: PANSIChar;
dbHandle : pointer;
rslt: longint;
lDPBBuffer : ANSIString;
lDPBLength : integer;
cItem: ANSIChar;
szRslt: PANSIChar; //array[0..IBResultBufferSize-1] of ANSIChar;
begin
Result := 0;
dbHandle := nil;
// init database parameter block with version number
lDPBBuffer := '';
SetLength(lDPBBuffer, 1);
lDPBBuffer[1] := ANSIChar(isc_dpb_version1);
lDPBLength := 1;
// fill Database Parameter Buffer with user name/password
lDPBBuffer := lDPBBuffer +
ANSIChar(isc_dpb_user_name) +
ANSIChar(Length(User)) +
ANSIString( User );
Inc(lDPBLength, 2 + Length(User));
lDPBBuffer := lDPBBuffer +
ANSIChar(isc_dpb_password) +
ANSIChar(Length(Pswd)) +
ANSIString( Pswd );
Inc(lDPBLength, 2 + Length(Pswd));
//Pointers naar naam + buffer
szDbName := PANSIChar(ANSISTring(DBName));
szDbParam := PANSIChar( lDPBBuffer );
// attach to the database and set dialect
rslt := isc_attach_database(#status, 0, szDbName, #dbHandle, lDPBLength, szDbParam);
if rslt <> 0 then
raise EDatabaseError.Create('Error attaching database! ISC# ' + IntToStr(rslt));
//Haal sql dialect op
szRslt := AllocMem(1000);
try
FillChar( szRslt^, 1000, 0);
cItem := ANSIChar( isc_info_db_SQL_dialect );
rslt := isc_database_info(#status, #DBHandle, 1, #cItem, 1000, szRslt);
if rslt <> 0 then
raise EDatabaseError.Create('Error retrieving database info ! ISC# ' + IntToStr(rslt));
Result := Ord(szRslt[3]); //3e positie is dialect
finally
FreeMem(szRslt);
end;
// Drop the connection to the database
rslt := isc_detach_database(#status, #dbHandle);
if rslt <> 0 then
raise EDatabaseError.Create('Error detaching database! ISC# ' + IntToStr(rslt));
end;
Example (2) from a third party library that does not seem to be optimized for Tokyo,
illustrating the case with enumerated types:
H2077 Value assigned to 'TppTemplate.StreamType' not used
Note that changing the assignment to Result := ftASCII; does not make the hint go away (my initial assumption that it was associated with the first enumeration value was incorrect).
type TppFormatType = (ftBinary, ftASCII);
function TppTemplate.StreamType(aStream: TStream): TppFormatType;
var
lSavePos: Integer;
begin
{save stream position}
lSavePos := aStream.Position;
Result := ftBinary;
try
ComputeOffsetFromStream(aStream);
aStream.Seek(FOffset, soBeginning);
if IsValidASCIISignature(aStream) then
Result := ftASCII
else if IsValidBinarySignature(aStream) then
Result := ftBinary
else
raise EInvalidTemplateError.Create(ppLoadStr(49));
finally
{restore stream position}
aStream.Seek(lSavePos, soBeginning);
end;
end; {function, StreamType}
The common denominator seems to be the Result assignments being in try/finally blocks.
Consider this code with a minimal reproduction of your scenario:
function Bar: Boolean;
begin
Result := Random<0.5;
end;
function Foo: Integer;
begin
Result := 0;
if Bar then
Result := 1
else
raise Exception.Create('');
end;
The compiler, even older versions, emits the following hint:
[dcc32 Hint]: H2077 Value assigned to 'Foo' never used
This is reasonable. The first assignment to Result is pointless and can be removed.
Now consider this variation:
function Foo: Integer;
begin
Result := 0;
try
if Bar then
Result := 1
else
raise Exception.Create('');
finally
end;
end;
Older versions of the compiler no longer emit the hint, but the latest version of the compiler does. This should be considered a compiler defect, for older versions. The two variants of Foo shown above are semantically identical. The compiler would be justified in generating identical code.
As you surmise, the assignment being inside the try/finally block is necessary to trigger the defect in previous versions.
We can conclude that the Embarcadero developers have fixed a defect in Tokyo. You can resolve the hints by removing the spurious initial assignments.
Of course, if your code is to be compiled by older versions of the compiler, as well as by new versions, then you are in a bind. With the code as it stands now, a hint is emitted by new versions of the compiler. Remove the initial assignment and a hint is emitted by old versions of the compiler.

How to Convert a TParams (TQuery) Object to TParameters (ADO) object.?

I am working on a legacy code which contains some TQuery components. I was trying to create a function which convert the TQuery Parameters into TParameters so that i can assign them into the Parameters property of an ADO Component (Like ADOQuery or ADODataSet).
I tried the following which i got from internet.
function ConvertToADOParms(Owner: TADODataset; aParams: TParams): TParameters;
var i: integer;
begin
// Convert a standard TParams object to an ADO-specific TParameters object
Result :=nil;
try
if aParams = nil then exit;
Result :=TParameters.create( Owner, TParameter);
for i:=0 to aParams.count - 1 do
begin
if aParams[i] = nil then continue;
with Result.AddParameter do
begin
Name := aParams[i].Name;
Datatype :=aParams[i].DataType;
Direction :=TParameterDirection(aParams[i].ParamType);
Size :=aParams[i].size;
Value :=aParams[i].value;
end;
end;
except
on e:exception do
begin
Result :=nil;
showmessage('Could not convert standard parameter object to ADO parameter object: '+e.message);
end;
end;
end;
But i am getting Invalid Class Typecast Error. When i debug the code i found that the error occurs at this function in ADODB unit
function TParameters.GetCommand: TADOCommand;
begin
Result := GetOwner as TADOCommand;
end;
Help Please. I am Using Delphi 5
I don't make much sense of the function prototype. It requests an owner for the collection that is returned by the function and as such should IMHO be independent.
I would simply get rid of that and operate directly with the passed ADO object. For example:
procedure FillParamsADO(Params: TParams; Dataset: TADODataset);
var
i: Integer;
begin
Dataset.Parameters.Clear;
for i := 0 to Params.Count-1 do
begin
with Dataset.Parameters.AddParameter do
begin
Name := Params[i].Name;
DataType := Params[i].DataType;
Direction := TParameterDirection(Params[i].ParamType);
Size := Params[i].Size;
Value := Params[i].Value;
end;
end;
end;

Resources