Home

Connecting Java and Delphi via CORBA

A tutorial by a beginner
or
What I always wanted to ask
 but found nobody to answer

Part I: Doing it all in Delphi
Part II: Tearing down the language barrier

 


 

Author: Leo Meyer (leo att leomeyer dott de)

 

Part II: Tearing down the language barrier

Previous: Part I: Doing it all in Delphi

Contents


Legal information: The information presented in this document is meant for personal education only. It comes with absolutely no warranty for absolutely nothing. Use this information on your own risk.
Any links from this document to other domains except the author's domain are presented for information purposes only and imply neither recommendation, disapproval or any other valuation. The author declines all responsibility for the content of websites on such domains.
This article may be reproduced - without modifications - in electronic form as long as no fees are demanded, neither for copying, distributing, storing, the physical means by which the data is accessed, or whatever. Reproduction in printed form requires written permission by the author.

Copyright Leo Meyer, 2005.


Prerequisites

I assume that you are using the Eclipse 3.0 IDE or later to develop your Java programs. CORBA of course is not limited to a certain IDE. As such, Eclipse has no built-in support for CORBA, that is, no designated IDL editor or compiler tools. You may check out the pages at http://jnius.tech.fr/ which apparently provide an IDL plugin, but as of yet, I have not been able to find a download location for it (please send me a note if you are). There is a free plugin called ECP (Eclipse CORBA plugin) which implements an IDL editor, however, it's still very basic.

I further assume that you have the JDK 1.5.0 or later installed and your Eclipse projects are actually using its libraries. The JDK ORB is located in rt.jar

To get some basic CORBA development support in Eclipse you can download my small idljtool.exe (sourcecode here) which you should register as an editor for the files of type *.idl. In its present form it requires that JacOrb has been installed to C:\jacorb. (Installing JacOrb requires the build tool "ant", see here). You should however be able to use it with other ORBs.

By the way, if you have downloaded the full distribution of JacOrb the pre-built jacorb.jar file is compiled without debug information. I recommend that you compile JacOrb to get debug support; to do this you will have to change the following lines in the file etc\common.xml:

<target name="init" depends="base-init">
  <
property name="debug" value="off"/>
  <!--
This value only used under Ant 1.5 -->
  <
property name="debuglevel" value="lines,source"/>

to

<target name="init" depends="base-init">
  <property name="debug" value="true"/>
  <!--
This value only used under Ant 1.5 -->
  <
property name="debuglevel" value="lines,vars,source"/>

and then run ant from the c:\jacorb directory. Building JacOrb takes a couple of minutes.

How to register idltool.exe in Eclipse:

You can then compile IDL files from the Eclipse Java perspective by right clicking the file and selecting idljtool. 

Important: If the file is already open in another editor in Eclipse, opening with idljtool does not work!

You will not see any program output from idljtool. Sad but unavoidable. To see the generated files, refresh the project. If no files have been generated, idljtool or the JacOrb IDL compiler have encountered an error and you will have to compile from a console window to see the cause. idljtool assumes that your IDL compiler is c:\jacorb\bin\idl.bat. Make sure that the file is present. If you have freshly installed JacOrb, you will have to copy and modify from the file idltemplate.bat. (Download my example idl.bat.)

By default, both server and client classes will be generated. idljtool compiles IDL files with an include path to the JacOrb IDL library (C:\jacorb\idl\omg). If the setup on your computer is different, you will have to modify the source and rebuild idljtool.exe. (It would be better to specify it in idl.bat though.)

