Delphi Clinic C++Builder Gate Training & Consultancy Delphi Notes Weblog Dr.Bob's Webshop
Bob Swart (aka Dr.Bob) - Medical Officer Dr.Bob's Kylix Kicks
 RemObjects SDK
See Also: more tool reviews for Delphi, C++Builder and JBuilder

Delphi 6 and 7 Enterprise allow us to build SOAP server applications. But you do not need the Enterprise versions to build SOAP applications, since a new product called RemObjects SDK (from RemObjects Software) allows you to produce multi-tier and SOAP applications even in the Professional version of Delphi.

RemObjects SDK
RemObjects SDK is made by RemObjects Software, and gives us the ability to build multi-tier applications (including web services) that offer a number of benefits over the technology found in Delphi such as increased performance and scalability. RemObjects SDK can be installed in Delphi 5, 6 or 7, but a Kylix version is in the works (or so I’ve been told). This is very significant for Kylix developers who currently have only one communication protocol available when building multi-tier applications, namely SOAP. And while SOAP is a standard and regarded by many (including Microsoft) as The Holy Grail as well as The Silver Bullet, it is by no means a high-performance standard. In fact, the SOAP envelopes (containing platform and language independent information) that are being sent back and forth from the client to the server are simply XML documents that have to be generated, parsed, marshalled, etc. for every request and response. This process is even slower than CORBA, which at least sends the request in an ORB binary format over the wire, and much slower than (D)COM.
Like I stated earlier, RemObjects SDK offers a more efficient way to build multi-tier applications. This is realised by the fact that RemObjects SDK supports both SOAP and a proprietary binary format as communication protocol. And if you implement your RemObjects server as a Smart Service, you can export the server (and expose your classes) using SOAP where needed, and using BIN where possible. Meaning that “regular” visitors can use the SOAP standard to communicate with your RemObjects server, but your special customers can use the much faster binary protocol. And the fact that RemObjects SDK will also be available for Kylix is very good (performance) news for developers who want to place their DataSnap or SOAP server on a Linux web server – but that’s a story for another day.

Installation and first use
Let’s see how easy (or hard) it is to install and use RemObjects SDK. A trial version is available for download from their website at http://www.RemObjects.com, so you are free to try for yourself. Installation is quite straightforward (you only have to take care not to use any special versions of Indy, otherwise you need to rebuild a package). After you’ve installed RemObjects SDK, there will be a new tab in the Object Repository with no less than seven icons. Six to start a new RemObjects Server Project, and one to create a specific RemObjects DataSnap Server Module. The latter can be used to combine RemObjects SDK with DataSnap and will also be covered at another time. The other six can be used to create different kinds of RemObjects server targets: from Apache, Apache 2, CGI, ISAPI/NSAPI (all based on a WebBroker web module) to a regular Windows or DLL project. The Windows executable can also be turned into an NT service with a simple checkbox option (really useful, since it means that nobody has to be logged in on the server machine to run the RemObjects server application itself).
Apart from those targets, it’s good to know that IntraWeb and ExpressWeb Framework (web server) applications can also host RemObjects servers.

Note that just about any application type can be a RemObjects clients, so the icons on the RemObjects SDK tab (of the Object Repository) are only used to create RemObjects server applications. Apart from the RemObjects SDK tab in the Object Repository, there’s also a new tab in the Component Palette with a number (18) of special RemObjects SDK components. These can be used to define the communication protocol for the messages (SOAP or BIN) and the transport (HTTP, TCP/IP, Windows messages and DLL channels). There are also two special DataSnap components and finally a “Powered by RemObjects” button that we’ll see in just a moment (nice idea).

