Extension using the C/C++ languages

We can extend the Ring Virtual Machine (RingVM) by adding new functions written in the C programming language or C++. The RingVM comes with many functions written in C that we can call like any Ring function.

We can extend the language by writing new functions then rebuilding the RingVM again, or we can create shared library (DLL/So/Dylib) file to extend the RingVM without the need to rebuild it.

The Ring language source code comes with two files to add new modules to the RingVM, ring_ext.h and ring_ext.c

ring_ext.h

The file ring_ext.h contains constants that we can change to include/exclude modules during the build process.

#ifndef ringext_h
#define ringext_h
/* Constants */
#define RING_VM_LISTFUNCS       1
#define RING_VM_REFMETA         1
#define RING_VM_MATH            1
#define RING_VM_FILE            1
#define RING_VM_OS              1
#define RING_VM_MYSQL           1
#define RING_VM_ODBC            1
#define RING_VM_OPENSSL         1
#define RING_VM_CURL            1
#define RING_VM_DLL             1
#endif

ring_ext.c

The file ring_ext.c check constants defined in ring_ext.h before calling the start-up function in each module.

Each module contains a function that register the module functions in the RingVM.

#include "ring.h"

void ring_vm_extension ( RingState *pRingState )
{
        /* Reflection and Meta-programming */
        #if RING_VM_REFMETA
                ring_vm_refmeta_loadfunctions(pRingState);
        #endif
        /* List Functions */
        #if RING_VM_LISTFUNCS
                ring_vm_listfuncs_loadfunctions(pRingState);
        #endif
        /* Math */
        #if RING_VM_MATH
                ring_vm_math_loadfunctions(pRingState);
        #endif
        /* File */
        #if RING_VM_FILE
                ring_vm_file_loadfunctions(pRingState);
        #endif
        /* OS */
        #if RING_VM_OS
                ring_vm_os_loadfunctions(pRingState);
        #endif
        /* MySQL */
        #if RING_VM_MYSQL
                ring_vm_mysql_loadfunctions(pRingState);
        #endif
        /* ODBC */
        #if RING_VM_ODBC
                ring_vm_odbc_loadfunctions(pRingState);
        #endif
        /* OPENSSL */
        #if RING_VM_OPENSSL
                ring_vm_openssl_loadfunctions(pRingState);
        #endif
        /* CURL */
        #if RING_VM_CURL
                ring_vm_curl_loadfunctions(pRingState);
        #endif
        /* DLL */
        #if RING_VM_DLL
                ring_vm_dll_loadfunctions(pRingState);
        #endif
}

Module Organization

Each module starts by include the ring header file (ring.h). This files contains the Ring API that we can use to extend the RingVM.

Each module comes with a function to register the module functions in the RingVM The registration is done by using RING_API_REGISTER() function.

The RING_API_REGISTER() function takes two parameters, the first is the function name that will be used by Ring programs to call the function. The second parameter is the function pointer in the C program.

for example, the ring_vmmath.c module contains the next code to register the module functions

#include "ring.h"

void ring_vm_math_loadfunctions ( RingState *pRingState )
{
        RING_API_REGISTER("sin",ring_vm_math_sin);
        RING_API_REGISTER("cos",ring_vm_math_cos);
        RING_API_REGISTER("tan",ring_vm_math_tan);
        RING_API_REGISTER("asin",ring_vm_math_asin);
        RING_API_REGISTER("acos",ring_vm_math_acos);
        RING_API_REGISTER("atan",ring_vm_math_atan);
        RING_API_REGISTER("atan2",ring_vm_math_atan2);
        RING_API_REGISTER("sinh",ring_vm_math_sinh);
        RING_API_REGISTER("cosh",ring_vm_math_cosh);
        RING_API_REGISTER("tanh",ring_vm_math_tanh);
        RING_API_REGISTER("exp",ring_vm_math_exp);
        RING_API_REGISTER("log",ring_vm_math_log);
        RING_API_REGISTER("log10",ring_vm_math_log10);
        RING_API_REGISTER("ceil",ring_vm_math_ceil);
        RING_API_REGISTER("floor",ring_vm_math_floor);
        RING_API_REGISTER("fabs",ring_vm_math_fabs);
        RING_API_REGISTER("pow",ring_vm_math_pow);
        RING_API_REGISTER("sqrt",ring_vm_math_sqrt);
        RING_API_REGISTER("unsigned",ring_vm_math_unsigned);
        RING_API_REGISTER("decimals",ring_vm_math_decimals);
        RING_API_REGISTER("murmur3hash",ring_vm_math_murmur3hash);
}

Tip

