前言
C#發展到現在,已是一門相當完善的語言,他基於C語言風格,演化於C++。並依靠強大的.NET底層框架。C#可以用來快速構建桌面及Web應用。然而在我們的實際工作中,盡管C#已經非常完善,但還是不能完成我們所有的工作。在很多工程計算中,C#語言的計算速度,精度,以及執行效率相對來說都達不到項目的要求。因此我們便考慮是否有一種方式將我們的工程計算部分和我們的項目分開,將計算部分用另一種執行更快,精度更高的語言來編寫,然后在C#中調用,最后完成我們的工作。答案是肯定的。
Fortran是一門古老的語言,它是世界上最早出現的計算機高級程序設計語言,廣泛應用於科學和工程計算領域。FORTRAN語言以其特有的功能在數值、科學和工程計算領域發揮着重要作用。然而Fortran程序本身不適合開發獨立的應用程序,例如我們傳統的桌面應用或者Web應用。因此這里我們便想將C#與Fortran結合,C#借助Fortran可以實現精度更高,計算更快的程序,而Fortran通過C#,便也能夠達到可視化設計。
一、基本思路
運用Fortran,編寫動態鏈接庫(DLL),在DLL中提供計算的函數接口,然后在C#中調用該DLL中計算部分的函數,實現計算過程。
這里需要注意的是,由於我們使用的是Fortran編譯器,生成的DLL屬於第三方非托管DLL,因此無法直接在程序中添加DLL的引用。具體的做法將在后續部分說明。
二、編寫Fortran程序,生成動態鏈接庫文件
知道思路之后便開始正式的Coding。首先新建一個空的Fortran Dynamic-link Library項目。
在Intel(R) Visual Fortran點擊Library,選中右圖的Dynamic-link Library.然后點擊OK.這時的項目如下所示:
點擊Sources File文件夾,選擇新建項。
添加一個新的Fortran文件
然后便開始Fortran代碼的編寫工作。這里我們主要實現兩個方法:
一個方法是求兩個數相加之和,並返回結果。
另一個是輸入一個數組,對這個數組進行排序,並找出最大值,最后返回排序后的結果,並返回最大值。
這里我們分別演示的是Fortran傳出一個數和一個數組有何不同。
關於Fortran的基本語法不是本文的討論范疇,請讀者自行查閱資料。
下面給出的上述我們要實現的功能的具體Fortran代碼:
DOUBLE PRECISION FUNCTION ADD(A,B) !DEC$ ATTRIBUTES DLLEXPORT::ADD !DEC$ ATTRIBUTES STDCALL,ALIAS:'Add'::ADD DOUBLE PRECISION:: A,B ADD=A+B END FUNCTION SORTANDFINDMAX(ARRAY,LENGTH) !DEC$ ATTRIBUTES DLLEXPORT::SORTANDFINDMAX !DEC$ ATTRIBUTES STDCALL,ALIAS:'Sortandfindmax'::SORTANDFINDMAX DOUBLE PRECISION ::ARRAY(LENGTH) INTEGER::I,J DOUBLE PRECISION::SORTANDFINDMAX,TEMP SORTANDFINDMAX=ARRAY(1) DO I=1,LENGTH-1 DO J=I+1,LENGTH IF(ARRAY(I).GT.ARRAY(J)) THEN TEMP=ARRAY(I) ARRAY(I)=ARRAY(J) ARRAY(J)=TEMP SORTANDFINDMAX=ARRAY(J) END IF END DO END DO END
上面我們聲明了兩個Fortran函數,一個是計算兩個數相加,一個是選擇排序並找出最大值。
之后我們點擊Visual Studio的Build Solution.開始編譯成DLL。
關於代碼段解釋:
!DEC$ ATTRIBUTES DLLEXPORT::ADD
!DEC$ ATTRIBUTES STDCALL,ALIAS:'Add'::ADD
這兩句代碼很關鍵。下面通過三個一致來簡單的說一下以上代碼段的意思和C#調用需要注意的問題。
1.函數名一致:
在Fortran編譯器中默認的導出函數名全部是大寫形式。而在C#中調用Fortran Dll時必須指定函數名一致。在Fortran方面解決的辦法是:使用ALIAS(別名)屬性指定導出函數名。
例如對於下面的Fortran函數:
DOUBLE PRECISION FUNCTION ADD(A, B) !DEC$ ATTRIBUTES DLLEXPORT:: ADD DOUBLE PRECISION A,B ADD =A+B END
對應的C#聲明為:
[DllImport("MathDll")] private static extern double ADD (double A, double B);
使用ALIAS修改后的定義如下:
Double Precision Function ADD (A, B) !DEC$ ATTRIBUTES DLLEXPORT:: ADD !DEC$ ATTRIBUTES ALIAS:'Add' :: Add Double Precision A,B Add =A+B End
對應的C#聲明為:
[DllImport("MathDll")] private static extern double Add (double A, double B);
而在C#中提供的解決方案是:通過使用Dlllmport的EntryPoint屬性指定導出的Fortran函數名。
例如:
Double Precision Function ADD(A, B) !DEC$ ATTRIBUTES DLLEXPORT:: ADD DOUBLE PRECISION A,B ADD =A+B END
對應的C#聲明為:
[DllImport("MathDll",EntryPoint = " ADD ")] private static extern double Plus(double A, double B);
此外,還可以使用 .NET Framework提供的dumpbin.exe工具查看DLL導出的函數名稱。
A. 在開始菜單中打開Microsoft Visual Studio 2010/Visual Studio Tools/ Visual Studio 2010 命令提示。
B. 在命令提示窗體中將路徑指向編譯生成.dll文件的路徑,然后輸入以下命令:
dumpbin /exports FileName.dll
即可查看當前目錄下FileName.dIl中導出的所有函數信息。
2. 堆棧管理一致
堆棧管理約定包括:在調用過程中子例程接受參數的數目和順序,調用完成后由哪一方來清理堆棧等。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("FileName.dll", CallingConvention = CallingConvention.StdCall)] [DllImport("FileName.dll", CallingConvention = CallingConvention.Cdecl)]
只有當Fortran程序和C#程序的堆棧管理一致時,才能保證正常的調用。
3.參數類型保持一致
在Fortran中常用的數據參數類型有:
REAL:表示浮點數據類型,即小數,等價於C#的float,
INTEGER:表示整數類型,相當於C#的int數據類型
DOUBLE PRECISION:表示雙精度數據類型,相當於C#的double數據類型。
在C#調用Fortran DLL是必須保證參數的一致性,例如在Fortran中變量定義的是REAL類型,而我們傳入的是Double,那么就會出現計算錯誤。
三、編寫C#代碼調用Fortran DLL
C#調用的Fortran的過程很簡單,只需要注意上述說的幾個問題即可。
這里我們先新建一個控制台應用程序:
然后將我們編譯的Fortran項目所生成的DLL拷貝到控制台應用程序的Debug文件夾下。
接着我們添加一個類:FortranMethod.cs
該類用來調用Fortran DLL。
代碼如下:
using System; using System.Text; using System.Runtime.InteropServices; namespace MixedProgram { public static class FortranMethod { [DllImport("TestDll.dll",CallingConvention = CallingConvention.Cdecl)] public static extern double Add(double a, double b); [DllImport("TestDll.dll", CallingConvention = CallingConvention.Cdecl)] public static extern double Sortandfindmax(double[] array, int length); } }
關於C#調用注意的事項在上面已說明,在此不再討論。
然后在Main函數中測試我們的Fortran DLL。
示例代碼如下:
using System; using System.Collections.Generic; using System.Text; namespace MixedProgram { class Program { static void Main(string[] args) { Console.WriteLine("請輸入兩個數相加:"); double num1=Convert.ToDouble(Console.ReadLine()); double num2 = Convert.ToDouble(Console.ReadLine()); Console.WriteLine("輸入的兩個數是:" + num1 + " ," + num2); double sum = FortranMethod.Add(num1,num2); Console.WriteLine("求和結果是:"+sum); double[] Array = { 1,5,2,4,3,7,6}; Console.WriteLine("初始數組:"); for (int i = 0; i < Array.Length; i++) Console.Write(Array[i]+" "); double b=FortranMethod.Sortandfindmax(Array, Array.Length); Console.WriteLine("\n"+"排序后:"); for (int i = 0; i < Array.Length; i++) Console.Write(Array[i]+" "); Console.WriteLine("\n"+"最大值為:"); Console.WriteLine(b); Console.ReadKey(); } } }
到此為止,所以的工作已經完成,下面看一下結果:
到此為止,C#與Fortran編程的小示例就已經完成了。
總結:
本文主要演示了如何使用C#調用Fortran的DLL來實現相關的計算工作。並主要講了C#調用時應該注意的事項。在工程計算中如果對於精度要求較高,計算較復雜時,我們便可以考慮通過C#與Fortran的混合編程來達到所需的要求。本文是基於本地調用Fortran DLL,下一篇將講解基於Web調用Fortran DLL.
關於C#與Fortran混合編程還可參加這篇文章:http://www.iepi.com.cn/BBS_CN/forum.php?mod=viewthread&tid=62&extra=page%3D1
(版權所有,轉載請標明出處)