Recital

Login Register

In this article Barry Mavin, CEO and Chief Software Architect for Recital details how to Build C Extension Libraries to use with Recital.

Overview

It is possible to extend the functionaliy of Recital products using "Extension libraries" that can be written in C. These extension libraries, written using the Recital/SDK API, are dynamically loadable from all Recital 9 products. This includes:

  • Recital
  • Recital Server
  • Recital Web

Building C Extension Libraries

You can create C wrappers for virtually any native operating system function and access these from the Recital 4GL. Unlike traditional APIs which only handle the development of C functions that are callable from the 4GL, the Recital/SDK allows you to build Classes that are accessible from all Recital products. e.g. You could create a GUI framework for Linux that handles VFP system classes!

To deploy your C Extension Libraries, copy them to the following location:

Windows:

\Program Files\Recital\extensions

Linux/Unix:

/opt/recital/extensions

Please see the Recital/SDK API Reference documentation for further details.

Sample code

Listed below is the complete example of a C Extension Library.:

////////////////////////////////////////////////////////////////////////////////
#include "mirage_demo.h"  
      
////////////////////////////////////////////////////////////////////////////////
// Declare your functions and classes below as follows:
//
//    Recital Function Name, C Function Name, Type (Function or Class)
//
#define MAX_ELEMENTS    7
static  struct  API_SHARED_FUNCTION_TABLE api_function_table[MAX_ELEMENTS] = {
        {"schar",   "fnSamplesCharacter",   API_FUNCTION},
        {"stype",   "fnSamplesType",           API_FUNCTION},
        {"slog",    "fnSamplesLogical",        API_FUNCTION},
        {"snum",    "fnSamplesNumeric",    API_FUNCTION},
        {"sopen",   "fnSamplesOpen",         API_FUNCTION},
        {"myclass", "clsMyClass",               API_CLASS},
        {NULL,      NULL,                   -1}
};

////////////////////////////////////////////////////////////////////////////////
// Recital API initialization. This should be in only ONE of your C files
// **IT SHOULD NEVER BE EDITED OR REMOVED**  
INIT_API;


///////////////////////////////////////////////////////////////////////
// This is an example of passing a character parameter and returning one.
RECITAL_FUNCTION fnSamplesCharacter(void)
{
    char    *arg1;
    
    if (!_parse_parameters(PCOUNT, "C", &arg1)) { 
        ERROR(-1, "Incorrect parameters");
    }
    
    _retc(arg1);
}


///////////////////////////////////////////////////////////////////////
// This is an example of passing a numeric parameter and returning one.
RECITAL_FUNCTION fnSamplesNumeric(void)
{
    int arg1;
    
    if (!_parse_parameters(PCOUNT, "N", &arg1)) { 
        ERROR(-1, "Incorrect parameters");
    }
    
    _retni(arg1);
}


///////////////////////////////////////////////////////////////////////
// This is an example returns the data type of the parameter passed.
RECITAL_FUNCTION fnSamplesType(void)
{
    char    result[10];

    if (PCOUNT != 1) { 
        ERROR(-1, "Incorrect parameters");
    }
    
    switch (_parinfo(1)) {
        case API_CTYPE:
            strcpy(result, "Character");
            break;
        case API_NTYPE:
            strcpy(result, "Numeric");
            break;
        case API_LTYPE:
            strcpy(result, "Logical");
            break;
        case API_DTYPE:
            strcpy(result, "Date");
            break;
        case API_TTYPE:
            strcpy(result, "DateTime");
            break;
        case API_YTYPE:
            strcpy(result, "Currency");
            break;
        case API_ATYPE:
            strcpy(result, "Array");
            break;
        default:
            strcpy(result, "Unkown");
            break;
    }

    _retc(result);
}


///////////////////////////////////////////////////////////////////////
// This is an example returns "True" or False.
RECITAL_FUNCTION  fnSamplesLogical(void)
{
    char    result[10];
    int     arg1;
    
    if (!_parse_parameters(PCOUNT, "L", &arg1)) { 
        ERROR(-1, "Incorrect parameters");
    }
    
    if (arg1) strcpy(result, "True");
    else strcpy(result, "False");

    _retc(result);
}


