远程控制编写之屏幕传输 MFC实现 屏幕截图 发送bmp数据 显示bmp图像:
一 :
首先要了解bmp图像的结构 详情请看我转载的一篇文章http://blog.csdn.net/hnust_xiehonghao/article/details/37656281
二: 被控端的代码
注意以下代码要放到一个线程中去 由于用到了while死循环 表示一直发送消息 直到对方关闭接收,发送失败后自己主动退出! 一定要放进线程
DWORD __stdcall SendScreen(LPVOID lparam)//线程处理屏幕传输 { DWORD *pParam = (DWORD *)lparam; SOCKET MainSocket =*pParam; DWORD dwLastSend; HWND hWnd = GetDesktopWindow();//获得屏幕的HWND. HDC hScreenDC = GetDC(hWnd); //获得屏幕的HDC. HDC MemDC = CreateCompatibleDC(hScreenDC); RECT rect; //该函数返回指定窗体的边框矩形的尺寸。该尺寸以相对于屏幕坐标左上角的屏幕坐标给出。 GetWindowRect(hWnd,&rect); SIZE screensize; screensize.cx=rect.right-rect.left; screensize.cy=rect.bottom-rect.top; //CreateCompatibleBitmap该函数创建与指定的设备hScreenDC环境相关的设备兼容的位图。 HBITMAP hBitmap = ::CreateCompatibleBitmap(hScreenDC,screensize.cx,screensize.cy); while(1) { dwLastSend = GetTickCount(); HGDIOBJ hOldBMP = ::SelectObject(MemDC,hBitmap); //该函数对hScreenDc环境区域中的像素进行位块转换,以传送到目标设备MemDC环境。 ::BitBlt(MemDC,0,0,screensize.cx,screensize.cy,hScreenDC,rect.left,rect.top,SRCCOPY); ::SelectObject(MemDC,hOldBMP); /***************************************************************/ HDC hDC =::CreateDC("DISPLAY",NULL,NULL,NULL); int iBits = ::GetDeviceCaps(hDC, BITSPIXEL) * ::GetDeviceCaps(hDC, PLANES);//当前分辨率下每一个像素所占字节数 ::DeleteDC(hDC); WORD wBitCount; //位图中每一个像素所占字节数 if (iBits <= 1) wBitCount = 1; else if (iBits <= 4) wBitCount = 4; else if (iBits <= 8) wBitCount = 8; else if (iBits <= 24) wBitCount = 24; else wBitCount = iBits; DWORD dwPaletteSize=0; //调色板大小, 位图中像素字节大小 if (wBitCount <= 8) dwPaletteSize = (1 << wBitCount) * sizeof(RGBQUAD); BITMAP bm; //位图属性结构 ::GetObject(hBitmap, sizeof(bm), (LPSTR)&bm); BITMAPINFOHEADER bi; //位图信息头结构 bi.biSize = sizeof(BITMAPINFOHEADER); bi.biWidth = bm.bmWidth; bi.biHeight = bm.bmHeight; bi.biPlanes = 1; bi.biBitCount = wBitCount; bi.biCompression = BI_RGB; //BI_RGB表示位图没有压缩 bi.biSizeImage = 0; bi.biXPelsPerMeter = 0; bi.biYPelsPerMeter = 0; bi.biClrUsed = 0; bi.biClrImportant = 0; DWORD dwBmBitsSize = ((bm.bmWidth * wBitCount+31)/32) * 4 * bm.bmHeight; HANDLE hDib = ::GlobalAlloc(GHND,dwBmBitsSize + dwPaletteSize + sizeof(BITMAPINFOHEADER)); //为位图内容分配内存 //HANDLE hDib = ::GlobalAlloc(GHND,3686440*3); //为位图内容分配内存 //锁定内存中指定的内存块,并返回一个地址值,令其指向内存块的起始处。除非用 GlobalUnlock 函数将内存块解锁,否则地址会一直保持有效。 LPBITMAPINFOHEADER lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib); *lpbi = bi; HANDLE hPal = ::GetStockObject(DEFAULT_PALETTE); // 处理调色板 HANDLE hOldPal=NULL; if (hPal) { hDC = ::GetDC(NULL); hOldPal = SelectPalette(hDC,(HPALETTE)hPal, FALSE); RealizePalette(hDC); } //将数据保存在指针(LPSTR)lpbi + sizeof(BITMAPINFOHEADER)+dwPaletteSize指向的位置 ::GetDIBits(hDC, hBitmap, 0, (UINT) bm.bmHeight,(LPSTR)lpbi + sizeof(BITMAPINFOHEADER)+dwPaletteSize,(BITMAPINFO*)lpbi,DIB_RGB_COLORS);// 获取该调色板下新的像素值 if (hOldPal)//恢复调色板 { SelectPalette(hDC, (HPALETTE)hOldPal, TRUE); RealizePalette(hDC); ::ReleaseDC(NULL, hDC); } BITMAPFILEHEADER bmfHdr; //位图文件头结构 bmfHdr.bfType = 0x4D42; // "BM" // 设置位图文件头 DWORD dwDIBSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwPaletteSize + dwBmBitsSize; bmfHdr.bfSize = dwDIBSize; bmfHdr.bfReserved1 = 0; bmfHdr.bfReserved2 = 0; bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER) + dwPaletteSize; MsgHead MsgSend; MsgSend.dwCmd = CMD_SCREEN_TO_SHOW; MsgSend.dwSize = dwBmBitsSize + dwPaletteSize + sizeof(BITMAPINFOHEADER); MsgSend.dwExtend1 = bmfHdr.bfSize; MsgSend.dwExtend2 = bmfHdr.bfOffBits; if(!SendMsg(MainSocket, (char*)lpbi, &MsgSend))//自己定义函数 其内进行2次发送数据。一次用send发送MsgHead结构体 一次发送字符buffer此处为lpbi {//使用本代码时 把SendMsg换成你自己的发送函数 本人的函数先发送一个自己定义结构体再发送lpbi 分2次发送 故以下主控端分2次接受 ::DeleteObject(MemDC); ::ReleaseDC(hWnd,hScreenDC); GlobalUnlock(hDib); //清除 GlobalFree(hDib); //::MessageBox(NULL, "发送失败","",MB_OK); return 0; } /* //将得到的屏幕截屏保存到E://mybitmap.bmp char strFilePath[111] = "E://mybitmap.bmp"; HANDLE hFile = CreateFile(strFilePath , GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);//创建位图文件 DWORD dwWritten; WriteFile(hFile, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL); // 写入位图文件头 WriteFile(hFile, (LPSTR)lpbi, dwDIBSize, &dwWritten, NULL);// 写入位图文件其余内容 */ GlobalUnlock(hDib); //清除 GlobalFree(hDib); //CloseHandle(hFile); if ((GetTickCount() - dwLastSend) < 110) Sleep(100); } return 0; }
三
主控端的代码:
1 因为主控端也要一个循环进行一直接收屏幕消息 所以以下这个函数的代码一定也要放进一个线程中去!也能够让一个线程去调用处理函数 我採用的是另外一种 用线程去调用此函数!
void CScreenDlg::GetFirstScreen() { MsgHead MsgSend; MsgSend.dwCmd = CMD_GETFIRST_SCREEN; MsgSend.dwSize = 0; if(!SendMsg(m_MainSocket, NULL, &MsgSend))//发出请求屏幕传输的要求 { ::MessageBox(NULL, "屏幕传输请求失败", "出错", MB_OK); closesocket(m_MainSocket); return ; } //下面为屏幕的获取, 一直获取并显示 直到接收不到 或设置计时器 一定时间后退出 DWORD dwLastSend; MsgHead MsgRecv; while(m_MainSocket != INVALID_SOCKET) { if(!RecvData(m_MainSocket, (char *)&MsgRecv, sizeof(MsgHead)))//自己定义接受函数 其内是用的recv函数 如使用本代码 替换为recv函数就可以 { ::MessageBox(NULL, "屏幕数据接收,命令接收失败", "出错", MB_OK); closesocket(m_MainSocket); m_MainSocket = INVALID_SOCKET; return ; } bmfHdr.bfType = 0x4D42; // "BM" // 设置位图文件头 成员变量bmfHdr类型为 BITMAPFILEHEADER bmfHdr.bfSize = MsgRecv.dwExtend1; bmfHdr.bfReserved1 = 0; bmfHdr.bfReserved2 = 0; bmfHdr.bfOffBits = MsgRecv.dwExtend2; m_InfoSize = MsgRecv.dwExtend2 - sizeof(BITMAPFILEHEADER);//m_InfoSize为info信息头和调色板的大小的和 if(!RecvData(m_MainSocket,(char *)pData,MsgRecv.dwSize))//自己定义接受函数 使用时换成你自己的函数 或者recv函数就可以 { ::MessageBox(NULL, "屏幕数据接收,数据接收失败", "出错", MB_OK); closesocket(m_MainSocket); m_MainSocket = INVALID_SOCKET; return ; } /* //将bitmap数据写入文件里 创建一个bmp图像文件 strcpy(strFilePath,"E://hehe.bmp");//strFilePath类型为char strFilePath[111] ; if(hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile); //HANDLE hFile; hFile = CreateFile(strFilePath , GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);//创建位图文件 DWORD dwWritten; WriteFile(hFile, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL); // 写入位图文件头 WriteFile(hFile, (LPSTR)pData, bmfHdr.bfSize, &dwWritten, NULL);// 写入位图文件其余内容 CloseHandle(hFile); */ Invalidate(TRUE);//发送重绘系统消息 Sleep(10); } }
上面的代码收到了bmp图像的信息保存到了pData中!
2 以下利用pData中的信息创建bmp图像的句柄
HBITMAP CScreenDlg::GetBitmapFromData() { HBITMAP hBitmap; PBITMAPINFO lpBmpInfo; //位图信息 lpBmpInfo = PBITMAPINFO(pData); HDC hDC = CreateDC("DISPLAY", NULL, NULL, NULL); // 创建DDB位图 hBitmap = CreateDIBitmap( hDC, &lpBmpInfo->bmiHeader, CBM_INIT, pData + m_InfoSize, lpBmpInfo, DIB_RGB_COLORS) ; DeleteDC(hDC); return hBitmap; }
关于DDB DIB 的差别 请參考本人博客MFC分栏 或者百度
3 以下为调用Invalidate后 重画图像 以下使用双缓冲技术 关于此技术參考以下文章中的第五条
http://blog.csdn.net/hnust_xiehonghao/article/details/37741307
以下的函数是在类向导中加入的消息响应函数 用来擦除窗体的
BOOL CScreenDlg::OnEraseBkgnd(CDC* pDC) { // TODO: 在此加入消息处理程序代码和/或调用默认值 //双缓存防止闪烁 CDC DCmem; DCmem.CreateCompatibleDC(pDC); CBitmap bitmap; //下面也能够从一个文件里加载bitmap图像 如凝视中的语句 bitmap.m_hObject = GetBitmapFromData();//(HBITMAP)LoadImage(AfxGetInstanceHandle(), strFilePath, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE|LR_CREATEDIBSECTION); CRect rect; GetClientRect(&rect); CBitmap *pOldBit=DCmem.SelectObject(&bitmap); //DCmem.FillSolidRect(rect,pDC->GetBkColor());//按原来背景填充客户区,不然会是黑色 pDC->BitBlt(0,0,rect.Width(),rect.Height(),&DCmem,0,0,SRCCOPY); DCmem.DeleteDC(); //删除DC bitmap.DeleteObject(); //删除位图 //return CDialog::OnEraseBkgnd(pDC); return TRUE; }
by hnust_xiehonghao