Delphi Dictionary and ordering data - delphi

My code is:
procedure TfrmSettings.btnFillDictClick(Sender: TObject);
var
Dict: TDictionary<string, string>;
Item: TPair<string, string>;
begin
Dict := TDictionary<string, string>.Create();
Dict.Add('Key1', 'Text1');
Dict.Add('Key2', 'Text2');
Dict.Add('Key3', 'Text3');
Dict.Add('Key4', 'Text4');
for Item in Dict do
begin
ShowMessage(Item.Key + ' ' + Item.Value);
end;
end;
Why almost every time I'm getting a different value in Showmessage?
Why values are not stored in the order in which they were added?
I'm a noob in Delphi and do not know how Dictionary is working. And I didn't find any information about this in Google.
Could you please explain me why it is so?
Is there any way to use Dictionary without using TList<> for sort data?
Thanks

Dictionary does not maintain order of elements because the way it is internally organized as look up table and it is ordered by the hash of the key. They are optimized for speed and not to preserve ordering.
If you need to maintain order of elements you need pair list instead of dictionary. Delphi does not provide that out of the box. You can use following code to implement simple pair list and customize it for your needs.
type
TPairs<TKey, TValue> = class(TList < TPair < TKey, TValue >> )
protected
fKeyComparer: IComparer<TKey>;
fValueComparer: IComparer<TValue>;
function GetValue(Key: TKey): TValue;
procedure SetValue(Key: TKey; const Value: TValue);
function ComparePair(const Left, Right: TPair<TKey, TValue>): Integer;
public
constructor Create; overload;
procedure Add(const aKey: TKey; const aValue: TValue); overload;
function IndexOfKey(const aKey: TKey): Integer;
function ContainsKey(const aKey: TKey): Boolean; inline;
property Values[Key: TKey]: TValue read GetValue write SetValue;
end;
constructor TPairs<TKey, TValue>.Create;
begin
if fKeyComparer = nil then fKeyComparer := TComparer<TKey>.Default;
if fValueComparer = nil then fValueComparer := TComparer<TValue>.Default;
inherited Create(TDelegatedComparer <TPair<TKey, TValue>>.Create(ComparePair));
end;
function TPairs<TKey, TValue>.ComparePair(const Left, Right: TPair<TKey, TValue>): Integer;
begin
Result := fKeyComparer.Compare(Left.Key, Right.Key);
if Result = 0 then Result := fValueComparer.Compare(Left.Value, Right.Value);
end;
function TPairs<TKey, TValue>.IndexOfKey(const aKey: TKey): Integer;
var
i: Integer;
begin
Result := -1;
for i := 0 to Count - 1 do
if fKeyComparer.Compare(Items[i].Key, aKey) = 0 then
begin
Result := i;
break;
end;
end;
function TPairs<TKey, TValue>.ContainsKey(const aKey: TKey): Boolean;
begin
Result := IndexOfKey(aKey) >= 0;
end;
function TPairs<TKey, TValue>.GetValue(Key: TKey): TValue;
var
i: Integer;
begin
i := IndexOfKey(Key);
if i >= 0 then Result := Items[i].Value
else Result := default (TValue);
end;
procedure TPairs<TKey, TValue>.SetValue(Key: TKey; const Value: TValue);
var
i: Integer;
Pair: TPair<TKey, TValue>;
begin
i := IndexOfKey(Key);
if i >= 0 then FItems[i].Value := Value
else
begin
Pair.Create(Key, Value);
inherited Add(Pair);
end;
end;
procedure TPairs<TKey, TValue>.Add(const aKey: TKey; const aValue: TValue);
begin
SetValue(aKey, aValue);
end;
And then you can use it the same way you would use dictionary, but order of elements will be maintained.
var
Pairs: TPairs<string, string>;
Item: TPair<string, string>;
begin
Pairs := TPairs<string, string>.Create();
Pairs.Add('Key1', 'Text1');
Pairs.Add('Key2', 'Text2');
Pairs.Add('Key3', 'Text3');
Pairs.Add('Key4', 'Text4');
Pairs.Add('Key5', 'Text5');
for Item in Pairs do
begin
Memo1.Lines.Add(Item.Key + ' ' + Item.Value);
end;
end;
SetValue update for newer Delphi versions where FItems is not available in TList<T> descendant classes.
procedure TPairs<TKey, TValue>.SetValue(Key: TKey; const Value: TValue);
var
i: Integer;
Pair: TPair<TKey, TValue>;
begin
i := IndexOfKey(Key);
if i >= 0 then
begin
Pair := Items[i];
Pair.Value := Value;
Items[i] := Pair;
end
else
begin
Pair.Create(Key, Value);
inherited Add(Pair);
end;
end;

Related

Storing up to 8 boolean values in a byte or up to 32 boolean values in an Integer

