程式整合技術應用於水稻田平衡模擬系統

林柏璋 周乃昉 鄭子璉
農委會林業處水利課 成功大學水利系 成功大學水利系
技士 副教授 研究助理

摘要

  一個大型的電腦分析模式,往往包含由不同研究機構人員所分別發展的程式模組,在土木水利工程中過去所發展協助分析的程式語言多為福傳 (Fortran) ,利用現成分析計算模組以快速完成設計及分析是工程師應具備的基本技能。

  在水稻田生態保護計畫中,對水稻田之調蓄洪水及地下水入滲補注功能之評估分析工作,由台灣大學農業工程研究所、中央大學土木工程研究所、中興大學土木工程研究所、成功大學水利及海洋工程研究所及屏東科技大學土木工程研究所等多所大學共同執行,各單位在發展計算分析模組時,均以研究人員本身所熟悉之程式語言撰寫,惟受限於各程式語言不易在所採用之工作平台上直接互相呼叫使用,使得各研究單位所發展之分析模組均難以整合為一整體計算系統。

  本研究以 32 位元視窗環境 (Win32) 為工作平台,同時採用 Visual Basic 與 Visual Fortran 編譯器,運用 Visual Basic 發展視窗環境使用者操作介面, Visual Fortran 發展計算模組,各合作之研究單位僅需針對分析主題部分進行模擬計算,配合本研究所發展之計算整合模組建立整體之分析計算系統。

  在本研究中以水稻田區之水文過程為例,運用程式整合技術,將各研究單位所發展之計算分析模組結合為整體水田水平衡系統之模擬模式,並配合各項研究目的可進行超量灌溉、地下水補注估算及防洪效益評估等模擬計算。

關鍵字:Visual Basic 、 Visual Fortran 、程式整合

一、前言

  在過去土木水利工程當藉由電子計算機輔助分析時,多利用大型工作站執行運算,而過去在工作站上最有計算效率又容易學習的程式語言即為 Fortran 語言,因此在土木水利工程學界中, Fortran 語言不但擁有其它程式語言所無法比擬的歷史,亦擁有過去先進所留下的分析模組及數值方法等巨大資產,這些資產多可取得原始程式碼或免費的數值分析程式庫,且相關數值分析程式庫的計算速度及發展分析模組所需時間亦較短,因此至目前為止, Fortran 語言仍為土木水利工程師所經常採用之程式語言。

  隨著個人用之中央處理器速度的倍增,使用個人電腦進行土木水利工程輔助分析,已較過去便捷,在使用工作站進行電腦輔助分析,須先進行遠端簽入,且必須與多人同時分用工作站的資源,若工作站上同時進行計算之程序稍多,其所需計算時間甚至超出在個人電腦上單獨進行計算,因此大多數的土木水利工程電子計算機輔助分析,已多由個人電腦負責。

  在目前個人電腦中多安裝微軟公司所開發之圖形介面視窗環境的作業系統,而在 Fortran 開發環境中,目前配合開發圖形介面之部分多艱深難懂,開發圖形介面之軟體工程需利用 Windows API 基本函數自行連結,而其他程式語言在圖形介面開發方面,多已封裝成各種快速開發元件,可在短時間內完成圖形介面的開發,其中 Visual Basic 為 Windows 上發展最悠久的視覺化程式語言,且其開發環境使用容易,語言提供的整合除錯環境功能強大,在應用程式開發上,十分快捷容易學習,因此,利用 Visual Basic 開發圖形介面,取用 Fortran 既有資源,可快速完成應用程式的開發與分析,使土木水利工程人員能以最少的時間成本完成最大的工作效益。

  在水稻田生態保護計畫中,對水稻田之調蓄洪水及地下水入滲補注功能之評估分析工作,由台灣大學農業工程研究所、中央大學土木工程研究所、中興大學土木工程研究所、成功大學水利及海洋工程研究所及屏東科技大學土木工程研究所等多所大學共同執行,各單位在發展計算分析模組時,均以研究人員本身所熟悉之程式語言撰寫,惟受限於各程式語言不易在所採用之工作平台上直接互相呼叫使用,使得各研究單位所發展之分析模組均難以整合為一整體計算系統。研究中規劃採用 32 位元視窗環境為工作平台,同時採用 Visual Basic 與 Visual Fortran 編譯器,運用 Visual Basic 發展視窗環境使用者操作介面, Visual Fortran 發展計算模組,各合作之研究單位僅需針對分析主題部分進行模擬計算,配合本研究所發展之計算整合模組建立整體之分析計算系統。

  在本研究中以水稻田區之水文過程為例,運用程式整合技術,將各研究單位所發展之計算分析模組結合為整體水田水平衡系統之模擬模式,並配合各項研究目的可進行超量灌溉、地下水補注估算及防洪效益評估等模擬計算。

