原贴地址:http://blog.sina.com.cn/s/blog_56dee71a010007h1.html
最近手机上的短信存储器快满了,应该删除一些短信以留出一些空间,但是有好多短信是各个MM发过来的,舍不得就这么删除了,想导出到电脑里面保存起来。万一哪天MM成了我女朋友了,有机会的时候可以给她看看,说明我是这么珍惜跟她相关的点点滴滴。^_^于是用数据线把手机连接到电脑上,打开EasyGPRS软件,读取手机中的短信到列表窗口中。但是可惜的是EasyGPRS软件没有提供导出短信内容的功能,于是只好自己想办法了。当然最简单的方法是,把各条短信的接收时间,对方号码,内容等信息在电脑上输入一遍,保存到文件中。但是近200条短信,我可以没有那个耐心。我当然得想跟简单快捷的方法。我想到的方法就是,写一个程序,把EasyGPRS已经从手机中读取的短信内容,从列表窗口复制并保存到一个文件中。想到了就做!原本以为比较简单的东西,没想到也花了我6,7个小时时间,当然是晚上下班后和早晨上班前的空余时间。:)
先对照程序界面说说其基本功能:
点击并拖动查找工具图标到EasyGPRS软件界面上的短信列表窗口上,释放图标,程序会把短信列表的内容复制到自己的列表窗口中;在窗口中央的格式文本框中输入导出的格式;点击【导出】按钮,程序就按照指定的格式(当然只是比较简单的格式定义)将短信内容输出到下面的文本框中,并复制到剪贴板上;这时打开一个文本编辑软键,如记事本,执行粘贴命令后保存就可以了。复制短信内容并执行导出后的界面如下(事关个人隐私,我不得不做了点简单的处理^_^):
写这个程序的难点,和本文的重点就在于,怎么把另一个程序(进程)中一个列表控件中内容,复制到自己的程序(进程)中?我采用了Windows挂钩的实现方式。下面就依编程的顺序进行讲述。
要将另一个进程中的列表控件的内容复制到自己的进程中,当然首先得找到这个列表控件了,方法比较简单,按下鼠标左键,拖动窗口上的查找工具图标到目标列表窗口上,释放鼠标键就可以了。从程序实现上来讲,在查找工具图标上按下鼠标左键时,开始鼠标捕获;移动鼠标时,需要在当前鼠标位置下的窗口边框上绘制一个矩形框,表示当前的目标窗口,方法是使用
WindowFromPoint()函数取得鼠标光标下的窗口句柄,使用GetWindowRect()取得其相对于屏幕坐标系的矩形,使用GetDC(NULL)取得屏幕设备描述表,使用Rectangle()在目标窗口上绘制外边框;释放鼠标键时,停止鼠标捕捉。
找到列表控件窗口后,就可以向它发送消息取得其列数,行数和各行数据了,我刚开始时就是这么做的。但是,发送消息取得列数和行数是正确的,却不能取得各行的数据。这是为什么呢?原来,Windows编程中,允许跨越进程边界向另一个进程中的窗口发送消息(跨进程发送消息当然也是跨线程发送消息,处理方式同在线程内部发送消息的处理方式有一些不同,详细情况可以参考《Windows核心编程》第26章《窗口消息》)。但是,跨进程边界发送的消息不能带有指针类型的参数,因为 指针不能跨越进程边界。在Windows系统中,各个进程是相互隔离的,有自己的独立地址空间,一个进程中有效的指针,传递到另一个进程中不一定有效;即使有效,它也是指向另一个进程的地址空间中的某个位置了,负责处理消息的目标窗口(线程)会从那个位置取得输入参数,或者将处理结果存放到那里去,发送消息的进程无法正确地传递消息参数,或者利用指针取得消息处理时返回的结果。跨进程传递指针需要特殊的处理,例如使用COM+,或者Java RMI等分布式处理系统。
由于需要一个指针作为其参数,所以不能用普通的SendMessage()函数向另一个进程中的列表控件发送消息,取得控件的某个项目的内容。那么如何解决这个问题,取得另一个进程中的列表控件的内容呢?我想到了前段时间学习的《Windows核心编程》一书中讲到过的高级DLL操作技术,是否可以用某种DLL技术解决这个问题呢?赶紧找出书来,翻开第22章《插入DLL和挂接API》,再次阅读第3节《使用Windows挂钩来插入DLL》,我就找到解决办法了。作者在这一节介绍了使用windows挂钩在某进程中插入DLL的方法,并给出了实际的应用示例,即用Windows挂钩向资源管理器进程Explorer.exe插入DLL,来实现保存和恢复桌面图标位置的功能。我就稍微变通一下,使用Windows挂钩向EasyGPRS进程插入DLL,来实现取得EasyGPRS进程中的短信列表窗口内容的功能。
首先简单介绍以下设置Windows挂钩的函数SetWindowsHookEx:
参数的简单说明:
找到列表控件窗口后,就可以向它发送消息取得其列数,行数和各行数据了,我刚开始时就是这么做的。但是,发送消息取得列数和行数是正确的,却不能取得各行的数据。这是为什么呢?原来,Windows编程中,允许跨越进程边界向另一个进程中的窗口发送消息(跨进程发送消息当然也是跨线程发送消息,处理方式同在线程内部发送消息的处理方式有一些不同,详细情况可以参考《Windows核心编程》第26章《窗口消息》)。但是,跨进程边界发送的消息不能带有指针类型的参数,因为 指针不能跨越进程边界。在Windows系统中,各个进程是相互隔离的,有自己的独立地址空间,一个进程中有效的指针,传递到另一个进程中不一定有效;即使有效,它也是指向另一个进程的地址空间中的某个位置了,负责处理消息的目标窗口(线程)会从那个位置取得输入参数,或者将处理结果存放到那里去,发送消息的进程无法正确地传递消息参数,或者利用指针取得消息处理时返回的结果。跨进程传递指针需要特殊的处理,例如使用COM+,或者Java RMI等分布式处理系统。
由于需要一个指针作为其参数,所以不能用普通的SendMessage()函数向另一个进程中的列表控件发送消息,取得控件的某个项目的内容。那么如何解决这个问题,取得另一个进程中的列表控件的内容呢?我想到了前段时间学习的《Windows核心编程》一书中讲到过的高级DLL操作技术,是否可以用某种DLL技术解决这个问题呢?赶紧找出书来,翻开第22章《插入DLL和挂接API》,再次阅读第3节《使用Windows挂钩来插入DLL》,我就找到解决办法了。作者在这一节介绍了使用windows挂钩在某进程中插入DLL的方法,并给出了实际的应用示例,即用Windows挂钩向资源管理器进程Explorer.exe插入DLL,来实现保存和恢复桌面图标位置的功能。我就稍微变通一下,使用Windows挂钩向EasyGPRS进程插入DLL,来实现取得EasyGPRS进程中的短信列表窗口内容的功能。
首先简单介绍以下设置Windows挂钩的函数SetWindowsHookEx:
HHOOK SetWindowsHookEx( int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId ); |
idHook 挂钩的种类,在我这个程序里面使用的是
WH_GETMESSAGE标志,表示安装一个监测投递到目标线程的消息的挂钩
lpfn 挂钩函数,一个应用程序定义的回调函数,当挂钩截取到消息时,系统将调用此函数
hMod 包含挂钩函数的DLL模块句柄
dwThreadId 要安装挂钩的目标线程
Windows挂钩函数必须是包含在Dll中的。因为挂钩截取到消息时,系统使用目标线程调用挂钩函数,而安装挂钩的线程可能和目标线程不在同一个进程中,不共享同一个地址空间。这样,目标线程没办法执行位于另一个进程中的挂钩函数,因为即使知道挂钩函数的入口地址,函数的可执行代码也是在另一个进程中的。Windows要求挂钩函数位于Dll中,这样,因为Dll可以被映射到不同的进程中。安装挂钩的时候,Windows可以将原本已经映射到源进程的Dll,再映射到目标进程中。这样,位于目标进程中的目标线程就可以调用挂钩函数了。需要注意的是:挂钩函数在两个进程中的入口地址很可能是不一样的,因为包含它的Dll的在不同进程中被映射到的地址很可能是不一样的。
安装挂钩函数后,系统在截取到相应类型的消息时,就会利用目标线程调用挂钩函数,这时候就可以直接用SendMessage()函数向目标列表控件发送消息,取得其某一行的内容了。但是问题是,取得的内容是在目标进程中的,还是无法传递到源进程中。我对这个问题的解决方法是在Dll中设置一个带共享属性的节,把取得的内容放到共享节中。这样目标线程取得内容后,源进程也可以通过位于共享节的变量得到内容。注意:这里共享节是位于Dll中的,而Dll是被同时映射到两个进程中的。
安装挂钩函数后,系统在截取到相应类型的消息时,就会利用目标线程调用挂钩函数,这时候就可以直接用SendMessage()函数向目标列表控件发送消息,取得其某一行的内容了。但是问题是,取得的内容是在目标进程中的,还是无法传递到源进程中。我对这个问题的解决方法是在Dll中设置一个带共享属性的节,把取得的内容放到共享节中。这样目标线程取得内容后,源进程也可以通过位于共享节的变量得到内容。注意:这里共享节是位于Dll中的,而Dll是被同时映射到两个进程中的。
最后一个问题是:源线程和目标线程需要以某种方式来进行通信和同步它们的操作。源线程必须告诉目标线程,要取得哪个列表窗口的哪一行的内容;目标线程了解了这个信息之后,执行挂钩函数,取得列表项内容,之后源线程才能利用共享节定义的变量,将列表项内容复制到自己的列表中。我采用的通信方式是线程消息和事件。
写了这么多,才发现自己的讲述比较乱,好像没太多条理,这里简单总结一下。我要解决的问题是,把另一个程序中的一个列表窗口的内容,复制自己的程序中。因为跨进程发送消息时,消息参数不能含有指针,所以不能简单地用SendMessage()发送相关消息给另一个程序中的列表窗口,来取得其内容。最终对这个问题的解决涉及到3个方面:
写了这么多,才发现自己的讲述比较乱,好像没太多条理,这里简单总结一下。我要解决的问题是,把另一个程序中的一个列表窗口的内容,复制自己的程序中。因为跨进程发送消息时,消息参数不能含有指针,所以不能简单地用SendMessage()发送相关消息给另一个程序中的列表窗口,来取得其内容。最终对这个问题的解决涉及到3个方面:
1 利用Windows挂钩机制,将含有挂钩函数的dll插入到目标进程。目标线程在调用挂钩函数时,可以简单地用SendMessage()函数取得列表项内容。
2 目标线程取得列表项内容后,如何传回源线程。我采用的方法是利用dll中的共享数据节。
3 对于目标线程和源线程的通信和同步,我采用了线程消息和事件的机制。
现在该看具体的代码了。这是含有挂钩函数的dll程序代码:
1 #include <windows.h>
2 #include <CommCtrl.h>
3 #include <shlwapi.h>
4
5 #define UM_GET_COL_INFO (WM_USER + 0x1000)
6 #define UM_GET_ITEM_INFO (WM_USER + 0x2000)
7
8 #pragma comment(lib,"shlwapi.lib")
9 #pragma comment(lib,"comctl32.lib")
10
11 #pragma data_seg("Shared")
12 HHOOK g_hhook=NULL;
13 extern "C" __declspec(dllexport) LVCOLUMN g_colinfo={0};
14 extern "C" __declspec(dllexport) LVITEM g_iteminfo={0};
15 extern "C" __declspec(dllexport) TCHAR g_szText[500]={0};
16 #pragma data_seg()
17
18 #pragma comment(linker,"/section:Shared,rws")
19
20 static HINSTANCE g_hDll;
21
22 BOOL WINAPI DllMain(HINSTANCE hInst,DWORD fdwReason,PVOID fImpLoad)
23 {
24 switch(fdwReason)
25 {
26 case DLL_PROCESS_ATTACH:
27 g_hDll = hInst;
28 break;
29 }
30 return TRUE;
31 }
32
33 static LRESULT WINAPI GetMsgProc(int ncode,WPARAM wParam,LPARAM lParam)
34 {
35 MSG* pMsg;
36 pMsg = (MSG*)lParam;
37 /* 取得列(ListView头部)信息 */
38 if (UM_GET_COL_INFO == pMsg->message)
39 {
40 HWND hList = (HWND)(pMsg->wParam);
41 HANDLE hEvent;
42
43 ZeroMemory(g_szText,sizeof(g_szText));
44 g_colinfo.mask = LVCF_FMT | LVCF_TEXT | LVCF_WIDTH;
45 g_colinfo.fmt = LVCFMT_CENTER;
46 g_colinfo.pszText = g_szText;
47 g_colinfo.cchTextMax = 500;
48
49 ListView_GetColumn(hList,pMsg->lParam,&g_colinfo);
50
51 hEvent = OpenEvent(EVENT_ALL_ACCESS,FALSE,"MsgProcessed");
52 SetEvent(hEvent);
53 CloseHandle(hEvent);
54 }
55 /* 取得数据项信息 */
56 else if (UM_GET_ITEM_INFO == pMsg->message)
57 {
58 HWND hList = (HWND)(pMsg->wParam);
59 HANDLE hEvent;
60
61 ZeroMemory(g_szText,sizeof(g_szText));
62 g_iteminfo.mask = LVIF_TEXT;
63
64 g_iteminfo.iItem = HIWORD(pMsg->lParam);
65 g_iteminfo.iSubItem = LOWORD(pMsg->lParam);
66
67 g_iteminfo.pszText = g_szText;
68 g_iteminfo.cchTextMax = 500;
69
70 ListView_GetItem(hList,&g_iteminfo);
71
72 hEvent = OpenEvent(EVENT_ALL_ACCESS,FALSE,"MsgProcessed");
73 SetEvent(hEvent);
74 CloseHandle(hEvent);
75 }
76 return CallNextHookEx(g_hhook,ncode,wParam,lParam);
77 }
78
79 extern "C" __declspec(dllexport) BOOL WINAPI SetMyHook(DWORD dwThreadId)
80 {
81 if (0 != dwThreadId)
82 g_hhook = SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,g_hDll,dwThreadId);
83 else
84 UnhookWindowsHookEx(g_hhook);
85 return TRUE;
86 }
87
对代码说明如下:
2 #include <CommCtrl.h>
3 #include <shlwapi.h>
4
5 #define UM_GET_COL_INFO (WM_USER + 0x1000)
6 #define UM_GET_ITEM_INFO (WM_USER + 0x2000)
7
8 #pragma comment(lib,"shlwapi.lib")
9 #pragma comment(lib,"comctl32.lib")
10
11 #pragma data_seg("Shared")
12 HHOOK g_hhook=NULL;
13 extern "C" __declspec(dllexport) LVCOLUMN g_colinfo={0};
14 extern "C" __declspec(dllexport) LVITEM g_iteminfo={0};
15 extern "C" __declspec(dllexport) TCHAR g_szText[500]={0};
16 #pragma data_seg()
17
18 #pragma comment(linker,"/section:Shared,rws")
19
20 static HINSTANCE g_hDll;
21
22 BOOL WINAPI DllMain(HINSTANCE hInst,DWORD fdwReason,PVOID fImpLoad)
23 {
24 switch(fdwReason)
25 {
26 case DLL_PROCESS_ATTACH:
27 g_hDll = hInst;
28 break;
29 }
30 return TRUE;
31 }
32
33 static LRESULT WINAPI GetMsgProc(int ncode,WPARAM wParam,LPARAM lParam)
34 {
35 MSG* pMsg;
36 pMsg = (MSG*)lParam;
37 /* 取得列(ListView头部)信息 */
38 if (UM_GET_COL_INFO == pMsg->message)
39 {
40 HWND hList = (HWND)(pMsg->wParam);
41 HANDLE hEvent;
42
43 ZeroMemory(g_szText,sizeof(g_szText));
44 g_colinfo.mask = LVCF_FMT | LVCF_TEXT | LVCF_WIDTH;
45 g_colinfo.fmt = LVCFMT_CENTER;
46 g_colinfo.pszText = g_szText;
47 g_colinfo.cchTextMax = 500;
48
49 ListView_GetColumn(hList,pMsg->lParam,&g_colinfo);
50
51 hEvent = OpenEvent(EVENT_ALL_ACCESS,FALSE,"MsgProcessed");
52 SetEvent(hEvent);
53 CloseHandle(hEvent);
54 }
55 /* 取得数据项信息 */
56 else if (UM_GET_ITEM_INFO == pMsg->message)
57 {
58 HWND hList = (HWND)(pMsg->wParam);
59 HANDLE hEvent;
60
61 ZeroMemory(g_szText,sizeof(g_szText));
62 g_iteminfo.mask = LVIF_TEXT;
63
64 g_iteminfo.iItem = HIWORD(pMsg->lParam);
65 g_iteminfo.iSubItem = LOWORD(pMsg->lParam);
66
67 g_iteminfo.pszText = g_szText;
68 g_iteminfo.cchTextMax = 500;
69
70 ListView_GetItem(hList,&g_iteminfo);
71
72 hEvent = OpenEvent(EVENT_ALL_ACCESS,FALSE,"MsgProcessed");
73 SetEvent(hEvent);
74 CloseHandle(hEvent);
75 }
76 return CallNextHookEx(g_hhook,ncode,wParam,lParam);
77 }
78
79 extern "C" __declspec(dllexport) BOOL WINAPI SetMyHook(DWORD dwThreadId)
80 {
81 if (0 != dwThreadId)
82 g_hhook = SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,g_hDll,dwThreadId);
83 else
84 UnhookWindowsHookEx(g_hhook);
85 return TRUE;
86 }
87
对代码说明如下:
1 第6,7行定义了两个自定义消息,源线程给目标线程发送这两个消息,分别表示要求取得列表控件的列信息或者某个项目的信息。消息的WPARAM参数是列表控件的窗口句柄,LPARAM参数是列序号或者项目序号(高16位是项目序号,低16位是子项目序号)
2 第9,10行定义了dll需要额外链接的库。这样用预编译指令在源文件里面写,就不需要改工程属性了,比较方便。
3 第12至17行在Dll中定义了一个名为Shared的节。注意节中定义的变量必须初始化,否则变量不是被放在指定的节,而是放在默认的未初始化全局变量节。(编译时编译器会给出警告信息)。节中的后三个变量加了extern "C" __declspec(dllexport)修饰,其中__declspec(dllexport)表示需要导出指定的变量,extern "C"表示以C语言方式导出,即不给修饰名,这样即使Dll是用C++语言编写的,C语言编写的客户端程序也可以调用它。第19行定义了Shared节的属性为rws。r = readable,w=writeable,s=shared,即在多个进程间共享本节。 共享节的意义是:Dll被多次映射时,共享节中的变量只有一份,而不是通常的对每个映射dll的进程有一份,变量在多个进程间共享,类似于C++类中的静态成员变量被类的多个实例共享一样。
3 第12至17行在Dll中定义了一个名为Shared的节。注意节中定义的变量必须初始化,否则变量不是被放在指定的节,而是放在默认的未初始化全局变量节。(编译时编译器会给出警告信息)。节中的后三个变量加了extern "C" __declspec(dllexport)修饰,其中__declspec(dllexport)表示需要导出指定的变量,extern "C"表示以C语言方式导出,即不给修饰名,这样即使Dll是用C++语言编写的,C语言编写的客户端程序也可以调用它。第19行定义了Shared节的属性为rws。r = readable,w=writeable,s=shared,即在多个进程间共享本节。 共享节的意义是:Dll被多次映射时,共享节中的变量只有一份,而不是通常的对每个映射dll的进程有一份,变量在多个进程间共享,类似于C++类中的静态成员变量被类的多个实例共享一样。
4 第34行的GetMsgProc()是挂钩函数,它将被目标线程调用。此函数对6,7行定义的消息进行处理,取得列表的列信息或者项目信息,放到共享节定义的变量中;然后打开用于进程间(源进程和目标进程)通信的事件,设置事件。注意:这里可以安全地对事件句柄调用CloseHandle()函数,因为事件的引用计数是2,不仅目标进程引用了它,创建这个事件的源进程也引用了它。这里调用CloseHandle()使事件引用计数减1,变成1,系统内核的事件对象并不会被销毁。
5 第80行的SetMyHook()函数供源进程调用以设置或者取消挂钩。函数比较简单,只是简单地调用一个API函数。不导出这个函数,直接在源进程中进行调用也是可以的,只是我在源进程采用了简单的隐含链接方式,而不是动态加载方式,这样做要方便一点(不导出这个函数时安装挂钩的方法是,调用GetModuleHandle("dll模块名")取得dll模块句柄,然后在调用SetWindowsHookEx时传递这个模块句柄)。
6 代码中导出函数和变量用
extern "C" __declspec(dllexport)修饰,显得比较累赘,更通常采用的方法是添加一个头文件,在其中添加以下代码
#ifdef MYDLL
#define MYAPI extern "C __declspec(dllexport)
#else
#define MYAPI extern "C" __declspec(dllimport)
#endif
这样定义后,编译dll和客户端程序时就可以使用同一个头文件。这也是各种相关书籍介绍的方法。各种相关书籍上一般还会介绍使用def文件的方法,这个更简便一些。我这里只是一个简单的dll,所以没有采用这两种方法。
下面该介绍客户端程序代码了。由于代码比较多,这里只给出与本文主题相关的,主要部分的代码如下:
1 #include <shlwapi.h>
2 #pragma comment(lib,"shlwapi.lib")
3
4 #pragma comment(lib,"MsgHookDll.lib")
5
6 extern "C" __declspec(dllimport) BOOL WINAPI SetMyHook(DWORD);
7 extern "C" __declspec(dllimport) LVCOLUMN g_colinfo;
8 extern "C" __declspec(dllimport) LVITEM g_iteminfo;
9 extern "C" __declspec(dllimport) TCHAR g_szText[500];
10
11 #define UM_GET_COL_INFO (WM_USER + 0x1000)
12 #define UM_GET_ITEM_INFO (WM_USER + 0x2000)
13
14 void CExportSMSDlg::CopyDataFromList()
15 {
16 if(NULL == m_hDestWnd) return;
17
18 HWND hList,hHeader;
19 int x,y,rows,cols;
20 DWORD dwThreadId;
21 HANDLE hEvent;
22
23 //取得行列数,清空客户端程序列表内容的代码省略
24
25 hEvent = CreateEvent(NULL,FALSE,FALSE,"MsgProcessed");
26 dwThreadId = GetWindowThreadProcessId(m_hDestWnd,NULL);
27 SetMyHook(dwThreadId);
28
29 // 复制ListView头部信息的代码省略
30
31 //复制各行数据的代码类似于取得列表
32 for(y = 0; y < rows; y++)
33 for(x = 0; x < cols; x++)
34 {
35 PostThreadMessage(dwThreadId,UM_GET_ITEM_INFO,(WPARAM)m_hDestWnd,MAKELPARAM(x,y));
36 WaitForSingleObject(hEvent,INFINITE);
37
38
39 g_iteminfo.pszText = g_szText;
40 g_iteminfo.mask |= LVIF_STATE;
41 g_iteminfo.state |= LVIS_SELECTED;
42 if (0 == x)
43 ListView_InsertItem(hList,&g_iteminfo);
44 else
45 ListView_SetItem(hList,&g_iteminfo);
46 ListView_EnsureVisible(hList,y,FALSE);
47 ProcessMessage();
48 }
49
50 CloseHandle(hEvent);
51 SetMyHook(0);
52 }
2 #pragma comment(lib,"shlwapi.lib")
3
4 #pragma comment(lib,"MsgHookDll.lib")
5
6 extern "C" __declspec(dllimport) BOOL WINAPI SetMyHook(DWORD);
7 extern "C" __declspec(dllimport) LVCOLUMN g_colinfo;
8 extern "C" __declspec(dllimport) LVITEM g_iteminfo;
9 extern "C" __declspec(dllimport) TCHAR g_szText[500];
10
11 #define UM_GET_COL_INFO (WM_USER + 0x1000)
12 #define UM_GET_ITEM_INFO (WM_USER + 0x2000)
13
14 void CExportSMSDlg::CopyDataFromList()
15 {
16 if(NULL == m_hDestWnd) return;
17
18 HWND hList,hHeader;
19 int x,y,rows,cols;
20 DWORD dwThreadId;
21 HANDLE hEvent;
22
23 //取得行列数,清空客户端程序列表内容的代码省略
24
25 hEvent = CreateEvent(NULL,FALSE,FALSE,"MsgProcessed");
26 dwThreadId = GetWindowThreadProcessId(m_hDestWnd,NULL);
27 SetMyHook(dwThreadId);
28
29 // 复制ListView头部信息的代码省略
30
31 //复制各行数据的代码类似于取得列表
32 for(y = 0; y < rows; y++)
33 for(x = 0; x < cols; x++)
34 {
35 PostThreadMessage(dwThreadId,UM_GET_ITEM_INFO,(WPARAM)m_hDestWnd,MAKELPARAM(x,y));
36 WaitForSingleObject(hEvent,INFINITE);
37
38
39 g_iteminfo.pszText = g_szText;
40 g_iteminfo.mask |= LVIF_STATE;
41 g_iteminfo.state |= LVIS_SELECTED;
42 if (0 == x)
43 ListView_InsertItem(hList,&g_iteminfo);
44 else
45 ListView_SetItem(hList,&g_iteminfo);
46 ListView_EnsureVisible(hList,y,FALSE);
47 ProcessMessage();
48 }
49
50 CloseHandle(hEvent);
51 SetMyHook(0);
52 }
说明如下:
1 第4行隐含链接含有钩子函数的dll;第6到第9行声明dll导入函数和变量
2 第11,12行定义两种自定义消息类型
3 第25行创建用于源进程和目标进程通信的事件对象
3 第26,27行取得目标窗口线程ID,调用导出函数SetMyHook安装挂钩
4 第35行,客户端发送消息给目标线程,要求取得某行的数据
5 第36行,客户端等待目标线程取得列表指定行数据,放入dll共享节的相关变量中之后,触发事件。关于这一点上文已有说明。关于事件对象采用全局命名空间,一个命名的事件可以在系统中的所有进程间共享这一点,我就不用详细解释了。
6 第39行是不是有点多此一举呢? 钩子函数中已经执行了g_iteminfo.pszText = g_szText;从列表中取得某项内容,这里还有必要写这一句吗?这一句不多余,而且还很重要。Dll映射到两个进程中的起始地址很可能不一样,则g_szText这个数组类型的全局变量在两个进程中的逻辑地址是不一样的。目标进程中执行g_iteminfo.pszText = g_szText;使g_iteminfo.pszText的值等于目标进程中g_szText的地址,这个地址大多数时候是不等于源进程中的g_szText地址值的。源进程如果不再次执行这个语句,则下面的43,45行代码企图在一个很可能只相对于目标进程有效的地址(g_szText的地址)处取得项目的文本信息,这很可能造成拒绝访问异常。关于dll被映射到两个进程中的不同地址处,造成g_szText在两个进程中的逻辑地址不一样这一点,可以参看操作系统原理类教材对地址重定位的讲解。
7 第40,41行设置新添加的项目为被选中状态
8 第47行是一个自定义函数,用于处理消息队列中积压的消息,其代码如下:
static void ProcessMessage()
{
MSG msg;
while(PeekMessage(&msg,0,0,0,PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
{
MSG msg;
while(PeekMessage(&msg,0,0,0,PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
9 第50行关闭事件对象句柄是很好的习惯
10 卸载挂钩,请参看上面给出的dll源代码
最后说明两点:
1 这里给出的取得列表控件的内容是通用的。虽然我是用它来取得EasyGPRS软件的短信列表框的内容,但是它完全也可以取得其他各种列表框的内容。(本文提到的列表框是指ListView控件)
2 本文提到的dll和客户程序的源代码,可以到我的网络硬盘上下载。地址:
http://www.800disk.com/,用户名:yaozijian110,密码:123456(这个帐号只具有下载文件的权限)
这篇文章终于写完了。这才发现自己的表达能力,对内容的组织能力还是有所欠缺的,以后还要多锻炼才是。
---------------------------------------
谢谢你提供的程序,我参考了你的钩子监控指定的程序成功。
我发现我出错的地方在dwThreadId = GetWindowThreadProcessId(m_hDestWnd,NULL);
我试过用 DWORD dwThreadId=0;
GetWindowThreadProcessId(m_hDestWnd, &dwThreadId);
hook指定程序就没反应。而用dwThreadId = GetWindowThreadProcessId(m_hDestWnd,NULL);
则成功hook到。这是为什么,请指教!
我发现我出错的地方在dwThreadId = GetWindowThreadProcessId(m_hDestWnd,NULL);
我试过用 DWORD dwThreadId=0;
GetWindowThreadProcessId(m_hDestWnd, &dwThreadId);
hook指定程序就没反应。而用dwThreadId = GetWindowThreadProcessId(m_hDestWnd,NULL);
则成功hook到。这是为什么,请指教!
----------------------------------------
这是MSDN中这个函数的声明:
DWORD GetWindowThreadProcessId(HWND hWnd,
LPDWORD lpdwProcessId);
函数的第二个参数用于返回窗口所在进程的ID,返回值是创建窗口的线程ID。
你这样调用GetWindowThreadProcessId(m_hDestWnd, &dwThreadId);取得的是进程ID,而不是线程ID,所以安装挂钩失败。
DWORD GetWindowThreadProcessId(HWND hWnd,
LPDWORD lpdwProcessId);
函数的第二个参数用于返回窗口所在进程的ID,返回值是创建窗口的线程ID。
你这样调用GetWindowThreadProcessId(m_hDestWnd, &dwThreadId);取得的是进程ID,而不是线程ID,所以安装挂钩失败。
---------------------------------------------
×××××××××××××××××××××××××××××××××××××××××××××××
***********************************************************************
×××××××××××××××××××××××××××××××××××××××××××××××
其他地方引用:
以下内容我在你的另外一个类似问题中已经回答, 这里将其再重复一遍, 同时作一些补充!
以下全部手打, 自认为写的很认真,很详细, 呵呵, 如果还有不明白随时欢迎
提出来!
首先说一句, 能够调用WIN32API的编程语言很多, 例如汇编, Fortran, VB, C, Java, 甚至脚本语言matlab, ruby等, 不过原理都是一样的, 这里就以C / C++ 来为例吧
-------------------
SetWindowsHookEx一般都是写在你要使用Hook的地方, 在进一步解释之前先简略说明一下Hook相关情况
-------------------
钩子有局部和远程两种类型, 与钩子相关的函数有:
1. 建立钩子:SetWindowsHookEx, 其四个参数分别为钩子类型, 钩子函数地址, 钩子函数所在DLL的实例句柄,安装钩子后想监控的线程的ID号, 返回参数为钩子句柄
2. UnhookWindowsHookEx, 参数只有一个,为要卸载的钩子句柄
3. 钩子函数(名称任意), 三个参数, 具体意义与钩子类型有关
这里以一个例子说明一下:比如你想写一个程序, 当鼠标移到哪里时就在主程序中显示鼠标所在窗口的名称
--------------------
(1)如果鼠标只是局限在窗口内, 那么以上1, 2, 3三个函数均写在运行的主程序中, 比如函数1可以写在按下某个按钮的消息响应函数中,函数 2 写在松开按扭的消息响应函数中,函数 3 只要不写在别的函数中就行, 因为它本生就是一个要定义的独立函数
(2)如果鼠标可以在屏幕任意位置移动, 那么以上函数1, 2位置同(1), 但函数3要写在一个另外写的DLL里, 因为此时安装的是全局钩子, 为了达到获取窗口名称的目的, 在DLL里可能还要做一些其他工作,比如设置共享段, 关于这些这里不细说了
------------------------
关于钩子,以上只是简单说了一下, 不过, 使用钩子确实也很简单, 因为主要的工作还是在钩子函数里
------------------------
------------------------
------------------------
关于钩子的使用其实真的很简单, 应该说 WIN32编程 其实真的很简单, 更进一步说, 不用动脑筋写算法只用熟练语法的编程 都很简单, 这里当然也包括钩子的应用啦 :)
说到这里忍不住发表一点题外话, 编程多年, 走过很多弯路, 体会很多, 关于编程, 其实学问很大, 小到学语法, 大到算法, 应用, 架构等等, 作为一门计算机科学, 与物理, 数学等有关系的分枝还有很多, 还有与电子相关的专业等, 内容庞杂
因此, 如果你真想学点东西, 不要局限在写一个有特殊用途的桌面程序,也不要整天沉浸在各种语言,语法中, 最主要的是不要贪多! 明确自己学编程到底想做什么, 是写软件, 开发游戏, 还是搞网络, 还是做算法, 还是解决理工,数学, 电子问题
以下全部手打, 自认为写的很认真,很详细, 呵呵, 如果还有不明白随时欢迎
提出来!
首先说一句, 能够调用WIN32API的编程语言很多, 例如汇编, Fortran, VB, C, Java, 甚至脚本语言matlab, ruby等, 不过原理都是一样的, 这里就以C / C++ 来为例吧
-------------------
SetWindowsHookEx一般都是写在你要使用Hook的地方, 在进一步解释之前先简略说明一下Hook相关情况
-------------------
钩子有局部和远程两种类型, 与钩子相关的函数有:
1. 建立钩子:SetWindowsHookEx, 其四个参数分别为钩子类型, 钩子函数地址, 钩子函数所在DLL的实例句柄,安装钩子后想监控的线程的ID号, 返回参数为钩子句柄
2. UnhookWindowsHookEx, 参数只有一个,为要卸载的钩子句柄
3. 钩子函数(名称任意), 三个参数, 具体意义与钩子类型有关
这里以一个例子说明一下:比如你想写一个程序, 当鼠标移到哪里时就在主程序中显示鼠标所在窗口的名称
--------------------
(1)如果鼠标只是局限在窗口内, 那么以上1, 2, 3三个函数均写在运行的主程序中, 比如函数1可以写在按下某个按钮的消息响应函数中,函数 2 写在松开按扭的消息响应函数中,函数 3 只要不写在别的函数中就行, 因为它本生就是一个要定义的独立函数
(2)如果鼠标可以在屏幕任意位置移动, 那么以上函数1, 2位置同(1), 但函数3要写在一个另外写的DLL里, 因为此时安装的是全局钩子, 为了达到获取窗口名称的目的, 在DLL里可能还要做一些其他工作,比如设置共享段, 关于这些这里不细说了
------------------------
关于钩子,以上只是简单说了一下, 不过, 使用钩子确实也很简单, 因为主要的工作还是在钩子函数里
------------------------
------------------------
------------------------
关于钩子的使用其实真的很简单, 应该说 WIN32编程 其实真的很简单, 更进一步说, 不用动脑筋写算法只用熟练语法的编程 都很简单, 这里当然也包括钩子的应用啦 :)
说到这里忍不住发表一点题外话, 编程多年, 走过很多弯路, 体会很多, 关于编程, 其实学问很大, 小到学语法, 大到算法, 应用, 架构等等, 作为一门计算机科学, 与物理, 数学等有关系的分枝还有很多, 还有与电子相关的专业等, 内容庞杂
因此, 如果你真想学点东西, 不要局限在写一个有特殊用途的桌面程序,也不要整天沉浸在各种语言,语法中, 最主要的是不要贪多! 明确自己学编程到底想做什么, 是写软件, 开发游戏, 还是搞网络, 还是做算法, 还是解决理工,数学, 电子问题