关于截图实现的大致原理可参看以下链接:
http://student.csdn.net/space.php?uid=110891&do=blog&id=38571
第二篇在:
http://www.cppblog.com/ArthasLee/archive/2010/11/24/134432.html#FeedBack
以下,笔者的文章大部分只是自己的学习体会和总结,并不是非常系统完整的流程叙述。当然也有对以上链接中提到的一些东西的补充。
开发环境:Visual Studio 2008(Visual Assist);
运行环境:Windows XP;
程序开发框架:MFC;
其他支持:MSDN、Google、CSDN;
首先是截图,主要运用的是BitBlt函数,用法和例程详见:
http://msdn.microsoft.com/en-us/library/dd183402(v=VS.85).aspx
1 void CZhaoChaV21Dlg::CaptureImage( DWORD*& pBuffer, int x, int y, int width, int height )
2 {
3 //get the handle of the game window
4 pGameWnd = FindWindow( NULL, _T("大家来找茬") );
5 if( !pGameWnd )
6 {
7 MessageBox( _T("FindWindow fail! Please run the game!") );
8 return ;
9 }
10
11 HBITMAP hBitMap;
12 HDC hSrcDC;
13 HDC hNewDC;
14 HWND hWnd = *pGameWnd;//主游戏句柄
15
16 hSrcDC = NULL;
17
18 //header of the BMP information
19 BITMAPINFOHEADER bi;
20 bi.biSize = sizeof(BITMAPINFOHEADER);
21 bi.biWidth = nWidth;
22 bi.biHeight = nHeight;
23 bi.biPlanes = 1;
24 bi.biBitCount = 32;
25 bi.biCompression = BI_RGB;
26 bi.biSizeImage = width * height;
27 bi.biXPelsPerMeter = 0;
28 bi.biYPelsPerMeter = 0;
29 bi.biClrUsed = 0;
30 bi.biClrImportant = 0;
31
32 hSrcDC = ::GetDC( *pGameWnd ); //获取游戏DC
33 hNewDC = CreateCompatibleDC( hSrcDC ); //创建兼容DC
34 hBitMap = CreateCompatibleBitmap( hSrcDC, width, height );//设置图大小
35 SelectObject( hNewDC, hBitMap ); //绑定图片
36
37 //将位图复制到DC中
38 BitBlt( hNewDC, 0, 0, width, height, hSrcDC, x, y, SRCCOPY);
39
40 //为图片申请一块内存空间
41 if( pBuffer ) //pBuffer is not NULL
42 {
43 delete []pBuffer;
44 pBuffer = NULL;
45 }
46
47 pBuffer = new DWORD[ width * height ];
48 if( !pBuffer )
49 {
50 MessageBox( _T("Apply memory for Bits fail!") );
51 return ;
52 }
53
54 //将图片数据存储到对应的pBuffer变量中,这步颇为重要,以为pBuffer指向的内容将被其他函数或者代码引用
55 GetDIBits( hNewDC, hBitMap, 0, (UINT)height, pBuffer, (BITMAPINFO *)&bi, DIB_RGB_COLORS );
56
57 //release DC
58 DeleteObject( hBitMap );
59 DeleteDC( hNewDC );
60
61 return ;
62 }
以上是笔者写的实现代码,主要来自本文最开始的第一个URL地址,由于MSDN上的代码过于繁琐,因此采用了CSDN blog上的。不过感觉MSDN的代码非常严谨,值得学习,但是对于笔记来说,太多与主题无关的东西,不利于攻克主要矛盾。
截图完毕之后,需要查看一下是否截图成功,笔者采用了把图片保存到BMP文件中,再打开查看的方法,实际上到了这一步,就可以实现一个半自动的外挂(发挥自己的想象力吧~两幅图片重叠在一起不断切换,效果很动画哦~),灭哈哈!
保存图片则主要是参考了MSDN上的代码:
1 void CZhaoChaV21Dlg::SaveBMP( DWORD*& pBuffer, CString m_fileName )
2 {
3 //try to use SetDIBits to communicate SaveBMP with its outer function CaptureImage or others
4
5 //header of the BMP information
6 BITMAPINFOHEADER bi;
7 BITMAPFILEHEADER bmfHeader;
8
9 bi.biSize = sizeof(BITMAPINFOHEADER);
10 bi.biWidth = nWidth;
11 bi.biHeight = nHeight;
12 bi.biPlanes = 1;
13 bi.biBitCount = 32;
14 bi.biCompression = BI_RGB;
15 bi.biSizeImage = nWidth * nHeight;
16 bi.biXPelsPerMeter = 0;
17 bi.biYPelsPerMeter = 0;
18 bi.biClrUsed = 0;
19 bi.biClrImportant = 0;
20
21 DWORD dwBmpSize = ( (nWidth * bi.biBitCount + 31) / 32 ) * 4 * nHeight;
22
23 // A file is created, this is where we will save the screen capture.
24 HANDLE hFile = CreateFile( m_fileName,
25 GENERIC_WRITE,
26 0,
27 NULL,
28 CREATE_ALWAYS,
29 FILE_ATTRIBUTE_NORMAL, NULL);
30
31 // Add the size of the headers to the size of the bitmap to get the total file size
32 DWORD dwSizeofDIB = dwBmpSize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
33
34 //Offset to where the actual bitmap bits start.
35 bmfHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER);
36
37 //Size of the file
38 bmfHeader.bfSize = dwSizeofDIB;
39
40 //bfType must always be BM for Bitmaps
41 bmfHeader.bfType = 0x4D42; //BM
42
43 DWORD dwBytesWritten = 0;
44 WriteFile(hFile, (LPSTR)&bmfHeader, sizeof(BITMAPFILEHEADER), &dwBytesWritten, NULL);
45 WriteFile(hFile, (LPSTR)&bi, sizeof(BITMAPINFOHEADER), &dwBytesWritten, NULL);
46 WriteFile(hFile, (LPSTR)pBuffer, dwBmpSize, &dwBytesWritten, NULL);
47
48 //Close the handle for the file that was created
49 CloseHandle(hFile);
50 }
笔者把MSDN上原来的代码里面很多不必要的语句都删除了。并不是说MSDN上的代码出现了冗余,而是笔者在开发这个程序的时候,基于这个框架,只需要通过pBuffer变量进行对图片在内存中的数据操作即可。至于详细原因,请看下面的笔记:
以下是笔者在学习过程中遇到的问题和心得,可能没有普适性。。。
问题1:变量放哪里?
背景:
在ZhaoChao项目中写SaveBMP函数时遇到了问题,网上blog文的例子是通过一个DWORD*类型的变量pBuffer来协同不同函数的操作,沟通不同函数操作时所需要的数据。
问题:
究竟是使用函数的局部变量来保障函数的可移植性和重用性,还是增加类的私有成员(牺牲前者)来减少参数传递的开销?结构性编程的时候经常运用前者的思想,当面向对象编程的时候,后者究竟会带来什么耀眼的好处呢?
子问题:
如何通过pBuffer协同其他必要的局部变量(if necessary)来保存(SaveBMP)缓冲区里面的数据到位图(硬盘中的文件)中?过程是怎样的?
子问题:
可以仅仅通过pBuffer来保存CaptureImage的结果并把结果运用到SaveBMP函数中么?
解决方案:
对于问题:
1)对于不同时候,不同地方被调用时,会发生改变的变量,放在函数中作为函数的局部变量,确保函数的可移植性和重用性。
2)类的私有成员变量由于有贯穿全个程序运行时间的生存期,因此可以保存一些整个程序通用,也不太经常改变的变量或者常量,通过这样来减少函数参数传递的开销。
3)面向对象编程中,类经常可以保存或者预处理一些对于某一类问题适用的共性的东西,因此不用每次运行的时候都重新初始化。成员函数可以直接访问类里面的成员变量,减少了开销。
对于子问题:
可以仅仅通过pBuffer来保存CaptureImage的结果并把结果运用到SaveBMP函数中。
发现其他的局部变量,诸如HBITMAP类型的hBitmap,HDC类型的hNewDC以及BITMAP类型的bmpScreen在SaveBMP函数中都是不必要的。单纯通过一个pBuffer,完全可以沟通CaptureImage和SaveBMP函数,因为pBuffer指向了一片连续的内存空间,所有数据的本质都在这片空间内。有内存,就有一切……
大致过程如下:
1)为之后的函数所需要传入的参数作准备,初始化:BITMAPINFOHEADER bi 和BITMAPFILEHEADER bmfHeader;
2)计算一下bitmap的大小;
3)创建个文件;
4)设置一些参数然后写文件;
5)关闭句柄。
至于其余MSDN文档里面提供的GetObject和GetDIBits之类的,都不需要。因为它那个文档里面的源码是紧紧跟在截图操作之后,进行保存操作。而我们已经有了pBuffer所指向的内容,这时只需要解决pBuffer这个主要矛盾即可,其他代码都是浮云……
方案来源:
http://msdn.microsoft.com/en-us/library/dd183402(v=VS.85).aspx
截图功能的实现和测试时对于位图的保存大部分都来自以上URL,msdn上的代码,可学,可写,可YY~
关键是保留了自己需要的代码,删掉了不少msdn上的例程。
http://student.csdn.net/space.php?uid=110891&do=blog&id=38571
很给力的一篇blog文,基本说明了如何实现这个辅助程序,也解释了截图的大概原理。下下篇中会有详细介绍。
感想:
1) 像图像大小和起始坐标这些值(nWidth、nHeight等),对于本程序来说,是万年不变的常量,作为类成员变量保存,一直在程序中存在,方便随时调用。
但是,hBitMap和hNewDC这些则不然,对于不同的函数,或者同一函数的不同调用,他们的内容都是可变的。如果作为类的成员变量保存,则很可能会出现:所要保存和操作的对象和数据只是前一个函数的作用结果,而非本函数所需要的结果。
2) 在写程序时,一定要先规划好,不要一开始就写。小至变量的存放位置,大至框架和设计模式都要注意。否则,以后修改到死,悲剧死!
3) 习惯很重要,申请完的资源或者内存一定要在它们已经不需要再被程序使用之后立刻释放掉。道理大家都懂,在应用层的话,不释放掉也许问题不严重。但在写驱动程序的时候,要是不记得释放某些资源,很可能会导致系统蓝屏。小细节,大道理。
问题2:简化debug过程。
背景:
经常要写测试代码,但是真正运行程序的时候又不需要,每次添加删除特定代码或者注释掉它们非常繁琐。
问题:
有没有办法在源文件中只做小型改动,就可以达到上述目的?
解决方案:
运用宏。开头定义一个DEBUG_CODE的空宏,程序后面的测试代码就放在#ifdef DEBUG_CODE 与 #endif 之间,这样通过在一开始注释掉或者保留#define DEBUG_CODE这行代码,就可以达到目的。很爽~
方案来源:
联想起《自己动手写操作系统》一书中在调试引导区程序时所用到的小技巧。
笔者在响应Capture按钮的消息出来函数中是这么写(当然,在这个文档开头的地方,增加了一行 #define DEBUG_CODE 的代码,不需要debug看结果的时候,注释掉就可以编译运行else之后的代码):
1 void CZhaoChaV21Dlg::OnBnClickedButtonCapture()
2 {
3 // TODO: 在此添加控件通知处理程序代码
4 #ifdef DEBUG_CODE
5 CaptureImage( pLeftBuffer, x1, y1, nWidth, nHeight );
6 SaveBMP( pLeftBuffer, _T("Debug_1.bmp") );
7 CaptureImage( pRightBuffer, x2, y2, nWidth, nHeight );
8 SaveBMP( pRightBuffer, _T("Debug_2.bmp") );
9 #else
10 CaptureImage( pLeftBuffer, x1, y1, nWidth, nHeight );
11 CaptureImage( pRightBuffer, x2, y2, nWidth, nHeight );
12 #endif
13 }
补充:MFC程序本身也有_DEBUG宏,大可以Google之,在此不详述。
暂时写到这里,关于比较图片和新建子窗口显示的问题,请期待:
QQ美女找茬(外挂)学习笔记(二)比较与显示
特别鸣谢:
技术支持:jingzhongrong
精神支持:vczh
posted on 2010-11-18 21:17
ArthasLee 阅读(4542)
评论(4) 编辑 收藏 引用 所属分类:
windows应用层编程