Gem #90: The Distributed Systems Annex, Part 4 — DSA and C
by Thomas Quinot —AdaCore
Let's get started...
The previous DSA Gems showed how components in a pure Ada application can be spread across several partitions and use static or dynamic remote calls to interact. Wouldn't it be nice if other languages such as C could also benefit from these features?
Of course, you can embed C code in an Ada partition just as you would in any nondistributed application. Your C code can also call back to Ada code (as long as the Ada subprograms have the C convention). Remote (RCI) subprograms can thus be called from C. If the call occurs on the partition to which the RCI is assigned, nothing special happens, this is just a regular call. On other partitions, the compiler-generated calling stubs are used, and this is a transparent remote call, just as it would be if it occurred in Ada code: a remote subprogram has nothing special at the call point; all the magic is done in the generated stubs.
This is all well and good, but you still have to write your complete application in Ada, and in particular have the main subprogram of each partition declared in the GNATDIST configuration file.
What if you would like to incorporate DSA client or server code in an existing C application? This can be achieved by combining the DSA with GNAT's stand-alone libraries, a feature allowing an Ada partition to generate a loadable module rather than a full-fledged executable image. Here's how...
Rebuild PolyORB with -fPIC
The "-fPIC" switch instructs the compiler to generate so-called Position Independent Code, that is, code that can be dynamically loaded as a shared library.
In order to have a DSA partition in a stand-alone library, you need to set CFLAGS="-O2 -g -fPIC" in your environment when calling the PolyORB configure script. (The resulting PolyORB build can also be used for normal applications.)
Build your Ada partitions as usual, also with -fPIC
Let's assume for example that your application has a server partition that is fully written in Ada, and a client partition meant for embedding in a C/C++ application as a shared object. The server partition will be built using:
po_gnatdist -fPIC xxxx.cfg server_partition
Create a dummy main subprogram for the client side
You need to provide a dummy main subprogram for the client partition. You should make this a null library subprogram that has WITH clauses for any package (including RCIs) that you want to reference from the C side.
Also, it may be convenient to include in this closure an "Exports" package containing suitable subprogram declarations for those routines that you want to call from C, with C-compatible argument types, and using pragma Export to give them friendly C names. (Note that this is not specific to the Distributed Systems Annex: such an interface package is typically created any time you need to call Ada code from C code.)
with RCI_1; ... with RCI_n; with Exports; procedure Client is begin null; end Client;
Build the client library
This is the crucial point. To build a partition as a stand-alone library instead of a regular executable, special arguments are passed to GNATDIST:
po_gnatdist -fPIC -g xxxx.cfg client_partition \ -bargs rci_1.ali ... rci_n.ali polyorb-dsa_p-partitions.ali \ -shared -LClientName \ -largs -shared
In this command line, you need to list the ALI files for all RCI packages referenced in your client partition (rci_1.ali .. rci_n.ali), and also the one for the internal RCI polyorb-dsa_p-partitions.ali.
You can replace the name "ClientName" with an arbitrary prefix of your choosing (it is used for some automatically generated symbols, see below).
This will generate a file client_partition, which you can rename to client_partition.so.
Call client library from C code
Once you have your loadable object generated, you can load it from C code using the standard dlopen(3) function.
Symbols from the library can then be obtained using the dlsym(3) function. You first need to retrieve the symbols ClientNameinit and ClientNamefinal from the library.
ClientNameinit corresponds to the elaboration of all Ada units in the library, and should be called once upon module load. This starts the Ada PCS and connects to the DSA name server to retrieve the initial location of RCI units.
ClientNamefinal corresponds to the finalization, and should be called once, just before unloading the module or terminating the application (ClientName here is the prefix you passed on the GNATDIST command line above).
Finally, you can retrieve and call the symbols for RCI subprograms, or any subprogram exported by your Ada units, and call them as though they were normal C routines.
About the Author
Thomas Quinot holds an engineering degree from Télécom Paris and a PhD from Université Paris VI. The main contribution of his research work is the definition of a flexible middleware architecture aiming at interoperability across distribution models. He joined AdaCore as a Senior Software Engineer in 2003, and is responsible for the distribution technologies. He also participates in the development, maintainance and support of the GNAT compiler.