Building CORBA applications with Delphi 8 and Janeva - Part 1 - by Pawe&#322 G&#322owacki

By: Pawel Glowacki

Abstract: This article shows necessary steps to build simple CORBA server and client applications with Borland Janeva 6.0 and Borland Delphi 8 for .NET.

The source code for this article can be downloaded from the BDN Code Central

Contents

Introduction

CORBA architecture is not a new technology. In 1989, the OMG (Object Management Group) was formed by some of the key IT market players to promote interoperability among different systems, and to avoid proprietary solutions. If you have never heard of CORBA, it is probably because it just works, and is already a matured technology for building distributed systems. The most complex, mission-critical software is typically based on the Common Object Request Broker Architecture, which allows for objects written in different programming languages, and executing on different operating systems, to communicate in a distributed fashion.

CORBA objects do not communicate directly. Between two arbitrary CORBA objects that want to communicate sits the ORB  Object Request Broker  which handles physical details of invoking a method of the remote CORBA object. The ORB is typically implemented as a set of client and server side libraries.

CORBA object are accessed through the distributed version of a pointer  the Interoperable Object Reference (IOR).

Borland provides VisiBroker the industry leading CORBA implementation  and Delphi  arguably the most powerful Integrated Development Environment  but unfortunately integration between this two products was never very strong.

This situation has changed since the release of Borland Janeva  the .NET version of the Borland VisiBroker ORB - and Borland Delphi 8 for the Microsoft .NET Framework. Finally Delphi developers have access to all features of the latest CORBA 2.4 standard, like Portable Object Adapters (POA) or Quality of Service (QoS), to name just two.

In this article we will be developing the SimpleStocks system. The CORBA server application will expose to CORBA clients one object with just one method  GetPrice  which will return current price for a given stock symbol.

Prerequisites

To compile and run sample applications described here, the Borland Delphi 8 Update 2 and Borland Janeva 6.0 must be installed. Installers for both products will make sure that Microsoft .NET Framework SDK 1.1 with added J# support is also installed.

The big picture

For objects written in a given programming language to communicate, there must a piece of shared knowledge that defines what methods client can call on the server, what types of parameters are expected and what kinds of exceptions can be raised.

In CORBA, this contract is described in the Interface Definition Language. The IDL is not a programming language. It is only used to define complex types and interfaces with methods. In a sense it is very similar to the interface section of the Delphi unit. The details of the implementation of the CORBA object cannot be expressed in the IDL. Implementation details are hidden and have to be defined in a particular programming language for which a mapping from IDL exists. Delphi 6 and 7 shipped with the IDL2PAS compiler that translates IDL constructs to Delphi syntax. However, this is not an option for Delphi 8, which has a lot of new language features. For example, existing IDL to Delphi mapping heavily uses typeless pointers, which cannot be used in the managed code world of the .NET.

Luckily, due to the very nature of the .NET Common Language Runtime (CLR), it is possible to integrate binary assemblies written in different programming languages to such an extent that types written in one .NET programming language  say C#  can be extended in a different language, for example Delphi for .NET.

Borland Janeva includes IDL-to-C# compiler (idl2cs.exe). With this tool we will be able to generate C# source code with all types necessary to implement CORBA server and client applications. Using C# compiler  which is a part of the .NET Framework  we will generate .NET assemblies that can be directly used from within Delphi code.

Before we start coding lets create a directory structure for our projects. Inside D8SimpleStocks directory we will have three subdirectories:

  • Shared for the IDL definition and the assembly that will be shared by server and client
  • Server for CORBA server project
  • Client for CORBA client project

Creating a shared assembly

Before getting into details of server and client applications lets define the contract that both client and server will adhere to. The IDL definition of our SimpleStocks system is listed below.


// File SimpleStocks.idl

module SimpleStocks
{
        interface StockMarket
        {
            float get_price(in string symbol);

        };

};

Listing 1: SimpleStocks.idl

