I am attempting to reference a procedure as a parameter of another procedure and am having trouble understanding the documentation.(http://docwiki.embarcadero.com/RADStudio/Sydney/en/Procedural_Types_(Delphi))
From what I understood I need to create a new type for the procedure..
type
TCallback = procedure of object;
and declare the higher order procedure as
procedure HigherOrder(pProc: TCallback);
I receive the compilation error " E2010 Incompatible types: 'TCallBack' and 'procedure, untyped pointer or untyped parameter' " when attempting to call the function(when the button is clicked)
type
TCallBack = procedure of object;
TfrmMain = class(TForm)
btnAct: TButton;
procedure btnActClick(Sender: TObject);
private
procedure HigherOrder(pProc: TCallback);
procedure Callback();
{ Private declarations }
public
{ Public declarations }
end;
var
frmMain: TfrmMain;
implementation
{$R *.dfm}
{ TfrmMain }
procedure TfrmMain.btnActClick(Sender: TObject);
begin
HigherOrder(Callback()); <--Error occurs here
end;
procedure TfrmMain.Callback;
begin
//Do some stuff
end;
procedure TfrmMain.HigherOrder(pProc: TCallback);
begin
//Do some other stuff
pProc();
end;
end.
Any help is greatly appreciated. I am quite new to programming in delphi.
The problem is that you are calling Callback() first and then trying to pass its return value (which, it doesn't have one) to HigherOrder(), but that is not what HigherOrder() is expecting, which is why you are getting the error. In other words, your code is roughly equivalent to this:
procedure TfrmMain.btnActClick(Sender: TObject);
begin
//HigherOrder(Callback());
var res := Callback();
HigherOrder(res);
end;
Except that the type of res is undefined since Callback() is a procedure and not a function.
When calling HigherOrder(), you need to remove the trailing () parenthesis from Callback() in order to pass Callback itself (well, its memory address, anyway) as the value of the pProc parameter, eg:
procedure TfrmMain.btnActClick(Sender: TObject);
begin
HigherOrder(Callback);
end;
Yes, you can also drop the parenthesis when calling a procedure without passing any parameters to it. But, in this case, the compiler is smart enough to know that the parenthesis-omitting Callback identifier is being assigned to a closure type and so will pass it as-is and not call it.
Related
I have a problem while loading procedures from a dll, either when loading it dynamically or statically. When I put procedures from dll to my unit, everything works fine. When I try to do it with dll it gives me
First chance exception at $00526399. Exception class $C0000005 with message 'access violation at 0x00526399: read of address 0x00000390'. Process Project1.exe (21988)
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls,Unit2;
type
TForm1 = class(TForm)
ListView1: TListView;
Button1: TButton;
Button2: TButton;
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
Edit4: TEdit;
Edit5: TEdit;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Refresh;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
type
plist = ^element;
element = record
artist,title,genre: string[20];
year,grade: integer;
wsk: plist;
end;
database = file of element;
var
base: database;
first: plist;
handler: HModule;
{$R *.dfm}
procedure TForm1.Refresh();
var
current: plist;
begin
ListView1.Clear;
current:= first;
while current<>nil do
begin
with ListView1.Items.Add do
begin
Caption:=current^.artist;
SubItems.Add(current^.title);
SubItems.Add(current^.genre);
SubItems.Add(IntToStr(current^.year));
SubItems.Add(IntToStr(current^.grade));
end;
current:=current^.wsk;
end;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var Save: procedure;
begin
handler:=LoadLibrary('lib.dll');
try
#Save:=GetProcAddress(handler, PChar(2));
if #Save = nil then raise Exception.Create('Load nie dziala');
Save();
finally
FreeLibrary(handler);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
Load: procedure;
begin
handler:=LoadLibrary('lib.dll');
try
#Load:=GetProcAddress(handler, PChar(1));
if #Load = nil then raise Exception.Create('Load nie dziala');
Load();
finally
FreeLibrary(handler);
end;
Refresh();
end;
procedure TForm1.Button1Click(Sender: TObject);
var
el: element;
Add: procedure(el:element);
begin
el.artist:=Edit1.Text;
el.title:=Edit2.Text;
el.genre:=Edit3.Text;
el.year:=StrToInt(Edit4.Text);
el.grade:=StrToInt(Edit5.Text);
handler:=LoadLibrary('lib.dll');
try
#Add:=GetProcAddress(handler, PChar(3));
if #Add = nil then raise Exception.Create('Load nie dziala');
Add(el);
finally
FreeLibrary(handler);
Refresh();
{Form2:=TForm2.Create(Form1);
Form2.ShowModal;
Form2.Free;}
end;
end;
end.
The dll file looks like this:
library lib;
{ Important note about DLL memory management: ShareMem must be the
first unit in your library's USES clause AND your project's (select
Project-View Source) USES clause if your DLL exports any procedures or
functions that pass strings as parameters or function results. This
applies to all strings passed to and from your DLL--even those that
are nested in records and classes. ShareMem is the interface unit to
the BORLNDMM.DLL shared memory manager, which must be deployed along
with your DLL. To avoid using BORLNDMM.DLL, pass string information
using PChar or ShortString parameters. }
uses
System.SysUtils,
System.Classes;
{$R *.res}
type plist = ^element;
element = record
artist,title,genre:string[20];
year,grade:integer;
wsk: plist;
end;
database = file of element;
var
first: plist;
base: database;
procedure add(el: element); stdcall;
var current,tmp: plist;
begin
New(current);
current^ := el;
current^.wsk := nil;
if first = nil then
begin
first:=current;
end else
begin
tmp:=first;
while tmp^.wsk<>nil do
begin
tmp:=tmp^.wsk;
end;
tmp^.wsk:=current;
end;
end;
procedure load();stdcall;
var
el: element;
i: integer;
begin
AssignFile(base, 'baza.dat');
if not FileExists('baza.dat') then
begin
Rewrite(base);
end else
begin
Reset(base);
for i := 0 to FileSize(base)-1 do
begin
read(base, el);
add(el);
end;
end;
CloseFile(base);
end;
procedure save();stdcall;
var
current: plist;
el: element;
begin
AssignFile(base, 'baza.dat');
Rewrite(base);
current:=first;
while current<>nil do
begin
el:=current^;
el.wsk:=nil;
write(base, el);
current:= current^.wsk;
end;
end;
exports
add index 1,
load index 2,
save index 3;
begin
end.
It also shows me an error:
Expected ';' but received and identifier 'index' at line 91
But exports are done like I red on web.
The obvious errors are:
You don't perform much error checking. You assume that the calls to LoadLibrary always succeed.
The calling conventions don't match. You use stdcall in the DLL and register in the executable.
The ordinals don't match. In the DLL it is add (1), load (2) and save (3). In the executable you have add (3), load (1) and save (2).
You load and unload the DLL every time you call functions from the DLL. That means that the global variables in the DLL that hold your state are lost each time the DLL is unloaded.
Frankly this code is a real mess. I suggest that you do the following:
Switch to load time linking using the function names rather than ordinals. This means to use the external keyword in the executable. This will greatly simplify your code by removing all those calls to LoadLibrary, GetProcAddress etc. If runtime linking is needed, you can add it later using the delayed keyword.
Stop using global state in the DLL and instead pass information back and forth between modules. Remove all global variables. But make sure you don't pass Delphi objects back and forth.
Use PChar rather than short strings across the module boundary.
Stop using linked lists and dynamic allocation. That's hard to get right. Use TList<T> in the DLL to store the list of elements.
I want a procedure to be executed when an event is happened. But that procedure is set by another procedure(SetNotifierProc).
Firstly I run this:
SetNotifierProc(Proc1);
And then Proc1 is executed whenever event triggered.
How could I code SetNotifierProc to get a procedure as an argument and how to inform event handler to execute that procedure?
Problem: I have a TCPServerExecute and want to run a procedure to show received data. But because I have multiple forms I want to set a procedure that handle received data.
Thanks
If your procedure is an ordinary procedure without arguments:
Type
TForm1 = Class(TForm)
..
private
FMyProc : TProcedure;
public
procedure SetEventProc(aProc : TProcedure);
procedure TheEvent( Sender : TObject);
end;
procedure Test;
begin
// Do something
end;
procedure TForm1.SetEventProc(aProc: TProcedure);
begin
Self.FMyProc := aProc;
end;
procedure TForm1.TheEvent(Sender: TObject);
begin
if Assigned(FMyProc) then
FMyProc;
end;
// to set the callback to procedure "Test"
Form1.SetEventProc(Test);
If your procedure has arguments, declare a procedure type:
Type
MyProcedure = procedure( aString : String);
And if your procedure is a method :
Type
MyMethod = procedure( aString : String) of Object;
See also documentation about Procedural types.
This should do the trick :-
Type
TTCPNotifyProc = Procedure(pData : String) Of Object;
TMyTCPServer = Class
Private
FNotifyProc : TTCPNotifyProc;
..
Public
Procedure SetNotifier(pProc : TTCPNotifyProc);
End;
Procedure TMyTCPServer.SetNotifier(pProc : TTCPNotifyProc);
Begin
FNotifyProc := pProc;
End;
Then whenever you need to call the procedure within your server class just call :-
If Assigned(FNotifyProc) Then
FNotifyProc(DataStringReceived);
I'm trying to extend a protected virtual method of TObjectDispatch. But this method never gets called.
[edited to reproduce the problem].
When I override GetPropInfo and use it in TMyDispatch it works as expected. The overrided method is called. However the overrided method on TMyDispatchItem when created by TMyDispatch (to simulate my real world example) is not called.
{$METHODINFO ON}
TExtDispatch = class(TObjectDispatch)
protected
function GetPropInfo(const AName: string; var AInstance: TObject;
var CompIndex: Integer): PPropInfo; override;
public
constructor Create;
end;
TMyDispatchItem = class(TExtDispatch)
private
FItemValue: string;
public
procedure ShowItemValue;
published
property ItemValue: string read FItemValue write FItemValue;
end;
TMyDispatch = class(TExtDispatch)
public
function GetItem: TMyDispatchItem;
private
FValue: string;
public
procedure ShowValue;
published
property Value: string read FValue write FValue;
end;
{$METHODINFO OFF}
TTestForm = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
TestForm: TTestForm;
implementation
{$R *.dfm}
procedure TTestForm.Button1Click(Sender: TObject);
var
V: Variant;
VI: Variant;
begin
V := IDispatch(TMyDispatch.Create);
V.Value := 100; //this calls inherited getpropinfo
V.ShowValue;
VI := V.GetItem;
VI.ItemValue := 5; //this doesn't
VI.ShowItemValue;
end;
{ TExtDispatch }
constructor TExtDispatch.Create;
begin
inherited Create(Self, False);
end;
function TExtDispatch.GetPropInfo(const AName: string; var AInstance: TObject;
var CompIndex: Integer): PPropInfo;
begin
Result := inherited GetPropInfo(AName, AInstance, CompIndex);
ShowMessage('GetPropInfo: ' + AName);
end;
{ TMyDispatch }
function TMyDispatch.GetItem: TMyDispatchItem;
begin
Result := TMyDispatchItem.Create;
end;
procedure TMyDispatch.ShowValue;
begin
ShowMessage('My dispatch: ' + Value);
end;
{ TMyDispatchItem }
procedure TMyDispatchItem.ShowItemValue;
begin
ShowMessage('My item value: ' + FItemValue);
end;
end.
I've actually found a way to overcome this problem by changing the datatype of TMyDispatch.GetItem to return as a Variant instead. Like this:
function TMyDispatch.GetItem: Variant;
begin
Result := IDispatch(TMyDispatchItem.Create);
end;
And now suddenly the overrided method is called. I really would like to understand what's going on here.
Any more ideas or explainations?
Virtual method dispatch in Delphi is known to work. So, if TExtDispatch.GetPropInfo is not being executed then these are the possible reasons:
The GetPropInfo method is not being called at all.
The actual instance on which GetPropInfo is being called is not an instance of TExtDispatch.
If you showed the rest of the code then we could be more sure, but the above options should be enough for you to work it out.
The only place that calls GetPropInfo is GetIDsOfNames. If your overridden GetIDsOfNames doesn't call GetPropInfo then nothing else will.
Considering your updated code, I ran it under the debugger. When the button is clicked, TObjectDispatch.GetPropInfo is called twice. The first time it is called as a result of the call to inherited GetPropInfo() in TExtDispatch.GetPropInfo. The second time it is called you can inspect ClassName to find out what class Self is. When you do that you will find that ClassName evaluates to 'TObjectDispatch'. In which case, item 2 from my list is the explanation.
I don't really understand what you are trying to do here. However, I suspect that your problem stems from the way GetItem is implemented. I suspect it should be like this:
function TMyDispatch.GetItem: IDispatch;
begin
Result := TMyDispatchItem.Create;
end;
There should have been alarm bells going off when you assigned the return value of a TInterfacedObject constructor to an object reference. That's always an error. You must assign that to an interface reference.
I expect that what happens is that the dispatch code will use an IDispatch if it encounters one, but if it finds an instance of a class instead it creates a new IDispatch to do the work. And that's the third instance of TObjectDispatch.
type
TMyForm= class(TForm)
sg : TStringGrid;
imgSortIt: TImage;
...
procedure imgSortItClick(Sender: TObject);
private
{ Private declarations }
// sortIt: TFMXObjectSortCompare;
function sortIt(item1, item2: TFmxObject): Integer;
public
{ Public declarations }
end;
var
frm: TMyForm;
implementation
{$R *.fmx}
procedure TMyForm.imgSortItClick(Sender: TObject);
begin
sg.Sort(???);
...
Hi,
I know how to switch rows to manually sort a grid...
But as a TSTringGrid has a procedure Sort, I try to use it with my own comparison function with this procedure...
How should I structure the type/function to make it work?
Actually, I get:
E2009 Incompatible types: 'regular procedure and method pointer'
or it compiles with a function declared like this: sortIt: TFMXObjectSortCompare; but how to implement the code to sort like I wish?
Thanks for your help.
You are looking at the XE3 documentation, according to which TFmxObjectSortCompare is declared as:
reference to function(Right, Left: TFmxObject): Integer;
In XE2, unfortunately, TFmxObjectSortCompare is declared like this:
function(item1, item2: TFmxObject): Integer;
So you will need to supply a regular procedure. That is, sortIt is not allowed to be a method of a class and must be just a plain old function:
function sortIt(item1, item2: TFmxObject): Integer;
begin
Result := ...
end;
I suspect that this was a design error in the XE2 FMX code. The sort compare function is much more flexible as reference to, which presumably is why it was changed.
Is it possible to pass interface's method as parameters?
I'm trying something like this:
interface
type
TMoveProc = procedure of object;
// also tested with TMoveProc = procedure;
// procedure of interface is not working ;)
ISomeInterface = interface
procedure Pred;
procedure Next;
end;
TSomeObject = class(TObject)
public
procedure Move(MoveProc: TMoveProc);
end;
implementation
procedure TSomeObject.Move(MoveProc: TMoveProc);
begin
while True do
begin
// Some common code that works for both procedures
MoveProc;
// More code...
end;
end;
procedure Usage;
var
o: TSomeObject;
i: ISomeInterface;
begin
o := TSomeObject.Create;
i := GetSomeInterface;
o.Move(i.Next);
// somewhere else: o.Move(i.Prev);
// tested with o.Move(#i.Next), ##... with no luck
o.Free;
end;
But it is not working because:
E2010 Incompatible types: 'TMoveProc' and 'procedure, untyped pointer or untyped parameter'
Of course I can do private method for each call, but that is ugly. Is there any better way?
Delphi 2006
Edit:
I know that I can pass whole interface, but then I have to specify which function use. I don't want two exactly same procedures with one different call.
I can use second parameter, but that is ugly too.
type
SomeInterfaceMethod = (siPred, siNext)
procedure Move(SomeInt: ISomeInterface; Direction: SomeInterfaceMethod)
begin
case Direction of:
siPred: SomeInt.Pred;
siNext: SomeInt.Next
end;
end;
Thanks all for help and ideas. Clean solution (for my Delphi 2006) is Diego's Visitor. Now I'm using simple ("ugly") wrapper (my own, same solution by TOndrej and Aikislave).
But true answer is "there is no (direct) way to pass interface's methods as parameters without some kind of provider.
If you were using Delphi 2009, you could do this with an anonymous method:
TSomeObject = class(TObject)
public
procedure Move(MoveProc: TProc);
end;
procedure Usage;
var
o: TSomeObject;
i: ISomeInterface;
begin
o := TSomeObject.Create;
i := GetSomeInterface;
o.Move(procedure() begin i.Next end);
The problem with trying to pass a reference to just the interface method is that you are not passing a reference to the interface itself, so the interface cannot be reference counted. But anonymous methods are themselves reference counted, so the interface reference inside the anonymous method here can be reference counted as well. That is why this method works.
I don't know the exact reason why you need to do that, but, personally, I think it would be better to pass the whole "Mover" object instead of one of its methods. I used this approach in the past, it's called "Visitor" pattern.
tiOPF, an object persistence framework, uses it extensively and gives you a good example of how it works: The Visitor Pattern and the tiOPF.
It's relatively long, but it proved very useful to me, even when I didn't use tiOPF. Note step 3 in the document, titled "Step #3. Instead of passing a method pointer, we will pass an object".
DiGi, to answer your comment: If you use Visitor pattern, then you don't have an interface implementing multiple methods, but just one (Execute). Then you'd have a class for each action, like TPred, TNext, TSomething, and you pass an instance of such classes to the object to be processed. In such way, you don't have to know what to call, you just call "Visitor.Execute", and it will do the job.
Here you can find a basic example:
interface
type
TVisited = class;
TVisitor = class
procedure Execute(Visited: TVisited); virtual; abstract;
end;
TNext = class(TVisitor)
procedure Execute (Visited: TVisited); override;
end;
TPred = class(TVisitor)
procedure Execute (Visited: TVisited); override;
end;
TVisited = class(TPersistent)
public
procedure Iterate(pVisitor: TVisitor); virtual;
end;
implementation
procedure TVisited.Iterate(pVisitor: TVisitor);
begin
pVisitor.Execute(self);
end;
procedure TNext.Execute(Visited: TVisited);
begin
// Implement action "NEXT"
end;
procedure TPred.Execute(Visited: TVisited);
begin
// Implement action "PRED"
end;
procedure Usage;
var
Visited: TVisited;
Visitor: TVisitor;
begin
Visited := TVisited.Create;
Visitor := TNext.Create;
Visited.Iterate(Visitor);
Visited.Free;
end;
Although the wrapper class solution works, I think that's an overkill. It's too much code, and you have to manually manage the lifetime of the new object.
Perhaps a simpler solution would be to create methods in the interface that returns TMoveProc
ISomeInterface = interface
...
function GetPredMeth: TMoveProc;
function GetNextMeth: TMoveProc;
...
end;
The class that implements the interface can provide the procedure of object and it will be accessible through the interface.
TImplementation = class(TInterfaceObject, ISomeInterface)
procedure Pred;
procedure Next;
function GetPredMeth: TMoveProc;
function GetNextMeth: TMoveProc;
end;
...
function TImplementation.GetPredMeth: TMoveProc;
begin
Result := Self.Pred;
end;
function TImplementation.GetNextMeth: TMoveProc;
begin
Result := Self.Next;
end;
How about this:
type
TMoveProc = procedure(const SomeIntf: ISomeInterface);
TSomeObject = class
public
procedure Move(const SomeIntf: ISomeInterface; MoveProc: TMoveProc);
end;
procedure TSomeObject.Move(const SomeIntf: ISomeInterface; MoveProc: TMoveProc);
begin
MoveProc(SomeIntf);
end;
procedure MoveProcNext(const SomeIntf: ISomeInterface);
begin
SomeIntf.Next;
end;
procedure MoveProcPred(const SomeIntf: ISomeInterface);
begin
SomeIntf.Pred;
end;
procedure Usage;
var
SomeObj: TSomeObject;
SomeIntf: ISomeInterface;
begin
SomeIntf := GetSomeInterface;
SomeObj := TSomeObject.Create;
try
SomeObj.Move(SomeIntf, MoveProcNext);
SomeObj.Move(SomeIntf, MoveProcPred);
finally
SomeObj.Free;
end;
end;
Here is another solution that is working in Delphi 20006. It is similar to the idea of #Rafael, but using interfaces:
interface
type
ISomeInterface = interface
//...
end;
IMoveProc = interface
procedure Move;
end;
IMoveProcPred = interface(IMoveProc)
['{4A9A14DD-ED01-4903-B625-67C36692E158}']
end;
IMoveProcNext = interface(IMoveProc)
['{D9FDDFF9-E74E-4F33-9CB7-401C51E7FF1F}']
end;
TSomeObject = class(TObject)
public
procedure Move(MoveProc: IMoveProc);
end;
TImplementation = class(TInterfacedObject,
ISomeInterface, IMoveProcNext, IMoveProcPred)
procedure IMoveProcNext.Move = Next;
procedure IMoveProcPred.Move = Pred;
procedure Pred;
procedure Next;
end;
implementation
procedure TSomeObject.Move(MoveProc: IMoveProc);
begin
while True do
begin
// Some common code that works for both procedures
MoveProc.Move;
// More code...
end;
end;
procedure Usage;
var
o: TSomeObject;
i: ISomeInterface;
begin
o := TSomeObject.Create;
i := TImplementation.Create;
o.Move(i as IMoveProcPred);
// somewhere else: o.Move(i as IMoveProcNext);
o.Free;
end;
You can't. Because of the scoping of Interfaces it would be possible (perhaps?) for the Interface to be released before you called the .Next function. If you want to do this you should pass the whole interface to your method rather than just a method.
Edited...
Sorry, this next bit, specifically the "Of Interface" bit was meant in jest.
Also, and I could be wrong here, i.Next is not a method Of Object, as per your type def, it would be a method Of Interface!
Redefine your function
TSomeObject = class(TObject)
public
procedure Move(Const AMoveIntf: ISomeInterface);
end;
Procedure TSomeObject.Move(Const AMoveIntf : ISomeInterface);
Begin
....;
AMoveIntf.Next;
end;
O.Move(I);
Hope this helps.
You currently have TMoveProc defined as
TMoveProc = procedure of object;
Try taking out the "of object" which implies a hidden "this" pointer as first parameter.
TMoveProc = procedure;
That should allow a normal procedure to be called.