Profil de 維IT : 是工作還是嗜好?PhotosBlogListesPlus Outils Aide

維 李

Cet espace perso ne contient aucune liste de musique.
謝謝您的瀏覽!
Veuillez patienter...
Le commentaire entré est trop long. Raccourcissez-le.
Vous n'avez rien entré. Réessayez.
Il est actuellement impossible d'ajouter votre commentaire. Réessayez plus tard.
Pour ajouter un commentaire, tu dois avoir l'autorisation de tes parents. Demander l'autorisation
Tes parents ont désactivé les commentaires.
Il est actuellement impossible de supprimer votre commentaire. Réessayez plus tard.
Vous avez dépassé le nombre maximal de commentaires qu'il est possible d'envoyer le même jour. Réessayez dans 24 heures.
Votre compte a pu laisser les commentaires désactivés parce que nos systèmes indiquent que vous risquez d'arroser d'autres utilisateurs de messages. Si vous pensez que votre compte a été désactivé par erreur, contactez l'assistance en ligne de Windows Live.
Effectuez la vérification de sécurité ci-dessous pour finaliser l'envoi de votre commentaire.
Les caractères entrés pour la vérification de sécurité doivent correspondre à ceux de l'image ou du fichier audio.
維 李a écrit :
>Delphi 的优化能力 在 Native Win32 上可说是“最差的”,工作中对执行效率要求很高的场合就较麻烦了...

你是指和VC++比吧, Delphi是一個應用程式開發工具, 和VC++是系統開發工具定位不同.

"对执行效率要求很高的场合" 是指什麼? 你需要明確一點.

我只知道目前CG正在做新一代的Delphi/C/C++的最佳化編譯器, 不過什麼時候出來我也不知道.

Il y a 12 heures
志华a écrit :
李维老师,您好! 有关DBExpress的性能问题想请教一下。我分别用D7和D2007的TSQLQuery组件查询Oracle和SQLServer 100000条记录,然后Next每一条记录,统计花费的时间,结果如下:D7 + MSSQL 用了563ms,D2007+MSSQL用了469ms,D2007稍快些;D7+Oracle 用了797ms,D2007+Oracle 用了4297msD2007+Oracle比D7慢了将近6倍,我测试使用的是XP下的MSSQL2005和Oracle9.2,偿试Oracle其它版本发现结果一样。请问这是不是DBX4的问题?应该如何解决?难道要放弃使用D2007回到原来的D7?  这个问题困扰很久了,麻烦李老师帮忙想想办法,谢谢!
以下是我测试使用的代码:

procedure TForm1.Button2Click(Sender: TObject);

var n: int64;

begin

  n := gettickcount;

  Self.SQLQuery2.SQL.Text := 'select * from b_emp where rownum <=100000';

  SQLQuery2.Close;

  SQLQuery2.Open;

  while not SQLQuery2.Eof do

    SQLQuery2.Next;

  showmessage(inttostr(gettickcount-n));

end;

Il y a 2 jours
胡光耀a écrit :
李先生您好,冒昧问您关于 Delphi 编译器优化方面的问题。
Delphi 的优化能力 在 Native Win32 上可说是“最差的”,但直至现在 CG 都没有改善这方面的意思,工作中对执行效率要求很高的场合就较麻烦了...
于此,想听听李先生的看法,及建议...
7 Déc.
維 李a écrit :
>我猜想是否是其它SDK中有同名函数。而编译器在编译时记录了可以内联展开的代码,而优先使用了内联展开的代码。

其實看一下編譯出來的assembly應該就很快可以知道為什麼了. 不過你說加了名字空间就沒問題, 那應該就和你的推論差不多了.
3 Déc.
颖卓 胡a écrit :
我已经查出来之前所说的编译器问题所在了。问题关键处在内联展开上。

我有一个函数叫“bool FileFind(const wchar_t *)”,但它并不是内联函数,也不是直接写在.h中的。
在C++Builder 2009中,如果内联展开,编译器就有一定可能性产生错误的代码,导致运行出错。加上名字空间前缀后,就正常了。我猜想是否是其它SDK中有同名函数。而编译器在编译时记录了可以内联展开的代码,而优先使用了内联展开的代码。
1 Déc.

IT : 是工作還是嗜好?

12 décembre

從BDE到DBX再結合DB Optimizer範例開始提供下載

忙了一天終於搞定了可下載的地方, 這麼晚才弄好實在不好意思, 也在這裡向星期5就來找下載的朋友說聲抱歉.



27 novembre

12月份的研討會 : 從BDE到dbExpress,就是要開發高效資料庫應用系統

大約在10月底之際,EMBT便請我在12月設計一場技術研討會,並且請我思考要做什麼主題時,我幾乎沒有花什麼時間考慮就立刻說我想做一場如何開發高效資料庫的技術研討會,為什麼? 因為在10月中旬左右我看到了EMBT推出了DB Optimizer 2.0,在看完DB Optimizer產品經理展示了2.0的功能之後,我立刻在心中驚呼其中的一些功能不正是我在數年前使用Delphi開發資料庫應用程式時最想擁有,但卻找不到解決方案的東西嗎?

雖然當我觀看DB Optimizer產品經理展示時,他都是以DBA或是資料庫開發人員的角度來說明,但對於像我這種同時擁有開發工具,中介軟體和資料庫開發經驗的人來說,卻能夠同時以用戶端,中介和後端開發的角度來體驗DB Optimizer,因此在我觀看的同時腦海中不斷的出現從前開發的歷史畫面並且不斷的對我自己詢問『啊,這個功能如果從Delphi/BCB或是Java/C#的開發人員來看的話會如何?』等問題。那麼為什麼當我在觀看DB Optimizer 2.0時會有這種反應呢? 這要從我還是蔡鳥程式師時說起。

記得在1992年我從喬治亞理工學院回台之後,第一份工作就在一家大型MRP/ERP公司上班,當時我是使用C/C++語言的系統工程師,那時我對資料庫並不熟悉,因此對於當時另外一個使用Informix部門就很好奇,因為看著那些Informix的開發人員能夠寫出非常複雜的SQL程式就覺得非常的佩服,自當『見賢思齊』,好好的學習一番。記得有一天上班時Informix部門在一台電腦前擠滿了開發人員,我就好奇的往前湊熱鬧看看發生什麼了什麼事,原來是因為有一支Informix的程式執行的非常緩慢,因此Informix部門最強的顧問正在想辦法調整程式。後來那位顧問終於讓那支Informix程式執行得比原先快了好幾倍的速度,我後來也和他談了一下,問他做了什麼可以讓結果差這麼多。他告訴我他試著改寫了一些程式碼(後來我才知道應該是改寫了SQL敘述),經過幾次的嘗試之後速度就上來了,但為什麼會這樣,他說應該是改寫的程式比較好的使用了索引,他也嘗試在資料庫中增加了一些索引來試著幫助改善速度,至於最後到底是因為什麼原因他也無法確定。我現在再回想當時的情形,他應該是憑著經驗在調整SQL敘述,每次調整了之後就『Try And Error』看看速度有沒有上來,由於他擁有比較多的知識和經驗,因此可以朝正確的方向和比較快速的找到比較好的結果。但由於當時沒有工具和精確的方法,因此這樣做仍然有許多的盲點,例如:

1. 如何在一開始就縮小『Try And Error』的範圍?

