Delphi global variable setter - delphi

I got problem with setting global variable in Delphi unit:
unit SomeUnit;
...
interface
...
var
variable1: String;
...
implementation
procedure TSomeForm.SetNewVersion(variable1: String);
begin
variable1 := variable1; //here is problem
end;
How to assign value to global variable if it have same name like local argument value from procedure? If that is some form value, that can be done like this:
TSomeForm.variable1 = variable1;
But problem is when variable is global variable in unit?
SomeUnit.variable1 = variable1; // this dont work

FWIW: The following works as one might expect:
var
SomeForm: TSomeForm;
variable1: string;
implementation
{$R *.dfm}
{ TSomeForm }
procedure TSomeForm.FormCreate(Sender: TObject);
begin
Assert(SomeUnit.variable1 = '');
SetNewVersion('1');
Assert(SomeUnit.variable1 = '1');
end;
procedure TSomeForm.SetNewVersion(variable1: string);
begin
SomeUnit.variable1 := variable1;
end;
To avoid such problems you might consider prefixing arguments with 'A' which is kind of semi-standard in Delphi. And while you're at it, make string parameters const:
procedure TSomeForm.SetNewVersion(const AVariable1: string);
begin
variable1 := AVariable1;
end;

You can solve your problem by either:
Choosing a different name for the parameter (or indeed the global variable). Personally I tend to use the name Value for the parameter of a setter method. Or,
Fully qualifying the name like this SomeUnit.variable1.
Note that the assignment operator is := and not =.
I would strongly recommend that you reconsider the design.
Should the variable really be global? If it is associated with a form instance as is implied by your setter, shouldn't it be a private member variable of the form class.
If the variable really is shared between instances, make the variable a private class variable and the setter a class method.
If your Delphi does not have class variables then move the global variable into the implementation section. As your code stands, there's no point in having a setter because you also expose the backing variable in the interface section.

Related

No overloaded version with these arguments - var param with read/write property

I have two overloaded procedures, of which I want to call the second one:
function ModifySyncProperties(AEWSItemId: String; AEvent: TcxSchedulerEvent; var AEWSChangeKey: String): Boolean; overload;
function ModifySyncProperties(AEWSItemId: String; ATTID: Integer; ASyncID: String; var AEWSChangeKey: String): Boolean; overload;
This fails with an error, though:
lSuccess := FDMExchange.ModifySyncProperties(lEWSId, lApp.EventID, lNewOutlookID, lApp.EWSItemChangeKey);
There is no overloaded version of 'ModifySyncProperties' that can be called with these arguments
This works, though:
lChangeKey := lApp.EWSItemChangeKey;
lSuccess := FDMExchange.ModifySyncProperties(lEWSId, lApp.EventID, lNewOutlookID, lChangeKey);
lApp.EWSItemChangeKey := lChangeKey;
Here are the types and variables:
lNewOutlookID,
lEWSID,
lChangeKey : String;
lApp : TEWSAppointment;
lSuccess : Boolean;
TEWSAppointment is defined in the interface section of another unit as this:
TEWSAppointment = class
private
FEventID: Integer;
...
FEWSItemChangeKey: String;
...
public
property EventID: Integer read FEventID write FEventID;
...
property EWSItemChangeKey: String read FEWSItemChangeKey write FEWSItemChangeKey;
...
end;
Why does the compiler not accept the read/write lApp property as a var parameter?
I'm using Delphi Rio 10.3.1.
The documentation for var parameters says:
If a routine's declaration specifies a var parameter, you must pass an assignable expression - that is, a variable, typed constant (in the {$J+} state), dereferenced pointer, field, or indexed variable to the routine when you call it.
A property does not meet this requirement.
Further, the documentation for properties calls this out explicitly:
Unlike fields, properties cannot be passed as var parameters, nor can the # operator be applied to a property.
At the implementation level, the ABI, var parameters are implemented by passed the address of the variable. Since a property does not necessarily have a backing variable with an address, the compiler cannot directly obtain such a variable address.
Furthermore, if the property getter or setter perform actions beyond reading or writing to a variable, then they would need to be called.
In principle at least the language could support the usage you wish by declaring a local variable and compiling this code:
localVar := myProperty;
foo(localVar);
myProperty := localVar;
However, the designers of the compiler did not implement this when properties were introduced to the language.

Are Delphi record constructors really needed?

