Delphi/C++Builder Distributed Computing on Heterogeneous Platforms

 

Natalia Elmanova

 


Using distributed computing on different platforms is a very effective way to create information systems containing fail-over safe or load-balanced sets of services, which are easy to support and upgrade. This report is devoted to the possibilities of creating multi-platform distributed systems provided by Delphi and C++Builder.

The questions to be reviewed are:

1 Why is it advantageous to use distributed computing?

2 General ideas of distributed computing

2.1 Types of middleware services

2.2 Special middleware services

2.3 Databases for server registration

2.4 How clients interact with servers

3 Creating a DCE functionality server

3.1 The task description

3.2 Defining a server interface and generating stubs

3.3 Creating a functionality server

3.4 Creating a client applications

4 Creating a CORBA functionality server

4.1 Defining a server interface and creating a server

4.2 Creating a Windows client

4.3 Creating a client application portable to other platforms

5 Using middleware services

6 A few words about data access servers

 


1. Why is it advantageous to use distributed computing?

This section describes the problems of tuning and supporting data access, problems of the fail-over safety of information systems, and the ways of solving these problems, including the use of multi-tiering.

Any information system of enterprise consists of services available to their employees. As a rule, such service can be the file access, e-mail, Internet access, service for providing some special calculations, access to enterprise data, operation of some equipment, etc. Some of these services require special resources, for example, non-standard hardware or software.

Using corporate databases requires tools and services for providing data access. They contain tools for networking based on network protocols. In the case of SQL servers, they include DBMS client software, which contains a low-level data access API (for example, Oracle Call Interface). In many cases, data access can be realized in a high-level API (for example, Borland Database Engine, SQL Links, ODBC drivers), which simplifies using low-level data access API of DBMS client software or implements standard data manipulations if desktop databases are used.

As a rule, multi-user information systems contain data access libraries in user workstations along with client applications. It implies that such workstations must provide data access services for themselves. Therefore, they must contain all data access services and software for their functioning.

Such requirements complicate workstation specifications and increase the cost of information system as a whole (Fig. 1):

Fig. 1. A client/server information system

It should be mentioned that all data access software must be tuned. The client application must "know" where the database is, what its type (DBMS type, desktop database format) is, what network protocol must be used to achieve it, what language parameters are used for sorting data and creating indexes. This work may be time-consuming, especially if different types of workstations are used. It is noteworthy that not all components of such software can be included into the client application installation because they must be distributed complying with license agreements of their vendors.

It is only one of the possible examples of services, which requires time and resources to be installed and tuned. We can find many other such services (for example, calculating functions, generating reports, obtaining aggregate data, obtaining data from some equipment, etc.).

How can we save our time and resources in this case?

We can use extensive actions (for example, to buy reserve workstations, to engage an additional system administrator for tuning and maintaining services installed at workstations, etc.).

We also can use intensive actions. They are based on a general idea of creating new services for information system users. In a general case, these services can be operated by both Windows and non-Windows platforms.

 

2. General ideas of distributed computing

This section describes what middleware services are. It also describes some general concepts of distributed computing: what stubs, skeletons, proxies, and RPC are, and why it is convenient to use IDL. It also describes what directory services are , how to identify the necessary server and how to find it.

Distributed computing is based on the functionality servers. They are in the middle position between database servers or file system services and client applications. So they are also known as middleware servers implementing middleware services. Often they have no user interface. They can also be implemented for different platforms inside libraries, applications or services of operation system.

We can use different technologies to implement and use such services. In particular, we can use DCE specification (Distributed Computing Environment) developed by OSF (Open Software Foundation) and Inprise Entera as its implementation. We can use CORBA specification (Common Object Request Broker Architecture) developed by OMG (Object Management Group) and implemented in some products, for example, in Inprise VisiBroker. In these cases, we can use a variety of platforms for middleware services.

We can also use Microsoft DCOM (Distributed Component Object Model) or its extensions (for example, Inprise MIDAS) and implement middleware services as OLE Automation servers or Microsoft Transaction Server objects. But in this case, our list of available server platforms consists of only different Windows versions.

2.1. Types of middleware services

It was mentioned above that custom middleware services could implement many types of functionality, for example, generating reports, operating some apparatus, providing calculations, etc. As a rule, they require non-standard resources (for example, non-standard hardware or software).

