2010 年 10 月 的封存

RAD Studio XE 北京世界巡回研討會

看著早已過期的台胞證心中不禁有無限的感慨,自從離開Borland之後有好幾年沒去北京了,回想以前東奔西跑的日子讓我時時刻刻不忘當初Borland的好同事王玉紅和左輕侯,也更懷念和各地開發人員討論,交換Delphi,C++Builder和JBuilder開發經驗的暢意。

最近很意外的接到了Embarcadero北京分公司的邀請於10月28日到北京發表RAD Studio XE的活動,由於想到能夠藉由這次成行的機會再次和許多老朋友再次聚首以及北京的開發人員交換開發心得因此欣然接受這個邀請。其實目前做為Delphi/C++Builder的使用者,我個人感覺Delphi/C++Builder在以進入Embarcadero之後的發展比起前幾年在Borland時是好上多了,在Embarcadero內Delphi/C++Builder的研發資源比在Borland時豐富多了,因此目前的Delphi/C++Builder不論在功能上和質量上都比Borland未期時的Delphi/C++Builder強勁許多。

RAD Studio XE在質量,DataSnap+和加入RTTI For C/C++讓Delphi XE/C++Builder XE擁有有始以來最好的表現,並且在加入Delphi Prism XE/RADPHP之後更提供iPhone和Facebook/Google的開發能力。因此我個人歡迎所有Delphi/C++Builder的開發人員在28日於北京中國大飯店一起見證RAD Studio XE為您帶來的強大開發功能.

廣告

5 則迴響

從原生API到REST API – 使用C++Builder XE開發REST應用程式

已經記不得是從什麼開始進入Windows程式設計的世界了,但這並不重要,因為我只要記得那時要寫Windows的程式就需要使用Windows API。在那個時候Windows API是用C定義的,因此當時是C/C++開發工具以及使用C/C++開發的工具最盛行的時代,原因無他,因為當時只能使用C/C++來呼叫Windows API。因此當Borland開發Turbo Pascal For Windows時,Borland需要把使用C定義的Windows API轉換為Pascal的語法,如此一來Pascal的程式碼才能夠順利的呼叫Windows API,當然,Delphi的Windows程式單元就是為了這個目的定義的。

隨著Java和.NET的流行,開發人員又面對了Java API以及.NET API,當然更別提網際網路上流行的PHP和Ruby API。麻煩的是要使用特定程式語言提供的資源或是服務,開發人員或是使用者就必須使用特定程式語言來存取,這妨礙了軟體成為虛擬資源的可能。然而隨著JSON突破了資料封裝的障礙之後,如果也能讓特定程式語言或是框架的API不再成為呼叫服務的障礙,那麼我們的確可以讓所有軟體轉換為虛擬資源,如此一來使用者就可以在任何設備上存取任何軟體提供的服務了,REST就是答案之一。

雲端技術的目的之一就是在轉換軟體服務為虛擬資源,讓用戶端能夠存取任何雲端架構中提供的服務,為了讓所有型態的用戶端能夠使用虛擬資源,使用REST做為呼叫API並且使用JSON做為封裝的機制就可以突破平台,程式語言,框架和各種設備的限制。例如Microsoft的Azure除了提供.NET的API之,也提供了REST API。這個趨勢的確是API演化的大突破,這代表了API已經從原生API轉換到跨越平台,程式語言和框架的API,使用REST API我們可以讓Delphi或是C++Builder的用戶端呼叫Java或是.NET的服務,當然我們也可以建立Delphi或是C++Builder的伺服器以提供服務給Java,.NET或是PHP,Ruby等用戶端來使用。

例如下圖是Azure API之一,

用戶端只需要使用HTTP的Get方法以及如下的URI就可以

http://myaccount.blob.core.windows.net/?comp=list

取得帳戶中的容器物件資訊。

再讓我們看看另外一個例子,我在網際網路上找到一個使用PHP撰寫的RESTful範例,在下面的URL中提供了CRUD服務:

http://labs.wso2.org/wsf/php/demo.php?name=RESTFulCRUD&demo=CRUDApplications/demo_client.php&src=./CRUDApplications

下圖是這個PHP RESTful CRUD的範例畫面:

我們可以看到,只要使用HTTP的Get方法和下面的URI:

http://labs.wso2.org/wsf/php/solutions/CRUDApplications/school_applications.php/applications

