When Borland shipped Delphi 6 in May of last year, one of its new
features was support for SOAP - most notably in the form of Web
services. Borland Kylix 2 (for Linux) is now also available with the
same capabilities, and as I write this article, Borland has just
announced the Borland Web Services Kit for Java, which will enable
Borland JBuilder to create and consume Web services using WSDL and
SOAP. Finally, by the time you read this article I expect Borland C++
Builder 6 to be announced (or available) with the same SOAP and Web
services capabilities that Delphi 6 and Kylix 2 currently have.
Perhaps even a bit more, since SOAP is ever evolving.
In this article, I want to demonstrate the ease of use of Delphi 6
and Kylix 2 (two RAD tools) by developing a Web service in a Delphi 6
server (running on Win32) and consuming it in a Kylix 2 client
running on Linux.
What is a Service?
For the Web Service "engine," I'm using Delphi 6 Enterprise. You can
download a free 60-day trial edition of Delphi 6 Enterprise from
their Web site at www.borland.com/downloads (although you may not
deploy applications written with this trial edition, you can follow
the instructions and steps in this article to see how easy it is to
write your own Web services).
To create a Web service, start Delphi 6 Enterprise and from the main
menu do File | New and then select Other to get the so-called Object
Repository dialog. Go to the Web Services tab, which contains three
icons: one for a Soap Server Application, one for a Soap Server Data
Module, and one for the Web Services Importer. If you've registered
your copy of Delphi 6 Enterprise (the real version), you can download
a few additional tools and wizards, which among others result in a
fourth icon here, namely the Invocable Wizard (see Figure 1).
To start a new Web service, double-click on the Soap Server
Application icon, which gives a dialog (see Figure 2) in which we
will specify the Web server application that will contain our Web
service. Note that although the latest SOAP protocol specifies more
than just the HTTP protocol for communication (including FTP and
SMTP), Delphi mainly supports HTTP using the wizards from Figure 1,
and hence uses a simple Web server application as wrapper.
Figure 2 shows that we have a number of choices here. For a
real-world Web service that will be used (consumed) a lot, you should
look at ISAPI/NSAPI or Apache DLL possibilities. For debugging
purposes, you can start with a Web App Debugger executable (you can
always transform your Web server project to another target). For our
example, I want to select a simple CGI standalone executable because
I want to deploy the resulting Web service on the Internet, and since
it's only an example with this article, I don't reckon it will be
consumed that much (so I don't need to turn it into a DLL for
improved efficiency).
Web Module
Once we've made our choice, we can click
on the OK button and a new Soap Server
application will be generated, including
a Web module (the core of our Web server application). Save the Web
module in file SWebMod.pas and the project in D6WebService.dpr before
you continue. As you may have noted by now, the Web module already
contains three configured components, which form the basis of our Web
service.
From left to right, the HTTPSoapDispatcher is used to receive
incoming SOAP requests and dispatch them to another component
(defined by the Dispatcher property) that can interpret the request
and execute it. The latter will be done by the HTTP SoapPascalInvoker
component, which receives the incoming SOAP request, executes
(invokes) a Pascal method, and produces the response back to the
HTTPSoapDispatcher. But before the HTTPSoapPascalInvoker can actually
invoke the requested Pascal method, it checks to see if the method's
interface and implementation class have been registered (in the
invocation registry; we'll get back to this in a moment). While the
first two components are used when the Web service is actually
consumed, the third component - WSDLHTML Publish - is used to produce
the WSDL (Web Service Description Language) that defines the Web
service itself.
Invokable Wizard
The three components on the Web module have already been configured,
so you don't have to work on them to make your Web service function.
In fact, right after you've created the Web module and saved your
project files, you should continue with the next step, which involves
starting the Invokable Wizard from the Object Repository. Again, do
File | New | Other, go to the Web Services tab and double-click on
the Invokable Wizard. This will result in the Invokable Interface &
Class Wizard where we can define the (file)name of our Web service
engine (see Figure 3).
Because we're building an example Web service, I've designed a simple
engine which does nothing more than convert some input value to an
output value. For this example, I've implemented a little Roman
number conversion routine - from decimal to Roman and back. As a
result, I would like to use the name "Roman" (the editbox in the
upper-left corner). Typing Roman here seems to result in a sensible
value for everything except the Invokable class type combobox, which
offers us two choices: TInterfacedObject or TInvok ableClass. The
latter is easier to manage, so select the TInvokableClass here.
Finally, click on Generate to create two new units: RomanIntf.pas
with the IRoman interface definition and Roman Impl.pas with the
TRoman class that implements the IRoman interface.
Without the Invokable Wizard to generate the two units, you can write
the units Roman Intf.pas and RomanImpl.pas yourself (see the two
listings that follow), so with the trial edition you need to enter
more code.
IRoman interface
Both the definition of IRoman and implementation of TRoman are still
empty, of course, so now is the time to actually write some code. For
our Roman number conversion, I want two methods: IntToRoman and
RomanToInt. The first would get an Integer as argument and produce a
String as result; the second would get a String as input argument and
would produce an Integer as result. In Object Pascal, the IRoman
definition should look like Listing 1 (unit RomanIntf.pas).
The code inside the initialization section will register the IRoman
interface in the Invokable Registry (where the HTTPSoapPascalInvoker
will look for it later). Note that you needed to write only two lines
(the functions IntToRoman and RomanToInt), and that the generated
comments already suggest you should use the stdcall calling
convention.
TRoman Implementation
Once we've saved the RomanIntf.pas file again, we can move to the
RomanImpl.pas unit to finish the TRoman class. Since TRoman is
implementing the two methods from IRoman, we must copy these
declarations inside the class definition of TRoman. With only two
methods, that's easy, but with a few dozen it can be a bit messy.
Fortunately, Delphi offers us some support with Code Insight. Just
move to the class definition of TRoman in the code editor and click
Ctrl+Space to show the available methods of TRoman and IRoman. The
Code Insight window will start with a number of methods in Red -
these are the abstract methods from IRoman that need to be
implemented. Just select all the Red methods and press enter to
insert them all inside the class definition of TRoman.
Once the class definition is complete, you can generate empty
placeholders for the actual implementation by pressing Ctrl+C. This
will produce the skeletons for the IntToRoman and RomanToInt
functions (see Listing 2).
Again, the code inside the initialization section will register the
TRoman class in the Invokable Registry (where the HTTPSoapPas
calInvoker will look for it).
The actual implementation of IntToRoman and RomanToInt is rather
lengthy, and not important for the Web service topic itself, so I
haven't included it here in the listing of unit RomanImpl.pas.
Deployment
This is it. After you've saved your project files, you can compile
the Web service application. In order to use it, you must now deploy
it as a Web server application. For your convenience, I've done that
already, and the D6Web Service.exe is available to use as an exercise
on my Web site at www.eBob42.com/cgi-bin/D6WebService.exe
Note that this direct URL won't do anything. You actually have to
pass the additional PathInfo/wsdl to get the WSDL (automatically
produced by the WSDLHTMLPublish component; see Figure 4).
If you click on the link for WSDL for IRoman (or
http://www.eBob42.com/cgi-bin/D6WebService.exe/wsdl/IRoman) you get the
full WSDL for the IRoman interface.Now we'll move over to Linux and
start Kylix 2 Enterprise to write a Web Service consumer for this
application.
Kylix 2 Enterprise
If you don't have Kylix 2 Enterprise, you can download a 60-day trial
version from the Borland Web site. But you can also write a Delphi 6
client for the Web service using the same steps needed for the Kylix
2 client, so it shouldn't be hard to play along either way.
A Web Service consumer can be any kind of application. But to keep
things simple, let's just start a new default visual application in
Kylix. Save the form in file MainForm.pas and the project in RomanC
lient.dpr (or any other filename you wish to use now).
Web Service Importer
Remember the Web Services tab of the Object Repository in Delphi 6?
Kylix 2 has a similar one (although I haven't seen the Invokable
Wizard for Kylix yet). This time, we need to use the Web Service
Importer, since we must import the WSDL that defines the Web service
and generate an Object Pascal import unit that defines the interface
of this particular Web service. So, do File | New and go to the
Webservices tab of the Object Repository and double-click on the Web
Service Importer icon, which will result in the Web Services Import
wizard (see Figure 5).
We now need to specify the URL to retrieve the WSDL information for
our Web Service, (www.eBob42.com/cgi-bin/D6WebService.exe/wsdl/IRoman). Look at the options in the Advanced
tab or directly click on the Generate button to create the Web
Service import unit. This will also work with Web services that are
written in another development environment, although some
interoperability issues between SOAP implementations remain to be
worked on.
Save the generated import unit in file Roman.pas and add it to the
uses clause of the MainForm unit. The generated unit Roman.pas can be
seen in Listing 3:
Note that the arguments to the two methods, IntToRoman and
RomanToInt, have been declared as "const" now. Other than that, the
importation of the IRoman interface looks exactly as the original
IRoman interface that we defined using Delphi 6.
Now, drop two TLabel, two TEdit, and two TButton components on the
main form. One label/edit pair is for the normal decimal number, and
one label/edit pair is for the Roman numeral. Finally, one button
will convert the contents of EditInt from Int to Roman and another
button will convert the contents of EditRoman back from Roman to Int
again. We also need to drop a HTTPRIO component from the Web Services
tab of the Component Palette (see Figure 6 for the final layout of
the main form).
HTTPRIO
The HTTPRIO component is our gateway (using HTTP) to the Remote
Invokable Object. Although we have the Roman.pas import unit that
defines the capabilities of our Web Service, we must tell the HTTPRIO
component some information as well (such as the Service and Port).
For this, we need
to specify the WSDL URL again in the WSDLLocation property of the
HTTPRIO component. Once you've set this property value, you can open
up the combobox for the Service property, to give it the value
IRomanservice; and then for the Port property, to give it the value
IRomanPort (see Figure 7).
Once the HTTPRIO component is configured, we can write two event
handlers and a little code to actually use the available methods. To
use the HTTPRIO component, we can simply cast it to
the IRoman interface (actually, the correct phrase would be to
extract the IRoman interface from the HTTPRIO component, but the
effect is the same). The implementation of the two-button OnClick
event handlers inside the main form
is shown in Listing 4.
The Web service consumer client running on Linux can be seen in Figure 8.
A final nice thing about Delphi 6 and Kylix 2 is that although the former runs
on Win32 and the latter runs on Linux,
the source code of their CLX projects
is actually cross-platform (usually with minor changes to make it work). In
this case, it doesn't involve a single
change in the source code, so you
can take the RomanCli ent.dpr project
from Kylix 2 and compile it with Delphi
6 to produce a working client on Windows.
Conclusion
I hope I've shown you that it's easy to build Web services and SOAP
applications with Delphi 6 and Kylix 2 using available components and
wizards for both the server side and the client (consumer)
side. And with the addition of the Borland
Web Services Kit for Java (for JBuilder) and the forthcoming release
of C++Builder 6, we'll have a full range of Borland RAD tools and
languages on platforms supporting SOAP and Web services.
For interoperability with other vendors and their SOAP
implementations, Borland participates in the SOAP Interoperability
studies, which can be found on the Borland Website at http://soap-server.borland.com/WebServices
Author Bio
Bob Swart (aka Dr.Bob; www.drbob42.com) is an independent author,
trainer and consultant based in The Netherlands who has spoken at
Delphi and Borland Developer Conferences since 1993. Bob is co-author
of the Revolutionary Guide to Delphi 2, Delphi 4 Unleashed,
C++Builder 4 Unleashed, C++Builder 5 Developer's Guide, Kylix
Developer's Guide, and Delphi 6 Developer's Guide, as well as a
contributing author for numerous computer magazines.
drbob@chello.nl
Delphi 6 and Kylix 2 Web Services, by Bob Swart
WSJ Vol 02 Issue 02 - pg.56
Listing 1
{ Invokable interface declaration unit for IRoman }
unit RomanIntf;
interface
uses
Types, XSBuiltIns;
type
IRoman = interface(IInvokable)
['{01304E12-D6DE-46E8-8585-2D3660B7D9CC}']
// Declare your invokable logic here using standard Object Pascal code
// Remember to include a calling convention! (usually stdcall)
function IntToRoman(I: Integer): String; stdcall;
function RomanToInt(R: String): Integer; stdcall;
end;
implementation
uses
InvokeRegistry;
initialization
InvRegistry.RegisterInterface(TypeInfo(IRoman), '', '');
end.
unit RomanIntf.pas
Listing 2
{ Invokable implementation declaration unit for TRoman,
which implements IRoman }
unit RomanImpl;
interface
uses
RomanIntf, InvokeRegistry;
type
TRoman = class(TInvokableClass, IRoman)
public
// Make sure you have your invokable logic implemented in IRoman
// first, then use CodeInsight(tm) to fill in this implementation
// section by pressing Ctrl+Space, marking all the interface
// declarations for IRoman, and pressing Enter.
function IntToRoman(I: Integer): String; stdcall;
function RomanToInt(R: String): Integer; stdcall;
// Once the declarations are inserted here, use ClassCompletion(tm)
// to write the implementation stubs by pressing Ctrl+Shift+C
end;
implementation
{ TRoman }
function TRoman.IntToRoman(I: Integer): String;
begin
end;
function TRoman.RomanToInt(R: String): Integer;
begin
end;
initialization
InvRegistry.RegisterInvokableClass(TRoman);
end.
Unit RomanImpl.pas
Listing 3.
Unit Roman;
interface
uses
Types, XSBuiltIns;
type
IRoman = interface(IInvokable)
['{01304E12-D6DE-46E8-8585-2D3660B7D9CC}']
function IntToRoman(const I: Integer): WideString; stdcall;
function RomanToInt(const R: WideString): Integer; stdcall;
end;
implementation
uses
InvokeRegistry;
initialization
InvRegistry.RegisterInterface(TypeInfo(IRoman), 'urn:RomanIntf-IRoman', '');
end.
Unit Roman.pas
Listing 4
unit MainForm;
interface
uses
SysUtils, Types, Classes, Variants, QGraphics, QControls, QForms, QDialogs,
Rio, SOAPHTTPClient, Roman, QStdCtrls;
type
TForm1 = class(TForm)
HTTPRIO1: THTTPRIO;
Label1: TLabel;
Label2: TLabel;
EditInt: TEdit;
EditRoman: TEdit;
btnIntToRoman: TButton;
BtnRomanToInt: TButton;
procedure btnIntToRomanClick(Sender: TObject);
procedure BtnRomanToIntClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.xfm}
procedure TForm1.btnIntToRomanClick(Sender: TObject);
var
Int: Integer;
begin
Int := StrToIntDef(EditInt.Text,0);
EditRoman.Text :=
(HTTPRIO1 as IRoman).IntToRoman(Int);
end;
procedure TForm1.BtnRomanToIntClick(Sender: TObject);
var
Int: Integer;
begin
Int := (HTTPRIO1 as IRoman).RomanToInt(EditRoman.Text);
EditInt.Text := IntToStr(Int);
end;
end.
Unit MainForm.pas