On Nov 9, 2005, at 5:20 PM, Neil Carlson wrote:
> Some software apps are made user-extensible by providing a mechanism
> for dynamically loading a user-written shared object file into the
> executable code. I'm know the details are platform-specific, and
> almost certainly involve some special C code, but can anyone point me
> to some how-tos, examples, etc., especially when it is done in the
Attached below is some production code of mine written around a decade
ago. This stuff is still in daily use (more like hourly, if not
minutely - it is used a lot here). I don't have a writeup explaining
the details, but I think you ought to be able to follow it roughly.
It uses the C dl (dynamic link) library and is thus highly
system-dependent in a way... but I've been able to port it to multiple
Unix platforms without horribly much change. I've just attached the
production code, without bothering to simplify it down, so it is larger
than really needed for a minimal example, I'm afraid. But I do have the
dynamic load stuff segregated by itself into into these files, so the
amount of distraction shouldn't be too bad. The main points involved
are
1. The C dl routines (dlsym, dlopen, dlclose). See man pages or other
sources for the details on these. They provide the core functionality.
My file dload.c is just wrappers to call them from Fortran.
2. Faking procedure pointers. From dlsym you get back the address of
the procedure to be called. How to then call a procedure with that
address is ... tricky. This should become a lot simpler with the
procedure pointers and C interop stuff in f2003. In fact, basically,
what I have here is a horribly system-dependent hack for the f2003
C_F_PROCPOINTER procedure. If you have a compiler that supports
pointers to procedures, you can probably do this part a lot more
cleanly.
See the wrapDlCalc.c file for the messy C wrappers for doing this for
the procedures I needed. I'll admit that I "cheated" to figure out just
the right C syntax for some of this stuff. I'd have *never* figured out
the combination of declaring " void (**sub)()" and then invoking it
with "(*sub)(args)" without cheating and looking at the C code
generated by the NAG f90 compiler. Again, this bit is
compiler-dependent (but is likely to look at least something like
this).
My dlinkCalc.f90 file is the Fortran module that wraps around this (and
is all that the rest of the program sees).
You won't be able to actually compile and run this. It is part of a
much larger program. But I post it in the hope that you can get some
good out of reading it.
Oh, and this is all just the code to use the dynamically loaded stuff.
Building an appropriate dynamically loadable library was relatively
straightforward - nothing "funny" at all required in the Fortran code.
You just need to use the appropriate linker switches to make a .so file
(that's the dynamically loadable module file) instead of an executable.
The hardest part tends to be handling things like other libraries that
are used by your dynamically loaded code. (Namely the Fortran runtime
libraries). Again, I don't have anything special in teh Fortran for
that, but it can require some Makefile "trickery", the details of which
can vary with the compiler you are using (namely depending on how the
compiler handles its runtime library).
/* $Id: dload.c,v 1.1.1.1 2003/03/06 19:38:16 maine Exp $ */
/* $Name: $ */
/* dload.c
* Wrapper routines for dynamic loading of sharable libraries.
*
* Version for SunOS 4.x and 5.x (Solaris 2), plus linux.
* 22 Apr 92, Richard Maine: Version 1.0
* 19 Nov 96, Richard Maine: add underscore in dl_symbol
* 19 Mar 97, Richard Maine: merge SunOS4 and Sol2 versions with ifdef.
* 3 Dec 02, Richard Maine: linux is just like Sol2. Accomodate in ifdef.
*/
/* Defines to make routines fortran-callable */
#define dl_open dl_open_
#define dl_close dl_close_
#define dl_symbol dl_symbol_
#include <dlfcn.h>
/* Open a sharable library. */
void dl_open(path, dl_ptr, path_len)
char *path;
void **dl_ptr;
int path_len;
{
int i;
char cstring[129];
/* Make path into a c string */
for (i=0; (i<path_len) && (i<127) && (path[i] != ' '); i++)
cstring[i] = path[i];
cstring[i] = '\0';
*dl_ptr = dlopen(cstring,1);
/* If we want to get error text, this is a way, but I doubt we need it. */
/* if (!*dl_ptr) printf("dlopen err: %s\n",dlerror()); */
}
/* Close a sharable library. */
void dl_close(dl_ptr)
void **dl_ptr;
{
int i;
i = dlclose(*dl_ptr);
/* If we want to get error text, this is a way, but I doubt we need it. */
/* if (i) printf("dlclose err: %s\n",dlerror()); */
}
/* Find a symbol address in a sharable library. */
void dl_symbol(dl_ptr, sym_name, sym_address, sym_name_len)
void **dl_ptr;
char *sym_name;
void (**sym_address)();
int sym_name_len;
{
int i;
char cstring[130];
/* Make sym_name into a c string, possibly prefixing an underscore. */
cstring[0] = '_';
for (i=0; (i<sym_name_len) && (i<127) && (sym_name[i] != ' '); i++)
cstring[i+1] = sym_name[i];
cstring[i+1] = '\0';
#ifndef SUNOS4
*sym_address = dlsym(*dl_ptr, &cstring[1]);
#else
*sym_address = dlsym(*dl_ptr, cstring);
#endif
/* If we want to get error text, this is a way, but I doubt we need it. */
/* if (!*sym_address) printf("dlsym err: %s\n",dlerror()); */
}
/* $Id: wrapDlCalc.c,v 1.1.1.1 2003/03/06 19:38:16 maine Exp $ */
/* $Name: $ */
/* wrapDlCalc.c
* Wrapper routines for calling dynamically loaded calcs.
* These all call the subroutine at the address passed as the first argument.
* The remaining arguments are passed through unchanged.
*
* Version for NAG f90 version 1.2 on SunOS 4.1.x
* 22 Apr 92, Richard Maine: Version 1.0
*/
/* Defines to make routines fortran-callable */
#define call_open_calc call_open_calc_
#define call_close_calc call_close_calc_
#define call_request_calc call_request_calc_
#define call_do_calc call_do_calc_
void call_open_calc (sub, calc_handle, calc_parameters, calc_string,
input_list, output_list, calc_description, error, len1, len2)
void (**sub)();
void *calc_handle, *calc_parameters, *input_list, *output_list, *error;
char *calc_string, *calc_description;
int len1, len2;
{
(*sub)(calc_handle, calc_parameters, calc_string,
input_list, output_list, calc_description, error, len1, len2);
}
void call_close_calc (sub, calc_handle)
void (**sub)();
void *calc_handle;
{
(*sub)(calc_handle);
}
void call_request_calc (sub, calc_handle, output_used, input_used)
void (**sub)();
void *calc_handle, *output_used, *input_used;
{
(*sub)(calc_handle, output_used, input_used);
}
void call_do_calc (sub, calc_handle, reset, time, input_data, output_data,
error)
void (**sub)();
void *calc_handle, *reset, *time, *input_data, *output_data, *error;
{
(*sub)(calc_handle, reset, time, input_data, output_data, error);
}
--
Richard Maine | Good judgment comes from experience;
[log in to unmask] | experience comes from bad judgment.
| -- Mark Twain
|