A DLL written in C or C++ can contain any sort of functions written in these languages. Note that the parameters of a C or C++ function are called by reference and that the function return value is an integer. Thus, your function head has to be of the following type
EXTERN int EXPORT MyFunction(double *arg1, double *arg2, ....)
Note:
EXTERN int EXPORT sum (double *x, double *n, double *s)
s
in the introductory example is in fact the result of the function.
It is important to initialize this variable in XploRe before calling
the DLL function.
The file dll.h
is a header file which ensures that
your C program is compatible with all the mentioned compilers. You
may put dll.h
into your directory where you have your C/C++
sources.
Let us shortly examine the include file dll.h
:
#ifdef __cplusplus #define EXTERN extern "C" #else #define EXTERN #endif /**** Compiler depended definitions ****/ /**** Definition of EXPORT */ /**** Definition of nan, inf and math constants */ /**** Definition of fixed byte datatypes */ /**** GNU C++ */ #ifdef __GNUC__ #define EXPORT double dnan = 0.0/0.0, dinf = 1.0/0.0; #define INT1 signed char #define INT2 short #define INT4 int #define INT8 long #define DBL4 float #define DBL8 double #endif /**** Visual C++ */ #ifdef _MSC_VER #define EXPORT __declspec(dllexport) #include <ymath.h> double dnan = _Nan._D, dinf = _Inf._D; #define M_PI 3.14159265358979323846 #define INT1 __int8 #define INT2 __int16 #define INT4 __int32 #define INT8 __int64 #define DBL4 float #define DBL8 double #endif /**** Borland C++ */ #ifdef __BORLANDC__ #define EXPORT __export double dnan = 0.0/0.0, dinf = 1.0/0.0; #define INT1 __int8 #define INT2 __int16 #define INT4 __int32 #define INT8 __int64 #define DBL4 float #define DBL8 double #endif #ifdef __SC__ #define EXPORT __export #include <fp.h> double dnan = NAN, dinf = INFINITY; #define INT1 char #define INT2 short #define INT4 long // #define INT8 not supported by Symantec :( #define DBL4 float #define DBL8 double #endif /**** If none applies then set some defaults values */ #ifndef EXPORT #define EXPORT #define INT1 signed char #define INT2 short #define INT4 int #define INT8 long #define DBL4 float #define DBL8 double #endif typedef void* voidp;
The first lines of the include file define the macro
EXTERN
. Every C/C++ compiler should define a macro called
__cplusplus
if the code is compiled as C++. If it is
compiled as C this macro should not be defined.
The reason to define EXTERN
as extern "C"
is
that C++ uses different internal names for the functions. E.g. if
you compile the introductory example under Symantec C++ you will
get a function name like ?sum@@YAHPAN00@Z
which is not easy
to remember.
Then we start with the compiler dependend definitions. For
example the EXPORT
macro allows to export a function from a
DLL. Under Unix (Gnu C/C++) you do not need to declare but under
Windows you may have private routines. Every C/C++ compiler
generates a unique macro e.g. __GNUC__
the GNU C/C++
compiler or __BORLANDC__
for the Borland C/C++ compiler.
This macro is used to set the macro EXPORT
correctly such
that this function will be really exported. An alternative way
under Windows would be to define a vecsum.def
file, please
have a look at your compiler manual.
Furthermore some mathematical constants and values are defined
as well as the data types DBL8
and so on.
If you are using other compilers you are welcomed to add
them in
dll.h
.
Once you have compiled a DLL you might want to check which are the exportable functions and how they are named.
Under Windows you can use the Explorer:
Export Table
you will find the exported functions
Under Unix you might use the tool nm
. Just do nm
vecsum.so
which will show all accessible functions from
your shared library.
If you have already an compiler then you should use it. If not, we can give some non-objective results of our trials:
Compiler | Size of the vecsum DLL |
GNU C/C++ on SUN Solaris | 4 KB |
GNU C/C++ on Linux | 3 KB |
Borland C++ 5.02 | 48 KB |
Visual C++ 5.0 | 143 KB |
Symantec C++ 7.0 | 30 KB |
F77 on SUN Solaris | 4 KB |
G77 on Linux | 3 KB |
Absoft Pro Fortran 6.0 | 4 KB |
Let us consider a second example. This example implements the random number generator RANDU for uniform random numbers. (You should not use this generator in practice! We will study the problems with this generator below!)
The file randu.c consists of the following C code:
#include <math.h> #include "dll.h" /* The RANDU random number generator. Do not use this function for generating random numbers!! Already in three dimensions, points obtained from this generator are located on hyperplanes. double *x : vector, contains random numbers after execution double *n : scalar, length of x double *seed : scalar, seed of generator */ EXTERN int EXPORT randu (double *x, double *n, double *seed) { long i, nn= (long) *n; double a = 65539.0, m = 2147483648.0;/* 2^31 */ printf("Calling randu ...\n");/* This output goes to the shell! */ *x = *seed; for (i=1; i<nn; i++) *(x+i) = fmod(a * *(x+i-1),m); *seed=fmod (a * *(x+nn-1),m); for (i=0; i<nn; i++) *(x+i) = *(x+i)/m; return (0); }
You can call this generator with the following XploRe program
testrandu.xpl
:
dlclose() dlopen("randu.so") ; or dlopen("randu.dll") under Windows ; x=matrix(3,500) dlcall("randu",x,prod(dim(x)),1234567890) ; func("plot") plot(x')
The first line containing
dlclose
closes all
open DLLs. This is included for the case that you want you run the
macro more than once. The
dlopen
command opens the
DLL randu.so
(Linux) (or randu.dll
under Windows).
Next, we create a matrix x
of dimension 3 by 500. The
resulting uniform numbers will be stored in x
. Note that
the x
here is a matrix whereas the randu C function is
written for a vector. This is possible because all XploRe matrices
and arrays are columnwise stored in memory. So in fact, the
function randu
is applied to the vectorized x
.
For the seed of the generator, the value 1234567890 is chosen.
Finally we plot the colums of x
. By rotating the point
cloud you can easily find a projection where all columns of
x
are situated on only a few hyperplanes.