我有一个将代码移植到64位的问题。它的目的是为WinAPI声明一个方法函数作为回调函数。有些人可能知道这是TCallbackThunk
(见this SO answer for some further explanation)。通过匿名函数将TCallbackThunk转换为64位
我认为这段代码比较老,但是使用了相同的方法。它也应该与TCallbackThunk
一起工作。 让我告诉你的代码,因为它适用于32位:
unit SubClassing;
interface
uses
Windows;
type
TCallbackMode = (cbNoCallSuper, cbKeepResult, cbUseSuperResult);
TWndProc = procedure(Window: HWND; var Message: LongInt;
var WParam: Longint; var LParam: Longint;
var LResult: LongInt; var Mode: TCallbackMode) of object;
type
PSubClassInfo = ^TSubClassInfo;
TSubClassInfo = record
OriginalWndProc: Pointer;
NewWndProc: TWndProc;
Handle: HWnd;
Stub: Pointer;
end;
function SubClassWindow(Handle: HWnd; WndProc: TWndProc): PSubClassInfo;
procedure UnSubClassWindow(var Info: PSubClassInfo);
implementation
uses
SysUtils;
function MakeProcInstance(Data: Pointer; Code: Pointer): Pointer;
begin
{$IFDEF WIN64}
Assert(False); // lacks implementation for 64-bit
{$ELSE}
// A simple GetMem will _not_ do the trick.
// To avoid conflicting with DEP it is essential that the page will
// be marked as being executable.
Result := VirtualAlloc(nil, 15, $3000, $40);
asm
MOV BYTE PTR [EAX], $B9
MOV ECX, Data
MOV DWORD PTR [EAX+$1], ECX
MOV BYTE PTR [EAX+$5], $5A
MOV BYTE PTR [EAX+$6], $51
MOV BYTE PTR [EAX+$7], $52
MOV BYTE PTR [EAX+$8], $B9
MOV ECX, Code
MOV DWORD PTR [EAX+$9], ECX
MOV BYTE PTR [EAX+$D], $FF
MOV BYTE PTR [EAX+$E], $E1
end;
{$ENDIF}
end;
procedure FreeProcInstance(ProcInstance: Pointer);
begin
VirtualFree(ProcInstance, 15, $8000);
end;
function MultiCaster(SubClassInfo: PSubClassInfo; Window: HWND; Message,
WParam: Longint; LParam: Longint): LongInt; stdcall;
var
Mode: TCallbackMode;
Res: LongInt;
begin
SubClassInfo.NewWndProc(Window, Message, WParam, LParam, Result, Mode);
if Mode <> cbNoCallSuper then
begin
Res := CallWindowProc(SubClassInfo^.OriginalWndProc, Window, Message, wParam, lParam);
if Mode = cbUseSuperResult then
Result := Res;
end;
end;
function SubClassWindow(Handle: HWnd; WndProc: TWndProc): PSubClassInfo;
begin
Result := new(PSubClassInfo);
ZeroMemory(Result, SizeOf(TSubClassInfo));
Result^.NewWndProc := WndProc;
Result^.Handle := Handle;
Result^.Stub := MakeProcInstance(Result, @MultiCaster);
Result^.OriginalWndProc := Pointer(SetWindowLong(Handle, GWL_WNDPROC, Integer(Result^.Stub)));
end;
procedure UnSubClassWindow(var Info: PSubClassInfo);
begin
if Assigned(Info) then
begin
if Assigned(Info^.OriginalWndProc) then
begin
SetWindowLong(Info^.Handle, GWL_WNDPROC, Integer(Info^.OriginalWndProc));
FreeProcInstance(Info^.Stub);
end;
Dispose(Info);
end;
Info := nil;
end;
end.
之前移植的MakeProcInstance
汇编代码为64位,我想先尝试与匿名功能的解决方案。这会在汇编代码变得过时时提供更好的可维护性。因此,我宣布
TMultiCasterFunc = reference to function(Window: HWND; Message,
WParam: Longint; LParam: Longint): LongInt stdcall;
,并重新声明TSubClassInfo
作为
TSubClassInfo = record
OriginalWndProc: Pointer;
NewWndProc: TWndProc;
Handle: HWnd;
Stub: TMultiCasterFunc;
end;
然后,我实现了一个功能
function GetMultiCasterFunction(const ASubClassInfo: PSubClassInfo): TMultiCasterFunc;
begin
Result := function(Window: HWND; Message, WParam: Longint; LParam: Longint): LongInt stdcall
begin
Result := MultiCaster(ASubClassInfo, Window, Message, WParam, LParam);
end;
end;
功能SubClassWindow
和UnSubClassWindow
进行编辑,以这样的:
function SubClassWindow(Handle: HWnd; WndProc: TWndProc): PSubClassInfo;
begin
Result := new(PSubClassInfo);
ZeroMemory(Result, SizeOf(TSubClassInfo));
Result^.NewWndProc := WndProc;
Result^.Handle := Handle;
Result^.Stub := GetMultiCasterFunction(Result);
Result^.OriginalWndProc := Pointer(SetWindowLong(Handle, GWL_WNDPROC, NativeInt(@(Result^.Stub))));
end;
procedure UnSubClassWindow(var Info: PSubClassInfo);
begin
if Assigned(Info) then
begin
if Assigned(Info^.OriginalWndProc) then
begin
SetWindowLong(Info^.Handle, GWL_WNDPROC, Integer(Info^.OriginalWndProc));
FreeProcInstance(@(Info^.Stub));
end;
Dispose(Info);
end;
Info := nil;
end;
我很高兴看到代码真的编译。我并不期待这一点。 不幸的是,当代码被执行时,我得到了各种异常。例如,拨打GetMultiCasterFunction
时,我在System._IntfCopy
中收到AV at address 0000000000419A32 reading address FFFFFFFFFFFFFFFF
。
我如何使用匿名函数有什么不对吗?仅供参考,我正在用Delphi XE4做这件事。我应该尝试什么?
我在ASM有一些经验。所以我可以为64位做一个单独的解决方案。但那应该是最后的手段。
对于32位和64位的VCL“WndProc”方法使用的thunking代码由'System.Classes'单元中的'MakeObjectInstance()'函数处理。 –