I recommend that you place the IDL files for an Eclipse project in the root source folder for that project. Thus, if you compile an IDL which contains module definitions (which they usually do) the Java output will be placed in the correct directories that correspond to the modules which simply map to Java packages. If you don't do this you'll have to move the generated files to the destination that matches the package names (from the Java compiler's point of view).

For example, if compiling this IDL file:

module demo {
  module hello {
    interface GoodDay {
      string hello_simple();
      wstring hello_wide( in wstring msg ); 
    };
  };
};

a package demo.hello (and its corresponding folders) will be created that contains the classes generated by the IDL compiler.

I recommend that you create a project "JacOrb Demos" in Eclipse and import the demo directory from your JacOrb installation. Each demo comes with an IDL file. Unfortunately, these files are usually named "server.idl". In order to compile them correctly from the Eclipse environment, you'll have to
1. rename each IDL file to a meaningful name, e.g. rename demo/bank/transaction/explicit/server.idl to bank_transaction_explicit.idl,
2. move the file to the project's root folder.
If you then compile the IDL file with the idljtool, the generated files will appear in the correct folders according to the module definition specified in the file.
As an alternative, you can build the demos using ant. See the JacOrb installation manual.

To compile the JacOrb demo Java files from Eclipse, add all of the libraries in C:\jacorb\lib as external JAR files to your project's Java build path. As long as you have not compiled the IDLs, you will see a lot of errors due to missing class files. You can either ignore these errors and go through the demos step by step or compile all of the demo IDLs at once.

If you want to run the JacOrb demos, you will have to start each main class with the VM arguments:
-Dorg.omg.CORBA.ORBClass=org.jacorb.orb.ORB
-Dorg.omg.CORBA.ORBSingletonClass=org.jacorb.orb.ORBSingleton

The Java CORBA support has a pluggable architecture that allows to specify which classes should be used for a concrete implementation of the ORB. By using those VM parameters the Java CORBA startup classes will not use Sun's default implementation (which is somewhat buggy, see below) but those provided in the JacOrb libraries, which is what you want.

One more step to go: The JacOrb libraries require the jacorb.properties file. You can supply this file's location on the command line or in a system property. If you don't, JacOrb will look in the current directory. For an application run by Eclipse, this is the project root directory. That means that you can copy the file c:\jacorb\etc\jacorb_properties.template to your Eclipse project root folder and rename it to jacorb.properties. Modify this file:

Comment out all present ORBInitRef.NameService settings and type:

ORBInitRef.NameService=corbaloc::127.0.0.1:900/NameService

Guess what? This is the default address of the naming service when using orbd. This line "bootstraps" JacOrb into the orbd naming service. Now you should be able to run the JacOrb demos, at least those which actually require a naming service (there are some that don't). Start with the any demo. (Note that the JDK idlj.exe compiler will not compile the any demo IDL file correctly. Use idljtool or JacOrb's compiler directly.)

Running the JacOrb Any demo

Run the Server class. Make sure that the VM parameters are present.
Run the Client class. Dito. This should already work.

For a test, set the jacorb.properties entry

jacorb.log.default.verbosity=1

to see less output from both the server and client. You have to restart the server for this setting to take effect. (The server, unlike the client, will usually continue running until it is manually stopped in the demos.)

Try to run the client without the previously mentioned VM parameters. That means, instead of using JacOrb the runtime system now starts using the internal Sun ORB implementation. (The Sun implementation automatically assumes that orbd is listening on port 900, so it requires no bootstrap parameters. It's just like using VisiBroker and the SmartAgent.) 

What you get is an exception:

java.lang.NullPointerException
at com.sun.corba.se.impl.corba.AnyImpl.write_value(Unknown Source)
at com.sun.corba.se.impl.encoding.CDROutputStream_1_0.write_any(Unknown Source)
at com.sun.corba.se.impl.encoding.CDROutputStream.write_any(Unknown Source)
at demo.any._AnyServerStub.generic(_AnyServerStub.java:30)
at demo.any.Client.main(Client.java:101)

Now stop the server and start it again without the VM arguments. Run the client again.

The exception still occurs. That means that the Sun ORB implementation is probably buggy as far as the transmission of "any" sequences is concerned (sequences are arrays of variable length), even if server and client use the same libraries. It also means that you should not use Sun's ORB for serious applications. Use JacOrb instead.

What we learn from this exception is how the CORBA requests are transferred over the network. The "stub class" which encapsulates the object on the client side calls the ORB library functions to write the method name and its parameters to an output stream (a process called "marshaling"). The content of this output stream is then transmitted over the network using the GIOP protocol which is again based on TCP/IP. On the other side the transferred data is decoded ("unmarshaled" or "demarshaled"). The server has some sort of lookup mechanism to decide which method to invoke on the object (remember, the object itself is known by its IOR which contains host address and port). In Java, this is usually done using reflection. For Delphi, there are other mechanisms. One by one the server unmarshals the method parameters and passes them to the method to call. The method result is then marshaled and sent back over the network just to be unmarshaled by the client again. If you are familiar with RMI you should know what I am talking about, and if you have ever attempted to write your own remote invocation library (like me) you know how valuable it is to have complete and tested libraries like CORBA available.

The whole process is completely invisible to the client and to the server which you have to implement. That means, you don't have to worry about anything concerned with the marshaling and transporting process - the ORB does that for you. The reason why this works cross-language is that the transport mechanisms and the marshaled data format is standardized (by OMG), so every programming language that implements the fundamental GIOP and the marshaling/unmarshaling can participate in the CORBA game.

Needless to say, if the ORB does not fully support the transport of a data type (as in the Sun example above) it cannot be said to be fully CORBA-compliant. There are different versions of the CORBA standard, though, and you need to know whether the libraries you use are compatible with each other. This is easy to test, and that's what we are going to do next.

The Bank example in Java

We are going to use this IDL file in the next example:

module Bank {
 
interface AccountManager {
    float open(
in string name);
  };
};

This file defines a module "Bank" which will be mapped to a java package "Bank". This package contains one distributable object "AccountManager" which supports one method "open". This method takes one parameter "name" which is a string. This parameter is an input parameter only; therefore it's prefixed with "in". The method returns a result of type float.

In Eclipse, create a new Java project called "Corba". Import the IDL file to the project root directory. Add the JacOrb libraries to the build path. Compile the IDL file with the idljtool or with Sun's JDK compiler idlj.exe. For idlj.exe you'll have to specify -fall on the command line to generate both client and server classes.
The result should look like this:

We're going to implement both server and client in Java first, then create a Delphi client.

Create two packages "Bank.server" and "Bank.client" containing these files:

The class AccountManagerImpl extends the class Bank.AccountManagerPOA. POA stands for "Portable Object Adapter" and basically contains wrapper code that allows your implemented code to be called from the underlying ORB.

Implement the AccountManagerImpl class like this:

package Bank.server;

import Bank.AccountManagerPOA;

public class AccountManagerImpl extends AccountManagerPOA {

  public float open(String name) {
    // output a message
    System.out.println("Opening new account: " + name);
    // return a random balance
    return (float)(Math.random() * 100.0);
  }
}

Implement the server class like this:

package Bank.server;

import org.omg.CORBA.ORB;
import org.omg.CosNaming.NameComponent;
import org.omg.CosNaming.NamingContextExt;
import org.omg.CosNaming.NamingContextExtHelper;
import org.omg.PortableServer.POA;
import org.omg.PortableServer.POAHelper;

import Bank.AccountManager;
import Bank.AccountManagerHelper;

public class Server {
    
    public static void main(String[] args) {
        try {
            // init the ORB
            ORB orb = ORB.init( args, null );
    
            // init the POA
            POA poa = 
                POAHelper.narrow( 
                        orb.resolve_initial_references( "RootPOA" ));
    
            poa.the_POAManager().activate();
    
            // create an AccountManager object
            AccountManagerImpl am = new AccountManagerImpl();
            
            // get object reference from the servant
            org.omg.CORBA.Object ref = 
                poa.servant_to_reference(am);
    
            AccountManager href = AccountManagerHelper.narrow(ref);
           
            // get the root naming context
            org.omg.CORBA.Object objRef =
               orb.resolve_initial_references("NameService");
           
            // Use NamingContextExt which is part of the Interoperable
            // Naming Service (INS) specification.
            NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);

            // bind the Object Reference in Naming
            String name = "AccountManager";
            NameComponent path[] = ncRef.to_name( name );
            ncRef.rebind(path, href);

            System.out.println("Bank Server ready and waiting ...");

            // wait for invocations from clients
            orb.run();
           
        } catch( Exception e ) {
               System.out.println( e );
        }
    }
}

As you see, the Server class contains a main method which passes the command line parameters over to the ORB on initialization. (Unlike with VisiBroker, you have the choice on which parameters to pass.) Next it obtains a reference to the RootPOA which we will consider later on. Then we create the object we want to make available to the client. Contrary to Delphi, we have to get a reference to the RootPOA object (in Delphi this is hidden away by the VisiBroker libraries). The RootPOA is another standard CORBA object like the NameService. It converts our object to a CORBA reference which we can bind to the NamingContext we have obtained from the ORB. The last thing to do is to keep the server and the ORB running.

This is the basic method for registering objects with a naming service. Once you have grasped it, it is very simple: get RootPOA, make object, get reference, get naming service, register. You will find this scheme in almost all of the examples we deal with here. Remember: The actual object implementation is not done in the Server class, but in the Impl class you use in the server. That allows for easy code re-use.

The client is equally simple:

package Bank.client;

import org.omg.CORBA.ORB;
import org.omg.CosNaming.NamingContextExt;
import org.omg.CosNaming.NamingContextExtHelper;

import Bank.AccountManager;
import Bank.AccountManagerHelper;

public class Client {

    public static void main(String[] args) {
        try{
            // create and initialize the ORB
            ORB orb = ORB.init(args, null);

            // get the root naming context
            org.omg.CORBA.Object objRef = 
            orb.resolve_initial_references("NameService");
            
            // Use NamingContextExt instead of NamingContext. This is 
            // part of the Interoperable naming Service.  
            NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);
     
            // resolve the Object Reference in Naming
            String name = "AccountManager";
            
            AccountManager am = AccountManagerHelper.narrow(ncRef.resolve_str(name));

            System.out.println("Obtained a handle on server object: " + am);
            
            // open a new account
            System.out.println(am.open("Example Account"));
            
        } catch (Exception e) {
              System.out.println("ERROR : " + e) ;
              e.printStackTrace(System.out);
        }
    }
}

