EDIT - See Update at end
This is for Delphi 7.0 Build 4.453
Summary
I need to be able to take the Handle property from a TMonitor object (an element in the Monitors array in the TScreen component) which is a HMONITOR, and turn it into the string you would use in calls to EnumDisplaySettings as the lpszDeviceName parameter.
(my end goal is to get a list of device settings from a given HMONITOR value, by passing the resolved lpszDeviceName into calls to EnumDisplaySettings).
Detailed Information
As mentioned above, the Screen.Monitors[x].Handle property is of type HMONITOR and is normally used to pass into the GetMonitorInfo function, which returns, geometry information, but no lpszDeviceName. (note: there is a TMonitorInfoEx structure that has a szDevice field, but it does not seem to get filled in on my system, even though i am setting the cbSize field to the appropriate size).
Alternatively, if i can use a szDeviceName to get the equivalent HMONITOR value, i could plug it into the following function, which would use it in a comparison (I have inserted a call to fictitious function called hMonitorFromDeviceName in the code below) to indicate how it would be used.
function GetMonitorDeviceName(hmon : HMONITOR) : string;
var
DispDev : TDisplayDevice;
deviceName : string;
nDeviceIndex : integer;
begin
Result := '';
FillChar(DispDev, sizeof(DispDev),0);
DispDev.cb := sizeof(DispDev);
nDeviceIndex := 0;
while (EnumDisplayDevices(nil, nDeviceIndex, DispDev, 0)) do
begin
if ( hMonitorFromDeviceName(DispDev.DeviceString) = hmon ) then
begin
Result := StrPas(DispDev.DeviceString);
exit;
end;
inc(nDeviceIndex);
end;
end;
Update
Thanks to David Heffernan, I have tested his solution, and here is a sample function to get the monitor name from a given handle:
function GetMonitorName(hmon : HMONITOR) : string;
type
TMonitorInfoEx = record
cbSize: DWORD;
rcMonitor: TRect;
rcWork: TRect;
dwFlags: DWORD;
szDevice: array[0..CCHDEVICENAME - 1] of AnsiChar;
end;
var
DispDev : TDisplayDevice;
deviceName : string;
monInfo : TMonitorInfoEx;
begin
Result := '';
monInfo.cbSize := sizeof(monInfo);
if GetMonitorInfo(hmon,#monInfo) then
begin
DispDev.cb := sizeof(DispDev);
EnumDisplayDevices(#monInfo.szDevice, 0, DispDev, 0);
Result := StrPas(DispDev.DeviceString);
end;
end;
I think that you must be calling GetMonitorInfo incorrectly. This code:
{$APPTYPE CONSOLE}
uses
SysUtils, MultiMon, Windows, Forms;
var
i: Integer;
MonitorInfo: TMonitorInfoEx;
begin
MonitorInfo.cbSize := SizeOf(MonitorInfo);
for i := 0 to Screen.MonitorCount-1 do
begin
if not GetMonitorInfo(Screen.Monitors[i].Handle, #MonitorInfo) then
RaiseLastOSError;
Writeln(MonitorInfo.szDevice);
end;
Readln;
end.
produces this output on my machine:
\\.\DISPLAY1
\\.\DISPLAY2
I suspect that your call to GetMonitorInfo is failing in some way and perhaps you are not checking the return value for errors.
Having searched QualityCentral I suspect you have fallen victim to a known bug in older versions of Delphi: QC#3239. This is reported fixed in version 10.0.2124.6661 which is Delphi 2006.
Your comments confirm this diagnosis. To fix the problem you'll need a new TMonitorInfoEx definition. Here's one that will work on your pre-Unicode Delphi:
type
TMonitorInfoEx = record
cbSize: DWORD;
rcMonitor: TRect;
rcWork: TRect;
dwFlags: DWORD;
szDevice: array[0..CCHDEVICENAME - 1] of AnsiChar;
end;
If you add that to the code above (before you declare the variables of course) then I believe it will resolve your problem.
As an interesting aside, even in XE3, these structs have not been translated correctly: QC#114460. Admittedly the error is rather benign as it only affects PMonitorInfoExA and TMonitorInfoExA, but the error caught me out whilst trying to solve the problem in this question!
Related
AS. since closing related questions - more examples added below.
The below simple code (which finds a top-level Ie window and enumerates its children) works Ok with a '32-bit Windows' target platform. There's no problem with earlier versions of Delphi as well:
procedure TForm1.Button1Click(Sender: TObject);
function EnumChildren(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
const
Server = 'Internet Explorer_Server';
var
ClassName: array[0..24] of Char;
begin
Assert(IsWindow(hwnd)); // <- Assertion fails with 64-bit
GetClassName(hwnd, ClassName, Length(ClassName));
Result := ClassName <> Server;
if not Result then
PUINT_PTR(lParam)^ := hwnd;
end;
var
Wnd, WndChild: HWND;
begin
Wnd := FindWindow('IEFrame', nil); // top level IE
if Wnd <> 0 then begin
WndChild := 0;
EnumChildWindows(Wnd, #EnumChildren, UINT_PTR(#WndChild));
if WndChild <> 0 then
..
end;
I've inserted an Assert to indicate where it fails with a '64-bit Windows' target platform. There's no problem with the code if I un-nest the callback.
I'm not sure if the erroneous values passed with the parameters are just garbage or are due to some mis-placed memory addresses (calling convention?). Is nesting callbacks infact something that I should never do in the first place? Or is this just a defect that I have to live with?
edit:
In response to David's answer, the same code having EnumChildWindows declared with a typed callback. Works fine with 32-bit:
(edit: The below does not really test what David says since I still used the '#' operator. It works fine with the operator, but if I remove it, it indeed does not compile unless I un-nest the callback)
type
TFNEnumChild = function(hwnd: HWND; lParam: LPARAM): Bool; stdcall;
function TypedEnumChildWindows(hWndParent: HWND; lpEnumFunc: TFNEnumChild;
lParam: LPARAM): BOOL; stdcall; external user32 name 'EnumChildWindows';
procedure TForm1.Button1Click(Sender: TObject);
function EnumChildren(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
const
Server = 'Internet Explorer_Server';
var
ClassName: array[0..24] of Char;
begin
Assert(IsWindow(hwnd)); // <- Assertion fails with 64-bit
GetClassName(hwnd, ClassName, Length(ClassName));
Result := ClassName <> Server;
if not Result then
PUINT_PTR(lParam)^ := hwnd;
end;
var
Wnd, WndChild: HWND;
begin
Wnd := FindWindow('IEFrame', nil); // top level IE
if Wnd <> 0 then begin
WndChild := 0;
TypedEnumChildWindows(Wnd, #EnumChildren, UINT_PTR(#WndChild));
if WndChild <> 0 then
..
end;
Actually this limitation is not specific to a Windows API callbacks, but the same problem happens when taking address of that function into a variable of procedural type and passing it, for example, as a custom comparator to TList.Sort.
http://docwiki.embarcadero.com/RADStudio/Rio/en/Procedural_Types
procedure TForm2.btn1Click(Sender: TObject);
var s : TStringList;
function compare(s : TStringList; i1, i2 : integer) : integer;
begin
result := CompareText(s[i1], s[i2]);
end;
begin
s := TStringList.Create;
try
s.add('s1');
s.add('s2');
s.add('s3');
s.CustomSort(#compare);
finally
s.free;
end;
end;
It works as expected when compiled as 32-bit, but fails with Access Violation when compiled for Win64. For 64-bit version in function compare, s = nil and i2 = some random value;
It also works as expected even for Win64 target, if one extracts compare function outside of btn1Click function.
This trick was never officially supported by the language and you have been getting away with it to date due to the implementation specifics of the 32 bit compiler. The documentation is clear:
Nested procedures and functions (routines declared within other routines) cannot be used as procedural values.
If I recall correctly, an extra, hidden, parameter is passed to nested functions with the pointer to the enclosing stack frame. This is omitted in 32 bit code if no reference is made to the enclosing environment. In 64 bit code the extra parameter is always passed.
Of course a big part of the problem is that the Windows unit uses untyped procedure types for its callback parameters. If typed procedures were used the compiler could reject your code. In fact I view this as justification for the belief that the trick you used was never legal. With typed callbacks a nested procedure can never be used, even in the 32 bit compiler.
Anyway, the bottom line is that you cannot pass a nested function as parameter to another function in the 64 bit compiler.
AS. since closing related questions - more examples added below.
The below simple code (which finds a top-level Ie window and enumerates its children) works Ok with a '32-bit Windows' target platform. There's no problem with earlier versions of Delphi as well:
procedure TForm1.Button1Click(Sender: TObject);
function EnumChildren(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
const
Server = 'Internet Explorer_Server';
var
ClassName: array[0..24] of Char;
begin
Assert(IsWindow(hwnd)); // <- Assertion fails with 64-bit
GetClassName(hwnd, ClassName, Length(ClassName));
Result := ClassName <> Server;
if not Result then
PUINT_PTR(lParam)^ := hwnd;
end;
var
Wnd, WndChild: HWND;
begin
Wnd := FindWindow('IEFrame', nil); // top level IE
if Wnd <> 0 then begin
WndChild := 0;
EnumChildWindows(Wnd, #EnumChildren, UINT_PTR(#WndChild));
if WndChild <> 0 then
..
end;
I've inserted an Assert to indicate where it fails with a '64-bit Windows' target platform. There's no problem with the code if I un-nest the callback.
I'm not sure if the erroneous values passed with the parameters are just garbage or are due to some mis-placed memory addresses (calling convention?). Is nesting callbacks infact something that I should never do in the first place? Or is this just a defect that I have to live with?
edit:
In response to David's answer, the same code having EnumChildWindows declared with a typed callback. Works fine with 32-bit:
(edit: The below does not really test what David says since I still used the '#' operator. It works fine with the operator, but if I remove it, it indeed does not compile unless I un-nest the callback)
type
TFNEnumChild = function(hwnd: HWND; lParam: LPARAM): Bool; stdcall;
function TypedEnumChildWindows(hWndParent: HWND; lpEnumFunc: TFNEnumChild;
lParam: LPARAM): BOOL; stdcall; external user32 name 'EnumChildWindows';
procedure TForm1.Button1Click(Sender: TObject);
function EnumChildren(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
const
Server = 'Internet Explorer_Server';
var
ClassName: array[0..24] of Char;
begin
Assert(IsWindow(hwnd)); // <- Assertion fails with 64-bit
GetClassName(hwnd, ClassName, Length(ClassName));
Result := ClassName <> Server;
if not Result then
PUINT_PTR(lParam)^ := hwnd;
end;
var
Wnd, WndChild: HWND;
begin
Wnd := FindWindow('IEFrame', nil); // top level IE
if Wnd <> 0 then begin
WndChild := 0;
TypedEnumChildWindows(Wnd, #EnumChildren, UINT_PTR(#WndChild));
if WndChild <> 0 then
..
end;
Actually this limitation is not specific to a Windows API callbacks, but the same problem happens when taking address of that function into a variable of procedural type and passing it, for example, as a custom comparator to TList.Sort.
http://docwiki.embarcadero.com/RADStudio/Rio/en/Procedural_Types
procedure TForm2.btn1Click(Sender: TObject);
var s : TStringList;
function compare(s : TStringList; i1, i2 : integer) : integer;
begin
result := CompareText(s[i1], s[i2]);
end;
begin
s := TStringList.Create;
try
s.add('s1');
s.add('s2');
s.add('s3');
s.CustomSort(#compare);
finally
s.free;
end;
end;
It works as expected when compiled as 32-bit, but fails with Access Violation when compiled for Win64. For 64-bit version in function compare, s = nil and i2 = some random value;
It also works as expected even for Win64 target, if one extracts compare function outside of btn1Click function.
This trick was never officially supported by the language and you have been getting away with it to date due to the implementation specifics of the 32 bit compiler. The documentation is clear:
Nested procedures and functions (routines declared within other routines) cannot be used as procedural values.
If I recall correctly, an extra, hidden, parameter is passed to nested functions with the pointer to the enclosing stack frame. This is omitted in 32 bit code if no reference is made to the enclosing environment. In 64 bit code the extra parameter is always passed.
Of course a big part of the problem is that the Windows unit uses untyped procedure types for its callback parameters. If typed procedures were used the compiler could reject your code. In fact I view this as justification for the belief that the trick you used was never legal. With typed callbacks a nested procedure can never be used, even in the 32 bit compiler.
Anyway, the bottom line is that you cannot pass a nested function as parameter to another function in the 64 bit compiler.
AS. since closing related questions - more examples added below.
The below simple code (which finds a top-level Ie window and enumerates its children) works Ok with a '32-bit Windows' target platform. There's no problem with earlier versions of Delphi as well:
procedure TForm1.Button1Click(Sender: TObject);
function EnumChildren(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
const
Server = 'Internet Explorer_Server';
var
ClassName: array[0..24] of Char;
begin
Assert(IsWindow(hwnd)); // <- Assertion fails with 64-bit
GetClassName(hwnd, ClassName, Length(ClassName));
Result := ClassName <> Server;
if not Result then
PUINT_PTR(lParam)^ := hwnd;
end;
var
Wnd, WndChild: HWND;
begin
Wnd := FindWindow('IEFrame', nil); // top level IE
if Wnd <> 0 then begin
WndChild := 0;
EnumChildWindows(Wnd, #EnumChildren, UINT_PTR(#WndChild));
if WndChild <> 0 then
..
end;
I've inserted an Assert to indicate where it fails with a '64-bit Windows' target platform. There's no problem with the code if I un-nest the callback.
I'm not sure if the erroneous values passed with the parameters are just garbage or are due to some mis-placed memory addresses (calling convention?). Is nesting callbacks infact something that I should never do in the first place? Or is this just a defect that I have to live with?
edit:
In response to David's answer, the same code having EnumChildWindows declared with a typed callback. Works fine with 32-bit:
(edit: The below does not really test what David says since I still used the '#' operator. It works fine with the operator, but if I remove it, it indeed does not compile unless I un-nest the callback)
type
TFNEnumChild = function(hwnd: HWND; lParam: LPARAM): Bool; stdcall;
function TypedEnumChildWindows(hWndParent: HWND; lpEnumFunc: TFNEnumChild;
lParam: LPARAM): BOOL; stdcall; external user32 name 'EnumChildWindows';
procedure TForm1.Button1Click(Sender: TObject);
function EnumChildren(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
const
Server = 'Internet Explorer_Server';
var
ClassName: array[0..24] of Char;
begin
Assert(IsWindow(hwnd)); // <- Assertion fails with 64-bit
GetClassName(hwnd, ClassName, Length(ClassName));
Result := ClassName <> Server;
if not Result then
PUINT_PTR(lParam)^ := hwnd;
end;
var
Wnd, WndChild: HWND;
begin
Wnd := FindWindow('IEFrame', nil); // top level IE
if Wnd <> 0 then begin
WndChild := 0;
TypedEnumChildWindows(Wnd, #EnumChildren, UINT_PTR(#WndChild));
if WndChild <> 0 then
..
end;
Actually this limitation is not specific to a Windows API callbacks, but the same problem happens when taking address of that function into a variable of procedural type and passing it, for example, as a custom comparator to TList.Sort.
http://docwiki.embarcadero.com/RADStudio/Rio/en/Procedural_Types
procedure TForm2.btn1Click(Sender: TObject);
var s : TStringList;
function compare(s : TStringList; i1, i2 : integer) : integer;
begin
result := CompareText(s[i1], s[i2]);
end;
begin
s := TStringList.Create;
try
s.add('s1');
s.add('s2');
s.add('s3');
s.CustomSort(#compare);
finally
s.free;
end;
end;
It works as expected when compiled as 32-bit, but fails with Access Violation when compiled for Win64. For 64-bit version in function compare, s = nil and i2 = some random value;
It also works as expected even for Win64 target, if one extracts compare function outside of btn1Click function.
This trick was never officially supported by the language and you have been getting away with it to date due to the implementation specifics of the 32 bit compiler. The documentation is clear:
Nested procedures and functions (routines declared within other routines) cannot be used as procedural values.
If I recall correctly, an extra, hidden, parameter is passed to nested functions with the pointer to the enclosing stack frame. This is omitted in 32 bit code if no reference is made to the enclosing environment. In 64 bit code the extra parameter is always passed.
Of course a big part of the problem is that the Windows unit uses untyped procedure types for its callback parameters. If typed procedures were used the compiler could reject your code. In fact I view this as justification for the belief that the trick you used was never legal. With typed callbacks a nested procedure can never be used, even in the 32 bit compiler.
Anyway, the bottom line is that you cannot pass a nested function as parameter to another function in the 64 bit compiler.
I'm not quite sure how to even ask this question, since I don't know whether it is related to the execution time, application process.message procedure or anything else.
I'm having (for me) weird situations, where the procedure fails to run and raises system exception on run, while it runs completely flawless if I put "showmessage" there in between (which I put so that I could quickly see what's going on in between. I prefer that way over watches somehow...).
I'm not sure whether the code matters or not, but I'll give it below:
procedure LoadSettings;
var SettingsBuffToLoad: TStringList;
begin
SettingsBuffToLoad:=TStringList.Create;
Encoding:=TEncoding.ANSI;
SettingsBuffToLoad.LoadFromFile('bin/settings.txt', Encoding);
// showmessage(settingsbufftoload.Strings[0]);
SettingsBuffer:=Decode(SettingsBuffToLoad);
// showmessage(settingsbuffer.Strings[0]); //decode
end;
The Decode procedure is declared as external and is read from the dll.
If I just remove those "/" , so that it becomes the code instead of comment, it works just fine. However, set as you see now, it raises exception, but after the procedure is already done. (the debugger last break point is stopped at "end;", after continuing however it raises exception instead of showing the form; this procedure is called as the last thing in FormCreate procedure.
Is there anything that has to do with the timing, which ShowMessage solves, or...? :/
Update:
The decode functions, as asked:
this is how it's declared, right above of the implementation and variables of the form:
function Decode(Buff: TStringList): TStringList; StdCall; external 'bin\settings.txt';
And this is in the dll:
function Decode(Buff: TStringList): TStringList; export;
var
t, u, h: integer;
s: String;
begin
DecodeBuffer.Clear;
DecodeBuffer:=Buff;
for h := 0 to DecodeBuffer.Count-1 do
begin
s := DecodeBuffer.Strings[h];
t := Length(s);
if t > 0 then
begin
for u := 0 to t-1 do
begin
s[u+1] := DecodeChar(s[u+1], (h mod 5) + 1);
end;
DecodeBuffer.Strings[h] := s;
end;
end;
Result:=DecodeBuffer;
end;
This code was discussed in a question at Delphi changing Chars in string - missunderstood behavior - XE3 and is used from Remy's answer. The DecodeChar is, I believe simply unimportant here, or is it?
Also, the same goes with the function to save settings, which is called at FormClose event:
This is:
procedure TScribbles.SaveSettings;
var SettingsBuffToSave: TStringList;
begin
SettingsBuffToSave:=TStringList.Create;
Encoding := TEncoding.ANSI;
// Showmessage(settingsbuffer.Strings[0]);
SettingsBuffToSave:=Encode(SettingsBuffer);
// Showmessage(settingsbufftosave.Strings[0]);
SettingsBuffToSave.SaveToFile('bin/settings.txt', Encoding);
end;
With the first ShowMessage used as code instead of comment, it works, while otherwise in a comment function as it is written above, it calls external exception the same way as on Decode.
Is it possible, that the SettingsBuffToSave is just not yet created when it already calls the function Encode, or what?
At that time, the SettingsBuffer exists and is populated, so it really seems weird that it raises errors, which disappears with simply putting ShowMessage in there.
(Function Encode is basically a mirror of Decode, so the code is not important here...)
This code is VERY VERY VERY dangerous on many levels. Using objects across the DLL boundary in an unsafe manner. Mismanagement of object pointers across function calls. You need a redesign. Try the following as a start:
procedure Decode(Buff: PChar; BuffLen: Integer; ListIndex: Integer); stdcall; export;
var
u: integer;
begin
for u := 0 to BuffLen-1 do
begin
Buff^ := DecodeChar(Buff^, (ListIndex mod 5) + 1);
Inc(Buff);
end;
end;
procedure Encode(Buff: PChar; BuffLen: Integer; ListIndex: Integer); stdcall; export;
var
u: integer;
begin
for u := 0 to BuffLen-1 do
begin
Buff^ := EncodeChar(Buff^, (ListIndex mod 5) + 1);
Inc(Buff);
end;
end;
procedure Decode(Buff: PChar; BuffLen: Integer; ListIndex: Integer); stdcall; external '...';
procedure Encode(Buff: PChar; BuffLen: Integer; ListIndex: Integer); stdcall; external '...';
procedure LoadSettings;
var
h: Integer;
begin
SettingsBuffer := TStringList.Create;
SettingsBuffer.LoadFromFile('bin/settings.txt', TEncoding.ANSI);
for h := 0 to SettingsBuff.Count-1 do
begin
Decode(PChar(SettingsBuff[h]), Length(SettingsBuff[h]), h);
end;
end;
procedure TScribbles.SaveSettings;
var
h: Integer;
begin
for h := 0 to SettingsBuff.Count-1 do
begin
Encode(PChar(SettingsBuff[h]), Length(SettingsBuff[h]), h);
end;
SettingsBuff.SaveToFile('bin/setpb95enc.dll', TEncoding.ANSI);
end;
The obvious problem here is that the code exists in a DLL. Most likely you didn't arrange for the DLL to share its host's heap. And a Delphi class cannot be passed across a DLL boundary.
If you want to share Delphi classes between modules, you must use packages. Of course, another option is to put all the code in the same module. That is remove the DLL, and compile everything in the executable. The final option is to use valid interop types for DLLs.
Of course, there could be other reasons for the actual error. The code smells bad. For instance, what is this:
DecodeBuffer:=Buff;
Is DecodeBuffer a global variable? If so then it is plausible that you refer to the object after it has been destroyed. Not that I can see evidence of anything being destroyed. Without wishing to seem rude, your code looks like it may have multiple problems. As a matter of urgency you need to:
Deal with the DLL problem described above.
Remove global variables.
Fix lifetime issues. Stop leaking.
Enable range checking to locate buffer overruns.
Add FastMM in debug mode to try to catch heap corruptions.
I think I know what's going on here: I think your stack is getting smashed.
Furthermore, I rather suspect the actual cause is the Decode procedure using an uninitialized variable. Your ShowMessage statement (it would be the first one that matters if I'm right) changes what's on the stack and thus changes the uninitialized variable.
If I'm right this is going to have some heisenbug attributes--anything you do to find out what's going on will change the value of the uninitialized variable.
One thing to try: Declare a large local variable (the idea is to use up stack space) and make sure it's not discarded by the compiler. This will move things in memory and thus likely defuse the blowup. If it works it's pretty conclusive at to what's going on.
AS. since closing related questions - more examples added below.
The below simple code (which finds a top-level Ie window and enumerates its children) works Ok with a '32-bit Windows' target platform. There's no problem with earlier versions of Delphi as well:
procedure TForm1.Button1Click(Sender: TObject);
function EnumChildren(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
const
Server = 'Internet Explorer_Server';
var
ClassName: array[0..24] of Char;
begin
Assert(IsWindow(hwnd)); // <- Assertion fails with 64-bit
GetClassName(hwnd, ClassName, Length(ClassName));
Result := ClassName <> Server;
if not Result then
PUINT_PTR(lParam)^ := hwnd;
end;
var
Wnd, WndChild: HWND;
begin
Wnd := FindWindow('IEFrame', nil); // top level IE
if Wnd <> 0 then begin
WndChild := 0;
EnumChildWindows(Wnd, #EnumChildren, UINT_PTR(#WndChild));
if WndChild <> 0 then
..
end;
I've inserted an Assert to indicate where it fails with a '64-bit Windows' target platform. There's no problem with the code if I un-nest the callback.
I'm not sure if the erroneous values passed with the parameters are just garbage or are due to some mis-placed memory addresses (calling convention?). Is nesting callbacks infact something that I should never do in the first place? Or is this just a defect that I have to live with?
edit:
In response to David's answer, the same code having EnumChildWindows declared with a typed callback. Works fine with 32-bit:
(edit: The below does not really test what David says since I still used the '#' operator. It works fine with the operator, but if I remove it, it indeed does not compile unless I un-nest the callback)
type
TFNEnumChild = function(hwnd: HWND; lParam: LPARAM): Bool; stdcall;
function TypedEnumChildWindows(hWndParent: HWND; lpEnumFunc: TFNEnumChild;
lParam: LPARAM): BOOL; stdcall; external user32 name 'EnumChildWindows';
procedure TForm1.Button1Click(Sender: TObject);
function EnumChildren(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
const
Server = 'Internet Explorer_Server';
var
ClassName: array[0..24] of Char;
begin
Assert(IsWindow(hwnd)); // <- Assertion fails with 64-bit
GetClassName(hwnd, ClassName, Length(ClassName));
Result := ClassName <> Server;
if not Result then
PUINT_PTR(lParam)^ := hwnd;
end;
var
Wnd, WndChild: HWND;
begin
Wnd := FindWindow('IEFrame', nil); // top level IE
if Wnd <> 0 then begin
WndChild := 0;
TypedEnumChildWindows(Wnd, #EnumChildren, UINT_PTR(#WndChild));
if WndChild <> 0 then
..
end;
Actually this limitation is not specific to a Windows API callbacks, but the same problem happens when taking address of that function into a variable of procedural type and passing it, for example, as a custom comparator to TList.Sort.
http://docwiki.embarcadero.com/RADStudio/Rio/en/Procedural_Types
procedure TForm2.btn1Click(Sender: TObject);
var s : TStringList;
function compare(s : TStringList; i1, i2 : integer) : integer;
begin
result := CompareText(s[i1], s[i2]);
end;
begin
s := TStringList.Create;
try
s.add('s1');
s.add('s2');
s.add('s3');
s.CustomSort(#compare);
finally
s.free;
end;
end;
It works as expected when compiled as 32-bit, but fails with Access Violation when compiled for Win64. For 64-bit version in function compare, s = nil and i2 = some random value;
It also works as expected even for Win64 target, if one extracts compare function outside of btn1Click function.
This trick was never officially supported by the language and you have been getting away with it to date due to the implementation specifics of the 32 bit compiler. The documentation is clear:
Nested procedures and functions (routines declared within other routines) cannot be used as procedural values.
If I recall correctly, an extra, hidden, parameter is passed to nested functions with the pointer to the enclosing stack frame. This is omitted in 32 bit code if no reference is made to the enclosing environment. In 64 bit code the extra parameter is always passed.
Of course a big part of the problem is that the Windows unit uses untyped procedure types for its callback parameters. If typed procedures were used the compiler could reject your code. In fact I view this as justification for the belief that the trick you used was never legal. With typed callbacks a nested procedure can never be used, even in the 32 bit compiler.
Anyway, the bottom line is that you cannot pass a nested function as parameter to another function in the 64 bit compiler.