How to add type-specific code for generics in Delphi - delphi

Is there any efficient way to add type specific code for delphi generics ?
For example something like this:
function TGT<T>.GetSize(a: T): integer;
begin
{$IF TypeInfo(T)=TypeInfo(String)}
result := Length(A);
{$ELSE}
result := SizeOf(A);
{$IFEND}
end;
function TGT<T>.Compare(a,b: T): integer;
begin
{$IF TypeInfo(T)=TypeInfo(String)}
result := AnsiCompareText(a,b);
{$ELSE}
result := a-b;
{$IFEND}
end;
So i need to implement some parts of the code in different ways depending on type.
For example if i implement Sort routine i would like to use direct comparison of values of integer/double/etc types (it is more efficient than calling of interface methods like delphi's standard generic containers do) and function AnsiCompareText for String type.
There some examples how to do it, but all of them based on check like this:
if TypeInfo(T)=TypeInfo(String) then xxx else if TypeInfo(T)=TypeInfo(Integer) then xxx
Problem here is that Delphi will check it in run-time only, that is (again) not so efficient.
I would like to make compiler to do all checks in compile-time and use only code specific for selected type.

Well, you certainly cannot hope to do anything like that with conditional compilation. Remember that conditional compilation for generics are handled in the generic compilation phase rather than the instantiation phase. And so you cannot expect different instantiations to be compiled with different branches of your conditional statement.
And you certainly can never get the compiler to accept Length(a) where the type of a is parametrised, because there is no way to specify a constraint that would allow the use of Length.
The only option is a run-time check.

Related

How to trigger a compile-time error when using GetTypeKind

In XE7 we have the new compiler intrinsic function GetTypeKind (as yet undocumented) that allows us to extract the flavor of a type at compile time.
The following code will generate a run-time error if the wrong type is used:
//make sure T is a procedure/function
procedure TDetours<T>.CheckTType;
{$IF CompilerVersion >= 28}
begin
// XE7 and up:Resolve all tests at compiletime.
if (Sizeof(T) <> Sizeof(Pointer)) or (GetTypeKind(T) <> tkProcedure) then begin
raise DetourException.Create('T must be a procedure or function');
end;
{$ELSE}
//Code for pre-XE7 versions
{$ENDIF}
end;
I want the compiler to generate a compile-time error if the correct type kind is not used.
This allows be to catch any errors at an earlier stage.
Is this possible?
My line of thinking is as follows:
- If the test is false then the code inside the test does not get generated.
- If the test is true then code does get generated.
Is there some code that I can put into the test that will trip up the compiler when generating code, but does not make the parser stop working?
In XE7 we have the new compiler intrinsic function GetTypeKind (as yet undocumented) that allows us to extract the flavor of a type at compile time.
In order for you to be able to do this, you would need to be able to put GetTypeKind into a conditional expression. So that you might write code like this:
{$IF GetTypeKind(T) <> tkProcedure}
{$Message Fatal 'Invalid type'}
{$ENDIF}
But the compiler does not accept this. The compiler requires the expression in the $IF conditional to be a constant expression, and GetTypeKind(T) <> tkProcedure is not.
I want the compiler to generate a compile-time error if the correct type kind is not used. This allows be to catch any errors at an earlier stage. Is this possible?
This is not possible. The only mechanism you have available is generic constraints. And generic constraints do not have sufficient granularity to specify the constraint that you require.
I think that your best bet is to put an assertion in the class constructor. It would look like this:
class constructor TDetours<T>.CreateClass;
begin
Assert(Sizeof(T) = Sizeof(Pointer));
Assert(GetTypeKind(T) = tkProcedure);
end;

Why does the is operator fail to return what I expect when passed an instance from a different module?

I work on Delphi project who interac with many other small libraries.
I use FastMM4 and I would like work with complex classes passed on dll parameter.
So for exemple I send my form to my dll. Into the dll I test the type of parameter with the operator "IS".
But into the Dll the operator "IS" return always "false"
Exemple
library Dll;
uses
FastMM4,
System.SysUtils,
System.Classes,
Vcl.Dialogs,
Vcl.Forms;
{$R *.res}
procedure Complex(L : TObject);stdcall;
begin
if L is TForm then
showmessage('Ok')
else
showmessage('Pas ok') ;
if L is TCustomFrame then
showmessage('Ok')
else
showmessage('Pas ok')
end;
exports
Complex;
begin
end.
And the call
procedure TffsIsOperator.Button2Click(Sender: TObject);
var
MaDLL : THandle;
Proc : procedure (l : TObject);
begin
try
MaDLL := LoadLibrary(PChar('Dll.dll'));
#Proc := GetProcAddress(MaDLL, 'Complex');
Proc(self);
finally
FreeLibrary(MaDLL);
end;
end;
Firstly, you have a calling convention mis-match. You must fix that by making the calling convention the same on both sides of the interop boundary.
Even when you fix that, the apparent misbehaviour of the is operator is to be expected. You have two instances of the VCL in your process. One in the host and one in the DLL. They each have distinct versions of the classes defined in the VCL. So, the DLL's TForm is a different class form the TForm in the host. And that is why is evaluates false.
The traditional way to handle this is to arrange that you only have one instance of the RTL/VCL in your process. And you achieve that through the use of runtime packages.
If runtime packages are not a viable option for you, and you must use a DLL, then you will have to give up passing any Delphi classes across the DLL boundary. I fully expect this to be unwelcome news, but that is just how it is. You cannot pass TObject instances across a DLL boundary and attempt to call methods, query type identity, etc. That is simply not supported for DLLs. Only for runtime packages.
So, if you have to use DLLs then you need to stick to simple types. Integers, floating point values, character types, arrays (but not dynamic arrays), records, pointers to such types, interfaces. As a simple rule of thumb, if you cannot find an example of your proposed interop in Win32, then it is probably invalid.

Access Violation when handling forms

I have procedure to show/hide one element on TForm like that:
procedure ShowHideControl(const ParentForm: TForm; const ControlName: String; ShowControl: Boolean);
var
i: Integer;
begin
for i := 0 to pred(ParentForm.ComponentCount) do
begin
if (ParentForm.Components[i].Name = ControlName) then
begin
if ShowControl then
TControl(ParentForm.Components[i]).Show
else
TControl(ParentForm.Components[i]).Hide;
Break;
end;
end;
end;
then I try to use it like:
procedure TForm1.Button6Click(Sender: TObject);
begin
ShowHideEveryControl(TForm(TForm1), 'Button4', True);
end;
Why do I get Access Violation on Button6 click?
For me everything is OK... Button4 exists as a child :)
This cast is wrong:
TForm(TForm1)
You are telling the compiler to ignore the fact that TForm1 is not a TForm instance, and asking it to pretend that it is. That is fine until you actually try to use it as an instance, and then the error occurs.
You need to pass a real instance to a TForm descendent. You can write it like this:
ShowHideEveryThing(Self, 'Button4', True);
Just in case you are not clear on this, the parameter of your procedure is of type TForm. That means you need to supply an instance of a class that either is, or derives from TForm. I repeat, you must supply an instance. But you supply TForm1 which is a class.
And then the next problem comes here:
if (ParentForm.Components[i].Name = FormName) then
begin
if ShowForm then
TForm(ParentForm.Components[i]).Show
else
TForm(ParentForm.Components[i]).Hide;
Break;
end;
Again you have used an erroneous cast. When the compiler tells you that it a particular object does not have a method, you must listen to it. It's no good telling the compiler to shut up and pretend that an object of one type is really an object of a different type. Your button is categorically not a form, so don't try to cast it to TForm.
It is very hard to know what you are actually trying to do here. When you write:
ShowHideEveryThing(Self, 'Button4', True);
It would seem to me to me more sensible to write:
Button4.Show;
It is not a good idea to refer to controls using their names represented as text. It is much safer and cleaner to refer to them using reference variables. That way you let the compiler do its job and check the type safety of your program.
The names used in your function are suspect:
procedure ShowHideEveryThing(const ParentForm: TForm; const FormName: String;
ShowForm: Boolean);
Let's look at them:
ShowHideEveryThing: but you claim that the function should show/hide one element. That does not tally with the use of everything.
FormName: you actually pass a component name, and then look for components owned by ParentForm that have that name. It seems that FormName is wrong.
ShowForm: again, do you want to control visibility of the form, or a single element?
Clearly you need to step back and be clear on the intent of this function.
As an aside, you should generally never need to write:
if b then
Control.Show
else
Control.Hide;
Instead you can write:
Control.Visible := b;
My number one piece of advice to you though is to stop casting until you understand it properly. Once you understand it properly, design your code if at all possible so that you don't need to cast. If you ever really do need to cast, make sure that your cast is valid, ideally by using a checked cast with the as operator. Or at least testing first with the is operator.
Your code shows all the hallmarks of a classic mistake. The compiler objects to the code that you write. You have learnt from somewhere that casting can be used to suppress these compiler errors and now you apply this technique widely as a means to make your program compile. The problem is that the compiler invariably knows what it is talking about. When it objects, listen to it. If ever you find yourself suppressing a compiler error with a cast, take a step back and think carefully about what you are doing. The compiler is your friend.

