Convert Integer to Enum in property getter/setter - delphi

I currently have an option management which works with properties, that use all the same getter/setter method but with an index.
This works fine for Ints and Bools, but I'd like to get this working for enums, too.
TIntegerOptions = (kInt1, kInt2, kInt3);
class property Integer1: Integer index kInt1 read GetOptionsValueInt write SetOptionsValueInt;
class property Integer2: Integer index kInt2 read GetOptionsValueInt write SetOptionsValueInt;
class property Integer3: Integer index kInt3 read GetOptionsValueInt write SetOptionsWertInt;
FOptionsListInt: array[TIntegerOptions] of IniIntObject; // Objects contain Inifile read&save functionalities
...
class function GetOptionsValueInt(const aOption: TIntegerOptions) : Integer
begin
Result := Ord(FOptionsListInt[aOption].GetValue());
end;
class procedure SetOptionsValueInt(const aOption: TIntegerOptions; const aValue: Integer);
begin
FOptionslistInt[aOption].ChangeTo(aValue);
end;
This is working so far, and now my problem:
TEnumOptions = (kEnum1, kEnum2, kEnum3);
TEnum1 = (e1o1, e1o2, e1o3);
TEnum2 = (e2o1, e2o2, e2o3);
TEnum3 = (e3o1, e3o2, e3o3);
// these props fail because my functions return / excpect Integers, not the matching Enums
class property Enum1: TEnum1 index kEnum1 read GetOptionsValueInt write SetOptionsValueEnum;
class property Enum2: TEnum2 index kEnum2 read GetOptionsValueInt write SetOptionsValueEnum;
class property Enum3: TEnum3 index kEnum3 read GetOptionsValueInt write SetOptionsValueEnum;
FOptionsListEnum: array[TEnumOptions] of IniIntObject; // Objects contain Inifile read&save functionalities
I marked the problems in the code. Can I somehow cast the Integer values returned by the getters/setters to the matching enum for each property?
For those who read the old question:
I decided to just use the same getter/setter for the enums, cause in the end they get saved as Integers, too. If I need to cast inside the getters somehow, I'll add them again, but i hoped for a solution inside the property-declaration.

Regarding your update, you want to use code like this:
class property Enum1: TEnum1 index kEnum1 read GetOptionsValueInt;
That fails to compile because GetOptionsValueInt returns a value of type Integer. Since the property has type TEnum1, that is a simple type mismatch. In order to have a property of type TEnum1, the getter must be a function that returns a value of type TEnum1.
If each of these properties has a different type, then you cannot share getter and setter. Shared indexed getters and setters can only be used for properties that share a common type. Since it looks like none of these properties share a type, you will not be able to share getters and setters.
Original answer to original question
You don't show all the declarations, so it's hard for us to know why your code does not compile. We don't know what FOptionsListeEnum is, and we don't know what Wert() is.
However, here's a complete program that demonstrates that enumerated types can be used as indexes for indexed properties:
{$APPTYPE CONSOLE}
type
TMyEnum = (evOne, evTwo);
TMyClass = class
private
class function GetValue(const Option: TMyEnum): Integer; static;
public
class property ValueOne: Integer index evOne read GetValue;
class property ValueTwo: Integer index evTwo read GetValue;
end;
class function TMyClass.GetValue(const Option: TMyEnum): Integer;
begin
Result := ord(Option);
end;
begin
Writeln(TMyClass.ValueOne);
Writeln(TMyClass.ValueTwo);
end.
So, that makes it clear that there's no issue using enumerated types as indices. In which case, what is your problem. Let's look at the code you have:
class function TKmpOption.GetOptionsWertEnum(const aOption: TEnumOptionen): Integer;
begin
Result := Ord(FOptionsListeEnum[aOption].Wert());
end;
class procedure TKmpOption.SetOptionsWertEnum(const aOption: TEnumOptionen; const aWert: Integer);
begin
FOptionslisteEnum[aOption].Aendern(aOption(aWert));
end;
If Ord(FOptionsListeEnum[aOption].Wert()) does not compile with a type mismatch error, then it would seem that Wert() is not an ordinal value.
As for aOption(aWert) that makes no sense at all. That can never compile. Perhaps you meant TEnumOptionen(aWert) but that also makes no sense. Why would the value be of the same type used to index the possible options.
I hope that the code above shows that the issue is not related to enumerated types as indexers and is in fact of a rather more prosaic nature.

Related

Automatically serializing a TObject to JSON using mormot