///////////////////////////////////////////////////////////////////////
// This example opens a table.
RECITAL_FUNCTION fnSamplesOpen(void)
{
    char    *arg1;
    
    if (!_parse_parameters(PCOUNT, "C", &arg1)) { 
        ERROR(-1, "Incorrect parameters");
    }
    
    if (_parinfo(1) == API_CTYPE) {
        _retni(COMMAND(arg1));
    } else {
        _retni(-1);
    }
}

///////////////////////////////////////////////////////////////////////
// Define the MyClass CLASS using the API macros
///////////////////////////////////////////////////////////////////////
RECITAL_EXPORT int DEFINE_CLASS(clsMyClass)
{
    /*-------------------------------------*/ 
    /* Dispatch factory methods and return */
    /*-------------------------------------*/
    DISPATCH_FACTORY();
 
    /*---------------------------------*/
    /* Dispatch constructor and return */ 
    /*---------------------------------*/ 
    DISPATCH_METHOD(clsMyClass, Constructor);

    /*--------------------------------*/
    /* Dispatch destructor and return */
    /*--------------------------------*/
    DISPATCH_METHOD(clsMyClass, Destructor); 

    /*-----------------------------------*/
    /* Dispatch DEFINE method and return */
    /*-----------------------------------*/
    DISPATCH_METHOD(clsMyClass, Define);

    /*------------------------------*/
    /* Dispatch SET or GET PROPERTY */
    /* method for property NumValue */
    /* then return.                 */
    /*------------------------------*/
    DISPATCH_PROPSET(clsMyClass, NumValue);
    DISPATCH_PROPGET(clsMyClass, NumValue); 

    /*------------------------------*/
    /* Dispatch SET or GET PROPERTY */
    /* method for property LogValue */
    /* then return.                 */
    /*------------------------------*/
    DISPATCH_PROPSET(clsMyClass, LogValue);
    DISPATCH_PROPGET(clsMyClass, LogValue);

    /*-------------------------------*/
    /* Dispatch SET or GET PROPERTY  */
    /* method for property DateValue */
    /* then return.                 */
    /*-------------------------------*/
    DISPATCH_PROPSET(clsMyClass, DateValue);
    DISPATCH_PROPGET(clsMyClass, DateValue);

    /*-------------------------------*/
    /* Dispatch SET or GET PROPERTY  */
    /* method for property TimeValue */
    /* then return.                  */
    /*-------------------------------*/
    DISPATCH_PROPSET(clsMyClass, TimeValue);
    DISPATCH_PROPGET(clsMyClass, TimeValue);
 
    /*-------------------------------*/
    /* Dispatch SET or GET PROPERTY  */
    /* method for property CurrValue */
    /* then return.                  */
    /*-------------------------------*/
    DISPATCH_PROPSET(clsMyClass, CurrValue);
    DISPATCH_PROPGET(clsMyClass, CurrValue);

    /*-------------------------------*/
    /* Dispatch SET or GET PROPERTY  */
    /* method for property CharValue */
    /* then return.                  */
    /*-------------------------------*/
    DISPATCH_PROPSET(clsMyClass, CharValue); 
    DISPATCH_PROPGET(clsMyClass, CharValue);

    /*------------------------------*/
    /* Dispatch SET or GET PROPERTY */ 
    /* method for property ObjValue */ 
    /* then return.                 */
    /*------------------------------*/
    DISPATCH_PROPSET(clsMyClass, ObjValue);
    DISPATCH_PROPGET(clsMyClass, ObjValue);

    /*-----------------------------------*/
    /* If message not found return error */
    /*-----------------------------------*/
    OBJECT_RETERROR("Unknown message type");
}

////////////////////////////////////////////////////////////////////////////////
// Define METHOD handlers
////////////////////////////////////////////////////////////////////////////////
DEFINE_METHOD(clsMyClass, Constructor) 
{
        struct example_data *objectDataArea;

        /* Allocate memory for objects objectData area */
        objectDataArea = (struct example_data *) 
           malloc(sizeof(struct example_data));
        if (objectDataArea == NULL) return(-1);
    
        /* Assign the default property values */
        strcpy(objectDataArea->prop_charvalue, "Test API object");
        objectDataArea->prop_numvalue = 15.2827;
        objectDataArea->prop_logvalue = 'F';
        strcpy(objectDataArea->prop_datevalue, DATE_DATE());
        strcpy(objectDataArea->prop_timevalue, DATE_DATETIME());
        strcpy(objectDataArea->prop_currvalue, "15.2827");
        strcpy(objectDataArea->object_name, "APIobject");
        objectDataArea->prop_objvalue 
             = OBJECT_NEW(objectDataArea->object_name, "exception", NULL);
    
        /* Set the object objectData area */
        OBJECT_SETDATA((char *)objectDataArea);
    
        return(0);
}
  
