[fpc-devel] Error with TypInfo.SetPropValue and enumerated types
Graeme Geldenhuys
graeme at mastermaths.co.za
Tue Jan 10 09:10:40 CET 2006
Hi,
Can someone confirm if this is a bug in FPC? The program below makes a
copy of a object, which contains an enumerated property. The Assign
only copies published properties for this example.
All other types seem to work, but enumerated types raise a EVariantError
exception. This code comes from a Delphi project which I am busy
porting to Lazarus. All my unit tests regarding this code passes under
Delphi, but fails under FPC.
King Regards,
- Graeme -
Sample of program output:
-------------------------
c:\Programming\Tests\Clone_Object>project1.exe
Creating first person
Person One is a 20 year old Unknown.
Creating second person
Person Two is a 30 year old Male.
Creating clone of Person One
An unhandled exception occurred at $00401690 :
Exception : Error setting property TPerson.Gender Message
------------------- CUT -----------------------------
{ This shows the error when trying to to use TypInfo.SetPropValue when
working with enumerated types. }
program Project1;
{$mode objfpc}{$H+}
uses
Classes, SysUtils, TypInfo;
const
// All string type properties
ctkString = [ tkChar, tkString, tkWChar, tkLString, tkWString,
tkAString ];
// Integer type properties
ctkInt = [ tkInteger, tkInt64 ];
// Float type properties
ctkFloat = [ tkFloat ];
// Numeric type properties
ctkNumeric = [tkInteger, tkInt64, tkFloat];
// All simple types (string, int, float)
ctkSimple = ctkString + ctkInt + ctkFloat;
cErrorSettingProperty = 'Error setting property %s.%s Message %s';
cErrorGettingProperty = 'Error getting property %s.%s Message %s';
type
TGender = (genUnknown, genMale, genFemale);
TtiObject = class;
const
cGenderGUI : array[TGender] of string =
( 'Unknown', 'Male', 'Female' );
type
//---------------------------------------------------------------
TtiObject = class(TPersistent)
private
FCaption: string;
protected
function GetCaption: string; virtual;
procedure AssignPublishedProp(pSource: TtiObject; psPropName:
string);
procedure AssignPublishedProps(pSource: TtiObject; pPropFilter:
TTypeKinds = []);
public
procedure Assign(const pSource: TtiObject); reintroduce; virtual;
published
property Caption: string read GetCaption write FCaption;
end;
//---------------------------------------------------------------
TPerson = class(TtiObject)
private
FAge: integer;
FGender: TGender;
FName: String;
protected
function GetCaption: string; override;
public
constructor Create;
{ As soon as you move gender to here, the Assign works due to not
actually assigning this property. }
// property Gender: TGender read FGender write FGender;
published
property Name: String read FName write FName;
property Age: integer read FAge write FAge;
property Gender: TGender read FGender write FGender;
end;
//---------------------------------------------------------------------
procedure tiGetPropertyNames( pPersistent: TPersistentClass;
pSL: TStringList;
pPropFilter: TTypeKinds = ctkSimple );
var
lCount : integer ;
lSize : integer ;
lList : PPropList ;
i : integer ;
lPropFilter : TTypeKinds ;
begin
Assert( pSL <> nil, 'pSL not assigned.' ) ;
lPropFilter := pPropFilter ;
pSL.Clear ;
lCount := GetPropList(pPersistent.ClassInfo, lPropFilter, nil, false);
lSize := lCount * SizeOf(Pointer);
GetMem(lList, lSize);
try
GetPropList(pPersistent.ClassInfo, lPropFilter, lList, false);
for i := 0 to lcount - 1 do
pSL.Add( lList^[i]^.Name );
finally
FreeMem( lList, lSize );
end ;
end ;
//--------------------------------------------------------------------
procedure tiGetPropertyNames( pPersistent: TPersistent; pSL: TStringList;
pPropFilter: TTypeKinds = ctkSimple );
begin
Assert( pPersistent <> nil, 'pPersistent not assigned.' ) ;
tiGetPropertyNames( TPersistentClass( pPersistent.ClassType ),
pSL,
pPropFilter );
end;
//--------------------------------------------------------------------
function tiIsReadWriteProp(const pData: TPersistentClass;
const psPropName: string): boolean;
var
lPropInfo : PPropInfo ;
begin
Assert( pData <> nil, 'pData not assigned' ) ;
Assert( IsPublishedProp( pData, psPropName ), psPropName +
' not a published property on ' + pData.ClassName ) ;
try
lPropInfo := GetPropInfo( pData, psPropName ) ;
{$IFDEF FPC}
{$NOTE BUG in FPC with ReadOnly properties. Remove this as soon as it
is fixed. }
result := (lPropInfo^.GetProc <> Pointer($01)) and
(lPropInfo^.SetProc <> Pointer($01));
{$ELSE}
{ this is how Delphi works }
result := ( lPropInfo^.GetProc <> nil ) and
( lPropInfo^.SetProc <> nil ) ;
{$ENDIF}
except
on e:exception do
raise exception.CreateFmt(
'Error calling tiIsReadWriteProp with class: %s and property %s',
[pData.ClassName, psPropName]);
end ;
end;
//--------------------------------------------------------------------
function tiIsReadWriteProp(const pData: TPersistent;
const psPropName: string ): boolean;
begin
result :=
tiIsReadWriteProp( TPersistentClass(pData.ClassType), psPropName );
end;
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* TPerson
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function TPerson.GetCaption: string;
const
C = '%s is a %d year old %s.';
begin
Result := Format(C, [Name, Age, cGenderGUI[Gender]]);
end;
//---------------------------------------------------------------------
constructor TPerson.Create;
begin
inherited Create;
FGender := genUnknown;
end;
{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* TtiObject
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * }
procedure TtiObject.AssignPublishedProps( pSource: TtiObject;
pPropFilter: TTypeKinds);
var
lsl: TStringList;
i: integer;
lsPropName: string;
lPropFilter: TTypeKinds;
begin
if pPropFilter = [] then
lPropFilter := ctkSimple + [tkEnumeration, tkVariant]
else
lPropFilter := pPropFilter;
lsl := TStringList.Create;
try
tiGetPropertyNames(self, lsl, lPropFilter);
for i := 0 to lsl.Count - 1 do
begin
lsPropName := lsl.Strings[i];
try
// Only clone read/write properties
if ( tiIsReadWriteProp( Self, lsPropName )) and
( IsPublishedProp( pSource, lsPropName )) then
AssignPublishedProp( pSource, lsPropName ) ;
except
on e: Exception do
raise Exception.CreateFmt(cErrorSettingProperty,
[ClassName, lsPropName, e.Message]);
end ;
end ;
finally
lsl.Free ;
end ;
end ;
// ----------------------------------------------------------------------
procedure TtiObject.Assign(const pSource: TtiObject);
begin
Assert(( pSource is Self.ClassType ) or
( Self is pSource.ClassType ),
pSource.ClassName +
' and ' +
ClassName +
' are not assignment compatable' ) ;
// AssignPublicProps( pSource ) ;
AssignPublishedProps( pSource ) ;
// AssignClassProps( pSource ) ;
end;
//----------------------------------------------------------------------
function TtiObject.GetCaption: string;
begin
Result := ClassName;
end;
// ---------------------------------------------------------------------
procedure TtiObject.AssignPublishedProp( pSource: TtiObject;
psPropName: string);
var
lPropType: TTypeKind;
lPropValue: Variant;
begin
lPropType := TypInfo.PropType( pSource, psPropName ) ;
if lPropType in ctkSimple + [tkVariant, tkEnumeration] then
begin
lPropValue := TypInfo.GetPropValue(pSource, psPropName);
TypInfo.SetPropValue( Self, psPropName, lPropValue);
end
else
raise Exception.CreateFmt(cErrorSettingProperty,
[ClassName, psPropName, 'Unknown property type']);
end;
//---------------------------------------------------------------------
var
lData1: TPerson;
lData2: TPerson;
begin
Writeln('Creating first person');
lData1 := TPerson.Create;
lData1.Name := 'Person One';
lData1.Age := 20;
Writeln(lData1.Caption);
Writeln('Creating second person');
lData2 := TPerson.Create;
lData2.Name := 'Person Two';
lData2.Age := 30;
lData2.Gender := genMale;
Writeln(lData2.Caption);
Writeln('Creating clone of Person One');
lData2.Assign(lData1);
Writeln('Person Two is now:');
Writeln(lData2.Caption);
lData1.Free;
lData2.Free;
end.
------------------- END -----------------------------
More information about the fpc-devel
mailing list