Building CORBA applications with Delphi 2005 and Janeva 6.5 - Part 2 - by Pawe&#322 G&#322owacki

By: Pawel Glowacki

Abstract: This article discusses using Smart Agent and CORBA Naming Service in Delphi 2005 Janeva applications

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

The first part of this article discussed all necessary steps to build simple client and server applications in Delphi 8 for .NET Framework with Janeva 6. Delphi 2005 improves on CORBA support as compared to Delphi 8. Not only Janeva 6.5 ships in the same box, but there is also dedicated Janeva integration that simplifies many of the steps discussed in the previous article.

During startup, Delphi 2005 looks into the registry to see if Janeva is installed on the local system and if it finds it, Janeva Integration is loaded and is available for C#Builder and Delphi for .NET personalities of Borland Developer Studio.

Note: Janeva integration is not available for Delphi for Win32 projects, because Janeva can only work on the .NET platform.

If Janeva integration is present then Add CORBA Reference and Add J2EE Reference options should be available in the Project menu, and also from the current project context menu in the Project Manager.

Add J2EE Reference option make it easy to write .NET clients for J2EE Enterprise Java Beans (EJB) components deployed in a J2EE-compliant application server, like Borland Enterprise Server. This is very exciting technology, but this topic is outside the scope of this discussion. If you are interested in J2EE interoperability watch the BDNtv episode "Using Borland Janeva for native communication between .NET and J2EE or CORBA.

In Part 1 of this article the process of making a CORBA object running on the server available to a client application was intentionally simplified to use a "stringified" IOR reference. This approach is good for demos or for simple local network systems where both client and server have access to the same file system  for example a shared network folder. However in more realistic scenarios you would probably want to use some kind of naming service functionality. These means that client and server applications will use a third component to  at the server-side  publish information about the availability of a CORBA object running in the server process or  at the client-side  to retrieve the information on how to connect to a CORBA object.

Before going any further make sure that you have Smart Agent available to your application. If you have Delphi 6 or 7 installed, and during installation you had chosen to install CORBA support, then there are big chances that you have Borland VisiBroker 4.1 or 4.5 for C++ already installed. If not, you would need to download Borland Enterprise Server from its downloads homepage.

BES comes in two editions: AppServer and VisiBroker. The AppServer edition is the most powerful and includes VisiBroker functionality, which  among other things  contains VisiBroker Smart Agent (osagent.exe) and CORBA Naming Service (nameserv.exe). If you do not want to play with J2EE and you do not have plans to add J2EE References to your projects, then the VisiBroker edition will suffice.

The easiest way to check if Smart Agent is installed is to enter osagent at the command-prompt (assuming that it is on the path). When it starts, its icon appears in the system tray. Double-clicking on the icon should display the Smart Agent window, where we could check on which port Smart Agent is listening. The default Smart Agent port is 14000. Smart Agent uses UDP packets for communication. There could be more than one instance of Smart Agent running in the network to provide basic fail over functionality. Another interesting VisiBroker functionality is OAD, which stands for Object Activation Daemon, and allows for server applications to be started automatically when needed.

Building CORBA server with Smart Agent

Lets create a new Delphi for .NET console application project, save it under D2005_SimpleStocks_Agent_Srv and select Add CORBA Reference from the Project menu. This pops Select a CORBA IDL file dialog where we need to select an IDL file. To keep example code simple Im going to reuse SimpleStocks.idl file that was used in the Part I of this article. It is also very handy to keep all project artifacts together, so before selecting the IDL file it was copied to the project directory.

After hitting Open button the selected IDL file is processed by Janeva idl2cs compiler and then from the C# source a .NET assembly is generated and added to the project. References to Borland.Janeva.Services.dll and Borland.Janeva.Runtime.dll Janeva assemblies and a .NET standard XML configuration file are also added to the project. We had to do all these steps manually in Delphi 8, and now most of the work is done for us.

Janeva integration during adding a CORBA reference also added app.config file to a project.


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <!-- Enable Janeva configuration section. -->
  <configSections>
    <section name="janeva" type="Janeva.Settings, Borland.Janeva.Runtime"/>
  </configSections>
  <!-- Janeva configuration section. -->
  <janeva>
	<!-- Select the naming-service/context resolving mechanism here.
              Examples:
                 1) Naming Service:
                     <naming url="corbaloc::localhost:4219/NameService"/>
                 2) VisiBroker Smart Agent:
                     <agent enabled="true" port="14000"/>
        -->
	<agent enabled="true" port="14000"/>
  </janeva>