2. 如何比較不同調整的SQL敘述的效率差異?

3. 每一個SQL敘述如何使用索引? 使用了那些索引? 那些索引反而會減緩SQL敘述的效率?

這些問題即使是到今天仍然不易回答,因此我們仍然只是依靠經驗豐富的DBA或是資料庫開發人員來調整SQL敘述,但是對於用戶端的Delphi/BCB/Java/C#或是撰寫中介軟體的開發人員來說,又如何能夠能夠寫出良好的SQL敘述?

另外一個場景是數年後我到了另一家使用Oracle的軟體公司,當時是Delphi 2的時代,也是主從架構正逐漸成為主流的時代。那家使用Oracle的軟體公司中也有許多的DBA和資料庫開發人員,他們習慣了使用PL/SQL來寫程式,因此我知道他們並不喜歡我們這些使用程式語言來開發資料庫應用系統的人,因此他們一開始堅持所有和資料庫處理有關的程式碼都要由他們使用PL/SQL和預儲程序/觸發器來撰寫,我們這些用戶端的開發人員只需要撰寫和UI有關的工作,如果需要資料時再呼叫他們寫好的PL/SQL和預儲程序即可。由於Delphi 2使用BDE存取Oracle,因此後來經常造成我們這些用戶端開發人員和後端PL/SQL開發人員的小磨擦,為什麼? 這是因為在用戶端開發人員當然是使用BDE來存取資料,但當開發人員使用BDE存取資料時,在許多的情形下是由BDE或是它的SQL Links驅動程式來產生SQL敘述,開發人員如果沒有深入的知識是無法瞭解或是掌握BDE/SQL Links會產生什麼樣的SQL敘述,雖然在大多數的應用中BDE/SQL Links可以產生不錯的SQL敘述,但有時候就不會那麼幸運了,當這種情形發生而造成資料庫效率下降時那些DBA和資料庫開發人員就不高興了。

不管是BDE/SQL Links,dbExpress,ADO,ADO.NET,JDBC或是未來資料存取技術逐漸走向OR Mapping的發展,這些資料存取技術如何產生SQL? 又如何和後端的資料來源互動? 這對於開發人員是非常重要的,因為它關係了應用系統的執行效率。如果您熟悉BDE或是dbExpress,您會說Delphi/BCB不是有SQL Monitor可以幫助我們觀察用戶端應用程式和資料來源之間的執行情形嗎? 是的,但SQL Monitor只能讓您看到用戶端和資料庫之間的互動,由BDE/SQL Link或是dbExpress產生的SQL敘述到達了資料庫之中之後如何使用資料庫的資源卻是無法看到的。到了資料庫之中DBA和資料庫開發人員或許可以看到資料庫如何執行這些SQL敘述,但DBA和資料庫開發人員卻又不知道這些SQL敘述是如何產生的,嗯,這聽起來像是陷入了兩難的境界了嗎?

這就是為什麼當我看到DB Optimizer 2.0時給我的震撼了,因為它可以使用來解決上面提到的困境,更棒的是藉由它的調校功能我們可以再把它拿到用戶端的Delphi/BCB/C#/Java來使用,可以立刻讓用戶端的資料庫應用程式使用最高效率的SQL敘述。

如果您看完了本篇簡短的文章之後也感同身受,也想瞭解如何結合Delphi/BCB和DB Optimizer 2.0來開發高效的資料庫應用系統,不管它是桌面型,主從架構型,Web應用型或是多層架構,都歡迎您來參加,讓我們一起度過一個深度,愉悅的技術之旅。

11 novembre

RAD Studio 2010, Delphi 2010, C++Builder 2010 Update 2已可下載

CodeGear已釋出 RAD Studio 2010, Delphi 2010和C++Builder 2010的Update 2, 我已經下載安裝完畢, 整個過程非常的順利.

如果您是合法的使用者的話, 那麼您可以在下面的URL下載

23 octobre

JSON 程式設計 – DataSnap的回叫機制

DataSnap 2010加入了回叫機制,當伺服端方法在執行的過程中可以回叫用戶端提供的方法以通知用戶端有關伺服端方法執行的狀態。

DataSnap的回叫機制非常適合使用在需要較長時間執行的伺服端方法,例如如果伺服端方法需要執行長時間的查詢時就很適合使用,或是當程式啟動或是執行時需要進行許多查詢的工作,那麼也都可以使用回叫機制。

要使用DataSnap 2010的回叫機制非常的簡單,開發人員只需要實作一個從TDBXCallback繼承下來的實體類別,並且在呼叫伺服端方法時把此實體類別的樣例當做參數傳遞給伺服端方法即可。

由於使用DataSnap 2010的回叫機制並不困難,因此讓我們使用一個範例來說明讀者就可以很快的瞭解。

在下列的範例中本文將使用Delphi 2010中新的IOUtils程式單元中的類別進行檔案搜尋和計數的工作,由於這將花上一些時間,因此我們正好使用它來展示使用同步和回叫機制的差異。

範例DataSnap伺服器

首先讓我們建立一個DataSnap伺服端,下面是這個伺服器輸出的伺服端方法,請注意的是,TServerMethods1輸出了兩個方法GetServerDirectoryInfo和GetServerDirectoryInfoAsync。這兩個方法都執行相同的工作,它們使用TDirectory類別搜尋和計數特定伺服端目錄下的檔案總數,它們的差異是GetServerDirectoryInfo使用同步的方式執行,因此當用戶端呼叫它時,用戶端會同時暫停反應直到GetServerDirectoryInfo執行完畢。

而GetServerDirectoryInfoAsync則是使用回叫機制的方式執行,因此當用戶端呼叫它之後,GetServerDirectoryInfoAsync在執行的過程中則可以藉由用戶端傳遞來的參數ACallback: TDBXCallback,來回叫回用戶端,告訴用戶端執行的狀態,用戶端因此也根據目前伺服端執行的情形來更新用戶端的資訊。

{$METHODINFO ON}

TServerMethods1 = class(TPersistent)

private

{ Private declarations }

fTotalFiles : Integer;

FResult : TJSONArray;

FCallback: TDBXCallback;

procedure ProcessPath(const sPath : string);

procedure ProcessPathAsync(const sPath : string);

procedure ShowMessage(sMessage : string);

procedure ProcessThisDirectory(const sPath : string);

public

{ Public declarations }

function EchoString(Value: string): string;

function GetServerDirectoryInfo(const sPath : string) : TJSONArray;

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

end;

{$METHODINFO OFF}

GetServerDirectoryInfoAsync是如何回叫回用戶端呢?其實非常的簡單,因為用戶端在呼叫它時已經把用戶端的回叫方法當成參數傳遞過來了,因此GetServerDirectoryInfoAsync只需要藉由這個參數即可回叫回用戶端。

因此我們可以從下面18行的程式碼看到,伺服端直接使用這個回叫參數呼叫用戶端,並且建立一個TJSONString型態的物件做為參數,在這個TJSONString物件中告訴了用戶端目前伺服端正在處理那一個目錄。

001 function TServerMethods1.GetServerDirectoryInfoAsync(ACallback: TDBXCallback;

002 const sPath: string): TJSONArray;

003 begin

004     FCallback := ACallBack;

005     FTotalFiles := 0;

006     FResult := TJSONArray.Create;  

