12 C++ Applications

This chapter describes how you can use the Pro*C/C++ Precompiler to precompile your C++ embedded SQL application, and how Pro*C/C++ generates C++ compatible code. This chapter contains the following topics:

12.1 Understanding C++ Support

To understand how Pro*C/C++ supports C++, you must understand the basic functional capabilities of Pro*C/C++. In particular, you must be aware of how Pro*C/C++ differs from Pro*C Version 1.

The basic capabilities of Pro*C/C++ are:

  • Full C preprocessor support. You can use #define, #include, #ifdef, and other preprocessor directives in your Pro*C/C++ program, to handle constructs that the precompiler itself must process.

  • Use of native C structures as host variables, including the ability to pass structs (or pointers to structs) as host variables to functions, and write functions that return host structures or struct pointers.

To support its C preprocessor capabilities and to enable host variables to be declared outside a special Declare Section, Pro*C/C++ incorporates a complete C parser. The Pro*C/C++ parser is a C parser; it cannot parse C++ code.

This means that for C++ support, you must be able to disable the C parser, or at least partially disable it. To disable the C parser, the Pro*C/C++ Precompiler includes command-line options to give you control over the extent of C parsing that Pro*C/C++ performs on your source code.

12.1.1 No Special Macro Processing

Using C++ with Pro*C/C++ does not require any special preprocessing or special macro processors that are external to Pro*C/C++. There is no need to run a macro processor on the output of the precompiler to achieve C++ compatibility.

If you are a user of a release of Pro*C/C++ Precompiler before this one, and you did use macro processors on the precompiler output, you should be able to precompile your C++ applications using Pro*C/C++ with no changes to your code.

12.2 Precompiling for C++

To control precompilation so that it accommodates C++, there are four considerations:

  • Code emission by the precompiler

  • Parsing capability

  • The output filename extension

  • The location of system header files

12.2.1 Code Generation

You must be able to specify what kind of code, C compatible code or C++ compatible code, the precompiler generates. Pro*C/C++ by default generates C code. C++ is not a perfect superset of C. Some changes are required in generated code so that it can be compiled by a C++ compiler.

For example, in addition to emitting your application code, the precompiler interposes calls to its runtime library, SQLLIB. The functions in SQLLIB are C functions. There is no special C++ version of SQLLIB. For this reason, if you want to compile the generated code using a C++ compiler, Pro*C/C++ must declare the functions called in SQLLIB as C functions.

For C output, the precompiler would generate a prototype such as

void sqlora(unsigned long *, void *);

But for C++ compatible code, the precompiler must generate

extern "C" {
void sqlora(unsigned long *, void *);
};

You control the kind of code Pro*C/C++ generates using the precompiler option CODE. There are three values for this option: CPP, KR_C, and ANSI_C. The differences between these options can be illustrated by considering how the declaration of the SQLLIB function sqlora differs among the three values for the CODE option:

void sqlora( /*_ unsigned long *, void * _*/);  /* K&R C */

void sqlora(unsigned long *, void *);           /* ANSI C */

extern "C" {                                    /* CPP */
void sqlora(unsigned long *, void *);
};

When you specify CODE=CPP, the precompiler

  • Generates C++ compilable code.

  • Gives the output file a platform-specific file extension (suffix), such as ".C" or ".cc", rather than the standard ".c" extension. (You can override this by using the CPP_SUFFIX option.)

  • Causes the value of the PARSE option to default to PARTIAL. You can also specify PARSE=NONE. If you specify PARSE=FULL, an error is issued at precompile time.

  • Allows the use of the C++ style // Comments in your code. This style of Commenting is also permitted inside SQL statements and PL/SQL blocks when CODE=CPP.

  • Pro*C/C++ recognizes SQL optimizer hints that begin with //+.

  • Requires that header files generated by OTT (Object Type Translator) must be included inside a declare section.

    See Also:

    "CODE" for information about the KR_C and ANSI_C values for the CODE option.

