Delphi Rtti for Interfaces in a General Context

for the framework, I wrote a wrapper that takes any object, interface, or record type to examine its properties or fields. The class declaration is as follows:

TWrapper<T> = class private FType : TRttiType; FInstance : Pointer; {...} public constructor Create (var Data : T); end; 

In the constructor, I am trying to get type information for further processing steps.

 constructor TWrapper<T>.Create (var Data : T); begin FType := RttiCtx.GetType (TypeInfo (T)); if FType.TypeKind = tkClass then FInstance := TObject (Data) else if FType.TypeKind = tkRecord then FInstance := @Data else if FType.TypeKind = tkInterface then begin FType := RttiCtx.GetType (TObject (Data).ClassInfo); //<---access violation FInstance := TObject (Data); end else raise Exception.Create ('Unsupported type'); end; 

I wonder if this access violation is a bug in the delphi compiler (I use XE). After further study, I wrote a simple test function that shows that querying the class name also raises this exception:

 procedure TestForm.FormShow (Sender : TObject); var TestIntf : IInterface; begin TestIntf := TInterfacedObject.Create; OutputDebugString(PChar (TObject (TestIntf).ClassName)); //Output: TInterfacedObject Test <IInterface> (TestIntf); end; procedure TestForm.Test <T> (var Data : T); begin OutputDebugString(PChar (TObject (Data).ClassName)); //access violation end; 

Can someone explain to me what is wrong? I also tried a procedure without the var parameter, which didn't work either. When using a non-standard procedure, everything works fine, but to simplify the use of the wrapper, a general solution would be nice, because it works for objects and records in the same way.

Respectfully,

Christian

+6
generics interface rtti delphi
Jun 08 2018-11-12T00:
source share
2 answers

Your code contains two incorrect assumptions:

  • So you can get meaningful RTTI from the interfaces. Alas, you can get RTTI from interface types.
  • The interface is always implemented by the Delphi object (therefore, your attempt to extract the RTTI from the Delphi object).

Both assumptions are wrong. Interfaces are very simple tables of VIRTUAL METHODS, very little magic for them. Since the interface is so narrowly defined, it cannot have RTTI . Unless, of course, you are implementing your own version of RTTI , and you shouldn't. LE: the interface itself cannot carry type information as TObject does, but the TypeOf () operator can get TypeInfo if equipped with IInterface

Your second assumption is also incorrect, but even more so. In the Delphi world, most interfaces will be implemented by Delphi objects, unless, of course, you get an interface from a DLL written in another programming language: Delphi interfaces are COM-compatible, so implementations can be used from any other COM-compatible language and vice versa. But since we are talking about Delphi XE here, you can use this syntax to cast an interface to it that implements the object in an intuitive and understandable way:

 TObject := IInterface as TObject; 

i.e. use the as operator. Delphi XE automatically converts a hard listing of this type from time to time:

 TObject := TObject(IInterface); 

to the "as" syntax mentioned, but I don't like this magic because it looks very contrast-intuitive and behaves differently in older versions of Delphi.

Bringing an Interface back to it, which implements the object, is also wrong from another point of view: it displays all the properties of the implementation object, and not just those that are associated with the interface, and this is very wrong because you use Interfaces to hide these implementation details Firstly!

Example: interface implementation not supported by Delphi object

Just for fun, here's a quick demo of an interface not supported by a Delphi object. Since the interface is nothing more than a pointer to a table of virtual methods, I will build a table of virtual methods, create a pointer to it and draw a pointer to the desired type of interface. All method pointers in my fake virtual method table are implemented using global functions and procedures. Imagine trying to extract RTTI from my i2 interface!

 program Project26; {$APPTYPE CONSOLE} uses SysUtils; type // This is the interface I will implement without using TObject ITestInterface = interface ['{CFC4942D-D8A3-4C81-BB5C-6127B569433A}'] procedure WriteYourName; end; // This is a sample, sane implementation of the interface using an // TInterfacedObject method TSaneImplementation = class(TInterfacedObject, ITestInterface) public procedure WriteYourName; end; // I'll use this record to construct the Virtual Method Table. I could use a simple // array, but selected to use the record to make it easier to see. In other words, // the record is only used for grouping. TAbnormalImplementation_VMT = record QueryInterface: Pointer; AddRef: Pointer; ReleaseRef: Pointer; WriteYourName: Pointer; end; // This is the object-based implementation of WriteYourName procedure TSaneImplementation.WriteYourName; begin Writeln('I am the sane interface implementation'); end; // This will implement QueryInterfce for my fake IInterface implementation. All the code does // is say the requested interface is not supported! function FakeQueryInterface(const Self:Pointer; const IID: TGUID; out Obj): HResult; stdcall; begin Result := S_FALSE; end; // This will handle reference counting for my interface. I am not using true reference counting // since there is no memory to be freed, si I am simply returning -1 function DummyRefCounting(const Self:Pointer): Integer; stdcall; begin Result := -1; end; // This is the implementation of WriteYourName for my fake interface. procedure FakeWriteYourName(const Self:Pointer); begin WriteLn('I am the very FAKE interface implementation'); end; var i1, i2: ITestInterface; R: TAbnormalImplementation_VMT; PR: Pointer; begin // Instantiate the sane implementation i1 := TSaneImplementation.Create; // Instantiate the very wrong implementation R.QueryInterface := @FakeQueryInterface; R.AddRef := @DummyRefCounting; R.ReleaseRef := @DummyRefCounting; R.WriteYourName := @FakeWriteYourName; PR := @R; i2 := ITestInterface(@PR); // As far as all the code using ITestInterface is concerned, there is no difference // between "i1" and "i2": they are just two interface implementations. i1.WriteYourName; // Calls the sane implementation i2.WriteYourName; // Calls my special implementation of the interface WriteLn('Press ENTER to EXIT'); ReadLn; end. 
+8
Jun 08 '11 at 12:40
source share

Two possible answers.

If this always happens, even if T is an object, then this is a compiler error, and you should submit a quality control report. (With interfaces, the cast-an-interface-to-the-object object requires black magic from the compiler, and it is possible that the generics subsystem does not implement it properly.)

If you take T , and not an object, for example, the type of record and getting this error, then everything works as designed; you are just using typecasts incorrectly.

In any case, there is a way to get RTTI information from an arbitrary type. Do you know how TRttiContext.GetType has two overloads? Use another. Instead of calling GetType (TObject (Data).ClassInfo) try calling GetType(TypeInfo(Data)) .

Oh, and declare FInstance as T instead of pointer . This will save you a lot of trouble.

+2
Jun 08 '11 at 12:40
source share



All Articles