就可以取得由PHP程式語言撰寫的服務。

在沒有REST之前,要讓C/C++用戶端存取 PHP的服務似乎有一點難度,但有了REST之後,我們就可以藉由REST API把PHP撰寫的服務當成是由C/C++程式碼撰寫的服務一樣來呼叫。例如下圖是我使用C++Builder XE撰寫的用戶端,它成功的呼叫了這個PHP RESTful CRUD的範例服務,並且取得了服務的結果,從下圖當中我們可以立即看到這個PHP RESTful CRUD的範例是使用XML格式來封裝交換的資料,而並不是使用JSON格式。

那麼我們如何撰寫C/C++的應用程式來呼叫這個PHP RESTful CRUD的範例服務呢? 很簡單,C++Builder XE中提供了TDSHTTP類別可以讓程式師執行HTTP方法,在下面的Button1Click事件處理函式中我建立了一個TDSHTTP物件,然後呼叫GetResponse方法要求TDSHTTP物件向Edit1中Text特性值指定的uri發出Get方法請求:

void __fastcall TForm8::Button1Click(TObject *Sender)

{

TDSHTTP *pDSHTTP;

System::UnicodeString sResult;

System::UnicodeString url;

 

pDSHTTP = new TDSHTTP();

pDSHTTP->HTTPOptions = pDSHTTP->HTTPOptions << hoKeepOrigProtocol;

try

{

url = Edit1->Text;

sResult = GetResponse(pDSHTTP, url);

Memo1->Lines->Text = sResult;

}

__finally

{

delete pDSHTTP;

}

}

GetResponse方法在009行呼叫TDSHTTP物件的Get方法並且傳入指定的uri以及一個TMemoryStream物件以儲存執行結果。接著在011行使用TStreamReader物件呼叫它的ReadToEnd方法並且把結果以字串的型態回傳給用戶端,而這個結果就是PHP RESTful CRUD範例服務執行的結果,我們藉由C/C++程式碼,使用REST API來呼叫使用它提供的服務。

001    System::UnicodeString TForm8::GetResponse(TDSHTTP *pDSHTTP, System::UnicodeString Url)

002    {

003      TStream *pMemoryStream;

004      TStreamReader *pReader;

005

006      pMemoryStream = new TMemoryStream();

007      try

008      {

009      pDSHTTP->Get(Url, pMemoryStream);

010      pMemoryStream->Position = 0;

011      pReader = new TStreamReader(pMemoryStream, true);

012      try

013      {

014        return pReader->ReadToEnd();

015      }

016      __finally

017      {

018        delete pReader;

019      }

020      }

021      __finally

022      {

023      delete pMemoryStream;

024      }

025

026      return “";

027    }

C++Builder XE除了可以使用REST API做為用戶端存取由其他程式語言撰寫的REST伺服器之外,由於C++Builder XE也加入了RTTI的功能(終於),因此現在C++Builder XE也可以開發DataSnap伺服器,而且XE版也可以開發使用DataSnap技術的REST伺服器應用程式,讓使用C/C++撰寫的服務由其他用戶端呼叫使用。

現在就讓我們看看如何使用C++Builder XE開發一個簡單的REST伺服器。

開發C/C++ REST應用程式

要開發DataSnap REST應用程式在C++Builder XE中非常的簡單,點選File|New|Other功能表,在新的DataSnap Server專案選項中點選DataSnap REST Application圖像即可,如下所示:

BCB XE提供三種型態的DataSnap REST應用程式,在這個範例中讓我們選擇建立可單獨執行的VCL應用程式型態做為DataSnap REST伺服器,如下所示:

點選Next按鈕之後REST精靈會詢問REST伺服器使用的HTTP通信埠,程式師也可以點選『Find Open Port』按鈕來搜尋可供使用的通信埠,內定上是使用8080。

再點選Next按鈕之後REST精靈會詢問開發人員是否需要使用安全認證功能以及是否需要REST精靈產生兩個範例輸出方法,在這裡讓我們點選Select all選擇使用所有的功能。

最後REST精靈會詢問伺服器方法使用的袓先類別,在這個範例中讓我們接受使用內定的TDSServerModule做為伺服器方法使用的袓先類別,如此一來稍後當我們在此類別中實作的任何方法都將可以自動輸出讓各種用戶端應用程式呼叫使用。