12.2.2 About Parsing Code

You must be able to control the effect of the Pro*C/C++ C parser on your code. You do this by using the PARSE precompiler option, which controls how the precompiler's C parser treats your code.

The values and effects of the PARSE option are:

Table 12-1 Values and Effects of the PARSE Option

Values Effects

PARSE=NONE

The value NONE has the following effects:

  • C preprocessor directives are understood only inside a declare section.

  • You must declare all host variables inside a Declare Section.

  • Precompiler release 1.x behavior

PARSE=PARTIAL

The value PARTIAL has the following effects:

  • All preprocessor directives are understood

  • You must declare all host variables inside a Declare Section

This option value is the default if CODE=CPP

PARSE=FULL

The value FULL has the following effects:

  • The precompiler C parser runs on your code.

  • All Preprocessor directives are understood.

  • You can declare host variables at any place that they can be declared legally in C.

This option value is the default if the value of the CODE option is anything other than CPP. It is an error to specify PARSE=FULL when CODE=CPP.

To generate C++ compatible code, the PARSE option must be either NONE or PARTIAL. If PARSE=FULL, the C parser runs, and it does not understand C++ constructs in your code, such as classes.

12.2.3 Output Filename Extension

Most C compilers expect a default extension of ".c" for their input files. Different C++ compilers, however, can expect different filename extensions. The CPP_SUFFIX option provides the ability to specify the filename extension that the precompiler generates. The value of this option is a string, without the quotes or the period. For example, CPP_SUFFIX=cc, or CPP_SUFFIX=C.

12.2.4 System Header Files

Pro*C/C++ searches for standard system header files, such as stdio.h, in standard locations that are platform specific. Pro*C/C++ does not search for header files with extensions such as hpp or h++. For example, on almost all UNIX systems, the file stdio.h has the full path name /usr/include/stdio.h.

But a C++ compiler has its own version of stdio.h that is not in the standard system location. When you are precompiling for C++, you must use the SYS_INCLUDE precompiler option to specify the directory paths that Pro*C/C++ searches to look for system header files. For example:

SYS_INCLUDE=(/usr/lang/SC2.0.1/include,/usr/lang/SC2.1.1/include)

Use the INCLUDE precompiler option to specify the location of non-system header files. The directories specified by the SYS_INCLUDE option are searched before directories specified by the INCLUDE option.

If PARSE=NONE, the values specified in SYS_INCLUDE and INCLUDE for system files are not relevant, since there is no need for Pro*C/C++ to include system header files. (You can, of course, still include Pro*C/C++-specific headers, such sqlca.h, using the EXEC SQL INCLUDE statement.)

Related Topics

12.3 Example Programs

This section includes three example Pro*C/C++ programs that include C++ constructs. Each of these programs is available on-line, in your demo directory.

12.3.1 cppdemo1.pc

/*  cppdemo1.pc
 *
 *  Prompts the user for an employee number, then queries the 
 *  emp table for the employee's name, salary and commission.
 *  Uses indicator variables (in an indicator struct) to 
 *  determine if the commission is NULL.
 */

#include <iostream.h>
#include <stdio.h>
#include <string.h>

// Parse=partial by default when code=cpp,
// so preprocessor directives are recognized and parsed fully.
#define     UNAME_LEN      20
#define     PWD_LEN        40

// Declare section is required when CODE=CPP or
// PARSE={PARTIAL|NONE} or both.
EXEC SQL BEGIN DECLARE SECTION;
  VARCHAR username[UNAME_LEN];  // VARCHAR is an ORACLE pseudotype
  varchar password[PWD_LEN];    // can be in lower case also

  // Define a host structure for the output values
  // of a SELECT statement
  struct empdat {
      VARCHAR   emp_name[UNAME_LEN];
      float     salary;
      float     commission;
  } emprec;

  // Define an indicator struct to correspond to the
  // host output struct
  struct empind {
      short     emp_name_ind;
      short     sal_ind;
      short     comm_ind;
  } emprec_ind;


  // Input host variables
  int   emp_number;
  int   total_queried;
