注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

jasonyang9的博客

随便写写

 
 
 

日志

 
 

(怀旧系列)VC程序设计(孙鑫老师)听课笔记:20 HOOK和数据库访问  

2013-03-06 10:40:40|  分类: programming |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
(怀旧系列)VC程序设计(孙鑫老师)听课笔记:20 HOOK和数据库访问 - jasonyang9 - jasonyang9的博客
 ================
HOOK和数据库访问
================

HOOK编程
--------

Windows的消息机制:
1、用户按下键盘或单击鼠标等,操作系统感知后将消息发送到应用程序的消息队列中;
2、应用程序从消息队列中取出消息;
3、应用程序将消息调度给操作系统;
4、操作系统调用窗口类的窗口过程(回调函数),处理消息。

有时需要截获某些消息,或屏蔽用户的部分输入。可以通过HOOK过程就是钩子过程,让操作系统在传递消息时将感兴趣的消息先传递给钩子过程,做了处理后再将消息返回给操作系统(某些被放行、某些被拦截)。

相关函数

HHOOK SetWindowsHookEx(
int idHook, // hook type
HOOKPROC lpfn, // hook procedure
HINSTANCE hMod, // handle to application instance
DWORD dwThreadId // thread identifier
);
安装应用程序定义的HOOK过程到HOOK链(HOOK链是指多个HOOK过程形成的一个链,后安装的HOOK过程排在HOOK链的前端)。
安装HOOK过程监视系统或某些特定的事件,这些事件可以和某个特定的线程相关,或者和同一桌面下的所有线程相关。
idHook [in],指定安装的HOOK过程的类型(如WH_MOUSE监视鼠标消息)。
lpfn [in],指向一个HOOK过程的指针(如果dwThreadId是0,或dwThreadId指定另一个进程的线程,则必须将lpfn指向一个动态链接库中的HOOK过程,否则lpfn可以指向当前进程相关的代码中定义的HOOK过程)。
hMod [in],指向一个包含lpfn指向的HOOK过程的动态链接库的句柄,如果lpfn指向当前进程相关的代码中定义的HOOK过程,则hMod必须设为NULL。
dwThreadId [in],指定和HOOK过程相关的线程标识符(0表示和同一桌面下的所有线程相关)。
如果函数成功,返回HOOK过程的句柄,如果失败,返回NULL。

LRESULT CALLBACK MouseProc(
int nCode, // hook code
WPARAM wParam, // message identifier
LPARAM lParam // mouse coordinates
);
nCode [in],指示HOOK过程如何处理消息(HC_ACTION表示wParam和lParam参数包含了鼠标消息的信息;HC_NOREMOVE表示消息不会从消息队列中移除)。
wParam [in],表示鼠标消息。
lParam [in],指向MOUSEHOOKSTRUCT结构体的指针。
如果nCode小于0,则必须返回调用CallNextHookEx获得的返回值。

LRESULT CallNextHookEx(
HHOOK hhk, // handle to current hook
int nCode, // hook code passed to hook procedure
WPARAM wParam, // value passed to hook procedure
LPARAM lParam // value passed to hook procedure
);
传递HOOK信息到HOOK链中的下一个HOOK过程。
hhk [in],当前HOOK过程的句柄(即SetWindowsHookEx的返回值)。
nCode [in], 和一般HOOK过程中的wParam、lParam一样,这里只是将这3个参数传递给下一个HOOK过程。
wParam [in],同上。
lParam [in],同上。


新建MFC AppWizard,InnerHook,Dialog based。

HHOCK g_hKeyboard = NULL; // 全局变量,键盘HOOK过程的句柄,用于调用CallNextHookEx

LRESULT CALLBACK MouseProc(
int nCode,
WPARAM wParam,
LPARAM lParam
)
{
return 1; // 返回非0值表示已处理鼠标消息,系统将不再处理该消息(相当于屏蔽)
}