二、視窗資料動態交換

  在 Win32 的環境中,一般常使用四種方法來與其它程式溝通,使某一程式可控制其它應用軟體或傳遞資料:

1. 動態資料交換 (DDE) 。

2. 物件崁入技術 (OLE) 。

3. 動態連結程式庫 (DDL) 。

4. 應用程式控制 (Application Control) 。

  由於在 Fortran 中,使用動態資料交換與物件崁入技術為一個非常繁雜的軟體工程,因此一般均不建議使用,在本研究中,將探討動態連結程式庫與應用程式控制兩方法的執行技術。

  在採用動態連結程式庫時,由於動態連結程式庫為一函式庫,因此執行時與 Visual Basic 會在相同的處理程序中,若採用使用應用程式控制技術時,則執行時與 Visual Basic 不在相同的處理程序中。在相同處理程序中的優點是程式會依程式碼順序執行,但若在 Fortran 的動態連結程式庫中所需執行時間過久,因該處理程序無法對螢幕繼續維護,很容易讓使用程式的人員誤以為程式當掉;不在相同的處理程序中的優點是兩程式以多工方式執行,各擁有各的處理程序,各程式可同時進行,但可能主處理程序需要存取被呼叫的處理程序所產生的資料時,各自執行的結果是主處理程序無法等待讀取被呼叫的處理程序所產生的資料。

  若某函數執行所需時間在 5 至 10 秒內,採用動態連結程式庫可以降低程式維護成本,若某函數執行所需時間大於常人所習慣的中斷時間,採用應用程式控制技術較為合適。

三、動態連結程式庫

  微軟公司在視窗環境中提供動態連結程式庫,可以用作應用程式之間共享資料的交換、儲存場所。動態連結程式庫是一個包含若干函數的可執行模組,主要用來為應用程式模組提供服務,在視窗環境中的應用程式皆可呼叫這些函數來完成相對應的工作。因此動態連結程式庫具有節省記憶體與硬碟空間的優點,並可使各程式之間享有共用資料及硬體等優點。

  在程式整合技術中,使用動態連結程式庫為使兩不同語言所發展程式互相連結的最簡易步驟,若將 Fortran 中的函數或副程式等程序編譯成為動態連結程式庫,則在 Visual Basic 中只需依照函數原型宣告呼叫方式即可使用,但是反過來讓 Visual Basic 編譯成為動態連結程式庫,讓 Fortran 呼叫,則困難度大為增加,這是因為在 Visual Basic 所編譯而得的動態連結程式庫為一包含物件類別的程式庫,而在 Fortran 規格中對物件技術支援能力不足,因此在使用上困難度極高,故在本研究中並不考慮將 Visual Basic 編譯成動態連結程式庫供 Fortran 呼叫使用。本研究中所探討動態連結程式庫的相關技術亦適用於 Fortran 與 Fortran 程式在視窗環境中的使用。

3.1 呼叫程序的宣告

  透過動態連結程式庫執行程序,如同呼叫副程式或函數一般,因此動態連結程式庫的使用關鍵,就在於函數原型的宣告。在 Visual Basic 宣告一個動態連結程式庫的函數,語法如下:

[Public|Private] Declare Sub|Function name Lib "libname" [Alias "aliasname"] [([arglist])]

  而在 Visual Fortran 中宣告一個可接受外部程式呼叫的動態連結程式庫函數,語法如下:

