What is the easiest solution for transparent remote communication with Delphi?

I have a two-level Delphi application for Win32 with a lot of business logic implemented in the god object, which I want to transfer to a separate service. This separate service should be accessed by several TCP / IP telnet clients.

How can I make the transition the easiest?

Exactly, I would like to keep this simplicity: I want to define each function only once. For example, if I want to add a PIN entry function to my application, I just need to define

 function Login(Username: string; PinCode: integer): boolean;

in any object on the server, then I can use it with clients without additional work.

In the worst case, I have to implement three functions instead of one. Firstly, the function object itself on the server, and secondly, the non-marshaller, which receives a text string from the network, unpacks it and checks for validity:

 procedure HandleCommand(Cmd: string; Params: array of string);
 begin
   ...
   if SameText(Cmd, 'Login') then begin
     CheckParamCount(Params, 2);
     ServerObject.Login(
       Params[0],
       StrToInt(Params[1])
     );
   end;
 end;

Thirdly, the marshaller, which, when calling the client, packs the parameters and sends them to the server:

function TServerConnection.Login(Username: string; PinCode: integer): boolean;
begin
  Result := StrToBool(ServerCall('Login '+Escape(Username)+' '+IntToStr(PinCode)));
end;

Obviously, I do not want this.

Until now, I have managed to get rid of the non-partisan. Working with Delphi RTTI, I wrote a universal unmarshaller that looks for a published method by name, checks the parameters, and calls it.

So now I can just add the published method to the server object, and I can call it from telnet:

 function Login(Username: string; PinCode: integer): boolean;

 > login john_locke
 Missing parameter 2 (PinCode: integer)!

? . , - , :

ServerConnection.Call('Login', [Username, Password]);

, , . , .

? , "GetFunctionList()" "GetFunctionPrototype (Name: string)" :

> GetFunctionList
Login
Logout
IsLoggedIn

> GetFunctionPrototype Login
function Login(Username: string; PinCode: integer): boolean;

, , , . : , , . !

- , :

procedure TServerConnection.GenericMarshaller(); assembler;
asm
  //finds the RTTI for the caller function, unwinds stack, pops out caller params,
  //packs them according to RTTI and sends to the server.
  //receives the result, pushes it to stack according to RTTI, quits
  //oh god
end;

function TServerConnection.Login(Username: string; PinCode: integer): boolean; assembler;
asm
  call GenericMarshaller
end;

, ( ), , -. , , , .

RPC, , IDL. Delphi IDL . OLE Delphi "safecall", . - -, EVERY SINGLE RPC:

function TServerWrapper.Login(Username: string; PinCode: integer): boolean;
begin
  try
    RealObject.Login(Username, Pincode);
  except
    on E: EOleException do
      if IndicatesDisconnect(E) then
        Disconnect;
      Reconnect;
      RealObject.Login(Username, Pincode);
  end;
end;

.

, ? ? Delphi?

+3
4

, , . RPC - -; , , , , (= > ) API.

. , , , , , ( !). , , .. .

- ( ) , : , n- - , , , . .

, , . , , , .

+7

DataSnap Delphi 2010 , Delphi XE. , , , DataSnap. RPC, , Delphi . , , - , . IDL, COM, , Delphi. REST/JSON , Delphi.

DataSnap - , - , , .

+5

, RemObjects SDK. , . , . /, - . .

+4
source

You can write a program that used RTTI to create the necessary units for you, and not for the entire code. I did this in the past with an ORM system containing thousands of tables ... it is faster and much easier to write a code generator that spits out classes and blocks needed to work with the system from a database schema.

Another advantage of this approach is that it is easier to test because it can be scaled with predictable behavior.

+2
source

Source: https://habr.com/ru/post/1769194/


All Articles