LRESULT CALLBACK KeyboardProc(
int code,
WPARAM wParam,
LPARAM lParam
)
{
if (VK_SPACE == wParam || VK_RETURN == wParam) // 屏蔽空格键(VK_SPACE)和回车键(VK_RETURN)
return 1;
else
return CallNextHookEx(g_hKeyboard, code, wParam, lParam); // 如果不是空格键或回车键则调用CallNextHookEx让HOOK链中的下一个HOOK过程处理
}

void CInnerHookDlg::OnInitDialog()
{
...
SetWindowsHookEx(WH_MOUSE, // 监视鼠标消息
MouseProc, // HOOK过程指针(函数名)
NULL, // 没有动态链接库
GetCurrentThreadId()); // 当前线程的ID

g_hKeyboard = SetWindowsHookEx(WH_KEYBOARD, // 监视键盘消息,并将返回的HOOK过程句柄保存到全局变量中
KeyboardProc,
NULL,
GetCurrentThreadId());
}

屏蔽Alt+F4(组合键),改写KeyboardProc。

LRESULT CALLBACK KeyboardProc(
int code,
WPARAM wParam,
LPARAM lParam
)
{
if (VK_F4 == wParam && (1 == (lParam>>29 & 1))) // 通过lParam的第29位(0为第1位)可判断ALT键是否被按下
return 1;
else
return CallNextHookEx(g_hKeyboard, code, wParam, lParam); // 如果不是空格键或回车键则调用CallNextHookEx让HOOK链中的下一个HOOK过程处理
}

让F2键成为退出程序的按键,改写代码。

HHOOK g_hMouse = NULL; // 全局变量,用于在WM_CLOSE后移除MouseProc HOOK
HHOCK g_hKeyboard = NULL; // 全局变量,键盘HOOK过程的句柄,用于调用CallNextHookEx
HWND g_hWnd = NULL; // 全局变量,用于保存对话框窗口句柄,调用::SendMessage

LRESULT CALLBACK MouseProc(
int nCode,
WPARAM wParam,
LPARAM lParam
)
{
return 1; // 返回非0值表示已处理鼠标消息,系统将不再处理该消息(相当于屏蔽)
}

LRESULT CALLBACK KeyboardProc(
int code,
WPARAM wParam,
LPARAM lParam
)
{
if (VK_F2 == wParam)
{
::SendMessage(m_hWnd, WM_CLOSE, 0, 0); // 发送WM_CLOSE消息
UnhookWindowsHookEx(g_hKeyboard); // 移除HOOK链中已安装的HOOK过程
UnhookWindowsHookEx(g_hMouse);
}
return 1;
}

void CInnerHookDlg::OnInitDialog()
{
...
g_hWnd = m_hWnd; // 保存对话框窗口句柄

g_hMouse = SetWindowsHookEx(WH_MOUSE, // 监视鼠标消息
MouseProc, // HOOK过程指针(函数名)
NULL, // 没有动态链接库
GetCurrentThreadId()); // 当前线程的ID

g_hKeyboard = SetWindowsHookEx(WH_KEYBOARD, // 监视键盘消息,并将返回的HOOK过程句柄保存到全局变量中
KeyboardProc,
NULL,
GetCurrentThreadId());
}

编写动态链接库中的HOOK过程,这样才可以在SetWindowsHookEx中让dwThreadId指向其他进程。
新建Win32 Dynamic-Link Library,Hook。

#include <windows.h>

HHOOK g_hMouse = NULL; // 全局变量,保存鼠标HOOK过程句柄
HHOOK g_hKeyboard = NULL; // 全局变量,保存键盘HOOK过程句柄
HWND g_hWnd = NULL; // 全局变量,保存传入的调用者窗口的句柄
HINSTANCE g_hInst; // 全局变量,用于保存动态链接库实例的句柄

LRESULT CALLBACK MouseProc( // 鼠标HOOK过程函数
int nCode,
WPARAM wParam,
LPARAM lParam
)
{
return 1; // 返回非0值表示已处理鼠标消息,系统将不再处理该消息(相当于屏蔽)
}