</configuration>

Information from this configuration file is read by Janeva at runtime. This is very handy because otherwise we need to pass this information manually to ORB.init method. Default app.config file added by Janeva Integration configures a project to use VisiBroker Smart Agent with its default port number 14000.

Coding the server is even easier then in the case of stringified IOR approach presented in the previous article. In fact the server code is exactly the same, but the code to convert CORBA object reference to string and to write it out to a file is no longer needed. Below is the complete source code for the server project. The implementation of the servant class TSimpleStocksImpl is the same as in the Part I of this article.


program D2005_SimpleStocks_Agent_Srv;

{$APPTYPE CONSOLE}

{%DelphiDotNetAssemblyCompiler 'SimpleStocks.dll'}
{%DelphiDotNetAssemblyCompiler 'c:\program files\borland\janeva\bin\Borland.Janeva.Services.dll'}
{%DelphiDotNetAssemblyCompiler 'c:\program files\borland\janeva\bin\Borland.Janeva.Runtime.dll'}
{%ConfigurationCompiler 'app.config'}

uses
  SysUtils,
  CORBA,
  PortableServer,
  uSimpleStocksImpl in 'uSimpleStocksImpl.pas';

var
  aORB: ORB;
  aRootPOA: POA;
  aPolicies: array of Policy;
  aSimpleStocksPOA: POA;
  aSimpleStocksServant: TSimpleStocksImpl;
  aManagerId: array of byte;

begin
  try
    aORB := ORB.Init(System.Environment.GetCommandLineArgs);
    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');

    writeln;
    writeln('Server ready');;
    aORB.Run;

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

Building CORBA client with Smart Agent

Now it is time to build a client for our SimpleStocks server. First steps are similar to the ones of building the server. Open the server project, right-click the "Project Group" node in the Project Manager and select "Add New Project". Add a New Console Delphi for .NET project. The important point here is to make sure that both server and client projects are saved in separate directories. Also the SimpleStocks.idl file was copied to the client project directory. Make sure to save the project before adding a CORBA reference. To prevent overwriting of assemblies generated from IDL files for different project, you may want to keep individual copies of IDL files in each project directory. Select Add CORBA Reference and choose the IDL file to be processed from the project folder. References to Janeva and generated SimpleStocks.dll assemblies are added to the project. The app.config file added by Janeva integration is exactly the same like in the server project.

So far so good, but watch out! There is the first obstacle on our way. The integration did not pass to idl2cs compiler the -bind parameter, and overloaded Bind methods were not added to generated StockMarketHelper class. This is critical to retrieving a reference to SimpleStocks CORBA object running on the server from the Smart Agent. To verify this finding just double-click SimpleStocks.dll in the Project Manager. This will launch Delphi 2005 integrated Borland Reflection, that helps inspect the content of arbitrary .NET assemblies.

Again we have to resort to the approach presented in the Part 1 of this article. Based on the SimpleStocks.idl file we are going to regenerate the SimpleStocks.dll used by the client project. There is no need to implement SimpleStocks servant on a client, so the only parameter passed to idl2cs compiler will be bind.


idl2cs bind SimpleStocks.idl

This will generate SimpleStocks.cs source file, which has to be compiled in order to generate SimpleStocks.dll to be used by the client project. The command-line for generating the .NET assembly remains exactly the same as in previous article.


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

The last step, which is not obvious, is to delete the SimpleStocks.dcpil file that was generated during adding a CORBA reference to a project. The Delphi for .NET compiler is generating a *.dcpil file for every .NET assembly added to a project, and this file is used during project compilation. Previously generated dcpil file does not contain bind code, and simple regeneration of the SimpleStocks.dll is not enough.If we manually delete this dcpil file, it will be recreated based on current content of SimpleStocks.dll during the next compilation.

The rest is simple. After ORB initialization, client code needs to call one of the overloaded StockMarketHelper.Bind methods to obtain a reference to StockMarket implementation.


program D2005_SimpleStocks_Agent_Cli;

{$APPTYPE CONSOLE}

