大家都知道每个窗口都有默认的窗口函数来进行对窗口消息的处理.
而子类化技术就是替换窗口的窗口函数为自己定义的函数的技术.例如下面的代码:
var
Form1: TForm1;
OldWndProc: Pointer;
implementation
{$R *.dfm}
function NewWndProc(hHwnd, Msg, wParam, lParam: LongWORD): Longint; stdcall;
begin
if Msg=WM_CLOSE then
exit;
Result := CallWindowProc(OldWndProc, hHwnd, Msg, wParam, lParam);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
{保存旧的窗口函数地址}
OldWndProc := Pointer(GetWindowLong(Self.Handle, GWL_WNDPROC));
{设置新的窗口函数为自定义函数}
SetWindowLong(Self.Handle, GWL_WNDPROC, Longint(@NewWndProc));
end;
这样在窗口建立时就对窗口实现了子类化,这时按下窗口的关闭按钮就会发现关不了窗口,因为新的窗口处理函数把WM_CLOSE消息过滤掉了,要取消子类化,只需要简单的把以前的窗口函数恢复过来就可以了.SetWindowLong(Self.Handle, GWL_WNDPROC, Longint(OldWndProc));
现在看来似乎很简单,只要对其它进程中的目标窗口进行子类化就可以实现对其消息的拦载监视了.但是在WIN32下,每一个进程都有自己独立的内存空间,新的窗口函数必须和目标窗口在同一个进程内,直接使用SetWindowLong(其它进程中窗口的句柄, GWL_WNDPROC, 新窗口函数)就会失败,所以就要想办法把我们的窗口函数代码放到目标进程内,这儿有二个办法,一是使用CreateRemoteThread在目标进程内建立线程,但这函数只在NT及以上操作系统实现,而且还要涉及到API地址重定位等问题,很麻烦(请参考http://www.csdn.net/develop/Read_Article.asp?Id=21079).另一个方法就是使用HOOK技术(SetWindowsHookEx,如果不知道,请先参考HOOK技术方面的文章),大家都知道,对其它进程进行HOOK时,此进程会自动加载HOOK过程所在的DLL,如果我们把窗口函数也放在DLL中,那窗口函数就相当于加载到了目标进程的地址空间中了,这方法简单易行.在这里我们就采用HOOK技术来实现跨进程子类化.
最后一个问题是如何在DLL中实现全局变量,因为DLL中的变量在每个进程加载这个DLL时都申请新的空间来存放变量,所以DLL中的变量在各个进程内不一样,可以利用内存文件映射,WM_COPYDATA等方法来实现全局变量.这儿采用内存文件映射.
现在需要的知识都已了解了,就让我们来看具体的代码吧(这儿是把所有函数放在一个DLL中):
library Hook;
uses
SysUtils,windows, Messages;
const
WM_UNSUBCLASS = WM_USER + 1001; {卸载子类化消息}
WM_NEWMESSAGE = WM_USER + 1002; {通知监视窗口拦到了新消息}
HOOK_EVENT_NAME = 'MyHook';
type
PMyDLLVar = ^TMyDLLVar;
TMyDLLVar = record
SubClass: Boolean; {是否已经子类化}
HookWindow, SpyWindow: LongWORD; {要安装HOOK的窗口及用于接收消息的窗口}
hHook: LongWORD; {HOOK句柄}
OldWndProc: pointer; {旧的窗口过程}
MsgHwnd: LongWORD;
Msg: TMessage;
end;
var
DLLData: PMyDLLVar;
{---------------------------------------}
{函数名:NewWndProc
{函数功能:新的窗口过程
{函数参数:hHwnd:窗口句柄 Msg:消息ID
{ wParam, lParam:消息参数
{函数返回值:下一个窗口过程的返回值
{---------------------------------------}
function NewWndProc(hHwnd, Msg, wParam, lParam: LongWORD): Longint; stdcall;
begin
if Msg = WM_UNSUBCLASS then {如果收到卸载子类化消息就恢复以前的WndProc}
begin
SetWindowLong(DLLData^.HookWindow, GWL_WNDPROC, longint(DLLData^.OldWndProc));
exit;
end;
{这儿是把收到的消息放在映射的内存中,我们自己的程序可以通过读这个内存来得到监视到的消息.}
DLLData^.Msg.Msg := Msg;
DLLData^.Msg.WParam := wParam;
DLLData^.Msg.LParam := lParam;
DLLData^.MsgHwnd := hHwnd;
{给监视窗口发送拦载新消息的消息}
SendMessage(DLLData^.SpyWindow, WM_NEWMESSAGE, 0, 0);
{这儿可以添加自己对目标进程消息处理的代码,因为己经是在目标进程的地址空间内,现在可以为所
欲为 ^_^)
Result := CallWindowProc(DLLData^.OldWndProc, hHwnd, Msg, wParam, lParam);
end;
{------------------------------------}
{过程名:HookProc
{过程功能:HOOK过程
{过程参数:nCode, wParam, lParam消息的相
{ 关参数
{------------------------------------}
procedure HookProc(nCode, wParam, lParam: LongWORD);stdcall;
var
hEvent: THandle;
begin
if not DllData^.SubClass then {如果此窗口未子类化}
begin {保存窗口过程地址并子类化}
if hEvent <> 0 then
begin
WaitForSingleObject(hEvent, INFINITE);
CloseHandle(hEvent);
end;
DLLData^.OldWndProc := pointer(GetWindowLong(DLLData^.HookWindow, GWL_WNDPROC));
SetWindowLong(DLLData^.HookWindow, GWL_WNDPROC, integer(@NewWndProc));
DLLData^.SubClass := True;
hEvent := OpenEvent(Synchronize, False, HOOK_EVENT_NAME);
end;
{调用下一个Hook}
CallNextHookEx(DLLData^.hHook, nCode, wParam, lParam);
end;
{------------------------------------}
{函数名:InstallHook
{函数功能:在指定窗口上安装HOOK
{函数参数:HWindow:要安装HOOK的窗口
{ SWindow:用于接收消息的窗口
{返回值:成功返回TRUE,失败返回FALSE
{------------------------------------}
function InstallHook(HWindow, SWindow: LongWORD):Boolean;stdcall;
var
ThreadID: LongWORD;
hEvent: THandle;
begin
Result := False;
DLLData^.hHook := 0;
DLLData^.HookWindow := HWindow;
DLLData^.SpyWindow := SWindow;
{得到指定窗口的线程ID}
ThreadID := GetWindowThreadProcessId(HWindow, nil);
{给指定窗口挂上钩子}
hEvent := CreateEvent(nil, True, False, HOOK_EVENT_NAME);
DLLData^.hHook := SetWindowsHookEx(WH_GETMESSAGE, @HookProc, Hinstance, ThreadID);
SetEvent(hEvent);
CloseHandle(hEvent);
if DLLData^.hHook > 0 then Result := True; {是否成功HOOK}
end;
{------------------------------------}
{过程名:UnHook
{过程功能:卸载HOOK
{过程参数:无
{------------------------------------}
procedure UnHook;stdcall;
begin
{发送卸载子类化消息给指定窗口}
SendMessage(DLLData^.HookWindow, WM_UNSUBCLASS, 0, 0);
DLLData^.SubClass := False;
{卸载Hook}
UnhookWindowsHookEx(DLLData^.hHook);
end;
{------------------------------------}
{过程名:DLL入口函数
{过程功能:进行DLL初始化,释放等
{过程参数:DLL状态
{------------------------------------}
procedure MyDLLHandler(Reason: Integer);
var
FHandle: LongWORD;
begin
case Reason of
DLL_PROCESS_ATTACH:
begin {建立文件映射,以实现DLL中的全局变量}
FHandle := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, $ff, 'MYDLLDATA');
if FHandle = 0 then
if GetLastError = ERROR_ALREADY_EXISTS then
begin
FHandle := OpenFileMapping(FILE_MAP_ALL_ACCESS, False, 'MYDLLDATA');
if FHandle = 0 then Exit;
end else Exit;
DLLData := MapViewOfFile(FHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if DLLData = nil then
CloseHandle(FHandle);
end;
DLL_PROCESS_DETACH:
if Assigned(DLLData) then
begin
UnmapViewOfFile(DLLData);
DLLData := nil;
end;
DLL_THREAD_ATTACH:;
DLL_THREAD_DETACH:;
end;
end;
{$R *.res}
exports
InstallHook, UnHook, HookProc;
begin
DLLProc := @MyDLLHandler;
MyDLLhandler(DLL_PROCESS_ATTACH);
end.
编译这个DLL,然后在我们的程序中加载这个DLL,并调用InstallHook(目标窗口句柄, 自己窗口句柄)就可 以实现对目标窗口消息的监视了(在接收到WM_NEWMESSAGE消息时读映射的内存),调用UnHook则可以卸载掉子类化和HOOK.具休的代码还请读者自行编写.