我有一個將代碼移植到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()'函數處理。 –