DEFINE_METHOD(clsMyClass, Destructor) 
{
        struct example_data *objectData = (struct example_data *)OBJECT_GETDATA();

        if (objectData != NULL) {
            if (objectData->prop_objvalue != NULL) 
              OBJECT_DELETE(objectData->prop_objvalue);
            free(objectData);
            objectData = NULL;
        }
        return(0);
}

DEFINE_METHOD(clsMyClass, Define) 
{
    struct example_data *objectData = (struct example_data *)OBJECT_GETDATA();
    struct      API_EXPRESSION result;
    char        buffer[512];
    int         rc;

/* Check the object class */
    OBJECT_GETPROPERTY(objectData->prop_objvalue, "class", buffer);
    rc = OBJECT_GETARG(buffer, &result);
    if (result.errno == 0 && result.type == 'C' 
        && strcmp(result.character, "Exception") == 0) { 
        switch (OBJECT_GETARGC()) {
            case 1: 
                rc = OBJECT_GETPARAMETER(1, &result);
                if (result.errno == 0 && result.type == 'C') {
                    OBJECT_SETARG(buffer, &result);
                    rc = OBJECT_SETPROPERTY(objectData->prop_objvalue,
                         "message", buffer);
                }
                break;
            case 2: 
                rc = OBJECT_GETPARAMETER(2, &result);
                if (result.errno == 0 && result.type == 'N') {
                    OBJECT_SETARG(buffer, &result);
                    rc = OBJECT_SETPROPERTY(objectData->prop_objvalue,
                         "errorno", buffer);
                }
         }
    }

    result.type = 'L';
    result.logical = (rc == 0 ? 'T' : 'F');  
    OBJECT_RETRESULT(&result);
}

////////////////////////////////////////////////////////////////////////////////
// Define GET property handlers
////////////////////////////////////////////////////////////////////////////////
DEFINE_PROPERTYGET(clsMyClass, NumValue) 
{
    struct example_data *objectData = (struct example_data *)OBJECT_GETDATA();

    if (objectData == NULL) return(-1);

    OBJECT_RETPROPERTY('N', objectData->prop_numvalue);
}

DEFINE_PROPERTYGET(clsMyClass, LogValue) 
{
    struct example_data *objectData = (struct example_data *)OBJECT_GETDATA();

    if (objectData == NULL) return(-1);

    OBJECT_RETPROPERTY('L', objectData->prop_logvalue);
}  

DEFINE_PROPERTYGET(clsMyClass, DateValue) 
{
    struct example_data *objectData = (struct example_data *)OBJECT_GETDATA();

    if (objectData == NULL) return(-1);

    OBJECT_RETPROPERTY('D', objectData->prop_datevalue);
}

DEFINE_PROPERTYGET(clsMyClass, TimeValue) 
{
    struct example_data *objectData = (struct example_data *)OBJECT_GETDATA();

    if (objectData == NULL) return(-1);

    OBJECT_RETPROPERTY('T', objectData->prop_timevalue);
}

DEFINE_PROPERTYGET(clsMyClass, CurrValue) 
{
    struct example_data *objectData = (struct example_data *)OBJECT_GETDATA();

    if (objectData == NULL) return(-1);

    OBJECT_RETPROPERTY('Y', objectData->prop_currvalue);
}

DEFINE_PROPERTYGET(clsMyClass, CharValue) 
{
    struct example_data *objectData = (struct example_data *)OBJECT_GETDATA();

    if (objectData == NULL) return(-1);

    OBJECT_RETPROPERTY('C', objectData->prop_charvalue);
}

DEFINE_PROPERTYGET(clsMyClass, ObjValue) 
{
    struct example_data *objectData = (struct example_data *)OBJECT_GETDATA();

    if (objectData == NULL) return(-1);

    OBJECT_RETPROPERTY('O', objectData->prop_objvalue);
} 