It performs the following steps:
1. Initialize the ORB (again using the command line parameters),
2. get the naming service object,
3. get the AccountManager object,
4. call the open(String) method and print the result.

You don't absolutely have to use the JacOrb library for this example. If you use the Sun ORB instead, you can save yourself the trouble entering the arguments for the VM. Make sure that orbd is running before you start both server and client.

The output you see from the server is:

Bank Server ready and waiting ...
Opening new account: Example Account

The client's output looks similar to this:

Obtained a handle on server object:
IOR:000000000000001c49444c3a42616e6b2f4163636f756e744d616e616765723a312e3000000
00001000000000000008a00010200000000103136392e3235342e3130322e3137340005d8000000
000031afabcb0000000020c3ad6c1a00000001000000000000000100000008526f6f74504f41000
0000008000000010000000014000000000000020000000100000020000000000001000100000002
050100010001002000010109000000010001010000000026000000020002
67.13465

As an exercise you should start orbd on a different port and try this example again. Then specify the command line parameter that should bootstrap the ORB into the naming service and see whether it works.

Now we finally come to the interesting part: Calling Java from a Delphi client.

Writing the Delphi client for the Bank example

Compile the IDL file to Pascal, create a new Delphi application and construct the following MainForm:

Try to write the code for the button handler yourself! It's not hard if you look at the previous examples. Remember to nicely format the resulting float value.