{%DelphiDotNetAssemblyCompiler 'c:\program files\borland\janeva\bin\Borland.Janeva.Services.dll'}
{%DelphiDotNetAssemblyCompiler 'c:\program files\borland\janeva\bin\Borland.Janeva.Runtime.dll'}
{%ConfigurationCompiler 'app.config'}
{%DelphiDotNetAssemblyCompiler 'SimpleStocks.dll'}

uses
  SysUtils,
  CORBA,
  SimpleStocks;

var
  aORB: ORB;
  aManagerId: array of byte;
  aStockMarket: StockMarket;
  aSymbol: string;
  aPrice: double;

begin
  try
    aORB := ORB.Init(System.Environment.GetCommandLineArgs);
    writeln('ORB initialized.');

    aManagerId := aOrb.StringToObjectId('SimpleStocksManager');
    writeln('The object id created');

    aStockMarket := StockMarketHelper.Bind('/SimpleStocksPOA', aManagerId);
    writeln('Bound to StockMarket implementation');

    writeln;
    writeln('Delphi 2005 SimpleStocks SmartAgent 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...');
    readln;
  except
    on ex: Exception do writeln('Exception ' + ex.ToString);
  end;
end.

Running it all

Before running the server and client applications, make sure that Borland VisiBroker SmartAgent is running somewhere in the local subnet (or even in your local machine) and is using the same port number as specified in server and client configuration files.

To start Smart Agent just type osagent at the command prompt or alternatively start it from the BES VisiBroker program group. When Agent is already running, you should be able to see a new little icon in the Windows System Tray

Next  outside the IDE  start server

 and then client

Using CORBA Naming Service

CORBA specification defines a number of common object services to make life easier for programmers, so they do not have to reinvent the wheel every time they need to use certain functionality like naming, events, transactions, time, security or even more specialized features like Telecoms Log Service. Think of it as having only Delphi compiler without the VCL component library. Horror! From the technical perspective there is no difference between a component that you have written and installed in Delphi, and components that were provided by Borland, and preinstalled in the IDE. Similarly every standard CORBA service has its own IDL definition, so technically there is no difference between CORBA objects that are defined and implemented as a part of our application, and CORBA objects with interfaces defined by the standard specification. This elegant approach makes it possible to seamlessly interoperate with services implemented by arbitrary vendors. CosNaming service is arguably the most widely used CORBA service, especially that all J2EE Application Servers are required by J2EE specification (7.2.2) to provide a name service that meets the requirements of the [CORBA] Interoperable Naming Service specification. If you would implement a CORBA object based on CosNaming.idl definition, then you will be having a 100% CORBA compliant naming service.

The Smart Agent is an easy to use, dynamically locatable directory service. However, it is not a complete solution to the naming problem. There is a number of reasons to consider implementing your CORBA application based on CosNaming service:

  • Interoperability. The osagent is a Visibroker proprietary directory service. Other ORB products cannot simply make use of its capabilities. The Naming Service is an OMG defined CORBA service that enables us to interoperate between differing ORB products.
  • Deployment. Smart Agent uses UDP protocol for communication. As a consequence you have to use an explicit host address if the osagent is outside of the subnet.
  • Implementation. If you use the Smart Agent, an object's interface name is defined at the time you compile your client and server applications. This means that if you change an interface name, you must recompile your applications. In contrast, the Naming service allows object implementations to bind logical names to its objects at runtime. The osagent uses a flat namespace that has no object name validity checking. At the other hand the Naming Service uses a hierarchical one. If you use the Smart Agent, an object may implement only one interface name. The Naming service allows you to bind more than one logical name to a single object.
  • No permament store. Smart Agent maintains its data in-memory, and there is no way to persist information about CORBA objects. Industry-strength implementation of CORBA Naming, like VisiBroker VisiNaming Service, provides features like pluggable backing stores to persist information in a relational database and other possibilities to configure Naming Service for load balancing and fault-tolerance.

CosNaming concepts

The CORBA Naming Service allows you to associate one or more logical names with an object reference and store those names in a namespace. With the Naming Service, your client applications can obtain an object reference by using the logical name assigned to that object. A CORBA object implementation can bind a name to one of its objects within a namespace. Client applications can then use the same namespace to resolve a name which returns an object reference to a naming context or an object. A Name is a sequence of Name Components. A NameComponent is a structure with two attributes, an identifier and a kind. Both the identifier and kind are defined as idl strings. The Naming Service does not assign, manage or interpret these attributes in any way. A NamingContext is a CORBA object that can contain a set of name bindings. A Name is always resolved relative to a NamingContext, therefore there are no absolute names.