當REST精靈結束之後就會在BCB整合發展環境中建立一個新的專案,其中包含了剛才設定相關的檔案以及許多JavaScript和HTML樣版檔案,如下所示:

我們以後有機會再討論如何使用這些產生檔案,現在讓我們集中焦點先為這個REST伺服器撰寫一個服務方法,這個服務方法將從資料表中讀取資料並且使用JSON的格式傳遞回用戶端,因此任何支援JSON的用戶端都能夠使用這個使用C/C++撰寫的服務方法。

因此現在請在REST應用程式中建立一個資料模組並且放入dbExpress元件連結MS SQL Server中的範例資料表。

接著開啟專案中的ServerMethodsUnit1程式單元,在其中實作GetFishInfo方法,GetFishInfo將建立一個TJSONArray物件,呼叫AddFishData在TJSONArray物件加入資料,最後回傳TJSONArray物件回用戶端,其實作程式碼如下:

TJSONArray* TServerMethods1::GetFishInfo()

{

TJSONArray *pContainer;

 

pContainer = new TJSONArray();

AddFishData(pContainer);

return pContainer;

}

而AddFishData方法使用資料模組中的dbExpress元件從資料表中取出一筆一筆的資料,同時把其中兩個欄位的資料值加入到一個TJSONPair物件中,再呼叫TJSONArray的AddElement方法把TJSONPair物件加入到TJSONArray物件中:

void TServerMethods1::AddFishData(TJSONArray *pContainer)

{

if (!dmFishFacts->cdsFishFacts->Active)

dmFishFacts->cdsFishFacts->Active = true;

 

try

{

dmFishFacts->cdsFishFacts->First();

while (!dmFishFacts->cdsFishFacts->Eof)

{

TJSONPair *pPair = new TJSONPair(dmFishFacts->cdsFishFacts->FieldByName(“Category")->AsString, dmFishFacts->cdsFishFacts->FieldByName(“Common_Name")->AsString);

pContainer->AddElement((TJSONValue *) pPair);

dmFishFacts->cdsFishFacts->Next();

}

}

__finally

{

dmFishFacts->cdsFishFacts->Active = false;

}

}

現在編譯這個範例REST應用程式並且執行它,下圖就是範例REST應用程式執行的畫面,點選表單中的Start按鈕以啟動伺服器:

現在我們就可以點選表單中的Open Browser按鈕啟動瀏覽器來呼叫範例REST應用程式輸出的服務。

下圖是瀏覽器啟動之後顯示的畫面,由於在前面我們建立範例REST應用程式時選擇了使用安全驗證功能,因此瀏覽器顯示了登錄資訊,由此現在我們還沒有在程式碼中使用安全驗證功能,因此讓我們點選Server Functions連結:

此時瀏覽器會顯示類似如下的內容,我們可以看到範例REST應用程式中輸出服務的類別TServerMethods1輸出了三個方法,其中包含了GetFishInfo。如果我們點選下圖中的EXECUTE按鈕就可以在瀏覽器下方看到呼叫GetFishInfo方法之後C/C++ REST伺服器回傳的執行結果:

這個執行結果的格式果然是使用JSON陣列封裝資料。

由於這個C/C++伺服器也是一個REST伺服器,因此我們可以直接使用uri來存取它提供的服務。在內定C++Builder XE的REST伺服器使用如下的格式讓用戶端存取它的服務:

http(s)://伺服器/datasnap/rest/輸出類別名稱/方法名稱/方法參數

由於這個範例REST伺服器輸出的類別是TServerMethods1,而我們呼叫的方法是GetFishInfo,因此如果我們在瀏覽器的url位址欄位中輸入如下的uri:

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

就可以取得範例C/C++ REST伺服器執行的結果:

果然,由C/C++程式語言撰寫的REST伺服器可以直接使用瀏覽器來呼叫,或是使用任何其他的程式語言,框架或是設備來呼叫這個C/C++ REST伺服器。

如何? C++Builder XE提供的REST和JSON功能是不是非常的強大又易於使用呢?

現在我們既然完成了這個範例C/C++ REST伺服器,那麼讓我們回到本文討論的首先範例程式,看看這個通用的REST用戶端是否能夠呼叫由C++Builder XE開發的REST伺服器。