One of the popular types of middleware services is a data access server. As a rule, it is a client of an SQL server, so it uses data access libraries. If we correctly divide "fat" database client functionality among a user application and a middleware service, it is possible to make a user application as a non-configurable "thin" client. All configuring such client is to inform it what the name (or digital identifier) of the data access server is, and what computer it must be running on (or on what computer a "thin" client can find the special directory service, whose task is to find a suitable implementation of the necessary server, Fig. 2).

Fig. 2. A three-tier information system with a data access server

Transaction monitors are data access servers too. They are used in information systems where data are stored in different databases (and, possibly, on different platforms) and where there are applications using distributed transactions (i.e. simultaneous data modifications in different databases). SQL servers support non-distributed transactions. Distributed transactions require support outside database servers, and transaction monitors provide this support. Typical transaction monitor is a data access server, which contains objects for distributed transaction processing.

It should be mentioned that any functionality server might implement a few services. Sometimes we can speak about different server objects, which can be created inside a server process.

2.2. Special middleware services

Most of distributed systems use some special middleware services that provide their proper functioning. Their names and terms used for their description depend on specifications. COM, CORBA and DCE use different names and terms for them.

Among them are services, which permit clients to gain access to servers according special rules. In the case of MS DCOM, it is Service Control Manager. In the case of using MIDAS and COM, it can also be OLEnterprise ObjectFactory or Borland Socket Server. If we use Inprise AppCenter as a monitoring tool in a distributed system, it can be AppCenter Agent. In the case of CORBA, it is OS Agent.

Such servers are designed to provide a secure access to middleware servers according to some sets of rules.

The next type of services is a directory service. It is a service that finds implementation of a middleware server for as client as a response to its request. It is often used in systems with a few similar functionality servers. Such services usually provide load balancing for them (Fig. 3):

Fig. 3. Using the Directory Service in a distributed system

If we use DCOM as it is, we do not have any directory service. If we use Inprise OLEnterprise, we can use the directory service - Business ObjectBroker (but it is not obligatory to use it). In the case of Entera we must use the directory service Entera Broker. If CORBA is used, we must also use the directory service - its name is OS Agent (VisiBroker Smart Agent, in the case of VisiBroker implementation of CORBA).

2.3. Databases for server registration

In a simple case, custom middleware servers can be started manually and wait for client requests. However, sometimes it is desirable that they should be activated on a client request and should be down if they are no longer necessary for clients.

In this case, we must have special databases, which store information on executables containing server implementations (computer name, directory name, executable name, implemented interfaces, etc.). In addition, we must have services to retrieve this information and activate the server executable when receiving a client request in accordance with rules distinctive for this specification and defined for this particular server.

In the case of COM, Windows Registry plays the role of such database - all COM servers must be registered, and using this database is necessary for COM. In the case of DCE and CORBA, such databases are not necessary, but in the case of CORBA they are actively used (they are known as interface repository and implementation repository, and, of course, they must not use any Windows specific tools and ideas such as Registry, because CORBA servers can exist on non-Windows platforms).

2.4. How clients interact with servers

All ways of interaction between the client and the functionality server are based on RPC and marshalling ideas. Marshalling is a data packet exchange between an object inside a client application (it can be called a client stub or a proxy) and another object inside server (it can be called a skeleton or a server stub). Terminology depends on a technology for implementing distributed computing.

The server stub is created inside the server process when the server receives a client request. It represents the client in the server process, so the server "thinks" that it works with its local object. There can be one or many such stubs - their quantity depends on the quantity of clients, rules of sharing server objects, etc. The client stub is created in the client process. It represents the server in the client process, so the client also "thinks" that it works with its local object. The client stub has the same list of methods as the corresponding server stub, but it never contains their real implementation. Instead of real implementation, it contains a special proxy code, for example, with API calls to libraries responsible for creating data packets, marshalling them to client using any possible way (for example, writing to sockets, etc.) and unmarshalling other packets with the results of requests and queries received from the server stub (Fig. 4):

Fig. 4. The client-server interaction

When the client closes connection with server, the corresponding pair of stub objects can be destroyed.

Many instruments for creating functionality servers contain utilities for automatic generation of the stub code based on IDL (Interface Definition Language) description. It should be mentioned that IDL dialects for DCE, COM and CORBA are slightly different. Delphi and C++Builder support CORBA IDL and COM IDL. In fact, IDL is a language-independent and platform-independent standard for server interface description.

