With RttiContext.FindType('Classes.TStringList') I get RttiType of TStringList with no problem. But with RttiContext.FindType('MyUnit.TMyClass') I always get nil (of course MyUnit is in uses clause). Why, what is wrong?
Example:
unit MyUnit;
interface
uses
Classes;
type
TMyClass = class(TStringList)
end;
implementation
end.
Main unit:
...
uses
MyUnit,
...
var
oCont: TRttiContext;
oType: TRttiType;
begin
oCont := TRttiContext.Create;
try
oType := oCont.FindType('MyUnit.TMyClass'); <== oType = nil !!
...
Probably the class has not included by the delphi linker in the final executable. A fast try can be the following:
Declare a static method on your class. This method should be an empty method, a simple begin end.
Call this static method in the initialization section of this unit.
Ensure that the unit is referenced in your project somewhere.
Now you should see the class with TRttiContext.FindType.
It could be a handful of things. Hard to say without seeing your code, but here are a few suggestions to look at. Is TMyClass a public type in the interface section? Is RTTI generation turned on for that unit? Is MyUnit in a package that hasn't been loaded yet?
Related
I have to develop a game in Lazarus for school, and I ran into an error that I can't find a solution for.
I have a dynamic array where I want to store classes in so that I can call procedures on those classes.
TKarte is the ancestor class, and I have many different classes (all representing different Cards) that have the same procedures as the ancestor class.
unit Karten;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils, Dialogs, ExtCtrls;
type
TKarte=class
public
class procedure GetPicture(Objekt:TImage);virtual;
class procedure OnPlay;virtual;
end;
type
Karte = class(TKarte)
public
class procedure GetPicture(Objekt:TImage);override;
class procedure OnPlay;override;
end;
type
Karte2 = class(TKarte)
public
class procedure GetPicture(Objekt:TImage);override;
class procedure OnPlay;override;
end;
implementation
class procedure Karte.OnPlay();
begin
ShowMessage(ClassName);
end;
class procedure Karte.GetPicture(Objekt:Timage);
begin
Objekt.Picture.LoadFromFile('Grafiken\Karten\Mindcontrol.png');
end;
class procedure Karte2.GetPicture(Objekt:Timage);
begin
Objekt.Picture.LoadFromFile('Grafiken\Karten\Mindcontrol.png');
end;
class procedure Karte2.OnPlay();
begin
ShowMessage(Karte2.ClassName);
end;
class procedure TKarte.OnPlay();
begin
ShowMessage(ClassName);
end;
class procedure TKarte.GetPicture(Objekt:TImage);
begin
Objekt.Picture.LoadFromFile('Grafiken\Sprites\Buttons\Button 1.png');
end;
end.
This is how I add them and call them from the array at the moment:
Hand: array of Class of TKarte;
procedure TSplashScreen.Button2Click(Sender: TObject);
begin
SetLength(Hand,Length(Hand)+1);
Hand[High(Hand)] := Karte;
Hand[High(Hand)].OnPlay();
Hand[High(Hand)].GetPicture(Image1);
end;
There is no problem with running the program, but when I try to add a new component, or I press CTRL + Space for the Auto-Complete, it gives me an error at the declaration of the array:
Error: Anonymous Class definitions are not allowed
I have tried to find an answer to this problem, but there seems to be noone with the same problem :(
Can somebody help me?
Offhand, I see nothing wrong with the code, and as you said the code does run correctly. It is only the IDE that is having a problem with it. As such, I would not suggest declaring the array's element type directly in the array's declaration. I would suggest declaring an alias for it before declaring the array, eg:
type
TKarte=class
...
end;
TKarteClass = Class of TKarte;
...
Hand: array of TKarteClass;
There are few problems in your code.
When creating array of some type you don't define the said type in the array itself but only tell which type needs to be used. So your array definition would be:
Hand: array of TKarte;
I also see that you have declared all your procedures as class procedure. There is a fundamental difference between class methods and ordinary methods. Most likely you won't need them to be declared as class methods based on what you are trying to achieve. While I could not find suitable Lazarus documentation on this topic you may refer to Delphi documentation on Class methods to get better understanding about their difference.
This is a bit puzzling for me as I'm working on an unit with several dozens of interfaces that are all based on this base interface definition:
type
IDataObject = interface(IInterface)
['{B1B3A532-0E7D-4D4A-8BDC-FD652BFC96B9}']
function This: TDataObject;
end;
ISomeObject = interface(IDataObject)
['{7FFA91DE-EF15-4220-A43F-2C53CBF1077D}']
<Blah>
end;
This means they all have a method 'This' that returns the class behind the interface, which is sometimes needed to put in listviews and stuff, but for this question it doesn't really matter because I want a generic class with additional functions that can be applied to any derived interface. (And any derived interface has their own GUID.) This is the generic class:
type
Cast<T: IDataObject> = class(TDataObject)
class function Has(Data: IDataObject): Boolean;
class function Get(Data: IDataObject): T;
end;
Doesn't look too complex and the use of class methods is because Delphi doesn't support global generic functions, unless they're in a class. So in my code I want to use Cast<ISomeObject>.Has(SomeObject) to check if the objects supports the specific interface. The Get() function is just to return the object as the specific type, if possible. So, next the implementation:
class function Cast<T>.Get(Data: IDataObject): T;
begin
if (Data.QueryInterface(T, Result) <> S_OK) then
Result := nil;
end;
class function Cast<T>.Has(Data: IDataObject): Boolean;
begin
Result := (Data.QueryInterface(T, Result) = S_OK);
end;
And this is where it gets annoying! Elsewhere in my code I use if (Source.QueryInterface(ISomeObject, SomeObject) = 0) then ... and it works just fine. In these generic methods the ISomeObject is replaced by T and should just work. But it refuses to compile and gives this error:
[dcc64 Error] DataInterfaces.pas(684): E2010 Incompatible types:
'TGUID' and 'T'
And that's annoying. I need to fix this but can't find a proper solution without hacking deep into the interface code of the System unit. (Which is the only unit I'm allowed to use in this code as it needs to run on many different platforms!)
The error is correct as QueryInterface expects a TGUID as parameter but it seems to get that from ISomeObject. So why not from T?
I think I'm trying to do the impossible here...
To be a bit more specific: Source.QueryInterface(ISomeObject, SomeObject) works fine without the use of any other unit. So I would expect it to work with a generic type, if that type is limited to interfaces. But that's not the case and I want to know why it won't accept T while it does accept ISomeObject.
Can you explain why it fails with a generic type and not a regular interface type?
QueryInterface() takes a TGUID as input, but an interface type is not a TGUID. The compiler has special handling when assigning an interface type with a declared guid to a TGUID variable, but that doesn't seem to apply inside of a Generic parameter that uses an Interface constraint. So, to do what you are attempting, you will just have to read the interface's RTTI at runtime to extract its actual TGUID (see Is it possible to get the value of a GUID on an interface using RTTI?), eg:
uses
..., TypInfo;
class function Cast<T>.Get(Data: IDataObject): T;
var
IntfIID: TGUID;
begin
IntfIID := GetTypeData(TypeInfo(T))^.GUID;
if (Data.QueryInterface(IntfIID, Result) <> S_OK) then
Result := nil;
end;
class function Cast<T>.Has(Data: IDataObject): Boolean;
begin
Cast<T>.Get(Data) <> nil;
end;
That being said, why are you duplicating functionality that the RTL already provides natively for you?
Your entire Cast class is unnecessary, just use SysUtils.Supports() instead (the SysUtils unit is cross-platform), eg:
uses
..., SysUtils;
//if Cast<ISomeObject>.Has(SomeObject) then
if Supports(SomeObject, ISomeObject) then
begin
...
end;
...
var
Intf: ISomeObject;
//Intf := Cast<ISomeObject>.Get(SomeObject);
if Supports(SomeObject, ISomeObject, Intf) then
begin
...
end;
Also, your IDataObject.This property is completely unnecessary, as you can directly cast an IDataObject interface to its TDataObject implementation object (Delphi has supported such casting since D2010), eg:
var
Intf: IDataObject;
Obj: TDataObject;
Intf := ...;
Obj := TDataObject(Intf);
I've been trying to extend a bunch of library classes inheriting from the same base class by overriding a virtual method defined in that base class. The modification is always the same so instead of creating N successors of the library classes I decided to create a generic class parameterized by the library class type, which inherits from the class specified by parameter and overrides the base class' method.
The problem is that the code below doesn't compile, the compiler doesn't allow inheriting from T:
program Project1;
type
LibraryBaseClass = class
procedure foo; virtual;
end;
LibraryClassA = class(LibraryBaseClass)
end;
LibraryClassB = class(LibraryBaseClass)
end;
LibraryClassC = class(LibraryBaseClass)
end;
LibraryClassD = class(LibraryBaseClass)
end;
MyClass<T:LibraryBaseClass> = class(T) //Project1.dpr(20) Error: E2021 Class type required
procedure foo; override;
end;
procedure LibraryBaseClass.foo;
begin
end;
procedure MyClass<T>.foo;
begin
end;
begin
MyClass<LibraryClassA>.Create.foo;
MyClass<LibraryClassB>.Create.foo;
MyClass<LibraryClassC>.Create.foo;
MyClass<LibraryClassD>.Create.foo;
end.
Any ideas how to make this work? Maybe there is a way to trick the compiler into accepting something equivalent because, for example, inheriting from Dictionary<T,T> compiles without problems.
Or what would you do if you had the same goal as I? Keep in mind that in the real situation I need to override more than one method and add some data members.
Thank you
As you've been told already, this is valid with C++ templates, not with C# or Delphi generics. The fundamental difference between templates and generics is that conceptually, each template instantiation is a completely separately compiled type. Generics are compiled once, for all possible types. That simply is not possible when deriving from a type parameter, because you could get constructs such as
type
LibraryBaseClass = class
procedure foo; virtual;
end;
LibraryClassA = class(LibraryBaseClass)
procedure foo; reintroduce; virtual;
end;
LibraryClassB = class(LibraryBaseClass)
end;
MyClass<T:LibraryBaseClass> = class(T)
procedure foo; override; // overrides LibraryClass.foo or LibraryClassA.foo ?
end;
Yet this can work in C++, because in C++ MyClass<LibraryClassA> and MyClass<LibraryClassB> are completely separated, and when instantiating MyClass<LibraryClassA>, foo is looked up and found in LibraryClassA before the base class method is found.
Or what would you do if you had the same goal as I? Keep in mind that in the real situation I need to override more than one method and add some data members.
It is possible to create types at runtime, but almost certainly an extremely bad idea. I have had to make use of that once and would have loved to avoid it. It involves reading the VMT, creating a copy of it, storing a copy of the original LibraryBaseClass.foo method pointer somewhere, modifying the VMT to point to a custom method, and from that overriding function, invoking the original stored method pointer. There's certainly no built-in language support for it, and there's no way to refer to your derived type from your code.
I've had a later need for this in C# once, too, but in that case I was lucky that there were only four possible base classes. I ended up manually creating four separate derived classes, implementing the methods four times, and using a lookup structure (Dictionary<,>) to map the correct base class to the correct derived class.
Note that there is a trick for a specific case that doesn't apply to your question, but may help other readers: if your derived classes must all implement the same interface, and requires no new data members or function overrides, you can avoid writing the implementation multiple times:
type
IMySpecialInterface = interface
procedure ShowName;
end;
TMySpecialInterfaceHelper = class helper for TComponent
procedure ShowName;
end;
procedure TMySpecialInterfaceHelper.ShowName;
begin
ShowMessage(Name);
end;
type
TLabelWithShowName = class(TLabel, IMySpecialInterface);
TButtonWithShowName = class(TButton, IMySpecialInterface);
In that case, the class helper method implementation will be a valid implementation for the interface method.
In Delphi XE and higher, you could also try something completely different: TVirtualMethodInterceptor.
What you are attempting to do is simply not possible with Delphi generics.
For what it is worth, the equivalent code is also invalid in C# generics. However, your design would work with C++ templates.
I probably misunderstood your description of the problem but from your simplified example it seems you could "turn it around" and insert a class in the hierarchy in the middle like this:
program Project1;
type
LibraryBaseClass = class
procedure foo; virtual;
end;
LibraryBaseFooClass = class(LibraryBaseClass)
procedure foo; override;
end;
LibraryClassA = class(LibraryBaseFooClass)
end;
LibraryClassB = class(LibraryBaseFooClass)
end;
LibraryClassC = class(LibraryBaseFooClass)
end;
LibraryClassD = class(LibraryBaseFooClass)
end;
procedure LibraryBaseClass.foo;
begin
end;
procedure LibraryBaseFooClass.foo;
begin
end;
begin
LibraryClassA.Create.foo;
LibraryClassB.Create.foo;
LibraryClassC.Create.foo;
LibraryClassD.Create.foo;
end.
I'm trying to fetch an interface using D2010 RTTI.
program rtti_sb_1;
{$APPTYPE CONSOLE}
{$M+}
uses
SysUtils,
Rtti,
mynamespace in 'mynamespace.pas';
var
ctx: TRttiContext;
RType: TRttiType;
MyClass: TMyIntfClass;
begin
ctx := TRttiContext.Create;
MyClass := TMyIntfClass.Create;
// This prints a list of all known types, including some interfaces.
// Unfortunately, IMyPrettyLittleInterface doesn't seem to be one of them.
for RType in ctx.GetTypes do
WriteLn(RType.Name);
// Finding the class implementing the interface is easy.
RType := ctx.FindType('mynamespace.TMyIntfClass');
// Finding the interface itself is not.
RType := ctx.FindType('mynamespace.IMyPrettyLittleInterface');
MyClass.Free;
ReadLn;
end.
Both IMyPrettyLittleInterface and TMyIntfClass = class(TInterfacedObject, IMyPrettyLittleInterface) are declared in mynamespace.pas, in particular
unit mynamespace;
interface
type
IMyPrettyLittleInterface = interface
['{6F89487E-5BB7-42FC-A760-38DA2329E0C5}']
end;
TMyIntfClass = class(TInterfacedObject, IMyPrettyLittleInterface)
end;
//...
Do anyone know why this doesn't work? Is there a way to solve my problem? Thanks in advance!
This is a strange behavior you have found. You can find the type using:
RType := ctx.GetType(TypeInfo(IMyPrettyLittleInterface));
But after you have done this once you can access it by name, so if you need to access it by Name you can do the following to make it work.
Example Program:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils,
Rtti,
TypInfo,
Unit1 in 'Unit1.pas';
var
ctx : TRttiContext;
IType : TRttiType;
begin;
ctx := TRttiContext.Create;
IType := ctx.FindType('Unit1.IExample');
if Assigned(IType) then
begin
writeln('Found');
Writeln(IType.QualifiedName);
end
else
writeln('Not Found');
ReadLn;
end.
Example Unit:
unit Unit1;
interface
type
IExample = interface
['{D61F3245-13FB-44BF-A89D-BB358FAE7D19}']
end;
implementation
uses Rtti;
var
C : TRttiContext;
initialization
C.GetType(TypeInfo(IExample));
end.
The problem is that neither the VMT nor the typeinfo of classes which implement an interface contain any references to the typeinfo of those interfaces. The typeinfo for the interfaces is then eliminated by the linker if not otherwise referenced in the program. To fix this, there would need to be a typeinfo format change for classes to reference the implemented interfaces' typeinfo, or else all interfaces would need to be strongly linked into the executable. Other kinds of fixes, such as strong-linking interfaces that are implemented by linked-in classes without actually including references in the class typeinfo, are problematic owing to how the compiler's integrated smart linker works.
Another way around this issue is to use the directive {$STRONGLINKTYPES ON}. This will cause all types in the EXE, BPL or DLL's root type table (the index that lets the RTL map qualified names to types) to be linked in with strong fixups rather than weak fixups. Symbols that only have weak fixups referencing them are eliminated by the smart linker; if one or more strong fixups references the symbol, then it gets included in the final binary and the weak references don't get nil'ed (actually #PointerToNil) out.
As described in other answers, TypeInfo(ITheInterface) in non-dead code fixes the problem; this is because the TypeInfo() magic function creates a strong fixup to the interface.
Does IMyPrettyLittleInterface have a GUID? I don't think the RTTI system will recognize it if it doesn't.
RTTI is generated for types declared while the $M switch is active. Like all compiler directives, that switch has per-unit scope. You set it in your DPR file, but that setting has no effect in the unit where you declared your types.
Set that switch prior to your interface and class declarations:
type
{$M+}
IMyPrettyLittleInterface = interface
['{6F89487E-5BB7-42FC-A760-38DA2329E0C5}']
end;
TMyIntfClass = class(TInterfacedObject, IMyPrettyLittleInterface)
end;
I'm trying to parse objects to XML in Delphi, so I read about calling the object's ClassInfo method to get its RTTI info.
The thing is, this apparently only works for TPersistent objects. Otherwise, I have to specifically add a compiler directive {$M+} to the source code for the compiler to generate RTTI info.
So I happily added the directive, only to find that, even if it did return something from the ClassInfo call (it used to return nil), now I cannot retrieve the class' properties, fields or methods from it. It's like it created the object empty.
Any idea what am I missing here? Thanks!
Did you put those properties and methods into the published section?
Besides that, 'classical' RTTI ($TYPEINFO ON) will only get you information on properties, not on methods. You need 'extended' RTTI ($METHODINFO ON) for those.
Good starting point for extended RTTI: David Glassborow on extended RTTI
(who would believe that just this minute I finished writing some code that uses extended RTTI and decided to browse the Stack Overflow a little:))
RTTI will only show you published properties,etc. - not just public ones.
Try your code with a TObject and see what happens - if that isn't working, post your code because not everyone is psychic.
Have you considered using the TXMLDocument component? It will look at your XML and then create a nice unit of Delphi classes that represents your XML file -- makes it really, really easy to read and write XML files.
As for the RttiType problem returning only nil, this probably occurs for one reason: in your test, you did not instantiate the class at any time. The compiler, because it never has a reference to this class (because it is not an instance at all), simply removes it from the information as a form of optimization. See the two examples below. The behavior is different when you have the class instantiated at some point in your code or not.
Suppose the following class:
type
TTest = class
public
procedure Test;
end;
and the following code below:
var
LContext: TRttiContext;
LType: TRttiType;
LTest: TTest;
begin
LContext := TRttiContext.Create;
for LType in LContext.GetTypes do
begin
if LType.IsInstance then
begin
WriteLn(LType.Name);
end;
end;
end;
so far, TTest class information is not available for use by RTTI. However, when we create at some point, within the application, then a reference is created for it within the compile, which makes this information available:
var
LContext: TRttiContext;
LType: TRttiType;
LTest: TTest;
begin
LTest := TTest.Create; //Here i´m using TTest.
//Could be in another part of the program
LContext := TRttiContext.Create;
for LType in LContext.GetTypes do
begin
if LType.IsInstance then
begin
WriteLn(LType.Name);
end;
end;
end;
At that point, if you use LContext.FindType ('TTest'), there will not be a nil return, because the compiler kept reference to the class. This explains the behavior you were having in your tests.