旧C++Builder FAQ - プログラミング

Abstract: この FAQは、旧 www.borland.co.jpに掲載されていた記事を転載したものです。記事は掲載時点の情報をあるがままに掲載しており、新バージョンでのご利用においては、コンポーネントや APIの仕様変更等によりご利用いただけない場合がありますのでご留意ください。

    dBASE テーブルを再構築するサンプルコード

このサンプルを実行するには、テーブルが排他的にオープンされていなければなりません。排他的にテーブルをオープンするには、TTableの Exclusiveプロパティを trueに設定します。

手順

  1. ファイル(F)|新規作成(N)|アプリケーションを実行して、フォーム上に TTableと TButtonコンポーネントを配置します。
  2. TTableの DatabaseName, TableNameプロパティを適当に設定し、Exclusiveプロパティを trueに設定します。
  3. TTableの Avtiveプロパティは falseにしておき、下記コードのように Form1の OnCreateイベントで、TTableをオープンします。
  4. utton1の OnClickイベントを下記のように実装します。
  5. コンパイルが通るように vcl\bde.hppをインクルードします。
  6. プロジェクトを再構築して、実行します。
  #include <vcl\bde.hpp>

  void __fastcall TForm1::FormCreate(TObject *Sender)
  {
     Table1->Open();
  }

  void __fastcall TForm1::Button1Click(TObject *Sender)
  {
     char   szMsg[255];
     Word  iErr;

     iErr = DbiPackTable(Table1->DBHandle, Table1->Handle, NULL, 
                          szDBASE, true);
        if (iErr != DBIERR_NONE) {
            DbiGetErrorString(iErr, szMsg);
            ShowMessage("テーブルの再構築に失敗しました:" + StrPas(szMsg));
        }
  }

    RichEditの IME 変換中の文字(99/03/04)

Q:

Windows98のワードパットでは RichEditを使用しています。

IME変換中の文字は白色で、背景が青色でとても見易いのですが、C++Builderで使用した RichEditで IME変換を行った場合、文字と文字背景が黒で見づらい。

A:

C++Builderの VCL は、マイクロソフト社で公開されている情報で作成しています。

つまり 標準の RichEditです。

Windows98付属のワードパットの RichEditは、RichEdit20Aというクラス名を使用して作成しています。

同じものを C++Builderで作成する場合は、次のように記述してください。

//-----------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
  LoadLibrary("RICHED20.DLL");

  hRich = CreateWindowEx( WS_EX_CLIENTEDGE, "RichEdit20A", "",
                          WS_CHILD | WS_VISIBLE, 0, 0, ClientWidth, ClientHeight,
                          Handle, 0, HInstance, NULL );
}
//-----------------------------------------------------------------

    open関数を使用してファイルを開いていますが、書き込みできません。(99/03/04)

Q:

open関数を使用してファイルを開いていますが、書き込みできません。

A:

openのオンラインのヘルプに記述されていますが、最初のリスト(読み込み/書き込みフラグ)は必ず1つは指定して下さい。

O_RDONLY, O_WRONLY, O_RDWR

次のように記述していただければ、問題なく書き込み可能です。

  handle = open( name, O_CREATE | O_WRONLY, S_IREAD | S_IWRITE );

    ActiveXコンポーネントのプロパティを保存したい(99/2/3)

該当するバージョン:C++Builder 3,C++Builder 4

Q:

ActiveXコンポーネントのプロパティを保存したい。

A:

ActiveXコンポーネントのプロパティのデータを保存するには ActiveXの APIを使用して下さい。

以下はサンプルになります。

VtChart1, VtChart2 は ActiveXコントロールの TVtChartです。