You may have noticed that I mentioned a number of different transport protocols: HTTP, TCP/IP as well as Windows messages and DLL channels. The latter two are especially useful if your RemObjects server and client are running on the same machine. The idea behind using Windows messages is that there is really no need to use sockets in such a case, so why create an overhead if you don’t need them. And DLL channels are even more efficient, since there's nothing more involved than sending a TStream back and forth. Apart from those transport protocols, you can also implement your own transport channel (like UDP or Pipes) by implementing the IROTransportChannel interface. And apart from adding your own transport channel, you can even define your own communication message format (currently SOAP or binary), by implementing the IROMessage interface. I must admit I haven’t tried to implement either interfaces, yet, but it sure looks like a flexible plug-in architecture. Possible protocols that can be added this way include XML-RPC, Java RMI and .NET binary. Only time will tell if these possibilities will actually be implemented (although .NET binary protocol sounds like a candidate with a lot of potential for RemObjects SDK).

Conversion Server
As a quick demo, I want to use RemObjects SDK to build a CGI Server Project (one that I can deploy on my eBob42 website and is actually deployed by the time you read this article). If you want to play along, just double-click on the CGI Server Project icon, which will start the RemObjects CGI Server Project dialog. In this dialog, I can specify the project name (ConversionServer), the service library name (Conversion) and the Service Name (MetricConversions). Note that by default one Service will be added to my project, but I can always add more services later.