When you start the client, don't forget to bootstrap its ORB into the running naming service. Otherwise you'll get an OBJECT_NOT_EXIST exception.

If you have done everything correctly, you will get the following output:

That means, you have successfully called a Java program from a Delphi client! Congratulations! ;-) Now try it on separate computers and see how it works.

The whole source code for this example is available here.

Bypassing the naming service

There are situations where you don't want to use a naming service. It requires installation and administration, may fail or crash unexpectedly or may simply not be worth using in very small environments. Fortunately there are means you can use to bypass the naming service.

I assume that you have a web server running on your computer which has its port 80 site root at C:\Inetpub\wwwroot. We will modify the bank example as follows:

The body of the main method in Server.java is changed to:

            // init the ORB
            ORB orb = ORB.init( args, null );
    
            // init the POA
            POA poa = 
                POAHelper.narrow( 
                        orb.resolve_initial_references( "RootPOA" ));
    
            poa.the_POAManager().activate();
    
            // create an AccountManager object
            AccountManagerImpl am = new AccountManagerImpl();
            
            // get object reference from the servant
            org.omg.CORBA.Object ref = 
                poa.servant_to_reference(am);
    
            AccountManager href = AccountManagerHelper.narrow(ref);
           
            // save the object reference to a file instead of registering it with the
            // naming service
            PrintStream ps = new PrintStream(new File("C:/Inetpub/wwwroot/accountmanager.ior"));
            ps.print(href);
            ps.close();
            
            System.out.println("Bank Server ready and waiting ...");

            // wait for invocations from clients
            orb.run();

