浅谈TC、BC和汇编混合编程连接技术及参数传递方法

摘要:本文主要讨论如何应用混合编程技术,让汇编语言与高级语言相结合,以简化编程工作。

关键词:Turbo C++;Borland C++;Turbo Assembler;混合编程

一、引言

在平常编写程序时,我们一般都希望选择C、Pascal、Basic等这样的高级语言来编写,这样的话程序开发周期比较短,省时省力。但在很多时候为了提 高程序的执行效率或其它原因,我们却不得不用汇编程序来编程。如果只用汇编语言编程的话是比较繁琐的,对于一个比较大的程序来说编写起来将非常耗时,开发 周期比较长。为了既能缩短程序开发周期,又能保证程序的执行效率,我们通常会考虑用高级语言编写主程序,而把程序中与执行效率关系密切的关键部分用汇编语 言来编写。这里就涉及到了混合编程的问题。本文选择Turbo C++和Borland C++这种以C语言为基础的程序开发工具与 Turbo Assembler这样的汇编开发工具为例,来讨论一下高级语言与汇编语言混合编程的相关问题。

TC与汇编混合编程

  将C语言和汇编语言混合使用的传统方式是,先完全用C语言或汇编语言写出单独的模块,编译C语言模块并汇编汇编语言模块,然后将分别编译过的模块连接到一起。按这种方式Turbo C++模块和Turbo Assembler模块可以很方便地进行连接。
  用户可以采用以下命令,使C源文件与汇编源文件混合产生可执行文件:
tcc filenam1 filenam2.asm
该命令指示Turbo C++首先将FILENAM1.C编译成FILENAM1.OBJ,再激活Turbo Assembler将 FILENAM2.ASM汇编成FILENAM2.OBJ,最后激活TLINK连接FILENAME1.OBJ与FILENAM2.OBJ并产生 FILENAM1.EXE。
  这种分别编译的方法对于可确定大小的汇编代码程序极为有用,因为它可以让用户使用Turbo Assembler的全部功能,在纯汇编语言环境中进行汇编语言程序设计,不需要asm关键字、额外的编译时间以及嵌入式汇编中与C相关的辅助开销。
  Turbo C++与Turbo Assembler的接口中,有两个重要的问题值得注意。首先,C代码与汇编码的各个不同部分必须恰当地连接在一 起。其次,汇编码必须能恰当地处理C风格的函数调用,包括访问传递过来的参数、返回值以及遵从C函数所要求的寄存器保存规则。为了将Turbo C++与 Turbo Assembler模块连接在一起,必须完成以下工作:
(1) Turbo C++与Turbo Assembler模块必须以Turbo C++可以接受的方式共享适当的函数和变量名。
(2) Turbo Assembler模块必须使用与Turbo C++兼容的段命名方案。
必须用TLINK将这些模块连接成可执行程序。此时,我们关心的是如何创建一种模式,以便可以按此模式编写与C兼容的汇编语言函数。为此,我们需要来了解一下以下内容:

1、内存模式和段

  对于一个给定的可以由C调用的汇编语言函数,该函数必须采用与C语言一致的内存模式(tiny、small、compact、medium、 large或huge),同时必须使用与C兼容的代码段。同样,为了让C代码能够访问在汇编语言模块中定义的数据,或让汇编代码能够访问C模块中定义的数 据,汇编语言代码务必遵从C语言的数据段命名约定。
  通常,汇编语言模块由三个基本段(代码段、初始化数据段和非初始化数据段)组成。每组信息被安排在各自的段中,段名取决与C程序所使用的内存模式。 Turbo assembler(TASM)提供了三种简化的段伪指令(.CODE,.DATA,.DATA?)来定义这些段。其中,.CODE段标志着 程序代码段的开始,告知Turbo Assembler用户指令究竟放在哪个代码段中。.DATA标志数据段的开始,用户应将内存变量放在数据段中,此段 中包含的是已初始化的数据。.DATA? 的使用与.DATA一样,但.DATA? 数据段中包含的是未初始化的数据。这些简化的段伪指令产生的段与 Turbo C++兼容。
  伪指令.MODEL告诉Turbo Assembler,用简化的段伪指令创建的段与选定的内存模式兼容,并控制用PROC伪指令创建的过程的隐含类 型。由.MODEL伪指令定义的内存模式与具有同样命名的Turbo C++模式是兼容的。若C程序选择了小内存模式,下面列出了采用简化的段伪指令组织 汇编模块的一种方式:
