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;
Related
I don't understand why this very simple code failed? I'm on Delphi Tokyo release 2.
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.Generics.Collections;
procedure Main;
var
aQueue: TQueue<TBytes>;
aBytes: TBytes;
begin
aQueue := TQueue<TBytes>.create;
aBytes := TEncoding.UTF8.GetBytes('abcd');
aQueue.Enqueue(aBytes);
aBytes := aQueue.Dequeue;
Writeln(Length(aBytes)); // outputs 4 as expected
aBytes := TEncoding.UTF8.GetBytes('abcd');
aQueue.Enqueue(aBytes);
aBytes := aQueue.Dequeue;
Writeln(Length(aBytes)); // outputs 0
end;
begin
Main;
Readln;
end.
Is this a bug?
NOTE: The code works correctly on XE4, but fails also on Berlin.
This is indeed a bug. The code works correctly in XE7, but not XE8. In XE8 the output is 0 for both attempts.
Certainly the XE8 generic collections were very buggy and subsequent releases fixed many of the defects. Clearly not all have been fixed.
In XE8 Embarcadero attempted to address the issue of generic bloat caused by weaknesses in their compile/link model. Unfortunately, instead of tackling the problem at the root, they chose instead to address the issue in the library code for generic collections. Doing so they completely broke many of the generic collection classes, proving that their unit testing was weak. And of course, by addressing the problem this way they failed to address the issue of generic bloat for classes other than those in the generic collections. All in all, a sorry story that is seemingly still not over.
loki has just submitted a bug report: RSP-20400.
Note that this bug report is incorrect because (at least according to Stefan Glienke) the bug has been fixed in Tokyo 10.2.3. So upgrading to 10.2.3 should be the simplest way to resolve the problem.
Perhaps this bug report is more appropriate: RSP-17728.
Writing a generic queue isn't even difficult. Here's one that is known to work:
type
TQueue<T> = class
private
FItems: TArray<T>;
FCount: Integer;
FFront: Integer;
private
function Extract(Index: Integer): T; inline;
function GetBack: Integer; inline;
property Back: Integer read GetBack;
property Front: Integer read FFront;
procedure Grow;
procedure RetreatFront; inline;
public
property Count: Integer read FCount;
procedure Clear;
procedure Enqueue(const Value: T);
function Dequeue: T;
function Peek: T;
public
type
TEnumerator = record
private
FCollection: TQueue<T>;
FCount: Integer;
FCapacity: Integer;
FIndex: Integer;
FStartIndex: Integer;
public
class function New(Collection: TQueue<T>): TEnumerator; static;
function GetCurrent: T;
property Current: T read GetCurrent;
function MoveNext: Boolean;
end;
public
function GetEnumerator: TEnumerator;
end;
function GrownCapacity(OldCapacity: Integer): Integer;
var
Delta: Integer;
begin
if OldCapacity>64 then begin
Delta := OldCapacity div 4
end else if OldCapacity>8 then begin
Delta := 16
end else begin
Delta := 4;
end;
Result := OldCapacity + Delta;
end;
{ TQueue<T> }
function TQueue<T>.Extract(Index: Integer): T;
begin
Result := FItems[Index];
if IsManagedType(T) then begin
Finalize(FItems[Index]);
end;
end;
function TQueue<T>.GetBack: Integer;
begin
Result := Front + Count - 1;
if Result>high(FItems) then begin
dec(Result, Length(FItems));
end;
end;
procedure TQueue<T>.Grow;
var
Index: Integer;
Value: T;
Capacity: Integer;
NewItems: TArray<T>;
begin
Capacity := Length(FItems);
if Count=Capacity then begin
SetLength(NewItems, GrownCapacity(Capacity));
Index := 0;
for Value in Self do begin
NewItems[Index] := Value;
inc(Index);
end;
FItems := NewItems;
FFront := 0;
end;
end;
procedure TQueue<T>.RetreatFront;
begin
inc(FFront);
if FFront=Length(FItems) then begin
FFront := 0;
end;
end;
procedure TQueue<T>.Clear;
begin
FItems := nil;
FCount := 0;
end;
procedure TQueue<T>.Enqueue(const Value: T);
begin
Grow;
inc(FCount);
FItems[Back] := Value;
end;
function TQueue<T>.Dequeue: T;
var
Index: Integer;
begin
Assert(Count>0);
Result := Extract(Front);
RetreatFront;
dec(FCount);
end;
function TQueue<T>.Peek: T;
begin
Assert(Count>0);
Result := FItems[Front];
end;
function TQueue<T>.GetEnumerator: TEnumerator;
begin
Result := TEnumerator.New(Self);
end;
{ TQueue<T>.TEnumerator }
class function TQueue<T>.TEnumerator.New(Collection: TQueue<T>): TEnumerator;
begin
Result.FCollection := Collection;
Result.FCount := Collection.Count;
Result.FCapacity := Length(Collection.FItems);
Result.FIndex := -1;
Result.FStartIndex := Collection.Front;
end;
function TQueue<T>.TEnumerator.GetCurrent: T;
var
ActualIndex: Integer;
begin
ActualIndex := (FStartIndex + FIndex) mod FCapacity;
Result := FCollection.FItems[ActualIndex];
end;
function TQueue<T>.TEnumerator.MoveNext: Boolean;
begin
inc(FIndex);
Result := FIndex<FCount;
end;
To add to David's answer, the bug is in the Enqueue method. The top branch should be handling all reference counted managed types.
if IsManagedType(T) then
if (SizeOf(T) = SizeOf(Pointer)) and (GetTypeKind(T) <> tkRecord) then
FQueueHelper.InternalEnqueueMRef(Value, GetTypeKind(T))
else
FQueueHelper.InternalEnqueueManaged(Value)
else
But here we see that dynamic arrays are conspicuously missing in InternalEnqueueMref, which falls through without doing anything:
procedure TQueueHelper.InternalEnqueueMRef(const Value; Kind: TTypeKind);
begin
case Kind of
TTypeKind.tkUString: InternalEnqueueString(Value);
TTypeKind.tkInterface: InternalEnqueueInterface(Value);
{$IF not Defined(NEXTGEN)}
TTypeKind.tkLString: InternalEnqueueAnsiString(Value);
TTypeKind.tkWString: InternalEnqueueWideString(Value);
{$ENDIF}
{$IF Defined(AUTOREFCOUNT)}
TTypeKind.tkClass: InternalEnqueueObject(Value);
{$ENDIF}
end;
end;
It's so egregious, in fact, that the compiler actually produces no code for Enqueue when compiled (other than preamble) since the futility of the exercise can be determined from the types at compile time.
Project1.dpr.15: aQueue.Enqueue(aBytes);
0043E19E 8B45F8 mov eax,[ebp-$08]
0043E1A1 8945F4 mov [ebp-$0c],eax
0043E1A4 8B45FC mov eax,[ebp-$04]
0043E1A7 83C008 add eax,$08
0043E1AA 8945F0 mov [ebp-$10],eax
Project1.dpr.16: aBytes := aQueue.Dequeue;
0043E1AD 8D45EC lea eax,[ebp-$14]
This bug, therefore, would be expected to affect TQueue<T> for T being any type of dynamic array.
I have 2 years old project which I am rebooting back to life. I have debug window which shows different information, and one part of them is that it serializes various google protobuf objects and shows them.
Here is how typical protobuf class looks like:
// Generated by the protocol buffer compiler. DO NOT EDIT!
// Source: message.proto
unit Poker.Protobufs.Objects.PingParams;
interface
uses
System.SysUtils,
{$IFNDEF FPC} System.Generics.Collections {$ELSE} Contnrs {$ENDIF},
pbOutput, Poker.Protobufs.Objects.Base, Poker.Protobufs.Reader, Poker.Types;
type
TPB_PingParams = class(TProtobufBaseObject)
private
const
kUptimeFieldNumber = 1;
var
FUptime: UInt32;
FHasBits: UINT32;
procedure set_has_Uptime;
procedure clear_has_Uptime;
procedure SetUptime(const AValue: UInt32);
public
constructor Create(const AFrom: TPB_PingParams; const ALightweight: Boolean = FALSE); overload;
destructor Destroy; override;
procedure LoadFromProtobufReader(const AProtobufReader: TProtobufReader; const ASize: Integer); override;
procedure MergeFrom(const AFrom: TPB_PingParams);
procedure Clear;
function IsInitialized: Boolean; override;
// required uint32 Uptime = 1;
function has_Uptime: Boolean;
procedure clear_Uptime;
property Uptime: UInt32 read FUptime write SetUptime;
end;
TPB_PingParamsList = class(TObjectList<TPB_PingParams>)
procedure Assign(const APB_PingParamsList: TList<TPB_PingParams>);
end;
implementation
uses
pbPublic;
constructor TPB_PingParams.Create(const AFrom: TPB_PingParams; const ALightweight: Boolean = FALSE);
begin
inherited Create(ALightweight);
MergeFrom(AFrom);
end;
destructor TPB_PingParams.Destroy;
begin
inherited;
end;
procedure TPB_PingParams.LoadFromProtobufReader(const AProtobufReader: TProtobufReader; const ASize: Integer);
var
tag, field_number, wire_type, endpos: Integer;
begin
endpos := AProtobufReader.getPos + ASize;
while (AProtobufReader.getPos < endpos) and
(AProtobufReader.GetNext(tag, wire_type, field_number)) do
case field_number of
kUptimeFieldNumber: begin
Assert(wire_type = WIRETYPE_VARINT);
FUptime := AProtobufReader.readUInt32;
set_has_Uptime;
end;
else
AProtobufReader.skipField(tag);
end;
end;
procedure TPB_PingParams.MergeFrom(const AFrom: TPB_PingParams);
begin
if AFrom.has_Uptime then
SetUptime(AFrom.Uptime);
end;
function TPB_PingParams.IsInitialized: Boolean;
begin
if (FHasBits and $1) <> $1 then
Exit(FALSE);
Exit(TRUE);
end;
procedure TPB_PingParams.clear_Uptime;
begin
FUptime := 0;
clear_has_Uptime;
end;
function TPB_PingParams.has_Uptime: Boolean;
begin
result := (FHasBits and 1) > 0;
end;
procedure TPB_PingParams.set_has_Uptime;
begin
FHasBits := FHasBits or 1;
end;
procedure TPB_PingParams.clear_has_Uptime;
begin
FHasBits := FHasBits and not 1;
end;
procedure TPB_PingParams.SetUptime(const AValue: UInt32);
begin
if not Lightweight then
Assert(not has_Uptime);
FUptime := AValue;
if not Lightweight then
ProtobufOutput.writeUInt32(kUptimeFieldNumber, AValue);
set_has_Uptime;
end;
procedure TPB_PingParams.Clear;
begin
if FHasBits = 0 then
Exit;
clear_Uptime;
end;
procedure TPB_PingParamsList.Assign(const APB_PingParamsList: TList<TPB_PingParams>);
var
pbobj: TPB_PingParams;
begin
Clear;
for pbobj in APB_PingParamsList do
Add(TPB_PingParams.Create(pbobj, TRUE));
end;
end.
And my serialization function:
function SerializeObject(const AObject: TObject): String;
var
t: TRttiType;
p: TRttiProperty;
properties: TArray<TRttiProperty>;
method: TRttiMethod;
begin
result := '';
if not Assigned(AObject) then
Exit;
t := TRttiContext.Create.GetType(AObject.ClassType);
properties := t.GetProperties;
for p in properties do
begin
method := t.GetMethod(Format('has_%s', [p.Name]));
if (Assigned(method)) and
(method.Invoke(AObject, []).AsBoolean) then
result := result + Format('%s: %s; ', [p.Name, ValueToStr(p, p.GetValue(AObject))]);
end;
end;
It is specifically designed to serialize fields that begin with has_ in protobuf objects. Now, I didn't change anything in the code over last 2 years, and this was working before. But now it doesn't. Line properties = t.GetProperties returns empty array for my protobuf classes.
My guess is that I had some globally defined compiler directive which allowed me to serialize public methods in the class. But I cannot figure out which one.
If I put {$M+} in front of my protobuf classes, and move methods to published, it works (kinda). But this worked before just like it is shown in the sources, without any {$M+} directives or similar. So I'm curious what I miss.
Compiler is same as before, XE2.
I need to save a TObjectList<TStrings> (or <TStringList>) in a TStream and then retrive it.
To be clear, how to apply SaveToStream and LoadFromStream to a TObjectList?
Try something like this:
procedure SaveListOfStringsToStream(List: TObjectList<TStrings>; Stream: TStream);
var
Count, I: Integer;
MStrm: TMemoryStream;
Size: Int64;
begin
Count := List.Count;
Stream.WriteBuffer(Count, SizeOf(Count));
if Count = 0 then Exit;
MStrm := TMemoryStream.Create;
try
for I := 0 to Count-1 do
begin
List[I].SaveToStream(MStrm);
Size := MStrm.Size;
Stream.WriteBuffer(Size, SizeOf(Size));
Stream.CopyFrom(MStrm, 0);
MStrm.Clear;
end;
finally
MStrm.Free;
end;
end;
procedure LoadListOfStringsFromStream(List: TObjectList<TStrings>; Stream: TStream);
var
Count, I: Integer;
MStrm: TMemoryStream;
Size: Int64;
SList: TStringList;
begin
Stream.ReadBuffer(Count, SizeOf(Count));
if Count <= 0 then Exit;
MStrm := TMemoryStream.Create;
try
for I := 0 to Count-1 do
begin
Stream.ReadBuffer(Size, SizeOf(Size));
SList := TStringList.Create;
try
if Size > 0 then
begin
MStrm.CopyFrom(Stream, Size);
MStrm.Position := 0;
SList.LoadFromStream(MStrm);
MStrm.Clear;
end;
List.Add(SList);
except
SList.Free;
raise;
end;
end;
finally
MStrm.Free;
end;
end;
Alternatively:
procedure SaveListOfStringsToStream(List: TObjectList<TStrings>; Stream: TStream);
var
LCount, SCount, Len, I, J: Integer;
SList: TStrings;
S: UTF8String;
begin
LCount := List.Count;
Stream.WriteBuffer(LCount, SizeOf(LCount));
if LCount = 0 then Exit;
for I := 0 to LCount-1 do
begin
SList := List[I];
SCount := SList.Count;
Stream.WriteBuffer(SCount, SizeOf(SCount));
for J := 0 to SCount-1 do
begin
S := UTF8String(SList[J]);
// or, if using Delphi 2007 or earlier:
// S := UTF8Encode(SList[J]);
Len := Length(S);
Stream.WriteBuffer(Len, SizeOf(Len));
Stream.WriteBuffer(PAnsiChar(S)^, Len * SizeOf(AnsiChar));
end;
end;
end;
procedure LoadListOfStringsFromStream(List: TObjectList<TStrings>; Stream: TStream);
var
LCount, SCount, Len, I, J: Integer;
SList: TStrings;
S: UTF8String;
begin
Stream.ReadBuffer(LCount, SizeOf(LCount));
for I := 0 to LCount-1 do
begin
Stream.ReadBuffer(SCount, SizeOf(SCount));
SList := TStringList.Create;
try
for J := 0 to SCount-1 do
begin
Stream.ReadBuffer(Len, SizeOf(Len));
SetLength(S, Len);
Stream.ReadBuffer(PAnsiChar(S)^, Len * SizeOf(AnsiChar));
SList.Add(String(S));
// or, if using Delphi 2007 or earlier:
// SList.Add(UTF8Decode(S));
end;
List.Add(SList);
except
SList.Free;
raise;
end;
end;
end;
What's in your list?
It depends on what type of objects you have in your objectlist.
You loop over the list and save each item in turn.
However the objects inside your list need to have a SaveToStream method.
For reasons unknown SaveToStream is not a method of TPersistent, instead it is implemented independently in different classes.
Test for stream support
If the VCL were built with interfaces in mind, in newer versions has been solved with the IStreamPersist interface.
If all your stuff in the list descents from a base class that has streaming built-in (e.g. TComponent) then there is no problem and you can just use TComponent.SaveToStream.
type
TStreamableClass = TStrings; //just to show that this does not depend on TStrings.
procedure SaveToStream(List: TObjectList; Stream: TStream);
var
i: integer;
begin
for i:= 0 to List.Count -1 do begin
if List[i] is TStreamableClass then begin
TStreamableClass(List[i]).SaveToStream(Stream);
end;
end; {for i}
end;
Add stream support
If you have items in your list that do not derive from a common streamable ancestor then you'll have to have multiple if list[i] is TX tests in your loop.
If the object does not have a SaveToStream method, but you have enough knowledge of the class to implement it yourself, then you have twothree options.
A: implement a class helper that adds SaveToStream to that class or B: add a descendent class that implements that option.
If these are your own objects, then see option C: below.
type
TObjectXStreamable = class(TObjectX)
public
procedure SaveToStream(Stream: TStream); virtual;
procedure LoadFromStream(Stream: TStream); virtual;
end;
procedure SaveToStream(List: TObjectList; Stream: TStream);
...
if List[i] is TObjectX then TObjectXStreamable(List[i]).SaveToStream(Stream);
...
Note that this approach fails if TObjectX has subclasses with additional data. The added streaming will not know about this extra data.
Option C: implement System.Classes.IStreamPersist
type
IStreamPersist = interface
['<GUID>']
procedure SaveToStream(Stream: TStream);
procedure LoadFromStream(Stream: TStream);
end;
//enhance your streamable objects like so:
TInterfaceBaseObject = TInterfacedObject //or TSingletonImplementation
TMyObject = class(TInterfaceBaseObject, IStreamPersist)
procedure SaveToStream(Stream: TStream); virtual;
procedure LoadFromStream(Stream: TStream); virtual;
See: Bypassing (disabling) Delphi's reference counting for interfaces
You test the IStreamPersist support using the supports call.
if Supports(List[i], IStreamPersist) then (List[i] as IStreamPersist).SaveToStream(Stream);
If you have a newer version of Delphi consider using a generic TObjectList, that way you can limit your list to: MyList: TObjectList<TComponent>;
Now you can just call MyList[i].SaveToStream, because Delphi knows that the list only contains (descendents of) TComponent.
You will need to create your own routine to do this: One for saving, the other for loading.
For saving, loop through the list, convert each pointer of the list into is hexadecimal (decimal, octal) then add a separator character like ','; When done write the string contain to the stream.
For loading, loop through the list, search for the first separator character, extract the value, convert it back as a pointer then add it to the list.
Procedure ObjListToStream(objList: TObjectList; aStream: TStream);
var
str: String;
iCnt: Integer;
Begin
if not assigned(aStream) then exit; {or raise exception}
for iCnt := 0 to objList.Count - 1 do
begin
str := str + IntToStr(Integer(objList.Items[iCnt])) + ',';
end;
aStream.Write(str[1], Length(str));
End;
Procedure StreamToObjList(objList: TObjectList; aList: String);
var
str: String;
iCnt: Integer;
iStart, iStop: Integer;
Begin
try
if not assigned(aStream) then exit; {or raise exception}
iStart := 0;
Repeat
iStop := Pos(',', aList, iStart);
if iStop > 0 then
begin
objList.Add(StrToInt(Copy(sList, iStart, iStop - iStart)));
iStart := iStop + 1;
end;
Until iStop = 0;
except
{something want wrong}
end;
End;
I haven't test it and wrote it from memory. But it should point you in the right direction.
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
I am trying to compile my program but I am getting this error:
Undeclared indentifier 'StreamLn'
i even tried to download PSock.dcu and put it into the library but it doesnt compile, it looks like its compactible with delphi 5,
unit ResourceInfo;
interface
uses
Classes, SysUtils, Windows;
type
TResourceInfo = class;
TDfmMode = ( dfmData, dfmResource, dfmASCII, dfmBinary);
TDfm = class
private
FOwner: TResourceInfo;
FName: string;
FData: TStream;
procedure SetName(const Value: string);
procedure SetOwner(const Value: TResourceInfo);
public
constructor Create(AOwner: TResourceInfo);
destructor Destroy; override;
function SaveToFile(FileName: TFileName; Mode: TDfmMode): Boolean;
property Data: TStream read FData;
property Name: string read FName write SetName;
property Owner: TResourceInfo read FOwner write FOwner;
end; {TDfm}
TResourceInfo = class(TComponent)
private
FActive: Boolean;
FDfms: TList;
FExeFileName: TFileName;
FModule: THandle;
FOnActivate: TNotifyEvent;
FOnDeactivate: TNotifyEvent;
procedure SetExeFileName(const Value: TFileName);
procedure SetActive(const Value: Boolean);
function GetDfms(Index: Cardinal): TDfm;
function GetDfmCount: Cardinal;
protected
procedure Clear;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function AddDfm(const Name: string; AData: TMemoryStream): Integer;
procedure DeleteDfm(const Name: string);
property DfmCount: Cardinal read GetDfmCount;
property Dfms[Index: Cardinal]: TDfm read GetDfms;
procedure EnumDfmNames;
property Module: THandle read FModule;
published
property Active: Boolean read FActive write SetActive;
property ExeFileName: TFileName read FExeFileName write SetExeFileName;
property OnActivate: TNotifyEvent read FOnActivate write FOnActivate;
property OnDeactivate: TNotifyEvent read FOnDeactivate write FOnDeactivate;
end; {TResourceInfo}
procedure Register;
implementation
uses
Winsock;
resourcestring
rsErrorLoadingExeFile = 'An error ocurred loading file %s, it may not be an executable module';
procedure Register;
begin
RegisterComponents('+HCU', [TResourceInfo]);
end; {Register}
{ TResourceInfo }
function TResourceInfo.AddDfm(const Name: string; AData: TMemoryStream): Integer;
var
FDfm: TDfm;
begin
FDfm := TDfm.Create(Self);
FDfm.Name := Name;
FDfm.Data.Size := AData.Size;
FDfm.Data.Seek(0, 0);
AData.Seek(0, 0);
FDfm.Data.CopyFrom(AData, AData.Size);
Result := FDfms.Add(FDfm);
end; {TResourceInfo.AddDfm}
constructor TResourceInfo.Create(AOwner: TComponent);
begin
inherited;
FActive := False;
FDfms := TList.Create;
FModule := 0;
end; {TResourceInfo.Create}
destructor TResourceInfo.Destroy;
begin
Clear;
FDfms.Free;
inherited;
end; {TResourceInfo.Destroy}
function CB_EnumDfmNameProc(hModule: THandle; lpszType, lpszName: PChar; lParam: Integer): Boolean; stdcall;
var
ms: TMemoryStream;
rs: TResourceStream;
Buffer: array of Byte;
begin
with TResourceInfo(lParam) do
begin
rs := TResourceStream.Create(TResourceInfo(lParam).Module, lpszname, lpszType);
try
ms := TMemoryStream.Create;
try
try
SetLength(Buffer, 4);
rs.Read(Buffer[0], SizeOf(Buffer));
if string(Buffer) = 'TPF0' then
begin
rs.Seek(0, 0);
ObjectBinaryToText(rs, ms);
ms.Seek(0, 0);
AddDfm(StrPas(lpszName), ms);
end;
except
raise;
end;
finally
ms.Free;
end;
finally
rs.free;
end;
end;
Result := True;
end; {CB_EnumDfmNameProc}
procedure TResourceInfo.EnumDfmNames;
begin
if FModule > 0 then
EnumResourceNames(FModule, RT_RCDATA, #CB_EnumDfmNameProc, Integer(Self));
end; {TResourceInfo.EnumDfmNames}
procedure TResourceInfo.DeleteDfm(const Name: string);
var
i: Cardinal;
begin
if FDfms.Count > 0 then
for i := Pred(FDfms.Count) downto 0 do
if UpperCase(TDfm(FDfms[i]).Name) = UpperCase(Name) then
begin
FDfms.Delete(i);
Break;
end;
end; {TResourceInfo.DeleteDfm}
procedure TResourceInfo.SetActive(const Value: Boolean);
begin
if FActive <> Value then
begin
if Value then
begin
if FModule > 0 then
FreeLibrary(FModule);
(* LOAD_LIBRARY_AS_DATAFILE
If this value is given, the function does a simple mapping of the file into the
address space. Nothing is done relative to executing or preparing to execute the
code in the mapped file. The function loads the module as if it were a data file.
You can use the module handle that the function returns in this case with the Win32
functions that operate on resources. Use this flag when you want to load a DLL in
order to extract messages or resources from it, and have no intention of executing
its code.If this value is not given, the function maps the file into the address
space in the manner that is normal for an executable module. The behavior of the
function is then identical to that of LoadLibrary in this regard. *)
FModule := LoadLibraryEx(PChar(FExeFileName), 0, LOAD_LIBRARY_AS_DATAFILE);
if not (FModule >= 32) then
raise Exception.CreateFmt(rsErrorLoadingExeFile, [FExeFileName]);
if Assigned(FOnActivate) then
FOnActivate(Self);
end
else
begin
Clear;
if FModule > 0 then
begin
FreeLibrary(FModule);
FModule := 0;
end;
if Assigned(FOnDeactivate) then
FOnDeactivate(Self);
end;
FActive := Value;
end;
end; {TResourceInfo.SetActive}
procedure TResourceInfo.SetExeFileName(const Value: TFileName);
begin
if FExeFileName <> Value then
FExeFileName := Value;
end; {TResourceInfo.SetExeFileName}
function TResourceInfo.GetDfms(Index: Cardinal): TDfm;
begin
Result := TDfm(FDfms[Index]);
end; {TResourceInfo.GetDfms}
function TResourceInfo.GetDfmCount: Cardinal;
begin
Result := FDfms.Count;
end; {TResourceInfo.GetDfmCount}
procedure TResourceInfo.Clear;
begin
if FDfms.Count > 0 then
while FDfms.Count > 0 do
FDfms.Delete(0);
end; {TResourceInfo.Clear}
{ TDfm }
constructor TDfm.Create(AOwner: TResourceInfo);
begin
inherited Create;
FData := TMemoryStream.Create;
FName := '';
SetOwner(AOwner);
end; {TDfm.Create}
destructor TDfm.Destroy;
begin
FData.Free;
inherited;
end; {TDfm.Destroy}
function TDfm.SaveToFile(FileName: TFileName; Mode: TDfmMode): Boolean;
function EndOfStream(Stream: TStream): Boolean;
begin
with Stream do
Result := Position = Size;
end; {EndOfStream}
var
fs: TFileStream;
ms: TMemoryStream;
s: string;
i, j: Byte;
begin
fs := TFileStream.Create(FileName, fmCreate or fmShareExclusive);
try
FData.Seek(0, 0);
case Mode of
dfmASCII:
begin
ms := TMemoryStream.Create;
try
s := FName + ' RCDATA' + #13#10 + '{';
StreamLN(fs, s);
ObjectTextToBinary(FData, ms);
ms.Seek(0, 0);
while not EndOfStream(ms) do
begin
s := '''';
for i := 0 to 15 do
begin
if ms.Read(j, SizeOf(j)) = 0 then
Break;
s := Concat(s, Format('%2.2x', [j]));
if (i = 15) or EndOfStream(ms) then
s := Concat(s, '''')
else
s := Concat(s, ' ');
end;
if EndOfStream(ms) then
s := Concat(s, #13#10 + '}');
StreamLN(fs, s);
end;
finally
ms.Free;
end;
end;
dfmBinary:
ObjectTextToBinary(FData, fs);
end;
finally
fs.Free;
end;
end; {TDfm.SaveToFile}
procedure TDfm.SetName(const Value: string);
begin
if FName <> Value then
FName := Value;
end; {TDfm.SetName}
procedure TDfm.SetOwner(const Value: TResourceInfo);
begin
FOwner := Value;
end; {TDfm.SetOwner}
end.
How can I declare it successfully?
Appears to me that WinSock unit does not have an StreamLn function (as PowerSock's PSock.pas unit uses Winsock as imported unit).
The StreamLn function in PSock.pas just adds an CRLF sequence to the string passed as parameter before calling the TStream.WriteBuffer method of the passed TStream parameter.
Here's the google cache snapshot from the Powersock's source code of PSock.pas
You need to either implement this function, or add a unit where this function is declared to your uses section.
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.