【windows核心编程】一个HOOK的例子
一、应用场景
封装一个OCX控件,该控件的作用是来播放一个视频文件,需要在一个进程中放置四个控件实例。 由于控件是提供给别人用的,因此需要考虑很多东西。
二、考虑因素
1、控件的父窗口resize时需要控件也随之resize
子窗体不能知道父窗口的resize情况,因为父窗口不会主动把这一情况通知子窗口。 因此需要放一个钩子来主动得知父窗口的resize事件,然后告诉控件窗口做出适当的改变。
这里用了一个局部钩子,即线程钩子。 被放置钩子的线程是控件的父窗口所在的线程(这也是考虑第3个因素的原因)。
2、这四个控件是否为同一个父窗口
在该场景中考虑到了多个控件在同一个父窗口的情况:这种情况下,当父窗口resize的时候,就需要通知每一个控件(子窗口)。 又因为多个控件的父窗口可能不是同一个,因此钩子就要被放置到每一个父窗口所在的线程。
3、执行四个控件UI相关代码的线程是否为同一个线程
由于控件的所在场景相对无法控制,因此控件的父窗口的代码有可能是在不同的线程中执行的(虽然99.999%可能性是同一个线程),这就需要在每个不同的线程中放置一个钩子(这基于一个前提,那就是 放置钩子的线程 == 执行钩子回调函数的线程)。
综合3个因素,可以得出这样一个 对应关系
一个线程Thread: 被放置钩子的线程 == 控件的父窗口所在的线程(可能这样说不准确,但权且这样说) == 执行钩子回调函数的线程
一个Hook: 每放置一个钩子就产生一个HOOK, 对于同一个线程只放置一次,如果四个控件的父窗口不是同一个线程,那么就要放置多个。还因为要在代码中使用CallNextHook将当前线程对应的钩子作为参数传给该函数,因此要保存线程和钩子的对应关系。
一个父窗口句柄: 即相同父窗口的控件的父窗口
若干个子窗口(控件):同一个父窗口有多个子窗口
对应关系如下
用代码描述对应关系
//key为父窗口, value为若干个子窗口
typedef map<HWND, vector<HWND> > MYMAP; typedef struct _tagHOOKInfo
{
HHOOK hHook; //钩子、线程、父窗口 三者一一对应
MYMAP parentAndOcx; //该父窗口和它的若干个子窗口(控件) }HOOKInfo, *PHOOKInfo; //key为线程ID , value为该线程对应的{钩子、父窗口、若干个子窗口 }
map<DWORD, HOOKInfo> g_ThreadParentOcxMap2;
三、关于钩子相关函数
1、WH_CALLWNDPROC钩子
这个钩子专门来截获SendMessage到窗口过程的消息,在消息被传递给窗口过程之前先被其截获。但是不能更改和丢弃消息。
2、 API 和 数据结构
//放置钩子
HHOOK SetWindowsHookEx(
__in int idHook, //钩子类型
__in HOOKPROC lpfn, //回调函数
__in HINSTANCE hMod, //模块句柄,如果是全局钩子则为DLL句柄,dwThreadID为0; 如果是局部钩子则为NULL,dwThreadID为某一线程ID
__in DWORD dwThreadId //全局钩子为0, 句柄钩子为线程ID
);
SetWindowsHookEx失败返回NULL,否则成功
WH_WNDWNDPROC对应的回调函数以及其他函数
//回调函数原型
LRESULT CALLBACK CallWndProc( __in int nCode, /*标识是否钩子回调函数必须处理这个消息。如果该值是HC_ACTION,回调函数必须处理。 如果该值大于等于0,强烈建议调用CallNextHookEx并且返回他的返回值。
如果该值小于0,钩子回调必须调用CallNextHookEx函数并且不能处理这个消息,然后必须返回CallNextHookEx的返回值。
如果钩子回调没有调用CallNextHookEx,则该函数必须返回0. */ __in WPARAM wParam, //标识消息是否由当前线程发送,非0是,0不是。
__in LPARAM lParam //一个指向CWPSTRUCT结构的指针,包含了该消息的许多细节
); typedef struct {
LPARAM lParam; //附加信息
WPARAM wParam; //附加信息
UINT message; //消息值
HWND hwnd; //消息的目标窗口
} CWPSTRUCT, *PCWPSTRUCT;
撤销钩子
BOOL UnhookWindowsHookEx(
__in HHOOK hhk //SetWindowsHookEx返回值
);
在控件的代码里获取其父窗口所在的线程ID,将钩子放置在该线程里
//根据窗口句柄获取他所在的进程和线程ID
DWORD GetWindowThreadProcessId(
__in HWND hWnd, //窗口句柄
__out LPDWORD lpdwProcessId //带回进程ID
); //该函数返回线程ID, 参数带回进程ID
四、上代码
/************************************************************************
四个视频控件,假如分别放在四个父窗口上
则每个父窗口对应的vecor子窗口中只有一个元素 假如一个父窗口上放了多个视频控件,则每个父窗口对应的vector子窗口中有多个元素 另:回调函数的主调线程 应该就是 钩子被放置的线程 【放置钩子的线程】很有可能不是【钩子被放置到的线程】
因此要保存一个 线程--钩子--父窗口(及其子窗口) 的对应关系 /************************************************************************/ typedef map<HWND, vector<HWND> > MYMAP; typedef struct _tagHOOKInfo
{
HHOOK hHook;
MYMAP parentAndOcx;
}HOOKInfo, *PHOOKInfo; map<DWORD, HOOKInfo> g_ThreadParentOcxMap2; LRESULT CALLBACK CallWndProc(
__in int nCode,
__in WPARAM wParam,
__in LPARAM lParam
)
{
//当前线程ID
DWORD dwCurrentID = GetCurrentThreadId(); PCWPSTRUCT pMsg = (PCWPSTRUCT)lParam;
map<DWORD, HOOKInfo>::iterator iter;
MYMAP::const_iterator iter2; //取得其 钩子句柄 父窗口 子窗口
PHOOKInfo pHookInfo = NULL;
HHOOK hHook = NULL;
MYMAP *pMyMap = NULL; //查找map中主键为当前线程ID的元素(认为 钩子回调函数的主调线程 == 钩子被设置的线程)
//当前接受消息的窗口为某个控件的父窗口 //经过Andy指点:进入此回调函数时就已经说明该线程对应的此钩子起作用了,那么就一定会找到当前线程对应的钩子,
//那么,又因为控件的父窗口所在UI线程是不变的,其对应的窗口句柄也不会变,钩子也一定能找到;
//下面if判断永远成立! else不会被执行到! 可以将此段if...else改造为单纯取map中value的操作,
//而不必担心主键线程ID 和 主键父窗口句柄 不存在的情况!
if ((iter = g_ThreadParentOcxMap2.find(dwCurrentID)) != g_ThreadParentOcxMap2.end()
&& (iter2 = (iter->second).parentAndOcx.find(pMsg->hwnd)) != iter->second.parentAndOcx.end() )
{
pHookInfo = &(iter->second);
hHook = pHookInfo->hHook;
pMyMap = &(pHookInfo->parentAndOcx);
}
else
{
//else永远不会被执行到!
return ;
} if (nCode == HC_ACTION )
{
if (NULL != pMsg && pMsg->message == WM_SIZE)
{
#if defined _DEBUG
OutputDebugString(TEXT("\r\n----------WM_SIZE happened: "));
OutputDebugString(TEXT("-----\r\n")); #endif for (vector<HWND>::const_iterator iter3 = iter2->second.begin();
iter3 != iter2->second.end(); ++ iter3)
{
::PostMessage(*iter3, WM_USER + , , );
}
} return ::CallNextHookEx(hHook, nCode, wParam, lParam);
}
else if (nCode < && NULL != hHook)
{
return ::CallNextHookEx(hHook, nCode, wParam, lParam);
} return ;
} extern "C" __declspec(dllexport) BOOL __stdcall StartHook(HWND hParentWnd, HWND hOCXWnd, DWORD dwThreadID)
{
DWORD dwCurrentID = GetCurrentThreadId();
BOOL bRet = FALSE; if(! ::IsWindow(hParentWnd) || ! ::IsWindow(hOCXWnd)) return FALSE; vector<HWND> hOCXWndVector;
hOCXWndVector.push_back(hOCXWnd); HOOKInfo hookInfo; map<DWORD, HOOKInfo>::iterator iterBig; //不存在主键为dwThreadID的元素,则为该线程放置钩子
if ((iterBig = g_ThreadParentOcxMap2.find(dwThreadID)) == g_ThreadParentOcxMap2.end())
{
HHOOK hHook = SetWindowsHookEx(WH_CALLWNDPROC, (HOOKPROC)CallWndProc, NULL, dwThreadID );
if(NULL == hHook) return FALSE; hookInfo.parentAndOcx.insert(map<HWND, vector<HWND> >::value_type(hParentWnd, hOCXWndVector));
hookInfo.hHook = hHook; g_ThreadParentOcxMap2.insert(map<DWORD, HOOKInfo>::value_type(dwThreadID, hookInfo));
}
else
{
//存在主键为dwThreadID的元素,则不必再设置钩子,只需要在map值字段(HOOKInfo结构体)中的map插入或追加
PHOOKInfo pHookInfo = &(iterBig->second); //判断HOOKInfo的map字段中是否存在主键为hParent的元素 MYMAP *pMap = &(pHookInfo->parentAndOcx);
if (NULL == pMap) return FALSE; MYMAP::iterator iterSmall; //如果没有 则将map<hParent, hOCXWndVector>插入
if((iterSmall = pMap->find(hParentWnd)) == pMap->end())
{
pMap->insert(MYMAP::value_type(hParentWnd, hOCXWndVector));
}
//如果有 则将hOCXWnd追加到其值vector中
else
{
(iterSmall->second).push_back(hOCXWnd);
}
} return TRUE; } extern "C" __declspec(dllexport) BOOL __stdcall StopHook()
{
for (map<DWORD, HOOKInfo>::const_iterator iter = g_ThreadParentOcxMap2.begin();
iter != g_ThreadParentOcxMap2.end(); ++ iter)
{
HHOOK hHook = (iter->second).hHook;
if(NULL != hHook) UnhookWindowsHookEx(hHook);
} return TRUE;
}
【windows核心编程】一个HOOK的例子的更多相关文章
- 【Windows核心编程】一个使用内存映射文件进行进程间通信的例子
进程间通信的方式有很多种,其底层原理使用的都是内存映射文件. 本文实现了Windows核心编程第五版475页上的demo,即使用内存映射文件来在进程间通信. 进程1 按钮[Create mappin ...
- windows核心编程---第八章 使用内核对象进行线程同步
使用内核对象进行线程同步. 前面我们介绍了用户模式下线程同步的几种方式.在用户模式下进行线程同步的最大好处就是速度非常快.因此当需要使用线程同步时用户模式下的线程同步是首选. 但是用户模式下的线程同步 ...
- 【windows核心编程】 第六章 线程基础
Windows核心编程 第六章 线程基础 欢迎转载 转载请注明出处:http://www.cnblogs.com/cuish/p/3145214.html 1. 线程的组成 ① 一个是线程的内核 ...
- Windows核心编程第二章,字符串的表示以及宽窄字符的转换
目录 Windows核心编程,字符串的表示以及宽窄字符的转换 1.字符集 1.1.双字节字符集DBCS 1.2 Unicode字符集 1.3 UTF-8编码 1.4 UTF - 32编码. 1.5 U ...
- 《Windows核心编程》读书笔记 上
[C++]<Windows核心编程>读书笔记 这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对 ...
- C++Windows核心编程读书笔记
转自:http://www.makaidong.com/%E5%8D%9A%E5%AE%A2%E5%9B%AD%E6%96%87/71405.shtml "C++Windows核心编程读书笔 ...
- windows核心编程 DLL技术 【转】
注:本文章转载于网络,源地址为:http://blog.csdn.net/ithzhang/article/details/7051558 本篇文章将介绍DLL显式链接的过程和模块基地址重定位及模块绑 ...
- 【转】《windows核心编程》读书笔记
这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对实现的推断,因此不少条款和Windows实际机制可能有出入 ...
- 《Windows核心编程系列》二十谈谈DLL高级技术
本篇文章将介绍DLL显式链接的过程和模块基地址重定位及模块绑定的技术. 第一种将DLL映射到进程地址空间的方式是直接在源代码中引用DLL中所包含的函数或是变量,DLL在程序运行后由加载程序隐式的载入, ...
- 《windows核心编程系列》十九谈谈使用远程线程来注入DLL。
windows内的各个进程有各自的地址空间.它们相互独立互不干扰保证了系统的安全性.但是windows也为调试器或是其他工具设计了一些函数,这些函数可以让一个进程对另一个进程进行操作.虽然他们是为调试 ...
随机推荐
- Session过期,跳出iframe等框架
//在你想控制跳转的页面,如login.jsp中的<head>与</head>之间加入以下代码: if(window != top){ //解决Sessio ...
- lintcode:Plus One 加一
题目: 加一 给定一个非负数,表示一个数字数组,在该数的基础上+1,返回一个新的数组. 该数字按照大小进行排列,最大的数在列表的最前面. 样例 给定 [1,2,3] 表示 123, 返回 [1,2,4 ...
- *[codility]MinAvgTwoSlice
https://codility.com/demo/take-sample-test/min_avg_two_slice 此题要求一个数组子段的最小的平均数(返回第一个数字的index).刚开始想记录 ...
- posix 线程(一):线程模型、pthread 系列函数 和 简单多线程服务器端程序
posix 线程(一):线程模型.pthread 系列函数 和 简单多线程服务器端程序 一.线程有3种模型,分别是N:1用户线程模型,1:1核心线程模型和N:M混合线程模型,posix thread属 ...
- Windows下Subversion和Apache的安装及配置(一)
1.序 Subversion可谓版本控制软件中的佼佼者,其开源性,易用性已受到众多软件开发者首选的版本控制软件.在这里我想记录我安装Subversion和Apache的过程.注意,Subversion ...
- Android:EditText 常用属性
属性 作用 android:hint="输入邮箱/用户名" 提示信息 android:inputType="textPassword" 设置文本的类型 andr ...
- EF 实体关系
基于共享主键的一对一: this.HasRequired(t => t.TRDConInfo) .WithOptional(t => t.TRDFoundationProjCheck); ...
- 极客编程必备的五大PHP开发应用
有了PHP应用可以帮助编码爱好者事半功倍,提升项目质量:有了这些最新的且灵活的PHP应用使创建编码项目更加简单.便捷.本文,我们收集了五大最新的PHP开发应用. PHP应用在网络上并不多见.最重要的是 ...
- Vim 命令笔记
给指定行添加序号 let la = 行a let lb = 行b +1 let lc = lb - la for i in range(lc) let cl = la + i call setline ...
- js、javascript正则表达式验证身份证号码
function isCardNo(card) { // 身份证号码为15位或者18位,15位时全为数字,18位前17位为数字,最后一位是校验位,可能为数字或字符X var reg = /(^\d{1 ...