FORTRAN和C#混合編程

  摘要:

          Fortran 語言是目前流行較廣的使用與科學計算的程序開發語言,但是它的圖形界面開發功能較弱.Visual C#是新一代面向對象開發語言,擅長於圖形界面系統開發.因此,在開發交互式解釋系統軟件時,可以採用C#與Fortran混合開發,這樣可以發揮C# 的高效開發特點,並且使得現有的經典Fortran 計算程序可以得到充分利用,從而避免資源的浪費.本文主要從混合編程的實現方面闡述了一些常用的方法和要點.

關鍵字: Fortran; C#; 混合編程; 動態鏈接庫

 

 1 引言

       所謂混合編程,是指利用兩種或兩種以上的程序設計語言組合起來的程序設計開發,彼此互相調用,進行參數傳遞,共享數據結構或數據信息,從而形成一個統一的程序實體的過程.利用Fortran 語言和C# 語言進行的混合編程的目的是爲了能夠更充分的發揮兩種語言各自的特點.Fortran是比較底層的數值計算語言,因此計算速度較高層的程序設計語言要快,且其自身就擁有非常強大的程序集和計算類的數據結構,因此非常適合科學計算.C# 是最近幾年纔出現的一門新型的界面開發語言,擁有十分強大的界面開發平臺,並且其最大的特點就是快速開發; C# 程序設計以C 和C++ 入門, 在保留C++ 設計的靈活性的基礎上, 加入了VB的快速開發.總體上, C#特點爲 Easy to learn and easy to use, 因此十分的適合圖形界面的交互系統開發.本文針對的是Compaq Visual Fortran 6.5版本的Fortran 和VS 2005 / VS 2008 版本的混合編程開發.

 2 混合編程的技術關鍵

       混合編程時,要注意調用程序與被調用程序遵守相同的規則,包括語言約定的一致性和數據處理的相容性等接口問題.

       2.1 語言約定一致

       <1> 命名約定.相互匹配的標識符在編程過程中應處理成一致的,因爲不同語言的編譯器智能機械的按本語言的命名約定進行處理.由於C# 語言的符號名需區分大小寫,Fortran 語言中不存在大小寫問題,如果處理不一致則導致程序連接失敗.Fortran的缺省方式使符號名在OBJ文件中變成大寫,如果在C# 中調用一個使用Fortran 缺省的子例程時,在C# 中需用一個純大寫的名稱來生成一個調用.

    public static extrern void FORDLL ( ) ;   // 將子例程名聲明爲純大寫的形式

如果想在C#中使用小寫聲明Fortran中的子例程,則應該在Fortran程序中需用C 和STDCALL 屬性將所有名稱轉換爲純小寫的形式; 若是在C# 中調用一個例程以大小寫混合形式出現的時候,需要使用Fortran 的ALIAS 屬性來解決混合形式之間的命名衝突.

  !DEC$ ATTRIBUTES ALIAS: "Fsort" :: Fsort      ! 限定子例程名爲Fsort的混合書寫形式

        <2> 調用約定. 混合編程中的調用約定,決定了程序對過程如何調用,如何傳遞參數等,可以從三個方面來考慮

         首先,調用例程利用調用約定決定傳遞給另一個例程的自變量順序;

         其次,被調用的例程利用調用約定決定接收傳遞過來的參數的順序;

         最後,所有涉及堆棧這樣一種數據結構的參數從堆棧中移去後,調用例程和被調用例程
              必須在調整堆棧的職責上取得一致.
