12 C++アプリケーション
この章では、Pro*C/C++プリコンパイラを使用してC++の埋込みSQLアプリケーションをプリコンパイルする方法と、Pro*C/C++がC++互換コードを生成する仕組みについて説明します。この章のトピックは、次のとおりです:
12.1 C++サポートの理解
Pro*C/C++でC++がどのようにサポートされているかを理解するには、Pro*C/C++の基本機能を理解する必要があります。特に、Pro*C/C++とPro*Cバージョン1との違いを認識する必要があります。
Pro*C/C++の基本機能は、次のとおりです。
- 
                        Cプリプロセッサの完全サポート。Pro*C/C++プログラム内で #define、#include、#ifdefおよびその他のプリプロセッサ・ディレクティブを使用して、プリコンパイラ自体で処理する必要のある構成体を扱うことができます。
- 
                        C言語の固有の構造体をホスト変数として使用可能。構造体(または構造体へのポインタ)をホスト変数として関数に渡す機能や、ホスト構造体または構造体ポインタを戻す書込み関数などがあります。 
Cプリプロセッサの機能をサポートし、特殊な宣言部の外でホスト変数を宣言できるようにするために、Pro*C/C++には完全なCパーサーが組み込まれています。Pro*C/C++パーサーはCパーサーであり、C++コードは解析できません。
したがって、C++をサポートするには、Cパーサーを完全または部分的に使用禁止にする必要があります。Cパーサーを使用禁止にするために、Pro*C/C++プリコンパイラには、Pro*C/C++がソース・コードに対して行うC解析の範囲を制御できるコマンドライン・オプションが組み込まれています。
関連項目
12.2 C++のプリコンパイル
C++に対応できるようにプリコンパイルを制御するには、次の4点を考慮する必要があります。
- 
                        プリコンパイラによるコード出力 
- 
                        解析機能 
- 
                        出力ファイル名の拡張子 
- 
                        システム・ヘッダー・ファイルの位置 
12.2.1 コードの生成
プリコンパイラで生成されるコードの種類(C互換コードまたはC++互換コード)を指定する必要があります。デフォルトでは、Pro*C/C++によってC言語のコードが生成されます。C++は、C言語の完全なスーパーセットではありません。生成されるコードをC++のコンパイラでコンパイルするには、コードを多少変更する必要があります。
たとえば、プリコンパイラでは、アプリケーション・コードが出力されるのみでなく、ランタイム・ライブラリSQLLIBに対するコールが挿入されます。SQLLIB内の関数は、C関数です。特殊なC++版のSQLLIBはありません。このため、C++コンパイラを使用して生成したコードをコンパイルするには、SQLLIB内でコールした関数をPro*C/C++によりC関数として宣言する必要があります。
C言語の出力では、プリコンパイラにより次のようなプロトタイプが生成されます。
void sqlora(unsigned long *, void *);
ただし、C++互換コードの場合には、プリコンパイラで次のようなコードを生成する必要があります。
extern "C" {
void sqlora(unsigned long *, void *);
};
Pro*C/C++によって生成されるコードの種類は、CODEというプリコンパイラ・オプションを使用して制御します。このオプションの値は、CPP、KR_CおよびANSI_Cの3つです。これらのオプションの違いは、SQLLIB関数sqloraの宣言方法がCODEオプションの3つの値で異なることを考えると説明できます。
void sqlora( /*_ unsigned long *, void * _*/);  /* K&R C */
void sqlora(unsigned long *, void *);           /* ANSI C */
extern "C" {                                    /* CPP */
void sqlora(unsigned long *, void *);
};
CODE=CPPを指定すると、プリコンパイラは次を行います。
- 
                           C++互換コードを生成します。 
- 
                           出力ファイルに、標準の「.c」拡張子ではなく、「.C」や「.cc」など、プラットフォーム固有のファイル拡張子(接尾辞)を付けます。(この設定は、CPP_SUFFIXオプションを使用して上書きできます。) 
