標籤:
前言
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代碼:
1 DOUBLE PRECISION FUNCTION ADD(A,B) 2 !DEC$ ATTRIBUTES DLLEXPORT::ADD 3 !DEC$ ATTRIBUTES STDCALL,ALIAS:‘Add‘::ADD 4 DOUBLE PRECISION:: A,B 5 ADD=A+B 6 END 7 8 FUNCTION SORTANDFINDMAX(ARRAY,LENGTH) 9 !DEC$ ATTRIBUTES DLLEXPORT::SORTANDFINDMAX10 !DEC$ ATTRIBUTES STDCALL,ALIAS:‘Sortandfindmax‘::SORTANDFINDMAX11 DOUBLE PRECISION ::ARRAY(LENGTH)12 INTEGER::I,J13 DOUBLE PRECISION::SORTANDFINDMAX,TEMP14 SORTANDFINDMAX=ARRAY(1)15 DO I=1,LENGTH-116 DO J=I+1,LENGTH17 IF(ARRAY(I).GT.ARRAY(J)) THEN18 TEMP=ARRAY(I)19 ARRAY(I)=ARRAY(J)20 ARRAY(J)=TEMP21 SORTANDFINDMAX=ARRAY(J)22 END IF23 END DO24 END DO25 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函數:
1 DOUBLE PRECISION FUNCTION ADD(A, B)2 3 !DEC$ ATTRIBUTES DLLEXPORT:: ADD4 5 DOUBLE PRECISION A,B 6 7 ADD =A+B8 9 END
對應的C#聲明為:
[DllImport("MathDll")]private static extern double ADD (double A, double B);
使用ALIAS修改後的定義如下:
1 Double Precision Function ADD (A, B) 2 3 !DEC$ ATTRIBUTES DLLEXPORT:: ADD 4 5 !DEC$ ATTRIBUTES ALIAS:‘Add‘ :: Add 6 7 Double Precision A,B 8 9 Add =A+B10 11 End
對應的C#聲明為:
[DllImport("MathDll")]private static extern double Add (double A, double B);
而在C#中提供的解決方案是:通過使用Dlllmport的EntryPoint屬性指定匯出的Fortran函數名。
例如:
1 Double Precision Function ADD(A, B)2 3 !DEC$ ATTRIBUTES DLLEXPORT:: ADD4 5 DOUBLE PRECISION A,B 6 7 ADD =A+B8 9 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。
代碼如下:
1 using System; 2 using System.Text; 3 using System.Runtime.InteropServices; 4 5 namespace MixedProgram 6 { 7 public static class FortranMethod 8 { 9 [DllImport("TestDll.dll",CallingConvention = CallingConvention.Cdecl)]10 public static extern double Add(double a, double b);11 12 [DllImport("TestDll.dll", CallingConvention = CallingConvention.Cdecl)]13 public static extern double Sortandfindmax(double[] array, int length);14 }15 }
關於C#調用注意的事項在上面已說明,在此不再討論。
然後在Main函數中測試我們的Fortran DLL。
範例程式碼如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 5 namespace MixedProgram 6 { 7 class Program 8 { 9 static void Main(string[] args)10 {11 Console.WriteLine("請輸入兩個數相加:");12 double num1=Convert.ToDouble(Console.ReadLine());13 14 double num2 = Convert.ToDouble(Console.ReadLine());15 Console.WriteLine("輸入的兩個數是:" + num1 + " ," + num2);16 double sum = FortranMethod.Add(num1,num2);17 Console.WriteLine("求和結果是:"+sum);18 19 double[] Array = { 1,5,2,4,3,7,6};20 Console.WriteLine("初始數組:");21 for (int i = 0; i < Array.Length; i++)22 Console.Write(Array[i]+" ");23 24 double b=FortranMethod.Sortandfindmax(Array, Array.Length);25 Console.WriteLine("\n"+"排序後:");26 27 for (int i = 0; i < Array.Length; i++)28 Console.Write(Array[i]+" ");29 30 Console.WriteLine("\n"+"最大值為:");31 Console.WriteLine(b);32 33 Console.ReadKey();34 }35 }36 }
到此為止,所以的工作已經完成,下面看一下結果:
到此為止,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
本文轉自:http://www.cnblogs.com/potential/archive/2012/11/05/2755899.html
[轉載:]C#與Fortran混合編程之本地調用Fortran動態連結程式庫