The PrintStream we have opened writes the IOR of the AccountManager object to a file which is accessible over HTTP. The client can now connect to the WWW server and obtain this IOR:

            // create and initialize the ORB
            ORB orb = ORB.init(args, null);
            
            // load the object reference from a file instead of retrieving it
            // from a naming service
            URL url = new URL("http://localhost/accountmanager.ior");
            URLConnection conn = url.openConnection();
            
            BufferedReader br = new BufferedReader(
                    new InputStreamReader(conn.getInputStream()));
            String ior = br.readLine();
            br.close();
            System.out.println(ior);
            
            AccountManager am = AccountManagerHelper.narrow(
                    orb.string_to_object(ior));

            System.out.println("Obtained a handle on server object: " + am);
            
            // open a new account
            System.out.println(am.open("Example Account"));

The URL could easily be supplied from a properties file or via the command line.

One problem with this approach is that the client gets no notification when the server process shuts down or crashes. However, that is the case with the naming service also. The other problem is that you cannot have more than one instance of the server running because they overwrite each other's IOR file. (In fact you can if the next server is started after a client has obtained the IOR of the current server. As the IORs are valid as long as the server is up, this poses no problem.) We'll test later how the naming service deals with multiple object instances.

A clever way to exploit this functionality is a scenario where you place a script behind the URL address that checks whether the server process is still running, if not, starts it and supplies the object's IOR to the client. In such a scenario you can be sure that a server object is always available to a client. Web servers are fairly stable, and the small delay in starting the server affects only the HTTP connection. Moreover, the HTTP overhead is incurred only once as the client from then on uses a direct connection to the object.

In Delphi it would look like this with VisiBroker:

tcpconn := TTcpClient.Create(self);
tcpconn.RemoteHost := 'localhost';
tcpconn.RemotePort := '80';
tcpconn.Connect;
tcpconn.Sendln('GET /accountmanager.ior');
ior := tcpconn.Receiveln();
tcpconn.Close;  // just to be sure
tcpconn.Free;

// Resolve the account manager object
am := TAccountManagerHelper.narrow(
        orb.StringToObject(ior));

However, this does not work, unfortunately; VisiBroker's IOR representation is different from Java's, so you get nil as an object reference if you are lucky, and an exception if not. The ORBs are incompatible on that level. In my opinion with VisiBroker there's no other choice than to use a naming service.

Using MTDORB: A free ORB for Delphi

In order to install MTDORB you will require the build tool ant. Ant is outside the scope of this tutorial, but there's plenty of material on it on the web. Install it and include the path to ant.bat in your PATH variable. I use ant's version 1.6.2 which works fine. The MTDORB version I use here is 1.0 beta 6.

When you have extracted the MTDORB zip file to a folder, e.g. C:\mtdorb, you will have to adjust the build properties file as described in the small readme. Copy the file build.properties.pattern to build.properties and make the following changes:

# Compilers
dir.delphi=C:/Program Files/Borland/Delphi7
dir.kylix=
delphi.version7=off

# Location
dir.bpl=C:/Program Files/Borland/Delphi7/Projects/bpl

# External libraries
dir.lib.jcl=
dir.lib.dunit=

# Build
build.debug=off

Of course you will have to use your local paths. Be sure to use slashes, not backslashes as path separators!
Setting delphi.version7=on even if you are using Delphi 7 will result in a build error because a required package can't be found. If you're into experimenting, you can set the debug switch to on to step through the compiled units.