007     ProcessPathAsync(sPath);

008     FResult.AddElement(TJSONString.Create('總檔案數' + ' : ' + IntToStr(FTotalFiles)));

009     Result := FResult;

010 end;

011

012 procedure TServerMethods1.ProcessPathAsync(const sPath: string);

013 var

014     rootDirectories : TStringDynArray;

015     i: Integer;

016 begin

017     ProcessThisDirectory(sPath);

018     FCallback.Execute(TJSONString.Create('處理目錄' + sPath + '中...'));

019     rootDirectories := TDirectory.GetDirectories(sPath);

020     for i := 0 to Length(rootDirectories) - 1 do

021     ProcessPathAsync(rootDirectories[i]);

022 end;

同步用戶端

範例的同步用戶端非常的簡單,它只是直接呼叫伺服端的GetServerDirectoryInfo方法。

procedure TForm3.btnGetServerInfoClick(Sender: TObject);

var

    aServer : TServerMethods1Client;

    ja : TJSONArray;

    jv : TJSONValue;

    I: Integer;

begin

    lStart := GetTickCount;

    aServer := TServerMethods1Client.Create(Self.SQLConnection1.DBXConnection);

    try

        ja := aServer.GetServerDirectoryInfo(Edit1.Text);

        lEnd := GetTickCount;

        for I := 0 to ja.Size - 1 do

        begin

            jv := ja.Get(I);

            lbResult.Items.Add(jv.ToString);

        end;

    finally

        aServer.Free;

        ShowRunTime(lEnd - lStart);

    end;

end;

在用戶端呼叫GetServerDirectoryInfo的過程中,用戶端暫停反應,使用者也無從瞭解伺服端執行的狀態。

回叫用戶端

再看看回叫用戶端,這個用戶端的關鍵從下面的012行開始,012行建立了TDSCallbackWithMethod物件,並且建立一個匿名方法做為用戶端的回叫方法傳遞給伺服端。從013行開始的匿名方法在被用戶端回叫的時候首先在013行把伺服端傳遞來的參數型態轉換為TJSONString的型態,接著更新用戶端的UI以通知使用者伺服端目前正在處理那一個目錄。最後在023行用戶端回叫方法如果執行成功就需要回傳TJSonTrue物件,如果失敗的話就需要回傳TJSonFalse物件。

001 procedure TForm3.btnGetServerInfoAsyncClick(Sender: TObject);

002 var

003     aServer : TServerMethods1Client;

004     LCallback : TDSCallbackWithMethod;

005     ja : TJSONArray;

006     jv : TJSONValue;

007     I: Integer;

008 begin

009     lStart := GetTickCount;

010     aServer := TServerMethods1Client.Create(Self.SQLConnection1.DBXConnection);

011     try

012         LCallback := TDSCallbackWithMethod.Create(

013             function(const Args: TJSONValue): TJSONValue

014             var

015                 asyncResult: TJSONString;

016                 I: Integer;

017                 LMessage: string;

018             begin

019                 asyncResult := TJSONString(Args);

020                 lbAsync.Items.Add(asyncResult.ToString);

021                 lbAsync.Update;

022                 Application.ProcessMessages;

023                 Result := TJSonTrue.Create;

024              end

025 );

026         ja := aServer.GetServerDirectoryInfoAsync(LCallback, Edit1.Text);

027

028

029         lEnd := GetTickCount;

030         for I := 0 to ja.Size - 1 do

031         begin

032             jv := ja.Get(I);

033             lbResult.Items.Add(jv.ToString);

034         end;

035     finally

036         aServer.Free;

037         ShowRunTime(lEnd - lStart);

038     end;

039 end;

那麼什麼是TDSCallbackWithMethod類別呢? 這要從TDBXCallback抽象類別開始談起。

TDBXCallback抽象類別

在討論TDSCallbackMethod之前我們必須先說明TDBXCallback抽象類別,因為TDSCallbackMethod是從TDBXCallback繼承下來的實體類別。事實上TDBXCallback類別即是使用DataSnap 2010回叫機制的關鍵,要使用回叫機制,開發人員必須實作一個從TDBXCallback繼承下來的實體類別,並且傳遞此實體類別的樣例給伺服端方法做為參數,如此一來伺服端方法就可以藉由這個樣例參數呼叫回用戶端,以通知用戶端伺服端方法執行的狀態。

TDBXCallback的虛擬方法Execute是衍生類別需要複載實作的,Execute接受一個型態為TJSONValue的參數並且回傳一個型態為TJSONValue的結果值,伺服端方法在回叫用戶端的方法時,可以把需要傳遞給用戶端的數值或是物件轉換為TJSONValue型態並且當成Execute方法的參數傳遞回用戶端,而用戶端的方法在被回叫執行完畢之後,也可以把執行結果轉換為TJSONValue型態並且回傳給伺服端。下面即是TDBXCallback抽象類別的宣告:

001 TDBXCallback = class abstract

002 public

003 function Execute(const Arg: TJSONValue): TJSONValue; virtual; abstract;

004 protected

005 procedure SetConnectionHandler(const ConnectionHandler: TObject); virtual;

006 procedure SetOrdinal(const Ordinal: Integer); virtual;

007 public

008 property ConnectionHandler: TObject write SetConnectionHandler;

009 property Ordinal: Integer write SetOrdinal;

010 end;

TDSCallbackMethod實體類別

瞭解了TDBXCallback扮演的角色之後解釋TDSCallbackMethod實體類別就簡單了,由於此範例應用程式要使用非同步呼叫機制,因此宣告TDSCallbackMethod從TDBXCallback繼承下來並且實作虛擬方法Execute。在TDSCallbackMethod的Execute方法中它只是簡單的呼叫在建構函式中儲存下來的用戶端方法的方法指標。

unit AsyncUtils;

interface

uses

Classes,

DbxDatasnap,

DBXJson;

type

TDSCallbackMethod = reference to function(const Args: TJSONValue): TJSONValue;

TDSCallbackWithMethod = class(TDBXCallback)

private

    FCallbackMethod: TDSCallbackMethod;

public

    constructor Create(ACallbackMethod: TDSCallbackMethod);

    function Execute(const Args: TJSONValue): TJSONValue; override;

end;

implementation

constructor TDSCallbackWithMethod.Create(ACallbackMethod: TDSCallbackMethod);

begin

    FCallbackMethod := ACallbackMethod;

end;

function TDSCallbackWithMethod.Execute(const Args: TJSONValue): TJSONValue;

var

aString: string;

begin

    Assert(Assigned(FCallbackMethod));

    Result := FCallbackMethod(Args);

end;

end.

下面的圖形分別是DataSnap伺服端執行的畫面以及同步用戶端,回叫用戶端的執行畫面,從圖2同步用戶端畫面可以看到它雖然比回叫用戶端執行的快(在筆者的機器中執行了7.078秒),但是在同步用戶端呼叫DataSnap伺服器時它的整個UI是暫停的,因此它無法在表單下方的ListBox中顯示任何伺服端執行的資訊,使用者必須等待它完全執行完畢才能取回用戶端應用程式的控制權。

image

圖1 DataSnaps伺服器的執行畫面

image

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

image

圖3 回客戶端的執行畫面