//-----------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  IStream *Stream;
  IPersistStreamInit  *SrcStream, *DestStream;
  IPersistFile   *SrcFile, *DestFile;
  IUnknown  *iUnk;
  HGLOBAL  FObjectData;

  iUnk = VtChart1->OleObject;
  OleCheck( iUnk->QueryInterface( IID_IPersistStreamInit, (void**)&SrcStream));

  //プロパティを格納するためのメモリを確保
  FObjectData = GlobalAlloc(GMEM_MOVEABLE, 0);

  if( FObjectData == 0 )
     OutOfMemoryError;

  try{
    // ---- propertyの内容をセーブ ----
    //確保したメモリを指すIStreamを取得
    OleCheck(CreateStreamOnHGlobal(FObjectData, false, &Stream));

    //プロパティの内容をメモリにSave
    OleCheck(SrcStream->Save(Stream, true));

    //IStream を TOleStream に割り当て
    TOleStream *OS = new TOleStream( Stream );
    TFileStream *FS = new TFileStream( "Ole.dat", fmCreate | fmOpenWrite );
    FS->CopyFrom( OS, 0 );  //File に書き込み

    delete OS;
    delete FS;

    OS = new TOleStream( Stream );
    FS = new TFileStream( "Ole.dat", fmOpenRead );
    OS->Read( FS, 0 );  //読み込み

    delete OS;
    delete FS;

    // ---- propertyの内容をロード ----
    // 転送先ActiveXのIPersistStreamInitを取得
    iUnk = VtChart2->OleObject;
    OleCheck( iUnk->QueryInterface( IID_IPersistStreamInit, (void **)&DestStream) );

    // 確保したメモリを指すIStreamを取得
    OleCheck( CreateStreamOnHGlobal(FObjectData, false, &Stream) );

    //メモリからプロパティの内容をLoad
    OleCheck( DestStream->Load(Stream) );
  }
  __finally{
    // メモリの破棄
    GlobalFree(FObjectData);
    FObjectData = 0;
  }
}
//-----------------------------------------------------------------

    ネットワークマシンをダイアログで、一覧表示したい(99/2/3)

Q:

ネットワークマシンをダイアログで、一覧表示したい。

A:

シェル APIを使用してください。

取得方法は次のようになります。

//-----------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{

  char lpBuff[MAX_PATH];
  BROWSEINFO bRowsInf;
  ITEMIDLIST *id, *pbrowse;
  IMalloc   *imllc;

  CoGetMalloc( 1, &imllc );
  memset( &bRowsInf, 0, sizeof(bRowsInf));
  SHGetSpecialFolderLocation( Handle, CSIDL_NETWORK, &id );

  bRowsInf.hwndOwner = Handle;
  bRowsInf.pidlRoot = id;
  bRowsInf.ulFlags = BIF_BROWSEFORCOMPUTER;
  bRowsInf.pszDisplayName = lpBuff;

  try{
    if( ( pbrowse = SHBrowseForFolder(&bRowsInf) ) != NULL )
    {
      ShowMessage( lpBuff );  //取得マシンの表示
    }
  }
  __finally{
    imllc->Free(pbrowse);
  }

  imllc->Release();

}
//-----------------------------------------------------------------

    構造体のアライメントについて(98/10/05)

Q:

造体のアライメントを [プロジェクトオプション] を使用し、4バイト境界から 1バイト境界に変更していますが、アプリケーション実行時に例外が発生します。

構造体のアライメントを変更方法について教えてください。

A:

VCL構造の仕様上、プロジェクトのオプション指定や -aといったコマンドオプション指定では、正常な結果をえられません。下記のように #pragma packを利用してください。

例1

       #pragma pack(push、1)
       struct  MyStruct {
            // 省略
       };
       #pragma pack(pop)

例2

       #pragma pack(1)
       struct  MyStruct {
           //省略
       };
       #pragma pack()

#pragma optionsは、オプション指定と同じ扱いになります。

    Visual C++で作成した DLLをリンクしましたが、名前が解決されません。(98/08/03)

Q:

Visual C++で作成した DLLをリンクしましたが、名前が解決されません。

A:

呼び出し規約は標準で __stdcallになりますが Visual C++では、アンダースコアがシンボル名の頭に付けられます。

C++Builderでは、シンボルの先頭にアンダースコアを付けません。

片方で、もう片方が使う場合を考慮して DLLを作成していただく必要があります。

以下の2通りの方法があります。

  1. Visual C++を C++Builderに合わせて DLLを作成する。

Visual C++側で DLLを作成する場合、アンダースコア付きで関数がエキスポートされないように、モジュール定義ファイルで名前を変更しておきます。

