//------------------------------------------------------------------
{ DLL单元 }
library Project1;uses
  ShareMem, //因为用到了string类型
  SysUtils,
  Classes,
  Unit1 in 'Unit1.pas';{$R *.res}
exports
  StartSpyMessage,
  StopSpyMessage;begin
end.
//------------------------------------------------------------------
{ DLL单元引用的Unit1单元 }
//------------------------------------------------------------------
unit Unit1;
{  }
interfaceuses
  Messages, Windows, Classes, SysUtils;{ 开始监视 hSpyWnd为调用者窗体上Edit控件的句柄 dwThreadId为要监视的线程的ID }
function StartSpyMessage(hSpyWnd: THandle; dwThreadId: DWORD): Boolean; stdcall;{ 结束监视 }
procedure StopSpyMessage; stdcall;implementationvar
  HWndProc: THandle; //WH_CALLWNDPROC钩子的句柄  { 保存StartSpyMessage在被调用时传进来的要监视的线程的ID }
  ghSpyWnd: HWND;    { WH_CALLWNDPROC的钩子过程 }
function CallWndProc(code: Integer; wP: WPARAM; lP: LPARAM): LRESULT; stdcall;
var
  s: string;
begin
  if code = HC_ACTION then
    if PCWPSTRUCT(lp)^.message = WM_COMMAND then //只想捕获菜单操作和快捷键操作
    begin
      { 设置DLL调用程序上的Edit控件的内容为 受控程序的菜单ID或快捷键ID }
      s := IntToStr(LOWORD(PCWPSTRUCT(lp)^.wParam));
      MessageBox(0, '截获到了', '', MB_OK);
      SendMessage(ghSpyWnd, WM_SETTEXT, Length(s), Longint(s)); { ghSpyWnd为DLL调用程序上的Edit控件的句柄 }
    end;
  Result := CallNextHookEx(HWndProc, code, wP, lP);
end;function StartSpyMessage(hSpyWnd: THandle; dwThreadId: DWORD): Boolean;
begin
  Result := False;
  ghSpyWnd := hSpyWnd;
  if HWndProc <> 0 then
    Exit;  { 因为只想监视指定程序,不想将DLL注入所有进程空间来挂WH_CALLWNDPROC钩子,所以使用了ThreadId 主要是想节省资源 }
  HWndProc := SetWindowsHookEx(WH_CALLWNDPROC, @CallWndProc, HInstance, dwThreadId);
  if HWndProc = 0 then
  begin
    StopSpyMessage;
    Exit;
  end;
  Result := True;
end;procedure StopSpyMessage;
begin
  UnhookWindowsHookEx(HWndProc);
  HWndProc := 0;
end;end.
//------------------------------------------------------------------
//------------------------------------------------------------------
{ DLL的调用程序 窗体上放两个Edit,两个Button }
unit Unit1;interfaceuses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Button1: TButton;
    Button2: TButton;
    Edit2: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;var
  Form1: TForm1;implementation{$R *.dfm}
function StartSpyMessage(hSpyWnd: THandle; dwThreadId: DWORD): Boolean; stdcall; external 'Project1.dll';
procedure StopSpyMessage; stdcall; external 'Project1.dll';
procedure TForm1.Button1Click(Sender: TObject);
begin
  { Edit2上的内容为16进制的线程ID(创建最外层窗体的线程的ID),可以用Spy++查得 }
  StartSpyMessage(Edit1.Handle, strtoint('$' + Edit2.Text)); //开始监视
end;procedure TForm1.Button2Click(Sender: TObject);
begin
  StopSpyMessage; //停止监视
end;procedure TForm1.FormDestroy(Sender: TObject);
begin
  StopSpyMessage;
end;end.
//------------------------------------------------------------------
现在的问题是:
在开始监视后用进程管理器可以看到Project1.dll和borlandmm.dll已经进入了受控程序的进程中。比如RealPlayer。
可是不论我操作其菜单还是使用快捷键,我的监控程序都监视不到!怎么回事?
另外在停止监视时受控程序会弹出出错对话框,报告“0X00000000指令引用的0X00000000内存。该内存不能为read”怎么回事?差哪呢?为什么就不能捕获WM_COMMAND消息呢?为什么?????

解决方案 »

  1.   

    在我的文章中已经说得很明白了,去看看吧:
    http://blog.csdn.net/linzhengqun
    找钩子的文章
      

  2.   

    呵呵,上面那位仁兄的就有了
    http://blog.csdn.net/linzhengqun/archive/2005/10/06/496082.aspx
      

  3.   

    兄第啊,看你写得程序还算不是菜鸟了, 怎么不会用 Google 呢?我帮你 Google 了一下 1000多个结果.  第 5个结果 就是 上面那位仁兄的文章.
      

  4.   

    谢谢各位来捧场。这个问题我已经解决了。to:Riddick2046(神为) 
    我上网的时间有限。太多资料我也看不过来。to:suncheng_hong(亮)
    我问完问题后就下线回家了。我也不知道回复情况如何!当时上网的时间到了。所以很着急,希望大家能尽快的回复我的帖子因此给大家发了短消息。不好意思,骚扰大家了。说说我是怎么解决的:
    写这段程序的时候我抱着侥幸的心理,不想使用映射共享内存的方法来进行主进程与DLL间的通信。认为在调用DLL时初始化DLL中的全局变量然后再将DLL注入其它进程的空间那么这个全局变量的值也会被复制过去。然而回家后经过试验发现当DLL被注入其它进程的地址空间的时候实际上是被重新初始化了。所以这个小聪明看来是不行的。还是乖乖的用影射共享内存的方法解决了。
    ◆◆◆不知道我上面的说法对不对?就是“发现当DLL被注入其它进程的地址空间的时候实际上是被重新初始化了”这句对不对?
      

  5.   

    to:suncheng_hong(亮)
    刚才到大富翁上看了一下。确实没人解决。怎么回事呢?大富翁怎么了?刚才忘了说了。不使用string类型了,也不引用ShareMem单元了。退出出错的问题没有了。
    在DLL中还是尽量不用这个内存管理器好。还是自己管理内存吧。麻烦就麻烦点吧。
      

  6.   

    呵呵,用VC就来得更简单一些,
    不过在Delphi就只能用内存映射了。
    DLL进入每一个进程实际就是完全不同的两份拷贝,所以你需要一种全局共享的内存供这些DLL使用。
      

  7.   

    Delphi我不会用,你的问题大概是因为没有共享段的问题,98下可能可以,但NT操作系统的话,DLL虽然是共享的,但是因为操作系统的拷贝写入机制存在,所以各个进程中的dll如果要写入某共享地址,那么在它写入的那个进程的地址将被重新分页,并成为进程的私有页面,所以进程中并不能通过DLL共享数据,VC下的解决方法是加一个新的共享数据段,或者使用文件映射内存,另外,在DLL入口DLLMAIN中不要初始化共享数据,否则会重复初始化