To resolve a name is to determine the object associated with a name in a given context. To bind a name associates a name with an object reference in a given context. Naming Service only allows organizing object references into logical graphs. The interpretation of these graphs is left to the individual application. Names are structures, not just character strings. No universal root is needed for a name hierarchy. Because of these features, most existing name structures can be encapsulated using the Naming Service. Logical Naming Graphs can be physically distributed.

To summarize we need a Naming Service so that we can:

  • Assign arbitrarily complex names to objects
  • Retrieve objects using these names
  • Build complex naming graphs that clearly represent our enterprise object structure
  • Provide a CORBA compliant means to bind names to object references

Building CORBA server with CORBA Naming Service

First steps of building Naming-based server application are exactly the same as in the case of the Smart Agent. Create new console Delphi for .NET project, save it in its own directory and add CORBA Reference using SimpleStocks.idl file.The server implementation contains all the code used by Smart Agent project, but it also performs two additional tasks: retrieving a reference to a Naming Service, and binding servant implementation to a name (SimpleStocksManager). The server is intentionally kept as simple as possible. In more realistic scenario, we would not bind servant to a root context. Instead we would create a graph of NameComponents and bind our objects to different nodes in the namespace hierarchy.


program D2005_SimpleStocks_Naming_Srv;

{$APPTYPE CONSOLE}

{%DelphiDotNetAssemblyCompiler 'SimpleStocks.dll'}
{%DelphiDotNetAssemblyCompiler 'c:\program files\borland\janeva\bin\Borland.Janeva.Services.dll'}
{%DelphiDotNetAssemblyCompiler 'c:\program files\borland\janeva\bin\Borland.Janeva.Runtime.dll'}
{%ConfigurationCompiler 'app.config'}

uses
  SysUtils,
  CORBA,
  PortableServer,
  CosNaming,
  uSimpleStocksImpl in 'uSimpleStocksImpl.pas';

var
  aORB: ORB;
  aRootPOA: POA;
  aPolicies: array of Policy;
  aSimpleStocksPOA: POA;
  aSimpleStocksServant: TSimpleStocksImpl;
  aManagerId: array of byte;
  aCORBAObject: CORBA.&Object;
  aRootCtx: CosNaming.NamingContextExt;

begin
  try
    aORB := ORB.Init(System.Environment.GetCommandLineArgs);
    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.');

    aCORBAObject := aSimpleStocksPOA.ServantToReference(aSimpleStocksServant);
    writeln('The object reference for servant obtained.');

    aRootCtx := NamingContextExtHelper.Narrow(aOrb.ResolveInitialReferences('NameService'));
    writeln('A reference to CORBA Naming Service obtained.');

    aRootCtx.Rebind(aRootCtx.ToName('SimpleStocksManager'), aCORBAObject);
    writeln('Servant associated with the name at the root context.');

    writeln;
    writeln('Delphi 2005 SimpleStocks CosNaming Server is ready.');;
    aORB.Run;

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

The last task of server implementation is to modify app.config file added by Janeva Integration to use naming service. If you do not have Borland VisiBroker installed, there are big chances that you have Java Runtime Environment present. To demonstrate interoperability with non-Borland Naming Service implementation we are going to use simple, transient naming service from jdk: tnameserv.exe. By default Java Naming Service uses port 900 for communication. Here is the modified version of the app.config file.


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="janeva" type="Janeva.Settings, Borland.Janeva.Runtime"/>
  </configSections>
  <janeva>
    <naming url="corbaloc::localhost:900/NameService"/>
  </janeva>
</configuration>

Exactly the same app.config file we are going to use for client project. Janeva documentation contains examples of naming service URLs used by other vendors, like BEA WebLogic, IBM WebSphere, Oracle 0C4J and Sybase.

Building CORBA client with CORBA Naming Service