代码:
  
  

.MODEL   SMALL
.CODE
…代码段…
.DATA
…初始化数据段…
.DATA?
…非初始化数据段…


2、汇编语言与Turbo C++的交互性

  此处着重讨论参数传递、返回值及寄存器使用约定。
  假设我们编写一个求最小数的函数min,其C语言的函数原型为:
  extern “C” int min(int V1,int V2);
此函数返回两个参数中的最小值。在汇编语言中其整体格式如下:
代码:
  
  

  PUBLIC C min
  min    PROC  C  NEAR
      …
  min    ENDP


这里假定min为near函数,若为far函数,可将上面格式中的near换成far。

(1)传递参数

首先考虑使用哪一种参数传递方法。除非有适当的理由,否则应该使用C参数传
  递方法而不用Pascal参数传递方法。对于16位的程序,当min被调用时,栈的内容如下:
SP+4:    V2
SP+2:    V1
SP:      返回地址
  如想不退栈就取得参数,则需保存基指针(BP),将栈顶指针(SP)送BP,然后用BP作为下标直接到栈中取得参数值。此时需注意:若把BP压入栈内,则参数的偏移量将加2,因为现在栈内又增加了两个字节。
  
(2)返回值

  正如C函数一样,可由C调用的汇编语言函数也可以返回值。函数值的返回形式在通常情况下,16位值返回到AX中;32位值返回到DX:AX中,其中 DX存放高16位值,而AX存放低16位值;浮点值返回到8087/80287栈顶(TOS)寄存器,即ST(0)中;如果使用8087/80287仿真 器,则返回值存放在仿真器的TOS寄存器中。结构的返回要稍复杂一些。1个字节长的结构返回到AL中;2个字节长的结构返回到AX中;4个字节长的结构返 回到DX:AX中;3字节的结构或大于5字节的结构则保存在静态数据区中,然后返回指向此静态数据的指针(小数据模式置于AX中,大数据模式只有DX: AX中)。调用子程序时必须把返回值拷贝到这个指针所指的单元中。
    例如,min函数返回的时16位数值,所以其返回值在AX中。下面是min的汇编代码:
代码:
  
  

PUBLIC  C min
min  PROC  C NEAR
  
  PUSH BP    ;保存BP
  MOV BP,SP  ;将SP拷贝到BP中
  MOV AX,[BP+4]  ;将V1送到AX中
  CMP AX,[BP+6]  ;与V2比较
  
  JLE EXIT  ;如果V1>V2
  MOV AX,[BP+6]  ;将V2送AX中
EXIT:  POP BP    ;恢复BP
  RET    ;返回C

min   ENDP


如果min是far函数,则主要在栈的入口处发生了变化,现在栈内的情况是:
    SP+6:  V2
    SP+4:  V1
    SP+2:  返回段地址
    SP:    返回偏移量
    这就意味着栈的偏移量已增加了两个字节,因为两个额外字节(返回段地址)被压进栈内。在min函数的far版本中,V1将在[BP+6]中,V2将在[BP+8]中。这是因为返回段地址被压入栈内,V1、V2的偏移量增加了两个字节。
如果使用Pascal参数传递顺序,则栈内情况变化如下(假设min是near函数):
    SP+4:  V1
    SP+2:  V2
    SP:    返回地址
此时,标识符min必须遵从Pascal类型的约定:使用大写字母和不加前缀下划线的标识符。
除了V1和V2交换了位置外,此方法还需要min在返回时用ret指令清栈。在本例中,必须弹出V1和V2的四个字节(返回地址由ret只能自动弹出)。
下面是采用Pascal参数传递方法的min汇编代码:
代码:
  
  

  PUBLIC  PASCAL min
min  PROC  PASCAL NEAR
  
  PUSH BP    ;保存BP
  MOV BP,SP  ;将SP拷贝到BP中
  MOV AX,[BP+6]  ;将V1送到AX中
  CMP AX,[BP+4]  ;与V2比较
  
  JLE EXIT  ;如果V1>V2
  MOV AX,[BP+6]  ;将V2送AX中
EXIT:  POP BP    ;恢复BP
  RET  4  ;清栈并返回

min   ENDP



