說明:本文經過一些改動,糾正了一些問題,因為原文無法修改,只好重發。
不久前我收到幾位朋友發來Mail說明他們在按照本文所述進行WebService應用開發時碰到的一個問題:在用ISAPI方式編寫本文例子程式時發生AV錯誤。根據改進後的本例子程式修改了本文,請注意文中加粗部分內容。
--2002-8-17
本文將做一個略複雜的例子,實現通過 SOAP 傳遞自訂的資料類型。本例子的功能是在服務端通過 ADO 的資料訪問控制項取得資料表內容,然後將其通過 SOAP 傳遞到用戶端再顯示。
服務端:
1.New|WebServices|Soap Server Application ,如,與 Delphi 6 + Update 2 相比,除了左上方的表徵圖以外,完全相同:
選 Web App Debugger executeable 類型, CoClass Name 為:wadSoapDemo2 ,如:
確定後將自動提示是否要建立一個介面,如,確定即可開啟建立介面嚮導,如果要以後再增加介面,可以在 New|WebServices 中選擇 SOAP Server Interface 同樣可開啟建立介面嚮導:
2.建立介面嚮導如,輸入介面名:DataTable 即可產生一個 SOAP 服務端介面:
關於此嚮導的其它說明見《C++ Builder 6 BizSnap/SOAP/WebService(1) -- 一個 Hello world! 的例子》(以下簡稱《(1)》);
3.(注意:原文的這部分有錯,現在為修改後的)建立一個 DataModule ,放入四個資料庫控制項: ADOConnection1, ADODataSet1, DataSetProvider1, ClientDataSet1 ,其各屬性設定如下表:
| ADOConnection1 |
ConnectionString = "Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;Initial Catalog=Northwind;Data Source=raptor/neutrino"; LoginPrompt = false; |
| ADODataSet1 |
Connection = ADOConnection1; CommandText = "select FirstName, LastName from Employees"; |
| DataSetProvider1 |
DataSet = ADODataSet1; |
| ClientDataSet1 |
ProviderName = DataSetProvider1; |
完成後的 DataModule 如:
(關於這部分的補充說明):原用 dbExpress 在用於 ISAPI 時會出現“Unable load dbexpint.dll”的錯誤,所以改用 ADO 。另,因沒有 ADOClientDataSet 控制項,否則若是 BDE/dbExpress/IBExpress 則只需要兩個控制項即可。要把資料控制項放在單獨的 DataModule ,而不能放在 WebModule 中的原因見《Web 應用程式的執行過程 -- 談談 WAD/CGI/ISAPI 的區別》,本文例子僅供參考,建議在涉及資料庫操作的應用中,最好用 SOAP Server Data Module (如《C++ Builder 6 BizSnap(3) -- DataSnap 資料庫應用》)。
4.SaveAll , Unit1 命名為: MainWM , Unit2 命名為 Demo2DM , Project1 命名為: Demo2 , DataTable 不改名;
5.在介面單元的標頭檔(DataTable.h)中增加一個自訂的類 -- TDataSetPack ,如下:
class TDataSetPack : public TRemotable {private : int FCount; AnsiString FXMLData;public : __fastcall TDataSetPack( TCustomClientDataSet * aClientDataSet ) : TRemotable(), FCount( aClientDataSet->RecordCount ), FXMLData( aClientDataSet->XMLData ) { }__published: __property int Count = { read = FCount }; __property AnsiString XMLData = { read = FXMLData };};
自訂 SOAP 資料類型必須是從 TRemotable 類派生的,這一點與 Delphi 相同。其中 ClientDataSet 的 XMLData 屬性是從 Delphi 6 開始新增的。其實 XMLData 中已經包含了記錄數資訊, Count 屬性並不是必須的,這裡為了示範自訂 SOAP 資料類型的使用,所以加入這個屬性。注意:要在此標頭檔中加入:#include <DBClient.hpp>
5.定義及實現 GetEmployeeTable 函數,其方法與《(1)》中相同,下面是在介面標頭檔(DataTable.h)和單元檔案(DataTable.cpp)中的介面/類定義和我們加入的方法及其實現:
// DataTable.h__interface INTERFACE_UUID("{CF057C28-4130-4508-9F24-0BBD1C2CA5F0}") IDataTable : public IInvokable{public: virtual TDataSetPack * GetEmployeeTable( void ) = 0; // 新增方法};typedef DelphiInterface _di_IDataTable;// DataTable.cppclass TDataTableImpl : public TInvokableClass, public IDataTable{public: TDataSetPack * GetEmployeeTable( void ); // 新增方法 /* IUnknown */ HRESULT STDMETHODCALLTYPE QueryInterface(const GUID& IID, void **Obj) { return GetInterface(IID, Obj) ? S_OK : E_NOINTERFACE; } ULONG STDMETHODCALLTYPE AddRef() { return TInterfacedObject::_AddRef(); } ULONG STDMETHODCALLTYPE Release(){ return TInterfacedObject::_Release(); } /* Ensures that the class is not abstract */ void checkValid() { delete new TDataTableImpl(); }};// 新增方法的實現:// 如果是 CGI/ISAPI 應用程式,則需要建立 DataModule1 ,相應修改見後面說明// 開啟 ClientDataSet ,構造 TDataSetPack ,// 關閉 ClientDataSet 和資料庫連接// 返回結果TDataSetPack * TDataTableImpl::GetEmployeeTable( void ){// 如果是 CGI/ISAPI 則要此句// Application->CreateForm(__classid(TDataModule1), &DataModule1); DataModule1->ClientDataSet1->Open(); TDataSetPack * p = new TDataSetPack( DataModule1->ClientDataSet1 ); DataModule1->ClientDataSet1->Close(); DataModule1->ADOConnection1->Close( ); return p;}
除了方法的實現部分以外,其它部分與《(1)》基本上一樣。這個方法的實現功能,正如程式中的注釋說明的那樣,用於取得資料集並轉換為我們定義的資料類型後返回。
(關於這部分的補充說明):注意如果是 CGI/ISAPI 應用程式,其中的 DataModule1 是動態建立(原因如《Web 應用程式的執行過程 -- 談談 WAD/CGI/ISAPI 的區別》一文所述),所以相應的要把 Project|Source ( 即 Demo2CGI.cpp/Demo2ISAPI.cpp )中如下程式碼片段那樣將自動建立語句去掉。
Application->CreateForm(__classid(TWebModule1), &WebModule1);// 如果是 CGI/ISAPI 應用程式則不要此句// Application->CreateForm(__classid(TDataModule1), &DataModule1); Application->Run();
6.註冊介面及其實作類別的部分也與《(1)》相同,就不再贅述了。
7.編譯之即可產生: Demo2.exe ;
先運行一次 Demo2.exe ,完成註冊的工作後啟動 Web App Debugger 。開啟瀏覽器, 輸入 URL 為: http://localhost:1024/Demo2.wadSoapDemo2 即可看到一個標準的 SOAP 應用說明頁面,點擊進入相應連結即可看到相關的 WSDL ,在其中可以看到我們自訂的資料類型說明,如下面的 WSDL 片斷所示:
<types> <xs:schema targetNamespace="urn:DataTable" xmlns="urn:DataTable"> <xs:complexType name="TDataSetPack"> <xs:sequence> <xs:element name="Count" type="xs:int"/> <xs:element name="XMLData" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:schema> </types>
用戶端程式:
1.New|Application 建立一個一般 VCL 應用程式;
2.SaveAll , Unit1 命名為 ClnMain , Project1 命名為 Client ;
3.New|Web Services|Web Services Importer :
在中的URL中輸入: http://localhost:1024/Demo2.wadSoapDemo2/wsdl/IDataTable,
如果上面用瀏覽器可以看到正確的 XML 文檔的話,選擇“Next”後將產生匯入的結果,如:
其中有我們在服務端定義的資料類型 TDataSetPack 、介面 IDataTable 及其方法 GetEmployeeTable ,選擇完成即可產生介面單元;
4.SaveAll, IDataTable 單元不改名儲存,再在 ClnMain 中 #include IDataTable.h ;
5.在 Form 上放上一個 ClientDataSet, DataSource, DBGrid, Button, Label 等幾個控制項,其各屬性設定如下表:
| ClientDataSet1 |
全部預設 |
| DataSource1 |
DataSet = ClientDataSet1; |
| DBGrid1 |
DataSource = DataSource1; |
| Button1 |
Caption = "Fetch data"; |
| Label1 |
Caption = "Count:0"; |
完成後的 Form 如:
6.雙擊 Button1 輸入下面的程式:
void __fastcall TForm2::Button1Click(TObject *Sender){ TDataSetPack * p = GetIDataTable()->GetEmployeeTable(); Label1->Caption = AnsiString( "Count:" ) + IntToStr( p->Count ); ClientDataSet1->XMLData = p->XMLData;}
7.編譯運行,按 Button1 , DBGrid1 中將顯示服務端返回的資料集內容, Label1 中將顯示記錄數,如(說明,此圖仍為原來用 InterBase 時的資料);
這隻是一個簡單的資料庫訪問的例子,只能從服務端取回資料集, C++ Builder 6 中已經將 MIDAS/DataSnap 和 SOAP/WebService 結合,可以通過 SOAP/WebService 實現非常強大的資料庫操作能力,這將在以後的文章中介紹。
[Mental Studio]猛禽 Apr.30-02