I'd like to pass a method of a class as callback to a WinAPI function. Is this possible and if yes, how?
Example case for setting a timer:
TMyClass = class
public
procedure TimerProc(Wnd:HWND; uMsg:DWORD; idEvent:PDWORD; dwTime:DWORD);
procedure DoIt;
end;
[...]
procedure TMyClass.DoIt;
begin
SetTimer(0, 0, 8, #TimerProc); // <-???- that's what I want to do (last param)
end;
Thanks for your help!
Edit: The goal is to specify a method of this class as callback. No procedure outside the class.
Edit2: I appreciate all your help but as long as the method has no "TMyClass." in front of its name it is not what I am searching for. I used to do it this way but wondered if could stay fully in the object oriented world. Pointer magic welcome.
Madshi has a MethodToProcedure procedure. It's in the "madTools.pas" which is in the "madBasic" package. If you use it, you should change the calling convention for "TimerProc" to stdcall and DoIt procedure would become,
TMyClass = class
private
Timer: UINT;
SetTimerProc: Pointer;
[...]
procedure TMyClass.DoIt;
begin
SetTimerProc := MethodToProcedure(Self, #TMyClass.TimerProc);
Timer := SetTimer(0, 0, 8, SetTimerProc);
end;
// After "KillTimer(0, Timer)" is called call:
// VirtualFree(SetTimerProc, 0, MEM_RELEASE);
I've never tried but I think one could also try to duplicate the code in the "classses.MakeObjectInstance" for passing other procedure types than TWndMethod.
Which version of Delphi are you using?
In recent ones you can use static class methods for this:
TMyClass = class
public
class procedure TimerProc(Wnd:HWND; uMsg:DWORD; idEvent:PDWORD; dwTime:DWORD); stdcall; static;
procedure DoIt;
end;
[...]
procedure TMyClass.DoIt;
begin
SetTimer(0, 0, 8, #TimerProc);
end;
The TimerProc procedure should be a standard procedure, not a method pointer.
A method pointer is really a pair of
pointers; the first stores the address
of a method, and the second stores a
reference to the object the method
belongs to
Edit
This might be as much OOP as you are going to get it. All the nasty stuff is hidden from anyone using your TMyClass.
unit Unit2;
interface
type
TMyClass = class
private
FTimerID: Integer;
FPrivateValue: Boolean;
public
constructor Create;
destructor Destroy; override;
procedure DoIt;
end;
implementation
uses
Windows, Classes;
var
ClassList: TList;
constructor TMyClass.Create;
begin
inherited Create;
ClassList.Add(Self);
end;
destructor TMyClass.Destroy;
var
I: Integer;
begin
I := ClassList.IndexOf(Self);
if I <> -1 then
ClassList.Delete(I);
inherited;
end;
procedure TimerProc(Wnd:HWND; uMsg:DWORD; idEvent:PDWORD; dwTime:DWORD); stdcall;
var
I: Integer;
myClass: TMyClass;
begin
for I := 0 to Pred(ClassList.Count) do
begin
myClass := TMyClass(ClassList[I]);
if myClass.FTimerID = Integer(idEvent) then
myClass.FPrivateValue := True;
end;
end;
procedure TMyClass.DoIt;
begin
FTimerID := SetTimer(0, 0, 8, #TimerProc); // <-???- that's what I want to do (last param)
end;
initialization
ClassList := TList.Create;
finalization
ClassList.Free;
end.
Edit: (as mentioned by glob)
Don't forget to add the stdcall calling convention.
Response to your second edit:
If you want a reply that includes a pointer to a TMyClass instance, you may be out of luck. Fundamentally, the procedure Windows will call has a certain signature and is not an object method. You cannot directly work around that, not even with __closure or procedure of object magic, except as described below and in other answers. Why?
Windows has no knowledge of it being an object method, and wants to call a procedure with a specific signature.
The pointer is no longer a simple pointer - it has two halves, the object instance and the method. It needs to save the Self, as well as the method.
By the way, I don't understand what is wrong with a short dip outside the object-oriented world. Non-OO code is not necessarily dirty if used well.
Original, pre-your-edit answer:
It's not possible exactly as you are trying to do it. The method that SetTimer wants must exactly follow the TIMERPROC signature - see the MSDN documentation. This is a simple, non-object procedure.
However, the method TMyClass.DoIt is an object method. It actually has two parts: the object on which it is called, and the method itself. In Delphi, this is a "procedure of object" or a "closure" (read about procedural types here). So, the signatures are not compatible, and you cannot store the object instance, which you need in order to call an object method. (There are also calling convention problems - standard Delphi methods are implemented using the fastcall convention, whereas TIMERPROC specifies CALLBACK which, from memory, is a macro that expands to stdcall. Read more about calling conventions and especially fastcall.)
So, what do you do? You need to map your non-object-oriented callback into object-oriented code.
There are several ways, and the simplest is this:
If you only have one timer ever, then you know that when your timer callback is called it is that specific timer that fired. Save a method pointer in a variable that is of type procedure of object with the appropriate signature. See the Embarcadero documentation link above for more details. It will probably look like:
type TMyObjectProc = procedure of object;
var pfMyProc : TMyObjectProc;
Then, initialise pfMyProc to nil. In TMyClass.DoIt, set pfMyProc to #DoIt - that is, it is now pointing at the DoIt procedure in the context of that specific TMyClass instantiation. Your callback can then call that method.
(If you're interested, class variables that are of a procedural type like this are how event handlers are stored internally. The OnFoo properties of a VCL object are pointers to object procedures.)
Unfortunately this procedural architecture is not object-oriented, but it's how it has to be done.
Here's what some full code might look like (I'm not at a compiler, so it may not work as written, but it should be close):
type TMyObjectProc = procedure of object;
var pfMyProc : TMyObjectProc;
initialization
pfMyProc = nil;
procedure MyTimerCallback(hWnd : HWND; uMsg : DWORD; idEvent : PDWORD; dwTime : DWORD); stdcall;
begin
if Assigned(pfMyProc) then begin
pfMyProc(); // Calls DoIt, for the object that set the timer
pfMyProc = nil;
end;
end;
procedure TMyClass.MyOOCallback;
begin
// Handle your callback here
end;
procedure TMyClass.DoIt;
begin
pfMyProc = #MyOOCallback;
SetTimer(0, 0, 8, # MyTimerCallback);
end;
Another way would be to take advantage of the fact your timer has a unique ID. Save a mapping between the timer ID and the the object. In the callback, convert from the ID to the pointer, and call the object's method.
Edit: I've noticed a comment to another answer suggesting using the address of your object as the timer ID. This will work, but is a potentially dangerous hack if you end up having two objects at the same address at different times, and you don't call KillTimer. I've used that method but don't personally like it - I think the extra bookkeeping of keeping a (timer ID, object pointer) map is better. It really comes down to personal style, though.
I've used MakeObjectInstance a few times to do the same.
Here's an article on the subject:
How to use a VCL class member-function as a Win32 callback
TMyClass = class
public
procedure DoIt;
procedure DoOnTimerViaMethod;
end;
var MyReceiverObject: TMyClass;
[...]
procedure TimerProc(Wnd:HWND; uMsg:DWORD; idEvent:PDWORD; dwTime:DWORD); stdcall:
begin
if Assigned(MyReceiverObject) then
MyReceiverObject.DoOnTimerViaMethod;
end;
procedure TMyClass.DoIt;
begin
MyReceiverObject := Self;
SetTimer(0, 0, 8, #TimerProc); // <-???- that's what I want to do (last param)
end;
Not perfect. Watch for the threads, variable overwriting etc. But it does the job.
Related
When I'm in a TForm (say, MyForm) that has a procedure oneProc (procedure TMyForm.oneProc), if I do inside any procedure of MyForm something like TThread.Queue(nil, oneProc);, how does it end up that when oneProc() is fired that Self is well initialized to MyForm?
Can you explain why this works?
procedure TMyForm.Button1Click(Sender: TObject);
begin
ProcA(ProcB);
end;
procedure TMyForm.ProcA(const Aproc: Tproc);
begin
Aproc;
end;
procedure TMyForm.ProcB;
begin
showmessage(self.className); // << this show TTMyForm (how?)
end;
First off, TThread.Queue() is overloaded to accept both TThreadMethod (non-anonymous) and TThreadProcedure (anonymous) parameters. Your text description is for code that would call the non-anonymous version. Your code example, on the other hand, is doing something a little different than what your described TThread.Queue() code would be doing. But in any case, to answer your question -
A non-anonymous method pointer actually carries 2 values within it - one is the address of the method, and one is the value passed to the method's Self parameter (a method pointer is represented by the TMethod record).
TProc is a reference to an anonymous method, and a non-anonymous method pointer can be assigned to an anonymous method reference. The documentation even says so, in the section on "Using Anonymous Methods":
Method references can also be assigned to methods as well as anonymous methods. For example:
type
TMethRef = reference to procedure(x: Integer);
TMyClass = class
procedure Method(x: Integer);
end;
var
m: TMethRef;
i: TMyClass;
begin
// ...
m := i.Method; //assigning to method reference
end;
However, the converse is not true: you cannot assign an anonymous method to a regular method pointer. Method references are managed types, but method pointers are unmanaged types. Thus, for type-safety reasons, assigning method references to method pointers is not supported. For instance, events are method pointer-valued properties, so you cannot use an anonymous method for an event. See the section on variable binding for more information on this restriction.
Internally, an anonymous method reference is implemented as a compiler-generated reference-counted interface with a single Invoke() method. When you write an anonymous method in code, the compiler generates a hidden class that implements Invoke() with that code. Any variables that the anonymous method captures are stored as members of that class.
When assigning a non-anonymous method pointer to an anonymous method reference, the compiler generates a class that captures that method pointer and then calls it inside of the generated Invoke().
So, given the code you have shown, the compiler would translate it into something roughly like the following (I'm leaving out implementation details that are not relevant):
type
//TProc = reference to procedure;
TProc_Intf = interface
procedure Invoke;
end;
TProc_Generated = class(TInterfacedObject, TProc_Intf)
FProc: procedure of object; // type of TMyForm.ProcB()
procedure Invoke;
end;
procedure TProc_Generated.Invoke;
begin
FProc; // <-- calls FProc.Code with FProc.Data as Self!
end;
procedure TMyForm.Button1Click(Sender: TObject);
var
Intf: TProc_Intf;
begin
//ProcA(ProcB);
Intf := TProc_Generated.Create;
//TProc_Generated(Intf).FProc := #ProcB;
TMethod(TProc_Generated(Intf).FProc).Code := Addr(ProcB);
TMethod(TProc_Generated(Intf).FProc).Data := Self; // <-- Self stored here!
ProcA(Intf);
end;
procedure TMyForm.ProcA(const Aproc: {TProc}TProc_Intf);
begin
//Aproc;
Aproc.Invoke;
end;
procedure TMyForm.ProcB;
begin
ShowMessage(Self.ClassName); // <-- Self valid here!
end;
That is how the Self gets from Button1Click() to ProcB() through ProcA().
I ran into a strange situation while testing something today.
I have a number of interfaces and objects. The code looks like this:
IInterfaceZ = interface(IInterface)
['{DA003999-ADA2-47ED-A1E0-2572A00B6D75}']
procedure DoSomething;
end;
IInterfaceY = interface(IInterface)
['{55BF8A92-FCE4-447D-B58B-26CD9B344EA7}']
procedure DoNothing;
end;
TObjectB = class(TInterfacedObject, IInterfaceZ)
procedure DoSomething;
end;
TObjectC = class(TInterfacedObject, IInterfaceY)
public
FTest: string;
procedure DoNothing;
end;
TObjectA = class(TInterfacedObject, IInterfaceZ, IInterfaceY)
private
FInterfaceB: IInterfaceZ;
FObjectC: TObjectC;
function GetBB: IInterfaceZ;
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
property BB: IInterfaceZ read GetBB implements IInterfaceZ;
property CC: TObjectC read FObjectC implements IInterfaceY;
end;
procedure TObjectB.DoSomething;
begin
Sleep(1000);
end;
procedure TObjectA.AfterConstruction;
begin
inherited;
FInterfaceB := TObjectB.Create;
FObjectC := TObjectC.Create;
FObjectC.FTest := 'Testing';
end;
procedure TObjectA.BeforeDestruction;
begin
FreeAndNil(FObjectC);
FInterfaceB := nil;
inherited;
end;
function TObjectA.GetBB: IInterfaceZ;
begin
Result := FInterfaceB;
end;
procedure TObjectC.DoNothing;
begin
ShowMessage(FTest);
end;
Now if I access the various implementations like this I get the following results:
procedure TestInterfaces;
var
AA: TObjectA;
YY: IInterfaceY;
ZZ: IInterfaceZ;
NewYY: IInterfaceY;
begin
AA := TObjectA.Create;
// Make sure that the Supports doesn't kill the object.
// This line of code is necessary in XE2 but not in XE4
AA._AddRef;
// This will add one to the refcount for AA despite the fact
// that AA has delegated the implementation of IInterfaceY to
// to FObjectC.
Supports(AA, IInterfaceY, YY);
YY.DoNothing;
// This will add one to the refcount for FInterfaceB.
// This is also allowing a supports from a delegated interface
// to another delegated interface.
Supports(YY, IInterfaceZ, ZZ);
ZZ.DoSomething;
// This will fail because the underlying object is actually
// the object referenced by FInterfaceB.
Supports(ZZ, IInterfaceY, NewYY);
NewYY.DoNothing;
end;
The first Supports call, which uses the variable in the implements, returns YY which is actually a reference to TObjectA. My AA variable is reference counted. Because the underlying reference counted object is a TObjectA, the second supports, which uses the interface in the supports call, works and returns me an interface. The underlying object is actually now a TObjectB. The internal object behind FInterfaceB is the object being reference counted. This part makes sense because GetBB is actually FInterfaceB. As expected here, the last call to Supports returns a null for NewYY and the call at the end fails.
My question is this, is the reference counting on TObjectA with the first supports call by design? In other words, when the property that implements the interface is returning an object and not an interface does this mean that the owner object will be the one doing the reference counting? I was always under the impression that implements would also result in the internal delegated object being reference counted instead of the main object.
The declarations are as follows:
property BB: IInterfaceZ read GetBB implements IInterfaceZ;
With this option above, the internal object behind FInterfaceB is the one that is reference counted.
property CC: TObjectC read FObjectC implements IInterfaceY;
With this second option above, TObjectA is the one that is being reference counted and not the delegated object FObjectC.
Is this by design?
Edit
I just tested this in XE2 and the behavior is different. The second Supports statement returns nil for ZZ. The debugger in XE4 tells me that the YY is referring to (TObjectA as IInterfaceY). In XE2 it tells me that its a (Pointer as IInterfaceY). Also, in XE2, the AA is not ref counted on the first support statement but the internal FObjectC is reference counted.
Additional Information after the question answered
There is one caveat to this. You can chain the Interface version but not the object version. That means that something like this will work:
TObjectBase = class(TInterfacedObject, IMyInterface)
…
end;
TObjectA = class(TInterfacedObject, IMyInterface)
FMyInterfaceBase: IMyInterface;
property MyDelegate: IMyInterface read GetMyInterface implements IMyInterface;
end;
function TObjectA.GetMyInterface: IMyInterface;
begin
result := FMyInterfaceBase;
end;
TObjectB = class(TInterfacedObject, IMyInterface)
FMyInterfaceA: IMyInterface;
function GetMyInterface2: IMyInterface;
property MyDelegate2: IMyInterface read GetMyInterface2 implements IMyInterface;
end;
function TObjectB.GetMyInterface2: IMyInterface;
begin
result := FMyInterfaceA;
end;
But the object version gives a compiler error with this saying that TObjectB doesn't implement the methods for the interface.
TObjectBase = class(TInterfacedObject, IMyInterface)
…
end;
TObjectA = class(TInterfacedObject, IMyInterface)
FMyObjectBase: TMyObjectBase;
property MyDelegate: TMyObjectBase read FMyObjectBase implements IMyInterface;
end;
TObjectB = class(TInterfacedObject, IMyInterface)
FMyObjectA: TObjectA;
property MyDelegate2: TObjectA read FMyObjectA implements IMyInterface;
end;
So if you want to start chaining the delegation then you need to stick to interfaces or work around it another way.
tl;dr This is all by design – it's just that the design changes between XE2 and XE3.
XE3 and later
There is quite a difference between delegation to an interface type property and delegation to a class type property. Indeed the documentation calls out this difference explicitly with different sections for the two delegation variants.
The difference from your perspective is as follows:
When TObjectA implements IInterfaceY by delegating to class type property CC, the implementing object is the instance of TObjectA.
When TObjectA implements IInterfaceZ by delegating to interface type property BB, the implementing object is the object that implements FInterfaceB.
One key thing to realise in all this is that when you delegate to a class type property, the class that is delegated to need not implement any interfaces. So it need not implement IInterface and so need not have _AddRef and _Release methods.
To see this, modify your code's definition of TObjectC to be like so:
TObjectC = class
public
procedure DoNothing;
end;
You will see that this code compiles, runs, and behaves exactly the same way as does your version.
In fact this is ideally how you would declare a class to which an interface is delegated as a class type property. Doing it this way avoids the lifetime issues with mixing interface and class type variables.
So, let's look at your three calls to Supports:
Supports(AA, IInterfaceY, YY);
Here the implementing object is AA and so the reference count of AA is incremented.
Supports(YY, IInterfaceZ, ZZ);
Here the implementing object is the instance of TObjectB so its reference count is incremented.
Supports(ZZ, IInterfaceY, NewYY);
Here, ZZ is an interface implemented by the instance of TObjectB which does not implement IInterfaceY. Hence Supports returns False and NewYY is nil.
XE2 and earlier
The design changes between XE2 and XE3 coincide with the introduction of the mobile ARM compiler and there were many low-level changes to support ARC. Clearly some of these changes apply to the desktop compilers too.
The behavioural difference that I can find concerns delegation of interface implementation to class type properties. And specifically when the class type in question supports IInterface. In that scenario, in XE2, the reference counting is performed by the inner object. That differs from XE3 which has the reference counting performed by the outer object.
Note that for a class type that does not support IInterface, the reference counting is performed by the outer object in all versions. That makes sense since there's no way for the inner object to do it.
Here's my example code to demonstrate the difference:
{$APPTYPE CONSOLE}
uses
SysUtils;
type
Intf1 = interface
['{56FF4B9A-6296-4366-AF82-9901A5287BDC}']
procedure Foo;
end;
Intf2 = interface
['{71B0431C-DB83-49F0-B084-0095C535AFC3}']
procedure Bar;
end;
TInnerClass1 = class(TObject, Intf1)
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
procedure Foo;
end;
TInnerClass2 = class
procedure Bar;
end;
TOuterClass = class(TObject, Intf1, Intf2)
private
FInnerObj1: TInnerClass1;
FInnerObj2: TInnerClass2;
public
constructor Create;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
property InnerObj1: TInnerClass1 read FInnerObj1 implements Intf1;
property InnerObj2: TInnerClass2 read FInnerObj2 implements Intf2;
end;
function TInnerClass1.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;
function TInnerClass1._AddRef: Integer;
begin
Writeln('TInnerClass1._AddRef');
Result := -1;
end;
function TInnerClass1._Release: Integer;
begin
Writeln('TInnerClass1._Release');
Result := -1;
end;
procedure TInnerClass1.Foo;
begin
Writeln('Foo');
end;
procedure TInnerClass2.Bar;
begin
Writeln('Bar');
end;
constructor TOuterClass.Create;
begin
inherited;
FInnerObj1 := TInnerClass1.Create;
end;
function TOuterClass.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;
function TOuterClass._AddRef: Integer;
begin
Writeln('TOuterClass._AddRef');
Result := -1;
end;
function TOuterClass._Release: Integer;
begin
Writeln('TOuterClass._Release');
Result := -1;
end;
var
OuterObj: TOuterClass;
I1: Intf1;
I2: Intf2;
begin
OuterObj := TOuterClass.Create;
Supports(OuterObj, Intf1, I1);
Supports(OuterObj, Intf2, I2);
I1.Foo;
I2.Bar;
I1 := nil;
I2 := nil;
Readln;
end.
The output on XE2 is:
TInnerClass1._AddRef
TOuterClass._AddRef
Foo
Bar
TInnerClass1._Release
TOuterClass._Release
The output on XE3 is:
TOuterClass._AddRef
TOuterClass._AddRef
Foo
Bar
TOuterClass._Release
TOuterClass._Release
Discussion
Why did the design change? I cannot answer that definitively, not being privy to the decision making. However, the behaviour in XE3 feels better to me. If you declare a class type variable you would expect its lifetime to be managed as any other class type variable would be. That is, by explicit calls to destructor on the desktop compilers, and by ARC on the mobile compilers.
The behaviour of XE2 on the other hand feels inconsistent. Why should the fact that a property is used for interface implementation delegation change the way its lifetime is managed?
So, my instincts tell me that this was a design flaw, at best, in the original implementation of interface implementation delegation. The design flaw has led to confusion and lifetime management troubles over the years. The introduction to ARC forced Embarcadero to review this issue and they changed the design. My belief is that the introduction of ARC required a design change because Embarcadero have a track record of not changing behaviour unless absolutely necessary.
The paragraphs above are clearly speculation on my part, but that's the best I have to offer!
You are mixing object pointers and interface pointers, which is always a recipe for disaster. TObjectA is not incrementing the reference count of its inner objects to ensure they stay alive for its entire lifetime, and TestInterfaces() is not incrementing the reference count of AA to ensure it survives through the entire set of tests. Object pointers DO NOT participate in reference counting! You have to manage it manually, eg:
procedure TObjectA.AfterConstruction;
begin
inherited;
FObjectB := TObjectB.Create;
FObjectB._AddRef;
FObjectC := TObjectC.Create;
FObjectC._AddRef;
FObjectC.FTest := 'Testing';
end;
procedure TObjectA.BeforeDestruction;
begin
FObjectC._Release;
FObjectB._Release;
inherited;
end;
AA := TObjectA.Create;
AA._AddRef;
Needless to say, manual reference counting undermines the use of interfaces.
When dealing with interfaces, you need to either:
Disable reference counting completely to avoid premature destructions. TComponent, for instance, does exactly that.
Do EVERYTHING using interface pointers, NEVER with object pointers. This ensures proper reference counting across the board. This is generally the preferred solution.
I have a record that looks like:
TBigint = record
PtrDigits: Pointer; <-- The data is somewhere else.
Size: Byte;
MSB: Byte;
Sign: Shortint;
...
class operator Implicit(a: TBigint): TBigint; <<-- is this allowed?
....
The code is pre-class operator legacy code, but I want to add operators.
I know the data should really be stored in a dynamic array of byte, but I do not want to change the code, because all the meat is in x86-assembly.
I want to following code to trigger the class operator at the bottom:
procedure test(a: TBignum);
var b: TBignum;
begin
b:= a; <<-- naive copy will tangle up the `PtrDigit` pointers.
....
If I add the implicit typecast to itself, will the following code be executed?
class operator TBigint.Implicit(a: TBigint): TBigint;
begin
sdpBigint.CreateBigint(Result, a.Size);
sdpBigint.CopyBigint(a, Result);
end;
(Will test and add the answer if it works as I expect).
My first answer attempts to dissuade against the idea of overriding the assignment operator. I still stand by that answer, because many of the problems to be encountered are better solved with objects.
However, David quite rightly pointed out that TBigInt is implemented as a record to leverage operator overloads. I.e. a := b + c;. This is a very good reason to stick with a record based implementation.
Hence, I propose this alternative solution that kills two birds with one stone:
It removes the memory management risks explained in my other answer.
And provides a simple mechanism to implement Copy-on-Write semantics.
(I do still recommend that unless there's a very good reason to retain a record based solution, consider switching to an object based solution.)
The general idea is as follows:
Define an interface to represent the BigInt data. (This can initially be minimalist and support only control of the pointer - as in my example. This would make the initial conversion of existing code easier.)
Define an implementation of the above interface which will be used by the TBigInt record.
The interface solves the first problem, because interfaces are a managed type; and Delphi will dereference the interface when a record goes out of scope. Hence, the underlying object will destroy itself when no longer needed.
The interface also provides the opportunity to solve the second problem, because we can check the RefCount to know whether we should Copy-On-Write.
Note that long term it might prove beneficial to move some of the BigInt implementation from the record to the class & interface.
The following code is trimmed-down "big int" implementation purely to illustrate the concepts. (I.e. The "big" integer is limited to a regular 32-bit number, and only addition has been implemented.)
type
IBigInt = interface
['{1628BA6F-FA21-41B5-81C7-71C336B80A6B}']
function GetData: Pointer;
function GetSize: Integer;
procedure Realloc(ASize: Integer);
function RefCount: Integer;
end;
type
TBigIntImpl = class(TInterfacedObject, IBigInt)
private
FData: Pointer;
FSize: Integer;
protected
{IBigInt}
function GetData: Pointer;
function GetSize: Integer;
procedure Realloc(ASize: Integer);
function RefCount: Integer;
public
constructor CreateCopy(ASource: IBigInt);
destructor Destroy; override;
end;
type
TBigInt = record
PtrDigits: IBigInt;
constructor CreateFromInt(AValue: Integer);
class operator Implicit(AValue: TBigInt): Integer;
class operator Add(AValue1, AValue2: TBigInt): TBigInt;
procedure Add(AValue: Integer);
strict private
procedure CopyOnWriteSharedData;
end;
{ TBigIntImpl }
constructor TBigIntImpl.CreateCopy(ASource: IBigInt);
begin
Realloc(ASource.GetSize);
Move(ASource.GetData^, FData^, FSize);
end;
destructor TBigIntImpl.Destroy;
begin
FreeMem(FData);
inherited;
end;
function TBigIntImpl.GetData: Pointer;
begin
Result := FData;
end;
function TBigIntImpl.GetSize: Integer;
begin
Result := FSize;
end;
procedure TBigIntImpl.Realloc(ASize: Integer);
begin
ReallocMem(FData, ASize);
FSize := ASize;
end;
function TBigIntImpl.RefCount: Integer;
begin
Result := FRefCount;
end;
{ TBigInt }
class operator TBigInt.Add(AValue1, AValue2: TBigInt): TBigInt;
var
LSum: Integer;
begin
LSum := Integer(AValue1) + Integer(AValue2);
Result.CreateFromInt(LSum);
end;
procedure TBigInt.Add(AValue: Integer);
begin
CopyOnWriteSharedData;
PInteger(PtrDigits.GetData)^ := PInteger(PtrDigits.GetData)^ + AValue;
end;
procedure TBigInt.CopyOnWriteSharedData;
begin
if PtrDigits.RefCount > 1 then
begin
PtrDigits := TBigIntImpl.CreateCopy(PtrDigits);
end;
end;
constructor TBigInt.CreateFromInt(AValue: Integer);
begin
PtrDigits := TBigIntImpl.Create;
PtrDigits.Realloc(SizeOf(Integer));
PInteger(PtrDigits.GetData)^ := AValue;
end;
class operator TBigInt.Implicit(AValue: TBigInt): Integer;
begin
Result := PInteger(AValue.PtrDigits.GetData)^;
end;
The following tests were written as I built up the proposed solution. They prove: some basic functionality, that the copy-on-write works as expected, and that there are no memory leaks.
procedure TTestCopyOnWrite.TestCreateFromInt;
var
LBigInt: TBigInt;
begin
LBigInt.CreateFromInt(123);
CheckEquals(123, LBigInt);
//Dispose(PInteger(LBigInt.PtrDigits)); //I only needed this until I
//started using the interface
end;
procedure TTestCopyOnWrite.TestAssignment;
var
LValue1: TBigInt;
LValue2: TBigInt;
begin
LValue1.CreateFromInt(123);
LValue2 := LValue1;
CheckEquals(123, LValue2);
end;
procedure TTestCopyOnWrite.TestAddMethod;
var
LValue1: TBigInt;
begin
LValue1.CreateFromInt(123);
LValue1.Add(111);
CheckEquals(234, LValue1);
end;
procedure TTestCopyOnWrite.TestOperatorAdd;
var
LValue1: TBigInt;
LValue2: TBigInt;
LActualResult: TBigInt;
begin
LValue1.CreateFromInt(123);
LValue2.CreateFromInt(111);
LActualResult := LValue1 + LValue2;
CheckEquals(234, LActualResult);
end;
procedure TTestCopyOnWrite.TestCopyOnWrite;
var
LValue1: TBigInt;
LValue2: TBigInt;
begin
LValue1.CreateFromInt(123);
LValue2 := LValue1;
LValue1.Add(111); { If CopyOnWrite, then LValue2 should not change }
CheckEquals(234, LValue1);
CheckEquals(123, LValue2);
end;
Edit
Added a test demonstrating use of TBigInt as value parameter to a procedure.
procedure TTestCopyOnWrite.TestValueParameter;
procedure CheckValueParameter(ABigInt: TBigInt);
begin
CheckEquals(2, ABigInt.PtrDigits.RefCount);
CheckEquals(123, ABigInt);
ABigInt.Add(111);
CheckEquals(234, ABigInt);
CheckEquals(1, ABigInt.PtrDigits.RefCount);
end;
var
LValue: TBigInt;
begin
LValue.CreateFromInt(123);
CheckValueParameter(LValue);
end;
There is nothing in Delphi that allows you to hook into the assignment process. Delphi has nothing like C++ copy constructors.
Your requirements, are that:
You need a reference to the data, since it is of variable length.
You also have a need for value semantics.
The only types that meet both of those requirements are the native Delphi string types. They are implemented as a reference. But the copy-on-write behaviour that they have gives them value semantics. Since you want an array of bytes, AnsiString is the string type that meets your needs.
Another option would be to simply make your type be immutable. That would let you stop worrying about copying references since the referenced data could never be modified.
It seems to me your TBigInt should be a class rather than a record. Because you're concerned about PtrDigits being tangled up, it sounds like you're needing extra memory management for what the pointer references. Since records don't support destructors there's no automatic management of that memory. Also if you simply declare a variable of TBigInt, but don't call the CreatBigInt constructor, the memory is not correctly initialised. Again, this is because you cannot override a record's default parameterless constructor.
Basically you have to always remember what has been allocated for the record and remember to manually deallocate. Sure you can have a deallocate procedure on the record to help in this regard, but you still have to remember to call it in the correct places.
However that said, you could implement an explicit Copy function, and add an item to your code-review checklist that TBitInt has been copied correctly. Unfortunately you'll have to be very careful with the implied copies such as passing the record via a value parameter to another routine.
The following code illustrates an example conceptually similar to your needs and demonstrates how the CreateCopy function "untangles" the pointer. It also highlights some of the memory management problems that crop up, which is why records are probably not a good way to go.
type
TMyRec = record
A: PInteger;
function CreateCopy: TMyRec;
end;
function TMyRec.CreateCopy: TMyRec;
begin
New(Result.A);
Result.A^ := A^;
end;
var
R1, R2: TMyRec;
begin
New(R1.A); { I have to manually allocate memory for the pointer
before I can use the reocrd properly.
Even if I implement a record constructor to assist, I
still have to remember to call it. }
R1.A^ := 1;
R2 := R1;
R2.A^ := 2; //also changes R1.A^ because pointer is the same (or "tangled")
Writeln(R1.A^);
R2 := R1.CreateCopy;
R2.A^ := 3; //Now R1.A is different pointer so R1.A^ is unchanged
Writeln(R1.A^);
Dispose(R1.A);
Dispose(R2.A); { <-- Note that I have to remember to Dispose the additional
pointer that was allocated in CreateCopy }
end;
In a nutshell, it seems you're trying to sledgehammer records into doing things they're not really suited to doing.
They are great at making exact copies. They have simple memory management: Declare a record variable, and all memory is allocated. Variable goes out of scope and all memory is deallocated.
Edit
An example of how overriding the assignment operator can cause a memory leak.
var
LBigInt: TBigInt;
begin
LBigInt.SetValue(123);
WriteBigInt(LBigInt); { Passing the value by reference or by value depends
on how WriteBigInt is declared. }
end;
procedure WriteBigInt(ABigInt: TBigInt);
//ABigInt is a value parameter.
//This means it will be copied.
//It must use the overridden assignment operator,
// otherwise the point of the override is defeated.
begin
Writeln('The value is: ', ABigInt.ToString);
end;
//If the assignment overload allocated memory, this is the only place where an
//appropriate reference exists to deallocate.
//However, the very last thing you want to do is have method like this calling
//a cleanup routine to deallocate the memory....
//Not only would this litter your code with extra calls to accommodate a
//problematic design, would also create a risk that a simple change to taking
//ABigInt as a const parameter could suddenly lead to Access Violations.
Since anonymous methods appeared in Delphi I wanted to use them in VCL components events. Obviously for backward compatibility the VCL wasn't updated, so I managed to make a simple implementation with a few caveats.
type
TNotifyEventDispatcher = class(TComponent)
protected
FClosure: TProc<TObject>;
procedure OnNotifyEvent(Sender: TObject);
public
class function Create(Owner: TComponent; const Closure: TProc<TObject>): TNotifyEvent; overload;
function Attach(const Closure: TProc<TObject>): TNotifyEvent;
end;
implementation
class function TNotifyEventDispatcher.Create(Owner: TComponent; const Closure: TProc<TObject>): TNotifyEvent;
begin
Result := TNotifyEventDispatcher.Create(Owner).Attach(Closure)
end;
function TNotifyEventDispatcher.Attach(const Closure: TProc<TObject>): TNotifyEvent;
begin
FClosure := Closure;
Result := Self.OnNotifyEvent
end;
procedure TNotifyEventDispatcher.OnNotifyEvent(Sender: TObject);
begin
if Assigned(FClosure) then
FClosure(Sender)
end;
end.
And this is how it's used for example:
procedure TForm1.FormCreate(Sender: TObject);
begin
Button1.OnClick := TNotifyEventDispatcher.Create(Self,
procedure (Sender: TObject)
begin
Self.Caption := 'DONE!'
end)
end;
Very simple I believe, there are two drawbacks:
I have to create a component to manage the lifetime of the anonymous method (I waste a bit more of memory, and it's a bit slower for the indirection, still I prefer more clear code in my applications)
I have to implement a new class (very simple) for every event signature. This one is a bit more complicated, still the VCL has very common event signatures, and for every special case when I create the class it's done forever.
What do you think of this implementation? Something to make it better?
You can take a look at my multicast event implementation in DSharp.
Then you can write code like this:
function NotifyEvent(Owner: TComponent; const Delegates: array of TProc<TObject>): TNotifyEvent; overload;
begin
Result := TEventHandler<TNotifyEvent>.Create<TProc<TObject>>(Owner, Delegates).Invoke;
end;
function NotifyEvent(Owner: TComponent; const Delegate: TProc<TObject>): TNotifyEvent; overload;
begin
Result := NotifyEvent(Owner, [Delegate]);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Button1.OnClick := NotifyEvent(Button1, [
procedure(Sender: TObject)
begin
Caption := 'Started';
end,
procedure(Sender: TObject)
begin
if MessageDlg('Continue?', mtConfirmation, mbYesNo, 0) <> mrYes then
begin
Caption := 'Canceled';
Abort;
end;
end,
procedure(Sender: TObject)
begin
Caption := 'Finished';
end]);
end;
You could make TNotifyEventDispatcher to be a subclass of TInterfacedObject so you do not need to care about freeing it.
But to be more pragmatic one would use traditional event assignment that takes less lines of code and is supported by the IDE.
Interesting approach.
(Disclaimer: haven't checked this, but it is something to investigate): You may have to be careful though about what goes on in capturing the state of the method that "assigns" the anonymous method to the event. Capturing can be an advantage but can also have side effects you do not want. If your anonymous method needs info about the form at the time it is fired, it may end up with info at the time of its assignment. Update: apparently this is not the case, see comment by Stefan Glienke.
You do not really need different classes. Using overloading you could create different class Create functions that each take a specific signature and return the corresponding event handler and the compiler will sort it out.
Managing lifetime could be simplified if you derived from TInterfacedObject instead of TComponent. Reference counting should then take care of destroying the instance when it is no longer used by the form. Update: this does require keeping a reference to the instance somewhere in the form, or refcounting won't help as the instance will be freed immediately after assigning the notify event. You could add an extra parameter on the class Create functions to which you pass a method that the instance can useto add itself to some list of the form.
Side note: All in all though I have to agree with David in his comment on the question: it does sound like a lot of work for the "sole purpose" of using anonymous methods...
What techniques exist to automatically free objects in delphi applications?
Use interfaces instead of objects. They are reference counted and freed automatically when the reference count reaches 0.
I have written a function GC(obj: TObject) (for Garbage Collect) which takes an object and frees it when the execution leaves the current method. It's kind of like a one-line shorthand function for a Try Finally Free block.
Instead of:
procedure Test;
var AQuery: TQuery;
begin
AQuery := TQuery.Create(nil);
try
...
finally
FreeAndNil(AQuery);
end;
end;
I just have:
procedure Test;
var AQuery: TQuery;
begin
AQuery := TQuery.Create(nil);
GC(AQuery);
...
end;
The GC function simply returns an object in the form of an interface.
function GC(obj: TObject): IGarbo;
begin
Result := TGarbo.Create(obj);
end;
Because the TGarbo class descends from TInterfacedObject, when the TGarbo object goes out of scope it will automatically get freed. In the destructor of the TGarbo object, it also frees the object you passed to it in it's constructor (the object you passed in the GC function).
type
IGarbo = interface
['{A6E17957-C233-4433-BCBD-3B53C0C2C596}']
function Obj: TObject;
end;
TGarbo = class(TInterfacedObject, IGarbo)
private
FObj: TObject;
public
constructor Create(AObjectToGC: TObject);
destructor Destroy; override;
function Obj: TObject;
end;
{ TGarbo }
constructor TGarbo.Create(AObjectToGC: TObject);
begin
inherited Create;
FObj := AObjectToGC;
end;
destructor TGarbo.Destroy;
begin
if Assigned(FObj) then
FreeAndNil(FObj);
inherited;
end;
function TGarbo.Obj: TObject;
begin
Result := FObj;
end;
Being stuck in the world of Delphi 7 with no sight of upgrading to a version of Delphi with built-in garbage collection in the near future, I'm addicted to using this short-hand method of easily freeing local temporary objects! :)
Along the lines of interfaces, you can try the Guard function in the JclSysUtils unit, part of the free Jedi Code Library. It allows you to associate an object with a separate interface reference, so when that interface reference is destroyed, the object is destroyed along with it. This can be useful when you don't have the option of modifying the classes you're using to make them support interfaces of their own.
var
G: ISafeGuard;
foo: TStrings;
begin
// Guard returns TObject, so a type-cast is necessary
foo := Guard(TStringList.Create, G) as TStrings;
// Use the object as normal
foo.Add('bar');
end; // foo gets freed automatically as G goes out of scope
There are overloads for objects and GetMem-allocated pointers. There is also IMultiSafeGuard, which can ensure that multiple objects get freed.
If you have a factory function, you might be creating an object, setting some of its properties, and then returning it. If an exception occurs while setting the properties, you'll want to make sure you free the object since you can't return it. One way to do that is like this:
function Slurp(const source: TFileName): TStrings;
begin
Result := TStringList.Create;
try
Result.LoadFromFile(source);
except
Result.Free;
raise;
end;
end;
With Guard, it would become this:
function Slurp(const source: TFileName): TStrings;
var
G: ISafeGuard;
begin
Result := Guard(TStringList.Create, G) as TStrings;
Result.LoadFromFile(source);
G.ReleaseItem;
end;
The ReleaseItem method revokes the ISafeGuard's ownership of the object. If an exception occurs before that happens, then as the stack unwinds and the interface is released, the guard will free the object.
I have to say, I don't like "hiding" the Free of an object. Far better to have the traditional code:
MyObject := TObject.Create;
try
// do stuff
finally
FreeAndNil(MyObject);
end;
No way it can go wrong, works as expected, and people recognise the pattern.
Use the object ownership of components that the VCL provides. As long as you create objects with a non-nil owner you don't need to free them explicitely. See also my answer to this question.
Here is the API for Boehm Garbage Collector DLL for Delphi. The Delphi API is written by Barry Kelly, who works for CodeGear writing the compiler now.
Smart Pointers work really well if you have Delphi 2009.
If you use Delphi for .Net / Delphi Prism you get Garbage Collection which takes care of all the freeing.