If your Delphi installation path contains spaces, as in my case, you will have to edit the build.xml file as well: Change all -U${path.search.windows} entries to -U'${path.search.windows}' (enclosing the build property in single quotes). It's probably best if you use Search and Replace for that.
(I have suggested this as a fix on the MTDORB web site but it may take some time until it's verified and actually fixed.)

Open a command console, go to the MTDORB installation directory and type "ant". Ant will find the build file and build MTDORB which takes about 40 seconds on my PC. If you encounter any errors, check whether they are "rmic failed" errors, in which case you can ignore them; it indicates that the external libraries are missing, which doesn't matter. If you see other errors, run ant with the "-v" or "-verbose" switch to get some details on what's going on.

After a successful build the dcu folder should be full of units and the build folder should contain several EXEs and DLLs, including idltopas.exe. As recommended in the readme file, copy MTDORB_UCUtils.dll to your appropriate system folder.

We're now going to try a demo using MTDORB. From the MTDORB folder demo/hello copy the file helloworld.idl to your Eclipse Corba project. Compile it with the idljtool and implement any hello method you like. Create a server class just like in the previous example, i.e. don't use a naming service but write the IOR to a file, if possible so that it's accessible by a web server. Run it. You can use the Sun ORB for that.

Open the Delphi project Hello.dpr. If you can't compile it immediately you have to include the path to the MTDORB units in the Delphi search path. Append ;C:\mtdorb\dcu to the Library Path in the Environment Options dialog. Now it should compile and run.

Suppose you have saved your server object's IOR to the file C:\Inetpub\wwwroot\helloworld.ior. What you can do with MTDORB now is the following:

Note that you can type the URL in directly; the MTDORB library will automatically recognize the URL, connect to the web server, obtain the IOR from there, resolve the IOR to get an object reference and use the returned object reference to call the server.

Alternatively, or if you don't have a web server, you may open the saved IOR file and copy and paste the contents (IOR:xxxxx...) to the IOR location edit field. You see that the MTDORB's IOR representation is compatible with the Sun ORB's representation (and, by the way, with JacOrb's as well - try it). Have a short look at the implementation to see how simple it is.

If you try this from another computer (which you should), make sure that the necessary DLL is present. Replace "localhost" with the correct name or IP address.

Using MTDORB has a couple of advantages over VisiBroker. First of all it's completely free and you even get the complete source code. Second, it's more compatible with the Java ORBs you are probably going to use. Third, it supports features that VisiBroker does not, including an easy way to bypass a naming service. Fourth, it's libraries are compatible with OMG specification syntax (this concerns the naming of the ORB functions), thus making code easier to port. Fifth, you have to deploy only one DLL; total executable size is a lot smaller even if the exe is bigger.

On the other hand, though, support for MTDORB is even worse than for VisiBroker. Considering the trouble I had with only installing it I'm not surprised that few people seem to actually use it. It's up to you to decide whether you are going to use it or not.

We will now try a more advanced example.

Passing and returning objects: The BetterBank example

In this example we will try to get an object from a method call. In another method call we will pass in two objects and get one object back. We will call a method on that object which will throw a remote exception.

This is the IDL we are going to use (download here):

// simple test setup
// returns an object
// tests passing in an object
// tests throwing an exception
module BetterBank {

   exception NotEnoughMoneyException {
       // Contains the account's balance.
       float balance;
   };

   // Represents an account
   interface Account {      
   
     // Returns the account balance.
     float getBalance();
     
     // Increases the balance of the account by value.
     void add(in float value);
     
     // Withdraws a certain amount of money (value) from the account.
     // If not enough money is available, raises a
     // NotEnoughMoneyException which contains the current balance.
     // If enough money is there, deducts the amount and returns
     // the remainder.
     float withdraw(in float value) raises (NotEnoughMoneyException);
   };
   
   // This class manages accounts
   interface AccountManager {      

     // Opens an account "name".
     Account open(in string name); 
     
     // Merges two accounts by adding the amount of the second to the first account.
     // Returns the first account.
     Account mergeAccounts(in Account account1, in Account account2);  
   };
};

Generating and implementing Java classes from this IDL should not be a problem for you any more. Save the IOR you have obtained for your AccountManager to the file BetterBankManager.ior in your wwwroot.

To return an object from a method, as in AccountManager.open(String), you will have to do the following:

  AccountImpl ai = new AccountImpl(name);

  // the secret is here:
  try {
    // make a corba object
    org.omg.CORBA.Object o = poa.servant_to_reference(ai);

    // activate and return the object
    return AccountHelper.narrow(o);

  } catch (Exception e) {
    e.printStackTrace();
    return null;
  }

For this you will need a reference to the RootPOA you have resolved in the server's main class. You hand it over to the AccountManager in the constructor:

POA poa;

AccountManagerImpl(POA poa) {
  this.poa = poa;
}