例: Tes.dllを作成します。(Visual C++側)

   //CPP のソース側------------------------------------------
   #include <windows.h>

   int WINAPI DllMain( HINSTANCE hI, DWORD fdwR, PVOID pvR )
   {
     return TRUE;
   }

   //エキスポートする関数定義
   extern "C" void  __stdcall Tes(  char *strA, char *strB  )
   {
     ::MessageBox( NULL, strA, strB, MB_OK );
   }
   //モジュール定義ファイル(Tes.def)-------------------------
   LIBRARY    Tes.DLL

   EXPORTS   
     Tes

この 2ファイルを使用して作られた DLL内の関数名は Tesとなり C++Builder側では、この DLLから implibでインポートライブラリを作成します。

プロトタイプ宣言は以下の様に記述して下さい。

   extern "C"{
     __declspec(dllimport) void __stdcall Tes( char *strA, char *strB );
   }
  1. Visual C++で作成された DLLに合わせて C++Builderの EXEを作成する。

VC側の DLL内の関数が既に _Tes@8などでエキスポートされている場合は、C++Builder側で名前を変更して使用しなければいけません。

変更にはモジュール定義ファイルを使用します。

例: Project1.exeを作成します。(C++Builder側)

   //モジュール定義ファイル(Project1.def)--------------------
   // Tes.dll内の _Tes@8を Tesに変更します。
   NAME  PROJECT1
   IMPORTS
     Tes=Tes._Tes@8
   //CPP のソース側------------------------------------------
   //Tes 関数のプロトタイプ宣言
   extern "C"{
     void __stdcall Tes( char *strA, char *strB );
   }
   __(途中省略)__
   void __fastcall TForm1::Button1Click(TObject *Sender)
   {
     Tes( "Test Now!!", "Test" );
   }

但し、こちらの場合を統合環境からメイクするとアドレス違反が発生します。

コマンドラインから以下のようにしてビルドして下さい。

make -f Project1.bpr

    Delphiと C++Builderの例外処理

Delphiでは try..except及び try..finallyの二つの例外処理を操作するブロックが用意されています。例えば次のように記述します。

  try       try ブロックで
    func1;  例外が発生すると
      :
    funcx;
  except    直ちに except
    func0;  ブロックに移動する
  end;
  try         try ブロックで
    func1;    例外が発生すると
      :       直ちに finallyブロックに
    funcx;    移動するが、例外が発生
    finally   しなくても finallyブロックは
      func0;  実行される。
    end;

と言った具合になります。ですからこれを組み合せて次のように記述することもできます。

  var
    sl: TStrings;
  begin
    sl := TStringList.Create;
  try
    try
      func1;
        :
      funcx;
    except
      func0;  // ここで例外が発生した時の処理を行なう。
    end;
  finally
    sl.Free;  // ここは例外が発生してもしなくても実行されるので
  end;        // TStringListのオブジェクトは正しく破棄されます。

C++では finallyはありません。ですから上の例を C++で記述するためには、次のように TStringListのオブジェクトを破棄するコードを二回記述する必要があります。(catchブロックで returnしない場合には、try...catchブロックの外側に delete siの記述を持って行くこともできます)

  TStrings *sl = new TStringList;
  try {
    func1();
      :
    funcx();
    delete sl;
  }
  catch(...) {
    delete sl;
  }

例外は throwという予約語を使用して送出します(Delphiでは raiseを使用します)。例えば int型の例外を送出する場合には、次のように行ないます。

throw int(-1);

クラスであれば次のようにオブジェクトを生成して送出します。

throw new Exception( "error ditected." );

また、これを受け取るブロックも分けることが出来ます。例えば、Exceptionクラスか int型の例外が発生する可能性のある try..catch ブロックは次のように受け取ることができます。

  try {
    :
  }
  catch( Exception e ) {
    // Exception クラスが送出された
  }
  catch( int ) {
    // int 型の例外が送出された
  }
  catch( ... ) {
    // 上記の catch ブロックと同等と見なされない例外クラスが送出された
  }

catchブロックは複数記述できますが、上で記述しているものほど特化されたクラスである必要があります。

  class A{};
  class B: public A{};
  class C: public B{};

  try {
    :
  }
  catch(C c) {
  }
  catch(A a) {
  }
  catch(...) {
  }