LRESULT CALLBACK KeyboardProc(
int code,
WPARAM wParam,
LPARAM lParam
)
{
if (VK_F2 == wParam)
{
::SendMessage(g_hWnd, WM_CLOSE, 0, 0); // 发送WM_CLOSE消息给调用进程窗口
UnhookWindowsHookEx(g_hKeyboard); // 移除HOOK链中已安装的HOOK过程
UnhookWindowsHookEx(g_hMouse);
}
return 1;
}

BOOL WINAPI DllMain( // 动态链接库的入口函数
HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpvReserved
)
{
g_hInst = hinstDLL; // 将实例的句柄保存到全局变量
}

void SetHook(HWND hwnd) // 唯一的参数将调用进程窗口句柄传入(为了在KeyboardProc中给这个窗口发送WM_CLOSE消息)
{
g_hWnd = hwnd; // 保存调用进程窗口句柄
g_hMouse = SetWindowsHookEx(WH_MOUSE, // 监视鼠标消息
MouseProc, // HOOK过程指针(函数名)
g_hInst, // 动态链接库实例的句柄,或者根本就不需要写DllMain这个入口函数,而是调用GetModuleHandle("Hook.dll")来获取该句柄
0); // 运行在同一桌面下的所有线程相关
g_hKeyboard = SetWindowsHookEx(WH_KEYBOARD, // 监视键盘消息
KeyboardProc,
g_hInst,
0);
}

在工程目录下新建文本文档Hook.def。

LIBRARY Hook
EXPORTS
SetHook @2 // 导出的函数名称,@后为指定其序号

编译生成Hook.dll。但还必须编写客户程序,加载这个动态链接库,调用SetHook函数,即设置一个全局的HOOK,和当前所有的线程相关。如果某个进程中的某个线程给一个窗口发送鼠标消息,操作系统会检查该线程是否安装了HOOK过程,由于SetHook这个全局HOOK过程已被安装,于是操作系统就会找到包含这个HOOK过程的动态链接库,并将其映射到进程的地址空间中,调用HOOK过程函数(MouseProc),本例中就是将鼠标消息屏蔽。

编写动态链接库的客户端程序。新建MFC AppWizard,HookTest,Dialog based。
工程设置Link标签页,Object/library modules中增加..\Hook\Debug\Hook.lib。
将动态链接库Hook.dll拷贝到工程目录下。

_declspec(dllimport) void SetHook(HWND hwnd); // 声明SetHook函数从动态链接库文件导入

BOOL CHookTestDlg::OnInitDialog()
{
...
SetHook(m_hWnd); // 调用Hook.dll中的SetHook函数,同时传入对话框自己的窗口句柄
return TRUE;
}

编译运行后,所有程序(线程)的鼠标和键盘操作都不起作用,都被屏蔽,除了F2键可以将HookTest程序结束。

修改程序,让其占据最前端,且不可被切换到其他程序界面。

_declspec(dllimport) void SetHook(HWND hwnd); // 声明SetHook函数从动态链接库文件导入

BOOL CHookTestDlg::OnInitDialog()
{
...
int cxScreen, cyScreen; // 屏幕的宽度和高度尺寸
cxScreen = GetSystemMetrics(SM_CXSCREEN); // 获取屏幕的宽度
cyScreen = GetSystemMetrics(SM_CYSCREEN); // 获取屏幕的高度
SetWindowPos(&wndTopMost, 0, 0, cxScreen, cyScreen, SWP_SHOWWINDOW); // 将窗口铺满整个屏幕并设为顶端

SetHook(m_hWnd); // 调用Hook.dll中的SetHook函数,同时传入对话框自己的窗口句柄
return TRUE;
}

