Server Frequently Asked Questions


How can I convert a VCL RODA Server to a DLL Server?

I have a standard RODA Server application that works great. Now, I want to create a new version for a single user that has simple install and simple usage.

Step 1: Create a new RO DLL Server and add it to the current project group. Right click on the project group and select Add New Project ... In the ensuing dialog, select the Remoting SDK for Delphi tab and DLL Server from the options. Leave all of the default options except do not create a test client (check box lower left). You'll get a new project created that has only project source available.

Step 2: With the new project created, add all the files from the original server with the exception of that projects main form - we don't want forms in our DLL!

If you have built a standard RODA server, you'll have a datamodule that gets loaded before your servers main form. We need to emulate this in the DLL. I changed my standard DLL project code to read like this:

library EBHD;

{#ROGEN:MyLibrary.rodl} // RemObjects: Careful, do not remove! }

uses
  uROComInit,
  Windows,
  uRODLLServer,
  uROBinMessage,
  { My added unit files };

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


procedure ROProc(Reason:integer);
begin
  case Reason of
    DLL_PROCESS_ATTACH: begin
      DMServer := TDMServer.Create(nil);
      RegisterMessage(DMServer.ROMessage);
    end;

    DLL_PROCESS_DETACH: begin
      DMServer.Free;
    end;
  end
end;

begin
  DLLProc:=@ROProc;
  ROProc(DLL_PROCESS_ATTACH);
end.

Note that I removed the var section including the global BinMessage variable. I also removed the instantiation and freeing of BinServer in the DLL_PROCESS_ATTACH and DLL_PROCESS_DETACH areas respectively, and added the instantiation and freeing of my datamodule instead (the datamodule variable DMServer is declared as a global variable in the datamodule itself). So far, so good.

If you check your standard server datamodule, you'll see that it already contains a server component and a message component. Our DLL server has it's own server component called DLLServer, created in the uRODLLServer unit (check the initialization section of the unit). In other words, the standard server component is superfluous to requirements. That said, we only want to maintain one set of source code here. Time for some conditional compilation.

Step 3: Add a {$DEFINE DLL} statement at the top of the datamodule and then add some conditional statements. The idea is that we'll instantiate the standard ROServer in the standard datamodule only if the datamodule will not be used in a DLL. Here's my example code:

type
  TDMServer = class(TDataModule)
    DADriverMgr: TDADriverManager;
    ROMessage: TROBinMessage;
    InMemSessMgr: TROInMemorySessionManager;
    DAIBODriver1: TDAIBODriver;
    DAConnectionMgr: TDAConnectionManager;
    procedure DataModuleCreate(Sender: TObject);
    procedure DataModuleDestroy(Sender: TObject);
  public
{$IFNDEF DLL}
    ROServer: TROIndyHTTPServer;
{$ENDIF}
  end;

 ...

procedure TDMServer.DataModuleCreate(Sender: TObject);
{$IFNDEF DLL}
var
  ROMD: TROMessageDispatcher;
begin
  ROServer      := TROIndyHTTPServer.Create( Self );
  ROMD          := ROServer.Dispatchers.Add as TROMessageDispatcher;
  ROMD.Message  := ROMessage;

  ROServer.Active := True;
{$ENDIF}
end;

The declaration and instantiation of the server is simple enough but note also the declaration, instantiation and assignment of the TROMessageDispatcher.

In essence, that's it! To test your new DLL Server, select Run | Parameters from the Delphi main menu. Declare the client app that you'll use to test the DLL, set your breakpoints in the DLL and then run from within the IDE.


How can I get IP address of the remote client from the service code?

.NET

The Service class has a public property ServerChannel which returns the IServerChannelInfo interface reference. For IP-based channels this interface can be casted to INetworkServerChannelInfo interface which is defined as follows:

public interface INetworkServerChannelInfo : IServerChannelInfo
{
  IPEndPoint LocalEndPoint { get; }
  IPEndPoint RemoteEndPoint { get; }
}

So you can get access to both IP enpoints used by the current connection, local and remote. System.Net.IPEndPoint class allows to get corresponding IP adresses, port numbers and so on.
Sample code:

public virtual System.DateTime GetServerTime()
{
  var sci = ServerChannel as INetworkServerChannelInfo;
  if (sci != null)
  {
    Console.WriteLine(String.Format("Remote endpoint: {0}", sci.RemoteEndPoint.ToString()));
    Console.WriteLine(String.Format("Local endpoint: {0}", sci.LocalEndPoint.ToString()));
  }
  else Console.WriteLine("INetworkServerChannelInfo is not supported");
  return DateTime.Now;
}

Delphi

Use TRORemoteDataModule.OnGetDispatchInfo event:

procedure TNewService.RORemoteDataModuleGetDispatchInfo(
  const aTransport: IROTransport; const aMessage: IROMessage);
var
  tcpinfo: IROTCPtransport;
  httpinfo: IROHTTPTransport;
  client_address: string;
begin
  client_address := '';
  if Supports(aTransport, IROTCPtransport, tcpinfo) then begin
    client_address := tcpinfo.GetClientAddress;
  end;
  if client_address = '' then begin
    if Supports(aTransport, IROHTTPTransport, httpinfo) then begin
      client_address := httpinfo.Headers['REMOTE_ADDR'];
      if client_address = '' then client_address := httpinfo.Headers['HTTP_CLIENT_IP'];
      if client_address = '' then .... // check for others headers like REMOTE_ADDR, 
                                       //HTTP_CLIENT_IP, HTTP_X_FORWARDED_FOR (can be comma delimited list of IPs), 
                                       //HTTP_X_FORWARDED, HTTP_X_CLUSTER_CLIENT_IP, 
                                       //HTTP_FORWARDED_FOR, HTTP_FORWARDED
    end;
  end;
  //Log('Client ' + client_address + ' connected!');
end;

How can I log details (IP address, method name, parameters, duration, etc.) of every call made to a server?

Check Dispatch Notifier sample.


How can I shut down a server?

Set server channel's Active property to False.

The server will stop listening for client calls and its resources will be released in due course.


Why Delphi server can freeze during shutdown?

When Delphi server is shutting down during execution service method dead lock will happen and it will stay in memory. It is Delphi specific problem: you can't destroy 2 form in the same time because Delphi uses TMultiReadExclusiveWriteSynchronizer during destroying forms and datamodules.

When you close application, it destroys form/datamodule that contains server component. The server component can't be destroyed while exist at least one spawned thread. by other hand, spawned thread waits while service method will be completed. so it is deadlock.

There are several workarounds:

  • Free server in OnClose event like:
procedure TServerForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  ROServer.Free;
end; 
  • Put destroying of server component into finalization section like:
procedure TServerForm.FormCreate(Sender: TObject);
begin
   with TROHTTPDispatcher(ROServer.Dispatchers.Add) do begin
     Message := ROMessage;
     Enabled := True;
     PathInfo := 'Bin';
   end;
   ROServer.Port := 8099;
   ROServer.Active := True;
end;

procedure TServerForm.FormDestroy(Sender: TObject);
begin
   ROServer.Dispatchers.Clear;
end;

initialization
   ROServer := TROIpHTTPServer.Create(nil);
finalization
   ROServer.Free;
end.
  • Choose Simple object as an ancestor instead of RO SDK Remote Datamodule (that is used by default), for your _impl.

Why do I get an 'An instance of type ... was being created, and a valid license could not be granted for the type ...' exception when one of Remoting SDK or DataAbstract classes is instantiated?

Please double-check the licenses.licx file in your project.

If it uses strong names of the assemblies like in the example below:

RemObjects.SDK.Server.IpTcpServerChannel, RemObjects.SDK.Server, Version=..., Culture=..., PublicKeyToken=...

then you will need to remove version and culture information and leave the row in the format (,) as follows:

RemObjects.SDK.Server.IpTcpServerChannel, RemObjects.SDK.Server

It is usually better to use license definitions like the last one, with no version specified. In this case there will be no need to correct it to set correct assembly version information after future Remoting SDK or DataAbstract upgrades.

If there is no licenses.licx file in your project folder then you have to add it to the project manually and add there line in the format (,) as follows:

RemObjects.SDK.Server.IpTcpServerChannel, RemObjects.SDK.Server

and then set its build action to Embedded Resource.