相反的畫面3則是回叫用戶端的執行結果,我們看到它是比同步用戶端慢(在筆者的機器中執行了12.984秒),但是在整個執行過程中使用者仍然可以控制用戶端應用程式,而且回叫用戶端能夠不停的在表單下方的ListBox中顯示目前伺服端正在處理的目錄資訊。因此就使用者經驗來說,回叫用戶端是比同步用戶端好多了。

也許可以更好

DataSnap 2010的回叫機制雖然使用上非常的簡單,但在許多的應用中卻仍然可能不便,例如現在的機制需要我們在把用戶端回叫方法傳遞給伺服端方法當成參數,但這實在有些囉嗦,因為如果用戶端有許多的回叫方法,那麼在每次呼叫伺服端方法時都需要傳遞一次。因此如果DataSnap 2010能夠提供一個全域的回叫方法註冊機制,讓用戶端只需要為每一個回叫方法註冊一次即可,而無需每次呼叫即傳遞一次,那麼在使用上將更為簡化。不過DataSnap 2010的確是一個大幅進步的版本,相信未來Delphi/BCB團隊會繼續的增強DataSnap的功能。

Have Fun!

28 septembre

JSON 程式設計 – 使用過濾器

在以前Tiburon遊記的文章解釋過JSON是使用字串型態來傳遞資料的,因此所有其他型態的資料都必須轉換為字串的型態,雖然如此一來在處理上比較簡單,但這也造成了其他的問題,例如一些敏感性的資料如果使用字串型態來傳遞的話就會有問題。DataSnap 2010為了解決這種問題因此加入了過濾器的機制,讓開發人員在傳遞特殊的資料時可以藉由過濾器來進行額外的處理,例如在傳遞資料出去時先加密,並且在接受到資料之後再進行解密。本篇文章的目的即在於討論如何使用DataSnap 2010的過濾器。

使用DataSnap 2010的過濾器非常的簡單,Delphi 2010也內建了一個壓縮過濾器,可以有效的壓縮使用TCP/IP通訊協定的資料傳遞。讓我們先說明如何使用這個內建的過濾器,稍後我們再深入的說明如何開發客製化過濾器。

讓我們仍然使用上篇文章的範例,要使用過濾器,請開啟ServerContainerUnit程式單元,點選表單中的TDSTCPServerTransport元件,並且雙擊它的Filters特性,此時Filters特性值編輯器會啟動,請於其中加入一個新的過濾器,點選此新的過濾器,然後在物件檢視器中選擇它的FilterId為ZlibCompression,如下所示:

clip_image002

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

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

implementation

uses DBXJSONReflect, DBXJSON, uServerProxy, uEmployee, DBXCompressionFilter;

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

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

clip_image004

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

clip_image006

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

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

開發客製化過濾器

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

函式名稱

回傳型態

說明

GetParameters

TDBXStringArray

回傳所有的參數

GetUserParameters

TDBXStringArray

回傳使用者可改變的參數

ProcessInput

TBytes

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

ProcessOutput

TBytes

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

Id

UnicodeString

過濾器的ID

GetParameterValue

UnicodeString

取得特定名稱的參數值

SetParameterValue

Boolean

設定特定名稱的參數值

瞭解了需要實作那些虛擬方法之後,我們就可以開始動手開發一個客製化過濾器了。在本文中筆者將撰寫一個非常簡單的加密/解密過濾器,其實這個加密/解密過濾器只是在傳遞資料時和一個字串進行xor的動作,到了另一端再次xor相同的字串而已。

首先讓我們宣告範例TTransportEncryptFilter類別從TTransportFilter類別繼承下來並且複載相關必要的虛擬方法:

TTransportEncryptFilter = class(TTransportFilter)

private

FEncrypt: TSimpleEncryptor;

FParameters: TDictionary<String, String>;

protected

function GetParameters: TDBXStringArray; override;

function GetUserParameters: TDBXStringArray; override;

public

function GetParameterValue(const ParamName: UnicodeString): UnicodeString;

override;

function SetParameterValue(const ParamName: UnicodeString;

const ParamValue: UnicodeString): Boolean; override;

constructor Create; override;

destructor Destroy; override;

function ProcessInput(const Data: TBytes): TBytes; override;

function ProcessOutput(const Data: TBytes): TBytes; override;

function Id: UnicodeString; override;

end;

TTransportEncryptFilter類別將使用TSimpleEncryptor進行字串xor的運算,在TSimpleEncryptor類別中實作了兩個方法,Encrypt和Decrypt,其實這兩個方法的實作程式碼是一樣的,只是為了說明方便分別實作成Encrypt和Decrypt以便讓讀者瞭解。Encrypt和Decrypt接受TBytes型態的參數,這個參數在Encrypt方法中是代表傳遞出去的資料,Encrypt方法使用程式碼加密之後再把加密過的資料以TBytes型態回傳。

而Decrypt的參數則是代表接受來的資料,Decrypt方法必須使用程式碼加以解密以還原資料。

TSimpleEncryptor = class

protected

public

function Encrypt(const Data: TBytes): TBytes;

function Decrypt(const Data: TBytes): TBytes;

constructor Create;

end;

下面是這兩個方法的實作程式碼,讀者可以看到這兩個方法的實作程式碼是 樣的,它們都接受的參數以'DexterHighlanderTiburonWeaver'這個鍵值字串進行xor的運算:

const

EncryptKey = 'DexterHighlanderTiburonWeaver';

constructor TSimpleEncryptor.Create;

begin

inherited Create;

end;

function TSimpleEncryptor.Decrypt(const Data: TBytes): TBytes;

var

i: Integer;

idx: Integer;

begin

Result := Data;

idx := 0;

for i := 0 to Length(Data) - 1 do

begin

Result[i] := Byte(Chr(Ord(Data[i]) xor Ord(EncryptKey[idx])));

Inc(idx);

if (idx > Length(EncryptKey)) then

idx := 0;

end;

end;

function TSimpleEncryptor.Encrypt(const Data: TBytes): TBytes;

var

i: Integer;

idx: Integer;

begin

Result := Data;

idx := 0;

for i := 0 to Length(Data) - 1 do

begin

Result[i] := Byte(Chr(Ord(Data[i]) xor Ord(EncryptKey[idx])));

Inc(idx);

if (idx > Length(EncryptKey)) then

idx := 0;

end;

end;

下面則是TTransportEncryptFilter類別的實作程式碼,讀者可以看到在在建構函式中建立了TSimpleEncryptor物件,並且分別在ProcessInput虛擬方法中呼叫TSimpleEncryptor物件的Encrypt方法加密傳遞的資料並且在ProcessOutput虛擬方法中呼叫TSimpleEncryptor物件的Decrypt方法以解密資料:

function TTransportEncryptFilter.GetUserParameters: TDBXStringArray;

begin

SetLength(Result, 1);

Result[0] := EncryptKey;

end;

function TTransportEncryptFilter.GetParameters: TDBXStringArray;

begin

SetLength(Result, 1);

Result[0] := EncryptKey;

end;

function TTransportEncryptFilter.GetParameterValue

(const ParamName: UnicodeString): UnicodeString;

begin

FParameters.TryGetValue(ParamName, Result);

if ( ParamName = EncryptKey ) and ( Result = '' ) then

Result := '0';

end;

function TTransportEncryptFilter.SetParameterValue

(const ParamName, ParamValue: UnicodeString): Boolean;

begin