我們只需要在C++Builder XE中載入首先範例應用程式,加入一個新的按鈕,然後讓按鈕也使用原先『呼叫PHP REST CRUD範例』按鈕的事件處理函式即可,我們甚至不需要撰寫任何新的程式碼。

編譯並且執行這個範例應用程式,然後在表單上方的Edit控制項中輸入:

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

點選新加入的『呼叫BCB DataSnap REST伺服器範例』按鈕,就可以看到下面的執行結果:

果然,我們仍然可以使用TDSHTTP元件呼叫使用C/C++程式碼撰寫的REST伺服器,現在這個範例用戶端應用程式可同時呼叫由PHP和C/C++撰寫的REST伺服器,事實上這個範例應用程式可以呼叫任何的REST伺服器。

好了,這篇文章也算不短了,就讓我們暫時到此為止,有機會的話下次再談談原生C/C++ DataSnap伺服器。

 

 

 

 

 

 

 

 

 

 

 

 

 

6 則迴響

Delphi XE程式設計系列 1-主從架構, 多層到JSON和REST

從桌面開發,主從架構,一直到多層架構,雖然都是廣泛被接受的觀念和技術,但在資訊技術的實作上卻從不是開放,相容的世界。Delphi從桌面開發到主從架構都是使用自己的資料傳遞格式以及通訊傳遞架構,到了多層架構雖然使用了Windows平台上的通訊協定,例如COM/DCOM/COM+,但是在傳遞的資料格式方面仍然是使用自己的架構,COM/DCOM/COM+也是MS專屬的通訊協定,和其他平台上使用的通訊協定也不一樣。當然,不光是Delphi/BCB,大部份的開發工具也是採用類似的方式,那就是都支援桌面開發,主從架構或是多層架構等通用觀念的架構,但使用來傳遞資料和溝通通訊協定都是封閉的架構。
直到JSON和REST的出現以及Delphi/BCB確定走向原生,跨平台的道路之後,Delphi/BCB從2010版便開始走向以JSON封裝資料,以REST做為通訊架構的方向。因此Delphi/BCB除了仍然支援原有的資料封裝格式以及通訊協定之外,也允許開發人員選擇使用JSON和REST,使用JSON和REST的好處是除了可以讓JSON和REST擁抱最新的資訊技術之外,也可以讓Delphi/BCB在不同的平台中使用相同的技術來開發主從架構,分散式多層以及Web應用,也可以更容易的和其他的程式語言,框架和技術整合在一起。
現在讓我們重溫舊夢一下,看看如何把一個簡單的主從架構應用程式轉換為使用JSON的架構。

    主從架構

下圖是一個簡單的主從架構的主表單,

它藉由下圖的dbExpress元件從資料庫的FishFacts資料表中取得資料,並且使用資料感知元件顯示在應用程式的主表單中。

雖然從BDE到dbExpress都使用專屬的格式封裝資料,但BDE和dbExpress也可以把資料封裝成較開放的XML格式,因此要把上圖中TClientDataSet中的資料轉換為XML的格式,我們只需要存取它的XMLData特性值即可:

dssmFishFact.cdsFishFact.XMLData;

XMLData特性值會回傳以下面格式封裝的XML資料:

<?xml version="1.0″ encoding="UTF-8″ standalone="yes"?>  <DATAPACKET Version="2.0″><METADATA><FIELDS><FIELD attrname="Category" fieldtype="string" WIDTH="15″/><FIELD fieldname="Species Name" attrname="Species_Name" fieldtype="string" WIDTH="40″/><FIELD fieldname="Length (cm)" attrname="Length__cm_" fieldtype="r8″/><FIELD attrname="Length_In" fieldtype="r8″/><FIELD attrname="Common_Name" fieldtype="string" WIDTH="30″/><FIELD attrname="Notes" fieldtype="string" WIDTH="50″/><FIELD attrname="Graphic" fieldtype="bin.hex" SUBTYPE="Binary" WIDTH="1″/><FIELD fieldname="Species No" attrname="Species_No" fieldtype="r8″/></FIELDS><PARAMS LCID="0″/></METADATA><ROWDATA><ROW Category="Triggerfish" Species_Name="Ballistoides conspicillum" Length__cm_="50″ Length_In="19.68503937007874″ Common_Name="Clown Triggerfish" Notes="Also known as the big spotted triggerfish.  Inhabi"…

