ShowMessage(TRttiContext.Create.GetType(TStringList)
.GetProperty('Strings').ToString);
Above code fails as .GetProperty returns nil on properties like "Strings", "Objects", "Values" (ones with indexers). I assume this is a known limitation and the question is if there's any way to access those indexed properties (preferably without falling back to the old RTTI utils).
Indexed properties don't have RTTI, but the underlying fields do. So you can access TStringList.FList directly through RTTI. Be careful, though, as this involves raw pointers, and make sure you don't go beyond the Count property. You can do similar things with other classes.
There are gaps in the RTTI. Indexed properties are one.
But when you don't get the property name, why you try to access them? ;-) When you know there is such a property you can try a cast instead.
You don't get RTTI for method parameters of the typ
procedure MyProc(const AParam: array of AType)
also.
Anybody knowing more elements were we can't get RTTI?
Related
I have a class which contains the profile information of a project of mine, with loads and loads of information, its called PROFILE. To have an easy access to all the different properties of this profile I have an indexed default property. This default property(TCHANNELLIST) is a record containig again a few properties as well as another record(TCHANNELPARAMETER). The property CHANNEL is the default property (indexed) of the default property TCHANNELLIST.
Now I do have a problem when constructing the setter of these properties. (To clearify: the read function is not the problem! Please don't bother except the solution can be found in it).
The Question: How do I construct the Property/procedure/function to get the following code running
MyProfile[i][j].Name := 'Thanks_for_the_help';
Since more is more here is the structure of my records I have used. I am also willing to change the general structure if there is a better way, so I am open for suggestions.
TChannelParameter = record
// each channel gets one record for itself
public
channelType : TKanalTyp;
display_number : Integer;
Name : string;
// and a few other but you will get the idea...
end;
TChannelList = record
private
FChannelparameter_List : array of TChannelParameter ;
function GetChannelParameter(Index: Integer): TChannelParameter ;
procedure SetChannelParameter(Index: Integer); //Here I need some help
public
property Channal_GlobalNumber[index: Integer]: TChannelParameter read GetChannelParameter write SetChannelParameter; //Here I need some help
end;
To be honest I just don't have an idea (and I cant find any help online) to get that line of code running. To Read everything is not a problem but to write stuff into the "subitem" of an indexed default property is a riddle to me. Here it does not matter if I use this
A_Channel_list[i].name := 'aName';
or
MyProfile[i][j].name := 'aName';
Both setters are till now not constructed! Since I lack the basic knowledge to do so! (further I did not include the class since the handling should be the same)
If I get one running the other one should not be a problem anymore. Maybe somebody knows that this kind of operation however is not possible, please also let me know this! I will then reconstruct my class and records.
For what you are trying to achieve you don't even need Channal_GlobalNumber property to be writable. Having it readable would be enough provided that your TChannelParameter object is class type instead record type that you have now.
You see if you declare your TChannelParameter as class your Channal_GlobalNumber property will return you a reference (pointer) to that object so you can then access any of its fields/properties like if you would have variable referencing such object.
This means that you could then be changing name property/field of individual channels simply by using:
A_Channel_list[i].name := 'aName';
So why doesn't this work when your TChannelParameter is of record type. As Uwe Raabe wrote in his comment your indexed property of record type won't return you the original record from your array but instead a copy of it. Therefore making any hanges on it would not change the original record from your array but instead copy.
EDIT: Don't forget that if you change your TChannelParameter object to class type you will have to write special routines for creating such object when you are changing size of your FChannelparameter_List array as in such case this array is array of poiters to TChannelParameter classes.
Now if you want to realy avoid this and use records only you could write multiple indexed properties in TChannelList object one for each field of TChannelParameter record. So then you can use these properties getter or setter method to Access items in your array.
Unfortunately I can't write you a code example righht now since I'm not on my development computer.
I need to be able to pass the same set of structures (basically arrays of different records) over two different interfaces
The first (legacy) which is working requires a pointer to a record and the record size
The second, which I am attempting to develop, is type-safe and requires individual fields to be set using Get/Set methods for each field
Existing code uses records (probably around 100 or so) with memory management being handled in a 3rd party DLL (i.e. we pass the record pointer and size to it and it deals with memory management of new records).
My original thought was to bring the memory management into my app and then copy over the data on the API call. This would be easy enough with the old interface, as I just need to be able to access SizeOf() and the pointer to the record structure held in my internal TList. The problem comes when writing the adapter for the new type-safe interface
As these records are reliant on having a known size, there is heavy use of array 0..n of char static arrays, however as soon as I try to access these via 2010-flavour RTTI I get error messages stating 'Insufficient RTTI information available to support this operation'. Standard Delphi strings work, but old short-strings don't. Unfortunately, fixing string lengths is important for the old-style interface to work properly. I've had a look at 3rd party solutions such as SuperObject and the streaming in MorMot, though they can't do anything out of the box which doesn't give me too much hope of a solution not needing significant re-work.
What I want to be able to do is something like the following (don't have access to my Delphi VM at the moment, so not perfect code, but hopefully you get the gist):
type
RTestRec = record
a : array [0..5] of char;
b : integer;
end;
// hopefully this would be handled by generic <T = record> or passing instance as a pointer
procedure PassToAPI(TypeInfo: (old or new RTTI info); instance: TestRec)
var
Field: RTTIField;
begin
for Field in TypeInfo.Fields do
begin
case Field.FieldType of
ftArray: APICallArray(Field.FieldName, Field.Value);
ftInteger: APICallInteger(Field.FieldName, Field.Value.AsInteger);
...
end;
end;
Called as:
var
MyTestRec: RTestRec;
begin
MyTestRec.a := 'TEST';
MyTestRec.b := 5;
PassToAPI(TypeInfo(TestRec), MyTestRec);
end;
Can the lack of RTTI be forced by a Compiler flag or similar (wishful thinking I feel!)
Can a mixture of old-style and new-style RTTI help?
Can I declare the arrays differently to give RTTI but still having the size constraints needed for old-style streaming?
Would moving from Records to Classes help? (I think I'd need to write my own streaming to an ArrayOfByte to handle the old interface)
Could a hacky solution using Attributes help? Maybe storing some of the missing RTTI information there? Feels like a bit of a long-term maintenance issue, though.
As we can find a Property or an Object using RTTI, can we search for a certain function or procedure (not from an object as a method but from an unit) loaded in memory knowing just it's name?
And if we can, is it possible to execute it sending it's parameters?
Delphi's RTTI system is based around types. However, procedures and functions with unit scope are not associated with types and so cannot be reached using RTTI.
I have a Delphi 6 class object that contains an array of 30 Variants, each of which is exposed via a different indexed property. For example:
property responseCode: integer
Index 7 read getIndexedProperty_integer write setIndexedProperty_integer;
I did this to make using the array of Variants easier (helps the IDE's auto-complete) and to provide type safety. It works fine but now I have a wrinkle. The array of Variants are initialized to NULL when the class that wraps it is constructed, so I can tell if a particular variant has ever been instantiated with a value. A consequence of this is if only some of the Variants are instantiated (given valid values), any attempt to access a property that currently represents a NULL Variant will cause a Variant conversion error when Delphi tries to convert the variant to the type declared by the indexed property.
I would much rather not declare an "isValid" property for each indexed property. I was wondering if there was a way to use the TypeInfo library to get the raw value of the underlying Variant without having to access the indexed property directly and thus triggering the conversion Exception. Then I could write code like (using the example property above):
isValidProperty(responseCode);
and that function would return TRUE if the Variant underlying the responseCode property is not NULL and FALSE if it is.
I know I can walk the PPropList property list for the class and access the properties by name, but then I would have to use code like:
isValidProperty('responseCode');
and pass the property name in string form instead of passing in the property directly like the first isValidProperty() above. Is there a way to do this?
So you want "to get the raw value of the underlying Variant without having to access the indexed property directly and thus triggering the conversion Exception". So long as you can access the underlying Variant itself, yes, you can. You will need to change the container class itself most likely.
From the Delphi XE2 help page on variant types:
The standard function VarType returns a variant's type code. The
varTypeMask constant is a bit mask used to extract the code from
VarType's return value, so that, for example,
VarType(V) and varTypeMask = varDouble
returns True if V contains a Double or an
array of Double. (The mask simply hides the first bit, which indicates
whether the variant holds an array.) The TVarData record type defined
in the System unit can be used to typecast variants and gain access to
their internal representation.
You should be able to use a combination of the methods and records mentioned here to find out anything you want about the internal data inside the variant, including if it's a NULL variant, as well as getting direct access to it.
(This system seems slightly dodgy design to me: it doesn't seem a very type safe implementation... see my comment above. I think a design based on the actual types of the values you are expecting might be safer. But, this will let you achieve your goal.)
I want to dynamically get a property value from an object instance.
I was able to get the class properties, ordinal types and strings. The delphi source of the GetPropValue does not support tkInterface. Is there any way of getting the interface using the property info. BTW all the properties exposed are published properties.
for time being, i am using the TObject as the return type. GetPropValue returns the address of the object instance. I am typecasting that to TObject and returning the result.
If I understand you right you want to use GetInterfaceProp() function. Usage is same as GetPropValue() but it returns an IInterface which you can "cast" to right type using ie Supports().