远程控制项目第四天 功能实现
发送屏幕内容
代码详解
1. 创建 CImage 对象并获取屏幕内容
首先,我们创建一个 CImage
对象,用于接收屏幕上的内容。要获取屏幕内容,我们需要先获取当前设备上下文(DC)。调用 ::GetDC(NULL)
函数,参数 NULL
表示我们要获取整个屏幕的设备上下文。
CImage screen;
HDC hScreen = ::GetDC(NULL);
int nBitPerPixel = GetDeviceCaps(hScreen, BITSPIXEL); // 获取屏幕颜色深度
int nWidth = GetDeviceCaps(hScreen, HORZRES); // 获取屏幕宽度
int nHeight = GetDeviceCaps(hScreen, VERTRES); // 获取屏幕高度
screen.Create(nWidth, nHeight, nBitPerPixel); // 创建 CImage 对象
2. 使用 BitBlt 获取屏幕内容
通过 BitBlt
函数,将当前屏幕的内容复制到 CImage
对象上。BitBlt
的本质就是将源图像的一部分搬运到目标图像的指定位置。
CImage screen;
HDC hScreen = ::GetDC(NULL);
int nBitPerPixel = GetDeviceCaps(hScreen, BITSPIXEL); // 获取屏幕颜色深度
int nWidth = GetDeviceCaps(hScreen, HORZRES); // 获取屏幕宽度
int nHeight = GetDeviceCaps(hScreen, VERTRES); // 获取屏幕高度
screen.Create(nWidth, nHeight, nBitPerPixel); // 创建 CImage 对象
3. 释放设备上下文
完成屏幕内容复制后,我们需要释放设备上下文,避免内存泄露。调用 ReleaseDC
函数来释放资源。
ReleaseDC(NULL, hScreen); // 释放设备上下文
4. 创建全局内存块和流对象
接下来,我们创建一个空的全局内存块,使用 GlobalAlloc
分配内存。GMEM_MOVEABLE
标志表示这块内存是可移动的。然后,我们创建一个 IStream
流对象,使用 CreateStreamOnHGlobal
将全局内存块与流对象绑定。
HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, 0); // 创建全局内存块
if (hMem == NULL) return -1; // 内存分配失败IStream* pStream = NULL;
HRESULT ret = CreateStreamOnHGlobal(hMem, TRUE, &pStream); // 创建流对象
if (ret != S_OK) return -1; // 创建失败
5. 将 CImage 数据保存到流对象
现在,我们可以通过 CImage::Save
方法将图像数据保存到流对象中。保存时,我们指定保存的格式(如 JPEG)。
screen.Save(pStream, Gdiplus::ImageFormatJPEG); // 将图像保存到流对象中
6. 流指针调整
调用 screen.Save
后,流指针已经指向了数据的末尾。如果不重置指针,接下来的读取操作可能会从流的末尾开始,这样读取到的数据可能为空。为了确保后续可以正确读取数据,我们需要通过 Seek
函数将流指针重置到开头。
LARGE_INTEGER bg = {0};
pStream->Seek(bg, STREAM_SEEK_SET, NULL); // 将流指针移回开头
7. 锁定内存块并读取数据
接下来,我们通过 GlobalLock
锁定全局内存块,获取指向内存的指针。这允许我们直接操作内存中的数据。在操作完数据后,我们使用 GlobalUnlock
解锁内存。
PBYTE pData = (PBYTE)GlobalLock(hMem); // 锁定内存并获取指针
SIZE_T nSize = GlobalSize(hMem); // 获取内存大小
8. 发送数据
使用获取的内存数据,构造数据包并发送。发送完成后,解锁内存。
CPacket packet(6, pData, nSize);
CServerSocket::getInstance()->Send(packet); // 发送数据GlobalUnlock(hMem); // 解锁内存
9. 释放资源
最后,我们需要释放流对象、全局内存块以及 CImage
对象的设备上下文。资源的释放顺序应该是先释放流对象,再释放全局内存块,最后释放设备上下文。
pStream->Release(); // 释放流对象
GlobalFree(hMem); // 释放全局内存块
screen.ReleaseDC(); // 释放 CImage 对象的设备上下文
总结
- 获取屏幕内容:通过
GetDC
获取设备上下文,使用BitBlt
将屏幕内容复制到CImage
对象。 - 流操作:使用
GlobalAlloc
创建全局内存块,使用CreateStreamOnHGlobal
将内存块和流绑定。使用CImage::Save
保存数据到流对象。 - 内存操作:使用
GlobalLock
锁定内存,获取数据后调用GlobalUnlock
解锁内存。注意流指针位置,要确保数据从流的起始位置读取。 - 资源释放:确保按顺序释放资源:先释放流对象,再释放内存块,最后释放设备上下文。
这样就能确保屏幕截图数据能够被正确保存、读取和发送,同时避免资源泄露。
锁机
通过_beginthreadex(NULL, 0, threadLockDlg, NULL, 0, &threatid);创建一个新线程来处理锁机逻辑,同时判断dlg.m_hWnd==NULL,dlg.m_hWnd==INVALID_HANDLE_VALUE要不要起新线程。线程函数threadLockDlg的逻辑是先通过Create(IDD_DIALOG_INFO,NULL);来创建对话框,NULL表示对话框没有父窗口,属于顶级窗口,然后ShowWindow显示窗口,再创建一个矩形对象,通过GetSyetemMetrics(SM_CYSCREEN);获取到屏幕大小,dlg.MoveWindow(rect);把对话框填满整个屏幕,还要dlg.SetWindowPos(&dlg.wndTopMost, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);使其对话框置顶,同时要让用户无法操控鼠标ShowCursor(false);::ShowWindow(::FindWindow(_T("Shell_TrayWnd"), NULL), SW_HIDE);的作用是查找任务栏窗口并隐藏它。FindWindow
查找 Shell_TrayWnd
类的窗口句柄,这通常是任务栏的窗口类,然后通过 ShowWindow(SW_HIDE)
隐藏任务栏。然后还要将光标限制在对话框内,通过调用ClipCursor(rect);后面就开始最重要的消息循环机制
- 作用:这是标准的 Windows 消息循环,用于处理来自操作系统的消息。
GetMessage(&msg, NULL, 0, 0)
:从消息队列中检索消息,直到收到退出消息(如关闭窗口)。返回FALSE
时退出消息循环。TranslateMessage
:翻译消息,通常是键盘消息,转换为字符消息。DispatchMessage
:分发消息,交给对应的窗口过程进行处理。if (msg.message == WM_KEYDOWN)
:检查是否是按键按下事件(WM_KEYDOWN
)。if (msg.wParam == 0x1B)
:判断按下的键是不是ESC
键(0x1B
是ESC
键的虚拟键代码)。
- 意识:通过消息循环,程序可以响应用户的输入,处理键盘事件或其他消息。这里实现了用户按
ESC
键退出对话框。
当退出循环,就要恢复鼠标,恢复任务栏,销毁对话框,并结束线程。
解锁
这里有个很重要的知识,每个线程都有自己独立的消息循环和消息处理,每个线程发出的消息只能被自己线程的消息循环接收到,所以我们要通过特定的函数来进行跨线程发送消息
PostThreadMessage(threatid, WM_KEYDOWN, 0x1B, 0);
这个函数可以把消息类型消息内容发送给指定的(threatid)线程,这样的话我们就可以把消息发给处理锁机逻辑的线程中,来解锁。