FParameters.AddOrSetValue(ParamName, ParamValue);

Result := True;

end;

constructor TTransportEncryptFilter.Create;

begin

inherited Create;

FParameters := TDictionary<String, String>.Create;

FEncrypt := TSimpleEncryptor.Create;

end;

destructor TTransportEncryptFilter.Destroy;

begin

FreeAndNil(FParameters);

FreeAndNil(FEncrypt);

inherited Destroy;

end;

function TTransportEncryptFilter.ProcessInput(const Data: TBytes): TBytes;

begin

OutputDebugString(PWideChar('Encrypted - ' + Stringof(Data)));

Result := FEncrypt.Encrypt(Data);

end;

function TTransportEncryptFilter.ProcessOutput(const Data: TBytes): TBytes;

begin

OutputDebugString(PWideChar('Decrypted - ' + Stringof(Data)));

Result := FEncrypt.Decrypt(Data);

end;

function TTransportEncryptFilter.Id: UnicodeString;

begin

Result := EncryptFilterName;

end;

最後我們需要在initialization部份註冊這個客製化過濾器並且在finalization部份解除註冊客製化過濾器:

initialization

TTransportFilterFactory.RegisterFilter(EncryptFilterName,

TTransportEncryptFilter);

finalization

TTransportFilterFactory.UnregisterFilter(EncryptFilterName);

使用客製化過濾器

OK,回到範例伺服器,開啟ServerContainer程式單元並且在它的OnCreate事件處理函式中使用TDSTCPServerTransport的AddFilter方法加入我們的客製化過濾器:

procedure TServerContainer1.DataModuleCreate(Sender: TObject);

var

i : Integer;

begin

i := DSTCPServerTransport1.Filters.AddFilter(uEncryptFilter.EncryptFilterName);

for i := 0 to DSTCPServerTransport1.Filters.Count - 1 do

Form1.lbFilters.Items.Add(DSTCPServerTransport1.Filters.GetFilter(i).Id);

end;

當然我們也需要在ServerContainer程式單元的uses句子中加入包含客製化過濾器的程式單元uEncryptFilter:

implementation

uses Windows, ServerMethodsUnit1, MainForm, uEncryptFilter;

現在執行DataSnap伺服器,我們就可以看到伺服器顯示它已經找到了我們的客製化過濾器:

clip_image008

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

clip_image010

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

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

clip_image012

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

clip_image014

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

clip_image016

現在您應該瞭解了如何使用DataSnap 2010的過濾器功能以及如何開發客製化過濾器,我們下次再討論DataSnap 2010的非同步機制。

Have Fun!

23 septembre

JSON 程式設計 – Marshaling/Unmarshaling

上篇文章中說明了如何使用Delphi 2010中通用的JSON相關類別來進行程式設計並且以一個TEmployee類別做為範例來說明如何把TEmployee物件轉換為JSON格式的資料並且藉由DataSnap傳遞到用戶端。

由於類似這種傳遞Value Object的應用在JSON程式設計的世界被非常廣泛的應用,因此DataSnap 2010正式加入了JSON Marshaling/Unmarshaling的功能,允許程式師把物件轉換為符合JSON格式的序列化物件傳遞,例如從伺服端傳遞到用戶端。這樣做的好處是只要支援JSON的技術都可以自由的解析其中的資料並且加入處理,如此一來DataSnap 2010的JSON Marshaling/Unmarshaling技術就可以達成和同質或是異質平台互動的能力,也就是說DataSnap 2010的JSON Marshaling/Unmarshaling技術是已經是一個跨平台的分散式架構了。

使用DataSnap 2010的JSON Marshaling/Unmarshaling非常的簡單,因為對大多數的物件而言,DataSnap 2010能夠自動的把它轉換為TJSONObject型態的物件,然後再加以傳遞,因此開發人員只需要撰寫非常簡易的程式碼就可以輕易的完成。現在讓我們仍然使用上篇文章中的TEmployee類別做為說明,看看使用JSON Marshaling/Unmarshaling功能之後在分散式環境中傳遞物件是如何的容易。

建立DataSnap伺服器

Delphi 2010提供了新的DataSnap精靈幫助開發人員快速建立DataSnap伺服器,如此一來我們就不必每次都不斷重覆的放入TDSServer和TDSTCPServerTransport等元件,可節省我們的開發時間。此外DataSnap 2010也開始支援HTTP/HTTPS通訊協定,DataSnap精靈允許開發人員自動設定更多的元件組態資訊。

啟動Delphi 2010,點選File|New啟動New Items對話盒並且於DataSnap Server分類中選擇DataSnap Server圖像以建立DataSnap伺服器:

Image1

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

Image2

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

Image3

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

Image4

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

Image5

由於Delphi 2010強化了RTTI提供Reflection的機制,因此DataSnap 2010允許開發人員選擇使用TDSServerModule,TDataModule或是TPersistent類別做為輸出服務的父代類別,因為Delphi 2010都可以藉由RTTI從任何父代類別取得必要的資訊。

現在讓我們在這個DataSnap伺服器專案中加入上篇文章中的TEmployee程式單元,並且輸出GetEmployee方法讓用戶端能夠從伺服端取得TEmployee物件。請注意GetEmployee回傳TJSONValue型態的數值,由於DataSnap 2010在參數列和回傳值都開始支援TJSONValue,因此這代表只要我們把任何物件轉換為TJSONValue物件,就可以自由傳遞到用戶端,或是從用戶端傳遞回伺服端,而這個流程就是DataSnap 2010的JSON Marshaling/Unmarshaling技術。

TServerMethods1 = class(TDSServerModule)

private

{ Private declarations }

public

{ Public declarations }

function EchoString(Value: string): string;

function GetEmployee : TJSONValue;

end;

要使用JSON Marshaling/Unmarshaling,請加入使用DBXJSONReflect程式單元,如下004行所示。

要以JSON格式傳遞物件非常的簡單,首先我們需要建立我們要傳遞的物件,一個TJSONMarshal物件以及一個TJSONConverter物件。因此下面的011行先建立TEmployee物件,012行建立TJSONMarshal物件,並且在它的建構函式中傳入一個TJSONConverter物件,最後我們只需要呼叫TJSONMarshal物件的Marshal虛擬方法並且傳入要傳遞的物件即可,如014行所示:

01 implementation

002

003 {$R *.dfm}

004 uses DBXJSONReflect, uEmployee;

005

006 function TServerMethods1.GetEmployee: TJSONValue;

007 var

008 anEmployee : TEmployee;

009 aMarshaler : TJSONMarshal;

010 begin

011 anEmployee := TEmployee.Create('王大明', 'wtm@somemail.com', '123456');

012 aMarshaler := TJSONMarshal.Create(TJSONConverter.Create);

013 try

014 Result := aMarshaler.Marshal(anEmployee);

015 finally

016 FreeAndNil(anEmployee);

017 FreeAndNil(aMarshaler);

018 end;

019 end;

如此一來TJSONMarshal和TJSONConverter類別就可以自動把傳入的物件轉換為JSON的格式並且傳遞出去。

建立DataSnap用戶端

現在建立一個VCL Form應用程式專案,放入TSQLConnection元件,連結到DataSnap伺服器(如果您不知道如何做,請參考筆者的Tiburon遊記系列文章),建立代表DataSnap伺服器輸出服務的用戶端程式單元uServerProxy,同樣把TEmployee程式單元加入到用戶端專案中,再參考DBXJSONReflect和DBXJSON程式單元,如下001行所示。