のようなコードが有る場合、tryブロックで Bが送出されると Bは Aから派生したクラスであるため catch(A a) {}のブロックが実行されることになります。

VCLでは Exceptionという例外クラスがあり、クラスに対して Eで始まる例外クラスが Exceptionクラスから派生されています。また、上記コードは Delphiで記述すると次のようになります。

  try
    :
  except
    on cObj: C do begin
      :
    end;
    on aObj: A do begin
      :
    end;
    else begin      // catch(...)にあたり、上記以外の例外全てを受け取る。
      :
    end;
  end;

また、C++では関数に対して例外指定を行なうことができます。例外指定はその関数がどのようなタイプの例外を送出するかをしらしめます。

  void func(void) throw(A);    // func は例外 A を送出する。
  void func(void) throw(A, B); // func は例外 A と B を送出する。
  void func(void) throw();     // func は例外を送出しない。

    構造体の境界値を 4以外にする

該当するバージョン:C++Builder 1,C++Builder 3

Q:

構造体の境界値を 4以外の数字にしたい場合はどうすればいいですか。

A:

C++Builderの構造体の境界値は 4に設定されてます。これは VCLのライブラリが、既に 4に設定されているためです。これをコンパイルオプションで変更(-a1など)すると、例外が発生してしまいます。

その場合は下記のように「#pragma pack()」で構造体を囲むように記述してください。

  #pragma pack(1) //構造体の境界値を 1に変更する。
  struct test{
    .....
  }a;
  #pragma pack()

    zentohanという全角文字を半角文字にする関数が使えません。

Q:

zentohanという全角文字を半角にする関数が Borland C++にありましたが C++Builderでは使えなくなったのですか。

A:

zentohanの代わり _mbctombb関数をお使い下さい。

Turbo C++ 1.01互換関数が使用できなくなっています。

変わりに MSC 7.0互換関数が使用できます。(mbstring.hで定義)

#include <mbstring.h>
unsigned int _mbctombb(unsigned int c);

解説

_mbctombbは、全角文字 cを半角に変換します。変換可能な文字は、アルファベット、数字、カタカナ、ひらがな(カタカナに変換されます)、および一部の記号のみです。

カタカナとひらがなの濁点や半濁点付きの文字は清音(濁点無しの文字)に変換されます。

戻り値

変換可能な文字ならば、変換後の文字を返します。変換できなければ、文字 cをそのまま返します。

    オートメーションコントローラを作成して、MS-WORD97を制御したい(99/6/4)

該当するバージョン:C++Builder 4

Q:

オートメーションコントローラを作成して、MS-WORD97を制御したい。

A:

C++Builderでは、いくつか方法があり、ここでは、以下の 2通りの方法を紹介します。

VTable インターフェース

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  //vtable インターフェースのインスタンスの生成
  TCOM_Application app = CoApplication_::Create();
  //可視状態にする
  app->Visible = true;

  //ドキュメントグループの取得
  TCOMDocuments dcmnts = app->Documents_;

  if( OpenDialog1->Execute() )
  {
    TCOM_Document dcmnt;

    TVariant vv = OpenDialog1->FileName;
    //ドキュメントを開く
    dcmnts->Open( &vv );
    //アクティブドキュメントの取得
    dcmnt = app->ActiveDocument;
    //印刷する
    dcmnt->PrintOut();

    //印刷するまで待機
    Sleep(5000);
  }
  app->Quit();
}

ディスパッチインターフェース

説明は上記と同じ

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  //ディスパッチインターフェースのインスタンスの生成
  _ApplicationDisp app;
  app.BindDefault();
  //可視状態にする
  app.Visible = true;

  //ドキュメントグループの取得
  DocumentsDisp dcs = app.Documents_;

  if( OpenDialog1->Execute() )
  {
    TVariant vv = OpenDialog1->FileName;
    //ドキュメントを開く
    dcs.Open( &vv );

    //アクティブドキュメントの取得
    _DocumentDisp dc = app.ActiveDocument;
    //印刷する
    dc.PrintOut();
    //印刷するまで待機
    Sleep( 5000 );
  }
  app.Quit();
}

