Delphi code migration issues - delphi

I´m facing a problem between Delphi 2010 and Delphi Berlin (last update) during my code migration....
I made a simple code to demonstrante an strange behaviour...
I have an application that use TList (the former one) and TList (from Generics.Collections)
I know that this piece of code (below) doesn´t make any sense for you, but it´s for demonstration purposes
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TTest = class
Name: string;
constructor Create(Nome: string);
end;
TForm1 = class(TForm)
btn1: TButton;
procedure btn1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
FList: TList;
end;
var
Form1: TForm1;
implementation
uses
System.Generics.Collections;
{$R *.dfm}
procedure TForm1.btn1Click(Sender: TObject);
var
tmpList: TList<TTest>;
begin
tmpList := TList<TTest>.Create;
tmpList.Add(TTest.Create('A'));
tmpList.Add(TTest.Create('B'));
tmpList.Add(TTest.Create('C'));
tmpList.Add(TTest.Create('D'));
tmpList.Add(TTest.Create('E'));
FList := TList(tmpList);
ShowMessage(TTest(FList[0]).Name);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FList := TList.Create;
end;
constructor TTest.Create(Nome: string);
begin
Name := Nome;
end;
end.
At Delphi 2010 the ShowMessage shows 'A' character, but on the Delphi Berlin it raises an Acess Violation
Both applications with Optimization set to False

FList := TList(tmpList);
This is the problem. The cast is simply wrong, because tmpList is not a TList.
Your code only compiles because of the cast, but the cast does not change the fact that the object on the right hand side is not of the type being casted to. All the cast does is stop the compiler from complaining and saving you from yourself. Your cast is a lie to the compiler, and the runtime error is the consequence.
This code might have worked in older versions, but only by chance. Your luck has changed.
Hard to know what to suggest for a fix. As you say, the code makes little sense. Every time you press the button, you leak a list. I'd suggest that you remove all the casts, stop using the non-Generic TList and use only Generic lists.

The class TList<T> is not castable to/from TList.
You cannot cast one to the other and expect sensible results any more than you could cast a TForm to TButton (for example).
In Delphi, typecasts of this form are unchecked, sometimes referred to as hard-casting. That is, the compiler will simply trust that you know what you are doing and will simply comply, but if the typecast is invalid then the results will be unpredictable.
For conversions between object reference types (and/or interface references) you can use a checked cast using the as operator:
FList := tmpList as TList;
If a checked cast is invalid (such as this one is) then the compiler will throw a runtime exception, alerting you to the mistake.
Why does the compiler even allow unchecked casts ?
In some cases unchecked casting can be useful and safely relied upon, within specific use cases. But outside of those specific conditions unchecked casts are at best trusting to luck or on specific compiler behaviours or RTL characteristics which may be subject to change.
e.g. the 32-bit trick of storing object references or other pointer values in an Integer variable. Such code may continue to work when recompiled for 64-bit, but now only as a matter of luck and only in some cases, since only a subset of possible 64-bit pointer values can safely be stored in a 32-bit Integer.
If you have code which is successfully hard-casting between TList and TList<T> then it worked only by luck, as a result of some particular behaviour of the compiler or RTL at that time.

Related

Is AtomicCmpExchange() broken?

If you make a new multi-device application project, set Project > Option > Compiling > Optimization : True, and then copy the code below to unit1.pas:
unit Unit1;
interface
uses
System.SysUtils,
FMX.Forms,
FMX.StdCtrls,
System.Classes,
FMX.Types,
FMX.Controls,
FMX.Controls.Presentation;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
FKey: integer;
public
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
procedure TForm1.Button1Click(Sender: TObject);
begin
FKey := 2;
var LCompareKey: integer := 2;
AtomicCmpExchange(FKey{target}, LCompareKey{NewValue}, LCompareKey{Comparand});
if FKey <> LCompareKey then raise Exception.Create('Error 2');
TThread.queue(nil,
procedure
begin
if LCompareKey <> FKey
then raise Exception.Create('Error 3');
end);
end;
end.
Why does this code crash on Win32 on if FKey <> LCompareKey then raise Exception.Create('Error 2');?
I'm using Delphi 10.4 Sydney Update 3. I didn't yet try in Delphi 11 Alexandria, so I don't know if it's working in that version.
Is there any workaround except deactivating the optimization?
Another question - is it really safe to activate the optimization?
Yes, codegen for AtomicCmpExchange is broken on Win32 compiler when optimization is turned on.
Problem happens in combination with anonymous method variable capture that happens in TThread.Queue call. Without variable capture, assembly code for AtomicCmpExchange is properly generated.
Workaround for the issue is using TInterlocked.CompareExchange.
...
var LCompareKey: integer := 2;
TInterlocked.CompareExchange(FKey{target}, LCompareKey{NewValue}, LCompareKey{Comparand});
if FKey <> LCompareKey then raise Exception.Create('Error 2');
...
TInterlocked.CompareExchange function still uses AtomicCmpExchange, but at place of call it works with captured variables through parameters instead of directly and generated code is correct in those situations.
class function TInterlocked.CompareExchange(var Target: Integer; Value, Comparand: Integer): Integer;
begin
Result := AtomicCmpExchange(Target, Value, Comparand);
end;
Another, less optimal solution would be turning off optimization around broken method Button1Click with {$O-} compiler directive and then turning it back on with {$O+}
Since AtomicCmpExchange is Delphi intrinsic function, its code is directly generated by compiler when it is called and bad codegen only affects that procedure, not general code - in other words anonymous method capture is working correctly in other code (unless there are other, bugs in compiler, unrelated to this particular one).
In other places in RTL where AtomicCmpExchange is used, there is no code where variable capture is involved, so RTL, VCL and FMX code is not affected by this issue and optimization can be turned on in application.
Note: There may be other optimization bugs in compiler that we don't know about.