Remember that the function ring_vm_math_loadfunctions() will be called by the ring_vm_extension() function (in the ring_ext.c file).

Function Structure

Each module function may contains the next steps

1 - Check Parameters Count

2 - Check Parameters Type

3 - Get Parameters Values

4 - Execute Code/Call Functions

5 - Return Value

The structure is very similar to any function (Input - Process - Output) But here we will use the Ring API for the steps 1,2,3 and 5.

Check Parameters Count

We can check the parameters count using the RING_API_PARACOUNT macro.

We can compare RING_API_PARACOUNT with any numeric value using == or != operators.

Example:

if ( RING_API_PARACOUNT != 1 ) {
        /* code */
}

Example:

if ( RING_API_PARACOUNT == 1 ) {
        /* code */
}

Display Error Message

We can display error messages using the RING_API_ERROR() function.

The function will display the error and end the execution of the program.

Note

the behaviour of this function can be changed by the Ring code using Try/Catch/Done statements, so in your C code, use Return after this function.

Syntax:

RING_API_ERROR(const char *cErrorMsg);

The Ring API comes with some of predefined error messages that we can use

#define RING_API_MISS1PARA "Bad parameters count, the function expect one parameter"
#define RING_API_MISS2PARA "Bad parameters count, the function expect two parameters"
#define RING_API_MISS3PARA "Bad parameters count, the function expect three parameters"
#define RING_API_MISS4PARA "Bad parameters count, the function expect four parameters"
#define RING_API_BADPARATYPE    "Bad parameter type!"
#define RING_API_BADPARACOUNT   "Bad parameters count!"
#define RING_API_BADPARARANGE   "Bad parameters value, error in range!"
#define RING_API_NOTPOINTER     "Error in parameter, not pointer!"
#define RING_API_NULLPOINTER    "Error in parameter, NULL pointer!"
#define RING_API_EMPTYLIST      "Bad parameter, empty list!"

Check Parameters Type

We can check the parameter type using the next functions

int RING_API_ISNUMBER(int nParameterNumber);
int RING_API_ISSTRING(int nParameterNumber);
int RING_API_ISLIST(int nParameterNumber);
int RING_API_ISCPOINTER(int nParameterNumber);
int RING_API_ISPOINTER(int nParameterNumber);  // List or C Pointer

The output of these functions will be 1 (True) or 0 (False).

Get Parameters Values

We can get parameters values using the next functions

double RING_API_GETNUMBER(int nParameterNumber);
const char *RING_API_GETSTRING(int nParameterNumber);
int RING_API_GETSTRINGSIZE(int nParameterNumber);
List *RING_API_GETLIST(int nParameterNumber);
void *RING_API_GETCPOINTER(int nParameterNumber, const char *cPoinerType);
int RING_API_GETPOINTERTYPE(int nParameterNumber);

Return Value

We can return values from our function using the next functions.

RING_API_RETNUMBER(double nValue);
RING_API_RETSTRING(const char *cString);
RING_API_RETSTRING2(const char *cString,int nStringSize);
RING_API_RETLIST(List *pList);
RING_API_RETCPOINTER(void *pValue,const char *cPointerType);
RING_API_RETMANAGEDCPOINTER(void *pValue,const char *cPointerType,
                                void (* pFreeFunc)(void *,void *))

Function Prototype

When we define new function to be used for RingVM extension, we use the next prototype

void my_function_name( void *pPointer );

or we can use the RING_FUNC() Macro

RING_FUNC(my_function_name);

Sin() Function Implementation

The next code represents the sin() function implementation using the Ring API and the sin() C function.

void ring_vm_math_sin ( void *pPointer )
{
        if ( RING_API_PARACOUNT != 1 ) {
                RING_API_ERROR(RING_API_MISS1PARA);
                return ;
        }
        if ( RING_API_ISNUMBER(1) ) {
                RING_API_RETNUMBER(sin(RING_API_GETNUMBER(1)));
        } else {
                RING_API_ERROR(RING_API_BADPARATYPE);
        }
}

Fopen() and Fclose() Functions Implementation

The next code represents the fopen() function implementation using the Ring API and the fopen() C Function.

The function takes two parameters, the first parameter is the file name as string. The second parameter is the mode as string.

In the file ring_vmfile.h we have some constants to use as the pointer type like

#define RING_VM_POINTER_FILE    "file"
#define RING_VM_POINTER_FILEPOS "filepos"

The function implementation in ring_vmfile.c

