C++Builder Tips - C++BuilderでのVCLのサポート

Abstract: C++BuilderでVCLをサポートするための拡張について説明します。このドキュメントにはすぐに利用できる内容が多く含まれていますので、適切なコーディングを行うために役立ててください。

このドキュメントでは、拡張された予約語やVCLで使われるデータ型について説明します。

    __property

C++Builderは、コンポーネントを用いたビジュアル開発をサポートするために、C++にいくつかの拡張を取り入れています。

プロパティという概念は、C++にないものですが、C++Builderでは、新しい予約語__propertyによってこれを表わします。__propertyは、クラス宣言において次のように使用します。

   class myClass (
   private:
       int FCount;
       void SetCount(int);
   public:
       myClass() {  FCount = 0; };
       __property int Count 
           = { read = FCount, write=SetCount };
   };

この単純なクラスは、Countというプロパティを持ちます。プロパティは、読み出しおよび書き込み処理を別個に定義できる変数のようなものです。この例では、Fcountという変数が、Countプロパティの実体ですが、書き込み時には、SetCountという関数によって値を設定できます。

SetCountのような書き込み関数は、プロパティの設定に副作用を与えることができます。例えば、Countプロパティの値を0から100に制限したい場合は、SetCount関数を次のように記述します。

   void myClass::SetCount(int Value)
   {
       if (Value < 0)
           FCount = 0;
       else if (Value > 100)
           FCount = 100;
       else
           FCount = Value;
   }

この効果は、Windowsコントロールをカプセル化したコンポーネントのプロパティを見ればよく分かります。例えば、EditコンポーネントのSelStartプロパティは、テキストの選択部分の開始位置を表わしますが、このプロパティを参照したときには、Windows APIによってコントロールの選択部分を取得し、その開始位置を返します。反対にSelStartに値を設定したときには、コントロールの選択開始位置を設定します。これらの操作は、C++Builderのコーディングでは単純な代入によって行なえます。

    __published

VCLが強力なのは、それが設計時の動作までもサポートしているからであるともいえます。C++Builderのビジュアル開発は、VCLが設計時の動作をサポートすることによって成り立っています。

オブジェクトインスペクタによって設定されるプロパティは、コンポーネントが持つ全てのプロパティではありません。コンポーネントは、privateやpublicといったアクセス指定子と同じように、設計時のアクセスを許可するメンバを持ちます。キーワード__publishedは、設計時にアクセスを許可するメンバを宣言するために用いるC++Builderによって拡張された新しい予約語です。

多くのプロパティは、__published宣言されています。これらのプロパティは、オブジェクトインスペクタによって設計時に値を設定できます。また、フォームのメンバ関数であるイベントハンドラは__published宣言され、オブジェクトインスペクタによってイベントに設定することができます。

    __fastcall

C++Builderのコンポーネントでは、__fastcall呼び出し規約を用います。__fastcall呼び出し規約は、__cdeclや__pascal、あるいはWIN32の標準呼び出し規約である__stdcallと同じように、関数の呼び出し規約を決定します。__fastcallは、引数をレジスタで渡します。関数の最初の3つの引数は、レジスタに収まるサイズであれば(左から右の順で)EAX、EBX、EDXに格納されます。

C++Builderでイベントハンドラを作成しようとすると、次のような雛形が生成されます。

   void __fastcall TForm1::Button1Click(
     TObject *Sender)
   {
   
   }

Button1のOnClickイベントに設定されるこのイベントハンドラは、TForm1のメンバ関数です。これらの関数は、全て__fastcall呼び出し規約でなければならず、例のように__fastcallが必ず付加されます。

__fastcall 関数名の前には、コンパイラによって単価記号(@)のプレフィクスが付けられます。このプレフィクスは、変形されていないCの関数名と、変形されているC++の関数名の両方に付けられます。

    VCLで用いられるデータ型

VCLは、ObjectPascalによって作成されており、ここで使用されているデータ型のいくつかは、C++ではサポートされていないものです。C++Builderでは、これらのデータ型をクラスというかたちで実装しています。例えば、文字列プロパティは、AnsiStringクラスのデータです。また、FontのStyleプロパティは、TFontStylesという集合型と呼ばれるSetクラスです。

これらのデータの取り扱いは、実装されたクラス固有のものがあり、これらを理解することもC++Builderのプログラミング手法を理解するための一歩であるといえます。

    AnsiStringクラス

AnsiStringクラスは、Delphiの長い文字列であるAnsiString型に相当し、資源が許すならば最大約2G文字の文字列を格納できます。AnsiStringクラスの変数は、演算子を用いて代入、結合、追加などの操作ができます。以下のような文字列操作は全て可能です。

   AnsiString s1 = "Borland", s2, s3;
   
   s2 = "C++Builder";
   s1 += " " + s2;
   s3 = s1 + " and Delphi";

