← 返回首页

CATIA V5 CAA 键盘捕获

一、目标

在 CATIA V5 的 CATStateCommand 中捕获用户的键盘输入(包括普通字母键、方向键、功能键以及 Ctrl/Shift/Alt 组合键)。


二、结论先行

方案 是否可行 备注
CAA 公开 API(CATCommand 通知、CATDialogAgent、AddCallback 等) 不可行 普通按键事件不会通过 CAA 公开的事件/通知体系传播
Windows SetWindowsHookEx(WH_KEYBOARD) 线程钩子 可行 ✓ 在 Windows 消息循环层面拦截,线程级、无需 DLL 注入

最终采用方案:Windows WH_KEYBOARD 线程级键盘钩子。


三、失败方案详细记录

方案 1:重写 AnalyseNotification

CATBoolean TestCmd::AnalyseNotification(CATCommand* cmd, CATNotification* notif)
  • 预期:作为 CATCommand 的虚函数,收到传播到本命令的所有通知
  • 实际:从未被调用。在 CATStateCommand 中,Dialog Engine 接管了通知分发,不会调用用户重写的 AnalyseNotification

方案 2:AddAnalyseNotificationCB + CATKeyboardEvent

AddAnalyseNotificationCB(pViewer, CATKeyboardEvent::ClassName(),
						(CATCommandMethod)&TestCmd::OnKeyEvent, NULL);
  • 预期:订阅 CATViewer 上的键盘事件通知
  • 实际:回调注册成功(CB > 0),但从未触发
  • 原因:CATViewer 不在 TestCmd 的父命令树分支上。AddAnalyseNotificationCB 只能接收沿 CATCommand 树向上冒泡的通知,而键盘事件根本不通过 CATCommand 通知机制传播

方案 3:CATDialogAgent + AcceptOnNotify

CATDialogAgent* pAgent = newCATDialogAgent("KeyAgent");
pAgent->AcceptOnNotify(pViewer, "CATKeyboardEvent");
  • 预期:Dialog Engine 将键盘事件路由到 Agent
  • 实际:Agent 从未触发
  • 原因:Dialog Engine 不处理 CATKeyboardEvent。Agent 的 AcceptOnNotify 机制只对 Dialog 控件(CATDlgXxx)产生的通知有效

方案 4:AddCallback(全局函数)+ CATViewer

::AddCallback(this, pViewer, CATKeyboardEvent::ClassName(),
			(CATSubscriberMethod)&TestCmd::OnKeyCallback, NULL);
  • 预期:通过 CATEventSubscriber 的回调机制直接订阅 CATViewer 的键盘事件
  • 实际:AB=7510(注册成功),但回调从未触发
  • 原因:CATViewer 虽然是 CATEventSubscriber 的子类,但它并不是键盘事件的发布者**。键盘事件由底层的 CATVizViewer 通过 DispatchCallbacks 发出,走的是 CATVizViewer → CATViewer::InterpretKeybdEvent 的内部管线**

方案 5:直接使用 CATVizViewer

CATVizViewer* pVizViewer = pViewer->GetVizViewer();
pVizViewer->GetKeyboardDevice();
  • 预期:从 CATVizViewer 获取 CATKeyboardDevice 并注册回调
  • 实际:编译阶段直接报错 CAAV5-ERROR: unauthorized API for CAA V5: CATVizViewer
  • 原因:CATVizViewer 被 CATIA CAA 许可检查标记为非授权 API**,禁止在 CAA V5 合规代码中使用**

方案 6:AddCallback + CATViewer + NULL 事件名

::AddCallback(this, pViewer, NULL,
			(CATSubscriberMethod)&TestCmd::OnKeyCallback, NULL);
  • 预期:从 CATViewer 订阅所有事件(包括键盘)
  • 实际:CB = -1(注册失败)
  • 原因:AddCallback 不接受 NULL 事件名

方案 7:AddAnalyseNotificationCB(NULL, NULL) — 通配订阅

AddAnalyseNotificationCB(NULL, NULL,
						(CATCommandMethod)&TestCmd::OnAnyNotif, NULL);
  • 预期:捕获所有源、所有类型的 CATCommand 通知

  • **实际部分成功:只有 Ctrl 和 Shift 键触发了回调

    [S1-AnalyseNotif] sender=CATNavigDefault notif=CATNotification
    

    **

  • 关键发现:

  • 只有修饰键(Ctrl/Shift)由 CATNavigDefault(导航命令)消费并产生通知

  • 普通字母键(A、B、C…)、方向键、Enter 等完全没有任何 CATCommand 通知产生

方案 8:CATDlgEditor 输入控件

  • 分析:CATDlgEditor 有 GetEditModifyNotificationSetInputCallback 等 API
  • 结论:需要 UI 控件(文本编辑框)才能接收输入,不适用于 3D 查看器场景

四、CATIA V5 键盘事件架构分析

通过以上 8 种方案的测试,得出以下结论:

物理键盘
   ↓
Windows 消息 (WM_KEYDOWN / WM_KEYUP)
   ↓
CATIA 主消息循环
   ↓
CATKeyboardDevice (内部类,无公开头文件)
   ↓
CATVizViewer::DispatchCallbacks (非授权 API)
   ↓
CATViewer::InterpretKeybdEvent (内部方法)
   ↓
┌──────────────────────────────────────────┐
│ 此处键盘事件被 CATIA 内部消费            │
│ ・修饰键 (Ctrl/Shift) → CATNavigDefault │
│ ・普通字母键 → 不产生任何 CATCommand 通知 │
│ ・快捷键 → 直接由 CommandHeader 消费      │
└──────────────────────────────────────────┘