SITUATION
I am studying "More Coding in Delphi" by Nick Hodges, and he is using a TFraction record to explain operator overloading. I have written by myself this record:
type
TFraction = record
strict private
aNumerator: integer;
aDenominator: integer;
function GCD(a, b: integer): integer;
public
constructor Create(aNumerator: integer; aDenominator: integer);
procedure Reduce;
class operator Add(fraction1, fraction2: TFraction): TFraction;
class operator Subtract(fraction1, fraction2: TFraction): TFraction;
//... implicit, explicit, multiply...
property Numerator: integer read aNumerator;
property Denominator: integer read aDenominator;
end;
Of course, I had to create a constructor because in Q (rationals) I must have a denominator that is not equal to zero.
constructor TFraction.Create(aNumerator, aDenominator: integer);
begin
if (aDenominator = 0) then
begin
raise Exception.Create('Denominator cannot be zero in rationals!');
end;
if ( (aNumerator < 0) or (aDenominator < 0) ) then
begin
Self.aNumerator := -aNumerator;
Self.aDenominator := -aDenominator;
end
else
begin
Self.aNumerator := aNumerator;
Self.aDenominator := aDenominator;
end;
end;
PROBLEM
Since the operator overloads return a TFraction, I am going to define an operation like this:
class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
var
tmp: TFraction;
begin
//simple algorithm of the sum
tmp := TFraction.Create(fraction1.Numerator*fraction2.Denominator+fraction1.Denominator*fraction2.Numerator, fraction1.Denominator*fraction2.Denominator);
tmp.Reduce;
//return the result
Result := tmp;
end;
As you can see here, I am creating a tmp that is returned from the function.
When I read Marco Cantu's book, he used another approach:
class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
begin
Result.aNumerator := (fraction1.Numerator*fraction2.Denominator+fraction1.Denominator*fraction2.Numerator);
Result.aDenominator := fraction1.Denominator*fraction2.Denominator;
end;
I have made some tests, and I see that both give me the correct result, but there is something that I cannot understand. In the first approach, I am declaring tmp and then I call the constructor so I can return a TFraction. In the second approach, I am instead not creating anything because records have an automatic constructor. The documentation, in fact, says that:
Records are constructed automatically, using a default no-argument
constructor, but classes must be explicitly constructed. Because
records have a default no-argument constructor, any user-defined
record constructor must have one or more parameters.
Here I have a user-defined record constructor. So:
Is the constructor call on tmp of the first approach not needed? If I want to call Reduce (which is a procedure), I need to create a variable. Is the Result just returning a copy of tmp without creating anything?
In the second approach, are Result.aNumerator and Result.aDenominator the parameters of the automatic created constructor?
A record constructor isn't anything magical. It's just an instance method like any other. You write:
tmp := TFraction.Create(...);
But you may equally well write it like this:
tmp.Create(...);
I personally find neither to be especially useful because I am used to constructor calling semantics for classes which allocate and default initialise memory, and then call the constructor method.
And especially the second variant grates with me because that looks like the classic mistake that novice Delphi programmers make when starting out and trying to create an instance of a class. That code would be no good if TFraction were a class, but for a record it is fine.
Were it me I would get rid of the record constructor and instead use a static class function that returned a newly minted instance of your record type. My convention is to name such things New. But these are matters of personal preference.
If you did that it would be declared like this:
class function New(aNumerator, aDenominator: Integer): TFraction; static;
It would be implemented like this:
class function TFraction.New(aNumerator, aDenominator: Integer): TFraction;
begin
Result.aNumerator := ...;
Result.aDenominator := ...;
end;
You would then call it like this:
frac := TFraction.New(num, denom);
But as I said, that's a matter of preference. If you like record constructors, feel free to stick with them.
You ask whether or not you can skip the constructor. In terms of allocation of the record, yes you can skip it. In terms of running the code in the constructor, only you can determine that. Do you want that code to execute or not?
If you wish that code to be executed, but don't want to use a temporary variable, then you can write the code like this:
class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
begin
Result.Create(
fraction1.Numerator*fraction2.Denominator + fraction1.Denominator*fraction2.Numerator,
fraction1.Denominator*fraction2.Denominator
);
Result.Reduce;
end;
Or if you preferred a static class function it would be:
class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
begin
Result := TFraction.New(
fraction1.Numerator*fraction2.Denominator + fraction1.Denominator*fraction2.Numerator,
fraction1.Denominator*fraction2.Denominator
);
Result.Reduce;
end;