在用戶端要反轉JSON物件回正確物件的流程就是Unmarshaling,我們只需要在013行建立TJSONUnMarshal物件,015行呼叫DataSnap伺服器的GetEmployee服務方法,GetEmployee回傳TJSONValue型態的物件,016行先存取回傳的TJSONValue物件的JSON格式文字值,接著018行呼叫TJSONUnMarshal的Unmarshal方法,傳入回傳的TJSONValue物件並且使用as運算元把它正確的轉換回TEmployee物件,之後我們就可以自由的存取TEmployee物件的特性值了。

001 uses DBXJSONReflect, DBXJSON, uServerProxy, uEmployee;

002

003 {$R *.dfm}

004

005 procedure TForm2.Button1Click(Sender: TObject);

006 var

007 aServer : TServerMethods1Client;

008 unMarshaler : TJSONUnMarshal;

009 aValue : TJSONValue;

010 anEmployee : TEmployee;

011 begin

012 aServer := TServerMethods1Client.Create(Self.SQLConnection1.DBXConnection);

013 unMarshaler := TJSONUnMarshal.Create;

014 try

015 aValue := aServer.GetEmployee;

016 Edit4.Text := aValue.ToString;

017

018 anEmployee := unMarshaler.Unmarshal(aValue) as TEmployee;

019 Edit1.Text := anEmployee.Name;

020 Edit2.Text := anEmployee.EMail;

021 Edit3.Text := anEmployee.Phone;

022

023 finally

024 FreeAndNil(anEmployee);

025 FreeAndNil(unMarshaler);

026 FreeAndNil(aServer);

027 end;

028 end;

從下圖可以看到TEmployee物件的確可以正確的從DataSnap伺服器傳遞到用戶端,當然用戶端也可以修改數值之後再傳遞回DataSnap伺服器再更新回資料庫。

Image6

使用DataExplorer存取DataSnap伺服器的服務

由於DataSnap 2010自動支援RESTful架構,因此既然在前面我們選擇讓DataSnap伺服器支援HTTP/HTTPS通訊協定,因此我們現在就可以使用瀏覽器來存取這個DataSnap伺服器的服務,我們只需要使用下面的樣例就可以存取到DataSnap伺服器的服務:

http://server url/datasnap/rest/服務程式單元/服務方法

https://server url/datasnap/rest/服務程式單元/服務方法

因此要存取前面的DataSnap伺服器,我們只需要使用下面的URL:

http://localhost:8080/datasnap/rest/TServerMethods1/GetEmployee

使用8080當然是因為我們前面設定TDSHTTPService元件的HttpPort特性值使用8080。

例如下圖就是筆者使用FireFox存取DataSnap伺服器:

Image7

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

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

Image8 Image9 Image10

建立Web用戶端

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

Image11

這是如何做到的?

由於篇幅的限制,因此讓我們簡單的說明一下好了。當開發人員使用TJSONMarshal類別Marshaling物件時,TJSONMarshal藉由新的Rtti功能取得任何物件的欄位值,再一一的根據欄位型態呼叫TXXXXConverter類別把欄位值轉換為JSON格式,最後再把所有欄位值組合成TJSONObject的型態傳遞出去。至於Unmarshaling則是進行相反的動作。

那麼DataSnap 2010的Marshaling/Unmarshaling能夠傳遞什麼型態的物件呢? 沒有限制嗎? 這要分2個層面回答。

首先回答第一個問題,Marshaling/Unmarshaling能夠傳遞什麼型態的物件呢?答案是任何從TObject繼承下來的物件都可以,因為Marshaling/Unmarshaling中的實作程式碼已經說明了一切:

procedure TTypeMarshaller<TSerial>.MarshalData(Data: TObject);

但另外一個關鍵則是物件的欄位型態,前面筆者已經說過,TXXXXConverter類別,因此如果您要傳遞的物件的欄位型態是特別的型態,或是目前DataSnap 2010的TXXXXConverter類別沒有支援的型態,那麼就不能傳遞,當然要克服這很簡單,因為您可以自己寫一個客製化TXXXXConverter衍生類別,並且註冊給DataSnap 2010就可以了,這屬於進階的功能,以後有機會再討論吧。

如何? DataSnap 2010的Marshaling/Unmarshaling功能是不是很強大又很容易使用? 各位讀者可以和上一篇我們自己傳遞TEmployee物件比較一下。善用DataSnap 2010的Marshaling/Unmarshaling功能可以讓我們輕易的開發出士又輕又快的跨平台分散式應用系統,成功的克服了以往COM/DCOM/COM+無法做到的事情,DataSnap 2010提供了目前最佳的分散式解決方案。

我們下次再見,Have Fun!

16 septembre

JSON程式設計

上篇文章簡單的介紹了如何使用DataSnap開發基於JSON分散式應用系統,Delphi 藉由原本的Midas/DataSnap和dbExpress的元件提供這個新的分散式計算能力,但使用這些元件都是資料的方式來存取遠端服務,然而我們也可以使用前面介紹的JSON類別再結合DataSnap來實作出存取遠端數值物件(Value Object)或是所謂的DTO(Data Transfer Object)的應用,讓用戶端可以存取遠端的物件。
在下面的範例中我們將展示如何開發使用VO/DTO的DataSnap應用程式伺服器,並且在用戶端擷取其中封裝的資料和物件。這個範例將開發一個可以讓用戶端查詢資料和物件的DataSnap應用程式伺服器,當用戶端查詢物件時,我們將使用TJSONObject封裝物件並且傳遞到用戶端。
X-3-1開發數值物件伺服器
首先建立一個VCL Form應用程式專案,在它的主表單中加入如下的元件:
 

圖7 VO DataSnap應用程式伺服器

接著在專案中建立一個新的程式單元,於其中實作一個簡單的TEmployee類別如下:

unit uEmployee;

interface

uses SysUtils, classes;

type
  TEmployee = class
  private
    { Private declarations }
    FName : string;
    FEMail : string;
    FPhone : string;
  public
    { Public declarations }
    constructor Create(sName  : string = ''; sEMail : string = ''; sPhone : string = '');

    property Name : string read FName write FName;
    property EMail : string read FEmail write FEMail;
    property Phone : string read FPhone write FPhone;
  end;

implementation

{ TEmployee }

constructor TEmployee.Create(sName, sEMail: string; sPhone: string);
begin
  FName := sName;
  FEmail := sEMail;
  FPhone := sPhone;
end;

end.

再於專案中建立一個新的程式單元稱為uEmployeeValueObject,其中定義TEmployeeVO類別如下,請注意TEmployeeVO類別使用了
{$MethodInfo ON}和{$MethodInfo Off}編譯器指令要求編譯器把TEmployeeVO類別的Reflection資訊編譯到執行檔中。

  {$MethodInfo ON}
  TEmployeeVO = class(TComponent)
  private
    { Private declarations }
    FEmployees : TObjectList<TEmployee>;

    function CreateEmployeeJSONObject(employee : TEmployee) : string;
    function CreateEmployeeJSONArray : string;
    function CreateEmployeeJSONObjectJ(employee : TEmployee) : TJSONObject;
  public
    { Public declarations }
    destructor Destroy; override;
    function GetEmployeeJ(const sName : string) : TJSONObject;
    function GetAllEmployeesJ : TJSONArray;
    procedure AddEmployee(const sName : string; const sEMail : string; const sPhone : string);
  end;
  {$MethodInfo Off}

