In previous blog post, I mentioned that the new stub generator framework had the features needed to also generate Delphi client stubs, to make it very easy to develop Delphi smart clients with full support for compile time type checking and IDE class/property assistance.

I did not expect to include it in the upcoming release of kbmMW, but I couldn’t help myself…. it had to be added to make a fairly complete initial stub generator release.

The stub generator will generate plain Delphi code, that require kbmMW smart client features to compile. But the code is complete, and contains all unit references needed. If you are using custom units containing objects, you will have to make those units available to the Delphi client developer along with the generated stub code.

The SimpleInvocation server will, in addition to OpenAPI support also support returning Delphi stub code directly via the REST interface.

Lets call it: http://localhost:888/myserver/delphi

This will return give you a dialog similar to this:

Saving it, the unit received will look like this:

unit uSMARTDEMO;

interface

uses
  kbmMWGlobal,
  kbmMWSmartUtils,
  kbmMWSmartClient,
  kbmMWClient,
  kbmMWClientDataset
  ,uObjects
  ,System.Generics.Collections
  ,kbmMWDateTime
  ,kbmMemTable
  ;

type
  // Name.......: SMARTDEMO
  // Version....: kbmMW_1.0
  // Abstract...: HTTP smart service supp. FastCGI
  // Description: Smart HTTP service (with optional FastCGI and URL rewrite support) which use annotations for publishing methods for both regular clients and REST clients.
  //-------------------------------------
  TSMARTDEMO = class
  private
     FClient:IkbmMWSmartClient;
  public
     constructor Create(const ATransport:TkbmMWCustomClientTransport); overload; virtual;
     constructor Create(const AConnectionPool:TkbmMWClientConnectionPool); overload; virtual;
     constructor Create(const AClient:IkbmMWSmartClient); overload; virtual;
     function StorePerson(const APerson:TPerson):Integer;
     function EchoPerson(const APerson:TPerson):TPerson;
     function EchoURL:string;
     function AddNumbers(const AValue1:Integer; const AValue2:Integer):Integer;
     function EchoString(const AString:string):string;
     function GetPersons:TObjectList<uObjects.TPerson>;
     function GetPerson(const id:Integer):TPerson;
     function ServerNow1:TkbmMWDateTime;
     function ServerNow2:TDateTime;
     function GetPerson3:TPerson3;
     function StorePerson3(const APerson:IPerson2):Integer;
     function StorePerson2(const APerson:TPerson):Integer;
     function ReverseString(const AString:string):string;
     function EchoBytes(const ABytes:TArray<System.Byte>):TArray<System.Byte>;
     function GetMemTable:TkbmMemTable;
     function ReverseStringFromConfig:string;
  end;

implementation

constructor TSMARTDEMO.Create(const ATransport:TkbmMWCustomClientTransport);
begin
     inherited Create;
     FClient:=TkbmMWSmartRemoteClientFactory.GetClient(ATransport,'SMARTDEMO');
end;

constructor TSMARTDEMO.Create(const AConnectionPool:TkbmMWClientConnectionPool);
begin
     inherited Create;
     FClient:=TkbmMWSmartRemoteClientFactory.GetClient(AConnectionPool,'SMARTDEMO');
end;

constructor TSMARTDEMO.Create(const AClient:IkbmMWSmartClient);
begin
     inherited Create;
     FClient:=AClient;
end;

function TSMARTDEMO.StorePerson(const APerson:TPerson):Integer;
var
   v0:variant;
begin
     v0:=Use.AsVariant(APerson,false);
     Result:=FClient.Service.StorePerson(v0);
end;

function TSMARTDEMO.EchoPerson(const APerson:TPerson):TPerson;
var
   v0:variant;
begin
     v0:=Use.AsVariant(APerson,false);
     Result:=Use.AsMyObject<TPerson>(FClient.Service.EchoPerson(v0));
end;

function TSMARTDEMO.EchoURL:string;
begin
     Result:=FClient.Service.EchoURL;
end;

