windowsSDK实现 屏幕实时监控 热键截图 功能

为了自己做出一个远控程序,自己花了点时间学习了有关绘图方面的知识点。


一.基本概念的了解

为了实现实时屏幕显示的功能,首先学习了下《windows程序设计》的第14章,位图和位块的传输。

理解了下位图和位块的区别,基本概念。

概念一:位图与图元的区别:

位图                  图元

点阵                  矢量      (位图与图元的主要区别就在这里了)

容易失真          不失真

较大                  较小

传输速度快      传输速度慢

概念二:与设备无关的位图

DDB   设备相关位图

DIB     设备无关位图

windows编程里面提到,这两个概念有时候是很难区分的。所以我在这里就不具体讲解两者的区别了,对我们的功能实现应该没什么关系。但是因为这是个经常出现的字眼,所以这里先提一下。

二.BitBlt函数

接下来我们来看BitBlt 函数,这个函数是我们这次功能实现的最关键的一个函数了~~~~

BOOL BitBlt(
  __in  HDC hdcDest,
  __in  int nXDest,
  __in  int nYDest,
  __in  int nWidth,
  __in  int nHeight,
  __in  HDC hdcSrc,
  __in  int nXSrc,
  __in  int nYSrc,
  __in  DWORD dwRop
);
参数虽然多,但是用起来不难

hdcDest 目标设备描述表

nXDest   目标起始x坐标位置

nYDest   目标起始y坐标位置

nWidth   宽度
nHeight 高度

hdcSrc   源设备描述表

nXSrc     源起始x坐标

nYSrc     源起始y坐标

dwRop   这个参数比较复杂,我们这里就是简单的原封不动的复制,所以使用了 SRCCOPY

更多关于这个函数的用法,参照MSDN

当然了,如果你想缩放图片,你还可以使用函数StretchBlt  这个函数比BitBlt多了两个参数。

在我的程序中,将屏幕的图像传输到我自己的程序中:

hdcClient = BeginPaint (hwnd, &ps);							//获得目标(本程序)设备描述符	
hdcWindow = GetWindowDC (NULL);			                                        //获得源目标(屏幕)设备描述符  
BitBlt (hdcClient, 0, 0, cxClient, cyClient, hdcWindow , 0, 0, SRCCOPY);

三.实时显示

我通过设置定时器,每隔1ms刷新一次软件的客户区。当然发消息没有这么快。但是至少这样能保证已经达到了最快的更新速度。


程序开始时触发定时器

   case WM_CREATE:
          SetTimer (hwnd, ID_TIMER, 100, TimerProc) ;
   return 0 ;
定时器函数

VOID CALLBACK TimerProc (HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime)
{	
	 InvalidateRect(hwnd, NULL, NULL);   //无效整个屏幕客户区,重绘客户区
}
当这个功能实现的时候,出现了一个很奇怪的问题~~~。给大家截个图看看当前效果:


有很多镜像!~~~这样的结果是正确的。我如果把我的程序放到我的另一个显示器上面(我有两个显示屏),那么程序显示就正常了,显示的是我原来那个屏幕上的图像。


四:保存bmp图片

这里直接贴代码了:

HBITMAP ScreenCapture(LPWSTR filename ,WORD BitCount,LPRECT lpRect)
{
	HBITMAP hBitmap;
	// 显示器屏幕DC
	HDC hScreenDC = CreateDC(L"DISPLAY", NULL, NULL, NULL);
	HDC hmemDC = CreateCompatibleDC(hScreenDC);
	// 显示器屏幕的宽和高
	int ScreenWidth = GetDeviceCaps(hScreenDC, HORZRES);
	int ScreenHeight = GetDeviceCaps(hScreenDC, VERTRES);
	// 旧的BITMAP,用于与所需截取的位置交换
	HBITMAP hOldBM;
	// 保存位图数据
	PVOID lpvpxldata;
	// 截屏获取的长宽及起点
	INT ixStart;
	INT iyStart;
	INT iX;
	INT iY;
	// 位图数据大小
	DWORD dwBitmapArraySize;
	// 几个大小
	DWORD nBitsOffset;
	DWORD lImageSize ;
	DWORD lFileSize ;
	// 位图信息头
	BITMAPINFO bmInfo;
	// 位图文件头
	BITMAPFILEHEADER bmFileHeader;
	// 写文件用
	HANDLE hbmfile;
	DWORD dwWritten;

	// 如果LPRECT 为NULL 截取整个屏幕
	ixStart = iyStart = 0;
	iX = ScreenWidth;
	iY = ScreenHeight;

	// 创建BTIMAP
	hBitmap = CreateCompatibleBitmap(hScreenDC, iX, iY);
	// 将BITMAP选择入内存DC。
	hOldBM = (HBITMAP)SelectObject(hmemDC, hBitmap);
	// BitBlt屏幕DC到内存DC,根据所需截取的获取设置参数
	BitBlt(hmemDC, 0, 0, iX, iY, hScreenDC, ixStart, iyStart, SRCCOPY);
	// 将旧的BITMAP对象选择回内存DC,返回值为被替换的对象,既所截取的位图
	hBitmap = (HBITMAP)SelectObject(hmemDC, hOldBM);
	if(filename == NULL)
	{
		DeleteDC( hScreenDC);
		DeleteDC(hmemDC);
		return hBitmap;
	}
	// 为位图数据申请内存空间
	dwBitmapArraySize = ((((iX*32) + 31) & ~31)>> 3)* iY;
	lpvpxldata = HeapAlloc(GetProcessHeap(),HEAP_NO_SERIALIZE,dwBitmapArraySize);
	ZeroMemory(lpvpxldata,dwBitmapArraySize);

	// 添充 BITMAPINFO 结构
	ZeroMemory(&bmInfo,sizeof(BITMAPINFO));
	bmInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	bmInfo.bmiHeader.biWidth = iX;
	bmInfo.bmiHeader.biHeight = iY;
	bmInfo.bmiHeader.biPlanes = 1;
	bmInfo.bmiHeader.biBitCount = BitCount;
	bmInfo.bmiHeader.biCompression = BI_RGB;

	// 添充 BITMAPFILEHEADER 结构
	ZeroMemory(&bmFileHeader,sizeof(BITMAPFILEHEADER));
	nBitsOffset = sizeof(BITMAPFILEHEADER) + bmInfo.bmiHeader.biSize;
	lImageSize =
		((((bmInfo.bmiHeader.biWidth * bmInfo.bmiHeader.biBitCount) + 31) & ~31)>> 3)
		* bmInfo.bmiHeader.biHeight;
	lFileSize = nBitsOffset + lImageSize;
	bmFileHeader.bfType = 'B'+('M'<<8);
	bmFileHeader.bfSize = lFileSize;
	bmFileHeader.bfOffBits = nBitsOffset;

	// 获取DIB用于写入到文件
	GetDIBits(hmemDC, hBitmap, 0, bmInfo.bmiHeader.biHeight,
		lpvpxldata, &bmInfo, DIB_RGB_COLORS);
	// 写文件
	hbmfile = CreateFile(filename,
		GENERIC_WRITE,
		FILE_SHARE_WRITE,
		NULL,
		CREATE_ALWAYS,
		FILE_ATTRIBUTE_NORMAL,
		NULL);

	WriteFile(hbmfile,&bmFileHeader,sizeof(BITMAPFILEHEADER),&dwWritten,NULL);
	WriteFile(hbmfile,&bmInfo,sizeof(BITMAPINFO),&dwWritten,NULL);
	WriteFile(hbmfile,lpvpxldata,lImageSize,&dwWritten,NULL);
	CloseHandle(hbmfile);

	// 释放内存,清除不同的DC。
	// 这里没有删除BITMAP对象,需在显示完成后删除
	HeapFree(GetProcessHeap(),HEAP_NO_SERIALIZE,lpvpxldata);
	ReleaseDC(0, hScreenDC);
	DeleteDC(hmemDC);
	return hBitmap;
}
这段代码基本上不加修改就能直接用了。


五.格式转换

一般远控在传输图像的时候都是把图片进行了压缩的。所以我想把bmp格式的图片转换成png。这里使用了GDI+的库。很方便就实现了。

windowsSDK程序需要的注意点:

1.保证是.cpp格式  .c格式的话就不能 因为这个库只有c++下面才能使用。

2.使用前引入头文件 和 库

#include <GdiPlus.h>
using namespace Gdiplus;
#pragma  comment(lib, "gdiplus.lib")

3.实例代码

参考下面这篇文章,将的非常好:http://blog.csdn.net/yuzl32/article/details/5389919