void ring_vm_file_fopen ( void *pPointer )
{
        FILE *fp  ;
        if ( RING_API_PARACOUNT != 2 ) {
                RING_API_ERROR(RING_API_MISS2PARA);
                return ;
        }
        if ( RING_API_ISSTRING(1) && RING_API_ISSTRING(2) ) {
                fp = fopen(RING_API_GETSTRING(1),RING_API_GETSTRING(2));
                RING_API_RETCPOINTER(fp,RING_VM_POINTER_FILE);
        } else {
                RING_API_ERROR(RING_API_BADPARATYPE);
        }
}

The next code represents the fclose() function implementation

void ring_vm_file_fclose ( void *pPointer )
{
        FILE *fp  ;
        if ( RING_API_PARACOUNT != 1 ) {
                RING_API_ERROR(RING_API_MISS1PARA);
                return ;
        }
        if ( RING_API_ISCPOINTER(1) ) {
                fp = (FILE *) RING_API_GETCPOINTER(1,RING_VM_POINTER_FILE) ;
                if ( fp != NULL ) {
                        RING_API_RETNUMBER(fclose(fp));
                        RING_API_SETNULLPOINTER(1);
                }
        } else {
                RING_API_ERROR(RING_API_BADPARATYPE);
        }
}

From fopen() and fclose() implementation we learned

1 - how to return C pointer using RING_API_RETCPOINTER() function

2 - how to check if the parameter is a pointer using the RING_API_ISCPOINTER() function

3 - how to get C pointer value using the RING_API_GETCPOINTER() function

4 - how to set the C pointer variable (in RingVM) to NULL using the RING_API_SETNULLPOINTER() function

Ring API - List Functions

In this section we will learn about the list functions provided by the Ring API to create new lists and manipulate the list items.

List * ring_list_new ( int nSize ) ;
void ring_list_newitem ( List *pList ) ;
Item * ring_list_getitem ( List *pList,int index ) ;
List * ring_list_delete ( List *pList ) ;
void ring_list_deleteitem ( List *pList,int index ) ;
void ring_list_print ( List *pList ) ;
int ring_list_gettype ( List *pList, int index ) ;
void ring_list_setint ( List *pList, int index ,int number ) ;
void ring_list_addint ( List *pList,int x ) ;
void ring_list_setpointer ( List *pList, int index ,void *pValue ) ;
void ring_list_addpointer ( List *pList,void *pValue ) ;
void ring_list_setfuncpointer ( List *pList, int index ,void (*pFunc)(void *) ) ;
void ring_list_addfuncpointer ( List *pList,void (*pFunc)(void *) ) ;
int ring_list_isfuncpointer ( List *pList, int index ) ;
void ring_list_setdouble ( List *pList, int index ,double number ) ;
void ring_list_adddouble ( List *pList,double x ) ;
void ring_list_setstring ( List *pList, int index ,const char *str ) ;
void ring_list_setstring2 ( List *pList, int index ,const char *str,int nStrSize ) ;
void ring_list_addstring ( List *pList,const char *str ) ;
void ring_list_addstring2 ( List *pList,const char *str,int nStrSize ) ;
List * ring_list_newlist ( List *pList ) ;
List * ring_list_getlist ( List *pList, int index ) ;
void ring_list_setlist ( List *pList, int index ) ;
void ring_list_setactiveitem ( List *pList, Items *pItems, int index ) ;
void ring_list_copy ( List *pNewList, List *pList ) ;
int ring_list_isnumber ( List *pList, int index ) ;
int ring_list_isstring ( List *pList, int index ) ;
int ring_list_islist ( List *pList, int index ) ;
int ring_list_ispointer ( List *pList, int index ) ;
void ring_list_deleteallitems ( List *pList ) ;
void ring_list_insertitem ( List *pList,int x ) ;
void ring_list_insertint ( List *pList,int nPos,int x ) ;
void ring_list_insertdouble ( List *pList,int nPos,double x ) ;
void ring_list_insertpointer ( List *pList,int nPos,void *pValue ) ;
void ring_list_insertstring ( List *pList,int nPos,const char *str ) ;
void ring_list_insertstring2 ( List *pList,int nPos,const char *str,int nStrSize ) ;
void ring_list_insertfuncpointer ( List *pList,int nPos,void (*pFunc)(void *) ) ;
List * ring_list_insertlist ( List *pList,int nPos ) ;
int ring_list_isiteminsidelist ( List *pList,Item *pItem ) ;
int ring_list_findstring ( List *pList,const char *str,int nColumn ) ;
int ring_list_finddouble ( List *pList,double nNum1,int nColumn ) ;
void ring_list_sortnum ( List *pList,int left,int right,int nColumn ) ;
void ring_list_sortstr ( List *pList,int left,int right,int nColumn ) ;
int ring_list_binarysearchnum ( List *pList,double nNum1,int nColumn ) ;
int ring_list_binarysearchstr ( List *pList,const char *cFind,int nColumn ) ;
void ring_list_swap ( List *pList,int x,int y ) ;
double ring_list_getdoublecolumn ( List *pList,int nIndex,int nColumn ) ;
char * ring_list_getstringcolumn ( List *pList,int nIndex,int nColumn ) ;
void ring_list_genarray ( List *pList ) ;
void ring_list_deletearray ( List *pList ) ;
void ring_list_genhashtable ( List *pList ) ;
void ring_list_genhashtable2 ( List *pList ) ;
void ring_list_refcopy ( List *pNewList, List *pList ) ;
void ring_list_clear ( List *pList ) ;
/* Macro */
ring_list_isdouble(pList,index)
ring_list_isint(pList,index)
ring_list_deletelastitem(x)
ring_list_gethashtable(x)
ring_list_getint(pList,index)
ring_list_getpointer(pList,index)
ring_list_getfuncpointer(pList,index)
ring_list_callfuncpointer(pList,index,x)
ring_list_getdouble(pList,index)
ring_list_getstring(pList,index)
ring_list_getstringobject(pList,index)
ring_list_getstringsize(pList,index)
ring_list_getsize(x) (x->nSize)

