Summary:
See also: Programs, Installation and Setup.
With C-Extensions, you can integrate your own C libraries in the runtime system, allowing C functions calls from the BDL code. This feature allows you to extend the language with custom libraries, or existing standard libraries, just by writing some 'wrapper functions' to interface with BDL.
C-Extensions libraries can be linked statically to fglrun, or, since version 1.30, C-Extensions can be loaded from shared libraries. Using shared libraries (or DLLs on Windows) avoids the need to re-link the fglrun program and simplifies deployment.
Your C functions must be known by the runtime system. Therefore, you define your C functions in the C interface file by specifying the name of the function, the C function pointer, the number of parameters and the number of returned values.
Function parameters and returned values are passed/returned on the legal BDL stack, using pop/push functions. Take care to pop and push the exact number of parameters/returns expected by the caller, otherwise a fatal stack error will be raise at runtime.
Static C-Extensions are provided for backward compatibility. When using Genero BDL version 1.30 or higher, we recommend you to use Dynamic C-Extensions instead.
In order to bind Static C-Extensions to the runtime system, you must:
You bind the Static C-Extension when creating the runner with the fglmkrun tool, by specifying the -ext/-aob/-alb options:
$ fglmkrun -d nodb -o runner -ext extfile.c -aob "module1.o module2.o" -alb "mylib1.a"
See Example 1.
Using Dynamic C-Extensions brings more flexibility, as there is no need to re-link the runner to use C functions.
The runtime system can load several Dynamic C-Extensions libraries, allowing you to properly split your libraries by defining each group of functions in separate C interface files.
In order to bind Dynamic C-Extensions to the runtime system, you must:
At runtime, you specify the Dynamic C-Extensions libraries to be loaded by using fglrun's -e option or FGLPROFILE entries.
The -e option is provided for development, use the FGLPROFILE settings in deployment.
The next example shows the usage of fglrun's -e option (you do not specify the .so or .dll file extension):
$ fglrun -e mylib program
The following lines show extension libraries defined in FGLPROFILE:
01
fglrun.extension.file.count = 302
fglrun.extension.file.1.name = "mylib1"03
fglrun.extension.file.2.name = "mylib2"04
fglrun.extension.file.3.name = "mylib3"
Tip: If you want to link a .42r program, you must specify the Dynamic C-Extensions libraries either with fglrun's -e option or FGLPROFILE entries. The following example links a program by specifying two extension libraries with the -e option:
$ fglrun -l -e lib1 -e lib2 -o program.42r main.42m module1.42m
See Example 2.
In order to make your C functions visible to the runtime system, you must define all the functions in the C Interface File. This is a C source file which defines two arrays of structures: The usrData array defines shared global variables (not used with dynamic C extensions) and the usrFunctions array defines C functions that can be called from BDL. The last record of each array must be a line with all the elements set to 0, to define the end of the list.
By default, the software package provides an empty C Interface File in $FGLDIR/lib/fglExt.c:
01
#include <f2c/fglExt.h>02
UsrData usrData[]={03
{0,0}04
};05
UsrFunction usrFunctions[]={06
{0,0,0,0}07
};
You can copy this default file and adapt it to your own needs.
01
#define FGL_API_MAIN02
#include <f2c/fglExt.h>
The first member of a usrFunctions element is the BDL name of the function, provided as a character string. The second member is the C function symbol. Therefore, you typically do a forward declaration of the C functions before the usrFunctions array initializer. The third member is the number of BDL parameters passed thru the stack to the function. The last member is the number of output parameters returned on the stack by the function; you can use -1 to specify a variable number of arguments.
01
#include <f2c/fglExt.h>02
03
UsrData usrData[]={04
{0,0}05
};06
07
int c_log(int);08
int c_cos(int);09
int c_sin(int);10
11
UsrFunction usrFunctions[]={12
{"log",c_log,1,1},13
{"cos",c_cos,1,1},14
{"sin",c_sin,1,1},15
{0,0,0,0}16
};
To pass values between a C function and a program, the C function and the runtime system use the BDL stack. The int parameter of the C function defines the number of input parameters passed on the stack, and the function must return an int value defining the number of values returned on the stack.
The runtime system library includes a set of functions to retrieve the values passed as parameters on the stack. The following table shows the library functions provided to pop values from the stack into C buffers:
Function | BDL Type | Notes |
void popdate(Date *dst); | DATE | Integer value corresponding to days since 12/31/1899. |
void popint(int *); | INTEGER | System dependent integer value |
void popshort(short *); | SMALLINT | 2 bytes integer value |
void poplong(long *); | INTEGER | 4 bytes integer value |
void popflo(float *); | SMALLFLOAT | 4 bytes floating point value |
void popdub(double *); | FLOAT | 8 bytes floating point value |
void popdec(dec_t *); | DECIMAL | See structure definition in $FGLDIR/include/f2c headers |
void popquote(char *val, int len); | CHAR(n) | len = strlen(val)+1 (for the '/0') |
void popvchar(char *val, int len); | VARCHAR(n) | len = strlen(val)+1 (for the '/0') |
void popdtime(dtime_t*,int size); | DATETIME | See structure definition in $FGLDIR/include/f2c headers |
void popinv(intrvl_t* dst,int size); | INTERVAL | See structure definition in $FGLDIR/include/f2c headers |
void poplocator(struct loc_t**dst); | BYTE, TEXT | See structure definition in $FGLDIR/include/f2c headers |
When using a pop function, the value is copied from the stack to the local C variable and the value is removed from the stack.
In BDL, Strings (CHAR, VARCHAR) are not terminated by '\0'. Therefore, the C variable must have one additional character to store the '\0'. For example, the equivalent of a VARCHAR(100) in BDL is a char [101] in C.
To return a value from the C function, you must use one of the following functions provided in the runtime system library:
Function | BDL Type | Notes |
void pushdate(Date v); | DATE | Integer value corresponding to days since 12/31/1899. |
void pushdec(dec_t* val,unsigned decp); | DECIMAL | See structure definition in $FGLDIR/include/f2c headers |
void pushint(int v); | INTEGER | System dependent integer value |
void pushlong(long v); | INTEGER | 4 bytes integer value |
void pushshort(short v); | SMALLINT | 2 bytes integer value |
void pushflo(float* v); | SMALLFLOAT | 4 bytes floating point value |
void pushdub(double* v); | FLOAT | 8 bytes floating point value |
void pushquote(char *val, int l); | CHAR(n) | len = strlen(val) (without '\0') |
void pushvchar(char *val, int l); | VARCHAR(n) | len = strlen(val) (without '\0') |
void pushdtime(dtime_t*); | DATETIME | See structure definition in $FGLDIR/include/f2c headers |
void pushinv(intrvl_t*val); | INTERVAL | See structure definition in $FGLDIR/include/f2c headers |
When using a push function, the value of the C variable is copied at the top of the stack.
With C-Extensions you can call C functions from the program, in the same way that you call normal functions.
The C functions that can be called from BDL must use the following signature:
int function-name( int )
The C function must be declared in the usrFunctions array in the C Interface File, as described below.
It is possible to call an BDL function from a C function, by using the fgl_call macro in your C function, as follows:
fgl_call ( function-name, nbparams );
Here function-name is the name of the BDL function to call, and nbparams is the number of parameters pushed on the stack for the BDL function.
The fgl_call macro is converted to a function that returns the number of values returned on the stack. The BDL function parameters must be pushed on the stack before the call, and the return values must be popped from the stack after returning. See Calling C functions from BDL for more details about push and pop library functions.
01
#include <stdio.h>02
#include <f2c/fglExt.h>03
int c_fct( int n )04
{05
int rc, r1, r2;06
pushint(123456);07
rc = fgl_call( fgl_fct, 1 );08
if (rc != 2) ... error ...09
popint(&r1);10
popint(&r2);11
return 0;12
}
From the C functions, it is possible to access global variables declared by the program, when using static C extensions.
Each global variable must be defined in the C Interface File with a GLOB_ macros (global variable definition macro), and as an external C variable and with the CNAME macro in the C module. Since global variables are instantiated in the runtime system, not by the C extensions, you must declare the variable as external. The global variables are internally redefined with a different name than in the source code, so you must use the CNAME macro to reference them in your C functions, and declare the global variables in the C Interface File with GLOB_ macros.
To declare a global variable in your C module, use a #define pre-processor instruction:
#define varname CNAME( varname )
varname is the name of the global variable as defined in the program.
01
#include <f2c/fglExt.h>02
#define num CNAME(num)03
#define str CNAME(str)04
extern int num;05
extern char str[101];06
in myfunc(int n)07
{08
num = 100;09
return 0;10
}
To declare a global variable in the C Interface File, use the GLOB_ macros. The following table shows all global variable declaration macros provided in the FGLDIR/include/f2c header files:
BDL Type | C Macro |
CHAR | GLOB_CHAR(name,size) |
VARCHAR |
GLOB_VARCHAR(name,size) |
SMALLINT | GLOB_SMALLINT(name) |
INTEGER | GLOB_INT(name) |
SMALLFLOAT | GLOB_SMALLFLOAT(name) |
FLOAT | GLOB_FLOAT(name) |
DECIMAL | GLOB_DECIMAL(name,scale,precision) |
MONEY | GLOB_MONEY(name,scale,precision) |
DATE | GLOB_DATE(name) |
01
#include <f2c/fglExt.h>02
GLOB_CHAR( str, 100 );03
GLOB_INT( num );04
UsrData usrData[]={05
GLOB(str),06
GLOB(num),07
{0,0}08
}09
UsrFunction usrFunctions[]={10
{0,0,0,0}11
};
01
#include <string.h>02
#include <f2c/fglExt.h>03
04
int fgl_split( int in_num );05
int fgl_split( int in_num )06
{07
char c1[101];08
char c2[101];09
char z[201];10
char *ptr_in;11
char *ptr_out;12
popvchar(z, 200); /* Getting input parameter */13
strcpy(c1, "");14
strcpy(c2, "");15
ptr_out = c1;16
ptr_in = z;17
while (*ptr_in != ' ' && *ptr_in != '\0')18
{19
*ptr_out = *ptr_in;20
ptr_out++;21
ptr_in++;22
}23
*ptr_out=0;24
ptr_in++;25
ptr_out = c2;26
while (*ptr_in != '\0')27
{28
*ptr_out = *ptr_in;29
ptr_out++;30
ptr_in++;31
}32
*ptr_out=0;33
pushvchar(c1, 100); /* Returning the first output parameter */34
pushvchar(c2, 100); /* Returning the second output parameter */35
return 2; /* Returning the number of output parameters (MANDATORY) */36
}
01
#include <f2c/fglExt.h>02
03
int fgl_split(int);04
05
UsrData usrData[]={06
{ 0, 0 }07
};08
09
UsrFunction usrFunctions[]={10
{ "fgl_split", fgl_split, 1, 2 },11
{ 0,0,0,0 }12
};
$ gcc -c module1.c -I $FGLDIR/include
$ fglmkrun -d nodb -o runner -ext extfile.c -aob "module1.o"
01
MAIN02
DEFINE str1, str2 VARCHAR(100)03
CALL fgl_split("Hello World") RETURNING str1, str204
DISPLAY str1, str205
END MAIN
$ export FGLRUN=./runner
$ fgl2p -o prog.42r main.4gl
$ ./runner prog.42r
This example uses the same C file as in the first example:
01
#define FGL_API_MAIN02
#include <f2c/fglExt.h>03
04
int fgl_split(int);05
06
UsrFunction usrFunctions[]={07
{ "fgl_split", fgl_split, 1, 2 },08
{ 0,0,0,0 }09
};
When building the shared library, the macro -D BUILDDLL should be defined. Most compiler accept it on command line.
gcc -c -D BUILDDLL -I $FGLDIR/include -g -fPIC module1.c gcc -c -D BUILDDLL -I $FGLDIR/include -g -fPIC extfile.c
gcc -shared -o myextension.so extfile.o module1.o
fglcomp main.4gl
fglrun -e myextension main
01
fglrun.extension.file.count = 102
fglrun.extension.file.1.name = "myextension"
fglrun main