Delphi Clinic C++Builder Gate Training & Consultancy Delphi Notes Weblog Dr.Bob's Webshop
Dr.Bob's Delphi Notes Dr.Bob's Delphi Clinics Dr.Bob's Delphi Courseware Manuals
 Dr.Bob Examines... #28
See Also: other Dr.Bob Examines columns or Delphi articles

An earlier version of this article originally appeared in Delphi Developer (Februari 2002). Copyright Pinnacle Publishing, Inc. All rights reserved.

Using Kylix 2 Web Services in Delphi 6
Two months ago, we used Kylix 2 Enterprise to build a special Euro-conversion Web Service with two methods: FromEuro and ToEuro. This time, we'll "consume" this web service by writing a client in Delphi 6 Enterprise as well as Kylix 2 Enterprise (with little changes).
Note that this article was written prior to Delphi 6 Update 2 (the version that makes importing/consuming Web Services even easier).

Internet Deployment
Before we start, I have a little surprise for you: the web service we've made last time is now deployed and actually available on the internet.The URL is http://www.drbob42.co.uk/cgi-bin/Euro42 and needs /wsdl in order to get the definition using the Web Service Description Language.I've deployed the web service on a Linux machine running Apache, but without Kylix installed, so this shows that you can easily deploy your web services out there on the internet (you obviously need to place it a cgi-bin directory and give it execute rights, but you don't need to deploy any additional files).

Web Services Importer
In order to build a web service consumer, we first need to generate an import unit that represents the interface of the web service (to describe what the engine can do).Remember how WSDL is used to describe the functionality of a web service (in a language and platform independent format).This means that in theory, we shouldn't even have to know (or care) that the web service that we want to use is actually implemented in Kylix 2.
To generate the import unit, we need to start Delphi 6 Enterprise, do File | New | Other and go to the Web Services tab of the Object Repository again (note that the steps are almost identical in Kylix 2 Enterprise, so you can use the steps that follow to create a Kylix client as well if you wish).This time, we need to select the Web Services Importer icon and click on OK.The Web Services Importer wizard contains two tabs: an Import tab and an Advanced tab.
The Import tab is the place where we can specify the URL of our web service (in case you want to dynamically retrieve the WSDL).Alternately, you can specify the location of a file that contains the WSDL description (remember how we saved the WSDL description in file IEuro42.wsdl or IEuro42.xml in the previous article).The benefits are clear: if you use the dynamic URL you are certain that you're always using an up-to-date definition to generate your Delphi import unit.If on the other hand the WSDL file contains some things that you feel you can and need edit manually (for example to edit or remove certain features), then the local WSDL file is a better option.I must admit I'm not fluent in WSDL, so I've always used the dynamic URL to retrieve the WSDL definition of a web service.You only need to retrieve it once to generate the import unit anyway.For our Euro42 example, I need to specify http://www.drbob42.co.uk/cgi-bin/Euro42/wsdl/IEuro in the WSDL editbox.

The Advanced tab is the place where you can specify some additional (advanced indeed) options, such as the choice to map the XML Schema string type as a Pascal WideString (the default) or a regular Pascal LongString. You also have a choice to change the Base Class of the generated import unit.By default, this is TRemotable, and you should obviously only change this into something else if you know what you're doing.

Import Unit
Basically, all you have to do in the Web Services Importer wizard is specify the location of the WSDL and click on Generate to create the import unit.For our IEuro interface, the generated import unit can be seen in Listing 1 (saved in file Euro.pas):

  unit Euro;
  interface
  uses
    Types, XSBuiltIns;

  type
    IEuro = interface(IInvokable)
      ['{2ACCD587-FF07-46B4-87BA-D590FF5C325A}']
      function FromEuro(const Currency: WideString;
        const Amount: Double): Double;  stdcall;
      function ToEuro(const Currency: WideString;
        const Amount: Double): Double;  stdcall;
    end;

  implementation
  uses
    InvokeRegistry;

  initialization
    InvRegistry.RegisterInterface(TypeInfo(IEuro), 'urn:Euro-IEuro', '');
  end.
Note the GUID (the first line in the IEuro interface declaration), which wasn't necessary on the Linux side using Kylix 2, but is required using Delphi 6.

Web Service Consuming
Once you have an import unit, you can create a new project (any project will do, but let's build a "normal" Windows GUI project first), save the main form in MainForm.pas and the project itself in Euro42.dpr, and then add the import unit Euro.pas to its uses clause of the main form of this new project.On the main form, I want to design a Euro-calculator: converting a certain amount of money in one currency to another currency.The great advantage of only using the 12 Euro currencies is that we're certain that the conversion rates won't change (Euro conversions are fixed).The downside is that the Euro itself will be legal tender as of 1-1-2002, and the national currencies will be useless not too long after that.In other words: the lifespan of this web service will be limited, but that's OK, it's only an example (albeit a real-world one).
On our mainform, I want to show the 12 currencies, and convert a certain give amount either to Euros or from Euros.A problem is that I can't remember anymore which 12 currencies are involved with the Euro.And this is a deliberate point that I'm trying to make: although the WSDL describes the methods and parameters of a web service, it does nothing to explain the semantics or effects of certain argument and parameter values.If you look at the FromEuro and ToEuro methods, you'll notice that the first argument in both cases must be the Currency, a string that identifies the target or source currency.And to anyone who wants to consume my Euro web service, I must explain which potential values of Currency are valid.I've listed them below (see Listing 2):

  const
     Finland = 'FIM';
     Italy = 'ITL';
     Netherlands = 'NLG';
     Spain = 'ESP';
     Belgium = 'BEF';
     Austria = 'ATS';
     Luxembourg = 'LUF';
     Germany = 'DEM';
     France = 'FRF';
     Ireland = 'IEP';
     Portugal = 'PTE';
     Greece = 'GRO';
Given these constants, I can fill a RadioGroup with the twelve strings.That will give the enduser the option of selecting one of the above mentioned currencies.

As you can see from Figure 3, apart from the RadioGroup (with the Columns property set to 3), I've used a TEdit for the input amount and a TEdit for the output (Readonly, no TabStop) as well as two buttons that will call the FromEuro and ToEuro methods.

HTTPRIO
This is the point where we actually start to use the web service.For that, we need a HTTPRIO component, which can be found on the Web Services tab.HTTPRIO stands for HTTP Remote Invokable Object (a remote object that we can invoke using only HTTP).
The HTTPRIO components needs the WSDL location again, assigned to the WSDLLocation property..And again, we can either specify the dynamic URL, or reference to a local file that stores the WSDL.Both will contain an actual reference where the web service can be found (in the service section of the WSDL document, that apart from the Service, also contains the Port and the soap:address which holds the location - using soap instead of wsdl).See listing 3

  <service xmlns="http://schemas.xmlsoap.org/wsdl/"
   name="IEuroservice">
    <port xmlns="http://schemas.xmlsoap.org/wsdl/"
     name="IEuroPort" binding="targetNamespace:IEurobinding">
      <soap:address
       location="http://www.drbob42.co.uk/cgi-bin/Euro42/soap/IEuro">
      </soap:address>
    </port>
  </service>
So again you can either specify the IEuro42.wsdl or IEuro42.xml file here (that we saved last time), or specify the WSDL Location itself, namely http://www.drbob42.co.uk/cgi-bin/Euro42/wsdl/IEuro
After we've assigned a value to the WSDLLocation property, we must then assign a value to the Service property, followed by a value for the Port property (in that order).For the Service property, we can select IEuroservice, and for the Port property we can select IEuroPort (in both cases, these values are the only choice).After these three properties, the HTTPRIO component can be used as if it's an implementation of the IEuro remote invokable interface.This can be done using the as statement in (HTTPRIO1 AS IEuro) to extract the IEuro interface.Listing 4 contains the implementation of the FromEuro and ToEuro methods using this technique:
  unit MainForm;
  interface
  uses
    Windows, Messages, SysUtils, Variants, Classes,
    Graphics, Controls, Forms, Dialogs, Euro,
    StdCtrls, ExtCtrls, Rio, SoapHTTPClient;

  type
    TForm1 = class(TForm)
      RadioGroupCurrency: TRadioGroup;
      BtnFromEuro: TButton;
      BtnToEuro: TButton;
      EditInput: TEdit;
      EditOutput: TEdit;
      HTTPRIO1: THTTPRIO;
      procedure BtnFromEuroClick(Sender: TObject);
      procedure BtnToEuroClick(Sender: TObject);
    end;

  var
    Form1: TForm1;

  implementation
  {$R *.dfm}

  procedure TForm1.BtnFromEuroClick(Sender: TObject);
  begin
    EditOutput.Text := FloatToStr(
      (HTTPRIO1 AS IEuro).FromEuro(
         RadioGroupCurrency.Items[RadioGroupCurrency.ItemIndex],
         StrToFloat(EditInput.Text))) + #32 +
       RadioGroupCurrency.Items[RadioGroupCurrency.ItemIndex];
  end;

  procedure TForm1.BtnToEuroClick(Sender: TObject);
  begin
    EditOutput.Text := FloatToStr(
      (HTTPRIO1 AS IEuro).FromEuro(
       RadioGroupCurrency.Items[RadioGroupCurrency.ItemIndex],
       StrToFloat(EditInput.Text))) + ' EUR'
  end;

  end.
Note that I've added a postfix of the actual converted currency in both cases (in the FromEuro method, the postfix is made up of the current selected string in the RadioGroup, in the ToEuro method, the postfix is just the string with EUR itself).
The result of a conversion of 42 Dutch Guilders to Euros can be seen in the final screenshot below:

Web Services and Efficiency
A final word on performance issues.Please note that it may take a while before you get a response when you first click on any of the two buttons.The delay is caused by the fact that a request SOAP request has to be put in a SOAP envelope, send over the internet to the web service (using HTTP).At the server machine, the web server CGI executable has to be started, it must unpacked the SOAP envelope, dispatch the SOAP request, execute the Object Pascal method, pack the result back in a SOAP envelope again, send it back to the web service consumer (again over the internet using HTTP) and then exit the web service CGI executable again.A more efficient approach for real-world web services will certainly be to deploy them a Apache DSO modules (see Kylix Developer's Guide for more details on writing Apache DSOs with Kylix), or as an ISAPI/NSAPI DLL using Delphi 6 for example.
Also, when you first click on the button, it looks as if some initialisation is done, so subsequent clicks will be faster.I can't really explain it, but my gut feeling (and experience) is that the first click is always much slower than any subsequent clicks (until you close and restart the client again).It probably has something to do with the HTTPRIO component and the interface that's available once we've made first use of it.

Kylix 2 Web Service Consumers
If you want to use Kylix 2 instead of Delphi 6 to build the Web Service client (consumer) then you need to change...nothing.Apart from the fact that a Kylix application is a CLX application, and we just build a VCL application (still the default in Delphi 6), there is no difference.In fact, I leave it as an exercise for the reader to build one (let me know if you're having problems, and I can send you the source code for a Kylix 2 consumer of the Euro42 web service).It's just that when using a Delphi 6 (on Windows) application to communicate with a Kylix 2 (on Linux) Web Service, you bring out the cross-platform nature of web services.Not the cross-language nature, yet (Delphi and Kylix both use Object Pascal), but we only have to wait a little while before JBuilder 6 ships with the Borland Web Services Toolkit for Java, which promises to create and consume web services that can also communicate with Delphi and Kylix.
And finally, you must remember that SOAP is still work in progress.And although it's nice for Delphi and Kylix to talk to each other, in the end much more environments on much more platforms should be able to talk with each other using Web Services and SOAP.And in order to check the interoperability of all these SOAP implementations, a special SOAP interoperability testing has been set up.Borland's efforts can be seen on the webpage at http://soap-server.borland.com/WebServices
It should be clear that SOAP, Web Services and their implementation in Borland development tools isn't finished, but has hardly started.We'll see lots of more SOAP in the future.I can promise you that! In the meantime, feel free to check out my website Dr.Bob's SOAP Bubbles at http://www.drbob42.com/SOAP

For more recent information on this topic, check out my Delphi 2010 XML, SOAP & Web Services courseware manual.


This webpage © 2002-2010 by Bob Swart (aka Dr.Bob - www.drbob42.com). All Rights Reserved.