摘 要:本文提出了用__emit()__函数或动态链接库实现C++Builder下硬件端口读写操作的原理、实现函数和实例,并对两种方法的工作量、执行效率和影响的等方面进行了比较。对于利用微机在数据检测、实时通信、系统仿真等应用中都具有普遍的指导和参考作用。
关键字:硬件端口、动态链接库、__emit()__函数
一、背景
C++Builder是传统C++开发工具的里程碑,是第三代C++应用程序开发环境。它既从Borland C++继承C语言的语法,又从Delphi继承可视化构件库,因而C++Builder是快速应用程序开发模式和可重用构件的一个完美的结合,代表着C++语言的未来发展方向。
但是,细心的编程人员会发现,C++Builder不再支持诸如inportb()、inportw()、outportb()和outportw()一类的硬件端口读写指令了。事实上,在Windows环境下,BorlandC++仅支持16位应用程序的端口读写操作,对32位应用程序的端口读写操作不再支持。由于C++Builder开发出来的应用程序是32位的,所以无法使用诸如inportb()、inportw()、outportb()和outportw()一类的硬件端口读写指令。显然,这是C++Builder在设计上不完善不成功的地方,因为在PC机中,I/O地址空间与内存地址空间是相互独立的,不存在相互挤占的问题,例如,同样是支持32位应用程序开发的Delphi,就通过Port数组实现了对硬件端口的访问。
针对上述问题,本文提出利用__emit__函数或动态链接库实现C++Builder下硬件端口读写操作的两种方法。
二、硬件端口读写操作的实现
2.1 利用__emit__函数实现硬件端口读写操作
2.1.1 __emit__函数简介
__emit__函数是C++Builder的一个内部函数,其原型在<dos.h>头文件中说明,C++Builder的编译器能够自动识别__emit__函数,因此不必加入头文件。通过使用C++Builder的Help菜单,可以得到该函数的联机帮助信息。
__emit__函数的用法为: void __emit__(argument,…);
__emit__函数调用的参数为机器语言指令。编译时,它将机器语言指令直接嵌入目标码中,不必借助于汇编语言和汇编指令程序。该函数无返回值。
2.1.2 硬件端口读写操作的实现
利用__emit__函数分别定义inportb()、inportw()、outportb()和outportw()函数,是它们具有C++中inportb()、inportw()、outportb()和outportw()函数的功能。
void __fastcall outportb(unsigned short int port,unsigned char value)
{//port参数为输出端口地址,value参数为输出值
__emit__(0x8b,0x95,&port);//把端口地址送到处理器32位EDX寄存器中
__emit__(0x8a,0x85,&value);//把value送到处理器8位AL寄存器中
__emit__(0x66,0xee);//把AL寄存器中的值送到端口}
unsigned char inportb(unsigned short int port
{//port参数值为输入端口值
unsigned char value;//指定变量value为无符号字符型
__emit__(0x8b,0x95,&port);//把端口地址送到处理器32位EDX寄存器中
__emit__(0x66,0xec);//从指定端口中将一数据字节送到8位AL寄存器中
__emit__(0x88,0x85,&value);//把AL寄存器中的值赋给value
return value;//返回函数值}
void __fastcall outportw(unsigned short int port,unsigned short int value)
{//port参数为输出端口地址,value参数为输出值
__emit__(0x8b,0x95,&port);//把端口地址送到处理器32位EDX寄存器中
__emit__(0x66,0x8b,0x85,&value);//把value送到处理器16位AX寄存器中
__emit__(0xef);//把Ax寄存器中的值送到端口}
unsigned short int inportw(unsigned short int port)
{//port参数值为输入端口值
unsigned short int value;//指定变量value为无符号短整型
__emit__(0x8b,0x95,&port);//把端口地址送到处理器32位EDX寄存器中
__emit__(0xed);//从指定端口中将一数据字送到16位AX寄存器中
__emit__(0x66,0x89,0x85,&value);//把AX寄存器中的值赋给value
return value;//返回函数值}
2.2 利用动态链接库实现硬件端口读写操作
当前市场上名气最大的C++开发工具非Microsoft公司的Visual C++莫属,Visual C++为我们提供了硬件端口读写的函数,那么,能否在C++Builder中调用Visual C++的硬件端口读写函数呢?我们在这方面进行了尝试并获得了成功。
直接将基于MFC的代码链接进VCL应用程序中是不可能的,因为VCL使用的是自己的对象格式,具有自己的启动代码和巨大的组件库,而这些显然都将与MFC发生冲突。但是,C++Builder 具有强大的创建和使用动态链接库(Dynamic Link Library)的能力,而这个DLL可以是其它任何Windows程序编写的,也可以被其它任何Windows程序所使用。利用动态链接库实现硬件端口读写操作的步骤为:①为Visual C++的DLL创建一个工作文件.def;②为Visual C++的DLL创建一个实际的导入库;③为C++ Builder的DLL创建一个头文件.h;④修改源文件;⑤将工作文件加入工程。
2.2.1 为Visual C++的DLL创建一个工作文件.def
在Ms DLL上运行impdef文件以生成.def文件,然后利用这个.def文件为我们所修改的硬件端口读写函数另起别名:
//File:msdll.cpp
extern "C" unsigned short __declspec(dllexport)
__stdcall _read_u_short_port
extern "C" unsigned short __declspec(dllexport)
__stdcall _write_u_short_port
extern "C" int __declspec(dllexport)
__stdcall _read_int_port
extern "C" int __declspec(dllexport)
__stdcall _write_int_port
extern "C" unsigned long __declspec(dllexport)
__stdcall _read_u_long_port
extern "C" unsigned long __declspec(dllexport)
__stdcall _write_u_long_port {
return 0; }
当上述代码编译到DLL中时,.def(用到impdef msdll.def msdll.dll)文件输出是:
LIBRARY MSDLL.DLL
EXPORTS
rw_port._read_int_port@4 = READINTPORT @1
rw_port._read_u_long_port@4 = READULONGPR @1
rw_port._read_u_shortport@4 = READUSHORTPORT @1
rw_port._write_int_port@8 = WRITEINTPORT @3
rw_port._write_u_long_port@8 = WRITEULONGPORT @3
rw_port._write_u_short_port@8 = WRITEUSHORTPORT @3
等号左边是端口读写函数在Microsoft中的函数名,等号右边是C++Builder中的函数名(下划线必须去掉)。最右边是赋给这个函数的序号。
在C++Builder中可以通过一个文本编辑起来产生.def文件。建立.def文件时应注意:①将需要的函数名放在等号的左边;②把模块前的Ms函数名(去掉前面没用的下划线)放在等号的右边;③将EXPORTS(导出)变为IMPORTS(导入);④完全去掉LIBRARY行。
按顺序做完上面的工作后,其输出结果为:
IMPORTS
READINTPORT = rw_port._read_int_port@4
READULONGPORT = rw_port._read_u_long_port@4
READUSHORTPORT = rw_port._read_u_short_port@4
WRITEINTPORT = rw_port._write_int_port@8
WRITEULONGPORT = rw_port._write_u_long_port@8
WRITEUSHORTPORT = rw_port._write_u_short_port@8
2.2.2 为Visual C++的DLL创建一个实际的导入库
运用上面同样的方法可以生成一个.LIB文件。只要去掉序号并调换以下函数名以使C++Builder中要用的函数名在等号的左边,而Ms函数名在等号的左边:
LIBRARY MSDLL.DLL
EXPORTS
READINTPORT = rw_port._read_int_port@4
READULONGPORT = rw_port._read_u_long_port@4
READUSHORTPORT = rw_port._read_u_short_port@4
WRITEINTPORT = rw_port._write_int_port@8
WRITEULONGPORT = rw_port._write_u_long_port@8
WRITEUSHORTPORT = rw_port._write_u_short_port@8
然后,在.def文件中运行implib以建立LIB文件。
2.2.3 为C++ Builder的DLL创建一个头文件.h
extern "C" unsigned short __declspec(dllimport) __stdcall
read_u_short_port(unsigned short port);
extern "C" unsigned short __declspec(dllimport) __stdcall
write_u_short_port(unsigned short port,unsigned short dataword);
extern "C" int __declspec(dllimport) __stdcall
read_int_port(unsigned short port);
extern "C" int __declspec(dllimport) __stdcall
write_int_port(unsigned short port,int dataword);
extern "C" unsigned long __declspec(dllimport) __stdcall
read_u_long_port(unsigned short port);
extern "C" unsigned long __declspec(dllimport) __stdcall
write_u_long_port(unsigned short port,unsigned long dataword);
2.2.4 修改源文件
这里,我们对源文件进行如下修改:
//ceship.cpp
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "ceship.h"
//--------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
extern "C" unsigned short __declspec(dllimport) __pascal
READUSHORTPORT(unsigned short port);
extern "C" unsigned short __declspec(dllimport) __pascal
WRITEUSHORTPORT(unsigned short port,unsigned short dataword);
extern "C" int __declspec(dllimport) __pascal
READINTPORT(unsigned short port);
extern "C" int __declspec(dllimport) __pascal
WRITEINTPORT(unsigned short port,int dataword);
extern "C" unsigned long __declspec(dllimport) __pascal
READULONGPORT(unsigned short port);
extern "C" unsigned long __declspec(dllimport) __pascal
WRITEULONGPORT(unsigned short port,unsigned long dataword);
2.2.5 将工作文件.def加入工程
在将工作文件.def加入工程后,即可运行工程文件。在进行C++Builder的程序开发时,可直接调用READUSHORTPORT(unsigned short port)、WRITEUSHORTPORT(unsigned short port,unsigned short dataword)、READINTPORT(unsigned short port)、WRITEINTPORT(unsigned short port,int dataword)、READULONGPORT(unsigned short port)和WRITEULONGPORT(unsigned short port,unsigned ln dataword)等I/O端口读写函数。
三、应用举例
这里分别介绍上述两种方法的应用举例。
3.1 _emit__函数实现硬件端口读写应用举例
启动Windows98下的C++Builder4.0,激活菜单File/New Application,创建一个工程。在单元文件的.cpp文件插入如下代码:
//声明inportb和outportb函数
void __fastcall outportb(unsigned short int port,unsigned char value)
//port参数为输出端口地址,value参数为输出值
{ __emit__(0x8b,0x95,&port);//把端口地址送到处理器32位EDX寄存器中
__emit__(0x8a,0x85,&value);//把value送到处理器8位AL寄存器中
__emit__(0x66,0xee);//把AL寄存器中的值送到端口 }
unsigned char inportb(unsigned short int port)//port参数值为输入端口值
{ unsigned char value;//指定变量value为无符号字符型
__emit__(0x8b,0x95,&port);//把端口地址送到处理器32位EDX寄存器中
__emit__(0x66,0xec);//从指定端口中将一数据字节送到8位AL寄存器中
__emit__(0x88,0x85,&value);//把AL寄存器中的值赋给value
return value;//返回函数值}
void __fastcall outportw(unsigned short int port,unsigned short int value)
//port参数为输出端口地址,value参数为输出值
{ __emit__(0x8b,0x95,&port);//把端口地址送到处理器32位EDX寄存器中
__emit__(0x66,0x8b,0x85,&value);//把value送到处理器8位AL寄存器中
__emit__(0xef);//把AL寄存器中的值送到端口 }
unsigned short int inportw(unsigned short int port)
//port参数值为输入端口值
{ unsigned short int value;//指定变量value为无符号字符型
__emit__(0x8b,0x95,&port);//把端口地址送到处理器32位EDX寄存器中
__emit__(0xed);//从指定端口中将一数据字节送到8位AL寄存器中
__emit__(0x66,0x89,0x85,&value);//把AL寄存器中的值赋给value
return value;//返回函数值 }
在表单上添加五个Button控件和四个Edit组件,如图1所示。
图1
分别双击Button1、Button2、Button3、Button4、Button5,产生OnClick事件函数。在单元文件的.cpp文件中添加如下代码:
void __fastcall TForm1::Button1Click(TObject *Sender)
{ outportb(0x2c0,0x00);//向地址为2c0H的端口输出数据
outportb(0x2c1,0x00);//向地址为2c1H的端口输出数据 }
//-------------------------------------------------------------------------void __fastcall TForm1::Button2Click(TObject *Sender)
{ int i;
i=inportb(0x2c2);//从地址为2c2H的端口读入数据
Form1->Edit2->Text=IntToStr(i); }
//-------------------------------------------------------------------------void __fastcall TForm1::Button5Click(TObject *Sender)
{ Application->Terminate(); }
//-------------------------------------------------------------------------void __fastcall TForm1::Button3Click(TObject *Sender)
{ outportw(0x2c0,0x0000);//向地址为2c0H的端口输出数据
outportw(0x2c1,0x0000);//向地址为2c1H的端口输出数据 }
//-------------------------------------------------------------------------
void __fastcall TForm1::Button4Click(TObject *Sender)
{ int i;
i=inportw(0x2c2);//从地址为2c2H的端口读入数据
Form1->Edit4->Text=IntToStr(i); }
按F9键,即可使程序运行,并对上述端口进行读写操作。
3.2 动态链接库实现硬件端口读写应用举例
按照2.2中所介绍的方法进行操作后,即可直接使用READUSHORTPORT(unsigned short port)、WRITEUSHORTPORT(unsigned short port,unsigned short dataword)、READINTPORT(unsigned short port)、WRITEINTPORT(unsigned short port,int dataword)、READULONGPORT(unsigned short port)和WRITEULONGPORT(unsigned short port,unsigned dataword)等进行端口读写操作。现以READUSHORTPORT(unsigned short port)和WRITEUSHORTPORT(unsigned short port,unsigned short dataword)为例介绍。
在表单上添加三个Button控件和两个Edit组件,如图2所示。
图2
分别双击Button1、Button2、Button5,产生OnClick事件函数。在单元文件的.cpp文件中添加如下代码:
void __fastcall TForm1::Button1Click(TObject *Sender)
{ WRITEUSHORTPORT(0x2c0,0x0000);//向地址为2c0H的端口输出数据
WRITEUSHORTPORT(0x2c1,0x0000);//向地址为2c1H的端口输出数据 }
//-------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{ int i;
i=READUSHORTPORT(0x2c2);//从地址为2c2H的端口读入数据
Form1->Edit2->Text=IntToStr(i); }
//-------------------------------------------------------------------------
void __fastcall TForm1::Button5Click(TObject *Sender)
{
Application->Terminate();
}
四、结束语
利用_emit__函数实现硬件端口读写操作的方法与利用动态链接库实现硬件端口读写操作的方法相比较,显然是前者占有优势,利用__emit__函数实现I/O端口读写操作的工作量小,程序的执行效率也远比后者要高。但是,动态链接库的方法对我们却有着更广泛的指导意义,毕竟,当前市场上名气最大的C++开发工具还是Microsoft公司的Visual C++,动态链接库的方法可以使我们轻而易举地实现C++Builder与Visual C++的结合,使这两种优秀的编程工具能够发挥出各自的优点。
参考文献
1.Charlie Calvert,et al.著. 徐科等译. Borland C++Builder应用开发大全. 北京, 清华大学出版社, 1999
2.任常锐,黎涛. C++Builder高级编程.北京,机械工业出版社,2000
|