I am trying to serialize an TObject to JSON using the mORMot framework. Unfortunately, the result is always null.
The class I am trying to serialize is:
type ApmTime = class(TObject)
private
function currentTime() : String;
published
property Current_time: String read currentTime;
public
constructor Create;
end;
constructor ApmTime.Create;
begin
inherited;
end;
function ApmTime.currentTime() : String;
begin
result := TimeToStr(Now);
end;
And the corresponding mORMot method is defined in SynCommons:
currentTime := ApmTime.Create;
Write(ObjectToJSON(currentTime, [woFullExpand]));
This always returns null. After having single-stepped in TTextWriter.WriteObject (located in unit SynCommons), the following piece of code seems to be where the resulting json is set to null:
if not(woFullExpand in Options) or
not(Value.InheritsFrom(TList)
{$ifndef LVCL} or Value.InheritsFrom(TCollection){$endif}) then
Value := nil;
if Value=nil then begin
AddShort('null');
exit;
I am expecting something along the line:
{
"Current_time" : "15:04"
}
Ran into this yesterday and worked out what's going on, so for the benefit of future people stumbling over this problem as well:
If you only add SynCommons.pas to your uses clause, then the default DefaultTextWriterJSONClass is set to TTextWriter which only supports serializing particular class types as you've seen, and doesn't support arbitrary classes/objects. See the following lines in SynCommons.pas where this default is set:
var
DefaultTextWriterJSONClass: TTextWriterClass = TTextWriter;
Now, in order to support serializing arbitrary objects to JSON, this global variable needs to be changed from the default TTextWriter to TJSONSerializer.
This class is defined in mORMot.pas, and in fact, if you add mORMot.pas to your uses clause, its initialization will override the above default and set TJSONSerializer as the new default for you.
This behaviour is in fact documented in SynCommons.pas if you read carefully enough, e.g. see the comments for "SetDEfaultJSONClass()" class method:
// you can use this method to override the default JSON serialization class
// - if only SynCommons.pas is used, it will be TTextWriter
// - but mORMot.pas initialization will call it to use the TJSONSerializer
// instead, which is able to serialize any class as JSON
class procedure SetDefaultJSONClass(aClass: TTextWriterClass);
So in short: To fix your issue, just add mORMot.pas to your uses clause in addition to SynCommons.pas which you should already have.
Try add a write to the published property.
property Current_time: String read currentTime write SetCurrentTime.
A readonly property is not serialized. Also ApmTime should be based on TPersistent
type
ApmTime = class(TPersistent)

Forward declarations for record types (or arrays)