Print complex number

Using embarcadero XE7 and System.VarCmplx - need to present a complex number as a string. Simple example where a complex number is created and the intent is to show it in the caption of the form. My problem is that I can not figure out how to get the complex number to a string - should be '1.23+4.56i'.
unit Unit57;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics,
System.VarCmplx,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs;
type
TForm57 = class(TForm)
procedure FormCreate(Sender: TObject);
end;
var
Form57: TForm57;
implementation
{$R *.dfm}
procedure TForm57.FormCreate(Sender: TObject);
v : Variant;
begin
v := VarComplexCreate( 1.23, 4.56 );
// following does not work
Caption := v.AsString;
end;
end.
It is quite simple, actually:
v := VarComplexCreate(1.23, 4.56);
Caption := v;
On my German Windows, this shows
1,23 + 4,56i
No need for .AsString.
If you want custom formatting, you can use the real and imaginary parts directly, and do something like:
var
A, B: Extended;
...
A := v.Real;
B := v.Imaginary;
Caption := Format('%.3f+%.3fi', [A, B], TFormatSettings.Invariant);
That shows:
1.230+4.560i
Note: There is a public property AsString in the implementation, but apparently only the published properties can be accessed from code. I guess it was made public because it is not needed by the user anyway.
Note that your code doesn't compile. Although it looks as if you copied and pasted this from the Delphi editor, there is no proper var section in the procedure, so that can't compile. Please always use copy and paste.
There is actually a (as far as I can see) much better implementation of complex numbers, by Hallvard Vassbotn, using extended records instead of variants. It comes with the Samples that are (usually) installed together with Delphi. Just look in the <your samples dir>\Delphi\RTL\ComplexNumbers directory. This uses extended records, so it can be used like a normal value type, i.e. like a Double or an Integer.

Is memory allocated by Delphi's `New` globally accessible for `Dispose`?