然而BDE/dbExpress雖然能夠把資料封裝成XML格式,但使用XML封裝資料時仍然會因為不同的資料存取使用不同的XML元素來封裝資料,因此在交換資料時仍然會造成許多的困擾,而且使用XML格式封裝資料的成本比起JSON來要昂貴許多(XML使用較多元素,較為複雜的規則封裝資料所致)。

因此Delphi/BCB要支援JSON/REST技術,其中一個工作就是必須能夠把資料封裝成JSON的格式,因此從Delphi/BCB 2010版開始便在VCL和RTL中加入了許多和JSON相關的類別以執行這項工作。到了XE版Delphi/BCB基本上不但能夠把資料封裝成JSON的格式,甚至提供了REST的API允許Delphi,BCB和任何支援JSON和REST的用戶端和使用Delphi/BCB開發的DataSnap伺服器整合在一起,下圖敘述了Delphi/BCB XE版支援的架構:

OK,現在先讓我們看看Delphi/BCB XE如何能夠把資料封裝成JSON的格式,以及提供解析,處理JSON封包的相關類別。稍後我們再討論Delphi/BCB XE如何支援REST API的使用和呼叫。

其實要把dbExpress中的資料封裝成JSON非常的簡單,就以直覺來說,我們只需要一個單向,唯讀的資料集,這個單向,唯讀的資料集可以直接從dbExpress的資料集元件建立,接著再一一的從這個單向,唯讀的資料集中讀取資料,根據JSON規則封裝即可。

因此在VCL中提供了TDBXDataSetReader這個單向,唯讀的資料集,它可以從dbExpress資料集元件建立,接著在TDBXJSONTools類別中提供了TableToJSON類別方法,它接受一個TDBXReader物件為第一個參數,第二個參數為要從第一個參數中封裝的記錄筆數,最後一個參數則代表在TableToJSON執行完畢之後是否需要釋放第一個參數物件:

class function TableToJSON(const Value: TDBXReader; const RowCount: Integer; const IsLocalConnection: Boolean): TJSONObject; static;

因此要封裝範例FishFacts資料表中的2筆資料為JSON格式,我們就可以使用下面的程式碼來完成這個工作。

在下面的程式碼中我們首先藉由資料模組中的TClientDataSet元件建立TDBXDataSetReader元件,接著呼叫TDBXJSONTools類別的TableToJSON類別方法把資料封裝成JSON格式:

procedure TForm1.btnToJSONClick(Sender: TObject);

var

aDBXReader : TDBXReader;

aJSonObj : TJSONObject;

begin

aDBXReader := TDBXDataSetReader.Create(dssmFishFact.cdsFishFact, False);

try

aJSonObj := TDBXJSONTools.TableToJSON(aDBXReader, 2, False);

Memo2.Lines.Text := aJSonObj.ToString;

ParseData(aJSonObj);

finally

aJSonObj.Free;

aDBXReader.Free;

end;

end;

下圖是執行

dssmFishFact.cdsFishFact.XMLData;

之後得到的XML格式的結果:

而下圖則是藉由TableToJSON轉換為JSON格式的結果:

如果我們觀察JSON格式的結果可以看到DataSnap是以JSON物件封裝資料,而每一個欄位則是以JSON陣列來封裝:

{“table":[[“Category",1,0,0,15,16,0,0,false,false,0,false,false],[“Species Name",1,1,0,40,41,0,0,false,false,0,false,false]….

由於使用JSON格式封裝資料比較簡單而且在解析上也比XML容易,我們可以使用VCLJSON相關的類別很容易的解析出其中封裝的資料。例如下面的ParseData從前面TableToJSON建立的JSON物件中解析其中封裝的FishFacts資料表的資料:

procedure TForm1.ParseData(aJSONObj: TJSONObject);

var

iPair : Integer;

aPair : TJSONPair;

begin

for iPair := 0 to aJSONObj.Size – 1 do

begin

aPair := aJSONObj.Get(iPair);

if (aPair.JsonString.ToString <> ‘"Graphic"‘) then

Memo3.Lines.Add(Format(‘%s : %s’, [aPair.JsonString.ToString, aPair.JsonValue.ToString]));

end;

end;

下圖即是ParseData執行後的結果:

由於DataSnap可以使用JSON封裝資料,因此任何支援JSON的程式語言或是框架都可以處理DataSnap封裝的資料,這也代表任何支援JSON的用戶端都可以連結到Delphi/BCB XE建立的DataSnap JSON伺服器並且呼叫它提供的服務。

OK,現在我們瞭解了如何使用DataSnap封裝資料為JSON格式,因此我們現在可以很容易的把這個傳統的主從架構應用程式轉換為DataSnap JSON伺服器,如此一來我們就提供了如何把傳統主從架構架逐漸構轉換為分散式JSON架構的可能性。

我們的第一步是把這個主從架構應用程式轉換為DataSnap JSON伺服器,要如此做我們需要讓這個主從架構把資料以JSON的格式輸出,以便用戶端能夠存取,使用。


轉換主從架構應用程式為DataSnap JSON伺服器

為了輸出主從架構應用程式的資料,讓我們首先在這個主從架構專案中建立一個Server Module,如下所示。Server Module能夠自動把包含它的應用程式的資料或是服務輸出給用戶端使用。

由於我們現在需要把主從架構應用程式中的資料輸出以便讓用戶端應用程式能夠存取,因此我們需要把原本主從架構中資料模組中的dbExpress相關元件移動這個建立的Server Module中,接著在原本主從架構的資料模組中加入TDSServerTDSTCPServerTransportTDSServerClass元件,如下所示:

接著在TDSServerClass元件的GetClass事件處理函式中設定它的PersistentClass參數為Server Module中的TClientDataSet元件類別:

procedure TdmFishFact.dsscFishFactGetClass(DSServerClass: TDSServerClass;

var PersistentClass: TPersistentClass);

begin

PersistentClass := usmFishFact.TdssmFishFact;

end;

完成了這個簡單的工作之後,現在如果我們編譯並且再次執行這個主從架構應用程式,那麼現在它不但仍然可以做為傳統主從架構應用程式來使用,它現在也已經成為了一個DataSnap JSON伺服器,現在我們就可以建立一個DataSnap用戶端來連結它並且取得FishFacts的資料。

建立Delphi DataSnap用戶端

建立一個VCL Form應用程式專案,在主表單中加入如下的元件:

要連結前面的範例DataSnap伺服器,我們只需要加入TSQLConnection元件,並且設定它的特性如下:

特性

特性值

Driver

Datasnap

加入一個TDSProviderConnection元件,設定它的特性值如下:

特性

特性值

SQLConnection

SQLConnection1

ServerClassName

TdssmFishFact

Name

DSPCFishFact

加入一個TClientDataSet元件,設定它的特性值如下:

特性

特性值

RemoteServer

DSPCFishFact

ProviderName

dspFishFact

在上面的設定中關鍵的兩個設定是TDSProviderConnection元件的ServerClassName特性值必須設定為DataSnap伺服器中Server Module的類別名稱,以及TClientDataSet元件的ProviderName必須設定為Server Module中的TDataSetProvider元件。而在這個範例DataSnap伺服器中的Server Module的類別名稱就是TdssmFishFact,而Server Module中的TDataSetProvider元件名稱則是dspFishFact

下圖就是設定TDSProviderConnection元件畫面:

而下面則是設定TClientDataSet元件的物件檢視器:

設定好了之後只要再連結相關的資料感知元件就可以完成用戶端應用程式了。

現在如果我們執行範例主從架構應用程式兼DataSnap伺服器,再執行DataSnap用戶端應用程式,那麼我們可以看到類似下面的畫面:

上圖中範例主從架構應用程式兼DataSnap伺服器執行時既是傳統的主從架構,也是DataSnap伺服器,因此右下方的DataSnap用戶端應用程式執行之後才能夠從這個主從架構應用程式兼DataSnap伺服器取得FishFacts資料。

如何? 瞭解了dbExpress/DataSnap如何使用JSON封裝資料的原理之後我們就可以容易的把它轉換為DataSnap伺服器。讀者可以使用類似的方式在保留主從架構架構的同時又逐漸的把主從架構轉換為DataSnap的分散式JSON架構。

我們下次再談談Delphi如何支援REST API,如此一來我們就可以讓其他的JSON用戶端連結並且使用DelphiDataSnap JSON伺服器提供的服務,再見了。


4 則迴響

Hello world! 哈囉!

Welcome to WordPress.com. This is your first post. Edit or delete it and start blogging!

發表留言