維's profileIT : 是工作還是嗜好?PhotosBlogListsMore Tools Help

Blog


    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 }

    fTotalFiles : Integer;

    FResult : TJSONArray;

    FCallback: TDBXCallback;

    procedure ProcessPath(const sPath : string);

    procedure ProcessPathAsync(const sPath : string);

    procedure ShowMessage(sMessage : string);

    procedure ProcessThisDirectory(const sPath : string);

    public

    { Public declarations }

    function EchoString(Value: string): string;

    function GetServerDirectoryInfo(const sPath : string) : TJSONArray;

    function GetServerDirectoryInfoAsync(ACallback: TDBXCallback; const sPath : string): TJSONArray;

    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中顯示任何伺服端執行的資訊,使用者必須等待它完全執行完畢才能取回用戶端應用程式的控制權。

    image

    圖1 DataSnaps伺服器的執行畫面

    image

    圖2 同步客戶端的執行畫面

    image

    圖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,如下所示:

    clip_image002

    ZlibCompression過濾器就是DataSnap 2010內建的壓縮過濾器,在加入了ZlibCompression過濾器之後,編譯並且執行範例DataSnap伺服器,現在DataSnap伺服器就提供了壓縮JSON資料的能力。

    現在再讓我們開啟用戶端應用程式,因為我們要在用戶端應用程式中加入解壓縮資料的能力,這個非常的簡單,我們只要在用戶端應用程式的主表單中加入使用DBXCompressionFilter程式單元即可,例如下面就是用戶端應用程式加入DBXCompressionFilter程式單元的程式碼:

    implementation

    uses DBXJSONReflect, DBXJSON, uServerProxy, uEmployee, DBXCompressionFilter;

    現在編譯並且執行用戶端應用程式,並且讓我們使用TCP Viewer來觀察使用壓縮過濾器之前的情形以及使用壓縮過濾器之後的效果。

    下圖是TCP Viewer顯示範例DataSnap應用系統使用壓縮過濾器之前的情形,從下圖中我們可以看到在DataSnap伺服器和用戶端應用程式之間傳遞的資料當然是使用字串的型態,所有傳遞的資料都一清二楚,同時請讀者注意下圖右邊顯示了從伺服器傳遞到用戶端的資料量(938位元組)以及從用戶端傳遞到伺服端的資料量(706位元組)。

    clip_image004

    而下圖則是使用壓縮過濾器之後的效果:

    clip_image006

    從上圖中可以看到傳遞的資料經過壓縮,因此不易看出原始的資料,而且請讀者注意下圖右邊顯示了從伺服器傳遞到用戶端的資料量(653位元組)以及從用戶端傳遞到伺服端的資料量(602位元組),可見到壓縮過濾器有效的減少了伺服器和用戶端之間的資料傳遞量,這不但可以增加分散式應用程式的執行速度,也可以增加支援的用戶端的數量。

    如何? 使用過濾器是不是又簡單,又有明顯的效果? 不過DataSnap 2010只提供了一個內建的過濾器實在太少,好在DataSnap 2010過濾器架構在設計時就考慮到了允許讓開發人員能夠自行開發過濾器並且內嵌到DataSnap之中,接下來筆者將討論如何開發客製化過濾器並且使用在DataSnap 2010的分散式應用系統中。

    開發客製化過濾器

    要開發客製化過濾器,開發人員必須從TTransportFilter類別衍生子代類別並且實作TTransportFilter類別中相關的虛擬方法,下面的表單說明了開發人員需要實作的虛擬方法:

    函式名稱

    回傳型態

    說明

    GetParameters

    TDBXStringArray

    回傳所有的參數

    GetUserParameters

    TDBXStringArray

    回傳使用者可改變的參數

    ProcessInput

    TBytes

    使用客製化程式碼正向處理傳遞的資料流

    ProcessOutput

    TBytes

    使用客製化程式碼反向處理傳遞的資料流

    Id

    UnicodeString

    過濾器的ID

    GetParameterValue

    UnicodeString

    取得特定名稱的參數值

    SetParameterValue

    Boolean

    設定特定名稱的參數值

    瞭解了需要實作那些虛擬方法之後,我們就可以開始動手開發一個客製化過濾器了。在本文中筆者將撰寫一個非常簡單的加密/解密過濾器,其實這個加密/解密過濾器只是在傳遞資料時和一個字串進行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伺服器,我們就可以看到伺服器顯示它已經找到了我們的客製化過濾器:

    clip_image008

    接著開啟用戶端應用程式,在主表單中也加入客製化過濾器的程式單元uEncryptFilter,編譯並且執行用戶端應用程式,再使用TCP Viewer觀察傳遞的資料,我們果然看到資料現在都經過加密了:

    clip_image010

    但是我們可以看到傳遞的資料量增加了。

    但是用戶端仍然可以正確的接受到資料:

    clip_image012

    當然我們也可以同時使用兩個過濾器,享受加密又壓縮的好處,下圖是伺服器同時支援了兩個過濾器:

    clip_image014

    如果我們再次使用TCP Viewer,就可以看到下圖,享受加密又壓縮的好處,因為資料加密了而且資料傳遞量又減少了。

    clip_image016

    現在您應該瞭解了如何使用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伺服器:

    Image1

    接著在New DataSnap Server對話盒中讓我們建立一般的VCL Forms應用程式做為DataSnap伺服器的型態,選擇使用TCP/IP和HTTP/HTTPS通訊協定,並且選擇使用TDSServerModule做為類別做為輸出服務的父代類別,如下所示:

    Image2

    點選OK按鈕之後Delphi 2010便會為自動我們建立如下的ServerContainerUnit1程式單元,其中即包含了所有必要的元件。其中的TDSHTTPService和TDSHTTPServiceAuthenticationManager是新的元件,這兩個元件是使用來支援HTTP/HTTPS通訊協定的。

    Image3

    現在讓我們把Unit1程式單元儲存為名為MainForm的程式單元:

    Image4

    接著點選TDSHTTPService元件,設定它的HttpPort特性值為8080,因為通信埠80已經被Web伺服器使用,因此在這個範例中我使用8080,如果通信埠8080在讀者的機器中已經被使用,那麼您可以使用任何尚未使用的通信埠。請注意TDSHTTPService的RESTContext特性,您可以看到它的特性值是Rest,這代表Delphi 2010的DataSnap伺服器一旦使用TDSHTTPService元件就自動支援RESTful的功能,我們稍後再討論RESTful。

    Image5

    由於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伺服器再更新回資料庫。

    Image6

    使用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伺服器:

    Image7

    使用瀏覽器存取DataSnap伺服器的服務

    我們也可以使用Delphi/BCB的DataExplorer來測試和存取DataSnap伺服器,例如下圖的三個圖形分別顯示了筆者使用DataExplorer來測試DataSnap伺服器是否支援TCP/IP,HTTP/HTTPS通訊協定,以及存取GetEmployee服務。請注意第3個圖形,它明確的顯示了DataSnap伺服器回傳的是物件型態(Object)。

    Image8 Image9 Image10

    建立Web用戶端

    既然範例DataSnap伺服器支援HTTP/HTTPS通訊協定,因此我們當然可以使用Web應用程式來存取它的服務,例如下圖就是筆者使用VCL For Web建立Web應用程式並且存取DataSnap伺服器的服務:

    Image11

    這是如何做到的?

    由於篇幅的限制,因此讓我們簡單的說明一下好了。當開發人員使用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 讓用戶端呼叫增加員工資訊
    GetEmployeeJ 讓用戶端以員工姓名查詢員工物件 請注意GetEmployeeJ回傳TJSONObject型態的結果值,這代表範例應用程式將把員工物件封裝在TJSONObject物件之中並且回傳給用戶端
    GetAllEmployeesJ 讓用戶端查詢所有的員工資訊 查詢後端所有員工資訊,請注意GetAllEmployeesJ回傳型態為TJSONArray的結果值,這代表範例應用程式將把所有查詢結果封裝在TJSONArray物件中回傳


    下面小節將簡單的說明這些方法的實作。

    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設計樣例來傳遞資料和物件,使用這樣的技術,開發人員也可以使用.NETJava或是任何支援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是由下面的兩個基本架構組成的:

    • “名稱/值”對的集合(A collection of name/value pairs)。在不同的程式語言中,這個架構經常以物件(object),紀錄(record),結構(struct),字典(dictionary),雜湊表(hash table),有鍵列表(keyed list),或者關聯陣列 (associative array)來實作。
    • 值的有序列表(An ordered list of values)。在大部分的程式語言中,這個架構經常使用陣列(array)來實作。

    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物件,因此我們只需要執行下面的步驟即可:

    • 建立TJSONObject物件
    • 建立TJSONPair物件
    • 把TJSONPair物件加入到TJSONObject物件

    要把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 20102010版是Embarcadero接手DelphiC++BuilderRAD Studio之後的第一個自行研發的版本,出乎我很意外的,2010版完全沒有延遲,不像在Borland的時期,每一個Delphi/C++Builder一定至少會延遲2個星期到一個月左右。從這次Delphi/C++Builder 2010按時推出可以說明Delphi/C++Builder研發團隊在Embarcadero終於獲得了合理的資源,而且從Delphi/C++Builder 2010推出之後,仔細的研究2010VCLRTL以及和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 201064位元編譯器,另一組在做跨平台的Delphi編譯器。另外32位元的編譯器小組也在研究未來第3方元件能夠以二進行形式直接昇級到新版本而無需元件的原始程式,明年應該就可以看到64位元或是跨平台的編譯器以及新的最佳化C++Builder編譯器所以編譯器快準備好了。

    dbExpress早已經好跨平台的準備,因為現在所有的dbExpress相關程式碼都可以使用Delphi來撰寫,此外藉由Delphi編譯器提供更豐富的RIIT資訊,Delphi/C++Builder團隊開始研發OR Mapping的能力。這個新的OR Mapping功能也應該是跨平台的,因為它也將整合到VCL了,所以dbExpressVCL框架和快準備好了。

    再看Delphi/C++Builder的分散式技術,一直以來Delphi/C++Builder都是使用Windows平台上的分散式技術做為基礎,從Midas使用的COM,到DCOM/COM+,這一塊一直不容易改變,還好在JSON逐漸取得廣泛的應用後,Delphi/C++Builder2009版開始支援JSON。只是Delphi/C++Builder 2009JSONdbExpress綁的太緊,無法輕易的使用Delphi/C++Builder來開發一般的JSON應用程式。到了Delphi/C++Builder 2010Delphi/C++Builder團隊終於把JSON的能力封裝在數個易於使用的類別中,讓開發人員終於可以使用Delphi/C++Builder 2010進行通用的JSON開發,此外又加入了非同步以及過濾器的能力,讓Delphi/C++Builder 2010可以基於JSON開發分散式應用系統,終於打通了跨平台分散式架構的任督兩脈,DataSnap也終於可以實作在Mac OSLinux平台中。

    再看看Delphi/C++Builder 2010火熱的觸碰/手勢技術,雖然目前只能使用在Window XPVistaWindows 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來宣告FDefaultEngineClassFDefaultRecognizerClass,以及FRecognizer,這種設計很明顯的是一個plug-in設計樣例,未來當Delphi/C++Builder團隊在其他平台,例如手機的移動平台,實作了觸碰/手勢技術,那麼只需要註冊新的引擎(Engine)Recognizer,再取代Windows平台的引擎(Engine)Recognizer,那麼跨平台的觸碰/手勢技術就實現了。

    最後回到RTLDelphi/C++BuilderFastCode專案仍然在持續進行中,如果您在Delphi/C++Builder 2010中搜尋就可以看到更多FastCode專案的成果,此外Delphi/C++Builder團隊也開始在RTL中加入跨平台的多執行緒函式庫,打造跨平台的高效RTL

    然而我仍然希望看到Delphi/C++Builder快點支援RIA的開發,Delphi/C++Builder 2010開始支持RESTful架構令人喜出望外,但我希望再上一層樓,結合RESTfulRIAWeb技術為一體。

    Delphi/C++Builder 2010在表面上呈現了許多非常實用又吸引人的新功能,而且許多的新功能都是領先業界的,不過如果您進入到Delphi/C++Builder 2010的實作原始程式世界,您會發現更多令人興奮的東西。Delphi/C++Builder團隊在擁有了足夠的研發資源之後,的確可以從Delphi/C++Builder  2010再次感受到如同Delphi 5Delphi 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框架中得知這些先進技術是如何實作出來的。在這一霎那,觸碰和手勢技術立刻從莫可探知的黑盒子成為了我們能夠掌握的技術白盒,DelphiBCB 2010已經成功的把這些先進的技術交付到了Delphi/BCB的開發人員手中,接下來的困難不再是這些技術,而是開發人員和架構師如何運用這些技術開發出更具創意的應用程式。

    Ø  想想觸碰和手勢技術與分散式應用系統的結合,你真的可以在本機中藉由觸碰一個按鈕以啟動遠端的服務而造成蝴蝶效應,Cool!

    Ø  想想觸碰和手勢技術與Web介面和Web應用程式的結合,觸碰和遠端遙控可以讓您無遠弗屆,Cool!

    Ø  想想觸碰和手勢技術與傳統MIS應用程式的結合,至少你的老板和客戶再也不需要面對大大的螢幕和小小的滑鼠,他們減少了使用滑鼠造成的「腕溝症候群」和『滑鼠手』等職業病的機會,我們也減少了被他們臭罵的機會,哈哈!


    在原生Win32的觸碰世界裡,Delphi 2010讓您做主!

    August 11

    我家出了個小綠綠!

    今年高中聯考終於放榜了,從6月開始每天煎熬的日子也終於結束了,回想當年我自己考高中時也沒有這麼的受煎熬,現在做父母的真是不好受。

    記得在2006518日我得知女兒考上祟光女中美術班時真是高興(其實祟光女中的美術班和音樂班是所謂的資優班,女兒的這一班真是厲害,全班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團隊確認在下一版的DelphiUTF8ContentParser會加入到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最後花落誰家似乎仍有變數!

    今年56日根據外電報導英國軟體廠商Micro Focus將收購Borland,雖然我早已離開Borland數年之久,也對後期的Borland實無好感,但從學生時期便建立的感情仍然讓我無法忽略Borland在業界的消息,在57日我得知Borland即將被收購的消息時,心中並無太大的心情波動,因為在離開Borland時我早已預測Borland將會瓦解,因為那時BorlandCEO每天只想著股票的價格,把產品搞得亂七八糟,又要求每個人都做銷售,連技術人員都被送去做銷售的培訓,取消了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
    Main Themes
    User Experience
    Enhance Connectivity
    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測試人員了。

    事實上在我朋友告訴我並且詢問我這個問題之後,我也親自測試了一下,我果然也是遇到錯誤340,無法連結MS SQL Server 2005,但在我確定它是DBX的臭蟲之前,我決定再啟動RAD Studio 2007看看有什麼不同,結果我發現在RAD Studio 2007中,DBX4是使用oledb用戶端函式庫來連結MS SQL Server,如下所示:

     
    但是在下一版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有如下的特性:
    • 它支援最新的MS SQL Server 2008,因此理論上應該支援SQL 2008新的資料型態和功能
    • 由於它使用MS SQL Server 2008原生用戶端安裝程式來連結MS SQL Server 2008,因此它可向後支援MS SQL Server 2005
    • 使用sqlncli10.dll而不再使用oledb,因此下一版的DBX速度會比以前更快

    嗯, 推論了上述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的未來似乎已經漸入佳境,為什麼? 因為:

    • 第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輸出的函式,我們可以得到如下的結果:

    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是 :

    http://www.sinter.com.tw/codegear/codegear_technique.html

    December 15

    『使用Delphi開發分散式JSON應用系統』的範例和PowerPoint檔案開始提供下載

    忙碌完了上星期四場的『使用Delphi開發分散式JSON應用系統』技術研討會之後,我要再次謝謝所有花時間參與的朋友們,希望您們都有所收穫。現在您可在下面的URL下載研討會的範例和PowerPoint檔案,謝謝。

     http://www.sinter.com.tw/freedownload/深入技術講座.rar