On the Delphi side we will use MTDORB this time, which needs different coding than VisiBroker. First of all, you don't use idl2pas.exe any more. Use idltopas.exe now. You will see that the generated units are named differently.

The code for the unit:

unit UBetterBankForm;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls,
  betterbank_int, betterbank,
  // MTDORB units
  orb_int, orb, req_int, code_int, imr, imr_int, env_int, stdstat, std_seq,
  orbtypes, exceptions, except_int, poa_int, poa;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  orb : IORB;
  obj : IORBObject;
  am : IBetterBank_AccountManager;
  ac1 : IBetterBank_Account;
  ac2 : IBetterBank_Account;
begin
  orb := ORB_Init(nil);
  obj := orb.string_to_object('http://localhost/BetterBankManager.ior');
  if obj <> nil then
    begin
      am := TBetterBank_AccountManager._narrow(obj);

      Memo1.Clear;

      // open an account
      Memo1.Lines.Add('Opening first account');
      ac1 := am.open('Test Account 1');
      Memo1.Lines.Add(FormatFloat('Balance: $###,##0.00', ac1.getBalance));

      // open another account
      Memo1.Lines.Add('Opening second account');
      ac2 := am.open('Test Account 2');
      Memo1.Lines.Add(FormatFloat('Balance: $###,##0.00', ac2.getBalance));

      Memo1.Lines.Add('Adding 100$ to 1st account');
      ac1.add(100);
      Memo1.Lines.Add(FormatFloat('New balance: $###,##0.00', ac1.getBalance));

      Memo1.Lines.Add('Merging the two accounts');
      ac1 := am.mergeAccounts(ac1, ac2);
      Memo1.Lines.Add(FormatFloat('Account 1 balance: $###,##0.00', ac1.getBalance));

      Memo1.Lines.Add('Withdrawing 50$');
      ac1.withdraw(50);
      Memo1.Lines.Add(FormatFloat('Account 1 balance: $###,##0.00', ac1.getBalance));

      Memo1.Lines.Add('Withdrawing 1000$');
      try
        ac1.withdraw(1000);
        Memo1.Lines.Add(FormatFloat('Account 1 balance: $###,##0.00', ac1.getBalance));
      except
        on e : TBetterBank_NotEnoughMoneyException do
          Memo1.Lines.Add(FormatFloat('Error! Account 1 has only $###,##0.00 left!', e.balance));
      end;
    end;
end;

end.

The result should look like this:

You can download the code for the whole BetterBank example here. If you want to try this on another computer, you have to implement a way to supply the URL on the command line, such as

if ParamCount = 1 then
  obj := orb.string_to_object(ParamStr(1))
else
  obj := orb.string_to_object('http://localhost/BetterBankManager.ior');

The drawback with the MTDORB package is that it requires so many different units which you have to include. If we'd use even more advanced features, I assume that we'd start sweating pretty soon.

Doing the Any demo from Delphi (VisiBroker)

Now take a look at the classes of the any demo again. The IDL any type is a placeholder for any other IDL type. In Delphi, this is called a variant. In fact, idl2pas maps the IDL any type to Object Pascal's variant type.

We will now try a more advanced example, namely implementing a Delphi client for the Any demo.

This is actually easier than one might think. After all, we have a complete client implementation in Java. So all we should have to do in theory is to re-code the Java client in Delphi. Do this!

1. Compile the any.idl file using idl2pas.exe
2. Create a new application with a MainForm like this:

3. Include the usual libraries: Corba, CosNaming, demo_any_i, demo_any_c
4. Test compile. 

In the AnyServerImpl.java file the JacOrb folks have forgotten to evaluate the long type. Therefore, add the following lines:

case TCKind._tk_long:
  result = "long: " + a.extract_long();
  break;

My implementation of the Delphi client can be downloaded here. I will go into the details of the most important features.

procedure TFormJacorbDemoAny.Button1Click(Sender: TObject);
var
...
s : AnyServer;
si : single;
a : Any;
begin
  // bind to the Root Context of the NS via the orb
  // create the naming component -- match server id and kind values
  // set up the Name sequence
  // Resolve the AnyServer object