I am thinking about the New and Dispose commands from Delphi and was wondering if I can use these commands in other procedures/functions/threads, etc. within my process.
I would like to store the address to a TList but I am a little insecure since it uses the var reference which could be used to 'Save' the vars actual address. I don't want any access violations or anything...
Here is my code:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
MyTList : TList;
public
{ Public declarations }
end;
var
Form1 : TForm1;
type
TMyStruct = record
Int1 : Integer;
Int2 : Integer;
Str1 : String;
Str2 : String;
end;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
P_TMyStruct : ^TMyStruct;
I : Integer;
begin
for I := 1 to 3 do begin
New (P_TMyStruct);
P_TMyStruct^.Int1 := I;
P_TMyStruct^.Int2 := 1337;
P_TMyStruct^.Str1 := inttostr(I);
P_TMyStruct^.Str2 := '1337';
MyTList.Add(P_TMyStruct);
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
I : Integer;
begin
for I := 0 to MyTList.Count - 1 do begin
ShowMessage(inttostr(TMyStruct(MyTList.Items[i]^).Int1));
ShowMessage(inttostr(TMyStruct(MyTList.Items[i]^).Int1));
ShowMessage(TMyStruct(MyTList.Items[i]^).Str1);
ShowMessage(TMyStruct(MyTList.Items[i]^).Str2);
Dispose(MyTList.Items[i]);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
MyTList := TList.Create;
end;
end.
So would that be safe since I'm not disposing it in the stack?
Dynamic memory allocations in Delphi will return memory on the heap and not the stack. (See Marco Cantu's explanation of these terms.) As such, the memory will be "globally accessible" in your application provided a reference to that memory is available. The New() procedure is one way to dynamically allocate memory. (Some others are: GetMem(), string assignments, object creation.)
So what you're proposing is safe (in terms of not causing access violations).
However, it will leak memory because the way you are using it, Dispose() will not deallocate all memory.
When you assign a string value to the struct, more memory is allocated on the heap. When you later Dispose your struct, the exact memory allocated in New() is deallocated, but at the low level, (simple Pointer reference to the memory), Delphi has no knowledge that there may be other internal structures that need deallocation; so the strings are leaked.
Fixing the memory leak
What you need to do is cast the pointer returned by MyTList.Items[i] to its correct type. But first you need to explicitly declare a type for the pointer to your struct. As a standard convention, most Delphi programmers will declare the pointer type with the same name, replacing the T prefix with P. I.e.
PMyStruct = ^TMyStruct;
TMyStruct = record
Int1 : Integer;
Int2 : Integer;
Str1 : String;
Str2 : String;
end;
Then when you do the following: Dispose(PMyStruct(MyTList.Items[i])), the compiler "recongnises" the type the pointer is referring to and will use that information to perform additional actions for its managed types. The point is the compiler can automatically handle the managed types correctly - but only if you give it the correct information about the struct that contains them.
The types affected by this are:
strings
dynamic arrays
interface references (which will need a ref released)
Also any indirect references to the above managed types will be managed. E.g.
If a Variant or OleVariant references and interface, it will be managed.
If a child record within a record (struct) references managed types, they will be managed.
arrays of strings, arrays of interfaces... each string/interface entry will also be managed.
Given that there are many more possible permutations to the above, it is safer to always ensure that Dispose() knows the type that was used in the initial New() allocation.
Non Managed Types
Perhaps given the above discussion, I should make a specific qualification about types that are not managed types. One can infer (and it would be accurate) that non managed types are not automatically deallocated when the containing structure is Dispose()d.
E.g. If your record structure contains an object reference, then because an object reference is not a managed type, you would still have to explicitly control the lifetime of that object instance. (Fortunately there are many techniques to do so.)

Why don't I get an access violation when I call methods on an uninitialized function result?

One of my coworkers show me a code written in Delphi-XE XE Version 15.0.3953.35171, which I believe it should raise an access violation. The code is bellow:
unit Unit3;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm3 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
function test:TstringList;
{ Public declarations }
end;
var
Form3: TForm3;
implementation
{$R *.dfm}
procedure TForm3.FormCreate(Sender: TObject);
var aStrList : TStringList;
begin
aStrList := TStringList.Create;
test;
FreeAndNil(aStrList);
end;
function TForm3.test: TstringList;
var i:Integer;
begin
for i:=0 to 1000 do
Result.Add('aaa');//AV?
end;
end.
Inspecting aStrList and Result has the following results:
aStrList: TStringList $12FEDC : $42138A
Result: TStringList $12FEC4 : $B01B90
I do not understand why it is working. Result.Add should raise an access violation
LE: It seems that is working only on Debug Build Configuration.
The Result variable in that function has not been initialized and could hold any value. Now, the implementation detail means that, in some combinations of compiler options, your code happens to run with Result referring to a valid object. But that's really just a coincidence of those implementation details.
If this were C++ then that function would exhibit undefined behaviour. Although that term does not have a formal meaning in Delphi, it can be helpful to use that term in a Delphi setting to mean the same thing as in the context of C++.
I would also make the point that even if Result did not refer to a valid string list object, your code would not be guaranteed to raise an access violation. It could be that Result points to a block of memory that just happens to look enough like a string list for that code to execute successfully.
If you do things properly, you can predict the behaviour of your program. If your code is flawed and induces undefined behaviour, then your program's behaviour becomes unpredictable. It may work. It may fail. Or that code may execute fine, but then lead to a failure later in the program's execution. And so on.

Declare public global variable in Delphi

Let's say I have two forms in a delphi project, I want to be able to access form1's variables from form2. Is there anyone to declare, say a 'public' variable in form1 which can be read from all forms?
I have tried putting a variable in the public declaration
{ private declarations }
public
{ public declarations }
test: integer;
end;
and in form 2 i have
unit Unit2;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, unit1;
type
{ TForm2 }
TForm2 = class(TForm)
procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
procedure FormCreate(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
end;
var
Form2: TForm2;
implementation
{$R *.lfm}
{ TForm2 }
procedure TForm2.FormCreate(Sender: TObject);
begin
form1 //<---------- DOES NOT GET RECOGNIZED
end;
end.
I then put 'Unit1' into the uses section on Form2, but it seems I can't do that due to a circular reference. I'd like to refrain from using pointers if possible.
First, it's better to pretend that globals don't exist at all. If you start out programming with the crutch of global variables, you'll just avoid learning the very simple techniques that allow you to do without them. You're worrying about using pointers (which actually wouldn't help your problem at all), but not concerned about using globals which are actually more dangerous.
Globals are dangerous because:
They provide a link between the units that share them (a concept called tight coupling)
This link is hidden in the sense that it's not obvious the exact nature of the link without examining the detail of the code.
Your code will become littered with side effects, which is to say that when you do something in a method, other unexpected things happen as well. This increases the possibility and complexity of subtle bugs.
The cumulative effect of the above points is that even if you break a project down into 20 units, your brain still has to think about all 20 units at the same time - as if they were only1 unit. This defeats the purpose of breaking your project down into smaller manageable units in the first place.
You'll quickly find that even medium sized projects start becoming very difficult to maintain.
Circular References
Please take a look at my answer to a previous question for thoughts on dealing with circular references more generally.
In your case, you simply need to move Unit1 from the interface uses to the implementation uses.
var
Form2: TForm2;
implementation
uses
Unit1;
{$R *.lfm}
{ TForm2 }
procedure TForm2.FormCreate(Sender: TObject);
begin
Form1.test; //Is now accessible, but remember: it will cause a runtime error if Form1 hasn't been created or has already been destroyed.
end;
Sharing data without globals
You'll notice that technically you're still using globals; albeit ones created by Delphi and not your own. Here's a technique you can use to share data in a more controlled fashion.
unit Unit3;
interface
type
TSharedData = class(TObject)
public
Test1: Integer;
Test2: Integer;
end;
Then in Form1 you add the following:
implementation
uses
...
Unit3;
type
TForm1 = class(TForm)
...
public
SharedData: TSharedData;
end;
//Inside either the constructor or OnCreate event add the following line:
SharedData := TSharedData.Create;
//Inside either the destructor or OnDestroyevent add the following line:
SharedData.Free;
Then in Form2 you do something slightly different because you want to use Form1's shared data not its own "shared data".
implementation
uses
...
Unit3;
type
TForm2 = class(TForm)
...
public
Form1SharedData: TSharedData;
end;
//Nothing added to constructor/destructor or OnCreate/OnDestroy events
Finally, after creating both forms, you give Form2 a refernce to Form1's shared data:
procedure RunApplicationWithoutFormGlobals;
var
LForm1: TForm1;
LForm2: TForm2;
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, LForm1);
Application.CreateForm(TForm2, LForm2);
LForm2.Form1SharedData := LForm1.SharedData;
Application.Run;
end;
The above illustrates how easily you can do away with even Delphi's global variables.
Disclaimer: Some of the code goes agaisnt generally accepted encapsulation principles, but is for illustrative purposes only.
First, if you must use globals (it's probably better not to use globals, as Craig has wisely pointed out) then you should put the globals you want to share in SharedGlobals.pas:
unit SharedGlobals;
interface
var
{variables here}
Something:Integer;
implementation
{ nothing here?}
Now use that unit, from the two units you want to share access to that variable in. Alternatively, have both reference another object, which is named something sensible, and have that object be designed, as the holder of state (variable values) that those two instances (forms or classes, or whatever) need to share.
Second idea, since your two units that you have already have dependencies on each other, you could also get around your circular dependency by using the unit that would create a circular dependency, from the implementation section instead of the interface:
unit Unit2;
interface
/// stuff
implementation
uses Unit1;
...
unit Unit1;
interface
/// stuff
implementation
uses Unit2;
the example shows Form1(main) and Form2(other) which has better use for organization;
FORM1 interface declaration only;
can be read from all forms;
interface
procedure oncreate(Sender: TObject);
implementation
uses Form2;
procedure TForm1.oncreate(Sender: TObject);
begin
Form2.test1;
//{Form2.test2} are not visible to Form1;
end;
FORM2 interface and implementation declarations;
implementation declaration are local to the unit; cannot be read from all forms;
interface
procedure test1;
implementation
procedure test2; //declared under implementation;
procedure TForm2.test1;
begin
ShowMessage('form2 test1 message');
end;
procedure TForm2.test2;
begin
ShowMessage('form2 test2 message');
end;
any procedure, function, type, variable can be local to the unit under implementation;
it also makes intellisense(Ctrl+Space) clean from declarations used only to the unit;

Resources