I have this need where I need to store either:
up to 8 Boolean values in a Byte
up to 32 Boolean values in a (U)Int32
up to 64 Boolean values in a (U)Int64
Is Byte more suitable than Char for 8-bits?
Do I use signed or unsigned for 32/64-bit?
Is there a Delphi-specific code sample to convert the Byte/Integer to/from an array of Booleans? And to set, say, the N-th item to true/false, such as:
SetItemBoolean(ItemNumber: Integer; Value: Boolean);
I found something to convert from a Char to an array of Booleans, I'm just wondering how to do it for Byte/Integer so I can support a bigger number of Boolean values.
https://ibeblog.com/2010/08/20/delphi-binary-data-storage/
Delphi offers TIntegerSet for this, which has the size of an Integer and thus can be cast on it.
var
Bits: TIntegerSet;
IntVal: Integer;
begin
if Value then
Include(Bits, ItemNumber)
else
Exclude(Bits, ItemNumber);
if ItemNumber in Bits then
{ Bit ItemNumber is set }
else
{ Bit ItemNumber is not set }
{ cast to Integer as needed }
IntVal := Integer(Bits);
{ or from Integer }
Bits := TIntegerSet(IntVal);
end;
For 8 bits in a byte you can declare a TByteSet in a similar way:
type
TByteSet = set of 0..7;
and cast it to or from a Byte variable.
This type implements arbitrarily sized bit sets.
type
TBitSet = record
private
FBitCount: Integer;
FSets: array of set of 0..255;
class function SetCount(BitCount: Integer): Integer; static;
procedure MakeUnique;
procedure GetSetIndexAndBitIndex(Bit: Integer; out SetIndex, BitIndex: Integer);
function GetIsEmpty: Boolean;
procedure SetBitCount(Value: Integer);
function GetSize: Integer;
public
class operator In(const Bit: Integer; const BitSet: TBitSet): Boolean;
class operator Equal(const bs1, bs2: TBitSet): Boolean;
class operator NotEqual(const bs1, bs2: TBitSet): Boolean;
class function SizeOfNativeSet(BitCount: Integer): Integer; static;
property BitCount: Integer read FBitCount write SetBitCount;
property Size: Integer read GetSize;
property IsEmpty: Boolean read GetIsEmpty;
procedure Clear;
procedure IncludeAll;
procedure Include(const Bit: Integer);
procedure Exclude(const Bit: Integer);
end;
{ TBitSet }
procedure TBitSet.MakeUnique;
begin
// this is used to implement copy-on-write so that the type behaves like a value
SetLength(FSets, Length(FSets));
end;
procedure TBitSet.GetSetIndexAndBitIndex(Bit: Integer; out SetIndex, BitIndex: Integer);
begin
Assert(InRange(Bit, 0, FBitCount-1));
SetIndex := Bit shr 8; // shr 8 = div 256
BitIndex := Bit and 255; // and 255 = mod 256
end;
function TBitSet.GetIsEmpty: Boolean;
var
i: Integer;
begin
for i := 0 to High(FSets) do begin
if FSets[i]<>[] then begin
Result := False;
Exit;
end;
end;
Result := True;
end;
procedure TBitSet.SetBitCount(Value: Integer);
var
Bit, SetIndex, BitIndex: Integer;
begin
if (Value<>FBitCount) or not Assigned(FSets) then begin
Assert(Value>=0);
FBitCount := Value;
SetLength(FSets, SetCount(Value));
if Value>0 then begin
(* Ensure that unused bits are cleared, necessary give the CompareMem call in Equal. This also
means that state does not persist when we decrease and then increase BitCount. For instance,
consider this code:
var
bs: TBitSet;
...
bs.BitCount := 2;
bs.Include(1);
bs.BitCount := 1;
bs.BitCount := 2;
Assert(not (1 in bs)); *)
GetSetIndexAndBitIndex(Value - 1, SetIndex, BitIndex);
for Bit := BitIndex + 1 to 255 do begin
System.Exclude(FSets[SetIndex], Bit);
end;
end;
end;
end;
function TBitSet.GetSize: Integer;
begin
Result := Length(FSets)*SizeOf(FSets[0]);
end;
class function TBitSet.SetCount(BitCount: Integer): Integer;
begin
Result := (BitCount + 255) shr 8; // shr 8 = div 256
end;
class function TBitSet.SizeOfNativeSet(BitCount: Integer): Integer;
begin
Result := (BitCount + 7) shr 3; // shr 3 = div 8
end;
class operator TBitSet.In(const Bit: Integer; const BitSet: TBitSet): Boolean;
var
SetIndex, BitIndex: Integer;
begin
BitSet.GetSetIndexAndBitIndex(Bit, SetIndex, BitIndex);
Result := BitIndex in BitSet.FSets[SetIndex];
end;
class operator TBitSet.Equal(const bs1, bs2: TBitSet): Boolean;
begin
Result := (bs1.FBitCount=bs2.FBitCount)
and CompareMem(Pointer(bs1.FSets), Pointer(bs2.FSets), bs1.Size);
end;
class operator TBitSet.NotEqual(const bs1, bs2: TBitSet): Boolean;
begin
Result := not (bs1=bs2);
end;
procedure TBitSet.Clear;
var
i: Integer;
begin
MakeUnique;
for i := 0 to High(FSets) do begin
FSets[i] := [];
end;
end;
procedure TBitSet.IncludeAll;
var
i: Integer;
begin
for i := 0 to BitCount-1 do begin
Include(i);
end;
end;
procedure TBitSet.Include(const Bit: Integer);
var
SetIndex, BitIndex: Integer;
begin
MakeUnique;
GetSetIndexAndBitIndex(Bit, SetIndex, BitIndex);
System.Include(FSets[SetIndex], BitIndex);
end;
procedure TBitSet.Exclude(const Bit: Integer);
var
SetIndex, BitIndex: Integer;
begin
MakeUnique;
GetSetIndexAndBitIndex(Bit, SetIndex, BitIndex);
System.Exclude(FSets[SetIndex], BitIndex);
end;
It's a simple binary logic. You can store data in any numeric type, but i recommend use Unsigned types. Here is example for BYTE type, but you can do write same for any(UInt16, UInt32, UInt64) just change type of AStorage param:
//for byte Index can be from 0 to 7
function GetByteBool(const AStorage : byte; AIndex : byte) : boolean;
begin
Result := (AStorage and (1 shl AIndex)) = (1 shl AIndex);
end;
procedure SetByteBool(var AStorage : byte; const AIndex : byte; const AValue : boolean);
begin
if AValue then begin
AStorage := AStorage or (1 shl AIndex);
end else begin
AStorage := AStorage xor (1 shl AIndex);
end;
end;
procedure TForm2.Button1Click(Sender: TObject);
begin
var b : byte := 17;
SetByteBool(b, 4, false);
if GetByteBool(b, 4) then
showmessage('true')
else
showmessage('false')
end;
In this case you will use just 1 BIT per 1 boolean value.

How can I pass either Variant or TObject to the same method argument?