编译运行后发现,如果用户按ALT+TAB切换到其他程序后按F2键无效,但当焦点切换回HookTest后按F2程序退出。
如果动态链接库中的代码和数据确实被多个进程共享,那么Hook.dll中的g_hWnd这个全局变量也应该被多个进程共享,也应该在处理HOOK过程时将WM_CLOSE消息发送到g_hWnd所指的HookTest对话框窗口。
实际上,多个进程不能共享同一份可写入的数据,否则将造成非常严重的后果(例如某个指针变量被修改)。Windows 2000以上系统对此采用了写入时拷贝的机制(例如,某个进程要将内容写入数据页面2,操作系统将DLL虚拟内存中的数据页面2拷贝一份到新的页面,然后将这个进程的数据页面2映射到这个新的页面,那么其他进程访问的是原数据页面2的数据,而这个进程访问的是一份拷贝)(注意:Windows 98将数据页面为每个进程复制一份拷贝)
可以修改Hoot工程,把g_hWnd放到一个新的节(SEGMENT)中,再将此节设为共享,就可实现在多个进程间共享。
(注意:新创建的节中的变量必须已经初始化)
修改Hoot.cpp。

// Hoot.cpp
#include <windows.h>

HHOOK g_hMouse = NULL; // 全局变量,保存鼠标HOOK过程句柄
HHOOK g_hKeyboard = NULL; // 全局变量,保存键盘HOOK过程句柄

#pragma data_seg("MySec") // 创建新的节
HWND g_hWnd = NULL; // 全局变量,保存传入的调用者窗口的句柄
#pragma data_seg() // 新的节结束

#pragma comment(linker, "/section:MySec,RWS") // 设置节的权限为R(Read,读)、W(Write,写)、S(Shared,共享)

HINSTANCE g_hInst; // 全局变量,用于保存动态链接库实例的句柄

LRESULT CALLBACK MouseProc( // 鼠标HOOK过程函数
int nCode,
WPARAM wParam,
LPARAM lParam
)
{
return 1; // 返回非0值表示已处理鼠标消息,系统将不再处理该消息(相当于屏蔽)
}

LRESULT CALLBACK KeyboardProc(
int code,
WPARAM wParam,
LPARAM lParam
)
{
if (VK_F2 == wParam)
{
::SendMessage(g_hWnd, WM_CLOSE, 0, 0); // 发送WM_CLOSE消息给调用进程窗口
UnhookWindowsHookEx(g_hKeyboard); // 移除HOOK链中已安装的HOOK过程
UnhookWindowsHookEx(g_hMouse);
}
return 1;
}

BOOL WINAPI DllMain( // 动态链接库的入口函数
HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpvReserved
)
{
g_hInst = hinstDLL; // 将实例的句柄保存到全局变量
}

void SetHook(HWND hwnd) // 唯一的参数将调用进程窗口句柄传入(为了在KeyboardProc中给这个窗口发送WM_CLOSE消息)
{
g_hWnd = hwnd; // 保存调用进程窗口句柄
g_hMouse = SetWindowsHookEx(WH_MOUSE, // 监视鼠标消息
MouseProc, // HOOK过程指针(函数名)
g_hInst, // 动态链接库实例的句柄,或者根本就不需要写DllMain这个入口函数,而是调用GetModuleHandle("Hook.dll")来获取该句柄
0); // 运行在同一桌面下的所有线程相关
g_hKeyboard = SetWindowsHookEx(WH_KEYBOARD, // 监视键盘消息
KeyboardProc,
g_hInst,
0);
}

此外可以在模块定义文件Hook.def中设定节的共享权限。

// Hook.def
LIBRARY Hook
EXPORTS
SetHook @2 // 导出的函数名称,@后为指定其序号
SEGMENTS
MySec READ WRITE SHARED // 指定节的权限

编译后用dumpbin -headers hook.dll就可查看其中的节信息。

...

SECTION HEADER #5
MySec name

...

Shared <--- 共享
Read Write <--- 读和写

将编译后的Hook.dll拷贝到HookTest工程目录下,重新编译运行后,切换到其他进程同样能够通过按F2键退出程序。

数据库编程
----------

每个数据库产品的接口(API)是不同的,那么为了使用这些产品而学习各种不同的API函数就增加了程序开发的难度,因此,微软使用了数据库访问技术,来提供统一的接口。

