How to distinguish between pointer and TObject entries in a TStringList?

We can add lines with some related objects to the TStringList:

list: TStringList; obj: MyObject; obj := MyObject.Create(); list.AddObject("real object", obj); 

In addition, it can be very convenient to simply connect the string to the pointer, i.e. integer value, for example:

 list.AddObject("just an index", Pointer(7)); 

If I later access the object in this list, find out if it is a MyObject or just 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!

+6
source share
4 answers

If you want to safely store both integers and objects in a single list of strings, define a container class option for storing integers or objects.

The following is the class that is usually described, 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; os := '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. 
+7
source

It’s perfectly possible to add an integer that just happens to point to the object. Similarly, it is entirely possible to have a pointer to an object in your list where the object is already freed.

On the bottom line, you can start looking in memory for anything you want, there is no bulletproof way to find out if your string list contains integers or pointers.

Since you should not mix different types anyway, you also don't need to know. A better approach would be to create two classes containing Stringlist and make the type of the outer classes safe to work with. Then your problem will become a problem.

Example assuming Delphi version does not 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 can implement such classes.

+5
source

A TObject is actually a pointer. Thus, there is simply no way to distinguish between a pointer and a TObject, given that the latter is the first.

If you know something about the object, and you need to get this knowledge later, do not throw this knowledge away. If you need to find out later, remember this.

+3
source

As @DavidHeffernan correctly pointed out, class types are pointers, so they are semantically equivalent, and there is no way to distinguish them without storing type directives.

However, if you ask, "How do I know if arbitrary pointers to an instance of an object are given?" there is a solution for this:

 /// <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 have posted all the documentation on the embedded document, so I believe that no more comments are needed, but I want to reiterate that intentionally invalid pointers, such as Pointer(7) your example, will most likely cause an access violation error. Thus, you can perform a preliminary check if the highest Word pointer is zero (only the same logic as in the Windows.IS_INTRESOURCE macro:

 function Is_IntResource(lpszType: PChar): BOOL; begin Result := ULONG_PTR(lpszType) shr 16 = 0; end; 
0
source

Source: https://habr.com/ru/post/958172/


All Articles