C++ Builder 和汇编混合编程

我在写定时提醒 时碰到一个问题:怎么发声?我开始是用 32 位 Windows 的 API 函数 MessageBeep( -1 ); 那声音又小又难听。原来在 16 位的 Windows API 中有的一套 PlaySound 的函数在 32 位 Windows 中又取消了, DOS 下的 Sound 函数更是早就不能用了。

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