type IData = interface function Ptr: Pointer; end; procedure AddData(Data1, Data2: IData; out OutData: IData); begin OutData := CreateMerged_IData(Data1.Ptr, Data2.Ptr); // Create_IData IData end; var DataArr: array of IData; procedure AddDataToAll(const AExtraData: IData); var i: Integer; begin if not Assigned(AExtraData) then Exit; for i := 0 to Length(DataArr) - 1 do AddData(DataArr[i], AExtraData, DataArr[i]); end;
var FCollection: TDictionary<TObject, Integer>; procedure KillObject(var Obj: TObject); begin if FCollection.ContainsKey(Obj) then begin //DoSomething obj.Free; FCollection.Remove(obj); obj := nil; end; end;
// Value := NewValue; // : if Assigned(Pointer(Value)) then Value._Release; if Assigned(Pointer(NewValue)) then NewValue._AddRef; Pointer(Value) := Pointer(NewValue);
procedure DoWork(AValue: IUnknown);
AValue can change inside a function. At the same time outside the value should not change. This means that the variable must be explicitly copied onto the stack, increasing its reference count. After all, if inside the function this variable is assigned a new value, then ._Release will be called. Also inside the DoWork compiler will carefully insert AValue._Release when exiting; procedure DoWork(AValue: Pointer); begin try //DoWork implementation finally IUnknown(AValue)._Release; end; end; MyValue._AddRef; DoWork(Pointer(MyValue));
procedure DoWork(const AValue: IUnknown);
The compiler based on the fact that the value of AValue inside the function does not change - there will be nothing to do with the reference count. The interface pointer will simply be copied onto the stack. procedure DoWork(const AValue: Pointer); begin //DoWork implementation end; DoWork(Pointer(MyValue));
procedure DoWork(var AValue: IUnknown);
Data which can be read is transferred to function, and outside it is expected that the data can be changed. Here the compiler, as in the case of const, does not make any gestures with a reference counter. procedure DoWork(var AValue: Pointer); begin //DoWork implementation end; DoWork(Pointer(MyValue));
procedure DoWork(out AValue: IUnknown);
Everything works differently from var. Out parameter means that uninitialized data can be passed to the function. For uninitialized data ._Release cannot be called. Therefore, before calling Delphi, it necessarily inserts ._Release, and inside the function cleans this out parameter. procedure DoWork(out AValue: Pointer); begin Pointer(AValue) := nil; // , ._Release //DoWork implementation end; MyValue._Release; DoWork(Pointer(MyValue));
function DoWork: IUnknown;
The compiler allocates a temporary variable on the stack, and works with it as with the var parameter. procedure DoWork(var Result: Pointer); begin //DoWork implementation end; var FuncResult: IUnknown; Pointer(FuncResult) := nil; DoWork(Pointer(FuncResult)); MyValue := FuncResult;
procedure AddData(Data1, Data2: IData; out OutData: IData);
Here, before calling, the reference count will be increased for Data1, Data2, and reduced for OutData. AddData(DataArr[i], AExtraData, DataArr[i]);
With DataArr [i], the counter will be increased and decreased. If the counter is first reduced and becomes zero, then the object will be destroyed. function AddData(Data1, Data2: IData): IData;
or set up a temporary variable before calling: AddData(DataArr[i], AExtraData, TmpData); DataArr[i] := TmpData;
Congratulations to Kemet for correctly explaining the problem in Task 1. var obj: TObject; obj := Nil; FCollection.Add(obj, 13); WriteLn(FCollection.Items[obj]); // 13, .. FCollection.Remove(obj)
quite working and has no problems. The hash seems to be taken from the pointer ... but it seems to be. function _LookupVtableInfo(intf: TDefaultGenericInterface; info: PTypeInfo; size: Integer): Pointer;
which, from the comparators table, VtableInfo gets the necessary function pointer depending on the type passed to info: PTypeInfo. For objects (tkClass) there will be a pointer in the table to EqualityComparer_Instance_Class: Pointer = @EqualityComparer_Vtable_Class; EqualityComparer_Vtable_Class: array[0..4] of Pointer = ( @NopQueryInterface, @NopAddref, @NopRelease, @Equals_Class, @GetHashCode_Class );
Here we are interested in: function Equals_Class(Inst: PSimpleInstance; Left, Right: TObject): Boolean; begin if Left = nil then Result := Right = nil else Result := Left.Equals(Right); end; function GetHashCode_Class(Inst: PSimpleInstance; Value: TObject): Integer; begin if Value = nil then Result := 42 else Result := Value.GetHashCode; end;
As you can see, if nil is passed to the function, then 42. Otherwise, some TObject methods are called. Here they are: function Equals(Obj: TObject): Boolean; virtual; function GetHashCode: Integer; virtual;
These are two virtual methods. This means that in the case of the transfer of an object that is not nil, the pointer is guaranteed to be renamed. class function Equals(Instance: TObject; Other: TObject): Boolean; virtual; class function GetHashCode(Instance: TObject): Integer; virtual;
Overlap capabilities are preserved, with this default behavior: a hash on pointers can be implemented. Anyway. function TObject.GetHashCode: Integer; begin Result := Integer(Self); end;
As we see it is very bad. In the case of allocations, we are more likely to get aligned data + granular size of the object, which potentially gives rise to many collisions.Source: https://habr.com/ru/post/269359/
All Articles