PAYATHON 2026

IBM PC/XT 上的 Turbo C 鼠标事件回调为何不生效?

数据小许

应先修正回调访问全局变量时的 DS,并将 fDone 声明为 volatile。原代码中的 far 只解决远调用的返回方式,不会自动保证回调使用程序的数据段。现有信息不足以确定真实机器上的唯一故障原因,还需检查鼠标驱动和回调是否实际执行。

为什么回调执行了,fDone 仍可能没有变化?

普通 C 函数访问全局变量时,依赖编译器约定的数据段环境。鼠标驱动调用事件处理函数时,这个前提不一定成立。Microsoft Mouse 文档说明,回调入口的 DS 指向鼠标驱动的数据段,处理函数需要自行设置所需的 DS,并通过远返回指令返回。Microsoft Mouse Programmer’s Reference Guide,第 6-19 页

因此,直接执行:

fDone = TRUE;

可能会写入错误的内存位置,导致主循环读取的 fDone 没有改变,也可能破坏驱动数据。

另一个独立问题是异步更新。应使用:

volatile int fDone = FALSE;

这样可以要求编译器在循环判断时重新读取变量。volatile 无法修复错误的数据段,因此两处都需要处理。

如何修改事件处理函数?

下面的替换示例仅适用于 Turbo C 的 small memory model,假设全局变量位于 DGROUP,并通过 Turbo Assembler 处理内联汇编。其他内存模型需要按实际段布局调整,不能直接套用。

#pragma inline

#include <dos.h>

#define FALSE 0
#define TRUE 1

volatile int fDone = FALSE;

void far mouseHandler(void)
{
    asm {
        push ax
        push ds

        mov ax, DGROUP
        mov ds, ax
        mov word ptr fDone, TRUE

        pop ds
        pop ax
    }
}

代码保存并恢复了汇编中修改的 AX 和 DS。函数保持 far,由编译器生成相应的远返回。不要直接改为 interrupt 函数:该修饰通常生成 IRET,与这里要求的远返回不匹配。

编译后,检查生成的汇编,确认返回指令是 RETF,并确认设置 DS 前没有编译器插入的运行库调用。如果启用了函数入口的栈检查,应关闭该回调的栈检查,或使用独立汇编入口。这个示例尚未在问题中的编译器和真实硬件上验证。

注册代码还需要调整什么?

事件掩码 4 | 16 是正确的:0x0004 表示左键释放,0x0010 表示右键释放,合并后为 0x0014。注册入口使用 ES:DX。FreeDOS mouse 仓库中的 INT 33H 接口说明

原代码只设置了 seg.es。建议先用 segread() 初始化段寄存器结构,再覆盖 ES:

segread(&seg);

r.x.ax = 0x000C;
r.x.cx = 0x0004 | 0x0010;
r.x.dx = FP_OFF(mouseHandler);
seg.es = FP_SEG(mouseHandler);
int86x(0x33, &r, &r, &seg);

while (!fDone) {
    /* Main program work. */
}

退出前仍需通过 AX=000CH、CX=0 取消回调,避免程序内存释放后驱动继续调用该地址。

如何判断是驱动问题,还是回调问题?

按以下顺序排查,可以减少相互干扰:

  1. 先在文本模式测试。暂时去掉 set_mode(0x13)。不能仅根据 IBM PC/XT 机型名称推断它支持 VGA mode 13H,需要确认实际显示适配器。
  2. 确认驱动存在并检查复位结果。老机器应先确认 INT 33H 向量有效;调用 AX=0000H 后检查返回的 AX。兼容接口通常以 FFFFH 表示驱动和鼠标已安装,0000H 表示未安装。原代码忽略了这个结果。FreeDOS mouse 的复位接口说明
  3. 在主循环中轮询 AX=0003H,检查返回的 BX 按钮状态是否随按下、释放而变化。如果轮询也没有变化,应先处理真实机器上的驱动、鼠标连接或硬件配置。
  4. 轮询正常后再定位回调。检查注册的 ES:DX 是否对应函数入口,通过调试器或汇编跟踪确认入口是否执行。如果入口执行了但主循环不退出,重点检查写入地址、DS 和循环是否重新读取 fDone。
  5. 检查实际循环内容。精简示例省略了循环中的操作。如果实际代码关闭中断后没有恢复,也可能阻止鼠标事件到达。

回调中只设置标志即可,把输出、文件访问和退出处理放回主循环,避免在异步回调中调用不可重入的 DOS 或运行库功能。

DOSBox-X 中运行成功,只能说明该环境下没有暴露问题。驱动实现、回调时的寄存器状态和硬件配置都可能不同;尚无证据能把差异归结为某一个 DOSBox-X 行为。

备注:内容仅供参考。