Type Function|Subroutine name ([arglist])
!Dec$attributes dllexport[, stdcall][, alias:'aliasname'] :: name

  在這兩個語法中,中括號表示可省略,垂線表示二選一,其它比較特別項目的說明如下:

name = 函數或副程式名
aliasname = 函數或副程式別名
arglist = 呼叫函數或副程式時所需的引數清單
!Dec$ = Visual Fortran 編譯命令識別字,在 PowerStation 中為 !MS$

  在 Fortran 中若不宣告函數或副程式別名,則編譯成動態連結程式庫時會將程序名變更為 _name@n ,供其它程式呼叫引用,且 n 值為依據引數清單所需的位元組動態變更,若不熟悉引數清單所需的位元組,很容易造成在動態連結程式庫找不到程序進入點,因此,最好事先宣告函數或副程式別名與原函數或副程式名相同,如此在其它程式中呼叫使用才不容易錯亂。

  而在引數清單中所傳遞的引數資料型態在 Visual Basic 與 Visual Fortran 中必須相互呼應,在 Visual Basic 與 Visual Fortran 中的變數資料型態的分類,摘要簡列於表 1

3.2 呼叫說明與範例

3.2.1 無傳遞任何引數

  在引用 Fortran 的現有程式中,將 Program ProName 改成 Subroutine ProName 可能是最方便的方式了,在過去已完成的程式中,一般 Fortran 程式均以 Program 為程式一開始執行的主程序,因此將此程序變更為副程式後,該程序並不需要引數列,可輕易提供給 Visual Basic 使用,範例程式碼摘要如下:

  在 Visual Fortran 中
Subroutine ProName()
!Dec$attributes dllexport alias:'ProName' :: ProName
[略]
End Sub
  在 Visual Basic 中
Declare Sub ProName Lib "c:\TLCheng\For\DLLTest\DLLTest.DLL" ()
ProName  ' 執行 Fortran 副程式 ProName

3.2.2 傳遞數值

  在引用 Fortran 的現有函數或副程式中,傳遞數值的部分是比較容易處理的技術,傳遞數值引數僅需要對照其變數資料型態,依型態設定變數資料即可輕易達到變數的傳遞,範例程式碼摘要如下:

  在 Visual Fortran 中
Integer*4 Function FunName(ValueR8)
!Dec$attributes dllexport alias:' FunName ' :: FunName
Real*8 ValueR8
[略]
End Function
  在 Visual Basic 中
Declare Function FunName Lib "c:\TLCheng\For\DLLTest\DLLTest.DLL" (ValueR8 as Double) as Long