核心发现:CATIA V5 的键盘事件从 CATKeyboardDevice 开始,经 CATVizViewer 分发到 CATViewer,然后被内部消费。这个过程不经过 CATCommand 通知树,也不触发 CATEventSubscriber 回调(即使 CATViewer 是 CATEventSubscriber 的子类)。

普通字母键的事件在 CATIA 内部处理链中静默终结,不会产生任何 CAA 层面可以订阅的通知或事件。


五、最终方案:Windows WH_KEYBOARD 线程钩子

原理

使用 SetWindowsHookEx(WH_KEYBOARD, ...) 在 Windows 消息循环层面拦截键盘消息,在 CATIA 处理之前就能看到所有按键。

关键特性

特性 说明
钩子类型 WH_KEYBOARD

(线程级)
作用范围 仅当前线程(CATIA UI 线程)
DLL 注入 不需要
对 CATIA 的影响 通过 CallNextHookEx 传递,不影响 CATIA 正常键盘处理
捕获范围 所有按键,包括字母、数字、方向键、功能键、组合键

核心代码

头文件(TestCmd.h):

#include"CATStateCommand.h"
#include<windows.h>

classTestCmd : public CATStateCommand
{
DeclareResource(TestCmd, CATStateCommand)
public:
TestCmd();
virtual ~TestCmd();
virtualvoidBuildGraph();

private:
static LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
static TestCmd* _pActiveInstance;
    HHOOK _hKeyboardHook;
};

实现(TestCmd.cpp 关键部分):

// 安装钩子(在 BuildGraph 中)
_hKeyboardHook = SetWindowsHookEx(
    WH_KEYBOARD,
    KeyboardProc,
NULL,                  // hInstance = NULL 表示线程钩子
GetCurrentThreadId()); // 当前线程

// 钩子回调
LRESULT CALLBACK TestCmd::KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode == HC_ACTION && _pActiveInstance)
    {
        BOOL isKeyDown = !(lParam & (1 << 31));  // bit31=0 表示按下
        BOOL isRepeat  = (lParam & (1 << 30));     // bit30=1 表示重复

if (isKeyDown && !isRepeat)
        {
// wParam = 虚拟键码 (VK_xxx)
            BOOL ctrl  = (GetKeyState(VK_CONTROL) & 0x8000) != 0;
            BOOL shift = (GetKeyState(VK_SHIFT)   & 0x8000) != 0;
            BOOL alt   = (GetKeyState(VK_MENU)    & 0x8000) != 0;

// 在这里处理按键逻辑
        }
    }
returnCallNextHookEx(NULL, nCode, wParam, lParam); // 必须传递
}

// 卸载钩子(在析构函数中)
if (_hKeyboardHook)
{
UnhookWindowsHookEx(_hKeyboardHook);
    _hKeyboardHook = NULL;
}

注意事项

  1. 必须调用 CallNextHookEx:否则会阻断 CATIA 的键盘处理
  2. 静态回调函数:SetWindowsHookEx 要求 C 风格回调,通过静态成员 _pActiveInstance 访问实例
  3. 生命周期管理:析构时必须 UnhookWindowsHookEx,否则钩子残留会导致异常
  4. 线程安全:线程钩子只在安装线程(UI 线程)上触发,无跨线程问题
  5. 系统库链接:user32.lib(包含 SetWindowsHookEx)在 mkmk 环境下自动链接,无需手动配置

六、关键经验教训

6.1 CATIA V5 CAA 事件体系的局限性

  • CATCommand 通知树:只传播 SendNotification 显式发出的通知,键盘事件不在其中
  • CATEventSubscriber 回调:需要明确的发布者-事件对应关系;CATViewer 虽是 EventSubscriber 子类,但不发布键盘事件
  • CATDialogAgent:只处理 Dialog 控件产生的通知,不处理底层设备事件
  • CATVizViewer / CATKeyboardDevice:是键盘事件的实际载体,但被标记为非授权 API****

6.2 调试方法论

  • 三策略并行诊断法:同时注册多种机制(通配通知、Viewer 回调、键盘事件回调),对比哪个触发,快速定位问题
  • 二分法排查:先确认事件是否存在(修饰键 vs 普通键),再缩小问题范围
  • 打印回调注册返回值:CB > 0 表示注册成功但不代表会触发;CB = -1 表示注册失败

6.3 CAA V5 授权限制

  • mkmk 编译器的 CAAV5-ERROR: unauthorized API 检查位于预处理阶段

  • 不能通过强制包含头文件或强制转换绕过

  • 遇到此错误意味着该 API 在 CAA 合规级别下不可用,需要寻找替代方案

6.4 Windows Hook 作为备选

  • 当 CATIA 框架层面没有公开 API 时,回退到 Windows 平台 API 是可行的

  • 线程级钩子(GetCurrentThreadId())比全局钩子安全,不影响其他进程

  • WH_KEYBOARD

    在消息分发到窗口过程之前触发,可以先于 CATIA 看到按键


七、扩展参考

可能的替代/增强方案

方案 说明 适用场景
WH_KEYBOARD_LL 低级键盘钩子,捕获更底层(需单独线程) 需要拦截系统键(如 Win 键)
WH_GETMESSAGE 消息钩子,可看到完整 MSG 结构 需要区分消息目标窗口
Raw Input API RegisterRawInputDevices

 + WM_INPUT
需要区分多个键盘设备
Subclass CATIA 窗口 SetWindowLongPtr(GWLP_WNDPROC)

 + WM_KEYDOWN
只关注特定窗口的按键

CATKeyboardEvent 内部键值定义(仅供参考)

LeftKey=1, RightKey=2, UpKey=3, DownKey=4
EscapeKey=11, ControlKey=13, EnterKey=14, AltKey=15

这些值在 CATKeyboardEvent.h 中定义,但由于键盘事件不通过 CAA API 传播,实际无法使用。

评论