Code generators for many languages are available with Entera. In the case of CORBA they are available for C++ and Java. If we create a COM or CORBA server with Delphi or C++Builder, appropriate code generation is possible when the type library is being created.

 

3. Creating a DCE functionality server

This section describes how to build a functionality server using IDL and Entera stub generators and how to create a client application for this server.

3.1. The task description

Let us create a function implementing, for example, some calculations (actually, it can implement any functionality). For example, let us calculate the sine function using series expansion. The Pascal code of it can be:

function sin1(x: Double): Double;
Var i:Integer; r,sl,xx:double;
Const DELTA=0.0001 ;
begin
 sl:=x; i:=1;
 r:=0; xx:=x*x;
 While (ABS(sl)>DELTA) do
 begin
  r:=r+sl;
  sl:=-(sl*xx/(2*i))/(2*i+1);
  inc(i);
 end;
 result:=r;
end;

C++ code for this function is:

double sin1(double x)
{
 int ii; double xx,r,sl,f,delta=0.0001;
 sl=x; ii=1; r=0; xx=x*x;
 f= fabs(sl);
 while (f>delta)
 {
  r=r+sl;
  sl=-(sl*xx/(2*ii))/(2*ii+1);
  f=fabs(sl);
  ii=ii+1 ;
 }
 return(r);
}

Let us create a simple Windows application to test it. Its main form must contain a button and TChart component with one line series created in it (Fig. 5):

Fig. 5. The user interface of the application which is to be split up into a functionality server and client

The code of the OnClick event handler for Button1 is:

procedure TForm1.BitBtn1Click(Sender: TObject);
VAR I:INTEGER;X:DOUBLE;Y:DOUBLE;
begin
 for i:=1 to 270 do
 begin
  x:=0.1*i;
  y:=sin1(x);
  Chart1.Series[0].AddXY(X,Y,FloatToStr(x),clWhite);
 end;
end;
end.

C++ Builder code for this event handler is:

void __fastcall TForm1::Button1Click(TObject *Sender)
{
 int i; double x1,y;
 for (i=1;i<271;i++)
 {
  x1=0.1*float(i);
  y=sin1(x1);
  Chart1->Series[0]->AddXY(x1,y,FloatToStr(x1),clWhite);
 }
}

3.2. Defining a server interface and generating stubs

Now let us split up this application into a functionality server for calculating the sine function, and a thin client drawing a function graph. In this case, we must define the server interface using DCE IDL.

[
uuid(cdf19a00-22c8-11d2-a36c-008048eb72de),
version(1.0)
]
interface a1{
double sin1(
[in] double x);
}

This code assigns UUID (Universal Unique Identifier) to the server. This UUID can be generated with the algorithm defined by OSF (we can use, for example, CoCreateGUID function of Windows API to obtain it). The server interface with our function is then declared (in common case there can be a few of them).

For creating the client and server applications, we must generate client and server stubs containing an RPC code. For example, the client stub must contain an RPC code instead of the real implementation.

In the case of Entera it can be done using the rpcmake utility. Its parameters in the case of Delphi client and Delphi server are:

rpcmake -d myserv.def -c delphi2.0 -s delphi2.0

In the case of C or C++ they are:

rpcmake -d myserv.def -c c -s c

Here -d is an IDL file name, -c is a programming language for which the client stub must be generated, and -s is programming language for which server stub must be generated. Of course, the server and client languages can be different.

Let us mention that at present time writing a stub code manually is used very rarely. Usually the automatic code generation is used for creating stubs.

3.3. Creating a functionality server

If we have chosen Delphi as a development tool for client and server, we shall obtain the following files: a1_c.pas (client stub code), a1_s.dpr (console application code for the functionality server), a1.pas - the file, in which we must implement our function, a1.inc - the interface section for the client application.

We need to insert the implementation code for our function into a1.pas:

unit a1;

interface

uses SysUtils, ODET3020;

{$include a1.inc}

implementation

function sin1(x: Double): Double;
Var i:Integer; r,sl,xx:double;
Const DELTA=0.0001 ;
begin
 sl:=x; i:=1;
 r:=0; xx:=x*x;
 while (ABS(sl)>DELTA) do
 begin
  r:=r+sl;
  sl:=-(sl*xx/(2*i))/(2*i+1);
  inc(i);
 end;