Again steps for building client application are the same. New Console Delphi for .NET Application, Save All and add CORBA Reference. When using the Naming Service we do not need Bind methods on SimpleStocksHelper class, so SimpleStocks.dll generated by the Janeva Integration will be fine. After ORB initialization we need to obtain a reference to the Naming Service. This line of code is identical as in the server application. In the client code we are not going to bind CORBA objects to naming context, but rather to resolve SimpleStocksManager reference. The Resolve method returns a reference to StockMarket implementation but of CORBA.Object type (note & character in source code used for escaping identifiers that conflicts with Delphi keywords). Generated helper class contains Narrow method that is used for typecasting a generic CORBA Object reference to a desired interface type, which is SimpleStocks in our case.


program D2005_SimpleStocks_Naming_Cli;

{$APPTYPE CONSOLE}

{%DelphiDotNetAssemblyCompiler 'SimpleStocks.dll'}
{%DelphiDotNetAssemblyCompiler 'c:\program files\borland\janeva\bin\Borland.Janeva.Services.dll'}
{%DelphiDotNetAssemblyCompiler 'c:\program files\borland\janeva\bin\Borland.Janeva.Runtime.dll'}
{%ConfigurationCompiler 'app.config'}

uses
  SysUtils,
  CORBA,
  CosNaming,
  SimpleStocks;

var
  aORB: ORB;
  aRootCtx: CosNaming.NamingContextExt;
  aCORBAObject: CORBA.&Object;
  aStockMarket: StockMarket;
  aSymbol: string;
  aPrice: double;

begin
  try
    aORB := ORB.Init(System.Environment.GetCommandLineArgs);
    writeln('ORB initialized.');

    aRootCtx := NamingContextExtHelper.Narrow(aOrb.ResolveInitialReferences('NameService'));
    writeln('A reference to CORBA Naming Service obtained');

    aCORBAObject := aRootCtx.Resolve(aRootCtx.ToName('SimpleStocksManager'));
    writeln('A reference to SimpleStocksManager resolved.');

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

    writeln;
    writeln('Delphi 2005 SimpleStocks CosNaming 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...');
    readln;
  except
    on ex: Exception do writeln('Exception ' + ex.ToString);
  end;
end.

Running it all

Before starting server application make sure that Naming Service is available. To demo purposes we are going to use a sample implementation of the standard CORBA Naming Service that ships with Java JDK  tnameserv.exe

When naming service is running, we can start the server

and finally the client. All from outside the Delphi 2005 IDE.

Summary

A distributed application can consist of many CORBA objects running in the network. Sooner or later we will face a problem of providing some kind of a naming service functionality that would act as a phonebook to find distributed objects. Borland Janeva provides two different solutions to this challenge: easy to use, dynamically locatable Smart Agent, and industry-strength full blown implementation of CORBA standard Naming Service  Borland VisiNaming with advanced features like pluggable backing stores, load balancing and fault tolerance. Both these approaches to naming were discussed here and two simple client/server systems were presented to demonstrate using these technologies in Delphi for .NET code.

The first part of this article was written at the timeframe of Delphi 8 where there was no special support for building Janeva applications. Since there is no idl2delphi compiler that would generate Delphi source code for building CORBA servers and clients, we had to resort to idl2cs compiler that generates a .NET library with definitions of interfaces and implementation of classes necessary to write client and server code. This approach would not be possible if not for the .NET Common Type System. With the CTS it is possible to define a class in one .NET language  say C#  and subclass it another  say Delphi for .NET. In the case of Janeva this possibility helps overcome the lack of native idl2delphi compiler, because we can directly access types in compiled .NET assembly. This gives Delphi developers full access to all the latest CORBA features as implemented in the Borland Janeva product. Janeva is effectively a CORBA ORB implemented in .NET. Borland has long tradition in CORBA world and was also the first to implement ORB on the Java platform with the VisiBroker for Java.

Delphi for .NET plus Janeva is a very powerful combination. Delphi language is very clean and suitable for implementing even the most complex applications. Many developers see Borland Janeva as primarily .NET-J2EE interoperability solution. In fact it is more than this. Janeva brings to the Delphi world CORBA architecture with its services and can add out-of-the-box to our Delphi systems features like scalability, load balancing, fault-tolerance, security, transactions and many others. Delphi developers for years have been implementing these critical features in a custom way, but why not to reuse this proven and reliable CORBA technology for low-level problems and have more time to implement the actual application logic?

Delphi + CORBA = Power Solution :-)

References


Server Response from: ETNASC03