TEmployeeVO宣告了三個方法說明如下:

方法名稱

說明

備註

AddEmployee 讓用戶端呼叫增加員工資訊
GetEmployeeJ 讓用戶端以員工姓名查詢員工物件 請注意GetEmployeeJ回傳TJSONObject型態的結果值,這代表範例應用程式將把員工物件封裝在TJSONObject物件之中並且回傳給用戶端
GetAllEmployeesJ 讓用戶端查詢所有的員工資訊 查詢後端所有員工資訊,請注意GetAllEmployeesJ回傳型態為TJSONArray的結果值,這代表範例應用程式將把所有查詢結果封裝在TJSONArray物件中回傳


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

AddEmployee方法的實作
AddEmployee方法非常的簡單,它從用戶端接受三個字串型態的參數,並且根據它們來建立TEmployee物件,最後再把建立的TEmployee物件加入在宣告為TObjectList<TEmployee>泛型型態的變數FEmployees之中。

procedure TEmployeeVO.AddEmployee(const sName, sEMail, sPhone: string);
var
  employee : TEmployee;
begin
  if (FEmployees = nil) then
    FEmployees := TObjectList<TEmployee>.create(true);
  employee := TEmployee.Create(sName, sEmail, sPhone);
  FEmployees.Add(employee);
end;

GetEmployeeJ方法的實作
GetEmployeeJ方法就非常的有趣了,它展示了如何於DataSnap應用程式伺服器中使用TJSONValue的相關類別,GetEmployeeJ回傳型態為TJSONObject的物件回用戶端。
GetEmployeeJ根據用戶端傳遞來查詢的員工姓名,在Fem ployees中一一的搜尋具有相同名稱的TEmployee物件,找到之涕在013行呼叫CreateEmployeeJSONObjectJ來建立回傳的TJSONObject物件。

001    function TEmployeeVO.GetEmployeeJ(const sName: string): TJSONObject;
002    var
003      ie : TList<uEmployee.TEmployee>.TEnumerator;
004      employee : TEmployee;
005    begin
006      Result := nil;
007      ie := FEmployees.GetEnumerator;
008      while (ie.MoveNext) do
009      begin
010        employee := ie.Current;
011        if (employee.Name = sName) then
012        begin
013          Result := CreateEmployeeJSONObjectJ(employee);
014          break;
015        end;
016      end;
017    end;

CreateEmployeeJSONObjectJ方法根據找到的TEmployee物件來建立TJSONObject物件,它呼叫我們前面學習過的AddPair方法,把每一個TEmployee物件的特性名稱和特性值做為一個JSON的中『名稱/值』配對加入到TJSONObject物件。

function TEmployeeVO.CreateEmployeeJSONObjectJ(employee: TEmployee): TJSONObject;
var
  aJO : TJSONObject;
begin
  aJO := TJSONObject.Create;
  aJO.AddPair(TJSONString.Create('姓名'), TJSONString.Create(employee.Name));
  aJO.AddPair(TJSONString.Create('EMail'), TJSONString.Create(employee.EMail));
  aJO.AddPair(TJSONString.Create('電話'), TJSONString.Create(employee.Phone));
  Result := aJO;
end;

GetAllEmployeesJ方法的實作
GetAllEmployeesJ方法會把所有存在泛型型態變數FEmployees之中的TEmployee物件封裝在TJSONArray物件中回傳給用戶端。
在013行仍然是呼叫CreateEmployeeJSONObjectJ為每一個CreateEmployeeJSONObjectJ建立一個TJSONObject物件,並且加入到回傳的TJSONArray物件中。

001    function TEmployeeVO.GetAllEmployeesJ: TJSONArray;
002    var
003      ie : TList<uEmployee.TEmployee>.TEnumerator;
004      employee : TEmployee;
005      jo : TJSONObject;
006      ja : TJSONArray;
007    begin
008      ie := FEmployees.GetEnumerator;
009      ja := TJSONArray.Create;
010      while (ie.MoveNext) do
011      begin
012        employee := ie.Current;
013        jo := CreateEmployeeJSONObjectJ(employee);
014        ja.AddElement(jo);
015      end;
016      Result := ja;
017    end;

最後不要忘記在主表單中註冊TEmployeeVO類別,如此一來用戶端才能看見並且呼叫TEmployeeVO輸出的方法:

procedure TForm10.FormCreate(Sender: TObject);
begin
  if DSServer1.Started then
    DSServer1.Stop;
  RegisterServers;
  DSServer1.Start;
end;

procedure TForm10.FormDestroy(Sender: TObject);
begin
  DSServer1.Stop;
end;

procedure TForm10.RegisterServers;
begin
  uEmployeeValueObject.RegisterServerClasses(Self, DSServer1);
end;

現在編譯並且執行DataSnap應用程式伺服器,並且準備開發用戶端。

開發範例用戶端
其實這個範例的重點就在於用戶端如何呼叫遠端支援TJSONValue相關類別的方法,這是因為在DataSnap 2009中無法支援TJSONValue相關類別,到了DataSnap 2010才支援。但是在筆者撰寫本章時Delphi 2010的Beta版仍然無法自動產生代表遠端類別的用戶端Proxy類別,因此想要呼叫前面實作的GetEmployeeJ和GetAllEmployeesJ方法,我們必須瞭解如何修改由Delphi產生的DataSnap用戶端類別。
筆者認為當Delphi 2010正式版釋出時,CodeGear應該會把這個問題修正,如果您發現您使用的Delphi已經能夠產生正確的用戶端Proxy類別,那麼就不需要如下所敘述的修改了。
首先建立一個VCL Form應用程式,放入TSQLConnection元件,設定Driver為DataSnap再把connected設定為True(請確定DataSnap應用程式伺服器已經在執行),接著用點選滑鼠右鍵,選擇Generate DataSnap client classes選項,儲存產生的程式單元為uServerProxy.pas,然後搜尋GetEmployeeJ方法,把012行修改為如下:

(註, 筆者已經在Delphi 2010正式版中試過現在沒有問題了, 而筆者之所以沒把上述段落內容刪除是因為這段內容可以讓讀者更瞭解修改下面程式碼的意義, 即使讀者使用Delphi 2010正式版而不需要再修改下面的程式碼, 也可以把uServerProxy.pas開啟, 再看看由Delphi 2010產生的程式碼代表的意義)

001    function TEmployeeVOClient.GetEmployeeJ(sName: string): TJSONObject;
002    begin
003      if FGetEmployeeJCommand = nil then
004      begin
005        FGetEmployeeJCommand := FDBXConnection.CreateCommand;
006        FGetEmployeeJCommand.CommandType := TDBXCommandTypes.DSServerMethod;
007        FGetEmployeeJCommand.Text := 'TEmployeeVO.GetEmployeeJ';
008        FGetEmployeeJCommand.Prepare;
009      end;
010      FGetEmployeeJCommand.Parameters[0].Value.SetWideString(sName);
011      FGetEmployeeJCommand.ExecuteUpdate;
012      Result := FGetEmployeeJCommand.Parameters[1].Value.GetJSONValue as TJSONObject;
013    end;