(3)寄存器的使用约定

    在min函数中,使用了多个寄存器。现在讨论在Turbo C++程序中将使用哪些寄存器,以及使用中的一些约定。
    在所有寄存器中,BP应首先引起注意,因为一旦进入汇编程序,BP的内容在子程序入口处被压入栈内,并且栈指针(SP)的当前值必须放在BP中,在子程序出口处BP被弹出,恢复原来的值。
    其他需注意的是SI和DI寄存器,这两个寄存器被Turbo C++用作寄存器变量。如果在一个汇编语言过程中使用它们,则应在进入时保存它们(可能在栈中),在退出时恢复它们。
    
3、Turbo C++中调用汇编模块的方法

  一般情况下,Turbo C++希望所有的外部标号均以下划线“_”开头。如果汇编模块中定义的函数及变量准备供Turbo C++调用,应以下划线 开头。若不想使用下划线的话,可在PUBLIC后加C标明调用的语言类型。如若有函数XXX,要定义为供Turbo C++调用的函数,可这样写为 PUBLIC C XXX 而不需写为通常的PUBLIC _XXX。汇编程序对字母的大小写并不敏感,所以不区别对待大写字母和小写字母,而皆以大写字 母对待。因为C语言区别对待大小写字母,所以在编写准备与C模块相连接的汇编模块时,应该注意符号名的大小写,以便保持一致。而且,要通知汇编程序对大小 写区别对待,至少对于C模块和汇编模块所共享的那些符号而言应该如此。汇编程序的命令行可选项 /ml 和 /mx 可以做到这一点。汇编程序的命令行可 选项 /ml 使得汇编只对公共标识符和外部标识符等按大小写区别对待。按本机配置编写一个Turbo C++和汇编混合编程的程序的步骤如下:
  
(1) 因为在本机上Turbo C++ 3.0(以下简称TC)和Turbo Assembler 5.0(以下简称TASM)都安装在E盘,其可执行 文件分别位于E:/TC/BIN及E:/TASM/BIN目录下。于是编辑环境变量中的系统变量内的path值,添加以下内容:E:/TC/BIN;  E:/TASM/BIN 。这样做的目的是为了可以在TC的集成开发环境中直接调用TASM来编译汇编文件,而不需转到TASM目录在命令行下编译汇编 文件。

(2) 启动TC集成开发环境新建一个工程,命名为ASMTEST.PRJ。添加ASMTEST.CPP及ADD.ASM两个文件,源码分别如下(已在TC下调试通过):
代码:
  
  

//  程序名:ASMTEST.CPP
#include "stdio.h"
extern "C"      //声明在外部模块中定义的函数
{
int addnum( int , int );
}

main()
{
int x,y;    //定义变量
printf("Please input x,y,the space key to compart:=>");
scanf("%d %d",&x,&y);
printf("x+y=%d/n",addnum(x,y));

return (0);
}


代码:
  
  

;程序名:ADD.ASM
  DOSSEG
  .MODEL small            ;声明内存模式
  
  ;.DATA                ;数据段未定义,此处为空
  
  .CODE                ;代码段
  PUBLIC C addnum            ;定义函数
addnum  PROC C NEAR x:WORD,y:WORD    ;定义变量
  
  mov ax,x
  mov bx,y
  add ax,bx
  ret

addnum ENDP
  END


以上的ASMTEST.CPP程序调用ADD.ASM中的addnum函数,来实现两个数相加。完成代码输入后,编译后即可运行。另一种在汇编程序中定义以下划线“_”开头的函数的方法可参考附件中TOTAL工程内的文件。

