The source code for this article can be downloaded from the
BDN Code Central
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.
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.
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
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).
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.
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.
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.
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.
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.