topameng's profileQuake3 启示录PhotosBlogListsMore ![]() | Help |
|
|
March 18 _beginthread还是CreateThreadhttp://www.diybl.com/ I. 起因 今天一个朋友问我程序中究竟应该使用_beginthread还是CreateThread,并且告诉我如果使用不当可能会有内存泄漏。其实我过去对这个问题也是一知半解,为了对朋友负责,专门翻阅了一下VC的运行库(CRT)源代码,终于找到了答案。 II. CRT CRT(C/C++ Runtime Library)是支持C/C++运行的一系列函数和代码的总称。虽然没有一个很精确的定义,但是可以知道,你的main就是它负责调用的,你平时调用的诸如strlen、strtok、time、atoi之类的函数也是它提供的。我们以Microsoft Visual.NET 2003中所附带的CRT为例。假设你的.NET 2003安装在C:\Program Files\Microsoft Visual Studio .NET 2003中,那么CRT的源代码就在C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\crt\src中。既然有了这些实现的源代码,我们就可以找到一切解释了。 III. _beginthread/_endthread 这个函数究竟做了什么呢?它的代码在thread.c中。阅读代码,可以看到它最终也是通过CreateThread来创建线程的,主要区别在于,它先分配了一个_tiddata,并且调用了_initptd来初始化这个分配了的指针。而这个指针最后会被传递到CRT的线程包装函数 _threadstart中,在那里会把这个指针作为一个TLS(Thread Local Storage)保存起来。然后_threadstart会调用我们传入的线程函数,并且在那个函数退出后调用_endthread。这里也可以看到, _threadstart用一个__try/__except块把我们的函数包了起来,并且在发生异常的时候,调用exit退出。(_threadstart和endthread的代码都在thread.c中) IV. CreateThread和CRT 或许有人会说,我用CreateThread创建线程以后,我也调用了C运行库函数,并且也使用ExitThread退出了,可是我的程序运行得好好的,既没有因为CRT没有初始化而崩溃,也没有因为忘记调用 _endthread而发生内存泄漏,这是为什么呢,让我们继续我们的CRT之旅。 V. 使用ptd的函数 那么,究竟那些函数使用了_getptd呢?很多!在CRT目录下搜索_getptd,你会发觉很多意想不到的函数都用到了它,除了strtok、 rand这类需要保持状态的,还有所有的字符串相关函数,因为它们要用到ptd中的locale信息;所有的mbcs函数,因为它们要用到ptd中的 mbcs信息,...。 VI. 测试代码 下面是一段测试代码(leaker中用到了atoi,它需要ptd): 代码: #include <windows.h> volatile bool threadStarted = false; void leaker() DWORD __stdcall CreateThreadFunc( LPVOID ) DWORD __stdcall CreateThreadFuncWithEndThread( LPVOID ) void __cdecl beginThreadFunc( LPVOID ) int main() VII. 总结 如果你使用了DLL方式链接的CRT库,或者你只是一次性创建少量的线程,那么你或许可以采取鸵鸟策略,忽视这个问题。上面一节代码中第3种方法基于对 CRT库的了解,但是并不保证这是一个好的方法,因为每一个版本的VC的CRT可能都会有些改变。看来,除非你的头脑清晰到可以记住这一切,或者你可以不厌其烦的每调用一个C函数都查一下CRT代码,否则总是使用 _beginthread(或者它的兄弟_beginthreadex)是一个不错的选择。 [后记] May 08 什么才是真相?简介 微软游戏技术组软件设计工程师 Chuck Walbourn Beware of QueryPerformanceCounter()When it comes to high-precision timing on Windows, many have gotten used to using the CPU's time stamp counter (TSC). The time stamp counter is a 64-bit counter that was added to most x86 CPUs starting around the Pentium era, and which counts up at the clock rate of the CPU. The TSC is generally readable via the RDTSC instruction from user mode, making it the fastest, easiest, and most precise time base available on modern machines. Alas, it is rather unsafe to use. The first problem you quickly run into is that there is no easy way to accurately and reliably determine the clock speed of the CPU, short of perhaps doing calibration over a longish period of time. Sometimes you don't need super accuracy or only need to deal with timing ratios, in which this doesn't matter. However, you're still screwed when you discover that on CPUs with speed switching, the speed at which the TSC counts will change when the CPU speeds up or slows down, which makes the TSC's rate swing all over the place. And if this weren't enough, the TSC is not always synchronized on dual-core or SMP systems, meaning that the reading from the TSC will jump back and forth by as much as 0.2ms as the kernel moves your thread back and forth across the CPUs. Programs which do not have adequate safety protection may be surprised when time momentarily runs backwards. For reasons like these, Microsoft now recommends that you use QueryPerformanceCounter() to do high-precision timing. What they don't tell you, though, is that QPC() is equally broken. The documentation for QueryPerformanceFrequency() says that not all systems have a high-performance counter. Truth be told, I've never seen a system that didn't support QPF/QPC, including ones running Windows 98, NT4, and XP. However, the timer that is used can vary widely between systems. On Win9x systems that I've seen, QPF() returns 1193181 -- which looks suspiciously like the clock rate of the venerable 8253/8254 timer. On a PIII-based Windows 2000 system, I got 3549545, which happens to be the frequency of the NTSC color subcarrier, but is probably just a factor of a common clock crystal used by some chipset timer. And I've also seen the CPU clock speed show up, or CPU clock divided by 3. Some of these timers used for QPC also have bugs. When I was looking at some anomalous capture logs from one of my systems, I noticed that the global_clock values from the capture subsystem, which were recorded in the capture log, occasionally jumped forward or backward by a few seconds compared to the video capture clock. (While video capture drivers are notoriously flaky, there were no gaps in the video and I'm pretty sure my PlayStation 2 didn't burp for three seconds.) When I tried Windows XP x64 Edition, the HAL used the CPU TSC for QueryPerformanceCounter() without realizing that Cool & Quiet would cause it to run at half normal speed. And recently, I've had the pleasure of seeing a dual-core system where use of the TSC exposed QPC-based programs to the same CPU-mismatch bug that RDTSC incurred. So, realistically, using QPC() actually exposes you to all of the existing problems of the time stamp counter AND some other bugs. So, what to do? I switched VirtualDub's capture subsystem from QueryPerformanceCounter() to timeGetTime(). I had to give up microsecond precision for only millisecond, but it's more reliable. If you don't really need high precision, you can use GetTickCount(), which has terrible precision on Win9x (55ms), but it's reliable, and it's fast, since it just reads a counter in memory. If you're a user suffering from this problem, you can try fixing the problem by adding /usepmtimer to the end of the boot.ini entry, which switches QPC() to use an alternate timer (usual disclaimers apply; back up data before trying; no purchase necessary; void where prohibited). April 21 建立调试符号库April 02 驱动级模拟(硬件模拟)转个文章,即使使用directinput 也不能屏蔽按键精灵的输入,而按键精灵不过调用了第三方的dll.来实现的知道这些有利于检测简单的按键程序 对于检测加速器就更简单了,其实只要你调试一下程序就能发现所谓加速器的秘密。不过是让2次timeGetTime 之类函数差值变长。甚好解决 如果上面的方法你都试过了,可是你发现目标程序却仍然顽固的不接受你模拟的消息,寒~~~~~~~~~还好,我还剩下最后一招,这就是驱动级模拟:直接读写键盘的硬件端口! 有一些使用DirectX接口的游戏程序,它们在读取键盘操作时绕过了windows的消息机制,而使用DirectInput.这是因为有些游戏对实时性控制的要求比较高,比如赛车游戏,要求以最快速度响应键盘输入。而windows消息由于是队列形式的,消息在传递时会有不少延迟,有时1秒钟也就传递十几条消息,这个速度达不到游戏的要求。而DirectInput则绕过了windows消息,直接与键盘驱动程序打交道,效率当然提高了不少。因此也就造成,对这样的程序无论用PostMessage或者是keybd_event都不会有反应,因为这些函数都在较高层。对于这样的程序,只好用直接读写键盘端口的方法来模拟硬件事件了。要用这个方法来模拟键盘,需要先了解一下键盘编程的相关知识。 在DOS时代,当用户按下或者放开一个键时,就会产生一个键盘中断(如果键盘中断是允许的),这样程序会跳转到BIOS中的键盘中断处理程序去执行。打开windows的设备管理器,可以查看到键盘控制器由两个端口控制。其中&H60是数据端口,可以读出键盘数据,而&H64是控制端口,用来发出控制信号。也就是,从&H60号端口可以读此键盘的按键信息,当从这个端口读取一个字节,该字节的低7位就是按键的扫描码,而高1位则表示是按下键还是释放键。当按下键时,最高位为0,称为通码,当释放键时,最高位为1,称为断码。既然从这个端口读数据可以获得按键信息,那么向这个端口写入数据就可以模拟按键了!用过QbASIC4.5的朋友可能知道,QB中有个OUT命令可以向指定端口写入数据,而INP函数可以读取指定端口的数据。那我们先看看如果用QB该怎么写代码: 假如你想模拟按下一个键,这个键的扫描码为&H50,那就这样 OUT &H64,&HD2 '把数据&HD2发送到&H64端口。这是一个KBC指令,表示将要向键盘写入数据 OUT &H60,&H50 '把扫描码&H50发送到&H60端口,表示模拟按下扫描码为&H50的这个键 那么要释放这个键呢?像这样,发送该键的断码: OUT &H64,&HD2 '把数据&HD2发送到&H64端口。这是一个KBC指令,表示将要向键盘写入数据 OUT &H60,(&H50 or &H80) '把扫描码&H50与数据&H80进行或运算,可以把它的高位置1,得到断码,表示释放这个键 好了,现在的问题就是在VB中如何向端口写入数据了。因为在windows中,普通应用程序是无权操作端口的,于是我们就需要一个驱动程序来帮助我们实现。在这里我们可以使用一个组件WINIO来完成读写端口操作。什么是WINIO?WINIO是一个全免费的、无需注册的、含源程序的WINDOWS2000端口操作驱动程序组件(可以到http://www.internals.com/上去下载)。它不仅可以操作端口,还可以操作内存;不仅能在VB下用,还可以在DELPHI、VC等其它环境下使用,性能特别优异。下载该组件,解压缩后可以看到几个文件夹,其中Release文件夹下的3个文件就是我们需要的,这3个文件是WinIo.sys(用于win xp下的驱动程序),WINIO.VXD(用于win 98下的驱动程序),WinIo.dll(封装函数的动态链接库),我们只需要调用WinIo.dll中的函数,然后WinIo.dll就会安装并调用驱动程序来完成相应的功能。值得一提的是这个组件完全是绿色的,无需安装,你只需要把这3个文件复制到与你的程序相同的文件夹下就可以使用了。用法很简单,先用里面的InitializeWinIo函数安装驱动程序,然后就可以用GetPortVal来读取端口或者用SetPortVal来写入端口了。好,让我们来做一个驱动级的键盘模拟吧。先把winio的3个文件拷贝到你的程序的文件夹下,然后在VB中新建一个工程,添加一个模块,在模块中加入下面的winio函数声明: Declare Function MapPhysToLin Lib "WinIo.dll" (ByVal PhysAddr As Long, ByVal PhysSize As Long, ByRef PhysMemHandle) As Long Declare Function UnmapPhysicalMemory Lib "WinIo.dll" (ByVal PhysMemHandle, ByVal LinAddr) As Boolean Declare Function GetPhysLong Lib "WinIo.dll" (ByVal PhysAddr As Long, ByRef PhysVal As Long) As Boolean Declare Function SetPhysLong Lib "WinIo.dll" (ByVal PhysAddr As Long, ByVal PhysVal As Long) As Boolean Declare Function GetPortVal Lib "WinIo.dll" (ByVal PortAddr As Integer, ByRef PortVal As Long, ByVal bSize As Byte) As Boolean Declare Function SetPortVal Lib "WinIo.dll" (ByVal PortAddr As Integer, ByVal PortVal As Long, ByVal bSize As Byte) As Boolean Declare Function InitializeWinIo Lib "WinIo.dll" () As Boolean Declare Function ShutdownWinIo Lib "WinIo.dll" () As Boolean Declare Function InstallWinIoDriver Lib "WinIo.dll" (ByVal DriverPath As String, ByVal Mode As Integer) As Boolean Declare Function RemoveWinIoDriver Lib "WinIo.dll" () As Boolean ' ------------------------------------以上是WINIO函数声明------------------------------------------- Declare Function MapVirtualKey Lib "user32" Alias "MapVirtualKeyA" (ByVal wCode As Long, ByVal wMapType As Long) As Long '-----------------------------------以上是WIN32 API函数声明----------------------------------------- 再添加下面这个过程: Sub KBCWait4IBE() '等待键盘缓冲区为空 Dim dwVal As Long Do GetPortVal &H64, dwVal, 1 '这句表示从&H64端口读取一个字节并把读出的数据放到变量dwVal中 'GetPortVal函数的用法是GetPortVal 端口号,存放读出数据的变量,读入的长度 Loop While (dwVal And &H2) End Sub 上面的是一个根据KBC规范写的过程,它的作用是在向键盘端口写入数据前等待一段时间,后面将会用到。 然后再添加如下过程,这2个过程用来模拟按键: Public Const KBC_KEY_CMD = &H64 '键盘命令端口 Public Const KBC_KEY_DATA = &H60 '键盘数据端口 Sub MyKeyDown(ByVal vKeyCoad As Long) '这个用来模拟按下键,参数vKeyCoad传入按键的虚拟码 Dim btScancode As Long btScancode = MapVirtualKey(vKeyCoad, 0) KBCWait4IBE '发送数据前应该先等待键盘缓冲区为空 SetPortVal KBC_KEY_CMD, &HD2, 1 '发送键盘写入命令 'SetPortVal函数用于向端口写入数据,它的用法是SetPortVal 端口号,欲写入的数据,写入数据的长度 KBCWait4IBE SetPortVal KBC_KEY_DATA, btScancode, 1 '写入按键信息,按下键 End Sub Sub MyKeyUp(ByVal vKeyCoad As Long) '这个用来模拟释放键,参数vKeyCoad传入按键的虚拟码 Dim btScancode As Long btScancode = MapVirtualKey(vKeyCoad, 0) KBCWait4IBE '等待键盘缓冲区为空 SetPortVal KBC_KEY_CMD, &HD2, 1 '发送键盘写入命令 KBCWait4IBE SetPortVal KBC_KEY_DATA, (btScancode or &H80), 1 '写入按键信息,释放键 End Sub 定义了上面的过程后,就可以用它来模拟键盘输入了。在窗体模块中添加一个定时器控件,然后加入以下代码: Private Sub Form_Load() If InitializeWinIo = False Then '用InitializeWinIo函数加载驱动程序,如果成功会返回true,否则返回false MsgBox "驱动程序加载失败!" Unload Me End If Timer1.Interval=3000 Timer1.Enabled=True End Sub Private Sub Form_Unload(Cancel As Integer) ShutdownWinIo '程序结束时记得用ShutdownWinIo函数卸载驱动程序 End Sub Private Sub Timer1_Timer() Dim VK_A as Long = &H41 MyKeyDown VK_A MyKeyUp VK_A '模拟按下并释放A键 End Sub 运行上面的程序,就会每隔3秒钟模拟按下一次A键,试试看,怎么样,是不是对所有程序都有效果了? 需要注意的问题: 要在VB的调试模式下使用WINIO,需要把那3个文件拷贝到VB的安装目录中。 键盘上有些键属于扩展键(比如键盘上的方向键就是扩展键),对于扩展键不应该用上面的MyKeyDown和MyKeyUp过程来模拟,可以使用下面的2个过程来准确模拟扩展键: QUOTE: Sub MyKeyDownEx(ByVal vKeyCoad As Long) '模拟扩展键按下,参数vKeyCoad是扩展键的虚拟码 Dim btScancode As Long btScancode = MapVirtualKey(vKeyCoad, 0) KBCWait4IBE '等待键盘缓冲区为空 SetPortVal KBC_KEY_CMD, &HD2, 1 '发送键盘写入命令 KBCWait4IBE SetPortVal KBC_KEY_DATA, &HE0, 1 '写入扩展键标志信息 KBCWait4IBE '等待键盘缓冲区为空 SetPortVal KBC_KEY_CMD, &HD2, 1 '发送键盘写入命令 KBCWait4IBE SetPortVal KBC_KEY_DATA, btScancode, 1 '写入按键信息,按下键 End Sub Sub MyKeyUpEx(ByVal vKeyCoad As Long) '模拟扩展键弹起 Dim btScancode As Long btScancode = MapVirtualKey(vKeyCoad, 0) KBCWait4IBE '等待键盘缓冲区为空 SetPortVal KBC_KEY_CMD, &HD2, 1 '发送键盘写入命令 KBCWait4IBE SetPortVal KBC_KEY_DATA, &HE0, 1 '写入扩展键标志信息 KBCWait4IBE '等待键盘缓冲区为空 SetPortVal KBC_KEY_CMD, &HD2, 1 '发送键盘写入命令 KBCWait4IBE SetPortVal KBC_KEY_DATA, (btScancode or &H80), 1 '写入按键信息,释放键 End Sub 还应该注意的是,如果要从扩展键转换到普通键,那么普通键的KeyDown事件应该发送两次。也就是说,如果我想模拟先按下一个扩展键,再按下一个普通键,那么就应该向端口发送两次该普通键被按下的信息。比如,我想模拟先按下左方向键,再按下空格键这个事件,由于左方向键是扩展键,空格键是普通键,那么流程就应该是这样的: MyKeyDownEx VK_LEFT '按下左方向键 Sleep 200 '延时200毫秒 MyKeyUpEx VK_LEFT '释放左方向键 Sleep 500 MyKeyDown VK_SPACE '按下空格键,注意要发送两次 MyKeyDown VK_SPACE Sleep 200 MyKeyUp VK_SPACE '释放空格键 March 31 PeekMessage 与 GetMessagePeekMessage 似乎是针对一个窗口线程的而GetMessage 是对整个进程的。当一个进程创建1个以上窗口的(这个很重要),当切换进程中不同窗口的时候PeekMessage 会得到WM_QUIT消息。而GetMessage不会. 似乎GetMessage比PeekMessage能更快的相应消息,但没有消息时GetMessage会挂起进程等待消息,所以好的做法是联合使用2个函数: while (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) //是否有消息 March 21 sdk 中使用 richedit21 使用richedit 控件首先要加载 richedit 的dll. January 14 双核原子操作#include "stdafx.h" struct Tf inline __int64 GetCycleCount() static unsigned int __stdcall QueueThreadFunc(void * pv) } int _tmain(int argc, _TCHAR* argv[]) for(int i = 0; i < 400; ++i) Sleep(100); for(int i=0;i<400;i++) January 05 LoadLibrary 998郁闷的问题。LoadLibrary 后 FreeLibrary 在load 任何dll 都会失败。用GetLastError 获得的错误码是998.浪费了2-3个小时,最后调试看卸载过程发现了 vld .在dll中引入了vld的头文件检查内存泄漏。但 FreeLibrary 并没有把它卸载掉。或许因为主进程中也用了vld.导致FreeLibrary dll 不能释放它(vld 加载释放由vld自己决定的)。当再次LoadLibrary 时因为资源没有释放完全,就会发生访问冲突 December 29 无锁多线程纹理资源读取崩溃了一天,终于搞定了,惨不忍睹啊。 LARGE_INTEGER n0,n1; __asm RDTSC November 22 软件Release 版本 Crash 堆栈信息收集比较详细的地址是:http://www.codeproject.com/debug/XCrashReportPt3.asp 不过作者考虑了没有vc等情况。 头文件 minidump.h #pragma once // based on dbghelp.h class MiniDumper #include "minidump.h" LPCTSTR MiniDumper::m_szAppName; MiniDumper::MiniDumper(LPCTSTR szAppName) MiniDumper::~MiniDumper() LONG MiniDumper::TopLevelFilter( struct _EXCEPTION_POINTERS *pExceptionInfo ) // firstly see if dbghelp.dll is around and has the function we need if (GetModuleFileName( NULL, szDbgHelpPath, _MAX_PATH )) if (hDll==NULL) LPCTSTR szResult = NULL; if (hDll) // work out a good place for the dump file _tcscat( szDumpPath, m_szAppName ); // ask the user if they want to save a dump file if (hFile!=INVALID_HANDLE_VALUE) // write the dump if (szResult) return retval; September 05 给 CListCtrl 加背景图片和文字居中.h memset(&lvc,0,sizeof(lvc)); lvc.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_FMT;
August 02 去掉webbrowse控件的滚动条 // TODO: 在此处添加消息处理程序代码 对于delphi vb 类很简单: IHTMLBodyElementDisp(IHTMLDocument2(WebBrowser1.document).body).scroll:= 'no'; vc 要查询接口才能使用,相应代码如下: HRESULT hr = S_OK; IDispatch* pDocDisp = m_wndWeb.get_Document(); //m_wndWeb 为 webbrowse控件类对象 if (pDocDisp) { IHTMLDocument2* pDoc = NULL; IHTMLElement* pElement = NULL; IHTMLBodyElement* pBodyElement = NULL; hr = pDocDisp->QueryInterface(IID_IHTMLDocument2, reinterpret_cast<void **>(&pDoc)); hr = pDoc->get_body(&pElement); hr = pElement->QueryInterface(IID_IHTMLBodyElement, (void**)&pBodyElement); pBodyElement->put_scroll(::SysAllocString(L"no")); pBodyElement->Release(); pElement->Release(); pDoc->Release(); pDocDisp->Release(); } 网页中也必须有去掉滚动条的格式说明,可以参考baidu,google等网站 July 25 0x7c921230 处未处理的异常: 用户断点 幸好这两天代码不多,当调试进程结束时遇到了这个错误,中断到了ntdll内,没有办法跟踪堆栈。 而非调试状态运行程序反而没有这个错误。当排出掉自己写的 int 3 之后(程序中也可以自己写断点中断)。感觉是退出的时候有些问题,在网上查找了一下,有人是DestroyWindow时窗口句柄不存在了。看了一下自己相关的代码没什么问题。无意间想到了在接受WM_CLOSE 消息时,调用了PostQuitMessage(0),case 块后面写成了 break ,进而调用了默认的消息处理进程。把它修改为return 0 之后,问题解决了。不知道是不是还有其他人遇到其它的情况,欢迎拍砖探讨 检测屏幕分辨率和色深 HDC hDC; July 12 紫光输入法 冲突 下面是一段常用的代码,而当使用 ctrl+shift 切换紫光输入法,输入文字。反复切换两次之后。GetMessage 系统函数取消息时冲突。而使用其它输入法都没有问题。经过 google 发现 firefox 也和紫光冲突,也有人和操作系统冲突的。说明此输入法 hook msg 有问题,不得不说紫光输入法确实好用。但下面的代码几乎每个mfc程序都差不多到了,或者有些程序直接调用GetMessage,而没有使用PeekMessage。冲突环境:使用 winxp+sp2 更新了所有系统补丁。紫光拼音3.0 。
如果你是开发人员,还是建议你不要用这个输入法了。
while (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) //获得 WM_QUIT 消息
{ MSG msg; if ( !GetMessage (&msg, NULL, 0, 0) ) { } // save the msg time, because wndprocs don't have access to the timestamp
TranslateMessage (&msg); DispatchMessage (&msg); } June 16 使用 IPicture 和 MemDC 前一段时间,用MFC作个界面程序:画图并在上面写一段文字。原来用IPicture 在DC上 Render ,然后用DC TextOut 出文字,结果文字闪耀。在 OnEraseBkgnd(CDC* pDC) 用了 Mem dc 也没有起作用, 初始还以为刷新快不能做到不闪耀,后来才发现 mem dc 忘了设置 Bitmap ,没有 bitmap 对于内存dc不能画上内容,而 OnPaint 中又没有使用内存DC。导致一直是OnPaint 中DC在画图和写字。
bool DrawPicture(CDC* pdc, IPicture* pic, const CRect& rcBounds)
{ CDC MemDC; //定义内存DC MemDC.CreateCompatibleDC(NULL); //注意不要写成.CreateCompatibleDC(pdc); MemDC.SetBkMode(TRANSPARENT); //设置透明,使写字没有白色的背景块 //得到图片的宽度和高度 SIZE sizeInHiMetric; pic->get_Width(&sizeInHiMetric.cx); //获取加载进pic的图像宽度和高度 pic->get_Height(&sizeInHiMetric.cy); CBitmap bmpFace; //创建bitmap
bmpFace.CreateCompatibleBitmap(pdc, rcBounds.Width(), rcBounds.Height()); //将这幅图片选入内存DC,原来memdc不起作用就是因为,bmp图片没有选入到dc中.郁闷
CBitmap* pOldBmp = MemDC.SelectObject(&bmpFace); //把整个图片 {0,cy,cx,-cy} .拷贝到 memdc 的rcBounds 区域
pic->Render(MemDC.GetSafeHdc(), rcBounds.left, rcBounds.top, rcBounds.right, rcBounds.bottom,
0, sizeInHiMetric.cy, sizeInHiMetric.cx, -sizeInHiMetric.cy, &rcBounds); MemDC.SetTextColor(RGB(0,255,0));
MemDC.TextOut(3,3,m_strFileTips); //拷贝memdc (0,0,rcbounds.width,rcbounds.height) 区域到 pdc
pdc->BitBlt(0,0,rcBounds.Width(),rcBounds.Height(),&MemDC,0,0,SRCCOPY); //拷贝 memdc - > dc MemDC.SelectObject(pOldBmp);
bmpFace.DeleteObject(); MemDC.DeleteDC(); return true; } 对于镂空图可以使用
void DrawTransPic(IPicture* pic,const CRect& rc,COLORREF clr)
{ if(pic == NULL) return; CDC dc; //内存DC dc.CreateCompatibleDC(NULL); CBitmap bmp; bmp.CreateCompatibleBitmap(&m_MemDC,m_RtDC.Width(),m_RtDC.Height()); //m_RtDC为内存DC区域 CBitmap* pold = dc.SelectObject(&bmp); SIZE sizeInHiMetric; pic->get_Width(&sizeInHiMetric.cx); pic->get_Height(&sizeInHiMetric.cy); //拷贝整个图片到dc整个客户区
pic->Render(dc.GetSafeHdc(), 0, 0, m_RtDC.Width(), m_RtDC.Height(), 0, sizeInHiMetric.cy, sizeInHiMetric.cx, -sizeInHiMetric.cy, NULL); //把 pic 画到真个dc 上 //这里的m_MemDC 也是内存DC,拷贝dc 整个区域到m_MemDC 的 rc 区域,clr 为要剔除掉的关键色
m_MemDC.TransparentBlt(rc.left,rc.top,rc.Width(),rc.Height(),&dc,0,0,m_RtDC.Width(),m_RtDC.Height(),clr); bmp.DeleteObject(); dc.SelectObject(pold); dc.DeleteDC(); } 最后 m_RealDC 真正的设备 dc, 获取全部 memdc 内容
m_RealDC->BitBlt(0,0,m_RtDC.Width(),m_RtDC.Height(),&m_MemDC,0,0,SRCCOPY);
可以在 OnPaint() 和 OnEraseBkgnd(CDC* pDC) 调用该函数DrawPicture 函数,OnEraseBkgnd(CDC* pDC) 注意要 return TRUE;
使用后不再有闪耀的问题。 使用对话框打开文件夹获得路径一般用CFileDialog 只能打开文件而不能打开文件夹。实现这个功能可以使用windows api
LPITEMIDLIST SHBrowseForFolder(LPBROWSEINFO lpbi);
BROWSEINFO bi; char path[MAX_PATH]; LPITEMIDLIST pbi; ZeroMemory(&bi,sizeof(BROWSEINFO)); bi.pszDisplayName = path; //用来存放文件夹名称的缓冲区 pbi = SHBrowseForFolder(&bi); //打开文件夹对话框 SHGetPathFromIDList(pbi,path); //path 就是获得的文件夹路径 September 25 鼠标HOOK程序 对于win32每个进程都有自己独立的4GB空间,这个每个程序相对于其他程序都是独立的,一个程序轻易不能访问其他程序地址,一旦访问了轻则跳出出错提示,重则蓝屏,然而当你离开了当前程序,却想要跟踪一些消息,就困难重重了。幸好windws 给我们提供了钩子(hook)函数。 钩子(hook)一般分为两个等级:全局钩子和局部钩子。全局顾名思义可以挂钩其他程序的消息,而局部则直挂钩使用钩子函数的进程。当然两种都要使用到dll,需要把dll注入到其他进程中,所以使用一定要小心,使用不当会影响其他程序的稳定性。下面先介绍一下windows的钩子函数: 安装钩子函数: HHOOK SetWindowsHookEx( int idHook,HOOKPROC lpfn,HINSTANCE hMod,DWORD dwThreadId); IdHook: 钩子类型,有鼠标、键盘、巨集等等10几种 Lpfn: 挂钩的函数,用来处理拦截消息的函数。必须是全局函数 HMod: 当前进程的句柄 dwThreadId: 设置要挂接的线程ID.为NULL则为全局钩子 卸载钩子函数 BOOL UnhookWindowsHookEx(HHOOK hhk); Hhk 要卸载的钩子句柄 还有一个函数: LRESULT CallNextHookEx(HHOOK hhk,int nCode,WPARAM wParam, LPARAM lParam); 用于把拦截的消息继续传递下去,不然其他程序可能会得不到相应的消息,在一个按钮上点击,而程序不能执行,是很让人费解的。 基本写钩子程序主要是用上面的3个函数。下面就在vc下,一步一步写一个鼠标钩子。 首先当然是创始一个工程了(呵呵,废话),选择 MFC AppWizard(dll),下一步后,选择 MFC Extension Dll,接着创建 mousehook.h 和mousehook.cpp 文件 mousehook.h 文件内容如下,是不是很短小呢,选择MFC Extension(扩展) Dll就可以使用类,其他普通和常规dll是不能办到的,不过选择了扩展dll,这个dll就只能在 MFC 程序中使用,在vb、delphi中就不能用了: // ____________________________________________________________________________ // // 类: CMousehook // 目的: 用来挂钩鼠标 // 描述: 该类会随着钩子客户端关闭而自动卸载 // ____________________________________________________________________________ #ifndef _MOUSEHOOK_ //控制头文件只包含一次 #define _MOUSEHOOK_ #define WM_MOUSE_HOOK WM_USER+110 //传递给钩子dll客户端的消息 class AFX_EXT_CLASS CMousehook:public CObject { public: CMousehook(); //析构时自动卸载钩子 ~CMousehook(); //安装钩子,把客户程序窗口句柄发给dll,拦截后dll向客户程序发送消息WM_MOUSE_POS BOOL installhook(HWND hclientwnd); //卸载钩子 BOOL uninstallhook(); //鼠标点击次数 int GetMouseClickCount(); //包含鼠标位置的数组。为什么使用POINT 结构传递坐标客户端赋值不能编译成功?? int* GetClickPosition(); }; #endif 下面是相应的实现文件: // ================== // INCLUDE FILES // ================== #include "stdafx.h" #include #include "mousehook.h" // ================== // DEFINES // ================== #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif //公共数据,用于不通进程的数据共享。不用进程都装载mousehook.dll,但都放在自己的范围,所以mousehook类成员是不能共享的,必须创建全局数据段。在不同程序之间共享这些数据, //还需要在相应的 .def 文件中加入 SECTIONS mymousedata READ WRITE SHARED //把该全局数据段设置为读写共享 //全局共享数据段声明 #pragma data_seg("mymousedata") static HINSTANCE hinst=NULL; //注入钩子的程序句柄 static HHOOK g_hook=NULL; //钩子句柄 static HWND g_hwnd=NULL; //需要处理钩子消息的客户端句柄 static int mouseclickcount=0; //鼠标点击次数 static int position[2]={0,0};//鼠标的位置信息 #pragma data_seg() static AFX_EXTENSION_MODULE MousehookDLL = { NULL, NULL }; extern "C" int APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { UNREFERENCED_PARAMETER(lpReserved); if (dwReason == DLL_PROCESS_ATTACH) { TRACE0("MOUSEHOOK.DLL Initializing!\n"); if (!AfxInitExtensionModule(MousehookDLL, hInstance)) return 0; new CDynLinkLibrary(MousehookDLL); } else if (dwReason == DLL_PROCESS_DETACH) { TRACE0("MOUSEHOOK.DLL Terminating!\n"); AfxTermExtensionModule(MousehookDLL); } //传给SetWindowsHookEx()函数的当前程序句柄; hinst=hInstance; return 1; // ok } //传递给钩子安装函数参数 lpfn ,一个要注入到各个进程的全局函数。必须声明为 extern “C” extern "C" __declspec(dllexport) LRESULT WINAPI mouseproc(int nCode,WPARAM wParam,LPARAM lParam) { //检测鼠标消息,鼠标钩子消息存放在 wParam 参数中,lParam 指向MOUSEHOOKSTRUCT //的指针,存放鼠标位置,当前窗口句柄,鼠标消息等等。 if(wParam==WM_MOUSEMOVE||wParam==WM_LBUTTONDOWN||wParam==WM_RBUTTONDOWN) { LPMOUSEHOOKSTRUCT mousedetials=(LPMOUSEHOOKSTRUCT) lParam; //由于c函数不支持结构赋值,使用MOUSEHOOKSTRUCT 变量传递坐标不能成功 position[0]=mousedetials->pt.x; position[1]=mousedetials->pt.y; if(wParam==WM_LBUTTONDOWN) { mouseclickcount++; } //给钩子客户端发送消息,使客户端刷新数据,使用PostMessage,不要使用//SendMessage, SendMessage要等到函数返回才继续执行。 PostMessage(g_hwnd,WM_MOUSE_HOOK,0,0); } //把钩子消息传递下去。 return CallNextHookEx(g_hook,nCode,wParam,lParam); } CMousehook::CMousehook() { } CMousehook::~CMousehook() { uninstallhook(); } BOOL CMousehook::installhook(HWND hclientwnd) { BOOL bRESULT=FALSE; //判断是否安装了一次钩子 if(g_hook!=NULL) { bRESULT=true; } else { //安装钩子 g_hook=SetWindowsHookEx(WH_MOUSE,(HOOKPROC)mouseproc,hinst,0); if(g_hook!=NULL) { bRESULT=TRUE; } else { //安装钩子失败(可能吗???) bRESULT=FALSE; MessageBox(NULL,"挂钩失败","Failure",MB_OK); } } //钩子的客户端窗口句柄 g_hwnd=hclientwnd; return bRESULT; } BOOL CMousehook::uninstallhook() { BOOL bResult=false; if (g_hook!=NULL) { // 卸载钩子 bResult=UnhookWindowsHookEx(g_hook); if (bResult) { g_hook=NULL; } } else { bResult=true; } return bResult; } int CMousehook::GetMouseClickCount() { //返回鼠标点击次数 return mouseclickcount; } int* CMousehook::GetClickPosition() { //返回鼠标坐标 return position; } 现在完成的是鼠标钩子的动态链接库,经过编译后需要经应用程序的调用才能实现对当前系统下各线程间鼠标消息的拦截处理。这部分同普通动态链接库的使用没有任何区别,在将其使用LoadLibrary 加载到进程后,首先调用动态链接库的Cmousehook: :installhook函数安装好钩子,此时即可对系统下的鼠标消息实施拦截处理。经上述编程,在安装好鼠标钩子后,鼠标在移动到系统任意窗口上时,马上就会通过对鼠标消息的拦截处理而获取到当前鼠标位置。系统钩子具有相当强大的功能,通过这种技术可以对几乎所有的Windows系统消息和事件进行拦截处理。这样向QQ自动隐藏(或者可停靠)功能,都很容易实现。以及一些简单的木马,当判断鼠标当前窗口是密码窗口时,只需要向该窗口发送WM_GETTEXT即可轻松得到密码。这种技术广泛应用于各种自动监控系统对进程外消息的监控处理。本文只对鼠标钩子的一些基本原理和一般的使用方法做了简要的探讨,感兴趣的读者完全可以在本文所述代码基础之上用类似的方法实现对 诸如键盘钩子、外壳钩子等其他类型钩子的安装与使用。本文所述代码在Windows 2000下由Microsoft Visual C++ 6.0编译通过。 |
|
|