您的位置:首页 > 运维架构

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

2012-07-11 19:17 344 查看
为了自己做出一个远控程序,自己花了点时间学习了有关绘图方面的知识点。

一.基本概念的了解

为了实现实时屏幕显示的功能,首先学习了下《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功能。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: