維's profileIT : 是工作還是嗜好?PhotosBlogListsMore ![]() | Help |
|
November 11 RAD Studio 2010, Delphi 2010, C++Builder 2010 Update 2已可下載CodeGear已釋出 RAD Studio 2010, Delphi 2010和C++Builder 2010的Update 2, 我已經下載安裝完畢, 整個過程非常的順利. 如果您是合法的使用者的話, 那麼您可以在下面的URL下載 October 23 JSON 程式設計 – DataSnap的回叫機制DataSnap 2010加入了回叫機制,當伺服端方法在執行的過程中可以回叫用戶端提供的方法以通知用戶端有關伺服端方法執行的狀態。 DataSnap的回叫機制非常適合使用在需要較長時間執行的伺服端方法,例如如果伺服端方法需要執行長時間的查詢時就很適合使用,或是當程式啟動或是執行時需要進行許多查詢的工作,那麼也都可以使用回叫機制。 要使用DataSnap 2010的回叫機制非常的簡單,開發人員只需要實作一個從TDBXCallback繼承下來的實體類別,並且在呼叫伺服端方法時把此實體類別的樣例當做參數傳遞給伺服端方法即可。 由於使用DataSnap 2010的回叫機制並不困難,因此讓我們使用一個範例來說明讀者就可以很快的瞭解。 在下列的範例中本文將使用Delphi 2010中新的IOUtils程式單元中的類別進行檔案搜尋和計數的工作,由於這將花上一些時間,因此我們正好使用它來展示使用同步和回叫機制的差異。 範例DataSnap伺服器 首先讓我們建立一個DataSnap伺服端,下面是這個伺服器輸出的伺服端方法,請注意的是,TServerMethods1輸出了兩個方法GetServerDirectoryInfo和GetServerDirectoryInfoAsync。這兩個方法都執行相同的工作,它們使用TDirectory類別搜尋和計數特定伺服端目錄下的檔案總數,它們的差異是GetServerDirectoryInfo使用同步的方式執行,因此當用戶端呼叫它時,用戶端會同時暫停反應直到GetServerDirectoryInfo執行完畢。 而GetServerDirectoryInfoAsync則是使用回叫機制的方式執行,因此當用戶端呼叫它之後,GetServerDirectoryInfoAsync在執行的過程中則可以藉由用戶端傳遞來的參數ACallback: TDBXCallback,來回叫回用戶端,告訴用戶端執行的狀態,用戶端因此也根據目前伺服端執行的情形來更新用戶端的資訊。 {$METHODINFO ON} TServerMethods1 = class(TPersistent) private { Private declarations }
public { Public declarations }
end; {$METHODINFO OFF} GetServerDirectoryInfoAsync是如何回叫回用戶端呢?其實非常的簡單,因為用戶端在呼叫它時已經把用戶端的回叫方法當成參數傳遞過來了,因此GetServerDirectoryInfoAsync只需要藉由這個參數即可回叫回用戶端。 因此我們可以從下面18行的程式碼看到,伺服端直接使用這個回叫參數呼叫用戶端,並且建立一個TJSONString型態的物件做為參數,在這個TJSONString物件中告訴了用戶端目前伺服端正在處理那一個目錄。 001 function TServerMethods1.GetServerDirectoryInfoAsync(ACallback: TDBXCallback; 002 const sPath: string): TJSONArray; 003 begin 004 FCallback := ACallBack; 005 FTotalFiles := 0; 006 FResult := TJSONArray.Create; 007 ProcessPathAsync(sPath); 008 FResult.AddElement(TJSONString.Create('總檔案數' + ' : ' + IntToStr(FTotalFiles))); 009 Result := FResult; 010 end; 011 012 procedure TServerMethods1.ProcessPathAsync(const sPath: string); 013 var 014 rootDirectories : TStringDynArray; 015 i: Integer; 016 begin 017 ProcessThisDirectory(sPath); 018 FCallback.Execute(TJSONString.Create('處理目錄' + sPath + '中...')); 019 rootDirectories := TDirectory.GetDirectories(sPath); 020 for i := 0 to Length(rootDirectories) - 1 do 021 ProcessPathAsync(rootDirectories[i]); 022 end; 同步用戶端 範例的同步用戶端非常的簡單,它只是直接呼叫伺服端的GetServerDirectoryInfo方法。 procedure TForm3.btnGetServerInfoClick(Sender: TObject); var aServer : TServerMethods1Client; ja : TJSONArray; jv : TJSONValue; I: Integer; begin lStart := GetTickCount; aServer := TServerMethods1Client.Create(Self.SQLConnection1.DBXConnection); try ja := aServer.GetServerDirectoryInfo(Edit1.Text); lEnd := GetTickCount; for I := 0 to ja.Size - 1 do begin jv := ja.Get(I); lbResult.Items.Add(jv.ToString); end; finally aServer.Free; ShowRunTime(lEnd - lStart); end; end; 在用戶端呼叫GetServerDirectoryInfo的過程中,用戶端暫停反應,使用者也無從瞭解伺服端執行的狀態。 回叫用戶端 再看看回叫用戶端,這個用戶端的關鍵從下面的012行開始,012行建立了TDSCallbackWithMethod物件,並且建立一個匿名方法做為用戶端的回叫方法傳遞給伺服端。從013行開始的匿名方法在被用戶端回叫的時候首先在013行把伺服端傳遞來的參數型態轉換為TJSONString的型態,接著更新用戶端的UI以通知使用者伺服端目前正在處理那一個目錄。最後在023行用戶端回叫方法如果執行成功就需要回傳TJSonTrue物件,如果失敗的話就需要回傳TJSonFalse物件。 001 procedure TForm3.btnGetServerInfoAsyncClick(Sender: TObject); 002 var 003 aServer : TServerMethods1Client; 004 LCallback : TDSCallbackWithMethod; 005 ja : TJSONArray; 006 jv : TJSONValue; 007 I: Integer; 008 begin 009 lStart := GetTickCount; 010 aServer := TServerMethods1Client.Create(Self.SQLConnection1.DBXConnection); 011 try 012 LCallback := TDSCallbackWithMethod.Create( 013 function(const Args: TJSONValue): TJSONValue 014 var 015 asyncResult: TJSONString; 016 I: Integer; 017 LMessage: string; 018 begin 019 asyncResult := TJSONString(Args); 020 lbAsync.Items.Add(asyncResult.ToString); 021 lbAsync.Update; 022 Application.ProcessMessages; 023 Result := TJSonTrue.Create; 024 end 025 ); 026 ja := aServer.GetServerDirectoryInfoAsync(LCallback, Edit1.Text); 027 028 029 lEnd := GetTickCount; 030 for I := 0 to ja.Size - 1 do 031 begin 032 jv := ja.Get(I); 033 lbResult.Items.Add(jv.ToString); 034 end; 035 finally 036 aServer.Free; 037 ShowRunTime(lEnd - lStart); 038 end; 039 end; 那麼什麼是TDSCallbackWithMethod類別呢? 這要從TDBXCallback抽象類別開始談起。 TDBXCallback抽象類別 在討論TDSCallbackMethod之前我們必須先說明TDBXCallback抽象類別,因為TDSCallbackMethod是從TDBXCallback繼承下來的實體類別。事實上TDBXCallback類別即是使用DataSnap 2010回叫機制的關鍵,要使用回叫機制,開發人員必須實作一個從TDBXCallback繼承下來的實體類別,並且傳遞此實體類別的樣例給伺服端方法做為參數,如此一來伺服端方法就可以藉由這個樣例參數呼叫回用戶端,以通知用戶端伺服端方法執行的狀態。 TDBXCallback的虛擬方法Execute是衍生類別需要複載實作的,Execute接受一個型態為TJSONValue的參數並且回傳一個型態為TJSONValue的結果值,伺服端方法在回叫用戶端的方法時,可以把需要傳遞給用戶端的數值或是物件轉換為TJSONValue型態並且當成Execute方法的參數傳遞回用戶端,而用戶端的方法在被回叫執行完畢之後,也可以把執行結果轉換為TJSONValue型態並且回傳給伺服端。下面即是TDBXCallback抽象類別的宣告: 001 TDBXCallback = class abstract 002 public 003 function Execute(const Arg: TJSONValue): TJSONValue; virtual; abstract; 004 protected 005 procedure SetConnectionHandler(const ConnectionHandler: TObject); virtual; 006 procedure SetOrdinal(const Ordinal: Integer); virtual; 007 public 008 property ConnectionHandler: TObject write SetConnectionHandler; 009 property Ordinal: Integer write SetOrdinal; 010 end; TDSCallbackMethod實體類別 瞭解了TDBXCallback扮演的角色之後解釋TDSCallbackMethod實體類別就簡單了,由於此範例應用程式要使用非同步呼叫機制,因此宣告TDSCallbackMethod從TDBXCallback繼承下來並且實作虛擬方法Execute。在TDSCallbackMethod的Execute方法中它只是簡單的呼叫在建構函式中儲存下來的用戶端方法的方法指標。 unit AsyncUtils; interface uses Classes, DbxDatasnap, DBXJson; type TDSCallbackMethod = reference to function(const Args: TJSONValue): TJSONValue; TDSCallbackWithMethod = class(TDBXCallback) private FCallbackMethod: TDSCallbackMethod; public constructor Create(ACallbackMethod: TDSCallbackMethod); function Execute(const Args: TJSONValue): TJSONValue; override; end; implementation constructor TDSCallbackWithMethod.Create(ACallbackMethod: TDSCallbackMethod); begin FCallbackMethod := ACallbackMethod; end; function TDSCallbackWithMethod.Execute(const Args: TJSONValue): TJSONValue; var aString: string; begin Assert(Assigned(FCallbackMethod)); Result := FCallbackMethod(Args); end; end. 下面的圖形分別是DataSnap伺服端執行的畫面以及同步用戶端,回叫用戶端的執行畫面,從圖2同步用戶端畫面可以看到它雖然比回叫用戶端執行的快(在筆者的機器中執行了7.078秒),但是在同步用戶端呼叫DataSnap伺服器時它的整個UI是暫停的,因此它無法在表單下方的ListBox中顯示任何伺服端執行的資訊,使用者必須等待它完全執行完畢才能取回用戶端應用程式的控制權。 圖1 DataSnaps伺服器的執行畫面 圖2 同步客戶端的執行畫面 圖3 回客戶端的執行畫面 相反的畫面3則是回叫用戶端的執行結果,我們看到它是比同步用戶端慢(在筆者的機器中執行了12.984秒),但是在整個執行過程中使用者仍然可以控制用戶端應用程式,而且回叫用戶端能夠不停的在表單下方的ListBox中顯示目前伺服端正在處理的目錄資訊。因此就使用者經驗來說,回叫用戶端是比同步用戶端好多了。 也許可以更好 DataSnap 2010的回叫機制雖然使用上非常的簡單,但在許多的應用中卻仍然可能不便,例如現在的機制需要我們在把用戶端回叫方法傳遞給伺服端方法當成參數,但這實在有些囉嗦,因為如果用戶端有許多的回叫方法,那麼在每次呼叫伺服端方法時都需要傳遞一次。因此如果DataSnap 2010能夠提供一個全域的回叫方法註冊機制,讓用戶端只需要為每一個回叫方法註冊一次即可,而無需每次呼叫即傳遞一次,那麼在使用上將更為簡化。不過DataSnap 2010的確是一個大幅進步的版本,相信未來Delphi/BCB團隊會繼續的增強DataSnap的功能。 Have Fun! September 28 JSON 程式設計 – 使用過濾器在以前Tiburon遊記的文章解釋過JSON是使用字串型態來傳遞資料的,因此所有其他型態的資料都必須轉換為字串的型態,雖然如此一來在處理上比較簡單,但這也造成了其他的問題,例如一些敏感性的資料如果使用字串型態來傳遞的話就會有問題。DataSnap 2010為了解決這種問題因此加入了過濾器的機制,讓開發人員在傳遞特殊的資料時可以藉由過濾器來進行額外的處理,例如在傳遞資料出去時先加密,並且在接受到資料之後再進行解密。本篇文章的目的即在於討論如何使用DataSnap 2010的過濾器。 使用DataSnap 2010的過濾器非常的簡單,Delphi 2010也內建了一個壓縮過濾器,可以有效的壓縮使用TCP/IP通訊協定的資料傳遞。讓我們先說明如何使用這個內建的過濾器,稍後我們再深入的說明如何開發客製化過濾器。 讓我們仍然使用上篇文章的範例,要使用過濾器,請開啟ServerContainerUnit程式單元,點選表單中的TDSTCPServerTransport元件,並且雙擊它的Filters特性,此時Filters特性值編輯器會啟動,請於其中加入一個新的過濾器,點選此新的過濾器,然後在物件檢視器中選擇它的FilterId為ZlibCompression,如下所示: ZlibCompression過濾器就是DataSnap 2010內建的壓縮過濾器,在加入了ZlibCompression過濾器之後,編譯並且執行範例DataSnap伺服器,現在DataSnap伺服器就提供了壓縮JSON資料的能力。 現在再讓我們開啟用戶端應用程式,因為我們要在用戶端應用程式中加入解壓縮資料的能力,這個非常的簡單,我們只要在用戶端應用程式的主表單中加入使用DBXCompressionFilter程式單元即可,例如下面就是用戶端應用程式加入DBXCompressionFilter程式單元的程式碼: implementation uses DBXJSONReflect, DBXJSON, uServerProxy, uEmployee, DBXCompressionFilter; 現在編譯並且執行用戶端應用程式,並且讓我們使用TCP Viewer來觀察使用壓縮過濾器之前的情形以及使用壓縮過濾器之後的效果。 下圖是TCP Viewer顯示範例DataSnap應用系統使用壓縮過濾器之前的情形,從下圖中我們可以看到在DataSnap伺服器和用戶端應用程式之間傳遞的資料當然是使用字串的型態,所有傳遞的資料都一清二楚,同時請讀者注意下圖右邊顯示了從伺服器傳遞到用戶端的資料量(938位元組)以及從用戶端傳遞到伺服端的資料量(706位元組)。 而下圖則是使用壓縮過濾器之後的效果: 從上圖中可以看到傳遞的資料經過壓縮,因此不易看出原始的資料,而且請讀者注意下圖右邊顯示了從伺服器傳遞到用戶端的資料量(653位元組)以及從用戶端傳遞到伺服端的資料量(602位元組),可見到壓縮過濾器有效的減少了伺服器和用戶端之間的資料傳遞量,這不但可以增加分散式應用程式的執行速度,也可以增加支援的用戶端的數量。 如何? 使用過濾器是不是又簡單,又有明顯的效果? 不過DataSnap 2010只提供了一個內建的過濾器實在太少,好在DataSnap 2010過濾器架構在設計時就考慮到了允許讓開發人員能夠自行開發過濾器並且內嵌到DataSnap之中,接下來筆者將討論如何開發客製化過濾器並且使用在DataSnap 2010的分散式應用系統中。 開發客製化過濾器 要開發客製化過濾器,開發人員必須從TTransportFilter類別衍生子代類別並且實作TTransportFilter類別中相關的虛擬方法,下面的表單說明了開發人員需要實作的虛擬方法:
瞭解了需要實作那些虛擬方法之後,我們就可以開始動手開發一個客製化過濾器了。在本文中筆者將撰寫一個非常簡單的加密/解密過濾器,其實這個加密/解密過濾器只是在傳遞資料時和一個字串進行xor的動作,到了另一端再次xor相同的字串而已。 首先讓我們宣告範例TTransportEncryptFilter類別從TTransportFilter類別繼承下來並且複載相關必要的虛擬方法: TTransportEncryptFilter = class(TTransportFilter) private FEncrypt: TSimpleEncryptor; FParameters: TDictionary<String, String>; protected function GetParameters: TDBXStringArray; override; function GetUserParameters: TDBXStringArray; override; public function GetParameterValue(const ParamName: UnicodeString): UnicodeString; override; function SetParameterValue(const ParamName: UnicodeString; const ParamValue: UnicodeString): Boolean; override; constructor Create; override; destructor Destroy; override; function ProcessInput(const Data: TBytes): TBytes; override; function ProcessOutput(const Data: TBytes): TBytes; override; function Id: UnicodeString; override; end; TTransportEncryptFilter類別將使用TSimpleEncryptor進行字串xor的運算,在TSimpleEncryptor類別中實作了兩個方法,Encrypt和Decrypt,其實這兩個方法的實作程式碼是一樣的,只是為了說明方便分別實作成Encrypt和Decrypt以便讓讀者瞭解。Encrypt和Decrypt接受TBytes型態的參數,這個參數在Encrypt方法中是代表傳遞出去的資料,Encrypt方法使用程式碼加密之後再把加密過的資料以TBytes型態回傳。 而Decrypt的參數則是代表接受來的資料,Decrypt方法必須使用程式碼加以解密以還原資料。 TSimpleEncryptor = class protected public function Encrypt(const Data: TBytes): TBytes; function Decrypt(const Data: TBytes): TBytes; constructor Create; end; 下面是這兩個方法的實作程式碼,讀者可以看到這兩個方法的實作程式碼是 樣的,它們都接受的參數以'DexterHighlanderTiburonWeaver'這個鍵值字串進行xor的運算: const EncryptKey = 'DexterHighlanderTiburonWeaver'; constructor TSimpleEncryptor.Create; begin inherited Create; end; function TSimpleEncryptor.Decrypt(const Data: TBytes): TBytes; var i: Integer; idx: Integer; begin Result := Data; idx := 0; for i := 0 to Length(Data) - 1 do begin Result[i] := Byte(Chr(Ord(Data[i]) xor Ord(EncryptKey[idx]))); Inc(idx); if (idx > Length(EncryptKey)) then idx := 0; end; end; function TSimpleEncryptor.Encrypt(const Data: TBytes): TBytes; var i: Integer; idx: Integer; begin Result := Data; idx := 0; for i := 0 to Length(Data) - 1 do begin Result[i] := Byte(Chr(Ord(Data[i]) xor Ord(EncryptKey[idx]))); Inc(idx); if (idx > Length(EncryptKey)) then idx := 0; end; end; 下面則是TTransportEncryptFilter類別的實作程式碼,讀者可以看到在在建構函式中建立了TSimpleEncryptor物件,並且分別在ProcessInput虛擬方法中呼叫TSimpleEncryptor物件的Encrypt方法加密傳遞的資料並且在ProcessOutput虛擬方法中呼叫TSimpleEncryptor物件的Decrypt方法以解密資料: function TTransportEncryptFilter.GetUserParameters: TDBXStringArray; begin SetLength(Result, 1); Result[0] := EncryptKey; end; function TTransportEncryptFilter.GetParameters: TDBXStringArray; begin SetLength(Result, 1); Result[0] := EncryptKey; end; function TTransportEncryptFilter.GetParameterValue (const ParamName: UnicodeString): UnicodeString; begin FParameters.TryGetValue(ParamName, Result); if ( ParamName = EncryptKey ) and ( Result = '' ) then Result := '0'; end; function TTransportEncryptFilter.SetParameterValue (const ParamName, ParamValue: UnicodeString): Boolean; begin FParameters.AddOrSetValue(ParamName, ParamValue); Result := True; end; constructor TTransportEncryptFilter.Create; begin inherited Create; FParameters := TDictionary<String, String>.Create; FEncrypt := TSimpleEncryptor.Create; end; destructor TTransportEncryptFilter.Destroy; begin FreeAndNil(FParameters); FreeAndNil(FEncrypt); inherited Destroy; end; function TTransportEncryptFilter.ProcessInput(const Data: TBytes): TBytes; begin OutputDebugString(PWideChar('Encrypted - ' + Stringof(Data))); Result := FEncrypt.Encrypt(Data); end; function TTransportEncryptFilter.ProcessOutput(const Data: TBytes): TBytes; begin OutputDebugString(PWideChar('Decrypted - ' + Stringof(Data))); Result := FEncrypt.Decrypt(Data); end; function TTransportEncryptFilter.Id: UnicodeString; begin Result := EncryptFilterName; end; 最後我們需要在initialization部份註冊這個客製化過濾器並且在finalization部份解除註冊客製化過濾器: initialization TTransportFilterFactory.RegisterFilter(EncryptFilterName, TTransportEncryptFilter); finalization TTransportFilterFactory.UnregisterFilter(EncryptFilterName); 使用客製化過濾器 OK,回到範例伺服器,開啟ServerContainer程式單元並且在它的OnCreate事件處理函式中使用TDSTCPServerTransport的AddFilter方法加入我們的客製化過濾器: procedure TServerContainer1.DataModuleCreate(Sender: TObject); var i : Integer; begin i := DSTCPServerTransport1.Filters.AddFilter(uEncryptFilter.EncryptFilterName); for i := 0 to DSTCPServerTransport1.Filters.Count - 1 do Form1.lbFilters.Items.Add(DSTCPServerTransport1.Filters.GetFilter(i).Id); end; 當然我們也需要在ServerContainer程式單元的uses句子中加入包含客製化過濾器的程式單元uEncryptFilter: implementation uses Windows, ServerMethodsUnit1, MainForm, uEncryptFilter; 現在執行DataSnap伺服器,我們就可以看到伺服器顯示它已經找到了我們的客製化過濾器: 接著開啟用戶端應用程式,在主表單中也加入客製化過濾器的程式單元uEncryptFilter,編譯並且執行用戶端應用程式,再使用TCP Viewer觀察傳遞的資料,我們果然看到資料現在都經過加密了: 但是我們可以看到傳遞的資料量增加了。 但是用戶端仍然可以正確的接受到資料: 當然我們也可以同時使用兩個過濾器,享受加密又壓縮的好處,下圖是伺服器同時支援了兩個過濾器: 如果我們再次使用TCP Viewer,就可以看到下圖,享受加密又壓縮的好處,因為資料加密了而且資料傳遞量又減少了。 現在您應該瞭解了如何使用DataSnap 2010的過濾器功能以及如何開發客製化過濾器,我們下次再討論DataSnap 2010的非同步機制。 Have Fun! September 23 JSON 程式設計 – Marshaling/Unmarshaling上篇文章中說明了如何使用Delphi 2010中通用的JSON相關類別來進行程式設計並且以一個TEmployee類別做為範例來說明如何把TEmployee物件轉換為JSON格式的資料並且藉由DataSnap傳遞到用戶端。 由於類似這種傳遞Value Object的應用在JSON程式設計的世界被非常廣泛的應用,因此DataSnap 2010正式加入了JSON Marshaling/Unmarshaling的功能,允許程式師把物件轉換為符合JSON格式的序列化物件傳遞,例如從伺服端傳遞到用戶端。這樣做的好處是只要支援JSON的技術都可以自由的解析其中的資料並且加入處理,如此一來DataSnap 2010的JSON Marshaling/Unmarshaling技術就可以達成和同質或是異質平台互動的能力,也就是說DataSnap 2010的JSON Marshaling/Unmarshaling技術是已經是一個跨平台的分散式架構了。 使用DataSnap 2010的JSON Marshaling/Unmarshaling非常的簡單,因為對大多數的物件而言,DataSnap 2010能夠自動的把它轉換為TJSONObject型態的物件,然後再加以傳遞,因此開發人員只需要撰寫非常簡易的程式碼就可以輕易的完成。現在讓我們仍然使用上篇文章中的TEmployee類別做為說明,看看使用JSON Marshaling/Unmarshaling功能之後在分散式環境中傳遞物件是如何的容易。 建立DataSnap伺服器 Delphi 2010提供了新的DataSnap精靈幫助開發人員快速建立DataSnap伺服器,如此一來我們就不必每次都不斷重覆的放入TDSServer和TDSTCPServerTransport等元件,可節省我們的開發時間。此外DataSnap 2010也開始支援HTTP/HTTPS通訊協定,DataSnap精靈允許開發人員自動設定更多的元件組態資訊。 啟動Delphi 2010,點選File|New啟動New Items對話盒並且於DataSnap Server分類中選擇DataSnap Server圖像以建立DataSnap伺服器: 接著在New DataSnap Server對話盒中讓我們建立一般的VCL Forms應用程式做為DataSnap伺服器的型態,選擇使用TCP/IP和HTTP/HTTPS通訊協定,並且選擇使用TDSServerModule做為類別做為輸出服務的父代類別,如下所示: 點選OK按鈕之後Delphi 2010便會為自動我們建立如下的ServerContainerUnit1程式單元,其中即包含了所有必要的元件。其中的TDSHTTPService和TDSHTTPServiceAuthenticationManager是新的元件,這兩個元件是使用來支援HTTP/HTTPS通訊協定的。 現在讓我們把Unit1程式單元儲存為名為MainForm的程式單元: 接著點選TDSHTTPService元件,設定它的HttpPort特性值為8080,因為通信埠80已經被Web伺服器使用,因此在這個範例中我使用8080,如果通信埠8080在讀者的機器中已經被使用,那麼您可以使用任何尚未使用的通信埠。請注意TDSHTTPService的RESTContext特性,您可以看到它的特性值是Rest,這代表Delphi 2010的DataSnap伺服器一旦使用TDSHTTPService元件就自動支援RESTful的功能,我們稍後再討論RESTful。 由於Delphi 2010強化了RTTI提供Reflection的機制,因此DataSnap 2010允許開發人員選擇使用TDSServerModule,TDataModule或是TPersistent類別做為輸出服務的父代類別,因為Delphi 2010都可以藉由RTTI從任何父代類別取得必要的資訊。 現在讓我們在這個DataSnap伺服器專案中加入上篇文章中的TEmployee程式單元,並且輸出GetEmployee方法讓用戶端能夠從伺服端取得TEmployee物件。請注意GetEmployee回傳TJSONValue型態的數值,由於DataSnap 2010在參數列和回傳值都開始支援TJSONValue,因此這代表只要我們把任何物件轉換為TJSONValue物件,就可以自由傳遞到用戶端,或是從用戶端傳遞回伺服端,而這個流程就是DataSnap 2010的JSON Marshaling/Unmarshaling技術。 TServerMethods1 = class(TDSServerModule) private { Private declarations } public { Public declarations } function EchoString(Value: string): string; function GetEmployee : TJSONValue; end; 要使用JSON Marshaling/Unmarshaling,請加入使用DBXJSONReflect程式單元,如下004行所示。 要以JSON格式傳遞物件非常的簡單,首先我們需要建立我們要傳遞的物件,一個TJSONMarshal物件以及一個TJSONConverter物件。因此下面的011行先建立TEmployee物件,012行建立TJSONMarshal物件,並且在它的建構函式中傳入一個TJSONConverter物件,最後我們只需要呼叫TJSONMarshal物件的Marshal虛擬方法並且傳入要傳遞的物件即可,如014行所示: 01 implementation 002 003 {$R *.dfm} 004 uses DBXJSONReflect, uEmployee; 005 006 function TServerMethods1.GetEmployee: TJSONValue; 007 var 008 anEmployee : TEmployee; 009 aMarshaler : TJSONMarshal; 010 begin 011 anEmployee := TEmployee.Create('王大明', 'wtm@somemail.com', '123456'); 012 aMarshaler := TJSONMarshal.Create(TJSONConverter.Create); 013 try 014 Result := aMarshaler.Marshal(anEmployee); 015 finally 016 FreeAndNil(anEmployee); 017 FreeAndNil(aMarshaler); 018 end; 019 end; 如此一來TJSONMarshal和TJSONConverter類別就可以自動把傳入的物件轉換為JSON的格式並且傳遞出去。 建立DataSnap用戶端 現在建立一個VCL Form應用程式專案,放入TSQLConnection元件,連結到DataSnap伺服器(如果您不知道如何做,請參考筆者的Tiburon遊記系列文章),建立代表DataSnap伺服器輸出服務的用戶端程式單元uServerProxy,同樣把TEmployee程式單元加入到用戶端專案中,再參考DBXJSONReflect和DBXJSON程式單元,如下001行所示。 在用戶端要反轉JSON物件回正確物件的流程就是Unmarshaling,我們只需要在013行建立TJSONUnMarshal物件,015行呼叫DataSnap伺服器的GetEmployee服務方法,GetEmployee回傳TJSONValue型態的物件,016行先存取回傳的TJSONValue物件的JSON格式文字值,接著018行呼叫TJSONUnMarshal的Unmarshal方法,傳入回傳的TJSONValue物件並且使用as運算元把它正確的轉換回TEmployee物件,之後我們就可以自由的存取TEmployee物件的特性值了。 001 uses DBXJSONReflect, DBXJSON, uServerProxy, uEmployee; 002 003 {$R *.dfm} 004 005 procedure TForm2.Button1Click(Sender: TObject); 006 var 007 aServer : TServerMethods1Client; 008 unMarshaler : TJSONUnMarshal; 009 aValue : TJSONValue; 010 anEmployee : TEmployee; 011 begin 012 aServer := TServerMethods1Client.Create(Self.SQLConnection1.DBXConnection); 013 unMarshaler := TJSONUnMarshal.Create; 014 try 015 aValue := aServer.GetEmployee; 016 Edit4.Text := aValue.ToString; 017 018 anEmployee := unMarshaler.Unmarshal(aValue) as TEmployee; 019 Edit1.Text := anEmployee.Name; 020 Edit2.Text := anEmployee.EMail; 021 Edit3.Text := anEmployee.Phone; 022 023 finally 024 FreeAndNil(anEmployee); 025 FreeAndNil(unMarshaler); 026 FreeAndNil(aServer); 027 end; 028 end; 從下圖可以看到TEmployee物件的確可以正確的從DataSnap伺服器傳遞到用戶端,當然用戶端也可以修改數值之後再傳遞回DataSnap伺服器再更新回資料庫。 使用DataExplorer存取DataSnap伺服器的服務 由於DataSnap 2010自動支援RESTful架構,因此既然在前面我們選擇讓DataSnap伺服器支援HTTP/HTTPS通訊協定,因此我們現在就可以使用瀏覽器來存取這個DataSnap伺服器的服務,我們只需要使用下面的樣例就可以存取到DataSnap伺服器的服務: http://server url/datasnap/rest/服務程式單元/服務方法 https://server url/datasnap/rest/服務程式單元/服務方法 因此要存取前面的DataSnap伺服器,我們只需要使用下面的URL: http://localhost:8080/datasnap/rest/TServerMethods1/GetEmployee 使用8080當然是因為我們前面設定TDSHTTPService元件的HttpPort特性值使用8080。 例如下圖就是筆者使用FireFox存取DataSnap伺服器: 使用瀏覽器存取DataSnap伺服器的服務 我們也可以使用Delphi/BCB的DataExplorer來測試和存取DataSnap伺服器,例如下圖的三個圖形分別顯示了筆者使用DataExplorer來測試DataSnap伺服器是否支援TCP/IP,HTTP/HTTPS通訊協定,以及存取GetEmployee服務。請注意第3個圖形,它明確的顯示了DataSnap伺服器回傳的是物件型態(Object)。 建立Web用戶端 既然範例DataSnap伺服器支援HTTP/HTTPS通訊協定,因此我們當然可以使用Web應用程式來存取它的服務,例如下圖就是筆者使用VCL For Web建立Web應用程式並且存取DataSnap伺服器的服務: 這是如何做到的? 由於篇幅的限制,因此讓我們簡單的說明一下好了。當開發人員使用TJSONMarshal類別Marshaling物件時,TJSONMarshal藉由新的Rtti功能取得任何物件的欄位值,再一一的根據欄位型態呼叫TXXXXConverter類別把欄位值轉換為JSON格式,最後再把所有欄位值組合成TJSONObject的型態傳遞出去。至於Unmarshaling則是進行相反的動作。 那麼DataSnap 2010的Marshaling/Unmarshaling能夠傳遞什麼型態的物件呢? 沒有限制嗎? 這要分2個層面回答。 首先回答第一個問題,Marshaling/Unmarshaling能夠傳遞什麼型態的物件呢?答案是任何從TObject繼承下來的物件都可以,因為Marshaling/Unmarshaling中的實作程式碼已經說明了一切: procedure TTypeMarshaller<TSerial>.MarshalData(Data: TObject); 但另外一個關鍵則是物件的欄位型態,前面筆者已經說過,TXXXXConverter類別,因此如果您要傳遞的物件的欄位型態是特別的型態,或是目前DataSnap 2010的TXXXXConverter類別沒有支援的型態,那麼就不能傳遞,當然要克服這很簡單,因為您可以自己寫一個客製化TXXXXConverter衍生類別,並且註冊給DataSnap 2010就可以了,這屬於進階的功能,以後有機會再討論吧。 如何? DataSnap 2010的Marshaling/Unmarshaling功能是不是很強大又很容易使用? 各位讀者可以和上一篇我們自己傳遞TEmployee物件比較一下。善用DataSnap 2010的Marshaling/Unmarshaling功能可以讓我們輕易的開發出士又輕又快的跨平台分散式應用系統,成功的克服了以往COM/DCOM/COM+無法做到的事情,DataSnap 2010提供了目前最佳的分散式解決方案。 我們下次再見,Have Fun! September 16 JSON程式設計 上篇文章簡單的介紹了如何使用DataSnap開發基於JSON分散式應用系統,Delphi 藉由原本的Midas/DataSnap和dbExpress的元件提供這個新的分散式計算能力,但使用這些元件都是資料的方式來存取遠端服務,然而我們也可以使用前面介紹的JSON類別再結合DataSnap來實作出存取遠端數值物件(Value Object)或是所謂的DTO(Data Transfer Object)的應用,讓用戶端可以存取遠端的物件。 在下面的範例中我們將展示如何開發使用VO/DTO的DataSnap應用程式伺服器,並且在用戶端擷取其中封裝的資料和物件。這個範例將開發一個可以讓用戶端查詢資料和物件的DataSnap應用程式伺服器,當用戶端查詢物件時,我們將使用TJSONObject封裝物件並且傳遞到用戶端。 X-3-1開發數值物件伺服器 首先建立一個VCL Form應用程式專案,在它的主表單中加入如下的元件: ![]() 圖7 VO DataSnap應用程式伺服器 接著在專案中建立一個新的程式單元,於其中實作一個簡單的TEmployee類別如下: unit uEmployee; interface uses SysUtils, classes; type TEmployee = class private { Private declarations } FName : string; FEMail : string; FPhone : string; public { Public declarations } constructor Create(sName : string = ''; sEMail : string = ''; sPhone : string = ''); property Name : string read FName write FName; property EMail : string read FEmail write FEMail; property Phone : string read FPhone write FPhone; end; implementation { TEmployee } constructor TEmployee.Create(sName, sEMail: string; sPhone: string); begin FName := sName; FEmail := sEMail; FPhone := sPhone; end; end. 再於專案中建立一個新的程式單元稱為uEmployeeValueObject,其中定義TEmployeeVO類別如下,請注意TEmployeeVO類別使用了 {$MethodInfo ON}和{$MethodInfo Off}編譯器指令要求編譯器把TEmployeeVO類別的Reflection資訊編譯到執行檔中。 {$MethodInfo ON} TEmployeeVO = class(TComponent) private { Private declarations } FEmployees : TObjectList<TEmployee>; function CreateEmployeeJSONObject(employee : TEmployee) : string; function CreateEmployeeJSONArray : string; function CreateEmployeeJSONObjectJ(employee : TEmployee) : TJSONObject; public { Public declarations } destructor Destroy; override; function GetEmployeeJ(const sName : string) : TJSONObject; function GetAllEmployeesJ : TJSONArray; procedure AddEmployee(const sName : string; const sEMail : string; const sPhone : string); end; {$MethodInfo Off} TEmployeeVO宣告了三個方法說明如下:
下面小節將簡單的說明這些方法的實作。 AddEmployee方法的實作 AddEmployee方法非常的簡單,它從用戶端接受三個字串型態的參數,並且根據它們來建立TEmployee物件,最後再把建立的TEmployee物件加入在宣告為TObjectList<TEmployee>泛型型態的變數FEmployees之中。 procedure TEmployeeVO.AddEmployee(const sName, sEMail, sPhone: string); var employee : TEmployee; begin if (FEmployees = nil) then FEmployees := TObjectList<TEmployee>.create(true); employee := TEmployee.Create(sName, sEmail, sPhone); FEmployees.Add(employee); end; GetEmployeeJ方法的實作 GetEmployeeJ方法就非常的有趣了,它展示了如何於DataSnap應用程式伺服器中使用TJSONValue的相關類別,GetEmployeeJ回傳型態為TJSONObject的物件回用戶端。 GetEmployeeJ根據用戶端傳遞來查詢的員工姓名,在Fem ployees中一一的搜尋具有相同名稱的TEmployee物件,找到之涕在013行呼叫CreateEmployeeJSONObjectJ來建立回傳的TJSONObject物件。 001 function TEmployeeVO.GetEmployeeJ(const sName: string): TJSONObject; 002 var 003 ie : TList<uEmployee.TEmployee>.TEnumerator; 004 employee : TEmployee; 005 begin 006 Result := nil; 007 ie := FEmployees.GetEnumerator; 008 while (ie.MoveNext) do 009 begin 010 employee := ie.Current; 011 if (employee.Name = sName) then 012 begin 013 Result := CreateEmployeeJSONObjectJ(employee); 014 break; 015 end; 016 end; 017 end; CreateEmployeeJSONObjectJ方法根據找到的TEmployee物件來建立TJSONObject物件,它呼叫我們前面學習過的AddPair方法,把每一個TEmployee物件的特性名稱和特性值做為一個JSON的中『名稱/值』配對加入到TJSONObject物件。 function TEmployeeVO.CreateEmployeeJSONObjectJ(employee: TEmployee): TJSONObject; var aJO : TJSONObject; begin aJO := TJSONObject.Create; aJO.AddPair(TJSONString.Create('姓名'), TJSONString.Create(employee.Name)); aJO.AddPair(TJSONString.Create('EMail'), TJSONString.Create(employee.EMail)); aJO.AddPair(TJSONString.Create('電話'), TJSONString.Create(employee.Phone)); Result := aJO; end; GetAllEmployeesJ方法的實作 GetAllEmployeesJ方法會把所有存在泛型型態變數FEmployees之中的TEmployee物件封裝在TJSONArray物件中回傳給用戶端。 在013行仍然是呼叫CreateEmployeeJSONObjectJ為每一個CreateEmployeeJSONObjectJ建立一個TJSONObject物件,並且加入到回傳的TJSONArray物件中。 001 function TEmployeeVO.GetAllEmployeesJ: TJSONArray; 002 var 003 ie : TList<uEmployee.TEmployee>.TEnumerator; 004 employee : TEmployee; 005 jo : TJSONObject; 006 ja : TJSONArray; 007 begin 008 ie := FEmployees.GetEnumerator; 009 ja := TJSONArray.Create; 010 while (ie.MoveNext) do 011 begin 012 employee := ie.Current; 013 jo := CreateEmployeeJSONObjectJ(employee); 014 ja.AddElement(jo); 015 end; 016 Result := ja; 017 end; 最後不要忘記在主表單中註冊TEmployeeVO類別,如此一來用戶端才能看見並且呼叫TEmployeeVO輸出的方法: procedure TForm10.FormCreate(Sender: TObject); begin if DSServer1.Started then DSServer1.Stop; RegisterServers; DSServer1.Start; end; procedure TForm10.FormDestroy(Sender: TObject); begin DSServer1.Stop; end; procedure TForm10.RegisterServers; begin uEmployeeValueObject.RegisterServerClasses(Self, DSServer1); end; 現在編譯並且執行DataSnap應用程式伺服器,並且準備開發用戶端。 開發範例用戶端 其實這個範例的重點就在於用戶端如何呼叫遠端支援TJSONValue相關類別的方法,這是因為在DataSnap 2009中無法支援TJSONValue相關類別,到了DataSnap 2010才支援。但是在筆者撰寫本章時Delphi 2010的Beta版仍然無法自動產生代表遠端類別的用戶端Proxy類別,因此想要呼叫前面實作的GetEmployeeJ和GetAllEmployeesJ方法,我們必須瞭解如何修改由Delphi產生的DataSnap用戶端類別。 筆者認為當Delphi 2010正式版釋出時,CodeGear應該會把這個問題修正,如果您發現您使用的Delphi已經能夠產生正確的用戶端Proxy類別,那麼就不需要如下所敘述的修改了。 首先建立一個VCL Form應用程式,放入TSQLConnection元件,設定Driver為DataSnap再把connected設定為True(請確定DataSnap應用程式伺服器已經在執行),接著用點選滑鼠右鍵,選擇Generate DataSnap client classes選項,儲存產生的程式單元為uServerProxy.pas,然後搜尋GetEmployeeJ方法,把012行修改為如下: (註, 筆者已經在Delphi 2010正式版中試過現在沒有問題了, 而筆者之所以沒把上述段落內容刪除是因為這段內容可以讓讀者更瞭解修改下面程式碼的意義, 即使讀者使用Delphi 2010正式版而不需要再修改下面的程式碼, 也可以把uServerProxy.pas開啟, 再看看由Delphi 2010產生的程式碼代表的意義) 001 function TEmployeeVOClient.GetEmployeeJ(sName: string): TJSONObject; 002 begin 003 if FGetEmployeeJCommand = nil then 004 begin 005 FGetEmployeeJCommand := FDBXConnection.CreateCommand; 006 FGetEmployeeJCommand.CommandType := TDBXCommandTypes.DSServerMethod; 007 FGetEmployeeJCommand.Text := 'TEmployeeVO.GetEmployeeJ'; 008 FGetEmployeeJCommand.Prepare; 009 end; 010 FGetEmployeeJCommand.Parameters[0].Value.SetWideString(sName); 011 FGetEmployeeJCommand.ExecuteUpdate; 012 Result := FGetEmployeeJCommand.Parameters[1].Value.GetJSONValue as TJSONObject; 013 end; 這是因為遠端GetEmployeeJ方法回傳TJSONObject型態的物件,因此我們需要把012行呼叫遠端方法執行的結果轉變型態為TJSONObject型態。 同樣的,我們也需要修改GetAllEmployeesJ方法,轉變型態為回傳TJSONArray物件,如下所示: function TEmployeeVOClient.GetAllEmployeesJ: TJSONArray; begin if FGetAllEmployeesJCommand = nil then begin FGetAllEmployeesJCommand := FDBXConnection.CreateCommand; FgetAllEmployeesJCommand.CommandType := TDBXCommandTypes.DSServerMethod; FGetAllEmployeesJCommand.Text := 'TEmployeeVO.GetAllEmployeesJ'; FGetAllEmployeesJCommand.Prepare; end; FGetAllEmployeesJCommand.ExecuteUpdate; Result := FGetAllEmployeesJCommand.Parameters[0].Value.GetJSONValue as TJSONArray; end; 瞭解了如何以及為什麼需要修改DataSnap用戶端類別之後,我們就可以開始實作呼叫遠端方法的程式碼了。 首先下面的程式碼藉由自動產生的TEmployeeVOClient類別呼叫AddEmployee方法,把用戶端輸入的員工資料加入在遠端的應用程式伺服器中: procedure TForm11.btnAddEmployeeClick(Sender: TObject); var evo : TEmployeeVOClient; begin evo := TEmployeeVOClient.Create(Self.SQLConnection1.DBXConnection); try evo.AddEmployee(edtAddName.Text, edtAddEMail.Text, edtAddPhone.Text); edtAddName.Text := ''; edtAddEMail.Text := ''; edtAddPhone.Text := ''; finally evo.Free; end; end; 下圖是執行範例用戶端應用程式並且點選『增加員工』按鈕以執行上述程式碼的畫面: ![]() 圖8 用戶端應用程式呼叫遠端AddEmployee方式加入員工資訊 接著是藉由TEmployeeVOClient呼叫GetEmployeeJ查詢員工資料的實作程式碼: procedure TForm11.btnQueryEmployeeClick(Sender: TObject); var evo : TEmployeeVOClient; jo : TJSONObject; employee : TEmployee; begin evo := TEmployeeVOClient.Create(Self.SQLConnection1.DBXConnection); try jo := evo.GetEmployeeJ(edtQname.Text); employee := TEmployee.Create(jo.Get(0).JsonValue.ToString, jo.Get(1).JsonValue.ToString, jo.Get(2).JsonValue.ToString); Self.edtQEMail.Text := employee.EMail; Self.edtQPhone.Text := employee.Phone; finally FreeAndNil(employee); FreeAndNil(jo); evo.Free; end; end; 在上面的程式碼中呼叫GetEmployeeJ取得代表員工的TJSONObject物件,再根據TJSONObject物件中的資訊於用戶端建立TEmployee物件,再顯示員工資訊於表單中,最後不要忘記釋放TEmployee物件,TJSONObject物件和TEmployeeVOClient物件。 下圖是先增加李維這筆員工資料之後,再輸入李維來查詢的畫面: ![]() 圖9 輸入員工姓名查詢員工資料 點選上圖中的『查詢單一員工』按鈕之後我們的確可以取得遠端員工物件的資訊,如下圖所示: 圖10 查詢的結果畫面 但是為什麼上圖中的員工資訊,例如員工的Email有引號包圍呢? 這當然是因為這是JSON封裝字串的規範,而在上面的程式碼中當我們建立TEmployee物件時是直接呼叫ToString方法,如果我們不希望TEmployee物件的特性值有引號包圍,那麼我們可以修改程式碼如下,改呼叫Value方法: employee := TEmployee.Create(jo.Get(0).JsonValue.Value, jo.Get(1).JsonValue.Value, jo.Get(2).JsonValue.Value); 那麼就會有如下正確的結果: 圖11 查詢的結果畫面 最後讓我們看看如何查詢所有的員工資料。下面的程式碼藉由TEmployeeVOClient呼叫GetAllEmployeesJ取得TJSONArray物件,然後進入for迴圈把其中的每一個元素取出再轉變型態為TJSONObject物件,最後再根據TJSONObject物件一一的建立用戶端的員工物件。 procedure TForm11.btnQueryAllEmployeesClick(Sender: TObject); var evo : TEmployeeVOClient; jo : TJSONObject; ja : TJSONArray; employee : TEmployee; iIndex: Integer; begin evo := TEmployeeVOClient.Create(Self.SQLConnection1.DBXConnection); try ja := evo.GetAllEmployeesJ; for iIndex := 0 to ja.Size - 1 do begin jo := ja.Get(iIndex) as TJSONObject; employee := TEmployee.Create(jo.Get(0).JsonValue.ToString, jo.Get(1).JsonValue.ToString, jo.Get(2).JsonValue.ToString); Memo1.Lines.Add(employee.Name); FreeAndNil(employee); end; finally FreeAndNil(ja); FreeAndNil(employee); evo.Free; end; end; 下圖是執行此查詢的結果,我們可以看到用戶端的確可以查詢到遠端的所有員工的資訊。 圖12 所有員工資料查詢的結果畫面 當然,我們一樣可以修改上面的程式碼如下: employee := TEmployee.Create(jo.Get(0).JsonValue.Value, jo.Get(1).JsonValue. Value, jo.Get(2).JsonValue. Value); 那麼我們會看到如下的結果: 圖13 所有員工資料查詢的結果畫面 本章敘述了Delphi VCL框架中支援JSON開發的相關類別,讀者從本章中可以瞭解這些不但是依照JSON規範設計的,而且非常的直覺,好用,只要我們對於JSON規範有基本的掌握就可以順利的使用它們。 此外,新版的DataSnap不但使用JSON做為封裝和傳遞資料的基礎,現在也加入支援使用TJSONValue和衍生類別做為遠端方法的參數和回傳值,如此一來可以讓開發人員撰寫客製化的JSON應用,例如使用VO/DTO設計樣例來傳遞資料和物件,使用這樣的技術,開發人員也可以使用.NET,Java或是任何支援JSON的程式語言來開發用戶端應用程式了。 練完2篇文章的基本功之後, 在下篇文章開發, 我們將介紹如何使用Delphi 2010新的DataSnap Server精靈來大幅簡化開發的工作, 並且會介紹DataSnap強大的Marshaling和unMarshaling的功能,Marshaling和unMarshaling將可以讓我們使用JSON傳遞物件變得非常簡單,使用Marshaling和unMarshaling也可以讓上面傳遞TEmployee物件的程式碼大幅減少. 我們下次再見, Have FUN! September 11 Delphi 2010 JSON程式設計從Delphi 2009開始Delphi便開始支援JSON,DataSnap 2009開始使用JSON做為開發分散式應用系統的基礎技術,開始逐漸捨棄使用COM/DCOM/COM+。由於JSON具備跨平台,跨程式語言和跨工具的特性,因此DataSnap在結合JSON和dbExpress之後也逐漸具備了跨平台的基礎。但是Delphi 2009對於JSON的支援只限於使用在DataSnap之中,因此如果開發人員需要進行通用的JSON程式設計,Delphi 2009在這方面仍然顯得不足。 到了Delphi 2010情形開始改變,因為VCL框架開始內建支援JSON的類別,因此Delphi的開發人員就可以利用這些和JSON相關的類別來進行JSON程式設計的工作。本章的重點就是討論VCL框架對於JSON的支援,本章將討論如何使用VCL框架中和JSON相關的類別來進行JSON開發的工作。不過在討論這些類別之前,我們需要先簡單的介紹什麼是JSON。 X-1 JSON是什麼? 簡單的說,JSON是一種資料封裝格式以及資料交換格式,JSON是JavaScript Object Notation的縮寫,它是一種輕量級的資料交換格式,易於一般人閱讀和編寫,同時也易於機器解析和生成。 JSON是基於JavaScript(Standard ECMA-262 3rd Edition - December 1999)的一個子集,JSON採用完全獨立于語言的文字格式,但是也使用了類似於C語言家族的習慣(包括C, C++, C#, Java, JavaScript, Perl, Python等),這些特性使JSON成為理想的資料交換語言。 JSON是由下面的兩個基本架構組成的:
OK,看了上述的定義之後,讀者可能還是覺得模模糊糊,也許讓我們使用實際的範例來說明可以讓讀者更容易瞭解。 假設現在我們有一個DataSnap伺服器,它使用JSON和用戶端進行資料交換的工作,那麼現在用戶端向這個DataSnap伺服器查詢筆者所撰寫的書籍,例如本書『實戰Delphi 2010』,那麼如何把這個資訊以JSON的格式傳遞給用戶端呢? 首先讓我們觀察下圖,在JSON中物件的代表方式是以“{”開始,以“}”結束,另外在前面我們也說明JSON是以『名稱/值』的格式來代表資料,因此從下圖中我們可以看到名稱和值之間是以“:”符號分隔,其中『名稱』是字串格式,而值則是以數值格式來代表:
圖1 JSON封裝物件的格式 在JSON規範中上圖的字串規範如下圖所示: 圖2 JSON封裝字串的格式 從上圖中我們可以知道在JSON中字串是以”開始,以”結束,可包含Unicode的字元組。而圖1中的數值格式則如下圖所示: 圖3 JSON封裝數值的格式 從圖3中可知,JSON的數值可以是字串,數字,物件,陣列或是true/false/null值。由於JSON的數值可以是物件或是陣列,而物件又可以包含字串:數值配對,因此JSON規範可以封裝任何複雜的資料。 有了上述對於JSON基本的瞭解之後,我們就可以使用如下的格式從DataSnap伺服器傳遞資料到用戶端: {“書名”:”實戰Delphi 2010”} 從這個格式中我們可以知道這是用JSON中封裝物件的規則,把『字串:數值』配對包含在{和}符號之中。 如果我們希望也傳遞作者的資訊,那麼可以使用逗號分離每一個『字串:數值』配對,如下所示,讀者也可以回頭再參考圖1就可以發現這是圖1的規則。 {“書名”:”實戰Delphi 2010”,“作者”:”李維”} 如果現在再把書籍出版年份也傳遞,那麼就可以使用下面的格式: {"書名":"實戰Delphi 2010","作者":"李維","出版年份":2010} 請注意的是,出版年份的數值是2010這個數字,因為如圖3所示數值可以是數字(number),而在JSON中數字的定義如下:
圖4 JSON封裝數字的格式 從圖4我們可以知道,在JSON中數字可以是整數或是浮點數。 {"書名":"實戰Delphi 2010","作者":"李維","出版年份":2010} 因此如果我們再傳遞書籍價格,那麼可以使用如下的格式: {"書名":"實戰Delphi 2010","作者":"李維","出版年份":2010,"價格":45.95} 最後如果我們再加入書籍已出版否資訊,那麼可以使用如下的格式: {"書名":"實戰Delphi 2010","作者":"李維","出版年份":2010,"價格":45.95,"已出版否":false} 從上面的範例中我們可以看到JSON規範如何封裝各種不同型態的資料。那麼如果我們需要一次傳遞多筆資料到用戶端的話,又要如何封裝呢?這可以使用JSON中陣列的規範。 圖5是JSON陣列的封裝規則,陣列“[”開始,以“]”結束,而陣列中的每一個元素都是數值,每一個元素使用逗號分隔。回頭參考圖3 JSON封裝數值的格式,由於數值可以是物件,因此我們如果需要一次傳遞多筆資料到用戶端,那麼就可以使用JSON的陣列來封裝。 圖5 JSON封裝陣列的格式 例如下面就是使用JSON的陣列封裝性三個代表書籍的物件,陣列中的每一個元素數值是物件,每一個元素數值使用逗號分隔: [{“書名”:”實戰Delphi 2010”},{“書名”:”Inside VCL”},{“書名”:”Borland傳奇”}] 如何? 一旦瞭解了JSON封裝資料的規則之後讀者是不是覺得使用JSON非常的簡潔呢? 正由於JSON在封裝資料的規則比較簡單,因此在解析JSON資料時速度也比較快,JSON的簡潔規則讓JSON擁有較少的資料流量和較快的處理速度,因此讓JSON在Web應用方面佔有優勢,也愈來愈受歡迎,這也是DataSnap現在選擇使用JSON做為基礎技術的原因。 在離開本小節之前,讓我們比較一下使用XML和JSON在封裝和交換資料方面的差異。下面是使用XML封裝一個員工的資訊: <?xml version="1.0" encoding="utf-8"?> <user> <name>李大明</name> <password>123456</password> <department>R&D</department> <gender>男</gender> <age>26</age> </user> 如果我們使用JSON來封裝的話,那麼就如下所示: { "name":"李大明", "password":"123456", "department":"R&D", "gender":"男", "age":"26" } 我們可以看到JSON使用了較XML少的資料量來封裝相同的資訊,此JSON在資料交換方面擁有比較多的優勢。 讀者可以參考www.json.org以瞭解更多有關JSON的資訊。 在對於JSON有了基本的瞭解之後,更重要的是我們需要知道如何在Delphi中進行JSON的程式設計。 X-2 VCL框架中支援JSON的類別 Delphi在2009開始使用JSON做為DataSnap 2009封裝和傳遞資料的格式,但是Delphi 2009對於JSON的支援和DataSnap的功能撰寫的非常緊密,開發人員除了能夠在DataSnap 2009中使用JSON之外,並不容易使用Delphi 2009來開發其他應用型態的JSON應用程式。到了Delphi 2010這個現象獲得了大幅的改善,Delphi 2010提供了許多通用的JSON相關類別,開發人員可以使用這些通用的JSON相關類別開發任何型態的JSON應用程式而不只限於DataSnap應用程式。 Delphi 2010在新的DBXJSON.pas程式單元中提供了這些JSON相關的類別,如果讀者仔細觀察前面JSON規則圖的話,就可以發覺如果我們需要使用類別來實作支援JSON的規範,那麼我們我們至少需要下面的三個類別: 類別 說明 JSONValue 用來代表和實作圖3的實體和關係 JSONPair 用來代表JSON規範中的『名稱/值』配對實體和關係 JSONObject 用來代表和實作圖1的實體和關係 JSONString 用來代表和實作圖2的實體和關係 JSONArray 用來代表和實作圖5的實體和關係 JSONNumber 用來代表和實作圖4的實體和關係 當然在上述的基礎類別中還存在繼承的關係,例如由於JSON規範中數值可以代表字串,物件,陣列等實體,因此上面的JSONObject,JSONString等類別可以從JSONValue類別繼承下來。 Delphi 2010中支援JSON等相關類別就是使用這個觀念實作出來的,因此只要讀者瞭解了前面解釋JSON規範的觀念,就可以非常直覺的瞭解這些實作類別。在下面的表格中整理了這些類別,並且提供了簡單的敘述: 類別 說明 TJSONAncestor 抽象類別,是所有JSON相關類別的根類別 TJSONPair 實作JSON規範中『名稱/值』配對的類別 TJSONValue 實作JSON規範中數值的類別,TJSONValue是TJSONAncestor的繼承類別 TJSONTrue 實作JSON規範中代表True的類別,從TJSONValue繼承下來 TJSONString 實作JSON規範中代表字串的類別,從TJSONValue繼承下來 TJSONNumber 實作JSON規範中代表數值的類別,它從TJSONString繼承下來,因為JSON使用文字格式傳遞封裝和傳遞資料,因此所有數值都將轉換為文字字串型態傳遞,到達目的地之後再反轉回數值 TJSONObject 實作JSON規範中代表物件的類別,從TJSONValue繼承下來 TJSONNull 實作JSON規範中代表Null的類別,從TJSONValue繼承下來 TJSONFalse 實作JSON規範中代表False的類別,從TJSONValue繼承下來 下圖是這些類別之間的繼承和關連關係圖,請讀者對照下圖和以前圖1到圖5的JSON規範,就可以瞭解這些類別的實作是完全從JSON規範中設計出來的,簡單又符合直覺: 圖6 Delphi中支援JSON的類別架構 接下來讓我們簡單的說明這些類別提供的服務,如此一來讀者就可以瞭解如何在程式碼中使用它們。 X-2-1 TJSONAncestor類別 TJSONAncestor類別是所有JSON相關類別的根類別,它主要定義了三個虛擬方法讓它的衍生類別來複載實作,下面的表格敘述了這些虛擬方法: 虛擬方法 說明 function ToString: UnicodeString; virtual; ToString虛擬方法會把JSON的內容轉換為文字字串形式回傳,TJSONAncestor的衍生類別需要實作這個虛擬方法,例如TJSONObject類別會實作這個虛擬方法並且把物件內容轉換為字串的形態回傳 function EstimatedByteSize: Integer; virtual; abstract; EstimatedByteSize虛擬方法回傳JSON物件預估的位元組大小,TJSONAncestor的衍生類別需要實作這個虛擬方法,每一個衍生類別都會回傳它本身需要佔據位元組的大小。EstimatedByteSize虛擬方法的目的是在封裝和傳遞JSON物件時程式碼能夠配置足夠的記憶體大小之用。 function ToBytes(const Data: TBytes; const Offset: Integer): Integer; virtual; abstract; ToBytes虛擬方法能夠把JSON物件以位元組的形式回傳,TJSONAncestor的衍生類別需要實作這個虛擬方法,每一個衍生類別都會在這個複載的虛擬方法中把自己轉換為位元組形態。 TJSONAncestor本身是一個抽象類別,因此開發人員並不應該直接在程式碼中使用它,而是應該使用下面介紹的衍生類別。 X-2-2 TJSONValue類別 TJSONValue類別本身也是一個抽象類別,它是直接從TJSONAncestor繼承下來的,TJSONValue類別只是做為一個Placeholder,它主要的目的是代表圖3中JSON規範value的概念,因此它的定義非常簡單,如下所示: TJSONValue = class abstract(TJSONAncestor) end; 稍後介紹的許多類別都是從TJSONValue繼承下來的,幾乎和圖3顯示的規範架構一樣。 X-2-3 TJSONPair類別 TJSONPair類別是實作JSON規範中『名稱/值』配對的類別,它從TJSONAncestor直接繼承下來,由於TJSONPair是實作『名稱/值』配對,因此它提供了兩個最重要的特性,JsonString和JsonValue: property JsonString: TJSONString read GetJsonString write SetJsonString; property JsonValue: TJSONValue read GetJsonValue write SetJsonValue; JSonString是代表『名稱/值』配對中名稱的特性,而JsonValue則代表其中值的特性。請注意的是JsonValue的型態是TJSONValue,而根據前面表格所敘述,TJSONValue可以代表任何從TJSONValue繼承下來的類別,因此JsonValue可以代表TJSONString,TJSONNumber或是TJSONObject等。 那麼如何建立TJSONPair呢? TJSONPair定義了四個複載的建構函式,其中最重要的三個如下所示: constructor Create(const Str: TJSONString; const Value: TJSONValue); overload; constructor Create(const Str: UnicodeString; const Value: TJSONValue); overload; constructor Create(const Str: UnicodeString; const Value: UnicodeString); overload; 上面第一個建構函式可以藉由TJSONString和TJSONValue物件來建立TJSONPair物件,第二個建構函式可以使用Unicode字串和TJSONValue物件來建立TJSONPair物件,至於第3個建構函式則是使用兩個Unicode字串來建立TJSONPair物件。 例如,如果我們要建立如下的JSON『名稱/值』配對: “書名”:”實戰Delphi 2010” 那麼我們可以使用如下的程式碼: var jp : TJSONPair; begin jp := TJSONPair.Create(TJSONString.Create('書名'), TJSONString.Create('實戰Delphi 2010')); try Memo1.Lines.Add(jp.ToString); finally jp.Free; end; 上面的程式碼使用了第一個建構函式建立TJSONPair物件,TJSONPair類別的ToString方法可以把JSON物件之中的內容以文字字串形式回傳,最後釋放TJSONPair物件時,TJSONPair物件會自動釋放傳入建構函式之中的兩個TJSONString物件。 當然您也可以使用第二個建構函式來建立TJSONPair物件,如下所示: jp := TJSONPair.Create('書名', TJSONString.Create('實戰Delphi 2010')); 或是使用第3個建構函式,因為在這個範例中『名稱/值』配對中的名稱和值都是字串: jp := TJSONPair.Create('書名', '實戰Delphi 2010'); 當然,如果您需要建立如下的JSON『名稱/值』配對: "價格":45.95 由於值是數字,因此我們只能使用第一個或是第二個建構函式來建立: jp := TJSONPair.Create(TJSONString.Create('價格'), TJSONNumber.Create(45.95)); 或是: jp := TJSONPair.Create('價格', TJSONNumber.Create(45.95)); 一旦建立了TJSONPair物件,我們也可以藉由存取它的JsonString和JsonValue特性來存取『名稱/值』配對中的名稱或是值了,例如: Memo1.Lines.Add('JSONString : ' + jp.JsonString.ToString); Memo1.Lines.Add('JSONValue : ' + jp.JsonValue.ToString); X-2-4 TJSONString類別 TJSONString是從TJSONValue類別繼承下來,它是使用來代表Unicode字串的資料,它可以是『名稱/值』配對中的名稱或是值或是兩者。 TJSONString最重要的方法應該是它的建構函式了,它接受一個Unicode字串做為參數: constructor Create(const Value: UnicodeString); overload; 在Unicode字串使用建立TJSONString物件之後,如果開發人員需要加入額外的字元,那麼可以呼叫AddChar虛擬程序,AddChar會在原本的Unicode字串之後加入參數Ch的字元內容。 procedure AddChar(const Ch: WideChar); virtual; 如果開發人員需要TJSONString物件中字串的內容值,可以呼叫ToString: function ToString: UnicodeString; override; 如果開發人員只是需要TJSONString物件中字串的內容值,而不需要額外的開始”符號以及結束的”符號,那麼可以呼叫TJSONString的Value函式 function Value: UnicodeString; override; 例如下面的程式碼: var js : TJSONString; begin js := TJSONString.Create('實戰Delphi 2010'); try Memo1.Lines.Add('TJSONString.ToString : '+ js.ToString); Memo1.Lines.Add('TJSONString.Value : '+ js.Value); finally js.Free; end; 下面是呼叫ToString和Value的差異: TJSONString.ToString : "實戰Delphi 2010" TJSONString.Value : 實戰Delphi 2010 X-2-5 TJSONObject類別 TJSONObject類別從TJSONValue直接繼承下來,它代表JSON規範中封裝物件的類別。要建立TJSONObject物件非常簡單,只需要呼叫它的建構函式即可: constructor Create; 那麼如果我們需要建立如下的JSON物件內容,那麼應該如何做呢? {“書名”:”實戰Delphi 2010”} 請注意上面的結構,其實是在JSON物件之中包含一個TJSONPair物件,因此我們只需要執行下面的步驟即可:
要把TJSONPair物件或是JSON『名稱/值』配對加入到TJSONObject物件中,我們可以使用TJSONObject類別中下面兩個複載的AddPair方法: procedure AddPair(const Pair: TJSONPair); overload; virtual; procedure AddPair(const Str: TJSONString; const Val: TJSONValue); overload; virtual; 因此如果我們需要建立如下內容的TJSONObject物件: {"書名":"實戰Delphi 2010","出版年份":2010} 那麼可以使用如下的程式碼: var jo : TJSONObject; jp : TJSONPair; begin jo := TJSONObject.Create; try jp := TJSONPair.Create('書名', '實戰Delphi 2010'); jo.AddPair(jp); jo.AddPair(TJSONString.Create('出版年份'), TJSONNumber.Create(2010)); Memo1.Lines.Add(jo.ToString); finally jo.Free; end; 同樣的TJSONObject類別的ToString方法可以把其中的內容以文字字串型態回傳: function ToString: UnicodeString; override; X-2-5 TJSONNumber類別 TJSONNumber類別是從TJSONString類別繼承下來的,這是因為在JSON規範中整數和浮點數都必須使用字串形式來代表,由於TJSONString已經提供了使用字串形式來代表JSON內容的功能,因此TJSONNumber只需要從它繼承下來並且把整數和浮點數轉換為文字字串型態即可。 在前面我們看過多次建立TJSONNumber物件的範例了,它的建構函式接受一個double值的參數: constructor Create(const Value: Double); overload; 一旦建立了TJSONNumber物件,呼叫它的ToString方法即可取得其內容。 在離開本小節之前再讓我們看看三個剩下簡單的類別,它們是TJSONTrue,TJSONFalse和TJSONNull。這三個類別分別實作圖3中的true,false和null三個JSON數值。例如我們如果需要下面的JSON內容: {"書名":"實戰Delphi 2010","出版否":false, “撰寫中”:true, “出版商”:null} 那麼我們使用下面的程式碼即可: var jo : TJSONObject; jp: TJSONPair; begin jo := TJSONObject.Create; try jp := TJSONPair.Create('書名', '實戰Delphi 2010'); jo.AddPair(jp); jp := TJSONPair.Create('書名', TJSONFalse.Create); jo.AddPair(jp); jp := TJSONPair.Create('撰寫中', TJSONTrue.Create); jo.AddPair(jp); jp := TJSONPair.Create('出版商', TJSONNull.Create); jo.AddPair(jp); Memo1.Lines.Add(jo.ToString); finally jo.Free; end; 在讀者瞭解了如何使用這些JSON相關的類別之後,讓我們看看如何使用它們在分散式應用系統之中。 Delphi 2010產品技術發表會投影片和範例檔!本來昨天(星期四)就應該把2個壓縮檔放上來, 但由於昨天我一直無法連上我的部落格, 到今天連上我的部落格才發現MS的部落格不能上傳檔案, 只能上傳相片, 哇勒, 而以前CSDN提供給我的地方也不能用了, 沒想到現在連個棲身之地都沒了, 因此我得再找其他地方, 等我找到地方上傳之後再公布下載位址吧, 在這裡向各位說一聲抱歉.OK, 我已經把Delphi 2010產品技術發表會投影片和範例檔給了興德請興德放在網上讓各位下載, 各位可以在下面的地址找到(噢, 我現在才發現他們把我的英文名字也拼錯了, 哇勒是Gordon不是Golden啊): http://www.sinter.com.tw/codegear/codegear_technique.html#golden_book September 07 RAD Studio 2010背後的變化!Embarcadero終於在2009年的8 月底完成了RAD Studio 2010,2010版是Embarcadero接手Delphi,C++Builder和RAD Studio之後的第一個自行研發的版本,出乎我很意外的,2010版完全沒有延遲,不像在Borland的時期,每一個Delphi/C++Builder一定至少會延遲2個星期到一個月左右。從這次Delphi/C++Builder 2010按時推出可以說明Delphi/C++Builder研發團隊在Embarcadero終於獲得了合理的資源,而且從Delphi/C++Builder 2010推出之後,仔細的研究2010的VCL,RTL以及和R&D的人對話之後,我發覺Delphi/C++Builder 2010是一個非常重要的版本,因為許多未來Delphi/C++Builder的發展方向都從2010版默默的展開了。 首先Delphi/C++Builder 2010在編譯器進行了許多的改善,除了持續強化2009版就出現的泛型程式設計和匿名方法之外,2010的編譯器開始提供更豐富的RTTI元資料以準備提供類似Java/C#的Reflection功能,2010也提供了背景編譯,允許開發人員在IDE中一邊持續工作,一邊在背景編譯專案,此外另外有兩個同時並行的編譯器團隊,一組在做Delphi/C++Builder 2010的64位元編譯器,另一組在做跨平台的Delphi編譯器。另外32位元的編譯器小組也在研究未來第3方元件能夠以二進行形式直接昇級到新版本而無需元件的原始程式,明年應該就可以看到64位元或是跨平台的編譯器以及新的最佳化C++Builder編譯器所以編譯器快準備好了。 dbExpress早已經好跨平台的準備,因為現在所有的dbExpress相關程式碼都可以使用Delphi來撰寫,此外藉由Delphi編譯器提供更豐富的RIIT資訊,Delphi/C++Builder團隊開始研發OR Mapping的能力。這個新的OR Mapping功能也應該是跨平台的,因為它也將整合到VCL了,所以dbExpress和VCL框架和快準備好了。 再看Delphi/C++Builder的分散式技術,一直以來Delphi/C++Builder都是使用Windows平台上的分散式技術做為基礎,從Midas使用的COM,到DCOM/COM+,這一塊一直不容易改變,還好在JSON逐漸取得廣泛的應用後,Delphi/C++Builder在2009版開始支援JSON。只是Delphi/C++Builder 2009把JSON和dbExpress綁的太緊,無法輕易的使用Delphi/C++Builder來開發一般的JSON應用程式。到了Delphi/C++Builder 2010,Delphi/C++Builder團隊終於把JSON的能力封裝在數個易於使用的類別中,讓開發人員終於可以使用Delphi/C++Builder 2010進行通用的JSON開發,此外又加入了非同步以及過濾器的能力,讓Delphi/C++Builder 2010可以基於JSON開發分散式應用系統,終於打通了跨平台分散式架構的任督兩脈,DataSnap也終於可以實作在Mac OS和Linux平台中。 再看看Delphi/C++Builder 2010火熱的觸碰/手勢技術,雖然目前只能使用在Window XP,Vista和Windows 7中,但是未來Delphi將提供跨平台和移動運算的能力,那麼觸碰/手勢如何使用在其他平台和移動移動呢? 如果去研究Delphi/C++Builder 2010如何實作觸碰/手勢技術,那麼我們會驚訝的發現,Delphi/C++Builder團隊在研發觸碰/手勢技術時已經考慮到了未來把觸碰/手勢技術移植到其他平台的能力,例如如果我們搜尋TGestureEngine類別,就可以發現下面的實作程式碼: TGestureEngine = class(TCustomGestureEngine) strict private class var FDefaultEngineClass: TGestureEngineClass; class var FDefaultRecognizerClass: TCustomGestureRecognizerClass; class var FRecognizer: TCustomGestureRecognizer; 哈,注意看看這個實作程式碼,目前在Windows平台上實作的觸碰/手勢技術只是內定(Default)的類別,因為它使用了class var來宣告FDefaultEngineClass和FDefaultRecognizerClass,以及FRecognizer,這種設計很明顯的是一個plug-in設計樣例,未來當Delphi/C++Builder團隊在其他平台,例如手機的移動平台,實作了觸碰/手勢技術,那麼只需要註冊新的引擎(Engine)和Recognizer,再取代Windows平台的引擎(Engine)和Recognizer,那麼跨平台的觸碰/手勢技術就實現了。 最後回到RTL,Delphi/C++Builder的FastCode專案仍然在持續進行中,如果您在Delphi/C++Builder 2010中搜尋就可以看到更多FastCode專案的成果,此外Delphi/C++Builder團隊也開始在RTL中加入跨平台的多執行緒函式庫,打造跨平台的高效RTL。 然而我仍然希望看到Delphi/C++Builder快點支援RIA的開發,Delphi/C++Builder 2010開始支持RESTful架構令人喜出望外,但我希望再上一層樓,結合RESTful,RIA和Web技術為一體。 Delphi/C++Builder 2010在表面上呈現了許多非常實用又吸引人的新功能,而且許多的新功能都是領先業界的,不過如果您進入到Delphi/C++Builder 2010的實作原始程式世界,您會發現更多令人興奮的東西。Delphi/C++Builder團隊在擁有了足夠的研發資源之後,的確可以從Delphi/C++Builder 2010再次感受到如同Delphi 5到Delphi 7時期蓬勃發展的精神。 August 17 Jake Peavy終於康復回來了!我隊的Jake Peavy終於康復回來了, 隊上的投手群也終於再次完整了, 剛好迎接季後賽, 希望Jake Peavy早日回復最強的戰力, 那麼我隊的Jake Peavy加上Justin Verlander, Matt Cain和Ricky Nolasco就可以形成堅強的投手陣線了! August 13 觸碰,手勢,創意和技術白盒子其實觸碰科技在現在的日子裡並不稀奇,我自己也有一個觸碰手機,更別說我一堆朋友都在用iPhone。在平日使用觸碰手機的經驗並不算太好,因為手機螢幕太小,用手觸碰經常失誤(特別是對手指胖的人),因此大多只能使用觸碰筆來使用。最近Windows 7的新聞被愈炒愈熱,從目前掌握的訊息來看,Windows 7似乎的確是比Vista好,至少在速度上應該比Vista來得能夠讓人接受吧,看來Windows 7才是正式版的Vista。有MS的朋友告訴我Windows 7是一個Better Vista,不過我想Windows 7才應該是WinXP的正式接班人吧。 Windows 7其中最引人注目的功能之一便是觸碰(Touch)和Gesture(手勢)技術,許多Windows 7的介紹影片都大肆宣傳這些功能,老實說當我看到這些新功能時,我也愈得不錯,但並沒有到達驚豔的程度,為什麼? 我想是因為觸碰技術早就每天在手機中使用,沒有什麼特別的感覺,只有手勢技術比較令我覺得有趣。不過在過了幾秒之後Windows 7的觸碰和手勢技術讓我覺得就像是使用觸碰手機一樣,一開始覺得很有趣,但是這是如何做出來的卻不太瞭解,因為我想觸碰手機應該是使用了特別的軟體和開發工具,對於一般的開發人員來說是不太容易掌握的。Windows 7的觸碰技術雖然比較開放,MS提供了SDK可以讓開發人員開發使用,但當我看到面對這麼先進的技術,但仍然是使用10幾年前C/C++加上Windows訊息程式設計的方式來慢慢的開發,我就仿傚掉進一個奇怪又衝突的世界,噢別誤會我,我就是從這樣的開發模式磨練出來的開發人員,但我真覺得這樣開發實在太慢了。 不過當我看到Delphi 2010如何支援觸碰和手勢技術之後,我真的說出了Cool這個字,因為當時我的感覺就類似10多年前第一次看到PC的分散式應用系統時一樣,為什麼? 因為在Delphi中你可以就10倍速的速度開發出結合這些最新技術的應用程式,因為我們可以使用VCL框架,而不必從一行一行的程式碼開始。因此當我看到Delphi 2010的觸碰和手勢技術後,我口中不自覺的說出Cool之後,我的腦袋中立刻開始問自己: Ø 如何使用援觸碰和手勢技術? Ø VCL框架中的元件可以結合援觸碰和手勢技術嗎? Ø 這些這麼Cool的技術是怎麼做出來的? Ø 舊的Delphi/BCB應用程式可以很簡單的加入這些最新的援觸碰和手勢技術嗎? Ø 能夠定義客製化的援觸碰和手勢嗎? Ø 援觸碰和手勢技術只能執行在Windows 7平台嗎? 我知道這些問題難不到Delphi/BCB開發人員,因為VCL框架是包含原始程式的,我可以立刻分析VCL的原始程式就能夠回答這些問題,如此一來我可以以10倍數的速度使用VCL元件為我的應用程式加上最新,最cool的援觸碰和手勢技術,又可以從分析VCL框架中得知這些先進技術是如何實作出來的。在這一霎那,觸碰和手勢技術立刻從莫可探知的黑盒子成為了我們能夠掌握的技術白盒,Delphi和BCB 2010已經成功的把這些先進的技術交付到了Delphi/BCB的開發人員手中,接下來的困難不再是這些技術,而是開發人員和架構師如何運用這些技術開發出更具創意的應用程式。 Ø 想想觸碰和手勢技術與分散式應用系統的結合,你真的可以在本機中藉由觸碰一個按鈕以啟動遠端的服務而造成蝴蝶效應,Cool! Ø 想想觸碰和手勢技術與Web介面和Web應用程式的結合,觸碰和遠端遙控可以讓您無遠弗屆,Cool! Ø 想想觸碰和手勢技術與傳統MIS應用程式的結合,至少你的老板和客戶再也不需要面對大大的螢幕和小小的滑鼠,他們減少了使用滑鼠造成的「腕溝症候群」和『滑鼠手』等職業病的機會,我們也減少了被他們臭罵的機會,哈哈! 在原生Win32的觸碰世界裡,Delphi 2010讓您做主! August 11 我家出了個小綠綠!今年高中聯考終於放榜了,從6月開始每天煎熬的日子也終於結束了,回想當年我自己考高中時也沒有這麼的受煎熬,現在做父母的真是不好受。 記得在2006年5月18日我得知女兒考上祟光女中美術班時真是高興(其實祟光女中的美術班和音樂班是所謂的資優班,女兒的這一班真是厲害,全班43人,考上北一女10人,中山女中也是10人左右),因為她首戰便告捷,比她當年的老爸好多了。 http://gordonliwei.spaces.live.com/blog/cns!CCE1F10BD8108687!840.entry 3年的時間也瞬間飛逝,女兒現在幾乎都比我高了,今天高中聯考放榜,她在電話中告訴我她考上了第一志願 : 北一女,那時我的心情真是又高興,又驕傲,卻又有點自私的失落,因為我心中是希望她做個師大附中小騎士,體驗一下她老爸最引以為傲的高中。 女兒,恭喜妳! 妳比妳爸爸優秀,未來3年妳將和全國最優秀的同學一起競爭向上,希望妳仍然保持國中一步一腳印的求學精神,努力朝向妳老爸當年唸的大學 : 台灣大學,前進吧! 希望3年後妳再次打敗妳老爸,我會很樂意的再次俯首稱臣。 July 14 VCL For Web 2009繁體中文問題解決方法在追了IntraWeb團隊和Delphi團隊將近半年之後終於追出了解決方案,由於了IntraWeb團隊和Delphi團隊都忙於下一版Delphi的開發工作,因此為了讓他們能夠解決這個問題實在是因為徹底發揮了『追,纏,黏』的功夫。不多說了,如果有朋友在VCL For Web下有繁體中文的問題的話,那麼請使用下列的方法即可解決: 1. 從http://www.atozed.com/intraweb/Download/Files/index.EN.aspx下載並且安裝IntraWeb build 10.0.15版 2. 把文後下附的UTF8ContentParser.pas放到您的專案目錄中 3. 在您的專案中加入UTF8ContentParser.pas 4. 在您的應用程式中的uses句子中加入參考UTF8ContentParser 5. 重新編譯和執行 現在VCL For Web在繁體中文作業系統中的問題就會自動解決了。 我已經和IntraWeb團隊和Delphi團隊確認在下一版的Delphi中UTF8ContentParser會加入到VCL框架中,因此下一版Delphi1出來時就不再需要上述的步驟了。 Have Fun! // TUTF8ContentParser is a WebRequest content parser that parses UTF-8 requests. // TUTF8ContentParser class automatically replace the default content parser when this unit (UTF8ContentParser) // is used in a web application. You should only use UTF8ContentParser in web applications that generate UTF-8 // responses. // // To generated UTF-8 encoded responses, set Response.ContentType as follows before setting Response.Content. // Response.ContentType := 'text/html; charset=UTF-8'; // // Note that, if your application uses the ReqMulti unit to parse multipart content, ReqMulti must appear in the application // uses list after UTF8ContentParser. unit UTF8ContentParser; interface uses SysUtils, Classes, Masks, Contnrs, HTTPApp, ReqFiles, HTTPParse; type { TUTF8ContentParser } TUTF8ContentParser = class(TContentParser) private FContentFields: TStrings; public destructor Destroy; override; function GetContentFields: TStrings; override; class function CanParse(AWebRequest: TWebRequest): Boolean; override; end; implementation uses WebConst, WebComp, BrkrConst, Windows; { TUTF8ContentParser } class function TUTF8ContentParser.CanParse(AWebRequest: TWebRequest): Boolean; begin Result := True; end; destructor TUTF8ContentParser.Destroy; begin FContentFields.Free; inherited Destroy; end; procedure ExtractHeaderFields(Separators, WhiteSpace: TSysCharSet; Content: PAnsiChar; Strings: TStrings; Decode: Boolean; Encoding: TEncoding; StripQuotes: Boolean = False); forward; function TUTF8ContentParser.GetContentFields: TStrings; begin if FContentFields = nil then begin FContentFields := TStringList.Create; if WebRequest.ContentLength > 0 then begin ExtractHeaderFields(['&'], [], PAnsiChar(WebRequest.RawContent), FContentFields, True, TEncoding.UTF8); end; end; Result := FContentFields; end; // Version of HTTP.ExtractHeaderFields that supports encoding parameter procedure ExtractHeaderFields(Separators, WhiteSpace: TSysCharSet; Content: PAnsiChar; Strings: TStrings; Decode: Boolean; Encoding: TEncoding; StripQuotes: Boolean = False); var Head, Tail: PAnsiChar; EOS, InQuote, LeadQuote: Boolean; QuoteChar: AnsiChar; ExtractedField: AnsiString; WhiteSpaceWithCRLF: TSysCharSet; SeparatorsWithCRLF: TSysCharSet; procedure AddString(const S: AnsiString); var LBytes: TBytes; LString: string; begin LBytes := BytesOf(S); LString := Encoding.GetString(LBytes); Strings.Add(LString); end; function DoStripQuotes(const S: AnsiString): AnsiString; var I: Integer; InStripQuote: Boolean; StripQuoteChar: AnsiChar; begin Result := S; InStripQuote := False; StripQuoteChar := #0; if StripQuotes then for I := Length(Result) downto 1 do if CharInSet(Result[I], ['''', '"']) then if InStripQuote and (StripQuoteChar = Result[I]) then begin Delete(Result, I, 1); InStripQuote := False; end else if not InStripQuote then begin StripQuoteChar := Result[I]; InStripQuote := True; Delete(Result, I, 1); end end; begin if (Content = nil) or (Content^ = #0) then Exit; WhiteSpaceWithCRLF := WhiteSpace + [#13, #10]; SeparatorsWithCRLF := Separators + [#0, #13, #10, '"']; Tail := Content; QuoteChar := #0; repeat while CharInSet(Tail^, WhiteSpaceWithCRLF) do Inc(Tail); Head := Tail; InQuote := False; LeadQuote := False; while True do begin while (InQuote and not CharInSet(Tail^, [#0, '"'])) or not CharInSet(Tail^, SeparatorsWithCRLF) do Inc(Tail); if Tail^ = '"' then begin if (QuoteChar <> #0) and (QuoteChar = Tail^) then QuoteChar := #0 else begin LeadQuote := Head = Tail; QuoteChar := Tail^; if LeadQuote then Inc(Head); end; InQuote := QuoteChar <> #0; if InQuote then Inc(Tail) else Break; end else Break; end; if not LeadQuote and (Tail^ <> #0) and (Tail^ = '"') then Inc(Tail); EOS := Tail^ = #0; if Head^ <> #0 then begin SetString(ExtractedField, Head, Tail-Head); if Decode then AddString(HTTPDecode(AnsiString(DoStripQuotes(ExtractedField)))) else AddString(DoStripQuotes(ExtractedField)); end; Inc(Tail); until EOS; end; initialization RegisterContentParser(TUTF8ContentParser); end. June 29 Borland最後花落誰家似乎仍有變數!今年5月6日根據外電報導英國軟體廠商Micro Focus將收購Borland,雖然我早已離開Borland數年之久,也對後期的Borland實無好感,但從學生時期便建立的感情仍然讓我無法忽略Borland在業界的消息,在5月7日我得知Borland即將被收購的消息時,心中並無太大的心情波動,因為在離開Borland時我早已預測Borland將會瓦解,因為那時Borland的CEO每天只想著股票的價格,把產品搞得亂七八糟,又要求每個人都做銷售,連技術人員都被送去做銷售的培訓,取消了Borland優良傳統的深度產品和技術培訓,那時我被強迫送去參加了數次的銷售培訓,最後我心中只想著Borland還要技術人員做什麼,這樣下去乾脆大家都轉做銷售人員不就好了,我只覺得那時的Borland實在是@!$#@%#$&^$%&。 沒想到最近又有消息指出在Micro Focus出了收購價之後,似乎又有不具名的對象也出價想要收購Borland,看來在Borland的股東沒有正式投票之前,Borland花落誰家似乎仍有變數。 May 19 CodeGear大幅揭露Delphi和C++Builder未來的發展方向!WoW,在這次的Delphi Live 2009的大會中CodeGear可以說是精銳盡出,透露了許多Delphi和C++Builder開發人員關心的產品發展方向,從這個次的大會中也終於看出Embarcadero在併購了CodeGear之後的確是真的投入研發資源讓Delphi和C++Builder放手發展,這對比於Borland在日前被Micro Focus併購,讓Borland這個昔日的金字招牌也蒙塵於競爭激烈的IT界,想必當初的Borland董事會在夜深人靜的時候也會深切懊惱不該斷送當初大好的開發工具江山吧。 在這次的Delphi Live 2009中CodeGear揭露了目前正在開發的專案,下面是這些專案的列表: Project Weaver User Experience Documentation IDE usability Team Productivity Touch IDE – Insight (easy Keyboard access to almost everything) Improvements to DataSnap Firebird Support .NET AOP SCM Support Enhanced RTTI Support Attribute Support Seamless .NET <> Native communication Windows 7 APIs and Direct 2D Full Support of SOAP 1.2 Clients Project X Cross-platform Windows, Mac OS, and Linux Cross-platform component library DataSnap on all platforms Project Chromium, Quality, Quality Quality, Quality, Quality Pascal Code Formatter Documentation of the OTA New Data binding model allowing binding to almost any property on a control More integration with the database tools. Project Commodore 64 Bit native Full compiler, RTL and VCL support for 64 native Multi-Core. Multi-threaded applications. 光從這些專案的列表就非常令人興奮了,因為很快的我們就可以在Delphi和C++Builder中看到這些技術實作出來,如果您不太瞭解其中的一些技術,沒有關係,在下面我將簡單的說明一下,因為既然Delphi Live 2009已經公開揭露了大部份的資訊,我也就可以說明一下今年年初我參加Embarcadero大會聽到的一些東西了。 Project Weaver就是即將推出的Delphi 2010和C++Builder 2010,雖然上面的列表主要是和Delphi相關的,但C++Builder 2010也將有許多新的功能。Project Weaver令人期待的就是支援Windows 7和Touch這個所謂的觸碰技術了,由於Windows 7已經獲得了業界好評因此Weaver將是第一個支援Windows 7開發的原生工具,此外Weaver也將觸碰技術實作在VCL框架之中,這代表Delphi和C++Builder的開發人員將可以非常簡單就能夠開發觸碰式的應用程式了,這實在太cool,更棒的是我聽說Weaver的觸碰技術不局限於Windows 7,似乎XP等不支援觸碰技術的OS也將由VCL框架來支援,只要您擁有觸碰設備即可,WoW,我等不及來玩玩這個功能了。 另外Weaver在編譯器方面將有大幅的強化,CG準備為Delphi和C++Builder提供類似Java和.NET的Reflection技術,這也就是Enhanced RTTI Support,因為Enhanced RTTI Support技術將做為稍後我即將說明的Delphi和C++Builder下一世代的資料存取技術New Data binding model的基石。 另外Weaver的DataSnap技術將有重大的進步,因為CG也即將把DataSnap帶入到Linux和Mac環境,DataSnap也終於開始實現它在跨平台方面的優勢。Weaver的DataSnap將支援更強大的JSON分散式架構,除了提供tcp/ip之外,也支援http/https,REST,資料加密,資料壓縮,非同步呼叫和伺服器回叫用戶端等強大的功能,我對DataSnap特別感到興趣,特別是想到DataSnap能夠提供跨平台的分散式開發時就令人高興。 Project X是跨平台Delphi的專案,我們也可以稱它為DelphiX,它將讓開發人員可以在Windows,Linux和Mac環境下使用Delphi,CG也在考慮開發一個跨平台的元件框架(VCLX? CLX?, 這個目前我也不清楚),而且會把DataSnap移植到Linux和Mac中,仔細看看下面這張由Robert Love提供的照片,CG已經快完成了Delphi在Mac下的編譯器,這張照片顯示CG的人使用Delphi For Mac的編譯器編譯一個簡單的文字程式並且實際執行在Mac OS中。 今年年初時CG便透露Delphi For Mac的編譯器可能在2009年的夏天可以完成,呵呵,想想當初Delphi 1.0即是用Delphi編譯器開發的模型,那麼Delphi For Mac 1.0似乎也即將…. Project Commodore是Delphi 64的專案,Commodore應該會在2011年出現,它即將把Delphi全面帶入64位元的世界,類似當年Delphi 2.0把Delphi從16位元帶入32位元世界的里程碑一樣,Commodore將在Delphi的發展史中再次設定另外一個里程碑,而且Commodore也將提供多核心,多執行緒開發的新技術/新函式庫。 Project Chromium應該是一個Delphi/C++Builder的再進化專案,其中最關鍵的技術是New Data binding model,這個技術是我早已等待多年的技術,因為它將提供類似MS Entity Framework,RoR的ActiveRecord,或是Hibernate等的功能,其實我應該說它是Chuck在離開Borland之前於Borland內部進行的秘密計劃Apollo的實現,早在數年前Chuck便想為Delphi加入這些先進的資料存取技術,看看Borland浪費多少年,又浪費了多少當年這些技術精英的眼光和技術(其實還有許多非常棒的秘密計劃到現在還未見曙光,包括了Chuck的Z語言,Danny的編譯器和除錯器先進技術等以及Anders早在Delphi 2就想做的Delphi Garbage Collector)。 由於我的時間不多,不能再把更多比較詳細的資訊和大家分享,很抱歉,不過各位可以到下面的URL中仔細看看Robert Love提供的照片,您會看到許多的秘密,有機會的話我再解釋它們好了,Have Fun! http://picasaweb.google.com/LovePhotoStore/DelphiLive2009?feat=directlink#5336279721373168578 April 29 對今年的NBA季後賽沒有多大的興趣了!Kevin Garnett是我個人自Michael Jordan之後最喜歡的球員,看Garnett打球也是我最喜歡的休閒活動之一,但今年Garnett深受受傷之苦,到現在季後賽開打也一直沒有回來,因此今年我也沒什麼興致看NBA季後賽了,希望Garnett能夠儘快康復,再打幾年的好球吧。 不過心底卻有一種說不出來的淡淡憂愁 : 『狼王似乎漸老矣』。 April 28 也許我不應該說的!自從不再是CG的正式員工之後,我也必須申請參加CG產品的Beta,獲得批准之後我才能拿到Beta的產品來測試。因此前一陣子我也和一些我的朋友一樣申請參加下一版C++Builder/Delphi的Beta,很幸運的我獲得了許可,也終於在最近拿到了Beta版來玩一玩,以便填補一下Diablo 3出來之前苦苦等待的真空時期。 下一版的C++Builder/Delphi在進行Beta測試早已不是新聞了,因為Delphi的產品經理Nick已經公開的討論了許多次,CG現在也在她的網站上公開歡迎有興趣的人申請參加,申請的URL如下: https://beta.embarcadero.com/callout/default.html?callid={56DB5BD1-6DD9-40EF-8743-7C3E293085E9} 事實上我也是在上述的URL中申請參加的。 那麼這篇文章到底是討論呢? OK,除了我希望把申請參加Beta的資訊和尚不知道的朋友分享之外,主要的原因就是我有許多已經參加Beta的朋友在測試新版Delphi時發現他們無法在XP下連結到MS SQL Server 2005或是2008,一旦新版Delphi在XP下試著連結2005或是2008時就會出現代號為340的錯誤: 我也在大陸的一些網站上看到許多人就開始批評下一版Delphi的DBX驅動程式有大臭蟲,又不穩定,連MS SQL Server都連不上,但我覺得很奇怪的是既然現在是Beta版那當然有臭蟲,否則進行Beta測試是做什麼的呢?不過我想說的是這個340錯誤並不是DBX的臭蟲,而是我的朋友們並不瞭解新的DBX對於MS SQL Server的驅動程式進行了大幅的改變,因此在使用下一版Delphi連結MS SQL Server 2005/2008之前,開發人員需要安裝一個軟體。那麼是什麼軟體呢? 在回答之前,我想順便談談如何進行有效的Beta測試並且以這個例子做為說明,一旦讀者掌握了之後就做個更好的Beta測試人員了。 但是在下一版Delphi中卻改變了使用的用戶端函式庫,DBX4改用了sqlncli10.dll,如下所示: 而sqlncli10.dll是MS SQL Server 2008的原生用戶端程式(Native Client),而這也就是為什麼會出現340錯誤的原因,因為在下一版Delphi的DBX是改用了Native Client來連結MS SQL Server而不再使用oledb,出現340錯誤是因為我們的機器中沒有安裝MS SQL Server 2008的原生用戶端程式的原因。 因此,我到下列的URL中下載MS SQL Server 2008的原生用戶端程式: http://www.microsoft.com/downloads/details.aspx?FamilyId=C6C3E9EF-BA29-4A43-8D69-A2BED18FE73C&displaylang=en 下載Microsoft SQL Server 2008原生用戶端安裝程式 : sqlncli.msi並且安裝它之後,就可以使用下一版Delphi來連結MS SQL Server 2005/2008了,也不會再出現340的錯誤了。 從上面的討論我們可以推論出下一版Delphi的DBX對於MS SQL Serevr有如下的特性:
嗯, 推論了上述3點之後我更喜歡新的DBX了。 April 21 善用C++Builder 2009的Pre-Compiled header精靈也許是我的記憶已經很模糊了,我記得在C++Builder 5時能夠提供了背景編譯的能力,允許BCB的開發人員在IDE中編譯專案時能夠啟動在背景編譯,如此一來BCB的開發人員就可以在等待BCB編譯專案的同時在IDE中執行一些其他的工作,BCB之所以提供這個功能實則是因為C++是一個3-pass的編譯器,因此需要遠多於One-Pass的Delphi編譯器更多的編譯時間。但當時BCB 5的背景編譯有許多的限制,例如開發人員無法異動正在編譯中的專案,也無法異動編譯專案的圖形使用者介面設計等,因此大部份BCB開發人員使用背景編譯編譯BCB專案時,大都是在BCB的IDE中對其他的專案進行同時開發的工作。 另外一個BCB非常重要的功能就是Code Insight,這個功能能夠幫助開發人員大幅減少需要撰寫打字的程式碼,進而增加開發的生產力。但是我知道很多BCB的開發人員關閉了這項功能,因為在早期的BCB版本中這個功能實在太慢了,導致許多BCB的開發人員抱怨為什麼BCB無法像Delphi的Code Insight一樣那麼的快速。其實BCB的Code Insight太過緩慢的問題我個人也是感同身受,因為我記得每次在做BCB的活動時,為了避免在BCB編輯器中撰寫程式碼反應太過緩慢的問題,我也都是關閉了BCB的Code Insight功能。 ![]() 最後一個我要討論的問題就是BCB的Pre-Compiler Header了,雖然BCB很早就提供了Pre-Compiler Header功能以加快編譯速度,但老實說早期BCB提供的Pre-Compiler Header雖然的確能夠幫助開發人員加快編譯速度,但這個Pre-Compiler Header在C++Builder 2009之前已經有數年沒有改善了,因此仍然有很大的進步空間。 OK,看到這裡您可能會想,為什麼同時敘述上述的三個問題呢?是它們有什麼共點嗎?不然這篇文章到目前看起來是令人摸不著頭緒的。OK,現在就讓我們回到本篇文章的正題。 下圖是我在RAD Studio 2009中的一個範例BCB專案,在沒有使用新的Pre-Compiled header精靈之前,在第一次編譯這個C++Builder專案時RAD Studio仍然會為這個專案建立Pre-Compiled header,如此一來當開發人員稍後再次編譯這個專案時,編譯速度就會加快。例如如果我修改下圖中的 this->Button1->Caption = "test"; 為 this->Button1->Caption = "測試"; 接著再次編譯,那麼此時C++Builder 2009需要再次編譯21281行的程式碼,雖然整個編譯速度算是相當快速,但仍然令人奇怪,為什麼只是修改一行的程式碼卻需要編譯21281行。 ![]() 其實仔細觀察原始程式就可以發現問題所在,因為在原始程式中存在如下的程式碼: #include <vcl.h> #pragma hdrstop #include "SecondForm.h" #include "MainForm.h" 舊的BCB Pre-Compiled header精靈只把VCL.H相關的程式碼預先編譯,但再看看原始程式的表頭檔,我們卻發現了下面的程式碼: #include <Classes.hpp> #include <Controls.hpp> #include <StdCtrls.hpp> #include <Forms.hpp> #include <DB.hpp> #include <DBCtrls.hpp> #include <DBGrids.hpp> #include <ExtCtrls.hpp> #include <Grids.hpp> 問題似乎就出現在這裡,因此我們需要一個能夠分析整個專案的Pre-Compiled header精靈幫助我們產生最佳的預先編譯表頭資訊,並且能夠讓開發人員進行客製化的調整。 C++Builder 2009新的Pre-Compiled header精靈就可以幫助我們完成這些工作,讓我們現在就來看看它的功效如何。 首先點選Tools|Pre-Compiled header Wizard…功能表啟動精靈,如下圖所示,如果你是第一次使用這個精靈,那麼請選擇第一個選項為專案進行一次完整的分析: ![]() 在精靈成功分析之後,它會顯示如下的對話盒,詢問你客製化的設定,例如你可以設定要包含那些表頭檔或是排除特定的表頭檔。 ![]() 點選『Next>>』按鈕之後會顯示最後精靈進行的設定,它決定了預先編譯表頭檔中包含的資訊: ![]() 繼續點選『Next>>』按鈕,最後BCB會產生一個新的表頭檔pch1.h,例如在我的範例中最後的pch1.h包含如下的資訊: /* This precompiled header include file was generated on 2009/4/21 下午 03:00:09 by the RAD Studio Precompiled Header Wizard with the following settings: Project: G:\MyBogs\20090421\Demos\pBCBDemo.cbproj AllowUnguarded = 0 ExcludeProjectFiles = -1 IncludePathsOn = -1 IncludePaths = ExcludePaths = IncludeCount = 1 ManageHeader = -1 */ #ifndef pch1_H #define pch1_H #include <vcl.h> #include <tchar.h> #include <DB.hpp> #include <DBClient.hpp> #include <DBGrids.hpp> #include <DBXMsSQL.hpp> #include <Provider.hpp> #include <SqlExpr.hpp> #include <DBXInterbase.hpp> #endif 在精靈產生了pch1.h並且加入到專案之中後,請先進行一次完整的專案Build讓BCB編譯器建立新的預先編譯表頭檔。有了新的預先編譯表頭檔之後如果我再回到前面修改: this->Button1->Caption = "測試"; 為 this->Button1->Caption = "第2次測試"; 接著選擇make專案,那麼現在BCB只會編譯86行的程式碼,比使用舊的預先編譯方式快了好幾倍的速度。 更棒的是,使用新的Pre-Compiled header精靈之後由於它會產生最佳化的表頭資訊,因此此時如果你再使用BCB的Code Insight,你會發現Code Insight快速的不得了,例如在我的機器中此時再使用BCB的Code Insight功能,它的反應速度幾乎和Delphi的Code Insight一樣快了,現在我再也不需要關閉BCB的Code Insight功能了。 BTW,RAD Studio 2009的Update 3不但修改了許多的臭蟲,IDE的速度又加快了不少,例如對於相同的BCB專案,使用Update 3的編譯速度硬是比Update 1和Update 2又快了許多,絕對建議您儘快昇級到Update 3。 OK,解決了預先編譯和Code Insight的問題之後,最後就是BCB的背景編譯了,在C++Builder 2006/2007/2009中背景編譯都被移除了,因為舊的背景編譯限制太多,在BCB 5那時沒有多核CPU的時代,舊的背景編譯架構也無法利用現在最新的多核CPU,不過我知道下一版的BCB也許將提供全新的背景編譯技術,不但速度快,限制又少,甚至可以讓開發人員在背景編譯專案的同時又進行相同專案的持續開發,這對於大型的BCB專案來說實在太棒了。 試著想想,未來在BCB中我們將可結合預先編譯表頭,背景編譯和快速的Code Insight,那麼使用BCB來進行C++專案的開發將會非常的愉快,因為如此一來BCB即可讓C++的開發人員享受類似Delphi,C#和Java等快速開發的環境了。 February 06 2009開年動動腦!在2009的大年初一終於去了花蓮一遊,應該有2, 3年沒有到花蓮了吧,當我走在海岸路之際乾涸許久的心靈仿佛立刻久旱逢甘霖,滋潤了起來,連腦筋似乎都活絡了起來。隔天騎著自行車沿著海岸路一路南行的路程中,封塵已久的思緒也逐漸的脫繭而出,回想這1,2年的變化可真大啊,不但在我個人的小世界中工作劇烈的改變中,世界的經濟狀況的變化更是令人瞠目結舌,對於5年級世代的人來說,在40多歲遇上這些巨大的衝擊應該是相當大的打擊吧。 回想這幾年來,從Borland,到DevCo,CodeGear最後到Embarcadero,我個人認為C++Builder,Delphi和JBuilder的未來似乎已經漸入佳境,為什麼? 因為:
http://dn.codegear.com/article/39174 在這篇文章中Nick說明了Delphi編譯器的歷史以及Delphi編譯器即將發展的方向,在其中Nick說Delphi64位元的編譯器將在2010年中才大概會準備好,因此合理的判斷Delphi 64最早應該會在2010底或是2011年初才可能出現,那麼在2009年C++Builder和Delphi會做什麼呢? 這也得從為什麼原生Delphi 64會從2009年底延遲到2010或2011年分析起。 在Nick的文章中說明了,CodeGear因為決定為C++Builder和Delphi開發一個共同的後端編譯器(Compiler Back End),因此延遲了原生BCB 64和Delphi 64,所謂共同的後端編譯器是指BCB 64和Delphi 64將使用一個共用的最佳化和機械程式碼產生器,如此一來不但日後在維護和發展上比較方便,由於BCB是使用由Object Pascal撰寫的VCL框架,因此當這兩個產品使用相同的後端編譯器之後,可讓BCB和Delphi的相容性更高。而且現在BCB在C/C++程式語言方面開始率先支援CPP0X標準,而Delphi又開始進入增加新的程式語言功能的階段,這個新的後端編譯器可以讓BCB和Delphi同時在程式語言方面大幅增加新的功能,因此非常讓人期待,更重要的是CodeGear可以在這個新的後端編譯器中加入更多最佳化的功能,在編譯器最佳化方面從Borland後期開始就很久沒有著墨了,因此我個人非常期待這個機會。 更有趣的是,在CodeGear中已經有許多人提倡混合編譯和跨平台編譯許多年了,但Borland/DevCo/CodeGear一直都沒有朝混合編譯和跨平台編譯發展,直到現在這個機會。在說明什麼是混合編譯和跨平台編譯之前,讓我們看看現在的BCB和Delphi這兩個產品如何編譯Delphi和C/C++原始程式。 在BCB和Delphi的BIN目錄下,BCC32和DCC32分別是BCB和Delphi的前端編譯器(Front End Compiler),目前BCB和Delphi分別是使用各自的後端編譯器,它們是comp32x.dll和dcc120.dll(我是使用RAD Studio 2009),如果我使用TDump來檢查這兩個dll輸出的函式,我們可以得到如下的結果: 2009開年動動腦! 在2009的大年初一終於去了花蓮一遊,應該有2, 3年沒有到花蓮了吧,當我走在海岸路之際乾涸許久的心靈仿佛立刻久旱逢甘霖,滋潤了起來,連腦筋似乎都活絡了起來。隔天騎著自行車沿著海岸路一路南行的路程中,封塵已久的思緒也逐漸的脫繭而出,回想這1,2年的變化可真大啊,不但在我個人的小世界中工作劇烈的改變中,世界的經濟狀況的變化更是令人瞠目結舌,對於5年級世代的人來說,在40多歲遇上這些巨大的衝擊應該是相當大的打擊吧。 回想這幾年來,從Borland,到DevCo,CodeGear最後到Embarcadero,我個人認為C++Builder,Delphi和JBuilder的未來似乎已經漸入佳境,為什麼? 因為: 第1, 2008年底Borland傳來非常不好的訊息,整個公司巨幅虧損不說,CEO等高階主管相繼離職,看來Borland出售開發工具之後不但沒有轉運,反而愈來愈糟糕,更證實了數年前 Borland決定放棄開發工具走向ALM是嚴重的錯誤,C++Builder,Delphi和JBuilder能夠離開Borland是最好的結果。 第2, 在2009年年初Embarcadero邀請我去舊金山參加全球大會,在Embarcadero的全球大會上我終於聽到了類似早年Borland全球大會的良好慣例,Embarcadero的CEO除了在銷售方面的強調之外,更強調了產品和品質的重要性,整個Embarcadero的全球大會也提供了許多產品和技術方面的訓練,而不像後期的Borland全球大會,Borland CEO只有滿嘴的空談和股票經,根本沒有公司和產品的發展計劃,整個Borland全球大會只有銷售訓練,產品和技術的內容一點都沒有。因此我在Embarcadero的全球大會上看到了C++Builder,Delphi和JBuilder在Embarcadero將有完全不一樣的未來。 第3, 全世界經濟狀況的改變讓許多人瞭解到簡單,好用的東西是最好的,因此許多人從昂貴的Java/.NET世界回到了原生Win32的開發世界中,準備迎接足夠未來數年使用的原生Win64開發平台的到來,這給了C++Builder,Delphi一個絕好的機會,因為原生Win64開發正是C++Builder,Delphi未來的發展道路。 第4, 這正是我想深入一點討論的內容,從許多跡象分析中可以發覺C++Builder,Delphi和JBuilder正在進行巨大的發展中,這些變化將帶來重大的改變,也將會激勵Win32和Win64開發工具的市場,對於C++Builder,Delphi和JBuilder的開發人員來說也將會覺得振奮不已。那麼C++Builder,Delphi和JBuilder會什麼樣的巨大改變呢? 讓我們一一的從一些蛛絲馬跡中分析一下,順便做為2009開年的腦筋體操,其實這些分析正是當我在花蓮海岸路一邊騎自行車一邊思考的結果。 首先我們知道從2年前開始DevCo和CodeGear便一直有公佈C++Builder,Delphi和JBuilder的發展路線圖,雖然後來DevCo和CodeGear的命運坎坷,以致C++Builder,Delphi和JBuilder的發展路線圖不斷的延後和改變,但在C++Builder,Delphi和JBuilder最終進入Embarcadero之後C++Builder,Delphi和JBuilder的發展路線也終於開始穩定下來。在前一陣子Delphi的產品經理Nick Hodges寫了一篇有關未來Delphi編譯器的文章: http://dn.codegear.com/article/39174 在這篇文章中Nick說明了Delphi編譯器的歷史以及Delphi編譯器即將發展的方向,在其中Nick說Delphi64位元的編譯器將在2010年中才大概會準備好,因此合理的判斷Delphi 64最早應該會在2010底或是2011年初才可能出現,那麼在2009年C++Builder和Delphi會做什麼呢? 這也得從為什麼原生Delphi 64會從2009年底延遲到2010或2011年分析起。 在Nick的文章中說明了,CodeGear因為決定為C++Builder和Delphi開發一個共同的後端編譯器(Compiler Back End),因此延遲了原生BCB 64和Delphi 64,所謂共同的後端編譯器是指BCB 64和Delphi 64將使用一個共用的最佳化和機械程式碼產生器,如此一來不但日後在維護和發展上比較方便,由於BCB是使用由Object Pascal撰寫的VCL框架,因此當這兩個產品使用相同的後端編譯器之後,可讓BCB和Delphi的相容性更高。而且現在BCB在C/C++程式語言方面開始率先支援CPP0X標準,而Delphi又開始進入增加新的程式語言功能的階段,這個新的後端編譯器可以讓BCB和Delphi同時在程式語言方面大幅增加新的功能,因此非常讓人期待,更重要的是CodeGear可以在這個新的後端編譯器中加入更多最佳化的功能,在編譯器最佳化方面從Borland後期開始就很久沒有著墨了,因此我個人非常期待這個機會。 更有趣的是,在CodeGear中已經有許多人提倡混合編譯和跨平台編譯許多年了,但Borland/DevCo/CodeGear一直都沒有朝混合編譯和跨平台編譯發展,直到現在這個機會。在說明什麼是混合編譯和跨平台編譯之前,讓我們看看現在的BCB和Delphi這兩個產品如何編譯Delphi和C/C++原始程式。 在BCB和Delphi的BIN目錄下,BCC32和DCC32分別是BCB和Delphi的前端編譯器(Front End Compiler),目前BCB和Delphi分別是使用各自的後端編譯器,它們是comp32x.dll和dcc120.dll(我是使用RAD Studio 2009),如果我使用TDump來檢查這兩個dll輸出的函式,我們可以得到如下的結果: Exports from comp32x.dll 44 exported name(s), 44 export addresse(s). Ordinal base is 1. Sorted by Name: RVA Ord. Hint Name -------- ---- ---- ---- 0016E65C 32 0000 ASMFILENAME 0016E664 34 0001 ASMOPTIONS 0010C008 44 0002 BCCGETINTERFACE 00005A44 25 0003 BREAKCOMPILE 000FFAB8 14 0004 BROWSERFILEINDEXTONAME 000FCE90 1 0005 BROWSERFINDDECLARATION 000FE48C 6 0006 BROWSERFINDSYMBOL 000FD1D0 4 0007 BROWSERGETAUTORESULTTYPE 000FD1C4 3 0008 BROWSERGETAUTOTYPE 000FF9DC 13 0009 BROWSERGETBASETYPE 000FE4B8 7 000A BROWSERGETOBJTYPE 000FD0D0 2 000B BROWSERGETREFERENCES 000FF82C 11 000C BROWSERGETRESULTTYPE 000FE5F8 8 000D BROWSERGETSYMBOLFLAGS 000FDCE0 5 000E BROWSERGETSYMBOLS 000FF070 9 000F BROWSERGETSYMBOLTEXT 000FF0EC 10 0010 BROWSERGETTYPECODE 000FF940 12 0011 BROWSERGETTYPESYMBOL 000054F4 28 0012 COMPILE 00005528 29 0013 COMPILEANDBROWSE 00005568 30 0014 COMPILEANDKIBITZ 000055E0 31 0015 COMPILEWITHOPTIONS 0014110C 36 0016 COMPMESSAGES 0014A0F3 24 0017 CONFIG 0016E7C0 27 0018 CUMLINENO 0010CCEC 22 0019 DbEval32InitProc 00141BC4 40 001A ERRBREAK 001CAD28 26 001B FILENAME 00141BC0 37 001C FIRSTERROR 00141BB8 38 001D FIRSTWARNING 00004B2C 42 001E FLUSHPCH 0011C0AC 43 001F GetCompilerListeners 00100888 16 0020 KIBITZGETARGINDEX 0010087C 15 0021 KIBITZGETKIND 00100D20 21 0022 KIBITZGETOVERLOADS 00100CEC 20 0023 KIBITZGETVALIDSYMBOLS 00100898 17 0024 KIBITZRESULTGETFLAGS 001008D0 19 0025 KIBITZRESULTGETNAME 001008B0 18 0026 KIBITZRESULTSETFLAGS 0016E660 33 0027 OBJFILENAME 00005614 41 0028 TOTALERRORCOUNT 00141BBC 39 0029 WARNINGCOUNT 00149C70 35 002A WARNINGS 0013E0F8 23 002B ___CPPdebugHook Exports from dcc120.dll 105 exported name(s), 112 export addresse(s). Ordinal base is 8. Sorted by Name: RVA Ord. Hint Name -------- ---- ---- ---- 0000ECA0 73 0000 BrowserFindDeclaration 0000F54C 84 0001 BrowserFindSymNamespace 0000CC24 58 0002 BrowserFindSymSource 0000F4FC 83 0003 BrowserFindSymUnit 0000CBA0 57 0004 BrowserFindSymbol 0000F4C8 82 0005 BrowserFindUnit 0000F660 89 0006 BrowserGetAncestor 0000F738 93 0007 BrowserGetArrayIndex 0000F6FC 92 0008 BrowserGetArrayIndexSymFromType 0000F7CC 96 0009 BrowserGetArrayOfSym 0000F7A8 95 000A BrowserGetArrayOfSymFromType 0000F7E8 97 000B BrowserGetArrayOfType 0000F754 94 000C BrowserGetArrayOfTypeFromType 0000F974 102 000D BrowserGetAssemblyLocation 0000F27C 78 000E BrowserGetAutoResultType 0000F21C 77 000F BrowserGetAutoType 0000EC64 72 0010 BrowserGetBaseType 0000E468 64 0011 BrowserGetCallingConvention 0000F620 88 0012 BrowserGetClassHelpers 0000F5A4 85 0013 BrowserGetClassRef 0000FAB0 104 0014 BrowserGetContainsList 0000F804 98 0015 BrowserGetDefaultProperty 0000E3C8 63 0016 BrowserGetDefaultValue 0000F2E0 79 0017 BrowserGetDirectlyUsedUnits 0000F6D8 91 0018 BrowserGetHelpedType 0000F6A0 90 0019 BrowserGetImplementedInterfaces 0000F5F0 87 001A BrowserGetInheritedScope 0000F0D0 75 001B BrowserGetObjType 0000D900 61 001C BrowserGetOverloads 0000F948 101 001D BrowserGetPointedType 0000F010 74 001E BrowserGetReferences 0000EB84 69 001F BrowserGetResultType 0000EBCC 70 0020 BrowserGetResultTypeType 0000F4B0 81 0021 BrowserGetSymbolDocumentation 0000DB5C 62 0022 BrowserGetSymbolFlags 0000FA88 103 0023 BrowserGetSymbolNameOffset 0000F3F4 80 0024 BrowserGetSymbolPath 0000E480 65 0025 BrowserGetSymbolText 0000E4B0 66 0026 BrowserGetSymbolTextBuff 0000F5CC 86 0027 BrowserGetSymbolValue 0000D7D0 60 0028 BrowserGetSymbols 0000F888 100 0029 BrowserGetSymbolsFromUnit 0000EB58 68 002A BrowserGetTypeCode 0000E9C8 67 002B BrowserGetTypeCodeType 0000F200 76 002C BrowserGetTypeFromSymbol 0000EC10 71 002D BrowserGetTypeSymbol 0000F874 99 002E BrowserGetUnitSymbol 0008852C 108 002F BuildPackages 0007A994 107 0030 CallNextUnitFreeHook 0000B4BC 52 0031 ClearAllStates 0000B4CC 53 0032 ClearCompState 00002868 20 0033 ClearPackageCache 00002854 19 0034 ClearUnitCache 000021BC 13 0035 CompilerCompile 000021E0 14 0036 CompilerCompileCmdLine 0000219C 12 0037 CompilerCompileUnit 00001FA8 11 0038 CompilerDone 00002738 18 0039 CompilerFindError 0000237C 15 003A CompilerGetUnit 000024B0 16 003B CompilerGetUnitSymbol 00001F2C 10 003C CompilerInit 00002B2C 26 003D CompilerShouldCompile 00002B70 27 003E CompilerVerifyCodePageOption 0001673C 8 003F DbEval32InitProc 00001754 9 0040 DccInitialize 000084F8 43 0041 DoneEvaluate 000084C0 41 0042 DoneProcess 00006D2C 30 0043 EvalCondition 000068A8 28 0044 Evaluate 00007C64 34 0045 FindLineCode 00007D1C 35 0046 FindLineCodes 00007DF0 36 0047 FindSourceLine 000088A8 44 0048 FreeCompile 00007428 31 0049 GetCallCount 000079CC 33 004A GetCallHeader 00007438 32 004B GetCallPos 000083C4 39 004C GetCodePosRanges 00008154 38 004D GetCodeRanges 0000A60C 48 004E GetFileCount 0000A8AC 51 004F GetFileIndex 0000A61C 49 0050 GetFileName 0000A27C 47 0051 GetNearestSymName 00008968 46 0052 GetPropertyInfo 000080AC 37 0053 GetSrcLines 000084E4 42 0054 InitEvaluate 00008474 40 0055 InitProcess 0000250C 17 0056 KibitzCompiler 000A19B0 112 0057 KibitzGetOverloads 0008C068 109 0058 KibitzGetValidSymbols 00090F5C 111 0059 LoadCompState 000088BC 45 005A LookupProc 0000CDF8 59 005B MakeSureUnitIsLinked 00006CFC 29 005C Modify 0000B588 54 005D NewCompState 000908F0 110 005E SaveCompState 0000B604 56 005F SetCompState 0000B5E4 55 0060 SetCompStateFast 0000A64C 50 0061 SetFileName 0007A974 106 0062 SetUnitFreeHook 00002974 23 0063 UIDiscardUnit 00002924 22 0064 UIGetExplicitUnitImports 000029A0 24 0065 UIGetPackagesUsed 00002878 21 0066 UILoadUnit 00002A08 25 0067 UISetDebugDir 001150FC 105 0068 ___CPPdebugHook 各位看到上面許多的輸出函式都和編譯工作有關的嗎? 從上面的結果我們可以使用下列簡化的圖形來呈現: ![]() 等到新的後端編譯器出來之後,BCB和Delphi的編譯架構會形成如下的狀態: ![]() OK,那麼混合編譯和跨平台編譯是什麼? 動腦想想如果我們再把機械碼產生器抽離出來形成如下的架構: ![]() 你看到了什麼? 現在回到BCB和Delphi本身,現在BCB/Delphi中的dbExpress是跨平台的架構,RTL在擁有了跨平台編譯之後也就可以跨平台了, If begin Delphi程式語言 + VCL + RTL + dbExpress ~= Delphi 而且 C/C++程式語言 + VCL + RTL + dbExpress ~= BCB 的話, end then begin Delphi程式語言 + RTL + dbExpress + 跨平台編譯 = ? C/C++程式語言 + RTL + dbExpress +跨平台編譯 = ? end 剩下的問題就是VCL了,VCL在原生Win32/Win64不是問題,但在Linux或是其他平台怎麼辦? 還記得CLX嗎? 本來Borland說要開放CLX,但現在CodeGear又似乎踩剎車了,為什麼? 我也不知道, 但我想如果我們把CLX帶入上面的公式: Delphi程式語言 + 新的CLX + RTL + dbExpress + 跨平台編譯 = ? C/C++程式語言 + 新的CLX + RTL + dbExpress +跨平台編譯 = ? 真是令人好奇啊,希望我猜的都能成真,那麼C++Builder和Delphi將進化成最佳的”跨平台原生開發工具”, 也別忘記現在由Delphi Prism建立的應用程式已經可以執行在Window, Linux和Mac等 3個平台之上了。 我知道現在BCB 2009/Delphi 2009的Patch 3和Patch 4都在開發之中,從目前的測試版來看CodeGear修正了許多多年的臭蟲,整個速度和穩定性更勝RAD Studio 2009,所以我猜想2009年CodeGear會再推出一個BCB/Delphi 2010的32位元版本,這個版本將會是最穩定,最快速的32位元版本,而且應該會支援Vista和Window 7等和其他許多最新的PC技術,CodeGear在離開Borland之後決心再把C++Builder/Delphi打造成最好的Win32/Win64開發工具看來是非常認真的。 好了,大過年的寫到這裡文章也夠長的了,我也該去打打Wii和Wii Fit了,下次再讓我們繼續的動動腦吧(後註, 這篇文章我在大年初四就寫完了, 只是忘了Post出來, 看來記性愈來愈差了)。 最後祝各位 新年快樂,2009年心想事成。 February 02 興德開始提供CodeGear技術文章如果您還不知道的話,興德資訊開始在興德的網站上提供我寫的一些技術文章的和書提供有需要的朋友下載使用,這個小小的園地希望能夠提供一些中文的CodeGear技術資料給有需要的朋友。
我們希望一開始至少在這個小小的技術網提供技術文章,我以前舊的書籍和技術研討會相關的slides和範例程式,我們更我們希望日後擁有更多的資源可以提供更多的東西,而且能夠繼續的維護下去,當然我們更需要您的支持,謝謝! 這個技術網目前的URL是 :
December 15 『使用Delphi開發分散式JSON應用系統』的範例和PowerPoint檔案開始提供下載忙碌完了上星期四場的『使用Delphi開發分散式JSON應用系統』技術研討會之後,我要再次謝謝所有花時間參與的朋友們,希望您們都有所收穫。現在您可在下面的URL下載研討會的範例和PowerPoint檔案,謝謝。 |
|
|