{ $Id$} { ***************************************************************************** * wslclclasses.pp * * --------------- * * * ***************************************************************************** ***************************************************************************** This file is part of the Lazarus Component Library (LCL) See the file COPYING.modifiedLGPL.txt, included in this distribution, for details about the license. ***************************************************************************** } unit WSLCLClasses; {$mode objfpc}{$H+} {$I lcl_defines.inc} {off$DEFINE VerboseWSRegistration} {off$DEFINE VerboseWSRegistration_methods} {off$DEFINE VerboseWSRegistration_treedump} {.$DEFINE VerboseWSBrunoK } interface //////////////////////////////////////////////////// // I M P O R T A N T //////////////////////////////////////////////////// // 1) Only class methods allowed // 2) Class methods have to be published and virtual // 3) To get as little as possible circles, the uses // clause should contain only those LCL units // needed for registration. WSxxx units are OK // 4) To improve speed, register only classes in the // initialization section which actually // implement something // 5) To enable your XXX widgetset units, look at // the uses clause of the XXXintf.pp //////////////////////////////////////////////////// uses Classes, SysUtils, LCLProc, LazLoggerBase; type { TWSPrivate } { Internal WidgetSet specific object tree } TWSPrivate = class(TObject) end; TWSPrivateClass = class of TWSPrivate; { For non-TComponent WS objects } TWSObject = class(TObject) public end; TWSObjectClass = class of TWSObject; { TWSLCLComponent } {$M+} TWSLCLComponent = class(TObject) public class function WSPrivate: TWSPrivateClass; inline; end; {$M-} TWSLCLComponentClass = class of TWSLCLComponent; { TWSLCLHandleComponent } TWSLCLReferenceComponent = class(TWSLCLComponent) published class procedure DestroyReference(AComponent: TComponent); virtual; end; TWSLCLReferenceComponentClass = class of TWSLCLReferenceComponent; function FindWSComponentClass(const AComponent: TComponentClass): TWSLCLComponentClass; function IsWSComponentInheritsFrom(const AComponent: TComponentClass; InheritFromClass: TWSLCLComponentClass): Boolean; procedure RegisterWSComponent(AComponent: TComponentClass; AWSComponent: TWSLCLComponentClass; AWSPrivate: TWSPrivateClass = nil); function RegisterNewWSComp(AComponent: TComponentClass): TWSLCLComponentClass; //inline; // Only for non-TComponent based objects function GetWSLazAccessibleObject: TWSObjectClass; procedure RegisterWSLazAccessibleObject(const AWSObject: TWSObjectClass); function GetWSLazDeviceAPIs: TWSObjectClass; procedure RegisterWSLazDeviceAPIs(const AWSObject: TWSObjectClass); // ~bk Search for already registered classes function FindWSRegistered(const AComponent: TComponentClass): TWSLCLComponentClass; //inline; { Debug : Dump the WSClassesList nodes } {$IFDEF VerboseWSBrunoK} const cWSLCLDirectHit : integer = 0; cWSLCLParentHit : integer = 0; cWSLCLRegister : integer = 0; procedure DumpWSClassesList; {$ENDIF} implementation uses LCLClasses; procedure DoInitialization; forward; //////////////////////////////////////////////////// // Registration code //////////////////////////////////////////////////// type PClassNode = ^TClassNode; TClassNode = record LCLClass: TComponentClass; { Class of the created instances } WSClass: TWSLCLComponentClass; { WidgetSet specific implementation class } VClass: Pointer; { Adjusted vmt table to handle WS virtual methods } VClassName: ShortString; { Class name attibuted when node was create } VClassNew: Boolean; { True Indicates that VClass=Parent.VClass. When True VClass is not runtime created } Parent: PClassNode; Child: PClassNode; Sibling: PClassNode; end; const // vmtAutoTable is something Delphi 2 and not used, we 'borrow' the vmt entry vmtWSPrivate = vmtAutoTable; type { TWSClassesList } // Holds list of already registered TWidgetSetClass'es so TLCLComponent.NewInstance // can find faster the WidgetSetClass of the newinstance. TWSClassesList = class(TFPList) private FLastFoundIdx: integer; FLastFoundClass: TClass; function FindWSClass(const AComponent: TComponentClass): TWSLCLComponentClass; function Get(Index: integer): PClassNode; inline; procedure Insert(aIndex: Integer; aItem: Pointer); function Search(const aItem: TClass; Out Index: integer): boolean; procedure UpdatLastFound(aClass: TClass; aIndex: integer); property Items[Index: integer]: PClassNode read Get; { write Put; default; } {$IFDEF VerboseWSBrunoK} {$ENDIF} {$IFDEF VerboseWSBrunoK} procedure DumpNode(aN : integer; aPClassNode : PClassNode); procedure DumpNodes; {$ENDIF} public constructor Create; end; var WSClassesList: TWSClassesList = nil; WSLazAccessibleObjectClass: TWSObjectClass; WSLazDeviceAPIsClass: TWSObjectClass; function FindNodeParent(AComponent: TClass): PClassNode; var idx: integer; begin while AComponent <> nil do begin if WSClassesList.Search(AComponent, idx) then Exit(PClassNode(WSClassesList[idx])); AComponent := AComponent.ClassParent; end; Result := nil; end; function FindClassNode(const AComponent: TComponentClass): PClassNode; var idx: integer; begin if WSClassesList.Search(AComponent, idx) then Result := WSClassesList[idx] else Result := FindNodeParent(AComponent.ClassParent); end; function FindWSComponentClass(const AComponent: TComponentClass): TWSLCLComponentClass; var Node: PClassNode; begin Node := FindClassNode(AComponent); if Assigned(Node) then Result := TWSLCLComponentClass(Node^.VClass) else Result := nil; end; function IsWSComponentInheritsFrom(const AComponent: TComponentClass; InheritFromClass: TWSLCLComponentClass): Boolean; var Node: PClassNode; begin Node := FindClassNode(AComponent); if Assigned(Node) then Result := TWSLCLComponentClass(Node^.WSClass).InheritsFrom(InheritFromClass) else Result := false; end; type TMethodNameTableEntry = {$if (FPC_FULLVERSION<30301) or NOT defined(FPC_REQUIRES_PROPER_ALIGNMENT)} packed {$endif} record Name: PShortstring; Addr: Pointer; end; TMethodNameTable = {$if (FPC_FULLVERSION<30301) or NOT defined(FPC_REQUIRES_PROPER_ALIGNMENT)} packed {$endif} record Count: DWord; Entries: packed array[0..9999999] of TMethodNameTableEntry; end; PMethodNameTable = ^TMethodNameTable; TPointerArray = packed array[0..9999999] of Pointer; PPointerArray = ^TPointerArray; { function GetClassNameP(aClassName:string) : Pointer; var lLen: integer; lShortStr : shortstring; begin lShortStr := aClassName + #0; lLen := Length(lShortStr); SetLength(lShortStr,lLen-1); Result := GetMem(lLen+1); move(lShortStr, Result^, lLen + 2); end; } function FindParentWSClassNode(const ANode: PClassNode): PClassNode; begin Result := ANode^.Parent; while Result <> nil do begin if Result^.WSClass <> nil then Exit; Result := Result^.Parent; end; Result := nil; end; function FindCommonAncestor(const AClass1, AClass2: TClass): TClass; begin Result := AClass1; if AClass2.InheritsFrom(Result) then Exit; Result := AClass2; while Result <> nil do begin if AClass1.InheritsFrom(Result) then Exit; Result := Result.ClassParent; end; Result := nil; end; procedure CreateVClass(const ANode: PClassNode; const AWSPrivate: TWSPrivateClass = nil; AOldPrivate: TClass = nil); var ParentWSNode: PClassNode; CommonClass: TClass; Vvmt, Cvmt, Pvmt: PPointerArray; Cmnt: PMethodNameTable; SearchAddr: Pointer; n, idx: integer; WSPrivate, OrgPrivate: TClass; Processed: array of boolean; VvmtCount, VvmtSize: integer; {$IFDEF VerboseWSRegistration} Indent: string; {$ENDIF} begin if AWSPrivate = nil then WSPrivate := TWSPrivate else WSPrivate := AWSPrivate; // Determine VMT count and size => https://wiki.freepascal.org/Compiler-generated_data_and_data_structures VvmtCount := 0; Vvmt := Pointer(ANode^.WSClass) + vmtMethodStart; // AWSComponent is equal to ANode^.WSClass; while (Vvmt^[VvmtCount] <> nil) do Inc(VvmtCount); { ~bk 1 more for nil at end } VvmtSize := vmtMethodStart + VvmtCount * SizeOf(Pointer) + SizeOf(Pointer); if ANode^.VClass = nil then begin ANode^.VClass := GetMem(VvmtSize); end else begin // keep original WSPrivate (only when different than default class) OrgPrivate := PClass(ANode^.VClass + vmtWSPrivate)^; if (OrgPrivate <> nil) and (OrgPrivate <> AOldPrivate) and OrgPrivate.InheritsFrom(WSPrivate) then begin {$IFDEF VerboseWSRegistration} DebugLn('Keep org private: ', WSPrivate.ClassName, ' -> ', OrgPrivate.ClassName); {$ENDIF} WSPrivate := OrgPrivate; end; end; // Initially copy the WSClass Move(Pointer(ANode^.WSClass)^, ANode^.VClass^, VvmtSize); // Set WSPrivate class ParentWSNode := FindParentWSClassNode(ANode); if ParentWSNode = nil then begin // nothing to do PClass(ANode^.VClass + vmtWSPrivate)^ := WSPrivate; {$IFDEF VerboseWSRegistration} DebugLn('Virtual parent: nil, WSPrivate: ', PClass(ANode^.VClass + vmtWSPrivate)^.ClassName); {$ENDIF} Exit; end; if WSPrivate = TWSPrivate then begin if ParentWSNode^.VClass = nil then begin LazLoggerBase.DebugLN('[WARNING] Missing VClass for: ', ParentWSNode^.WSClass.ClassName); PClass(ANode^.VClass + vmtWSPrivate)^ := TWSPrivate; end else PClass(ANode^.VClass + vmtWSPrivate)^ := PClass(ParentWSNode^.VClass + vmtWSPrivate)^; end else PClass(ANode^.VClass + vmtWSPrivate)^ := WSPrivate; {$IFDEF VerboseWSRegistration} DebugLn('Virtual parent: ', ParentWSNode^.WSClass.ClassName, ', WSPrivate: ', PClass(ANode^.VClass + vmtWSPrivate)^.ClassName); {$ENDIF} // Try to find the common ancestor CommonClass := FindCommonAncestor(ANode^.WSClass, ParentWSNode^.WSClass); {$IFDEF VerboseWSRegistration} DebugLn('Common: ', CommonClass.ClassName); Indent := ''; {$ENDIF} Vvmt := ANode^.VClass + vmtMethodStart; Pvmt := ParentWSNode^.VClass + vmtMethodStart; SetLength(Processed, VvmtCount); while CommonClass <> nil do begin Cmnt := PPointer(Pointer(CommonClass) + vmtMethodTable)^; if Cmnt <> nil then begin {$IFDEF VerboseWSRegistration_methods} DebugLn(Indent, '*', CommonClass.ClassName, ' method count: ', IntToStr(Cmnt^.Count)); Indent := Indent + ' '; {$ENDIF} Cvmt := Pointer(CommonClass) + vmtMethodStart; Assert(Cmnt^.Count < VvmtCount, 'MethodTable count is larger than determined VvmtCount'); // Loop through the VMT to see what is overridden for n := 0 to Cmnt^.Count - 1 do begin SearchAddr := Cmnt^.Entries[n].Addr; {$IFDEF VerboseWSRegistration_methods} DebugLn('%sSearch: %s (%p)', [Indent, Cmnt^.Entries[n].Name^, SearchAddr]); {$ENDIF} for idx := 0 to VvmtCount - 1 do begin if Cvmt^[idx] = SearchAddr then begin {$IFDEF VerboseWSRegistration_methods} DebugLn('%sFound at index: %d (v=%p p=%p)', [Indent, idx, Vvmt^[idx], Pvmt^[idx]]); {$ENDIF} if Processed[idx] then begin {$IFDEF VerboseWSRegistration_methods} DebugLn(Indent, 'Processed -> skipping'); {$ENDIF} Break; end; Processed[idx] := True; if (Vvmt^[idx] = SearchAddr) //original and (Pvmt^[idx] <> SearchAddr) //overridden by parent then begin {$IFDEF VerboseWSRegistration_methods} DebugLn('%sUpdating %p -> %p', [Indent, Vvmt^[idx], Pvmt^[idx]]); {$ENDIF} Vvmt^[idx] := Pvmt^[idx]; end; Break; end; if idx = VvmtCount - 1 then begin LazLoggerBase.DebugLn('[WARNING] VMT entry "', Cmnt^.Entries[n].Name^, '" not found in "', CommonClass.ClassName, '"'); Break; end; end; end; end; CommonClass := Commonclass.ClassParent; end; // Adjust classname ANode^.VClassName := '(V)' + ANode^.WSClass.ClassName; PPointer(ANode^.VClass + vmtClassName)^ := @ANode^.VClassName; // Adjust classparent PPointer(ANode^.VClass + vmtParent)^ := @ParentWSNode^.WSClass; // Delete methodtable entry PPointer(ANode^.VClass + vmtMethodTable)^ := nil; end; { Get PClass node is recursive, we want to detect if a new node may be an unregistered intermediate in the ancestor class tree } function GetPClassNode(AClass: TClass; AWSComponent: TWSLCLComponentClass; aParentGet: boolean; aLeaf: boolean): PClassNode; var idx: Integer; lParentNode : PClassNode; lClassNode : TClassNode; { A temp local node to fake normal processing of a node that won't be stored aParentGet = 0 and TWSLCLComponentClass = nil } lInsertNode : boolean; { Indicator that New(Result) has been requested } begin if (AClass = nil) or not (AClass.InheritsFrom(TLCLComponent)) then Exit(nil); if not WSClassesList.Search(AClass, idx) then begin lInsertNode := aParentGet or Assigned(AWSComponent); if lInsertNode then New(Result) else Result := @lClassNode; Result^.LCLClass := TComponentClass(AClass); Result^.WSClass := nil; Result^.VClass := nil; Result^.VClassName := ''; Result^.VClassNew := aParentGet; Result^.Child := nil; lParentNode := GetPClassNode(AClass.ClassParent, AWSComponent, True, False); Result^.Parent := lParentNode; { Unregistered Intermediate nodes are patched with the parent information } if aParentGet then begin Result^.WSClass := lParentNode^.WSClass; Result^.VClass := lParentNode^.VClass; PPointer(Result^.VClass + vmtWSPrivate)^ := PPointer(lParentNode^.VClass + vmtWSPrivate)^; // Build a VClassName if aLeaf then { Node that has an empty WSRegisterClass procedure } Result^.VClassName := '(L)' + Result^.WSClass.ClassName else { Internal node needed for tree consistency } Result^.VClassName := '(I)' + Result^.WSClass.ClassName end; if lParentNode = nil then begin Result^.Sibling := nil; if aLeaf then Result^.VClassName := '(ROOT)' + AClass.ClassName; end else if lInsertNode then begin Result^.Sibling := lParentNode^.Child; lParentNode^.Child := Result; end else Result^.Sibling := nil; if lInsertNode then begin WSClassesList.Search(aClass, idx); WSClassesList.Insert(idx, Result); end else Result := nil; end else Result := WSClassesList[idx]; end; // Create VClass at runtime procedure RegisterWSComponent(AComponent: TComponentClass; AWSComponent: TWSLCLComponentClass; AWSPrivate: TWSPrivateClass = nil); procedure UpdateChildren(const ANode: PClassNode; AOldPrivate: TClass); var Node: PClassNode; begin Node := ANode^.Child; while Node <> nil do begin if (Node^.WSClass <> nil) and (not Node^.VClassNew) then begin {$IFDEF VerboseWSRegistration} DebugLn('Update VClass for: ', Node^.WSClass.ClassName); {$ENDIF} CreateVClass(Node, AWSPrivate, AOldPrivate); end; UpdateChildren(Node, AOldPrivate); Node := Node^.Sibling; end; end; var Node: PClassNode; OldPrivate: TClass; begin if not Assigned(WSClassesList) then DoInitialization; Node := GetPClassNode(AComponent, AWSComponent, False, True); if Node = nil then // No node created Exit; { If AWSComponent specified but node already exists, nothing more to do. } if Assigned(AWSComponent) and (Node^.WSClass = AWSComponent) then Exit; Node^.WSClass := AWSComponent; // childclasses "inherit" the private from their parent // the child privates should only be updated when their private is still // the same as their parents if Node^.VClass = nil then OldPrivate := nil else OldPrivate := PClass(Node^.VClass + vmtWSPrivate)^; {$IFDEF VerboseWSRegistration} DebugLn('Create VClass for: ', AComponent.ClassName, ' -> ', Node^.WSClass.ClassName); {$ENDIF} CreateVClass(Node, AWSPrivate); // Since child classes may depend on us, recreate them UpdateChildren(Node, OldPrivate); end; // Do not create VClass at runtime but use normal Object Pascal class creation. function RegisterNewWSComp(AComponent: TComponentClass): TWSLCLComponentClass; var n: PClassNode; begin (* RegisterNewWSComp should only be called, if a previous FindWSRegistered failed => WSClassesList should be created already *) Assert(Assigned(WSClassesList), 'RegisterNewWSComp: WSClassesList=Nil'); n := GetPClassNode(AComponent, Nil, True, True); if n <> nil then Result := TWSLCLComponentClass(n^.VClass) else Result := nil; end; function GetWSLazAccessibleObject: TWSObjectClass; begin Result := WSLazAccessibleObjectClass; end; procedure RegisterWSLazAccessibleObject(const AWSObject: TWSObjectClass); begin WSLazAccessibleObjectClass := AWSObject; end; function GetWSLazDeviceAPIs: TWSObjectClass; begin Result := WSLazDeviceAPIsClass; end; procedure RegisterWSLazDeviceAPIs(const AWSObject: TWSObjectClass); begin WSLazDeviceAPIsClass := AWSObject; end; function FindWSRegistered(const AComponent: TComponentClass): TWSLCLComponentClass; begin if not Assigned(WSClassesList) then DoInitialization; Result := WSClassesList.FindWSClass(AComponent); end; {$IFDEF VerboseWSBrunoK} procedure DumpWSClassesList; begin WSClassesList.DumpNodes; end; {$ENDIF} { TWSClassesList } constructor TWSClassesList.Create; begin FLastFoundClass:=TClass(High(UIntPtr)); end; function TWSClassesList.FindWSClass(const AComponent: TComponentClass): TWSLCLComponentClass; var I: integer; begin {$IFDEF VerboseWSBrunoK} Write('Searching ', AComponent.ClassName); {$ENDIF} if Search(AComponent, i) then begin {$IFDEF VerboseWSBrunoK} WriteLn(' -> FOUND'); {$ENDIF} Exit(TWSLCLComponentClass(Items[i]^.VClass)); end; {$IFDEF VerboseWSBrunoK} WriteLn(' -> NOT FOUND'); {$ENDIF} Result := nil; end; function TWSClassesList.Get(Index: integer): PClassNode; begin Result := PClassNode(inherited Get(Index)); end; procedure TWSClassesList.Insert(aIndex: Integer; aItem: Pointer); begin inherited Insert(aIndex, aItem); UpdatLastFound(TClass(aItem), aIndex); end; { Searches a match for AComponent.ClassType. Returns index in items of the matching AComponent or the next bigger one } function TWSClassesList.Search(const aItem: TClass; out Index: integer): boolean; var L, R: integer; lLCLClass: TClass; begin if aItem = FLastFoundClass then begin Index := FLastFoundIdx; Exit(True); end; L := 0; R := Count - 1; Index := 0; if R < 0 then exit(False); // Use binary search. while (L < R) do begin Index := cardinal(L + R) div 2; if Pointer(aItem) <= Pointer(PClassNode(List^[Index])^.LCLClass) then R := Index else begin L := Index + 1; end; end; Index := L; lLCLClass := PClassNode(List^[Index])^.LCLClass; if aItem = lLCLClass then begin UpdatLastFound(lLCLClass, Index); Exit(True); end; if Pointer(aItem) < Pointer(lLCLClass) then Index := L else Index := L + 1; Result := False; end; procedure TWSClassesList.UpdatLastFound(aClass: TClass; aIndex: integer); begin FLastFoundClass := TComponentClass(aClass); FLastFoundIdx := aIndex; end; {$IFDEF VerboseWSBrunoK} procedure TWSClassesList.DumpNode(aN: integer; aPClassNode: PClassNode); var LCLClassClassName, lWSClassClassName, lVClassName, ParentVClassName: string; lClassNode : PClassNode; begin with aPClassNode^ do begin if Assigned(LCLClass) then LCLClassClassName := LCLClass.ClassName else LCLClassClassName := '???'; if Assigned(WSClass) then lWSClassClassName := WSClass.ClassName else lWSClassClassName := '???'; if Assigned(VClass) then lVClassName := TClass(VClass).ClassName else lVClassName := '???'; if Assigned(Parent) and Assigned(PClassNode(Parent)^.WSClass) then ParentVClassName := PClassNode(Parent)^.WSClass.ClassName else ParentVClassName := '???'; writeln( aN, ';', { DbgCreateSeq, ';', } HexStr(aPClassNode), ';', HexStr(LCLClass), ';', // : TComponentClass; LCLClassClassName, ';', HexStr(WSClass), ';', // : TWSLCLComponentClass; lWSClassClassName, ';', HexStr(VClass), ';', // : Pointer; VClassName, ';', // VVmtCount, ';', lVClassName, ';', VClassNew, ';', // : Boolean; HexStr(Parent), ';', // Parent: PClassNode; ParentVClassName, ';', // ShortString; HexStr(Child), ';', // Child: PClassNode; HexStr(Sibling) // Sibling: PClassNode; ); end; end; procedure TWSClassesList.DumpNodes; var i: integer; begin WriteLn('n;', // aN, ';', { 'CreateSeq;', // DbgCreateSeq, ';', } 'PClassNode;', // Node 'LCLClass;', // HexStr(LCLClass), ';', // : TComponentClass; 'LCLClassName;', // LCLClassClassName, ';', 'WSClass;', // HexStr(WSClass), ';', // : TWSLCLComponentClass 'WSClassName;', // lWSClassClassName, ';', 'VClass;', // HexStr(VClass), ';', // : Pointer; 'VClassName;', // VClassName { 'VVmtCount', } // VVmtCount, ';', 'VClassName;', // lVClassName, ';', 'VClassNew;', // VClassNew, ';', // : Boolean; 'Parent;', // HexStr(Parent), ';', // Parent: PClassNode; 'Parent.Name;', // ParentClassName, ';', // ShortString; 'Child;', // HexStr(Child), ';', // Child: PClassNode; 'Sibling' // HexStr(Sibling) // Sibling: PClassNode; ); for i := 0 to Count - 1 do DumpNode(i, PClassNode(Items[i])); end; {$ENDIF} { TWSLCLComponent } class function TWSLCLComponent.WSPrivate: TWSPrivateClass; begin Result := TWSPrivateClass(PClass(Pointer(Self) + vmtWSPrivate)^); end; { TWSLCLHandleComponent } class procedure TWSLCLReferenceComponent.DestroyReference(AComponent: TComponent); begin end; procedure DoInitialization; begin WSClassesList := TWSClassesList.Create; end; procedure DoFinalization; var n: Integer; Node: PClassNode; begin {$IFDEF VerboseWSBrunoK} WSClassesList.DumpNodes; WriteLn; WriteLn('cWSLCLDirectHit=', cWSLCLDirectHit, ' cWSLCLParentHit=', cWSLCLParentHit, ' cWSLCLRegister=', cWSLCLRegister); {$ENDIF} for n := 0 to WSClassesList.Count - 1 do begin Node := WSClassesList[n]; if (Node^.VClass <> nil) and (not Node^.VClassNew) then Freemem(Node^.VClass); Dispose(Node); end; FreeAndNil(WSClassesList); {$IFDEF VerboseWSBrunoK} Write('Press enter to quit > '); ReadLn; {$ENDIF} end; finalization DoFinalization; end.