This IDL file contains SimpleStocks module declaration, which is just a CORBA way of dealing with namespaces. Inside the module there is a definition of the interface StockMarket that corresponds to a class declaration in a given programming language used for the implementation. The StockMarket interface contains only one method get_price that returns float and accepts one input string parameter named symbol. The direction of a parameter in IDL must be defined explicitly, thats why the method parameter is prepended with in modifier. Notice the semicolons after closing curly brackets.

The bin directory of the Borland Janeva includes idl2cs compiler that generates C#-source code that provides definitions of different .NET types necessary to implement server and client applications.

Different idl2cs compiler switches controls details of the process of transforming IDL to C# source code.

Assuming that idl2cs compiler is on the path (typically located in the C:\Program Files\Borland\Janeva\bin) the following command should generate SimpleStocks.cs. The -servant switch enables generation of the server side supporting code SimpleStocksPOA.


idl2cs servant SimpleStocks.idl

Listing 2: The command to generate C# source code for a shared assembly (idl2cs_params.bat).

The generated SimpleStocks.cs source will contain all necessary type definitions to write CORBA server and client. Using the C# command-line compiler that is installed with the .NET Framework (csc.exe) the SimpleStocks.cs source can be compiled to a .NET assembly (SimpleStocks.dll), with the following command (assuming that csc.exe is on the path, typically found in the C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322).


csc /t:library /r:"C:\Program Files\Borland\Janeva\Bin\Borland.Janeva.Runtime.dll"
/r:"C:\Program Files\Borland\Janeva\Bin\Borland.Janeva.Services.dll" SimpleStocks.cs

Listing 3: The command to build shared assembly to be used in the server and in the client code (csc_params.bat).

Writing the server

Once a shared assembly SimpleStocks.dll is generated we can create a new Delphi 8 Console Application to host our CORBA server code D8SimpleStocksSrvIOR. In order to use types defined in the shared assembly a reference to it must be added to the CORBA server project. References to Borland.Janeva.Runtime and Borland.Janeva.Services must be added to the project as well. Even though the server project contains no code yet, lets compile it to see if project references can be successfully resolved. Compiling the project will generate some errors about identifiers redeclared that can be safely ignored.

Lets add a new unit  called uSimpleStocksImpl  which will host the implementation of the CORBA servant. TSimpleStocksImpl Delphi class derives from StockMarketPOA defined in the shared assembly.


unit uSimpleStocksImpl;

interface

uses
  SimpleStocks;

type
  TSimpleStocksImpl = class(StockMarketPOA)
  public
    function GetPrice(Symbol: string): single; override;
  end;

implementation

{ TSimpleStocksImpl }

function TSimpleStocksImpl.GetPrice(Symbol: string): single;
var i : integer;
begin
  Result := 0;
  if Symbol.Length > 0 then
  for i:=1 to Symbol.Length do
    Result := Result + Convert.ToInt16(Symbol[i]);
end;

end.

Listing 4: SimpleStocks servant class.

The implementation of the GetPrice method will use the ASCII codes of the characters of the symbol string passed as an argument. This is just for demonstration purposes. In the real life scenario, the servant class would retrieve price information from the database or some other system.

Now that we have the servant class, we can implement the actual CORBA server. This will include initializing the ORB with command line parameters, obtaining a reference to the RootPOA, creating a new SimpleStocksPOA, instantiating SimpleStocks servant, activating it on the SimpleStocksPOA, and activating the POA Manager.

In order to access a CORBA object running in the process space of the CORBA server, a client must use an Interoperable Object Reference (IOR). In this simple server implementation, the server will create a file (D8SimpleStocks.ior) with stringified IOR. The actual filename of the ior file will be read from the standard .NET configuration file, which has to have the same name as our executable and followed by .config.

In reality, the CORBA client application would obtain an IOR from the standard CORBA Naming Service or through Borland Smart Agent.

To add a configuration file to the project, we can use File | New | Other | Markup files | XML File Delphi 8 option, and add the following elements to a new XML file:


<configuration>
  <appSettings>
    <add key="FileNameIOR" value="C:\D8SimpleStocks.ior"/>
  </appSettings>