summy=FunName(1#)  ' 執行 Fortran 函數 FunName

3.2.3 傳遞數值陣列

   Visual Basic 在動態陣列上的使用,均比其他語言更容易使用,採用動態陣列的優點為節省記憶體,在使用陣列時才決定陣列的大小與維數,並可於不使用該陣列時,釋放該陣列所佔據的記憶體,較靜態陣列節省系統資源。過去由於動態陣列執行時較靜態陣列執行時所需時間為長,在選用陣列型態時需仔細評量,伴隨處理器執行效能的提高,此項缺點所造成的時間延遲已可忽視,範例特別採用動態陣列做為範本,當然亦可運用在靜態陣列上,而陣列在運用上一般不建議採用傳值呼叫,因此本例僅展示對傳址呼叫。

  由於 Fortran 函數或副程式並無法事先得知傳遞之陣列大小,因此在 Fortran 中陣列宣告採不定長度宣告,也就是陣列大小由父程序決定,在 Fortran 中則利用 LBOUND 及 UBOUND 函數取得陣列的上下標,範例程式碼摘要如下:

  在 Visual Fortran 中
Integer*4 Function FunName(ArrayI4)
!Dec$attributes dllexport alias:' FunName ' :: FunName
Integer*4 ArrayI4(*)
[略]
End Function
  在 Visual Basic 中
Declare Function FunName Lib "c:\TLCheng\For\DLLTest\DLLTest.DLL" (ArrayI4 as Long) as Long

Redim TestArray(1 to 100)
summy=FunName(TestArray(1))   ' 執行 Fortran 函數 FunName

3.2.4 傳遞字串

  在 Visual Basic 中,字串是採用雙位元方式處理,而根據不同的宣告,不定長度字串及固定長度字串在記憶體內排列方式亦不同,而在 Fortran 中,字串是採用單位元方式處理,其字串在記憶體中的處理方式又與 Visual Basic 不同,且只有固定長度字串,因此造成 Visual Basic 與 Fortran 傳遞字串引數的困擾。由於 Fortran 函數或副程式並無法事先得知傳遞之字串大小,因此在 Fortran 中字串宣告採不定長度宣告,也就是字串大小由父程序決定,在 Fortran 中則利用 LEN 函數取得字串的長度。在這部分範例程式碼與其他程序範例程式碼不同的是在 Fortran 中只有一個引數接收字串,而在 Visual Basic 利用傳值呼叫先傳遞字串內容後,再補上字串長度整數,使 Fortran 收到 Fortran 內定的字串型態,範例程式碼摘要如下:

  在 Visual Fortran 中
Integer*4 Function FunName(String)
!Dec$attributes dllexport alias:' FunName ' :: FunName
Character*(*) String
[略]
End Function
  在 Visual Basic 中
Declare Function FunName Lib "c:\TLCheng\For\DLLTest\DLLTest.DLL" (Byval forString as String, Byval LenString as Long) as Long

myPath="c:\TLCheng\For\DLLTest\"
summy=FunName(myPath, Len(myPath))   ' 執行 Fortran 函數 FunName

3.2.5 傳遞使用者自訂型態

  使用者自訂型態經常配合特殊需求使用,由於可以在一個變數中儲存不同性質的變量,因此已廣泛的運用在程式撰寫的技巧中。在 Visual Basic 中,缺少複數的變數型態,若要傳遞或取用 Fortran 中的複數資源,將無法直接使用,必須透過使用者自訂型態方式處理,對於字串陣列的傳遞,亦採用使用者自訂型態較易處理,範例程式碼摘要如下:

  在 Visual Fortran 中
Integer*4 Function FunName(myComplex)
!Dec$attributes dllexport alias:' FunName ' :: FunName
Complex*4 myComplex
[略]
End Function
  在 Visual Basic 中
Declare Function FunName Lib "c:\TLCheng\For\DLLTest\DLLTest.DLL" (myComplex as vbSingleComplex) as Long
Type vbSingleComplex
  Real as Single  ' 實部
  Imag as Single  ' 虛部
End Type

Dim myComplex as vbSingleComplex
summy=FunName(myComplex)   ' 執行 Fortran 函數 FunName

四、應用程式控制

  應用程式控制技術依處理方式可分成兩大項:

1. 參數控制

2. 視窗程式遙控

4.1 參數控制

  在各程式語言所發展的程式中,一般均可接收附加於程式執行檔後的參數,稱為命令列 (Command Line) ,對於簡單的函數或副程式可以僅以命令列來傳遞,對於需傳遞較複雜之參數可以透過檔案傳遞,利用命令列傳遞檔名。

  在 Fortran 程式中可預先加入當有命令列傳入時,所需處理的原則,以利用命令列傳遞參數檔名,並以開啟參數檔為範例,程式碼摘要如下:

  在 Visual Fortran 中
use dflib
integer*2 status2
character*256 MyBuffer
[略]
call getarg(1,MyBuffer,status2)
if (status2 .eq. -1) then ' 代表沒有命令列引數
else ' 代表有命令列引數
  open(1,file=MyBuffer(1:status2)) ' 開啟由命令列傳遞的參數檔名
end if
[略]
End

  在前面討論到若利用應用程式控制技術,則執行時將不在同一處理程序,若主處理程序需要被呼叫的處理程序所產生的資料,則主處理程序應等待被呼叫的處理程序所產生的資料完成後,主處理程序才繼續執行。

  要讓主處理程序進入等待被呼叫的處理程序完成,需分兩步驟執行:

1. 找出被呼叫的處理程序的視窗代碼 (hWnd);

  在 Visual Basic 中找出被呼叫的處理程序的視窗代碼一般均依照 Shell 函數所傳回之處理程序代碼判斷,由目前視窗環境內所有的視窗中,找出視窗代碼的處理程序代碼與 Shell 函數傳回之處理程序代碼相同的視窗,程式碼摘要如下:

  在 Visual Basic 中
Declare Function GetWindow Lib "user32" (ByVal hWnd As Long, ByVal wCmd As Long) As Long
Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As Long, lpdwProcessId As Long) As Long
Declare Function GetDesktopWindow Lib "user32" () As Long
Declare Function IsWindow Lib "user32" (ByVal hWnd As Long) As Long
Const GW_HWNDNEXT = 2
Const GW_CHILD = 5

