导航技术论坛's Archiver

hg 发表于 2017-1-12 19:25

C#与Fortran混合编程之本地调用Fortran动态链接库

[b]前言[/b]
C#发展到现在,已是一门相当完善的语言,他基于C语言风格,演化于C++。并依靠强大的.NET底层框架。C#可以用来快速构建桌面及Web应用。然而在我们的实际工作中,尽管C#已经非常完善,但还是不能完成我们所有的工作。在很多工程计算中,C#语言的计算速度,精度,以及执行效率相对来说都达不到项目的要求。因此我们便考虑是否有一种方式将我们的工程计算部分和我们的项目分开,将计算部分用另一种执行更快,精度更高的语言来编写,然后在C#中调用,最后完成我们的工作。答案是肯定的。
Fortran是一门古老的语言,它是世界上最早出现的计算机高级程序设计语言,广泛应用于科学和工程计算领域。FORTRAN语言以其特有的功能在数值、科学和工程计算领域发挥着重要作用。然而Fortran程序本身不适合开发独立的应用程序,例如我们传统的桌面应用或者Web应用。因此这里我们便想将C#与Fortran结合,C#借助Fortran可以实现精度更高,计算更快的程序,而Fortran通过C#,便也能够达到可视化设计。

[b]一、基本思路[/b]运用Fortran,编写动态链接库(DLL),在DLL中提供计算的函数接口,然后在C#中调用该DLL中计算部分的函数,实现计算过程。
这里需要注意的是,由于我们使用的是Fortran编译器,生成的DLL属于第三方非托管DLL,因此无法直接在程序中添加DLL的引用。具体的做法将在后续部分说明。