</configuration>

Listing 5: CORBA server application configuration file (D8SimpleStocksSrvIOR.exe.config).

The full source code of the server project is listed below:


program D8SimpleStocksSrvIOR;

{$APPTYPE CONSOLE}

{%DelphiDotNetAssemblyCompiler '..\sharedSimpleStocks.dll'}
{%DelphiDotNetAssemblyCompiler 'c:\program files\borland\janeva\bin\Borland.Janeva.Runtime.dll'}
{%DelphiDotNetAssemblyCompiler 'c:\program files\borland\janeva\bin\Borland.Janeva.Services.dll'}
{%File 'D8SimpleStocksSrvIOR.exe.config'}

uses
  System.IO,
  System.Configuration,
  SysUtils,
  CORBA,
  PortableServer,
  uSimpleStocksImpl;

var
  i, aParamCount: integer;
  args: array of string;
  aORB: ORB;
  aRootPOA: POA;
  aPolicies: array of Policy;
  aSimpleStocksPOA: POA;
  aSimpleStocksServant: TSimpleStocksImpl;
  aManagerId: array of byte;
  aFileNameIOR: string;
  aWriter: StreamWriter;
  aIORString: string;

begin
  try
   aParamCount := ParamCount;
   SetLength(args, aParamCount);

   writeln('D8SimpleStocks CORBA server started with '
     + IntToStr(aParamCount) + ' arg(s)');
   writeln('(' + ParamStr(0) + ')');

   // ParamStr(0) is always set to a filename of a program being executed
   if aParamCount > 0 then
   for i:=1 to aParamCount do
   begin
     args[i] := ParamStr(i);
     writeln('Param #' + IntToStr(i) + ': ' + args[i])
   end;
   writeln;

   aORB := ORB.Init(args);
   writeln('ORB initialized.');

   aRootPOA := POAHelper.Narrow(aORB.ResolveInitialReferences('RootPOA'));
   writeln('A reference to the root POA obtained.');

   SetLength(aPolicies,1);
   aPolicies[0] := aRootPOA.CreateLifespanPolicy(LifespanPolicyValue.PERSISTENT);
   writeln('Policies for persistent POA created.');

   aSimpleStocksPOA := aRootPOA.CreatePOA('SimpleStocksPOA',
     aRootPOA.ThePOAManager, aPolicies);
   writeln('SimpleStocksPOA created.');

   aSimpleStocksServant := TSimpleStocksImpl.Create;
   writeln('SimpleStocks servant created.');

   aManagerId := aOrb.StringToObjectId('SimpleStocksManager');
   writeln('The id for the servant will be ''SimpleStocksManager''');

   aSimpleStocksPOA.ActivateObjectWithId(aManagerId, aSimpleStocksServant);
   writeln('The servant with the ID activated on the SimpleStocksPOA');

   aRootPOA.ThePOAManager.Activate;
   writeln('POA Manager activated');

   aFileNameIOR := ConfigurationSettings.AppSettings['FileNameIOR'];
   if aFileNameIOR = ''
   then aFileNameIOR := 'C:\D8SimpleStocks.ior';

   aWriter := StreamWriter.Create(aFileNameIOR);
   aIORString := aORB.ObjectToString(
      aSimpleStocksPOA.ServantToReference(aSimpleStocksServant));
   aWriter.WriteLine(aIORString);
   aWriter.Close;
   writeln('SimpleStocks IOR written out to the file: ' + aFileNameIOR);

   writeln;
   writeln('Server is running.');

   aORB.Run;

  except
    on ex: Exception do writeln('Exception ' + ex.ToString);
  end;
end.

Listing 6: Delphi 8 SimpleStocks CORBA server program source.

When the SimpleStocksSrvIOR application is started, it prints out to the console the debugging information, generates a file with stringified IOR and waits for incoming requests.

Writing the client