EXEC SQL END DECLARE SECTION;

// Define a C++ class object to match the desired
// struct from the preceding declare section.
class emp {
  char  ename[UNAME_LEN];
  float salary;
  float commission;
public:
  // Define a constructor for this C++ object that
  // takes ordinary C objects.
  emp(empdat&, empind&);
  friend ostream& operator<<(ostream&, emp&);
};

emp::emp(empdat& dat, empind& ind)
{
  strncpy(ename, (char *)dat.emp_name.arr, dat.emp_name.len);
  ename[dat.emp_name.len] = '\0';
  this->salary = dat.salary;
  this->commission = (ind.comm_ind < 0) ? 0 : dat.commission;
}

ostream& operator<<(ostream& s, emp& e)
{
  return s << e.ename << " earns " << e.salary << 
              " plus " << e.commission << " commission." 
           << endl << endl;
}

// Include the SQL Communications Area
// You can use #include or EXEC SQL INCLUDE
#include <sqlca.h>

// Declare error handling function
void sql_error(char *msg);

main()
{
  char temp_char[32];

  // Register sql_error() as the error handler
  EXEC SQL WHENEVER SQLERROR DO sql_error("ORACLE error:");

  // Connect to ORACLE.  Program calls sql_error()
  // if an error occurs
  // when connecting to the default database.
  // Note the (char *) cast when
  // copying into the VARCHAR array buffer.
  username.len = strlen(strcpy((char *)username.arr, "SCOTT"));
  password.len = strlen(strcpy((char *)password.arr, "TIGER"));
  
  EXEC SQL CONNECT :username IDENTIFIED BY :password;

  // Here again, note the (char *) cast when using VARCHARs
  cout << "\nConnected to ORACLE as user: "
       << (char *)username.arr << endl << endl;

  // Loop, selecting individual employee's results
  total_queried = 0;
  while (1)
  {
      emp_number = 0;
      printf("Enter employee number (0 to quit): ");
      gets(temp_char);
      emp_number = atoi(temp_char);
      if (emp_number == 0)
        break;

      // Branch to the notfound label when the 
      // 1403 ("No data found") condition occurs
      EXEC SQL WHENEVER NOT FOUND GOTO notfound;

      EXEC SQL SELECT ename, sal, comm
         INTO :emprec INDICATOR :emprec_ind // You can also use 
                                            // C++ style
         FROM EMP                  // Comments in SQL statemtents.
         WHERE EMPNO = :emp_number;

      {
        // Basic idea is to pass C objects to
        // C++ constructors thus
        // creating equivalent C++ objects used in the
        // usual C++ way
        emp e(emprec, emprec_ind);
        cout << e;
      }

      total_queried++;
      continue;
notfound:
      cout << "Not a valid employee number - try again." 
           << endl << endl;
  } // end while(1)

  cout << endl << "Total rows returned was " 
       << total_queried << endl;
  cout << "Have a nice day!" << endl << endl;

  // Disconnect from ORACLE
  EXEC SQL COMMIT WORK RELEASE;
  exit(0);
}


void sql_error(char *msg)
{
    EXEC SQL WHENEVER SQLERROR CONTINUE;
    cout << endl << msg << endl;
    cout << sqlca.sqlerrm.sqlerrmc << endl;
    EXEC SQL ROLLBACK RELEASE;
    exit(1);
}

12.3.2 cppdemo2.pc

The next application is a simple modular example. First, execute the following SQL script, cppdemo2.sql, in SQL*Plus:

Rem  This is the SQL script that accompanies the cppdemo2 C++ Demo
Rem  Program.  Run this prior to Precompiling the empclass.pc file.
/
CONNECT SCOTT/TIGER
/
CREATE OR REPLACE VIEW emp_view AS SELECT ename, empno FROM EMP
/
CREATE OR REPLACE PACKAGE emp_package AS
  TYPE emp_cursor_type IS REF CURSOR RETURN emp_view%ROWTYPE;
  PROCEDURE open_cursor(curs IN OUT emp_cursor_type);
END emp_package;
/
CREATE OR REPLACE PACKAGE BODY emp_package AS
  PROCEDURE open_cursor(curs IN OUT emp_cursor_type) IS
  BEGIN
    OPEN curs FOR SELECT ename, empno FROM emp_view ORDER BY ename ASC;
  END;
END emp_package;
/
EXIT
/

The header file empclass.h defines the class emp:

// This class definition may be included in a Pro*C/C++ application
// program using the EXEC SQL INCLUDE directive only.  Because it
// contains EXEC SQL syntax, it may not be included using a #include
// directive.  Any program that includes this header must be
// precompiled with the CODE=CPP option.  This emp class definition
// is used when building the cppdemo2 C++ Demo Program.

class emp
{
  public:
    emp();   // Constructor: ALLOCATE Cursor Variable
    ~emp();  // Desctructor: FREE Cursor Variable

    void open();              // Open Cursor
    void fetch() throw (int); // Fetch (throw NOT FOUND condition)
    void close();             // Close Cursor

    void emp_error();         // Error Handler

    EXEC SQL BEGIN DECLARE SECTION;
      // When included using EXEC SQL INCLUDE, class variables have 
      // global scope and are thus basically treated as ordinary
      // global variables by Pro*C/C++ during precompilation.
      char ename[10];
      int empno;
    EXEC SQL END DECLARE SECTION;

  private:
    EXEC SQL BEGIN DECLARE SECTION;
      // Pro*C/C++ treats this as a simple global variable also.
      SQL_CURSOR emp_cursor;
    EXEC SQL END DECLARE SECTION;
};

The code in empclass.pc contains the emp methods:

#include <stdio.h>
#include <stdlib.h>

// This example uses a single (global) SQLCA that is shared by the
// emp class implementation as well as the main program for this
// application.
#define SQLCA_STORAGE_CLASS extern
#include <sqlca.h>

// Include the emp class specification in the implementation of the
// class body as well as the application program that makes use of it.
EXEC SQL INCLUDE empclass.h;

emp::emp()
{
  // The scope of this WHENEVER statement spans the entire module.
  // Note that the error handler function is really a member function
  // of the emp class.
  EXEC SQL WHENEVER SQLERROR DO emp_error();
  EXEC SQL ALLOCATE :emp_cursor;  // Constructor - ALLOCATE Cursor.
}

emp::~emp()
{
  EXEC SQL FREE :emp_cursor;      // Destructor - FREE Cursor.
}

void emp::open()
{
  EXEC SQL EXECUTE
    BEGIN
      emp_package.open_cursor(:emp_cursor);
    END;
  END-EXEC;
}

void emp::close()
{
  EXEC SQL CLOSE :emp_cursor;
}

void emp::fetch() throw (int)
{
  EXEC SQL FETCH :emp_cursor INTO :ename, :empno;
  if (sqlca.sqlcode == 1403)
    throw sqlca.sqlcode;     // Like a WHENEVER NOT FOUND statement.
}

void emp::emp_error()
{
  printf("%.*s\n", sqlca.sqlerrm.sqlerrml, sqlca.sqlerrm.sqlerrmc);
  EXEC SQL WHENEVER SQLERROR CONTINUE;
  EXEC SQL ROLLBACK WORK RELEASE;
  exit(1);
}

The main program, cppdemo2.pc, uses the cursor variable:

