当前位置: 首页 > news >正文

键盘重映射禁用 CtrlAltDel 键的利弊

目录

前言

一、Scancode Map 的规范

二、禁用 CtrlAltDel 的方法及其缺陷

三、编程实现和测试

3.1 C++ 实现的简易修改工具

3.2 C# 实现的窗口工具

四、总结


本文属于原创文章,转载请注明出处:

https://blog.csdn.net/qq_59075481/article/details/136104444。

前言

在 Ndr-LRPC Hook 和 WMsg Hook 等方法完善前,网络上公开的禁用 CtrlAltDel 键的方法是使用 "Scancode Map" 键盘扫描码映射表这个方法,本质上是利用微软提供的注册表设置来达到屏蔽的效果。这确实在前一阶段是较好的解决方案,所以在更新完前两种方案后,我不打算对这个方法避而不谈,相反,我觉得该方法可以用于更广泛的方面,甚至许多键盘快捷键修改程序就利用了类似的方法。本文将就具体的实现细节给出通用修改工具以及谈谈这种方法存在优缺点。工具有两个,一个是我重写的 C++ 简化实现,另一个是基于 C# 的开源工具(查阅资料时偶然发现)。

一、Scancode Map 的规范

Scancode Map 注册表项位于注册表如下路径:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout

它是 Keyboard Layout 子键下名为"Scancode Map"的二进制值项,如果没有你可以新建一个。

这个值项可实现对键盘按键的映射。这里映射的意思可理解为“替换”,可将任意一键替换成其它键,或者禁用。

Scancode Map 注册表项具有类似下面的格式:

以 16 进制表示,可分为五个部分,为了方便讲解,相邻字节之间用逗号隔开。

"Scancode Map" = 00,00,00,00,00,00,00,00,02,00,00,00,01,00, 02,00,00,00,00,00

前 8 个字节: 表示版本信息号,一般为 0。
紧接着的 4 个字节: 表示映射键的总数 + 1。按照二进制的读写规则,低位在左,高位在右。02 00 00 00 这个数实际就是:00 00 00 02 ,该用例表示当前修改 1 个键。
紧接着的 2 个字节: 表示替换后按键的“扫描码”。如:ESC 键的扫描码是 01 ,所以就表示 01 00 。再比如左 Ctrl 键扫描码是 1D 00, 而右 Ctrl 键是 1D E0 。
紧接着的 2 个字节:表示原按键的“扫描码”,格式同上。
最后以四个 00 结束,(8个也行)。

键盘的按键扫描码可以参考下面这幅图(已经按照书写格式排列):

按键名称-扫描码对照表

图片来源:https://www.bilibili.com/read/cv8001022/

二、禁用 CtrlAltDel 的方法及其缺陷

根据上面提供的信息,我们了解到需要禁用 Ctrl + Alt + Del 键,只禁用 Del 就可以了,不需要修改左键,原因是 Ctrl / Alt 需要经常用,而 Del 用的频率较少。

经查表,Del 键对应的扫描码是 53 E0,所以按照规则,我们只需要将 53 E0 映射为其他按键或者映射为空即可,比如下图:
 

注册表按键映射表

这张图是注册表中修改过后的结果,圈出来的就是需要关注的的位置, 02 表示总数加一,连同后面 3 个 00,一共四个字节;后面两字节是替换的扫描码,我这里替换成数字 1(4F 00)。原键的扫描码是 53 E0。

这个方法优点就是实现简单。 

当然这个方法缺陷很明显,不可避免的按键要被无差别禁用,Del 键却还有其他功能,所以会影响使用体验。

三、编程实现和测试

那么如何通过代码来实现修改呢?很简单,就是读取注册表和修改注册表,只不过需要按照字节序构建数据。

3.1 C++ 实现的简易修改工具

为了方便制作一个简易的测试工具,我为工具定义了 config.cfg 配置文件的规则,样例如下:

# 此处填写要修改的总数,当然程序写入注册表时,按照规则注册表的记录总数比用户实际修改的个数多加1,这在之前也介绍过。
[ReMapKeyNum]
3
# 修改的键的顺序
[MapKeyRank]
1
# 修改的键的原始扫描码,格式:十六进制,两个字符。
[MapKeyOriCode]
1d00
# 修改的键的新扫描码,格式:十六进制,两个字符。
[MapKeyNewCode]
1de0
[MapKeyRank]
2
[MapKeyOriCode]
3800
[MapKeyNewCode]
38e0
[MapKeyRank]
3
[MapKeyOriCode]
53e0
[MapKeyNewCode]
0000

配置文件放在任意位置均可,目前已经实现功能的参数列表如下:

  • /enumerate :解析注册表中重映射按键的数据,该方法会逆编码原始按键名称(不过目前未实现区分左右按键)
  • /setremap <configFilePath>:指定一个路径作为配置文件路径,配置文件按照规范书写,程序将解析配置文件并用该配置文件覆盖注册表按键映射表
  • /queryActiveKey:动态查询键盘输入的按键的扫描码(不区分左右按键,这点需要改进)
  • /deleteAllMap:删除整个注册表按键映射表,恢复系统默认配置(需要二次确认操作)。注意:该操作无法撤销删除
  • /modifyDelKey:删除注册表按键映射表中指定的条目。该命令首先解析所有条目信息,随后通过输入要删除的 Item 编号即可实现删除
  • /modifyInsertKey:在指定的 Item 编号之后添加新的数据条目。需要手动输入三个参数,第一个为原始按键扫描码,第二个为修改后的按键扫描码,第三个为 Item 编号
  • /modifyAddKey:不需要指定位置编号,在结尾追加新的条目。
  • /statusMap <bEnable>:启用或禁用重映射键注册表项(该操作通过将映射表标记为 DISABLED 来禁用整张表,所以操作可以恢复),status 参数为 0 表示禁用,为 1 表示启用
  • /makebackup:备份注册表当前重映射表数据。该命令拷贝一个按照时间记录的注册表副本。
  • /recoverMap <backupTime>:恢复按照时间标签备份的副本,该操作将覆盖当前的注册表按键映射表。
  • /readbackup:读取所有注册表备份(按照时间标签列表显示)