Private Function GetWindowHandleLow(ByVal hWndStart As Long, hProcess As Long) As Long

Dim hWnd As Long, hWndReturn As Long, hProcessCur As Long

hWnd = GetWindow(hWndStart, GW_CHILD)
Do Until hWnd = 0
  GetWindowThreadProcessId hWnd, hProcessCur
  If hProcessCur = hProcess Then
    GetWindowHandleLow = hWnd
    Exit Function
  End If
  hWndReturn = GetWindowHandleLow(hWnd, hProcess)
  If hWndReturn <> 0 Then
    GetWindowHandleLow = hWndReturn
    Exit Function
  End If
  hWnd = GetWindow(hWnd, GW_HWNDNEXT)
Loop
GetWindowHandleLow = 0

End Function

Private Function GetWindowHandle(hProcess As Long) As Long

GetWindowHandle = GetWindowHandleLow(GetDesktopWindow, hProcess)

End Function

〔使用範例〕
' 步驟 1
hProcessProgram = Shell(RunFilename, WindowStyle)
hWnd = GetWindowHandle(hProcessProgram)

' 步驟 2
Do
  DoEvents
Loop Until IsWindow(hWnd) = 0

2. 判斷被呼叫的處理程序的視窗代碼是否還代表一個活動中的視窗;

  在被呼叫的處理程序在執行完畢時,要能自動關閉,當視窗關閉後,則視窗代碼代表一個不存在的視窗,此時主處理程序即可繼續執行。要讓 Visual Fortran 所編譯之程式能於執行完畢後自動結束,需設定結束後自動關閉:

status4 = SETEXITQQ (QWIN$EXITNOPERSIST)

  若要讓 MS-DOS 模式方式執行之程式能於執行完畢後自動結束,在 Visual Basic 中需以下面的方式啟動被呼叫的處理程序:

hProcessProgram = Shell("command.com /c " + RunFilename, WindowStyle)

4.2 視窗程式遙控

  在主處理程序啟動被呼叫的處理程序後,若不採用參數控制,亦可以程式模擬人工輸入方式輸入,其原理為將鍵盤訊號送至作用中的處理程序內,因此,除可控制被呼叫的處理程序外,亦可控制本身程式。此方式因為採用 Visual Basic 內建的 SendKeys 陳述式,所以有兩點限制:

1. 只能送出鍵盤訊號,因此遙控軟體執行以鍵盤操作部分為限;

2. 無法對 MS-DOS 模式視窗送出鍵盤訊號;

  以送出關閉視窗為範例,程式碼摘要如下:

  在 Visual Basic 中
hProcessProgram = Shell(RunFilename, WindowStyle)
AppActivate hProcessProgram, True  ' 讓 Shell 所執行的處理程序處於可輸入鍵盤的狀態
SendKeys "%{F4}", True  ' 送出鍵盤 Alt+F4 關閉視窗

五、應用程式整合技術於水稻田平衡模擬系統

在水稻田區之水文過程模擬中,計算模組除整合系統外,包含常水量的長期模擬模組、地下水入滲模擬模組、暴雨期間的水筒分析模擬模組、排水分析及淹水模擬模組等多個計算分析部分,各計算模組間利用共用記憶體或資料輸入輸出檔交換資料,系統結構圖如圖 1 ,整合成果如圖 2

六、結論與建議

