You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

IBM PC/XT 上的自定义鼠标事件处理程序

IBM PC/XT 上的自定义鼠标事件处理程序

看起来你踩了老DOS实模式编程里的经典兼容性陷阱——DOSBox为了方便开发做了很多“宽容”处理,但真实的PC/XT硬件和老鼠标驱动可没这么友好!让我们一步步找出你的代码在真实XT上失效的原因,以及怎么修复。

1. 最关键的错误:事件处理程序不是中断服务程序(ISR)

你的mouseHandler是普通的far C函数,但INT33H调用事件处理程序时,是以中断方式触发的,必须遵循实模式中断服务程序的严格约定:

  • 自动保存所有通用寄存器(AX、BX、CX、DX、SI、DI、BP、DS、ES)
  • IRET指令返回(而非普通函数的RET

Turbo C的普通far函数不会生成这些代码,这会导致鼠标驱动调用你的handler后栈被破坏,后续的鼠标事件无法正常触发,甚至系统不稳定。

修复方法:给mouseHandler加上interrupt关键字,让Turbo C生成符合ISR要求的代码:

void interrupt far mouseHandler() {
    fDone = TRUE;
    // 注意:Turbo C的interrupt函数会自动完成寄存器保存/恢复和IRET返回
}

2. 鼠标事件掩码设置错误(和你的注释完全不符)

你的代码里写了cx = 4 | 16,但注释标注的是“right or left mouse-up”——这完全不匹配!查INT33H 000CH的官方文档,事件掩码的位定义是:

  • 位1(值为2):左键释放
  • 位3(值为8):右键释放
  • 位2(值为4):右键按下
  • 位4(值为16):中键释放

你当前的掩码对应右键按下+中键释放,和你想要的“左右键释放”没有任何关系!这就是为什么你按左右键时handler根本不会被触发。

修复方法:把cx改成正确的释放事件掩码:

r.x.cx = 2 | 8; // 左键释放 | 右键释放

3. 实模式下的变量优化问题

Turbo C的优化器可能会把while (!fDone)优化成死循环——因为它不知道这个变量会被中断处理程序异步修改。

修复方法:给fDone加上volatile关键字,告诉编译器这个变量可能被外部(比如中断)修改,禁止优化:

volatile int fDone = FALSE;

4. 真实XT硬件的额外注意事项

(1)确认鼠标驱动初始化成功

有些老XT可能没有安装鼠标驱动,或者驱动版本过旧。你可以在初始化后检查返回值,确认鼠标是否被正确识别:

r.x.ax = 0;
int86(0x33, &r, &r);
if (r.x.ax == 0) {
    // 无鼠标驱动,退出程序
    set_mode(0x03);
    // 可以在屏幕上打印错误信息(模式3下支持文本输出)
    return;
}

(2)XT显卡的模式兼容性

XT的CGA显卡模式13h是320x200的4色模式,和DOSBox模拟的VGA 256色模式13h有差异,但这对鼠标事件触发没有影响,只是如果要调试的话,写显存的方式要对应CGA的格式(不过你的代码里不需要修改这部分)。

修复后的完整核心代码

#pragma inline

#include <dos.h>
#include <stdio.h>

#define FALSE 0
#define TRUE 1

volatile int fDone = FALSE;

void interrupt far mouseHandler() {
    fDone = TRUE;
    // 可选:在屏幕上画一个像素点,直观确认handler被触发
    char far *vmem = (char far *)0xA0000000L;
    vmem[100*320 + 100] = 0x0F; // 模式13h下在(100,100)画白色像素
}

void set_mode(unsigned char mode) {
    asm {
        mov ah, 0
        mov al, mode
        int 0x10
    }
}

void main() {
    union REGS r;
    struct SREGS seg;

    set_mode(0x13);

    // 初始化鼠标
    r.x.ax = 0;
    int86(0x33, &r, &r);
    if (r.x.ax == 0) {
        set_mode(0x03);
        printf("Mouse driver not found!\n");
        return;
    }
    // 显示鼠标光标
    r.x.ax = 1;
    int86(0x33, &r, &r);

    // 设置鼠标事件处理程序
    r.x.ax = 0x0C;
    r.x.cx = 2 | 8; // 左键释放 + 右键释放
    r.x.dx = FP_OFF(mouseHandler);
    seg.es = FP_SEG(mouseHandler);
    int86x(0x33, &r, &r, &seg);

    while (!fDone) {
        // 空循环等待事件
    }

    // 清理鼠标设置
    r.x.ax = 0x0C;
    r.x.cx = 0; // 关闭事件处理程序
    int86(0x33, &r, &r);
    r.x.ax = 2; // 隐藏鼠标光标
    int86(0x33, &r, &r);

    set_mode(0x03);
    printf("Mouse event triggered!\n");
}

最后总结

最可能导致你代码失效的就是前两个问题:缺失interrupt关键字和事件掩码设置错误。修复这两点后,你的代码应该能在真实XT硬件上正常工作了。

老DOS实模式编程就是这样,细节决定成败——DOSBox的“宽容”反而容易让我们忽略这些严格的硬件规则😅。如果还是有问题,可以尝试在handler里加显存输出,直观确认handler是否被触发,再一步步排查。

火山引擎 最新活动