Back to Contents


Implementing C-Extensions

Summary:

See also: Programs, Installation and Setup.


Basics

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.

Warning: Using C functions in your applications can cause problems when you port the application to another platform. For example, you can expect problems when porting an application from UNIX to Windows and vice versa. Portability problems can also occur when using incompatible C data types or when using platform-specific system calls.

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.


Binding Static C-Extensions

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:

  1. Define the list of global variables and functions in the C interface file.
  2. Compile the C modules implementing the functions.
  3. Create static libraries with your C modules (if needed).
  4. Link a runner with the compiled C interface file and C modules and/or libraries.

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.


Binding Dynamic C-Extensions

Using Dynamic C-Extensions brings more flexibility, as there is no need to re-link the runner to use C functions.

Warning: Global variables cannot be shared when using Dynamic C-Extensions.

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.

Warning: Read carefully the man page of the ld dynamic loader, and any documentation of your operating system, related to shared libraries. Some platforms require specific configuration and command line options when linking a shared library, or when linking a  program using a shared library (+s option on HP for example).

In order to bind Dynamic C-Extensions to the runtime system, you must:

  1. Define the list functions in the C interface file.
  2. Compile the C interface file.
  3. Compile the C modules
  4. Create the shared library with the compiled C interface file and C modules.
  5. Specify the shared library to the runtime system.

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 = 3
02 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.


The C Interface File

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.

Warning: When defining the interface file for a Dynamic C Extension library, you must define the FGL_API_MAIN macro before including the fglExt.h file, and NOT define the usrData array (no global variables can be shared with dynamic C extensions).

01 #define FGL_API_MAIN
02 #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.

Example:

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 };

The BDL Stack

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.


Calling C functions from BDL

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 )

Warning: function-name must be written in lowercase letters. The fglcomp compiler converts all BDL functions names to lowercase.

The C function must be declared in the usrFunctions array in the C Interface File, as described below.


Calling BDL functions from C

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.

Warning: function-name must be written in lowercase letters (The fglcomp compiler converts all BDL functions names to lowercase)

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.

Example:

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 }

Sharing Global Variables (Static C Extensions only!)

From the C functions, it is possible to access global variables declared by the program, when using static C extensions.

Warning: Sharing global variables is supported for backward compatibility, it is not recommended to share global variables, encapsulate data with set/get functions instead.

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.

Warning: Global RECORD and ARRAY statements are not supported.

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.

Example:

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)

Example:

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 };

Example 1: Binding Static C Extensions

Create the "module1.c" file:

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 }

Create the "myext.c" C interface file:

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 };

Compile your C module:

$ gcc -c module1.c -I $FGLDIR/include

Create a new runtime system program:

$ fglmkrun -d nodb -o runner -ext extfile.c -aob "module1.o"

Create the BDL program "main.4gl":

01 MAIN
02   DEFINE str1, str2 VARCHAR(100)
03   CALL fgl_split("Hello World") RETURNING str1, str2
04   DISPLAY str1, str2
05 END MAIN

Compile the BDL program:

$ export FGLRUN=./runner
$ fgl2p -o prog.42r main.4gl

Run the program with the new runtime system program:

$ ./runner prog.42r

Example 2: Binding Dynamic C Extensions

Warning: This example has shows how to create a dynamic C extension library on Linux using gcc. The command line options to compile and link shared libraries can change depending on the operating system and compiler/linker used.

This example uses the same C file as in the first example:

Create the "myext.c" C interface file (pay attention to FGL_API_MAIN definition):

01 #define FGL_API_MAIN
02 #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 };

Compile the C Module and the interface file:

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

Create the shared library:

gcc -shared -o myextension.so extfile.o module1.o

Compile the 4gl module:

fglcomp main.4gl

To test, run the program by specifying the library (without .so extension!):

fglrun -e myextension main

Create an FGLPROFILE with following entries:

01 fglrun.extension.file.count = 1
02 fglrun.extension.file.1.name = "myextension"

Run the program again without the -e option:

fglrun main