二、 Borland C++与汇编的混合编程
  在本机上Borland C++(简称为BC)版本为5.02,安装在F:/BC5目录下,可执行文件位于F:/BC5/BIN目录。在此讨论在BC开发的Windows 程序中调用汇编程序。
  BC调用汇编模块与TC调用汇编模块的方法基本相同,只有几个需注意的地方:
  因为和 16 位 Windows 下的把代码分成 DATA,CODE 等段的内存模式不同,WIN32 只有一种内存模式,即 FLAT 模式, 意思是"平坦"的内存模式,再没有 64K 的段大小限制,所有的 WIN32 的应用程序运行在一个连续、平坦、巨大的 4GB 的空间中。这同时也意 味着您无须和段寄存器打交道,您可以用任意的段寄存器寻址任意的地址空间,这对于程序员来说是非常方便的。在Win32下编程,有许多重要的规则需要遵 守。有一条很重要的是:Windows 在内部频繁使用 ESI,EDI,EBP,EBX 寄存器,而且并不去检测这些寄存器的值是否被更改,这样当您要 使用这些寄存器时必须先保存它们的值,待用完后再恢复它们。
  在WIN32指定内存模式参数调用规则时可这样定义:
  .MODEL FLAT,STDCALL
  此处的STDCALL 告诉编译器参数的传递约定。前面我们已经谈到了调用约定的问题,这里我们再来仔细讨论一下:在Win16下有两种约定:C 和  PASCAL。C 约定规定参数传递顺序是从右到左,即最右边的参数最先压栈,由调用者恢复堆栈指针。PASCAL约定和C约定正好相反,它规定参数是 从左向右传递,由被调用者恢复堆栈。Win16采用了PASCAL约定, 因为PASCAL约定产生的代码量要小。STDCALL是C约定和PASCAL 约定的混合体,它规定参数的传递是从右到左,恢复堆栈的工作交由被调用者。
  在BC所建立的工程中的汇编模块,调用C模块中的外部变量时可不在变量前加下划线“_”。由此,可建立一个调用汇编模块的简单示例程序如下(已在BC下调试通过):
在BC中新建一个AsmInc工程,此处使用SDK方法,即不使用OWL,只使用WINAPI。设置为静态连接,添加以下几个文件:
代码:
  
  

//程序名:AsmInc.cpp
//
//预处理

#include "windows.h"
#include "stdio.h"
#include "string.h"

/
//全局变量

extern "C"
{
  void Gen( void );            //定义外部模块
  char username[255] = "";    //定义用户名和密码
  char serial[255] = "";
}
HINSTANCE hInst;  //应用程序进程句柄,一般程序中经常用到此变量,故使用全局变量
HWND hwnd;    //主窗口句柄,一般程序中经常用到此变量,故使用全局变量

/
//函数声明

LRESULT CALLBACK KeyGen(HWND, UINT, WPARAM, LPARAM);


//主函数

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
  DialogBox(hInst,MAKEINTRESOURCE( 1 ),NULL,(DLGPROC) KeyGen);
  return (FALSE);
}



//对话框主函数

LRESULT CALLBACK KeyGen(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
  char *sn;            //用于临时存放生成的密码
  
  switch(message)
  {
  case WM_COMMAND:
    switch(LOWORD (wParam))
    {
    case WM_INITDIALOG:
      LoadIcon( hInst , MAKEINTRESOURCE(1));  //设置程序图标
      break;
      
    case 105:            //处理输入的用户名
      GetDlgItemText( hDlg, 103, username, 255);
      Gen();                                           //调用外部模块
      sn = serial;
      SetDlgItemText( hDlg , 104 , sn);      //把计算后的密码输出
      break;
      
    case 106:
      {
        MessageBox( NULL, TEXT("********混合编程示例********/n/n*******By cao_cong********/n******2005年2月5日*****"),
          TEXT("关于"),MB_OK);
        return 0;
      }
      break;
      
    case 107:
      EndDialog ( hDlg , 107);
      break;
    }
    
    default : return (FALSE);
  }
  return (TRUE);
}


代码:
  
  

;程序名:Gen.asm
.386
.MODEL  FLAT STDCALL     ;定义使用的内存模式及参数调用方式

PUBLIC  Gen              ;声明函数

.DATA
EXTERN  username         ;声明在其他模块中定义的变量
EXTERN  serial

.CODE
Gen  PROC C           ;主函数
  
  PUSH EBX         ;保存EBX
  XOR EAX,EAX
  XOR EBX,EBX
  XOR ECX,ECX
@LOOP:  MOV EBX,[username+ECX]   ;获得用户输入的姓名
  MOV serial+ECX,EBX       ;把姓名送到密码中
  INC ECX
  CMP BYTE PTR ES:[username+ECX],0   ;检测是否已送完
  JNZ @LOOP
  
  POP EBX           ;恢复EBX
  ret

Gen endp
END


代码:
  
  