這是因為遠端GetEmployeeJ方法回傳TJSONObject型態的物件,因此我們需要把012行呼叫遠端方法執行的結果轉變型態為TJSONObject型態。
同樣的,我們也需要修改GetAllEmployeesJ方法,轉變型態為回傳TJSONArray物件,如下所示:

function TEmployeeVOClient.GetAllEmployeesJ: TJSONArray;
begin
  if FGetAllEmployeesJCommand = nil then
  begin
    FGetAllEmployeesJCommand := FDBXConnection.CreateCommand;
    FgetAllEmployeesJCommand.CommandType := TDBXCommandTypes.DSServerMethod;
    FGetAllEmployeesJCommand.Text := 'TEmployeeVO.GetAllEmployeesJ';
    FGetAllEmployeesJCommand.Prepare;
  end;
  FGetAllEmployeesJCommand.ExecuteUpdate;
  Result := FGetAllEmployeesJCommand.Parameters[0].Value.GetJSONValue as TJSONArray;
end;

瞭解了如何以及為什麼需要修改DataSnap用戶端類別之後,我們就可以開始實作呼叫遠端方法的程式碼了。
首先下面的程式碼藉由自動產生的TEmployeeVOClient類別呼叫AddEmployee方法,把用戶端輸入的員工資料加入在遠端的應用程式伺服器中:

procedure TForm11.btnAddEmployeeClick(Sender: TObject);
var
  evo : TEmployeeVOClient;
begin
  evo := TEmployeeVOClient.Create(Self.SQLConnection1.DBXConnection);
  try
    evo.AddEmployee(edtAddName.Text, edtAddEMail.Text, edtAddPhone.Text);
    edtAddName.Text := '';
    edtAddEMail.Text := '';
    edtAddPhone.Text := '';
  finally
    evo.Free;
  end;
end;

下圖是執行範例用戶端應用程式並且點選『增加員工』按鈕以執行上述程式碼的畫面:


圖8 用戶端應用程式呼叫遠端AddEmployee方式加入員工資訊

接著是藉由TEmployeeVOClient呼叫GetEmployeeJ查詢員工資料的實作程式碼:
procedure TForm11.btnQueryEmployeeClick(Sender: TObject);
var
  evo : TEmployeeVOClient;
  jo : TJSONObject;
  employee : TEmployee;
begin
  evo := TEmployeeVOClient.Create(Self.SQLConnection1.DBXConnection);
  try
    jo := evo.GetEmployeeJ(edtQname.Text);
    employee := TEmployee.Create(jo.Get(0).JsonValue.ToString, jo.Get(1).JsonValue.ToString, jo.Get(2).JsonValue.ToString);
    Self.edtQEMail.Text := employee.EMail;
    Self.edtQPhone.Text := employee.Phone;
  finally
    FreeAndNil(employee);
    FreeAndNil(jo);
    evo.Free;
  end;
end;

在上面的程式碼中呼叫GetEmployeeJ取得代表員工的TJSONObject物件,再根據TJSONObject物件中的資訊於用戶端建立TEmployee物件,再顯示員工資訊於表單中,最後不要忘記釋放TEmployee物件,TJSONObject物件和TEmployeeVOClient物件。
下圖是先增加李維這筆員工資料之後,再輸入李維來查詢的畫面:
 

圖9 輸入員工姓名查詢員工資料
點選上圖中的『查詢單一員工』按鈕之後我們的確可以取得遠端員工物件的資訊,如下圖所示:

 
圖10 查詢的結果畫面

但是為什麼上圖中的員工資訊,例如員工的Email有引號包圍呢? 這當然是因為這是JSON封裝字串的規範,而在上面的程式碼中當我們建立TEmployee物件時是直接呼叫ToString方法,如果我們不希望TEmployee物件的特性值有引號包圍,那麼我們可以修改程式碼如下,改呼叫Value方法:
    employee := TEmployee.Create(jo.Get(0).JsonValue.Value, jo.Get(1).JsonValue.Value, jo.Get(2).JsonValue.Value);

那麼就會有如下正確的結果:

 
圖11 查詢的結果畫面
最後讓我們看看如何查詢所有的員工資料。下面的程式碼藉由TEmployeeVOClient呼叫GetAllEmployeesJ取得TJSONArray物件,然後進入for迴圈把其中的每一個元素取出再轉變型態為TJSONObject物件,最後再根據TJSONObject物件一一的建立用戶端的員工物件。

procedure TForm11.btnQueryAllEmployeesClick(Sender: TObject);
var
  evo : TEmployeeVOClient;
  jo : TJSONObject;
  ja : TJSONArray;
  employee : TEmployee;
  iIndex: Integer;
begin
  evo := TEmployeeVOClient.Create(Self.SQLConnection1.DBXConnection);
  try
    ja := evo.GetAllEmployeesJ;
    for iIndex := 0 to ja.Size - 1 do
    begin
      jo := ja.Get(iIndex) as TJSONObject;
      employee := TEmployee.Create(jo.Get(0).JsonValue.ToString, jo.Get(1).JsonValue.ToString, jo.Get(2).JsonValue.ToString);
      Memo1.Lines.Add(employee.Name);
      FreeAndNil(employee);
    end;
  finally
    FreeAndNil(ja);
    FreeAndNil(employee);
    evo.Free;
  end;
end;

下圖是執行此查詢的結果,我們可以看到用戶端的確可以查詢到遠端的所有員工的資訊。

 
圖12 所有員工資料查詢的結果畫面
當然,我們一樣可以修改上面的程式碼如下:
      employee := TEmployee.Create(jo.Get(0).JsonValue.Value, jo.Get(1).JsonValue. Value, jo.Get(2).JsonValue. Value);

那麼我們會看到如下的結果:


圖13 所有員工資料查詢的結果畫面

本章敘述了Delphi VCL框架中支援JSON開發的相關類別,讀者從本章中可以瞭解這些不但是依照JSON規範設計的,而且非常的直覺,好用,只要我們對於JSON規範有基本的掌握就可以順利的使用它們。

此外,新版的DataSnap不但使用JSON做為封裝和傳遞資料的基礎,現在也加入支援使用TJSONValue和衍生類別做為遠端方法的參數和回傳值,如此一來可以讓開發人員撰寫客製化的JSON應用,例如使用VO/DTO設計樣例來傳遞資料和物件,使用這樣的技術,開發人員也可以使用.NETJava或是任何支援JSON的程式語言來開發用戶端應用程式了。

 練完2篇文章的基本功之後, 在下篇文章開發, 我們將介紹如何使用Delphi 2010新的DataSnap Server精靈來大幅簡化開發的工作, 並且會介紹DataSnap強大的Marshaling和unMarshaling的功能,Marshaling和unMarshaling將可以讓我們使用JSON傳遞物件變得非常簡單,使用Marshaling和unMarshaling也可以讓上面傳遞TEmployee物件的程式碼大幅減少. 我們下次再見, Have FUN!



 
和Delphi相關的資訊
C++Builder相關技術
和JBuilder相關的資訊
La liste est vide.
結合ECO和VCL For Web開發的相關資訊
La liste est vide.
好玩又好用的RoR/XXXX相關資訊
La liste est vide.
Photo 1 sur 8
Autres albums (1)