拓冰建站拓冰建站
首页 / 资讯中心 / 正文

WinCC 7.4 全屏运行时禁用Windows系统热键的脚本实现

1. 项目缘起一个被热键打断的自动化噩梦如果你在工业自动化或者SCADA系统领域工作尤其是负责过WinCC上位机项目那你大概率遇到过这个让人血压飙升的场景你精心设计的WinCC画面正在全屏运行操作员通过触摸屏或鼠标进行关键的生产监控与操作。突然某个操作员不小心按下了AltTab或者CtrlEsc画面瞬间从全屏的WinCC切换到Windows桌面或者干脆弹出了开始菜单。生产线上的关键数据流中断了操作员的注意力被打断了更严重的是如果此时有紧急报警弹出却被切换画面所掩盖后果不堪设想。这就是我们今天要解决的核心痛点如何在全屏运行的WinCC 7.4环境中彻底掌控Windows系统热键防止它们干扰关键的人机交互过程。项目标题“wincc7.4使用脚本禁用、启用windows热键”直指这个在工控现场非常实际的需求。它不是一个炫技的功能而是一个关乎系统稳定性、操作安全性和用户体验的硬性要求。WinCC作为西门子经典的监控组态软件其7.4版本虽然在界面和功能上有了长足进步但它本质上仍然运行在Windows操作系统之上。Windows为了方便用户预设了大量的系统级快捷键比如Win键打开开始菜单、AltTab切换窗口、CtrlEsc等同于Win键、AltF4关闭窗口等。在办公环境下这些热键是效率工具但在要求沉浸式、防误操作的生产监控环境下它们就成了潜在的“捣蛋鬼”。网络上相关的热词如“windows脚本命令闪退”、“shell脚本”、“powershell开机自启脚本”都反映了大家试图用脚本自动化解决Windows管理问题的普遍尝试。而“禁用”、“启用”则是我们操作的核心动作。我们的目标很明确写一段脚本在WinCC项目运行时能够按需屏蔽这些系统热键在需要退出或维护时又能优雅地恢复它们。这不仅仅是调用一个API那么简单它涉及到对Windows消息机制的理解、对WinCC运行时环境的把握以及对脚本稳定性的苛刻要求。2. 核心原理钩住Windows的消息流要实现禁用系统热键我们必须深入到Windows操作系统的运行机制中去。Windows是一个基于消息驱动的系统用户的每一次按键、每一次鼠标点击都会被系统捕获并转化为一个特定的“消息”然后投递给当前具有焦点的应用程序窗口。系统热键的本质就是Windows系统内部对一些特定按键组合如WinL进行了监听并触发了对应的系统功能。因此我们的思路不是去修改Windows的系统设置那通常需要管理员权限且影响全局而是在我们自己的应用程序层面进行拦截。更具体地说是在WinCC的运行画面窗口层面进行拦截。这里一个强大的Windows API——SetWindowsHookEx就登场了。它可以让我们安装一个“钩子”这个钩子能监视整个系统或特定线程的消息队列。对于键盘消息我们使用WH_KEYBOARD_LL低级键盘钩子或WH_KEYBOARD线程特定键盘钩子。为什么选择低级键盘钩子WH_KEYBOARD_LL是一个全局的低级钩子它可以监听到所有线程的键盘输入而且它是在系统将键盘消息投递到线程消息队列之前就被调用。这意味着我们有机会在系统热键生效之前就“吃掉”这个消息让它无法继续传递。这对于拦截Win键这类系统级热键至关重要。相比之下WH_KEYBOARD是线程特定的更适合拦截发送到本程序窗口的按键。在WinCC的语境下我们通常会将这个钩子安装在WinCC运行系统的线程上。当钩子函数被触发时我们能收到一个结构体里面包含了详细的按键信息虚拟键码、扫描码、以及按键状态按下或释放。我们的任务就是判断当前按下的键是不是我们想要屏蔽的系统热键比如Win键的虚拟键码是VK_LWIN和VK_RWIN如果是那么我们就让钩子函数返回一个非零值告诉系统“这个消息我已经处理了你别管了。”这样系统就不会再对这个按键组合做出响应Win键也就失效了。启用热键则更简单就是卸载我们之前安装的钩子让Windows的消息流恢复畅通无阻。这个“禁用-启用”的开关我们将通过WinCC的脚本通常是C脚本或VBScript来触发并将其与画面上的按钮、全局脚本或项目事件关联起来实现灵活控制。注意使用低级钩子WH_KEYBOARD_LL要求钩子函数必须在一个动态链接库中。这意味着我们不能直接在WinCC的C脚本里写钩子函数而需要借助一个外部的DLL文件。这是本项目第一个也是最重要的技术分水岭。3. 实战准备构建拦截热键的DLL核心明白了原理我们就开始动手。整个方案的核心是一个用C/C编写的DLL文件。这个DLL将封装安装钩子、卸载钩子和钩子处理函数的全部逻辑。为什么不用WinCC自带的C脚本因为标准C脚本环境不支持编译生成DLL也不方便直接调用SetWindowsHookEx这样的底层API。3.1 开发环境与项目创建我使用的是Visual Studio 2019进行开发。选择创建一个“动态链接库”项目语言为C。为了兼容性和简洁性我选择创建的是一个非MFC、非ATL的纯Win32 DLL项目。项目名称可以定为WinCC_BlockHotkey。创建好后你会得到dllmain.cpp等文件。我们需要的主要是三个部分共享数据段由于钩子函数可能被系统在不同线程上下文调用我们需要一个地方来存储钩子句柄这样的全局变量。使用#pragma data_seg来创建一个共享的数据段是标准做法。钩子处理函数这是核心逻辑所在一个LowLevelKeyboardProc回调函数。导出函数提供两个接口给WinCC调用InstallHook和UninstallHook。3.2 核心代码实现以下是dllmain.cpp的关键代码片段我加入了详细注释// WinCC_BlockHotkey.cpp : 定义 DLL 的导出函数。 #include “pch.h” #include windows.h // 1. 定义共享数据段用于存储钩子句柄确保全局唯一 #pragma data_seg(“.SHARED”) HHOOK g_hKeyboardHook NULL; #pragma data_seg() #pragma comment(linker, “/section:.SHARED,rws”) // 告诉链接器该段具有读、写、共享属性 // 2. 低级键盘钩子处理函数 LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode HC_ACTION) { KBDLLHOOKSTRUCT *pKbStruct (KBDLLHOOKSTRUCT *)lParam; // 判断是否为Win键左Win或右Win if (pKbStruct-vkCode VK_LWIN || pKbStruct-vkCode VK_RWIN) { // 直接返回1拦截此消息阻止系统处理Win键 return 1; } // 你可以在这里扩展其他需要屏蔽的热键 // 例如屏蔽AltTab (需要同时判断Alt键和Tab键的状态) // 屏蔽AltF4 if (pKbStruct-vkCode VK_F4 (GetAsyncKeyState(VK_MENU) 0x8000)) { // Alt键的虚拟键码是VK_MENU return 1; // 拦截AltF4 } // 注意低级钩子中精确拦截AltTab比较复杂因为它涉及两个键的顺序和状态。 // 一种更简单粗暴的方法是直接屏蔽Alt键但这会影响所有Alt组合键。 // 对于AltTab另一种思路是使用WH_KEYBOARD钩子并配合GetKeyState但那是线程钩子。 } // 如果不是要拦截的键则调用下一个钩子如果有的话最终让系统默认处理 return CallNextHookEx(g_hKeyboardHook, nCode, wParam, lParam); } // 3. 导出函数安装钩子 extern “C” __declspec(dllexport) BOOL InstallHook() { // 如果钩子已经安装则不再重复安装 if (g_hKeyboardHook ! NULL) { return FALSE; } // 设置低级键盘钩子 // 第一个参数WH_KEYBOARD_LL指定钩子类型 // 第二个参数是钩子处理函数地址 // 第三个参数是包含钩子函数的模块实例句柄这里用NULL表示当前DLL // 第四个参数0表示全局钩子监视所有线程 g_hKeyboardHook SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), 0); return (g_hKeyboardHook ! NULL); } // 4. 导出函数卸载钩子 extern “C” __declspec(dllexport) BOOL UninstallHook() { BOOL bResult FALSE; if (g_hKeyboardHook ! NULL) { bResult UnhookWindowsHookEx(g_hKeyboardHook); g_hKeyboardHook NULL; } return bResult; } // DLL入口函数这里我们不需要特殊处理 BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }代码关键点解析共享数据段g_hKeyboardHook这个钩子句柄必须存储在共享段中因为安装钩子的线程和调用钩子函数的线程可能不同。如果不共享UninstallHook可能无法正确找到要卸载的钩子。钩子类型WH_KEYBOARD_LL是低级钩子它不需要注入到其他进程但要求钩子过程必须在DLL中并且会收到所有按键消息。拦截逻辑在LowLevelKeyboardProc中我们检查vkCode。对于Win键直接拦截。对于AltF4我们不仅检查F4键还通过GetAsyncKeyState(VK_MENU)检查Alt键是否也被按下。这是一种简化的组合键判断。CallNextHookEx对于不想处理的按键必须调用这个函数将消息传递下去否则会影响其他应用程序的正常键盘输入。3.3 编译与部署使用Visual Studio编译该项目生成WinCC_BlockHotkey.dll文件。你需要将这个DLL文件复制到你的WinCC项目计算机上一个合适的路径。我个人的习惯是在WinCC项目目录下创建一个Bin或Lib文件夹。将DLL文件放入其中。在WinCC的全局脚本或画面脚本中使用绝对路径或相对路径来调用它。更推荐使用绝对路径因为WinCC运行时的当前目录可能不确定。重要提示这个DLL需要与WinCC运行系统的位数匹配。如果你的WinCC是32位的绝大多数WinCC 7.4是32位应用那么你必须编译生成32位x86的DLL。如果你的操作系统是64位的32位DLL依然可以正常工作。编译时在Visual Studio的解决方案平台中选择Win32。4. WinCC脚本集成连接DLL与画面控制DLL准备好了接下来就是在WinCC中调用它。WinCC支持C脚本和VBScript。这里我主要介绍更接近底层、性能更好的C脚本方式。我们将在WinCC中创建两个全局脚本函数分别对应“启用热键”和“禁用热键”。4.1 声明外部DLL函数首先我们需要在C脚本中声明DLL中的导出函数这样编译器才知道它们的存在。这通常在全局脚本的“头”部分或者直接在函数内部用#pragma指令完成。打开WinCC的全局脚本编辑器Global Script创建一个新的C脚本动作我们命名为DisableWindowsHotkeys。#include “apdefap.h” // 声明从外部DLL导入的函数 // 函数名和调用约定必须与DLL中的导出函数完全一致 BOOL __stdcall InstallHook(); BOOL __stdcall UninstallHook(); // 可选声明Windows API函数用于加载DLL如果使用动态加载方式 // HMODULE __stdcall LoadLibraryA(LPCSTR lpLibFileName); // BOOL __stdcall FreeLibrary(HMODULE hLibModule); // FARPROC __stdcall GetProcAddress(HMODULE hModule, LPCSTR lpProcName); void OnStartDisableHotkeys(char* lpszPictureName, char* lpszObjectName, char* lpszPropertyName) { // 方法一静态链接需在WinCC计算机上正确注册DLL或指定路径WinCC C脚本环境支持有限更推荐动态加载 // 直接调用声明好的函数 // if (InstallHook()) { // printf(“系统热键已禁用。\n”); // } else { // printf(“禁用热键失败\n”); // } // 方法二动态加载DLL更灵活、更可靠 HMODULE hDll NULL; FARPROC pFuncInstall NULL; FARPROC pFuncUninstall NULL; BOOL bRet FALSE; // 指定DLL的完整路径。请根据你的实际存放位置修改。 // 例如放在WinCC项目路径下的Bin文件夹里 char dllPath[MAX_PATH] “C:\\WinCC_Projects\\MyProject\\Bin\\WinCC_BlockHotkey.dll”; // 1. 加载DLL hDll LoadLibrary(dllPath); if (hDll NULL) { printf(“错误无法加载DLL ‘%s’。错误代码%d\n”, dllPath, GetLastError()); return; } // 2. 获取函数地址 pFuncInstall GetProcAddress(hDll, “InstallHook”); pFuncUninstall GetProcAddress(hDll, “UninstallHook”); if (pFuncInstall NULL || pFuncUninstall NULL) { printf(“错误在DLL中找不到所需的函数。\n”); FreeLibrary(hDll); return; } // 3. 调用安装钩子函数 // 将函数指针转换为正确的类型 BOOL (__stdcall *lpfnInstallHook)() (BOOL (__stdcall *)())pFuncInstall; bRet lpfnInstallHook(); if (bRet) { printf(“INFO: Windows系统热键Win键等已成功禁用。\n”); // 可以将这个状态写入一个WinCC内部变量供其他脚本或画面显示 SetTagBit(“Hotkey_Disabled”, TRUE); } else { printf(“ERROR: 禁用热键失败钩子可能已安装或遇到系统错误。\n”); SetTagBit(“Hotkey_Disabled”, FALSE); } // 注意这里我们加载了DLL但没有立即释放它。 // 因为钩子函数在DLL中如果释放了DLL钩子可能会失效甚至导致崩溃。 // 通常我们在程序退出时或在启用热键的函数里再释放DLL。 // 我们可以将hDll句柄保存到一个全局变量或Tag中但WinCC C脚本管理全局变量较麻烦。 // 一种简化方案在DLL内部管理钩子生命周期WinCC只调用入口函数不关心DLL加载/卸载。 // 更常见的做法是将LoadLibrary的调用放在项目启动脚本中FreeLibrary放在项目退出脚本中。 // 本例中为了演示单个功能的完整性我们先不释放假设DLL会一直驻留。 }同样地我们创建另一个函数EnableWindowsHotkeys。void OnStartEnableHotkeys(char* lpszPictureName, char* lpszObjectName, char* lpszPropertyName) { HMODULE hDll NULL; FARPROC pFuncUninstall NULL; BOOL bRet FALSE; char dllPath[MAX_PATH] “C:\\WinCC_Projects\\MyProject\\Bin\\WinCC_BlockHotkey.dll”; hDll LoadLibrary(dllPath); if (hDll NULL) { // 如果DLL还没加载过尝试加载虽然理论上禁用函数应该先加载了 printf(“警告尝试启用热键时DLL未加载正在加载…\n”); // 这里加载只是为了获取函数地址钩子可能并未安装但调用UninstallHook是安全的。 } else { // 如果已经加载过GetModuleHandle可能更合适但LoadLibrary会增加引用计数也没问题。 } // 实际上为了确保能找到函数我们这里重新LoadLibrary或使用GetModuleHandle // 更健壮的做法是在项目初始化时加载一次DLL并将句柄保存在一个全局可访问的地方。 // 这里我们采用一个简单策略每次都尝试加载如果失败则说明DLL不存在或路径错误。 if (hDll NULL) { hDll LoadLibrary(dllPath); } if (hDll NULL) { printf(“错误无法加载DLL ‘%s’。无法恢复热键。\n”, dllPath); return; } pFuncUninstall GetProcAddress(hDll, “UninstallHook”); if (pFuncUninstall NULL) { printf(“错误在DLL中找不到UninstallHook函数。\n”); FreeLibrary(hDll); return; } BOOL (__stdcall *lpfnUninstallHook)() (BOOL (__stdcall *)())pFuncUninstall; bRet lpfnUninstallHook(); if (bRet) { printf(“INFO: Windows系统热键已成功恢复。\n”); SetTagBit(“Hotkey_Disabled”, FALSE); } else { printf(“WARNING: 恢复热键失败可能钩子本就未安装。\n”); } // 卸载钩子后可以安全地释放DLL FreeLibrary(hDll); // 注意如果之前禁用热键时也加载了DLL且未释放这里的FreeLibrary只是减少引用计数。 // 当引用计数为0时DLL才会真正从内存卸载。 }4.2 在WinCC画面中调用脚本创建好全局脚本函数后我们就可以在画面中使用了。最常见的方式是创建两个按钮“禁用热键”和“启用热键”。在WinCC图形编辑器中拖放两个按钮到画面上。右键单击“禁用热键”按钮选择“属性” - “事件” - “鼠标” - “按左键”。点击“…”打开动作编辑器选择“C动作”。在函数选择框中找到并选择我们刚才创建的OnStartDisableHotkeys函数你可能需要先在全局脚本中编译它才能在选择框中看到。同样地为“启用热键”按钮的鼠标左键事件关联OnStartEnableHotkeys函数。这样操作员在运行系统中点击相应按钮即可触发热键的禁用与启用。你也可以将这两个函数的调用关联到项目事件上比如在“项目启动时”自动禁用热键在“项目关闭时”自动启用热键实现全自动管理。5. 避坑指南与进阶优化在实际部署和测试过程中我遇到了不少坑。这里把关键的经验和优化点分享出来希望能帮你节省大量时间。5.1 权限与UAC问题Windows的低级键盘钩子WH_KEYBOARD_LL对权限要求相对较低在Windows 7及更高版本中即使非管理员用户通常也能安装。但是如果你的WinCC运行在受限制的用户账户下或者系统启用了非常严格的安全策略在一些工业环境中常见可能会失败。SetWindowsHookEx会返回NULL通过GetLastError()可以获取错误代码。解决方案确保运行WinCC Runtime的用户账户具有正常的用户权限。如果是在Windows 10/11上检查是否被安全软件如Defender、第三方杀毒软件拦截。可以将你的DLL文件路径添加到杀毒软件的信任区。最根本的与系统管理员沟通说明该功能对生产环境稳定性的必要性争取将运行WinCC的账户权限适当放宽。5.2 钩子失效与资源管理这是最容易出问题的地方。我们的方案中DLL由WinCC脚本动态加载。这里有一个生命周期管理的问题场景A在DisableHotkeys函数中LoadLibrary在EnableHotkeys函数中FreeLibrary。如果用户只禁用了热键然后直接关闭了WinCC项目而没有点击“启用”按钮那么DLL就没有被释放钩子可能依然存在导致内存泄漏甚至可能影响后续WinCC的启动。场景B在项目启动脚本中LoadLibrary在项目退出脚本中FreeLibrary并调用UninstallHook。这是更健壮的做法但需要你管理好DLL模块句柄的传递可以通过一个全局的脚本变量或Tag来存储。我的推荐做法在WinCC项目的“全局脚本” - “项目函数”中创建两个函数Project_Initialize和Project_Deinitialize名称自定。在“计算机属性” - “启动”中将Project_Initialize指定为“项目启动时”执行的动作。在“计算机属性” - “参数”中将Project_Deinitialize指定为“项目关闭时”执行的动作。在Project_Initialize中加载DLL并调用InstallHook。将DLL句柄保存到一个静态变量或一个WinCC内部Tag数据类型为“文本变量8位字符集”长度足够存放指针转换后的字符串但这比较hack中。更简单的方法是在DLL内部使用共享数据段这样只要DLL被加载钩子句柄就是全局有效的WinCC脚本无需保存句柄只需调用导出函数即可。在Project_Deinitialize中调用UninstallHook并FreeLibrary。这样确保了钩子的生命周期与WinCC项目进程完全绑定。5.3 拦截更多热键与精细控制我们最初的DLL只拦截了Win键和AltF4。在实际项目中你可能需要屏蔽更多组合键比如CtrlEsc等同于Win键、AltTab、WinL锁屏、WinD显示桌面等。拦截AltTab的挑战在低级键盘钩子中AltTab的拦截比较棘手因为你需要跟踪Alt键的按下和Tab键的按下顺序。一个相对简单但“粗暴”的方法是直接拦截Alt键VK_MENU的按下事件。但这会禁用所有Alt组合键如AltF4我们已经单独处理了但Alt字母菜单快捷键也会失效这可能不是你想要的效果。更精细的方案可以考虑使用WH_KEYBOARD线程特定钩子而不是全局钩子。将它安装到WinCC运行画面的窗口线程上。这样只有当WinCC画面窗口是活动窗口时钩子才生效。这对于AltTab的拦截会更友好因为你可以结合GetKeyState和消息循环来判断。但它的缺点是如果用户先切换到其他窗口比如通过鼠标点击再按AltTab这个钩子就拦截不到了。对于需要绝对禁止离开WinCC画面的场景全局低级钩子仍是更可靠的选择。扩展DLL功能你可以修改DLL增加一个导出函数例如SetBlockKeys(DWORD dwKeyFlags)通过参数来动态指定要屏蔽哪些热键。这样WinCC脚本可以在不同工艺画面下灵活地启用不同的热键屏蔽策略。5.4 调试与日志输出我们的脚本中使用了printf来输出信息。这些信息会显示在WinCC的“诊断”窗口或系统输出中。在生产环境你可能不希望这些日志干扰操作员。可以将其改为写入一个日志文件或者通过WinCC的报警系统记录一条内部消息。在DLL中也可以使用OutputDebugString函数输出调试信息这些信息可以被DebugView等工具捕获对于在开发机上排查问题非常有用。5.5 64位系统与32位WinCC的兼容性重申一遍WinCC 7.4是32位应用程序。你编译的DLL也必须是32位的。在64位Windows上32位进程WinCC加载32位DLL运行在WOW64子系统下这是完全支持的。不要尝试编译64位DLLWinCC将无法加载它。如果你将来升级到更高版本的WinCC如WinCC Professional V16/V17它们有64位版本那么你需要重新编译64位版本的DLL并更新脚本中的路径。6. 效果验证与最终部署完成所有开发和集成后必须进行严格的测试。单元测试DLL可以先用一个简单的C控制台程序测试DLL的InstallHook和UninstallHook是否工作正常。编译一个测试程序调用这两个函数观察Win键是否被屏蔽和恢复。在WinCC开发环境中测试在图形编辑器中运行画面点击按钮尝试按Win键、AltTab、AltF4等。观察是否被拦截。同时打开记事本等其他程序测试在这些程序中热键是否正常我们的全局钩子理论上也会影响其他程序这是需要注意的副作用。测试项目启动/关闭自动执行配置好项目启动和关闭脚本后重启WinCC Runtime观察热键是否在项目启动时自动禁用在关闭时自动恢复。长时间运行测试让系统连续运行数小时甚至数天检查是否有内存泄漏通过任务管理器观察WinCC进程内存是否稳定、钩子是否意外失效。在目标生产机测试最终一定要在部署的工控机上测试因为那里的软件环境杀毒软件、组策略可能与开发机不同。部署清单[ ]WinCC_BlockHotkey.dll文件已编译为32位版本。[ ] DLL文件已放置在生产机上一个稳定的、有读取权限的路径下如C:\Program Files\Siemens\WinCC\bin或项目目录内。[ ] WinCC脚本中的DLL路径已更新为生产机的绝对路径。[ ] 全局脚本函数已正确创建并编译。[ ] 画面按钮事件或项目事件已正确关联脚本函数。[ ] 已在生产环境完成上述所有测试项目。这个方案虽然需要一些C和Windows编程的基础但它提供了最直接、最可靠的热键控制能力。相比于一些通过修改注册表或组策略的全局方法它更灵活、更可控影响范围仅限于WinCC运行期间不会对操作系统其他部分造成永久性改变。对于追求稳定和专业的工业自动化项目来说这种基于脚本和DLL的精细化控制是值得投入时间实现的解决方案。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门