数据库访问技术
* ODBC(Open Database Connectivity),开放数据库互连。ODBC是二十世纪八十年代末九十年代初出现的技术,为编写关系数据库的客户软件提供了一种统一的接口。ODBC提供一个单一的API,可用于处理不同数据库的客户应用程序。使用ODBC API的应用程序可以和任何具有ODBC驱动程序的关系数据库进行通信。
* DAO(Data Access Object),数据访问对象。DAO就是一组Microsoft Access/Jet数据库引擎的COM自动化接口。DAO不像ODBC那样是面向C/C++程序员的,它是微软提供给Visual Basic开发人员的一种简单的数据访问方法,用于操纵Access数据库。
* RDO(Remote Data Object),远程数据对象。由于RDO直接调用ODBC API(而不是像DAO那样通过Jet引擎),所以可以为使用关系数据库的应用程序提供更好的性能。

ODBC体系架构

客户程序 ←→ ODBC驱动程序管理器 ←→ ODBC驱动程序 ←→ 各种关系数据库
←→ ODBC驱动程序 ←→ 各种关系数据库
←→ ODBC驱动程序 ←→ 各种关系数据库

* OLE DB,对象链接和嵌入数据库。OLE DB在两个方面对ODBC进行了扩展。首先,OLE DB提供了一个数据库编程的COM接口;第二,OLE DB提供了一个可用于关系型和非关系型数据源的接口。OLE DB的两个基本结构是OLE DB提供程序(Provider)和OLE DB用户程序(Consumer)。
* ADO(ActiveX Data Object),ActiveX数据对象,它建立在OLE DB之上。ADO是一个OLE DB用户程序。使用ADO的应用程序都要间接地使用OLE DB。ADO简化了OLE DB,提供了对自动化的支持,使得像VBScript这样的脚本语言可能够使用ADO访问数据库。

OLE DB体系架构

使用ADO的客户程序


OLE DB用户程序(Consumer) ↓
使用OLE DB访问数据库的程序 ADO
↑ ↑
↓ ↓
OLE DB提供程序(Provider)
↑ ↑ ↑ ↑ ↑
↓ 丨 丨 丨 丨
ODBC 丨 丨 丨 丨
↑ 丨 丨 丨 丨
↓ ↓ ↓ ↓ ↓
ODBC数据库 ODBC数据库 电子 电子 其他非关
表格 邮件 系型存储

ADO的三个核心对象
* Connection对象。表示了到数据库的连接,它管理应用程序和数据库之间的通信。Recordset和Command对象都有一个ActiveConnection属性,该属性用来引用Connection对象。
* Command对象。被用来处理重复执行的查询,或处理需要检查在存储过程调用中的输出或返回参数的值的查询。
* Recordset对象。被用来获取数据。Recordset对象存放查询的结果,这些结果由数据的行(称为记录)和列(称为字段)组成。每一列都存放在Recordset的Fields集合中的一个Field对象中。

在VB中利用ADO访问数据库(略)

新建MFC AppWizard,Ado,Dialog based。
增加一个列表框(IDC_LIST1),一个按钮(IDC_BTN_QUERY,“查询”),增加命令响应。

// StdAfx.h

#import "C:\Program Files\Common Files\System\ado\msado15.dll" no_namespace rename("EOF", "rsEOF");
// 编译一次,将VC生成的msado15.tlh和msado15.hli导入工程。tlh相当于头文件,tli相当于源文件,实际上并不需要导入它们,只是为了跟踪代码方便

