Backport of RTTI.TRttiIndexedProperty to Delphi XE - delphi

Facts:
Successfull independent efforts to bring Rtti.TVirtualInterface introduced in Delphi XE2 to prior Delphi versions were made respectively by
Vincent Parrett in Delphi.Mocks.VirtualInterface unit (Delphi Mocks)
Stefan Glienke in DSharp.Core.VirtualInterface.pas unit (DSharp)
Findings:
TRttiIndexedProperty is derived from TRttiMember.
TRttiType and TRttiInstanceType depend on TRttiIndexedProperty.
Rtti.pas depends on TypInfo.pas where some breaking changes where also introduced.
Question:
Is there a hope that one day someone will make it possible to bring TRttiIndexedProperty on Delphi XE ?

TRttiIndexedProperty can't be back-ported to older Delphi versions because it depends on the compiler writing out RTTI data for indexed properties, what only Delphi XE2's compiler does. You can't read something that isn't there.
The only possibility you have would be to write this data by hand. So you have to write a parser that runs over all your code and generates the necessary type information for all indexed properties. And because your parser isn't the compiler you would also have to write little helper functions that write and read the indexed-property.
The output could be something like this:
TMyClass = class
private
...
public
property MyArray[Index: Integer]: TMyObject read GetMyArray write SetMyArray;
// autogenerated code
class procedure RegisterIndexedPropertyInfos(Registry: TMyRttiIndexedPropertyRegistry); static;
end;
// autogenerated code
class procedure TMyClass.RegisterIndexedPropertyInfos(Registry: TMyRttiIndexedPropertyRegistry): TMyRttiIndexedProperty;
begin
Registry.Register('MyArray', [TMyRttiIndex.Create('Index', TypeInfo(Integer))], TypeInfo(TMyObject), #TMyClass.GetMyArray, #TMyClass.SetMyArray);
end;
// When using RichRTTI you can omit this line and use the the RttiContext to find RegisterIndexedPropertyInfos
RegisterIndexedPropertyClass(TMyClass, #TMyClass.RegisterIndexedPropertyInfos);

Related

Delphi 10.2.3: Where is in Delphi the function VarType ()?

I am trying to convert Delphi2005 code to Delphi Tokyo 10.2.3 code.
The function VarType is no longer recognized.
I need the function VarType to determine the basic type of a variant variable. In general I find, according to many postings, that it should be in the unit System.Variants. However, if I search e.g. in:
http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/!!FUNCTIONS_System.html
It is not in this unit. Furthermore, I cannot find the unit variants, only a unit variant.
However, using the unit variant I get a runtime error:
Record, object or class type necessary
. So this doesn't work.
if (System.Variant.VarType(Value) and varTypeMask) =
System.Variant.varString then // VarType(Value) unbekannt
begin
TByte8Array(PRecFORMULA3(PBuf).Value)[0] := 0;
end;
Anyway I don't find VarType in System.variant. Does variants not exist anymore?
Can anyone help me?
The documentation you linked to is quite old. It is for Delphi 2009, which predates the introduction of Unit Scope Names. But even in that old documentation, VarType() is documented as being in the Variants unit (not in the Variant unit, which does not exist).
Unit Scope Names, like System, were added to RTL/VCL unit names in XE2 (thus, the Variants unit became System.Variants).
Embarcadero's newer DocWiki, which replaces the old Docs site, clearly shows that the VarType() function is indeed located in the System.Variants unit.
Make sure that either:
you have System.Variants in your uses clause:
uses
..., System.Variants;
you have System in your project's list of Unit Scope Names, and then you can use Variants in your uses clause:
uses
..., Variants;
Either way, you can then use VarType() as expected, without having to fully qualify it:
if (VarType(Value) and varTypeMask) = varString then
begin
TByte8Array(PRecFORMULA3(PBuf).Value)[0] := 0;
end;

Generics in Delphi and returning a reference to tlist<class>

I still use Delphi XE4 (newest compiler I use of multiple Delphi compilers) and need a specific workaround for the fact they completely hid FClients in TBasicAction in this version. I connect/disconnect clients runtime while setting enabled/disabled (to avoid flicker with ~100+ actions and ui elements) thus this workaround for XE4:
Here's my naive attempt and simply returning the field.
TmscBasicActionCrack = class(TBasicAction)
end;
{$IFDEF mymsDELPHIXE4}
TmscBasicActionHelper = class helper for TBasicAction
public
function Helper_Get_Private_FClients: TList<System.Classes.TBasicActionLink>;
end;
{$ENDIF}
{$IFDEF mymsDELPHIXE4}
//------------------------------------------------------------------------------
function TmscBasicActionHelper.Helper_Get_Private_FClients: TList<System.Classes.TBasicActionLink>;
begin
Result := Self.FClients;
end;
{$ENDIF}
However, I get error
E2003 Undeclared identifier: TList<>
I must admit I never go around to using generics with Delphi since I initially heard of stability problems + I need to maintain compability with Lazarus/FreePascal.
I am aware the most recent versions Delphi has altered class helpers again, but I am for now mostly interested in getting this to work with Delphi XE4
The error is indicating that the TList<T> type is unknown to the compiler. To use it you must include System.Generics.Collections in your uses clause.

Delphi 2005, can't build Indy 10

I'm receiving an error when building the IdMessageHelper.pas unit in the IndyProtocols90 package. All instances of LoadFromStream and LoadFromFile are claiming there is an issue with the signature:
[Error] IdMessageHelper.pas(78): E2250 There is no overloaded version of 'LoadFromStream' that can be called with these arguments
procedure Internal_TIdMessageHelper_LoadFromStream(AMsg: TIdMessage; AStream: TStream;
const AHeadersOnly: Boolean; const AUsesDotTransparency: Boolean);
var
LMsgClient: TIdMessageClient;
begin
if AUsesDotTransparency then begin
AMsg.LoadFromStream(AStream, AHeadersOnly);
end else
begin
// clear message properties, headers before loading
AMsg.Clear;
LMsgClient := TIdMessageClient.Create;
try
Internal_TIdMessageClientHelper_ProcessMessage(LMsgClient, AMsg, AStream, AHeadersOnly, False);
finally
LMsgClient.Free;
end;
end;
end;
I see that IdMessageHelper is new to this version, but the method that's being called (IdMessage.LoadFromStream for example), the arguments for it haven't changed from the last few versions - at least not for the ones that I have the source.
procedure TIdMessage.LoadFromStream(AStream: TStream; const AHeadersOnly: Boolean = False);
var
LMsgClient: TIdMessageClient;
begin
// clear message properties, headers before loading
Clear;
LMsgClient := TIdMessageClient.Create;
try
LMsgClient.ProcessMessage(Self, AStream, AHeadersOnly);
finally
LMsgClient.Free;
end;
end;
I'm pretty sure I removed all previous versions and packages since this was a clean install of D2005.
The IdMessageHelper unit introduces new LoadFrom...() and SaveTo...() methods for the TIdMessage component, to add an AUsesDotTransparency parameter when loading/saving emails.
In Delphi 2005 and later, it does this by defining a class helper (which is a feature introduced in Delphi 2005) to add new methods to the TIdMessage component without having to modify the IdMessage.pas unit itself. This allows Indy to let people use familiar IdMessage1.LoadFrom...() and IdMessage1.SaveTo...() syntax when using the new functionality 1.
Things were done this way so as not to cause interface breaking changes in the IdMessage unit itself. I blogged about this new addition at the time the IdMessageHelper.pas unit was first added to Indy:
New TIdMessage helper
In your case, the error message is complaining about Line 78:
AMsg.LoadFromStream(AStream, AHeadersOnly);
That line is the new 3-parameter TIdMessageHelper.LoadFromStream() method attempting to call the pre-existing 2-parameter TIdMessage.LoadFromStream() method when AUsesDotTransparency is True:
procedure TIdMessage.LoadFromStream(AStream: TStream; const AHeadersOnly: Boolean = False);
I have tested this new class helper in later Delphi versions and it works fine for me. You should not be getting a compiler error, as there should not be any ambiguity.
However, I have not tested the class helper in Delphi 2005 specifically (as I do not have that version installed), so it is possible that the compiler error could be indicating that class helpers (being a new language feature at the time) were still a little buggy, and were fixed later on.
If you can't find the cause of the ambiguity, you can work around the issue by modifying IdMessageHelper.pas to undefine HAS_CLASS_HELPER for Delphi 2005 1, and then recompile Indy again.
1: In older versions of Delphi where class helpers are not available, IdMessageHelper.pas also defines several standalone TIdMessageHelper_LoadFrom...() and TIdMessageHelper_SaveTo...() functions, so people can still utilize the new AUsesDotTransparency functionality, just with a less-desirable calling syntax.
EDIT: it turns out that class helpers were very buggy in Delphi 2005, and were not officially supported until Delphi 2006:
Class helpers have now been formally introduced in the Win32 compiler [in Delphi 2006]. In Delphi 2005 class helpers were not formally available, and although you could use them they were actually quite buggy. It was quite easy to get internal compiler errors while using them, nothing you could complain to with Borland about as this feature was not officially supported.
So, I have now disabled the TIdMessageHelper helper class in Delphi 2005, and have updated the above mentioned blog article accordingly.

When a class implements a descendant interface, why doesn't it automatically count as implementing the base interface?

What's the reason this won't compile?
type
IInterfaceA = interface ['{44F93616-0161-4912-9D63-3E8AA140CA0D}']
procedure DoA;
end;
IInterfaceB = interface(IInterfaceA) ['{80CB6D35-E12F-462A-AAA9-E7C0F6FE0982}']
procedure DoB;
end;
TImplementsAB = class(TSingletonImplementation, IInterfaceB)
procedure DoA;
procedure DoB;
end;
var
ImplementsAB: TImplementsAB;
InterfaceA: IInterfaceA;
InterfaceB: IInterfaceB;
begin
ImplementsAB := TImplementsAB.Create;
InterfaceA := ImplementsAB; >> incompatible types
...
end
In contrast this is how I make it work:
InterfaceA := ImplementsAB as InterfaceB;
or
InterfaceA := InterfaceB;
I mean, if IInterfaceB inherits from IInterfaceA and TImplementsAB implements IInterfaceB, it wouldn't be logical to also implement IInterfaceA and be type compatible?
This so because early OLE/COM had a bug and Borland decided to be compatible with it. This is mentioned in this article: New Delphi language feature: Multiple inheritance for interfaces in Delphi for .NET. The solution is to list all ancestor interfaces explicitly in the class as Mikael wrote.
Some quotes from the linked article:
The problem was in COM itself. To load a module, COM would load the DLL, GetProcAddress on a well-known entry point that was supposed to be exported from the DLL, call the DLL function to obtain an IUnknown interface, and then QueryInterface for IClassFactory. The problem was, when Microsoft added support for IClassFactory2, they added the QueryInterface for IClassFactory2 after the existing code that queried for IClassFactory. IClassFactory2 would only be requested if the query for IClassFactory failed.
Thus, COM would never request IClassFactory2 on any COM server that implemented both IClassFactory2 and IClassFactory.
This bug existed in COM for a long time. Microsoft said that they couldn't fix the COM loader with an OS service pack because both Word and Excel (at the time) relied on the buggy behavior. Regardless of whether it's fixed in the latest releases of COM or not, Borland has to provide some way to preserve this behavior in Win32 Delphi for the forseeable future. Suddenly adding all ancestors into an implementing class that weren't there before is very likely to break existing code that unintentionally falls into the same pattern as the COM loader.
Another way to make it work is to include both interfaces in the class declaration.
TImplementsAB = class(TSingletonImplementation, IInterfaceA, IInterfaceB)
procedure DoA;
procedure DoB;
end;
I guess this is what is required for the compiler to realize that TImplementsAB implements both IInterfaceA and IInterfaceB.

Saving a TObject to a File

How can one save an Object, in its current state, to a file? So that it can immediately be read and restored with all its variables.
As already stated, the easiest way is to use a Stream and its WriteComponent and ReadComponent methods.
But be aware that :
- it works for descendants of TComponent, not plain TObject;
- only for the published properties (those saved in a dfm), not the public ones nor (a fortiori) the privwte ones;
- you have to pay a special attention for the Name property when restoring the component.
You may find some code you could use in these SO answers: Replace visual component at runtime in Delphi, Duplicating components at Run-Time
What you are looking for is called object persistance. This article might help, and there are many others if you google for "delphi persisting objects".
If you descend your object from TComponent, you can use some built-in functionality to stream the object to a file. I think this only works well for simple objects.
Some sample code to get you started:
unit Unit1;
interface
uses
Classes;
type
TMyClass = class(TComponent)
private
FMyInteger: integer;
FMyBool: boolean;
FMyString: string;
public
procedure ToFile(AFileName: string);
published
property MyInteger: integer read FMyInteger write FMyInteger;
property MyString: string read FMyString write FMyString;
property MyBool: boolean read FMyBool write FMyBool;
end;
implementation
{ TMyClass }
procedure TMyClass.ToFile(AFileName: string);
var
MyStream: TFileStream;
begin
MyStream := TFileStream.Create(AFileName);
try
Mystream.WriteComponent(Self);
finally
MyStream.Free;
end;
end;
end.
There's also a roll-your-own XML method here on S.O.
Very simple and efficient solution: DragonSoft's XML Class Serializer
Also you can use JVCL TJvAppXMLFileStorage:
uses
JvAppXMLStorage;
var
Storage: TJvAppXMLFileStorage;
begin
Storage := TJvAppXMLFileStorage.Create(nil);
try
Storage.WritePersistent('', MyObject);
Storage.Xml.SaveToFile('S:\TestFiles\Test.xml');
Storage.Xml.LoadFromFile('S:\TestFiles\Test.xml');
Storage.ReadPersistent('', MyObject);
finally
Storage.Free;
end;
end;
There is a good tutorial here. Keep in mind that you have to have RTTI (run time type information) to save an object at run-time using this approach, so it will only capture published properties of a class.
You've already gotten some good answers to your question. Depending on what you're actually doing, it might be desirable to use a pre-built library or component to save objects. This is an inexpensive and nifty library/component set that makes it trivial to persist and restore objects, and pretty easily (i.e., with a little bit of code) accommodates persisting even unpublished members of an object:
http://www.deepsoftware.ru/rsllib/index.html Not something that's rocket science, but if you're doing a lot of this sort of thing this component provides a nice framework for it.
Developer Express also includes a general purpose cxPropertiesStore component as part of the ExpressEditors library that comes with some of their components.

Resources