- 
                           PARSEオプションの値をデフォルトのPARTIALにします。また、PARSE=NONEも指定できます。PARSE=FULLを指定すると、プリコンパイル時にエラーが発生します。 
- 
                           C++形式の//コメントをコード内で使用可能にします。CODE=CPPのときは、この形式のコメントをSQL文およびPL/SQLブロックの中でも使用できます。 
- 
                           Object Type Translator(OTT)によって生成されるヘッダー・ファイルは、宣言部の中にインクルードする必要があります。 関連項目: CODEオプションの値KR_CとANSI_Cの詳細は、CODEを参照してください。 
12.2.2 コードの解析について
Pro*C/C++のCパーサーによるコードへの効果を制御する必要があります。この制御はPARSEプリコンパイラ・オプションを使用することで可能になります。このオプションでは、プリコンパイラのCパーサーがコードの取扱方法を制御できます。
PARSEオプションの値と効果を次に示します。
表12-1 PARSEオプションの値と効果
| 値 | 効果 | 
|---|---|
| PARSE=NONE | 値NONEの効果は次のとおりです。 
 | 
| PARSE=PARTIAL | 値PARTIALの効果は次のとおりです。 
 このオプション値は、CODE=CPPのときデフォルトです。 | 
| PARSE=FULL | 値FULLの効果は次のとおりです。 
 | 
このオプション値は、CODEオプションの値がCPP以外のときのデフォルト値です。CODE=CPPのときにPARSE=FULLを指定すると、エラーになります。
C++互換コードを生成するには、PARSEオプションにNONEまたはPARTIALのいずれかを指定する必要があります。PARSE=FULLのときはCパーサーが動作し、コードにあるC++クラスなどの構文は認識されません。
12.2.4 システム・ヘッダー・ファイル
Pro*C/C++では、プラットフォーム固有の標準的な位置でstdio.hなどの標準のシステム・ヘッダー・ファイルが検索されます。Pro*C/C++では、hppまたはh++などの拡張子が付いたヘッダー・ファイルは検索されません。たとえば、ほとんどのUNIXシステムではstdio.hファイルのフルパス名は/usr/include/stdio.hです。 
                     
ただし、C++コンパイラは独自のバージョンのstdio.hを持っており、これはシステムの標準位置にはありません。C++でのプリコンパイル時には、Pro*C/C++でシステム・ヘッダー・ファイルを検索できるように、SYS_INCLUDEプリコンパイラ・オプションを使用してディレクトリ・パスを指定する必要があります。次に例を示します。
SYS_INCLUDE=(/usr/lang/SC2.0.1/include,/usr/lang/SC2.1.1/include)
システム・ヘッダー・ファイル以外の位置を指定するには、INCLUDEプリコンパイラ・オプションを使用します。SYS_INCLUDEオプションで指定したディレクトリは、INCLUDEオプションで指定したディレクトリよりも前に検索されます。
PARSE=NONEのときは、Pro*C/C++はシステム・ヘッダー・ファイルをインクルードする必要がないため、システム・ファイルについてSYS_INCLUDEおよびINCLUDEで指定した値は無視されます。(ただし、当然ながら、sqlca.hなどのPro*C/C++固有のヘッダーは、EXEC SQL INCLUDE文を使用してインクルードできます。)
                     
関連項目
12.3 サンプル・プログラム
この項には、C++構造体を含んでいるサンプルのPro*C/C++プログラムを3つ記載しています。これらのプログラムはそれぞれdemoディレクトリにオンラインで利用可能な形で入っています。
                     
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
次のアプリケーションは、簡単なモジュール化の例です。最初にSQL*Plusの次のSQLスクリプトcppdemo2.sqlを実行します。
                        
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 /
ヘッダー・ファイルempclass.hでは、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;
};
empclass.pcのコードには、empメソッドが含まれています。
                        
#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);
}
メイン・プログラムcppdemo2.pcでは、カーソル変数が使用されます。
                        
// 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);
}