Implementing Custom Binary Search for TObjectList<myClass> (Delphi XE)

I need to implement a binary search on TObjectList that uses a custom comparer, I believe using TCustomComparer.
Goal: binary search returns instances in the list that conform to a particular property parameter.
For example:
TMyClass=class
public
Index:integer
end;
TMyObjectList=TObjectList<TMyClass>;
begin
...
aMyClass.Index:=1;
aMyObjectList.binarysearch(aMyClass, aMyClassRef)
...
end;
Or simply:
begin
...
aMyObjectList.binarysearch(1, aMyClassRef)
...
end;
I want to loop and get back instances of TMyClass in the list that also have Index==1.
In C++, overloading the '==' operator achieves this goal.
The new Delphi 'help' is rather sparse and scattered around making things hard to find, and I'm not that familiar with all the nuances of the new Delphi generics.
So - how do I do it in Delphi XE with the Generics.TObjectList?
(Using Delphi XE).
TIA
The help file is indeed a little limited here. I generally just read the source code to Generics.Defaults and Generics.Collections. Anyway, you need to provide an IComparer<TMyClass>. There's lots of ways to do that. For example, using an anonymous function:
var
List: TObjectList<TMyClass>;
Target: TMyClass;
Index: Integer;
Comparer: IComparer<TMyClass>;
Comparison: TComparison<TMyClass>;
....
Comparison :=
function(const Left, Right: TMyClass): Integer
begin
//Result := ??;//your comparison rule goes here
end;
Comparer := TComparer<TMyClass>.Construct(Comparison);
List.BinarySearch(Target, Index, Comparer);
If you don't want to use an anonymous function you can implement Comparison some other way. For example a method of some object, or a class function, or even just a plain old fashioned non-OOP function. It just has to have the same signature as above.
As always with comparison functions, return <0 if Left<Right, >0 if Left>Right and 0 if Left=Right.

