The answer to Which language elements can be annotated using attributes language feature of Delphi? suggests that it is possible to add attributes to ordinary procedures and functions. My question is how can one retrieve that information given the string name of the procedure or function?
[myProcAttribute('Some useful info')]
procedure myProc;
begin
// Do something
end;
Given the string 'myProc' I would like to retrieve the associate attribute.
I'm using XE6
The article you link to says:
There's no way of retrieving any sort of RTTI for "unit" level or local variables and procedures, hence no way of retrieving information about attributes.
I believe that this is correct. The documentation lists a number of methods for TRttiContext. These are:
Create
DropContext
FindType
Free
GetPackages
GetType
GetTypes
KeepContext
These give you means to locate types, but not procedures. Once you locate a type you can enumerate its methods, but that's no use to here because you want to find a procedure rather than a method.
Related
Is there any way to get the names of the parameters of a given method of a class/object in an array/delimited string in Delphi 7? Somehow like this:
var
s : string;
aSL : TStringList;
begin
...
// using rtti calls in TMethodUtility.collectParamNames
s := TMethodUtility.collectParamNames( TStringList.addObject );
// or
TMethodUtility.collectParamNames( TStringList.addObject, aSL );
...
end;
Thanks in advance!
It is technically possible (otherwise the IDE's Form Designer could not generate code for component event handlers at design-time), however, there are important caveats that will hinder your goal (and make the particular example in your question impossible to resolve):
the necessary RTTI is only generated for properties that are declared as published (such as component events). RTTI is not generated for methods themselves (even published ones), or for properties that are not published.
TObject has a public MethodAddress() method for getting the memory address of a published method (the DFM streaming system uses this when hooking up event handlers), however you cannot get the necessary RTTI from a method pointer alone.
IF you can match a given method pointer to the value of a published event, then you can extract the parameter names from the event's RTTI. Obtain a TypInfo.PPropInfo pointer for the event using the TypInfo.GetPropInfo() function, then pass its PropType field value to the TypInfo.GetTypeData() function to get a TypInfo.PTypeData pointer, and then you can iterate through its ParamList field (which is an array of records containing ParamName and TypeName fields).
See the following blog article on this topic for more details:
Getting the parameters of published methods.
For what you are attempting, a general-purpose solution would require Extended RTTI that was introduced in Delphi 2010 and thus is not available in Delphi 7. Extended RTTI is not limited to published items, and is much more detailed than what the old-style RTTI provides.
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.
Delphi XE6 - looking to write 3 or 4 RELATED applications, all of which will be using the same basic data routines, so I am looking to make a common, flexible API which all the apps will use. My issue comes about with one portion of the API, moving COMPANY data. Data on a company will have around 30 attributes, MOST of which will come from a table, but a handful will be calculations, etc... I can put this in a simple class.
My question comes from the fact that I will not always know HOW MANY companies will get passed between routines. For example, I will have an API call that says "Look for Company name starting with some value. There might be 1 or there might be 15. How do I process the data in the subroutine but then provide access to it in the calling routine. My first thought is to use a VAR TObjectList. The called routine will then CLEAR the TObjectList, process the data, and ADD to the TObjectList. I have not worked with TObjectList before. Is there a reason NOT to do this?
You can use a callback routine: a routine (A) that you pass as parameter to the companies extracting routine (B). B calls A for each matching record found in the dataset.
{MainForm}
procedure TMainForm.HandleCompany(ACompany: TCompany);
begin
//For example
Memo1.Lines.Add(ACompany.Name);
Memo1.Lines.Add(ACompany.Address);
end;
procedure TMainForm.Button1Click(Sender: TObject);
begin
MyAPI.GetCompanies(HandleCompany);
end;
{MyAPI}
type
TCompanyHandler = procedure (ACompany: TCompany) {of object};
procedure GetCompanies(CompanyHandler: TCompanyHandler);
var
Company: TCompany;
begin
for Company in Companies do
CompanyHandler(Company);
end;
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.
It looks like the Delphi compiler does not honor const record parameters when
"records-with-methods" are involved.
Having not tried to abuse the const convention previously, I was a little surprised
to find the compiler accepted code like that:
type
TTest = record
Field : String;
procedure Update;
end;
procedure TTest.Update;
begin
Field := Field + '+1';
end;
procedure DoStuff(const t : TTest);
begin
ShowMessage(t.Field);
t.Update;
ShowMessage(t.Field);
end;
While if you try to do a
t.Field:='doh'; in DoStuff f.i., the compiler will properly complain, but you're allowed to call methods that modify the "const" record without even a hint or warning. So this is different behavior than for reference types (such as classes or dynamic arrays), where direct field writes are allowed (as const only restricts changes to the parameter itself).
Addendum: this allows to modify declared compile-time constants this way too, as in:
const
cTest : TTest = (Field : '1');
...
cTest.Update; // will show '1' then '1'+'1'
ShowMessage(cTest.Field); // will show '1' (because optimized at compile-time)
Is that an accepted/documented behavior? or just a compiler shortcoming?
const never places any restrictions on method calls in Delphi, be they on records or instances of classes. So I don't think there is anything inconsistent with the treatment of method calls.
If methods could not be called on record passed as a const parameter, then that would pretty much render records with methods useless. It would mean, for example, that a property getter could not be called. In order to place restrictions on such records passed as const, there would need to be an equivalent concept to the const member functions of C++. That would allow the compiler to know that certain methods were non-mutating.
David analyzed the restriction pretty well. If the compiler was to check out such details it could really do it with some penalty. Additionally I don't see anything wrong with the compiler's behaviour. The method which gets the record can't directly alter its data, but only when using the method it contains. The record in this case works like an object: you can in the same way an object as a const and still have the same problem you described, ie. the object's methods can be used to alter its data.
The benefit of the object is, that such methods can be declared to be private, which enables you to protect its data. You could even create an inherited class which does just that, namely hiding all possibility to alter its data. Maybe you want to try this approach?