2012年8月17日 星期五

a problem Read & ReadEx in Powerbuilder 10

Powerbuilder 10 (含)以後的版本,檔案的讀取分為 Read 與 ReadEx 兩個指令,按照說明文件的用法 ReadEx 支援 StreamMode! 讀取每次可以超過 32767 個位元組,並且支援 TextMode! 的讀取模式,算是很好用的;但是這次的問題卻出在使用 LineMode! 開啟檔案 時 PB10 的 Read 這個指令上:

先看看這段程式碼哪裡出了問題,我有純文字一份文件 A.txt (ANSI編碼)內容為:
數據窗口揭秘:未公開的數據窗口事件 (作者:Mark Brown) 到目前為止,PB的數據窗口控件仍是PB眾多控件中功能最強大,最複雜的控件。 數據窗口固有的行為
然後利用下面這段程式碼讀取內容並顯示在 mle_1(MultiLine Editor)上面
integer li_fn string ls_str mle_1.text = "" li_fn = FileOpen("A.txt",LineMode!,Read!) if li_fn > 0 then //逐行讀取並顯示在mle_1上面 do while FileRead(li_fn , ls_str) >= 0 mle_1.text += ls_str +"~r~n" loop FileClose(li_fn) end if
mle_1物件上的顯示內容:
數據窗口揭秘:未公開的數據窗口事件 的數據窗口事件 事件 (作者:Mark Brown) ) 到目前為止,PB的數據窗口控件仍是PB眾多控件中功能最強大,最複雜的控件。 件中功能最強大,最複雜的控件。 複雜的控件。 鞳C 數據窗口固有的行為 的行為
發現了嗎?讀取出的內容怪怪的,幾乎每一行後段文字都重複被讀出,導致整個內容大亂。
但是,這段程式碼如果放在 PB6、PB7、PB8、PB9 執行,卻一點問題就沒有。

整個詭異到不行。

但是如果是英數文內容的檔案,不管哪個版本 PB6 or PB10 就完全不會有問題,測試檔案內容如下:

calculate the height of the element and divides that by two to center it the value is inversed so as to pull the window above the target the offset of 12 is added to account for the height of the field form element
後來,我把 Read 改成 ReadEx 指令後,在進行一次測試,發現無此問題:

integer li_fn string ls_str mle_1.text = "" li_fn = FileOpen("A.txt",LineMode!,Read!) if li_fn > 0 then //逐行讀取並顯示在mle_1上面 do while FileReadEx(li_fn , ls_str) >= 0 mle_1.text += ls_str +"~r~n" loop FileClose(li_fn) end if
看起來像是中文字造成衝碼問題,但仔細檢查被中斷文字前後都沒有所謂衝碼字元,以前PB6會有蚯蚓符號『~』的衝碼問題,PB10以後應該不會有此問題啊?

後來我注意到,讀出的內容有種巡迴式的重覆中斷現象,不像單純字碼衝碼的現象,且 ReadEx 指令是PB10以後的版本第一次出現的新指令,而 PB10 的特性是支援 Unicode ,所以就假設會不會是字元長度計算錯誤,由其比較明顯的是在 ANSI 編碼文字下,中文是 2Byte (半形)英數字僅 1Byte。
所以就用此假設試算一下 A.txt 的第一行:
數據窗口揭秘:未公開的數據窗口事件
此段文字不包含換換行字元共 34Byte 採用 ANSI 計算長度就是 34 ,
若採 unicode 算法(一個中文只會當成一個字)其實只得到長度 17,
假設 PB10 底層計算採用了 unicode 方式計算了該字串的長度 17 加上換行 2 個字元,
共 19 個字元
由於檔案是ANSI編碼,因此當 PB10 進行 File Seek 定位時會以 Byte 為單位計算到下一行起始字元的動作時,就會定位到該行『的』這個字,所以下一次 FileRead 時就會變成讀到『的數據窗口事件』這段文字。

得到以上結論以後,我更大膽架設,如果 A.txt 是 Unicode 編碼的話就不會有此現象,所以將 A.txt 用記事本開啟以後,另存新檔,並將編碼方式改成 Unicode ,重新以第一段程式碼讀取。

果然就不會出現此現象了。

為什麼純英數文字檔案不會有此問題? 那是因為純英數文字在ANSI編碼下一個字就是長度 1,而 Unicode 下也是一個字長度也是 1,這就是為什麼不會錯的問題。

尤其 PB10 以後的版本是以 unicode 為主體,所以長度計算指令 Lan(stringblob)算出來的是 unicode 長度,若要計算 ANSI 長度就要使用 LanA(Stringblob) 這個指令。

所以,在  PB10 的 Help 中關於 FileOpen 的說明有一段耐人尋味的描述:
FileRead and FileWrite have limitations on the amount of data that can be read or written and are maintained for backward compatibility.
也就是說為了保持相容性,對這些舊指令加了一些限制,也畢竟是外國人設計的軟體,通常開發者對其他雙字元集語系國家文字就可能沒有發現此類問題,否則就要看看Sybase是否還有出 EBF 可以修正這問題了。

在還沒有 EBF 可以修正以前,改用 ReadEx 指令會是比較正確的作法。

2012年8月10日 星期五

powerbuilder 的邏輯運算函數

powerbuilder 的位元運算能力幾乎同等『沒有』。

但是使用 windows API 又常常遇到許多變數需要邏輯運算過,但是看看目前的 PFC 的邏輯運算效能也不好。

因此突發奇想,把原本 PFC 的邏輯運算改一改,發現也蠻好用的,效能上也有改善。

Function名稱:f_bitoperator
回傳值:unsignedlong
傳遞參數:aul_s1 [unsignedlong][value]、aul_s2 [unsignedlong][value]、as_operator [string][value]

/* PFC簡易版兩組數字做邏輯運算 輸入來源:aul_s1 [uLong],aul_s2 [uLong],as_operator [String] as_operator為邏輯運算描述如:"and" , "or" , "xor" , "not"只需要第一組(aul_s1)輸入, aul_s2放0就可以了 返回:運算結果[Long],當參數錯誤返回NULL */ uLong ll_ret boolean lb_s1[32] , lb_s2[32] , lb_u integer li_bit as_operator = lower(trim(as_operator)) //參數檢查 if isnull(aul_s1) or isnull(aul_s2) or isnull(as_operator) then setnull(ll_ret) Return ll_ret end if if as_operator = "" then setnull(ll_ret) Return ll_ret end if //位元轉換 for li_bit = 32 to 1 step -1 if mod(aul_s1 , 2) = 1 then lb_s1[li_bit] = true else lb_s1[li_bit] = false end if aul_s1 = truncate(aul_s1 / 2 , 0) next if as_operator <> "not" then for li_bit = 32 to 1 step -1 if mod(aul_s2 , 2) = 1 then lb_s2[li_bit] = true else lb_s2[li_bit] = false end if aul_s2 = truncate(aul_s2 / 2 , 0) next end if //邏輯運算 ll_ret = 0 for li_bit = 32 to 1 step -1 choose case as_operator case "and" lb_u = lb_s1[li_bit] and lb_s2[li_bit] case "or" lb_u = lb_s1[li_bit] or lb_s2[li_bit] case "xor" lb_u = not (lb_s1[li_bit] or lb_s2[li_bit]) case "not" lb_u = not lb_s1[li_bit] end choose if lb_u then ll_ret += 2^(32 - li_bit) next Return ll_ret

雖然說可以用 VC++ 寫一個支援的 DLL 來作邏輯運算,但是殺雞用牛刀好像小題大作了。

其次,在某些特別環境下用 VC++ 寫的 DLL 要重新編譯過,例如:Pocket Powerbuilder 在做手持終端設備開發就不太方便了。

這次改的部份大致有兩點:一、邏輯儲存使用 Boolean 陣列,節省轉換與記憶體運算空間,二、這個 Function 就可以做到 AND、OR、XOR、NOT 四種運算了,使用上很方便。

另外,就是 in/out 使用 UnsignedLong 長達 32 bit 的運算,即使你是傳遞 integer、long 都沒問題,
負數也是可以邏輯運算的。 ^_^

2012年8月9日 星期四

匯入 EDI 檔案到 DataStore

在台灣寫程式常常會需要使用到其他公司的資料交換,而大部份的合作夥伴都很多都會使用EDI檔案傳遞,因此處理EDI檔案就成了家常便飯,但有時候又不想要遇到一個專案就要再寫一次,所以幾乎都將它轉成PFC來使用。

上一次提到了 EDI 轉出 Function,所以這次來看看轉入EDI檔案的 Function怎麼做吧。

Function 名稱:f_edi2ds
回傳值:Long
傳遞參數:ads_dest[datastore][refrence]、as_file[string][readonly]、as_error[string][refrence]

/* 將ED文字檔(as_file)I匯入到 DataStore(ads_dest) EDI切割模式依照 dataObject 的 column.limit 訂定的長度 */ Integer li_ColCount,li_Pos,li_Col,li_file Long ll_collimit[],ll_rc,ll_totl,ll_cc,ll_new String ls_colname[],ls_coltype[],ls_data,ls_val,ls_date,ls_time Datetime ldt_1 boolean lb_fin //檔案存在否? IF not FileExists(as_file) THEN Return -1 //DataStore存在否? IF isnull(ads_dest) OR not isValid(ads_dest) THEN Return -1 END IF //欄位存在否? li_ColCount = integer(ads_dest.Describe("datawindow.column.count")) IF li_ColCount <= 0 THEN Return -1 ll_totl = 0 //取得欄位屬性 FOR li_Col=1 TO li_ColCount ls_colname[li_Col] = ads_dest.Describe("#" + string(li_Col) + ".Name") ll_collimit[li_Col] = Integer(ads_dest.Describe("#" + string(li_Col) + ".Edit.Limit")) //合計欄位總長度 ll_totl += ll_collimit[li_Col] ls_coltype[li_Col] = lower(ads_dest.Describe("#" + string(li_Col) + ".ColType")) //排除括弧字元 li_Pos = Pos(ls_coltype[li_Col] , "(") IF li_Pos > 0 THEN ls_coltype[li_Col] = Left(ls_coltype[li_Col] , li_Pos - 1) END IF NEXT //開啟檔案(以LinMode讀取) li_file = FileOpen(as_file , LineMode! , Read! ,LockReadWrite!) IF li_file <= 0 THEN Return -2 ll_cc = 0 //逐行讀取 do while fileRead(li_file , ls_data) >= 0 //行數計數 ll_cc ++ //筆對長度 IF len(ls_data) < ll_totl THEN as_error += "第" + String(ll_cc) + "行長度過少:" + String(len(ls_data)) +& "<" + String(ll_totl) + "~r~n" Continue END IF //新增一筆ROW到DataStore ll_new = ads_dest.InsertRow(0) li_pos = 1 //定位歸0 lb_fin = true //錯誤標記歸0 FOR li_col = 1 TO li_ColCount //讀取指定位置範圍字串 ls_val = MidA(ls_data ,li_pos , ll_collimit[li_Col]) //依照欄位類型轉換 Choose Case ls_coltype[li_Col] Case "char" //文字 IF trim(ls_val) = "" THEN SetNull(ls_val) ads_dest.SetItem(ll_new , li_col , ls_val) Case "datetime" //日期時間 IF trim(ls_val) <> "" THEN ls_date = left(ls_val,4)+"/"+mid(ls_val,5,2)+"/"+mid(ls_val,7,2) IF not isDate(ls_date) THEN as_error += "第"+String(ll_cc)+"行,位置:"+String(li_pos)+",長度:"+& String(ll_collimit[li_Col])+"無法轉換Datetime~r~n" lb_fin = false EXIT END IF ls_time = left(mida(ls_val , 9)+"000000",6) ls_time = left(ls_time,2)+":"+mid(ls_time,3,2)+":" +mid(ls_time,5,2) IF not isTime(ls_time) THEN as_error += "第"+String(ll_cc)+"行,位置:"+String(li_pos)+",長度:"+& String(ll_collimit[li_Col])+"無法轉換Datetime~r~n" lb_fin = false EXIT END IF ldt_1 = Datetime(date(ls_date),time(ls_time)) ELSE SetNull(ldt_1) END IF ads_dest.SetItem(ll_new , li_col , ldt_1) Case "date" //日期 IF trim(ls_val) <> "" THEN ls_date = left(ls_val,4)+"/"+mid(ls_val,5,2)+"/"+mid(ls_val,7,2) IF not isDate(ls_date) THEN as_error += "第"+String(ll_cc)+"行,位置:"+String(li_pos)+",長度:"+& String(ll_collimit[li_Col])+"無法轉換Date~r~n" lb_fin = false EXIT END IF ELSE SetNull(ls_date) END IF ads_dest.SetItem(ll_new , li_col , Date(ls_date)) Case "time" //時間 IF trim(ls_val) <> "" THEN ls_time = left(ls_val+"000000",6) ls_time = left(ls_time,2)+":"+mid(ls_time,3,2)+":"+mid(ls_time,5,2) IF not isTime(ls_time) THEN as_error += "第"+String(ll_cc)+"行,位置:"+String(li_pos)+",長度:"+& String(ll_collimit[li_Col])+"無法轉換Time~r~n" lb_fin = false EXIT END IF ELSE SetNull(ls_time) END IF ads_dest.SetItem(ll_new , li_col , Time(ls_time)) Case "long" ,"ulong" //整數 IF trim(ls_val) = "" THEN SetNull(ls_val) END IF ads_dest.SetItem(ll_new , li_col , long(ls_val)) Case "decimal" //數值 IF trim(ls_val) = "" THEN SetNull(ls_val) END IF ads_dest.SetItem(ll_new , li_col , dec(ls_val)) Case "real" //浮點數 IF trim(ls_val) = "" THEN SetNull(ls_val) END IF ads_dest.SetItem(ll_new , li_col , real(ls_val)) End Choose //定位點移到下一個指定位置 li_pos = li_pos + ll_collimit[li_Col] NEXT //處理錯誤跳出,未完成者取消此筆ROW IF lb_fin = false THEN ads_dest.rowsDisCard(ll_new , ll_new , Primary!) END IF loop //關閉檔案 FileClose(li_file) //回傳處理行數 Return ll_cc
與之前轉出EDI作法有點不一樣的地方,是在於取得 DataObject 的 DataWindow 欄位屬性時,我直接使用了幾個陣列(ll_collimit[]、ls_colname[]、ls_coltype[])來儲存,上次是使用一個structure來儲存,結果是相同,寫法不同,可以提供一個比較參考。

其次,這次轉入EDI檔案時欄位長度的參考,我是設計在每個欄位的 Edit.Limit 上,所以這個Function 使用時只要設計好 DataWindow 的模型就很好用了。

其次,使用上需要特別注意的地方是[日期時間]、[日期]、[時間]的轉換方式,我的設計上是假設EDI上的欄位日期是西元年的 YYYYMMDDHHMMSS 的格式的,所以 Datetime
欄位的 Limit 可以設定 8、10、14、16碼的,怎麼說呢?

假設日期欄來源資料為 YYMMDDHHMM的話,後面SS會自動補上00
例如:201208091325 -> 20120809132500 -> 2012/8/9 13:25:00
例如:2012080913 -> 20120809130000 -> 2012/8/9 13:00:00
如此可以簡化不少操作

倘偌需要更多格式的日期轉換就需要改寫轉換規則就可以了。

2012年3月8日 星期四

datawindow使用陣列參數傳遞

如果你的 DataWindow 需要 in 的條件,要注意的是 Datawindow 裡面 SQL 的 in 參數是需要一個陣列型態的參數,所以你必須在指定 DataWindow 參數的時候宣告它為陣列型態:

1、選擇 Design > Retrieval Arguments


2012年2月14日 星期二

jQuery ajax.responseText注意事項

jQuery操作ajax技巧是相當方便的,
但是操作ajax.responseText,不得不注意到同步問題,如下面的例子:

var rtxt = $.ajax({type:"GET",
                   url:"empl.cfm",
                   data:{eno:"A001",
                   dept:"A"},
                   async:true}).responseText;
var msg = eval(rtext).message;
alert(msag);

這個是以 jQuery 利用ajax非同步呼叫 empl.cfm 頁面取得人事資料,而該頁面會以 JSON 格式回傳,該格式結構裡面會包含 message 的變數。

這個看似正常的程式碼其實隱含著很大的問題,
測試過程中,當伺服器回應在200ms以內時這個畫面會顯示 msag 內容,
當大於 200ms 就容易出現無反應 ( alert沒有執行 )。

