李桂巖 魏 賓 賈瑞鳳
[摘要]介紹C++Builder在Windows 95/98操作系統(tǒng)平臺(tái)IO端口沒(méi)有受到保護(hù)的情況下,實(shí)現(xiàn)IO端口讀寫(xiě)操作的兩種方法,在Windows NT/2000/XP操作系統(tǒng)平臺(tái)IO端口受到保護(hù)的情況下,應(yīng)用WinIO程序庫(kù)實(shí)現(xiàn)IO端口讀寫(xiě)的方法。對(duì)于應(yīng)用C++Builder進(jìn)行硬件開(kāi)發(fā)的編程人員有重要參考價(jià)值。
[關(guān)鍵詞]C++Builder IO端口讀寫(xiě) WinIO程序庫(kù)
中圖分類號(hào):TP3文獻(xiàn)標(biāo)識(shí)碼:A文章編號(hào):1671-7597(2009)0910070-02
C++Builder繼承了C語(yǔ)言簡(jiǎn)潔、快速的優(yōu)點(diǎn),采用面向?qū)ο蟮能浖こ淘O(shè)計(jì)方法和可視化界面設(shè)計(jì)技術(shù),融合了Windows編程、數(shù)據(jù)庫(kù)編程、網(wǎng)絡(luò)編程等技術(shù),使得程序員可以快速高效地開(kāi)發(fā)出高質(zhì)量的Windows應(yīng)用程序。但在C++Builder中,不能夠使用Turbo C中的outputb和inputb端口讀寫(xiě)函數(shù)。給工業(yè)控制方面的開(kāi)發(fā)帶來(lái)不便,特別是不利于IO卡的直接輸入輸出操作。筆者為在C++Builder中實(shí)現(xiàn)這個(gè)功能專門(mén)在Windows的不同版本下進(jìn)行了嘗試取得了成功?,F(xiàn)就具體方法介紹如下供C++Builder編程人員參考。
一、在Windows 95/98操作系統(tǒng)平臺(tái)下實(shí)現(xiàn)端口讀寫(xiě)操作
共有兩種方法,一種為內(nèi)嵌匯編語(yǔ)言,另一種為使用__emit__函數(shù)。
(一)通過(guò)內(nèi)嵌匯編語(yǔ)言實(shí)現(xiàn)端口的讀寫(xiě)
在C++Builder中,匯編語(yǔ)句必須被包含在以關(guān)鍵字asm為起始的一對(duì)大括號(hào)中:
asm {
匯編語(yǔ)句1
……
}
利用內(nèi)嵌匯編語(yǔ)言編制端口輸出函數(shù)如下:
void OutPort(unsigned short port,unsigned char value)
//port參數(shù)為輸出端口地址,value參數(shù)為輸出值
{
asm{
mov dx , port //把端口地址送到處理器DX寄存器中
mov al , value // 把value 送到處理器AL寄存器中
out dx , al // 把AL寄存器中的值送到端口
};
}
該函數(shù)將無(wú)符號(hào)字符型8位的數(shù)據(jù)value寫(xiě)入地址為port的端口上,port的數(shù)據(jù)類型是unsigned short,16位無(wú)符號(hào)短整形。
利用內(nèi)嵌匯編語(yǔ)言編制端口輸入函數(shù)如下:
unsigned char InPort(unsigned short port)
//port參數(shù)為輸入端口地址,返回為輸入值
{
unsigned char value ;
asm{
mov dx, port // 把端口地址送到處理器DX寄存器中
in al, dx // 從DX指定端口中將一數(shù)據(jù)送到AL寄存器中
mov value,al // 把AL寄存器中的值賦給value
};
return value; //返回端口數(shù)據(jù)
}
函數(shù)InPort從地址為port的端口讀入一個(gè)無(wú)符號(hào)8位的字符型數(shù)據(jù),其其參數(shù)只一個(gè),即端口號(hào)。返回的數(shù)據(jù)為unsigned char類型的,為從端口讀取的值。
(二)通過(guò)__emit__函數(shù)實(shí)現(xiàn)端口的讀寫(xiě)
__emit__ 函數(shù)一般極少用到。其用法如下:
void _ _emit_ _(argument, . . .);
該函數(shù)為C++Builder 的一個(gè)內(nèi)部函數(shù),調(diào)用的參數(shù)為機(jī)器語(yǔ)言指令。它在編譯的時(shí)候,將機(jī)器語(yǔ)言指令直接嵌入目標(biāo)碼中,不必借助于匯編語(yǔ)言和匯編編譯程序。
利用__emit__函數(shù)編制端口輸出函數(shù)如下:
void OutPort(unsigned short port,unsigned char value)
//port參數(shù)為輸出端口地址,value參數(shù)為輸出值
{
__emit__(0x8b,0x95,&port); // 把端口地址送到處理器EDX寄存器中
__emit__(0x8a,0x85,&value); // 把value 送到處理器AL寄存器中
__emit__(0x66,0xee); // 把AL寄存器中的值送到端口
}
利用__emit__函數(shù)編制端口輸入函數(shù)如下:
unsigned char InPort(unsigned short port)
//port參數(shù)為輸入端口地址,返回為輸入值
{
unsigned char value;
__emit__(0x8b,0x95,&port) ; // 把端口地址送到處理器DX寄存器中
__emit__(0x66,0xec); // 從DX指定端口中將一數(shù)據(jù)送到AL寄存器中
__emit__(0x88,0x85,&value); // 把AL寄存器中的值賦給value
return value; //返回端口數(shù)據(jù)
}
由這兩種方法所編制的函數(shù)注釋可以看出,它們每一句的功能都是一樣的,只是一個(gè)是嵌入了匯編語(yǔ)言,另一個(gè)是直接使用機(jī)器語(yǔ)言。
二、在Windows NT/2000/XP操作系統(tǒng)平臺(tái)下實(shí)現(xiàn)端口讀寫(xiě)操作
上述介紹的實(shí)現(xiàn)端口讀寫(xiě)操作兩種方法,在Windows 95/98下面工作很正常,但是在Windows NT/2000/XP上就會(huì)出現(xiàn)非法指令調(diào)用的問(wèn)題。這些非法指令來(lái)自于底層對(duì)IO端口的直接地址訪問(wèn)。在Windows 95/98時(shí)代,這些操作都沒(méi)有受到保護(hù)的,而在Windows NT/2000/XP下就會(huì)出現(xiàn)保護(hù)問(wèn)題。為了解決這個(gè)問(wèn)題需要使用第三方提供的WinIO程序庫(kù)。
(一)WinIO程序庫(kù)簡(jiǎn)介
WinIO程序庫(kù)允許在32位的Windows應(yīng)用程序中直接對(duì)I/O端口和物理內(nèi)存進(jìn)行存取操作。通過(guò)使用一種內(nèi)核模式的設(shè)備驅(qū)動(dòng)器和其它幾種底層編程技巧,它繞過(guò)了Windows系統(tǒng)的保護(hù)機(jī)制。
WindowsNT/2000/XP下,WinIO函數(shù)庫(kù)只允許被具有管理者權(quán)限的應(yīng)用程序調(diào)用。如果使用者不是以管理者的身份進(jìn)入的,則WinIO.DLL不能夠被安裝,也不能激活WinIO驅(qū)動(dòng)器。通過(guò)在管理者權(quán)限下安裝驅(qū)動(dòng)器軟件就可以克服這種限制。然而,在這種情況下,ShutdownWinIo函數(shù)不能在應(yīng)用程序結(jié)束之前被調(diào)用,因?yàn)樵摵瘮?shù)將WinIO驅(qū)動(dòng)程序從系統(tǒng)注冊(cè)表中刪除。
該函數(shù)庫(kù)提供8個(gè)函數(shù)功能調(diào)用,其中直接對(duì)I/O端口操作有4個(gè)函數(shù):
bool _stdcall InitializeWinIo();
本函數(shù)初始化WioIO函數(shù)庫(kù)。
必須在調(diào)用所有其它功能函數(shù)之前調(diào)用本函數(shù)。
如果函數(shù)調(diào)用成功,返回值為非零值。
如果調(diào)用失敗,則返回值為0。
void _stdcall ShutdownWinIo();
本函數(shù)在內(nèi)存中清除WinIO庫(kù)
本函數(shù)必須在中止應(yīng)用函數(shù)之前或者不再需要WinIO庫(kù)時(shí)調(diào)用,
bool _stdcall GetPortVal(WORD wPortAddr, PDWORD pdwPortVal, BYTE bSize);
使用此函數(shù)從一個(gè)輸入或輸出端口讀取一個(gè)字節(jié)/字/雙字?jǐn)?shù)據(jù)。
參數(shù):
wPortAddr輸入輸出端口地址
pdwPortVal指向雙字變量的指針,接收從端口得到的數(shù)據(jù)。
bSize需要讀的字節(jié)數(shù),可以是1 (BYTE), 2 (WORD) or 4 (DWORD).
如果調(diào)用成功,則返回非零值。
如果函數(shù)調(diào)用失敗,則函數(shù)返回值為零。
bool _stdcall SetPortVal(WORD wPortAddr, DWORD dwPortVal, BYTE bSize);
使用本函數(shù)將一個(gè)字節(jié)/字/雙字的數(shù)據(jù)寫(xiě)入輸入或輸出接口。
參數(shù):
wPortAddr輸入輸出口地址
dwPortVal要寫(xiě)入口的數(shù)據(jù)
bSize要寫(xiě)的數(shù)據(jù)個(gè)數(shù),可以是 1 (BYTE), 2 (WORD) or 4 (DWORD).
如果調(diào)用成功,則返回非零值。
如果函數(shù)調(diào)用失敗,則函數(shù)返回值為零。
(二)WinIO程序庫(kù)的應(yīng)用
在C++Builder中應(yīng)用WinIO程序庫(kù)需要做如下工作。
1.首先將 winio.dll, winio.vxd 和 winio.sys三個(gè)文件拷貝到用C++Builder開(kāi)發(fā)的工程文件目錄下;
2.在DOS提示符下用 implib 命令創(chuàng)建導(dǎo)入庫(kù)。implib 命令格式如下:
implibwinio.libwinio.dll;
3.將winio.lib 添加到用C++Builder開(kāi)發(fā)的工程中。其操作方法是,在C++BuilderIDE 中選擇 Project→Add to project…命令,在彈出的Add to project對(duì)話框中“文件類型”下拉列表框中選擇Library file (*.lib)項(xiàng),會(huì)出現(xiàn) .lib文件。選擇winio.lib文件并單擊“打開(kāi)”按鈕,添加操作成功;
4.將winio.h中的WINIO_API刪除;
5.在源文件中添加頭文件“#include winio.h”;
6.調(diào)用初始化命令函數(shù) InitializeWinIo();
7.調(diào)用庫(kù)函數(shù)GetPortVal、SetPortVal實(shí)現(xiàn)端口的輸入輸出操作;
8.當(dāng)所有的端口輸入輸出操作全部完成,調(diào)用庫(kù)函數(shù)ShutdownWinIo
在內(nèi)存中清除WinIO庫(kù)。
上述的幾種方法筆者在不同的應(yīng)用環(huán)境下使用都是正常的沒(méi)有發(fā)現(xiàn)異?,F(xiàn)象,其中內(nèi)嵌匯編語(yǔ)言和使用__emit__函數(shù)的方法編制IO端口讀寫(xiě)函數(shù),在Windows 98操作系統(tǒng)和C++Builder5.0編程語(yǔ)言環(huán)境下使用多年,工作一直很穩(wěn)定,沒(méi)有出現(xiàn)任何問(wèn)題。后來(lái)因操作系統(tǒng)升級(jí)為Windows XP發(fā)現(xiàn)原有的函數(shù)不能用了,后在C++Builder6.0編程語(yǔ)言環(huán)境下,改用WinIO程序庫(kù)的輸入或輸出函數(shù)進(jìn)行端口的直接操作工作一切正常。