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 🙂