I want to do this in XE5:
type
TMyRec = record
// fields
class function GetList: TMyRecArr; static;
end;
TMyRecArr = array of TMyRec;
I've already seen "Forward declarations for record types" and "how to do a typed forward declaration?", but they seem irrelevant since my problem is not passing the record as a parameter.
You cannot use a forward declaration to declare a record type or an array type. But not to fear. You can use a generic dynamic array, TArray<T>.
type
TMyRec = record
class function GetList: TArray<TMyRec>; static;
end;
This is actually better than declaring TMyRecArr as per the code in your question. That's because the generic TArray<T> has more flexible type identity than a traditional dynamic array type. You can use TArray<T> with generic types defined in libraries that are independent and unaware of your code.
Now, you could declare the type like this:
type
TMyRec = record
type TMyRecArray = array of TMyRec;
class function GetList: TMyRecArray; static;
end;
And then your array type is TMyRec.TMyRecArray. But I urge you not to do this. You'll have a type that can only be used with you code, and cannot be used with third party code.
In summary, TArray<T> is your friend.
Since declarations of pointers types are permitted before the type definition, slightly modifying your function you're allowed to do this:
type
PMyRecArr = ^TMyRecArr;
TMyRec = record
// fields
class procedure GetList(const arr: PMyRecArr); static;
end;
TMyRecArr = array of TMyRec;
The procedure implementation and its usage follow:
class procedure TMyRec.GetList(const arr: PMyRecArr);
begin
SetLength(arr^, 4);
end;
var
arr: TMyRecArr;
begin
TMyRec.GetList(#arr);
Writeln(Length(arr));//prints 4
end.
Well, I searched a little more, and found that I can use helpers to do this:
type
TMyRec = record
// fields
end;
TMyRecArr = array of TMyRec;
TMyRecHelper = record helper for TMyRec
class function GetList: TMyRecArr; static;
end;
Sure it lacks benefits of generics that David mentioned in his answer and comments, but it doesn't make the code auto-complete unusable! I mean one may comes to this conclusion that the flexibility that TArray<T> offers is not needed in some piece of code. Then it would be nothing more than increment in memory usage of the final running application or possibly lower performance.
Instead of trying to add forward decalrations to records (which up until now still isn't possible in Delphi), there is a way around this : move functions that reference another record type towards a record helper, declared where both record types are in scope.
type
RecordA = record
// [...]
end;
RecordB = record
// [...]
end;
RecordAHelper = record helper for RecordA
procedure Call(argument: RecordB);
end;
After obvioulsy implementing this, above allows one to write (and compile and run) : RecordA_variable.Call(RecordB_variable)

How to assign an empty set to a record using overloaded operators

I'm using a record to encapsulate two dissimular sets.
I've put in operators to allow the assignment of either set to the record. Doing so will clear the other set.
However I cannot assign an empty set.
See the following example code:
Program test;
{$Apptype console}
type
TSomeThing = (a,b,c);
TOtherThing = (x,y,z);
TSomeThings = set of TSomething;
TOtherThings = set of TOtherThing;
TSomeRecord = record
strict private
Fa: TSomeThings;
Fb: TOtherThings;
public
class operator Implicit(a: TSomeThings): TSomeRecord;
class operator Implicit(a: TOtherThings): TSomeRecord;
end;
implementation
class operator TSomeRecord.Implicit(a: TSomeThings): TSomeRecord;
begin
Result.Fa:= a;
Result.Fb:= [];
end;
class operator TSomeRecord.Implicit(a: TOtherThings): TSomeRecord;
begin
Result.Fa:= [];
Result.Fb:= a;
end;
var
SomeRec: TSomeRecord;
begin
SomeRec:= [];
end.
[dcc64 Error] InstructionList.pas(512): E2010 Incompatible types: 'TSomeRecord' and 'Set'
How do I make it so I can assign the empty set to my record?
I can misuse the implicit operator to allow SomeRec:= nil;, but that looks very ugly.
The compiler cannot tell whether you mean an empty set of TSomeThing or an empty set of TOtherThing. You can declare typed constants to allow the compiler to resolve the overload:
const
EmptySomeThings: TSomeThings = [];
EmptyOtherThings: TOtherThings = [];
Then the following assignments compile and resolve as you would expect:
SomeRec:= EmptySomeThings;
SomeRec:= EmptyOtherThings;
Of course, you know that either one of these has the same effect, because the implementation of the Implicit operators sets one field, and clears the other. But the compiler cannot know this.
If you wish to clear both members of the record you can always use:
SomeRec:= Default(TSomeRecord);
I personally might wrap that up in a static class method like this:
class function Default: TSomeRecord; static;
....
class function TSomeRecord.Default: TSomeRecord;
begin
Result := Default(TSomeRecord);
end;
Then you can write:
SomeRec:= TSomeRecord.Default;
In an ideal world you'd be able to declare a constant in the type, but the language designers did not think of that and it is sadly not possible.
Update
Rudy correctly points out in a comment, that constants can be added to a record type by way of a record helper. This was news to me as I mistakenly believed that helpers could only add methods. This is what I love about Stack Overflow. Even when you think you know something pretty well, there's always scope for more knowledge to be acquired. Thanks Rudy.
So you might write:
type
TSomeRecordHelper = record helper for TSomeRecord
public
const
Default: TSomeRecord = ();
end;

Use Rtti to set method field

I'm using Delphi XE to write a base class, which will allow descending classes to have dll methods mapped by applying an annotation. However I get a typecasting error, which is understandable.
In essence the base class should look like this:
TWrapperBase = class
public
FLibHandle: THandle;
procedure MapMethods;
end;
procedure TWrapperBase.MapMethods;
var
MyField: TRttiField;
MyAttribute: TCustomAttribute;
pMethod: pointer;
begin
FLibHandle := LoadLibrary(PWideChar(aMCLMCR_dll));
for MyField in TRttiContext.Create.GetType(ClassType).GetFields do
for MyAttribute in MyField.GetAttributes do
if MyAttribute.InheritsFrom(TMyMapperAttribute) then
begin
pMethod := GetProcAddress(FLibHandle, (MyAttribute as TMyMapperAttribute).TargetMethod);
if Assigned(pMethod) then
MyField.SetValue(Self, pMethod); // I get a Typecast error here
end;
And a descending class could look like this:
TDecendant = class(TWrapperBase)
private type
TSomeDLLMethod = procedure(aParam: TSomeType); cdecl;
private
[TMyMapperAttribute('MyDllMethodName')]
FSomeDLLMethod: TSomeDLLMethod;
public
property SomeDLLMethod: TSomeDLLMethod read FSomeDLLMethod;
end;
I could implement this differently, by hard coding the linking for each method in an overriden 'MapMethods'. This would however require each descendant to do so which I'd like to avoid.
I know that the TValue as used in this case will contain a pointer and not of the correct type (procedure(aParam: TSomeType); cdecl; in this case).
My question: Is there a way to pass the pointer from 'GetProcAdress' as the correct type, or to set the field directly (for example by using the field address 'PByte(Self)+MyField.Offset', which you can use to set the value of a record property)?
With the old Rtti, this could be done but only for published properties and without any type checking:
if IsPublishedProp(Self, 'SomeDLLMethod') then
SetMethodProp(Self, 'SomeDLLMethod', GetProcAddress(FLibHandle, 'MethodName');
There are two problems:
First your EInvalidCast is caused by TValue being very strict about type conversions. You are passing in a Pointer and want to set a field of type TSomeDLLMethod. You need to explicitly pass a TValue that has the correct type info.
if Assigned(pMethod) then
begin
TValue.Make(#pMethod, MyField.FieldType.Handle, value);
MyField.SetValue(Self, value);
end;
Now you will run into another EInvalidCast exception which is triggered because of a bug in XE inside the GetInlineSize method of the Rtti.pas which returns 0 for a tkProcedure kind of type. I don't know in what version this got fixed but it does not exist anymore in XE5.
For XE this can be fixed by using a unit I wrote some while ago (and which I just updated to fix this bug): RttiPatch.pas.
I also reported the original issue because Pointer is assignment compatible to a procedure type so TValue should also handle this: http://qc.embarcadero.com/wc/qcmain.aspx?d=124010
You could try something like:
Move(pMethod, PByte(Self) + Field.Offset, SizeOf(Pointer));
or
PPointer(PByte(Self) + Field.Offset)^ := pMethod;

Delphi - records with variant parts

I want to have a record (structure) with a 'polymorphic' comportment. It will have several fields used in all the cases, and I want to use other fields only when I need them. I know that I can accomplish this by variant parts declared in records. I don't know if it is possible that at design time I can access only the elements I need. To be more specific, look at the example bellow
program consapp;
{$APPTYPE CONSOLE}
uses
ExceptionLog,
SysUtils;
type
a = record
b : integer;
case isEnabled : boolean of
true : (c:Integer);
false : (d:String[50]);
end;
var test:a;
begin
test.b:=1;
test.isEnabled := False;
test.c := 3; //because isenabled is false, I want that the c element to be unavailable to the coder, and to access only the d element.
Writeln(test.c);
readln;
end.
Is this possible?
All variant fields in a variant record are accessible at all times, irrespective of the value of the tag.
In order to achieve the accessibility control you are looking for you would need to use properties and have runtime checks to control accessibility.
type
TMyRecord = record
strict private
FIsEnabled: Boolean;
FInt: Integer;
FStr: string;
// ... declare the property getters and settings here
public
property IsEnabled: Boolean read FIsEnabled write FIsEnabled;
property Int: Integer read GetInt write SetInt;
property Str: string read GetString write SetString;
end;
...
function TMyRecord.GetInt: Integer;
begin
if IsEnabled then
Result := FInt
else
raise EValueNotAvailable.Create('blah blah');
end;
Even if I heard that by original Niklaus Wirth's Pascal definition all should work as you expected, I saw no such behaviour in Delphi, starting from its ancestor, Turbo Pascal 2.0. Quick look at FreePascal showed that its behaviour is the same. As said in Delphi documentation:
You can read or write to any field of any variant at any time; but if you write to a field in one variant and then to a field in another variant, you may be overwriting your own data. The tag, if there is one, functions as an extra field (of type ordinalType) in the non-variant part of the record."
Regarding your intent, as far as I understood it, I would use two different classes, kind of
a = class
b : Integer
end;
aEnabled = class(a)
c: Integer
end;
aDisabled = class(a)
d: String //plus this way you can use long strings
end;
This way you can get some support from IDE's Code Editor even at designtime. More useful, though, is that it will be much more easier to modify and support later.
However, if you need quick switching of record variable values at runtime, #David Heffernan's variant , to use properties and have runtime checks, is more reasonable.
The example given is NOT a variant record, it includes all the fields all the time.
A true variant record has the variants sharing the same memory. You just use the "case discriminator: DiscType of ..... " syntax, no need for a separate field telling you what variant is active.

Resources