Up to here it's the usual stuff which you should know by now.

  Memo1.Clear;

  // In Java we would create a new Any now.
  // In Delphi, as an Any is just a variant, we can simply
  // assign the value to a.

  // char
  Memo1.Lines.Add('Passing a char...');
  a := char(#65);
  Memo1.Lines.Add( s.generic( a ) );

  // short
  Memo1.Lines.Add('Passing a short...');
  a := byte(5);
  Memo1.Lines.Add( s.generic( a ) );

  // long
  Memo1.Lines.Add('Passing an integer...');
  a := integer(-4711);
  Memo1.Lines.Add( s.generic( a ) );

{
  // long long
  Memo1.Lines.Add('Passing a longlong...');
  a := int64(4711);
  Memo1.Lines.Add( s.generic( a ) );
}


  // float
  Memo1.Lines.Add('Passing a float...');
  si := Pi;
  a := Single(si); // doubly explicit! :-)
  Memo1.Lines.Add( s.generic( a ) );

  // string
  Memo1.Lines.Add('Passing a string...');
  a := 'Hi there';
  Memo1.Lines.Add( s.generic( a ) );

{
  // wstring
  Memo1.Lines.Add('Passing a Wstring...');
  a := WideString('WString: äöüßÄÖÜâáàêéèîíìôóòûúù');
  Memo1.Lines.Add( s.generic( a ) );
}

The lines in italics are commented out - because they don't work! Try it. 

{
  // array
  Memo1.Lines.Add('Passing an array...');
  a := ORB.MakeArray(tk_string, ['hello', 'another', 'world']);
  Memo1.Lines.Add( s.generic( a ) );
}

{
  // sequence of strings
  Memo1.Lines.Add('Passing a sequence of strings...');
  a := ORB.MakeSequence(tk_string, ['hello', 'world']);
  Memo1.Lines.Add( s.generic( a ) );
}

{
  // sequence of octets
  a := ORB.MakeSequence(tk_octet, [1, 2, 3, 4]);
  Memo1.Lines.Add('Passing a sequence of octets...');
  Memo1.Lines.Add( s.generic( a ) );
}

If you run this example (orbd running, server with JacOrb running, VisiBroker bootstrapped into orbd) you will see this output:

If you try passing an array you will get an AccessViolation error. This is however not the interesting thing. It's the way how different Delphi variant types are mapped to Java types on the other side.

First of all, a character we pass in appears as a string on the Java side.
Passing a byte appears as a char.
Passing an integer yields a long (which is ok, because that is the proper 32-bit IDL type)
Passing a single gives us a double (IDL supports both float and double, as well as an extended type)
Passing a string works.
When passing a WString (WideString in Delphi, which is a Unicode string) the result depends on the library. The Sun ORB throws an exception (BAD_INV_ORDER). With JacOrb the server displays as many question marks as the string has characters, indicating that something's wrong with the string, but throws no exception.
If we try to pass an array, sequence or struct we get exceptions straightaway on the client side. Those elements never make it to the server.

While the any demo works well with JacOrb, VisiBroker seems to have problems converting the Delphi variant types to CORBA IDL types and dealing with arrays, structs and sequences. I have tried this demo with MTDORB, but got an error when compiling the IDL file. Even though some incomplete pascal code has been generated, the code seems to be buggy.

The any demo seems to be a test for advanced features of CORBA which both VisiBroker, MTDORB as well as Sun's implementation can't cope with. Consequently in practical use these advanced features should be avoided. It also shows you that the ORBs are not abstract, perfect things but real code which, of course, is more likely bug-ridden than not (even the OMG CORBA specifications contain bugs). I must apologize to pushing you into the bug bin nose-ahead, but you (and I) need to know the code we are working with if we are ever going to use it successfully in practice.

Let me suggest that you continue with examining the demos now. There are resources in the Delphi example directory as well as with JacOrb and MTDORB, but not in the JDK's demo package. On the web you may find additional samples. Remember: CORBA is all about doing, and getting the demos to run is the best preparation for doing a successful project on your own. If you found this tutorial useful or have any suggestions and/or corrections, please don't hesitate to send me your comment. Good luck and have fun!

Recommended further reading

The Common Object Request Broker: Architecture and Specification

The CORBA-related guides in the JDK documentation (Java IDL, RMI-IIOP).

 

 

Author: Leo Meyer (leo att leomeyer dott de) Document version: 1.0 Date: 31.1.2005