function TSMARTDEMO.AddNumbers(const AValue1:Integer; const AValue2:Integer):Integer;
begin
     Result:=FClient.Service.AddNumbers(AValue1,AValue2);
end;

function TSMARTDEMO.EchoString(const AString:string):string;
begin
     Result:=FClient.Service.EchoString(AString);
end;

function TSMARTDEMO.GetPersons:TObjectList<uObjects.TPerson>;
begin
     Result:=Use.AsMyObject<TObjectList<uObjects.TPerson>>(FClient.Service.GetPersons);
end;

function TSMARTDEMO.GetPerson(const id:Integer):TPerson;
begin
     Result:=Use.AsMyObject<TPerson>(FClient.Service.GetPerson(id));
end;

function TSMARTDEMO.ServerNow1:TkbmMWDateTime;
begin
     Result:=Use.AsType<TkbmMWDateTime>(FClient.Service.ServerNow1);
end;

function TSMARTDEMO.ServerNow2:TDateTime;
begin
     Result:=FClient.Service.ServerNow2;
end;

function TSMARTDEMO.GetPerson3:TPerson3;
begin
     Result:=Use.AsMyObject<TPerson3>(FClient.Service.GetPerson3);
end;

function TSMARTDEMO.StorePerson3(const APerson:IPerson2):Integer;
var
   v0:variant;
begin
     v0:=Use.AsVariant(APerson);
     Result:=FClient.Service.StorePerson3(v0);
end;

function TSMARTDEMO.StorePerson2(const APerson:TPerson):Integer;
var
   v0:variant;
begin
     v0:=Use.AsVariant(APerson,false);
     Result:=FClient.Service.StorePerson2(v0);
end;

function TSMARTDEMO.ReverseString(const AString:string):string;
begin
     Result:=FClient.Service.ReverseString(AString);
end;

function TSMARTDEMO.EchoBytes(const ABytes:TArray<System.Byte>):TArray<System.Byte>;
var
   v0:variant;
begin
     v0:=Use.AsVariant(ABytes);
     Result:=Use.AsBytes(FClient.Service.EchoBytes(v0));
end;

function TSMARTDEMO.GetMemTable:TkbmMemTable;
begin
     Result:=Use.AsMyObject<TkbmMemTable>(FClient.Service.GetMemTable);
end;

function TSMARTDEMO.ReverseStringFromConfig:string;
begin
     Result:=FClient.Service.ReverseStringFromConfig;
end;

end.

As you may notice, all the methods in the SmartInvocation service that have been marked with the kbmMW_Method attribute has been made available in the stub code.

The stub generator automatically detected various other units which are required to compile, like kbmMemTable, and uObjects and added to the uses clause.

So how did we return the stub code from the REST interface?

In the unit containing the REST service, we simply added a function returning a string and made it accessible from REST.

     [kbmMW_Rest('method:get, path: "delphi", responseMimeType:"text/plain"')]
     function DelphiAPI:string;

The function implementation is pretty simple

// Return Delphi stub.
function TkbmMWCustomService2.DelphiAPI:string;
var
   unitName:string;
begin
     // Return Delphi client stub unit for all methods in this service marked with
     // kbmMW_Method attribute.
     // Add 'servers: [ "url1", "url2",.. "urln" ]' to ASettings if you want to
     // embed server location information in the comments of the returned unit.
     Result:=TkbmMWSmartDelphiStubGenerator.GenerateDelphi('',self,unitName);
     SetResponseFileName(unitName);
end;

Obviously you can call GenerateDelphi from any unit you wish, and you can reference any kbmMW Smartservice. The first argument to GenerateDelphi is an optional unit naming prefix. When nothing has been specified, the unitnames will start with ‘u’. In this case we will receive a file named uSMARTDEMO.pas since its the SMARTDEMO service that is being described in a Delphi client stub.

Lets try to use the generated code in a client. We create a standard VCL based client (kbmMW works fine with Firemonkey too for cross platform use). We add a client transport (in this case TkbmMWTCPIPIndyClientTransport), a client connection pool and some buttons that will activate the calls. In addition there is an edit box to type an IP address of the server we will want to connect to, if it is not running on the local host.

