Why can't Delphi variants hold objects?
Asked Answered
S

3

14

Why can't Delphi variants hold objects? More importantly, what's the reason behind this limitation?

Short answered 14/12, 2008 at 9:27 Comment(0)
L
8

This is just an opinion, from my experience with what variants can and cannot do.

If you put a COM object into it, it will be stored as an IDispatch reference, and thus any method calls or properties you access on this object will be converted into some code that looks up the internal DISPID of the method/property, an array with method arguments will be constructed, and the method will be invoked through the IDispatch interface.

In other words, IDispatch is handled for you, the way you would normally have to do it, but it's done automagically by the compiler.

However, for normal Delphi objects, things get harder. You can use RTTI to find, and call published methods and properties, but that's about it. If you have the name of a non-published, non-virtual method, Delphi can't find the right address for it on your method.

In other words, all you would be able to do would be to just hold the object, you wouldn't be able to use it. Perhaps they could add support for just freeing it, but again, that would probably be it.

I know for a fact that if you implement IDispatch correctly, you can safely store, and use the object through a variant. I have a class that can be used as the base class for Delphi objects you want to do this on. It will automatically expose published methods/properties, and you can add more if you want through some protected method calls. If there is an interest in such a class I can place it somewhere.

But again, this is through IDispatch, and it uses the published methods, the rest is manual code, so the support for variants have to be built into your objects, by you.

Which is why I think they just said: This will just generate complaints, that we can hold an object but it's just useless.

But that's just my thoughts. Perhaps someone official have a much better answer.

Laskowski answered 14/12, 2008 at 9:44 Comment(4)
You're pretty much right. You should also mention the TCustomVariantType and TInvokeableVariantType classes. Those classes show all the things a class would have to do to be able to be used (not just stored) in a Variant, without necessarily implementing IDispatch.Docile
+1 to Rob: TCustomVariantType and TInvokeableVariantType are pretty powerfull. Some performance penalties, but which may be circumvent with some low-level hacking.Diuretic
You can legitimately put IInterface AKA IUnknown into the variant. Then yuo can use Delphi standard Supports function to query any other interface. IDispatch is a lot of overkill here. Just make a single-purpose "object holder" interface and query it. Actually, there ALREADY exists such an interface startying with Delphi 2009 (the one used to implement "AS" operator on interfaces to cast them back to obj, only make your own implementation of it), also a smilar pre-2009 intf https://mcmap.net/q/651018/-how-to-cast-a-interface-to-a-object-in-delphiImprove
@RobKennedy what about TypInfo's TPublishableVariantType = class(TInvokeableVariantType, IVarInstanceReference) exists in Delphi 2007 at least, also the intf is defined in stock unit variants, seems the best possible approach to me. Making a wrapper would be trivial excersize...Improve
P
26

You can definitely store an object inside a Variant variable - just cast it into a NativeUInt. An object is just a pointer, anyway.

obj := TObject.Create;
v := NativeUInt(obj);
obj := TSomeObject(NativeUInt(v));
Paunch answered 14/12, 2008 at 10:3 Comment(0)
L
8

This is just an opinion, from my experience with what variants can and cannot do.

If you put a COM object into it, it will be stored as an IDispatch reference, and thus any method calls or properties you access on this object will be converted into some code that looks up the internal DISPID of the method/property, an array with method arguments will be constructed, and the method will be invoked through the IDispatch interface.

In other words, IDispatch is handled for you, the way you would normally have to do it, but it's done automagically by the compiler.

However, for normal Delphi objects, things get harder. You can use RTTI to find, and call published methods and properties, but that's about it. If you have the name of a non-published, non-virtual method, Delphi can't find the right address for it on your method.

In other words, all you would be able to do would be to just hold the object, you wouldn't be able to use it. Perhaps they could add support for just freeing it, but again, that would probably be it.

I know for a fact that if you implement IDispatch correctly, you can safely store, and use the object through a variant. I have a class that can be used as the base class for Delphi objects you want to do this on. It will automatically expose published methods/properties, and you can add more if you want through some protected method calls. If there is an interest in such a class I can place it somewhere.

But again, this is through IDispatch, and it uses the published methods, the rest is manual code, so the support for variants have to be built into your objects, by you.

Which is why I think they just said: This will just generate complaints, that we can hold an object but it's just useless.

But that's just my thoughts. Perhaps someone official have a much better answer.

Laskowski answered 14/12, 2008 at 9:44 Comment(4)
You're pretty much right. You should also mention the TCustomVariantType and TInvokeableVariantType classes. Those classes show all the things a class would have to do to be able to be used (not just stored) in a Variant, without necessarily implementing IDispatch.Docile
+1 to Rob: TCustomVariantType and TInvokeableVariantType are pretty powerfull. Some performance penalties, but which may be circumvent with some low-level hacking.Diuretic
You can legitimately put IInterface AKA IUnknown into the variant. Then yuo can use Delphi standard Supports function to query any other interface. IDispatch is a lot of overkill here. Just make a single-purpose "object holder" interface and query it. Actually, there ALREADY exists such an interface startying with Delphi 2009 (the one used to implement "AS" operator on interfaces to cast them back to obj, only make your own implementation of it), also a smilar pre-2009 intf https://mcmap.net/q/651018/-how-to-cast-a-interface-to-a-object-in-delphiImprove
@RobKennedy what about TypInfo's TPublishableVariantType = class(TInvokeableVariantType, IVarInstanceReference) exists in Delphi 2007 at least, also the intf is defined in stock unit variants, seems the best possible approach to me. Making a wrapper would be trivial excersize...Improve
C
7

I had used Variants to hold objects in the past using the Variant internals, the code is something like this:

var
  MyObject: TMyObject;
  Value: Variant;
begin
  MyObject:= TMyObject.Create;
  TVarData(Value).VType:= VarByRef or VarUnknown;
  TVarData(Value).VPointer:= MyObject;
Corticosterone answered 14/12, 2008 at 14:52 Comment(6)
Note that you're cheating. VarByRef is a flag meant to modify the underlying type field. Your code says the variable is empty, but it's empty by reference.Docile
Conceptually it's OK I think; logically, an untyped reference to a location is equivalent to an untyped pointer, i.e. void* in C or Pointer in Delphi.Langford
It should be varByRef or varVariant to work properly in Delphi.Allyson
VarByRef is a flag in the upper four bits of the type. Since the VarByRef flag is being set by itself on the type, the lower twelve bits which hold the actual type will be zero, which for a variant means the type integer. A better idea is to set the type to varByRef | varUnknown - really that's what this is, a pointer to a type that is unknown to the variant.Morrismorrison
@RobKennedy has his comment when the earlier revision of the text has it VarByRef wish indeed is equal to varEmpty or varByRef - and i agree this one was a neat trick. Using explicitly impossible values erring on passive size - i like the idea. But importanly someone changed the code in 2018 and now it became VarUnknown wioch means auto-ref-counted IInterface not object !!! This is a very dangerous change for now any standard-compliable code would possible fail with access violation. Even while VCL TComponent does have IInterface implemented method offsets are DIFFERENT!Improve
@BarryKelly danger warningImprove

© 2022 - 2024 — McMap. All rights reserved.