C# 和Fortran 都可以使用C 屬性或STDCALL 屬性作爲調用約定,參數的傳遞方式爲從左到右傳遞.

        <3> 參數傳遞約定.只有以同樣的方式發送和接收參數,才能獲得正確的數據傳送和正確的程序結果.常見的參數傳遞爲值傳遞和引用傳遞兩種.C# 默認的是值傳遞,而Fortran 默認的是引用傳遞,因此在混合編程過程中應注意保持傳遞方式的一致性.

        若統一爲應用傳遞類型,則將C# 的參數類型定義爲引用類型,此時需使用ref 關鍵字
   public static extrern void FORDLL(ref int m);//通過ref將整形參數m被定義爲引用傳遞 

        若統一爲值傳遞類型,則將Fortran 的參數定義爲值類型,此時使用VALUE 關鍵字

   !DEC$ ATTRIBUTES VALUE :: m                  ! 使用VALUE將m定義爲值傳遞方式      

        若傳遞過程中既有值類型也有引用類型,則爲上面的兩種綜合使用,Fortran 中使用引用爲REFERENCE.

   !DEC$ ATTRIBUTES REFERENCE :: m              ! 使用REFERENCEE將m定義爲引用傳遞方式

   2.2 數據類型一致
       數據傳遞也就是涉及基本的數值傳遞和字符串的傳遞,其中數值傳遞又包括單值型數據和數組型的數據.

              <1> 數值傳遞滿足好引用類型就基本可以實現,但是在傳遞數組的時候要特別注意: C# 的數組是從0開始記錄的,而Fortran則是從1開始記錄;另外,C# 採用的是按行存放,而Fortran 則爲按列存放.在遞時應詳細考慮數組問題.

           例如在Fortran中的A[2][3]數組,在C# 中就應爲A[2][3].

           並且數組傳遞還應注意,只需要傳遞數組的首地址即可,DLL需要的是數組的起始位置.

                            int[] bb=new int[5];

                            FORDLL(ref bb[0]);

             <2> 字符串傳遞. 傳遞和返回字符串是混合編程中最爲複雜,也是需要考慮問題最多的部分.C#和Fortran的字符串傳遞同樣麻煩,首先,C# 中的字符串是雙字節的Unicode類型,而Fortran中默認的是單字節的Ansi類型.這樣產生的問題是C# 會自動把Fortran 的每兩個字符合併爲一個字符,Fortran 中的128 個字符的字符串變成了C#中的64個字符的垃圾.另外,C# 和Fortran 關於字符串的表示方式也有所不同: C# 的字符串表示方式與C 語言相同,使用/0表示字符串的結束;Fortran中則採用在最右端添加空格表示,並在最右端使用一個隱藏的參數表示實際的長度,因此要正確的傳遞字符串,應該解決如何正確表達自付串長度的問題.

解決方案1:

原理很簡單,就是避免直接的進行不同類型的字符串傳遞
換用數組傳遞,具體操作流程如下:
C#中的字符串傳向FORTRAN
step1: 在C#中將字符串分割爲字符數組
step2: 再將字符數組轉爲ASCII碼數組,然後傳遞給FORTRAN
step3: 在FORTRAN中利用CHAR()函數將ASCII碼還原爲字符串即可
NOTE: FORTRAN傳向C#也是可以採用相同的思路進行

Applied Example
Example Codes:
---------------------------------------------------------------------
//C#中的字符串分割,並轉存爲ASCII碼數組
            string c = "abcdefg";          
            ASCIIEncoding ascii = new ASCIIEncoding();
            int[] num = new int[c.Length];
            for (int i = 0; i < c.Length; i++)
            {
                num[i] = (int)ascii.GetBytes(c)[i];
               
            }
            
            int m = c.Length;
            S(ref num[0],ref m);
---------------------------------------------------------------------
//C#調用DLL聲明及操作
[DllImport("DLL TEST.dll", SetLastError = true, CharSet = CharSet.Unicode, 
                           CallingConvention = CallingConvention.StdCall)]
public static extern void S(ref int ka, ref int m);

//參數說明: ka爲ASCII碼數組名; m爲ka數組大小(即字符串長度)
----------------------------------------------------------------------
!FORTRAN中還原操作
subroutine s(ka,m)
 !dec$ attributes dllexport ::s
 character(m)::str
 dimension ka(m)
 integer i
 integer x,y
 do i=1,m
   str(i:i)=char(ka(i))
 enddo 
end
-----------------------------------------------------------------------

解決方案2:

根據Compaq Visual Fortran Version 6.6 隨機幫助文件中的Programmer's Guide->Creating Fortran DLLs &->Programming with Mixed Languages 和Language Reference->A to Z Reference->A to B->ATTRIBUTES 等相關部分的內容做出的一個測試例程,用來說明C#調用Fortran DLL過程中參數傳入Fortran的實現(只涉及到了數值類型和字符及字符串類型,其它類型讀者可以繼續到幫助中閱讀相關資料)。

!FortranDLL.f90文件的內容如下:
!用來建立DLL項目
!函數WTSTR(Str)用來建立Str文件並在該文件裏記錄Str字符串的內容
Subroutine writestr(str)
!DEC$ ATTRIBUTES DLLEXPORT,ALIAS:'WTSTR':: Writestr
Character*(*) str
Open(1,file=str)
Write(1,*) str  
End subroutine writestr
!
!函數Iadd2(A,B)用來就算兩個整型數A、B的和並將結果輸出到屏幕
Subroutine iadd2(a,b)
  !DEC$ ATTRIBUTES C, DLLEXPORT,ALIAS:'Iadd2'::Iadd2
  integer::a,b
  integer::sum
  sum=a+b
  write(*,*)"The sum:",sum,"in DLL iadd2"
End subroutine iadd2
!上述Iadd2函數中用來C屬性字段,是用來說明參數是按值來傳送的,而在WTSTR函數中因爲是
!用來傳遞字符類型的,所以不能用“C”。(If C or STDCALL is specified for a subprogram, 
!arguments (except for arrays and characters) are passed by value. )