#include <windows.h>  
    #include <gdiplus.h>  
    #include <stdio.h>  
    using namespace Gdiplus;  
      
    #pragma comment(lib,"gdiplus")  
      
    int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)  
    {  
       UINT  num = 0;          // number of image encoders  
       UINT  size = 0;         // size of the image encoder array in bytes  
      
       ImageCodecInfo* pImageCodecInfo = NULL;  
         
       //2.获取GDI+支持的图像格式编码器种类数以及ImageCodecInfo数组的存放大小  
       GetImageEncodersSize(&num, &size);  
       if(size == 0)  
          return -1;  // Failure  
      
       //3.为ImageCodecInfo数组分配足额空间  
       pImageCodecInfo = (ImageCodecInfo*)(malloc(size));  
       if(pImageCodecInfo == NULL)  
          return -1;  // Failure  
      
       //4.获取所有的图像编码器信息  
       GetImageEncoders(num, size, pImageCodecInfo);  
      
       //5.查找符合的图像编码器的Clsid  
       for(UINT j = 0; j < num; ++j)  
       {  
          if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )  
          {  
             *pClsid = pImageCodecInfo[j].Clsid;  
             free(pImageCodecInfo);  
             return j;  // Success  
          }      
       }  
      
       //6.释放步骤3分配的内存  
       free(pImageCodecInfo);  
       return -1;  // Failure  
    }  
      
    INT main()  
    {  
       GdiplusStartupInput gdiplusStartupInput;  
       ULONG_PTR gdiplusToken;  
         
       //1.初始化GDI+,以便后续的GDI+函数可以成功调用  
       GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);  
      
       CLSID   encoderClsid;  
       Status  stat;  
         
       //7.创建Image对象并加载图片  
       Image*   image = new Image(L"f://11.bmp");  
      
       // Get the CLSID of the PNG encoder.  
       GetEncoderClsid(L"image/png", &encoderClsid);  
      
       //8.调用Image.Save方法进行图片格式转换,并把步骤3)得到的图像编码器Clsid传递给它  
       stat = image->Save(L"11.png", &encoderClsid, NULL);  
      
       if(stat == Ok)  
          printf("Bird.png was saved successfully/n");  
       else  
          printf("Failure: stat = %d/n", stat);   
      
       //9.释放Image对象  
       delete image;  
       //10.清理所有GDI+资源  
       GdiplusShutdown(gdiplusToken);  
       return 0;  
    }  

六.键盘热键实现截图。

按下空格实现截图。其实这里还可以改进,焦点必须在程序里面才能进行截图。其实可以Hook键盘消息来进行截图操作。


最后发一个不完善的版本:

/*---------------------------------------
屏幕实时监控 热键截图软件
作者:Miibotree
  ---------------------------------------*/

#include <windows.h>
#include <GdiPlus.h>

using namespace Gdiplus;

#pragma  comment(lib, "gdiplus.lib")

#define ID_TIMER    1

HBITMAP ghBitmap = NULL;

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
VOID    CALLBACK TimerProc (HWND, UINT, UINT,   DWORD );