Ring API - String Functions

In this section we will learn about the string functions provided by the Ring API to create new string and manipulate the string content.

String * ring_string_new ( const char *str ) ;
String * ring_string_new2 ( const char *str,int nStrSize ) ;
String * ring_string_delete ( String *pString ) ;
int ring_string_size ( String *pString ) ;
void ring_string_set ( String *pString,const char *str ) ;
void ring_string_set2 ( String *pString,const char *str,int nStrSize ) ;
void ring_string_add ( String *pString,const char *str ) ;
void ring_string_add2 ( String *pString,const char *str,int nStrSize ) ;
void ring_string_print ( String *pString ) ;
void ring_string_setfromint ( String *pString,int x ) ;
char * ring_string_lower ( char *cStr ) ;
char * ring_string_upper ( char *cStr ) ;
char * ring_string_lower2 ( char *cStr,int nStrSize ) ;
char * ring_string_upper2 ( char *cStr,int nStrSize ) ;
char * ring_string_find ( char *cStr1,char *cStr2 ) ;
char * ring_string_find2 ( char *cStr1,int nStrSize1,char *cStr2,int nStrSize2 ) ;
/* Macro */
ring_string_tolower(x)
ring_string_toupper(x)
ring_string_get(x)

MySQL_Columns() Function Implementation

The next code presents the MySQL_Columns() function implementation.

This function returns table columns information.

void ring_vm_mysql_columns ( void *pPointer )
{
        MYSQL *con  ;
        MYSQL_RES *result  ;
        int nColumns,x  ;
        MYSQL_ROW row  ;
        MYSQL_FIELD *field  ;
        List *pList, *pList2  ;
        if ( RING_API_PARACOUNT != 1 ) {
                RING_API_ERROR(RING_API_MISS1PARA);
                return ;
        }
        if ( RING_API_ISCPOINTER(1) ) {
                con = (MYSQL *) RING_API_GETCPOINTER(1,RING_VM_POINTER_MYSQL) ;
                if ( con == NULL ) {
                        return ;
                }
                result = mysql_store_result(con);
                if ( result == NULL ) {
                        RING_API_RETNUMBER(0);
                        return ;
                }
                pList = RING_API_NEWLIST ;
                nColumns = mysql_num_fields(result);
                if ( row = mysql_fetch_row(result) ) {
                        while ( field = mysql_fetch_field(result) ) {
                                pList2 = ring_list_newlist(pList);
                                ring_list_addstring(pList2,field->name);
                                ring_list_adddouble(pList2,field->length);
                                ring_list_adddouble(pList2,field->type);
                                ring_list_adddouble(pList2,field->flags);
                        }
                }
                mysql_free_result(result);
                RING_API_RETLIST(pList);
        } else {
                RING_API_ERROR(RING_API_BADPARATYPE);
        }
}

Lists are of type List, in the previous function we declared two pointers of type List using List *pList, *pList2;

Note

The function uses RING_API_NEWLIST to create new list instead of ring_list_new() to create the list in Temp. Memory related to the function scope. This way we can return the list from the function. Also we don’t delete the list, if it’s stored in a variable by Ring Code it will be saved, if not it will be automatically deleted by RingVM.

The list can contains sub lists, we used the function ring_list_newlist() to create a sublist.

