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

Blog


    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

    November 28

    平行處理機制,平行Linq和Delphi Prism

    在上篇我從一些蛛絲馬跡來猜測Delphi除了朝原生64位元發展之外,也可能會在非同步,平行執行方向進行突破的發展。當這篇文章貼出之後,很快的我的朋友就問我『這樣的』Delphi會在什麼時候出現,說實話我不知道,因為這也是我從一些蛛絲馬跡『猜測』的,所以我當然不知道,只能等時間來告訴我們了,呵呵。
    Delphi Prism程式語言中提供了許多非同步,平行執行的機制,除了上次我們看到的async和parallel之外,當我們使用這些非同步,平行執行的機制結合PFX 框架時,也能夠很方便的在使用Linq時也享受平行執行的好處,例如下面是傳統使用Linq的一個典型,簡單的範例:

    class method ConsoleApp.Linq;
    var
      words : sequence of string;
    begin
      words := ['嗨', '世界' , 'Linq', 'Parallel Linq' , 'Delphi Prism', 'Delphi 2009', 'BCB 2009', '3rdRail'];
      var rQuery := from word in words select word;

      for each s in rQuery do
        Console.WriteLine(s);

      Console.ReadLine;
    end;

    當我們使用Delphi Prism編譯並且執行之後,會看到如下的結果:
    http://blufiles.storage.live.com/y1pnfguIQKXzsE1F49XruVvqOLio8MYDAdvlGGt7aDrP2UdtPAi5PKF_AcX1PVgZisw

    我們從上圖的執行結果可以看到,它輸出的結果次序和我們在程式碼中定義sequence的次序是一樣。
    但是如果我們稍微修改一點程式碼,在words之後呼叫擴展方法AsParallel如下所示:

    class method ConsoleApp.PLinq;
    var
      words : sequence of string;
    begin
      words := ['嗨', '世界' , 'Linq', 'Parallel Linq' , 'Delphi Prism', 'Delphi 2009', 'BCB 2009', '3rdRail'];
      var rQuery := from word in words.AsParallel select word;

      rQuery.ForAll<string>(word -> Console.WriteLine(word));
      Console.ReadLine;
    end;

    那麼我們會看到如下的執行結果:
    http://blufiles.storage.live.com/y1piJJHrhRgrHyDrKVst5KDd5kJbI-DL-bzA5_85VIIIdXjmXkUNeNYbLbKPucIyLJahttp://blufiles.storage.live.com/y1pFfkk9_1ena1VTyZedMmv5gXjo4_miYp14SPNbsdlE7H2ZAHcplG_1DSSMfteZgcK
     
    從上圖中可以發現每次執行平行查詢時執行的結果次序都不同,而且輸出的次序也不是程式碼中定義的次序,這個證明了當使用Delphi Prism的平行機制結合PFX框架時的確是以多個平行執行的核心來處理的。
    使用.NET Reflector反組編PLinq方法也可以看到它的確是呼叫PFX框架中提供的平行處理機制:

    class method ConsoleApp.PLinq;
    begin
    ParallelEnumerable.ForAll<String>(ParallelQuery.AsParallel<String>(array of(['嗨', '世界', 'Linq', 'Parallel Linq', 'Delphi Prism', 'Delphi 2009', 'BCB 2009', '3rdRail'])), delegate (word: String)
        begin
            Console.WriteLine(word)
        end;
        Console.ReadLine
    end;

    和呼叫Linq方法是截然不同的。

    class method ConsoleApp.Linq;
    begin
        var rQuery: IEnumerable<String> := array of(['嗨', '世界', 'Linq', 'Parallel Linq', 'Delphi Prism', 'Delphi 2009', 'BCB 2009', '3rdRail']);
        if (rQuery <> nil) then
            {pseudo} goto Label_0058;
        rQuery.GetEnumerator;
        var enumerator: IEnumerator<String> := nil;
        if (enumerator <> nil) then
            while enumerator.MoveNext do begin
                var s: String := enumerator.Current;
                Console.WriteLine(s)
            end;
        Console.ReadLine
    end;

    Delphi Prism提供的先進非同步,平行執行機制突然讓原生Delphi 32/64有了新的發展方向。



    November 24

    使用Delphi開發分散式JSON應用系統

    在12月我又受委託將舉行4場有關DataSnap和分散式應用系統開發的技術研討會, 在這次的研討會中我將討論下面相關的內容:

    • 什麼是JSON?
    • Delphi和JSON程式設計
    • 如何昇級/轉換COM/DCOM/COM+的分散式架構為JSON分散式架構
    • 如何使用DataSnap 2009開發新一代的分散式架構應用程式
    • DataSnap 2009的JSON分散式架構管理機制
    • JSON伺服器生命週期
    • 開發可異動的JSON分散式應用系統
    • 結合Web應用程式和JSON分散式架構

    同樣的, 這是免費的技術研討會, 日期是在12/09~12/12, 如果您有興趣的話, 您可以在下面的URL報名參加:

    http://www.sinter.com.tw/codegear/seminars/delohi200920081120.html




    November 21

    非同步,平行執行技術,從Delphi到Delphi Prism,再到未來的Delphi?

    前一陣子Delphi的現任總架構師Allen Bauer討論了許多有關非同步和平行處理的文章,而更早MS也在.NET平台上推出Microsoft Parallel Extensions to Framework 3.5套件,開始進行對於平行開發技術的演進,這個趨勢是想見而知的,因為現今的CPU都是多核心,每一個核心又都支援多執行緒執行的能力,因此軟體技術如何搭配硬體的進步,進而發揮硬體的計算能力是軟體突破的重要方向之一。如果各位嗅覺靈敏的話,從Allen的一系列文章以及他的研究方向來看,各位應該可以開始猜測未來Delphi的發展方向了。
    除了這些蛛絲馬跡之外,CodeGear最近又宣佈了Delphi Prism,Delphi Prism除了是一個獨立的產品之外,也將和Delphi/C++Builder 2009整合為RAD Studio 2009,提供在.NET方面的解決方案。不過Delphi Prism並不是Delphi.NET,Delphi Prism是.NET平台上使用類似Delphi語法的程式語言,並且包含了許多先進的程式語言功能。有趣的是Delphi Prism已經在程式語言本身加入了許多非同步,平行執行功能,當結合Microsoft PFX framework時,能夠自動使用PFX中的平行執行技術。

    今天我在CodeGear的部落格上看到了Andreano討論有關Delphi Prism的文章,其中就有討論到Delphi Prism程式語言中使用關鍵字async來建立非同步執行的範例,我覺得蠻有意思的,因為這代表現在可以開始討論Delphi Prism了。在Andreano的範例中,他使用了async來宣告一個方法,在Delphi Prism中使用async關鍵字宣告的方法就合格成為一個非同步執行的方法,讓我使用一個範例來說明一下。
    在下面的程式碼中宣告了5個類別方法,以及兩個使用async宣告的非同步方法CallLoops和CallParallelLoops:

    type
      ConsoleApp = class
      public
        class method Main;
        class method CallAsyncMethod;
        class method CallAsyncWithParallel;
        class method Linq;
        class method PLinq;
        method CallLoops(iID : Integer) ; async;
        method CallParallelLoops(iID : Integer); async;
      end;

    接著我在類別方法Main中呼叫其他四個類別方法:

    class method ConsoleApp.Main;
    begin
      ConsoleApp.CallAsyncMethod; 
      ConsoleApp.CallAsyncWithParallel;
      ConsoleApp.Linq;
      ConsoleApp.PLinq;
    end;

    在CallAsyncMethod中,它在一個迴圈中呼叫了使用async宣告的CallLoops方法,因此.NET會產生四個執行緒來執行CallLoops:

    class method ConsoleApp.CallAsyncMethod;
    begin
      Console.WriteLine('開始執行非同步方法');

      with lConsoleApp := new ConsoleApp() do
      begin
        for i : Integer := 0 to 4 do
           lConsoleApp.CallLoops(i);
      end;
      Console.WriteLine('結束執行非同步方法');
      Console.ReadLine;
    end;

    而CallLoops方法只是很簡單的執行一個迴圈,列印訊息並且使用Thread在每一次迴圈暫時暫停0.1秒。

    method ConsoleApp.CallLoops(iID : Integer) ;
    begin
      for i : Integer := 0 to 4 do
      begin
        Console.WriteLine('執行緒' + iID.ToString + ' : ' + i.ToString);
        System.Threading.Thread.Sleep(100);
      end;
        Console.WriteLine('執行緒' + iID.ToString + '執行完成');
    end;

    讓我們看看這個執行的結果,從下圖中讀者可以看到.NET果然以四個執行緒,以不定的次序執行每一個執行緒,所以在Delphi Prism中要開發非同步執行的方法非常的簡單的,只要使用關鍵字async來宣告方法即可。

    http://blufiles.storage.live.com/y1peVkQA8uAo_Fv5OSB6PgOgi86eB7sFjTSjxNwMu1dcbMswRKjjjGjB6JJJu_YRiI8

    然而讀者如果仔細觀察上圖的執行狀態,會發現雖然四個執行緒以不定的次序執行,但是每一個執行緒在執行CallLoops方法的迴圈時仍然是以順序的方式在執行迴圈,這代表CallLoops雖然是在獨立的執行緒中執行,但仍然沒有使用平行執行技術。但在Delphi Prism中我們只需要使用一個parallel關鍵字就可以立刻使用Microsoft PFX framework提供的平行技術。
    例如下面的CallAsyncWithParallel使用了幾乎和CallAsyncMethod一樣的程式碼,只是CallAsyncWithParallel呼叫了非同步方法CallParallelLoops:

    class method ConsoleApp.CallAsyncWithParallel;
    begin
      Console.WriteLine('開始執行非同步, 並行方法');

      with lConsoleApp := new ConsoleApp() do
      begin
        for i : Integer := 0 to 4 do
           lConsoleApp.CallParallelLoops(i);
      end;
      Console.WriteLine('結束執行非同步, 並行方法');
      Console.ReadLine;
    end;

    而CallParallelLoops也幾乎和CallLoops一樣,只是注意CallParallelLoops在呼叫for迴圈時使用了另外一個關鍵字parallel

    method ConsoleApp.CallParallelLoops(iID : Integer) ;
    begin
      for parallel i : Integer := 0 to 4 do
      begin
        Console.WriteLine('執行緒' + iID.ToString + ' : ' + i.ToString);
        System.Threading.Thread.Sleep(100);
      end;
        Console.WriteLine('執行緒' + iID.ToString + '執行完成');
    end;

    在Delphi Prism中如果使用關鍵字parallel而且開發人員參考了Microsoft PFX framework,那麼Delphi Prism便會編譯出如下的程式碼:

    method ConsoleApp.@AsyncProc_0_CallParallelLoops(iID: Int32);
    beginvar c__1: <>c__0 := new <>c__0;
    c__1.iID := iID;
    Parallel.&For(0, 5, 1, new Action<Int32, ParallelState>(c__1,<CallParallelLoops>__0));
    Console.WriteLine(String.Concat('執行緒', iID.ToString, '執行完成'))
    end;

    在上面的程式碼中Delphi Prism編譯器會自動產生PFX的Parallel.For的程式碼提供平行執行for迴圈的能力。
    下圖是CallAsyncWithParallel執行的結果,讀者可以仔細觀察,CallAsyncWithParallel也產生了四個執行緒來執行,但是每一個執行緒在執行CallParallelLoops迴圈時卻不是以順序的方式來執行,而是使用平行的方式執行for迴圈。
    http://blufiles.storage.live.com/y1pOShWV2i4BG0HEKm6wd1hBLqfXEwbjFJiabr50xz62y69ZbFYromv4M1tcIYSiMI8


     從上面的討論中我們可以瞭解Delphi Prsim提供了非常方便的非同步和平行處理的機制,讓開發人員能夠在結合PFX時開發出平行執行架構。
    在下次的文章中讓我們再看看Delphi Prism在支援Linq和平行Linq時是多麼的方便。



    November 20

    DBX框架篇 : 第1章 初探DBX4框架 - 2

    1-2 執行SQL命令 : TDBXCommand類別
    在DBX4框架中TDBXCommand類別是使用來執行SQL命令的,在上一小節中我們已經在開發人員必須呼叫TDBXConnection的CreateCommand方法來建立TDBXCommand物件,再藉由TDBXCommand物件來執行SQL命令。
    下面的表格列出了TDBXCommand類別中最常使用的方法和特性:
    http://blufiles.storage.live.com/y1pOwd9QTSD0WhDnFhU2el9IqMPIA7RlNxOTDYcMemmOWaXBIqQ9tp8rG8njaG-qikD

    要使用TDBXCommand物件來執行SQL命令,開發人員必須把 SQL命令指定給Text特性,例如

    aCommand.Text := ‘Select count(*) from DBXTESTTABLE’;

    如果要執行擁有動態參數的SQL命令,那麼動態參數必須使用’?’字元來代表,例如下面的SQL命令藉由在執行時指定“研討會名稱”欄位的實際數值從DBXTESTTABLE資料表中選擇符合條件的結果資料集:

    aCommand.Text := ‘Select * from DBXTESTTABLE where “研討會名稱” = ? ’;

    那麼如何在應用程式執行時實際設定SQL命令中的動態參數值呢? 這就必須使用TDBXParameter類別。

    1-2-1 代表SQL敘述中動態參數的類別 : TDBXParameter
    TDBXParameter類別物件可代表SQL命令中的每一個以’?’字元代表的動態參數,因此SQL命令中擁有多少個以’?’字元代表的動態參數,那麼開發人員就必須為每一個以’?’字元代表的動態參數建立一個相對應的TDBXParameter物件,然後設定TDBXParameter物件中正確的參數特性值,最後再呼叫TDBXCommand物件的Parameters特性的AddParameter方法把每一個TDBXParameter物件加入到TDBXCommand物件,最後才能呼叫TDBXCommand的ExecuteQuery或是ExecuteQuery真正的執行SQL命令。
    TDBXParameter類別也擁有許多的方法和特性,下面的表格列出了比較重要的方法和特性:
    http://blufiles.storage.live.com/y1pVu3Ef8VfE3lg2WYfYak2I71tQbe5FOjwpAKtAlbaIxfd5jDO_K4FcOQC3p6QOQrS

    現在讓我們看看如何使用TDBXParameter來執行動態SQL,假設我們需要執行下面的SQL敘述:

    select * from SEMINARS where “主講人” = ?

    由於在上面的SQL敘述中包含一個以’?’字元代表的動態參數,因此我們需要建立一個TDBXParameter物件,設定它的資料型態和數值再加入到TDBXCommand物件中才能呼叫ExecuteQuery來執行此SQL敘述。下面的程式碼展示了如何執行上面的動態SQL敘述:
    001    procedure TForm5.執行SQL命令;
    002    var
    003      aCommand : TDBXCommand;
    004      aParameter : TDBXParameter;
    005      aReader: TDBXReader;
    006    begin
    007      if (Assigned(dbx4Conn)) then
    008      begin
    009        aCommand := dbx4Conn.CreateCommand;
    010        try
    011          aCommand.CommandType := TDBXCommandTypes.DbxSQL;
    012          aCommand.Text := Self.edtSQL.Text;
    013          aParameter := aCommand.CreateParameter;
    014          aParameter.DataType := TDBXDataTypes.WideStringType;
    015          aParameter.Value.AsString := Self.edt主講人.Text;
    016          aCommand.Parameters.AddParameter(aParameter);
    017          aReader := aCommand.ExecuteQuery;
    018          while (aReader.Next) do
    019          begin
    020            Self.ListBox1.Items.Add(aReader.Value['名稱'].GetWideString + ' : ' + aReader.Value['主講人'].GetWideString);
    021          end;
    022        finally
    023          FreeAndNil(aReader);
    024          FreeAndNil(aCommand);
    025        end;
    026      end;

    012行設定了TDBXCommand要執行的SQL敘述之後,013行建立TDBXParameter物件,014行設定TDBXParameter物件的資料型態,015行設定實際動態參數的數值,016行把TDBXParameter物件加入到TDBXCommand物件,017行執行動態SQL敘述,018行之後藉由TDBXReader物件來處理結果資料集,在下一小節中將說明TDBXReader類別。
    下圖是上面程式碼執行動態SQL敘述的結果:
     
    http://blufiles.storage.live.com/y1pACQpal0WSGSlEH2V5_-Ooq_90pKNDZXDEaIscT96nWcp-X3Zw8nGjLq8MYYstqVX
    圖1-4使用TDBXCommand和TDBXParameter執行動態SQL敘述

    在使用TDBXCommand物件執行命令時,開發人員必須指定TDBXCommand物件執行的命令種類,而這些TDBXCommand物件能夠執行的命令種類是由TDBXCommandTypes類別定義。TDBXCommandTypes類別定義了數個常數值(const)來定義不同的命令種類,開發人員需要使用它來設定TDBXCommand物件的CommandType特性,類似上面的011行程式碼。
    下面的表格整理了TDBXCommandTypes定義的常數值以及這些常數值使用的意義:
    http://blufiles.storage.live.com/y1pq3CdkGLWl73tb0Iv6Zg0VKM-m2r3ulE4Gt0w6y-oGwrDLT2hz8AguvRnp2h1z5-e

    從上表可知,TDBXCommand不但可以執行SQL命令,執行後端預儲程序,存取資料庫元資料,更可以執行DataSnap 2009中加入的DataSnap伺服器輸出的方法。例如,假設我們有一個DataSnap 2009開發的JSON伺服器,它輸出了一個GetSpeakerCount方法能夠回傳後端Speaker資料表中所有記錄的總數值,那麼我們就可以使用下面的程式碼在用戶端使用TDBXCommand物件來執行遠端JSON伺服器中的GetSpeakerCount方法並且取得執行結果:

    function TDSServerModule2Client.GetSpeakerCount: Integer;
    begin
      if FGetSpeakerCountCommand = nil then
      begin
        FGetSpeakerCountCommand := FDBXConnection.CreateCommand;
        FGetSpeakerCountCommand.CommandType := TDBXCommandTypes.DSServerMethod;
        FGetSpeakerCountCommand.Text := 'TDSServerModule2.GetSpeakerCount';
        FGetSpeakerCountCommand.Prepare;
      end;
      FGetSpeakerCountCommand.ExecuteUpdate;
      Result := FGetSpeakerCountCommand.Parameters[0].Value.GetInt32;
    end;


    我們只需要如上設定TDBXCommand物件的CommandType特性值為TDBXCommandTypes.DSServerMethod命令種類之後就可以執行遠端服務方法了。
    在稍後討論如何開發DataSnap 2009分散式應用程式時會再詳細的說明如何在用戶端執行遠端JSON伺服器輸出的方法。

    1-2-2 存取結果資料集 : TDBXReader類別

    當開發人員使用TDBXCommand執行命令時,TDBXCommand的ExecuteQuery方法會回傳TDBXReader物件,在DBX4框架中TDBXReader通常使用來代表執行命令之後回傳到用戶端的結果物件,如果開發人員執行的結果是資料集,那麼就可以使用TDBXReader來取得結果資料集,如果執行命令的結果是單一數值,那麼也會以結果資料集的形式儲存在TDBXReader物件中,開發人員仍然可以使用TDBXReader物件來存取結果數值。
    TDBXReader是一個單向,只能往前存取的資料結構型態,類似TDataSet。當開發人員取得TDBXReader物件並且欲存取其中的執行結果時,一開始先要呼叫它的Next方法以便把cursor指到TDBXReader物件中第一筆的資料位置,那麼在處理完每一筆資料之後再呼叫Next指到下一筆資料,而在每一筆資料位置上,開發人員是藉由存取TDBXReader的Value特性來取得每一個欄位的執行結果,下面的表格整理了TDBXReader類別中最重要的方法和特性的說明:
    http://blufiles.storage.live.com/y1pPsK8v6_zrKz7zWTiRAwjeRSu0nmP4YkwdrRNatMu6JV2lHVlGXAEYcZKSpSo-3hw

    TDBXReader的Value特性是一個複載的特性,開發人員可以使用名稱或是索引值來存取TDBXReader物件中每一筆資料中每一個欄位其中的數值,在TDBXReader中Value有如下的複載的宣告:

        property Value[const Ordinal: TInt32]: TDBXValue read GetValue; default;
        property Value[const Name: UnicodeString]: TDBXValue read GetValueByName; default;

    例如在前面執行SQL敘述的範例中,我們藉由TDBXReader複載的Value來存取結果資料集中'名稱'欄位的數值如下:

    aReader.Value['名稱'].GetWideString;

    如果'名稱'欄位是SEMINARS資料表中的第二個欄位,那麼我們也可以使用索引值的方式來如下它的數值如下:

    aReader.Value[1].GetWideString;

    因此使用TDBXReader物件通常擁有下面的兩個型式:
    • 使用TDBXReader存取單一執行結果:
    aDBXReader.Next;
    aDBXReader. Value[0].Get資料型態

    • 使用TDBXReader存取多個執行結果:
    while (aDBXReader.Next)
    begin
      aDBXReader. Value[0].Get資料型態;
      aDBXReader. Value[1].Get資料型態;
      …
    end;

    或是:
    while (aDBXReader.Next)
    begin
        for iCount := 0 to aDBXReader. ColumnCount – 1 do
        begin
          aDBXReader. Value[iCount].Get資料型態;
          aDBXReader. Value[iCount].Get資料型態;
        end;
      …
    end;

    此外,當開發人員實際在使用DBX4開發資料庫或是分散式應用程式時,應該大都是使用DBX4的元件,只有在很少的情形下才會直接使用TDBXReader,不過如果實的遇上必須使用TDBXReader時,那麼TDBXReader可以轉換為TDataSet嗎? 答案是可以的,在DXB4框架中TCustomSQLDataSet類別提供了一個重載建構函式,這個建構函式可以接受TDBXReader參數並且根據它建立TDataSet物件,如此一來就可以完成轉換TDBXReader物件為TDataSet物件的須要。下面即即是此TCustomSQLDataSet重載的建構函式宣告:

    TCustomSQLDataSet = class(TWideDataSet)

        constructor Create(AOwner: TComponent; DBXReader: TDBXReader; AOwnsInstance: Boolean); reintroduce; overload;

    因此我們可以使用如下簡單的程式碼就可以轉換TDBXReader物件為TDataSet物件:

      aDataSet := TCustomSQLDataSet.Create(nil, aCommand.ExecuteQuery, True);

    在取得了TDataSet物件之後,它就更容易使用而且能夠和DBX4的元件一起工作了。

    1-2-3 TDBXValue類別
    在上面的討論中,當我們使用TDBXReader的Value特性存取執行結果數值時,實際上是取得TDBXValue類別物件,每一個TDBXValue物件中包含一個執行結果數值,或是包含一個執行結果資料集欄位的數值。
    TDBXValue類別定義了許多GetXXXX方法讓開發人員取得執行結果數值,而XXXX就是不同的執行結果數值的資料型態。例如,如果執行結果是整數值,那麼就呼叫GetInt32方法來取得結果,如果是字串數值,就呼叫GetWideString,開發人員可以藉由判斷TDBXValue的ValueType特性來判斷其中數值的資料型態。
    下面的表格整理了TDBXValue類別重要的方法和特性:
    http://blufiles.storage.live.com/y1pXy2Uf6HW0zqMIJFQ1v6M19mbPxhBiLlZgsWtvqsHOTbuoD50ZA5eR3qPDOzUmlMH

    因此在一般的應用中,我們經常會使用如下的程式碼樣例來使用TDBXValue:

      case aDBXValue.ValueType.DataType of
        TDBXDataTypes.DateType:
          Result := IntToStr(aDBXValue.GetDate);
        TDBXDataTypes.WideStringType:
          Result := aDBXValue.GetWideString;
        TDBXDataTypes.Int32Type:
          Result := IntToStr(aDBXValue.GetInt32);
        TDBXDataTypes.DoubleType:
          Result := FloatToStr(aDBXValue.GetDouble);
        else
          ...
      end;



    November 14

    DBX框架篇 : 第1章 初探DBX4框架

    DBX技術從Delphi 6開始歷經了2次重大的改變,從Delphi 6一直到Delphi 2006是DBX技術的第1階段,在這段DBX的目的是在發展出一個輕量級,可跨平台的通用資料存取框架,以取代BDE/IDAPI成為Delphi/C++Builder的標準資料存取技術。在這個階段中,DBX技術是由VCL框架中的DBX相關類別/介面和DBX驅動程式組成,由於當時的DBX驅動程式是使用C/C++撰寫的,因此VCL中相關的DBX類別/介面也大量的使用了指標和靜態連結的方式來實作,這樣的架構在日後的擴展和維護上比較麻煩。
    從Delphi 2006之後,DBX進入第二個階段,這是因為DBX除了提供Win32的存取能力之外,也需要在.NET框架 2.0之後也改變架構的ADO.NET之上加入DBX的技術,以取代BDP.NET技術,同時DBX在此時也決定採取了另外一個重大的變革,那就是將重新使用Object Pascal程式語言來實作所有的DBX驅動程式。由於DBX驅動程式改用了Object Pascal實作,因此在VCL框架中和DBX驅動程式相關的程式碼再也不需要大量使用指標來呼叫其中的函式,這個優勢讓VCL中的DBX框架有了重新使用更好的方式來實作的機會,再加上Object Pascal也能夠執行於.NET平台之後(CodeGear在.NET平台上的Object Pascal稱為Delphi程式語言),DBX框架也終於可以使用相同的設計架構執行於Win32和.NET平台,因此第二階段的DBX改以現代化的類別和介面的架構為設計,實作基礎,讓DBX真正成為完全以物件導向思維設計出來的框架,在讀者隨著本書的章節閱讀之後也將對DBX框架有更完整的瞭解。
    現在讓我們先開始討論DBX4框架中的骨架類別,並且從這些最基礎而重要的類別中逐漸展開我們對於DBX4框架的掌握。
    任何資料存取技術都由一些固定的樣版類別或是物件來完成,例如首先需要連結到資料來源,需要執行SQL命令,需要存取資料庫中的物件資訊等,所有的資料存取技術都是由這些基本的類別/物件提供的服務再逐漸的提供更多豐富的功能。DBX也不例外,在DBX框架中也是以這些基本而重要的類別為核心架構而成,例如:
    •     TDBXConnection類別提供資料來源連結的能力,
    •     TDBXCommand類別提供執行SQL命令的功能
    •     TDBXMetaDataProvider類別提供存取和處理資料庫物件的功能
    要使用DBX連結資料來源,首先我們需要取得代表資料來源連結的物件: TDBXConnection,但是如何取得TDBXConnection物件以便和資料來源進行連結?又如何使用它來進行其他的工作? 要回答這些我們需要從TDBXConnectionFactory類別開始說起。

    1-1 使用DBX4技術連結資料庫 – TDBXConnectionFactory類別
    DBX框架中的TDBXConnectionFactory類別嚴格的說是一個抽象父類別,它的類別靜態方法GetConnectionFactory可以回傳一個從它繼承下來的實體類別,這實體類別將實作如何載入DBX驅動程式和資料來源連結設定等重要的資訊。在目前的DBX4框架實作中,這個實體類別是TDBXIniFileConnectionFactory。
    TDBXIniFileConnectionFactory會根據dbxdrivers.ini檔案中的驅動程式設定以載入驅動程式,並且根據dbxconnections.ini檔案中設定的資訊來設定連結資訊。
    因此在DBX4要和資料來源連結,首先我們可以使用下面的程式碼來取得TDBXIniFileConnectionFactory物件:
    TDBXConnectionFactory.GetConnectionFactory.
    接著在TDBXConnectionFactory類別中的GetConnection方法即可讓我們取得可連結資料來源的TDBXConnection物件。GetConnection有兩個原型宣告,它是複載(override)方法:

        function GetConnection( const ConnectionName : UnicodeString;
                                const UserName: UnicodeString;
                                const Password: UnicodeString) : TDBXConnection; overload;
        function GetConnection(ConnectionProperties : TDBXProperties) : TDBXConnection; overload;

    第一個GetConnection重載方法接受一個連結名稱,資料來源的登錄使用者名稱和密碼來回傳TDBXConnection物件,第二個GetConnection重載方法則接受一個TDBXProperties參數而回傳TDBXConnection物件。
    讓我們先解釋如何使用第一個GetConnection方法,它的第一個參數ConnectionName其實就是讀者在Delphi中使用DataExplorer建立的連結別名,例如下圖所示,假設我們有一個InterBase的連結別名IB_UCONNECTION,那麼我們就可以使用下面的程式碼來呼叫GetConnection以取得連結到InterBase的TDBXConnection物件:

    http://blufiles.storage.live.com/y1pw3XknILfNjkvrE03i0OHM3_PqiTqEdl3PPOjoDfP3MSYUKW-qLpooGMw7co_CU_N
    圖1-1 DataExplorer顯示存在dbxconnections.ini中的連結資訊

    var
      dbx4Conn : TDBXConnection;
    begin
      …
      dbx4Conn :=  TDBXConnectionFactory.GetConnectionFactory.GetConnection(
        ‘IB_UCONNECTION’, 'sysdba', 'masterkey');
      …

    或是建立下面的TDBXProperties物件,設定必要的特性值之後再傳遞TDBXProperties物件給GetConnection來取得和資料來源的連結:
    var
      dbxProp : TDBXProperties;
      dbxConn : TDBXConnection;
    begin
      dbxProp := TDBXProperties.Create;
      try
        dbxProp[TDBXPropertyNames.ConnectionName] := 'IB_UCONNECTION';
        dbxProp[TDBXPropertyNames.UserName] := 'sysdba';
        dbxProp[TDBXPropertyNames.Password] := 'masterkey';
        dbxConn :=  TDBXConnectionFactory.GetConnectionFactory.GetConnection(dbxProp);
        Assert(Assigned(dbxConn), TDBXPropertyNames.ConnectionName +'連結失敗');
      finally
        dbxProp.Free;
        dbxConn.Close;
        dbxConn.Free;
      end;

    上面程式碼中的TDBXPropertyNames類別中定義了所有資料來源可能需要設定的特性值名稱,例如下面是TDBXPropertyNames中的一些定義:

      TDBXPropertyNames = class
      Const
        ConnectionName              = 'ConnectionName';    
        UserName                    = 'User_Name';        
        Password                    = 'Password';         
        …
    end;

    上面的程式碼就設定了TDBXProperties物件要連結的資料來源名稱,登錄的使用者名稱和密碼等資訊,GetConnection方法就根據這些資訊取得資料來源的連結並且回傳代表此連結的TDBXConnection物件。

    TDBXConnectionFactory類別最重要的功能當然就是回傳TDBXConnection物件,因為有了TDBXConnection物件之後開發人員就可以使用它來執行各種工作,例如執行SQL命令,存取資料來源物件等,稍後本書都會說明。除此之外,TDBXConnectionFactory類別也可以讓開發人員取得所有資料來源的連結資訊,也就是圖1-1中DataExplorer顯示的資訊,以及所有dbExpress驅動程式資訊以及目前執行中的應用程式中註冊的dbExpress驅動程式資訊。

    例如TDBXConnectionFactory的GetConnectionItems可以取得圖1-1中的所有資料來源連結:
     
      aSL := TStringList.Create;
      TDBXConnectionFactory.GetConnectionFactory.GetConnectionItems(aSL);

    而GetDriverNames可以取得所有在dbxdrivers.ini檔案中的驅動程式名稱:

      aSL := TStringList.Create;
      TDBXConnectionFactory.GetConnectionFactory.GetDriverNames(aSL);

    GetRegisteredDriverNames則可以取得在目前的應用程式中註冊的dbExpress驅動程式,只有在應用程式中真正註冊的驅動程式才能使用來連結相對的資料來源。

      aSL := TStringList.Create;
      TDBXConnectionFactory.GetConnectionFactory.GetRegisteredDriverNames(aSL);

    例如下圖是使用上面的三個方法取得資料來源連結資訊,驅動程式以及這個範例應用程式中註冊的驅動程式,從圖中讀者就可以猜到這個範例是使用InterBase做為範例資料庫,因為目前註冊的驅動程式是InterBase,稍後我們會說明什麼是在應用程式中註冊的驅動程式。
     
    http://blufiles.storage.live.com/y1p5aESZm2GbXVDVBO1WZ8h1JyZzFpgSrBgCUPtM2EIUKtQloxpoWmjdT9yhe_5gNX1
    圖1-2 TDBXConnectionFactory可以存取資料來源和驅動程式資訊

    在離開TDBXConnectionFactory之前,我們必須再討論一下前面我們看到的GetConnectionFactory方法。
    GetConnectionFactory的功能是回傳TDBXConnectionFactory物件,但它實際回傳的是使用Singleton設計樣例的TDBXIniFileConnectionFactory物件。

    GetConnectionFactory方法首先會試著載入dbxdrivers.ini和dbxconnections.ini這兩個重要的檔案內容,載入的次序是
    1.    先從應用程式目前的目錄中搜尋,如果沒有,
    2.    再從系統註冊器中的HKEY_CURRENT_USER\Software\CodeGear\BDS\6.0\dbExpress中找尋,
    3.    最後再後從系統註冊器中的HKEY_LOCAL_MACHINE\Software\CodeGear\BDS\6.0\dbExpress中找尋,

    因此在您的系統註冊器中應該可以看到如下的資訊,記載了dbxdrivers.ini和dbxconnections.ini檔案的所在地:
     
    http://blufiles.storage.live.com/y1pOOJgaKPCvzB1QupXDDdKnewqrCcureaT8AZuPCkBHk0ehDnL1IkN5x7IL2ac285O
    圖1-3 系統註冊器中記錄了dbxdrivers.ini和dbxconnections.ini所在地

    之後,GetConnectionFactory會建立唯一的一個TDBXIniFileConnectionFactory物件,再呼叫由TDBXIniFileConnectionFactory物件複載TDBXConnectionFactory的兩個抽象方法LoadDrivers和LoadConnections來分析dbxdrivers.ini和dbxconnections.ini中的資料來源資訊和驅動程式資訊:

        procedure LoadDrivers; virtual;
        procedure LoadConnections; virtual;

    並且為每一個資料來源建立一個相對的TDBXProperties物件,而TDBXProperties物件中記錄的資訊就是LoadDrivers和LoadConnections方法從dbxdrivers.ini和dbxconnections.ini中分析出來的資訊。
    最後當開發人員呼叫GetConnection要取得TDBXConnection物件時,TDBXConnectionFactory便會根據開發人員欲開啟的資料來源相關的TDBXProperties物件中記錄的資訊來真正的載入dbExpress驅動程式。

    1-1-1 TDBXIniFileConnectionFactory類別
    TDBXIniFileConnectionFactory是從TDBXConnectionFactory繼承下來的實體類別,當開發人員呼叫TDBXConnectionFactory. GetConnectionFactory方法後實際取得的物件也就是TDBXIniFileConnectionFactory物件,在目前的DBX框架實作中使用了Singleton設計樣例,也就是說整個DBX應用程式中只會建立一個TDBXIniFileConnectionFactory物件。
    TDBXIniFileConnectionFactory類別最重要的就是複載TDBXConnectionFactory宣告的兩個抽象LoadDrivers和LoadConnections,這在上節中已經說明過了:

    TDBXIniFileConnectionFactory = class(TDBXConnectionFactory)

      protected
        procedure LoadDrivers; override;
        procedure LoadConnections; override;
      …
    end;

    TDBXIniFileConnectionFactory提供了ConnectionsFile特性可以存取資料來源設定檔,也就是dbxconnections.ini的完整檔名,DriversFile特性則可以存取dbxdrivers.ini的完整檔名。例如下面的程式碼可以分別取得目前的DBX應用程式載入和使用的dbxconnections.ini和dbxdrivers.ini檔案。

    (TDBXConnectionFactory.GetConnectionFactory as TDBXIniFileConnectionFactory).ConnectionsFile
    (TDBXConnectionFactory.GetConnectionFactory as TDBXIniFileConnectionFactory).DriversFile

    1-1-2 TDBXConnection類別
    TDBXConnection類別物件是真正代表連結到資料來源的物件,TDBXConnection類別主要的功能是讓開發人員可以建立執行SQL命令的TDBXCommand物件,啟動資料庫交易,確定資料庫交易或是取消交易,存取MetaData物件以處理資料來源物件以及存取資料來源中特定資訊等功能,可以說TDBXConnection是開發人員實際執行有關資料庫處理工作中最基礎而重要的類別。
    TDBXConnection提供了許多的方法和特性讓開發人員能夠進行重要的工作,下面的表格列出了其中最重要的方法/特性以及簡單的說明:

    名稱

    方法或特性

    說明

    BeginTransaction

    方法

    啟動資料庫交易

    CommitFreeAndNil

    方法

    確定完成資料庫交易並釋放相關的資源

    RollbackFreeAndNil

    方法

    取消資料庫交易並釋放相關的資源

    RollbackIncompleteFreeAndNil

    方法

    取消未完成的資料庫交易並釋放相關的資源

    CreateCommand

    方法

    建立一個TDBXCommand物件

    GetCommandTypes

    方法

    取得能夠執行的命令型態

    DatabaseMetaData

    特性

    取得TDBXDatabaseMetaData物件,TDBXDatabaseMetaData物件可以提供相關的元資料庫資訊


    上述的一些方法都擁有複載(override)的原型,例如BeginTransaction方法就提供了下面的兩個原型宣告:
       
        function  BeginTransaction(Isolation: TDBXIsolation): TDBXTransaction; overload; virtual;
        function  BeginTransaction: TDBXTransaction; overload; virtual;

    讓我們看看如何使用TDBXConnection物件讀者就可以瞭解如何使用上面的方法了。假設現在我們希望藉由DBX4執行下面的SQL命令:

    select count(*)  from DBXTESTTABLE

    我們需要使用下面的步驟來執行這個SQL命令:
    1.    取得TDBXConnection物件
    2.    使用TDBXConnection物件建立TDBXCommand物件,稍後的小節會討論TDBXCommand類別
    3.    使用TDBXCommand物件執行SQL命令
    4.    使用TDBXReader物件擷取執行結果,稍後的小節會討論TDBXReader類別

    前面我們已經討論如何解決步驟1的方法,步驟2的TDBXCommand物件可以藉由呼叫TDBXConnection物件的CreateCommand方法來建立,例如下面的程式碼即是如何使用DBX4執行SQL命令的範例,在008行呼叫CreateCommand方法建立TDBXCommand物件。
    接著步驟3的工作在010到012行完成,010行先設定TDBXCommand物件的CommandType特性值為執行SQL命令,011行設定TDBXCommand物件要執行的SQL命令,最後012行呼叫TDBXCommand的ExecuteQuery方法執行SQL命令。

    001    procedure TForm1.執行SQL命令;
    002    var
    003      aCommand : TDBXCommand;
    004      aReader: TDBXReader;
    005    begin
    006      if (Assigned(dbx4Conn)) then
    007      begin
    008        aCommand := dbx4Conn.CreateCommand;
    009        try
    010          aCommand.CommandType := TDBXCommandTypes.DbxSQL;
    011          aCommand.Text := Self.edtSQL.Text;
    012          aReader := aCommand.ExecuteQuery;
    013          if aReader.Next then
    014          begin
    015            if aReader.ValueType[0].DataType = TDBXDataTypes.Int64Type then
    016              Self.Caption := IntToStr(aReader.Value[0].GetInt64)
    017            else
    018              Self.Caption := IntToStr(aReader.Value[0].GetInt32);
    019          end;
    020        finally
    021          FreeAndNil(aReader);
    022          FreeAndNil(aCommand);
    023        end;
    024      end;
    025    end;

    TDBXCommand的ExecuteQuery方法在執行完SQL命令之後會回傳TDBXReader物件,TDBXReader物件包含了執行SQL命令的結果資料集,藉由TDBXReader的方法開發人員就可以取得執行結果。由於在這個範例中執行的結果只是一個數值,因此013行先呼叫TDBXReader的Next方法把指標指到第一筆的執行結果,再藉由存取它的Value特性值來取得最後的結果。

    讀者需要注意的是,在使用完TDBXCommand和TDBXReader物件之後都需要釋放它們,這在021和022行可以看到。

    再讓我們使用另外一個範例來看看如何使用TDBXConnection物件控制資料庫交易。下面的程式碼展示了如何使用DBX4在資料庫中建立資料表,這是由DBX4框架中的MetaData等相關類別提供的服務,在稍後的章節中我們會詳細的說明這些MetaData類別,在這裡讓我們先集中焦點在TDBXConnection類別。

    在下面的013行中呼叫了TDBXConnection的BeginTransaction啟動資料庫交易,之後的程式碼就執行在這個資料庫交易環境,當一切正確的執行後033行呼叫TDBXConnection的CommitFreeAndNil確定資料庫交易完成,如果在015~033行中發生的錯誤就會產生例外狀況,此時035行呼叫了TDBXConnection的RollbackFreeAndNil來取消資料庫交易。
    001    procedure TForm1.建立測試資料表;
    002    var
    003      aProvider: TDBXMetaDataProvider;
    004      Transaction: TDBXTransaction;
    005      MetaDataTable: TDBXMetaDataTable;
    006      anIndex : TDBXMetaDataIndex;
    007      aColumn : TDBXInt32Column;
    008    begin
    009      if (Assigned(dbx4Conn)) then
    010      begin
    011        aProvider := DoGetDBXMetaProvider(dbx4Conn);
    012        try
    013          Transaction := dbx4Conn.BeginTransaction;
    014          try
    015            aProvider.DropTable(TESTTABLENAME);
    016            MetaDataTable := TDBXMetaDataTable.Create;
    017            MetaDataTable.TableName := TESTTABLENAME;
    018            aColumn := TDBXInt32Column.Create('序號');
    019            aColumn.Nullable := False;
    020            MetaDataTable.AddColumn(aColumn);
    021            MetaDataTable.AddColumn(TDBXUnicodeVarCharColumn.Create('研討會名稱', 32));
    022            MetaDataTable.AddColumn(TDBXUnicodeVarCharColumn.Create('主講人', 10));
    023            MetaDataTable.AddColumn(TDBXDateColumn.Create('日期'));
    024            aProvider.CreateTable(MetaDataTable);
    025   
    026            anIndex := TDBXMetaDataIndex.Create;
    027            anIndex.TableName := MetaDataTable.TableName;
    028            anIndex.IndexName := 'pidx_序號';
    029            anIndex.Unique := True;
    030            anIndex.AddColumn('序號');
    031            aProvider.CreatePrimaryKey(anIndex);
    032   
    033            dbx4Conn.CommitFreeAndNil(Transaction);
    034          except
    035            dbx4Conn.RollbackFreeAndNil(Transaction);
    036          end;
    037        finally
    038          anIndex.Free;
    039          MetaDataTable.Free;
    040          aProvider.Free;
    041          dbx4Conn.RollbackIncompleteFreeAndNil(Transaction);
    042        end;
    043      end;
    044    end;

    請注意的是在041行又呼叫了TDBXConnection的RollbackIncompleteFreeAndNil方法,為什麼呢?這是因為在上面的except敘述中呼叫了TDBXConnection的RollbackFreeAndNil來取消資料庫交易,但是RollbackFreeAndNil本身也可能發生而又產生例外狀況,這有可能造成一些資源未被釋放,因此才在037行開始的finally程式區塊中呼叫RollbackIncompleteFreeAndNil來釋放任何可能未被釋放的資源,而RollbackIncompleteFreeAndNil本身不會產生例外狀況,它只是試著釋放應該被釋放的資源而已。
    因此在使用TDBXConnection物件來啟動資料庫交易時,可以使用如下的設計樣例來撰寫程式碼:

    aTransaction := TDBXConnection物件.BeginTransaction;
    try
      //實際工作程式碼
      TDBXConnection物件.CommitFreeAndNil(aTransaction);
    Except
      TDBXConnection物件. RollbackFreeAndNil(Transaction);
    Finally
      TDBXConnection物件. RollbackIncompleteFreeAndNil(Transaction)
    End;




    November 06

    第3章 使用DBX4的測試框架類別(2) : 如何客製化自動產生測試資料的流程

    面展示了如何使用TDbxDataGenerator自動產生測試資料,雖然DBX4框架提供了簡單的自動產生資料的能力,但在實際的開發中開發人員一定會需要產生有意義的測試資料,那麼開發人員如何能夠讓TDbxDataGenerator使用開發人員的方式來產生測試資料呢? 答案就在TDBXDataGeneratorColumn類別。
        當TDbxDataGenerator要產生測試資料時,它會使用DBX4的元資料相關類別來判斷欲產生測試資料的資料表的結構,然後根據資料表的欄位型態來建立TDBXDataGeneratorColumn衍生類別的物件來產生測試資料。也許讓我們來看看TDBXDataGeneratorColumn類別的定義,再以解釋讀者就更容易瞭解這整個流程。
    下面是TDBXDataGeneratorColumn的定義,它是一個抽象類別,其中定義了許多的虛擬方法以便讓衍生類別繼承並且加以實作。


     TDBXDataGeneratorColumn = class abstract
      public
        constructor Create(const InMetaDataColumn: TDBXMetaDataColumn);
        procedure Open; virtual;
        destructor Destroy; override;
        function GetString(const Row: Int64): UnicodeString; virtual; abstract;
        function GetBoolean(const Row: Int64): Boolean; virtual;
        function GetInt8(const Row: Int64): Byte; virtual;
        function GetInt16(const Row: Int64): SmallInt; virtual;
        function GetInt32(const Row: Int64): Integer; virtual;
        function GetInt64(const Row: Int64): Int64; virtual;
        function GetDouble(const Row: Int64): Double; virtual;
        function GetSingle(const Row: Int64): Single; virtual;
        function GetBytes(const Row: Int64): TBytes; virtual;
        function GetDecimal(const Row: Int64): UnicodeString; virtual;
        function GetYear(const Row: Int64): Integer; virtual;
        function GetMonth(const Row: Int64): Integer; virtual;
        function GetDay(const Row: Int64): Integer; virtual;
        function GetHour(const Row: Int64): Integer; virtual;
        function GetMinute(const Row: Int64): Integer; virtual;
        function GetSeconds(const Row: Int64): Integer; virtual;
        function GetMilliseconds(const Row: Int64): Integer; virtual;
        procedure SetValue(const Row: Int64; const Value: TDBXWritableValue); virtual; abstract;
      protected
        procedure SetDataGenerator(const DataGenerator: TDBXCustomDataGenerator); virtual;
        function GetColumnName: UnicodeString; virtual;
        function GetDataType: Integer; virtual;
        function GetMetaDataColumn: TDBXMetaDataColumn; virtual;
      private
        function TypeNotSupported: TDBXDataGeneratorException;
      protected
        FMetaDataColumn: TDBXMetaDataColumn;
        FDataGenerator: TDBXCustomDataGenerator;
      public
        property DataGenerator: TDBXCustomDataGenerator write SetDataGenerator;
        property ColumnName: UnicodeString read GetColumnName;
        property DataType: Integer read GetDataType;
        property MetaDataColumn: TDBXMetaDataColumn read GetMetaDataColumn;
      end;


    例如假設欲產生測試資料的資料表的第一個欄位型態是整數序列號,那麼TDbxDataGenerator便會建立TDBXInt32SequenceGenerator的物件來產生測試資料,由於TDBXInt32SequenceGenerator是提供產生整數序列號的測試資料,因此它只複載了TDBXDataGeneratorColumn的GetInt32虛擬方法以提供整數序列號數值,複載GetString虛擬方法以字串的型態提供整數序列號數值, TDBXInt32SequenceGenerator並不需要複載所有TDBXDataGeneratorColumn的虛擬方法。

      TDBXInt32SequenceGenerator = class(TDBXDataGeneratorColumn)
      public
        constructor Create(const MetaData: TDBXMetaDataColumn);
        procedure Open; override;
        function GetInt32(const Row: Int64): Integer; override;
        function GetString(const Row: Int64): UnicodeString; override;
        procedure SetValue(const Row: Int64; const Value: TDBXWritableValue); override;
      end;

    而例如假設欲產生測試資料的資料表的第一個欄位型態是字串型態,那麼TDbxDataGenerator便會建立TDBXWideStringSequenceGenerator的物件來產生測試資料,TDBXWideStringSequenceGenerator的定義如下,它的使用方式和前面介紹的TDBXInt32SequenceGenerator是一樣的,只是它是負責提供產生字串型態的測試資料:

      TDBXWideStringSequenceGenerator = class(TDBXDataGeneratorColumn)
      public
        constructor Create(const MetaData: TDBXMetaDataColumn);
        procedure Open; override;
        function GetString(const Row: Int64): UnicodeString; override;
        procedure SetValue(const Row: Int64; const Value: TDBXWritableValue); override;
      end;

    在DBX4框架中類似TDBXInt32SequenceGenerator類別以提供各種不同型態的測試資料類別超過10個以上,而且開發人員還可以自己定義/實作客製化類別,稍後我們就會討論。下面的表格說明了這些類別:

    類別

    說明

    TDBXBooleanSequenceGenerator

    提供產生布林值型態的測試資料

    TDBXBlobSequenceGenerator

    提供產生Blob型態的測試資料

    TDBXAnsiStringSequenceGenerator

    提供產生Ansi字串型態的測試資料

    TDBXDateSequenceGenerator

    提供產生日期型態的測試資料

    TDBXDecimalSequenceGenerator

    提供產生Decimal型態的測試資料

    TDBXDoubleSequenceGenerator

    提供產生Double型態的測試資料

    TDBXInt16SequenceGenerator

    提供產生16位元整數型態的測試資料

    TDBXInt32SequenceGenerator

    提供產生32位元整數型態的測試資料

    TDBXInt64SequenceGenerator

    提供產生64位元整數型態的測試資料

    TDBXInt8SequenceGenerator

    提供產生8位元型態的測試資料

    TDBXTimeSequenceGenerator

    提供產生時間型態的測試資料

    TDBXTimestampSequenceGenerator

    提供產生Timestamp型態的測試資料

    TDBXWideStringSequenceGenerator

    提供產生WideString/UnicodeString型態的測試資料



    讓我們看看TDBXWideStringSequenceGenerator如何產生測試資料,下面是TDBXWideStringSequenceGenerator的GetString方法,它會在TDbxDataGenerator要為字串型態的欄位產生測試資料時呼叫:

    function TDBXWideStringSequenceGenerator.GetString(const Row: Int64): UnicodeString;
    var
      Value: UnicodeString;
    begin
      Value := 'W' + IntToStr(Row);
      if FMetaDataColumn.FixedLength then
        while Length(Value) < FMetaDataColumn.Precision do
          Value := Value + ' ';
      Result := Value;
    end;

    從上面的實作程式碼可以知道,TDBXWideStringSequenceGenerator.GetString在產生測試資料時是以’W’字元為起頭並且加入正在產生測試資料的列行數的數值,例如為第一筆資料產生的是W1,第2筆就是W2等以此類推,這也解釋了為什麼圖3-2中『研討會名稱』和『主講人』欄位的測試資料就是這樣的內容。
    瞭解了上面討論的原理之後,那麼我們如何讓TDbxDataGenerator產生我們需要的測試資料而不是內定的簡單而無意義的測試資料呢? 答案很簡單,我們只需要進行下面的兩項工作:

    1. 實作從TDBXDataGeneratorColumn衍生的類別,在這個衍生的類別中使用程式碼或是其他方式(例如從檔案中載入測試資料)來產生測試資料
    2. 通知TDbxDataGenerator建立我們實作的衍生的類別來為測試資料表的欄位來產生測試資料

    首先讓我們看看如何完成上面的第二個步驟,這個工作很簡單,我們只需要建立客製化TDBXDataGeneratorColumn衍生類別物件,並且加入到TDbxDataGenerator物件之中即可。
    例如在下面的程式碼中,我們首先在047行中藉由元資料類別取得測試資料表的每一個欄位物件,然後在049到067行中一一的判斷每一個欄位物件的資料型態,接著就建立相對應的TDBXDataGeneratorColumn客製化衍生類別物件,再呼叫TDbxDataGenerator的AddColumn方法把它加入到TDbxDataGenerator物件之中。

    001    procedure TForm5.加入欄位物件(aProvider : TDBXMetaDataProvider; DataGenerator: TDbxDataGenerator);
    002   
    003      function 建立整數欄位(cName : string) : TDBXInt32SequenceGenerator;
    004      var
    005        col : TDBXInt32Column;
    006      begin
    007        col := TDBXInt32Column.Create(cName);
    008        try
    009          Result := TDBXInt32SequenceGenerator.Create(col);
    010        finally
    011          FreeAndNil(col);
    012        end;
    013      end;
    014   
    015      function 建立字串欄位(cName : string; iLength : Integer) : TDBXWideStringSequenceGenerator;
    016      var
    017        col : TDBXUnicodeVarCharColumn;
    018      begin
    019        col := TDBXUnicodeVarCharColumn.Create(cName, iLength);
    020        try
    021          if (col.ColumnName = '主講人') then
    022            Result := T主講人產生器.Create(col)
    023          else
    024            if (col.ColumnName = '研討會名稱') then
    025              Result := T研討會產生器.Create(col)
    026            else
    027              Result := TDBXWideStringSequenceGenerator.Create(col);
    028        finally
    029          FreeAndNil(col);
    030        end;
    031      end;
    032   
    033      function 建立日期欄位(cName : string) : TDBXDateSequenceGenerator;
    034      var
    035        col : TDBXDateColumn;
    036      begin
    037        col := TDBXDateColumn.Create(cName);
    038        try
    039          Result :=TDBXDateSequenceGenerator.Create(col);
    040        finally
    041          FreeAndNil(col);
    042        end;
    043      end;
    044    var
    045      cols : TDBXColumnsTableStorage;
    046    begin
    047      cols := 取得欄位物件(aProvider, TESTTABLENAME);
    048   
    049      while (cols.InBounds) do
    050      begin
    051        case cols.DbxDataType of
    052          TDBXDataTypes.UnknownType, TDBXDataTypes.Int32Type :
    053          begin
    054            DataGenerator.AddColumn(建立整數欄位(cols.ColumnName));
    055          end;
    056          TDBXDataTypes.WideStringType :
    057          begin
    058            DataGenerator.AddColumn(建立字串欄位(cols.ColumnName, cols.Precision));
    059          end;
    060          TDBXDataTypes.DateType :
    061          begin
    062            DataGenerator.AddColumn(建立日期欄位(cols.ColumnName));
    063          end;
    064        end;
    065        cols.Next;
    066      end;
    067    end;

    那麼步驟1如何完成? 也很簡單,我們只需要定義從TDBXDataGeneratorColumn或是它的衍生類別繼承下來的客製化類別,再於其中撰寫如何產生測試資料的程式碼即可。
    例如下面就是上面程式碼使用的『T研討會產生器』類別,這個類別負責在測試資料表的『研討會名稱』欄位中產生測試資料:

    001    unit u研討會產生器;
    002   
    003    interface
    004    uses
    005      DBXCommon,
    006      DBXCommonTable,
    007      DBXMetaDataProvider,
    008      SysUtils,
    009      DBXCustomDataGenerator;
    010   
    011    type
    012      T研討會產生器 = class(TDBXWideStringSequenceGenerator)
    013      public
    014        constructor Create(const MetaData: TDBXMetaDataColumn);
    015        procedure Open; override;
    016        function GetString(const Row: Int64): UnicodeString; override;
    017        procedure SetValue(const Row: Int64; const Value: TDBXWritableValue); override;
    018      private
    019        dataArray : array[0..5] of UnicodeString;
    020        iPos : Integer;
    021        procedure InitailizeData;
    022      end;
    023   
    024    implementation
    025   
    026    { TDBXMyWideStringSequenceGenerator }
    027   
    028    constructor T研討會產生器.Create(
    029      const MetaData: TDBXMetaDataColumn);
    030    begin
    031      inherited Create(MetaData);
    032      InitailizeData;
    033    end;
    034   
    035    function T研討會產生器.GetString(
    036      const Row: Int64): UnicodeString;
    037    begin
    038      Result := dataArray[iPos mod 6];
    039      Inc(iPos);
    040    end;
    041   
    042    procedure T研討會產生器.InitailizeData;
    043    begin
    044      dataArray[0] := 'Delphi 2009產品技術發表會';
    045      dataArray[1] := 'C++Builder 2009產品技術發表會';
    046      dataArray[2] := 'Delphi 2009 Unicode程式設計';
    047      dataArray[3] := 'Delphi 2009 DataSnap程式設計';
    048      dataArray[4] := 'C++Builder 2009 Unicode程式設計';
    049      dataArray[5] := 'C++Builder 2009 DataSnap程式設計';
    050      iPos := 0;
    051    end;
    052   
    053    procedure T研討會產生器.Open;
    054    begin
    055      inherited open;
    056    end;
    057   
    058    procedure T研討會產生器.SetValue(const Row: Int64;
    059      const Value: TDBXWritableValue);
    060    begin
    061      Value.SetWideString(GetString(Row));
    062    end;
    063   
    end.

    正是由於我們提供了客製化的『T研討會產生器』類別,因此TDbxDataGenerator才在圖3-3中產生了客製化的測試資料,如何?整個客製化的流程很簡單吧。

    Ok,希望現在各位就瞭解如何使用和整合DBX4框架來自動測試任何的測試資料了。


    October 26

    第3章 使用DBX4的測試框架類別(1)

    DBX框架中除了處理資料庫的類別之外,其實還包含了許多有用的輔助類別,這些輔助類別除了可以幫助開發人員撰寫資料處理功能之外,也可以幫助開發人員建立測試資料庫,測試資料表以及自動建立測試資料等一般在開發資料庫應用程式經常需要進行的工作。DBX框架甚至擴展了DUnit的測試驅動類別讓開發人員可以建立資料庫測試案例以測試應用程式以及資料是否正確,如此一來可以讓開發人員結合測試驅動開發(TDD)以及資料庫開發的工作。
    本章的討論重點就是為各位介紹這些非常有用的相關類別,在解釋了這些類別之後本章將使用一些範例來展示如何使用這些有用的類別,現在讓我們從最基礎的TDBXCustomDataGenerator類別開始說起。

    3-1 可客製化的自動建立測試資料類別 - TDBXCustomDataGenerator
    TDBXCustomDataGenerator類別從它的名稱上我們就可以猜到這個類別提供的功能是能夠進行客製化工作的資料產生類別,沒錯,TDBXCustomDataGenerator提供的服務就是和上一章討論的TDBXMetaDataProvider類別合作,一起在開發人員指定的資料表中自動建立測試資料並且進行各種測試的工作。
    下面的表格整理了TDBXCustomDataGenerator提供的公用(public)方法和特性的說明,開發人員可以藉由呼叫這些方法或是存取這些特性來自動建立測試資料:
    http://blufiles.storage.live.com/y1pX_1ak1MLTU9iePlc4goIvo9kCPt-FZ9jTOG0TkVV9U-Ko9713VFg55uZ1bnnr-Km

    在上一章中我們詳細的說明了TDBXMetaDataProvider類別的功能,由於TDBXMetaDataProvider類別可提供處理資料庫物件的功能,因此TDBXCustomDataGenerator類別可以藉由TDBXMetaDataProvider提供的服務來自動建立測試資料庫,測試資料表和測試資料表中的欄位,之後再根據這些資訊來建立正確的Insert SQL敘述在測試資料表中自動建立測試資料。
    此外,當TDBXCustomDataGenerator自動產生測試資料時,是使用TDBXDataGeneratorColumn類別來產生測試資料表欄位中的數值,而TDBXDataGeneratorColumn是一個抽象類別,它的實體類別由其他10多個繼承類別來實作以提供特定的資料型態欄位的數值,開發人員可以直接使用這些實體類別來自動產生欄位資料,也可以再從這些實體類別繼承下來實作客製化的TDBXDataGeneratorColumn類別來產生符合應用程式需要的測試資料,在稍後的小節中我們將會說明TDBXDataGeneratorColumn和它相關的實體類別。
    雖然開發人員可以直接使用TDBXCustomDataGenerator類別,但是DBX框架提供了一個TDBXCustomDataGenerator的繼承類別: TDBXDataGenerator,開發人員應該盡量使用TDBXDataGenerator而不是直接使用TDBXCustomDataGenerator類別,因為TDbxDataGenerator又額外提供許多驗證資料的服務,因此接下來讓我們說明TDbxDataGenerator類別的功能。

    3-2 DBX4自動建立測試資料類別 –TDbxDataGenerator
    TDBXDataGenerator是TDBXCustomDataGenerator的子類別,它在TDBXCustomDataGenerator提供的服務之外主要是加上驗證資料的服務。TDBXDataGenerator藉由提供許多靜態類別的方法來提供驗證資料的服務,這些方法都是以Validate為開頭的方法,例如ValidateAnsiString,ValidateWideString等,下面即是TDBXDataGenerator的類別宣告:
      TDbxDataGenerator = class(TDBXCustomDataGenerator)
      private
        FParam: TParam;
        //Remove once all drivers support Fractions (milliseconds) for timestamp (255605, 255767, 255768, 255769)
        function GetIsFractionsSupported: Boolean;
        procedure SetParameter(Ordinal: Integer; Row: Integer; Param: TParam);
        procedure ValidateParam(Ordinal: Integer; Row: Integer; Param: TParam);
      public
        constructor Create;
        destructor Destroy; override;
        class procedure ValidateAnsiString(Value1, Value2: String); static;
        class procedure ValidateWideString(Value1, Value2: String); static;
        class procedure ValidateBoolean(Value1, Value2: Boolean); static;
        class procedure ValidateInt64(Value1, Value2: Int64); static;
        class procedure ValidateDouble(Value1, Value2: Double); static;
        class procedure ValidateBcd(Value1, Value2: TBcd); static;
        class procedure ValidateDate(Value1, Value2: TDBXDate); static;
        class procedure ValidateTime(Value1, Value2: TDBXTime); static;
        class procedure ValidateTimestamp(Value1, Value2: TSQLTimeStamp); static;
        class procedure ValidateBytes(Value1, Value2: TBytes); static;
        class function BytesEquals(const Value1: TBytes; const Value2: TBytes; Count: Integer): Boolean; static;
        class procedure ReadBytesFromStream(Value: TDBXValue; var Buffer: TBytes);

        function ValidateRow(const Reader: TDBXReader; Row: Integer): Boolean; overload;
        function ValidateRow(const DataSet: TDataSet; Row: Integer): Boolean; overload;
        procedure ValidateParameters(const Command: TDBXCommand; Row: Integer);
        procedure ValidateParams(const Params: TParams);
        procedure AddParameters(const Command: TDBXCommand); overload;
        procedure AddParameters(const SQLQuery: TSQLQuery); overload;
        {$IF DEFINED(CLR)}
        procedure AddParameters(const Command: DbCommand); overload;
        {$IFEND}
        procedure SetInsertParameters(const Command: TDBXCommand; Row: Integer); overload;
        procedure SetInsertParameters(const SQLQuery: TSQLQuery; Row: Integer); overload;
        {$IF DEFINED(CLR)}
        procedure SetInsertParameters(const Command: DbCommand; Row: Integer); overload;
        {$IFEND}
        procedure PopulateParams(Params: TParams);
        procedure PopulateDataset(DataSet: TDataSet; RowCount: Integer);
        //Remove once all drivers support Fractions (milliseconds) for timestamp (255605, 255767, 255768, 255769)
        property IsFractionsSupported: Boolean read GetIsFractionsSupported;
      end;

    當開發人員使用TDBXCustomDataGenerator建立了測試資料之後,就可以再藉由TDbxDataGenerator的服務來測試資料,因此開發人員直接標準專家TDbxDataGenerator類別就可以同時進行這兩個工作。
    從上面的各個ValidateXXXX方法我們可以猜到,開發人員能夠藉由呼叫這些不同的ValidateXXXX方法來測試各種不同的資料型態的欄位數值,至於TDbxDataGenerator類別其他的一些方法則是使用來在其他物件中根據TDbxDataGenerator代表的資料表的欄位在其他物件中建立相對應的物件,例如下面的AddParameters方法:

        procedure AddParameters(const Command: TDBXCommand); overload;


    它的功能就根據TDbxDataGenerator代表的資料表欄位在它的參數TDBXCommand物件中建立TDBXParameter物件。
    至於SetInsertParameters方法:

        procedure SetInsertParameters(const Command: TDBXCommand; Row: Integer); overload;

    則是根據TDbxDataGenerator代表的資料表欄位數值設定它的參數TDBXCommand物件中TDBXParameter物件的數值。
    說明了TDBXCustomDataGenerator和TDbxDataGenerator這兩個類別之後,讓我們來看看如何使用它們。


    3-3 使用TDbxDataGenerator類別
    現在讓我們在一個資料表中來自動產生測試資料並且使用TDbxDataGenerator提供的驗證方法來驗證資料,從這個範例中我們將可以瞭解如何使用TDBXCustomDataGenerator和TDbxDataGenerator類別。

    3-3-1 使用TDBXCustomDataGenerator自動產生測試資料
    在第2章中我們使用了DBX的Meta類別展示了如何建立資料表DBXTESTTABLE,因此讓我們繼續使用這個範例資料表來展示如何在其中動建立測試資料。DBXTESTTABLE是一個擁有如下結構的資料表:
     
    http://blufiles.storage.live.com/y1p0y6rO7sMZt3Fx3VQG_5lULFUN1VAyvaYHe2V8Upn4Gm--FATaxbbrdh_8Vb4nxiR
    圖3-1 測試資料表DBXTESTTABLE的結構

    要在其中產生測試資料,我們需要建立TDbxDataGenerator物件,但首先我們需要先取得DBX連結物件,再取得DBX元物件之後才能使用TDbxDataGenerator物件。因此首先呼叫『取得資料來源』連結使用第一章介紹的TDBXConnectionFactory類別來連結DBX資料來源:
    procedure TForm5.btn建立測試資料重構的版本Click(Sender: TObject);
    begin
      取得資料來源連結;
    end;

    取得了連結資料來源的TDBXConnection物件之後,就可以呼叫『建立測試資料』方法使用TDbxDataGenerator來自動建立測試資料了:

    procedure TForm5.btn建立測試資料重構的版本Click(Sender: TObject);
    begin
      取得資料來源連結;
      建立測試資料;
    end;

    『建立測試資料』方法的工作就是使用TDbxDataGenerator自動建立測試資料,因此它需要進行的工作如下:
    1.    藉由TDBXMetaDataProvider取得要建立測試資料的資料表的結構
    2.    根據步驟1的結果建立正確的TDbxDataGenerator物件
    3.    呼叫TDbxDataGenerator物件的方法建立資料

    下面的程式碼中顯示了如何完成上述的兩個步驟,005行的dbx4Conn即是前面呼叫『取得資料來源』方法而取得的TDBXConnection物件,007行的『取得DBXMetaProvider』即可取得TDbxDataGenerator物件,接著009行呼叫『資料表存在嗎』方法來判斷資料庫中是否存在要建立測試資料的資料表TESTTABLENAME。
    001    procedure TForm5.建立測試資料;
    002    var
    003      aProvider: TDBXMetaDataProvider;
    004    begin
    005      if (Assigned(dbx4Conn)) then
    006      begin
    007        aProvider := 取得DBXMetaProvider(dbx4Conn);
    008        try
    009          if (資料表存在嗎(aProvider, TESTTABLENAME)) then
    010          begin
    011          end;
    012        finally
    013          aProvider.Free;
    014        end;
    015      end;
    016    end;

    現在有了TDBXConnection和TDBXMetaDataProvider這兩個必要的物件之後下面的013行建立TDbxDataGenerator物件,014行設定它的目標資料表名稱,015行設定它連結的TDBXMetaDataProvider物件,接著016行呼叫加入『欄位物件』方法在TDbxDataGenerator物件中建立測試資料相對應的欄位物件:

    001    procedure TForm5.建立測試資料;
    002    var
    003      aProvider: TDBXMetaDataProvider;
    004      DataGenerator: TDbxDataGenerator;
    005    begin
    006      if (Assigned(dbx4Conn)) then
    007      begin
    008        aProvider := 取得DBXMetaProvider(dbx4Conn);
    009        try
    010          if (資料表存在嗎(aProvider, TESTTABLENAME)) then
    011          begin
    012            try
    013              DataGenerator := TDBXDataGenerator.Create;
    014              DataGenerator.TableName := TESTTABLENAME;
    015              DataGenerator.MetaDataProvider := aProvider;
    016              加入欄位物件(aProvider, DataGenerator);
    017            finally
    018              Reader.Free;
    019              DataGenerator.Free;
    020            end;
    021          end;
    022        finally
    023          aProvider.Free;
    024        end;
    025      end;
    end;

    為什麼需要執行上面『欄位物件』方法呢? 這是因為TDbxDataGenerator在資料表中自動建立測試資料時,它需要知道測試資料表每一個欄位的型態以便呼叫稍後會介紹的TDBXDataGeneratorColumn的衍生類別物件來建立測試資料,例如假設測試資料表中的第一個欄位是整數型態,那麼TDbxDataGenerator便會呼叫我們在TDbxDataGenerator物件中加入的TDBXDataGeneratorColumn的衍生類別物件來自動產生整數值。也許在我們解釋了『欄位物件』方法是如何工作之後讀者會更瞭解這個工作原理。

    下面即是『欄位物件』方法的實作程式碼,為了節省篇幅因此我只列出了測試資料表欄位使用的資料型態,由於測試資料表的欄位型態只使用了整數,UnicodeString和日期型態,因此在045~057的case敘述中我判斷這些資料型態,在實際的程式碼中是應該判斷所有DBX支援的欄位資料型態的。OK,現在就讓我們說明『欄位物件』方法是如何工作的。

    在041行中呼叫『取得欄位物件』方法藉由TDBXMetaDataProvider物件來取得測試資料表中所有的欄位物件,接著進入while迴圈一一的判斷每一個欄位物件的資料型態來建立相對應的TDBXDataGeneratorColumn的衍生類別物件來產生欄位值,例如如果測試資料表的欄位型態是整數型態,那麼就在007行先建立TDBXInt32Column物件,再於009行建立TDBXInt32SequenceGenerator物件並且加入到TDbxDataGenerator物件中,因此稍後當我們使用TDbxDataGenerator物件來產生測試資料時,TDbxDataGenerator物件中就會在測試資料表的整數型態欄位中使用TDBXInt32SequenceGenerator物件來自動產生測試欄位值。相同的,015行的『建立字串欄位』函式是使用TDBXWideStringSequenceGenerator來建立UnicodeString欄位的測試值,027行的『建立日期欄位』函式是使用TDBXDateSequenceGenerator物件在日期型態的欄位中建立測試值。

    001    procedure TForm5.加入欄位物件(aProvider : TDBXMetaDataProvider; DataGenerator: TDbxDataGenerator);
    002   
    003      function 建立整數欄位(cName : string) : TDBXInt32SequenceGenerator;
    004      var
    005        col : TDBXInt32Column;
    006      begin
    007        col := TDBXInt32Column.Create(cName);
    008        try
    009          Result := TDBXInt32SequenceGenerator.Create(col);
    010        finally
    011          FreeAndNil(col);
    012        end;
    013      end;
    014   
    015      function 建立字串欄位(cName : string; iLength : Integer) : TDBXWideStringSequenceGenerator;
    016      var
    017        col : TDBXUnicodeVarCharColumn;
    018      begin
    019        col := TDBXUnicodeVarCharColumn.Create(cName, iLength);
    020        try
    021          Result := TDBXWideStringSequenceGenerator.Create(col);
    022        finally
    023          FreeAndNil(col);
    024        end;
    025      end;
    026   
    027      function 建立日期欄位(cName : string) : TDBXDateSequenceGenerator;
    028      var
    029        col : TDBXDateColumn;
    030      begin
    031        col := TDBXDateColumn.Create(cName);
    032        try
    033          Result :=TDBXDateSequenceGenerator.Create(col);
    034        finally
    035          FreeAndNil(col);
    036        end;
    037      end;
    038    var
    039      cols : TDBXColumnsTableStorage;
    040    begin
    041      cols := 取得欄位物件(aProvider, TESTTABLENAME);
    042   
    043      while (cols.InBounds) do
    044      begin
    045        case cols.DbxDataType of
    046          TDBXDataTypes.Int32Type :
    047          begin
    048            DataGenerator.AddColumn(建立整數欄位(cols.ColumnName));
    049          end;
    050          TDBXDataTypes.WideStringType :
    051          begin
    052            DataGenerator.AddColumn(建立字串欄位(cols.ColumnName, cols.Precision));
    053          end;
    054          TDBXDataTypes.DateType :
    055          begin
    056            DataGenerator.AddColumn(建立日期欄位(cols.ColumnName));
    057          end;
    058        end;
    059        cols.Next;
    060      end;
    end;

    現在我們已經建立了正確的TDbxDataGenerator物件之後,接著下來的工作就是完成上面的第3個步驟的工作,首先我們如第一章所述建立TDBXCommand物件來準備執行新增資料的SQL敘述,如下面001行所示。

    001    Command := dbx4Conn.CreateCommand;
    002    Command.Text := DataGenerator.CreateParameterizedInsertStatement;
    003    DataGenerator.AddParameters(Command);
    004    for iRow := 1 to RowCount do
    005    begin
    006      DataGenerator.SetInsertParameters(Command, iRow);
    007      Command.ExecuteUpdate;
    end;

    接著在在002行呼叫TDbxDataGenerator的CreateParameterizedInsertStatement方法在TDBXCommand物件中產生新增資料的SQL敘述,例如在這個測試資料表中產生如下的SQL敘述:

    INSERT INTO DBXTESTTABLE ("序號","研討會名稱","主講人","日期") VALUES (?,?,?,?)

    013行呼叫AddParameters方法為TDBXCommand物件中的每一個以『?』代表的動態參數設定正確的資料型態。
    最後004的迴圈就是重點了,我們在006行呼叫TDbxDataGenerator的SetInsertParameters方法來實際的設定每一個動態參數的測試值,而測試值則是由前面『加入欄位物件』方法在TDbxDataGenerator物件中加入的欄位物件來真正的產生。
    執行上面的程式碼之後,我們可以在測試資料表TESTTABLENAME之中看到如下自動產生的測試資料:


    http://blufiles.storage.live.com/y1p8_nyyIyAMo7On1IfJ5fVpFLoe9t_UerMp66HxZjJ69lV-vZwSgShua81ZDFPsRX5
    圖3-2 TDbxDataGenerator在測試資料表中自動產生的測試資料

    從上面的討論中我們可以瞭解了DBX框架中的TDbxDataGenerator類別提供了自動化的機制讓我們在開發資料庫應用程式時能夠把許多的工作自動化,讓我們在開發資料庫應用程式時有更多的機制幫助我們撰寫出品質更高的程式碼。
    當然,我知道讀者現在會問TDbxDataGenerator產生的資料是沒有意義的,能夠使用TDbxDataGenerator產生更實際的測試資料嗎? 這是當然的,DBX框架提供的資料庫測試是提供一個可用的框架讓開發人員使用,開發人員在瞭解了這個機制之後當然可以使用這些相關的類別進行客製化的工作,也可以藉由掌握了這些原理之後來產生實際的測試資料。
    例如筆者可以藉由稍後討論的TDBXDataGeneratorColumn衍生類別在測試資料表中自動產生客製化的測試資料,如下所示:
     
    http://blufiles.storage.live.com/y1px7zbP7LY1TjUJo0p8NgqwYnGDqRjBH9SnCiAetEcFP7ExUtSfn1IoLeK2D0502yy
    圖3-3 在測試資料表中自動產生客製化的測試資料

    藉由TDBXDataGeneratorColumn衍生類別我們可以產生任何實際的測試資料,而且完全自動化,接著再結合稍後介紹的TDBXTestCase類別,開發人員可以在開發資料庫應用程式的同時使用TDD的開發模式。

    3-3-2 使用TDbxDataGenerator驗證資料
    『待續』




    September 24

    淺談如何使用Delphi 2009的泛型容器類別(續)

    使用TDictionary<T,T>容器類別

    Delphi 2009中的容器類別TDictionary<T,T>對於許多日常開發的工作中非常的有用,例如我們經常需要在應用程式中使用一個鍵值來搜尋相關的資料,在這種應用中TDictionary<T,T>容器類別便非常適合。
    TDictionary<T,T>容器類別的第一個<T>即代表鍵值,第二個<T>代表這個鍵值相關的數值,由於TDictionary<T,T>是泛型容器類別,所以它的鍵值和數值都可以是任何的型態。現在就讓我們看看如何使用TDictionary<T,T>,我們仍然繼續使用前面討論的範例做為說明。
    假設現在我們希望統計一下glProduct中Delphi和BCB的產品個數是多少,那麼我們可以使用TDictionary<T,T>來儲存<產品名稱, 個數>這樣的資訊,因此我們需要建立一個TDictionary<string, Integer>的物件來使用。
    TDictionary<T,T>提供了許多不同的原型的建構函式,其中最簡單的建構函式擁有如下的原型宣告:

        constructor Create(ACapacity: Integer = 0); overload;

    這個建構函式接受一個內定大小的參數ACapacity,這個參數是指TDictionary<T,T>物件在建立之後先在其中預先建立多少個元素。為什麼需要這樣?這是因為我們在建立了TDictionary<T,T>物件之後一定會在其中加入元素(不然為什麼需要建立TDictionary<T,T>物件呢?),由於TDictionary<T,T>比較複雜,因此如果我們預先在其中先建立一些元素空間以便稍後使用,這樣的執行效率會比較好,否則稍後當我們在TDictionary<T,T>物件中加入元素時TDictionary<T,T>物件就需要動態增加它的的元素空間大小,這樣會影響執行效率。
    下面的程式碼就是實作統計glProduct中Delphi和BCB的產品個數是多少的程式碼,首先我們先建立TDictionary<string, Integer>物件aDic並且預先在其中建立可儲存5個元素大小的空間,接著我們藉由TEnumerator物件一一從glProduct中最出產品名稱,然後呼叫TDictionary<T,T>的ContainsKey方法來查詢這個產品名稱鍵值是否已經存在aDic之中,如果產品名稱鍵值已經存在,那麼就呼叫TDictionary<T,T>的AddOrSetValue方法增加這個產品名稱的個數,否則就在aDic中加入這個<產品名稱, 1>這對鍵值和數值,最後呼叫DisplayProductCount來顯示統計的數值。

    procedure TForm17.btn統計產品總數Click(Sender: TObject);
    var
      aDic: TDictionary<string, Integer>;
      aEnum : TEnumerator<TProduct>;
    begin
      aDic := TDictionary<string,Integer>.Create(5);
      try
        aEnum := glProduct.GetEnumerator;

        while (aEnum.MoveNext) do
        begin
          if (aDic.ContainsKey(aEnum.Current.GetCategory)) then
            aDic.AddOrSetValue(aEnum.Current.GetCategory, aDic.Items[aEnum.Current.GetCategory] + 1)
          else
            aDic.Add(aEnum.Current.GetCategory, 1);
        end;
        DisplayProductCount(aDic);
      finally
        aDic.Free;
      end;
    end;

    DisplayProductCount接受型態為TDictionary<string, Integer>的參數,然後也是藉由TDictionary<T, T>的Enumerator來一一取出其中的元素來處理,不過TDictionary<T, T>提供了三種Enumerator,分別是TPairEnumerator,TKeyEnumerator和TValueEnumerator。從這三個Enumerator的名稱我們便可以知道,TPairEnumerator可以取出<T,T>這對<鍵值, 數值>的元素,而TKeyEnumerator則可取出<鍵值>元素,最後的TValueEnumerator則是取出<數值>元素。
    由於TPairEnumerator,TKeyEnumerator和TValueEnumerator都是宣告在TDictionary<T, T>之中的內嵌類別,因此在宣告這三個Enumerator類別的變數時需要在之前加上TDictionary<T, T>。例如下面的程式碼中aEnum是宣告為TpairEnumerator,但是在這之前我們需要加入TDictionary<T,T>的宣告並且正確的帶入資料型態,因此aEnum的正確型態宣告是TDictionary<string, Integer>.TPairEnumerator。
    TPairEnumerator的使用方法和TEnumerator是一樣的,但是TPairEnumerator的Current特性值的型態是:
    TPair<TKey,TValue>
    而TPair只是鍵值和數值成對的記錄型態而已

      TPair<TKey,TValue> = record
        Key: TKey;
        Value: TValue;
      end;


    TPair的目的是方便讓開發人員一次可以藉由Current取出對應的鍵值和數值。
    procedure TForm17.DisplayProductCount(aDic: TDictionary<string, Integer>);
    var
      aEnum : TDictionary<string, Integer>.TPairEnumerator;
    begin
      Self.lb產品資料.Items.Clear;
      aEnum := aDic.GetEnumerator;
      while (aEnum.MoveNext) do
      begin
        Self.lb產品資料.Items.Add(aEnum.Current.Key + ' : ' + IntToStr(aEnum.Current.Value));
      end;
    end;

    http://blufiles.storage.live.com/y1pIWmLiW7ATJpZlzUH56N41TM0E5CWGCPogYk7wOdwjpg595VMMqML_4i9k-gXGHKr
    TCollectionNotifyEvent<T>

    elphi 2009的泛型容器類別也提供了通知事件,允許開發人員連結此事件以便撰寫程式碼來處理元素在容器類別中異動的情形。在Generics.Collections程式單元中定義了如下的通知事件:
      TCollectionNotification = (cnAdded, cnRemoved, cnExtracted);
      TCollectionNotifyEvent<T> = procedure(Sender: TObject; const Item: T;
        Action: TCollectionNotification) of object;

    開發人員可以藉由實作型態為TCollectionNotifyEvent<T>的函式,再連結到容器類別物件即可在元素被加入,移除或是取出容器類別被容
    器類別物件呼叫知會。
    例如我們可以使用下面的程式碼連結glProduct容器類別物件:

    procedure TForm17.btn連結通知事件Click(Sender: TObject);
    begin
      glProduct.OnNotify := ProductNotifier;
    end;
    而ProductNotifier就是實作TCollectionNotifyEvent<T>原型的方法,在這裡ProductNotifier會在容器類別物件中的元素異動時顯示簡單的訊息:
    procedure TForm17.ProductNotifier(Sender: TObject; const Item: TProduct;
      Action: TCollectionNotification);
    begin
      case Action of
        cnAdded:
          Self.lb通知事件.Items.Add('加入 : ' + Item.GetName);
        cnRemoved:
          Self.lb通知事件.Items.Add('移除 : ' + Item.GetName);
        cnExtracted:
          Self.lb通知事件.Items.Add('取出 : ' + Item.GetName);
      end;
    end;
    http://blufiles.storage.live.com/y1pS2z8SEXGjkQLwsz9gLqxKlU3AcJECGzDX37ZL7vxz0yPPqKxGPEIO7O3cCz8vSYw
    深入討論匿名方法
    前面討論了匿名方法,匿名方法除了可以和泛型一起使用之外,也可以使用在非泛型的應用程式之中。由於匿名方法是在程式碼中定義函式本身,因此匿名方法也需要一個定義的範圍和呼叫模式,否則匿名方法本身要儲存在那裡呢?因此現在讓我們稍微深入討論一下匿名方法的實作機制,讓各位對於匿名方法有更基礎的瞭解。
    Delphi 2009是使用介面的方式來實作匿名方法,這樣做有許多的好處,主要的原因是使用介面可以進行型態檢查以及以及在Win32下缺少資源回收機制的環境中可以比較方便進行資源管理,此外Delphi(Object Pascal)又是強型程式語言,因此在編譯時期提供較為嚴格的檢查也和Delphi程式語言相當的契合。
    讓我們看一個範例也許會讓讀者更為瞭解匿名方法的實作方式。假設我們有下面的程式碼:
    type
      TFuncOfInt = reference to function(x: Integer): Integer;

    function MakeAdder(addendum: Integer): TFuncOfInt;
    begin
      Result := function(x: Integer)
      begin
        Result := addendum + x;
      end;
    end;

    procedure Use;
    var
      f: TFuncOfInt;
    begin
      f := MakeAdder(20);
      Writeln(f(22)); // prints '42'
    end;

    begin
      Use;
    end.
    在MakeAdder中定義了一個匿名方法,而且又把這個匿名方法當成MakeAdder的回傳參數,而這個匿名方法的原型則是由TFuncOfInt定義的。OK,那麼Delphi 2009是如何實作上面使用匿名方法的程式碼呢? 首先Delphi 2009的編譯器會把TfuncOfInt重新定義為如介面,並且在其中定義一個Invoke方法,類似如下:
    type
      TFuncOfInt = interface
        function Invoke(x: Integer): Integer;
      end;
    接著在MakeAdder中實際定義了匿名方法,因此在MakeAdder方法中必須定義此匿名方法的程式區塊,因為匿名方法可以擁有它自己的參數,變數,堆疊資源等。因此下面是MakeAdder可能實作的程式碼:
    function MakeAdder(addendum: Integer): TFuncOfInt;
    type
      IClosure1 = interface
        function Invoke(x: Integer): Integer;
      end;
      TMakeAdderFrame = class(TInterfacedObject, IClosure1)
        addendum: Integer;
        function IClosure1.Invoke = Closure1;
        function Closure1(x: Integer): Integer;
      end;
     
      function TMakeAdderFrame.Closure1(x: Integer);
      begin
        Result := Self.addendum + x;
      end;
    首先MakeAdder會宣告一個IClosure介面,其中定義Invoke方法,接著MakeAdder會宣告一個內嵌類別TMakeAdderFrame,這個類別將實作Closure介面並且把匿名方法拉出去成為內嵌類別的方法,接著把匿名方法的父方法的參數宣告為物件變數,如此一來就可以解決巢狀參數/變數的問題。
    最後在MakeAdder方法的實作程式碼中,就建立內嵌類別物件,最後MakeAdder方法要回傳匿名方法時,這裡是稍微複雜的地方。
    var
      frameInstance: TMakeAdderFrame;
    begin
      frameInstance := TMakeAdderFrame.Create;
      frameInstance.addendum := addendum;
      Result := reinterpret_cast<TFuncOfInt>(interface_cast<IClosure1>(frameInstance));
    end;
    首先MakeAdder必須把TMakeAdderFrame物件轉變型態為IClosure介面,這可以使用interface_cast來調整vTable的內容指標,接著使用reinterpret_cast強迫轉變型態為TFuncOfInt,也就是MakeAdder回傳的型態,如此一來就大功告成了。
    procedure Use;
    var
      f: TFuncOfInt;
    begin
      f := MakeAdder(20);
      Writeln(f.Invoke(22));
    end;

    begin
      Use;
    end.

    OK,我這篇淺談如何使用Delphi 2009泛型容器類別的文章也就到此結束了,希望對於想使用Delphi 2009泛型實體類別的讀者有一些基本的幫助。

    Have Fun!





    September 23

    淺談如何使用Delphi 2009的泛型容器類別

    Delphi 2009在Delphi程式語言方面加入了兩個主要的功能,一個是泛型程式設計(Generics Programming),另外一個就是匿名方法(Anonymous Method)。Delphi 2009在Win32加入了泛型程式設計之後,Delphi程式語言便可以同時在Win32,.NET平台下使用泛型程式設計。由於Delphi 2009在Delphi程式語言本身加入了泛型程式設計,因此在Delphi RTL中也加入了一些新的泛型容器類別(Generic Container Class)以方便開發人員使用於日常的開發程式碼中。本文將為讀者簡單的介紹如何使用使用這些新的泛型容器類別,希望能夠幫助讀者快速學習上手。

    簡單的說,泛型程式設計允許開發人員撰寫可泛用的運算法則,這些運算法則能夠使用於各種不同的資料型態,因此一旦開發人員開發完成這些泛用的運算法則,其他的開發人員就可以根據需要的處理資料型態帶入泛用的運算法則來完成運算的工作。例如Delphi最早提供的TList容器類別在實作時由於寫死是使用TObject的資料型態(類別型態),因此開發人員只能在TList中使用TObject樣例。但是TList容器提供的運算法則其實是能夠應用於任何型態的,而且開發人員在許多情形中也希望能夠使用TList來暫時處理一些程式碼中的物件,而不只是TObject樣例。

    因此所謂泛型的TList就是讓TList提供的運算法則能夠適用於各種不同的資料型態(類別型態),例如讓TList可以處理字串,整數,浮點數或是任何開發人員定義的類別型態。在一般的程式語言中泛型是使用<T>符號來代表的,其中的代表Type或是Template的意思,因此泛型的TList就是:


    TList + <T> = TList<T>


    因此要讓TList處理字串,就使用TList<String>,
    因此要讓TList處理TComponent,就使用TList<TComponent>,
    以此類推,所以使用泛型程式設計並不困難,把<T>代換成你要使用的資料型態(類別型態)即可。
    當然,在要使用泛型容器類別之前也是需要建立它們才能使用,在建立泛型容器類別時,記得也要代入你建立泛型容器類別之後要在其中使用的資料型態(類別型態),例如前面的例子中,要讓TList處理字串就必須如下的建立TList泛型容器類別物件:
    var
      ltstring : TList<String>;
    begin
      …
      tlstring := TList<String>.create;

    讓TList處理TComponent必須如下的建立TList泛型容器類別物件:
    var
      tlcomponent: TList<TComponent>;
    begin
      …
      tlcomponent := TList<Tcomponent>.create;

    以此類推。
    下面列出了Delphi 2009中提供的泛型容器類別:

    •     TList<T>,TObjectList<T: Class>
    •     TQueue<T>,TObjectQueue<T: Class>
    •     TStack<T>,TObjectStack<T: Class>
    •     TDictionary<T>,TObjectDictionary<T: Class>

    上面的泛型容器類別使用方法和前面介紹的TList差不多,只是不同的泛型容器類別提供了對於其中包含的元素不同的運算法則。
    其中需要稍為解釋的是為什麼每一個泛型容器類別都有一個對應的Object泛型容器類別?例如TList<T>既然已經能夠處理任何的型態,包含了類別型態,那麼為什麼還需要TObjectList<T>呢? 沒錯TList<T>是能夠放入任何的資料型態/類別型態,而TList<T>和TObjectList<T>的差別是TObjectList<T>能夠自動管理在TObjectList<T>中包含的物件的生命週期,簡單的說TObjectList<T>能夠在本身的樣例釋放時自動釋放它管理的所有物件樣例。
    例如TObjectList<T>有如下的建構函式原型:
        constructor Create(AOwnsObjects: Boolean = True); overload;
    我們可以看到它的建構函式接受一個AOwnsObjects的參數,它的內定值為True,這代表一旦開發人員建立了TObjectList<T>物件之後,如果在其中放入物件樣例,那麼TObjectList<T>物件在被釋放時也會釋放它包含的所有物件樣例。例如假設現在我們有一個TProduct類別,如果我們使用TObjectList<T>來管理TProduct類別,那麼可以使用如下的程式碼:
    var
      aProduct: TProduct;
      ptList : TObjectList<TProduct>;
    begin
      …
      ptList := TObjectList<TProduct>.create;
      try
        ptList.Add(TProduct.create(‘Delphi’));
        ptList.Add(TProduct.create(‘BCB’));
        ptList.Add(TProduct.create(‘JBuilder’));
        …
      finally
        ptList.Free;
      end;
      ..
    那麼當上面的程式碼執行到ptList.Free時,ptList中包含的3個TProduct物件也會自動被釋放。當然,如果您不希望TObjectList<T>自動刪除其中管理的物件,那麼在建立時傳遞False給它的建構函式做為參數,或是在建立之後設定它的OwnsObjects特性值為False也可以。
    有了這些基本的知識之後,讓我們使用一個簡單的範例來說明如何使用TObjectList<T>和TEnumerator<T>,TDictionary<T,T>以及TComparer<T>等泛型容器類別,在讀者瞭解了如何使用這四個類別之後對於使用其他的泛型容器類別應該就非常簡單了。

    使用TObjectList和TEnumerator

    為了說明起見,讓我們定義一個TProduct類別如下:
      TProduct = class
      private
        FName : string;
        FCode : String;
        FCategory : string;
        FVersion : double;
      public
        constructor Create(sName, sCode : string; dVersion : double; sCategory : string = 'Delphi');
        destructor Destroy; override;
        function GetName : string;
        function GetCode : string;
        function GetCategory : string;
        function GetVersion : double;
      end;

    接著我們建立一個TObjectList<TProduct>:
    procedure TForm17.FormCreate(Sender: TObject);
    begin
      glProduct := TObjectList<TProduct>.Create(True);
    end;
    Delphi 2009的泛型容器類別是定義在一個新的程式單元Generics.Collections之中,因此當然要記得先在uses句子中加入使用Generics.Collections。
    接著其中建立一些範例TProduct物件:
    procedure TForm17.CreateTempProducts;
    begin
      glProduct.Add(TProduct.Create('Delphi 2009', 'Tiburon', 12.0));
      glProduct.Add(TProduct.Create('Delphi 2007', 'Highlander', 11.5));
      glProduct.Add(TProduct.Create('Delphi 2006', 'Dexter', 11.0));

      glProduct.Add(TProduct.Create('C++Builder 2009', 'Tiburon', 12.0, 'BCB'));
      glProduct.Add(TProduct.Create('C++Builder 6', 'Riptide', 6.0, 'BCB'));
      glProduct.Add(TProduct.Create('C++Builder 5', 'Rampage', 5.0, 'BCB'));
    end;

    在一般使用泛型容器類別時,最常應用的情形是需要從泛型容器類別中一一取出它包含的元素來處理。在這種應用中大都是使用TEnumerator物件來幫助開發人員存取其中的元素,一般來說泛型容器類別都會提供GetEnumerator方法讓開發人員取得泛型容器類別對應的TEnumerator物件,再藉由TEnumerator物件來一一存取其中的元素。
    在Delphi 2009中Tenumerator<T>有如下的宣告:
      TEnumerator<T> = class abstract
      protected
        function DoGetCurrent: T; virtual; abstract;
        function DoMoveNext: Boolean; virtual; abstract;
      public
        property Current: T read DoGetCurrent;
        function MoveNext: Boolean;
      end;

    其中開發人員藉由呼叫MoveNext方法來判斷是否還有未存取的元素,而Current特性可以讓開發人員取得目前的元素。
    在TList<T>類別中也定義了GetEnumerator方法可以取得它相關的TEnumerator物件:
        function GetEnumerator: TEnumerator; reintroduce;

    例如假設現在我們希望把剛才加入的所有TProduct物件顯示在TListBox中,那麼就可以使用如下的程式碼:
    procedure TForm17.btn使用EnumeratorClick(Sender: TObject);
    var
      aEnum : TEnumerator<TProduct>;
    begin
      aEnum := glProduct.GetEnumerator;
      DisplayProducts(aEnum);
    end;

    首先我們呼叫TObjectList的GetEnumerator取得TEnumerator物件,由於這個TEnumerator物件是存取TProduct物件,因此它的型態定義是TEnumerator<TProduct>,也就是把<T>代換成<TProduct>。
    接著DisplayProducts就藉由TEnumerator物件進入while迴圈,如果TEnumerator物件的MoveNext持續回傳True就代表尚有未存取的TProduct。在while迴圈中開發人員可以藉由TEnumerator物件的Current特性值取得目前要處理的TProduct物件再進行後續的處理工作。
    procedure TForm17.DisplayProducts(aEnum: TEnumerator<TProduct>);
    begin
      lb產品資料.Items.Clear;
      while (aEnum.MoveNext) do
      begin
        lb產品資料.Items.Add(aEnum.Current.GetName + ' : ' + aEnum.Current.GetCode);
      end;
    end;

    排序泛型容器類別

    另外一個經常使用的情形是需要排序泛型容器類別之中的元素,由於泛型容器類別可以包含任何型態的物件,因此如何排序其中的元素物件應該是根據不同的型態而異的,因此開發人員必須提供如何排序的程式碼給泛型容器類別來呼叫以決定元素之間的次序。
    TObjectList<T>提供了兩個排序方法:
        procedure Sort; overload;
        procedure Sort(const AComparer: IComparer<T>); overload;

    其中第一個Sort是使用內定的排序方式,開發人員如果需要定義自己的排序法則就需要使用第二個Sort方法。第二個Sort方法接受一個IComparer<T>介面的參數,IComparer<T>定義如下:
      IComparer<T> = interface
        function Compare(const Left, Right: T): Integer;
      end;

    IComparer<T>介面定義了Compare方法,它回傳整數,-1代表小於,0代表等於而1代表大於。
    因此開發人員需要實作一個實作IComparer<T>介面的類別,再把這個類別的物件傳遞給上面的第二個Sort方法來實際的排序。
    實作IComparer<T>最簡單的方法是定義一個從TComparer<T>類別繼承下來的子類別,因為Delphi2 2009已經定義了如下的TComparer<T>類別:
      TComparer<T> = class(TInterfacedObject, IComparer<T>)
      public
        class function Default: IComparer<T>;
        class function Construct(const Comparison: TComparison<T>): IComparer<T>;
        function Compare(const Left, Right: T): Integer; virtual; abstract;
      end;

    因此我們要排序glProduct之中的TProduct物件,讓我們定義TproductLineComparer類別,它從TComparer<T>類別繼承下,我們只需要複載實作Compare來撰寫如何排序TProduct物件。在下面的程式碼中我們先以TProduct的Category特性值來排序,如果Category特性值相同,就以TProduct的Version特性值來做子排序條件:
    type
      TProductLineComparer = class(TComparer<TProduct>)
      public
        function Compare(const Left, Right: TProduct): Integer; override;
      end;

    implementation

    { TProductLineComparer<T> }

    function TProductLineComparer.Compare(const Left, Right: TProduct): Integer;
    begin
      Result := 0;
      if (Left.GetCategory < Right.GetCategory) then
        Result := -1
      else
        if (Left.GetCategory > Right.GetCategory) then
          Result := 1
        else
        begin
          if (Left.GetVersion < Right.GetVersion) then
            Result := -1
          else
            if (Left.GetVersion > Right.GetVersion) then
              Result := 1;
        end;
    end;

    有了TProductLineComparer之後就可以使用下面的程式碼來排序glProducts之中所有TProduct物件的次序了:
    procedure TForm17.btn依產品線排序Click(Sender: TObject);
    var
      aPLC : TProductLineComparer;
    begin
      aPLC := TProductLineComparer.Create;
      try
        glProduct.Sort(aPLC);
        DisplayProducts(glProduct.GetEnumerator);
      finally
        aPLC.Free;
      end;
    end;

    上面的程式碼先建立TProductLineComparer物件,傳遞給Sort做為參數即可。
    下圖是先呼叫TEnumerator顯示glProducts中所有的TProduct物件,讀者可以看到物件是以我們加入glProducts之中的次序顯示,而呼叫依產品線排序之後TProduct物件就以正確的產品種類+產品版本的次序來顯示。
    http://blufiles.storage.live.com/y1p-ZwKhSNvOBLnXuhKIgS3u_2MpaCZk3qj7jgmV9Cbdvgy8ffF_56-OVrdwsO660MN
     
    結合匿名方法

    現在讓我們稍為展示如何結合泛型和匿名方法。
    Delphi 2009新的程式語言功能之一就是匿名方法,匿名方法主要的功能是允許開發人員在程式碼中定義一些小而簡單的程式碼來使用,而這些小而簡單的程式碼不需要正式定義成方法,而是使用完之後就不再需要的情形之中。此外匿名方法可以和泛型程式設計一起使用。現在讓我們再對glProducts中的TProduct進行排序,但是我們只需要對Delphi的產品排序。
    因此我們需要先過濾出Delphi的產品物件,再根據過濾出Delphi的產品物件進行排序,下面的程式碼就可以完成這個工作:
    001    procedure TForm17.btn依Delphi版本排序Click(Sender: TObject);
    002    var
    003      aDelphiFilter : TFunc<TProduct, Boolean>;
    004      ltDelphi: TList<TProduct>;
    005      aEnum : TEnumerator<TProduct>;
    006      aPLC: TProductLineComparer;
    007    begin
    008      ltDelphi := TList<TProduct>.Create;
    009      try
    010        aEnum := glProduct.GetEnumerator;
    011        aDelphiFilter := function (aProduct : TProduct) : Boolean
    012        begin
    013          Result := False;
    014          if (aProduct.GetCategory = 'Delphi') then
    015            Result := True;
    016        end;
    017   
    018        while aEnum.MoveNext do
    019        begin
    020          if (aDelphiFilter(aEnum.Current)) then
    021            ltDelphi.Add(aEnum.Current);
    022        end;
    023   
    024        aPLC := TProductLineComparer.Create;
    025        try
    026          ltDelphi.Sort(aPLC);
    027          Self.lb產品資料.items.Add('========依Delphi產品線排序==========');
    028          DisplayProducts(ltDelphi.GetEnumerator);
    029        finally
    030          aPLC.Free;
    031        end;
    032      finally
    033        ltDelphi.Free;
    034      end;
    035 end;
    上面程式碼的邏輯很簡單,我們使用一個匿名方法從glProduct中過濾出Delphi的產品,放入另外一個TList<T>泛型容器類別之中,最後再使用前面介紹的TProductLineComparer來排序。
    在上面的程式碼中,003行的aDelphiFilter宣告的型態是TFunc<TProduct, Boolean>,那麼是TFunc<TProduct, Boolean>呢? TFunc就是一個接受泛型的匿名方法,它定義在SysUtils程式單元中,它的原型如下:
      TFunc<T,TResult> = reference to function (Arg1: T): TResult;

    事實上在Delphi 2009的SysUtils程式單元中已經定義了許多通用的泛型匿名方法可以讓開發人員使用,它們的定義如下:
    // Generic Anonymous method declarations
    type
      TProc = reference to procedure;
      TProc<T> = reference to procedure (Arg1: T);
      TProc<T1,T2> = reference to procedure (Arg1: T1; Arg2: T2);
      TProc<T1,T2,T3> = reference to procedure (Arg1: T1; Arg2: T2; Arg3: T3);
      TProc<T1,T2,T3,T4> = reference to procedure (Arg1: T1; Arg2: T2; Arg3: T3; Arg4: T4);

      TFunc<TResult> = reference to function: TResult;
      TFunc<T,TResult> = reference to function (Arg1: T): TResult;
      TFunc<T1,T2,TResult> = reference to function (Arg1: T1; Arg2: T2): TResult;
      TFunc<T1,T2,T3,TResult> = reference to function (Arg1: T1; Arg2: T2; Arg3: T3): TResult;
      TFunc<T1,T2,T3,T4,TResult> = reference to function (Arg1: T1; Arg2: T2; Arg3: T3; Arg4: T4): TResult;

      TPredicate<T> = reference to function (Arg1: T): Boolean;

    回到前面的程式碼中,在011到016定義了匿名方法並且指定給匿名方法變數aDelphiFilter,接著藉由TEnumerator物件一一的存取glProduct中的元素,並且使用aDelphiFilter來過濾出Delphi的產品,最後在026行呼叫ltDelphi的Sort方法並且傳入TProductLineComparer做為參數。
    下面是執行這個方法的結果畫面:
    http://blufiles.storage.live.com/y1pong1Lyqt3E-J3E1vi1ChBcmdP29ZhFH8qr1p84xvpynCIj7OwnFys8IFl1hMljcs
     
    OK,下次讓我解釋如何結合使用TDictionary<T>以及匿名方法更為技術面的內容,Have Fun!。



    September 20

    回覆RURU有關ODBC, OLEDB, ADO和dbExpress的問題

    我看了你說的那篇文章以及那篇文章指到的另外一篇文章,老實說我在一看到那2篇文章的開始就和我的認知有差別,在我的認知中OLEDB應該不可能比ODBC快,為什麼?這是一個故事,也是ODBC,OLEDB,ADO和dbExpress各自發展的原因,因此我們需要對於這些資料存取技術的背景有一個簡單的瞭解。

    ODBC當初是MS為了在Window下提供一個共通的資料存取技術而發展出來的,目的是突破當時三大資料庫廠商Oracle,Sybase和Informix對於MS的SQL Server的圍堵,此外MS也想藉由這個共通的資料存取技術幫助MS Access擊倒Lotus和Approach,因此最早的ODBC的策略是提供一個最小公約數API,讓一個標準可以存取所有的資料庫,因此在ODBC的初期ODBC的效率很緩慢,因為除了實作技術不成熟之外,當時MS SQL Server的功能也不好,因此以SQL Server為中心思想發展出來的最小公約數ODBC在存取其他資料庫時簡直慢的不想話,這也是為什麼後來Borland發展出了BDE/IDAPI之後在功能和效率方面都比ODBC好上一大截的原因。但是當MS Server SQL逐漸成熟,ODBC的實作技術也慢慢成熟之後,以最小公約數為發展中心的ODBC因為功能最簡單,因此負荷最小,到了最後ODBC的效率是最好的資料存取之一。

    OLEDB是當時MS想把COM/DCOM/COM+技術推為Windows平台的唯一技術時發展出來的,如此一來在Window平台所有的通訊,資料存取和元件都以COM/DCOM/COM+為核心,因此一度Borland也準備以COM/DCOM/COM+發展IDE,資料存取等。MS當時為了這個原因,因此有了OLEDB,但是舊的ODBC怎麼辦? 因此又發展出了ODBC和OLEDB Adapter的技術,讓ODBC也可以執行在COM/DCOM/COM+的世界,讓資料存取端認為ODBC也是OLEDB。OLEDB為什麼不會比ODBC快? 因為OLEDB功能比較多,而且OLEDB是以Ole Automation封裝傳遞的資料,負荷比ODBC大,因此ODBC應該會比較快。

    至於ADO則是後來MS為MS Access,MS SQL Server特別在Windows平台發展出來的存取技術,因為當時由於Internet/Intranet的沖擊,MS的COM/DCOM/COM+不適合使用在Internet/Intranet上,因此MS逐漸準備放棄COM/DCOM/COM+,這也是OLEDB的長日將盡之日,ADO接手演出之時。ADO和ODBC不同的地方是,ADO再也不是使用最小公約數為中心,而是完全以MS的資料庫為核心,這也是為什麼當ADO推出後其他廠商並不熱衷支援ADO,我記得當時Oracle拒絕推出ADO For Oracle,MS迫不得已自己為Oracle寫了ADO驅動程式,但是又慢臭蟲又多,而Borland也決定繼續發展BDE/IDAPI,只是為ADO做簡單的封裝處理,但Delphi/BCB仍然以BDE/IDAPI為核心資料存取技術。

    由於ADO不是使用最小公約數為中心,因此功能比ODBC多太多了,例如資料連結池,執行緒池,可分段存取資料,資料存取到用戶端時不是把所有記錄中的資料存取到用戶端,而是只把Key存取到用戶端,一旦當用戶端實際需要整筆資料時才藉由Key把資料存取到用戶端。因此如果只是簡單的單一用戶端存取資料測試,ADO不一定會比ODBC快,但是應該差不多,不過在實際的應用中,ADO應該是比ODBC整體效率快,但是記得ADO是為了MS的資料庫而生的,因此如果是使用ADO在其他廠商資料庫上則不一定。這是為什麼一些資料庫管理工具如果是管理多種不同的後端資料庫,那麼大多仍然是使用ODBC,為什麼? 因為簡單,因此穩定一點,所以快一點。例如Embarcadero的產品就是使用ODBC來管理後端多種資料庫。

    OK,討論到這裡你應該知道ODBC,OLEDB和ADO的使用時機了。

    現在回到dbExpress,dbExpress是為了跨平台發展出來的資料存取技術,不是為了特定的資料庫,也不是只為了Window 32平台。dbExpress可以執行在Win32,.NET,Win64,Linux,可以存取MS,Oracle等以及CodeGear自己的InterBase和BlackfishSQL。而dbExpress是結合跨平台和一些目前用戶端先進的資料存取概念為中心,例如dbExpress也提供連結池,執行緒池等。
    • OK,因此如果你使用dbExpress和ADO來比較存取MS的資料庫,那麼一定是ADO快一點,為什麼? 因為ADO是為了MS的資料庫而生。
    • 如果你使用dbExpress和ODBC進行單一簡單的資料存取比較,那麼ODBC快一點,因為ODBC功能少,負荷少。但是在比較多的用戶端進行比較複雜的資料處理時,dbExpress就比ODBC表現的好多了,這和ADO比ODBC是類似的。
    • 如果你想使用一種統一的資料存取技術,不管對於什麼平台,什麼資料庫都有良好的執行速度,因此當你改變資料庫時你的執行效率仍然很穩定,那麼dbExpress是王者。
    • ODBC,ADO和OLEDB已停止開發,因此如果你想使用一種仍然在開發之中而且能夠應用在未來Win64和多核心,多執行緒的世界,那麼dbExpress是比較好的解決方案。
    最後新一代的dbExpress.NET很快會出來(我猜應該是在Q4或是明年Q1),接著dbExpress Win64會出來,CodeGear已經在發展未來3年的dbExpress技術,因此CodeGear會不會放棄dbExpress呢?

    你是技術人員,由你來回答這個答案吧,Have Fun!

    September 15

    Delphi 2009產品技術發表會圓滿完成

    99日到912日一連4天的Delphi 2009產品技術發表會終於順利完成了,在這連續4天的活動中我看到了Delphi許久未有的盛況,因為台北,新竹,台中和高雄的參加人數都創下近幾年的新高,每一場活動都座無虛席,我也看到了許多已經很久未見的老Delphi客戶,看來Delphi 2009的確是受到許多Delphi客戶的重視,也希望Delphi在新東家的手中能夠有更好的發展。

    下面的URLDelphi 2009產品技術發表會使用的Slides和一些簡單的範例,有需要的朋友可以自行下載

    http://www.sinter.com.tw/borland/download/Delphi2009PL.zip

    最後我也要謝謝所有參加Delphi 2009產品技術發表會的朋友,Have Fun!

    August 27

    Embarcadero正式發佈Delphi 2009/C++Builder 2009

    Embarcadero在美國時間8/25日正式發表了開發代號為TiburonDelphi 2009/C++Builder 2009,其實對於參加過多次Borland/CodeGear beta產品測試的我來說早就在數天前就大約猜到了Tiburon將在8月底左右正式發表,為什麼呢?因為在進入820日左右Tiburonbeta 版本就開始愈來愈頻繁,從早期大約每個星期一個版本,到2,3天一個版本,再到8月中左右開始每天一個版本,到820日左右一天23個版本就可以推測Tiburon離正式發表不久了,因為最後頻繁的beta版本都應該都是在修正必須修改的臭蟲。

    有異於以往Delphi/BCB一前一後發表的模型,Tiburon這次同時公佈了Delphi 2009C++Builder 2009,這代表了從Tiburon開始DelphiC++Builder使用的核心RTL/VCL都已經同步化,在編譯器方面Delphi的編譯器和C++Builder的編譯器也進行了更緊密的結合和同步化,因此這也讓如何進行Delphi 2009C++Builder 2009產品技術發表會產生了令人頭痛的問題,由於場地和時間的限制,因此Embarcadero決定先在9月進行Delphi 2009的產品技術發表會:

    地點

    時間

    場地

    台北

    9/9 () 13:30 ~ 16:30

    台北市信義區松高路68

    (亞太會館B1大觀宴會廳A)

    新竹

    9/10 () 13:30 ~ 16:30

    新竹科學工業園區工業東二路1205

    (科技生活館)

    台中

    9/11 () 13:30 ~ 16:30

    台中市公益路二段5110B1

    (國泰公益大樓)

    高雄

    9/12 () 13:30 ~ 16:30

    高雄市中正二路17517樓之3

    (維士比大樓)

    10月初再進行C++Builder 2009產品技術發表會,如此一來可以讓不同的使用者參加最適合的產品技術發表會。有人可能會說為什麼不同時進行Delphi 2009C++Builder 2009產品技術發表會呢? 這是因為這次雖然Tiburon同時發佈了Delphi 2009C++Builder 2009,但是這次的Delphi 2009C++Builder 2009雖然擁有許多相同的新功能,但是C++Builder 2009擁有更多獨特的功能,例如Together For C++Cpp0x,支援ACE/Boost等,因此在發表會的內容和範例上都會和Delphi 2009有著許多的不同,因此Embarcadero才決定讓DelphiC++Builder的使用者參加各自專屬的產品技術發表會,這樣會讓不同的工具使用者盡興而歸。

    如果您想參加Delphi 2009的產品技術發表會,您可以藉由下面的URL報名參加:

    http://w3.sinter.com.tw/eSinterWeb/EnrollActivityMain?ACTIVITY_OID=appsvr120080818085541148&PAGE_COMMAND=ENROLL_ACTIVITY

    如果您想參加C++Builder 2009的產品技術發表會,請您稍待一會,一有確定的訊息我就會公告出來。 Have Fun!