Delphi closure and "old style" object type

Working with anonymous functions I found out that sometimes the compiler throws the following error:
E2555 Cannot capture symbol 'Self' when I try to use some field of the object.
I also noticed that this error seems to be related to the fact that a type, the method belongs to, is declared with "object" key word:
MyType = object()
field: integer;
...
end;
MyType.Method1()
begin
p := procedure
begin
// do something with field
end;
end;
However when a type is declared with "class" keyword it seems it works fine.
I know that to prevent the compiler error I can make a local copy of needed fields and use them inside the anonymous functions, but just to be sure - is "object" type cause of the compiler error and what's the reason of that?
Thanks in advance
As David properly analyzed it is because Self in your case is a value and not a reference. It cannot be moved to the internally created class - same is the case with any method arguments that are records. They also cannot be captured for the very same reason.
For arguments I usually copy them to a local variable which is being captured.
The same can be done for capturing Self in a record or object.
However if you capture it as value you get a copy and calling the closure later might have the "wrong" state because it captured a copy. To make it work similar you would have to capture a reference to Self but then for a value type you cannot guarantee that this reference is still valid when you call the closure.
You can see this in the following code:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TProc = reference to procedure;
PRecord = ^TRecord;
TRecord = object
y: Integer;
procedure Foo;
function GetProc: TProc;
end;
procedure TRecord.Foo;
begin
Writeln(y);
end;
function TRecord.GetProc: TProc;
var
this: PRecord;
begin
this := #Self;
Result :=
procedure
begin
this.Foo;
end;
end;
procedure Nested(var p: TProc);
var
r: TRecord;
begin
p := r.GetProc();
r.y := 0;
p();
r.y := 32;
p();
end;
procedure Main;
var
p: TProc;
begin
Nested(p);
p(); // <- wrong value because PRecord not valid anymore
end;
begin
Main;
end.
If you would capture TRecord it would do a local copy that it captures - you can see that it then will print 0 all the time.
Since Turbo Pascal object is long deprecated, it is reasonable for new language features not to have support for object.
There's not really any need to look much further. Since you are maintaining legacy code, I would not expect you to be introducing new language features like anonymous methods. Once you start introducing such language features, this no longer feels like legacy code maintenance and it would be reasonable to re-factor the code away from the legacy language features like object.
Having said that, I do note that the same restriction to capture applies in methods of advanced records.
type
TProc = reference to procedure;
TRecord = record
procedure Foo;
end;
procedure TRecord.Foo;
var
P: TProc;
begin
P :=
procedure
begin
Foo;
end;
end;
This fails to compile with error:
E2555 Cannot capture symbol 'Self'
Why does this code fail, even though advanced records are a fully supported modern feature?
I don't have an explanation for that and the documentation does not make it clear. A plausible explanation is that records are value types. When a local variable is captured, it is hoisted from being a stack allocated variable to a variable owned by an internally created class. That's possible for Self when Self is a reference to an instance of a class. But when Self is a value like a record, it is too late to hoist the record.
Or perhaps it is much more prosaic. Maybe the designers just implemented the most important use case (capturing Self for a class) and omitted the less widely used cases for expediency. It is frustrating that the documentation does not appear to give any rules for what can and cannot be captured.

Use Rtti to set method field