プロパティや関数の使用方法などは MS-Word97 VBAに従ってプログラミングしていただくことになります。

詳しくは MS-Word97 VBA関連書籍を参考にしてください。

    TWinControlから派生したクラスを動的に配列で確保したい(99/5/12)

該当するバージョン:C++Builder 4

Q:

TWinControl から派生したクラスを動的に配列で確保もしくは、デフォルトコンストラクタの無いクラスを動的に配列で確保したい。

A:

C++Builder4.0Jでは 2通りの方法があります。

4.0Jから新しく追加された DynamicArrayテンプレートを使用する方法と、C++の機能を利用する方法です。

以下は、それぞれの利用方法のサンプルになります。

DynamicArrayの例

ヘッダー側

   //-----------------------------------------------------------------
   class TForm1 : public TForm
   {
   __published: // IDE管理のコンポーネント
   private:     // ユーザー宣言
     DynamicArray<TButton *>  pBtn;  //TButtonの DynamicArray変数

   public:      // ユーザー宣言
     __fastcall TForm1(TComponent* Owner);
     __fastcall ~TForm1();
   };

ソース側

   //-----------------------------------------------------------------
   __fastcall TForm1::TForm1(TComponent* Owner)
     : TForm(Owner)
   {
     int cnt = 30;

     pBtn.Length = cnt;

     for( int i=0; i < cnt; i++ )
     {
       pBtn[i] = new TButton(this);
       pBtn[i]->Parent = this;
       pBtn[i]->Width = 200;
       pBtn[i]->Height = 20;
       pBtn[i]->Top = i*5;
       pBtn[i]->Left = i*5;
     }
   }
   //-----------------------------------------------------------------
   __fastcall TForm1::~TForm1()
   {
     for( int i=0; i < pBtn.Length; i++ )
     {
       delete pBtn[i];
     }
   }
   //-----------------------------------------------------------------

標準 C++の例

ヘッダー側

   //-----------------------------------------------------------------
   class TForm1 : public TForm
   {
   __published: // IDE管理のコンポーネント
   private:     // ユーザー宣言
     TButton  **pBtn;    //TButtonのポインタのポインタ変数
     int cnt;

   public:      // ユーザー宣言
     __fastcall TForm1(TComponent* Owner);
     __fastcall ~TForm1();
   };

ソース側

   //-----------------------------------------------------------------
   __fastcall TForm1::TForm1(TComponent* Owner)
     : TForm(Owner)
   {
     cnt = 30;

     pBtn = new TButton *[cnt];

     for( int i=0; i < cnt; i++ )
     {
       pBtn[i] = new TButton(this);
       pBtn[i]->Parent = this;
       pBtn[i]->Width = 200;
       pBtn[i]->Height = 20;
       pBtn[i]->Top = i*5;
       pBtn[i]->Left = i*5;
     }
   }
   //-----------------------------------------------------------------
   __fastcall TForm1::~TForm1()
   {
     for( int i=0; i < cnt; i++ )
     {
       delete pBtn[i];
     }

     delete[] pBtn;
   }
   //-----------------------------------------------------------------

    MFCのウィザードで作成したプログラムから VCLを使用したい(99/5/12)

該当するバージョン:C++Builder 4

Q:

MFCのウィザードで作成したプログラムから VCLを使用したい。

A:

ウィザードで作成したプロジェクトソースファイル Project1.cppに USEUNITでユニットを追加して下さい。

例えば、そのプロジェクトに Formを追加すると USEFORMが記述されますが、これを USEUNIT("Unit1.cpp"); に変更して下さい。

MFCのソースから Formのメソッドを呼び出す場合は Unit1.hをインクルードして下さい。

その時のインクルードの記述は 1番上に記述して下さい。

   //
   // Project.cpp : アプリケーション用クラスの機能定義を行います。
   //
   #include "Unit1.h"
   #undef _WINDOWS_

   #include "stdafx.h"     //MFC のヘッダ
   #include "Project.h"    //MFC のヘッダ

また、VCL をインクルードすると

"WINDOWS.H already included. MFC apps must not #include <windows.h>" 

のエラーが表示されますので、例のように "#undef _WINDOWS_"も記述して下さい。