//资源文件:AsmInc.rc(摘录)
#define IDI_ICON  1
#define IDD_KEYGEN  1
#define IDC_STATICTEXT1  101
#define IDC_STATICTEXT2  102
#define IDC_NAME  103
#define IDC_SN  104
#define IDEXIT  107
#define IDABOUT  106
#define IDGEN  105

IDD_KEYGEN DIALOG 0, 0, 208, 85
STYLE DS_MODALFRAME | DS_3DLOOK | DS_CENTER | DS_CONTEXTHELP | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "混合编程示例"
FONT 9, "宋体"
LANGUAGE LANG_CHINESE , SUBLANG_CHINESE_SIMPLIFIED
{
 CONTROL "姓名:", IDC_STATICTEXT1, "static", SS_CENTER | WS_CHILD | WS_VISIBLE, 8, 16, 36, 12
 CONTROL "密码:", IDC_STATICTEXT2, "static", SS_CENTER | WS_CHILD | WS_VISIBLE, 8, 40, 36, 12
 CONTROL "", IDC_NAME, "edit", ES_LEFT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 44, 12, 156, 14
 CONTROL "", IDC_SN, "edit", ES_LEFT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 44, 36, 156, 17
 CONTROL "生成密码", IDGEN, "button", BS_PUSHBUTTON | BS_CENTER | BS_FLAT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 24, 60, 44, 17
 CONTROL "关    于", IDABOUT, "button", BS_PUSHBUTTON | BS_CENTER | BS_FLAT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 90, 60, 44, 17
 CONTROL "退出", IDEXIT, "button", BS_PUSHBUTTON | BS_CENTER | BS_FLAT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 156, 60, 44, 17
}


  以上程序功能主要是把你输入的姓名在密码框中输出,主要用于说明调用汇编模块的方法。



参考文献:
1、林亨利、陈维兴、成渝 编译:面向对象的程序设计系统 TURBO C++应用教程(中),清华大学出版社,1992
2、杨季文 等编著:80X86 汇编语言程序设计教程,清华大学出版社,1998
3、求实 编著:最新 Borland C++ 实用教程 1:Borland C++ 入门,科学出版社,1994
4、王松 张良治 编:Borland C++ 实用技术入门精解(3.1~4.0),电子科技大学出版社,1995

【版权声明】 本文纯属技术交流, 转载请注明作者并保持文章的完整, 谢谢!
 
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Part 1 Predefined symbols $ .......................... 2 @code ...................... 2 @CodeSize .................. 2 @CPU ...................... 2 @curseg .................... 2 @data ...................... 2 @DataSize .................. 2 ??date ...................... 2 @fardata .................... 2 @fardata? ................... 2 @FileName ................. 2 ??filename .................. 2 @Model .................... 2 @Startup ................... 3 ??time ...................... 3 ??version ................... 3 @WordSize ................. 3 Part 2 Operators Ideal mode operator precedence ................. 6 MASM mode operator precedence ................. 6 Operators .................. 7 () .......................... 7 * + (binary) ................. . + (unary) .................. . - (binary) .................. . - (unary) ................... . / ......................... . ? .......................... . [] ......................... . AND ....... : .............. . BYTE ..................... . BYTE PTR ................. . CODEPTR ................. . DATAPTR ................. . DUP ...................... . DWORD .................. . 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 8 9 E N T s DWORDPTR ............... 9 EQ ......................... 9 FAR ........................ 9 FARPTR ................... 9 FWORD .................... 9 FWORDPTR ................ 9 GE ......................... 9 GT ......................... 9 HIGH ...................... 9 HIGH ..................... 10 LARGE .................... to LE ........................ 10 LENGTH .................. 10 LOW ...................... 10 LOW ...................... 10 LT ........................ 10 MASK ..................... 10 MOD ...................... 10 NE ........................ 11 NEAR ..................... 11 NEARPTR ................ 11 NOT ...................... 11 OFFSET ................... 11 OR ........................ 11 PROC ..................... 11 PROC PTR ................. 11 PTR ....................... 11 PWORD ................... 12 PWORD PTR .............. 12 QWORD .................. 12 QWORD PTR .............. 12 SEG ....................... 12 SHL ....................... 12 SHORT .................... 12 SHR ....................... 12 SIZE ...................... 12 SMALL .................... 13 SYMTYPE ................. 13 TBYTE .................... 13 TBYTE PTR ................ 13 THIS ...................... 13 .TYPE ..................... 13

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值