result:=r;
end; {sin1}
end. {unit}

Bold characters are used for the code that must be added manually.

Then we can compile the server from command prompt:

Dcc32 -b a1_s.dpr

We can also make ODET3020.pas available to our project. It is an interface to ODET3020.DLL, which is the library with Entera API.

If we have chosen C or C++ as a programming language for client and server, we shall obtain the following files: a1_c.c (client stub code), a1_s.c and a1_s.h (console application code for the functionality server), a1.c - the file, in which we must implement our function, a1.h - the header file for the client application.

We need to insert the implementation code for our function into a1.c:

USEUNIT("a1_s.c");
USELIB("odet30.lib");
//-----------------------------------------------------------------
double sin1(double x)
{
 int ii; double xx,r,sl,f,delta=0.0001;
 sl=x; ii=1; r=0; xx=x*x;
 f= fabs(sl);
 while (f>delta)
 {
  r=r+sl;
  sl=-(sl*xx/(2*ii))/(2*ii+1);
  f=fabs(sl);
  ii=ii+1 ;
 }
 return(r);
} 

Then we can compile the server. It should be mentioned that in this case it could be compiled with any C++ compiler (for any available platform).

If we want to develop the client application, we must create a configuration file to describe environment variables for the server:

DCE_BROKER=KLASEC12,16000
DCE_DEBUGLEVEL=DEBUG,DEBUG
DCE_LOG=server.log

We also must create a configuration file to describe environment variables for Entera Broker (it is the directory service that allows access to our server). It must be running somewhere in the network:

DCE_BROKER=KLASEC12,16000
DCE_DEBUGLEVEL=DEBUG,DEBUG
DCE_LOG=broker.log

Now we must run Entera Broker:

start broker -e broker.env

Thereafter we can create a batch file to start our server:

set odedir = c:\Openenv\Entera\TCP
start "IT IS A SERVER" a1_s -e server.env

Then we must create a configuration file for the client application (it allows the client to know where the directory service is running, and the client application must refer to it calling the Entera API dce_setenv function):

DCE_BROKER=KLASEC12,16000
DCE_DEBUGLEVEL=DEBUG,DEBUG
DCE_LOG=client.log

Now we can create the client application (it can be created using the same PC or, in a general case, using another PC).

3.4. Creating client applications

Client stub code in a1_c.pas or a1_c.c contains the sin1 function. But this function consists of Entera API calls for writing data into sockets instead of sine calculation by a series expansion.

Let us create the client application using this client stub. For this purpose, we must create a new project, add a1_c.pas (or a1_c.c), and refer to it and to odet3020.pas (odet30.lib) in the main form unit.

Now we can create the user interface similar to that shown at Fig. 5 and write three event handlers. In the case of Delphi they are:

unit sin_cln1;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Buttons, TeEngine, Series, ExtCtrls, TeeProcs, Chart;

type
TForm1 = class(TForm)
Chart1: TChart;
Series1: TFastLineSeries;
BitBtn1: TBitBtn;
procedure BitBtn1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);

private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;
implementation
uses a1_c, odet3020;
{$R *.DFM}

procedure TForm1.BitBtn1Click(Sender: TObject);
VAR I:INTEGER;X:DOUBLE;Y:DOUBLE;
begin
FOR I:=1 TO 270 DO
BEGIN
X:=0.1*I;
Y:=sin1(X);
CHART1.SERIES[0].ADDXY(X,Y,FLOATTOSTR(X),clwHITE);
END;
end;

procedure TForm1.FormCreate(Sender: TObject);
Var rv : integer;
msg : array [0 .. 200] of char;
begin
 rv := dce_setenv ('client.env', nil, nil);
 if (rv = 0) then
 begin
  dce_error (msg);
  MessageDlg('TCP Error: ' + msg, mtInformation, [mbOK], 0);
  PostMessage(Handle, WM_QUIT, 0, 0);
 end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
dce_close_env;
end;

end.

In the case of C++ they are:

//-----------------------------------------
#include "dceinc.h"
#include "myserv.h"
USEUNIT("a1_c.c");
USELIB("odet30.lib");
//-----------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
 dce_setenv("client.env",NULL,NULL);
}
//-------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
 dce_release();
}
//-------------------------------------------
void __fastcall TForm1::BitBtn1Click(TObject *Sender)
{
 int i; double x1,y;
 for (i=1;i<271;i++)
 {
  x1=0.1*float(i);
  y=sin1(x1);
  Chart1->Series[0]->AddXY(x1,y,FloatToStr(x1),clWhite);
 }
}
//-----------------------------------------

