c++ - 键盘记录器和鼠标跟踪器:我应该使用非阻塞 I/O 吗?
问题描述
我正在用 C/C++ for Windows 编写一个简单的键盘记录器/鼠标记录器。为此,我使用 Win32 函数LowLevelMouseProc
和LowLevelKeyboardProc
.
如果相关,这里是我的代码的GitHub 要点,它是超基本的:定义事件回调并将其与 SIGINT 的回调一起注册。我将在问题的末尾添加一个摘要版本。
我的问题如下:为了尽量减少开销,我应该如何将这些事件保存到磁盘?
欢迎使用 C 或 C++ 的答案。
每次收到新事件时简单地写入缓冲文件并在缓冲区已满时让文件处理刷新是一种好习惯吗?我听说过非阻塞 I/O,但微软的文档说有额外的开销。最后,我不确定是否应该为此创建第二个线程。
我想使用某种缓冲来避免许多小磁盘 I/O。理想情况下,我会在我的进程被终止之前写入磁盘一次。但我不知道如何实现这一目标。
代码:
#include "pch.h"
#include <stdio.h>
#include <Windows.h>
HHOOK handle;
LRESULT CALLBACK lowLevelMouseProc(
_In_ int nCode,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)
{
MSLLHOOKSTRUCT* lp = (MSLLHOOKSTRUCT*)lParam;
if (wParam == WM_MOUSEMOVE) {
// Best way to save pt.x and pt.y to disk?
printf("%d %d \n", lp->pt.x, lp->pt.y);
}
return CallNextHookEx(0, nCode, wParam, lParam);
}
int main()
{
handle = SetWindowsHookExA(WH_MOUSE_LL, &lowLevelMouseProc, NULL, 0);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0));
UnhookWindowsHookEx(handle)
return 0;
}
解决方案
使用 2 个缓冲区。一个用于写入,一个用于读取(刷新到磁盘)。一旦满足某些条件(缓冲区已满,程序关闭,...),交换缓冲区并开始在单独的线程中刷新到磁盘。
这可能看起来像:
#include <Windows.h>
#include <vector>
#include <thread>
#include <fstream>
#include <atomic>
struct Point
{
long x, y;
};
class Buffer
{
public:
Buffer(std::string _file = "log.txt", const size_t _buffer_size = 100000) : buffer_size(_buffer_size), file(_file)
{
points1.reserve(_buffer_size);
points2.reserve(_buffer_size);
}
void write(Point p)
{
buf->push_back(p);
if (buf->size() >= buffer_size && !thread_running.load())
to_disk();
}
private:
const size_t buffer_size;
const std::string file;
std::atomic<bool> thread_running{ false };
std::vector<Point> points1, points2;
std::vector<Point> *buf = &points1, *other = &points2;
void swap_buffer()
{
std::swap(buf, other);
}
void to_disk()
{
swap_buffer();
auto tmp_buf = other;
auto tmp_file = file;
auto tmp_flag = &thread_running;
auto fn = [tmp_buf, tmp_file, tmp_flag]() {
tmp_flag->store(true);
std::fstream f(tmp_file, std::ios::app);
for (auto &v : *tmp_buf)
f << v.x << ' ' << v.y << '\n';
tmp_buf->clear();
tmp_flag->store(false);
};
std::thread t(fn);
t.detach();
}
};
Buffer buffer("log.txt");
HHOOK handle;
LRESULT CALLBACK lowLevelMouseProc(
_In_ int nCode,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)
{
MSLLHOOKSTRUCT* lp = (MSLLHOOKSTRUCT*)lParam;
if (wParam == WM_MOUSEMOVE) {
buffer.write({ lp->pt.x, lp->pt.y });
}
return CallNextHookEx(0, nCode, wParam, lParam);
}
int main()
{
handle = SetWindowsHookExA(WH_MOUSE_LL, &lowLevelMouseProc, NULL, 0);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0));
UnhookWindowsHookEx(handle);
return 0;
}
在这种情况下,当达到某个大小限制时,缓冲区会被写入磁盘。这可以进一步优化,例如不检查每次写入的大小。
注意:在此示例中,省略了错误处理,应相应管理内部缓冲区的生命周期。
推荐阅读
- lldb - 使用 lldb 在主二进制文件中查找名为 '_OBJC_IVAR__$_DIRect._width' 的非外部符号?
- reactjs - 使用 React Router 处理多个路由
- openstack - 泊坞窗上的 OpenStack
- javascript - 比 `setTimeout` 和 `.length` 更好的检查元素是否存在的方法
- android - 在 Android 上访问 IP 子域
- sqoop - 如何将 Sqoop list-tables 命令的输出存储到文本文件?
- css - CSS repeat-x 但只是我想要的图像中的一部分
- java - 为什么编译器不会产生重复的错误?
- java - Java - 应用插件与插件之间的区别
- python - 如何动态访问 ForeignKey 对象的属性