Is there any way with the current delphi to implement.
a) String (as a class) with operator overloads (ie. +, =)
b) Class Helper's so one could add custom string methods
I gather the string is a native type so class helpers will not work without
setting up a class etc.
Yes, string is a native type with some special compiler magic added.
I don't know what operator overloading you would want. + and = already work as concatenation and equality operators.
However I've thought about doing something similar myself. It might work with a record type with implicit convertors and overloaded add and equals operators (in Win32 Delphi only records can have operator overloading. This is available in D2006 (?2005) only.)
I suspect there may be some performance hit as well.
The syntax would be something like the following:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TString = record
private
Value : string;
public
class operator Add(a, b: TString): TString;
class operator Implicit(a: Integer): TString;
class operator Implicit(const s: string): TString;
class operator Implicit(ts: TString): String;
function IndexOf(const SubStr : string) : Integer;
end;
var
Form1: TForm1;
implementation
class operator TString.Add(a, b : TString) : TString;
begin
Result.Value := a.Value + b.Value;
end;
class operator TString.Implicit(a: Integer): TString;
begin
Result.Value := IntToStr(a);
end;
class operator TString.Implicit(ts: TString): String;
begin
Result := ts.Value;
end;
function TString.IndexOf(const SubStr : string) : Integer;
begin
Result := Pos(SubStr, Value);
end;
class operator TString.Implicit(const s: string): TString;
begin
Result.Value := s;
end;
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
ts : TString;
begin
ts := '1234';
ShowMessage(ts);
ShowMessage(IntToStr(Ts.IndexOf('2')));
end;
end.
Apparently you can have "record helpers" as well, but I've never tried it myself.
You can use operator overloading in Delphi (since Delphi 2006) only on records not on classes, and not on built-in native types like strings.
The reason is that Delphi does not have garbage collection, so operator overloading is limited to value types (types that are not living on the heap).
You can download the replay of my session "Nullable Types with Records, Methods and Operator Overloading" at the CodeRage III Replay download page.
Just search for the session name.
There is also a page with the download for the session samples and slides.
It contains quite a few examples that get you going, including a description of some issues in the Delphi 2006 compiler that have been solved in Delphi 2007 and up.
See also this question: Can I overload operators for my own classes in Delphi?
Isn't a better solution to write your custom functions / procedures?
For example
Function StrToBase64(AString): string;
Procedure StrToGridLayout(AString: string; AGrid: TDBGrid);
Function ExtractWord(aString): string;
Function GetStrColumn(aString: string; aCol: integer): string;
And if you want to group these functions / procedures which reside in the same unit in functional categories you can use records for this:
TStringConversions = record
class Function StrToBase64(AString): string;
class Procedure StrToGridLayout(AString: string; AGrid: TDBGrid);
end;
TStringParsing = record
class Function ExtractWord(aString): string;
class Function GetStrColumn(aString: string; aCol: integer): string;
end;
And then, you can call them in code in a much cleaner way:
myFirstWord := TStringParsing.ExtractWord('Delphi is a very good tool');
HTH
Related
I have a class I want to pass to a datasnap server, but the class contains this field Picture which should be a TPicture but for now I use an integer to avoid getting the marshall error "tkPointer currently not supported" :(
I have tried omitting a field/property "Picture" from getting marshalled by adding [JSONMarshalled(False)] but with no luck.
I have added the units as suggested in the thread here
JSONMarshalled not working in Delphi
unit TestObjU;
interface
uses
Classes, System.Generics.Collections, System.SyncObjs, System.SysUtils,
JSON, DBXJsonReflect, REST.JSON,
Data.FireDACJSONReflect, FireDAC.Comp.Client, vcl.ExtCtrls,
pngimage, graphics, variants,
GlobalFunctionsU, GlobalTypesU;
{$M+}
{$RTTI EXPLICIT FIELDS([vcPrivate])}
type
EPerson = class(Exception);
EPersonsList = class(Exception);
TGender = (Female, Male);
TPerson = class(TObject)
private
FFirstName: string;
FLastName: string;
FId: Integer;
FGender: TGender;
FModified : Boolean;
[JSONMarshalled(False)]
FPicture: Integer;
// [JSONMarshalled(False)] FPicture : TPicture;
function GetName: string;
procedure SetFirstName(const Value: string);
procedure SetLastName(const Value: string);
function GetId: Integer;
procedure SetGender(const Value: TGender);
procedure SetModified(const Value: Boolean);
public
property Id : Integer read GetId;
property Name : string read GetName;
property FirstName : string read FFirstName write SetFirstName;
property LastName : string read FLastName write SetLastName;
property Gender : TGender read FGender write SetGender;
property Modified : Boolean read FModified write SetModified;
// property Picture : TPicture read FPicture write FPicture;
[JSONMarshalled(False)]
property Picture : Integer read FPicture write FPicture;
function Update : Boolean;
function Delete : Boolean;
constructor Create(AId : Integer; AFirstName, ALastName : string; AGender : TGender); overload;
constructor Create(AFirstName, ALastName : string; AGender : TGender); overload;
destructor destroy; override;
function ToJsonString: string;
end;
But clearly it has no effect on the marshalling, Picture is still there - what am I missing?
function TPerson.ToJsonString: string;
begin
result := TJson.ObjectToJsonString(self);
end;
08-03-2016 10:26:24 [NORMAL] AddPerson serialized {"firstName":"Donald","lastName":"Duck","id":24,"gender":"Female","modified":false,"picture":92415648}
You are using TJson.ObjectToJsonString from REST.Json unit and that one needs different attribute to skip fields named JSONMarshalledAttribute
You should change your code to [JSONMarshalledAttribute(False)]
Delphi has a bit of mix up between older Data.DBXJsonReflect and newer REST.Json units and you should not mix them together in same code. Pick only one of them.
REST.Json.TJson.ObjectToJsonString
REST.Json.Types.JSONMarshalledAttribute
Data.DBXJSONReflect.JSONMarshalled
Yes - I found the solution, when using DBX (and not REST) you'll need add this unit "Data.DBXJSON" rather than the "REST.JSON" and change the two "from/to" methods for un/marshaling the object something like this.
NOTE. ToJSONString leaks for some reason, I'll have to investigate that more.
function TPerson.ToJsonString: string;
var
JSONMarshal: TJSONMarshal;
begin
result := '';
JSONMarshal := TJSONMarshal.Create(TJSONConverter.Create);
try
Result := JSONMarshal.Marshal(self).ToString;
finally
JSONMarshal.Free;
end;
end;
class function TPerson.FromJsonString(AJSONString: string): TPerson;
var
JSONUnMarshal: TJSONUnMarshal;
begin
JSONUnMarshal := TJSONUnMarshal.Create;
try
Result := JSONUnMarshal.Unmarshal(TJSONObject.ParseJSONValue(AJSONString)) as TPerson;
finally
JSONUnMarshal.Free;
end;
end;
I would like to declare a generic record like so:
type
TMyDelegate<T: constraint> = record
private
fDelegate: T;
public
class operator Implicit(a: T): TMyDelegate;
class operator Implicit(A: TMyDelegate: T);
end;
I'd like to limit T to reference to procedure/function. (As much as possible).
I've tried this, but it does not compile:
program Project3;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
TProc1 = reference to procedure(a: Integer);
TProc2 = reference to procedure(b: TObject);
TTest<T: TProc1, TProc2> = record
private
fData: T;
public
class operator Implicit(a: T): TTest<T>;
class operator Implicit(a: TTest<T>): T;
end;
{ TTest<T> }
class operator TTest<T>.Implicit(a: T): TTest<T>;
begin
Result.fData:= a;
end;
class operator TTest<T>.Implicit(a: TTest<T>): T;
begin
Result:= a.fData;
end;
var
Delegate1: TProc1;
Delegate2: TProc2;
var
MyTest1: TTest<TProc1>; <<-- error
MyTest2: TTest<TProc2>;
begin
MyTest1:=
procedure(a: Integer)
begin
WriteLn(IntToStr(a));
end;
end.
This gives compile error:
[dcc32 Error] Project3.dpr(39): E2514 Type parameter 'T' must support interface 'TProc2'
Is there a way to constrain a generic type to (a list of) anonymous types?
David's is the correct answer but as a work around something like this may help:
program Project51;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
TTest<T> = record
type
TProcT = reference to procedure(a: T);
private
fData: TProcT;
public
class operator Implicit(a: TProcT): TTest<T>;
class operator Implicit(a: TTest<T>): TProcT;
end;
{ TTest<T> }
class operator TTest<T>.Implicit(a: TProcT): TTest<T>;
begin
Result.fData:= a;
end;
class operator TTest<T>.Implicit(a: TTest<T>): TProcT;
begin
Result:= a.fData;
end;
var
MyTest1: TTest<Integer>;
MyTest2: TTest<TObject>;
begin
MyTest1:=
procedure(a: Integer)
begin
WriteLn(IntToStr(a));
end;
MyTest2:=
procedure(a: TObject)
begin
WriteLn(a.ClassName);
end;
end.
There is no way to specify such a constraint. Possible constraints are:
Value type.
Class, derived from specific ancestor.
Interface, derived from specific ancestor.
Parameterless constructor.
This is covered in the documentation: http://docwiki.embarcadero.com/RADStudio/en/Constraints_in_Generics
What the documentation does not make clear is that reference procedures types count as interfaces. Which is why your generic type compiles, with that constraint. But this is never any use to you. Because reference procedure types have no inheritance. And so the only thing that can meet a specific reference procedure type constraint is something of that specific type.
In fact your type cannot be instantiated. That's because the constraint
T: TProc1, TProc2
specifies that T supports both of those reference procedure interfaces. And nothing can do that. Nothing can simultaneously support both TProc1 and TProc2.
Thanks to GrayMatter and David I've come up with a solution to the problem.
The solution is to redefine the anonymous procedure to fit within the constraints.
The following functions are defined.
TA = reference to procedure(const &In, &Out: TArray<TOmniValue>);
TB = reference to procedure(const &In, &Out: TArray<IOmniBlockingCollection>);
TC = .....
The trick is to redefine the methods like so:
program Project3;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
IData<Tin, Tout> = interface
['{D2132F82-CAA9-4F90-83A9-9EFD6221ABE2}']
function GetInput: TArray<TIn>;
function GetOutput: TArray<Tout>;
end;
TData<TIn, TOut> = class(TInterfacedObject, IData<Tin, Tout>)
private
fInput: TArray<Tin>;
fOutput: TArray<Tout>;
public
constructor Create(const input: TArray<TIn>; const output: TArray<TOut>);
function GetInput: TArray<Tin>;
function GetOutput: TArray<Tout>;
end;
TDelegate<Tin, Tout> = reference to procedure(const Data: IData<TIn, TOut>);
{ TSimpleData }
constructor TData<TIn, TOut>.Create(const input: TArray<TIn>;
const output: TArray<TOut>);
begin
finput:= input;
foutput:= output;
end;
function TData<Tin, Tout>.GetInput: TArray<Tin>;
begin
Result:= fInput;
end;
function TData<Tin, Tout>.GetOutput: TArray<TOut>;
begin
Result:= fOutput;
end;
var
IntegerDelegate: TDelegate<Integer, Integer>;
input, output: TArray<Integer>;
i: Integer;
Data: TData<Integer, Integer>;
begin
IntegerDelegate:= procedure(const Data: IData<Integer, Integer>)
var
i: Integer;
input: TArray<Integer>;
begin
input:= Data.GetInput;
for i:= 0 to High(input) do begin
Data.GetOutput[i]:= input[i]+10;
end;
end;
SetLength(input,10);
SetLength(output, Length(input));
for i:= Low(input) to High(input) do begin
input[i]:= i;
end;
Data:= TData<Integer, Integer>.Create(input, output);
IntegerDelegate(Data);
for i in output do Writeln(i);
Readln;
end.
I can now limit the delegate to the types allowed (more or less).
program Project15;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.Rtti, System.TypInfo;
type
TRecord = record
public
AField: integer;
constructor Init(test: integer);
end;
TOldObject = object
public
AField: integer;
constructor Init(test: integer);
procedure Fancy; virtual; <<--- compiles
class operator Implicit(test: TRecord): TOldObject; <<-- does not compile.
end;
procedure IsObjectARecord;
var
ARecord: TRecord;
AObject: TOldObject;
v: TValue;
s: String;
begin
v:= TValue.From(ARecord);
case v.Kind of
tkRecord: WriteLn('it''s a Record');
end;
ARecord:= TRecord.Init(10);
AObject.Init(10);
v:= TValue.From(AObject);
case v.Kind of
tkRecord: begin
WriteLn('object is a record?');
if v.IsObject then s:= 'true'
else s:= 'false';
WriteLn('isObject = ' + s);
WriteLn('ToString says: '+v.ToString);
end;
end;
end;
{ TOldSkool }
constructor TOldObject.Init(test: integer);
begin
AField:= 10;
end;
constructor TRecord.Init(test: integer);
begin
AField:= 10;
end;
begin
IsObjectARecord;
Readln;
end.
The outcome of the test proc reads:
ARecord is a Record
AObject is a record?
isObject(AObject) = false
AObject.ToString says: (record)
However object <> record from a functionality point of view.
Object supports inheritance and virtual calls.
Record supports class operators.
Is there a way to tell TP5.5-objects and records apart using RTTI?
Is there even a need to tell them apart -ever-?
Note that I'm not planning to use object, I'm just enumerating types using RTTI so that my generic HashTable with pointers can clean up after itself properly.
Yes I know that object lives on the stack by default (or the heap with special effort) and do not normally need to be freed.
Bonus points if someone knows why virtual calls with TP5.5-objects no longer work, they used to work in Delphi 2007
To the very best of my knowledge, in the eyes of Delphi's RTTI framework, an old-style object cannot be distinguished from a record. This program
{$APPTYPE CONSOLE}
uses
System.Rtti;
type
TOldObject = object
end;
var
ctx: TRttiContext;
RttiType: TRttiType;
begin
RttiType := ctx.GetType(TypeInfo(TOldObject));
Writeln(TValue.From(RttiType.TypeKind).ToString);
Writeln(RttiType.IsRecord);
Readln;
end.
outputs
tkRecord
TRUE
Old object is deprecated.
So you should not use it in conjunction with the new rtti.
First step of deprecation was to disallow virtual methods. Due I suppose to compiler regressions.
This is the Embarcadero decision to mimic C# and his struct / class paradigm. Wrong decision imho.
I am absolutely new at calling functions from DLLs (call it bad programming habits, but I never needed to).
I have this C++ dll (CidGen32.dll at https://skydrive.live.com/redir?resid=4FA1892BF2106B62!1066) that is supposed to export a function with the following signature:
extern "C" __declspec(dllexport) int GetCid(const char* pid, char* cid);
What it should do is to get a 13 char string such as '1111111111118' and return a 20 char hash.
I have tried for the last couple of days to call this function in Delphi 6 but to no avail. I have desperately tried I guess 50+ combinations and I got quite close on one occasion but my computer froze and I lost all my effort. Since it was based on luck, I could not redo it anymore.
I am also aiming not to register the DLL, but rather place it in the same folder.
Anyway, the plan was to have something like this:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
function GenerateCID(Prm: string): string;
var
aCID: PAnsiChar;
uCID: AnsiString;
i: integer;
Hbar: Thandle;
GetCID: function (X: PAnsiChar; Y: PAnsiChar): integer; {$IFDEF WIN32} stdcall; {$ENDIF}
begin
ucid := '';
hbar := LoadLibrary('CidGen32.dll');
if Hbar >= 32 then
begin
#GetCID := GetProcAddress(HBar, 'GetCID');
if Assigned(GetCID) then
begin
i := GetCID(pAnsiChar(prm), aCID);
uCID := aCID;
end;
FreeLibrary(HBar);
end
else
begin
//ShowMessage('Error: could not find dll');
end;
result := uCID;
end;
begin
ShowMessage(GenerateCID('1111111111118'));
end;
end.
But it seems I am dead wrong.
You are using the wrong name to import the function. Its name is GetCid but you are trying to import GetCID. Letter case matters when you call GetProcAddress. If that still doesn't result in the GetProcAddress call succeeding, double check the name with which the function is exported using a tool like Dependency Walker.
The function is cdecl so you should declare it like this:
GetCID: function(pid, cid: PAnsiChar): Integer; cdecl;
And the other problem is that you are responsible for allocating the buffer behind cid. You did not do that. Do it like this:
SetLength(uCID, 20);
i := GetCID(pAnsiChar(prm), pAnsiChar(uCID));
And delete the aCID variable. And that >32 error check is wrong, compare against 0.
Following situation:
type
TRec = record
Member : Integer;
end;
TMyClass = class
private
FRec : TRec;
public
property Rec : TRec read FRec write FRec;
end;
The following doesn't work (left side cannot be assigned to), which is okay since TRec is a value type:
MyClass.Rec.Member := 0;
In D2007 though the following DOES work:
with MyClass.Rec do
Member := 0;
Unfortunately, it doesn't work in D2010 (and I assume that it doesn't work in D2009 either). First question: why is that? Has it been changed intentionally? Or is it just a side effect of some other change? Was the D2007 workaround just a "bug"?
Second question: what do you think of the following workaround? Is it safe to use?
with PRec (#MyClass.Rec)^ do
Member := 0;
I'm talking about existing code here, so the changes that have to be made to make it work should be minimal.
Thanks!
That
MyClass.Rec.Member := 0;
doesn't compile is by design. The fact that the both "with"-constructs ever compiled was (AFAICT) a mere oversight. So both are not "safe to use".
Two safe solution are:
Assign MyClass.Rec to a temporary record which you manipulate and assign back to MyClass.Rec.
Expose TMyClass.Rec.Member as a property on its own right.
In some situtations like this where a record of a class needs 'direct manipulation' I've often resorted to the following:
PMyRec = ^TMyRec;
TMyRec = record
MyNum : integer
end;
TMyObject = class( TObject )
PRIVATE
FMyRec : TMyRec;
function GetMyRec : PMyRec;
PUBLIC
property MyRec : PMyRec << note the 'P'
read GetMyRec;
end;
function TMyObject.GetMyRec : PMyRec; << note the 'P'
begin
Result := #FMyRec;
end;
The benefit of this is that you can leverage the Delphi automatic dereferencing to make readable code access to each record element viz:
MyObject.MyRec.MyNum := 123;
I cant remember, but maybe WITH works with this method - I try not to use it!
Brian
The reason why it can't be directly assigned is here.
As for the WITH, it still works in D2009 and I would have expected it to work also in D2010 (which I can't test right now).
The safer approach is exposing the record property directly as Allen suggesed in the above SO post:
property RecField: Integer read FRec.A write FRec.A;
Records are values, they aren't meant to be entities.
They even have assignment-by-copy semantics! Which is why you can't change the property value in-place. Because it would violate the value type semantics of FRec and break code that relied on it being immutable or at least a safe copy.
They question here is, why do you need a value (your TRec) to behave like an object/entity?
Wouldn't it be much more appropriate for "TRec" to be a class if that is what you are using it for, anyways?
My point is, when you start using a language feature beyond its intent, you can easily find yourself in a situation where you have to fight your tools every meter on the way.
The reason it has been changed is that it was a compiler bug. The fact that it compiled didn't guarantee that it would work.
It would fail as soon as a Getter was added to the property
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm2 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
FPoint: TPoint;
function GetPoint: TPoint;
procedure SetPoint(const Value: TPoint);
{ Private declarations }
public
{ Public declarations }
property Point : TPoint read GetPoint write SetPoint;
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
procedure TForm2.Button1Click(Sender: TObject);
begin
with Point do
begin
X := 10;
showmessage(IntToStr(x)); // 10
end;
with Point do
showmessage(IntToStr(x)); // 0
showmessage(IntToStr(point.x)); // 0
end;
function TForm2.GetPoint: TPoint;
begin
Result := FPoint;
end;
procedure TForm2.SetPoint(const Value: TPoint);
begin
FPoint := Value;
end;
end.
You code would suddenly break, and you'd blame Delphi/Borland for allowing it in the first place.
If you can't directly assign a property, don't use a hack to assign it - it will bite back someday.
Use Brian's suggestion to return a pointer, but drop the With - you can eaisly do Point.X := 10;
Another solution is to use a helper function:
procedure SetValue(i: Integer; const Value: Integer);
begin
i := Value;
end;
SetValue(MyClass.Rec.Member, 10);
It's still not safe though (see Barry Kelly's comment about Getter/Setter)
/Edit: Below follows the most ugly hack (and probably the most unsafe as well) but it was so funny I had to post it:
type
TRec = record
Member : Integer;
Member2 : Integer;
end;
TMyClass = class
private
FRec : TRec;
function GetRecByPointer(Index: Integer): Integer;
procedure SetRecByPointer(Index: Integer; const Value: Integer);
public
property Rec : TRec read FRec write FRec;
property RecByPointer[Index: Integer] : Integer read GetRecByPointer write SetRecByPointer;
end;
function TMyClass.GetRecByPointer(Index: Integer): Integer;
begin
Result := PInteger(Integer(#FRec) + Index * sizeof(PInteger))^;
end;
procedure TMyClass.SetRecByPointer(Index: Integer; const Value: Integer);
begin
PInteger(Integer(#FRec) + Index * sizeof(PInteger))^ := Value;
end;
It assumes that every member of the record is (P)Integer sized and will crash of AV if not.
MyClass.RecByPointer[0] := 10; // Set Member
MyClass.RecByPointer[1] := 11; // Set Member2
You could even hardcode the offsets as constants and access directly by offset
const
Member = 0;
Member2 = Member + sizeof(Integer); // use type of previous member
MyClass.RecByPointer[Member] := 10;
function TMyClass.GetRecByPointer(Index: Integer): Integer;
begin
Result := PInteger(Integer(#FRec) + Index)^;
end;
procedure TMyClass.SetRecByPointer(Index: Integer; const Value: Integer);
begin
PInteger(Integer(#FRec) + Index)^ := Value;
end;
MyClass.RecByPointer[Member1] := 20;