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.
Related
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;
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 am modifying the edit control with autocomplete from here:
Auto append/complete from text file to an edit box delphi
I want to load autocomplete strings from DB. I declared new properties on autocomplete control descendant:
FACDataSource : TDataSource;
FACFieldName : string;
I call this to load autocomplete strings:
procedure TAutoCompleteEdit.ReadSuggestions;
begin
FAutoCompleteSourceList.Clear;
if (not Assigned(FACDataSource)) or (not Assigned(FACDataSource.DataSet)) or (not ACEnabled) then
exit;
with FACDataSource.DataSet do
begin
if Active and (RecordCount > 0) and (FACFieldName <> '') then
begin
First;
while not EOF do
begin
FAutoCompleteSourceList.Add(FACDataSource.DataSet.FieldByName(FACFieldName).AsString);
Next;
end;
if FAutoCompleteSourceList.Count > 0 then
ACStrings := FAutoCompleteSourceList;
end;
end;
end;
However, I get AccessViolation when assigning FAutoCompleteSourceList to ACStrings. The setter for ACStrings is:
procedure TAutoCompleteEdit.SetACStrings(const Value: TStringList);
begin
if Value <> FACList.FStrings then
FACList.FStrings.Assign(Value);
end;
I get AccessViolation in the line: FACList.FStrings.Assign(Value); (READ of address XXXYYY). Value is defined and not garbage at that point (e.g. in I can view the string list in the debugger). 'FStrings' is an empty stringlist.
It works fine when the control is dropped on the form. But doesn't if I place it within a custom inplace editor shown when user enters a DBGridEH cell.
The inplace editor is like this:
unit UInplaceAutoCompleteEditor;
interface
uses UDBAutoComplete, UMyInplaceEditor, classes, windows, Controls, Buttons, DB;
type TInplaceAutoCompleteEditor = class(TMyInplaceEditor)
private
FEditor : TAutoCompleteEdit;
FButton : TSpeedButton;
FShowButton : boolean;
procedure SetShowButton(value : boolean);
public
constructor Create(AOwner : TComponent); override;
procedure SetFocus; override;
destructor Destroy; override;
protected
procedure EditorKeyDown(Sender : TObject; var Key : Word; Shift : TShiftState);
function GetACDataSource : TDataSource;
procedure SetACDataSource(value : TDataSource);
function GetACFieldName : string;
procedure SetACFieldName(value : string);
procedure SetACEnabled(value : boolean);
function GetACEnabled : boolean;
published
property Editor : TAutoCompleteEdit read FEditor;
property ACDataSource : TDataSource read GetACDataSource write SetACDataSource;
property ACFieldName : string read GetACFieldName write SetACFieldName;
property ACEnabled : boolean read GetACEnabled write SetACEnabled;
property Button : TSpeedButton read FButton;
property ShowButton : boolean read FShowButton write SetShowButton;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('nikolaev', [ TInplaceAutoCompleteEditor ]);
end;
{ TInplaceAutoCompleteEditor }
constructor TInplaceAutoCompleteEditor.Create(AOwner: TComponent);
begin
inherited;
FEditor := TAutoCompleteEdit.Create(self);
FEditor.Parent := self;
FEditor.Align := alClient;
FEditor.Visible := true;
FEditor.WantTabs := true;
FEditor.OnKeyDown := EditorKeyDown;
FButton := TSpeedButton.Create(self);
FButton.Parent := self;
FButton.Align := alRight;
self.FOwnHeight := -1;
self.FOwnWidth := -1;
SetShowButton(false);
end;
destructor TInplaceAutoCompleteEditor.Destroy;
begin
Feditor.Destroy;
FButton.Destroy;
inherited;
end;
procedure TInplaceAutoCompleteEditor.EditorKeyDown(Sender: TObject;
var Key: Word; Shift: TShiftState);
begin
if Key in [ VK_Return, VK_Tab ] then
begin
self.Value := FEditor.Text;
Key := 0;
ConfirmValue;
end;
if Key = VK_Escape then
begin
Key := 0;
CancelValue;
end;
inherited;
end;
function TInplaceAutoCompleteEditor.GetACDataSource: TDataSource;
begin
Result := FEditor.ACDataSource;
end;
function TInplaceAutoCompleteEditor.GetACEnabled: boolean;
begin
Result := FEditor.ACEnabled;
end;
function TInplaceAutoCompleteEditor.GetACFieldName: string;
begin
Result := FEditor.ACFieldName
end;
procedure TInplaceAutoCompleteEditor.SetACDataSource(value: TDataSource);
begin
FEditor.ACDataSource := value;
end;
procedure TInplaceAutoCompleteEditor.SetACEnabled(value: boolean);
begin
FEditor.ACEnabled := value;
end;
procedure TInplaceAutoCompleteEditor.SetACFieldName(value: string);
begin
FEditor.acfieldname := value;
end;
procedure TInplaceAutoCompleteEditor.SetFocus;
begin
inherited;
FEditor.SetFocus;
end;
procedure TInplaceAutoCompleteEditor.SetShowButton(value: boolean);
begin
if value <> FShowButton then
begin
FShowButton := value;
FButton.Visible := value;
end;
end;
end.
This inplace editor inherits from an abstract class like this:
unit UMyInplaceEditor;
interface
uses Windows, classes, types, dbGridEh, ExtCtrls, Controls;
type TMyInplaceEditor = class (TWinControl)
private
FOnValueConfirmed : TNotifyEvent;
FOnCanceled : TNotifyEvent;
FWantTabs : boolean;
procedure AdjustPosition;
protected
FOwnHeight, FOwnWidth : integer;
FValue : Variant;
function GetIsEditing : boolean;
procedure SetIsEditing(value : boolean); virtual;
procedure ConfirmValue;
procedure CancelValue;
procedure SetValue(val : Variant); virtual;
public
property OnValueConfirmed : TNotifyEvent read FOnValueConfirmed write FOnValueConfirmed;
property OnCanceled : TNotifyEvent read FOnCanceled write FOnCanceled;
property Value : Variant read FValue write SetValue;
property IsEditing : boolean read GetIsEditing write SetIsEditing;
procedure SetPosition(parentControl : TWinControl; rect : TRect); virtual;
function ColumnEditable(column : TColumnEH) : boolean; virtual;
constructor Create(AOwner : TComponent); override;
property WantTabs : boolean read FWantTabs write FWantTabs;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('nikolaev', [TMyInplaceEditor]);
end;
constructor TMyInplaceEditor.Create(AOwner : TComponent);
begin
inherited Create(AOwner);
self.AutoSize := false;
self.Visible := false;
self.FOwnHeight := -1;
self.FOwnWidth := -1;
end;
procedure TMyInplaceEditor.AdjustPosition;
var xOffset, yOffset : integer;
begin
xoffset := self.Left + self.Width - self.Parent.Width;
if xOffset > 0 then
self.Left := self.Left - xOffset;
yOffset := self.Top + self.Height - self.Parent.height;
if yOffset > 0 then
self.Top := self.Top - yOffset;
end;
function TMyInplaceEditor.GetIsEditing : boolean;
begin
Result := self.Visible;
end;
procedure TMyInplaceEditor.SetIsEditing(value: Boolean);
begin
self.Visible := value;
self.BringToFront;
{if Visible then
self.SetFocus;}
end;
procedure TMyInplaceEditor.SetPosition(parentControl : TWinControl; rect: TRect);
begin
self.Parent := parentControl;
self.Top := rect.Top;//parentControl.Top;
self.Left := rect.Left;//parentControl.left;
if self.FOwnWidth = -1 then
self.Width := rect.Right - rect.Left
else
self.Width := self.FOwnWidth;
if self.FOwnHeight = -1 then
self.Height := rect.Bottom - rect.Top
else
self.Height := self.FOwnHeight;
AdjustPosition;
end;
function TMyInplaceEditor.ColumnEditable(column : TColumnEH) : boolean;
begin
Result := true;
end;
procedure TMyInplaceEditor.ConfirmValue;
begin
if Assigned(FOnValueConfirmed) then
FOnValueConfirmed(self);
end;
procedure TMyInplaceEditor.CancelValue;
begin
if Assigned(FOnCanceled) then
FOnCanceled(self);
end;
procedure TMyInplaceEditor.SetValue(val : Variant);
begin
FValue := val;
end;
end.
The InplaceEditor is used in a descendant from DBGridEH. I override ShowEditor and HideEditor to show / hide my editor in certain cases.
Again, the autocomplete control only throws exception when embedded in the inplaceeditor control.
What causes access violation?
The problem is that the code you are using mis-handles interface reference counting. Here are the relevant extracts:
type
TEnumString = class(TInterfacedObject, IEnumString)
....
Note that this class is derived from TInterfacedObject and so it manages its lifetime using reference counting.
Then the code goes on like this:
type
TAutoCompleteEdit = class(TEdit)
private
FACList: TEnumString;
....
So we are going to hold a reference to the object rather than the interface. That looks dubious already.
Then we do this:
constructor TAutoCompleteEdit.Create(AOwner: TComponent);
begin
inherited;
FACList := TEnumString.Create;
....
end;
destructor TAutoCompleteEdit.Destroy;
begin
FACList := nil;
inherited;
end;
There's nothing here to keep the object alive. At other points in the code we take a reference to the IEnumString interface. But then as soon as that reference is released, the object thinks that there are no references left. And so it is deleted. Then, later on, the code refers to FACList which now points at an object that has been destroyed.
A simple way to fix this would be to make sure that the TAutoCompleteEdit control always holds a reference to the interface:
type
TAutoCompleteEdit = class(TEdit)
private
FACList: TEnumString;
FEnumString: IEnumString;
....
constructor TAutoCompleteEdit.Create(AOwner: TComponent);
begin
inherited;
FACList := TEnumString.Create;
FEnumString := FACList;
....
end;
And with this change you can then remove the destructor for TAutoCompleteEdit since the object behind FEnumString will get destroyed by the reference counting mechanism.
Another way to fix this would be to change TEnumString to disable automatic reference counting. That would look like this:
type
TEnumString = class(TObject, IInterface, IEnumString)
private
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
....
end;
function TEnumString.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;
function TEnumString._AddRef: Integer;
begin
Result := -1;
end;
function TEnumString._Release: Integer;
begin
Result := -1;
end;
And then you'd need the TAutoCompleteEdit destructor to look like this:
destructor TAutoCompleteEdit.Destroy;
begin
FACList.Free;
inherited;
end;
And a final option would be to avoid holding a TEnumString at all and instead only hold an IEnumString reference. Let the reference counting manage lifetime as in the first solution. But then you'd need to implement another interface that allowed the TAutoCompleteEdit to obtain the TStrings object.
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;
I want to duplicate a class. It is sufficient that I copy all properties of that class. Is it possible to:
loop thru all properties of a class?
assign each property to the other property, like a.prop := b.prop?
The getters and setters should take care of the underlying implementation details.
EDIT:
As Francois pointed out I did not word my question carefully enough. I hope the new wording of the question is better
SOLUTION:
Linas got the right solution. Find a small demo program below. Derived classes work as expected. I didn't know about the new RTTI possibilities until several people pointed me at it. Very useful information. Thank you all.
unit properties;
interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls,
RTTI, TypInfo;
type
TForm1 = class(TForm)
Memo1: TMemo;
Button0: TButton;
Button1: TButton;
procedure Button0Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
public
procedure GetObjectProperties (AObject: TObject; AList: TStrings);
procedure CopyObject<T: class>(ASourceObject, ATargetObject: T);
end;
TDemo = class (TObject)
private
FIntField: Int32;
function get_str_field: string;
procedure set_str_field (value: string);
public
constructor Create; virtual;
property IntField: Int32 read FIntField write FIntField;
property StrField: string read get_str_field write set_str_field;
end; // Class: TDemo //
TDerived = class (TDemo)
private
FList: TStringList;
function get_items: string;
procedure set_items (value: string);
public
constructor Create; override;
destructor Destroy; override;
procedure add_string (text: string);
property Items: string read get_items write set_items;
end;
var Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.GetObjectProperties(AObject: TObject; AList: TStrings);
var ctx: TRttiContext;
rType: TRttiType;
rProp: TRttiProperty;
AValue: TValue;
sVal: string;
const SKIP_PROP_TYPES = [tkUnknown, tkInterface];
begin
if not Assigned(AObject) and not Assigned(AList) then Exit;
ctx := TRttiContext.Create;
rType := ctx.GetType(AObject.ClassInfo);
for rProp in rType.GetProperties do
begin
if (rProp.IsReadable) and not (rProp.PropertyType.TypeKind in SKIP_PROP_TYPES) then
begin
AValue := rProp.GetValue(AObject);
if AValue.IsEmpty then
begin
sVal := 'nil';
end else
begin
if AValue.Kind in [tkUString, tkString, tkWString, tkChar, tkWChar]
then sVal := QuotedStr(AValue.ToString)
else sVal := AValue.ToString;
end;
AList.Add(rProp.Name + '=' + sVal);
end;
end;
end;
procedure TForm1.CopyObject<T>(ASourceObject, ATargetObject: T);
const
SKIP_PROP_TYPES = [tkUnknown, tkInterface, tkClass, tkClassRef, tkPointer, tkProcedure];
var
ctx: TRttiContext;
rType: TRttiType;
rProp: TRttiProperty;
AValue, ASource, ATarget: TValue;
begin
Assert( Assigned(ASourceObject) and Assigned(ATargetObject) , 'Both objects must be assigned');
ctx := TRttiContext.Create;
rType := ctx.GetType(ASourceObject.ClassInfo);
ASource := TValue.From<T>(ASourceObject);
ATarget := TValue.From<T>(ATargetObject);
for rProp in rType.GetProperties do
begin
if (rProp.IsReadable) and (rProp.IsWritable) and not (rProp.PropertyType.TypeKind in SKIP_PROP_TYPES) then
begin
//when copying visual controls you must skip some properties or you will get some exceptions later
if SameText(rProp.Name, 'Name') or (SameText(rProp.Name, 'WindowProc')) then
Continue;
AValue := rProp.GetValue(ASource.AsObject);
rProp.SetValue(ATarget.AsObject, AValue);
end;
end;
end;
procedure TForm1.Button0Click(Sender: TObject);
var demo1, demo2: TDemo;
begin
demo1 := TDemo.Create;
demo2 := TDemo.Create;
demo1.StrField := '1023';
Memo1.Lines.Add ('---Demo1---');
GetObjectProperties (demo1, Memo1.Lines);
CopyObject<TDemo> (demo1, demo2);
Memo1.Lines.Add ('---Demo2---');
GetObjectProperties (demo2, Memo1.Lines);
end;
procedure TForm1.Button1Click(Sender: TObject);
var derivate1, derivate2: TDerived;
begin
derivate1 := TDerived.Create;
derivate2 := TDerived.Create;
derivate1.IntField := 432;
derivate1.add_string ('ien');
derivate1.add_string ('twa');
derivate1.add_string ('drei');
derivate1.add_string ('fjour');
Memo1.Lines.Add ('---derivate1---');
GetObjectProperties (derivate1, Memo1.Lines);
CopyObject<TDerived> (derivate1, derivate2);
Memo1.Lines.Add ('---derivate2---');
GetObjectProperties (derivate2, Memo1.Lines);
end;
constructor TDemo.Create;
begin
IntField := 321;
end; // Create //
function TDemo.get_str_field: string;
begin
Result := IntToStr (IntField);
end; // get_str_field //
procedure TDemo.set_str_field (value: string);
begin
IntField := StrToInt (value);
end; // set_str_field //
constructor TDerived.Create;
begin
inherited Create;
FList := TStringList.Create;
end; // Create //
destructor TDerived.Destroy;
begin
FList.Free;
inherited Destroy;
end; // Destroy //
procedure TDerived.add_string (text: string);
begin
FList.Add (text);
end; // add_string //
function TDerived.get_items: string;
begin
Result := FList.Text;
end; // get_items //
procedure TDerived.set_items (value: string);
begin
FList.Text := value;
end; // set_items //
end. // Unit: properties //
Try this code (but I won't advise copying properties of visual components because then you'll need to manually skip some properties):
uses
Rtti, TypInfo;
procedure CopyObject<T: class>(ASourceObject, ATargetObject: T);
procedure TForm1.CopyObject<T>(ASourceObject, ATargetObject: T);
const
SKIP_PROP_TYPES = [tkUnknown, tkInterface, tkClass, tkClassRef, tkPointer, tkProcedure];
var
ctx: TRttiContext;
rType: TRttiType;
rProp: TRttiProperty;
AValue, ASource, ATarget: TValue;
begin
Assert( Assigned(ASourceObject) and Assigned(ATargetObject) , 'Both objects must be assigned');
ctx := TRttiContext.Create;
rType := ctx.GetType(ASourceObject.ClassInfo);
ASource := TValue.From<T>(ASourceObject);
ATarget := TValue.From<T>(ATargetObject);
for rProp in rType.GetProperties do
begin
if (rProp.IsReadable) and (rProp.IsWritable) and not (rProp.PropertyType.TypeKind in SKIP_PROP_TYPES) then
begin
//when copying visual controls you must skip some properties or you will get some exceptions later
if SameText(rProp.Name, 'Name') or (SameText(rProp.Name, 'WindowProc')) then
Continue;
AValue := rProp.GetValue(ASource.AsObject);
rProp.SetValue(ATarget.AsObject, AValue);
end;
end;
end;
Usage example:
CopyObject<TDemoObj>(FObj1, FObj2);
Your question as it is does not make much sense to me.
Are you really trying to create a new class by copying an existing one?
Or are you trying to do a deep copy of an instance A of a class into another instance B of the same class?
In that case, see this discussion about cloning in another SO question.
You didn't mention your Delphi version, but here's a good start. You need to explore the Delphi RTTI which allows you to obtain runtime type information. You'd have to iterate your source class for types, then provide a method for assigning each type.
About RTTI
If you're designing your own simple classes, you could just override assign and do your own property assignments there.