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);
If we would like to get pointers to char, int, float & double
We can use the next functions
char *RING_API_GETCHARPOINTER(int nParameterNumber);
int *RING_API_GETINTPOINTER(int nParameterNumber);
void RING_API_ACCEPTINTVALUE(int nParameterNumber);
float *RING_API_GETFLOATPOINTER(int nParameterNumber);
void RING_API_ACCEPTFLOATVALUE(int nParameterNumber);
double *RING_API_GETDOUBLEPOINTER(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.
This section contains the most important functions required for common use-cases
For a complete list of function, including functions that store and process integers, pointers & objects, see this file: ring/language/include/rlist.h
If your list will be modified by Ring code then it’s required to use the _gc version of the functions
Lists created using ring_list_new()/ring_list_new_gc() must be deleted using ring_list_delete()/ring_list_delete_gc().
If we created a list using RING_API_NEWLIST then deleting the list is not required and may cause problems, because Ring VM will delete it automatically after the end of the caller scope.
Using RING_API_NEWLISTUSINGBLOCKS1D and RING_API_NEWLISTUSINGBLOCKS2D is similar to RING_API_NEWLIST but we have better performance when we create large lists.
// Create and delete lists
List * RING_API_NEWLIST
List * RING_API_NEWLISTUSINGBLOCKS1D(int nItems)
List * RING_API_NEWLISTUSINGBLOCKS2D(int nRows,int nColumns)
List * ring_list_new_gc ( void *pState,unsigned int nSize ) ;
List * ring_list_delete_gc ( void *pState,List *pList ) ;
List * ring_list_new ( unsigned int nSize ) ;
List * ring_list_delete ( List *pList ) ;
// Add Items
void ring_list_adddouble_gc ( void *pState,List *pList,double x ) ;
void ring_list_addstring_gc ( void *pState,List *pList,const char *str ) ;
void ring_list_addstring2_gc ( void *pState,List *pList,const char *str,int nStrSize ) ;
List * ring_list_newlist_gc ( void *pState,List *pList ) ;
void ring_list_adddouble ( List *pList,double x ) ;
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 ) ;
// Get List Size
int ring_list_getsize( List *pList );
// Check Item Type
int ring_list_isdouble ( List *pList, unsigned int index ) ;
int ring_list_isstring ( List *pList, unsigned int index ) ;
int ring_list_islist ( List *pList, unsigned int index ) ;
int ring_list_isobject ( List *pList ) ;
// Get Items
double ring_list_getdouble ( List *pList, unsigned int index ) ;
char * ring_list_getstring ( List *pList, unsigned int index ) ;
int ring_list_getstringsize ( List *pList, unsigned int index ) ;
String * ring_list_getstringobject ( List *pList, unsigned int index ) ;
List * ring_list_getlist ( List *pList, unsigned int index ) ;
// Insert Items
void ring_list_insertdouble_gc ( void *pState,List *pList,unsigned int nPos,double x ) ;
void ring_list_insertstring_gc ( void *pState,List *pList,unsigned int nPos,const char *str ) ;
void ring_list_insertstring2_gc ( void *pState,List *pList,unsigned int nPos,const char *str,int nStrSize ) ;
List * ring_list_insertlist_gc ( void *pState,List *pList,unsigned int nPos ) ;
void ring_list_insertdouble ( List *pList,unsigned int nPos,double x ) ;
void ring_list_insertstring ( List *pList,unsigned int nPos,const char *str ) ;
void ring_list_insertstring2 ( List *pList,unsigned int nPos,const char *str,int nStrSize ) ;
List * ring_list_insertlist ( List *pList,unsigned int nPos ) ;
// Set Items
void ring_list_setdouble_gc ( void *pState,List *pList, unsigned int index ,double number ) ;
void ring_list_setstring_gc ( void *pState,List *pList, unsigned int index ,const char *str ) ;
void ring_list_setstring2_gc ( void *pState,List *pList, unsigned int index ,const char *str,int nStrSize ) ;
void ring_list_setlist_gc ( void *pState,List *pList, unsigned int index ) ;
void ring_list_setdouble ( List *pList, unsigned int index ,double number ) ;
void ring_list_setstring ( List *pList, unsigned int index ,const char *str ) ;
void ring_list_setstring2 ( List *pList, unsigned int index ,const char *str,int nStrSize ) ;
void ring_list_setlist ( List *pList, unsigned int index ) ;
// Delete Items
void ring_list_deleteitem_gc ( void *pState,List *pList,unsigned int index ) ;
void ring_list_deleteallitems_gc ( void *pState,List *pList ) ;
void ring_list_deleteitem ( List *pList,unsigned int index ) ;
void ring_list_deleteallitems ( List *pList ) ;
// Copy Lists
void ring_list_copy_gc ( void *pState,List *pNewList, List *pList ) ;
void ring_list_copy ( List *pNewList, List *pList ) ;
// Find Items
int ring_list_findstring ( List *pList,const char *str,unsigned int nColumn ) ;
int ring_list_finddouble ( List *pList,double nNum1,unsigned int nColumn ) ;
// Swap
void ring_list_swap ( List *pList,unsigned int x,unsigned int y ) ;
void ring_list_swaptwolists ( List *pList1, List *pList2 ) ;
// Print
void ring_list_print ( List *pList ) ;
void ring_list_print2 ( List *pList,unsigned int nDecimals ) ;
void ring_list_printobj ( List *pList, unsigned int nDecimals ) ;
RING_API_STATE
To modify or delete the Lists and Strings created using Ring code, We have to use specific Lists/Strings functions. These functions requires that the Ring State to be passed as the first parameter.
Also, these functions add _gc to the function name which means that it uses the Ring Garbage Collector.
Some of these functions are
void ring_list_setdouble_gc ( void *pState, List *pList, int index , double number ) ;
void ring_list_adddouble_gc ( void *pState, List *pList, double x ) ;
void ring_list_deleteitem_gc ( void *pState, List *pList, int index ) ;
Example:
The next C code implement the filterList() function
We pass a list and a number to this function and it will check each item in the list.
If the item value is not greater than the passed number, then the item will be deleted.
In the implementation we uses ring_list_deleteitem_gc() function to delete the item and we pass Ring state as the first parameter using RING_API_STATE.
RING_FUNC(ring_filterlist)
{
List *pList;
int x;
// Check Parameters Count
if (RING_API_PARACOUNT != 2) {
RING_API_ERROR(RING_API_MISS2PARA);
return;
}
// Check Parameters Type
if ( ! ( RING_API_ISLIST(1) && RING_API_ISNUMBER(2) ) ) {
RING_API_ERROR(RING_API_BADPARATYPE);
return;
}
// Filter List Items
pList = RING_API_GETLIST(1);
for(x = ring_list_getsize(pList) ; x >= 1 ; x--)
if ( ring_list_isdouble(pList,x) )
if ( ! (ring_list_getdouble(pList,x) >
RING_API_GETNUMBER(2)) )
ring_list_deleteitem_gc(RING_API_STATE,pList,x) ;
// Return Output
RING_API_RETLIST(pList);
}
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.
As in List functions, the GC version of these functions (which add _gc to the name) exist.
Here, Using the GC version of the functions is not necessary for stability but it could be helpful for performance.
Why it’s not necessary for stability as we have in the List functions?
Because Ring API uses List * to pass lists from Ring VM to C code and also uses List * to return lists from C code to Ring. These lists are passed by pointers, They are the same lists, and we need the same memory functions to process the list items.
While when using Strings, Ring API create new strings when using RING_API_GETSTRING() or RING_API_RETSTRING()
For a complete list of function, including functions that uses the GC, see this file: ring/language/include/rstring.h
// Create and delete strings
String * ring_string_new ( const char *str ) ;
String * ring_string_new2 ( const char *str,int nStrSize ) ;
String * ring_string_delete ( String *pString ) ;
// Get string
char * ring_string_get ( String *pString ) ;
int ring_string_size ( String *pString ) ;
// Add text to the string
void ring_string_add ( String *pString,const char *str ) ;
void ring_string_add2 ( String *pString,const char *str,int nStrSize ) ;
// Change the string
void ring_string_set ( String *pString,const char *str ) ;
void ring_string_set2 ( String *pString,const char *str,int nStrSize ) ;
void ring_string_setfromint ( String *pString,int x ) ;
// Print the string
void ring_string_print ( String *pString ) ;
// Convert to lower/UPPER case
void ring_string_tolower(String *pString)
void ring_string_toupper(String *pString)
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 ) ;
// Find
char * ring_string_find ( char *cStr1,char *cStr2 ) ;
char * ring_string_find2 ( char *cStr1,int nStrSize1,char *cStr2,int nStrSize2 ) ;
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_gc(RING_API_STATE,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_gc() 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.
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