////////////////////////////////////////////////////////////////////////////////
// Define SET property handlers
////////////////////////////////////////////////////////////////////////////////
DEFINE_PROPERTYSET(clsMyClass, NumValue) 
{
    struct example_data *objectData = (struct example_data *)OBJECT_GETDATA();
    struct API_EXPRESSION result;
    int rc = OBJECT_ERROR;

    OBJECT_GETVALUE(&result);
    if (result.errno == 0 && result.type == 'N') {
        objectData->prop_numvalue = result.number;
        rc = OBJECT_SUCCESS;
    }

    return(rc);
}

DEFINE_PROPERTYSET(clsMyClass, LogValue) 
{
    struct example_data *objectData = (struct example_data *)OBJECT_GETDATA();
    struct API_EXPRESSION result;
    int rc = OBJECT_ERROR;

    OBJECT_GETVALUE(&result);
    if (result.errno == 0 && result.type == 'L') {
        objectData->prop_logvalue = result.logical;
        rc = OBJECT_SUCCESS;
    }

    return(rc);
}

DEFINE_PROPERTYSET(clsMyClass, DateValue) 
{
    struct example_data *objectData = (struct example_data *)OBJECT_GETDATA();
    struct API_EXPRESSION result;
    int rc = OBJECT_ERROR;
    OBJECT_GETVALUE(&result);
    if (result.errno == 0 && result.type == 'D') {
        strcpy(objectData->prop_datevalue, DATE_DTOS(result.date));
        rc = OBJECT_SUCCESS;
    }

    return(rc);
}

DEFINE_PROPERTYSET(clsMyClass, TimeValue) 
{
    struct example_data *objectData = (struct example_data *)OBJECT_GETDATA();
    struct API_EXPRESSION result;
    int rc = OBJECT_ERROR;

    OBJECT_GETVALUE(&result);
    if (result.errno == 0 && result.type == 'T') {
        strcpy(objectData->prop_timevalue, DATE_TTOS(result.datetime));
        rc = OBJECT_SUCCESS;
    }

    return(rc); 
}

DEFINE_PROPERTYSET(clsMyClass, CurrValue) 
{
    struct example_data *objectData = (struct example_data *)OBJECT_GETDATA();
    struct API_EXPRESSION result;
    int rc = OBJECT_ERROR;

    OBJECT_GETVALUE(&result);
    if (result.errno == 0 && result.type == 'Y') {
        strcpy(objectData->prop_currvalue, CURR_YTOS(result.currency));
        rc = OBJECT_SUCCESS;
    }

    return(rc);
}

DEFINE_PROPERTYSET(clsMyClass, CharValue)  
{
    struct example_data *objectData = (struct example_data *)OBJECT_GETDATA();
    struct API_EXPRESSION result;
    int rc = OBJECT_ERROR;

    OBJECT_GETVALUE(&result);
    if (result.errno == 0 && result.type == 'C') {
        strcpy(objectData->prop_currvalue, result.character);
        rc = OBJECT_SUCCESS;
    }

    return(rc);
}

DEFINE_PROPERTYSET(clsMyClass, ObjValue) 
{
    struct example_data *objectData = (struct example_data *)OBJECT_GETDATA();
    OBJECT  objvalue;
    int rc = OBJECT_ERROR;

    if (OBJECT_GETTYPE() == 'O') {
        objvalue = OBJECT_GETOBJECT(); 
        objectData->prop_objvalue = OBJECT_ASSIGN(objvalue, objectData->object_name);
        rc = OBJECT_SUCCESS;
    }

    return(rc);
}
Published in Blogs
Read more...
Recital is a dynamic programming language particularly suited to the development of database applications. While Recital still supports standard procedural programming, new extensions to the language give you the power and flexibility of object-oriented programming. Object-oriented design and object-oriented programming represent a change in focus from standard procedural programming. This short primer will give you a good understanding of how to program object-oriented Recital.
Published in Blogs
Read more...

All temporary files created by Recital are stored in the directory specified by the environment variable DB_TMPDIR.

 
In order to have these files stored in memory first create a temporary directory
mkdir /opt/recital/tmp
 
Then mount the directory with the tmpfs command
mount -t tmpfs -o size=1g recitaltmpfs /usr/recital/tmp
 
Then change the DB_TMPDIR variable in the recital.conf to point to the newly created temporary directory.
Published in Blogs
Read more...


Recital is a dynamic programming language with an embedded high performance database engine particularly well suited for the development and deployment of high transaction throughput applications.