void CAdoDlg::OnBtnQuery()
{

CoInitialize(NULL); // 由于ADO是OLE DB(基于COM技术)的用户程序,需要初始化COM库

_ConnectionPtr pConn(__uuidof(Connection)); // _ConnectionPtr是一个智能指针(也是一个类),__uuidof获取Connection这个接口的GUID
_RecordsetPtr pRst(__uuidof(Recordset)); // 同上

pConn->ConnectionString = "Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;Initial Catalog=pubs";
// 连接字符串,从VB拷贝
PConn->Open("", "", "", adConnectUnspecified); // 打开到数据库的连接

pRst = pConn->Execute("select * from authors", NULL, adCmdText); // 查询数据

while (!pRst->rsEOF) // 循环直到记录末尾
{
((CListBox*)GetDlgItem(IDC_LIST1))->AddString((_bstr_t)pRst->GetCollect("au_lname")); // 获取字段并加到列表控件
pRst->MoveNext(); // 游标向下移动一格
}

pRst->Close(); // 关闭记录
pConn->Close(); // 关闭连接
pRst.Release(); // 释放智能指针(注意要用.而不是->)
pConn.Release(); // 同上

CoUninitialize(); // 访问完COM库后,用CoUninitialize卸载
}

编译运行,单击“查询”按钮即可在列表控件中看到结果。

或者用pRst智能指针对象执行查询。

void CAdoDlg::OnBtnQuery()
{

CoInitialize(NULL); // 由于ADO是OLE DB(基于COM技术)的用户程序,需要初始化COM库

_ConnectionPtr pConn(__uuidof(Connection)); // _ConnectionPtr是一个智能指针(也是一个类),__uuidof获取Connection这个接口的GUID
_RecordsetPtr pRst(__uuidof(Recordset)); // 同上

pConn->ConnectionString = "Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;Initial Catalog=pubs";
// 连接字符串,从VB拷贝
PConn->Open("", "", "", adConnectUnspecified); // 打开到数据库的连接

pRst->Open("select * from authors", _variant_t((lDispatch*)pConn), // 查询记录
adOpenDynamic, adLockOptimistic, adCmdText);

while (!pRst->rsEOF) // 循环直到记录末尾
{
((CListBox*)GetDlgItem(IDC_LIST1))->AddString((_bstr_t)pRst->GetCollect("au_lname")); // 获取字段并加到列表控件
pRst->MoveNext(); // 游标向下移动一格
}

pRst->Close(); // 关闭记录
pConn->Close(); // 关闭连接
pRst.Release(); // 释放智能指针(注意要用.而不是->)
pConn.Release(); // 同上

CoUninitialize(); // 访问完COM库后,用CoUninitialize卸载
}

编译运行,结果是一样的。
或者还可以用Command智能指针对象执行查询。

void CAdoDlg::OnBtnQuery()
{

CoInitialize(NULL); // 由于ADO是OLE DB(基于COM技术)的用户程序,需要初始化COM库

_ConnectionPtr pConn(__uuidof(Connection)); // _ConnectionPtr是一个智能指针(也是一个类),__uuidof获取Connection这个接口的GUID
_RecordsetPtr pRst(__uuidof(Recordset)); // 构造Recordset智能指针对象
_CommandPtr pCmd(__uuidof(Command)); // 构造Command智能指针对象

pConn->ConnectionString = "Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;Initial Catalog=pubs";
// 连接字符串,从VB拷贝
PConn->Open("", "", "", adConnectUnspecified); // 打开到数据库的连接

pCmd->put_ActiveConnection(_variant_t((lDispatch*)pConn)); // 设置活动连接
pCmd->CommandText = "select * from authors"; // 设置命令文本
pRst = pCmd->Execute(NULL, NULL, adCmdText); // 查询记录

while (!pRst->rsEOF) // 循环直到记录末尾
{
((CListBox*)GetDlgItem(IDC_LIST1))->AddString((_bstr_t)pRst->GetCollect("au_lname")); // 获取字段并加到列表控件
pRst->MoveNext(); // 游标向下移动一格
}

pRst->Close(); // 关闭记录
pConn->Close(); // 关闭连接
pRst.Release(); // 释放智能指针(注意要用.而不是->)
pConn.Release(); // 同上
pCmd.Release(); // 同上

CoUninitialize(); // 访问完COM库后,用CoUninitialize卸载
}

编译运行,结果是一样的。

在VC中用ADO访问数据库比较麻烦,在VB中则比较方便。
而且需要数据库的知识、SQL的知识、COM的知识才能很好的访问数据库。
  评论这张
 
阅读(366)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017