If we run our client application, we can observe the same result as shown in Fig. 5.

It should be mentioned that in this case we might compile the server by any compiler available for target platform, because, in fact, it does not have any user interface (only log files and console output). It also possible to generate a stub code for other programming languages.

It should be pointed out that we are able to include the client stub into an application for another platform (especially C++ stub). Of course, it is possible to generate a client code for other development tools (and even create Entera client in an MS Word document).

 

4. Creating a CORBA functionality server

This section describes how to build a functionality server portable to other platforms using C++Builder and VisiBroker and how to create a Windows client. It also demonstrates how to create a client application portable to other platforms.

Now let us solve the same task using CORBA.

Let us create a CORBA server, which provides the same calculations. It should be mentioned that methods for producing the server and client code are different for Delphi 4 and C++Builder 4 in this case. It should be emphasized that C++ is an industrial standard, it exists for many platforms, so we must use just C++Builder to create a code portable to other platforms.

4.1. Defining a server interface and creating a server

At first, we must create a new CORBA IDL file:

interface a1
{
double sin1(in double x);
} ;

To create the CORBA server, we must choose the CORBA server icon from the Multitier page of C++Builder Object Repository (Fig. 6):

Fig. 6. Choosing the CORBA Server icon from C++Builder Object Repository

Then we must create a console application without VCL - it allows us to compile the code generated with any other C++ compiler, including compilers for other platforms (Fig. 7):

Fig. 7. CORBA Server Wizard

Therefore, we must add our IDL file to the project created and compile it. Compiling the IDL file results in two units, sin1_s.cpp and sin1_c.cpp, with a stub and skeleton code.

Now we must create the implementation of our function. To do this, we can choose the CORBA Object Implementation icon from the Multitier page of C++Builder Object Repository. In the CORBA Object Implementation Wizard we must choose an interface name, and define the unit name and the implementation class name. In this example, we provide instantiation of the CORBA object when the application starts up. In this case, it will be available to receive client requests immediately after startup (Fig. 8):

Fig. 8. CORBA Object Implementation Wizard

Then we can review the updates that were made by Wizard. In particular, we must insert our implementation code into the method generated for calculating the sine function (Fig. 9):

Fig. 9. Inserting implementation code into the generated unit

Now we can compile and close our project.

Before creating the client, we must start VisiBroker Smart Agent - it is a CORBA directory service that allows access to CORBA servers. Now we can start our server application.

Now we can create client applications.

4.2. Creating a Windows client

The first client application can be a standard Windows application with the same interface as shown in Fig. 5. To create it, we must choose the CORBA Client icon from the Multitier page of C++Builder Object Repository.

In this example, we shall use static binding with the server object (it works faster). To provide it, we must choose the "Edit/Use CORBA object" option from the IDE menu. In the Use CORBA Object Wizard we must add the same IDL file to the project and answer the questions about the name of a new property of the VCL form, that must be addressed to call our server (Fig. 10):

Fig. 10. Defining properties for CORBA object access in a client application

Then we must create the user interface similar to that shown in Fig. 5 (it means placing a button and a TChart component and creating one series in it). The next step is to write the OnClick event handler for Button1:

void __fastcall TForm1::Button1Click(TObject *Sender)
{
 int i; double x1,y;
 for (i=1;i<271;i++)
 {
  x1=0.1*float(i);
  y=a1_1->sin1(x1);
  Chart1->Series[0]->AddXY(x1,y,FloatToStr(x1),clWhite);
 }
}

The result must be a sine function graph in the client application.

Of course, it is not a problem to write the same GUI client with Delphi (but this technology is slightly different).

4.3. Creating a client application portable to other platforms

Now let us create an application portable to other platforms. It must be a console application without using VCL. In this case we must check the "Console Application" option and uncheck the "Enable VCL" checkbox.

Other manipulations may be the same except writing the user interface code. The simplest way to create such interface in the console application is to write the output code directly in the project file:

//--------------------------------------------------------------
#include <corbapch.h>
#pragma hdrstop
//--------------------------------------------------------------
#include "sin1_c.hh"
#include <corba.h>
#include <condefs.h>
USEIDL("sin1.idl");
USEUNIT("sin1_c.cpp");
USEUNIT("sin1_s.cpp");
//--------------------------------------------------------------
#pragma argsused
int main(int argc, char* argv[])
{
try
{
// Initialize the ORB and BOA
CORBA::ORB_var orb = CORBA::ORB_init(argc, argv);
CORBA::BOA_var boa = orb->BOA_init(argc, argv);
a1_var a1_1 = a1::_bind("a1Object");

cout<<"Sine function table \n";
int i; double x1,y;
for (i=1;i<271;i++)
{
 x1=0.1*float(i);
 y=a1_1->sin1(x1);
 cout<<x1<<" "<<y<<"\n"; 
}
}
catch(const CORBA::Exception& e)
{
cerr << e << endl;
return(1);
}
return 0;
}
//-------------------------------------------------------------

In this case, we should obtain a table with the results of calculations. It is important to note that such client application can be compiled with any C++ compiler, particularly, for non-Windows platform.

5. Using middleware services

In the case of both CORBA and DCE we must use directory services (VisiBroker Smart Agent and Entera Broker correspondingly). If we have a few server implementations, both directory services provide load balancing for them. In addition, if the client application periodically connects to and disconnects from the server, they provide the fail-over safety - if one of the server implementations fails, its clients will be reconnected to other implementations.

How to test the existence of load balancing? The simplest way is to create slightly different server implementations. For example, let us create two implementations of our "sine" Entera server, and let one of them give a zero result instead of calculating sine. If we start both servers and one instance of client, after pressing a Draw button, we can obtain a picture similar to Fig. 11:

 

Fig. 11. The demo of load balancing in the case of using Entera Broker

This picture means, that every time when client attempts to call the remote function for calculating sine, it asks the directory service to find an implementation of this function. The directory service finds one of two implementations randomly, which results in two different sets of points in the graph. The first set consists of the real sine values provided by the first server implementation, and the second one consists of zero values provided by the second server implementation.

We can also create two different implementations of our CORBA server and run them. In this case, we can run a few instances of our CORBA client. A half of them will contain a graph similar to shown at Fig. 5, and other half of them will contain a graph with a straight line. The reason is that any client asks a directory service to find an implementation of the function only once, and after that it uses just the implementation found while this implementation is accesible.

But if we stop one of the server implementations and press the Draw button on the client, which was served by this particular server implementation, we can observe how a load balancing works. After a short time delay, we shall observe a graph similar to Fig. 12.

Fig. 12. The demo of fail-over safety

This graph means, that we have obtained also two sets of points from two server implementations. The first set of points was provided by the first server implementation. Then this server implementation was stopped. Afetr pressing the Draw button, client could not find a server. So it has sent a request to find another implementation to the directory service, and the directory service has returned a reference to another implementation, which provided the second set of points. Now the client uses the residuary server implementation. Thus, the directory services allow us to create a fail-over set of server implementations.

As it was mentioned above, using registration databases is not necessary for either DCE or CORBA. However, in the case of Entera we can use the AppCenter database. In the case of CORBA we can use implementation repository. The detailed instructions how to achieve an automatic server startup on demand are given in the C++Builder documentation.

 

6. A few words about data access servers

Data access servers are very popular types of middleware servers. What must we do to create a data access server portable to other platforms?

The popular way to create a data access server is to use MIDAS and to create remote DataModules as COM or CORBA objects. In this case, we can obtain COM or CORBA server, but this server can work only in Windows environment because it uses VCL, which is based on Windows API, and BDE, which is also a Windows library.

If we want to create a portable data access server, VCL and BDE are not to be used. What should be done instead?

Let us recall that all functionality of data access servers is a set of SQL queries and nothing else. BDE is a query generator for MIDAS servers. However, we can generate a query in a different way. For example, we can simply use a low-level API, such as Oracle Call Interface. For most of DBMS servers, such API exists for many platforms and contains a similar set of functions for all of them. Using such API is not more difficult than calculating sine.

In the case of Entera we also can create a text file with the queries and then use special Entera servers such as orastart, sqlstart, etc, to execute them. They execute queries stored in files, and they, undoubtedly, use low-level API too.

Therefore, using Delphi and C++Builder provides us with many ways to create distributed systems with different functionality servers on heterogeneous platforms.