6.1 結論

  1. 在本研究中所探究的視窗資料動態交換技術,有助於現有的計算分析模組之整合應用。
  2. 程序執行所需時間在常人所習慣接受的中斷時間內,採用動態連結程式庫可以降低程式維護成本,反之採用應用程式控制技術較為合適。
  3. 應用程式控制技術以參數控制搭配參數檔使用較易開發,可降低開發分析模組時間成本,且分析模組亦可建立純文字輸入輸出介面,可獨立執行分析計算。
  4. Visual Basic 在圖形介面開發快速,整合除錯環境功能強大,配合本研究所探討之相關技術,適合作為在視窗環境中,為既有的 Fortran 語言程式包裝。

6.2 建議

  1. 尚未開發的分析模組建議以開發圖形介面之程式語言為開發工具,以提高系統穩定性及相容度,已開發的分析模組建議採用本研究所得技術成果,以降低開發分析模組時間成本。
  2. 採用現有的分析模組時,建議以最小的修正量達到最大的相容度為主,避免大量修改造成時間成本的增加。
  3. Fortran 語言在數值計算上仍比 Visual Basic 語言為快,且 Fortran 語言已有大量現成之數值分析程式庫或原始碼,在考量應用程式的穩定度下,建議以 Visual Basic 為數值計算開發工具,在考量執行效能下,建議以 Fortran 開發數值計算模組,並以本研究探討之相關技術進行整合。
  4. 本研究中目前尚以單一處理程序為最小探討單位,建議可探討採單一執行緒時,利用多工同時分析以提高執行效能。

參考文獻

  1. Microsoft Corporation, "July 1999 release of MSDN Library", Online Book Level 1, 1999.7.
  2. Digital Equipment Corporation, "DIGITAL Visual Fortran Version 5.0", Online documentation, 1997.1.
  3. 鄭子璉、周乃昉,「福傳語言」,http://feitsui.hyd.ncku.edu.tw/TLCheng/Fortran/
  4. 瑩圃電腦軟體研究開發部,「Windows API 手冊」,瑩圃,民國 82 年 10 月。
  5. 瑩圃電腦軟體研究開發部,「Windows 3.x 程式設計進階」,瑩圃,民國 82 年 11 月。
  6. 黃逸萍、黃小萍,「Fortran 與視窗程式設計」,儒林,民國 86 年 3 月。

誌謝

  本研究承蒙農委會經費補助(計畫編號88農建-8.5-林-02)研究計畫之部分研究成果,謹致謝忱。研究進行期間,承蒙美商威能資訊台灣分公司廖建華先生及逢甲大學土木及水利研究所黃逸萍教授提供相關參考資料,在此致予誠摯的謝忱。

註釋

  為尊重智慧財產權,特將本文所提及之各項軟體、商標及所屬公司名稱列出,以示尊重。列出說明如下:

  在 Visual Fortran Programmer's Guide 內,所記載相關於 Visual Basic 與 Visual Fortran 間字串引數的傳遞,經實際測試並無法於 Visual Basic 5.0 中文版及 Visual Fortran 5.0 內執行。

表 1 Visual Basic 與 Visual Fortran 中的變數資料型態摘要

Fortran Basic 值域
BYTE Byte 0 到 255
INTEGER*1 -128 到 127
INTEGER*2 Integer -32,768 到 32,767
INTEGER*4 Long -2,147,483,648 到 2,147,483,647
REAL*4 Single ±3.402823E38 到 ±1.401298E-45
REAL*8 Double ±4.94065645841247E-324 到 ±1.79769313486232E308
Character*1 可以用 Byte 來傳遞,再用 Chr(Byte) 轉換
Character*(*) String 1 到大約 65,400 (固定長度字串)
COMPLEX*4 需改用使用者自訂型態傳遞
COMPLEX*8 需改用使用者自訂型態傳遞
所有LOGICAL Integer True 或 False,應搭配使用LOGICAL*2
Variant 本項目以下在 Fortran 中均無法識別
Date January 1, 100 到 December 31, 9999
Currency ±922,337,203,685,477.5808 之間

圖 1 田間水平衡整合系統架構示意圖

圖 2 田間水平衡整合系統執行成果圖