Can I use generics to do the same operation on similar types of controls?

I am using Delphi 2010 and I have a unit where over the years I have added my own procedures and functions that can be used with any project I make, such as:
function ListBoxIsSelected(ListBox: TListBox): Boolean;
begin
Result:= ListBox.ItemIndex <> -1;
end;
The above uses TListBox as a parameter, so whenever the above function is used I must supply a listbox that is of TListBox class.
Now suppose I have some other component libraries that could work with the same function, For example the Jedi component classes.
How could I use the above function, when the Jedi listbox is TJvListBox class and my function is looking for TListBox class? Although both components are practically the same, the class names are different. If I provided the same function specifically for the TJvListBox it would likely work because they are both "listboxes":
function ListBoxIsSelected(ListBox: TJvListBox): Boolean;
begin
Result:= ListBox.ItemIndex <> -1;
end;
Now, I have whole load of procedures and functions written in the same kind of way where I need to pass a component as a parameter. Having to rewrite them again just to work with a different component class is not feasible!
How can I write this with generics?
You can't write that with generics, unless your target classes all descend from the same base class of course. (But then you wouldn't need generics for it.)
If you really want something that can check if the ItemIndex property on any object <> -1, though, you can do that with a different Delphi 2010 feature: extended RTTI.
uses
SysUtils, RTTI;
function IsSelected(item: TObject): boolean;
var
context: TRttiContext;
cls: TRttiType;
prop: TRttiProperty;
ItemIndex: integer;
begin
if item = nil then
raise Exception.Create('Item = nil');
context := TRttiContext.Create;
cls := context.GetType(item.ClassType);
prop := cls.GetProperty('ItemIndex');
if prop = nil then
raise Exception.Create('Item does not contain an ItemIndex property.');
ItemIndex := prop.GetValue(item).AsInteger;
result := ItemIndex <> -1;
end;
Careful, though. There's no compile-time type checking here, and this process is significantly slower than your original routine. You probably won't notice it, but if you call something like this in a tight loop, it will slow it down.
I don't understand how I can write this with Generics?
You can’t – not unless your component implements a common interface or inherits from a common base class with the standard ListBox, and that interface / base class offers the ItemIndex property.
In fact, this use-case isn’t such a great example of generics because using an interface or base class in the declaration would work just as well.
In this case, you can write two overloaded functions, one expecting TJvListBox and the other expecting TListBox.
In more complex cases this approach may not apply so well, but I think your case is simple enough for this solution.
I cannot look it up right now (on holiday, no Delphi), but don't TJvListBox and TListBox descend from a common ancestor (my guess would be: TCustomListBox)? In that case something like this should work:
interface
function TListBox_IsItemSelected(_ListBox: TCustomListBox): boolean;
implementation
function TListBox_IsItemSelected(_ListBox: TCustomListBox): boolean;
begin
Result := _ListBox.ItemIndex <> -1;
end;
Just in case ItemIndex (as I said: I cannot check right now) is protected in TCustomListBox, you can just use a typecast hack:
type
TListBoxHack = class(TCustomListBox)
end;
function TListBox_IsItemSelected(_ListBox: TCustomListBox): boolean;
begin
Result := TListBoxHack(_ListBox).ItemIndex <> -1;
end;
(I just thought I should mention this since the original question has already been answered: Using Generics does not help here.)

Resources