5-5 VB与Fortran的混合编程

用了DLL ,内存中都只有该DLL 的一个副本。当没有程序使用它时,系统就将它移出内存,减少了对内存和磁盘的要求。所以,使用DLL 的一个明显的好处就是可以节省系统资源。

此外,DLL 被广泛地使用,还基于下面的一些原因:

(1) DLL 作为一种基于Windows 的程序模块,不仅可以包含可执行代码,还可以包括数据和各种资源等,扩大了库文件的使用范围。有些设备驱动程序也是由动态链接库实现的(扩展名一般是DRV) 。

(2) DLL 技术对于开发大型软件系统也有可用之处。一个大型系统,如果用一个执行文件完成,程序将很庞大,而且可能有许多重复的功能。如果将程序分成主程序和DLL ,可以减少开发的工作量。而且由于每个模块减小了,访问的速度也提高了。

(3) DLL 另一个用途是支持世界各国的语言。开发者可以将依赖于语言的函数和资源分离出来,专门放进DLL 中,例如中文、英文、法文等。各地使用软件的用户可以安装或运行适当的DLL ,以获取正确的本地信息。这是实现软件商品国际化的一项技术。

(4) 将一些功能模块做成DLL 后,如果需要对系统进行升级,只要将个别DLL 进行升级,然后用新的DLL 文件覆盖掉旧的DLL 文件就可以了,而不需要将整个系统进行重新编译和链接。

(5) DLL 还独立于编程语言。例如,在Fortran 环境中开发的DLL 程序可以在Delphi 、Visual Basic 和Visual C + + 环境中方便地使用。

正是由于以上诸多优点,DLL 有着非常广泛的用途,是Windows 程序设计中的一个非常重要的组成部分。

2 Fortran 与VB之间调用约定的协调

VB 应用程序调用Fort ran 创建的DLL 时,必须保证VB 应用程序中所声明的例程(子程序和函数) 类型及例程名和DLL 中输出的一致,实参和形参在堆栈中的排列顺序一致,实参和形参的数据类型一致以及实参和形参的传递方式一致。

(1) 例程实现机制的对应

例程是子程序和函数的统称,二者的区别是子程序没有返回值,而函数有返回值,它们在Fortran 和VB中的声明关键字也不是完全一致的。Fortran 中的子程序用Subroutine 声明,在VB 中声明该例程时应该用Sub 对应;Fortran 中的函数用Function 声明,在VB 中亦是用Function。

(2) 参数在堆栈中排列顺序的一致性

Fort ran 的调用约定有C、Stdcall 和缺省约定,它们都使用堆栈传递例程参数,且传递方向是从左到右。也就是说,在执行例程调用时,调用程序将参数从右到左依次压入堆栈,被调用例程在接收参数时,将参数从左到右依次从堆栈弹出。VB 应用程序调用DLL 中的例程时遵循

6

从左到右的传递方向,且由被调方负责清除堆栈,因此Fortran 的调用约定应声明为Stdcall 或缺省,不能声明为由调用程序清除堆栈的C。

(3) 例程名的一致性

Fortran 本身不区分大小写,但若Fortran 源代码中未使用别名(Alias) 属性,则建造DLL ,生成目标代码时, 目标代码中的例程名随调用约定的不同而呈现出不同的形式,表1为Fortran 目标代码中的例程名的形式。而VB 区分大小写,因此,若Fort ran 采用缺省的调用约定,则在VB 中须声明为:

[ Private | Public ]Declare Sub name Lib″libname″Alias″_NAME @n″[ ( [ [ByVal ] variable [As type ] [ , [ByVal ] variable [Astype ] ] ?]) ] 。

表1 不同调用约定对应的目标例程名

Fortran 调用约定 缺省 C Stdcall 源代码中的例程名 Name Name Name 目标代码中的例程名 _NAME@n (全部大写) name (全部小写) _name @n (全部小写) 注:表中n 是例程参数列表所占堆栈字节数

显然,这样做是很麻烦的,也容易出错。我们可以利用Alias 属性来消除调用约定给目标代码中的例程名带来的影响。最简单也最不容易出错的办法便是在Fortran 源代码(假如其中含有一个名为DllApp 的子程序) 中添加声明:

!MS$ ATTRIBUTES Alias :’DllApp’ : :DllApp,

则生成的目标例程名就固定为DllApp了,与源代码中的例程名完全相同。 此时在VB 中可简单地声明为:

[ Private| Public ] Declare Sub DllApp Lib ″libname″[ ( [ [ByVal ] variable[As type ] [ ,[ByVal ] variable [As type ] ] ?]) ] ,不必写出Alias 属性,VB 默认为所有源例程名和目标例程名相同。

(4) 参数数据类型的一致性

VB 调用Fortran 创建的DLL 中的例程时,形参和实参的数据类型必须匹配,否则会发生错误。在Fortran90 中,它的数据类型和VB 的并不是全部一一对应的,但我们用于计算的常用数据类型有着精确的匹配,如表2 所示。

表2 VB与Fortran 数据类型对照表

Fortran Integer(2)

Visual basic 6.0 Integer 7

Visual basic 2005 short 字节数 2 Integer(4),integer Real(4),real Real(8) (5) 参数传递方式的一致性

Long Single double integer Single Double 4 4 8 参数的传递方式分为值传递和引用传递两种。在VB 中声明DLL 中的例程时,若按值传递,则在参数前面加上“ByVal”关键字, 若按引用传递,则在参数前面加上”ByRef”关键字;在Fort ran 的例程中,按值传递和按引用传递可分别用VALUE 和REFERENCE 属性来说明。当通过值来传递变量时,实际上是将变量的一个副本传给调用的例程,该例程可以修改变量值,但这个值的改变只影响到副本,不能影响变量本身。若采用引用传递来传递变量时,传递的是变量实际内存地址,所以在例程内修改变量的值会改变变量本身的值。在缺省情况下,Fort ran 和VB 的参数按引用方式传递。例如,在Fortran 中有一例程如下:

subroutine DllApp (a ,b ,c)

!DEC$ATTRIBUTES DLLEXPORT : :DllApp

!DEC$ATTRIBUTES Alias :’DllApp’: :DllApp ! 指定目标例程名为DllApp !DEC$ATTRIBUTES Value : : a ,b !规定参数a ,b 按值传递,参数c 默认按引用传递 integer (4) a ,b ,c c = a + b

end subroutine DllApp 则在VB 中应声明为:

Private Declare Sub DllApp Lib “ForVB. Dll”(ByVal a As Long , ByVal b As Long , ByRef c As Long)

在参数传递过程中,应注意数组的传递。

数组只能按引用传递,传递数组的第一个元素就是把数组的首地址传递给了Fortran ,完成VB 向Fortran的数组传递。另外,在缺省情况下,VB 数组的第一元素的下标为0 ,而Fort ran 数组的第一元素下标为1 ,因此,要在VB 应用程序数组定义部分写出下列类似语句:

Dim Arr (1 to 100 ,1 to 50) As Long ,

以使两者的下标值一致。例如,Fort ran 中有一例程: subroutine DllApp (a ,c)

! DEC $ATTRIBUTES DLL EXPORT : :DllApp ! DEC $ATTRIBUTES Alias :’DllApp’: :DllApp integer(4) a (2) ,c

8

c = a (1) + a (2) ! 求和运算,由c 返回运算值。形参a 和c 默认按引用传递 end subroutine DllApp 则在VB 中的调用形式如下: Call DllApp (a (1) , c)

’在VB 中数组a 须定义为Dim a (1 To 2) As Long 3 Dll的导出声明

要使Fortran 创建的DLL 能被外部模块调用,在Fortran 例程中须通过DLL EXPORT 属性进行导出声明,例如:

!MS$ ATTRIBUTES DLLEXPORT : :DllApp ! 声明导出例程DllApp 4 Dll 的导入声明

在VB 应用程序中调用DLL 中的例程时,应先通过以下两种方法之一导入例程:静态装载和动态装载。

(1) 静态装载 这种方法十分简单,只需在VB 的通用部分声明一个外部例程,之后,应用程序便可对它进行调用。声明的形式前面已经介绍过了,这里不再赘述。静态装载在应用程序运行时便装入库模块,程序运行结束时卸载库模块。

(2) 动态装载 它是指在应用程序运行时让程序和库模块进行动态连接,而不必在程序首次装入内存时便进行连接。当程序调用DLL 中的例程时,应用程序首先装入要使用的DLL 库,并直接检索所需例程的地址,以上操作成功后应用程序就可调用这个例程了。这可以通过三个Windows API 函数来实现:

LoadLibrary、Get ProcAddress 和FreeLibrary。例如:

hHandle = LoadLibrary (″ForVB. dll″) ’函数LoadLibrary 将DLL 动态地装入内存 if hHandle < > 0 then ’判断装载是否成功 lpSub = Get ProcAddress(hHandle , ″DllApp″)

’函数Get ProcAddress 获取DLL 中指定模块的地址 if lpSub < > null then ’执行体 end if

FreeLibrary (hHandle) ’函数FreeLibrary 动态地卸载DLL ,并释放资源 end if

5 Dll文件搜索路径

在前面的讲述中,VB 应用程序声明一个例程时的Lib 中只写了库名:Lib ″ForVB. dll″,

9

并未指明具体的路径,此时Windows 会按下述步骤进行查找:

当前目录、Windows 目录、Windows 系统目录、包含当前任务可执行文件的目录、列在PATH 环境变量中的目录、网络的映像目录列表。

当然我们也可以指明DLL 所在的具体路径来装载。例程导入后,我们就可以像调用普通例程一样调用它们了。

6 Microsoft 扩展属性及其含义

下面将一些可能会用到的Microsoft 扩展属性及其含义列在下表中,以备查阅。 名称含义 备注 为函数或子程序指定在调用方的名一般应为输出函数或子字,也可为数据对象指明其他名称 程序指明该属性 指明C方式属性的调用约定 若二者均未指定,则使用C 不同于二者的缺省方式 指明STDCALL方式属性的调用约STDCALL 定 DLLEXPORT 调用方仅可调用指明该属性的函数 或子程序 DLLIMPORT 在调用其它DLL文件的出口文件时 使用 指明其它源文件的全局变量 EXTERN REFERENCE 按引用方式传递参数或调用函数 VALUE VARYING 指定参数传递为传值方式 参数类型强制匹配 属性 ALIAS 八、 一个简单的应用实例

这里通过一个简单的实例,来说明混合编程的完整的过程,同时将代码给出来,可以做一个简单的练习。

这个混合编程例子要完成如下的任务:用Fortran编写一个Dll文件,其中包含计算两个双精度实数加、减、乘、除的过程和函数,而后在VB环境下设置简单的界面,通过调用这些过程和函数来完成两个输入双精度实数的加、减、乘、除运算,并将计算结果显示出来。

步骤和源代码如下。

1 Fortran中创建一个名为mathfunction的Dll项目,在项目中添加一个名为mathfunction的固定格式源文件,添加如下代码:

*************subroutine******************** subroutine subplus(a,b,c) !MS$ attributes dllexport :: subplus

10

联系客服:779662525#qq.com(#替换为@) 苏ICP备20003344号-4