SimpleStocks CORBA will be also be implemented as Delphi 8 Console Application  D8SimpleStocksCliIOR. After setting a default namespaces for the client application to BorlandPSO.CORBA.Demos we must add necessary project references to SimpleStocks.dll shared assembly and Borland.Janeva.Runtime.dll. Compilation at this stage will yield the same errors about redeclared identifiers, which can be ignored.

The next step will be to add the client project a configuration file, which is exactly the same like for the server, except for the filename, which should be changed to D8SimpleStocksCliIOR.exe.config. Both configuration files should point to the same filename, or otherwise client will not be able to talk to the server.

The full source code of the server project is listed below:


program D8SimpleStocksCliIOR;

{$APPTYPE CONSOLE}

{%File 'D8SimpleStocksCliIOR.exe.config'}
{%DelphiDotNetAssemblyCompiler 'c:\program files\borland\janeva\bin\Borland.Janeva.Runtime.dll'}
{%DelphiDotNetAssemblyCompiler '..\sharedSimpleStocks.dll'}

uses
  System.IO,
  System.Configuration,
  SysUtils,
  CORBA,
  SimpleStocks;

var
  i, aParamCount: integer;
  args: array of string;
  aORB: ORB;
  aCORBAObject: CORBA.Object;
  aStockMarket: StockMarket;
  aFileNameIOR: string;
  aReader: StreamReader;
  aIORString: string;
  aSymbol: string;
  aPrice: single;

begin
  try
   aParamCount := ParamCount;
   SetLength(args, aParamCount);

   writeln('D8SimpleStocks CORBA client started with '
     + IntToStr(aParamCount) + ' arg(s)');
   writeln('(' + ParamStr(0) + ')');

   // ParamStr(0) is always set to a filename of a program being executed
   if aParamCount > 0 then
   for i:=1 to aParamCount do
   begin
     args[i] := ParamStr(i);
     writeln('Param #' + IntToStr(i) + ': ' + args[i])
   end;
   writeln;

   aORB := ORB.Init(args);
   writeln('ORB initialized.');

   aFileNameIOR := ConfigurationSettings.AppSettings['FileNameIOR'];
   if aFileNameIOR = ''
   then aFileNameIOR := 'C:\D8SimpleStocks.ior';

   aReader := StreamReader.Create(aFileNameIOR);
   aIORString := aReader.ReadLine;
   aReader.Close;
   writeln('SimpleStocks IOR read from the file: ' + aFileNameIOR);

   aCORBAObject := aORB.StringToObject(aIORString);
   writeln('A reference to CORBA object destringified.');

   aStockMarket := StockMarketHelper.Narrow(aCORBAObject);
   writeln('A reference to SimpleStock object narrowed.');

   writeln;
   writeln('Delphi 8 SimpleStocks Client is ready.');
   write('Enter stock symbol name: ');
   readln(aSymbol);

   aPrice := aStockMarket.GetPrice(aSymbol);

   writeln('Current price for "' + aSymbol + '" is ' + FloatToStr(aPrice));

   writeln;
   writeln('Press <Enter> to exit...');
  except
    on ex: Exception do writeln('Exception ' + ex.ToString);
  end;
  Readln;
end.

Listing 7: Delphi 8 SimpleStocks CORBA client program source.

Testing

After both CORBA server and client applications are ready, we can start testing them. Both applications must be started from outside the Delphi IDE. The server application must be started first and running, before the client will be able to call its functionality.

Summary

CORBA support in Delphi was never as complete as the support in Java and C++. With the introduction of .NET versions of Delphi and Borland VisiBroker for .NET (Borland Janeva), this situation has changed. Now Delphi programmers have full access to all features of the latest CORBA 2.4 standard as implemented by Borland Enterprise Server 6.0 including POA, OBV and QoS, and also EJB components running on any J2EE 1.3 compliant application server, because the J2EE spec requires support for the IIOP protocol.

About the author

Paweł Głowacki (pawel.glowacki@borland.com) is Borland Certified Instructor and works as Enterprise Consultant for Borland Professional Services Organization (PSO) in the Netherlands. Paweł specializes in Delphi, CORBA and .NET.

References


Server Response from: ETNASC04