Is there some common ancestor for Delphi delegates which are declared with of object clause?
I need to find a common ancestor for TNotifyEvent and my custom delegate:
TMyEvent = procedure(Sender: TObject; msg: stringh); of object;
to make an universal method for firing these events.
Should I use Pointer? or TObject?
You need to get down and dirty with the implementation details for method pointers. These are stored as a so-called double pointer value. One pointer for the subject of the method call (the instance) and one pointer for the method itself (the code).
You can use the type TMethod from the System unit to represent method pointers. Its declaration looks like so (with the comparison operators removed for simplicity):
type
TMethod = record
Code, Data: Pointer;
end;
You need to use a typecast to make assignments between these types:
uses
System.Classes;
var
Event: TNotifyEvent;
Method: TMethod;
begin
Method := TMethod(Event);
TMethod(Event) := Method;
end.
Obviously none of this is type-safe so you need to ensure correctness. The compiler cannot help you. There is nothing like the checked type conversion operator, as, to work with method pointers. That is, it is up to you that when you cast from TMethod to a specific method pointer type, you have to make sure that the TMethod instance really is an instance of the method pointer type to which you cast. Think of this whole process as being analogous to casting from a typed pointer to an untyped pointer, and then back again.
Now, if you are going to store arbitrary method pointers into TMethod instances, that's fine. But what happens when you subsequently need to fire these methods. You need to know which type of method pointer is really behind each TMethod instance. So that you know how to cast it, what arguments it needs, and so how to call it. That's going to mean you have to store extra information about the true type of the method, alongside the raw method itself.
So, I think that I have perhaps answered the question that you asked, but I'm not sure it's going to be of much use to you. To understand that I think we'd really need to know more about what you are trying to achieve, and what information you have, when.
For instance, if you know the arguments that are to be passed to the method at the time you need to store it away, you could use variable capture and wrap it in an anonymous method. That would allow you to retain type-safety and avoid any of the rather dubious casts that I demonstrate above. Perhaps you need partial application, as a means of adapting your non-homogeneous method pointers to have the same interface. In which case again anonymous methods can help.
Related
I am having some difficulty understanding typecasting when using a class that is a passed parameter. I tried searching for this but couldn't find other answers.
I am working with some legacy Delphi code, using Delphi 2006, which doesn't support Generics (introduced in Delphi 2009).
The code is using TLists to store pointers to instantiated classes of particular types. When clearing the list, they use this:
procedure ClearList(AList: TList);
var i: Integer;
begin
for i := 0 to AList.Count - 1 do
TObject(AList[i]).Free;
AList.Clear;
end;
And it is called like this:
ClearList(FExtraVisitTypes);
ClearList(FDiagnoses);
ClearList(FProcedures);
ClearList(FImmunizations);
ClearList(FSkinTests);
ClearList(FPatientEds);
ClearList(FHealthFactors);
ClearList(FExams);
My understanding of this may be off, but I am concerned that if the pointed-to objects are freed as TObject, that the destructor of the descendant object won't be called, potentially leading to a memory leak. (My polymorphisim kung-fu is a bit rusty, which may be causing my confusion.)
So I tried to change the clear function as below:
procedure ClearList(AList: TList; ItemClass: TPCEItemClass); //mod to add ItemClass
var i: Integer;
begin
for i := 0 to AList.Count - 1 do begin
(AList[i] as ItemClass).Free;
end;
AList.Clear;
end;
TPCEItemClass is defined like this:
TPCEItemClass = class of TPCEItem;
I then changed the clear calls like this:
ClearList(FExtraVisitTypes, TPCEProc);
ClearList(FDiagnoses, TPCEDiag);
ClearList(FProcedures, TPCEProc);
ClearList(FImmunizations, TPCEImm);
ClearList(FSkinTests, TPCESkin);
ClearList(FPatientEds, TPCEPat);
ClearList(FHealthFactors, TPCEHealth);
ClearList(FExams, TPCEExams);
But the compiler won't allow this and gives this error:
[Pascal Error] uPCE.pas(1730): E2015 Operator not applicable to this operand type
For this erroneous line:
(AList[i] as ItemClass).Free;
Questions:
Does the original way of coding, where the item is freed by simply calling the great-great-great (etc) ancestor Free method end up effecting the descendant's destructor method? As I write this, I'm now thinking that it actually does. But I don't know why. So any answers to help me keep this in my head would be great.
Why does my method of trying to typecast via the parameter which is of type class not work? Is this just not allowed? Or is my syntax wrong? Is there another way to do this?
Am I going about this all wrong? Is there a better way?
Thanks
I am concerned that if the pointed-to objects are freed as TObject, that the destructor of the descendant object won't be called, potentially leading to a memory leak.
That is not the case for classes that are properly implemented.
All classes derive from TObject. TObject.Free() calls the TObject.Destroy() destructor, which is virtual. Any descendant that requires destruction logic must override that destructor (if it doesn't, it has a flaw that needs fixing).
So, in properly written code, the original code will work perfectly fine as shown. Calling Free() on any valid and correctly implemented object will invoke its most-derived destructor.
Now, that being said, there have been plenty of cases over the years of people forgetting to override the destructor when their classes require it, thus causing the kinds of memory leaks you are worried about. So, make sure you pay attention to what your classes are doing, and you will be fine.
So I tried to change the clear function as below ... But the compiler won't allow this and gives this error
Correct, because you can't perform a type-cast on an object using a variable to a metaclass type, like you are trying to do. Type-casts require the target type to be specified at compile-time, but metaclass variables are not assigned until runtime.
Does the original way of coding, where the item is freed by simply calling the great-great-great (etc) ancestor Free method end up effecting the descendant's destructor method?
The original code will work just fine 99% of the time, yes. Most Delphi coders are good about override'ing the destructor when it is appropriate. But that other 1% is only when you are dealing with classes that are not implemented correctly, in which case it is their author's responsibility to fix them, not your responsibility to fix the code that is calling Free() on them.
As I write this, I'm now thinking that it actually does. But I don't know why.
Polymorphic dispatch of the virtual destructor, just like when calling any other virtual method.
Why does my method of trying to typecast via the parameter which is of type class not work? Is this just not allowed?
Correct. It is illegal.
Is there another way to do this?
No (well, yes, but it involves walking an object's class structure's manually at runtime, but that requires a deep understanding of how the compiler lays out objects in memory, so I'm not going to get into that here).
I have a list of type TList<TForm>. I need to cast it and use it as TList<TObject> like this:
procedure mainForm.testCast;
var
listT: TList<TForm>;
listW: TList<TObject>;
obj: TObject;
begin
listT := TList<TForm>.create;
listT.add(form1);
listT.add(form2);
listW := TList<TObject>(listT); // Casting is OK
// This works, but is this fine?
for obj in listW do
memo1.lines.add(obj.className);
end;
The sample works as expected, but is it ok to cast like this between generic lists? Will this cause some data structure corruption etc?
I use it only for looping (DoGetEnumerator) purposes and some string checks i.e. I'll not add/remove items.
The real function is little more complicated. It gets reference to listT using RTTI in a TValue.
The main goal is not to link FMX.Forms in my unit.
Update:
Why are TGeneric<Base> and TGeneric<Descendant> incompatible types?
Well, your code will work, but it somewhat dubious in my view. Simply put the cast is not legal because
TList<TForm>.InheritsFrom(TList<TObject>)
is false. So a TList<TForm> object is not a TList<TObject>. If it were, then the cast would not be needed.
That this is so is because Delphi's generic types are invariant. More details can be found here:
Why is a class implementing an interface not compatible with the interface type when used in generics?
If you have any difficulty understanding why the designers made generic types invariant, consider for a moment the effect of writing listW.Add(TObject.Create) in your code. Think what it means to the true underlying object of type TList<TForm>.
So the language promises you nothing. You are venturing outside its guarantees. It so happens that the implementation of these two un-related types is compatible enough for your code to work. But that is really just an accident of implementation.
Since you are already using RTTI, then I suggest that you iterate over the list with RTTI. You can call GetEnumerator and so on using RTTI. That way you will call the actual methods of the object.
Does anyone here know how Delphi represents a reference to procedure?
for example
var
proc: TProc;
...
proc = procedure begin beep end;
What do we got in "proc"?
I know that for "method variable" the memory representation is 4 bytes for the "procedure address" followed by 4 bytes for the "object address", but for "reference to procedure" is somewhat different and I cannot quite figure it out.
The reason I want this is because I have some legacy code that I want to make it work with references.
Does anyone know something about it?
Method references are implemented as a COM-style interface with a single method called Invoke, which has the same signature as the method reference.
So TProc looks like this:
type
TProc = interface(IInterface) // so inherits QI, AddRef, Release
procedure Invoke;
end;
It's a valid question to ask, as Delphi has interoperability with the C++ product. By using a pre-existing reference-counted type and idiom (COM lifetime rules), interop with C++ at the method reference level is possible.
Anonymous methods generate a hidden class which implements an interface isomorphic to the method reference interface, i.e. exactly the same shape, but not with the same symbolic identity. The hidden class doesn't implement the method reference interface directly because it may need to implement the interface multiple times (a single block may contain multiple anonymous methods all assigned to locations of the same method reference type).
Just discovered something rather funny:
var
Queue : TQueue <TProc>;
MyProc : TProc;
...
MyProc := Queue.Dequeue;
I think you see what is intendend here. However, the compiler thinks I want to store the Queue.Dequeue method (type "procedure of object") in MyProc and reports an error
E2010 Incompatible Types: 'TProc' und 'Procedure of object'
The workaround I came up with goes like this
MyProc := TProc (Pointer (Queue.Dequeue));
Is there a more elegant solution?
There's a bit of syntactical ambiguity there about whether the name "Dequeue" refers to the function itself, or the function's return value. And since you're dealing with an anonymous method pointer which you can assign a normal function to, it's trying to interpret that as a function assignment, not a function result assignment. Casting it to a pointer is the wrong solution, as that would force the function assignment to go through, which would then cause all sorts of fun errors when you attempt to invoke MyProc.
The correct way to fix it is by removing the syntactical ambiguity. Put an empty parenthesis after Dequeue, so that the compiler is sure that you're calling the function and not simply referencing it by name, and then it'll work.
MyProc := Queue.Dequeue();
As Mason said, there's an ambiguity in the Delphi syntax. Where TFoo.Bar is a method, it's not clear that FooValue.Bar means to refer to the result of calling TFoo.Bar, or a method pointer (or reference) TFoo.Bar itself (with implied Self argument of FooValue).
In the comments of Mason's answer, Rob Kennedy seems to suggest that the compiler simply figure this out based on the types of everything involved. This isn't simple; the compiler already does a lot of work to figure out whether you mean to refer to a method pointer value or a method call. It actually parses expressions in a different way when the expected receiver is a method pointer (or reference, or function pointer) type. The effort is especially involved when overloads are brought into the picture: the compiler scans through every overload candidate and checks for method pointer types in every parameter position, and then parses arguments differently depending on whether or not that parameter position contains a function pointer in one of the overloads. Then, if an overload that expects a function pointer isn't matched, the compiler changes the parse tree from function pointer to method call. The overloading mechanism itself needs to figure out which to use when its doing value to parameter comparisons. It's pretty messy, and it would be great if we didn't make it messier.
A prefix-style operator like # or Addr() isn't much help in resolving this ambiguity, not least because functions may return function pointers, and so on; how many # do you need to inhibit implicit (no () necessary) calling to grab the right value out? So when anonymous methods were introduced, a change in the expression parsing was made: I introduced the possibility of using () to force an invocation.
You can read more about it here:
http://blog.barrkel.com/2008/03/odd-corner-of-delphi-procedural.html
and here:
http://blog.barrkel.com/2008/03/procedurally-typed-expressions-redux.html
I need a way to write a generic procedure to act upon an object type or any of its descendants.
My first attempt was to declare
procedure TotalDestroy(var obj:TMyObject);
but when using it with a descendant object
type TMyNewerObject = class(TMyObject);
var someNewerObject: TMyNewerObject;
TotalDestroy(someNewerObject);
I get the infamous error "types of formal and actual parameters must be identical"
So, while strugling to find a solution, I looked at the source code of Delphi system FreeAndNil procedure. And I found this awesome declaration, along with this astonishing comment
{ FreeAndNil frees the given TObject instance and
sets the variable reference to nil.
Be careful to only pass TObjects to this routine. }
procedure FreeAndNil(var Obj);
It avoids the type checking error, but it uses no safety net.
My question is ... is there any safe way to check the type of an untyped var parameter?
or in other words, can you improve this Delphi source code so that the warning would not be needed?
procedure FreeAndNil(var Obj);
var
Temp: TObject;
begin
Temp := TObject(Obj);
Pointer(Obj) := nil;
Temp.Free;
end;
Let's examine what you want to do.
You want to call a method that takes X, passing in an object of type Y, where Y is a descendant of X. The snag, the parameter is a "var" parameter.
Let's analyze what you could do if that was possible.
type
TBase = class
end;
TDescendant = class(TBase)
end;
procedure Fiddle(var x: TBase);
begin
x := TDescendant.Create;
end;
type
TOtherDescendant = class(TBase)
end;
var a: TOtherDescendant;
a := TOtherDescendant.Create;
Fiddle(a);
Uh-oh, now a no longer contains an instance of TOtherDescendant, it contains an instance of TDescendant. That probably comes as a surprise to the code that follows the call.
You must not only consider what you intend to do with the syntax you propose, but effectively what you could do with the syntax.
You should read Eric Lipperts excellent blog post about similar issues in .NET, found here: Why do ref and out parameters not allow type variation?.
I've written about this before, using an example very similar to Lasse's:
Delphi Q&A: Why must the types of actual and formal var parameters be identical?
Unless you're writing an assignment statement to change the value of the input parameter itself, and not just one of its properties, you shouldn't pass a parameter by reference in the first place.
If you are writing an assignment statement to change the parameter's value, then the compiler message really is true, and you should heed it.
One reason for needing to by-pass the error is when you're writing a function like TApplication.CreateForm. Its job is to change the input parameter's value, and the type of the new value varies and cannot be determined at compile time. If you're writing such a function, then your only option with Delphi is to use an untyped var parameter, and then there is extra burden on both the caller and the receiver to make sure everything goes right. The caller needs to make sure it passes a variable that is capable of holding values of whatever type the function will put in it, and the function needs to make sure it stores a value of a type compatible with what the caller requested.
In the case of CreateForm, the caller passes in a class-reference literal and a variable of that class type. The function instantiates the class and stores the reference in the variable.
I don't think very highly of either CreateForm or FreeAndNil, largely because of the way their untyped parameters sacrifice type safety in return for comparatively little extra convenience. You haven't shown the implementation of your TotalDestroy function, but I suspect its var parameter will ultimately provide the same low utility as in those other two functions. See my articles on both:
When should I use FreeAndNil?
Why shouldn't I call Application.CreateForm?
In addition to what Lasse wrote, which is quite correct, most of the time you don't want to pass an object to a var parameter anyway.
An object is a reference type. What you see as the object is actually a reference to it. You would only want to pass an object reference to a var parameter if you wanted to change your object out for a new object. If you just want to be able to modify the members of the object, then you can do that by simply passing it to a normal parameter. Make method call take a TMyObject parameter instead of a var TMyObject parameter and it should work.
Of course, if you really are replacing the object, then feel free to disregard all this, and see Lasse's answer.
can you improve this Delphi source code so that the warning would not be needed?
Yes, you can get a type safe way to avoid the compiler error.
In the newest Delphi 10.4 Sidney, the FreeAndNil procedure has been changed into this:
procedure FreeAndNil(const [ref] Obj: TObject);
var
Temp: TObject;
begin
Temp := Obj;
TObject(Pointer(#Obj)^) := nil;
Temp.Free;
end;
It is type safe for objects and will catch errors when passing an interface reference for example.
The way to pass a parameter by const [ref] means that the parameter is passed by reference. Without the [ref] attribute, parameters with size equal and smaller than a pointer would otherwise be passed by value.
Here, even though the object is passed as a constant, the reference will be modified.
In that sense, it is not a perfect declaration, but will do its job better than the former implementation.
From New features in Delphi 10.4:
This means that incorrect usage of FreeAndNil will now cause a compiler error. In the past, incorrect usage would not be caught, leading to difficult bugs. Note that although the parameter is declared as const, the by-reference variable is indeed modified.
A new, but ‘not as bad’, class of incorrect calling is possible with this declaration of FreeAndNil: the method can be called passing in properties or a method result, as well as cast expressions, a type’s implicit conversion to TObject, etc. The nil-ed value will then be the temporary variable in the expression.