Pinnacle Server Extensions

The Basic Idea

The Pinnacle Server supports the standard RC21 API. You may add your own API, which you can call from your client program, written in Microsoft Visual C++, linked into a DLL, and placed in the path of the server kernel. Such extensions are C++ code that is executed on the server machine, presumably close to the database file, improving performance by about a factor of 10! Here’s how it’s done:

How You Do It

*       Design and implement your nonmember functions, classes, and member functions

*       Test them with the RC21 embedded library (the single-user version)

*       For each function – whether a member function or a nonmember function – assign an opcode. These opcodes should be added to the enum datatype SERVERFUNCTION in the header file RC21.H.

*       On the client side, you implement each function as stream operation using the Pinnacle ValueStream stream class. For each function, you send its opcode followed by its arguments

*       On the server side, implement each function as a global function with a void argument list; you will get all the arguments from the argument stream – the ValueStream object. If you are implementing a member function, the first argument should be a pointer to the object; you may return 0 or more values using the argument stream

*       Create a dispatcher; The dispatcher will take the opcode and make the appropriate function call; a skeleton dispatcher is in CustomDispatch.cpp. There is an example below.

*       Create a Visual C++ Console project that includes your dispatcher, the library CustomServer.lib, and any other files you will need to implement your custom server API. The project should define WIN32_SERVER. A sample .dsw file is included.

Example

Here’s a simple example that simply uses the client-server mechanism to implement a (silly) function to reverse the characters in a string. RC21.H has been modified as follows:

 
enum SERVERFUNCTION
   {
   .
   .
   .
   csdb_S_Custom=1000,
// Place Server Extension opcodes here:
   csdb_C_ReverseString,
   };

The client code is:

 
#include <rc21.h>
#include <iostream.h>
 
void main(void)
   {
   ServerConnection x("localhost:16000");
 
   x << csdb_C_ReverseString << "abcdefg" << endf;
   char *r;
   x >> r;
   cout << r << endl;
   }

The custom dispatcher looks like this:

#include <rc21.h>
void CustomDispatch(ValueStream *c,   SERVERFUNCTION f)
   {
   switch (f)
      {
      case csdb_C_ReverseString:
         {
         char *s;
         *c >> s;
         int n=strlen(s);
         int m=n/2;
         for (int i=0; i<m; ++i)
            {
            char c = s[i];
            s[i] = s[n-1-i];
            s[n-1-i] = c;
            }
         *c << s;
         }
      default:
         {
         iDB_SetError (DB_ERR_UNKNOWN_SERVER_FUNCTION);
         *c << endf;
         }
      }
   }

Now, this example is trivial and does not even use any RC21 functions. Here’s a more realistic example

 

Another Example

Suppose you want to get all the values in a column. If you wrote a program to do this it would look something like:

Step 1

 
#include <rc21.h>
#include <iostream.h>
 
void MakeDB(void)
   {
   ServerConnection x("localhost:16000");
   x.Privilege("xyzzy");  // set privilege so we can remove old file
   Remove("test.db");
   Create("test.db");
   Database b("test.db");
   b.AddTable("t");
   Table t(b,"t");
   t.AddColumn("c");
   Column c(t, "c");
   for (int i=0;i<1000;++i)
      {
      t.AddRow();
      c << i;
      }
   b.Commit();
   }
 
DEFT_ERROR
 
void ShowDB(void)
   {
   ServerConnection x("localhost:16000");
 
   Database d("test.db");
   Table t(d, "t");
   Column c(t, "c");
   ForAllRows(t)
      {
      cout << (int)c << endl;
      }
   }
 
void main(void)
   {
   SET_DEFT_ERROR
   MakeDB();  // make a test database
   ShowDB();  // display it
   }

Now, in the above program, we have a simple function to make a test database, and another one to print it out. Let’s modify the print-out program to return an array of values. Then, we will print out the values in the main program. Here’s what we end up with:

Step 2

 
#include <rc21.h>
#include <iostream.h>
 
 
DEFT_ERROR
 
void GetValues(DBCOL c, int*&a, int&n)
   {
   DBTAB t = c->Tab();
   n = t->CountRows();
   a = new int[n];
   int i=0;
   ForAllRows(t)
      {
      a[i++] = c->GetInteger();
      }
   }
 
void main(void)
   {
   SET_DEFT_ERROR
 
   ServerConnection x("localhost:16000");
   Database d("test.db");
   Table t(d, "t");
   Column c(t, "c");
 
   int n; int* array;
   GetValues(c, array, n);
   for (int i=0; i<n; ++i)
      {
      cout << array[i] << endl;
      }
   delete[] array;
   }

Step 3

The next step is to select an enum value to indicate the call to the custom routine. We do this by modifying the enum in RC21.h:

 
enum SERVERFUNCTION
   {
   .
   .
   .
   csdb_S_Custom=1000,
// Place Server Extension opcodes here:
   csdb_C_GetColumnValues,
   };

Step 3

Next, we modify the dispatcher: There are several things to note here:

*       Normally, your custom function would not reside in the dispatcher. It is placed here just to keep the example simple.

*       You receive the arguments from the client (stream). Read the documentation for valuestream.h for more on this.

*       You return one or more arguments back to the client (stream). Note the use of the ValueStream::BLOB constructor. You need to do this to transmit unstructured data (the int array) to and from client/server.

*       This is by no means a general function. It only handles int’s. We could templatize it for many types.

 
#include <rc21.h>
 
void Server_GetColumnValues(DBCOL c, int*&a, int&n)
   {
   DBTAB t = c->Tab();
   n = t->CountRows();
   a = new int[n];
   int i=0;
   ForAllRows(t)
      {
      a[i++] = c->GetInteger();
      }
   }
 
void CustomDispatch(ValueStream *c,   SERVERFUNCTION f)
   {
   switch (f)
      {
      case csdb_C_GetColumnValues:
         {
         DBCOL col; int n; int* a;
         *c >> col;
         Server_GetColumnValues(col, a, n);
         *c << n << ValueStream::BLOB(a, n*sizeof(int)) << endf;
         delete a;
         break;
         }
      default:
         {
         iDB_SetError (DB_ERR_UNKNOWN_SERVER_FUNCTION);
         *c << endf;
         }
      }
   }