我在寫定時提醒 時碰到一個問題:怎么發聲?我開始是用 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 ); }