Why can't I change pointers for methods? - delphi

I'm trying to change the value of a TNotifyEvent, same I do with integers with a pointer to it's value. But, when I try to do it to TNotifyEvent I get an exception (access violation). How can I do this ?
My goal is change the value of an external variable. Here is a code to explain here I get the error:
procedure TForm11.Button3Click(Sender: TObject);
var
LInteger: integer;
LPinteger: ^Integer;
LPNotify: ^TNotifyEvent;
LNotify: TNotifyEvent;
begin
LInteger := 10;
LPinteger := #LInteger;
LPinteger^ := 20; //It's ok, it make both variables with the same value
Caption := Format('Pointer: %d | Value: %d', [LPinteger^, LInteger]);
LNotify := Button3Click;
LPNotify := #LNotify;
LPNotify^ := nil; //Here I get the error
Caption := Format('Pointer: %d | Value: %d', [Integer(LPNotify), Integer(#LNotify)]);
end;
Tks

There is a different treatment of the # operator for variables of procedural type. The documentation says:
In some situations it is less clear how a procedural variable should
be interpreted. Consider the statement:
if F = MyFunction then ...;
In this case, the occurrence of F results in a function call; the
compiler calls the function pointed to by F, then calls the function
MyFunction, then compares the results. The rule is that whenever a
procedural variable occurs within an expression, it represents a call
to the referenced procedure or function. In a case where F references
a procedure (which doesn't return a value), or where F references a
function that requires parameters, the previous statement causes a
compilation error. To compare the procedural value of F with
MyFunction, use:
if #F = #MyFunction then ...;
#F converts F into an untyped pointer variable that contains an
address, and #MyFunction returns the address of MyFunction.
To get the memory address of a procedural variable (rather than the
address stored in it), use ##. For example, ##F returns the address of
F.
This is your scenario. Instead of
LPNotify := #LNotify;
you need
LPNotify := ##LNotify;
If you compiled with the typed address option enabled, then the compiler would have rejected LPNotify := #LNotify as a type mismatch. I can find no sound explanation for Embarcadero continuing with typed address defaulting to disabled.
The final line of your function should probably be
Caption := Format(
'Pointer: %d | Value: %d',
[Int64(#LNotify), Int64(TMethod(LPNotify^))]
);
I'm assuming that you use the 32 bit compiler for the Int64 casts.

Related

Data Type Conversion of Delphi var Parameter

I have a function that has a var Extended parameter. The compiler complains if I try to use a Double type argument when I call the function. However, if I pass the Extended value back as the function Result (and assign to a Double variable), then the compiler is happy.
Is this expected? If so, is there any way to fool the compiler to reduce the precision of the parameter to match the argument?
function foo1(var e: extended): boolean;
begin
e := 0.0;
Result := true;
end;
function foo2(): extended;
begin
Result := 0.0;
end;
procedure CallFoo();
var
d: double;
begin
if foo1(d) then Exit; // compiler complains
d := foo2; // compiler happy
end;
var parameters require the actual argument to match exactly. You can only pass an Extended variable.
One option for you is to introduce overloads for each of the floating point types you need. But my advice is to stop using Extended and switch to Double. The Extended type only exists on 32 Intel platforms and very seldom offers any benefit over Double. On the contrary, its unusual size of 10 bytes often leads to poor performance due to misalignment and inefficient cache usage.

How to Call Function from Fortran DLL in delphi?

I have problem with delphi code. I want to call the function in delphi to process the fortran function, but I have transferred to DLL. Here is code Fortran
SUBROUTINE c_zsn(m,d,k,f,zsn,nf)
! Specify that the routine name is to be made available to callers of the
! DLL and that the external name should not have any prefix or suffix
!MS$ ATTRIBUTES DLLEXPORT :: c_zsn
!MS$ ATTRIBUTES ALIAS:'c_zsn' :: c_zsn
!MS$ ATTRIBUTES VALUE :: m,d,k,nf
!MS$ ATTRIBUTES REFERENCE :: f,zsn
IMPLICIT NONE
INTEGER :: nf,i
REAL(KIND(0.D0)) :: m,d,k,f(0:(nf-1)),zsn(0:(nf-1)),om,pi
COMPLEX(KIND(0.D0)) :: j
j = (0.d0, 1.d0)
pi = 4.d0 * datan(1.d0)
do i=0,nf-1
om = 2.d0*pi*f(i)
zsn(i) = abs(-om**2*m-j*om*d+k)
end do
END SUBROUTINE
and here is code for the Delphi that I used
procedure TForm1.Button2Click(Sender: TObject);
type tarray=array[0..10]of double;
var a:thandle;
fcn:function(s,d,f:double;var g,h:tarray;n:integer):double;
e,f,d,g,h,i,j:double;
k:tarray;
l,o:tarray;
n,m:integer;
begin
a:=LoadLibrary('dllsub.dll');
if (A=0) then
begin
Application.MessageBox('Failed to open library','Error', MB_OK or MB_ICONEXCLAMATION);
exit;
end;
#fcn:=GetProcAddress(a, 'c_zsn');
if #b=nil then
begin
ShowMessage('Failed to open function');
exit;
end;
e:=2;
f:=200;
d:=0.01;
n:=10;
for m:=0 to n do
l[m]:=m;
fcn(e,d,f,l,o,n); // this is the problem
FreeLibrary(a);
end;
I cannot call the function (the bold one).
I would declare the function like this:
procedure c_zsn(
m: Double;
d: Double;
k: double;
f: PDouble;
zsn: PDouble;
n: Integer
); stdcall; external 'dllsub.dll';
You do need to specify the calling convention. You omitted that which meant that your code used the default register calling convention which is private to Delphi. I'm guessing that the calling convention is stdcall but it may be cdecl. Check the compiler documentation to be sure.
And it is not at all obvious to me why you declared a function that returns a double. The Fortran does not do that.
Other than that I changed the parameter names to match the Fortran code. I also switched to load time linking which is easier to code against. You can skip the calls to LoadLibrary and GetProcAddress and let the loader resolve the linkage.
Finally, I think the two arrays are better passed as PDouble (that is pointer to Double) rather than committing at compile time to fixed size arrays.
You can call the function like this:
c_zsn(e,d,f,#l[0],#o[0],n);
Do note that you have declared arrays of length 11 rather than length 10. Did you mean to do that? I think you should declare the arrays like this:
var
l, o: array [0..9] of Double;
One final point is that the Fortran code is very simple. It would be very easy indeed to translate it into Delphi.

Algol60 passing integer element of array as parameter - error bad type

I have following problem.
When I try to run the code with arun file.obj (I have compiled with algol.exe file)
BEGIN
INTEGER PROCEDURE fun(tab,index,lower,upper);
INTEGER tab,index,lower,upper;
BEGIN
INTEGER t;
text (1, "Start");
t := 0;
FOR index := lower STEP 1 UNTIL upper DO
t := t + tab;
fun := t;
END;
INTEGER ARRAY t[1:10];
INTEGER i,result,lower,upper;
lower := 1;
upper := 10;
FOR i := 1 STEP 1 UNTIL 10 DO
t[i] := i;
i := 1;
result := fun(t[i],i,lower,upper);
END FINISH;
I am still getting error:
ERROR 3
ADD PBASE PROC LOC
07D4 0886 1 13
083A 0842 0 115
The compiler I use is "The Rogalgol Algol60" product of RHA (Minisystems) Ltd.
Error 3 means "3 Procedure called where the actual and the formal parameter types do not match."
But I do not understand why. The reason of error is t[i] (If I change to i - it is ok).
Someone knows what I am doing wrongly?
I compile the code on the dosbox (linux)
Problem is that the index of the integer array that you're passing to your procedure isn't the same as the integer that he's expecting. I can't remember what an integer array is full of, but I guess it isn't integers... Have to admit I never use them, but can't remember why. Possibly because of limitations like this. I stick to Real arrays and EBCDIC ones.
You can almost certainly fix it by defining a new integer, j; inserting "j := t[i];" before your invocation of 'fun'; then invoking 'fun' with 'j' rather than t[i].
BTW you may want to make the array (and the 'for' loop) zero-relative. ALGOL is mostly zero-relative and I think it may save memory if you go with the flow.
Let me know if this helps....

Delphi:Is a temporary PChar guaranteed to have the same value after a string variable pointed by the PChar is changed?

The following code runs as expected in my system, but I'm not sure whether the P variable is guaranteed to have the same value after MyArray[0] is changed to a new value.
procedure Test;
var
MyArray: array of string;
P : PChar;
begin
SetLength(MyArray, 2);
MyArray[0] := 'ABCD';
MyArray[1] := '1234';
// Is P guaranteed to have the same value all the time?
P := PChar(MyArray[0]);
MyArray[0] := MyArray[1];
MyArray[1] := P;
WriteLn(MyArray[0]);
WriteLn(MyArray[1]);
end;
Your code is technically invalid. It only runs at all due to an implementation detail that should not be relied upon.
Let's take a look at the pertinent section of code:
P := PChar(MyArray[0]);
MyArray[0] := MyArray[1];
MyArray[1] := P;
First we make P point to the first character of MyArray[0]. Then we assign to MyArray[0]. At this point there's no reason for the string buffer that P points at to be kept alive. No string variable refers to it. Its reference count has gone to zero, and so it should be deallocated. Which makes the subsequent use of P invalid.
In which case, why does your code run? Because the strings you use happen to be literals. And so they are stored with reference count equal to -1 and bypass the normal heap allocation routines used by strings. But if you were to use string values that were not literals, then what I describe in the paragraph above would come to pass. And I expect that your real code doesn't use literals.
So your actual question is somewhat moot. The P pointer just points at a block of memory. It remains pointing at the same block of memory until you modify the pointer. If you modify the contents of the block of memory, then P will see those modifications if you de-reference it. It's just a pointer like any other pointer.
You need to take care using a PChar variable. In your use, it is an unmanaged pointer into a compiler managed object. That provides lots of scope for errors and you've fallen into the trap. If you want a copy of a string, take a copy into another string variable.
It seems that type casting from a string to PChar is different than taking its address.
See the code below, it will take the address of string.
procedure Test;
var
MyArray: array of string;
P : ^String;
begin
SetLength(MyArray, 2);
MyArray[0] := 'ABCD';
MyArray[1] := '1234';
// take the pointer
P := #MyArray[0];
WriteLn(MyArray[0]);
WriteLn(MyArray[1]);
WriteLn(P^);
// when content of array changes, P^ will change as well
MyArray[0] := 'HELLO';
WriteLn(P^);
end;

How to use TComparer to sort a list of records?

I have a record that holds data about a file:
TYPE
RFile= record
public
FileName : string;
Resolution : Integer;
FileSize : Cardinal;
Rating : Byte;
end;
PFile= ^RFile;
And I keep a list of these files/records in a TList<>
TFileList= class(TList<PFile>)
procedure SortByFilename;
procedure SortByRating;
procedure SortByResolution;
procedure SortBySize;
end;
I have methods like SortByFilename, SortBySize, etc in which I sort the list.
I do "classic" sorting.
Now I want to upgrade to the new-and-cool System.Generics.Defaults.TComparer.
From what I understand I need to assign a comparer to my TFileList, like
TIntStringComparer = class(TComparer<String>)
public
function Compare(const Left, Right: String): Integer; override;
end;
How do I do this?
How do I deal with one comparer for each data field (filename, filesize, resolution)?
Update:
This code compiles but I have an EIntegerOverflow because the FileSize is a cardinal while I return an integer (diff between two cardinals).
Sort(TComparer<PFile>.Construct(
function(CONST A,B: PFile): integer
begin
Result:= A.FileSize - B.FileSize;
end
));
When you write a comparer for a numeric type, you should never use subtraction, even if your data type is signed.
Indeed, try to compare a = 100 and b = -2147483640 as Integers; clearly a > b, but subtraction will yield the wrong result.
Instead, you should always do something similar to
if a = b then
Result := 0
else if a < b then
Result := -1
else
Result := 1;
But Delphi's RTL already contains functions for this: there are several CompareValue overloads in the Math unit (for different types of integers and floats -- but, unfortunately, not for Cardinals).
Thus, although your snippet will work "most of the time" if you do
Result := Integer(A.FileSize) - Integer(B.FileSize)
this is not good enough: For one thing, not every Cardinal will fit in a Integer. Also, as noted above, subtraction is not the way to go.
In your case, you can simply use the if thing above directly, or you could create a new CompareValue overload for Cardinals. Or, you could do
Result := CompareValue(Int64(A.FileSize), Int64(B.FileSize)).
(Also, as others have stated in comments, you should reconsider if it is wise to use a Cardinal to store a file size in the first place. If you upgrade this to an Int64 or UInt64 you can write simply
Result := CompareValue(A.FileSize, B.FileSize).)

Resources