二、编写Fortran程序,生成动态链接库文件知道思路之后便开始正式的Coding。首先新建一个空的Fortran Dynamic-link Library项目。
[img]http://pic002.cnblogs.com/images/2012/357342/2012110520275365.jpg[/img]
在Intel(R) Visual Fortran点击Library,选中右图的Dynamic-link Library.然后点击OK.这时的项目如下所示:
[img]http://pic002.cnblogs.com/images/2012/357342/2012110520281721.jpg[/img]
点击Sources File文件夹,选择新建项。
[img]http://pic002.cnblogs.com/images/2012/357342/2012110520283755.jpg[/img]
添加一个新的Fortran文件
[img]http://pic002.cnblogs.com/images/2012/357342/2012110520285147.jpg[/img]
然后便开始Fortran代码的编写工作。这里我们主要实现两个方法:
一个方法是求两个数相加之和,并返回结果。
另一个是输入一个数组,对这个数组进行排序,并找出最大值,最后返回排序后的结果,并返回最大值。
这里我们分别演示的是Fortran传出一个数和一个数组有何不同。
关于Fortran的基本语法不是本文的讨论范畴,请读者自行查阅资料。
下面给出的上述我们要实现的功能的具体Fortran代码:
[img]http://common.cnblogs.com/images/copycode.gif[/img]
[color=#000]DOUBLE PRECISION FUNCTION ADD(A,B)[/color]![color=#000]DEC$ ATTRIBUTES DLLEXPORT::ADD[/color]!DEC$ ATTRIBUTES STDCALL,ALIAS:[color=#8000]'[/color][color=#8000]Add[/color][color=#8000]'[/color][color=#000]::ADD    DOUBLE PRECISION:: A,B    ADD[/color]=A+[color=#000]BENDFUNCTION SORTANDFINDMAX(ARRAY,LENGTH)[/color]![color=#000]DEC$ ATTRIBUTES DLLEXPORT::SORTANDFINDMAX[/color]!DEC$ ATTRIBUTES STDCALL,ALIAS:[color=#8000]'[/color][color=#8000]Sortandfindmax[/color][color=#8000]'[/color][color=#000]::SORTANDFINDMAXDOUBLE PRECISION ::ARRAY(LENGTH)INTEGER::I,JDOUBLE PRECISION::SORTANDFINDMAX,TEMPSORTANDFINDMAX[/color]=ARRAY([color=#80080]1[/color][color=#000])DO I[/color]=[color=#80080]1[/color],LENGTH-[color=#80080]1[/color][color=#000]    DO J[/color]=I+[color=#80080]1[/color][color=#000],LENGTH        IF(ARRAY(I).GT.ARRAY(J)) THEN            TEMP[/color]=[color=#000]ARRAY(I)            ARRAY(I)[/color]=[color=#000]ARRAY(J)            ARRAY(J)[/color]=[color=#000]TEMP            SORTANDFINDMAX[/color]=[color=#000]ARRAY(J)            END IF    END DO    END DOEND[/color][img]http://common.cnblogs.com/images/copycode.gif[/img]

上面我们声明了两个Fortran函数,一个是计算两个数相加,一个是选择排序并找出最大值。
之后我们点击Visual Studio的Build Solution.开始编译成DLL。
关于代码段解释:
!DEC$ ATTRIBUTES DLLEXPORT::ADD
!DEC$ ATTRIBUTES STDCALL,ALIAS:'Add'::ADD
这两句代码很关键。下面通过三个一致来简单的说一下以上代码段的意思和C#调用需要注意的问题。
[color=#0080][b]1.函数名一致:[/b][/color]
在Fortran编译器中默认的导出函数名全部是大写形式。而在C#中调用Fortran Dll时必须指定函数名一致。在Fortran方面解决的办法是:使用ALIAS(别名)属性指定导出函数名。
例如对于下面的Fortran函数:
[img]http://common.cnblogs.com/images/copycode.gif[/img]
[color=#000]DOUBLE PRECISION FUNCTION ADD(A, B)!DEC$ ATTRIBUTES DLLEXPORT:: ADDDOUBLE PRECISION A,B  ADD [/color]=A+[color=#000]BEND[/color][img]http://common.cnblogs.com/images/copycode.gif[/img]

对应的C#声明为:
[DllImport([color=#8000]"[/color][color=#8000]MathDll[/color][color=#8000]"[/color][color=#000])][/color][color=#00ff]private[/color] [color=#00ff]static[/color] [color=#00ff]extern[/color] [color=#00ff]double[/color] ADD ([color=#00ff]double[/color] A, [color=#00ff]double[/color] B);
使用ALIAS修改后的定义如下:
[img]http://common.cnblogs.com/images/copycode.gif[/img]
[color=#000]Double Precision Function ADD (A, B)[/color]![color=#000]DEC$ ATTRIBUTES DLLEXPORT:: ADD[/color]!DEC$ ATTRIBUTES ALIAS:[color=#8000]'[/color][color=#8000]Add[/color][color=#8000]'[/color][color=#000] :: AddDouble Precision A,B  Add [/color]=A+[color=#000]BEnd[/color][img]http://common.cnblogs.com/images/copycode.gif[/img]

对应的C#声明为:
[DllImport([color=#8000]"[/color][color=#8000]MathDll[/color][color=#8000]"[/color][color=#000])][/color][color=#00ff]private[/color] [color=#00ff]static[/color] [color=#00ff]extern[/color] [color=#00ff]double[/color] Add ([color=#00ff]double[/color] A, [color=#00ff]double[/color] B);
而在C#中提供的解决方案是:通过使用Dlllmport的EntryPoint属性指定导出的Fortran函数名。
例如:
[img]http://common.cnblogs.com/images/copycode.gif[/img]
[color=#000]Double Precision Function ADD(A, B)[/color]![color=#000]DEC$ ATTRIBUTES DLLEXPORT:: ADDDOUBLE PRECISION A,B  ADD [/color]=A+[color=#000]BEND[/color][img]http://common.cnblogs.com/images/copycode.gif[/img]

对应的C#声明为:
[DllImport([color=#8000]"[/color][color=#8000]MathDll[/color][color=#8000]"[/color],EntryPoint = [color=#8000]"[/color][color=#8000] ADD [/color][color=#8000]"[/color][color=#000])][/color][color=#00ff]private[/color] [color=#00ff]static[/color] [color=#00ff]extern[/color] [color=#00ff]double[/color] Plus([color=#00ff]double[/color] A, [color=#00ff]double[/color] B);
此外,还可以使用 .NET Framework提供的dumpbin.exe工具查看DLL导出的函数名称。
    A. 在开始菜单中打开Microsoft Visual Studio 2010/Visual Studio Tools/ Visual Studio 2010 命令提示。
[img]http://pic002.cnblogs.com/images/2012/357342/2012110520345274.jpg[/img]
     B. 在命令提示窗体中将路径指向编译生成.dll文件的路径,然后输入以下命令:
        dumpbin /exports FileName.dll
即可查看当前目录下FileName.dIl中导出的所有函数信息。
[img]http://pic002.cnblogs.com/images/2012/357342/2012110520353730.jpg[/img]
[color=#0080][b]2. 堆栈管理一致[/b][/color]
堆栈管理约定包括:在调用过程中子例程接受参数的数目和顺序,调用完成后由哪一方来清理堆栈等。C#语言在windows平台上的调用模式默认为StdCall模式,既由被调用方清理堆栈。而Fortran语言则默认由调用方清除。因此必须统一调用双方的堆栈清除方式才能保证2种语言间的正常函数调用。这一约定在Fortran语言或C#语言中均可以采取措施进行统一。
在Fortran语言中可以通过编译指令“!DEC$”后的可选项“C”或“STDCALL”参数来实现:
A.
!DEC$ ATTRIBUTES STDCALL ::Object
该语句语句中的STDCALL模式指定由被调用方清除堆栈(其中“Object”为变量名或函数名)。
B.
!DEC$ ATTRIBUTES C :: Object
该语句中的C模式声明由主调函数清除堆栈(但在传递数组和字符串参数时不能用此方法指定)。
如果在C#语言内做改动,则需要在DllImport属性中设置CallingConvention字段的值为Cdecl(表示由调用方清理堆栈)或StdCall(表示由被调用方清理堆栈)。
[DllImport([color=#8000]"[/color][color=#8000]FileName.dll[/color][color=#8000]"[/color], CallingConvention =[color=#000] CallingConvention.StdCall)][DllImport([/color][color=#8000]"[/color][color=#8000]FileName.dll[/color][color=#8000]"[/color], CallingConvention = CallingConvention.Cdecl)]
只有当Fortran程序和C#程序的堆栈管理一致时,才能保证正常的调用。
[color=#0080][b]3.参数类型保持一致[/b][/color]
在Fortran中常用的数据参数类型有:
[b][color=#ff00]REAL[/color][/b]:表示浮点数据类型,即小数,等价于C#的float,
[b][color=#ff00]INTEGER[/color][/b]:表示整数类型,相当于C#的int数据类型
[color=#ff00][b]DOUBLE[/b] [b]PRECISION[/b][/color]:表示双精度数据类型,相当于C#的double数据类型。
  在C#调用Fortran DLL是必须保证参数的一致性,例如在Fortran中变量定义的是REAL类型,而我们传入的是Double,那么就会出现计算错误。
[b][color=#000]三、编写C#代码调用Fortran DLL[/color][/b]
C#调用的Fortran的过程很简单,只需要注意上述说的几个问题即可。

这里我们先新建一个控制台应用程序:

[img]http://pic002.cnblogs.com/images/2012/357342/2012110520394462.jpg[/img]

然后将我们编译的Fortran项目所生成的DLL拷贝到控制台应用程序的Debug文件夹下。
[img]http://pic002.cnblogs.com/images/2012/357342/2012110520401260.jpg[/img]
接着我们添加一个类:FortranMethod.cs
该类用来调用Fortran DLL。
代码如下:
[img]http://common.cnblogs.com/images/copycode.gif[/img]
[color=#00ff]using[/color][color=#000] System;[/color][color=#00ff]using[/color][color=#000] System.Text;[/color][color=#00ff]using[/color][color=#000] System.Runtime.InteropServices;[/color][color=#00ff]namespace[/color][color=#000] MixedProgram{   [/color][color=#00ff]public[/color] [color=#00ff]static[/color] [color=#00ff]class[/color][color=#000] FortranMethod    {       [DllImport([/color][color=#8000]"[/color][color=#8000]TestDll.dll[/color][color=#8000]"[/color],CallingConvention =[color=#000] CallingConvention.Cdecl)]       [/color][color=#00ff]public[/color] [color=#00ff]static[/color] [color=#00ff]extern[/color] [color=#00ff]double[/color] Add([color=#00ff]double[/color] a, [color=#00ff]double[/color][color=#000] b);       [DllImport([/color][color=#8000]"[/color][color=#8000]TestDll.dll[/color][color=#8000]"[/color], CallingConvention =[color=#000] CallingConvention.Cdecl)]       [/color][color=#00ff]public[/color] [color=#00ff]static[/color] [color=#00ff]extern[/color] [color=#00ff]double[/color] Sortandfindmax([color=#00ff]double[/color][] array, [color=#00ff]int[/color][color=#000] length);    }}[/color][img]http://common.cnblogs.com/images/copycode.gif[/img]

关于C#调用注意的事项在上面已说明,在此不再讨论。
然后在Main函数中测试我们的Fortran DLL。
示例代码如下:
[img]http://common.cnblogs.com/images/copycode.gif[/img]
[color=#00ff]using[/color][color=#000] System;[/color][color=#00ff]using[/color][color=#000] System.Collections.Generic;[/color][color=#00ff]using[/color][color=#000] System.Text;[/color][color=#00ff]namespace[/color][color=#000] MixedProgram{    [/color][color=#00ff]class[/color][color=#000] Program    {        [/color][color=#00ff]static[/color] [color=#00ff]void[/color] Main([color=#00ff]string[/color][color=#000][] args)        {            Console.WriteLine([/color][color=#8000]"[/color][color=#8000]请输入两个数相加:[/color][color=#8000]"[/color][color=#000]);            [/color][color=#00ff]double[/color] num1=[color=#000]Convert.ToDouble(Console.ReadLine());            [/color][color=#00ff]double[/color] num2 =[color=#000] Convert.ToDouble(Console.ReadLine());            Console.WriteLine([/color][color=#8000]"[/color][color=#8000]输入的两个数是:[/color][color=#8000]"[/color] + num1 + [color=#8000]"[/color][color=#8000] ,[/color][color=#8000]"[/color] +[color=#000] num2);            [/color][color=#00ff]double[/color] sum =[color=#000] FortranMethod.Add(num1,num2);            Console.WriteLine([/color][color=#8000]"[/color][color=#8000]求和结果是:[/color][color=#8000]"[/color]+[color=#000]sum);            [/color][color=#00ff]double[/color][] Array = { [color=#80080]1[/color],[color=#80080]5[/color],[color=#80080]2[/color],[color=#80080]4[/color],[color=#80080]3[/color],[color=#80080]7[/color],[color=#80080]6[/color][color=#000]};            Console.WriteLine([/color][color=#8000]"[/color][color=#8000]初始数组:[/color][color=#8000]"[/color][color=#000]);            [/color][color=#00ff]for[/color] ([color=#00ff]int[/color] i = [color=#80080]0[/color]; i < Array.Length; i++[color=#000])                Console.Write(Array[i][/color]+[color=#8000]"[/color] [color=#8000]"[/color][color=#000]);            [/color][color=#00ff]double[/color] b=[color=#000]FortranMethod.Sortandfindmax(Array, Array.Length);            Console.WriteLine([/color][color=#8000]"[/color][color=#8000]\n[/color][color=#8000]"[/color]+[color=#8000]"[/color][color=#8000]排序后:[/color][color=#8000]"[/color][color=#000]);            [/color][color=#00ff]for[/color] ([color=#00ff]int[/color] i = [color=#80080]0[/color]; i < Array.Length; i++[color=#000])                Console.Write(Array[i][/color]+[color=#8000]"[/color] [color=#8000]"[/color][color=#000]);            Console.WriteLine([/color][color=#8000]"[/color][color=#8000]\n[/color][color=#8000]"[/color]+[color=#8000]"[/color][color=#8000]最大值为:[/color][color=#8000]"[/color][color=#000]);            Console.WriteLine(b);            Console.ReadKey();        }    }}[/color][img]http://common.cnblogs.com/images/copycode.gif[/img]

到此为止,所以的工作已经完成,下面看一下结果:
[img]http://pic002.cnblogs.com/images/2012/357342/2012110520412960.jpg[/img]
到此为止,C#与Fortran编程的小示例就已经完成了。


总结:

本文主要演示了如何使用C#调用Fortran的DLL来实现相关的计算工作。并主要讲了C#调用时应该注意的事项。在工程计算中如果对于精度要求较高,计算较复杂时,我们便可以考虑通过C#与Fortran的混合编程来达到所需的要求。本文是基于本地调用Fortran DLL,下一篇将讲解基于Web调用Fortran DLL.

关于C#与Fortran混合编程还可参加这篇文章:[url=http://www.iepi.com.cn/BBS_CN/forum.php?mod=viewthread&tid=62&extra=page%3D1][color=#0066cc]http://www.iepi.com.cn/BBS_CN/forum.php?mod=viewthread&tid=62&extra=page%3D1[/color][/url]
[color=#0066cc][/color]
[color=#0066cc][/color]

页: [1]

Powered by Discuz! Archiver 7.0.0  © 2001-2009 Comsenz Inc.