//C#中的調用代碼如下:
//這部分代碼將編譯生成.EXE可執行程序,調用上面的DLL
using System;
using System.Runtime.InteropServices;
namespace CCallDll
{
    class Program
    {
        [DllImport("dlltest.dll")] //WTSTR函數說明部分,注意此處字符串參數的傳遞
        public static extern void WTSTR(String str, int strlength);
        [DllImport("dlltest.dll")] //Iadd2函數說明部分
                                   //兩種類型的參數傳遞涉及到在Fortran程序中的處理也不一樣
        public static extern void Iadd2(int a, int b);
        //主程序入口
        static void Main(string[] args)
        {
            //Delcare the String Variable. Notice:it's Unicode 
            String unicodeString = "This.txt";
            Console.WriteLine("Original string is      : {0}", unicodeString );
            Console.WriteLine("The length of the string: {0}",  unicodeString .Length);
            //Call the Function of WTSTR( ,) 此處增加了一個表示字符串長度的參數是因爲
            //Fortran、和C#存貯字符串的方法不一樣(詳情查閱相關資料,有很多介紹)
            WTSTR(unicodeString, unicodeString.Length ); 
            Iadd2(1000, 10);
        }
    }
}

 

 3 混合編程的基本步驟

 3.1 創建Fortran DLL

     step1: 進入到CVF 6.5 集成環境下,依次打開File|New|Fortran Dynamic Link Library,爲
            新的動態庫命名 如:FORTRAN.DLL

     step2: 使用Ctrl+N 快捷方式,添加文件FreeFormat的*.f90 子程序作爲當前工作空間.

     step3: Fortran建立動態鏈接庫是使用子函數形式的,我們推薦是用Subroutine而拒絕 
            function.同時要記得使用編譯爲DLL的註釋性命名:

                !DEC$ ATTRIBUTES DLLEXPORT :: FORDLL

     step4: 當完成DLL的程序設計以後,編譯生成DLL文件.進入工程文件中的Bin/Debug目錄,便
            能找到該庫文件.

 3.2 C# 調用Fortran DLL 的過程

    準備工作:

    (1) 將Fortran 生成的DLL文件拷貝至C# 工程文件的bin/Debug目錄下,目的是確保可執行程
        序與庫文件必須在同一文件夾中,便於Windows自動查找DLL文件.

    (2) 在進行DllImport 連接之前,需要在C# 中增加對動態連接庫操作的類的引用:

          using System.Runtime.Interopservices; 

         完成相關的連接設置就可以在C# 程序中使用Fortran DLL中的子例程了.

         C# 調用動態鏈接庫的參數設置

         使用DllImport 屬性來實現.然後使用一個實例來裝載傳遞過來的子例程和相應的參數.

         Example:        

         [DllImport("FORTRAN.DLL",SetLastError=True,CharSet=CharSet.Unicode,

          CallingConvention=CallingConvention.StdCall)]

         public static extern void FORDLL()

  DllImport的命名參數:       

 a. CallingConvention    指示入口點的調用約定.如果未有指定,則使用默認值:  
                          CallingConvention.Winapi

 b. CharSet              指示用在入口點中字符集,默認爲CharSet.Auto

 c. EntryPoint            給出DLL中入口點的名稱.若未指定,則使用方法本身的名稱

 d. ExactSpelling         指示EntryPoint是否必須與指示的入口點的拼寫完全匹配.默認爲
                          False

 e. PreserveSig           指示方法的簽名被保留還是被轉換.當簽名被轉換時,它被轉換爲一
                          個具有HRESULT返回值和該返回該值的一個名爲retval 的附加輸出 
                          參數的簽名.默認值爲True

 f. SetLastError          指示方法是否保留Win32 "上一錯誤".默認爲False

   FORDLL()修飾符說明:
       
 public      用來說明這個函數是公用的,可以在程序中的其它地方訪問它;

 static       則表示這個函數是靜態的,即C# 不會在調用的時候不會出入除參數以外的其它信 
              息;

 extern     則表示這個函數由程序以外的模塊實現;

 void        代表函數的返回類型.

 

參考文獻:

[1] 林國勇, 董  洵, 吳婉凡. 在.NET平臺下C#和Fortran的混合語言編程.

[2] http://bbs.pfan.cn/post-276118.html

[3] 肖曉玲,盧正鼎, 張  翔. VC與Fortran混合編程及其在大地電磁測深中的應用. 物探化探
    技術, 2000.2, 22(1):82~85

[4] 周振紅, 宋宇偉, 郭恆亮等. Visual Fortran 基於Win32 DLL的混合編程技術. 鄭州大學學
    報(工學版),2003.9,24(3): 10~13

[5] 袁  平. 多語言混合編程的技術關鍵及方法. 電腦與信息技術,19
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章