Howdey,
I am using TVirtualInterface to implement some interfaces. Those interfaes represent Keys that can be found in a DB. I generate the interface definitions with a custom made code generator. For example :
// Base code
IKey = interface
function KeyFields : string;
function KeyValues : Variant;
function GetKeyValue(const aKeyName : string) : Variant;
procedure SetKeyValue(const aKeyName : string; Value : Variant);
end;
// Generated code
ITable1Key = interface(IKey)
end;
ITable1Key1 = interface(ITable1Key)
procedure SetField1(const Value : string);
function GetField1 : string;
property Field1 : string read GetField1 write SetField1;
end;
ITable1Key2 = interface(ITable1Key)
procedure SetField1(const Value : string);
function GetField1 : string;
property Field1 : string read GetField1 write SetField1;
procedure SetField2(const Value : string);
function GetField2 : string;
property Field2 : string read GetField1 write SetField1;
end;
// Other generated declarations
I use the TVirtualInterface to implement each IKey interface instead of implementing them one by one.
Though, in my TVirtualInterface :
TKey = TVirtualInterface
public
constructor Create(aType : PTypeInfo);
function Cast : IKey;
end;
TKey<T : IKey>
public
constructor Create; reintroduce;
function Cast : T;
end;
constructor TKey.Create(aType : PTypeInfo)
begin
inherited Create(aType, aHandlerMethod);
end;
function TKey.Cast;
var
pInfo: PTypeInfo;
begin
pInfo := TypeInfo(IKey);
if QueryInterface(GetTypeData(pInfo).Guid, Result) <> 0 then
begin
raise Exception.CreateFmt('Sorry, TKey is unable to cast %s to its interface ', [string(pInfo.Name)]);
end;
end;
constructor TKey<T>.Create;
begin
inherited Create(TypeInfo(T));
end;
function TKey<T>.Cast;
var
pInfo: PTypeInfo;
begin
pInfo := TypeInfo(T);
if QueryInterface(GetTypeData(pInfo).Guid, Result) <> 0 then
begin
raise Exception.CreateFmt('Sorry, TKey<T> is unable to cast %s to its interface ', [string(pInfo.Name)]);
end;
end;
I have no problem casting the TKey virtual interface to the T type using the TKey.Cast method, though TKey.Cast returns a Interface not supported error.
I checked in System.Rtti for the part that wasn't working the way I wanted it to :
function TVirtualInterface.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if iid = FIID then
begin
_AddRef;
Pointer(Obj) := #VTable;
Result := S_OK;
end
else
Result := inherited
end;
Now, how can I force the TVirtualInterface to cast itself to a IID that is a parent interface of the FIID field ? Do I have to create another instance of the TVirtualInterface for the IKey interface ?
Thank you very much.
You are misusing TVirtualInterface. It is just an RTTI helper, you should not be deriving from it at all. You should be deriving from TInterfacedObject instead.
Also, both of your TKey classes are ignoring the PTypeInfo that is passed to the constructor. The non-Generic TKey.Cast() is always querying for IKey only, never a descendant interface. And the Generic TKey<T>.Cast is always re-querying T's RTTI to get its IID. So get rid of the PTypeInfo in the constructor, it is wasted.
Since the non-Generic TKey is just a base class that doesn't actually implement any derived interfaces at all, TKey.QueryInterface() will always fail for any interface other than IKey itself. At least the Generic TKey can query a derived interface.
Your Cast functions are redundant anyway, since you can use the as operator, or the SysUtils.Supports() function, to cast one interface to another. These are the preferred methods, not using QueryInterface() manually.
In any case, your interfaces are missing IIDs in their declarations, so you can't cast between interfaces anyway.
Try something more like this:
// Base code
IKey = interface
['{D6D212E0-C173-468C-8267-962CFC3FECF5}']
function KeyFields : string;
function KeyValues : Variant;
function GetKeyValue(const aKeyName : string) : Variant;
procedure SetKeyValue(const aKeyName : string; Value : Variant);
end;
// Generated code
ITable1Key = interface(IKey)
['{B8E44C43-7248-442C-AE1B-6B9E426372C1}']
end;
ITable1Key1 = interface(ITable1Key)
['{0C86ECAA-A8E7-49EB-834F-77DE62BE1D28}']
procedure SetField1(const Value : string);
function GetField1 : string;
property Field1 : string read GetField1 write SetField1;
end;
ITable1Key2 = interface(ITable1Key)
['{82226DE9-221C-4268-B971-CD72617C19C7}']
procedure SetField1(const Value : string);
function GetField1 : string;
property Field1 : string read GetField1 write SetField1;
procedure SetField2(const Value : string);
function GetField2 : string;
property Field2 : string read GetField1 write SetField1;
end;
// Other generated declarations
type
TKey = class(TInterfacedObject, IKey)
public
function Cast : IKey;
// IKey methods...
end;
TKey<T : IKey> = class(TInterfacedObject, IKey, T)
public
function Cast : T;
end;
TTable1Key = class(TKey, IKey, ITable1Key)
end;
TTable1Key1 = class(TTable1Key, IKey, ITable1Key, ITable1Key1)
public
// ITable1Key1 methods...
end;
TTable1Key2 = class(TTable1Key, IKey, ITable1Key, ITable1Key2)
public
// Table1Key2 methods...
end;
// and so on ...
function TKey.Cast: IKey;
begin
if not Supports(Self, IKey, Result) then
raise Exception.Create('Sorry, unable to cast to IKey');
end;
function TKey<T>.Cast: T;
begin
if not Supports(Self, GetTypeData(TypeInfo(T)).Guid, Result) then
raise Exception.CreateFmt('Sorry, unable to cast to %s', [string(TypeInfo(T).Name)]);
end;
// other class methods as needed ...
Also note how the derived classes have to repeat the interfaces implemented by their base classes. That is a known Delphi limitation. Derived classes do not inherit base class interfaces. Each class has to explicitly specify the interfaces it implements, even if the actual implementation is in a base class.
Related
I encountered some error for which i just can't find a proper hint on the net. Hopefully one of you can point me to the right direction.
Simple Problem: I've got a class inheriting from TObject. I've got a constructor named Create and i want to call Inherited on the very first line of the very only constructor.
Does not work!
On compile i get a
[dcc32 Fehler] ULSRAware.pas(58): E2008 Inkompatible Typen
If I comment the inherited out it compiles fine but on runtime on creating the object, while I can access methods regulary (like some private _InitAdo method), every access to a property yields an access violation error.
I guess it's coming from calling the inherited nonetheless but without any sufficient success.
This is the declaration at the head of the Unit. Just to mention it, it's just this class in the unit. And of course in the implementation section the implementation.
type TLAConnect = class( TObject )
private
_mailHost : String;
_mailPort : Integer;
_mailUsername : String;
_mailPassword : String;
_mailAddress : String;
_sql_script_sms : String;
_sql_script_mail: String;
_sms_mail_addon : String;
//connection : TADOConnection;
(*
procedure SendMessage( recp:String; subj, body : String );
procedure _InitAdo( config_filename : String; path: String );
function GetMsgId( msg : String ) : Integer;
function GetMsgIdFromByteBit( byte, bit : String ) : Integer;
function ProcessMessage( msgId : Integer ): String;
procedure Trigger( msgId : Integer );
procedure QuittMsg( msgId : Integer );
procedure MakeMessage( _msgid : Integer; _fsms, _fmail : Boolean; _smsgl, _smsgs : String );
function CreateNewByteTrigger( byte, bit : String ) : Integer;
*)
public
Constructor Create( config : String );
Destructor Destroy; override;
//function Call( msg:String ) : Boolean;
end;
And the implementation of the constructor and the desctructor.
Constructor TLAConnect.Create( config : String );
begin
inherited.Create;
//self._InitAdo( config, 'lsraware ado' );
_mailHost := 'blabla';
_mailPort := 587;
_mailUsername := 'blabla_user';
_mailPassword := 'blabla_pass';
_mailAddress := 'blabal';
end;
Destructor TLAConnect.Destroy;
begin
self.connection.Free;
Inherited;
end;
I am trying to write a spec utility library.
One of the Specification is a TExpressionSpecification. Basically, it implements the Specification pattern by evaluating an inner TExpression.
One of the TExpression is a TPropertyExpression. It's simply an expression that gets the value of a property by its name with Rtti.
I implemented it the simplest way I could, but really cannot understand why it throws an AV at me.
I stepped throrouly with the debugger. All types are what they are supposed to be. I just dont know why the TRttiProperty.GetValue is wrecking havoc.
Can anybody help?
unit Spec;
interface
uses
Classes;
type
TPropertyExpression<TObjectType, TResultType> = class
private
FPropertyName: string;
public
constructor Create(aPropertyName: string); reintroduce;
function Evaluate(aObject: TObjectType): TResultType;
property PropertyName: string read FPropertyName write FPropertyName;
end;
procedure TestIt;
implementation
uses
Rtti;
constructor TPropertyExpression<TObjectType, TResultType>.Create(aPropertyName:
string);
begin
inherited Create;
PropertyName := aPropertyName;
end;
function TPropertyExpression<TObjectType, TResultType>.Evaluate(aObject:
TObjectType): TResultType;
var
aCtx : TRttiContext;
aModelType : TRttiType;
aResultType : TRttiType;
aProperty : TRttiProperty;
aValue : TValue;
begin
aCtx := TRttiContext.Create;
aModelType := aCtx.GetType(System.TypeInfo(TObjectType));
aResultType := aCtx.GetType(System.TypeInfo(TResultType));
aProperty := aModelType.GetProperty(PropertyName);
aValue := aProperty.GetValue(Addr(aObject));
Result := aValue.AsType<TResultType>;
end;
procedure TestIt;
var
aComponent : TComponent;
aSpec : TPropertyExpression<TComponent, string>;
begin
aComponent := TComponent.Create(nil);
aComponent.Name := 'ABC';
aSpec := TPropertyExpression<TComponent, string>.Create('Name');
WriteLn(aSpec.Evaluate(aComponent));
Readln;
end;
end.
GetValue expects the instance pointer (aObject) but you are passing it the address of the pointer variable (#aObject).
Constrain your TObjectType to a class type:
type
TPropertyExpression<TObjectType: class; TResultType> = class...
Then, instead of Addr(aObject), pass the instance directly:
aValue := aProperty.GetValue(Pointer(aObject));
I have a class I want to pass to a datasnap server, but the class contains this field Picture which should be a TPicture but for now I use an integer to avoid getting the marshall error "tkPointer currently not supported" :(
I have tried omitting a field/property "Picture" from getting marshalled by adding [JSONMarshalled(False)] but with no luck.
I have added the units as suggested in the thread here
JSONMarshalled not working in Delphi
unit TestObjU;
interface
uses
Classes, System.Generics.Collections, System.SyncObjs, System.SysUtils,
JSON, DBXJsonReflect, REST.JSON,
Data.FireDACJSONReflect, FireDAC.Comp.Client, vcl.ExtCtrls,
pngimage, graphics, variants,
GlobalFunctionsU, GlobalTypesU;
{$M+}
{$RTTI EXPLICIT FIELDS([vcPrivate])}
type
EPerson = class(Exception);
EPersonsList = class(Exception);
TGender = (Female, Male);
TPerson = class(TObject)
private
FFirstName: string;
FLastName: string;
FId: Integer;
FGender: TGender;
FModified : Boolean;
[JSONMarshalled(False)]
FPicture: Integer;
// [JSONMarshalled(False)] FPicture : TPicture;
function GetName: string;
procedure SetFirstName(const Value: string);
procedure SetLastName(const Value: string);
function GetId: Integer;
procedure SetGender(const Value: TGender);
procedure SetModified(const Value: Boolean);
public
property Id : Integer read GetId;
property Name : string read GetName;
property FirstName : string read FFirstName write SetFirstName;
property LastName : string read FLastName write SetLastName;
property Gender : TGender read FGender write SetGender;
property Modified : Boolean read FModified write SetModified;
// property Picture : TPicture read FPicture write FPicture;
[JSONMarshalled(False)]
property Picture : Integer read FPicture write FPicture;
function Update : Boolean;
function Delete : Boolean;
constructor Create(AId : Integer; AFirstName, ALastName : string; AGender : TGender); overload;
constructor Create(AFirstName, ALastName : string; AGender : TGender); overload;
destructor destroy; override;
function ToJsonString: string;
end;
But clearly it has no effect on the marshalling, Picture is still there - what am I missing?
function TPerson.ToJsonString: string;
begin
result := TJson.ObjectToJsonString(self);
end;
08-03-2016 10:26:24 [NORMAL] AddPerson serialized {"firstName":"Donald","lastName":"Duck","id":24,"gender":"Female","modified":false,"picture":92415648}
You are using TJson.ObjectToJsonString from REST.Json unit and that one needs different attribute to skip fields named JSONMarshalledAttribute
You should change your code to [JSONMarshalledAttribute(False)]
Delphi has a bit of mix up between older Data.DBXJsonReflect and newer REST.Json units and you should not mix them together in same code. Pick only one of them.
REST.Json.TJson.ObjectToJsonString
REST.Json.Types.JSONMarshalledAttribute
Data.DBXJSONReflect.JSONMarshalled
Yes - I found the solution, when using DBX (and not REST) you'll need add this unit "Data.DBXJSON" rather than the "REST.JSON" and change the two "from/to" methods for un/marshaling the object something like this.
NOTE. ToJSONString leaks for some reason, I'll have to investigate that more.
function TPerson.ToJsonString: string;
var
JSONMarshal: TJSONMarshal;
begin
result := '';
JSONMarshal := TJSONMarshal.Create(TJSONConverter.Create);
try
Result := JSONMarshal.Marshal(self).ToString;
finally
JSONMarshal.Free;
end;
end;
class function TPerson.FromJsonString(AJSONString: string): TPerson;
var
JSONUnMarshal: TJSONUnMarshal;
begin
JSONUnMarshal := TJSONUnMarshal.Create;
try
Result := JSONUnMarshal.Unmarshal(TJSONObject.ParseJSONValue(AJSONString)) as TPerson;
finally
JSONUnMarshal.Free;
end;
end;
I'm an newbee concerning interfaces. I googled a lot but i can't figure out what to do in the following situation.
i created serveral interfaces, which use each other:
IPart = interface(IInterface)
Function getName: string;
procedure setName(aValue: string)
property Name: string read getName write setname;
end;
IOfferLine= interface(iInterface)
Function getPart: IPart;
function getAmount: double;
procedure setPart(aPart: IPart);
procedure setAmount(value: double);
property Amount: double read getAmount write setAmount;
property Part: IPart read GetPart write setPart;
end;
IOffer= interface(iInterface)
function getOffLines: tList<IOfferline>;
procedure setOffLines(aList: tList<IOfferline>);
property OffLines: tList<IOfferlines> read getOffLines write setOfflines;
end;
Now i want to implement those interface.
TPart = class(TInterfacedObject, IPart)
private
_Name: string;
function getName: string;
procedure setName(aValue: string);
public
property Name: string read getName write setName;
end;
TOfferLine = class(TInterfacedObject, IOfferLine)
private
_amount: double;
_part: TPart;
function getAmount: double;
function getPart: tPart;
procedure setAmount(aValue: double);
procedure setPart(aPart: TPart);
public
property Amount: double read getAmount write setAmount;
property Part: TPart read GetPart write SetPart;
end;
TOffer = class(TInterfacedObject, IOffer)
private
_OfferLines: tList<TOfferline>;
function getOffLines: tList<tOfferline>;
procedure setOffLines(aList: tList<tOfferline>);
public
property offLines: tList<TOfferline> read getOffLines write setOffLines;
end;
I have added the implementation.
function TOfferLine.getPart: tPart;
begin
result := _part;
end;
But i still get 'Missing implementation of interface method IOfferline.GetPart;'
And i Can't figure out why.
I dont know what you are trying to to but if you didn't write you code so messy it would be easier to read. But thank God we have a Source formatter.
There are seval problems in you code:
First You have your property declared as property OffLines: TList<IOfferline**s**> while your interface is named IOfferline
Then TOfferline you have a method procedure setPart(aPart: TPart); that should be procedure setPart(aPart: IPart); because thats how you declared your interface. And all the other places where you Use TPart should be IPart.
And the same goes for TOffer
Here is a cleaned up version of your code :
unit Unit20;
interface
uses
Generics.Collections;
type
IPart = interface(IInterface)
function getName: string;
procedure setName(aValue: string);
property Name: string read getName write setName;
end;
IOfferLine = interface(IInterface)
function getPart: IPart;
function getAmount: double;
procedure setPart(aPart: IPart);
procedure setAmount(value: double);
property Amount: double read getAmount write setAmount;
property Part: IPart read getPart write setPart;
end;
IOffer = interface(IInterface)
function getOffLines: TList<IOfferLine>;
procedure setOffLines(aList: TList<IOfferLine>);
property OffLines: TList < IOfferLine > read getOffLines write setOffLines;
end;
TPart = class(TInterfacedObject, IPart)
private
_Name: string;
function getName: string;
procedure setName(aValue: string);
public
property Name: string read getName write setName;
end;
TOfferline = class(TInterfacedObject, IOfferLine)
private
_amount: double;
_part: TPart;
function getAmount: double;
function getPart: IPart;
procedure setAmount(aValue: double);
procedure setPart(aPart: IPart);
public
property Amount: double read getAmount write setAmount;
property Part: IPart read getPart write setPart;
end;
TOffer = class(TInterfacedObject, IOffer)
private
_OfferLines: TList<TOfferline>;
function getOffLines: TList<IOfferLine>;
procedure setOffLines(aList: TList<IOfferLine>);
public
property OffLines: TList < IOfferLine > read getOffLines write setOffLines;
end;
implementation
{ TOfferline }
function TOfferline.getAmount: double;
begin
end;
function TOfferline.getPart: IPart;
begin
end;
procedure TOfferline.setAmount(aValue: double);
begin
end;
procedure TOfferline.setPart(aPart: IPart);
begin
end;
{ TOffer }
function TOffer.getOffLines: TList<IOfferLine>;
begin
end;
procedure TOffer.setOffLines(aList: TList<IOfferLine>);
begin
end;
{ TPart }
function TPart.getName: string;
begin
end;
procedure TPart.setName(aValue: string);
begin
end;
end.
The reason the compiler is saying that the implementation is missing is simply because the implementation is missing.
Your interface for IOfferLine declares this getPart method:
IOfferLine= interface(iInterface)
..
function getPart: IPart;
..
end;
But your implementing class does not provide this method. The getPart method in your class is implemented to return an object reference, not an interface reference:
TOfferLine = class(TInterfacedObject, IOfferLine)
private
..
function getPart: tPart;
..
end;
You need to ensure that your implementing class actually provides the members required by the interfaces that it implements, exactly and precisely:
TOfferLine = class(TInterfacedObject, IOfferLine)
private
..
function getPart: IPart;
..
end;
function TOfferline.getPart: IPart;
begin
result := _part as IPart;
end;
However, since the reference to the Part maintained by the OfferLine object (in the _part variable) is an object reference, then references to that object obtained using interfaces (via the getPart: IPart method) could result in that Part object being destroyed since the object reference in OfferLine is not counted (literally).
You can of course avoid this by making the Part reference held by OfferLine an interface reference itself, but whether this is valid is difficult to say with out a complete picture of your entire object model. If the lifetimes of your objects are ensured by some other mechanism not apparent from the question then it may not be an issue, but if it is not something that has been specifically considered thus far then it probably does need addressing.
Although it is possible to do safely, as a general rule mixing object references and interface references to the same objects is a recipe for problems.
I have a base class test define below
type
Tinfo = procedure of object;
Test = class(TObject)
public
procedure Add ( const a : Tinfo ); reintroduce ;
end;
procedure Test.Add(const a: Tinfo);
begin
Writeln('base class add function');
// dosomething more
end;
and I have a derived generic class from this base class
TTesting<T> = class(Test)
public
procedure Add ( const a : T ); reintroduce ;
end;
and I am typecasting T to Tinfo but it gives me the error
procedure TTesting<T>.Add(const a : T );
begin
inherited Add(Tinfo(a) ); // gives me error here
end;
is there any way I can implement this?
First your cast is wrong, you obviously want to cast a and not T.
However if you want to type cast on a procedure of object which is a type that cannot be polymorphic in any way it makes no sense to put that into a generic type at all.
What should T be? It only can be a TInfo in your code.
If you however want T to be any event/method type you should store a TMethod in your base class and then work with that in your generic class. But remember that you cannot have a constraint that limits T to be an event type. So you might check that in your constructor.
type
PMethod = ^TMethod;
Test = class(TObject)
public
procedure Add(const a: TMethod ); reintroduce ;
end;
procedure Test.Add(const a: TMethod);
begin
Writeln('base class add function');
// dosomething more
end;
type
TTesting<T> = class(Test)
public
constructor Create;
procedure Add(const a: T); reintroduce ;
end;
constructor TTesting<T>.Create;
begin
Assert(PTypeInfo(TypeInfo(T)).Kind = tkMethod);
inherited Create;
end;
procedure TTesting<T>.Add(const a: T);
begin
inherited Add(PMethod(#a)^);
end;