C++ Builder 和汇编混合编程
猛禽
我在写定时提醒 时碰到一个问题：怎么发声？我开始是用 32 位 Windows 的 API 函数 MessageBeep( -1 ); 那声音又小又难听。原来在 16 位的 Windows API 中有的一套 PlaySound 的函数在 32 位 Windows 中又取消了， DOS 下的 Sound 函数更是早就不能用了。 幸好我对硬件还算了解，知道 PC Speaker 的声音是通过系统中的定时计数芯片 8253/8254 产生的，只要通过硬件端口访问芯片就可以产生想要的声音了。 问题在于 Windows 是工作在保护模式下，大多数硬件端口都要在特权级0(PL0， 这是搞硬件的人的说法，后来我才知道在搞 OS 和 Driver 的人中是叫 Ring 0 的， 这才比较正确，因为如果不是 Intel 的 CPU 可能就不叫 PL 了)中， 即操作系统核心态中，才可以访问（比如硬盘口，访问时是不会出错，但结果不正确）， 这也就意味着要写成驱动程序的形式，天啊！ VxD 和 WDM 我都不会，怎么办？ 事实上没有这么困难，像 PC Speaker 这种无伤大体的端口， Windows 是不保护的， 即在用户态下也可以正常访问。 现在还有一个问题就是用什么语句访问端口？ DOS 中 C 语言里的那几个端口操作函数在 Windows 中都取消了，只好用汇编。我开始是用 ASM 语句插入汇编代码，结果发现 BCB 在编译时碰到 ASM 时会把 BCB 文件编译成一个巨大的 ASM 文件， 再重新启动汇编程序汇编，速度太慢。最后采用了我在 DOS 编程时常用的方法， 做一个单独的 ASM 文件加入工程文件中。 下面是两个用于发声的函数，最前面声明了两个外部 C 调用形式的函数， 是两个用汇编写的字节端口输入/输出函数，注意：在 C++ 中一定要注意外部函数应为 C 调用形式。程序中多处强制类型转换是为了不出现警告，我对程序一向要求 Error/Warning/Hint 全为 0。 extern "C" { Byte InPortB( int aPort ); void OutPortB( int aPort, Byte aValue ); } void __fastcall Sound( int aFreq ) { if ( ( aFreq >= 20 ) && ( aFreq <= 20000 ) ) { aFreq = 1193181 / aFreq; Byte b = InPortB( 0x61 ); if ( ( b & 3 ) == 0 ) { OutPortB( 0x61, Byte( b | 3 ) ); OutPortB( 0x43, 0xb6 ); } OutPortB( 0x42, ( Byte )aFreq ); OutPortB( 0x42, ( Byte )( aFreq >> 8 ) ); } } void __fastcall NoSound( void ) { Byte b = Byte( InPortB( 0x61 ) & 0xfc ); OutPortB( 0x61, b ); } 下面是两个端口 I/O 的函数的汇编源程序，即定时提醒（Alarm）中的 IOPortB.asm 文件的全部内容，是在 BCB 产生的 ASM 文件基础上作了一点点的优化。 注意： 1 、最前面的 .386p 必不可少，指定用 32 位保护模式，至于 modal flat 我也不太明白是 What ，跟 16 位时的 tiny, small... 不同，大概是指用 32 位保护模式的平坦地址间模式吧； 2 、在 32 位保护模式中， CS/IP 为 32 位，参数在栈中的位置与 16 位时不同； 3 、最后的 public 也不可少，前缀的下划线也是必须的，另外记得用大小写敏感方式汇编。 .386p model flat _TEXT segment dword public use32 ''CODE'' _InPortB proc near push ebp mov ebp, esp mov dx, word ptr [ebp + 8] in al, dx pop ebp ret _InPortB endp _OutPortB proc near push ebp mov ebp, esp mov dx, word ptr [ebp + 8] mov al, byte ptr [ebp + 12] out dx, al pop ebp ret _OutPortB endp public _InPortB public _OutPortB _TEXT ends end 注意：此法在 Windows NT 上行不通，因为 Windows NT 保护了所有的端口，必须用 WDM，连 VxD 也不行，它只用于 Windows 95 ，在 Windows 98 中也可以用，但 Windows NT 和 Windows 2000 都不支持。