また、他のデータ型を文字列に代入することもできます。

   AnsiString s1, s2;
   double x;
   
   s1 = 400;
   x = 27.365;
   s2 = x / 2;

AnsiStringクラスでは、比較演算子も用いることができます。使用できる演算子は、==、!=、>、<、>=、<=です。これらは、strcmp関数を用いた比較と同じように機能します。

   if (s1 == s2)  // if (strcmp(s1, s2) == 0) と同じ
   if (s1 != s2)  // if (strcmp(s1, s2) != 0) と同じ
   if (s1 > s2)   // if (strcmp(s1, s2) > 0) と同じ

AnsiStringクラスをヌルで終わる文字列型に変換するには、c_str関数を用います。この関数を用いれば、標準ライブラリやWindows APIの文字列をあたえる関数に、AnsiStringクラスの変数を使用することができます。

   AnsiString s("Borland C++Builder");
   char buf[32];
   
   strcpy(buf, s.c_str());

c_str関数は、AnsiStringクラスの文字列領域へのポインタを返す関数です。AnsiStringクラスは、文字列領域を動的に確保しますから、c_str関数で取得したポインタを使って、直接文字列を変更するような処理には注意が必要です。

   AnsiString s;
   s.SetLength(20); // この関数を呼び出さないと領域が
                    // 確保されていないのでエラーになる
   sprintf(s.c_str(), "%20d", 500);

c_str関数を用いて取得したアドレスへの代入は、特定の状況でのみ安全に機能します。

    TDateTimeクラス

Borland C++Builderでは、日付と時刻を表わす型としてTDateTimeクラスを用意しています。TDateTimeクラスは、DelphiのTDateTime型に相当します。TDateTimeクラスの変数は、以下のように宣言することができます。

   TDateTime dt;

初期値を与えたい場合は、以下のように記述することもできます。

   TDateTime dt1("1997/5/1");
   TDateTime dt2 = AnsiString("97/5/1 8:30");
   
   // TDateTimeクラスをあたえる
   TDateTime dt3(Now());
   // 西暦で年月日をあたえる
   TDateTime dt4(1997, 6, 3);
   // 時分秒とミリ秒をあたえる
   TDateTime dt5(12, 45, 20, 0); 

比較演算子も使えます。==、!=、>、>=、<、<=はふたつの日付時刻を比較します。

    集合型(Setクラス)

VCLコンポーネントでは、集合型のプロパティを使うことがあります。集合型はObject Pascalのデータ型ですが、Borland C++Builderでは、テンプレートクラスとして実装しています。Object Pascalでは、以下のようにして集合型を定義できました。

   Digits = set of 0..9;

Borland C++Builderでは、これを次のように定義します。

   Set <int, 0, 9> Digits;

集合型へ要素を追加するには<<演算子を、要素を取り除くには>>演算子を用います。

   TFontStyles Style;
   Style << fsBold << fsItalic;
   Style >> fsBold;

全ての要素を取り除くにはClear関数を使います。

   Style.Clear();

ふたつの集合型を結合したり、差をとったりすることもできます。

   TFontStyles Style1, Style2, Style3;
   
   Style1 << fsBold << fsUnderline;
   Style2 << fsItalic << fsUnderline;
   // fsBold, fsItalic, fsUnderlineがセットされます
   Style3 = Style1 + Style2;
   // fsBold, fsItalicがセットされます
   Style3 = Style1 - Style2;
   // fsUnderlineがセットされます
   Style3 = Style1 * Style2;

これらの操作は、+=、-=、*=といった単項演算子によっても使用できます。

   Style2 += Style1;

比較演算子は、==、!=だけが使えます。全ての要素が一致するかしないか以外の比較は意味を持ちません。特定の要素が含まれているかどうかを調べるには、Containsを使います。

   if (Style1.Contains(fsBold)) ...

集合型を使ったプロパティの値を変更するときは、注意が必要です。プロパティへの値の設定を行なうと、内部的にはプロパティのWriteメソッド(Set関数)が呼ばれます。例えば、Font->Styleへの代入は、SetStyle関数をコールし、結果として表示状態の変更も行なわれます。そのため、Setクラスの演算子は、プロパティへの代入操作を行なわない限り正しく反映されません。プロパティへの設定では、以下のように記述します。

   Label1->Font->Style 
       = Label1->Font->Style << fsBold;

    オープン配列パラメータ

TTableのFindKeyやFormat関数のように、オープン配列パラメータを指定する関数では、OPENARRAYマクロやEXISTINGARRAY、SLICEマクロを用います。これらのパラメータは、TVarRec型のデータの配列として表わされます。OPENARRAYを使ってFormatを用いた例を示します。

   int day = 10;
   Label1->Caption = Format("%s %d %s"
     OPENARRAY(TVarRec, ("今日は", day, "日です")));