HBITMAP ScreenCapture(LPWSTR filename ,WORD BitCount,LPRECT lpRect);							//全屏截图
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid);														// Get the CLSID of the PNG encoder.  
BOOL Bmp2Png();																														//进行格式转化

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName [] = TEXT ("BitBlt") ;
     HWND         hwnd ;
     MSG          msg ;
     WNDCLASS     wndclass ;

     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
     wndclass.lpfnWndProc   = WndProc ;
     wndclass.cbClsExtra    = 0 ;
     wndclass.cbWndExtra    = 0 ;
     wndclass.hInstance     = hInstance ;
     wndclass.hIcon         = LoadIcon (NULL, IDI_INFORMATION) ;
     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
     wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
     wndclass.lpszMenuName  = NULL ;
     wndclass.lpszClassName = szAppName ;
     
     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, TEXT ("BitBlt Demo"), 
                          WS_OVERLAPPEDWINDOW, 
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, iCmdShow) ;
     UpdateWindow (hwnd) ;

     while (GetMessage (&msg, NULL, 0, 0))
     {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
     }
     return msg.wParam ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static int  cxClient, cyClient, cxSource, cySource ;
     HDC         hdcClient, hdcWindow ;
     int         x, y ;
     PAINTSTRUCT ps ;

	 HDC  hMemDC;																										// 内存设备描述表  
	 HBITMAP  hBitmap, hOldBitmap;																			// 位图句柄,用于替换内存中图像
	 RECT rect;																												//矩形区域
     
     switch (message)
     {
     case WM_CREATE:
		  SetTimer (hwnd, ID_TIMER, 100, TimerProc) ;
          return 0 ;

     case WM_SIZE:
          cxClient = LOWORD (lParam) ;																				//本程序窗口宽度
          cyClient = HIWORD (lParam) ;																				//本程序窗口长度
          return 0 ;

	 case WM_KEYDOWN:
		 switch(wParam)
		 {
			 case VK_SPACE:
				 //截图函数
				 ghBitmap = ScreenCapture(L"D:\\taskmgr.bmp" ,32, NULL); 
				 //格式转换
				 Bmp2Png();	
			 return 0;
		 }
		 return 0;

     case WM_PAINT:
		 hdcClient = BeginPaint (hwnd, &ps);																	//获得目标(本程序)设备描述符	
         hdcWindow = GetWindowDC (NULL);																	//获得源目标(屏幕)设备描述符  
		 BitBlt (hdcClient, 0, 0, cxClient, cyClient, hdcWindow , 0, 0, SRCCOPY);
				 //目标设备                                      源设备 

		 cxSource = GetSystemMetrics (SM_CXSCREEN);													//获得屏幕分辨率
		 cySource = GetSystemMetrics (SM_CYSCREEN);
		 rect.left = 0; rect.right = cxSource; rect.top = 0; rect.bottom = cySource;
	 
         ReleaseDC (hwnd, hdcWindow) ;
         EndPaint (hwnd, &ps) ;
         return 0 ;

     case WM_DESTROY:
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

VOID CALLBACK TimerProc (HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime)
{	
	 InvalidateRect(hwnd, NULL, NULL);
}

HBITMAP ScreenCapture(LPWSTR filename ,WORD BitCount,LPRECT lpRect)
{
	HBITMAP hBitmap;
	// 显示器屏幕DC
	HDC hScreenDC = CreateDC(L"DISPLAY", NULL, NULL, NULL);
	HDC hmemDC = CreateCompatibleDC(hScreenDC);
	// 显示器屏幕的宽和高
	int ScreenWidth = GetDeviceCaps(hScreenDC, HORZRES);
	int ScreenHeight = GetDeviceCaps(hScreenDC, VERTRES);
	// 旧的BITMAP,用于与所需截取的位置交换
	HBITMAP hOldBM;
	// 保存位图数据
	PVOID lpvpxldata;
	// 截屏获取的长宽及起点
	INT ixStart;
	INT iyStart;
	INT iX;
	INT iY;
	// 位图数据大小
	DWORD dwBitmapArraySize;
	// 几个大小
	DWORD nBitsOffset;
	DWORD lImageSize ;
	DWORD lFileSize ;
	// 位图信息头
	BITMAPINFO bmInfo;
	// 位图文件头
	BITMAPFILEHEADER bmFileHeader;
	// 写文件用
	HANDLE hbmfile;
	DWORD dwWritten;

	// 如果LPRECT 为NULL 截取整个屏幕
	ixStart = iyStart = 0;
	iX = ScreenWidth;
	iY = ScreenHeight;

	// 创建BTIMAP
	hBitmap = CreateCompatibleBitmap(hScreenDC, iX, iY);
	// 将BITMAP选择入内存DC。
	hOldBM = (HBITMAP)SelectObject(hmemDC, hBitmap);
	// BitBlt屏幕DC到内存DC,根据所需截取的获取设置参数
	BitBlt(hmemDC, 0, 0, iX, iY, hScreenDC, ixStart, iyStart, SRCCOPY);
	// 将旧的BITMAP对象选择回内存DC,返回值为被替换的对象,既所截取的位图
	hBitmap = (HBITMAP)SelectObject(hmemDC, hOldBM);
	if(filename == NULL)
	{
		DeleteDC( hScreenDC);
		DeleteDC(hmemDC);
		return hBitmap;
	}
	// 为位图数据申请内存空间
	dwBitmapArraySize = ((((iX*32) + 31) & ~31)>> 3)* iY;
	lpvpxldata = HeapAlloc(GetProcessHeap(),HEAP_NO_SERIALIZE,dwBitmapArraySize);
	ZeroMemory(lpvpxldata,dwBitmapArraySize);

	// 添充 BITMAPINFO 结构
	ZeroMemory(&bmInfo,sizeof(BITMAPINFO));
	bmInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	bmInfo.bmiHeader.biWidth = iX;
	bmInfo.bmiHeader.biHeight = iY;
	bmInfo.bmiHeader.biPlanes = 1;
	bmInfo.bmiHeader.biBitCount = BitCount;
	bmInfo.bmiHeader.biCompression = BI_RGB;

	// 添充 BITMAPFILEHEADER 结构
	ZeroMemory(&bmFileHeader,sizeof(BITMAPFILEHEADER));
	nBitsOffset = sizeof(BITMAPFILEHEADER) + bmInfo.bmiHeader.biSize;
	lImageSize =
		((((bmInfo.bmiHeader.biWidth * bmInfo.bmiHeader.biBitCount) + 31) & ~31)>> 3)
		* bmInfo.bmiHeader.biHeight;
	lFileSize = nBitsOffset + lImageSize;
	bmFileHeader.bfType = 'B'+('M'<<8);
	bmFileHeader.bfSize = lFileSize;
	bmFileHeader.bfOffBits = nBitsOffset;

	// 获取DIB用于写入到文件
	GetDIBits(hmemDC, hBitmap, 0, bmInfo.bmiHeader.biHeight,
		lpvpxldata, &bmInfo, DIB_RGB_COLORS);
	// 写文件
	hbmfile = CreateFile(filename,
		GENERIC_WRITE,
		FILE_SHARE_WRITE,
		NULL,
		CREATE_ALWAYS,
		FILE_ATTRIBUTE_NORMAL,
		NULL);

	WriteFile(hbmfile,&bmFileHeader,sizeof(BITMAPFILEHEADER),&dwWritten,NULL);
	WriteFile(hbmfile,&bmInfo,sizeof(BITMAPINFO),&dwWritten,NULL);
	WriteFile(hbmfile,lpvpxldata,lImageSize,&dwWritten,NULL);
	CloseHandle(hbmfile);

	// 释放内存,清除不同的DC。
	// 这里没有删除BITMAP对象,需在显示完成后删除
	HeapFree(GetProcessHeap(),HEAP_NO_SERIALIZE,lpvpxldata);
	ReleaseDC(0, hScreenDC);
	DeleteDC(hmemDC);
	return hBitmap;
}

int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)  
{  
	UINT  num = 0;          // number of image encoders  
	UINT  size = 0;         // size of the image encoder array in bytes  

	ImageCodecInfo* pImageCodecInfo = NULL;  

	//2.获取GDI+支持的图像格式编码器种类数以及ImageCodecInfo数组的存放大小  
	GetImageEncodersSize(&num, &size);  
	if(size == 0)  
		return -1;  // Failure  

	//3.为ImageCodecInfo数组分配足额空间  
	pImageCodecInfo = (ImageCodecInfo*)(malloc(size));  
	if(pImageCodecInfo == NULL)  
		return -1;  // Failure  

	//4.获取所有的图像编码器信息  
	GetImageEncoders(num, size, pImageCodecInfo);  

	//5.查找符合的图像编码器的Clsid  
	for(UINT j = 0; j < num; ++j)  
	{  
		if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )  
		{  
			*pClsid = pImageCodecInfo[j].Clsid;  
			free(pImageCodecInfo);  
			return j;  // Success  
		}      
	}  

	//6.释放步骤3分配的内存  
	free(pImageCodecInfo);  
	return -1;  // Failure  
}  

BOOL Bmp2Png()
{
	GdiplusStartupInput gdiplusStartupInput;  
	ULONG_PTR gdiplusToken;  

	//1.初始化GDI+,以便后续的GDI+函数可以成功调用  
	GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);  

	CLSID   encoderClsid;  
	Status  stat;  

	//7.创建Image对象并加载图片  
	Image*   image = new Image(L"D:\\taskmgr.bmp");  

	// Get the CLSID of the PNG encoder.  
	GetEncoderClsid(L"image/png", &encoderClsid);  

	//8.调用Image.Save方法进行图片格式转换,并把步骤3)得到的图像编码器Clsid传递给它  
	stat = image->Save(L"D:\\taskmgr.png", &encoderClsid, NULL);  

	if(stat == Ok)  
		MessageBoxA(NULL, "格式转换成功", "成功", MB_OK);
	else  
		MessageBoxA(NULL, "格式转换失败", "失败", MB_OK | MB_ICONERROR);  

	//9.释放Image对象  
	delete image;  
	//10.清理所有GDI+资源  
	GdiplusShutdown(gdiplusToken);  
	return TRUE;
}


七.改进之处

可以自动根据时间戳创建文件以及文件夹,加上键盘Hook功能。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值