撰文-Aweay
最近作者用20天写了一个小软件,在csdn的Bcb论坛和chinabcb发表,得到了大家的支持,并有很多朋友询问是否可以公开源代码,作者其实也不是一个保守的人,以前就公开了自己的游戏源代码,但后来这个游戏被别人盗用,还那他当共享软件收费,我非常生气,也算有了教训,这次作者在短期之内是不会公开源代码了,但面对那么多网友的支持,作者认为有责任写一些东西来帮助广大bcber爱好者共同进步,所以就写了这篇开发手记,希望对初学者有一定帮助,如果你是高手,就当我班门弄斧好了。
如果你还没有见过MySpy,你可以从http://www.chinabcb.com/bbs/viewtopic.php?t=6445 下载,这个地址需要你注册登录才能看到下载地址,否则只有介绍。
作者不打算介绍整个开发过程,就拿出问的最多的问题来介绍一下吧:
1. 如何将图标保存为真彩色图标的?
其实,这本来不改算是个问题,但在Bcb和Delphi里这可真是一个非常难解决的问题,不知大家是否知道,Bcb和Delphi的TIcon类保存的图标仅支持16色,这样不管是256色还是真彩色的图标,都只能用16色来显示,这样的保存结果不要也罢,作者曾一度想放弃保存图标的功能,但最后还是没有这样做,那么作者是怎么解决的呢?
首先作者想到了Internet,既然我有这样的问题,那么世界上肯定还有其他人遇到了这样的问题,去google上应该可以找到其他人解决问题的方法,作者花了一个下午的时间,在各大Delphi站点、faq、控件区都没有找到合适的解决方法,最后在一个日本网战上找到了一个TIconEx,从几个认识的中文里,我猜出了这个东西正好是我要的,我立刻开着快车把他下了下来,很可惜没有源代码,日本人真tmd的讨厌,怀着对日本人无限的愤怒之情,我把那个TIconEx扔进了垃圾箱。
看来他山之石是借不出来了,那么作者只好从底层出发寻找解决方案了,从这个地址http://www.csdn.net/dev/format/ ,作者找到了Icon的文件格式和一段微软工程师写的例子代码,作者用了一个晚上来研究这个Icon格式和工程师的代码,明白后,作者决定把他用Bcb封装起来,因为原来的代码是用c语言编写的,而作者是个 oo 爱好者,所以作者决定把他类化然后加入了从HICON保存文件的代码(原来的只能从模块中抽取出来保存)。
代码是写完了,但是把他用到Bcb还真是不容易,不过这已经不是技术问题了,大家如果有兴趣就自己试一试吧,对了,上面介绍Icon格式的文章是英文的,如果作者有时间了,会把他翻译成为中文的,那篇文章写的相当好。
2. 如何HOOK?
HOOK在MySpy里是用的最多的技术,从获取密码到截获系统的消息都是HOOK技术,可以说这个技术已经不是什么技术了,网上随便一个编程站点都可以找到相关介绍资料,但截获系统消息的还是比较麻烦的,作者就介绍一下它吧。
截获系统消息无非是安装系统钩子WH_CALLWNDPROC、WH_CALLWNDPROCRET和WH_GETMESSAGE,为什么要安装这么多,因为系统消息有很多不同的类型,有Send,Post还有处理消息返回也是需要截获的,当然如果你对系统菜单也感兴趣,还需要安装WH_SYSMSGFILTER钩子,因为这是一个系统范围的钩子,所以必须编写成为DLL,而核心代码也不过就是:
LRESULT CALLBACK MsgHookProc1(int nCode, WPARAM wParam,LPARAM lParam)
{
if (nCode>=0)
{
CWPSTRUCT* mm=(CWPSTRUCT*)lParam;
MsgData data;
data.msg=mm->message;
data.wparam=(int)mm->wParam;
data.lparam=(int)mm->lParam;
data.type=1;
data.result=0;
COPYDATASTRUCT copydata;
copydata.dwData=0;
copydata.cbData=sizeof(MsgData);
copydata.lpData=&data;
if(mm->hwnd==HookStruct->HookedWnd)
{
SendMessage(HookStruct->ParentWnd,WM_COPYDATA,0,(LPARAM)©data);
}
}
return(CallNextHookEx(HookStruct->MsgHook1,nCode,wParam,lParam));
}
可以看到作者是使用WM_COPYDATA向MySpy发送通知消息的,上面是其中一个HOOK的回调函数,其他类似。MySpy收到WM_COPYDATA消息后,会将lParam参数转换成为MsgData,而这个结构体封装了关于该消息全部有用的信息,就等着MySpy处理了。HOOK其实真的很简单,就是调试麻烦,作者在调试过程中,至少死机10多次,如果使用Win9x系统,可能你就光死机了。
到此你可能认为写个截获消息的功能就算完成了,如果你注意MySpy的话,并对Win32API有一定了解,你会发现,我们得到的消息是个整数的id,而我们不可能只给用户显示个id,我们需要WM_XXX这个的友好表示,但API里并没有提供把整数id转换成为友好字符串的函数,这该如何解决呢?作者最后使用了一个记录文件,文件格式如下:
0x0000 WM_NULL
0x0001 WM_CREATE
0x0002 WM_DESTROY
0x0003 WM_MOVE
0x0005 WM_SIZE
0x0006 WM_ACTIVATE
……
有了这个文件,查找对应的友好字符串就简单了,而且扩展这个记录文件非常容易,你可以加入你已知id的对应字符串。关键在于查找效率,这个也不难,排序后快速查找再合适不过了。到现在,作者也不知道我的解决方法是不是最好的,还请各位赐教?
3. 如何得到网页上的元素,并取得他的相关信息?
这个问题还是有一点难度的,特别对于不懂COM的人,这个问题就更难了。
想要操作HTML首先要得到IHtmlDocument2接口,这个接口是IE4.0以上提供用来专门与Html交互的COM对象表露的,如果我们用asp或JavaScript来操作Html,非常简单,当然得到网页里的元素也非常简单,而使用Bcb这样的开发工具来操作Html,没有微软的帮助恐怕是不行(至少作者不知道),还好微软确实提供了这方面的支持。这个支持就是OLEACC.DLL。
这个DLL的功能很多,其中之一就是从HWND得到关联的COM对象的接口,那么代码到底是什么样的呢?
CComPtr<IAccessible> spAccess;
hr=AccessibleObjectFromWindow(hwnd,0,IID_IAccessible,(void**) &spAccess);
if ( SUCCEEDED(hr) )
{
CComPtr<IServiceProvider> spServiceProv;
hr=spAccess->QueryInterface(IID_IServiceProvider,(void**)&spServiceProv);
if(hr==S_OK)
{
CComPtr<IHTMLWindow2> spWin;
hr=spServiceProv->QueryService(IID_IHTMLWindow2,IID_IHTMLWindow2,(void**)&spWin);
if(hr==S_OK)
spWin->get_document(&pDoc2);
}
}
上面就是从HWND取得IHTMLDocument2接口的核心代码了,当然你需要加入Oleacc.h和Oleacc.lib文件,得到IHTMLDocument2接口后我们就用了同ASP同样强大的能力去造作HTML了,关于如何操作HTML这里就不谈了,有ASP知识的朋友一看到这里我想都明白了吧。
这里的关键函数就是Oleacc.dll导出的AccessibleObjectFromWindow,这个函数的作用看一下名字就知道了,MSDN里给的说明如下:
Retrieves the address of the specified interface to the object associated with the given window.
所以这个函数不光可以得到IHTMLDocument2接口还可以得到Office等接口,有兴趣的可以试一试.
4. 如何编写插件?
MySpy支持插件,而且为MySpy开发插件很容易,当然这主要靠作者的合理设计和完整的开发文档(大家不要砸我:)),编写插件本来不应该算MySpy的特色功能,或者说绝对不是什么核心技术,不过既然MySpy写了,又有人问,我就顺便介绍一下如何开发插件吧。
作者在开始设计插件结构的时候,徘徊在使用COM插还是普通DLL插的交叉路口,使用COM会容易编写代码,对于开发人员容易理解,而且现在的大多数软件都是使用COM作为插件的,但使用COM的最大问题是,将大大增加MySpy的身体臃肿度和增加MySpy的开发时间,因为作者需要单独编写一个DLL来封装整个MySpy,然后表露接口,这样就额外带来了将近1倍额外脂肪,这是作者不能接受的,还有就是编写COM插件对于开发人员要求太高了;使用DLL来做插件又有很多方法,是导出若干函数来负责若干功能还是导出1-2个函数来泛化设计呢?还有就是要不要插件同MySpy有交互呢(存在双向数据访问吗?)
最后作者选择了现在的解决方法,就是仅导出一个函数的泛化设计,这个设计类似于Windows的消息机制,就是MySpy发送若干消息给插件来通知插件做什么事情,插件也可以发送消息给MySpy来控制MySpy,而开发人员仅需要处理相应消息并返回必要的值,剩下的事情由MySpy完成,这样的解决方法不需要额外的DLL,在MySpy代码内部的适当位置发送适当的消息给插件,然后处理返回值就可以了。我们来看一下导出函数的格式:
extern "C" __declspec(dllexport)
int MMPlugin(MMSG msg,MPARAM wParam,MPARAM lParam)
{
switch(msg)
{
case MM_INIT:
//place your init code here
break;
case MM_DESTROY:
//place your destroy code here
break;
…
}
}
可以看到这个导出函数,很像WndProc函数,作者这样设计的灵感完全来源于Windows消息机制的巧妙,一个整型的MPARAM参数可以传递任何类型,任何大小的数据,作者真的很佩服Windows的消息机制,不知各位朋友有什么好的方法,不妨大家一起讨论一下。
至此MySpy的关键技术已经全部介绍完了,大家可能也看到了,作者对所有的技术介绍都是点到为止,并没有讨论的很详细,这并不是作者不愿意给出源代码,而是没有必要,在作者看来解决问题的方法是思想,如果大家有兴趣,自己试一试,像作者一样花个10-20天,肯定大有收获。
作者非常欢迎大家来信讨论 siney@yeah.net 。