本帖最后由 阳光不锈 于 2011-10-27 01:44 编辑
C#与Fortran混合编程 .NET是Microsoft 推出的完全面向对象开发的平台,用户可以在这个平台上快速建立企业级Web应用程序和高性能桌面应用程序。C#语言是由C++的发展和演化出来的,它是专门为.Net平台开发设计的一个先进的、安全的、面向对象的编程语言。其设计目的之一就是支持快速应用程序开发(Rapid Application Development),与Visual Basic的开发方式相似。C#允许我们使用设计器快速而简单地设计Windows应用程序和Web应用程序,在实际工程应用中存在着许多已经历时间的考验、成熟、稳定的Fortran计算程序,至今在工程计算中仍发挥着重要作用,但 Fortran语言本身并不适合用来开发、Windows界面程序和、Web应用程序。因此,为了提高程序的使用效率,提高代码的重用率,有必要对原F0RTRAN程序进行改进,使得原有性能优良的F0RTRAN代码可以被在C#下调用,最大限度的发挥两种语言各自的优点。 本文介绍了如何利用Microsoft的.NET平台使用DllImport属性支持.NET平台语言和普通编程语言的混合编程方法,来实现由C#访问Fortran的动态连接库(DLL)函数的功能。 混合编程的方法 使用C#与Fortran混合语言编程,就是利用C#实现友好的用户界面,用FORTRAN编写的过程进行所有的计算工作。在程序中,必须以C#为主程序来调用FORTRAN程序。实现混合编程的关键就是将FORTRAN计算程序编译为动态链接库,而后在C 中调用此动态链接库。下面将给出这种混合语言编程的方法和有关注意事项。 1. 函数命名 从DLL中导出的Fortran函数的名字都被FORTRAN编译器自动转换为全部大写,这样在C#中就有可能无法找到与FORTRAN源代码中声明相同的函数名。可以使用 .NET Framework提供的dumpbin.exe工具查看DLL导出的函数名称。 A.在开始菜单中打开Microsoft Visual Studio 2008/Visual Studio Tools/ Visual Studio 2008 命令提示。 B. 在命令提示窗体中将路径指向编译生成.dll文件的路径,然后输入以下命令: dumpbin /exports FileName.dll 即可查看当前目录下FileName.dIl中导出的所有函数信息。 对于Fortran编译器导出函数其函数名默认全为大写的情况,Fortran方面的解决方案是使用ALIAS(别名)属性指定导出函数名,例如对于下面的Fortran函数: DOUBLE PRECISION FUNCTION PLUS(A, B) !DEC$ ATTRIBUTES DLLEXPORT::PLUS DOUBLE PRECISION A,B PLUS =A+B END 对应的C#声明为: [DllImport("MathDll")] private static extern double PLUS(ref double A, ref double B); 使用ALIAS修改后的定义如下: Double Precision Function PLUS(A, B) !DEC$ ATTRIBUTES DLLEXPORT::PLUS !DEC$ ATTRIBUTES ALIAS:'Plus' :: Plus Double Precision A,B Plus=A+B End 对应的C}}声明为: [DllImport("MathDll")] private static extern double Plus(ref double A, ref double B); 而来自C#方面的解决方案是通过使用Dlllmport的EntryPoint属性指定导出的Fortran函数名。 Double Precision Function PLUS(A, B) !DEC$ ATTRIBUTES DLLEXPORT::PLUS DOUBLE PRECISION A,B PLUS =A+B END 对应的C#声明为: [DllImport("MathDll",EntryPoint = "PLUS")] private static extern double Plus(ref double A, ref double B); 2.堆栈管理 堆栈管理约定包括:在调用过程中子例程接受参数的数目和顺序,调用完成后由哪一方来清理堆栈等。C#语言在windows平台上的调用模式默认为StdCall模式,既由被调用方清理堆栈。而Fortran语言则默认由调用方清除。因此必须统一调用双方的堆栈清除方式才能保证2种语言间的正常函数调用。这一约定在Fortran语言或C#语言中均可以采取措施进行统一。 Fortran不同调用约定对照 调用约定
| 参数是否 可变
| 堆栈清除方式
| 参数传递方式
| 编译后的目标函数名
| 大小写
| 前缀
| 后缀
| 默认
| 是
| 被调函数清除
| 传址调用
| 大写
| 下划线
| @n
| C
| 是
| 主调函数清除
| 传值调用 (仅限标量)
| 小写
| 下划线
| 无
| STDCALL
| 否
| 被调函数清除
| 传值调用 (仅限标量)
| 小写
| 下划线
| @n |
在Fortran语言中可以通过编译器的通过编译指令“!DEC$”后的可选项“C”或“STDCALL”参数来实现: !DEC$ ATTRIBUTES STDCALL ::Object !DEC$ ATTRIBUTES C :: Object 第一条语句中的STDCALL模式指定由被调用方清除堆栈(其中“Object”为变量名或函数名),第二条语句中的C模式声明由主调函数清除堆栈(但在传递数组和字符串参数时不能用此方法指定)。 如果在C#语言内做改动,则需要在DllImport属性中设置CallingConvention字段的值为Cdecl(表示由调用方清理堆栈)或StdCall(表示由被调用方清理堆栈)。 [DllImport("FileName.dll", CallingConvention = CallingConvention.StdCall)] [DllImport("FileName.dll", CallingConvention = CallingConvention.Cdecl)] 3.函数调用 不同的语言在函数调用返回时清理堆栈的方式是不同的.一般分为由调用方清除和由被调用方清除两种。C#语言采取的是与C相同的堆栈方式.由调用方清除;而Fortran语言采取的则是stdcall方式.即由被调用方清除。我们必须统一调用双方的堆栈清除方式才能让函数调用能够正常进行下去。 对于这个问题。来自于Fortran方面的解决方案是使用_cdecl声明。 Double Precision Function PLUS(A, B) !DEC$ ATTRIBUTES DLLEXPORT::PLUS !DEC$ ATTRIBUTES C, ALIAS:'Plus' :: Plus C#中引用声明为: [DllImport("MathDll")] private static extern double Plus(double A, double B); 来自C#方面的解决方案是在DllImport 的CallingConvention属性中指定调用方法。 Double Precision Function PLUS(A, B) !DEC$ ATTRIBUTES DLLEXPORT::PLUS !DEC$ ATTRIBUTES ALIAS:'Plus' :: Plus C#中引用声明为: [DllImport("MathDll",CallingConvention = CallingConvention.StdCall)] private static extern double Plus(double A, double B); 4. 传递数值参数 函数的参数传递有两种方式,一种是传值(Pass by value),被调用方得到参数值调用方传入参数拷贝而非传入参数本身;另一种是传参(Pass by reference),被调用方和调用方使用的同一个参数。在处理C#与Fortran之间的相互调用必须妥善处理这个问题,否则很容易引起非法内存访问的问题。 默认情况下,Fortran对所有的参数使用“传参”的方式传递参数,但是对已C#来说,情况就复杂的多。在C#中,变量分为两种类型:值类型(Value Type)和引用类型(Reference Type)。顾名思义,值类型在作为函数参数调用时采用“传值”方式而引用类型采用“传参”方式。C#中所有的数值类型都是Value type而表示字符串的String则是Reference Type。 为了避免传递数值参数引发内存错误,我们可以有以下两种解决方案。 A. 在Fortran方面,利用Fortran的VALUE属性指定参数使用“传值”的方式。 Double Precision Function Plus(A, B) !DEC$ ATTRIBUTES DLLEXPORT::Plus !DEC$ ATTRIBUTES STDCALL, ALIAS:'Plus' :: Plus Double Precision A,B Plus=A+B End 当然,我们也可以分别指定各参数的传递类型,如下面代码段所示,函数传递参数中的参数“A”是以值传递,而参数“B”则是引用传递。 Double Precision Function PLUS(A, B) !DEC$ ATTRIBUTES DLLEXPORT::PLUS !DEC$ ATTRIBUTES ALIAS:'Plus' :: PLUS !DEC$ ATTRIBUTES VALUE::A !DEC$ ATTRIBUTES REFERENCE::B Double Precision A,B Plus=A+B End B. 在C#方面,可以使用C#的ref关键字,确保函数传递给Fortran的参数时是传递的该参数的地址。 [DllImport("MathDll")] privatestaticexterndoublePlusRef(refdoubleA, refdoubleB); |