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).
|