Linking Ada, C and Haskell Together Statically

Do you want to link object code from C, Ada and Haskell into one executable statically? Probably not. But if you did, how could it be done? Many programming languages provide a means of exporting subroutines to make them callable from C code and importing C functions so they can be called. You can easily find documentation on how to statically link C to one other language. But what if you want to link C with two other languages in the same project? Just compile all of the code and link the object files, right? Haha! Not so fast. Programming languages have different run-time systems; they have their own libraries and they may use more than just a stack and a heap and a space for global variables. So if you want to include object code from several languages in a single executable, you'll often have to build run-time code for each application and link it in to the executable as well. I'm going to show you how you can accomplish this in a project that uses C, Ada and Haskell.

We will build 3 applications: one with a main program written in C, one with a main program written in Ada, and one with a main program written in Haskell. Each application will call functions written in C, Ada and Haskell and print what those functions returned.

For compiler tools, we will use make, gcc, GHC (the Glasgow Haskell Compiler) and GNAT (GNU Ada compiler Tools).

I won't tell you how to import and export functions from Ada and Haskell. That's been explained well elsewhere. Instead, I'll point you to some resources and make some observations.

The Example

Exported functions:

Main programs that import these functions:

The makefile:

Both GNAT and GHC use gcc to compile and link. In fact, if you add the -v option to a GNAT or GHC command, you'll see the commands they are issuing to gcc. On Windows at least, GNAT and GHC install versions of gcc in separate places, and these versions are not necessarily the same. This can cause problems. When you do have Ada and Haskell in the same project, specify which gcc they should use so they are using the same version. With GNAT tools, this is done with the --GCC=. In GHC, this is done with the -pgmc and -pgml options.

Compiling the Example

The first step is to compile the code that exports C functions. That's simple.

  gnatmake --GCC=$(CC) -c ada_sample
  $(CC) -c c_sample.c
  ghc --make haskell_sample.hs

Preparing Ada object code for linking

Some Ada object files can be linked into a C executable with no special processing. Some can't. It depends on whether or not the object code uses the Ada run-time system. Certain features in Ada including tasks, exceptions, and functions that return unbounded (variable-length) strings require the Ada run-time system. Code that doesn't use any such features doesn't require it. ada_sample catches an exception, so if we're going to include it in an application, we'll have to add two steps to our build process:

  gnatbind -n ada_sample
  gnatlink --GCC=$(CC) -r -nostdlib ada_sample.ali -o ada.o

The first step is to run gnatbind. The "-n" option tells gnatbind that the package that it is binding doesn't contain a main subroutine. gnatbind generates an Ada package that imports all the necessary parts of the Ada run-time system and exports two subroutines, adainit and adafinal, which initialize and finalize the Ada run-time system.

The second step is to run gnatlink on the package. gnatlink will look for the package generated by gnatbind for ada_sample, compile it, and link it together with ada_sample.o and all of the necessary run-time object code. "-r" indicates that the output should be relocatable, that is, we should be able to link it to other object code later on. "-nostdlib" tells the linker not to link in any standard library code; we'll that in later when we're linking it to our main programs.

The end product is ada.o, an object file that is ready to be linked into our main programs.

Building the Main Programs

It would be nice to make a relocatable object file that contains the Haskell run-time system so we could just link it into whatever applications we wanted, but that is either difficult or impossible. Building a dynamic library (e.g. DLL) with the Haskell run-time system in it is easy, but I haven't succeeded in building a working static library with the Haskell run-time system in it. GHC is, however, good at building executable programs. Pass in any Haskell modules, Haskell code, C code, and/or object files, and GHC will compile and link them all together more or less automagically. So we will use GHC to build all of our main programs.

For the C main program:

  ghc --make C_main.c -no-hs-main -pgmc $(CC) -pgml $(CC)  haskell_sample c_sample.o ada.o -o C_main

The -no-hs-main option instructs GHC not to use the Haskell RTS' main program; we're using the one defined in C_main.c.

For the Ada main program:

  gnatmake --GCC=$(CC) -c ada_main
  gnatbind ada_main
  gnatlink --GCC=$(CC) -nostdlib -r ada_main.ali -o ada_main_program.o
  ghc --make ada_main_program.o -no-hs-main -pgmc $(CC) -pgml $(CC) haskell_sample c_sample.o -o Ada_main

Once again, we use GNAT to create an object file with the Ada parts of the application and the Ada run-time system.

For Haskell,

  ghc --make haskell_main -pgmc $(CC) -pgml $(CC) c_sample.o ada.o

And there you have it – three programs each built from Ada, C, and Haskell.

[ My Home Page | My Blog ]