all,
In Delphi, I created a simple class called T_Test (see below).
T_Test = class(TObject)
private
F_Int : Integer;
public
constructor Create(inInt: Integer);
destructor Destroy; override;
property Int: Integer read F_Int write F_Int;
function showInt : String;
end;
constructor T_Test.Create(inInt: Integer);
begin
F_Int := inInt;
end;
destructor T_Test.Destroy;
begin
self.Free;
end;
function T_Test.showInt : String;
var outputLine : String;
begin
result := IntToStr(Int);
outputLine := result;
Form1.Memo1.Lines.Add(outputLine);
end;
Then I have a procedure in which I want to make a TList of T_Test object and call the
showInt method function on them.
I tried like this :
procedure testTlist;
var
a, b: T_Test;
i : Integer;
begin
a := T_Test.Create(5);
b := T_Test.Create(10);
listTest := TList.Create;
listTest.Add(a);
listTest.Add(b);
listTest[i].showInt;
end;
But I keep getting an an that says I have to use a Record, Object or Class Type on the
call of 'listTest[i].showInt'
Does anyone know how to call this method ?
Cast the listTest[i] pointer back to T_Test and then call its method:
T_Test(listTest[i]).showInt;
Alternatively, if available, use a templated TObjectList class to store T_Test instances directly.
Martin's answer is correct. But it's worth noting that if you might be adding different classes to your list, a more robust fragment of code would be ...
var pMmember: pointer;
pMember := listTest[i];
if TObject( pMember) is T_Test then
T_Test( pMember).ShowInt;
Martin's point about TObjectList is quiet correct. Another option to consider would be TList<T_Test>. David's comment about the error in your destructor is correct too.
I note that you did not initialise the value of i. So the fragment above is pretending you did. If you also wanted to check that the index variable was at a valid value, and not call ShowInt if it was invalid, then you could do something like this...
if (i >= 0) and (i < listTest.Count) and (TObject(listTest[i]) is T_Test) then
T_Test(listTest[i]).ShowInt;
The above code fragment relies on short-circuit boolean evaluation.
Related
We can add strings along with some associated objects to a TStringList:
list: TStringList;
obj: MyObject;
obj := MyObject.Create();
list.AddObject("real object", obj);
In addition it can be very handy to simply connect a string with a Pointer, i.e., an integer value, like this:
list.AddObject("just an index", Pointer(7));
If I later access to an object in this list how to know if it is a MyObject or simply a Pointer? I want something like this:
for i := 0 to list.Count-1 do
if list.Objects[i] is MyObject then
begin
// ...
// Do something with list.Objects[i]
// ...
end;
but this obviously leads to access violation if list.Objects[i] is just a Pointer.
Thanks in advance!
If you want to safely store both integers and objects into one stringlist, define a variant container class to hold integers or objects.
Below is such a class roughly outlined including a test project.
unit VariantContainer;
interface
uses Variants,SysUtils;
Type
TVariantContainer = class
private
FVariant : Variant;
public
constructor Create(aValue: Integer); overload;
constructor Create(aValue: TObject); overload;
function IsInteger: Boolean;
function IsObject: Boolean;
function AsObject: TObject;
function AsInteger: Integer;
end;
implementation
function TVariantContainer.AsInteger: Integer;
begin
if not IsInteger then
raise Exception.Create('Variant is not Integer');
Result := FVariant;
end;
function TVariantContainer.AsObject: TObject;
begin
if not IsObject then
raise Exception.Create('Variant is not TObject');
Result := TVarData(FVariant).VPointer;
end;
function TVariantContainer.IsInteger: Boolean;
begin
Result := VarIsType( FVariant, varInteger);
end;
function TVariantContainer.IsObject: Boolean;
begin
Result := VarIsType(FVariant, varByRef);
end;
constructor TVariantContainer.Create(aValue: Integer);
begin
Inherited Create;
FVariant := aValue;
end;
constructor TVariantContainer.Create(aValue: TObject);
begin
Inherited Create;
TVarData(FVariant).VType:= VarByRef;
TVarData(FVariant).VPointer:= aValue;
end;
end.
program ProjectTestVariantContainer;
{$APPTYPE CONSOLE}
uses
Variants,SysUtils,Classes,VariantContainer;
Type
TMyObj = class
s:String;
end;
var
sList: TStringList;
o: TMyObj;
i: Integer;
begin
o := TMyObj.Create;
o.s := 'Hello';
sList := TStringList.Create;
sList.OwnsObjects := True; // List owns container objects
sList.AddObject('AnInteger',TVariantContainer.Create(3));
sList.AddObject('AnObject',TVariantContainer.Create(o));
for i := 0 to sList.Count-1 do
begin
if Assigned(sList.Objects[i]) then
begin
if TVariantContainer(sList.Objects[i]).IsInteger then
WriteLn( TVariantContainer(sList.Objects[i]).AsInteger)
else
if TVariantContainer(sList.Objects[i]).IsObject then
WriteLn( TMyObj(TVariantContainer(sList.Objects[i]).AsObject).s);
end;
end;
ReadLn;
o.Free;
sList.Free;
end.
It's perfectly possible to add an integer wich just happens to point
to an object. Likewise it's perfectly possible to have a pointer to an
object in your list where the object already has been freed.
Bottom line, you can start looking around in memory all you want, there is no bulletproof way to know if your stringlist contains integers or pointers.
As you shouldn't mix different types anyway, there's also no need to know. A better approach would be to create two classes containing a Stringlist and make the outer classes type safe to work with. Your problem then becomes a non-issue.
Example assuming your Delphi version doesn't support generics
TStringIntegerMap = class
private FStringIntegerList: TStringList;
public
procedure Add(const Key: string; Value: Integer);
... // Add the other required equivalent TStringlist methods
end;
TStringObjectMap = class
private FStringObjectList: TStringList;
public
procedure Add(const Key: string; Value: TObject);
... // Add the other required equivalent TStringlist methods
end;
Note that this is just to give you the gist of how you might implement such classes.
A TObject is in fact a pointer. So there is simply no way to distinguish between a pointer and a TObject considering that the latter is a former.
If you know something about an object, and you need to retrieve that knowledge at a later time, don't throw away that knowledge. If you need to know something later, remember it.
As #DavidHeffernan correctly pointed out, class types are pointers, so they are semantically equivalent and there is no way to distinguish them without having some type indication stored.
However, if you going to ask "How to find out if given arbitrary pointer points to the object instance?" there is a solution for that:
/// <summary>
/// Verifies that the argument points to valid object instance.
/// </summary>
/// <exception cref="EAccessViolation">
/// If segmentation fault occurs while reading VMT and/or its field from the
/// specified memory address.
/// </exception>
/// <remarks>
/// Delphi only, incompatible with FPC.
/// </remarks>
/// <example>
/// <code>
/// procedure TForm1.FormCreate(Sender: TObject);
/// begin
/// ShowMessage(BoolToStr(IsInstance(Self), True));
/// end;
/// </code>
/// </example>
function IsInstance(Data: Pointer): Boolean;
var
VMT: Pointer;
begin
VMT := PPointer(Data)^;
Result := PPointer(PByte(VMT) + vmtSelfPtr)^ = VMT;
end;
I've posted whole inline documentation, so I feel that more comments are unnecessary, but I want to recap what intentionally invalid pointers like Pointer(7) of your example will certainly cause an access violation fault. So you can perform a preliminary check if the higher Words of the pointer are zero (just the same logic as in Windows.IS_INTRESOURCE macro:
function Is_IntResource(lpszType: PChar): BOOL;
begin
Result := ULONG_PTR(lpszType) shr 16 = 0;
end;
I have two classes: one base class an one derived class
The base class defines a virtual method with a parameter:
function ToName(MsgIfNil:string=''); virtual;
The derived class redefines the method:
function ToName(MsgIfNil:string=''); reintroduce;
The implementation of both methods is similar to this code:
function TBaseClass.ToName(MsgIfNil:string)
begin
if (Self=nil) then
Result := MsgIfNil
else
Result := Self.SomeProperty;
end;
The issue is that:
1) If I do not reintroduce the method in the derived class, but use the regular override keyword, any call to this method triggers an access violation
2) When I call the method from an object being nil, and the presumed class of the objet is TBaseObject, it crashes (AV) instead of calling the base virtual method
If no parameter is defined in the method, the right method is called, without any AV. It works well even if the method in the derived class is overriden.
Note that the above solution works well with objects of any class derived from TBaseClass
How can I define a virtual method that can be called with Self=nil, can be virtual and use parameters?
I certainly must enhance my understanding of internal virtual method call plumbering...
Note: Calling on a nil object is legitimate in my use cases. It is not used to hide exceptions, but to report on non linked objects.
Example: myEdit.Text := APerson.Manager.ToName('No manager defined');
Thanks for any advise on a proper solution
Using Delphi 2010 with upd5
Edit: Adding a more complete example of code that triggers an AV
TBaseClass = class(TObject)
private
FMyName: string;
public
property MyName: string read FMyName;
function ToName(MsgIfNil:string=''):string; virtual;
end;
TDerivedClass = class(TBaseClass)
private
FSpecialName: string;
public
property SpecialName:string read FSpecialName;
function ToName(MsgIfNil:string=''):string; reintroduce;
end;
TBaseClass.ToName(MsgIfNil:string):string;
begin
if (Self=nil) then
Result := MsgIfNil
else
Result := MyName;
end;
TDerivedClass.ToName(MsgIfNil:string):string;
begin
if (Self=nil) then
Result := MsgIfNil
else
Result := SpecialName;
end;
// Now a sample program
var
aPerson: TBaseClass;
aSpecialist: TDerivedClass;
begin
aPerson := TBaseClass.Create;
aPerson.MyName := 'a person';
aSpecialist := TDerivedClass.Create;
aSpecialist.SpecialName := 'a specialist';
aSpecialist := nil; // For example sake, never do this in my use case :)
// This works here,
// but triggers an AV if ToName is marked as override instead of reintroduce
ShowMessage('Name of the specialist: '+aSpecialist.ToName('No specialist!'));
aPerson := nil;
// This triggers an AV, TBaseClass.ToName is never called
ShowMessage('Name of the person: '+aPerson.ToName('No person!'));
end;
The above code may not compile, this is only intended to be a more complete example
Takeway
I now understand that VMT is linked to the object reference and, regardless of the object class, calling a virtual method on a nil object is not possible (the object will not even look at its declared type to get the matching address of the ToName method)
I accepted hvd's solution because it is really effective for methods that must check vs nil (only one base method to add).
Thanks for all answers,
Calling a virtual method on nil doesn't make sense: virtual means "check the class type to see which method to call". There is no class type, so there is no method to call.
What you can do is create a nonvirtual method that calls a virtual method:
// TBase
public:
function ToName(MsgIfNil: string = ''): string;
protected:
function ToNameImpl: string; virtual;
// TDerived
protected:
function ToNameImpl: string; override;
function TBase.ToName(MsgIfNil: string): string;
begin
if (Self=nil) then
Result := MsgIfNil
else
Result := ToNameImpl;
end;
function TBase.ToNameImpl: string;
begin
Result := MyName;
end;
function TDerived.ToNameImpl: string;
begin
Result := MyDerivedName;
end;
This ensures that ToNameImpl, the virtual method, is only called when Self is not nil.
Edit: By the way, this is exactly what the nonvirtual TObject.Free does to call the virtual TObject.Destroy.
Theoretically You can call a method of a nil object. But this practice is very unwanted and dangerous. Avoid it. Rethink your logic. Take a look at class methods.
They will work more like a "static" methods with a lot of limits. You can't acces any properties nor methods that references to properies including Self, nor inherited; because object simply not exists.
Object must be valid before any method call, propery access.
If your function return an object instance that can be nil or in some circumstances your object can be nil, you need to check it before any method call or property access:
O := MyFactory.GetObject;
if Assigned(O) then O.MyMethod;
How can I define a virtual method that can be called with Self=nil, can be virtual and use parameters?
That can't be done in delphi, because you will need a VMT for a virtual method call. And Nil-objects don't have VMT.
Calling on a nil object is legitimate in my use cases.
You will have to rethink your logic. For example you can create some sort of "Empty" object. In this case your APerson.Manager will return this special object, which is an ancestor of TBaseClass with special behaviour. Some sample code:
TManager = class
//...
function GetSalary: integer; virtual;
procedure SetSalary(ASalary: integer) virtual;
end;
TEmptyManager = class(TManager)
//...
function GetSalary: integer; override;
procedure SetSalary(ASalary: integer) override;
end;
//...
function TManager.GetSalary: integer;
begin
//Some calculations here
end;
procedure TManager.SetSalary(ASalary: integer);
begin
//Some work here
end;
function TEmptyManager.GetSalary: integer;
begin
Result := 0;
end;
procedure TEmptyManager.SetSalary(ASalary: integer) override;
begin
//Some sort of safety belt
raise EException.Create('You can''t work with empty manager');
end;
var
EManager: TEmptyManager = Nil;
//Since we won't work with empty manager, one instance will be enough
function EmptyManager: TManager;
begin
if not Assigned(EManager) then
EManager := TEmptyManager.Create;
Result := EManager;
end;
//...
function TPerson.GetManager: TManager;
begin
if SomeCondition then
Result := FManager
else
Result := EmptyManager;
end;
I'm using a very large delphi third party library without source code, this library has several classes with abstract methods. I need to determine when an abtract method is implemented by a Descendant class in runtime to avoid the EAbstractError: Abstract Error and shows a custom message to the user or use another class instead.
for example in this code I want to check in runtime if the MyAbstractMethod is implemented.
type
TMyBaseClass = class
public
procedure MyAbstractMethod; virtual; abstract;
end;
TDescendantBase = class(TMyBaseClass)
public
end;
TChild = class(TDescendantBase)
public
procedure MyAbstractMethod; override;
end;
TChild2 = class(TDescendantBase)
end;
How I can determine if an abstract method is implemented in a Descendant class in runtime?
you can use the Rtti, the GetDeclaredMethods function get a list of all the methods that are declared in the reflected (current) type. So you can check if the method is present in the list returned by this function.
function MethodIsImplemented(const AClass:TClass;MethodName : string): Boolean;
var
m : TRttiMethod;
begin
Result := False;
for m in TRttiContext.Create.GetType(AClass.ClassInfo).GetDeclaredMethods do
begin
Result := CompareText(m.Name, MethodName)=0;
if Result then
break;
end;
end;
or you can compare the Parent.Name property of the TRttiMethod and check if match with the current class name.
function MethodIsImplemented(const AClass:TClass;MethodName : string): Boolean;
var
m : TRttiMethod;
begin
Result := False;
m:=TRttiContext.Create.GetType(AClass.ClassInfo).GetMethod(MethodName);
if m<>nil then
Result:=CompareText(AClass.ClassName,m.Parent.Name)=0;
end;
function ImplementsAbstractMethod(AObj: TMyBaseClass): Boolean;
type
TAbstractMethod = procedure of object;
var
BaseClass: TClass;
BaseImpl, Impl: TAbstractMethod;
begin
BaseClass := TMyBaseClass;
BaseImpl := TMyBaseClass(#BaseClass).MyAbstractMethod;
Impl := AObj.MyAbstractMethod;
Result := TMethod(Impl).Code <> TMethod(BaseImpl).Code;
end;
Look at the implementation of the 32-bit version of the TStream.Seek() method in the VCL source code (in Classes.pas). It performs a check to make sure the 64-bit version of Seek() has been overridden before calling it. It doesn't involve TRttiContext lookups to do that, just a simple loop through its Parent/Child VTable entries, similar to how Zoƫ's answer shows.
I'm new to generics and need some help to structure a class and implement methods.
I'm trying to use generics to serialize any TObject-JSON. Moreover, I want to be able to reuse the code.
These are my questions:
How do I create a generic constructor? I want to be able to use Self or Default(T), but it returns just nil.
V := Marshal.Marshal(ReturnObject) - This method requires a TObject, but I do not know how to reference the current object that was passed in.
How can I use this inside a method? Look at the code snipped below, marked with "Question 3".
This is my code:
TFileOperationResult = class(TObject)
private
FSuccess: Boolean;
//Error: PException;
FLastError: Integer;
function GetFailure: Boolean;
property Failure: Boolean read GetFailure;
public
property Success: Boolean read FSuccess write FSuccess;
property LastError: Integer read FLastError write FLastError;
end;
TResponseObject<T: class> = class(TObject)
private
FReturnObject: T;
function GetReturnObject: T;
function BaseStringsConverter(Data: TObject): TListOfStrings;
public
constructor Create; overload;
property ReturnObject: T read GetReturnObject;
procedure Serialize;
end;
constructor TResponseObject<T>.Create;
begin
// Question 1 - What should go in here?
end;
function TResponseObject<T>.GetReturnObject: T;
begin
Result := Default(T);// Is this correct?
end;
procedure TResponseObject<T>.Serialize;
var
Marshal: TJSONMarshal;
V: TJSONValue;
begin
Marshal := TJSONMarshal.Create(TJSONConverter.Create);
Marshal.RegisterConverter(TResponseObject<T>, BaseStringsConverter);
V := Marshal.Marshal(ReturnObject); // Question 2 - How Can I refer to 'Self'?
OutPut := V.ToString;
Marshal.Free;
end;
Calling code:
procedure TForm1.Test;
var
FileOperationResult: TResponseObject<TFileOperationResult>;
begin
FileOperationResult := TResponseObject<TFileOperationResult>.Create;
FileOperationResult.Serialize;
end;
Question 3:
procedure TForm1.MoveCopyFile<THowNowResponse>(ASource, DDestination: String);
var
FileOperationResult: TFileOperationResult;
begin
FileOperationResult := TFileOperationResult.Create;
// What to do?
end;
Any other comments are much appreciated.
It's hard to tell exactly what you're trying to do here, but I can make a guess. For TResponseObject, you want an object that can contain another object and operate on it. In that case, you probably want to pass it in to the constructor, like so:
constructor TResponseObject<T>.Create(value: T);
begin
FReturnObject := value;
end;
Likewise, if you make a GetReturnObject method, it should probably return the value of the FReturnObject field. (Or you could make the read accessor of the property just reference FReturnObject directly.)
function TResponseObject<T>.GetReturnObject: T;
begin
Result := FReturnObject;
end;
It's really hard to answer #3 since I don't know what you're trying to do with this, but hopefully my answers to the first two will help you get back on track. Just remember that generics don't have to be confusing; they're basically just type substitution. Anywhere you'd use a normal type in a non-generic routine, you can replace it with a <T> to create a generic routine, and then substitute any type that fits the constraints for that particular T.
I have a TObjectList with OwnsObjects = true. It contains quite a few objects. Now I want to remove the object at index Idx from that list, without freeing it.
Is the Extract method the only option?
ExtractedObject := TheList.Extract(TheList[Idx]);
All other methods seem to free the object. I am looking for something a little bit more efficient, that does not do a linear search every time, since I already know the index of the object. Something like an overloaded ...
ExtractedObject := TheList.Extract(Idx);
... which does not exist.
Why not just set OwnsObjects to false, do your removal, then set it to true again?
If you look at the code for delete, it's the notify method which causes the freeing to happen.
This should work :
TMyObjectList = Class(TObjectList)
private
fNotify: Boolean;
{ Private declarations }
procedure EnableNotification;
procedure DisableNotification;
protected
procedure Notify(Ptr: Pointer; Action: TListNotification); override;
public
constructor Create(AOwnsObjects: Boolean);overload;
constructor Create; overload;
function Extract(const idx : Integer) : TObject;
end;
constructor TMyObjectList.Create(AOwnsObjects: Boolean);
begin
inherited Create(AOwnsObjects);
fNotify := True;
end;
constructor TMyObjectList.Create;
begin
inherited Create;
fNotify := True;
end;
procedure TMyObjectList.DisableNotification;
begin
fnotify := False;
end;
procedure TMyObjectList.EnableNotification;
begin
fNotify := True;
end;
function TMyObjectList.Extract(const idx: Integer) : TObject;
begin
Result := Items[idx];
DisableNotification;
try
Delete(idx);
finally
EnableNotification;
end;
end;
procedure TMyObjectList.Notify(Ptr: Pointer; Action: TListNotification);
begin
if fNotify then
inherited;
end;
This is where class helpers can be usefull
TObjectListHelper = class helper for TObjectList
function ExtractByIndex(const AIndex: Integer): TObject;
end;
function TObjectListHelper.ExtractByIndex(const AIndex: Integer): TObject;
begin
Result := Items[AIndex];
if Result<>nil then
Extract(Result);
end;
You can now use:
MyObjList.ExtractByIndex(MyIndex);
The proposed helperclass (by Gamecat) will result in the same lookup that Thomas would like to get rid of.
If you take a look at the source, you can see what Extract() really does, and then use the same approach.
I will suggest something like tis:
obj := list[idx];
list.list^[idx] := nil; //<- changed from list[idx] := nil;
list.delete(idx);
This will give you the object, as Extract() does, and then delete it from the list, without any lookups. Now you can put this in a method some where, a helperclass or subclass or wher ever you like.
I don't use Delphi/C++Builder some time ago, but as far as I can renmember thats the only way.
My suggestion is to use a TList instead, and manually delete the objects when required.
Anything wrong with:
ExtractedObject := TExtractedObject.Create;
ExtractedObject.Assign(Thelist[Idx]);
TheList.Delete(idx);
There is time needed for the create and assign but not for the search of the list. Efficiency depends on the size of the object -v- the size of the list.