Then make sure to add uSMARTDEMO.pas to the uses clause of the unit. Now we have access to the stub code which makes it easy to call the Smart services hosted by the kbmMW application server.

The eventhandler for StorePerson 1 could look like this:

procedure TForm1.Button1Click(Sender: TObject);
var
   i:integer;
   person:IPerson2;
   demo:TSMARTDEMO;
begin
     Transport.Host:=eIP.Text;

     demo:=TSMARTDEMO.Create(Transport);
     try
        person:=TPerson2.Create;
        person.Name:='Frank Borland Jr';
        person.Address:='California';
        person.Age:=45;
        i:=demo.StorePerson3(person);
     finally
        demo.Free;
     end;
end;

The sample here instantiates a TSMARTDEMO instance with the transport we would like to use. We could have provided the connection pool or a Smart Client instead if we wanted to. The stub code supports all 3 variations.

Further this button shows how to send an interfaced object to the server.

The eventhandler for StorePerson 2 could look like this:

procedure TForm1.Button2Click(Sender: TObject);
var
   person:TPerson;
   i:integer;
   demo:TSMARTDEMO;
begin
     Transport.Host:=eIP.Text;

     demo:=TSMARTDEMO.Create(Transport);
     try
        person:=TPerson.Create;
        try
           person.Name:='Trudy Borland';
           person.Address:='California';
           person.Age:=45;
           i:=demo.StorePerson(person);
        finally
           person.Free;
        end;
     finally
        demo.Free;
     end;
end;

The slight difference is that we provide an object for the call. After the call we free the object. All completely normal Delphi code.

The eventhandler for Simple Calls could look like this:

procedure TForm1.Button3Click(Sender: TObject);
var
   dt1:TkbmMWDateTime;
   dt2:TDateTime;
   s:string;
   i:integer;
   demo:TSMARTDEMO;
   ba,ba1:TArray<System.Byte>;
begin
     Transport.Host:=eIP.Text;

     demo:=TSMARTDEMO.Create(Transport);
     try
        s:=demo.EchoString('abc');

        SetLength(ba1,5);
        ba1[0]:=99;
        ba1[1]:=98;
        ba1[2]:=97;
        ba1[3]:=96;
        ba1[4]:=95;
        ba:=demo.EchoBytes(ba1);

        i:=demo.AddNumbers(34,7);
        dt1:=demo.ServerNow1;
        dt2:=demo.ServerNow2;
     finally
        demo.Free;
     end;
end;

It shows various types being sent and received.

The eventhandler for GetPerson could look like this:

procedure TForm1.Button4Click(Sender: TObject);
var
   person:TPerson;
   person3:TPerson3;
   persons:TObjectList<TPerson>;
   demo:TSMARTDEMO;
begin
     Transport.Host:=eIP.Text;

     demo:=TSMARTDEMO.Create(Transport);
     try
        person:=demo.GetPerson(1);
        person.Free;

        person3:=demo.GetPerson3;
        person3.Free;

        persons:=demo.GetPersons;
        persons.Free;
     finally
        demo.Free;
     end;
end;

Again… just showing how to receive various types of objects (regular ones without use of kbmMWNullable, objects with use of kbmMWNullable and lists of objects).

Finally the eventhandler GetMemTable could look like this:

procedure TForm1.Button5Click(Sender: TObject);
var
   mt:TkbmMemtable;
   demo:TSMARTDEMO;
begin
     Transport.Host:=eIP.Text;

     demo:=TSMARTDEMO.Create(Transport);
     try
        mt:=demo.GetMemTable;
        mt.Free;
     finally
        demo.Free;
     end;
end;

This sample show how a memory table instance containing 5 rows produced on the server, can be returned from the server to the client.

Can it be easier to make advanced application servers in Delphi, which supports all sorts of clients via REST or native Delphi code? I don’t think so 🙂

So… if you like what you see, please share the word. Reshare blog posts and let others know about the product!

Essentially help us to help you 🙂

Loading

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.