I have the two overload methods:
procedure TProps.SetProp(Value: TObject); overload;
procedure TProps.SetProp(const Value: Variant); overload;
They do pretty much the same repeating code except from minor variations depending on whether the Value is Variant or TObject.
I want to use a common method:
procedure TProps.DoSetProp(Value: <what type here?>); // <--
So I can pass both Variant or TObject from SetProp and be able to distinguish between the two types. what are my options?
Edit: for now I used:
procedure TProps.DoSetProp(Value: Pointer; IsValueObject: Boolean);
begin
// common code...
if IsValueObject then
PropValue.Obj := Value
else
PropValue.V := PVariant(Value)^;
// common code...
if IsValueObject then
PropValue.Obj := Value
else
PropValue.V := PVariant(Value)^;
// etc...
end;
and the overload methods:
procedure TProps.SetProp(const Value: Variant); overload;
begin
DoSetProp(#Value, False);
end;
procedure TProps.SetProp(Value: TObject); overload;
begin
DoSetProp(Value, True);
end;
I'm not sure I like this solution because of the IsValueObject. I would rather detect the type from a common type "container".
I could use TVarRec:
VarRec: TVarRec;
// for Variant:
VarRec.VType := vtVariant;
VarRec.VVariant := #Value;
// for TObject
VarRec.VType := vtObject;
VarRec.VObject := Value;
And pass the VarRec to the common method. but I'm not sure I like it either.
EDIT 2: What I am trying to do? I'm trying to extend properties for any TObject similar to SetProp API.
Here is the entire MCVE:
function ComparePointers(A, B: Pointer): Integer;
begin
if Cardinal(A) = Cardinal(B) then
Result := 0
else if Cardinal(A) < Cardinal(B) then
Result := -1
else
Result := 1
end;
type
TPropValue = class
private
V: Variant;
Obj: TObject;
procedure SetValue(const Value: Pointer; IsValueObject: Boolean);
end;
TPropNameValueList = class(TStringList)
public
destructor Destroy; override;
procedure Delete(Index: Integer); override;
end;
TObjectProps = class
private
BaseObject: TObject;
PropList: TPropNameValueList;
public
constructor Create(AObject: TObject);
destructor Destroy; override;
end;
TProps = class(TComponent)
private
FList: TObjectList;
protected
procedure DoSetProp(AObject: TObject; const PropName: string; const Value: Pointer; IsValueObject: Boolean);
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
public
function Find(AObject: TObject; var Index: Integer): Boolean;
procedure SetProp(AObject: TObject; const PropName: string; const Value: Variant); overload;
procedure SetProp(AObject: TObject; const PropName: string; Value: TObject); overload;
function RemoveProp(AObject: TObject; const PropName: string): Boolean;
function RemoveProps(AObject: TObject): Boolean;
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
{ TPropValue }
procedure TPropValue.SetValue(const Value: Pointer; IsValueObject: Boolean);
begin
if IsValueObject then
Obj := Value
else
V := PVariant(Value)^;
end;
{ TPropNameValueList }
destructor TPropNameValueList.Destroy;
var
I: Integer;
begin
for I := 0 to Count - 1 do
Objects[I].Free; // TPropValue
inherited;
end;
procedure TPropNameValueList.Delete(Index: Integer);
begin
Objects[Index].Free;
inherited;
end;
{ TObjectProps }
constructor TObjectProps.Create(AObject: TObject);
begin
BaseObject := AObject;
PropList := TPropNameValueList.Create;
PropList.Sorted := True;
PropList.Duplicates := dupError;
end;
destructor TObjectProps.Destroy;
begin
PropList.Free;
inherited;
end;
{ TProps }
constructor TProps.Create(AOwner: TComponent);
begin
inherited;
FList := TObjectList.Create(true);
end;
procedure TProps.Notification(AComponent: TComponent; Operation: TOperation);
begin
inherited;
if (Operation = opRemove) and (AComponent <> nil) then
begin
RemoveProps(AComponent);
end;
end;
destructor TProps.Destroy;
begin
FList.Free;
inherited;
end;
function TProps.Find(AObject: TObject; var Index: Integer): Boolean;
var
L, H, I, C: Integer;
begin
Result := False;
L := 0;
H := FList.Count - 1;
while L <= H do
begin
I := (L + H) shr 1;
C := ComparePointers(TObjectProps(FList[I]).BaseObject, AObject);
if C < 0 then L := I + 1 else
begin
H := I - 1;
if C = 0 then
begin
Result := True;
L := I;
end;
end;
end;
Index := L;
end;
procedure TProps.DoSetProp(AObject: TObject; const PropName: string; const Value: Pointer;
IsValueObject: Boolean);
var
OP: TObjectProps;
PropValue: TPropValue;
Index, NameIndex: Integer;
Found: Boolean;
I: Integer;
begin
Found := Find(AObject, Index);
if not Found then
begin
OP := TObjectProps.Create(AObject);
if AObject is TComponent then
TComponent(AObject).FreeNotification(Self);
PropValue := TPropValue.Create;
PropValue.SetValue(Value, IsValueObject);
OP.PropList.AddObject(PropName, PropValue);
FList.Insert(Index, OP);
end
else
begin
OP := TObjectProps(FList[Index]);
NameIndex := OP.PropList.IndexOf(PropName);
if NameIndex <> -1 then
begin
PropValue := TPropValue(OP.PropList.Objects[NameIndex]);
PropValue.SetValue(Value, IsValueObject);
end
else
begin
PropValue := TPropValue.Create;
PropValue.SetValue(Value, IsValueObject);
OP.PropList.AddObject(PropName, PropValue);
end;
end;
end;
procedure TProps.SetProp(AObject: TObject; const PropName: string; const Value: Variant);
begin
DoSetProp(AObject, PropName, #Value, False);
end;
procedure TProps.SetProp(AObject: TObject; const PropName: string; Value: TObject);
begin
DoSetProp(AObject, PropName, Value, True);
end;
function TProps.RemoveProp(AObject: TObject; const PropName: string): Boolean;
var
Index, NameIndex: Integer;
OP: TObjectProps;
begin
Result := False;
if not Find(AObject, Index) then Exit;
OP := TObjectProps(FList[Index]);
NameIndex := OP.PropList.IndexOf(PropName);
if NameIndex <> -1 then
begin
OP.PropList.Delete(NameIndex);
Result := True;
end;
end;
function TProps.RemoveProps(AObject: TObject): Boolean;
var
Index: Integer;
OP: TObjectProps;
begin
if not Find(AObject, Index) then
begin
Result := False;
Exit;
end;
OP := TObjectProps(FList[Index]);
Result := FList.Remove(OP) <> -1;
end;
Usage:
Props := TProps.Create(Self);
Props.SetProp(Button1, 'myprop1', Self); // TObject property
Props.SetProp(Button1, 'myprop2', 666); // variant
Props.SetProp(Button2, 'myprop', 'Hello'); // variant
Props.SetProp(MyObject, 'foo', 123.123);
Note: TProps.GetProp is not yet implemented.
You are fighting the compiler; You should continue to use overloads.
"I would rather detect the type from a common type 'container'."
Your choices are variant or untyped pointer. You are going to have to unpack the "Value" parameter. With an untyped pointer you will have to do all the work; with a variant you will have to do most of the work. Very messy.
"They do pretty much the same repeating code except from minor variations depending on whether the Value is Variant or TObject."
If that is really true then you should still continue to use overloads but add an internal "SetProp" method that takes "normalized" data that does the actual work. Your "repeating" code is the setting of the property values. But you still have specific code to write to crack the incoming "Value" parameter whether you have one method that accepts a "container" type or multiple overloaded methods that take the various types you want to accept. In the one-method-container type you will have a (complex) if-then-else block that cracks the Value. In the overloaded-methods type there is no if-testing; you just crack the Value for the type that each method accepts.
The major advantage is that your object is better documented: you can see what types are acceptable for "Value" and, better still, the compiler helps you because it "knows" what types are acceptable. With your one-method approach the compiler will not be able to help you enforce the type of "Value"; you are doing all the work.
Also, using the overloaded methods, I wouldn't have one that accepts variant (although the example below does). Have an separate overload for each of string, integer, double, etc.
type
TNormalizedPropValue = record
// ....
end;
procedure TProps.internalSetProp(Value : TNormalizedPropValue);
begin
//
// Set the property value from the "Normalized" pieces and parts.
//
end;
procedure TProps.SetProp(Value : TObject);
var
NormalizedObjectPropValue : TNormalizedPropValue;
begin
// Copy the pieces and parts from "Value" into NormalizedObjectPropValue
//
internalSetProp(NormalizedObjectPropValue);
end;
procedure TProps.SetProp(Value : variant);
var
NormalizedVariantPropValue : TNormalizedPropValue;
begin
// Crack "Value" variant and copy the pieces and parts into NormalizedVariantPropValue
//
internalSetProp(NormalizedVariantPropValue);
end;

How to fix byte ordering issue in this piece of code?

To read a index file in a specific format, I cooked the following piece of code without considering byte ordering:
unit uCBI;
interface
uses
SysUtils,
Classes,
Generics.Collections;
type
TIndexList = class
private
FIndexList:TList<Cardinal>;
FOwnedStream:Boolean;
FMemoryStream: TMemoryStream;
function GetCount: Integer;
protected
public
constructor Create(AStream:TMemoryStream; OwnedStream:Boolean=True);
destructor Destroy; override;
function Add(const Value: Cardinal): Integer;
procedure Clear;
procedure SaveToFile(AFileName:TFileName);
procedure LoadFromFile(AFileName:TFileName);
property Count: Integer read GetCount;
end;
implementation
{ TIndexList }
function TIndexList.Add(const Value: Cardinal): Integer;
begin
Result := FIndexList.Add(Value)
end;
procedure TIndexList.Clear;
begin
FIndexList.Clear;
end;
constructor TIndexList.Create(AStream: TMemoryStream; OwnedStream: Boolean);
begin
FMemoryStream := AStream;
FOwnedStream := OwnedStream;
FIndexList := TList<Cardinal>.Create;
end;
destructor TIndexList.Destroy;
begin
if (FOwnedStream and Assigned(FMemoryStream)) then
FMemoryStream.Free;
FIndexList.Free;
//
inherited;
end;
function TIndexList.GetCount: Integer;
begin
Result := FIndexList.Count;
end;
procedure TIndexList.LoadFromFile(AFileName: TFileName);
var
lMemoryStream:TMemoryStream;
lCount:Cardinal;
begin
lMemoryStream := TMemoryStream.Create;
try
lMemoryStream.LoadFromFile(AFileName);
lMemoryStream.ReadBuffer(lCount,SizeOf(Cardinal));
if (lCount = Cardinal((lMemoryStream.Size-1) div SizeOf(Cardinal))) then
begin
FMemoryStream.Clear;
lMemoryStream.Position :=0;
FMemoryStream.CopyFrom(lMemoryStream,lMemoryStream.Size)
end else
raise Exception.CreateFmt('Corrupted CBI file: %s',[ExtractFileName(AFileName)]);
finally
lMemoryStream.Free;
end;
end;
procedure TIndexList.SaveToFile(AFileName: TFileName);
var
lCount:Cardinal;
lItem:Cardinal;
begin
FMemoryStream.Clear;
lCount := FIndexList.Count;
FMemoryStream.WriteBuffer(lCount,SizeOf(Cardinal));
for lItem in FIndexList do
begin
FMemoryStream.WriteBuffer(lItem,SizeOf(Cardinal));
end;
//
FMemoryStream.SaveToFile(AFileName);
end;
end.
It tested it and seems to work well as needed. Great was my suprise when I pursue extensive tests with real sample file. In fact the legacy format was devised with Amiga computer with a different byte ordering.
My Question:
How can I fix it ?
I want to keep the code unchanged and wonder wether a decorated TMemorySream will do so that I can transparently switch between big endian and little endian.
To change 'endianness' of Cardinals you can use the following:
function EndianChange(Value: Cardinal): Cardinal;
var
A1: array [0..3] of Byte absolute Value;
A2: array [0..3] of Byte absolute Result;
I: Integer;
begin
for I:= 0 to 3 do begin
A2[I]:= A1[3 - I];
end;
end;
If you want to keep your code unchanged, you can write your own TMemoryStream descendant and override its Read and Write methods using the above function, like that:
function TMyMemoryStream.Read(var Buffer; Count: Integer): Longint;
var
P: PCardinal;
I, N: Integer;
begin
inherited;
P:= #Buffer;
Assert(Count and 3 = 0);
N:= Count shr 2;
while N > 0 do begin
P^:= EndianChange(P^);
Inc(P);
Dec(N);
end;
end;

How to "scan" the full list of currently-installed VCL components

I still haven't found a truly satisfactory answer to this question, and am now considering rolling my own. I have ModelMaker and GExperts, and neither seems to load the comprehensive class-hierarchy I am looking for. As well, I don't think the folks at DevExpress will fork over the CDK code which compiles a full class list to inherit from... ;-)
SO...
If ALL I want to do is build a self-referencing table of all registered component classes (or even all classes including non-components, if that's just as easy/possible), what would be the best way to go about doing that?
Note: I don't really need property / method details; JUST a complete list of class names (and parent names) I can store to a table and put in a treeview. Anything beyond that, though, is more than welcome as bonus info. :-)
Update later:
One answer that shows up in my "recent" section on SO, but not here on the question (maybe they erased it?), was this:"u may want to take a look on code of Component Search, it may help you to enumrate all components installed." Is that code available? Is so, where is it hiding? Would be interesting to study.
Unfortunately, the code implementing the RegisterClass mechanism is hidden in Classes implementation section.
If you need this for getting the list of components installed in the IDE, you can write a design package, install it into the IDE and use IOTAPackageServices in ToolsAPI unit. This will give you the list of installed packages and their components.
Note: You'll have to add designide.dcp to your 'requires' clause to be able to use Delphi's internal units like ToolsAPI.
A bit more work but a more generic way would be to enumerate all loaded modules. You can call GetPackageInfo (SysUtils) on a package module to enumerate contained unit names and required packages. However this will not give you a list of classes contained in the package.
You could enumerate the package's list of exported functions (e.g. with TJclPeImage in the JCL) and search for those named like this:
#<unit_name>#<class_name>#
for example: '#System#TObject#'.
By calling GetProcAddress with the function name you get the TClass reference. From there you can walk the hierarchy using ClassParent. This way you can enumerate all classes in all packages loaded in a process running a Delphi executable compiled with runtime packages (Delphi IDE, too).
Another idea is to scan for type information which is on top of the list of exported functions so you can skip enumerating further. The type infos are exported with names starting with prefix '#$xp$'. Here's an example:
unit PackageUtils;
interface
uses
Windows, Classes, SysUtils, Contnrs, TypInfo;
type
TDelphiPackageList = class;
TDelphiPackage = class;
TDelphiProcess = class
private
FPackages: TDelphiPackageList;
function GetPackageCount: Integer;
function GetPackages(Index: Integer): TDelphiPackage;
public
constructor Create; virtual;
destructor Destroy; override;
procedure Clear; virtual;
function FindPackage(Handle: HMODULE): TDelphiPackage;
procedure Reload; virtual;
property PackageCount: Integer read GetPackageCount;
property Packages[Index: Integer]: TDelphiPackage read GetPackages;
end;
TDelphiPackageList = class(TObjectList)
protected
function GetItem(Index: Integer): TDelphiPackage;
procedure SetItem(Index: Integer; APackage: TDelphiPackage);
public
function Add(APackage: TDelphiPackage): Integer;
function Extract(APackage: TDelphiPackage): TDelphiPackage;
function Remove(APackage: TDelphiPackage): Integer;
function IndexOf(APackage: TDelphiPackage): Integer;
procedure Insert(Index: Integer; APackage: TDelphiPackage);
function First: TDelphiPackage;
function Last: TDelphiPackage;
property Items[Index: Integer]: TDelphiPackage read GetItem write SetItem; default;
end;
TDelphiPackage = class
private
FHandle: THandle;
FInfoTable: Pointer;
FTypeInfos: TList;
procedure CheckInfoTable;
procedure CheckTypeInfos;
function GetDescription: string;
function GetFileName: string;
function GetInfoName(NameType: TNameType; Index: Integer): string;
function GetShortName: string;
function GetTypeInfoCount(Kinds: TTypeKinds): Integer;
function GetTypeInfos(Kinds: TTypeKinds; Index: Integer): PTypeInfo;
public
constructor Create(AHandle: HMODULE; AInfoTable: Pointer = nil);
destructor Destroy; override;
property Description: string read GetDescription;
property FileName: string read GetFileName;
property Handle: THandle read FHandle;
property ShortName: string read GetShortName;
property TypeInfoCount[Kinds: TTypeKinds]: Integer read GetTypeInfoCount;
property TypeInfos[Kinds: TTypeKinds; Index: Integer]: PTypeInfo read GetTypeInfos;
end;
implementation
uses
RTLConsts, SysConst,
PSAPI, ImageHlp;
{ Package info structures copied from SysUtils.pas }
type
PPkgName = ^TPkgName;
TPkgName = packed record
HashCode: Byte;
Name: array[0..255] of Char;
end;
PUnitName = ^TUnitName;
TUnitName = packed record
Flags : Byte;
HashCode: Byte;
Name: array[0..255] of Char;
end;
PPackageInfoHeader = ^TPackageInfoHeader;
TPackageInfoHeader = packed record
Flags: Cardinal;
RequiresCount: Integer;
{Requires: array[0..9999] of TPkgName;
ContainsCount: Integer;
Contains: array[0..9999] of TUnitName;}
end;
TEnumModulesCallback = function (Module: HMODULE; Data: Pointer = nil): Boolean;
TEnumModulesProc = function (Callback: TEnumModulesCallback; Data: Pointer = nil): Boolean;
const
STypeInfoPrefix = '#$xp$';
var
EnumModules: TEnumModulesProc = nil;
function PackageInfoTable(Module: HMODULE): PPackageInfoHeader; forward;
function AddPackage(Module: HMODULE; Data: {TDelphiPackageList} Pointer): Boolean;
var
InfoTable: Pointer;
begin
Result := False;
if (Module <> HInstance) then
begin
InfoTable := PackageInfoTable(Module);
if Assigned(InfoTable) then
TDelphiPackageList(Data).Add(TDelphiPackage.Create(Module, InfoTable));
end;
end;
function GetPackageDescription(Module: HMODULE): string;
var
ResInfo: HRSRC;
ResData: HGLOBAL;
begin
Result := '';
ResInfo := FindResource(Module, 'DESCRIPTION', RT_RCDATA);
if ResInfo <> 0 then
begin
ResData := LoadResource(Module, ResInfo);
if ResData <> 0 then
try
Result := PWideChar(LockResource(ResData));
UnlockResource(ResData);
finally
FreeResource(ResData);
end;
end;
end;
function EnumModulesPS(Callback: TEnumModulesCallback; Data: Pointer = nil): Boolean;
var
ProcessHandle: THandle;
SizeNeeded: Cardinal;
P, ModuleHandle: PDWORD;
I: Integer;
begin
Result := False;
ProcessHandle := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, False, GetCurrentProcessId);
if ProcessHandle = 0 then
RaiseLastOSError;
try
SizeNeeded := 0;
EnumProcessModules(ProcessHandle, nil, 0, SizeNeeded);
if SizeNeeded = 0 then
Exit;
P := AllocMem(SizeNeeded);
try
if EnumProcessModules(ProcessHandle, P, SizeNeeded, SizeNeeded) then
begin
ModuleHandle := P;
for I := 0 to SizeNeeded div SizeOf(DWORD) - 1 do
begin
if Callback(ModuleHandle^, Data) then
Exit;
Inc(ModuleHandle);
end;
Result := True;
end;
finally
FreeMem(P);
end;
finally
CloseHandle(ProcessHandle);
end;
end;
function EnumModulesTH(Callback: TEnumModulesCallback; Data: Pointer = nil): Boolean;
begin
Result := False;
// todo win9x?
end;
function PackageInfoTable(Module: HMODULE): PPackageInfoHeader;
var
ResInfo: HRSRC;
Data: THandle;
begin
Result := nil;
ResInfo := FindResource(Module, 'PACKAGEINFO', RT_RCDATA);
if ResInfo <> 0 then
begin
Data := LoadResource(Module, ResInfo);
if Data <> 0 then
try
Result := LockResource(Data);
UnlockResource(Data);
finally
FreeResource(Data);
end;
end;
end;
{ TDelphiProcess private }
function TDelphiProcess.GetPackageCount: Integer;
begin
Result := FPackages.Count;
end;
function TDelphiProcess.GetPackages(Index: Integer): TDelphiPackage;
begin
Result := FPackages[Index];
end;
{ TDelphiProcess public }
constructor TDelphiProcess.Create;
begin
inherited Create;
FPackages := TDelphiPackageList.Create;
Reload;
end;
destructor TDelphiProcess.Destroy;
begin
FPackages.Free;
inherited Destroy;
end;
procedure TDelphiProcess.Clear;
begin
FPackages.Clear;
end;
function TDelphiProcess.FindPackage(Handle: HMODULE): TDelphiPackage;
var
I: Integer;
begin
Result := nil;
for I := 0 to FPackages.Count - 1 do
if FPackages[I].Handle = Handle then
begin
Result := FPackages[I];
Break;
end;
end;
procedure TDelphiProcess.Reload;
begin
Clear;
if Assigned(EnumModules) then
EnumModules(AddPackage, FPackages);
end;
{ TDelphiPackageList protected }
function TDelphiPackageList.GetItem(Index: Integer): TDelphiPackage;
begin
Result := TDelphiPackage(inherited GetItem(Index));
end;
procedure TDelphiPackageList.SetItem(Index: Integer; APackage: TDelphiPackage);
begin
inherited SetItem(Index, APackage);
end;
{ TDelphiPackageList public }
function TDelphiPackageList.Add(APackage: TDelphiPackage): Integer;
begin
Result := inherited Add(APackage);
end;
function TDelphiPackageList.Extract(APackage: TDelphiPackage): TDelphiPackage;
begin
Result := TDelphiPackage(inherited Extract(APackage));
end;
function TDelphiPackageList.First: TDelphiPackage;
begin
Result := TDelphiPackage(inherited First);
end;
function TDelphiPackageList.IndexOf(APackage: TDelphiPackage): Integer;
begin
Result := inherited IndexOf(APackage);
end;
procedure TDelphiPackageList.Insert(Index: Integer; APackage: TDelphiPackage);
begin
inherited Insert(Index, APackage);
end;
function TDelphiPackageList.Last: TDelphiPackage;
begin
Result := TDelphiPackage(inherited Last);
end;
function TDelphiPackageList.Remove(APackage: TDelphiPackage): Integer;
begin
Result := inherited Remove(APackage);
end;
{ TDelphiPackage private }
procedure TDelphiPackage.CheckInfoTable;
begin
if not Assigned(FInfoTable) then
FInfoTable := PackageInfoTable(Handle);
if not Assigned(FInfoTable) then
raise EPackageError.CreateFmt(SCannotReadPackageInfo, [ExtractFileName(GetModuleName(Handle))]);
end;
procedure TDelphiPackage.CheckTypeInfos;
var
ExportDir: PImageExportDirectory;
Size: DWORD;
Names: PDWORD;
I: Integer;
begin
if not Assigned(FTypeInfos) then
begin
FTypeInfos := TList.Create;
try
Size := 0;
ExportDir := ImageDirectoryEntryToData(Pointer(Handle), True, IMAGE_DIRECTORY_ENTRY_EXPORT, Size);
if not Assigned(ExportDir) then
Exit;
Names := PDWORD(DWORD(Handle) + DWORD(ExportDir^.AddressOfNames));
for I := 0 to ExportDir^.NumberOfNames - 1 do
begin
if StrLIComp(PChar(DWORD(Handle) + Names^), STypeInfoPrefix, StrLen(STypeInfoPrefix)) <> 0 then
Break;
FTypeInfos.Add(GetProcAddress(Handle, PChar(DWORD(Handle) + Names^)));
Inc(Names);
end;
except
FreeAndNil(FTypeInfos);
raise;
end;
end;
end;
function TDelphiPackage.GetDescription: string;
begin
Result := GetPackageDescription(Handle);
end;
function TDelphiPackage.GetFileName: string;
begin
Result := GetModuleName(FHandle);
end;
function TDelphiPackage.GetInfoName(NameType: TNameType; Index: Integer): string;
var
P: Pointer;
Count: Integer;
I: Integer;
begin
Result := '';
CheckInfoTable;
Count := PPackageInfoHeader(FInfoTable)^.RequiresCount;
P := Pointer(Cardinal(FInfoTable) + SizeOf(TPackageInfoHeader));
case NameType of
ntContainsUnit:
begin
for I := 0 to Count - 1 do
P := Pointer(Cardinal(P) + StrLen(PPkgName(P)^.Name) + 2);
Count := Integer(P^);
P := Pointer(Cardinal(P) + SizeOf(Integer));
if (Index >= 0) and (Index < Count) then
begin
for I := 0 to Count - 1 do
P := Pointer(Cardinal(P) + StrLen(PUnitName(P)^.Name) + 3);
Result := PUnitName(P)^.Name;
end;
end;
ntRequiresPackage:
if (Index >= 0) and (Index < Count) then
begin
for I := 0 to Index - 1 do
P := Pointer(Cardinal(P) + StrLen(PPkgName(P)^.Name) + 2);
Result := PPkgName(P)^.Name;
end;
ntDcpBpiName:
if PPackageInfoHeader(FInfoTable)^.Flags and pfPackageModule <> 0 then
begin
for I := 0 to Count - 1 do
P := Pointer(Cardinal(P) + StrLen(PPkgName(P)^.Name) + 2);
Count := Integer(P^);
P := Pointer(Cardinal(P) + SizeOf(Integer));
for I := 0 to Count - 1 do
P := Pointer(Cardinal(P) + StrLen(PUnitName(P)^.Name) + 3);
Result := PPkgName(P)^.Name;
end;
end;
end;
function TDelphiPackage.GetShortName: string;
begin
Result := GetInfoName(ntDcpBpiName, 0);
end;
function TDelphiPackage.GetTypeInfoCount(Kinds: TTypeKinds): Integer;
var
I: Integer;
begin
CheckTypeInfos;
Result := 0;
for I := 0 to FTypeInfos.Count - 1 do
if (Kinds = []) or (PTypeInfo(FTypeInfos[I])^.Kind in Kinds) then
Inc(Result);
end;
function TDelphiPackage.GetTypeInfos(Kinds: TTypeKinds; Index: Integer): PTypeInfo;
var
I, J: Integer;
begin
CheckTypeInfos;
Result := nil;
J := -1;
for I := 0 to FTypeInfos.Count - 1 do
if (Kinds = []) or (PTypeInfo(FTypeInfos[I])^.Kind in Kinds) then
begin
Inc(J);
if J = Index then
begin
Result := FTypeInfos[I];
Break;
end;
end;
end;
{ TDelphiPackage public }
constructor TDelphiPackage.Create(AHandle: HMODULE; AInfoTable: Pointer = nil);
begin
inherited Create;
FHandle := AHandle;
FInfoTable := AInfoTable;
FTypeInfos := nil;
end;
destructor TDelphiPackage.Destroy;
begin
FTypeInfos.Free;
inherited Destroy;
end;
initialization
case Win32Platform of
VER_PLATFORM_WIN32_WINDOWS:
EnumModules := EnumModulesTH;
VER_PLATFORM_WIN32_NT:
EnumModules := EnumModulesPS;
else
EnumModules := nil;
end;
finalization
end.
Unit of the test design package installed in the IDE:
unit Test;
interface
uses
SysUtils, Classes,
ToolsAPI;
type
TTestWizard = class(TNotifierObject, IOTAWizard, IOTAMenuWizard)
private
{ IOTAWizard }
procedure Execute;
function GetIDString: string;
function GetName: string;
function GetState: TWizardState;
{ IOTAMenuWizard }
function GetMenuText: string;
end;
implementation
uses
TypInfo,
PackageUtils;
function AncestryStr(AClass: TClass): string;
begin
Result := '';
if not Assigned(AClass) then
Exit;
Result := AncestryStr(AClass.ClassParent);
if Result <> '' then
Result := Result + '\';
Result := Result + AClass.ClassName;
end;
procedure ShowMessage(const S: string);
begin
with BorlandIDEServices as IOTAMessageServices do
AddTitleMessage(S);
end;
{ TTestWizard }
procedure TTestWizard.Execute;
var
Process: TDelphiProcess;
I, J: Integer;
Package: TDelphiPackage;
PInfo: PTypeInfo;
PData: PTypeData;
begin
Process := TDelphiProcess.Create;
for I := 0 to Process.PackageCount - 1 do
begin
Package := Process.Packages[I];
for J := 0 to Package.TypeInfoCount[[tkClass]] - 1 do
begin
PInfo := Package.TypeInfos[[tkClass], J];
PData := GetTypeData(PInfo);
ShowMessage(Format('%s: %s.%s (%s)', [Package.ShortName, PData^.UnitName, PInfo^.Name, AncestryStr(PData^.ClassType)]));
end;
end;
end;
function TTestWizard.GetIDString: string;
begin
Result := 'TOndrej.TestWizard';
end;
function TTestWizard.GetName: string;
begin
Result := 'Test';
end;
function TTestWizard.GetState: TWizardState;
begin
Result := [wsEnabled];
end;
function TTestWizard.GetMenuText: string;
begin
Result := 'Test';
end;
var
Index: Integer = -1;
initialization
with BorlandIDEServices as IOTAWizardServices do
Index := AddWizard(TTestWizard.Create);
finalization
if Index <> -1 then
with BorlandIDEServices as IOTAWizardServices do
RemoveWizard(Index);
end.
You have to add designide to your requires clause. When you install this design package a new menu item Test should appear under Delphi's Help menu. Clicking it should display all loaded classes in the Messages window.
Have you tried Delphi's own class browser?
The browser gets loaded with shortcut CTRL-SHIFT-B. I believe you can access its options by right clicking in the browser. Here you have the option to show only the classes in your project or all known classes.
I haven't checked but I expect every descendant from TComponent, including installed components to be visible below the TComponent node. Use CTRL-F to search for a particular class.
Edit: according to this Delphi Wiki page, CTRL+SHIFT+B is only available in Delphi5. I don't have Delphi 2007 to check for this but if you can not find a class browser in your version, I'd suspect there isn't any.

Object Status (SubSet) Persistence

I need help on this:
im storing object properties in a DataPacket class.
The properties are defined like this
type
TProperty = class
private
FName: string;
public
constructor Create(const AName: string);
property Name: string read FName;
end;
TIntegerProperty = class(TProperty)
private
FValue: Integer;
protected
procedure SetValue(const AValue:integer);
public
property Value: Integer read FValue write SetValue;
end;
the DataPacket class:
type
TDataPacket = class
private
FProperties: TStringList;
public
function GetIntegerValue(const APropertyName: string): integer;
.....
procedure SetIntegerValue(const APropertyName: string; AValue: integer);
end;
and they are implemented like:
function TDataPacket.GetIntegerValue(const APropertyName: string): integer;
var
Pos: integer;
begin
Pos := FProperties.IndexOf(APropertyName);
if Pos > -1 then
Result := TIntegerProperty(FProperties.Objects[Pos]).Value
else
Result := 0;
end;
procedure TDataPacket.SetIntegerValue(const APropertyName: string; AValue: integer);
var
Pos: integer;
AProperty: TIntegerProperty;
begin
Pos := FProperties.IndexOf(APropertyName);
if Pos >- 1 then
TIntegerProperty(FProperties.Objects[Pos]).Value := AValue
else
begin
AProperty:= TIntegerProperty.Create(APropertyName);
AProperty.Value := AValue;
FProperties.AddObject(APropertyName, AProperty);
end;
end;
now the question: i need to define an Status property defined as TObjectStatus where:
type
TStatus = (Deleted, Unchanged, Added , Modified, ChildsModified);
TObjectStatus = Set of TStatus;
any idea on how can i define, store and retrieve it?
sorry for the long explanation and thanks in advance for you help
Michael
First:
Result := TIntegerProperty(FProperties.Objects[Pos]).Value
Is risky because you will crash if it is not a TIntegerProperty.
Use something like:
Pos := FProperties.IndexOf(APropertyName);
if (Pos >= 0) and (FProperties.Objects[Pos] is TIntegerProperty) then
Result := TIntegerProperty(FProperties.Objects[Pos]).Value
else
Result := 0;
Next the status, I don't think you need them al:
For the list
- Deleted a child: Deleted
- Added a child: Added
- Child had been changed: ChildsModified
You don't need unchanged because in that case, the set is empty. And you don't need Modified because in that case the set is not empty.
For the properties you can just add a Changed value.
You can add ChildsModified directly if a child is changed. Or you can use lazy evaluation and walk all children to check for Changed.
Ok you can do something like this:
type
TStatus = (stDeleted, stAdded , stModified, stChildsModified);
TObjectStatus = Set of TStatus;
TDataPacket = class;
TProperty = class
private
FName : string;
FParent : TDataPacket;
protected
procedure NotifyChange(const AStatus: TStatus);
public
constructor Create(const AParent: TDataPacket; const AName: string);
property Name: string read FName;
end;
TIntegerProperty = class(TProperty)
private
FValue: Integer;
procedure SetValue(const AValue:integer);
public
property Value: Integer read FValue write SetValue;
end;
TDataPacket = class
private
FProperties: TStringList;
FStatus : TObjectStatus;
protected
procedure NotifyChange(const AStatus: TStatus);
function GetProperty(const AName: string): TProperty;
public
function GetIntegerValue(const APropertyName: string): integer;
procedure SetIntegerValue(const APropertyName: string; AValue: integer);
end;
procedure TProperty.NotifyChange(const AStatus: TStatus);
begin
FParent.NotifyChange(AStatus);
end;
constructor TProperty.Create(const AParent: TDataPacket; const AName: string);
begin
Assert(AParent<>nil);
FName := AName;
FParent := AParent;
end;
procedure TIntegerProperty.SetValue(const AValue:integer);
begin
if AValue<>FValue then begin
FValue := AValue;
NotifyChange(stChildsModified);
end;
end;
procedure TDataPacket.NotifyChange(const AStatus: TStatus);
begin
if AProp=nil then begin
case AStatus of
TStatus = (stDeleted, stAdded , stModified, stChildsModified);
FStatus := FStatus + [AStatus];
end;
function TDataPacket.GetProperty(const AName: string): TProperty;
var
i : Integer;
begin
i := FProperties.IndexOf(AName);
if i>=0 then
Result := TProperty(FProperties.Objects[i])
else
Result := nil;
end;
function TDataPacket.GetIntegerValue(const APropertyName: string): integer;
var
prop : TProperty;
begin
prop := GetProperty(APropertyName);
if (prop<>nil) and (prop is TIntegerProperty) then
Result := TIntegerProperty(prop).Value
else
Result := 0;
end;
procedure TDataPacket.SetIntegerValue(const APropertyName: string; AValue: integer);
var
prop : TProperty;
intprop : TIntegerProperty;
begin
prop := GetProperty(APropertyName);
if (prop<>nil) and not (AProperty is TIntegerProperty) then begin
// PANIC!
end else begin
if prop=nil then begin
intprop := TIntegerProperty.Create(self, APropertyName);
intprop.Value := AValue;
FProperties.AddObject(APropertyName, intprop);
NotifyChange(stAdded);
end else begin
TIntegerProperty(prop).Value := AValue;
end;
end;
end;
And off course add support for deletion.
You can let the Property handle all changes (Add when constructed and Delete when freed).
If you're storing an object's properties, and one of those properties should be a status property, then all you ultimately need to do is the same as what you did for TIntegerProperty, but replacing Integer with TObjectStatus.
First, define another property class that holds your TObjectStatus value:
type
TObjectStatusProperty = class(TProperty)
private
FValue: TObjectStatus;
protected
procedure SetValue(const AValue: TObjectStatus);
public
property Value: TObjectStatus read FValue write SetValue;
end;
Next, add methods to your data packet to work with that type of property:
function TDataPacket.GetObjectStatusValue(
const APropertyName: string): TObjectStatus;
var
Pos: integer;
Prop: TProperty;
begin
Pos := FProperties.IndexOf(APropertyName);
if Pos >= 0 then begin
Prop := FProperties.Objects[Pos] as TProperty;
Assert(Prop.Name = APropertyName);
if Prop is TObjectStatusProperty then
Result := TObjectStatusProperty(Prop).Value
else
raise EWrongPropertyType.CreateFmt('Expected %s but got %s',
[TObjectStatusProperty.ClassName, Prop.ClassName]);
end else
Result := [];
end;
procedure TDataPacket.SetObjectStatusValue(
const APropertyName: string; AValue: TObjectStatus);
var
Pos: integer;
Prop: TProperty;
begin
Pos := FProperties.IndexOf(APropertyName);
if Pos >= 0 then begin
Prop := FProperties.Objects[Pos] as TProperty;
Assert(Prop.Name = APropertyName);
if Prop is TObjectStatusProperty then
TObjectStatusProperty(Prop).Value := AValue
else
raise EWrongPropertyType.CreateFmt('Expected %s but got %s',
[TObjectStatusProperty.ClassName, Prop.ClassName]);
end else begin
Prop := TObjectStatusProperty.Create(APropertyName);
TObjectStatusProperty(Prop).Value := AValue;
FProperties.AddObject(APropertyName, Prop);
end;
end;
This could be a great opportunity to use generics to reduce the number of TXProperty classes you need to write, if you had Delphi 2009 and if Delphi's generics supported sets.

Resources