// Pro*C/C++ sample program demonstrating a simple use of Cursor Variables
// implemented within a C++ class framework.  Build this program as follows
//
//   1. Execute the cppdemo2.sql script within SQL*Plus
//   2. Precompile the empclass.pc program as follows
//      > proc code=cpp sqlcheck=full user=scott/tiger lines=yes empclass
//   3. Precompile the cppdemo2.pc program as follows
//      > proc code=cpp lines=yes cppdemo2
//   4. Compile and Link
//
// Note that you may have to specify various include directories using the
// include option when precompiling.

#include <stdio.h>
#include <stdlib.h>
#include <sqlca.h>

static void sql_error()
{
  printf("%.*s\n", sqlca.sqlerrm.sqlerrml, sqlca.sqlerrm.sqlerrmc);
  EXEC SQL WHENEVER SQLERROR CONTINUE;
  EXEC SQL ROLLBACK WORK RELEASE;
  exit(1);  
}

// Physically include the emp class definition in this module.
EXEC SQL INCLUDE empclass.h;

int main()
{
  EXEC SQL BEGIN DECLARE SECTION;
    char *uid = "scott/tiger";
  EXEC SQL END DECLARE SECTION;

  EXEC SQL WHENEVER SQLERROR DO sql_error();
  EXEC SQL CONNECT :uid;

  emp *e = new emp(); // Invoke Constructor - ALLOCATE Cursor Variable.

  e->open();          // Open the Cursor.

  while (1)
    {
      // Fetch from the Cursor, catching the NOT FOUND condition
      // thrown by the fetch() member function.
      try { e->fetch(); } catch (int code)  
        { if (code == 1403) break; }
      printf("Employee:  %s[%d]\n", e->ename, e->empno);
    }

  e->close();         // Close the Cursor.

  delete e;           // Invoke Destructor - FREE Cursor Variable.

  EXEC SQL ROLLBACK WORK RELEASE;
  return (0);
}

12.3.3 cppdemo3.pc

/*
 * cppdemo3.pc : An example of C++ Inheritance
 *
 * This program finds all salesman and prints their names
 * followed by how much they earn in total (ie; including 
 * any commissions).
 */
 
#include <iostream.h>
#include <stdio.h>
#include <sqlca.h>
#include <string.h>

#define NAMELEN 10

class employee {    // Base class is a simple employee
public:
  char ename[NAMELEN];
  int sal;
  employee(char *, int);
};

employee::employee(char *ename, int sal)
{
  strcpy(this->ename, ename);
  this->sal = sal;
}

// A salesman is a kind of employee
class salesman : public employee
{
  int comm;
public:
  salesman(char *, int, int);
  friend ostream& operator<<(ostream&, salesman&);
};

// Inherits employee attributes
salesman::salesman(char *ename, int sal, int comm)
  : employee(ename, sal), comm(comm) {}  

ostream& operator<<(ostream& s, salesman& m)
{
  return s << m.ename << m.sal + m.comm << endl;  
}

void print(char *ename, int sal, int comm)
{
  salesman man(ename, sal, comm);
  cout << man;
}

main()
{
  EXEC SQL BEGIN DECLARE SECTION;
    char *uid = "scott/tiger";
    char  ename[NAMELEN];
    int   sal, comm;
    short comm_ind;
  EXEC SQL END DECLARE SECTION;

  EXEC SQL WHENEVER SQLERROR GOTO error;

  EXEC SQL CONNECT :uid;
  EXEC SQL DECLARE c CURSOR FOR
    SELECT ename, sal, comm FROM emp WHERE job = 'SALESMAN'
      ORDER BY ename;
  EXEC SQL OPEN c;

  cout << "Name    Salary" << endl << "------  ------" << endl;

  EXEC SQL WHENEVER NOT FOUND DO break;
  while(1)
   {
     EXEC SQL FETCH c INTO :ename, :sal, :comm:comm_ind;
     print(ename, sal, (comm_ind < 0) ? 0 : comm);
   }
  EXEC SQL CLOSE c;
  exit(0);

error:
  cout << endl << sqlca.sqlerrm.sqlerrmc << endl;
  exit(1);
}