The Recital database engine is not a standalone process with which the application program communicates. Instead, the Recital database is an integral part of any applications developed in Recital. 

Recital implements most of the SQL-99 standard for SQL, but also provides lower level navigational data access for performing high transaction throughput. It is the choice of the application developer whether to use SQL, navigational data access, or a combination of both depending upon the type of application being developed.

The Recital database engine, although operating as an embedded database in the user process, multiple users and other background processes may access the same data concurrently. Read accesses are satisfied in parallel. Recital uses automatic record level locking when performing database updates. This provides for a high degree of database concurrency and superior application performance and differentiates the Recital database from other embeddable databases such as sqlite that locks the entire database file during writing. 

Key features of the Recital scripting language include:

  • High performance database application scripting language
  • Modern object-oriented language features
  • Easy to learn, easy to use
  • Fast, just-in-time compiled
  • Loosely-typed
  • Garbage collected
  • Static arrays, Associative arrays and objects
  • Develop desktop or web applications
  • Cross-platform support
  • Extensive built-in functions
  • Superb built-in SQL command integration
  • Navigational data access for the most demanding applications
  • Scripting  language is upward compatible with FoxPRO

Key features of the Recital database include:

  • A broad subset of ANSI SQL 99, as well as extensions
  • Cross-platform support
  • Stored procedures
  • Triggers
  • Cursors
  • Updatable Views
  • System Tables
  • Query caching
  • Sub-SELECTs (i.e. nested SELECTs)
  • Embedded database library
  • Fault tolerant clustering support
  • Chronological data versioning with database timelines
  • Optional DES3 encrypted data
  • Hot backup
  • Client drivers for ODBC, JDBC and .NET 
Published in Blogs
Read more...
Recital 10 enhances the APPEND FROM command. The enhancement was added to the following syntax ;
APPEND FROM  <table-name> 
Before when appending into a shared Recital table each new row was locked along with the table header, then unlocked after it was inserted. This operation has now been enhanced to lock the table once, complete inserting all the rows from the table and then unlock the table. The performance of this operation has been increased by using this method. All the database and table constraints are still enforced.
Published in Blogs
Read more...
After installing nomachine, if you get an error connecting whereby nomachine errors out after  "Negotiating link parameters"
 

When installing nomachine on redhat 5.3 64-bit be sure to:

  1. Make sure you have installed the 64-bit packages as the 32-bit ones will not work.
  2. add the hostname to /etc/hosts
  3. Check "Disable encryption of all traffic" (in configuration / advanced tab)
On Centos 32-bit:
  1. add the hostname to /etc/hosts
  2. make sure the host IP is not specified as 127.0.0.1 line
  3. Uncheck "Disable encryption of all traffic" (in configuration / advanced tab)
 
Published in Blogs
Read more...

After an extended period of intense software development, we are pleased to announce the release of Recital 10 which is a milestone in our development efforts.
 
Recital 10 is comprised of major new versions of all of our products (which are all now Cluster Ready) as well as some new products, and a collection of open source technologies fully supported by ourselves to our customer base. 

The Recital 10 release notes can be found here.
  • Recital

    A powerful scripting language with an embedded database used for developing desktop database applications on Linux and Unix.

  • Recital Server

    A cross-platform SQL database and application server.

  • Recital Web

    A server-side scripting language with an embedded SQL database for creating web 2.0 web applications.

Published in Blogs
Read more...

Recital is a proven and cost-effective database solution that will help reduce the cost of your database and application software infrastructure substantially. As an added benefit, Recital can run many legacy applications with little to no change as it understands FoxBASE, FoxPRO and Clipper languages as a subset of it's overall capability.
Published in Blogs
Read more...

If you have 4 GB or more RAM use the Linux kernel compiled for PAE capable machines. Your machine may not show up total 4GB ram. All you have to do is install PAE kernel package.

This package includes a version of the Linux kernel with support for up to 64GB of high memory. It requires a CPU with Physical Address Extensions (PAE).

The non-PAE kernel can only address up to 4GB of memory. Install the kernel-PAE package if your machine has more than 4GB of memory (>=4GB).

# yum install kernel-PAE

If you want to know how much memory centos is using type this in a terminal:

# cat /proc/meminfo
Published in Blogs
Read more...

Copyright © 2025 Recital Software Inc.

Login

Register

User Registration
or Cancel