I've got some problems with EnumWindows function that uses callback function from a nested class:
TProcessWatch = class(TObject)
private
...
type
TProcessInfo = class(TObject)
private
type
PEnumCallbackParam = ^TEnumCallbackParam;
TEnumCallbackParam = class
A : Integer;
...
end;
private
FOwner : TProcessWatch;
function FEnumWindowsCallback(hWindow : HWND; lParam : LPARAM) : BOOL; export;
procedure SomeProc;
...
end;
private
FProcesses : TProcessInfo;
...
public
...
Within the SomeProc there is a call to EnumWindows
EnumCallbackParam := TEnumCallbackParam.Create;
try
EnumCallbackParam.A := 0;
EnumWindows(#TProcessWatch.TProcessInfo.FEnumWindowsCallback, LongInt(#EnumCallbackParam));
...
finally
EnumCallbackParam.Free;
end;
And here is a FEnumWindowsCallback function listing:
function TProcessWatch.TProcessInfo.FEnumWindowsCallback(hWindow: HWND;
lParam : LPARAM): BOOL; export;
var
CallbackParam : PEnumCallbackParam;
begin
CallbackParam := Pointer(lParam); // A is inaccessible
Result := True;
...
end;
In runtime, when EnumWindows is called, FEnumWindowsCallback is always receiving hWindow = 0 and lParam is pointing to inaccessible value.
All this working fine if callback function is declared private in the form, but when I've tried to make this function private in nested class it went wrong.
Why? And how to make this working? The goal is to make FEnumWindowsCallback and all other involved functions private in TProcessWatch.
The callback is declared wrongly. It should be:
class function EnumWindowsCallback(hWindow: HWND;
lParam: LPARAM): BOOL; static; stdcall;
You were using the wrong calling convention, and an instance method.
Other comments:
EnumCallbackParam is already a pointer. You can pass it as the param.
Cast to LPARAM rather then LongInt so that you code will work if you ever compile to 64 bit.
The export keyword has no meaning in 32 or 64 bit Delphi. It is ignored and you should not use it since it adds clutter and may confuse.
Related
In the following, the method Obtain() works, but the GetAs() method would be neater for callers.
However, I can't figure out how to pass an interface as a generic parameter and obtain its GUID.
Declaration:
TInterfaceRegistry = class
private
fRegistry: TDictionary<TGUID, IInterface>;
public
...
procedure Register(const IID: TGUID; IntfObj: IInterface; const Replace: Boolean = False);
function Obtain(const IID: TGUID; out IntfObj): Boolean;
function GetAs<I: IInterface>(): I;
end;
Implementation:
function TInterfaceRegistry.Obtain(const IID: TGUID; out IntfObj): Boolean;
var
Found: IInterface;
begin
Result := fRegistry.TryGetValue(IID, Found);
if Result then
if not Supports(Found, IID, IntfObj) then
raise EBatSoft.Create(SEBatSoftBug);
end;
function TInterfaceRegistry.GetAs<I>(): I;
begin
if not Obtain(I, Result) then
raise EBatSoft.Create(SEDoesNotImplement);
end;
Calling it looks like this...
IntfReg.Register(IMyIntf, TMyImpl.Create);
Getting implementation:
var
Intf: IMyIntf;
begin
// Less type-safe (can pass anything into 2nd parameter)
if IntfReg.Obtain(IMyIntf, Intf) then
...
// Fully type-safe, and simple
Intf := IntfReg.GetAs<IMyIntf>();
You have to use typeinfo via GetTypeData(TypeInfo(I)).GUID.
Keep in mind though this might return an empty guid if you did not declare any for the given interface you are using while the non generic approach simply would not compile.
How can I define callback function of timeSetEvent as an instance method?
TUDPBC = class(TObject)
private
hTimer: word;
...
public
procedure sendUDPBC;
...
end;
procedure DoTimer(uTimerID, uMessage: UINT; dwUser,dw1,dw2: DWORD); stdcall;
procedure TUDPBC.sendUDPBC; //send UDP Broadcast
begin
...
hTimer := TimeSetEvent(FTimeOut, uRes, DoTimer, 0, TIME_ONESHOT); //need DoTimer as a TUDPBC class method
...
end;
How can I define callback function of timeSetEvent as an instance method?
The simple answer is that you cannot. The signature of the callback is not something that you can vary. It is defined by the API and must be a simple unbound procedure with this signature:
typedef void ( CALLBACK *LPTIMECALLBACK)(
UINT uTimerID,
UINT uMsg,
DWORD_PTR dwUser,
DWORD_PTR dw1,
DWORD_PTR dw2
);
What you can do though is pass a pointer to timeSetEvent that contains the address of your instance. The pattern runs like this:
procedure TimeProcCallback(uTimerID, uMessage: UINT; dwUser,dw1,dw2: DWORD_PTR); stdcall;
begin
TUDPBC(dwUser).TimeProc(uTimerID, uMessage);
end;
This is the callback that you pass to timeSetEvent. Your class would look like this:
type
TUDPBC = class
private
FTimerID: MMRESULT;
procedure TimeProc(uTimerID, uMessage: UINT);
end;
This method of the class will be called by the callback function and so have access to the instance variables.
Set the timer like this:
FTimerID := timeSetEvent(TimeOut, uRes, TimeProcCallback, DWORD_PTR(Self), TIME_ONESHOT);
So, you pass the address of the instance in the dwUser argument. That is then passed on to your callback. That callback can, in turn, call an instance method.
Is there some trick how to get pointer of a member function in Lazarus / delphi?
I have this code which won't compile.... Error is
in Delphi:
variable required
in Lazarus:
Error: Incompatible types: got "<procedure variable type of function(Byte):LongInt of object;StdCall>" expected "Pointer"
The code:
TClassA = class
public
function ImportantFunc(AParameter: byte): integer; stdcall;
end;
TClassB = class
public
ObjectA: TClassA;
ImportantPtr: pointer;
procedure WorkerFunc;
end;
function TClassA.ImportantFunc(AParameter: byte): integer; stdcall;
begin
// some important stuff
end;
procedure TClassB.WorkerFunc;
begin
ImportantPtr := #ObjectA.ImportantFunc; // <-- ERROR HERE
end;
Thanks!
A member function cannot be represented by a single pointer. It needs two pointers, one for the instance and one for the code. But that's implementation detail and you just need to use a method type:
type
TImportantFunc = function(AParameter: byte): integer of object; stdcall;
You can then assign ImportantFunc to a variable of this type.
Since you are using stdcall I suspect you are trying to use this as a Windows callback. That's not possible for a member function. You need a function with global scope, or a static function.
type
TImportantFunc = function(AParameter: byte): integer of object;stdcall;
ImportantPtr: TImportantFunc;
procedure TClassB.WorkerFunc;
begin
ImportantPtr := ObjectA.ImportantFunc; // <-- OK HERE
end;
ObjectA.ImportantFunc is not a memory location, so address operator # can't be applied to it - hence compiler error. It is 2 pointers, #TClassA.ImportantFunc (method code) and ObjectA (Self argument). An answer to your question depends on what you really need - code pointer, Self, both or none.
If you need just to scope a function name use static class method
TClassA = class
public
class function ImportantFunc(Instance: TClassA; AParameter: byte): integer;
stdcall; static;
end;
Using Delphi XE2 update 3 or update 4 on Win7 64 bit.
Calling enumwindows does not work like it used to work in Delphi 6.
In Delphi 6 enumwindows processed windows until the callback function returned False. That is what the documentation says it should do:
"To continue enumeration, the callback function must return TRUE; to stop enumeration, it must return FALSE."
Making a call to enumwindows as follows:
procedure TForm1.Button1Click(Sender: TObject);
begin
EnumWindows(#FindMyWindow,0);
if GLBWindowHandle <> 0 then begin
ShowMessage('found');
end;
end;
Here is the callback function:
function FindMyWindow(hWnd: HWND; lParam: LPARAM): boolean; stdcall;
var TheText : array[0..150] of char;
str : string;
begin
Result := True;
GLBWindowHandle := 0;
if (GetWindowText(hWnd, TheText, 150) <> 0) then
begin
str := TheText;
if str = 'Form1' then
begin
GLBWindowHandle := hWnd;
Result := False;
end
else
result := True;
end;
end;
Just to be clear the callback function is defined in code BEFORE the buttonclick event so it is found by the compiler without needing to be defined in the interface section.
If this is run using Delphi 6 the enumeration of windows stops once the False result is returned and GLBWindowHandle is not zero
If this is run using Delphi XE2 the enumeration continues after the False result is returned and GLBWindowHandle is always zero.
WTF? Anybody have any ideas why the enumeration is not stopping like the documentation states it should and how it used to in Delphi 6?
Cheers!
This declaration is incorrect:
function FindMyWindow(hWnd: HWND; lParam: LPARAM): boolean; stdcall;
It should be:
function FindMyWindow(hWnd: HWND; lParam: LPARAM): BOOL; stdcall;
You have to be careful not to mix up Boolean and BOOL since they are not the same thing. The former is a single byte, the latter is 4 bytes. This mismatch between what EnumWindows expects and what your callback function delivers is enough to cause the behaviour you observe.
In addition, Rob Kennedy contributed this excellent comment:
The compiler can help find this error if you get out of the habit of using the # operator before the function name when you call EnumWindows. If the function signature is compatible, the compiler will let you use it without #. Using # turns it into a generic pointer, and that's compatible with everything, so the error is masked by unnecessary syntax. In short, using # to create function pointers should be considered a code smell.
Discussion
Unfortunately the Windows.pas header translation defines EnumWindows in a most unhelpful manner, like this:
function EnumWindows(lpEnumFunc: TFNWndEnumProc; lParam: LPARAM): BOOL; stdcall;
Now, the problem is in the definition of TFNWndEnumProc. It is defined as:
TFarProc = Pointer;
TFNWndEnumProc = TFarProc;
This means that you have to use the # operator to make a generic pointer, because the function needs a generic pointer. If TFNWndEnumProc were declared like this:
TFNWndEnumProc = function(hWnd: HWND; lParam: LPARAM): BOOL; stdcall;
then the compiler would have been able to find the error.
type
TFNWndEnumProc = function(hWnd: HWND; lParam: LPARAM): BOOL; stdcall;
function EnumWindows(lpEnumFunc: TFNWndEnumProc;
lParam: LPARAM): BOOL; stdcall; external 'user32';
function FindMyWindow(hWnd: HWND; lParam: LPARAM): Boolean; stdcall;
begin
Result := False;
end;
....
EnumWindows(FindMyWindow, 0);
The compiler rejects the call to EnumWindows with the following error:
[DCC Error] Unit1.pas(38): E2010 Incompatible types: 'LongBool' and 'Boolean'
I think I will QC this issue and try my luck at persuading Embarcadero to stop using TFarProc.
I am having a problem passing a class reference as the parameter to the ThreadProc in a call to CreateThread. Here is a sample program that demonstrates the problem I am having:
program test;
{$APPTYPE CONSOLE}
uses
SysUtils, Windows, Dialogs;
type
TBlah = class
public
fe: Integer;
end;
function ThreadProc(param: Pointer) : DWORD;
begin
ShowMessage(IntToStr(TBlah(param).fe));
Result := 0;
end;
var
tID: DWORD;
handle: THandle;
b: TBlah;
begin
b := TBlah.Create;
b.fe := 54;
handle := CreateThread(nil, 0, #ThreadProc, Pointer(b), 0, tID);
WaitForSingleObject(handle, INFINITE);
end.
The call to ShowMessage pops up a message box that has something like 245729105 in it, not 54 like I expect.
This is probably just a basic misunderstanding of how Delphi works, so could someone please tell me how to get this working properly?
The problem here is that your thread function has the wrong calling convention. You need to declare it with the stdcall convention:
function ThreadProc(param: Pointer) : DWORD; stdcall;
Having said that, it would be more idiomatic to just use a TThread descendant which handles the OOP to C function back to OOP transitioning for you. That would look like this:
type
TBlah = class(TThread)
protected
procedure Execute; override;
public
fe: Integer;
end;
procedure TBlah.Execute;
begin
ShowMessage(IntToStr(fe));
end;
var
b: TBlah;
begin
b := TBlah.Create(True);
b.fe := 42;
b.Start;
b.WaitFor;
end.
Incidentally, does anyone know why Windows.pas declares TFNThreadStartRoutine as TFarProc rather than a proper typed function pointer?
You're forgetting the stdcall directive.
function ThreadProc(param: Pointer) : DWORD; stdcall;
And don't use Pointer cast in:
handle := CreateThread(nil, 0, #ThreadProc, **Pointer**(b), 0, tID);
b is already a Pointer (a Class, which is an Object Pointer)