I'm using Delphi XE to write a base class, which will allow descending classes to have dll methods mapped by applying an annotation. However I get a typecasting error, which is understandable.
In essence the base class should look like this:
TWrapperBase = class
public
FLibHandle: THandle;
procedure MapMethods;
end;
procedure TWrapperBase.MapMethods;
var
MyField: TRttiField;
MyAttribute: TCustomAttribute;
pMethod: pointer;
begin
FLibHandle := LoadLibrary(PWideChar(aMCLMCR_dll));
for MyField in TRttiContext.Create.GetType(ClassType).GetFields do
for MyAttribute in MyField.GetAttributes do
if MyAttribute.InheritsFrom(TMyMapperAttribute) then
begin
pMethod := GetProcAddress(FLibHandle, (MyAttribute as TMyMapperAttribute).TargetMethod);
if Assigned(pMethod) then
MyField.SetValue(Self, pMethod); // I get a Typecast error here
end;
And a descending class could look like this:
TDecendant = class(TWrapperBase)
private type
TSomeDLLMethod = procedure(aParam: TSomeType); cdecl;
private
[TMyMapperAttribute('MyDllMethodName')]
FSomeDLLMethod: TSomeDLLMethod;
public
property SomeDLLMethod: TSomeDLLMethod read FSomeDLLMethod;
end;
I could implement this differently, by hard coding the linking for each method in an overriden 'MapMethods'. This would however require each descendant to do so which I'd like to avoid.
I know that the TValue as used in this case will contain a pointer and not of the correct type (procedure(aParam: TSomeType); cdecl; in this case).
My question: Is there a way to pass the pointer from 'GetProcAdress' as the correct type, or to set the field directly (for example by using the field address 'PByte(Self)+MyField.Offset', which you can use to set the value of a record property)?
With the old Rtti, this could be done but only for published properties and without any type checking:
if IsPublishedProp(Self, 'SomeDLLMethod') then
SetMethodProp(Self, 'SomeDLLMethod', GetProcAddress(FLibHandle, 'MethodName');
There are two problems:
First your EInvalidCast is caused by TValue being very strict about type conversions. You are passing in a Pointer and want to set a field of type TSomeDLLMethod. You need to explicitly pass a TValue that has the correct type info.
if Assigned(pMethod) then
begin
TValue.Make(#pMethod, MyField.FieldType.Handle, value);
MyField.SetValue(Self, value);
end;
Now you will run into another EInvalidCast exception which is triggered because of a bug in XE inside the GetInlineSize method of the Rtti.pas which returns 0 for a tkProcedure kind of type. I don't know in what version this got fixed but it does not exist anymore in XE5.
For XE this can be fixed by using a unit I wrote some while ago (and which I just updated to fix this bug): RttiPatch.pas.
I also reported the original issue because Pointer is assignment compatible to a procedure type so TValue should also handle this: http://qc.embarcadero.com/wc/qcmain.aspx?d=124010
You could try something like:
Move(pMethod, PByte(Self) + Field.Offset, SizeOf(Pointer));
or
PPointer(PByte(Self) + Field.Offset)^ := pMethod;

getting "E2197 Constant object cannot be passed as var parameter" when passing var parameter

this code works fine:
procedure TForm2.Timer1Timer(Sender: TObject);
var
Text: string;
begin SetLength (Text,555);
GetWindowText (getforegroundwindow, PChar (Text),555);
Form2.gtListBox1.Items.Add (
IntToStr (getforegroundwindow) + ': ' + Text);
end;
but when i put
var
Text: string;
from Timer1Timer event handler to
units implementation section or ''text : string'' in the units var section i get error : E2197 Constant object cannot be passed as var parameter
according to documentation :
This error message appears when you
try to send a constant as a var or out
parameter for a function or procedure.
but i didnt declared text as constant then why am i getting this error?
Edit:#mason wheeler: i do not understand than why does this work:
implementation
{$R *.dfm}
var
char :integer;//first of all why does delphi let me declare variable that is also a type name
procedure TForm2.Button1Click(Sender: TObject);
begin
char:=11;
showmessage(IntToStr(char));
end;
my first code was not working because i declared text as string ,you say : ''the compiler might think it's a reference to the type and not to the variable'' than why doesnt the compiler think its a reference to the type and not to the variable in this case? i am confused
Edit2: i now understand what was wrong but still have 1 confusion i did'nt use a with statement then why delphi is treating as if i am using:
with
form1 do
text := 'blahblahblah';
this is wrong on the delphi part i mean delphi should not let us do text := 'blah' but form1.text := blah; or with form1 do text := 'blah'; do i need to turn on/off some compiler setting(s) i am using delphi 2010 without any ide experts
Actually if you declare Text in implementation section and use it in Timer1Timer(Sender: TObject), compiler will consider Text as Form1.Text.
Change the name of text as sText and it will work.
Edit 1:
Because there is no property/Field for form like Form1.Char.
It's probably a name confusion. "Text" is a type name as well, a legacy textfile type. So if you declare the variable in a different scope, the compiler might think it's a reference to the type and not to the variable. Try naming it something else and it should work.
With regard to your Edit #2:
That's a standard convention of object-oriented programming. When you're writing a method for an object, the code is implicitly interpreted as being in the scope of the object. In other words, every object method can be considered as being inside a hidden with self do block.

Resources