The function ring_list_addstring() is used to add string items to the list/sublist.

The function ring_list_adddouble() is used to add numeric items to the list/sublist.

Note

All numeric items in lists returned from RingVM extension functions must be of type double and added to the list using ring_list_adddouble() function.

We return the list from the extension function using the RING_API_RETLIST() function.

Dynamic/Shared Libraries (DLL/So/Dylib) and LoadLib() function

Instead of rebuilding the RingVM after writing new functions using C/C++ and the Ring API, we can create a DLL/So/Dylib file and dynamically use the functions provided by this file in the runtime using the LoadLib() function.

Dynamic library example in C

#include "ring.h"

RING_DLL __declspec(dllexport)

RING_FUNC(ring_ringlib_dlfunc)
{
        printf("Message from dlfunc");
}

RING_LIBINIT
{
        RING_API_REGISTER("dlfunc",ring_ringlib_dlfunc);
}

the idea is to create the RING_LIBINIT function, this function will be called by the RingVM when we use the generated DLL file through the LoadLib() function.

Inside the RING_LIBINIT function we can register the module function or call a function that do the registration process for all of the module functions.

The next Ring code demonstrates how to use the DLL library during the runtime.

See "Dynamic DLL" + NL
LoadLib("ringlib.dll")
dlfunc()

Output:

Dynamic DLL
Message from dlfunc

Using RING_API_RETMANAGEDCPOINTER()

Using RING_API_RETMANAGEDCPOINTER() the Ring extensions written in C/C++ languages can return a managed pointer to Ring. This pointer can be controlled by the Ring VM using reference counting.

This is important to avoid the need to write code that free the unmanaged resources like QPixMap objects in RingQt.

Also the Code Generator for extensions is updated to automatically use RING_API_RETMANAGEDCPOINTER() based on need.

Syntax:

RING_API_RETMANAGEDCPOINTER(void *pValue,const char *cPointerType,
                                void (* pFreeFunc)(void *,void *))

Example:

The next example from ring_qt.cpp - QPixMap Class - Scaled() Method.

RING_FUNC(ring_QPixmap_scaled)
{
        QPixmap *pObject ;
        if ( RING_API_PARACOUNT != 5 ) {
                RING_API_ERROR(RING_API_BADPARACOUNT);
                return ;
        }
        RING_API_IGNORECPOINTERTYPE ;
        if ( ! RING_API_ISCPOINTER(1) ) {
                RING_API_ERROR(RING_API_BADPARATYPE);
                return ;
        }
        pObject = (QPixmap *) RING_API_GETCPOINTER(1,"QPixmap");
        if ( ! RING_API_ISNUMBER(2) ) {
                RING_API_ERROR(RING_API_BADPARATYPE);
                return ;
        }
        if ( ! RING_API_ISNUMBER(3) ) {
                RING_API_ERROR(RING_API_BADPARATYPE);
                return ;
        }
        if ( ! RING_API_ISNUMBER(4) ) {
                RING_API_ERROR(RING_API_BADPARATYPE);
                return ;
        }
        if ( ! RING_API_ISNUMBER(5) ) {
                RING_API_ERROR(RING_API_BADPARATYPE);
                return ;
        }
        {
                QPixmap *pValue ;
                pValue = new QPixmap() ;
                *pValue = pObject->scaled( (int ) RING_API_GETNUMBER(2),
                         (int ) RING_API_GETNUMBER(3),
                         (Qt::AspectRatioMode )  (int) RING_API_GETNUMBER(4),
                         (Qt::TransformationMode )  (int) RING_API_GETNUMBER(5));
                RING_API_RETMANAGEDCPOINTER(pValue,"QPixmap",ring_QPixmap_freefunc);
        }
}

The function that will free the memory takes two parameters (Ring State and the allocated Memory Pointer)

Example:

void ring_QPixmap_freefunc(void *pState,void *pPointer)
{
        QPixmap *pObject ;
        pObject = (QPixmap *) pPointer;
        delete pObject ;
}

Memory Functions

Ring API provides the next functions for Memory Management

These functions uses the Memory pool provided by Ring VM

RING_API_MALLOC(nSize)
RING_API_CALLOC(nItems,nItemSize)
RING_API_REALLOC(pPointer,nSize)
RING_API_FREE(pPointer)
RING_API_FREEFUNC
  • Use RING_API_MALLOC() instead of the malloc() function.

  • Use RING_API_CALLOC() instead of the calloc() function.

  • Use RING_API_REALLOC() instead of the realloc() function.

  • Use RING_API_FREE() instead of the free() function.

  • RING_API_FREEFUNC provides a function pointer to the ring_state_free() function