The message class combobox gives me the choice between TROSOAPMessage and TROBINMessage. The former will use SOAP as communication protocol, which means that anything will be contained in an XML document which is a standard, but not particularly fast or efficient. The alternative means that we use a binary format to send requests and receive responses. This is indeed a propriety format (I can hear you thinking), so it will only be able to communicate with applications that were also built using the RemObjects SDK (or who know how to interpret the binary format). Let’s select TROSOAPMessage for now, because I want to make sure that anyone – even those developers without RemObjects SDK on their machine – can use the MetricConversions service that I’m about to implement. But don’t worry, the binary message format will also be used – we’ll just have to add it explicitly ourselves (to turn the application into what is called a SmartService). After you’ve also specified the project directory and click on OK, a new project will be copied (from the Object Repository directory) to the specified location. Save the project as ConversionServer. The source code for the project main file is as follows:
  program ConversionServer;

  {$APPTYPE CONSOLE}
  {#ROGEN:Conversion.rodl} // RemObjects: Careful, do not remove!

  uses
    WebBroker,
    CGIApp,
    Unit1 in 'Unit1.pas' {WebModule1: TWebModule};

  {$R *.RES}
  {$R RODLFile.RES} // RemObjects: Careful, do not remove!

  begin
    Application.Initialize;
    Application.CreateForm(TWebModule1, WebModule1);
    Application.Run;
  end.
Note the two lines with the RemObjects SDK comments to warn you not to remove them. The RODL file contains the Remote Objects Definition Language that defines the services for our library. This is a human-readable XML file (much easier to read than a WSDL file for a standard SOAP service). Apart from the external RODL file itself, the RODLFile.res seems to contain the Windows binary resource version of the RODL file contents (so it’s linked in with your application). RemObjects SDK comes with a special ServiceBuilder utility (installed in the Delphi menu between the Tools and Windows menus) that can help you to work on the contents of the RODL file, and generate the corresponding Delphi source code as well as keep the RODLFile.res up to date.

ServiceBuilder
The ServiceBuilder can be started from the RemObjects menu in the Delphi IDE. It can be compared to a Type Library, in that we can use it to define new interfaces, methods, but also types and more. If you first start the ServiceBuilder dialog, it shows that the MetricConversions service already contains two sample methods: Sum and GetServerTime. Since you probably don’t want those, click on the button with the minus character right on top of the two methods to delete them. Now click on the plus button to add a new method. In fact, let’s add a few methods to convert meters to other (non-standard) distance units. The actual definition and implementation of these metric conversion routines can be found in the Delphi 7 unit StdConvs.pas, from which I want to use the distance unit values for Meters, Inches, Feet and Yards and Miles, and while we’re at it, also for Light Years. As a true European and user of the ISO standard, I want all my conversions to be either from or to the Meter, which yields the following 10 methods:

  ConvertMetersToInches
  ConvertInchesToMeters
  ConvertMetersToFeet
  ConvertFeetToMeters
  ConvertMetersToYards
  ConvertYardsToMeters
  ConvertMetersToMiles
  ConvertMilesToMeters
  ConvertMetersToLightYears
  ConvertLightYearsToMeters
Each of the above is a function taking a double argument (the input value), returning a double again – the converter value. Using the ServiceBuilder we can quickly define these 10 functions.

According to the information in StdConvs.pas, an inch is 0.0254 meter (or 2.54 centimeter). A foot is 12 inches, a yard is 3 feet, a mile is 5280 foot (or 1760 yards), and a lightyear is a lot more. Now, close the ServiceBuilder and hit Ctrl+F9 to compile the project. This will actually generate a number of additional source files for us (triggered by the included .rodl file). We get three new units that are generated and added to the project: Conversion_intf.pas, Conversion_Invk.pas and MetricConversions_Impl.pas. The first two units are for our conversion interface, while the last one implements our specific metric conversions service. Go to the MetricConversions_Impl.pas unit and implement the 10 metric conversion routines as follows (this takes only 10 lines of code since the skeletons are already in place). In the following listing, also pay attention to the uses clauses that clearly indicate which units belong to VCL, which units are RemObjects specific, and which units have been generated by the ServiceBuilder. A small thing, but helpful.
  unit MetricConversions_Impl;
  {----------------------------------------------------------------------------}
  { This unit was automatically generated by the RemObjects SDK after reading  }
  { the RODL file associated with this project.                                }
  {                                                                            }
  { This is where you are supposed to code the implementation of your objects. }
  {----------------------------------------------------------------------------}
  interface
  uses
    {vcl:} Classes,
    {RemObjects:} uROClientIntf, uROServer, uROServerIntf,
    {Generated:} Conversion_Intf;

  type
    TMetricConversions = class(TRORemotable, MetricConversions)
    private
    protected
      function ConvertMetersToInches(const Value: Double): Double;
      function ConvertMetersToFeet(const Value: Double): Double;
      function ConvertMetersToYards(const Value: Double): Double;
      function ConvertMetersToMiles(const Value: Double): Double;
      function ConvertMetersToLightYears(const Value: Double): Double;
      function ConvertInchesToMeters(const Value: Double): Double;
      function ConvertFeetToMeters(const Value: Double): Double;
      function ConvertYardsToMeters(const Value: Double): Double;
      function ConvertMilesToMeters(const Value: Double): Double;
      function ConvertLightYearsToMeters(const Value: Double): Double;
    end;

  implementation
  uses
    {Generated:} Conversion_Invk,
    StdConvs;
  {
  StdConvs defines the following constants for us:

    MetersPerInch = 0.0254;
    MetersPerFoot = MetersPerInch * 12;
    MetersPerYard = MetersPerFoot * 3;
    MetersPerMile = MetersPerFoot * 5280;
    MetersPerLightSecond = 2.99792458E8;
    MetersPerLightYear = MetersPerLightSecond * 31556925.9747;
  }

  procedure Create_MetricConversions(out anInstance : IUnknown);
  begin
    anInstance := TMetricConversions.Create;
  end;

  function TMetricConversions.ConvertMetersToInches(const Value: Double): Double;
  begin
    Result := Value / MetersPerInch
  end;

  function TMetricConversions.ConvertMetersToFeet(const Value: Double): Double;
  begin
    Result := Value / MetersPerFoot
  end;

  function TMetricConversions.ConvertMetersToYards(const Value: Double): Double;
  begin
    Result := Value / MetersPerYard
  end;

  function TMetricConversions.ConvertMetersToMiles(const Value: Double): Double;
  begin
    Result := Value / MetersPerMile
  end;

  function TMetricConversions.ConvertMetersToLightYears(const Value: Double): Double;
  begin
    Result := Value / MetersPerLightYear
  end;

  function TMetricConversions.ConvertInchesToMeters(const Value: Double): Double;
  begin
    Result := Value * MetersPerInch
  end;

  function TMetricConversions.ConvertFeetToMeters(const Value: Double): Double;
  begin
    Result := Value * MetersPerFoot
  end;

  function TMetricConversions.ConvertYardsToMeters(const Value: Double): Double;
  begin
    Result := Value * MetersPerYard
  end;

  function TMetricConversions.ConvertMilesToMeters(const Value: Double): Double;
  begin
    Result := Value * MetersPerMile
  end;

  function TMetricConversions.ConvertLightYearsToMeters(const Value: Double): Double;
  begin
    Result := Value * MetersPerLightYear
  end;

  initialization
    TROClassFactory.Create('MetricConversions', Create_MetricConversions,
       TMetricConversions_Invoker);
  finalization

  end.
After having written 10 lines of code in the skeleton that ServiceBuilder already created for us, it’s almost time to build the RemObjects server.

SmartService
Before we can build and deploy the project, we have to go to the Web Module (in Unit1.pas) and specify a value for the CustomLocation property of the ROMessage component. The value should be http://www.eBob42.com/cgi-bin/ConversionServer.exe/SOAP since I want to deploy it on my own website. Setting this option may not be necessary for an ISAPI (or stand-alone RemObjects) server, but apparently there’s a small bug in Delphi’s CGI framework that doesn’t pass the Request.Host and Request.URL values correctly. It doesn’t hurt, just something to think about. The Web Module that was generated for us has two components: apart from the ROMessage component (of type TROSOAPMessage), there is also the ROServer (of type TROWebBrokerServer). Using the TROSOAPMessage will make sure that my RemObjects CGI application uses SOAP as message format. However, if I also want to use the more efficient binary format, I need to drop a TROBINMessage component from the RemObjects tab of the Component Palette onto the Web Module.

We also need to add the TROBINMessage component to the Dispatchers property of the ROServer (otherwise it won’t be used). We can do this by clicking on the ellipsis next to the Dispatchers property, which will start the ROServer Dispatchers dialog:

Just add the TROBINMessage component. As you can see from the dialog, the SOAP message will be using the /SOAP pathinfo, while the binary message will be sent when using the /BIN pathinfo. Something to remember when we connect clients to it. I’ve compiled and deployed this RemObjects server on my eBob42 website, and you can request the WSDL (Web Service Definition Language) at http://www.eBob42.com/cgi-bin/ConversionServer.exe/SOAP. In fact, you can use this URL to import the web service with Delphi’s own WSDL Importer, and use the web service yourself.

RemObjects Client
Apart from importing the WSDL definition of our RemObjects server with Delphi’s WSDL Importer, I also want to show you how we can connect to a RemObjects server using the SOAP and BIN protocols and RemObjects’ own components. Like I said, any application can be a RemObjects client, so start a regular new Delphi application and drop five components from the RemObjects tab: a TROSOAPMessage, TROBINMessage, two TROBPDXHTTPChannels and one TRoPoweredByRemObjectsButton. The last one is only used to show the “Powered by RemObjects” button, of course. The two message buttons are used to communicate with their counterpart in the RemObjects server application. Since both need to connect to a different URL (one ending with /SOAP and the other ending with /BIN), we need two different TROBPDXHTTPChannel components that use HTTP to connect to a given TargetURL - http://www.eBob42.com/cgi-bin/ConversionServer.exe/SOAP for SOAP and http://www.eBob42.com/cgi-bin/ConversionServer.exe/BIN for the binary protocol. To finish the user interface, I’ve used a RadioGroup (with 10 options to allow us to select one of the ten possible conversions), two LabelEdits (to hold the input and output values), and three buttons, for three different connections.

The first button will use the import unit generated by Delphi’s WSDL Importer, and call GetMetricConversions to get a handle to our MetricConversions interface. The second button will use the RemObjects SOAP message format. In order to get our hands on the RemObjects server, we either need to copy the Conversion_intf.pas unit from our server project, or – if you do not have access to the source code of the RemObjects server – we need to import the RODL file in order to (re-)generate the same unit. Inside Conversion_intf.pas, we find the definition of the MetricConversions interface, as well as a CoMetricConversions class with one class method called Create. This class method can be used to create an instance of the RemObjects server – just like when using regular COM. Create has two arguments: the first expects something that implements the IROMessage interface (implemented by both the TROSOAPMessage and TROBINMessage components) and the second one expects something that implements the IROTransportChannel interface – which is done by the TROBPDXHTTPChannel components. So, to cut a long story short, in order for the second button to connect to the RemObjects server using SOAP, we must call CoMetricConversions.Create with the TROSOAPMessage component as first argument and the TROBPDXHTTPChannel component that points to the /SOAP TargetURL as second argument. Likewise, the third button will connect to the RemObjects server using the binary message format, so it will use the TROBINMessage as first argument to CoMetricConversions.Create and the TROBPDXHTTPChannel component that points to the /BIN TargetURL as second argument. The full implementation of the event handlers in the client form is as follows:
  unit ClientForm;
  interface
  uses
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
    Dialogs, StdCtrls, uROBaseConnection, uROProxy, uROBPDXTCPChannel,
    uROBPDXHTTPChannel, uROClient, uROSOAPMessage, uROBINMessage,
    uROPoweredByRemObjectsButton, ExtCtrls;

  type
    TForm2 = class(TForm)
      btnRemObjectsSOAP: TButton;
      ROSOAPMessage1: TROSOAPMessage;
      ROBPDXHTTPChannel1: TROBPDXHTTPChannel;
      btnBorlandSOAP: TButton;
      ROBINMessage1: TROBINMessage;
      btnRemObjectsBIN: TButton;
      RoPoweredByRemObjectsButton1: TRoPoweredByRemObjectsButton;
      ROBPDXHTTPChannel2: TROBPDXHTTPChannel;
      Conversion: TRadioGroup;
      ledInput: TLabeledEdit;
      ledOutput: TLabeledEdit;
      procedure btnRemObjectsSOAPClick(Sender: TObject);
      procedure btnBorlandSOAPClick(Sender: TObject);
      procedure btnRemObjectsBINClick(Sender: TObject);
    end;

  var
    Form2: TForm2;

  implementation
  uses
    ImportedService, uMetricConversions;
  {$R *.dfm}

  procedure TForm2.btnBorlandSOAPClick(Sender: TObject);
  var
    Value: Double;
  begin
    with GetMetricConversions do
    begin
      Screen.Cursor := crHourGlass;
      try
        Value := StrToFloatDef(ledInput.Text,0);
        case Conversion.ItemIndex of
          0: Value := ConvertMetersToInches(Value);
          1: Value := ConvertMetersToFeet(Value);
          2: Value := ConvertMetersToYards(Value);
          3: Value := ConvertMetersToMiles(Value);
          4: Value := ConvertMetersToLightYears(Value);
          5: Value := ConvertInchesToMeters(Value);
          6: Value := ConvertFeetToMeters(Value);
          7: Value := ConvertYardsToMeters(Value);
          8: Value := ConvertMilesToMeters(Value);
          9: Value := ConvertLightYearsToMeters(Value)
        end;
        ledOutput.Text := FloatToStr(Value)
      finally
        Screen.Cursor := crDefault
      end
    end
  end;

  procedure TForm2.btnRemObjectsSOAPClick(Sender: TObject);
  var
    Value: Double;
  begin
    with CoMetricConversions.Create(ROSOAPMessage1, ROBPDXHTTPChannel1) do
    begin
      Screen.Cursor := crHourGlass;
      try
        Value := StrToFloatDef(ledInput.Text,0);
        case Conversion.ItemIndex of
          0: Value := ConvertMetersToInches(Value);
          1: Value := ConvertMetersToFeet(Value);
          2: Value := ConvertMetersToYards(Value);
          3: Value := ConvertMetersToMiles(Value);
          4: Value := ConvertMetersToLightYears(Value);
          5: Value := ConvertInchesToMeters(Value);
          6: Value := ConvertFeetToMeters(Value);
          7: Value := ConvertYardsToMeters(Value);
          8: Value := ConvertMilesToMeters(Value);
          9: Value := ConvertLightYearsToMeters(Value)
        end;
        ledOutput.Text := FloatToStr(Value)
      finally
        Screen.Cursor := crDefault
      end
    end
  end;

  procedure TForm2.btnRemObjectsBINClick(Sender: TObject);
  var
    Value: Double;
  begin
    with CoMetricConversions.Create(ROBINMessage1, ROBPDXHTTPChannel2) do
    begin
      Screen.Cursor := crHourGlass;
      try
        Value := StrToFloatDef(ledInput.Text,0);
        case Conversion.ItemIndex of
          0: Value := ConvertMetersToInches(Value);
          1: Value := ConvertMetersToFeet(Value);
          2: Value := ConvertMetersToYards(Value);
          3: Value := ConvertMetersToMiles(Value);
          4: Value := ConvertMetersToLightYears(Value);
          5: Value := ConvertInchesToMeters(Value);
          6: Value := ConvertFeetToMeters(Value);
          7: Value := ConvertYardsToMeters(Value);
          8: Value := ConvertMilesToMeters(Value);
          9: Value := ConvertLightYearsToMeters(Value)
        end;
        ledOutput.Text := FloatToStr(Value)
      finally
        Screen.Cursor := crDefault
      end
    end
  end;

  end.
The final screenshot shows the client in action, connecting to the RemObjects server as deployed on the eBob42.com website, and converting 42 meters to 1653.5 inches.

Although it’s hard to measure, Delphi’s SOAP and RemObjects’ SOAP implementation should execute at about the same speed, but the RemObjects’ binary connection (sending binary messages back and forth) should be faster. Of course, for this small example (with a tiny message contents), the difference is hard to measure, especially if you consider the fact that the actual RemObjects server is a CGI executable that gets loaded and unloaded for every incoming request (and the time to do that will surely dwarf the time that is used to process the SOAP request). However, for more real-world situations, where bigger SOAP envelopes are used (as well as ISAPI DLLs for better performance at the web server), the performance increase of using the binary protocol versus the SOAP protocol will certainly be noticeable. I’m planning some more detailed experiments with RemObjects SDK versus DataSnap myself.

Support
RemObjects SDK is quite new, and to be honest, the documentation on the website isn’t always finished (there are a number of articles and tutorials that can use some more flesh). By the time of reading, this “issue” may well have been solved already, and this article tries to help you a little bit as well. The product does include a number of sample applications that provide insight to the inner workings, and guidelines how to proceed by yourself. However, if you ever encounter problems, it’s good to know that the support in the newsgroups is quick and good.

Conclusion
In short, I’m impressed by what RemObjects SDK can do, especially when it comes to flexibility (adding your own message formats or communication channel protocols) and efficiency. I can’t wait until the Kylix version comes out to I can try this in a cross-platform setting (and a .NET version will also be welcome, of course). If you’re only remotely interesting in this stuff, make sure to download the trial version and find out what it can mean for you. Feel free to share your experiences, good or bad. You know where to find me!
A RemObjects SDK full license can be purchased from RemObjects Software for US$250 (per developer). It comes with full source code for Delphi 5, 6 and 7. There are no runtime fees that you have to pay. A trial version is also available, so check the RemObjects Software website.

(Bob Swart)

This review was first published in the January/February 2003 issue of the UK-BUG Developer's Magazine
This webpage © 2002-2009 by Bob Swart (aka Dr.Bob - www.drbob42.com). All Rights Reserved.