完整代码如下(注意所有的修改除了备份数据其他操作对于大部分应用需要重启计算机生效):

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <Windows.h>
#include <iomanip>// 定义宏
#define REGISTRY_KEY L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layout"
#define REGISTRY_VALUE_NAME L"Scancode Map"
#define CONFIG_FILE_SECTION_REMAP_KEY_NUM "[ReMapKeyNum]"
#define CONFIG_FILE_SECTION_MAP_KEY_RANK "[MapKeyRank]"
#define CONFIG_FILE_SECTION_MAP_KEY_ORI_CODE "[MapKeyOriCode]"
#define CONFIG_FILE_SECTION_MAP_KEY_NEW_CODE "[MapKeyNewCode]"
#define BACKUP_KEY_PREFIX L"Scancode_Map_Back_"
#define DESABLED_KEY_PREFIX L"_DISABLED"// 全局变量用于记录按键状态
int remainingKeys = 0;
int remainingRounds = 0;
int numKeys = 0;
std::vector<std::pair<std::string, DWORD>> keyNameValuePairs;// 解析注册表中的重映射信息
bool ParseRemapInfo(std::vector<std::pair<WORD, WORD>>& remapInfo) {HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_READ, &hKey) == ERROR_SUCCESS) {DWORD bufferSize = 0;DWORD dataType;if (RegQueryValueEx(hKey, REGISTRY_VALUE_NAME, NULL, &dataType, NULL, &bufferSize) == ERROR_SUCCESS && dataType == REG_BINARY) {std::vector<BYTE> buffer(bufferSize);if (RegQueryValueEx(hKey, REGISTRY_VALUE_NAME, NULL, NULL, buffer.data(), &bufferSize) == ERROR_SUCCESS) {DWORD totalRemappedKeys = *(DWORD*)(buffer.data() + 8) - 1;for (DWORD i = 0; i < totalRemappedKeys; ++i) {WORD remappedScanCode = *(WORD*)(buffer.data() + 12 + i * 4);WORD originalScanCode = *(WORD*)(buffer.data() + 14 + i * 4);remapInfo.emplace_back(originalScanCode, remappedScanCode);}}RegCloseKey(hKey);return true;}else {RegCloseKey(hKey);return false;}}return false;
}// 枚举当前重映射的按键并逆向转换扫描码为键名
void EnumerateRemappedKeys(std::vector<std::pair<WORD, WORD>> remapInfo) {std::cout << "Remapped keys:\n";CHAR remappedKeyName[30] = { 0 }, originalKeyName[30] = { 0 };WORD remappedScanCode = 0, originalScanCode = 0;int newKeyret = 0, oldKeyret = 0;for (size_t i = 0; i < remapInfo.size(); ++i) {newKeyret = 0, oldKeyret = 0;memset(remappedKeyName, 0, sizeof(remappedKeyName));memset(originalKeyName, 0, sizeof(originalKeyName));originalScanCode = remapInfo[i].first;remappedScanCode = remapInfo[i].second;newKeyret = GetKeyNameTextA(remappedScanCode << 16,remappedKeyName, sizeof(remappedKeyName) / sizeof(*remappedKeyName));oldKeyret = GetKeyNameTextA(originalScanCode << 16,originalKeyName, sizeof(originalKeyName) / sizeof(*originalKeyName));// 逆向转换扫描码为键名std::cout << "Item " << i + 1 << ":\n";std::cout << "  Remapped Scan Code: 0x" << std::hex << remappedScanCode << "\n";std::cout << "  Remapped Key Name: " << (newKeyret > 0  ? remappedKeyName : "(null)") << "\n";std::cout << "  Original Scan Code: 0x" << std::hex << originalScanCode << "\n";std::cout << "  Original Key Name: " << (oldKeyret > 0 ? originalKeyName : "(null)") << "\n";}
}// 根据配置文件修改或添加重映射的按键
void ModifyRemappedKeys(const std::string& configFile) {std::ifstream file(configFile);if (!file.is_open()) {std::cerr << "Failed to open config file: " << configFile << std::endl;return;}std::string line;int totalRemappedKeys = 0;WORD tempCode = 0, theCode = 0;std::vector<WORD> originalScanCodes, newScanCodes;while (std::getline(file, line)) {if (line.empty() || line[0] == '#') // 绕过空行和注释continue;if (line.find(CONFIG_FILE_SECTION_REMAP_KEY_NUM) != std::string::npos) {// 循环绕过多行注释或空行do{std::getline(file, line); // 读取包含总重映射按键数的下一行}while (line.empty() || line[0] == '#');std::istringstream iss(line);iss >> totalRemappedKeys;originalScanCodes.reserve(totalRemappedKeys);newScanCodes.reserve(totalRemappedKeys);}else if (line.find(CONFIG_FILE_SECTION_MAP_KEY_ORI_CODE) != std::string::npos) {do {std::getline(file, line); // 读取包含原始扫描码的行} while (line.empty() || line[0] == '#');std::istringstream iss(line);std::string hexCode;iss >> hexCode;tempCode = static_cast<WORD>(std::stoi(hexCode, nullptr, 16));// stoi 读取时默认小端序,会导致数据颠倒,需要恢复一下theCode = ((tempCode & 0xFF) << 8) | ((tempCode >> 8) & 0xFF);originalScanCodes.push_back(theCode);}else if (line.find(CONFIG_FILE_SECTION_MAP_KEY_NEW_CODE) != std::string::npos) {do {std::getline(file, line); // 读取包含新扫描码的行} while (line.empty() || line[0] == '#');std::istringstream iss(line);std::string hexCode;iss >> hexCode;tempCode = static_cast<WORD>(std::stoi(hexCode, nullptr, 16));// stoi 读取时默认小端序,会导致数据颠倒,需要恢复一下theCode = ((tempCode & 0xFF) << 8) | ((tempCode >> 8) & 0xFF);newScanCodes.push_back(theCode);}}if (originalScanCodes.size() != totalRemappedKeys || newScanCodes.size() != totalRemappedKeys) {std::cerr << "Invalid configuration file format." << std::endl;return;}// 创建基于配置数据的缓冲区std::vector<BYTE> buffer(20 + totalRemappedKeys * 4, 0); // 头部(20 字节)+ 重映射按键数据*(DWORD*)(buffer.data() + 8) = totalRemappedKeys + 1; // 记录个数 = 实际个数 + 1(规则)for (int i = 0; i < totalRemappedKeys; ++i) {std::cout << "OriKeyCode: 0x" << std::hex << originalScanCodes[i] <<"\t NewKeyCode: 0x" << std::hex << newScanCodes[i] << "\n";// 写入数据到向量结构中*(WORD*)(buffer.data() + 12 + i * 4) = newScanCodes[i];*(WORD*)(buffer.data() + 14 + i * 4) = originalScanCodes[i];}// 修改注册表中的重映射按键HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_SET_VALUE, &hKey) == ERROR_SUCCESS) {if (RegSetValueEx(hKey, REGISTRY_VALUE_NAME, 0, REG_BINARY, buffer.data(), buffer.size()) == ERROR_SUCCESS) {std::cout << "Scancode Map updated successfully.\n";}else {std::cerr << "Failed to update Scancode Map.\n";}RegCloseKey(hKey);}else {std::cerr << "Failed to open registry key.\n";}
}// 钩子过程函数,处理键盘输入消息
LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam) {if (nCode == HC_ACTION) {KBDLLHOOKSTRUCT* kbStruct = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {CHAR keyName[256];if (GetKeyNameTextA(kbStruct->scanCode << 16, keyName, sizeof(keyName)) > 0) {keyNameValuePairs.emplace_back(keyName, kbStruct->scanCode);if (--remainingKeys == 0) {std::cout << "Round completed.\n";if (--remainingRounds == 0) {std::cout << "Maximum rounds reached.\n";std::cout << "Continuous Input:\n";for (const auto& pair : keyNameValuePairs) {std::cout << pair.first << ": 0x" << std::hex << pair.second << "\n";}PostQuitMessage(0); // 退出消息循环}else {std::cout << "Continuous Input so far:\n";for (const auto& pair : keyNameValuePairs) {std::cout << pair.first << ": 0x" << std::hex << pair.second << "\n";}keyNameValuePairs.clear();remainingKeys = numKeys;std::cout << "\nPlease press " << numKeys << " keys per round.\n";}}}}}return CallNextHookEx(NULL, nCode, wParam, lParam);
}// 监视键盘输入以查询给定键的扫描码
void QueryScanCodeForKey() {// 键入循环参数do {std::cout << "Please press numbers of keys per round, " <<"and a maximum of rounds (for at least 1 key for 1 round)." << std::endl;std::cin >> numKeys >> remainingRounds;} while (numKeys < 1 || remainingRounds < 1);std::cout << "Monitoring " << numKeys << " keys per round, for a maximum of " << remainingRounds << " rounds.\n";remainingKeys = numKeys;// 安装键盘输入的钩子HHOOK keyboardHook = SetWindowsHookExW(WH_KEYBOARD_LL, KeyboardHookProc, NULL, 0);// 消息循环以保持钩子活动MSG msg;while (GetMessage(&msg, NULL, 0, 0)) {TranslateMessage(&msg);DispatchMessage(&msg);}// 卸载钩子UnhookWindowsHookEx(keyboardHook);
}// 从注册表中删除所有重映射按键
void DeleteAllRemappedKeys() {// 二次确认std::cout << "Are you sure you want to delete all remapped keys? This action cannot be undone. (yes/no)\n";std::string confirmation;std::cin >> confirmation;if (confirmation != "yes") {std::cout << "Action cancelled.\n";return;}// 删除注册表中的重映射按键HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_SET_VALUE, &hKey) == ERROR_SUCCESS) {LONG result = RegDeleteValue(hKey, REGISTRY_VALUE_NAME);if (result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND) {std::cout << "All remapped keys deleted successfully.\n";}else {std::cerr << "Failed to delete remapped keys.\n";}RegCloseKey(hKey);}else {std::cerr << "Failed to open registry key.\n";}
}void BackupScancodeMap() {// 打开注册表键HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_WRITE | KEY_READ, &hKey) == ERROR_SUCCESS) {// 获取当前 Scancode Map 值的大小DWORD dataSize = 0;DWORD dataType;if (RegQueryValueEx(hKey, REGISTRY_VALUE_NAME, NULL, &dataType, NULL, &dataSize) == ERROR_SUCCESS && dataType == REG_BINARY) {// 获取当前时间作为备份文件名的一部分SYSTEMTIME sysTime;GetLocalTime(&sysTime);std::wstringstream backupKeyNameStream;backupKeyNameStream << BACKUP_KEY_PREFIX << std::setw(4) << std::setfill(L'0') << sysTime.wYear << L"_"<< std::setw(2) << std::setfill(L'0') << sysTime.wMonth << L"_"<< std::setw(2) << std::setfill(L'0') << sysTime.wDay << L"_"<< std::setw(2) << std::setfill(L'0') << sysTime.wHour << L"_"<< std::setw(2) << std::setfill(L'0') << sysTime.wMinute << L"_"<< std::setw(2) << std::setfill(L'0') << sysTime.wSecond;std::wstring backupKeyName = backupKeyNameStream.str();std::vector<BYTE> buffer(dataSize);if (RegQueryValueEx(hKey, REGISTRY_VALUE_NAME, NULL, NULL, buffer.data(), &dataSize) == ERROR_SUCCESS) {// 创建新的值项来存储备份数据LONG result = RegSetValueEx(hKey, backupKeyName.c_str(), 0, REG_BINARY, buffer.data(), dataSize);if (result == ERROR_SUCCESS) {std::wcout << L"Backup of Scancode Map value successful. Backup key name: " << backupKeyName << std::endl;// 询问用户是否删除原始键值项std::wcout << L"Do you want to delete the original registry key value? (Y/N): ";wchar_t response;std::wcin >> response;if (response == L'Y' || response == L'y') {if (RegDeleteValue(hKey, REGISTRY_VALUE_NAME) == ERROR_SUCCESS) {std::wcout << L"Original registry key value deleted successfully." << std::endl;}else {std::wcerr << L"Failed to delete original registry key value." << std::endl;}}else {std::wcout << L"Original registry key value not deleted." << std::endl;}}else {std::cerr << "Failed to write Scancode Map value to backup key: " << result << std::endl;}}else {std::cerr << "Failed to read registry value." << std::endl;}}else {std::cerr << "No Scancode Map value found in the registry." << std::endl;}// 关闭键RegCloseKey(hKey);}else {std::cerr << "Failed to open registry key." << std::endl;}
}// 枚举并读取所有备份的 Scancode Map 值的键名称
void EnumerateAndPrintBackupKeys() {// 打开注册表键HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) {// 枚举所有值项的名称DWORD index = 0;WCHAR subKeyName[MAX_PATH];DWORD subKeyNameSize = MAX_PATH;while (RegEnumValue(hKey, index, subKeyName, &subKeyNameSize, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) {// 检查值项的名称的前缀,如果是备份值项则输出时间信息if (wcsncmp(subKeyName, BACKUP_KEY_PREFIX, wcslen(BACKUP_KEY_PREFIX)) == 0) {std::wcout << L"Backup time: " << subKeyName << std::endl;}// 重置键名缓冲区大小subKeyNameSize = MAX_PATH;// 移动到下一个值项index++;}RegCloseKey(hKey);}else {std::cerr << "Failed to open registry key." << std::endl;}
}// 还原指定时间的 Scancode Map 值备份
void RestoreBackupScancodeMap(const std::wstring& backupTime) {// 打开注册表键HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_SET_VALUE | KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) {// 构造备份值项的名称std::wstring backupKeyName = BACKUP_KEY_PREFIX + backupTime;DWORD dataSize = 0;DWORD dataType;// 获取备份值项的大小if (RegQueryValueEx(hKey, backupKeyName.c_str(), NULL, &dataType, NULL, &dataSize) == ERROR_SUCCESS && dataType == REG_BINARY) {// 读取备份值项std::vector<BYTE> buffer(dataSize);if (RegQueryValueEx(hKey, backupKeyName.c_str(), NULL, NULL, buffer.data(), &dataSize) == ERROR_SUCCESS) {// 将备份值项的数据写入 Scancode Mapif (RegSetValueEx(hKey, REGISTRY_VALUE_NAME, 0, REG_BINARY, buffer.data(), dataSize) == ERROR_SUCCESS) {std::wcout << L"Restored Scancode Map from backup created at: " << backupTime << std::endl;}else {std::cerr << "Failed to restore Scancode Map from backup." << std::endl;}}}else {std::cerr << "Backup key not found or not a valid REG_BINARY value." << std::endl;}RegCloseKey(hKey);}else {std::cerr << "Failed to open registry key." << std::endl;}
}// 更新重映射信息到注册表
bool UpdateRemapInfo(const std::vector<std::pair<WORD, WORD>>& remapInfo) {std::vector<BYTE> buffer(20 + remapInfo.size() * 4, 0);*(DWORD*)(buffer.data() + 8) = remapInfo.size() + 1;for (size_t i = 0; i < remapInfo.size(); ++i) {*(WORD*)(buffer.data() + 12 + i * 4) = remapInfo[i].second;*(WORD*)(buffer.data() + 14 + i * 4) = remapInfo[i].first;}HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_SET_VALUE, &hKey) == ERROR_SUCCESS) {if (RegSetValueEx(hKey, REGISTRY_VALUE_NAME, 0, REG_BINARY, buffer.data(), buffer.size()) == ERROR_SUCCESS) {RegCloseKey(hKey);return true;}RegCloseKey(hKey);}return false;
}// 删除重映射信息中的指定项
void DeleteRemapItemByIndex(std::vector<std::pair<WORD, WORD>>& remapInfo) {// 枚举原有数据EnumerateRemappedKeys(remapInfo);std::cout << "Enter the numbers of items you want to delete (e.g., 1 2 3), separated by spaces: ";std::string input;std::getline(std::cin, input);std::istringstream iss(input);std::vector<int> deleteIndices;int index;while (iss >> index) {deleteIndices.push_back(index - 1); // 索引从1开始,转换为从0开始}std::vector<std::pair<WORD, WORD>> updatedRemapInfo;for (size_t i = 0; i < remapInfo.size(); ++i) {if (std::find(deleteIndices.begin(), deleteIndices.end(), i) == deleteIndices.end()) {updatedRemapInfo.push_back(remapInfo[i]);}}if (UpdateRemapInfo(updatedRemapInfo)) {std::cout << "Remapped items deleted successfully.\n";}else {std::cerr << "Failed to delete remapped items.\n";}
}// 添加重映射按键
void AddRemapItemByIndex(std::vector<std::pair<WORD, WORD>>& remapInfo,std::pair<WORD, WORD> NewMapNode, size_t dwIdToInsert) {// 参数范围校验if (dwIdToInsert < 1 || dwIdToInsert > remapInfo.size() ){std::cerr << "Error invalid parameters.\n";return;}// 检查是否有重复的映射for (const auto& pair : remapInfo) {if (pair.first == NewMapNode.first || pair.second == NewMapNode.second) {std::cerr << "Error Duplicate remapped item.\n";return;}}// 向 remapInfo 中索引为 dwIdToInsert 前面插入元素 NewMapNode// 索引为 dwIdToInsert-1 后面插入等同于在索引为 dwIdToInsert 前插入 auto it = remapInfo.begin() + dwIdToInsert;remapInfo.insert(it, NewMapNode);// 利用新的结构信息更新注册表if (UpdateRemapInfo(remapInfo)) {std::cout << "Remapped items add successfully.\n";}else {std::cerr << "Failed to insert remapped items.\n";}
}// 禁用 Remap 注册表项
void ChangeScancodeMapStatus(BOOL bEnable) {// 打开注册表键HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_WRITE | KEY_READ, &hKey) == ERROR_SUCCESS) {// 获取当前 Scancode Map 值的大小DWORD dataSize = 0;DWORD dataType;// 构造新的名称std::wstringstream disabledKeyNameStream;std::wstringstream checkKeyNameStream;disabledKeyNameStream << REGISTRY_VALUE_NAME << (bEnable ? L"" : DESABLED_KEY_PREFIX);std::wstring disabledKeyName = disabledKeyNameStream.str();checkKeyNameStream << REGISTRY_VALUE_NAME << (bEnable ? DESABLED_KEY_PREFIX : L"");std::wstring checkKeyName = checkKeyNameStream.str();if (RegQueryValueEx(hKey, checkKeyName.c_str(), NULL, &dataType, NULL, &dataSize) == ERROR_SUCCESS && dataType == REG_BINARY) {std::vector<BYTE> buffer(dataSize);if (RegQueryValueEx(hKey, checkKeyName.c_str(), NULL, NULL, buffer.data(), &dataSize) == ERROR_SUCCESS) {// 创建新的值项来存储备份数据LONG result = RegSetValueEx(hKey, disabledKeyName.c_str(), 0, REG_BINARY, buffer.data(), dataSize);if (result == ERROR_SUCCESS) {std::wcout << (bEnable ? L"Enable" : L"Disable")<< L" Scancode Map value successful. New key name: " << disabledKeyName << std::endl;// 删除原始键值项if (RegDeleteValue(hKey, checkKeyName.c_str()) == ERROR_SUCCESS) {std::wcout << L"Original registry key value deleted successfully." << std::endl;}else {std::wcerr << L"Failed to delete original registry key value." << std::endl;RegDeleteValue(hKey, disabledKeyName.c_str());}}else {std::cerr << "Failed to write Scancode Map value to disabled key: " << result << std::endl;}}else {std::cerr << "Failed to read registry value." << std::endl;}}else {if (RegQueryValueEx(hKey, disabledKeyName.c_str(), NULL, &dataType, NULL, &dataSize) == ERROR_SUCCESS && dataType == REG_BINARY) {std::wcout << L"Remapping keys were already "<< (bEnable ? L"enabled" : L"disabled")<< L" earlier." << std::endl;}else {std::cerr << "No Scancode Map value found in the registry." << std::endl;}}// 关闭键RegCloseKey(hKey);}else {std::cerr << "Failed to open registry key." << std::endl;}
}// 将 char* 类型的字符串转换为 std::wstring
std::wstring ConvertToWideString(const char* str) {int size = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);if (size == 0) {// 转换失败return L"";}std::wstring result(size, L'\0');MultiByteToWideChar(CP_UTF8, 0, str, -1, &result[0], size);return result;
}int main(int argc, char* argv[]) {if (argc < 2) {std::cerr << "Usage: " << argv[0] << " <option> [configFile]\n";return 1;}std::string option = argv[1];if (option == "/enumerate") {   // 解析目前注册表中重映射键值的数据std::vector<std::pair<WORD, WORD>> remapInfo;if (ParseRemapInfo(remapInfo)) {EnumerateRemappedKeys(remapInfo);}else {std::cerr << "Failed to parse remap info.\n";}}else if (option == "/setremap") {  // 向注册表中覆盖写入重映射键值的数据if (argc != 3) {std::cerr << "Usage: " << argv[0] << " /setremap <configFilePath>\n";return 1;}std::string configFile = argv[2];ModifyRemappedKeys(configFile);}else if (option == "/queryActiveKey") {   // 动态查询按键的扫描码QueryScanCodeForKey();}else if (option == "/deleteAllMap")   // 删除所有重映射键数据{DeleteAllRemappedKeys();}else if (option == "/modifyDelKey") {    // 删除指定的重映射键数据std::vector<std::pair<WORD, WORD>> remapInfo;if (ParseRemapInfo(remapInfo)) {DeleteRemapItemByIndex(remapInfo);}else {std::cerr << "Failed to parse remap info.\n";}}else if (option == "/modifyInsertKey")      // 在指定的元素后面添加新的按键映射{std::vector<std::pair<WORD, WORD>> remapInfo;WORD oldScanCode = 0, newScanCode = 0;size_t index = 0;if (ParseRemapInfo(remapInfo)) {  // 解析注册表参数// 枚举原有数据EnumerateRemappedKeys(remapInfo);// 选择插入的位置和数据std::cout << "Enter the Original Key scan code: ";std::cin >> std::hex >> oldScanCode;std::cout << "Enter the Key scan code you wannt to replace to: ";std::cin >> std::hex >> newScanCode;std::cout << "Enter the number of item you wannt to insert after: ";std::cin >> index;// 调整字节顺序oldScanCode = ((oldScanCode & 0xFF00) >> 8) | ((oldScanCode & 0x00FF) << 8);newScanCode = ((newScanCode & 0xFF00) >> 8) | ((newScanCode & 0x00FF) << 8);// 写入数据AddRemapItemByIndex(remapInfo,std::make_pair(oldScanCode, newScanCode), index);}else {std::cerr << "Failed to parse remap info.\n";}}else if (option == "/modifyAddKey")     // 在映射表的末尾追加按键映射{std::vector<std::pair<WORD, WORD>> remapInfo;WORD oldScanCode = 0, newScanCode = 0;size_t index = 0;if (ParseRemapInfo(remapInfo)) {  // 解析注册表参数// 选择插入的位置和数据std::cout << "Enter the Original Key scan code: ";std::cin >> std::hex >> oldScanCode;std::cout << "Enter the Key scan code you wannt to replace to: ";std::cin >> std::hex >> newScanCode;index = remapInfo.size(); // 在结尾插入// 调整字节顺序oldScanCode = ((oldScanCode & 0xFF00) >> 8) | ((oldScanCode & 0x00FF) << 8);newScanCode = ((newScanCode & 0xFF00) >> 8) | ((newScanCode & 0x00FF) << 8);// 写入数据AddRemapItemByIndex(remapInfo,std::make_pair(oldScanCode, newScanCode), index);}else {std::cerr << "Failed to parse remap info.\n";}}else if (option == "/statusMap")   // 启用或者禁用重映射键(可恢复,1表示启用,0表示禁用){if (argc != 3) {std::cerr << "Usage: " << argv[0] << " /statusMap <bEnable>\n";return 1;}if (std::string(argv[2]) == "1"){std::cout << "Try to enable ReMap Key.\n";ChangeScancodeMapStatus(TRUE);}else if (std::string(argv[2]) == "0"){std::cout << "Try to disable ReMap Key.\n";ChangeScancodeMapStatus(FALSE);}}else if (option == "/makebackup") {  // 备份当前注册表重映射键数据BackupScancodeMap();}else if (option == "/recoverMap") {  // 按照备份日期还原数据if (argc != 3) {std::cerr << "Usage: " << argv[0] << " /recoverMap <backupTime>\n";return 1;}std::wstring backupTime = ConvertToWideString(argv[2]);if (backupTime == L"")return 1;RestoreBackupScancodeMap(backupTime);}else if (option == "/readbackup") {   // 读取备份文件列表EnumerateAndPrintBackupKeys();}else {std::cerr << "Invalid option.\n";return 1;}return 0;
}

程序需要管理员权限,设置清单文件即可:

标题

测试截图如下:

标题

3.2 C# 实现的窗口工具

Medo 制作的开源工具 Scancode Map 是一个不错的选择,它具有完备的查询、修改、删除等功能,我的程序一定程度上参考了他的思路。

这是该工具的界面视图:

Scancode Map 截图 1
Scancode Map 截图 2

原文链接为:Scancode Map 1.11 – Medo's Home Page。

网盘下载链接:https://pan.baidu.com/s/1Dxn8R3GEdol59ObYAMTLHw?pwd=vtq3

提取码:vtq3 (里面有我重新编译好的文件在 /source/bin 内,并且包含我写的控制台程序)

官网程序下载链接:https://www.medo64.com/download/scancodemap111.exe。

源代码 Github 链接:Release Most recent build · medo64/ScancodeMap · GitHub。

四、总结

本文简单介绍了使用 "Scancode Map" 键盘扫描码映射表禁用 CtrlAltDel 键的方法。浅谈了该方法的利弊。修改注册表屏蔽该按键的方法还有另外一个稳定的方法,就是利用 Disable 项隐藏相应的选项,这个方法也是以前讨论最多的方法,比按键扫描码用的还广泛,还有一个就是进程冻结法。但是都有缺点,比如可以注册表方法是可以轻易恢复的;进程冻结存在不稳定性,冻结后容易让进程陷入 RPC 死锁导致无法恢复。


【保留备用】

本文属于原创文章,转载请注明出处:

https://blog.csdn.net/qq_59075481/article/details/136104444。

发布于:2024.02.13,更新于:2024.02.13.

相关文章:

键盘重映射禁用 CtrlAltDel 键的利弊

目录 前言 一、Scancode Map 的规范 二、禁用 CtrlAltDel 的方法及其缺陷 三、编程实现和测试 3.1 C 实现的简易修改工具 3.2 C# 实现的窗口工具 四、总结 本文属于原创文章&#xff0c;转载请注明出处&#xff1a; https://blog.csdn.net/qq_59075481/article/details…...

【网工】华为设备命令学习(综合实验一)

实验要求和实验成果如图所示。 LSW2不需要其他配置&#xff0c;其下就一台设备&#xff0c;不需要区分。 LSW3配置如下&#xff1a; <Huawei>sy Enter system view, return user view with CtrlZ. [Huawei]un in en //关闭系统提示信息 Info: Information …...

JavaScript中的常见算法

一.排序算法 1.冒泡排序 冒泡排序比较所有相邻的两个项&#xff0c;如果第一个比第二个大&#xff0c;则交换它们。元素项向上移动至 正确的顺序&#xff0c;就好像气泡升至表面一样。 function bubbleSort(arr) {const { length } arrfor (let i 0; i < length - 1; i)…...

桥接模式:连接抽象与实现的设计艺术

桥接模式&#xff1a;连接抽象与实现的设计艺术 在软件开发中&#xff0c;设计模式是帮助我们以优雅的方式解决问题的模板。桥接模式&#xff08;Bridge Pattern&#xff09;是一种结构型设计模式&#xff0c;它的主要目标是将抽象部分与实现部分分离&#xff0c;这样两者可以…...

C语言——oj刷题——字符串左旋

问题&#xff1a; 实现一个函数&#xff0c;可以左旋字符串中的k个字符。 例如&#xff1a; ABCD左旋一个字符得到BCDA ABCD左旋两个字符得到CDAB 实现&#xff1a; 当我们谈到字符串左旋时&#xff0c;我们指的是将字符串中的字符向左移动一定数量的位置。这个问题在编程中…...

神经网络(Nature Network)

最近接触目标检测较多&#xff0c;再此对最基本的神经网络知识进行补充&#xff0c;本博客适合想入门人工智能、其含有线性代数及高等数学基础的人群观看 1.构成 由输入层、隐藏层、输出层、激活函数、损失函数组成。 输入层&#xff1a;接收原始数据隐藏层&#xff1a;进行…...

【Unity】QFramework通用背包系统优化:使用Odin优化编辑器

前言 在学习凉鞋老师的课程《QFramework系统设计&#xff1a;通用背包系统》第四章时&#xff0c;笔者使用了Odin插件&#xff0c;对Item和ItemDatabase的SO文件进行了一些优化&#xff0c;使物品页面更加紧凑、更易拓展。 核心逻辑和功能没有改动&#xff0c;整体代码量减少…...

基本算法--贪心

1.简述 贪心法的效率非常高&#xff0c;复杂度常常为O&#xff08;1&#xff09;&#xff0c;是一种局部最优的解题方法&#xff0c;而很多问题都需要求全局最优&#xff0c;&#xff0c;所以在使用贪心法之前需要评估是否能从局部最优推广到全局最优。 2.思路 作为算法的贪…...

13. 串口接收模块的项目应用案例

1. 使用串口来控制LED灯工作状态 使用串口发送指令到FPGA开发板&#xff0c;来控制第7课中第4个实验的开发板上的LED灯的工作状态。 LED灯的工作状态&#xff1a;让LED灯按指定的亮灭模式亮灭&#xff0c;亮灭模式未知&#xff0c;由用户指定&#xff0c;8个变化状态为一个循…...

Python re找到特定pattern并将此pattern重复n次

要找到字符串s中的数字&#xff0c;并将这些数字重复3次&#xff1a; import re s "abc123def456ghi789" # 找到所有的数字 numbers re.findall(r\d, s) # 重复每个数字3次 repeated_numbers [num * 3 for num in numbers] # 将重复的数字放回原位置 #…...

ChatGpt报错:We ran into an issue while authenticating you解决办法

在登录ChatGpt时报错&#xff1a;Oops&#xff01;,We ran into an issue while authenticating you.(我们在验证您时遇到问题)&#xff0c;记录一下解决过程。 完整报错&#xff1a; We ran into an issue while authenticating you. If this issue persists, please contact…...

如何从 iPhone 恢复已删除的视频:简单有效方法

无论您是在尝试释放空间时不小心删除了 iPhone 上的视频&#xff0c;还是在出厂时清空了手机&#xff0c;现在所有数据都消失了&#xff0c;都不要放弃。有一些方法可以恢复这些视频。 在本文中&#xff0c;我们将向您展示六种最有效的数据恢复方法&#xff0c;可以帮助您从 i…...

【python量化交易】qteasy使用教程02 - 获取和管理金融数据

qteasy教程2 - 获取并管理金融数据 qteasy教程2 - 获取并管理金融数据开始前的准备工作获取基础数据以及价格数据下载交易日历和基础数据查看股票和指数的基础数据下载沪市股票数据从本地获取股价数据生成K线图 数据类型的查找定期下载数据到本地回顾总结 qteasy教程2 - 获取并…...

数据库学习案例20240206-ORACLE NEW RAC agent and resource关系汇总。

1 集群架构图 整体集群架构图如下&#xff1a; 1 数据库启动顺序OHASD层面 操作系统进程init.ohasd run启动ohasd.bin init.ohasd run 集群自动启动是否被禁用 crsctl enable has/crsGIHOME所在文件系统是否被正常挂载。管道文件npohasd是否能够被访问&#xff0c; cd /var/t…...

TypeScript 入门

课程地址 ts 开发环境搭建 npm i -g typescript查看安装位置&#xff1a; $ npm root -g C:\Users\Daniel\AppData\Roaming\npm\node_modules创建 hello.ts&#xff1a; console.log("hello, ts");编译 ts 文件&#xff0c;得到 js 文件&#xff1a; $ tsc foo.…...

linux 磁盘相关操作

1.U盘接入虚拟机 &#xff08;1&#xff09;在插入u盘时&#xff0c;虚拟机会检测usb设备&#xff0c;在弹出窗口选择连接到虚拟机即可。 &#xff08;2&#xff09;或 直接在虚拟机--->可移动设备--->找到U盘---->连接 2.检测U盘是否被虚拟机识别 ls /dev/sd* 查…...

PyTorch: torch.max()函数详解

torch.max函数详解&#xff1a;基于PyTorch的深入探索 &#x1f335;文章目录&#x1f335; &#x1f333;引言&#x1f333;&#x1f333;torch.max()函数简介&#x1f333;&#x1f333;torch.max()的返回值&#x1f333;&#x1f333;torch.max()的应用示例&#x1f333;&am…...

Rust基础拾遗--核心功能

Rust基础拾遗 前言1.所有权与移动1.1 所有权 2.引用3.特型与泛型简介3.1 使用特型3.2 特型对象3.3 泛型函数与类型参数 4.实用工具特型5.闭包 前言 通过Rust程序设计-第二版笔记的形式对Rust相关重点知识进行汇总&#xff0c;读者通读此系列文章就可以轻松的把该语言基础捡起来…...

MySQL:常用指令

MySQL官网 一、在Windows 系统 cmd窗口里执行的命令 启动:net start MySQL停止:net stop MySQL卸载:sc delete MySQL 二、在macOS系统终端里执行的命令 启动&#xff1a;mysql.server start停止&#xff1a;mysql.server stop重启&#xff1a;mysql.server restart 三、执行帮…...

Scrapy:Python中强大的网络爬虫框架

Scrapy&#xff1a;Python中强大的网络爬虫框架 在当今信息爆炸的时代&#xff0c;从互联网上获取数据已经成为许多应用程序的核心需求。Scrapy是一款基于Python的强大网络爬虫框架&#xff0c;它提供了一种灵活且高效的方式来提取、处理和存储互联网上的数据。本文将介绍Scrap…...

linux系统非关系型数据库redis的配置文件

redis配置文件 Redis的配置文件位于Redis安装目录下&#xff0c;文件名为redis.conf&#xff0c;配置项说明如下 Redis默认不是以守护进程的方式运行&#xff0c;可以通过该配置项修改&#xff0c;使用yes启用守护进程 daemonize no当Redis以守护进程方式运行时&#xff0c;Red…...

电力负荷预测 | 基于LSTM、TCN的电力负荷预测(Python)

文章目录 效果一览文章概述源码设计参考资料效果一览 文章概述 电力负荷预测 | 基于LSTM、TCN的电力负荷预测(Python) 源码设计 #------------------...

Java+SpringBoot实习管理系统探秘

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…...

c入门第十六篇——学生成绩管理系统

师弟&#xff1a;“师兄&#xff0c;我最近构建了一个学生成绩管理系统&#xff0c;有空试用一下么&#xff1f;” 我&#xff1a;“好啊&#xff01;” 一个简单的学生成绩管理系统&#xff0c;基本功能包括&#xff1a;添加学生信息、显示所有学生信息、按学号查找学生信息、…...

大文件上传如何做断点续传?

文章目录 一、是什么分片上传断点续传 二、实现思路三、使用场景小结 参考文献 一、是什么 不管怎样简单的需求&#xff0c;在量级达到一定层次时&#xff0c;都会变得异常复杂 文件上传简单&#xff0c;文件变大就复杂 上传大文件时&#xff0c;以下几个变量会影响我们的用…...

SpringCloud-Eureka原理分析

Eureka是Netflix开源的一款用于实现服务注册与发现的工具。在微服务架构中&#xff0c;服务的动态注册和发现是必不可少的组成部分&#xff0c;而Eureka正是为了解决这一问题而诞生的。 一、为何需要Eureka 在微服务架构中&#xff0c;服务之间的协同合作和高效通信是至关重要…...

LeetCode周赛——384

1.修改矩阵&#xff08;模拟&#xff09; class Solution { public:vector<vector<int>> modifiedMatrix(vector<vector<int>>& matrix) {int n matrix.size();int m matrix[0].size();vector<int> ans(m);for(int i 0; i < m; i)for(…...

C#,巴都万数列(Padonve Number)的算法与源代码

1 巴都万数列&#xff08;Padovan Sequence&#xff09; 巴都万数列&#xff08;Padovan Sequence&#xff09;是一个整数数列。 首数个值为1, 1, 1, 2, 2, 3, 4, 5, 7, 9, 12, 16, 21, 28, 37 ... 此数列以建筑师理察巴都万命名&#xff0c;他的论文Dom&#xff08;1994年&a…...

NSSCTF Round#18 RE GenshinWishSimulator WP

恶搞原神抽卡模拟器 看到软件的界面&#xff0c;大致有三种思路&#xff1a; 修改石头数量一直抽&#xff0c;如果概率正常肯定能抽到&#xff08;但是估计设置的概率是0&#xff09;在源码里找flag的数据把抽卡概率改成100%直接抽出来 Unity逆向&#xff0c;根据经验应该dnsp…...

鸿蒙系统对应安卓版本

鸿蒙系统对应安卓版本 使用安卓studio 新建一个app 然后添加代码打印&#xff1a; Log.d(“MainActivity”, "SDK Version: " Build.VERSION.SDK_INT); 或者把 Build.VERSION.SDK_INT 添加到显示的字符串上面 我这里 build.gradle.kts 配置 android {compileSdk…...

算法-16-并查集

并查集简介 并查集&#xff1a;一开始&#xff0c;把a&#xff0c;b&#xff0c;c放入并查集&#xff0c;a自己一个集合&#xff0c;b自己一个&#xff0c;c自己一个 提供的方法 1.boolean isSameSet(a,b)&#xff0c;判断ab是否在同一个集合 2.void union(a,b),把a所…...

【C/C++】2024春晚刘谦春晚魔术步骤模拟+暴力破解

在这个特别的除夕夜&#xff0c;我们不仅享受了与家人的温馨团聚&#xff0c;还被电视机前的春节联欢晚会深深吸引。特别是&#xff0c;魔术师刘谦的精彩表演&#xff0c;为我们带来了一场视觉和心灵的盛宴。在我的博客“【C/C】2024春晚刘谦春晚魔术步骤模拟暴力破解”中&…...

Java运算符和表达式

Java运算符和表达式 和C语言一样&#xff0c;java也有基础的运算符和表达式&#xff0c;用来完成一些基础的数学计算&#xff0c;以及逻辑运算&#xff0c;我们一起来学习一下吧。 算数运算符 首先&#xff0c;这个算数运算符与数学中即C语言的运算符的功能一样&#xff0c;利…...

波奇学Linux:软硬链接

ln指令建立链接 软链接 硬链接 所属者的前的数字表示硬链接数&#xff0c;引用计数&#xff0c;file.txt和soft_link是软链接所以都为2 软链接有独立inode&#xff0c;硬链接没有&#xff0c;所以硬链接不是独立文件&#xff0c;软链接是独立文件&#xff0c;且硬链接的属性会…...

HTTP网络通信协议基础

目录 前言&#xff1a; 1.HTTP协议理论 1.1协议概念 1.2工作原理 1.3工作场景 2.HTTP抓包工具 2.1Fiddler工具 2.2抓包原理 2.3抓包结果 3.HTTP协议格式 3.1HTTP请求 3.2HTTP响应 3.3格式总结 前言&#xff1a; 在了解完网络编程的传输层UDP和TCP通信协议后&#…...

Java实现河南软件客服系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统管理人员2.2 业务操作人员 三、系统展示四、核心代码4.1 查询客户4.2 新增客户跟进情况4.3 查询客户历史4.4 新增服务派单4.5 新增客户服务费 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的河…...

【小沐学GIS】基于C++QT绘制三维数字地球Earth(OpenGL)

&#x1f37a;三维数字地球系列相关文章如下&#x1f37a;&#xff1a;1【小沐学GIS】基于C绘制三维数字地球Earth&#xff08;456:OpenGL、glfw、glut&#xff09;第一期2【小沐学GIS】基于C绘制三维数字地球Earth&#xff08;456:OpenGL、glfw、glut&#xff09;第二期3【小沐…...

如何生成生成一个修仙世界的狗血短剧剧本

如何生成生成一个修仙世界的狗血短剧剧本 生成一个修仙世界的狗血短剧剧本将上述剧本转为对话 生成一个修仙世界的狗血短剧剧本 剧本名称&#xff1a;《仙途情缘》 角色&#xff1a; 易天行&#xff1a;男主角&#xff0c;天赋异禀的修仙者&#xff0c;性格坚毅&#xff0c;正…...

【MIMO】

MIMO技术入门 1.简介 MIMO(多入多出):多天线技术。 注意&#xff1a;此处的多天线&#xff0c;并不是有多个天线板&#xff0c;对基站来讲指天线有多套振子&#xff08;每一套振子都可以看成一个独立的天线&#xff09;。 4G 8天线&#xff1b;5G 64T64R&#xff1b;不仅基站…...

ZooKeeper分布式锁

ZooKeeper是一个开源的分布式协调服务&#xff0c;它主要用于维护配置信息、提供分布式同步、命名服务等。ZooKeeper的数据模型类似于文件系统&#xff0c;它的数据结构中的每个数据节点称为znode&#xff0c;可以用它来实现分布式锁。 ZooKeeper分布式锁的原理&#xff1a; …...

WPF是不是垂垂老矣啦?平替它的框架还有哪些

WPF&#xff08;Windows Presentation Foundation&#xff09;是微软推出的一种用于创建 Windows 应用程序的用户界面框架。WPF最初是在2006年11月推出的&#xff0c;它是.NET Framework 3.0的一部分&#xff0c;为开发人员提供了一种基于 XAML 的方式来构建丰富的用户界面。 W…...

浅析Linux追踪技术之ftrace:Tracepoint

文章目录 概述Tracepoint使用定义Tracepoint添加Tracepoint调用 Tracepoint数据结构TRACE_EVENT实现DECLARE_TRACE__DECLARE_TRACE trace_xxx函数相关参考 概述 Tracepoint&#xff08;跟踪点&#xff09;是添加到代码流程中的调用点&#xff0c;并且允许开发者注册自定义的回…...

python ftp文件断点续传 并判断ftp文件下载完成

在Python中实现FTP文件的断点续传&#xff0c;通常涉及到以下步骤&#xff1a; 连接到FTP服务器。获取远程文件的大小。检查本地文件是否存在以及它的大小。如果本地文件不存在或大小小于远程文件&#xff0c;从上次中断的位置开始下载。下载完成后&#xff0c;检查文件大小以…...

SpringBoot+Vue3 完成小红书项目

简介 该项目采用微服务架构&#xff0c;实现了前后端分离的系统设计。在前端&#xff0c;我们选择了 Vue3 配合 TypeScript 和 ElementUi 框架&#xff0c;以提升开发效率和用户体验。而在后端&#xff0c;则是运用 SpringBoot 和 Mybatis-plus 进行开发&#xff0c;保证了系统…...

springboot集成Sa-Token及Redis的redisson客户端

文章目录 什么是Sa-Token?为什么集成Redis的redisson客户端?如何集成?maven依赖application.yml配置过滤器配置验证参考什么是Sa-Token? Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权…...

SQL世界之命令语句Ⅴ

目录 一、SQL CREATE INDEX 语句 1.SQL CREATE INDEX 语句 2.SQL CREATE INDEX 语法 3.SQL CREATE UNIQUE INDEX 语法 4.SQL CREATE INDEX 实例 二、SQL 撤销索引、表以及数据库 1.SQL DROP INDEX 语句 2.SQL DROP TABLE 语句 3.SQL DROP DATABASE 语句 4.SQL TRUNCA…...

Springboot拦截器中跨域失效的问题、同一个接口传入参数不同,一个成功,一个有跨域问题、拦截器和@CrossOrigin和@Controller

Springboot拦截器中跨域失效的问题 一、概述 1、具体场景 起因&#xff1a; 同一个接口&#xff0c;传入不同参数进行值的修改时&#xff0c;一个成功&#xff0c;另一个竟然失败&#xff0c;而且是跨域问题拦截器内的request参数调用getHeader方法时&#xff0c;获取不到前端…...

WordPress如何自建txt文本经典语录并随机显示一句话经典语录?

前面跟大家分享的『WordPress集成一言&#xff08;Hitokoto&#xff09;API经典语句功能』一文中就提供有自创API&#xff0c;其中懿古今顶部左上角显示的经典语录用的就是自建一个txt文本文件&#xff0c;然后再在前端网页指定位置随机显示语录。具体操作方法如下&#xff1a;…...

Java中JVM常用参数配置(提供配置示例)

目录 前言一、内存参数配置二、垃圾收集器配置三、GC策略配置3.1、基础通用配置3.2、Parallel 和 Parallel Old 常用参数配置3.3、CMS 常用参数配置3.4、G1 常用参数配置 四、GC日志配置五、dump 日志参数配置5.1、OutOfMemory异常时生成dump文件5.2、发生Full GC时生成dump文件…...

图论与图数据应用综述:从基础概念到知识图谱与图智能

目录 前言1 图论基础概念1.1 节点度1.2 度分布1.3 邻接矩阵 2 探索图的高级概念2.1 最短路径的关键性2.2 图的直径与平均路径的意义2.3 循环与路径类型的多样性 3 深入探讨图的广泛应用领域3.1 知识图谱的知识管理3.2 图智能在复杂决策中的应用3.3 图数据挖掘与分析的多领域应用…...