2009年11月10日 星期二

Powerbulder 呼叫 Windows API 要注意的地方-字串的傳遞

Windows API 函式(以下簡稱API)

PowerBuilder(以下簡稱PB,以下範例使用PB6,PB10因為有UNICODE編碼關係會比較麻煩在此不方便做DEMO)的特性大部分用的人都知道,其缺點就是UI介面太單調、低階操作完全不行(我是指位元組變數運算操作還有I/O控制),但是還好它支援API的呼叫使用。

但是另一個問題又來了,API的呼叫很多參數都牽涉到位元組處理還有定址等太多問題,所以本篇僅針對字串傳遞的問題發響。

關於API傳遞字串,其實會使用的人應該都會注意到一件事情,就是API要求字串傳遞時,通常都是以傳址方式處理,而且通常接下來一個參數就是字串長度的值。

舉個例子,例如記憶體拷貝的指令 RtlMoveMemory,在MSDN上的定義:
VOID 
  RtlMoveMemory(
    IN VOID UNALIGNED  *Destination,
    IN CONST VOID UNALIGNED  *Source,
    IN SIZE_T  Length
    )


可以知道這個函式可以將 Source 拷貝指定長度 length 到 Destination 的位置上;因此如果你想要利用它來複製字串,則你必須在PB的External Function定義:

subroutine CopyMemory(ref String lpDest ,ref String lpSource , Long cbCopy) library "kernel32.dll" alias for "RtlMoveMemory"

然後程式的寫法可能就是:

String ls_source , ls_destination
Long ll_len

ls_source = '12345678'
ll_len = Len(ls_cource)
ls_destination = space(ll_len)
CopyMemory(ls_destination , ls_source , ll_len)

因此你可以發現其執行結果同等如下的陳數句

ls_destination = ls_source

其中 ls_source先給予一段字串 '12345678'
然後由 CopyMemory 將 ls_source 內容複製 ll_len 指定位元組長度到 ls_destination

為什麼複製一個字串而已,還要指定長度?

那是因為在高階程式語言中,當指定字串內容時,通常AP層(這裡指的是PB底層)會向OS(指windows)要求配置一段符合長度的連續位址,並重新將字串變數的指標指向該位址,然後舊有的字串佔用的空間就會被AP層的回收機制給回收並釋放,所以看起來像是字串內容變更了,其實是重新變更了字串記憶體的空間指標,所以使用起來簡單,陳述句少,所以高階語言的特性就是如此,但是對於較低階的位元組操作就會變得不順,也不易理解。

要注意的是,當AP向OS要求配置記憶體時,OS本身也會紀錄該AP使用了哪些記憶體空間,這樣做是為防止當AP關閉或當掉後,OS才能知道要將哪些記憶體空間一並釋放掉。

而API本來就並非原本AP的函式,屬於外部函式,為了保持其獨立性與彈性,因此不會跟AP友直接的關連,如果外部函式擅自向OS要求配置記憶體空間給回傳變數值,這樣的結果可能會導致AP層根本不知道外部函式配置了多少記憶體給它,因此會無法正常由回收機制釋放該記憶體空間,而Windows也無法得知該配給的記憶體空間到底是誰在使用是API?還是AP?或是還有其他第三方AP?,因此在AP結束程式或API執行結束後,OS也無法釋放該記憶體,這樣的結果就導致記憶體被閒置佔用,系統主記憶體空間就越來越少。

因此為了考量記憶體管理的效能,所以API本身設計上就盡量不會替傳來的參數重新配置記憶體空間,以免發生問題。

那下面這段的用意為何?

ls_destination = space(ll_len)

由於API在對指定回傳的參數,尤其是文字字串型 ,通常都是傳址的型態,由於不會配置記憶體空間,因此對於目的字串的資料填入,是採用直接寫入的方式,因此在傳遞給API處理以前一定要先配好足夠空間。

如果你的目的字串沒有預先給予空間的話,會導致API將資料寫入非配給記憶體空間,而這個非配給記憶體空間,若不巧裡面的資料是屬於執行程式碼的部份,很可能導致執行程式碼錯誤而發生意外終止,因此你應該最常看到的windows訊息為:記憶體XXXXXX位置不可為Read或Write之類的訊息。

嚴重一點的可能導致當機。

因此如果你要操作API,儘可能注意API本身要求的條件,否則程式容易當掉,尤其我看過很多寫VB的工程師,常犯此錯誤,可能的原因在於,VB本身陳述程式編輯向來沒有人會將它設定成嚴謹模式,二來過於不嚴謹的程式撰寫會導致程式設計師本身對於呼叫偏向C/C++/C#提供的元件時,忽略了其運作原理,未正確的操作,導致過多的程式不穩定狀況。

2 個回應:

匿名 提到...

試了快一個禮拜已經快沒動力的時候
剛好看到這篇文章,馬上去試,結果成功了!
真的是太棒了>0< 開心到要落淚了~呵呵~
謝謝您~~~~真是個好部落格^^讚!

WILDOX 提到...

不必客氣,我也是為了要教給接手的工程師寫的文件,有幫忙到你就太好了。