後來反覆檢查的結果,發現原來是 async 參數會作祟,當 async為 true 時,ajax 為非同步運作:請求頁面同時,script 繼續執行並不等待請求回應,由於 responseText 指令在執行時會需要瀏覽器即時編譯,當編譯完成執行時,若請求的頁面已經返回回應資料,則此時會正常的繼續執行,若請求頁面來不及在編譯執行前返回回應資料,則會造成 rtxt 變成 undefined 狀態,使得接下來的 eval(rtext) 也變成 undefined ,如此直接取得結構 .message 時就會導致 javascript 執行錯誤停止。

當然正常的情況可以不要加入 async 屬性,因為其預設值就是 false 狀態。

2012年1月20日 星期五

programming DLL using C/C++ for Powerbuilder external function

真的,用PowerBuuilder的人實在太少了,再加上又會寫C語言作為輔助的更是寥寥可數,只好自己用功一點『自學』了。

C語言有很強大的功能,但每種版本的C語言又有許多的不一樣(C、C++、C#、Object C...),真的學起來相當辛苦,如果電腦原理基礎觀念不好,操作C語言可能真的會成為夢饜,雖然C++與C#有許多強大的功能與函式,但是作為可以被『異種』程式語言呼叫的獨立函式庫,可能就非常不理想,因此需要以標準的C語言編譯,讓其他程式可以使用就是一個原則(這是我的想法,如果你有不同的意見歡迎提出)。

雖然C語言的原始碼可以跨平台,不過會由於編譯器的不同而有不太一樣的指令方式。
例如,我的開發環境在Visual Studio 2005底下,使用到某些C指令就會有所警告,像是:
strcpy => strcpy_s
strdup => _scrdup
諸如此類,雖然編譯會發生警告,但是還是可以使用。但是以 Visual Studio 的觀點來說,這些都是被列為不安全的指令。所以我撰寫的程式碼都是在符合 Visual Studio 環境底下設計的,若你需要使用到原本C語言原始指令,就可能要自行改寫或是參考一下MSDN的說明了。

好了,來說說如何製作一個標準的DLL供 PowerBuilder 使用吧(不一定限定PB,也可以是VB、Delphi之類):

首先,你必須建立一個新的專案(部分C開發環境沒有專案概念,例如:Dev-C,每個檔案都必須自行建立與整理)

2012年1月9日 星期一

利用 coldfusion 實作 CAPTCHA 驗證功能

 我當然知道這樣輸入很沒效率,但是這是目前比較好的防廣告文手段。

CAPTCHA,中文常被稱為『驗證碼』,其全名為 Completely Automated Public Turing test to tell Computers and Humans Apart (全自動區分計算機和人類的圖靈測試)。媽呀,好長的名字!!!

需要詳細瞭解者可以看中文版維基的解釋或是英文版本的。

如果需要輕鬆一點的可以看看 直角兄-淺談各式各樣的網路圖形驗證碼(Captcha)

因為網路上我真的找不太到中文關於使用原生 CFML 來設計 CAPTCHA 驗證碼的文章,

真的有點感嘆,所以就自己動手寫起來以備日後的紀錄與查找。

原始碼出處:http://www.bennadel.com/blog/873-Using-CAPTCHA-In-ColdFusion-8.htm

當然英文看不輪轉的就看我這裡的吧,我已經把註解都中文化,並且稍微修改了一些。

2012年1月4日 星期三

SQL [Sybase] 於輸出數值前方補上0

參考來自這篇文章,只是 Sybase SQL 的指令不如 Microsoft 來得多,所以改寫成自己要的方式。

很多時候,公家機關特別喜歡的EDI格式都是固定長度的欄位資料,尤其是數字前面要補零的。

在 SQL 語法上就沒那麼便利像 Powerbuilder 的 String 指令,可以直接給一個 Format,

因此就需要一點變通了,

先來看一看舊式寫法好了,這是使用長度計算後的捕入方式:

DECLARE @Number INT ,@Digits INT ,@Result CHAR(8)

SELECT @Number  = 123

SELECT @Digits = 8

SELECT replicate('0',@Digits - char_length(Convert(varchar , @Number)))+Convert(varchar , @Number)


Convert(varchar , @Number) 將該數值先轉換成字串值,使用 varchar 可以得到與數值相同字元長度的字串。