For the things we have to learn before we can do them, we learn by doing them.
— Aristotle (384 BCE – 322 BCE)
This tutorial guides you through the process of writing the classic “Hello World!” example using the Babel tools. In the process, you will learn that the most vexing problem is getting the compiler and linker flags set up properly. Closely followed by the hassles of encoding this information in portable Makefiles.
This section offers a spectrum of possible solutions, from the minimalist but not portable, to the very robust and portable but not trivial, and a few options in between. The following sections start from the simplest setup, and work up in terms of complexity and features.
Assuming Babel is built, installed and your PATH environment variable has been set, we need to set up a few directories for this exercise. One can verify it was built and installed properly by going to the directory where it was built and typing make installcheck. (Warning: It can take a few hours on a good workstation for Babel’s exhaustive tests to complete.) To verify Babel is in your path, simply try running it with the --version or --help option. Now pick a starting directory and issue the following commands to create some directories
% mkdir -p hello/minimal/libCxx
% mkdir -p hello/minimal/libF90
% cd hello
Now we write a SIDL file to describe the calling interface. It will be used by the Babel tools to generate glue code that hooks together different programming languages. A complete description of SIDL can be found in Chapter 6. We will use the same SIDL file for several different coding exercises and Makefile setups, so the SIDL file need not be large or complex for our purposes.
For this particular application, we will write a SIDL file that contains a class World in a package Hello. Method getMsg() in class World returns a string containing the traditional computer greeting. Using your favorite text editor, create a file called hello.sidl in the hello/ directory containing the following:
1 package Hello version 1.0 { 2 class World { 3 string getMsg(); 4 } 5 } |
The package statement provides a scope (or namespace) for class World, which contains only one method, getMsg(). The version clause of the statement identifies this as version 1.0 of the Hello package.
Babel has the ability to generate simple, minimal GNU Makefiles using the
--
makefile command-line option. The generated GNU Makefiles
require GNU make and are intended primarily as an aid to get you
started. They assume that babel and babel-config are in your
path.
We will write the C++ implementation in the minimal/libCxx/ subdirectory of hello/. The first step is to run the Babel code generator on the SIDL file and have it generate the appropriate code. The simplified command to generate the Babel library code (assuming Babel is in your PATH) is 1:
% cd minimal/libCxx
% babel -sC++ –makefile ../../hello.sidl
In this Babel command, the “-sC++” flag, or its long form “--server=C++”, indicates that we wish to generate C++ bindings for an implementation2. This command will generate a large number of C and C++ header and source files. It is often surprising to newcomers just how much code is generated by Babel. Rest assured, each file has a purpose and there is a lot of important things being done as efficiently as possible under the hood.
Files are named after the fully-qualified class-name. For instance, a package Hello and class World would have a fully qualified name (in SIDL) as Hello.World. This corresponds to file names beginning with Hello_World3. For each class, there will be files with _IOR, _Skel, or _Impl appended after the fully qualified name. Stubs often go without the _Stub suffix. IOR files are always in ANSI C (source and headers), containing Babel’s Intermediate Object Representation. Impl files contain the actual implementation, and can be in any language that Babel supports, in this case, they’re C++ files. Impl files are the only files that a developer need look at or touch after generating code from the SIDL source. Skel files perform translations between the IORs and the Impls. In some cases (like Fortran) the Skels are split into a few files: some in C, some in the Impl language. In the case of C++, the Skels are pure C++ code wrapped in extern "C" {} declarations. If the file is neither an IOR, Skel, nor Impl, then it is likely a Stub. Stubs are the proxy classes of Babel, performing translations between the caller language and the IOR. Finally, the file babel.make is a Makefile fragment that is used by GNUmakefile or to simplify making your own custom build. You may ignore the babel.make file if you wish. There are also babel.make.depends and babel.make.package files. These were added by external contributors, and we will ignore them in this document.
The only files that should be modified by the developer (that’s you since you’re implementing Hello World) are the “Impls”, which are in this case files ending with _Impl.hxx or _Impl.cxx Babel generates these implementation files as a starting point for developers. These files will contain the implementation of the Hello library. Every implementation file contains many pairs of comment “splicer” lines such lines 4 and 6 in the following sample:
1 ::std::string 2 Hello::World_impl::getMsg_impl () 3 { 4 // DO-NOT-DELETE splicer.begin(Hello.World.getMsg) 5 // Insert-Code-Here {Hello.World.getMsg} (getMsg method) 6 // DO-NOT-DELETE splicer.end(Hello.World.getMsg) 7 } |
Any modifications between these splicer lines will be saved after subsequent invocations of the Babel tool. Any changes outside the splicer lines will be lost. This splicer feature was developed to make it easy to do incremental development using Babel. By keeping your edits within the splicer blocks, you can add new methods to the hello.sidl file and rerun Babel without the loss of your previous method implementations. You shouldn’t ever need to edit the file outside the splicer blocks.
For our hello application, the implementation is trivial. Add the following return statement between the splicer lines in the lib/Hello_World_Impl.cxx file:
::std::string Hello::World_impl::getMsg_impl () { // DO-NOT-DELETE splicer.begin(Hello.World.getMsg) return "Hello from C++!"; // DO-NOT-DELETE splicer.end(Hello.World.getMsg) } |
The following is a listing of the generated GNUmakefile excluding the boilerplate copyright notice. It uses some of the tools installed along with Babel to automated generating a static and shared library provided that the machine supports both. The babel-config provides information about which compilers and compiler flags to use, and babel-libtool manages the building of object files and libraries.
1 include babel.make 2 # please name the server library here 3 LIBNAME=hello 4 # please name the SIDL file here 5 SIDLFILE=../../hello.sidl 6 # extra include/compile flags 7 EXTRAFLAGS= 8 # extra librarys that the implementation needs to link against 9 EXTRALIBS= 10 # library version number 11 VERSION=0.1.1 12 # PREFIX specifies the top of the installation directory 13 PREFIX=/usr/local 14 # the default installation installs the .la and .scl (if any) into the 15 # LIBDIR 16 LIBDIR=$(PREFIX)/lib 17 # the default installation installs the stub header and IOR header files 18 # in INCLDIR 19 INCLDIR=$(PREFIX)/include 20 21 # most of the rest of the file should not require editing 22 23 ifeq ($(IMPLSRCS),) 24 SCLFILE= 25 BABELFLAG=--client=cxx 26 MODFLAG= 27 else 28 SCLFILE=lib$(LIBNAME).scl 29 BABELFLAG=--server=cxx 30 MODFLAG=-module 31 endif 32 33 all : lib$(LIBNAME).la $(SCLFILE) 34 35 CC=`babel-config --query-var=CC` 36 CXX=`babel-config --query-var=CXX` 37 INCLUDES=`babel-config --includes-cxx` 38 CFLAGS=`babel-config --flags-c` 39 CXXFLAGS=`babel-config --flags-cxx` 40 LIBS=`babel-config --libs-cxx-client` 41 42 STUBOBJS=$(STUBSRCS:.cxx=.lo) 43 IOROBJS=$(IORSRCS:.c=.lo) 44 SKELOBJS=$(SKELSRCS:.cxx=.lo) 45 IMPLOBJS=$(IMPLSRCS:.cxx=.lo) 46 47 PUREBABELGEN=$(IORHDRS) $(IORSRCS) $(STUBSRCS) $(STUBHDRS) $(SKELSRCS) 48 BABELGEN=$(IMPLHDRS) $(IMPLSRCS) 49 50 $(IMPLOBJS) : $(STUBHDRS) $(IORHDRS) $(IMPLHDRS) 51 52 lib$(LIBNAME).la : $(STUBOBJS) $(IOROBJS) $(IMPLOBJS) $(SKELOBJS) 53 babel-libtool --mode=link --tag=CXX $(CXX) -o lib$(LIBNAME).la \ 54 -rpath $(LIBDIR) -release $(VERSION) \ 55 -no-undefined $(MODFLAG) \ 56 $(CXXFLAGS) $(EXTRAFLAGS) $^ $(LIBS) \ 57 $(EXTRALIBS) 58 59 $(PUREBABELGEN) $(BABELGEN) : babel-stamp 60 @if test -f $@; then \ 61 touch $@; \ 62 else \ 63 rm -f babel-stamp ; \ 64 $(MAKE) babel-stamp; \ 65 fi 66 67 babel-stamp: $(SIDLFILE) 68 @rm -f babel-temp 69 @touch babel-temp 70 babel $(BABELFLAG) $(SIDLFILE) 71 @mv -f babel-temp $@ 72 73 lib$(LIBNAME).scl : $(IORSRCS) 74 ifeq ($(IORSRCS),) 75 echo "lib$(LIBNAME).scl is not needed for client-side C bindings." 76 else 77 -rm -f $@ 78 echo '<?xml version="1.0" ?>' > $@ 79 echo '<scl>' >> $@ 80 if test `uname` = "Darwin"; then scope="global"; else scope="local"; \ 81 fi ; \ 82 echo ' <library uri="'`pwd`/lib$(LIBNAME).la'" scope="'"$$scope"'" resolution="lazy" >' >> $@ 83 grep __set_epv $^ /dev/null | awk 'BEGIN {FS=":"} { print $$1}' | sort -u | sed -e 's/_IOR.c//g' -e 's/_/./g' | awk ' { printf " <class name=\"%s\" desc=\"ior/impl\" />\n", $$1 }' >>$@ 84 echo " </library>" >>$@ 85 echo "</scl>" >>$@ 86 endif 87 88 .SUFFIXES: .lo .cxx .c 89 90 .c.lo: 91 babel-libtool --mode=compile --tag=CC $(CC) $(INCLUDES) $(CFLAGS) $(EXTRAFLAGS) -c -o $@ $< 92 93 .cxx.lo: 94 babel-libtool --mode=compile --tag=CXX $(CXX) $(INCLUDES) $(CXXFLAGS) $(EXTRAFLAGS) -c -o $@ $< 95 96 clean : 97 -rm -f $(PUREBABELGEN) babel-temp babel-stamp *.o *.lo 98 99 realclean : clean 100 -rm -f lib$(LIBNAME).la lib$(LIBNAME).scl 101 -rm -rf .libs 102 103 install : install-libs install-headers install-scl 104 105 106 install-libs : lib$(LIBNAME).la 107 -mkdir -p $(LIBDIR) 108 babel-libtool --mode=install install -c lib$(LIBNAME).la \ 109 $(LIBDIR)/lib$(LIBNAME).la 110 111 install-scl : $(SCLFILE) 112 ifneq ($(IORSRCS),) 113 -rm -f $(LIBDIR)/lib$(LIBNAME).scl 114 -mkdir -p $(LIBDIR) 115 echo '<?xml version="1.0" ?>' > $(LIBDIR)/lib$(LIBNAME).scl 116 echo '<scl>' >> $(LIBDIR)/lib$(LIBNAME).scl 117 if test `uname` = "Darwin"; then scope="global"; else scope="local"; \ 118 fi ; \ 119 echo ' <library uri="'$(LIBDIR)/lib$(LIBNAME).la'" scope="'"$$scope"'" resolution="lazy" >' >> $(LIBDIR)/lib$(LIBNAME).scl 120 grep __set_epv $^ /dev/null | awk 'BEGIN {FS=":"} { print $$1}' | sort -u | sed -e 's/_IOR.c//g' -e 's/_/./g' | awk ' { printf " <class name=\"%s\" desc=\"ior/impl\" />\n", $$1 }' >>$(LIBDIR)/lib$(LIBNAME).scl 121 echo " </library>" >>$(LIBDIR)/lib$(LIBNAME).scl 122 echo "</scl>" >>$(LIBDIR)/lib$(LIBNAME).scl 123 endif 124 125 install-headers : $(IORHDRS) $(STUBHDRS) 126 -mkdir -p $(INCLDIR) 127 for i in $^ ; do \ 128 babel-libtool --mode=install cp $$i $(INCLDIR)/$$i ; \ 129 done 130 131 .PHONY: all clean realclean install install-libs install-headers install-scl |
The details of this makefile deserve careful explanation.
- line 1:
- babel.make is a file that Babel generates when it generates code. It defines some standard names so our makefiles don’t have to know every SIDL type declared in the file.
- line 3:
- LIBNAME defines the name of the library file that will be generated. This is the only line that I had to edit in the babel-generated GNUmakefile.
- line 7:
- Use EXTRAFLAGS to define additional compile flags to be used when compiling files. For this example, it should be empty.
- line 9:
- EXTRALIBS should hold any extra libraries needed for your application. For this example, it should be empty.
- line 11:
- The VERSION should be three numbers separated by periods.
- line 13:
- The PREFIX is for libraries that will be installed.
- line 21–end:
- This part of the GNUmakefile works from the definitions above and usually does not require changes unless you are incorporating it into a more complex build system.
- line 33:
- The first build target in a Makefile is also the default target. Common convention is to make this target’s name “all.”
- lines 35–40:
- Note that we use a script called babel-config to hand us some information. This tool is very useful for getting the same information that Babel’s configure script was given as Babel configuring itself. Increasingly, it is being used to also get information about tools and flags used in Babel’s makefiles, and its utility for getting at this level of information is limited…mostly because we rely on a multilayered stack of tools and some are less forthcoming than others.
- lines 42–45:
- Here you see some of the variables defined in babel.make and some fancy suffix substitution to define STUBOBJS, IOROBJS, SKELOBJS, and IMPLOBJS.
- lines 53:
- This is the rule to combine the .o files into a static and dynamic library using babel-libtool.
- line 88:
- This is a often misunderstood detail. To override the default suffix rules, one must give the list of new suffices to consider.
- line 90–94:
- Here we start to override make’s default suffix rules for converting C and C++ sourcecode into .o files.
- lines 96 & 99:
- How you go about various levels of cleanliness in your makefiles largely depends on matters of taste. The important point here is what is not removed. One thing not cleaned up is babel.make it is generated by Babel, but the makefile won’t work without the file present because of the include in line 5. Another important thing to not remove is the Impl files, since they have hand-edited regions in the generated file.
- line 103–129:
- Here we define some simple rules for installing libraries into standard system locations.
- line 131:
- Technically, any phony targets like “clean” and “new” are supposed to be listed in this variable. Many makefile implementations will work well if you skip this line, but.
With the GNUmakefile in place we can simply go to that directory and build everything by typing make.
Before writing the client, let’s generate a Fortran implementation as well. It is highly instructive to see how the makefiles differ between the different language bindings. From within the minimal/libCxx directory we do.
% cd ../libF90
% babel –makefile -sF90 ../../hello.sidl
This time there’s even more files generated (Fortran 90/95 bindings are harder after all), and we need to add our implementation to the Hello_World_Impl.F90 file. The modified code will look like this.
1 ! 2 ! Method: getMsg[] 3 ! 4 5 recursive subroutine Hello_World_getMsg_mi(self, retval, exception) 6 use sidl 7 use sidl_BaseInterface 8 use sidl_RuntimeException 9 use Hello_World 10 use Hello_World_impl 11 ! DO-NOT-DELETE splicer.begin(Hello.World.getMsg.use) 12 ! Insert-Code-Here {Hello.World.getMsg.use} (use statements) 13 ! DO-NOT-DELETE splicer.end(Hello.World.getMsg.use) 14 implicit none 15 type(Hello_World_t) :: self ! in 16 character (len=*) :: retval ! out 17 type(sidl_BaseInterface_t) :: exception ! out 18 19 ! DO-NOT-DELETE splicer.begin(Hello.World.getMsg) 20 retval='Hello from Fortran 90!' 21 ! DO-NOT-DELETE splicer.end(Hello.World.getMsg) 22 end subroutine Hello_World_getMsg_mi |
Note that the C function appears as a subroutine in Fortran. What was the return value appears here as the argument retval (line 5). For Fortran 90/95 there are also two splicer blocks per subroutine, one for use statements (lines 11–13) and another for the actual implementation (lines 19–21). This is where we put our implementation by setting retval to the string we want.
There are important differences in this Makefile from the C++ implementation, so we reproduce it in its entirety here.
1 include babel.make 2 # please name the server library here 3 LIBNAME=hello 4 # please name the SIDL file here 5 SIDLFILE=../../hello.sidl 6 # extra include/compile flags 7 EXTRAFLAGS= 8 # extra librarys that the implementation needs to link against 9 EXTRALIBS= 10 # library version number 11 VERSION=0.1.1 12 # PREFIX specifies the top of the installation directory 13 PREFIX=/usr/local 14 # the default installation installs the .la and .scl (if any) into the 15 # LIBDIR 16 LIBDIR=$(PREFIX)/lib 17 # the default installation installs the stub header and IOR header files 18 # in INCLDIR 19 INCLDIR=$(PREFIX)/include 20 21 # most of the rest of the file should not require editing 22 23 ifeq ($(IMPLSRCS),) 24 SCLFILE= 25 BABELFLAG=--client=f90 26 MODFLAG= 27 else 28 SCLFILE=lib$(LIBNAME).scl 29 BABELFLAG=--server=f90 30 MODFLAG=-module 31 endif 32 33 all : lib$(LIBNAME).la $(SCLFILE) 34 35 CC=`babel-config --query-var=CC` 36 CPP=`babel-config --query-var=CPP` 37 FC=`babel-config --query-var=FC` 38 INCLUDES=`babel-config --includes` `babel-config --includes-f90` 39 CFLAGS=`babel-config --flags-c` 40 FCFLAGS=`babel-config --flags-f90` 41 MODINCLUDES=`babel-config --includes-f90-mod` 42 LIBS=`babel-config --libs-f90-client` 43 F90CPPSUFFIX=`babel-config --query-var=F90CPPSUFFIX` 44 45 STUBOBJS=$(STUBSRCS:.c=.lo) 46 STUBMODULEOBJS=$(STUBMODULESRCS:.F90=.lo) 47 TYPEMODULEOBJS=$(TYPEMODULESRCS:.F90=.lo) 48 IOROBJS=$(IORSRCS:.c=.lo) 49 SKELOBJS=$(SKELSRCS:.c=.lo) 50 IMPLOBJS=$(IMPLSRCS:.F90=.lo) 51 IMPLMODULEOBJS=$(IMPLMODULESRCS:.F90=.lo) 52 BASICMODULEOBJ=$(BASICMODULESRC:.F90=.lo) 53 ARRAYMODULEOBJS=$(ARRAYMODULESRCS:.F90=.lo) 54 ALLOBJS=$(STUBOBJS) $(STUBMODULEOBJS) $(TYPEMODULEOBJS) $(IOROBJS) \ 55 $(SKELOBJS) $(IMPLOBJS) $(IMPLMODULEOBJS) $(BASICMODULEOBJ) \ 56 $(ARRAYMODULEOBJS) 57 58 PUREBABELGEN=$(ARRAYMODULESRCS) $(BASICMODULESRC) $(STUBMODULESRCS) \ 59 $(TYPEMODULESRCS) \ 60 $(IORHDRS) $(IORSRCS) $(STUBSRCS) $(STUBHDRS) $(SKELSRCS) 61 BABELGEN=$(IMPLSRCS) $(IMPLMODULESRCS) 62 63 $(TYPEMODULEOBJS) : $(BASICMODULEOBJ) 64 $(ARRAYMODULEOBJS) : $(TYPEMODULEOBJS) 65 $(STUBMODULEOBJS) : $(ARRAYMODULEOBJS) $(TYPEMODULEOBJS) 66 $(IMPLMODULEOBJS) : $(STUBMODULEOBJS) 67 $(IMPLOBJS) : $(IMPLMODULEOBJS) $(STUBMODULEOBJS) \ 68 69 lib$(LIBNAME).la : $(ALLOBJS) 70 babel-libtool --mode=link --tag=FC $(FC) -o lib$(LIBNAME).la \ 71 -rpath $(LIBDIR) -release $(VERSION) \ 72 -no-undefined $(MODFLAG) \ 73 $(FCFLAGS) $(EXTRAFLAGS) $^ $(LIBS) \ 74 $(EXTRALIBS) 75 76 $(PUREBABELGEN) $(BABELGEN) : babel-stamp 77 @if test -f $@; then \ 78 touch $@; \ 79 else \ 80 rm -f babel-stamp ; \ 81 $(MAKE) babel-stamp; \ 82 fi 83 84 babel-stamp: $(SIDLFILE) 85 @rm -f babel-temp 86 @touch babel-temp 87 babel $(BABELFLAG) $(SIDLFILE) 88 @mv -f babel-temp $@ 89 90 lib$(LIBNAME).scl : $(IORSRCS) 91 ifeq ($(IORSRCS),) 92 echo "lib$(LIBNAME).scl is not needed for client-side C bindings." 93 else 94 -rm -f $@ 95 echo '<?xml version="1.0" ?>' > $@ 96 echo '<scl>' >> $@ 97 if test `uname` = "Darwin"; then scope="global"; else scope="local"; \ 98 fi ; \ 99 echo ' <library uri="'`pwd`/lib$(LIBNAME).la'" scope="'"$$scope"'" resolution="lazy" >' >> $@ 100 grep __set_epv $^ /dev/null | awk 'BEGIN {FS=":"} { print $$1}' | sort -u | sed -e 's/_IOR.c//g' -e 's/_/./g' | awk ' { printf " <class name=\"%s\" desc=\"ior/impl\" />\n", $$1 }' >>$@ 101 echo " </library>" >>$@ 102 echo "</scl>" >>$@ 103 endif 104 105 .SUFFIXES: .lo .F90 .c 106 107 .c.lo: 108 babel-libtool --mode=compile --tag=CC $(CC) $(INCLUDES) $(CFLAGS) $(EXTRAFLAGS) -c -o $@ $< 109 110 .F90.lo: 111 $(CPP) -traditional $(INCLUDES) -P -o $(@:.lo=.tmp) -x c $< 112 sed -e 's/^#pragma.*$$//' < $(@:.lo=.tmp) > $(@:.lo=$(F90CPPSUFFIX)) 113 babel-libtool --mode=compile --tag=FC $(FC) $(MODINCLUDES) $(FCFLAGS) -c -o $@ $(@:.lo=$(F90CPPSUFFIX)) 114 rm -f $(@:.lo=$(F90CPPSUFFIX)) $(@:.lo=.tmp) 115 116 clean : 117 -rm -f $(PUREBABELGEN) babel-temp babel-stamp *.o *.lo *.mod 118 119 realclean : clean 120 -rm -f lib$(LIBNAME).la lib$(LIBNAME).scl 121 -rm -rf .libs 122 123 install : install-libs install-headers install-scl 124 125 126 install-libs : lib$(LIBNAME).la 127 -mkdir -p $(LIBDIR) 128 babel-libtool --mode=install install -c lib$(LIBNAME).la \ 129 $(LIBDIR)/lib$(LIBNAME).la 130 131 install-scl : $(SCLFILE) 132 ifneq ($(IORSRCS),) 133 -mkdir -p $(LIBDIR) 134 -rm -f $(LIBDIR)/lib$(LIBNAME).scl 135 echo '<?xml version="1.0" ?>' > $(LIBDIR)/lib$(LIBNAME).scl 136 echo '<scl>' >> $(LIBDIR)/lib$(LIBNAME).scl 137 if test `uname` = "Darwin"; then scope="global"; else scope="local"; \ 138 fi ; \ 139 echo ' <library uri="'$(LIBDIR)/lib$(LIBNAME).la'" scope="'"$$scope"'" resolution="lazy" >' >> $(LIBDIR)/lib$(LIBNAME).scl 140 grep __set_epv $^ /dev/null | awk 'BEGIN {FS=":"} { print $$1}' | sort -u | sed -e 's/_IOR.c//g' -e 's/_/./g' | awk ' { printf " <class name=\"%s\" desc=\"ior/impl\" />\n", $$1 }' >>$(LIBDIR)/lib$(LIBNAME).scl 141 echo " </library>" >>$(LIBDIR)/lib$(LIBNAME).scl 142 echo "</scl>" >>$(LIBDIR)/lib$(LIBNAME).scl 143 endif 144 145 install-headers : $(IORHDRS) $(STUBHDRS) $(STUBDOCS) 146 -mkdir -p $(INCLDIR) 147 for i in $^ ; do \ 148 babel-libtool --mode=install cp $$i $(INCLDIR)/$$i ; \ 149 done 150 151 .PHONY: all clean realclean install install-libs install-headers install-scl |
- line 1:
- Again Babel will generate a babel.make file, but we will see that its contents are different.
- line 3:
- The name of the library will be libhello.la again. This is the only line that I had to edit in the babel-generated GNUmakefile.
- lines 7–19:
- These variables have the same meanings as above. EXTRAFLAGS are extra compile flags, and EXTRALIBS defines extra libraries to list when linkings. PREFIX defines where the library should be installed.
- lines 35–43:
- Note that we use babel-config to generate the proper flag for the preprocessor to find the Babel Fortran headers, and the compiler to find the Babel MOD files.
- lines 45–56:
- Are building a $(OBJS) variable like before, but this time we see suffix substitutions for more kinds of files.
- lines 63–67:
- The order that files are compiled is important because Fortran 90/95’s use of MOD files4 makes the ordering of these items very important. (Not Babel’s fault, blame the Fortran 90/95 language designers.) C/C++ has no such constraint on the order that individual units of compilation are performed. As long as Fortran 90/95 programmers stick with the ordering shown in these lines, they should not encounter compiler complaints about dependent MOD files not found.
- lines 110–114:
- This bit of code admittedly looks very strange, but the explanation is simple. We preprocess our Fortran 90/95 source to workaround the 31 character limit specified in the language. Check out Chapter 11 for more details about this issue.
Again, we simply type make, and should end up with another libhello.la file.
Now, finally, we are ready to write a client. For this exercise, we wrote our driver in C and built two executables; each one linking in one of the two implementation libraries. We will put our driver in the minimal/ directory (which happens to be the parent directory of where the C++ and Fortran 90/95 implementations are, though this detail is only relevant to makefile construction). From our Fortran 90/95 subdirectory, we go up one and generate the client-side C bindings.
% cd ..
% babel –makefile -cC ../hello.sidl
The “-cC” flag, or its equivalent long-form “--client=C”, tells the Babel code generator to create only the C stub calling code, not the entire library implementation.
There are a few details worth noting here. The C bindings generate function names by combining packages, classes, and method names with underscores (e. g. Hello_World_getMsg(). Whenever you see double underscores in Babel generated symbols, they indicate something built-in to (and sometimes specific to) the language binding. The _create() method is built-in to every instantiatable class defined in SIDL, triggering the creation of Babel internal data structures as well as the constructor of the actual object implementation.
The code listing below shows a well crafted driver with full error checking. To try this example, use your favorite text editor to copy this text into a file named main.c.
1 #include <stdio.h> 2 #include "Hello_World.h" 3 #include "sidl_BaseInterface.h" 4 #include "sidl_Exception.h" 5 #include "sidl_String.h" 6 7 int main() { 8 Hello_World h; 9 sidl_BaseInterface ex; 10 char * msg; 11 12 /* create instance of Hello World */ 13 h = Hello_World__create(&ex); SIDL_CHECK(ex); 14 if ( h == NULL ) { 15 fprintf(stderr,"%s:%d Failed to create an instance of Hello_World!\n", 16 __FILE__,__LINE__); 17 return 2; 18 } 19 20 /* get the message from the object */ 21 msg = Hello_World_getMsg(h, &ex); SIDL_CHECK(ex); 22 if ( msg == NULL ) { 23 fprintf(stderr, "%s:%d Hello_World_getMsg() returned a NULL instead " 24 "of a string!\n",__FILE__,__LINE__); 25 return 3; 26 } 27 28 /* done with object so we can release it */ 29 Hello_World_deleteRef(h,&ex); SIDL_CHECK(ex); 30 31 /* print the string */ 32 printf("%s\n",msg); 33 34 /* release the string */ 35 sidl_String_free(msg); 36 37 return 0; 38 39 EXIT: /* this is error handling code for any exceptions that were thrown */ 40 { 41 fprintf(stderr,"%s:%d: Error, exception caught\n",__FILE__,__LINE__); 42 sidl_BaseInterface ignore = NULL; 43 sidl_BaseException be = sidl_BaseException__cast(ex,&ignore); 44 45 msg = sidl_BaseException_getNote(be, &ignore); 46 fprintf(stderr,"%s\n",msg); 47 sidl_String_free(msg); 48 49 msg = sidl_BaseException_getTrace(be, &ignore); 50 fprintf(stderr,"%s\n",msg); 51 sidl_String_free(msg); 52 53 sidl_BaseException_deleteRef(be, &ignore); 54 SIDL_CLEAR(ex); 55 return 1; 56 } 57 } |
As with other examples, we will go through this one line by line. It is important to note that nowhere in this file is any indication of what language the Babel object is implemented in. When you see the makefile, we will show that this code can be linked against multiple implementations in different languages.
- line 2:
- This line includes the C stub for the Hello.World type.
- line 3:
- We also include the C stub for the sidl.BaseInterface type which all classes and interfaces ultimately inherit from. In C, we often use this type to hold the exception argument.
- line 4:
- There is no sidl.Exception type. This header actually introduces some useful macros for dealing with exception handling in C.
- line 13:
- This is where the object is _create()’ed. Note that the creation may fail, so we use the SIDL_CHECK macro introduced from sidl_Exception.h to test the exception and goto EXIT (line 39), if necessary.
- line 21:
- With a live reference to the object, we now try to get the message out of it. Note that we check if the exception is thrown and if the string is NULL.
- line 29:
- Once we have the message, we can dispose of our reference to the object.
- line 32:
- Print the message
- line 35:
- Free the string. Return values have the same semantics as out parameters which is the caller always recieves a reference count and is obligied to dispose of it when done.
- line 37:
- Normal termination.
- lines 39–56:
- This is exception handling code. Its hard to imagine so many possibilities for failure in our little example, but it is useful to see how exception classes can be cast to appropriate types (line 43), and be queried for both original error message and the trace of the call stack from which it was thrown.
- line 42:
- Note that Babel generated methods always throw exceptions, but in exception handling code, we often ignore them. Do not call SIDL_CHECK after the EXIT as this can easily result in an infinite loop.
Now we need to edit the GNUmakefile that builds the code in this directory and links it with the C++ or Fortran 90/95 implementations in the two subdirectories. This case requires more editing that the previous two examples.
1 include babel.make 2 # please name the server library here 3 LIBNAME=client 4 # please name the SIDL file here 5 SIDLFILE=../hello.sidl 6 # extra include/compile flags 7 EXTRAFLAGS= 8 # extra libraries that the implementation needs to link against 9 EXTRALIBS= 10 # library version number 11 VERSION=0.1.1 12 # PREFIX specifies the top of the installation directory 13 PREFIX=/usr/local 14 # the default installation installs the .la and .scl (if any) into the 15 # LIBDIR 16 LIBDIR=$(PREFIX)/lib 17 # the default installation installs the stub header and IOR header files 18 # in INCLDIR 19 INCLDIR=$(PREFIX)/include 20 21 22 # most of the rest of the file should not require editing 23 24 ifeq ($(IMPLSRCS),) 25 SCLFILE= 26 BABELFLAG=--client=c 27 MODFLAG= 28 else 29 SCLFILE=lib$(LIBNAME).scl 30 BABELFLAG=--server=c 31 MODFLAG=-module 32 endif 33 34 all : lib$(LIBNAME).la $(SCLFILE) runC2Cxx runC2F90 35 36 CXX=`babel-config --query-var=CXX` 37 runC2Cxx: lib$(LIBNAME).la libCxx/libhello.la main.lo 38 babel-libtool --mode=link --tag=CXX $(CXX) -static main.lo \ 39 lib$(LIBNAME).la libCxx/libhello.la -o runC2Cxx 40 41 runC2F90: lib$(LIBNAME).la libF90/libhello.la main.lo 42 babel-libtool --mode=link --tag=CC $(CC) -static main.lo \ 43 lib$(LIBNAME).la libF90/libhello.la -o runC2F90 44 45 CC=`babel-config --query-var=CC` 46 INCLUDES=`babel-config --includes` 47 CFLAGS=`babel-config --flags-c` 48 LIBS=`babel-config --libs-c-client` 49 50 STUBOBJS=$(STUBSRCS:.c=.lo) 51 IOROBJS=$(IORSRCS:.c=.lo) 52 SKELOBJS=$(SKELSRCS:.c=.lo) 53 IMPLOBJS=$(IMPLSRCS:.c=.lo) 54 55 PUREBABELGEN=$(IORHDRS) $(IORSRCS) $(STUBSRCS) $(STUBHDRS) $(SKELSRCS) 56 BABELGEN=$(IMPLHDRS) $(IMPLSRCS) 57 58 $(IMPLOBJS) : $(STUBHDRS) $(IORHDRS) $(IMPLHDRS) 59 60 lib$(LIBNAME).la : $(STUBOBJS) $(IOROBJS) $(IMPLOBJS) $(SKELOBJS) 61 babel-libtool --mode=link --tag=CC $(CC) -o lib$(LIBNAME).la \ 62 -rpath $(LIBDIR) -release $(VERSION) \ 63 -no-undefined $(MODFLAG) \ 64 $(CFLAGS) $(EXTRAFLAGS) $^ $(LIBS) \ 65 $(EXTRALIBS) 66 67 $(PUREBABELGEN) $(BABELGEN) : babel-stamp 68 @if test -f $@; then \ 69 touch $@; \ 70 else \ 71 rm -f babel-stamp ; \ 72 $(MAKE) babel-stamp; \ 73 fi 74 75 babel-stamp: $(SIDLFILE) 76 @rm -f babel-temp 77 @touch babel-temp 78 babel $(BABELFLAG) $(SIDLFILE) 79 @mv -f babel-temp $@ 80 81 lib$(LIBNAME).scl : $(IORSRCS) 82 ifeq ($(IORSRCS),) 83 echo "lib$(LIBNAME).scl is not needed for client-side C bindings." 84 else 85 -rm -f $@ 86 echo '<?xml version="1.0" ?>' > $@ 87 echo '<scl>' >> $@ 88 if test `uname` = "Darwin"; then scope="global"; else scope="local"; \ 89 fi ; \ 90 echo ' <library uri="'`pwd`/lib$(LIBNAME).la'" scope="'"$$scope"'" resolution="lazy" >' >> $@ 91 grep __set_epv $^ /dev/null | awk 'BEGIN {FS=":"} { print $$1}' | sort -u | sed -e 's/_IOR.c//g' -e 's/_/./g' | awk ' { printf " <class name=\"%s\" desc=\"ior/impl\" />\n", $$1 }' >>$@ 92 echo " </library>" >>$@ 93 echo "</scl>" >>$@ 94 endif 95 96 .SUFFIXES: .lo 97 98 .c.lo: 99 babel-libtool --mode=compile --tag=CC $(CC) $(INCLUDES) $(CFLAGS) $(EXTRAFLAGS) -c -o $@ $< 100 101 clean : 102 -rm -f $(PUREBABELGEN) babel-temp babel-stamp *.o *.lo 103 104 realclean : clean 105 -rm -f lib$(LIBNAME).la lib$(LIBNAME).scl 106 -rm -rf .libs 107 108 install : install-libs install-headers install-scl 109 110 111 install-libs : lib$(LIBNAME).la 112 -mkdir -p $(LIBDIR) 113 babel-libtool --mode=install install -c lib$(LIBNAME).la \ 114 $(LIBDIR)/lib$(LIBNAME).la 115 116 install-scl : $(SCLFILE) 117 ifneq ($(IORSRCS),) 118 -rm -f $(LIBDIR)/lib$(LIBNAME).scl 119 -mkdir -p $(LIBDIR) 120 echo '<?xml version="1.0" ?>' > $(LIBDIR)/lib$(LIBNAME).scl 121 echo '<scl>' >> $(LIBDIR)/lib$(LIBNAME).scl 122 if test `uname` = "Darwin"; then scope="global"; else scope="local"; \ 123 fi ; \ 124 echo ' <library uri="'$(LIBDIR)/lib$(LIBNAME).la'" scope="'"$$scope"'" resolution="lazy" >' >> $(LIBDIR)/lib$(LIBNAME).scl 125 grep __set_epv $^ /dev/null | awk 'BEGIN {FS=":"} { print $$1}' | sort -u | sed -e 's/_IOR.c//g' -e 's/_/./g' | awk ' { printf " <class name=\"%s\" desc=\"ior/impl\" />\n", $$1 }' >>$(LIBDIR)/lib$(LIBNAME).scl 126 echo " </library>" >>$(LIBDIR)/lib$(LIBNAME).scl 127 echo "</scl>" >>$(LIBDIR)/lib$(LIBNAME).scl 128 endif 129 130 install-headers : $(IORHDRS) $(STUBHDRS) 131 -mkdir -p $(INCLDIR) 132 for i in $^ ; do \ 133 babel-libtool --mode=install cp $$i $(INCLDIR)/$$i ; \ 134 done 135 136 .PHONY: all clean realclean install install-libs install-headers install-scl |
- line 1:
- Again we include the Babel-generated makefile fragment. Again we see that its contents depend on the language being generated.
- line 3:
- Here we edit the name to be client.
- lines 5–19:
- These have the same meanings as in the examples above.
- lines 34–43:
- Here we must modify the all target definition and add lines to link runC2Cxx and runC2F90. Note that when linking C to C++, we must use the C++ compiler.
At last, we can make the two executables and run them.
% make all
% ./runC2Cxx
Hello from C++!
% ./runC2F90
Hello from Fortran 90/95!
Since Babelized software must be built the same way that Babel itself was configured, it seems reasonable to lean on babel-config quite heavily. By now you are probably wondering how many secrets babel-config holds and can provide on request. The simplest way to find out is to ask it. (Though you may get a slightly different result than what is shown here depending on the version of Babel.)
% babel-config --dump-vars | wc -l
128
babel-config can provide a wealth of information determined by Babel during its configuration and installation. Even if you do not use the GNUmakefile’s that Babel can generate, you will likely end up using babel-config to determine important information about Babel’s installation
Congratulations! You are now ready to develop a parallel scalable linear solver package.
The preceding process may seem to be the most complicated way to write the world’s simplest program but, of course, the same process will also work for significantly more complex applications. “Hello World” is small enough to experiment with in the language of your choice. Parallel, multithreaded, scientific simulation codes are another matter entirely.