我在寫定時提醒 時碰到一個問題:怎麼發聲?我開始是用 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 都不支持。