你好,欢迎来到电脑编程技巧与维护杂志社! 杂志社简介广告服务读者反馈编程社区  
合订本订阅
 
 
您的位置:杂志经典 / 编程语言
C++Builder的硬件端口读写实现与实例
 

  :本文提出了用__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++BuilderHelp菜单,可以得到该函数的联机帮助信息。

__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);//把端口地址送到处理器32EDX寄存器中

         __emit__(0x8a,0x85,&value);//value送到处理器8AL寄存器中

         __emit__(0x66,0xee);//AL寄存器中的值送到端口}

        unsigned char inportb(unsigned short int port

       {//port参数值为输入端口值       

         unsigned char value;//指定变量value为无符号字符型

         __emit__(0x8b,0x95,&port);//把端口地址送到处理器32EDX寄存器中

         __emit__(0x66,0xec);//从指定端口中将一数据字节送到8AL寄存器中

         __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);//把端口地址送到处理器32EDX寄存器中

        __emit__(0x66,0x8b,0x85,&value);//value送到处理器16AX寄存器中

        __emit__(0xef);//Ax寄存器中的值送到端口}

        unsigned short int inportw(unsigned short int port)

        {//port参数值为输入端口值

        unsigned short int value;//指定变量value为无符号短整型

        __emit__(0x8b,0x95,&port);//把端口地址送到处理器32EDX寄存器中

       __emit__(0xed);//从指定端口中将一数据字送到16AX寄存器中

       __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++ BuilderDLL创建一个头文件.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++ BuilderDLL创建一个头文件.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文件插入如下代码:

//声明inportboutportb函数

        void __fastcall outportb(unsigned short int port,unsigned char value)

        //port参数为输出端口地址,value参数为输出值

       { __emit__(0x8b,0x95,&port);//把端口地址送到处理器32EDX寄存器中

         __emit__(0x8a,0x85,&value);//value送到处理器8AL寄存器中

         __emit__(0x66,0xee);//AL寄存器中的值送到端口 }

      unsigned char inportb(unsigned short int port)//port参数值为输入端口值

       { unsigned char value;//指定变量value为无符号字符型

         __emit__(0x8b,0x95,&port);//把端口地址送到处理器32EDX寄存器中

         __emit__(0x66,0xec);//从指定端口中将一数据字节送到8AL寄存器中

         __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);//把端口地址送到处理器32EDX寄存器中

         __emit__(0x66,0x8b,0x85,&value);//value送到处理器8AL寄存器中

         __emit__(0xef);//AL寄存器中的值送到端口 }

        unsigned short int inportw(unsigned short int port)

        //port参数值为输入端口值

       { unsigned short int value;//指定变量value为无符号字符型

         __emit__(0x8b,0x95,&port);//把端口地址送到处理器32EDX寄存器中

         __emit__(0xed);//从指定端口中将一数据字节送到8AL寄存器中

         __emit__(0x66,0x89,0x85,&value);//AL寄存器中的值赋给value

         return value;//返回函数值 }

在表单上添加五个Button控件和四个Edit组件,如图1所示。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


1

 

分别双击Button1Button2Button3Button4Button5,产生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

 

    分别双击Button1Button2Button5,产生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++BuilderVisual C++的结合,使这两种优秀的编程工具能够发挥出各自的优点。

参考文献

1.Charlie Calvert,et al.. 徐科等译. Borland C++Builder应用开发大全. 北京, 清华大学出版社, 1999

2.任常锐,黎涛. C++Builder高级编程.北京,机械工业出版社,2000

  推荐精品文章

·2024年9月目录 
·2024年8月目录 
·2024年7月目录 
·2024年6月目录 
·2024年5月目录 
·2024年4月目录 
·2024年3月目录 
·2024年2月目录 
·2024年1月目录
·2023年12月目录
·2023年11月目录
·2023年10月目录
·2023年9月目录 
·2023年8月目录 

  联系方式
TEL:010-82561037
Fax: 010-82561614
QQ: 100164630
Mail:gaojian@comprg.com.cn

  友情链接
 
Copyright 2001-2010, www.comprg.com.cn, All Rights Reserved
京ICP备14022230号-1,电话/传真:010-82561037 82561614 ,Mail:gaojian@comprg.com.cn
地址:北京市海淀区远大路20号宝蓝大厦E座704,邮编:100089