メイン

C++Builder アーカイブ

2006年03月04日

BDS2006 Trial

Borland Developer Studio 2006 Architectの30日間使えるTrial版が公開されている。

2006年03月06日

API を使って縦書きなどのフォントを指定する

API を使って縦書きなどのフォントを指定する を参考に BCB6 でやってみたけど、文字化けした。

GetObject() を使えばうまく行きました。以下がそのコード。

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  TLogFont lbl;
  GetObject(Canvas->Font->Handle, sizeof(TLogFont), &lbl);
  // 日本語の場合、幅:高さ=1:2にする
  lbl.lfWidth = 10;
  lbl.lfHeight = 20;
  // 文字の太さ(0~1000)
  // 「標準」の太さは400、「太字」の太さは700
  lbl.lfWeight = 400;
  // 半時計回りの角度(単位は1/10度)
  lbl.lfEscapement = 2700;
  // 下線なし(デフォルトでは「あり」)
  lbl.lfUnderline = false;
  // 打ち消し線なし(デフォルトでは「あり」)
  lbl.lfStrikeOut = false;
  // 斜体無効(デフォルトでは有効)
  lbl.lfItalic = false;
  // 縦書き用の「@」がつくフォントを使用する
  strcpy(lbl.lfFaceName, "@MS ゴシック");

  // フォントを作成
  Canvas->Font->Handle = CreateFontIndirect(&lbl);
  // 背景を透明に
  Canvas->Brush->Style = bsClear;
  // 文字列を描画
  Canvas->TextOut(300, 300, "縦書き文字");
}

2006年06月15日

BDS2006(C++Builder)レビュー(1)

Borland Developer Studio 2006 Update 2 が公開されたので、C++Builderを使ってみました。

C++Builder6と比較して。

  • エディタの機能強化が強化されている

    もともと補完機能ぐらいしか取り柄のないシンプルなエディタでしたが、 BDS2006では、機能が向上していました。
    エディタは一番よく使う機能なので助かります。

  • メモリの消費量がそんなに多くない

    パッケージには、メモリが512MB(推奨!GB)以上必要とありましたが、 C++Builderだけなら、そんなに必要ないようです。
    400MBでも十分余っていました。

  • ヘルプファイルが使いにくい。

    ヘルプが非常に使いにくい。
    必要ない.NETなどのヘルプまであるからでしょうか。
    慣れるまで苦労しそう。

  • リファクタリングの機能がおかしい。

    リファクタリングの機能を使うと、関係ない箇所が変更されます。
    (追記) Hotfixが公開されていました。
    Hotfixes for Borland Developer Studio 2006 Update 2のHotfix 6

2006年06月16日

BDS2006(C++Builder)レビュー(2)

BDS2006(C++Builder)を使っていて気がついたこと。

  • プロジェクトマネージャーのファイルをドラッグ&ドロップで、順番を入れ替えることができる。
    C++Builder6でもできた。

  • ToDoリストがおかしい
    アクション項目が表示されないものがある。
    項目を開いたとき、カーソルの位置がずれるものがある。

  • コーディング時とデバッグ時でレイアウトが自動的に変わって便利。

  • デスクトップツールバーの「Classic Undocked」を選択するとBCB6に似たレイアウトになる。

2006年06月18日

BDS2006(BCB)のWindowStateの値がおかしい

BDS2006(BCB Update2)のWindowStateの値がおかしい。

最小化している状態でWindowStateの値を確認すると、wsMinimizedではなくwsNormalになっている。

最小化状態かどうかについては、

IsIconic(Application->Handle);

で調べることができる。
trueなら最小化状態。

2006年06月20日

BDS2006でstd::ofstreamを使うと、CodeGuardがエラーを報告する

BDS2006でstd::ofstreamを使うと、CodeGuardがエラーを報告します。

関数に不正なファイル/パイプ ストリーム (0xXXXXXXXX) が渡されました。

QCにありました。

とりあえず、メニューの「プロジェクト」→「オプション」→「リンカ(ilink32)」→「リンク」の「動的RTLを使う」のチェックをはずすことで回避することにしました。

これが最善策かどうかはわかりません。

2006年06月26日

BDS(Delphi/BCB)用SQLiteライブラリ

Borland Developer Studio(Delphi/BCB)でSQLiteを扱うためのライブラリを調べました。

追記
Delphi2009用のSQLiteライブラリを開発されている方がいました。

2006年06月27日

EVariantErrorをキャッチしないと、CodeGuardがメモリリークを検出する

例外が投げられたときにEVariantErrorをキャッチしないと、CodeGuardがメモリリークを検出します。
Exceptionはキャッチしなくてもいいようです。 (BDS2006 C++Builder Update2)

これはメモリリークを検出しません。

try
{
  VarToDateTime("");
}
catch (EVariantError &E)
{
}

これはメモリリークを報告します。

try
{
  VarToDateTime("");
}
catch (...)
{
}

2006年07月15日

BDS2006(BCB)でWinHelpを使う

C++ Builder 6 で作成したプロジェクトを Borland Developer Studio 2006 に更新すると、ヘルプファイルを開けなくなる。

Application->HelpContext() などでヘルプを表示しようとすると、「状況関知型ヘルプがインストールされていません。」というエラーメッセージが表示されるようになった。

原因は、標準のヘルプシステムが WinHelp から HTMLHelp に変更されたため。

Borland Developer Studio 2006 で WinHelp を使用するには、Main フォームに次の行を追加する。

#pragma link "WinHelpViewer"

これで、C++ Builder 6 と同じように WinHelp を使用することができる。

2006年07月16日

BDS2006(C++Builder)からRubyを使う

Borland Developer Studio 2006(C++Builder)からRubyを使ってみます。

箏音の日記 - C++とRubyC#からRubyのコードを実行するテスト を参考にしました。

Rubyをコンパイルする手間を省くために、Ruby-mswin32 から 最新リリース版である ruby-1.8.4-i386-mswin32.zip をダウンロードします。

ダウンロードした ruby-1.8.4-i386-mswin32.zip を展開し、bin\msvcrt-ruby18.dll をC++Builderのプロジェクトを作成するディレクトリにコピーします。

C++Builderを起動し、VCLフォームアプリケーションを作成します。

まず、起動時にDLLを読み込み、終了時に解放します。

Unit1.hに次のコードを追加。

private:
  HINSTANCE hDll;

Unit1.cppに次のコードを追加。

__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
  hDll = LoadLibrary("msvcrt-ruby18.dll");
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
  if (hDll) FreeLibrary(hDll);
}

DLLが正しく読み込まれていることを確認します。

次は、Rubyプログラムを実行してみます。

Unit1.hに次のコードを追加。

private:
  __declspec(dllexport) void (*ruby_init)(void);
  __declspec(dllexport) unsigned long (*rb_eval_string)(const char*);
  __declspec(dllexport) int (*ruby_cleanup)(int);

Unit1.cppに次のコードを追加。

__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
  hDll = LoadLibrary("msvcrt-ruby18.dll");
  if (hDll)
  {
    ruby_init = (void (*)(void))GetProcAddress(hDll,"ruby_init");
    rb_eval_string = (unsigned long (*)(const char*))GetProcAddress(hDll,"rb_eval_string");
    ruby_cleanup = (int (*)(int))GetProcAddress(hDll,"ruby_cleanup");
  }
}

フォームにボタンを追加し、ボタンを押したときのイベントを記述します。

//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  if (!hDll)
  {
    Application->MessageBox("msvcrt-ruby18.dll が見つかりませんでした。",
                            "DLL エラー",
                            MB_ICONSTOP | MB_OK);
    return;
  }
  //Rubyインタプリタの初期化
  ruby_init();
  //スクリプトの実行
  rb_eval_string("File.open('a.txt', 'w' ) { |f| f.puts 'success' }");
  //Rubyインタプリタのクリーンアップ
  ruby_cleanup(0);
}

実行してみましょう。 ボタンを押すとsuccessと記述されたa.txtが作成されると成功。

次に、スクリプトをファイルから読み込んで実行します。

Unit1.hに次のコードを追加。

private:
  __declspec(dllexport) void (*ruby_init_loadpath)(void);
  __declspec(dllexport) void (*rb_load)(unsigned long, int);
  __declspec(dllexport) unsigned long (*rb_str_new2)(const char*);

Unit1.cppに次のコードを追加。

__fastcall TForm1::TForm1(TComponent* Owner)
  : TForm(Owner)
{
  hDll = LoadLibrary("msvcrt-ruby18.dll");
  (中略)
  ruby_init_loadpath = (void (*)(void))GetProcAddress(hDll,"ruby_init_loadpath");
  rb_load = (void (*)(unsigned long, int))GetProcAddress(hDll,"rb_load");
  rb_str_new2 = (unsigned long (*)(const char*))GetProcAddress(hDll,"rb_str_new2");

フォームにもう一つボタンを作成し、ボタンを押したときのイベントを記述します。

//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
  if (!hDll)
  {
    Application->MessageBox("msvcrt-ruby18.dll が見つかりませんでした。",
                            "DLL エラー",
                            MB_ICONSTOP | MB_OK);
    return;
  }

  //Rubyインタプリタの初期化
  ruby_init();
  ruby_init_loadpath();
  // スクリプトをファイルから読み込んで実行
  rb_load(rb_str_new2("test.rb"), 0);
  //Rubyインタプリタのクリーンアップ
  ruby_cleanup(0);
}

test.rbファイルはこう書いてみました。

File.open('b.txt', 'w' ) { |f|
  f.puts 'success'
}

実行してみましょう。 ボタンを押すとsuccessと記述されたb.txtが作成されると成功。

続く

2006年08月16日

BDS2006(C++Builder)からRubyを使う(2)

前回の続き

メソッドの実行結果を受け取り、結果を表示します。

フォームにメモとボタンをを貼り付けます。

Unit1.h の private に次のメソッドを追加します。

__declspec(dllexport) char* (*rb_string_value_cstr)(volatile unsigned long*);

Unit1.cpp の TForm1::TForm1(TComponent* Owner) に次の行を追加します。

rb_string_value_cstr = (char* (*)(volatile unsigned long*))GetProcAddress(hDll,"rb_string_value_cstr");

ボタンのイベントを追加します。

void __fastcall TForm1::Button3Click(TObject *Sender)
{
  if (!hDll)
  {
    Application->MessageBox("msvcrt-ruby18.dll が見つかりませんでした。",
                            "DLL エラー",
                            MB_ICONSTOP | MB_OK);
    return;
  }
  //Rubyインタプリタの初期化
  ruby_init();

  //スクリプトの実行
  unsigned long value = rb_eval_string("'実行結果'");
  //実行結果の文字列を取得する
  AnsiString result = rb_string_value_cstr(&value);
  Memo1->Lines->Add(result);

  //スクリプトの実行
  value = rb_eval_string("File.open('test.rb', 'r' ).read");

  //実行結果の文字列を取得する
  TStringList* list = new TStringList();
  list->Text = rb_string_value_cstr(&value);
  Memo1->Lines->Add(list->Text);
  delete list;

  //Rubyインタプリタのクリーンアップ
  ruby_cleanup(0);

}

ボタンをクリックして、メモに「実行結果」と「test.rb」の内容が表示されたら成功です。

2006年08月19日

BDS2006(C++Builder)でActiveXコントロールをインポートする方法

BDS2006(C++Builder)でActiveXコントロールをインポートする方法です。

ここでは、ScriptControlをインポートします。

  1. メニューから[ファイル]-[新規作成]-[パッケージ]を選択します。

    パッケージのプロジェクトが作成されます。

  2. メニューから[コンポーネント]-[コンポーネントのインポート]を選択します。

    「コンポーネントのインポート」ダイアログが表示されます。

  3. 「タイプライブラリの取り込み」を選択して、「次へ」ボタンをクリックします。

  4. 「登録されたタイプライブラリ」から「Microsoft Script Control 1.0」を選択して、「次へ」ボタンをクリックします。

  5. 「パレットページ名」に、ツールパレットに登録するパレット名を入力して、「次へ」ボタンをクリックします。

    私は「ActiveX」と入力しました。

  6. 「Package1.bdsprojプロジェクトにユニットを追加」を選択して、「完了」ボタンをクリックします。

  7. 「プロジェクトマネージャ」(IDEの右上に配置されているウィンドウ)の「Package1.bpl」を右クリックして、「インストール」を選択します。

    プロジェクトがビルドされた後、コンポーネントが登録されます。

BDS2006(C++Builder)でツールパレットに登録しないでActiveXコンポーネントを使用する

BDS2006(C++Builder)でツールパレットに登録しないでActiveXコンポーネントを使用する方法です。

例としてScriptControlを使用します。

  1. メニューから[ファイル]-[新規作成]-[VCL フォームアプリケーション]を選択します。

    新しいプロジェクトが作成されます。

  2. メニューから[コンポーネント]-[コンポーネントのインポート]を選択します。

    「コンポーネントのインポート」ダイアログが表示されます。

  3. 「タイプライブラリの取り込み」を選択して、「次へ」ボタンをクリックします。

  4. 「登録されたタイプライブラリ」から「Microsoft Script Control 1.0」を選択して、「次へ」ボタンをクリックします。

  5. 「ユニットディレクトリ名」にユニットを保存するディレクトリを選択して、「次へ」ボタンをクリックします。

    「パレットページ名」は、パレットに登録しないので「(none)」のままにしました。

  6. 「Project1.bdsprojプロジェクトにユニットを追加」を選択して、「完了」をクリックします。

以上で、準備ができました。

フォームに TEdit と TButton を貼り付けて、TButton のクリックイベントを記述します。

#include "MSScriptControl_OCX.h"

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  TScriptControl* sc = new TScriptControl(this);
  sc->Language = "VBScript";
  Edit1->Text = VarToStr(sc->Eval(WideString("FormatDateTime(Now())").c_bstr()));
  delete sc;
}

TEditに現在の日時が表示されます。

2006年09月06日

BuilderBooster

C++Builderのメーリングリストで紹介されていた Borland Developer Studio 2006 Registered User Updates の「FREE 3rd Party Tools」。

このうちの「BuilderBooster」で、

  • ビルド時間が20%短縮される
  • タブの切替が早くなる

などのメリットがあるらしい。

さっそくインストールしよう。

Turbo Downloads

Turbo Downloads のページから、

  • Turbo Delphi
  • Turbo Delphi for .NET
  • Turbo C++
  • Turbo C#

がダウンロードできるようだ。

2006年09月21日

Borland Developer Studio 2006 アップデート2 Hotfix 7-9

Hotfix 1-6(2は除く)と、新たに追加された Hotfix 7-9を全て含むHotfix-Rollupが公開されています。

Hotfix 7-9については、Borland Developer Studio 2006 アップデート2 Hotfix 7-9 に情報があります。

Hotfix-Rollupのインストール方法は、ファイルをダウンロードして展開後、BDS2006HotfixRollup.exeを実行するだけです。

インストールするには、Borland Developer Studio 2006 アップデート2 が必要です。

2006年10月27日

.NETのヘルプを削除する

ヘルプを登録しなおすには

これで、.NETのヘルプを削除できるみたいです。

cd "C:\Program Files\Borland\BDS\4.0\Help\Common"
regHelp.exe -7
regHelp.exe 5

2006年11月05日

Windows Vista 対応アプリケーションの作り方

Delphiで、Windows Vista Readyアプリケーションを作る

Delphiのアプリケーションを、Vistaの新しいユーザーエクスペリエンス機能に対応する方法。

Delphiで作成したアプリケーションは、基本的にはVistaでも動作する。 しかし、Vistaの新しいユーザーエクスペリエンス機能に対応するには6つのポイントを対応する必要がある。

  1. 新しいフォントに関する問題
  2. 秘密のウィンドウに対する対処
  3. アプリケーションの透明化
  4. タスクダイアログのプロンプト
  5. イメージがすべて
  6. 新しいコモンダイアログ

詳しい説明とサンプルプログラムがありがたい。

こちらで、要約されていました。
某所 | DelphiアプリケーションをVista対応させるには

2006年11月07日

Delphi2006でLightReport2を使うには

Delphi2006でLightReport2を使うには

http://onigiri.s3.xrea.com:8080/delphi/index.php?Delphi2005%2FTIPSGoogleキャッシュ で見つかった「Delphi2005でLightReport2を使うには」を参考にしました。

DelphiVerに {$IFDEF VER180} の項目を追加する以外は Delphi2005と同じです。

LightRep、ScaledPreviewとTLightRep、StretchDIBDrawで以下を修正

InfoSize: integer; -> InfoSize: longword;
ImageSize: integer; -> ImageSize: longword;

DelphiVerに以下を追加

その1

{
  Delphi Version 判断の定義
    VER80  Delphi1
    VER90  Delphi2
    VER100 Delphi3
    VER120 Delphi4
    VER130 Delphi5
    VER140 Delphi6
    VER150 Delphi7
    VER160 Delphi8
    VER170 Delphi9
}

その2

{$IFDEF VER160}
  {$DEFINE Delphi8Only}
  {$DEFINE Delphi1Over}
  {$DEFINE Delphi2Over}
  {$DEFINE Delphi3Over}
  {$DEFINE Delphi4Over}
  {$DEFINE Delphi5Over}
  {$DEFINE Delphi6Over}
  {$DEFINE Delphi7Over}
  {$DEFINE Delphi8Over}
  {$DEFINE Delphi1-8}
  {$DEFINE Delphi2-8}
  {$DEFINE Delphi3-8}
  {$DEFINE Delphi4-8}
  {$DEFINE Delphi5-8}
  {$DEFINE Delphi6-8}
  {$DEFINE Delphi7-8}
{$ENDIF}
{$IFDEF VER170}
  {$DEFINE Delphi9Only}
  {$DEFINE Delphi1Over}
  {$DEFINE Delphi2Over}
  {$DEFINE Delphi3Over}
  {$DEFINE Delphi4Over}
  {$DEFINE Delphi5Over}
  {$DEFINE Delphi6Over}
  {$DEFINE Delphi7Over}
  {$DEFINE Delphi8Over}
  {$DEFINE Delphi9Over}
  {$DEFINE Delphi1-9}
  {$DEFINE Delphi2-9}
  {$DEFINE Delphi3-9}
  {$DEFINE Delphi4-9}
  {$DEFINE Delphi5-9}
  {$DEFINE Delphi6-9}
  {$DEFINE Delphi7-9}
  {$DEFINE Delphi8-9}
{$ENDIF}
{$IFDEF VER180}
  {$DEFINE Delphi9Only}
  {$DEFINE Delphi1Over}
  {$DEFINE Delphi2Over}
  {$DEFINE Delphi3Over}
  {$DEFINE Delphi4Over}
  {$DEFINE Delphi5Over}
  {$DEFINE Delphi6Over}
  {$DEFINE Delphi7Over}
  {$DEFINE Delphi8Over}
  {$DEFINE Delphi9Over}
  {$DEFINE Delphi1-9}
  {$DEFINE Delphi2-9}
  {$DEFINE Delphi3-9}
  {$DEFINE Delphi4-9}
  {$DEFINE Delphi5-9}
  {$DEFINE Delphi6-9}
  {$DEFINE Delphi7-9}
  {$DEFINE Delphi8-9}
{$ENDIF}

2006年11月13日

TMS SoftwareのDelphiとWindows Vista関連の記事

ボーランドテクノロジーパートナー(BTP)の TMS Softwareによる、Delphi 2006開発者がどのようにWindows Vista対応のアプリケーションを開発できるかについて説明した記事です。

TMS SoftwareのDelphiとWindows Vista関連の記事

Windows Vistaの新しいTask Dialog、File Open、Save DialogをDelphi 2006で使う方法。

Delphiアプリケーションのメモリリーク検出法

Delphi Win32アプリケーションのメモリリーク検出手順を紹介します。また、Delphi 2006(BDS 2006)から導入された新しいメモリマネージャFastMMでの方法、またその効果についても検証します。

Delphiアプリケーションのメモリリーク検出法

フリーのメモリリーク検出ツール「MemCheck」と、Delphi 2006で標準搭載の「FastMM」の使い方が解説されています。

2006年12月07日

QuickReport Standard for BDS 2006

この QuickReport Standard ですが、Delphi for Win32 パーソナリティ用のみですが正式にダウンロードして頂けるようになりました。

QuickReport Standard

ということで、QuickReport Standard for BDS 2006のダウンロード先とインストールの詳しい手順が詳細されています。

C++Builderでも使えるのでしょうか?

今週は試す時間がとれそうにありません。

2006年12月12日

Borland Developer Studio 2006 / Turbo C++ 2006 から Boost を利用する

第3回デベロッパーキャンプのプレゼンテーション資料では、BCC32 5.8.x で Boost を利用する方法として基本的なコードを紹介しています。この記事では、さらに少し高度な利用例を解説します。

Borland Developer Studio 2006 / Turbo C++ 2006 から Boost を利用する

BDS2006からBoostを使う方法。

第3回デベロッパーキャンプのプレゼンテーション資料では、インストール方法が詳しく解説されています。
他にも、boost::regexなどのサンプルコードもありました。
ありがたいです。

2006年12月14日

QuickReport 4

この記事では、Borland Developer Studio 2006でQuickReport 4を使うためのインストール/環境設定の方法を解説します。また、既存のQuickReportアプリケーションをマイグレーションするときに、同時に考慮しておかなければならない注意点を説明します。

Borland Developer Studio 2006でQuickReport 4を使う

QuickReport 4 Professional(製品版)の入手方法、Delphi Win32・Delphi .NET・C++Builderへのインストール方法、など。

2006年12月15日

Delphi/C++Builder用データベースコンポーネント

Firebirdのメーリングリストで、Delphi用のデータベースコンポーネントが紹介されていました。
なかなか興味深いです。

Zeosがおもしろそう。ここにも記事がありました。MySQLやPostgreSQLにも対応しているみたい。

2006年12月29日

C++BuilderによるUnlha32.dllを使用した書庫の展開(1)

C#で書かれた「Unlha32.dll」を使用した書庫の展開を参考にしながら、 C++Builderで書庫を展開するコードを作成しました。

main() 関数

C#ではデスクトップの絶対パスを取得するのに System.Environment.GetFolderPath() を使用しています。
C++Builderでは、同様の機能がライブラリにないため、Win32API を使って実装しました。(GetFolderPath()関数)

C#では、パスから拡張子部分をのぞいたファイル名を取得するのに Path.GetFileNameWithoutExtension() を使用しています。
C++Builderでは、ChangeFileExt() を使い、同様の機能を実現しました。

GetFolderPath() 関数

GetFolderPath()関数は、デスクトップの絶対パスを取得する関数です。
SHGetMalloc()などを使用するために、#include vcl.h の前に、shlobj.h を include します。

#define NO_WIN32_LEAN_AND_MEAN
#include <shlobj.h>
#include <vcl.h>

LhaExtractArchive()関数

UNLHA32.DLLで書庫を展開する関数です。

C#の System.IO.File.Exists() と同様の機能が、C++Builder では FileExists() になります。

C#のプログラムでは SearchPath() で、DLLの有無を確認しています。
C++Builderの方は、実際にDLLをロードしてその成否によって確認するようにしました。
VCLのクラスである TDll を使うと、デストラクタで FreeLibrary() が呼ばれるため、自分で記述する必要が無くなり、ソースコードが簡潔になります。
TDllを使うには、utilcls.h を include します。

//DLL(Unlha32.dll)の読みこみ
TDll dll("unlha32.dll");
  //DLLの存在を確認
  if (!dll)
  {
    throw Exception("UNLHA32.DLLが見つかりません。");
  }

C#では、DLLの関数を使用するのに DllImport を使用します。
C++Builderでは、GetProcAddress() で関数のアドレスを取得して使用します。

typedef WORD (WINAPI *PUnlhaGetVersion)(void);
PUnlhaGetVersion UnlhaGetVersion = (PUnlhaGetVersion)dll.GetProcAddress("UnlhaGetVersion");
WORD ver = UnlhaGetVersion();

ToCommandStringFromFile()

ファイル名をコマンドとして適切な文字列に変換する関数です。
ホームページ上にはありませんが、ダウンロードしたソースコードには、この関数が作成されていました。

C#では、StartsWith() や EndsWith() が便利ですね。
C++Builderでは、AnsiPos() や SubString() を使いました。
もっと上手な方法がありそうです。

一方、文字列をダブルクオート(")でくくる処理が、C#では、

cmd = "\"" + cmd + "\"";

となっているところを、C++Builderでは、AnsiQuotedStr() を使い、

cmd = AnsiQuotedStr(cmd, '"');

と書けます。

ソースコード

//---------------------------------------------------------------------------

#define NO_WIN32_LEAN_AND_MEAN
#include <shlobj.h>
#include <vcl.h>
#pragma hdrstop
#include <stdio.h>
#include <utilcls.h>
#include <string.h>
//---------------------------------------------------------------------------

#pragma argsused
//---------------------------------------------------------------------------
/**
 * デスクトップのファイルを物理的に格納するフォルダのパスを取得する
 * @return フォルダのパス。エラーが発生した時は、""
 */
AnsiString GetFolderPath()
{
  PItemIDList PIDL;
  LPMALLOC malloc;

  // IMallocインターフェイスへのポインタを取得
  SHGetMalloc(&malloc);

  //フォルダの識別子を取得
  if (FAILED(SHGetSpecialFolderLocation(Application->Handle, CSIDL_DESKTOPDIRECTORY, &PIDL)) == TRUE)
  {
    return "";
  }

  // 識別子をディレクトリに変換
  char szPath[MAX_PATH + 1];
  SHGetPathFromIDList(PIDL, szPath);
  malloc->Free(PIDL);

  return AnsiString(szPath);
}
//---------------------------------------------------------------------------
/**
 * ファイル名をコマンドとして適切な文字列に変換する
 * @param fileName ファイル名
 * @return コマンドとなる文字列
 */
AnsiString ToCommandStringFromFile(AnsiString fileName)
{
  AnsiString cmd = fileName;

  //ファイル名が「-」や「@」ではじまる時は、「.\」を付加
  if (cmd.AnsiPos("-") == 1 || cmd.AnsiPos("@") == 1)
  {
    cmd = ".\\" + cmd;
  }

  //ファイル名にスペースが含まれる時は、"で囲む
  if (cmd.AnsiPos(" ") != 0 &&
      !(cmd.AnsiPos("\"") == 0 && cmd.SubString(cmd.Length(), 1) == "\""))
  {
    cmd = AnsiQuotedStr(cmd, '"');
  }
  return cmd;
}
//---------------------------------------------------------------------------
/**
 * UNLHA32.DLLで書庫を展開する
 * @param archiveFile 書庫ファイル名
 * @param extractTo 展開先のフォルダ名
 */
void LhaExtractArchive(AnsiString archiveFile, AnsiString extractTo)
{
  //指定されたファイルがあるか調べる
  if (!FileExists(archiveFile))
  {
    throw Exception("指定されたファイル'" + archiveFile + "'が存在しません。");
  }

  //DLL(Unlha32.dll)の読みこみ
  TDll dll("unlha32.dll");

  //DLLの存在を確認
  if (!dll)
  {
    throw Exception("UNLHA32.DLLが見つかりません。");
  }

  //DLLのチェック
  typedef WORD (WINAPI *PUnlhaGetVersion)(void);
  PUnlhaGetVersion UnlhaGetVersion = (PUnlhaGetVersion)dll.GetProcAddress("UnlhaGetVersion");
  WORD ver = UnlhaGetVersion();
  printf("UNLHA32.DLLのバージョン:%d\n", ver);

  //動作中かチェック
  typedef BOOL (WINAPI *PUnlhaGetRunning)(void);
  PUnlhaGetRunning UnlhaGetRunning = (PUnlhaGetRunning)dll.GetProcAddress("UnlhaGetRunning");
  if (UnlhaGetRunning())
  {
    throw Exception("UNLHA32.DLLが現在動作中です。");
  }

  //展開できるかチェック
  typedef BOOL (WINAPI* PUnlhaCheckArchive)(LPCSTR, const int);
  PUnlhaCheckArchive UnlhaCheckArchive = (PUnlhaCheckArchive)dll.GetProcAddress("UnlhaCheckArchive");
  if (!UnlhaCheckArchive(archiveFile.c_str(), 0))
  {
    throw Exception("UNLHA32.DLLでは展開できません。");
  }

  //展開先フォルダの作成
  bool createdDir = false;
  if (!DirectoryExists(extractTo))
  {
    ForceDirectories(extractTo);
    createdDir = true;
  }

  //ファイル名とフォルダ名を修正する
  archiveFile = ToCommandStringFromFile(archiveFile);

  //フォルダ名は必ず\で終わる必要がある
  extractTo = IncludeTrailingPathDelimiter(extractTo);
  extractTo = ToCommandStringFromFile(extractTo);

  //展開する
  typedef int WINAPI (*PUnlha)(const HWND, LPCSTR, LPSTR , const DWORD);
  PUnlha Unlha = (PUnlha)dll.GetProcAddress("Unlha");
  char output[1024];
  AnsiString cmdLine = AnsiString().sprintf("x %s %s *", archiveFile, extractTo);
  int ret = Unlha(Application->Handle,
                  cmdLine.c_str(),
                  output, sizeof(output));
  //結果
  if (ret != 0)
  {
    //展開先フォルダが空の時は削除(削除できないかも)
    if (createdDir)
    {
      RemoveDir(extractTo);
    }

    throw new Exception("書庫の展開に失敗しました。");
  }
  printf(output);
}
//---------------------------------------------------------------------------
int main(int argc, char* argv[])
{
  if (argc < 1)
  {
    printf("展開するファイルが指定されていません。");
    return 0;
  }

  //展開
  for (int i = 1; i < argc; ++i)
  {
    AnsiString f = argv[i];
    //展開先を決定
    AnsiString extractDir = GetFolderPath();
    extractDir = IncludeTrailingPathDelimiter(extractDir) +
                 ChangeFileExt(ExtractFileName(f), "");
    //展開
    try
    {
      LhaExtractArchive(f, extractDir);
      printf("%sを%sに展開しました。\n", f.c_str(), extractDir.c_str());
    }
    catch (Exception &E)
    {
      printf("%sの展開に失敗しました。\n", f.c_str());
      printf("ERROR:%s\n", E.Message.c_str());
    }
  }

  getc(stdin);
  return 0;
}
//---------------------------------------------------------------------------

2007年01月02日

GExperts

D_DevLog GExperts

GExpertsのいくつかの機能の解説、さらにフォントの変更方法の説明もあります。
助かりました。

2007年02月10日

BDS2006(C++Builder2006)でメール送信

BDS2006(C++Builder2006)でメールを送信するには、IndyのTIdSMTPを使います。

文字コードをShift_JISからJISに変換するのには、IndyのEncode2022JP()を使いました。

Subjectは自動的にBase64でエンコードしてくれるようです。

AnsiString host = "mail.example.jp";
AnsiString subject = Encode2022JP("メールの件名");
AnsiString to = yamamoto@example.jp";
AnsiString from = yamamoto@example.jp;
AnsiString text = Encode2022JP("メールの本文");
IdSMTP1->QuickSend(__classid(TIdSMTP), host, subject, to, from, text);

ファイルを添付したり、細かいことをしたいときは、TIdMessageを使用する。

TIdSMTP* smtp = new TIdSMTP(this);
TIdMessage* mail = new TIdMessage(this);
try
{
  //件名(Subject)
  mail->Subject = Encode2022JP("メールの件名");
  //差出人(From)
  mail->From->Name = Encode2022JP("山本");
  mail->From->Address = "yamamoto@example.jp";
  //宛先(To)
  TIdEMailAddressItem* item = mail->Recipients->Add();
  item->Name = "山本";
  item->Address = "yamamoto@example.jp";
  //メールの本文(Body)
  mail->Body->Text = Encode2022JP("メールの本文。");

  //添付ファイル
  AnsiString filename = "C:\\サンプル.txt";
  TIdAttachment* attach = new TIdAttachment(mail->MessageParts, filename);
  attach->ContentTransfer = "base64";
  //添付ファイルのファイル名に日本語を含むときはBase64にエンコードする必要がある
  attach->FileName = EncodeHeader(Encode2022JP(ExtractFileName(filename)),
                                  TSysCharSet(), 'B', iso2022jp, "ISO-2022-JP");
  //ファイルにあわせて text/plain image/gif image/jpeg application/octet-stream など
  attach->ContentType = "text/plain";

  //重要度「高」
  mail->Priority = mpHigh;
  mail->ContentType = "text/plain";
  mail->CharSet = "ISO-2022-JP";

  //SMTPサーバー
  smtp->Host = "mail.example.jp";
  //PORT(必要に応じて設定)
  smtp->Port = 25;

  //サーバーに接続して送信
  smtp->Connect();
  smtp->Send(mail);
  smtp->Disconnect();
}
__finally
{
  delete smtp;
  delete mail;
}

2007年03月08日

C++BuilderでPNGファイルを扱う

C++Builder MLで話題になっていた、Delphi/C++BuilderからPNGファイルを扱うPNG Delphiというライブラリを試してみました。

簡単な使い方

  1. PNG Delphiからダウンロードして展開します。
  2. プロジェクトと同じフォルダに、pasファイルとobjフォルダをコピーします。
  3. プロジェクトにpngimage.pasを追加します。
  4. コードに #include "pngimage.hpp" を追加します。

以上で、使えます。

サンプル

//---------------------------------------------------------------------------
/**
 * BitmapファイルをPNGファイルに変換する。
 */
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  //Bitmapファイルを選択する
  if (!OpenDialog1->Execute()) return;
  //Bitmapファイルを開く
  Graphics::TBitmap* bitmap = new Graphics::TBitmap();
  bitmap->LoadFromFile(OpenDialog1->FileName);
  //PNGファイルに変換して保存する
  TPNGObject* png = new TPNGObject();
  png->Assign(bitmap);
  png->SaveToFile(ChangeFileExt(OpenDialog1->FileName, ".png"));

  delete png;
  delete bitmap;
}

TPNGObjectは、TBitmapやTJPEGImageと同じ感覚で使えます。

//---------------------------------------------------------------------------
/**
 * PNGファイルを表示する
 */
void __fastcall TForm1::Button2Click(TObject *Sender)
{
  //PNGファイルを選択する
  if (!OpenDialog1->Execute()) return;
  //PNGファイルを表示する
  Image1->Picture->LoadFromFile(OpenDialog1->FileName);
}
//---------------------------------------------------------------------------

なぜか、TPicture::LoadFromFile()でPNGファイルが読み込めるようになりました。

手軽に使えるのが素敵なライブラリです。

追記
C++Builder 2009では、標準でPNGファイルを扱うことができるようになりました。
PNGファイルを扱う - C++Builder Tips

2007年03月10日

Borland Developer Studio 2006 アップデート2 Hotfix 11

Borland Developer Studio 2006 アップデート2 Hotfix 11が公開されました。
ダウンロードは、C++Builder 2006 registered user downloadsから。

Borland Developer Studio 2006 アップデート2 Hotfix 11 についてを読むと、 「2007年に実施される新しい夏時間の対応についての対応」ということです。
日本ではあまり問題はないのかもしれません。

このHotfixは、C++Builder 6以前のバージョンにも適用できます。

2007年05月15日

C++Builder 2007発表

CodeGear、Windows Vista?対応のC++ RADツールの新バージョンC++Builder 2007を発表

ということで、興味深いところは、

BoostおよびDinkumwareライブラリのサポートを含むANSI C++など標準仕様への準拠

どこまでBoostをサポートしてくれるのか。
Boostを標準添付してくれたら、インストールの手間が省けて助かる。

新しいINDY 10 インターネットプロトコルコンポーネントスイート

INDY 9との互換性が気になるところ。

C++Builder 2007は、2007年6月中旬の出荷を予定しています。

早い。あと一月。

2007年06月28日

C++でラムダ・クラス

C++の設計と進化を読んで。
感動した。

Lambda x;
list<int>::iterator p = find_if(lst.begin(), lst.end(), x < 7);

で、Lambdaクラスが、こんな感じ。
(ちょっと編集しています。もっと上手に書けないかな。)

class Lambda{};
template<class T> binder1st<greater<T> > operator<(Lambda, const T& v)
{
  return bind1st(greater<T>(), v);
}

2007年06月30日

cross-cast

これも、C++の設計と進化を読んで知った。
cross-castと言うらしい。

class Robot {...};
class Human {...};
class Android : public Robot, public Human {...};

Robot* pr;
Human* ph = dynamic_cast<Human*>(pr);

これ、JavaやC#でできるの?

オーバライドでリターンタイプの緩和

これも、C++の設計と進化を読んで知った。

C++では、オーバライド関数の返値は、ベースクラスの関数と正確に一致しなくても良い。

BがDのアクセス可能なベースクラスであるときには、B*をD*で、そしてB&をD&でオーバライドできるようにした。

class B {
  virtual B* clone() { return new B(*this); }
};
class D : public B {
  D* clone() { return new D(*this); }
};

2007年07月10日

同期編集モード

Delphi 2006のエディタで、マウスでコードを選択していくと左側にアイコンが現れます。 そしてこのアイコンをクリックすると、同期編集モードというものになります。 これは、ある変数を書き換えると、その選択範囲内の同じ変数も書き換わるという機能です。

同期編集モード

アイコンが表示されることは気がついていたけど、あのアイコンにはそういう意味があったのか。
試してみたけど、これは便利。
もちろんC++Builderでも使える。
感謝。

2007年07月18日

マウスホイールのイベント処理

マウスホイールのイベント処理

class TForm1 : public TForm
{
private:    // ユーザー宣言
  void __fastcall WMMouseWheel(TMessage &Msg);
BEGIN_MESSAGE_MAP
  VCL_MESSAGE_HANDLER(WM_MOUSEWHEEL, TMessage, WMMouseWheel)
END_MESSAGE_MAP(TComponent)
};
//---------------------------------------------------------------------------
/**
 * マウスホイールを動かしたときの処理
 */
void __fastcall TForm1::WMMouseWheel(TMessage &Msg)
{
  if (Msg.WParam > 0)
  { 
    //ホイールを奥に動かした時の処理
  }
  else
  { 
    //ホイールを手前に動かした時の処理
  }
}

2007年07月26日

C++Builder2007への移行作業

C++Builder2006からC++Builder2007へ移行中。
今のところ大きな問題は起きていない。

唯一の問題が、一部のプロジェクトで発生したVCLのコンパイルエラー。
includeする順番を変更(問題が発生するファイルを上に移動)することで回避できた。

2007年07月31日

C++Builder2007で実行時エラー

C++Builder2007でコンパイルしたプログラムが実行時にエラー(EAccessViolation)になった。
問題を切り分けしてわかったこと。

  • プロジェクトオプションの「最適化」で「サイズ」か「速度優先」を選択してコンパイルしたプログラムは、実行時にエラー(EAccessViolation)になった。
  • 「なし」または「選択対象のみ」を選択してコンパイルした場合は、問題なく動作した。
    • 「選択対象のみ」の場合は、すべて選択しても問題なし。
  • 特定のプロジェクトでのみ発生する。
    問題が発生しないプロジェクトも存在する。

追記
C++Builder 2007 Update 2で解決しました。

2007年08月02日

C++Builder2007でCG32.DLLがリンクされてしまう問題

C++Builder2007では、コンパイルの設定にかかわらず、CG32.DLLがリンクされてしまうようです。

とりあえず、プロジェクトファイルの次の2行を削除することで、CG32.DLLがリンクされなくなるようです。

<LinkCodeGuard>true</LinkCodeGuard>
<BCC_CodeguardDebugLevel>Level1</BCC_CodeguardDebugLevel>

追記
C++Builder 2007 Update 2で修正されました。

2007年08月10日

C++Builder 2007 Update 2

C++Builder 2007 Update 2が公開されました。

解決された問題点は、

  • 「classic undocked」レイアウトを VCL デザイナとともに使用しているときに、IDE を最小化してもデザインウインドウが最小化しない
  • -O1 もしくは ?O2 オプションを使用したときにコンパイラがスタックポインタを破壊する。また、最適化を行うべき状況で、コンパイラが最適化しない場合に比べて遅く、肥大した不要なコードを追加する
  • プライベートの複数のTRect フィールドを持つDelphiコンポーネントを使用したときにC++コンパイラがTRect 構造体のオフセットアドレスのミスマッチを起こす
  • 頻繁な "incorrect project override option" エラーが発生し、C++Builder の再起動なしに、プロジェクトがコンパイルできない
  • CodeGuard オプションをオンに設定すると、すべての設定でcg32.lib をリンクし、設定をオフにできない
  • C++ ユニットに結び付けられていないヘッダファイルでコード補完が失敗する
  • ヘッダファイルでコード補完に失敗したときに、"Unable to open input file" というメッセージがビルドタブに表示される
  • C++ コンパイラが"Internal back end error C1038" というメッセージを表示する
  • 大きな .obj ファイルがある場合に、依存関係のチェックに時間がかかる

これで、だいぶ使えるようになりそう。

アップデートは、スタートメニューのスタート→プログラム→CodeGear RAD Studio→アップデートの確認から、簡単にできるようになった。

追記
「Update2の実行時に「オリジナルのセットアップファイルが保存されている場所」のダイアログが表示さる現象について説明が公開されました。

2007年09月04日

第6回CodeGearデベロッパーキャンプの資料

2007年8月28日に開催された第6回CodeGearデベロッパーキャンプのプレゼンテーション資料がダウンロードできるようになりました。

お薦めは「知って得する!現役ヘルプデスクが答えるDelphiテクニカルエッセンス」。

次の質問に対する回答がソースコート付きであります。Delphiですけど。

  • OLEを利用したExcelの出力処理で件数が多い場合、処理時間が長くて困っています。
  • DBGridで行毎にチェックをを付けさせることは可能ですか?
  • DBGridの表示状態をユーザーごとに持たせることはできますか?
  • Editコンポーネントで数値項目を表示する場合、右寄せで表示することはできますか?
  • 実行プログラムが動作しているクライアント端末のIPアドレスを取得することはできますか?
  • 複数の言語環境で稼動するシステムを作成する効率の良い手法はありますか?

「C++Builder 2007の新機能と活用のポイント」も参考になりました。

  • C++Builder の変更点

    • Dinkumware v5.01
    • Indy 10.1.5
    • Vistaダイアログ
    • ランタイムテーマ
    • ApplicationとMainForm
    • プロジェクト管理
    • MSBuild
    • データベースドライバとDataSetがUnicodeに対応

    よくわかっていないことも多い。学習が必要だと感じました。

  • .NET FrameworkをC++Builderから利用する

    • .NETのアセンブリはCOMとして利用可能

      へぇ~

  • BDE+Paradox を移行する

    BDEもParadoxも、だいぶ前に使用をやめたので関係なし。

  • C++Builder 2007 対応(予定)の製品など

    • Quick Report Professional 4.07(リリース予定)
    • TMS Unicode Component Pack Ver1.3.1.0
    • EurekaLog Ver6.0.7

    Quick Report対応するんですね。

    • Boost をbcc32 v5.91 でビルドするには?

    私のサイトでも導入方法をまとめています。
    =>「C++Builder2007でBoostを使用する方法

うーん、やっぱり行きたかったなぁ。

2007年09月16日

C++Builder 2007 Update 3

C++Builder 2007 Update 3まとめ。

C++Builder 2007 Update 3が公開されました。
Quality Central報告された48個の問題を含む350以上の修正が行われています。

C++Builder 2007 Update 3のインストール方法は、「RAD Studio アップデートに関する重要な情報」に説明があります。
自動アップデート機構を使う方法が簡単です。

今回の修正は、ファイルサイズが非常に大きくなっていてダウンロードに時間がかかります。
回線が遅い場合は、先に修正ファイルをダウンロードした方がいいかもしれません。
C++Builder 2007 Update 3 - full .zip version」からダウンロードできます。

追記
ISOファイルも公開されました。ダウンロードは「CodeGear RAD Studio 2007 ISO」から。
関連ファイルの一覧は「C++Builder 2007 Registered User Downloads」にあります。

2007年09月19日

bcbboost 1.34.1-5.9.2-0.2

Boost 1.34.1 と C++Builder 2007 Update 3 に対応した bcbboost が公開されています。

C++Builder2007でBoostを使用する方法」を書き直さなければ。

GExperts 1.32

C++Builder 2007に対応した「GExperts 1.32」が公開されています。

「Ctrl + D」で表示されるメッセージダイアログのウィザードや、 コントロールの位置に応じて自動的に各コントロールのTab Orderを設定してくれる「Set Tab Order」など、 便利な機能が満載です。

早速インストールしました。問題なく日本語が使えるみたいです。

2007年10月01日

C++Builder2007 Update3 でコンテキストメニューが表示されない問題

C++Builder2007にUpdate3を適用すると、TMemoやTEditで右クリックしたときにコンテキストメニューが表示されなくなります。

この問題に対して、修正プログラムを作成した人がいました。
Inofficial RAD Studio 2007 Patchからダウンロードできます。
C++Builder2007でも使用できました。

C++Builder2007 Update3でメニューのキャプションが文字化けする問題

C++Builder2007にUpdate3を適用して作成したプログラムをWindows Vistaで実行すると、メニューのキャプションが文字化けします。

この問題を回避する方法が「Delphi2007Update3のバグ」で紹介されています。

C++Builder2007でも同じ方法で問題が回避できることを確認しました。

  1. 現在のプロジェクトがあるフォルダに、「C:\Program Files\CodeGear\RAD Studio\5.0\source\Win32\vcl\Menus.pas」をコピーします。

  2. プロジェクトにコピーした「Menus.pas」を追加します。

  3. Menus.pasの1150行目を以下のように変更します。

    変更前:
    DrawThemeTextEx(ThemeServices.Theme[teMenu], ACanvas.Handle, MENU_POPUPITEM,
      MenuStates[MenuItem.Enabled], PWideChar(WideString(Text)), Length(Text), Flags, @Rect, Options);
    
    
    変更後:
    DrawThemeTextEx(ThemeServices.Theme[teMenu], ACanvas.Handle, MENU_POPUPITEM,
     MenuStates[MenuItem.Enabled], PWideChar(WideString(Text)), Length(WideString(Text)), Flags, @Rect, Options);
    
  4. Unit1.hに次にコードを追加します。

    #include "Menus.hpp"
    

以上です。

作成したプログラムをWindows Vistaで実行しても、メニューは文字化けしませんでした。

2007年10月10日

WindowsXP以降では、ビジュアルスタイルを使用すると入力制限文字数が変わる

WindowsXP以降では仕様が変更され、ビジュアルスタイルを使用すると入力制限文字数が変わります。

WindowsXP で Manifest ファイルを使用したアプリケーションで Common Control バージョン 6 を使用した場合、Edit コントロールへの EM_LIMITTEXT メッセージによる入力量制限を設定する際の単位が、バイト数単位ではなく文字数単位に変更されました。この動作は仕様です。

[WinXP] Common Control 6.0 の EM_LIMITTEXT による入力制限

2007年10月31日

第7回 CodeGearデベロッパーキャンプの資料が公開されました

第7回 CodeGearデベロッパーキャンプの資料が公開されました。

中でも、特に興味深いのが「wxFormsで始めるwxWidgetsプログラミング」。

wxWidgetsは、おなじみのマルチプラットフォームに対応したGUIツールキット。
マルチプラットフォーム対応というと、WideStudioやTkのように独自の外観になりがちだが、 wxWidgetsはOSが提供するコントロールを使用するため、見た目に違和感がない。

で、TwinForms社が開発したC++Builder用プラグイン「wxForms」を使うと、C++BuilderでwxWidgetsを使った開発ができる。
wxFormsのホームページにあるスクリーンショットを見ると、VCLと同じように違和感なく開発できることがわかる。

公開された資料には、wxFormsのインストールから、MacOSやLinuxでのビルド方法まで紹介されている。

Kylixで見たマルチプラットフォームの開発ツールの夢が実現できそうだ。

2007年11月02日

ビデオ - Delphi Hour in Tokyo

ビデオ - Delphi Hour in Tokyoが公開されました。

charの発音は「ちゃー」だったのか。

2007年11月16日

RAD Studio 2007 ヘルプアップデート1

RAD Studio 2007 ヘルプアップデート1が公開されました。

評判の悪かったヘルプが改善されることは嬉しく思います。

インストールは、スタートメニューの「スタート」→「プログラム」→「CodeGear RAD Studio」→「アップデートの確認」を選択します。
あとは画面の指示に従って、インストールします。

ダウンロードは、「ID: 25163, CodeGear RAD Studio 2007 Help Update 1」から。
474MBもあるんですね。

2007年11月29日

RAD Studio Help Update 1 CHM files - Japanese

CHM files that can be viewed in the HTML Help viewer. The Japanese content applies to RAD Studio 2007 including Delphi, C++, and .NET Help Update 1.

25267, RAD Studio Help Update 1 CHM files - Japanese

後で試す。

追記

「RAD Studio ヘルプアップデート 1」の CHM 形式ファイルをリリースしました。
このファイルは、先日公開した「RAD Studio ヘルプアップデート 1」と同一の内容で、HTML ヘルプビュアーで閲覧可能な CHM 形式のファイルです。

RAD Studio ヘルプアップデート 1 (CHM ファイル) をリリースしました

ということで、内容は同じらしい。

C++ Builder Tipsを更新しました

C++ Builder Tipsを更新しました。
ほとんど、自分用の備忘録。
まとめておかないと、調べたことを忘れてしまいますからね。

2007年12月06日

C++では三項演算子を右辺にも書ける

三項演算子は右辺にも書ける。

(a<b?a:b<c?b:c) = val();

2007年12月07日

C++の自己初期化

=演算子はどう解釈される?」から。

自己初期化の問題は、『C++プログラミングの処方箋』の鉄則21で取り上げられています。

C++では、初期化子が解釈される前に変数の名前が有効になります。

C++プログラミングの処方箋

int var = 12;
{
  double var = var;

変数varの値は?

自信のない方は、『C++プログラミングの処方箋』を読んでみてはいかがでしょうか。
鉄則21では、自己初期化の問題で陥りやすい落とし穴について解説されています。

ちなみに、変数varの値は不定値になります。

以下のコードは、C++Builder2007ではコンパイルできました。

time_t now( time( &now ) );
time_t t( time( &t ) );

2007年12月18日

CodeGear RAD Studio 2007 December Update

CodeGear RAD Studio 2007、Delphi 2007 for Win32、C++Builder 2007のパッチが公開されたようだ。

CodeGear RAD Studio 2007 December Update

追記。

日本語訳が出ました。

RAD Studio 2007、Delphi 2007 for Win32 および C++Builder 2007 アップデート(December 2007 Update)リリースノート

追記。

RAD Studio 2007 December Updateでは、C++Builder 2007向けに 46件(うち12件はQuality Centralより)の修正が加えられています。

C++Builder 2007 - December Updateの修正リスト

C++Builderの修正は46件とのこと。結構、多いですね。

UnitTestでprivateにアクセスする方法

UnitTestでprivateにアクセスする方法。

#ifdef UNITTEST
#define private public
#endif

箱を白くするには..

なるほど。
friendを使うよりも、この方がスマートかも。

2007年12月19日

CodeGear RAD Studio 2007 Trial

CodeGear RAD Studio 2007 Trialが公開されたようです。
日本語版も選択できるようです。

CodeGear RAD Studio 2007 Trial

2007年12月20日

Serial Number が不正のエラー

同一マシンで Delphi 2007 をアンインストールし、再インストールした際に、一部のお客様で"Serial Number が不正" のエラーの問題が発生することがあります。

Delphi 2007 ? Serial Number が不正のエラーについて

C++Builderも同じです。はまりました。

単純にインストーラから削除するだけでは不十分なようです。

手作業でいくつかのファイルやディレクトリを削除しなければならない模様。

正しいアンインストールの方法は「RAD Studioのアンインストール」を参照。

2007年12月31日

C++Builder 2007 Update 3 + December 2007 UpdateにBoostをインストール方法

C++Builder2007でBoostを使用する」の記事を更新しました。

C++Builder Update 3| + December 2007 Updateに対応しました。

2008年01月09日

C++Builder2007で「'DesignIntf.dcu' が見つかりません」のエラーメッセージが出たときの対処法

C++Builder2007で「'DesignIntf.dcu' が見つかりません」のエラーメッセージが出たときの対処法。

CODEGEARのページに情報がありました。
DesignIntf.dcu and Proxies.dcu not found.

  1. メニューから「プロジェクト」「オプション」でプロジェクトオプションの画面を表示します。
  2. 「Delphiコンパイラ」「その他のオプション」を選択します。
  3. 「コンパイル時に使用するパッケージ」欄に「DesignIDE」と入力します。

2008年01月10日

C++Builder2007(Indy10)のBase64のデコードの方法が変わっている

C++Builder2006(Indy9)とC++Builder2007(Indy10)で、Base64のデコードの方法が変わっているようです。

C++Builder2006(Indy9)の場合、

AnsiString enc = ~; //エンコードされた文字列
TFileStream* fs = new TFileStream(fileName, fmCreate);
IdDecoderMIME1->DecodeToStream(enc, fs);

C++Builder2007(Indy10)の場合、

AnsiString enc = ~; //エンコードされた文字列
TFileStream* fs = new TFileStream(fileName, fmCreate);
IdDecoderMIME1->DecodeBegin(fs);
IdDecoderMIME1->Decode(enc);
IdDecoderMIME1->DecodeEnd();

2008年01月17日

派生クラスでオーバーライドしたときにvirtualをつけると読みやすくなる

派生クラスでオーバーライドしたときにvirtualをつけると読みやすくなる。
という話が『C++ Coding Standards』にあったと思います。たぶん。

//Base.h
class Base
{
  virtual void Foo();
  …
};

//Derived.h
class Derived : public Base
{
  void Foo();
  void Bar();
  …
};

Derivedクラスのメソッド「Foo」「Bar」は、Baseクラスのメソッドをオーバーライドしているのか、 Derived.hを見ただけではわかりません。
Base.hを確認する必要があります。

時として、意図しないオーバーライドで思わぬ問題が発生することがあります。
# 特にアクセス制限の少ないrubyで。
# 明示してオーバーライドするDelphiやC#がうらやましい。

派生クラスの方にも、「virtual」をつけると多少わかりやすくなります。

//Derived.h
class Derived : public Base
{
  //Foo()は基本クラスをオーバーライドしている
  virtual void Foo();
  //Bar()は基本クラスをオーバーライドしていない
  void Bar();
  …
};

2008年01月21日

C++Builderで国際化

CODE GEARのTeam Japanのブログで「C++Builderにおける国際化支援機能」というエントリーがありました。
紹介されている4つのツールは、いずれも有料みたい。

他にないのかと探してみると見つかりました。

一つは、「GNU gettext for Delphiを使って国際化してみる」で紹介されている「GNU Gettext for Borland Delphi, C++ Builder and Kylix」。
もう一つは「Delphiコンポーネント:国際化対応コンポーネント IniLang」で紹介されている「IniLang
どちらも無料で使えるようです。

2008年02月07日

2008 Delphi Survey

2008年度のDelphiアンケート(Delphi Survey)の日本語版を公開しました。

日本語版アンケート

この調査は、Delphiの今後の方針を決定する際の参考にさせていただく重要なアンケートです。ぜひご協力ください。

2008 Delphi Survey

Delphiユーザーの方は是非。

C++Builderユーザーの方は、

C++Builder開発者の方向けの調査は、別途実施したいと考えています。

2008年度 Delphi アンケート

ということです。

2008年02月09日

C++BuilderでPNGファイルを扱う方法

PNGファイルを扱う」を更新しました。

pngimage.hppの作成方法を追加しました。

お知らせありがとうございました。

2008年02月11日

コンボボックスのドロップダウンリストを表示する

コンボボックスのドロップダウンリストをプログラムから表示する方法。

# ドロップダウンリストを表示する
ComboBox1->Perform(CB_SHOWDROPDOWN, 1, 0);

2008年02月21日

C++Builder2007のWindows XP/Vistaでの表示について

Windows Vistaでのメニューの表示

TMainMenuはAreoのスタイルで表示されますが、
TActionMainMenuBarはAreoのスタイルで表示されません。

cb01.jpg

Windows XPでのメニューの表示

TMainMenuはメニューの下に線が表示されます。
TActionMainMenuBarは普通に表示されます。

cb02.gif

Windows VistaでのTPageControlの表示

Windows Vistaでは違和感なく表示されますが、
Windows XPでは、タブの部分が白く表示されます。

cb03.gif

ランタイムを無効にすれば古いスタイルで表示されるので、
Windows Vista、Windowx XPともに普通に(古いスタイルで)表示されます。

cb04.gif

2008年02月29日

RAD Studio 2007 Help Update 2

RAD Studio 2007 Help Update 2 には、ヘルプ システムのバグ修正のほか、改良がいくつか施されています。

RAD Studio 2007 ヘルプアップデート 2 インストールおよびリリースノートRAD Studio 2007 ヘルプアップデート 2 インストールおよびリリースノート

C++Builder 2007の一番大きい欠点は、ヘルプのできが悪いことだと思います。

今回のアップデートでヘルプファイルが改善されて、使いやすくなると良いのですが。

2008年04月23日

Apr08 Hotfix for CodeGear RAD Studio 2007

Apr08 Hotfix for CodeGear RAD Studio 2007がリリースされました。

2008年05月19日

「EurekaLog」が便利そう

EurekaLog(その1) - 不意に起こる例外に対処する」で紹介されている「EurekaLog」が便利そうです。
突発的なエラーが発生した時、詳細な情報が得られるのは、非常に助かります。

さっそく、EurekaLogのホームページを見たところ、

Prices begin at $99(US).

えっと、有償のライブラリでしたか。
でも、確かにそれだけの価値はあるかもしれません。

追記
EurekaLogについて詳しい記事がありました。やっぱり良さそう。

さらに追記

EurokaLogを導入しました。とても優秀なツールでした。
詳細については「EurekaLog導入レポート」をご覧下さい。

2008年07月20日

IDE Fix Pack 1.6 for RAD Studio 2007

RAD Studio 2007のいくつかのバグが修正されたようです。

Fixes RAD Studio 2007 IDE bugs

ID: 25412, IDE Fix Pack 1.6 for RAD Studio 2007

DDevExtensions 1.6

DDevExtensions adds new features to the Delphi/C++Builder IDE.

ID: 25536, DDevExtensions 1.6

IDEの機能を拡張するプログラム。

* Adds keybinding for extended HOME and indent/unindent TAB/Shift-TAB

とか、地味だけど嬉しいかも。

試してみよう。

追記
こっちが公式サイト=>DDevExtensions

DelphiSpeedUp 2.78

DelphiSpeedUp improves the startup and overall performance of the Delphi and C++Builder IDE.

ID: 25436, DelphiSpeedUp 2.78

IDEの起動や全体のパフォーマンスが向上するらしい。

2008年07月22日

C++Builder 2009には、Boost C++ librariesが標準添付か

We are adding the Boost C++ libraries to C++Builder in Project Tiburon.

Boost support in Tiburon C++Builder 2009

C++Builder 2009には、Boost C++ librariesが標準でついてくるということでしょうか。

Boostの正規表現ライブラリには、お世話になっています。
インストールする手間が省けて助かりますね。

2008年09月06日

Delphi 2009/C++Builder 2009を購入するには

Delphi 2009/C++Builder 2009の発表がありましたが、
バージョンアップのDMが届かないですね。

そこで、Delphi 2009/C++Builder 2009はどこで購入できるのか、調べました。

CodeGearのホームページ「製品の購入」によると、
新規購入でもバージョンアップ版でも
SEshop.com:codegearショップ」で購入できるようです。

SEshop.com:codegearショップでは、バージョンアップ版お買い得キャンペーン実施中でした。

バージョンアップ版 10%OFF お買い得キャンペーン
2008年9月12日(金)~2008年12月26日(金)まで、『Delphi 2009、『C++Builder 2009』、『Delphi 2009 & C++Builder 2009 Bundle』バージョンアップ版 お買い得キャンペーンを実施中です!!

続き。
Delphi 2009は(C++Builder2009も)SEshopよりもニフティストアの方が安い

2008年09月08日

DelWikiのTiburonのページがよくまとまっている

DelWikiのTiburonのページがよくまとまっている。
すばらしい。

C++Builder関連の情報は少ないけど。

2008年09月11日

Delphi 2009/C++Builder 2009のUnicode対応について、DEKOのざつだん。の解説が勉強になります

DEKOのざつだん。のページで、Delphi 2009/C++Builder 2009から導入されるUnicode対応について、丁寧に解説されていました。

このページを読んで、Unicode対応についてだいぶ理解できました。
ありがとうございます。

印象としては、Delphi 2009/C++Builder 2009は上手にUnicodeに対応したように思います。
日本語OSに限定すれば、既存コードの修正は少なそうです。

文字コードの変換が簡単にできるようになるのは嬉しいですね。
コードページにあわせて、暗黙に変換してくれるとのこと。

type
  SJISString = type AnsiString(932);   // Shift-JIS(CP932) 
  EUCJString = type AnsiString(20932); // EUC-JP(CP20932) 
var
  SJIS: SJISString;  
  EUCJ: EUCJString; 
  A: AnsiString;    // 日本語環境ならデフォルトはCP932 
  U8: UTF8String;  
begin
  A    := 'あいうえお';
  SJIS := A;
  EUCJ := A;
  U8   := A;

  ShowMessage(SJIS);
  ShowMessage(EUCJ);
  ShowMessage(U8);
end;

ただ、拙作のソフトについて言えば、文字処理については泥臭い処理を行っているプログラムもありますから、修正は必要になりそうです。

2008年09月13日

Delphi 2009は(C++Builder2009も)SEshopよりもニフティストアの方が安い

Delphi 2009は(C++Builder2009も)SEshopよりもニフティストアの方が安いようだ。

Delphi 2009 Professional

Delphi 2009 Professional バージョンアップ版

C++Builder 2009 Professional

C++Builder 2009 Professional バージョンアップ版

2008年09月15日

Firebird Embedded Serverの使い方

C++ Builder Tipsに「Firebird Embedded Serverの使い方」の記事を加えました。

2008年09月18日

CDNでDelphiのUnicode対応に関する記事が公開されています

CDNでDelphiのUnicode対応に関する記事が公開されています。

既存のコードがたくさんありますので、Delphi Unicodeワールド パートIII: コードをUnicode対応にするをよく読んで、どのような対応が必要なのか、勉強したいと思います。

第10回エンバカデロ・デベロッパーキャンプの資料がダウンロードできるようになりました

第10回エンバカデロ・デベロッパーキャンプの資料がダウンロードできるようになりました。

興味のあるところを斜め読み。

追加されたC++0x機能
* Strongly typed enumerations
* Explicit Conversion Operators
* static assertions
* Type trait functions
* alignof operator on types
* extern templates
* Variadic templates
* Rvalue references
* decltype
* New Unicode char types (char16t and char32t)
* Attributes - noreturn and final
* nullptr … ×(NULLの代わりですが、今回は実装されませんでした…)

C++ Builder次期バージョン"2009"活用法

C++0xは知らないことがたくさん。
勉強するぞ。

強く型付けされたEnum
* enum class 列挙型名 { …. };

明示的なキャスト演算子
* explicit operator XXX(){ … }

静的なアサーションによるチェック
* static_assert (constant-expression, error-message);

型特性を判定する関数
* bool _isXXXX(typename T, ...)
* bool _hasYYYY(typename T, ...)

C++ Builder次期バージョン"2009"活用法

知らないなぁ。
知識をアップデートしていかないと。

Quiz: マイグレーションとイベントハンドラ
* 旧C++Builderで作成したプロジェクトを、C++Builder 2009 にインポートしました。
* TStringGridを配置して、OnSetEditTextイベントを使用しています
* 日本語/中国語混じりの文字列を入力してみます
* C++Builder 2009 でビルド & 実行しようとすると、どうなる?
 1. コンパイルエラーが発生する
 2. ビルドは成功するが、実行時にエラー/例外が発生する
 3. 日本語は正しく表示されるが、中国語が ? に化ける
 4. フォント設定にも依るが、基本的に正しく表示される

C++ Builder次期バージョン"2009"活用法

気になっていた部分ですが、資料を見ただけではよくわかりませんでした。
説明が欲しいところ。

プリコンパイル済みヘッダーウィザード
* デフォルトで pch1.h を生成し、ビルド時に暗黙のうちにincludeされます
* #pragma hdrstop の上部は変更する必要なし

C++ Builder次期バージョン"2009"活用法

C++BuilderのIDEの新機能の一つ。

国際化支援機能 - ITE/ETM
* C++Builder2006,C++Builder2007で消された
* ITE(Integrated Translation Environment)
* ETM(External Translation Manager)がC++Builder2009で復活しました!!
* 製品のインストール時に、英語版(English)を選択すると
* VCL/RTLのシステムメッセージを、デフォルトで英語にすることも可能

C++ Builder次期バージョン"2009"活用法

いつの間にかなくなっていた国際化支援機能が復活。
開発手順のデモは見たかった。

Tips: C++Builderでリソース文字列
* .RCではなく、リソース文字列を XXXX.pas で定義する
* VCLのLoadResourceString関数を利用する
* ITE/ETMでも認識され、ローカライズ可能

C++ Builder次期バージョン"2009"活用法

リソース文字列を.pasファイルに書けるとは知りませんでした。
新機能?
ファイルの管理が楽になります。

Delphi言語の強化
* UnicodeStringをデフォルト文字列として採用
* ジェネリックス
* 無名メソッド

CodeGearプロダクトアップデート

Delphiも言語機能が強化されました。
こないだまでは時代遅れの言語だったのに。
かなり使える言語になりましたね。

C++Builder 2009の新機能
* 言語とライブラリの強化
* 次世代C++標準「C++0x」の言語機能をコンパイラ・IDEの双方で早期にサポート
* Decltypeキーワード
* Explicit conversion operators
* Externテンプレート
* rvalue リファレンスを伴うMove Semantics
* Scoped Enumerations
* ネイティブType Traitsを伴うStatic Assertions
* Unicode文字型 char16t および char32t
* [[final]] および [[noreturn]] 属性
* Delphiとの互換性強化
* 仮想クラスメソッド
* スタティックプロパティ
* UnicodeString クラス
* 仮想メソッドToString、GetHashCode、Equalsを新たに持つTObject
* ANSI/ISO 標準ライブラリ Technical Report 1
* Boost library 1.35の統合

CodeGearプロダクトアップデート

C++0xに加えて、Technical Report 1も勉強しなければ。
Boostは、どれぐらいのライブラリに対応しているか興味のあるところ。

やっぱり実際に参加して話を聞くのが一番ですね。

C++Builder2009が手元に届く日が楽しみです。

2008年09月27日

C++ TR1 強い型付けの列挙型

C++ TR1の新しいenumの構文では、enumの後にclassがつけられるようになり、型も指定できるようになりました。

enum class 列挙型名 : 型 {
  …,
}

enum class days : char {
  Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday
}

classも型も指定しない場合は、従来のC++と同じ動作になります。
型を指定しない場合は、intになります。

//従来と同じ書き方もできる。
enum months {
  January, February, March, April, May, June,
  July, August, September, October, November, December
}

classを指定した場合は、明示的にスコープを指定する必要があります。

days day = days::Sunday; //エラー
days day = Sunday; //エラー

classを指定しない場合は、スコープを指定する必要はありません。

months month = January;

前方宣言も可能になりました。

enum days : char;

前方宣言ができるようになったことと、明示的なスコープ指定を強制できるようになったことがメリットかな。

2008年09月28日

nested classからは親のprivate空間にアクセスできるか。

nested classからは親のprivate空間にアクセスできるか。

C++Builder 2009でもエラーになりました。残念。

[BCC32 エラー] File1.cpp(25): E2247 'Hoge::func()' はアクセスできない

元ネタは、!Inner classes have no special access to the outer class in C++.

class Hoge {
public:
  Hoge () : bar_(*this) {}
private:
  class Bar {
  public:
    Bar(Hoge& hoge) : hoge_(hoge) {}
    void func() {
      hoge_.func();
    }
    Hoge& hoge_;
  };
  Bar bar_;
  void func(){
    cout << "func" << endl;
  }
public:
  Bar& Bar() { return bar_; }
};

int main()
{
  Hoge hoge;
  hoge.Bar().func();
  return 0;
}

C++ TR1 静的な表明

static_assertは、コンパイル時に式が条件を満たすかテストします。
式がfalseを返すときは、コンパイルに失敗します。

static_assert(テストする式, コンパイルエラーのメッセージ)

static_assert(sizeof(int) < sizeof(long), "error");

has_virtual_destructorは、型に仮想デストラクタが存在するかどうかをテストします。

#include <type_traits>
static_assert(
  std::tr1::has_virtual_destructor<TObject>::value, 
  "destructor must be virtual");

例えば、longがintより大きいサイズであることに依存するアルゴリズムの実装のように、標準では保障されていない箇所の確認などの用途がある

C++0x - Wikipedia

なるほど。
実行時ではなく、コンパイル時にテストできるのは嬉しいかもしれません。

2008年10月04日

Delphi 2009/C++Builder 2009で導入された新しい文字列型の不具合

Delphi 2009/C++Builder 2009で導入された新しい文字列型の不具合について、Embarcadero Discussion Forumsで話題になっていました。

マルチバイト言語圏の特有の問題のようです。

Delphi 2009/C++Builder 2009の開発者の方はぜび、Voteを

2008年10月05日

System.Abs 関数とSystem.Sqr 関数が使えない

System.Abs 関数とSystem.Sqr 関数が使えない。
C++Builder 2007とC++Builder2009の両方で確認。
どちらの関数もちゃんとヘルプには記載されているのに。

#include <System.hpp>
Abs(1);
Sqr(1);

とすると、エラーになる。

[BCC32 エラー] File1.cpp(11): E2268 未定義の関数 'Abs' を呼び出した
[BCC32 エラー] File1.cpp(12): E2268 未定義の関数 'Sqr' を呼び出した

何か間違えているのかな。

2008年10月08日

Delphi 2009/C++Builder 2009の新しい文字列型の問題

Embarcadero Discussion Forums: Delphiで、新しい文字列型(AnsiString)の話題が盛り上がっています。

新しい文字列型(AnsiString)は、実装上の理由により、仕様が複雑でわかりにくくなっているという印象を受けます。
さらにヘルプの記述が不十分で混乱を増しています。

文字列という基本的な部分であるだけに、わかりやすく直感的な仕様にしてもらいたいものです。

複数の文字コードを扱うプログラムを書くときは、まだしばらくの間はC++Builder 2009ではなく、C++Builder 2007を使おうと思います。

がんばれ、Embarcadero。

C++0x unique_ptr

C++0x: auto_ptr から unique_ptr への記事でunique_ptrの存在を知りしました。
試しに使ってみたところ、C++Builder 2009で使えました。
ちゃんとサポートされているのですね。

unique_ptrは、auto_ptrの所有権の問題を解決したスマートポインタ。

Wikipeidaの記述によると、

unique_ptrがauto_ptrの代わりとして導入され、auto_ptrは非推奨とされる。

とありました。

既存のコードも少しずつunique_ptrへ書き換えていきたいです。

2008年10月09日

C++Builder 2009への移植作業 その1

既存のコードをC++Builder 2009へ移植してして、文字列関連の処理の変更が大きいかもしれないことに気づいた。

AnsiString text = "あいうえお";
return text.AnsiPos("う");

C++Builder 2007では「5」を返すが、
C++Builder 2009では「3」を返す。

C++Builder 2007はバイト数で計算しているのに対し、
C++Builder 2009は文字数で計算している。

文字列関連の処理は、すべて見直しが必要かも…

2009年5月28日追記
Update3で修正されました。
C++Builder 2009 Update3では「5」を返します。

2008年10月10日

C++Builder 2009への移植作業 その2 C++Builder 2007と2009のAnsiStringの動作の違いの調査

C++Builder 2007と2009のAnsiStringの動作の違いを検証した。
比較の対象としてC++Builder 2009のUnicodeStringも比較した。

AnsiString s = "あいうえお";

として、各動作を確認した。

結論から言うと、動作が異なったのはAnsiString#AnsiPos()だけ。

C++Builder 2007から2009へ移植するときは、AnsiPos()に注意すればよさそうだ。

WideStringの動作は変更が無く、UnicodeStringと同じ挙動らしい。

C++Builder2007C++Builder2009(AnsiString)C++Builder2009(UnicodeString)C++Builder2007(WideString)C++Builder2009(WideString)
s.AnsiPos("うえ");53
s.Pos("うえ");55333
s.Delete(3, 2);あうえおあうえおあいおあいおあいお
s.Insert("ん", 3);あんいうえおあんいうえおあいんうえおあいんうえおあいんうえお
s.Length();1010555
s.SubString(3, 2);うえうえうえ
s.LastDelimiter("え");944

追記
AnsiString::LastDelimiterの挙動も異なるようです。

2009年5月28日追記
C++Builder 2009 Update3でAnsiStringの挙動が修正されました。

C++Builder2007C++Builder2009 Update3(AnsiString)C++Builder2009 Update3(UnicodeString)C++Builder2007(WideString)C++Builder2009(WideString)
s.AnsiPos("うえ");55
s.Pos("うえ");55333
s.Delete(3, 2);あうえおあうえおあいおあいおあいお
s.Insert("ん", 3);あんいうえおあんいうえおあいんうえおあいんうえおあいんうえお
s.Length();1010555
s.SubString(3, 2);うえうえうえ
s.LastDelimiter("え");994

2008年10月12日

AnsiDequotedStrの不具合

C++Builderには、引用符付き文字列から引用符を削除する「AnsiDequotedStr」という便利な関数が用意されています。

AnsiDequotedStr("\"abc\"", '"'); //=> abc
AnsiDequotedStr("\"あいう\"", '"'); //=> あいう

ところが、引用符が連続するとき、空の文字列を返してしまうようです。

AnsiDequotedStr("\"\"abc\"\"", '"'); //=> 
AnsiDequotedStr("\"\"あいう\"\"", '"'); //=>

C++Builder 2007とC++Builder 2009で現象を確認しました。

C++Builder 2009への移植作業 その3 C++Builder 2009におけるTStringList#LoadFromFileの挙動の変化

C++Builder 2007からC++Builder 2009への変更でTStringList#LoadFromFileの動作が変わりました。
文字コードがシフトJIS以外のファイルをTStringList#LoadFromFileで読み込むと、動作の違いにとまどうことになります。

挙動が変わった理由は、TStringListが内部でUnicodeStringでデータを保持するようになったため。
C++Builder 2009では、TStringList#TextやTStringList#StringsがUnicodeStringを返すようになりました。
そのためUnicodeの変換が行われます。

例えば以下のコードでは、次のような文字コード変換が行われます。
# デフォルトANSIコードページがシフトJISと仮定します。

TStringList* file = new TStringList();

// ファイルの文字コードをシフトJISとして読み込み、Unicodeに変換する
file->LoadFromFile("C:\\sample.txt");

// UnicodeをシフトJISに変換する
AnsiString text = file->Text;

2行目の文字コード変換が問題です。
文字列がシフトJIS以外だとしても、シフトJISとして変換されてしまいます。

C++Builder 2009ではLoadFromFileの引数で、ファイルのコードページが指定できるようになっています。

TStringList* list = new TStringList();
TEncoding* encoding = TEncoding::GetEncoding(20932); //euc-jp
list->LoadFromFile("C:\\sample.txt", encoding);

ただし、この方法はあらかじめコードページがわかっている場合にしか使えません。
ファイルを読み込んでから文字コードを判別して…、という場合には違う方法が必要です。

C++Builder 2009のTStringList#LoadFromFileには、BOMでコードページを自動判別する機能が追加されている

Delphi 2009では(C++Builder 2009も)BOMでコードページを自動判別する機能が追加されているそうです。

BOMからLE UTF-16/BE UTF-16/UTF-8を自動判別してくれます。
BOMがなければANSIのデフォルトのコードページになります。

UTF8とUTF-16のファイルが扱いやすくなりそう。

2008年10月13日

C++Builder 2009のTMemIniFileとエンコードの挙動を調べた

C++Builder 2009のTMemIniFileとエンコードの挙動を調べた。

結論から先に言うと、エンコードを指定する場合は「TEncoding::Unicode」を使うこと。
TEncoding::UTF8やEncoding:UTF7を使うと、不可思議な挙動に悩まされる。

エンコードを指定しないC++Builder 2007と同じ書き方。

//書き込み
std::unique_ptr<TMemIniFile> ini(new TMemIniFile(path));
ini->WriteString(L"日本語", L"こんにちは", L"こんにちは");
ini->WriteString(L"中国語", L"こんにちは", L"你好");
ini->UpdateFile();

ファイルには、ANSIのデフォルトのコードページ(Shift_JIS)で保存される。
したがって、文字化けする。

//読み込み
std::unique_ptr<TMemIniFile> ini(new TMemIniFile(path));
ini->ReadString(L"日本語", L"こんにちは", "no data"); //=> こんにちは
ini->ReadString(L"中国語", L"こんにちは", "no data"); //=> ?好

エンコードにTEncoding.Unicodeを指定する書き方。
リトルエンディアンバイトオーダーのUTF-16で保存される。
ファイルには16進数 FFFE のバイトオーダーマークが付く。

//書き込み
std::unique_ptr<TMemIniFile> ini(new TMemIniFile(path, TEncoding::Unicode));
ini->WriteString(L"日本語", L"こんにちは", L"こんにちは");
ini->WriteString(L"中国語", L"こんにちは", L"你好");
ini->UpdateFile();

//読み込み
std::unique_ptr<TMemIniFile> ini(new TMemIniFile(path, TEncoding::Unicode));
ini->ReadString(L"日本語", L"こんにちは", "no data"); //=> こんにちは
ini->ReadString(L"中国語", L"こんにちは", "no data"); //=> 你好

書き込み、読み込みともに問題なく動作する。

なお、ファイルにバイトオーダーマークが付いているので、エンコードを指定しなくても、自動的に識別してくれる。

std::unique_ptr<TMemIniFile> ini(new TMemIniFile(path));
ini->ReadString(L"日本語", L"こんにちは", "no data"); //=> こんにちは
ini->ReadString(L"中国語", L"こんにちは", "no data"); //=> 你好

エンコードにTEncoding::UTF8を指定する書き方。
UTF-8形式で保存される。
ファイルには、16進数 EFBBBF のバイトオーダーマークが付く。

//書き込み
std::unique_ptr<TMemIniFile> ini(new TMemIniFile(path, TEncoding::UTF8));
ini->WriteString(L"日本語", L"こんにちは", L"こんにちは");
ini->WriteString(L"中国語", L"こんにちは", L"你好");
ini->UpdateFile();

//読み込み
std::unique_ptr<TMemIniFile> ini(new TMemIniFile(path, TEncoding::UTF8));
Memo1->Lines->Add(ini->ReadString(L"日本語", L"こんにちは", "no data")); //=> こんにちは
Memo1->Lines->Add(ini->ReadString(L"中国語", L"こんにちは", "no data")); //=> 你好 / ?好

ここで、奇妙な動作になる。

ファイルが存在しないとき、つまり新規保存になるときは正しく、UTF-8で保存される。
ところが、すでにファイルが存在するときには、ANSIのデフォルトのコードページ(Shift_JIS)で保存されてしまう。

TEncoding::UTF8ではなくTEncoding::UTF7を使っても、同様の現象が起こる。

2008年10月14日

C++0x final属性

C++0xに新しく追加された機能の一つに「final属性」があります。
Javaなどの言語でおなじみの属性です。
サブクラスでの継承やオーバーライドを禁止します。

C++Builder 2009で、次のコードをコンパイルすると、エラーになります。

class TFoo
{
  virtual void f() {};
};
class TBar : public TFoo
{
  //仮想メンバ関数宣言のオーバーライドを禁止する
  void f [[final]] () {};
};
class TBaz : public TBar
{
  void f() {};
};

エラーメッセージ

[BCC32 エラー] Unit1.h(24): E2542 'f' は 'final' にマークされ、上書きできない

クラスに対しても、final属性を追加できます。

class TFoo
{
  virtual void f() {};
};
//クラスの継承を禁止する
class TBar [[final]] : public TFoo
{
  void f() {};
};
class TBaz : public TBar
{
  void f() {};
};

エラーメッセージ

[BCC32 エラー] Unit1.h(22): E2542 'TBar' は 'final' にマークされ、上書きできない

[[final]]というのがちょっと読みにくく感じますが、
これでC++プログラミングがより堅牢になりました。

C++Builder 2009のコード補完機能

C++Builder 2009のコード補完機能について

エディタのコード補完機能がおかしいような? たとえば、フォームにラベルを配置して「Label1->」と入力しても、ほんのちょっとしか候補が出てきません。

NRTTKR Blog日記のお部屋

私も、最初は同様の現象が起きていました。
候補が少ししか出ない。変だな。と。

でも、今ではC++Builder 2007と同じようにたくさんの候補が表示されます。

いつからちゃんと表示されるようになったのかは不明です。
しばらく使っていると良くなるのかな。

2008年10月18日

C++0x explicit 変換演算子

C++0xの新機能の一つ「explicit 変換演算子」。
暗黙の型変換を禁止します。

class TFoo
{
public:
  TFoo(int i) {};
};

class TBar
{
public:
  explicit TBar(int i) {}; //暗黙の型変換を禁止
};

int _tmain(int argc, _TCHAR* argv[])
{
  TFoo foo = 1;
  TBar bar = 1; //ここでコンパイルエラー

エラーメッセージ

[BCC32 エラー] File1.cpp(25): E2034 'int' 型は 'TBar' 型に変換できない

C++Builder 2009のTIdHTTP::Getの文字コードの処理を検証した

C++Builder 2009のTIdHTTP::Get(URL)はUnicodeStringを返すようになりました。

もしかして、文字コードを自動認識してくれるかも。
と期待して、動作を検証してみたところ、

//UTF-8のページを読み込む
Memo1->Lines->Text = IdHTTP1->Get("http://www.yahoo.co.jp/");

しっかりと、文字化けしました。

自分で文字コードを設定しなければならないようです。

//UTF-8のページを読み込む
std::unique_ptr<TMemoryStream> ms(new TMemoryStream());
IdHTTP1->Get("http://www.yahoo.co.jp/", ms.get());
ms->Position = 0;
Memo1->Lines->LoadFromStream(ms.get(), TEncoding::UTF8);

文字列で取得する場合。

// UTF-8のページを読み込む
std::unique_ptr<TMemoryStream> ms(new TMemoryStream());
IdHTTP1->Get("http://www.yahoo.co.jp/", ms.get());
UTF8String s = reinterpret_cast<char*>(ms->Memory);
//文字コードがShift_JISの場合
//AnsiStringT<932> s = reinterpret_cast<char*>(ms->Memory);
//文字コードがEUC_JPの場合
//AnsiStringT<20932> s = reinterpret_cast<char*>(ms->Memory);

UnicodeStringになって、面倒になったような気がします。

2008年11月06日

C++Builder好きの秘密基地さんで「Xerces3.0.0をC++Builder 2009でビルドする方法」が公開されていました。

C++Builder好きの秘密基地さんで「Xerces3.0.0をC++Builder 2009でビルドする方法」が公開されていました。

定番のXMLライブラリXercesがC++Builderで利用できるのは嬉しい話です。

はconst wchar_t*でやりとりするみたいなので、そのままUnicodeStringが使えそう。でも、何か嫌な予感が。

とありますが、どうなのでしょう。

時間があれば試してみたいところです。

Delphi and C++Builder 2009 Update 1

Delphi and C++Builder 2009 Update 1がリリースされました。

インストールは自動アップデート機構で、簡単にできます。
C++Builder 2007から用意された「自動アップデート機構」は便利ですね。
隠れたヒット作だと思います。

このアップデートの実行には20分、あるいはそれ以上の時間がかかります。

とあります。
アップデートするときは、ご注意下さい。

このアップデートでは、約50件の問題が修正されています。

修正されたバグの一覧は、Delphi 2009 および C++Builder 2009 Update 1 バグフィックスリストで確認できます。

C++Builder 2009のofstreamの問題とその回避方法

C++Builder 2009のofstreamの問題とその回避方法が「NRTTKR Blog日記のお部屋」で掲載されていました。

fstreamにファイル名をUnicodeStringで渡すと問題が発生する模様。

ファイル名をAnsiStringに代入してからなら問題は発生しないそうです。

UnicodeString周りの挙動は、不安がつきまといますね。

2008年11月11日

Delphi/C++Builder 2009 Japanese Hotfix 1

Delphi/C++Builder 2009 Japanese Hotfix 1がリリースされました。

以前フォーラムでご指摘いただきました日本語版固有のオプション設定に関する問題を修正するホットフィックスをリリースしました。

このような日本語固有の問題に対するホットフィックスの公開は、日本語環境に対するサポートが強化されているような印象を受け、嬉しく思います。

このホットフィックスで差し替えるファイル「coreide120.jp」と「delphicompro120.jp」は、「C:\Program Files\CodeGear\RAD Studio\6.0\bin」にあります。

2008年12月01日

RAD Studio 2007用とRAD Studio 2009用のIDE Fix Pack 2.1が公開されました。

RAD Studio 2007用とRAD Studio 2009用のIDE Fix Pack 2.1が公開されました。

開発者のAndreas Hausladenさんのブログ

2008年12月10日

第11回 エンバカデロ・デベロッパーキャンプ ? 資料ダウンロード

2008年12月3日、盛況のうちに終了した第11回エンバカデロ・デベロッパーキャンプのプレゼンテーション資料をダウンロードいただけます。

第11回 エンバカデロ・デベロッパーキャンプ ? 資料ダウンロード

第11回 エンバカデロ・デベロッパーキャンプの資料がダウンロードできるようになりました。

参加者の方々のブログを読んでいると面白そうで、参加できなかったことが残念です。

この資料を読んで、勉強します。

TThreadのSynchronizeとQueueについて

第11回 エンバカデロ・デベロッパーキャンプの資料にある「Delphiでのマルチスレッドプログラミング」を読んで。

TThreadの排他制御にSynchronizeの他にQueueという機能がDelphi2005から追加されたそうです。

これは知りませんでしたので、早速試してみました。

まずはSynchronizeから。

//---------------------------------------------------------------------------
void __fastcall TSampleThread::Execute()
{
  FMsg = "Start";
  Synchronize(&WriteMsg);

  FMsg = "End";
  Synchronize(&WriteMsg);
}
//---------------------------------------------------------------------------
void __fastcall TSampleThread::WriteMsg()
{
  Sleep(1000);
  Form1->Memo1->Lines->Add(FMsg);
}

Synchronizeは並行動作ではないため、実行が終わるまでワーカースレッドは待たされます。

実行結果は次のようになります。

Start
End

次に新機能のQueueです。

//---------------------------------------------------------------------------
void __fastcall TSampleThread::Execute()
{
  FMsg = "Start";
  Queue(&WriteMsg);

  FMsg = "End";
  Queue(&WriteMsg);
}
//---------------------------------------------------------------------------
void __fastcall TSampleThread::WriteMsg()
{
  Sleep(1000);
  Form1->Memo1->Lines->Add(FMsg);
}

Queueでは、メインスレッド側の実行は非同期で並行して行われます。

実行結果は次のようになります。

End
End

WriteMsg()の中でSleep(1000)している間に、Execute()でFMsgが"End"に更新されています。

SynchronizeとQueueを上手に使い分けることで、より効率の良いプログラムができそうです。

DataSnap 2009について

第11回 エンバカデロ・デベロッパーキャンプの資料にある「単層から多層アーキテクチャへの遷移」を読んで。

Delphi2009に搭載されたDataSnap 2009は、すごく簡単で便利そう。
非常に強力なコンポーネントのようです。

使ってみたいとは思いますが、DataSnap2009が搭載されているのはEnterprise版かArchitect版で、Professional版では搭載されていませんよね。残念。

C++Builder版では制限があるようです。
C++BuilderにおけるDataSnap 2009のサポートについて

コンポーネント自作テク ニック

第11回 エンバカデロ・デベロッパーキャンプの資料にある「開発効率を飛躍的に高めるコンポーネント自作テク ニック」 を読んで。

すばらしい資料ですね。
自作コンポーネントの作成方法がよくまとまっています。
あまり見かけない情報ですから、大変貴重な資料だと思います。

Delphi/C++Builder オープンソースコンポーネント実践活用法

第11回 エンバカデロ・デベロッパーキャンプの資料にある 「Delphi/C++Builder オープンソースコンポーネント実践活用法」 を読んで。

これもすばらしい。
大変有益な情報です。
ありがとうございます。

紹介されているDelphi/C++Builder用フリーコンポーネント。
時間があったら試してみたいと思います。

JVCLのコンポーネント紹介も助かります。
面白そうなコンポーネントがたくさんありますね。
JCL・JVCLはコンポーネントがたくさんありすぎて、使用を諦めていました。
もう一度挑戦したいと思いました。
JCLのインストール方法で、C++Builderでコンパイルエラーが発生した場合の対処方法についての記載があるのが嬉しいです。

Delphi 2009への既存コード移行のポイント

第11回 エンバカデロ・デベロッパーキャンプの資料にある 「Delphi 2009ではじめるUnicodeアプリケーション - 既存コード移行のポイント」 を読みました。

Delphi 1からDelphi 2007までのバージョンから、Delphi 2009へコードを移行するためのポイントがまとめられています。
各バージョンでの移行作業がわかるすばらしい資料です。

移行しなければならないプログラムがたくさんありますので、大変助かります。
この資料を参考にして、移行作業を行います。

Delphi/C++Builder 2009のUnicodeに関する情報も充実してきました。
そろそろ本格的に2009に乗り換える時期ですね。

2008年12月12日

C++Builder2009におけるStringの変更による既存コードの修正

C++Builder2007まではコンパイルできた次のコードが、C++Builder2009ではコンパイルエラーになります。

String dir = "C:\\日本語.txt";
FILE* f = fopen(dir.c_str(), "rt");

[BCC32 エラー] File1.cpp(14): E2034 'wchar_t *' 型は 'const char *' 型に変換できない
[BCC32 エラー] File1.cpp(14): E2342 パラメータ '__path' は const char * 型として定義されているので wchar_t * は渡せない

StringがAnsiStringからUnicodeStringに変わったため、String#c_str()の返す型がchar*からwchar_t*に変わりました。

fopenには引数にchar*を受け取るfopen(char*)はありますが、wchar_t*を受け取るfopen(wchar_t*)は存在せず、C++Builder2009ではコンパイルエラーになります。

C++Builder2009でも一度AnsiStringに変換するか、_wfopenを使えば、コンパイル可能です。

# AnsiStringに変換
FILE* f = fopen(AnsiString(dir).c_str(), "rt");

# _wfopenを使用
FILE* f = _wfopen(dir.c_str(), L"rt");

AnsiStringに変換した方のコードはfopen(char*)を使用しますが、

FILE* fp = fopen("日本語.txt","rt");
あるいは
std::ifstream file("日本語.txt");
これ、現在のC/C++では正しく 日本語.txt が
オープンできる保証はどこにもないんです。少なくとも言語仕様としては。

東方算程譚

なのだそうです。
コメント欄も勉強になります。
今まで意識したことがありませんでしたが、微妙な問題があるのですね。

C++0xでは、UTF-8, UTF-16, UTF-32の三つのUnicode符号化方式がサポートされる。

C++0x - Wikipedi

C++0xによって、このあたりがどのように変わるのでしょう。

2008年12月16日

Delphi 2009 / C++Builder 2009 Update 2

Delphi 2009 と C++Builder 2009 の Update 2 がリリースされました。

このアップデートに含まれるのは、データベース関連の修正だけです。

リリースノート: Delphi 2009 および C++Builder 2009 Update 2

今回のアップデートはデータベース関連の修正だけとのこと。
こまめに素早く修正がリリースされるのは嬉しいです。
多くの国の言語に対応する必要があったりと、リリース作業は大変だと思いますが、頑張って下さい。

2008年12月20日

Delphi / C++Builder 2009 Help Update 1

Delphi 2009 および C++Builder 2009 Help Update 1 には、ヘルプ システムのバグ修正のほか、改良がいくつか施されています。

リリースノート: Delphi 2009 および C++Builder 2009 Help Update 1

ということで、Delphi / C++Builder 2009 のHelp Update 1がリリースされました。

2008年12月22日

C++のクラス定義を復習する

C++のクラス定義を復習する。

クラス定義の基本形。

class Foo
{
};

内部クラス。クラス定義の中に他のクラスを定義できる。
Javaと違い、外部クラスとの関連はない。

class Outer
{
public:
  class Inner
  {
  public:
    void say()
    {
      std::puts("Hello");
    }
  }:
};

Outer::Inner c;
c.say();

内部クラスによく似たもので、無名クラス。
C言語の無名構造体のC++版。

class Outer
{
public:
  class
  {
  public:
    void say()
    {
      std::puts("Hello");
    };
  } inner;
};

Outer c;
c.inner.say();

クラスは関数内でも定義できる。

int main(int argc, char* argv[])
{
  class Outer
  {
  };
  Outer o;
  return 0;
}

おなじみテンプレートクラス。

template<class T>
class Foo
{
public:
  T data;
};

Foo<int> foo;
foo.data = 1; //dataはint型。

テンプレートには、型パラメータの他に、非型パラメータも定義できる。
非型パラメータはint、short、charなどの整数値か列挙型が使える。

template<class T, int size>
class Foo
{
  T data[size];
};

Foo<int, 10> foo;

内部クラスにもテンプレートを使用できる。

class Outer
{
public:
  template<class T>
  class Inner
  {
  public:
    T data;
  };
};

Outer::Inner<int> c;
c.data = 1; //dataはint型

外部クラスのテンプレートは、内部クラスにも有効。

template<class T>
class Outer
{
public:
  class Inner
  {
  public:
    T data;
  };
};

Outer<int>::Inner c;
c.data = 1; //dataはint型

これを使うと、コンパイル時に内部クラスを一括して設定することができる。

template<class T>
class Outer
{
public:
  class Inner1
  {
    T data;
  };
  class Inner2
  {
    T data;
  };
  class Inner3
  {
    T data;
  };
};

パラメータ化継承。上位クラスをパラメータにすることができる。

template<class SuperClass>
class Foo : public SuperClass
{
};

パラメータ化継承を使うと、柔軟かつ強力なクラス設計ができる。

template<class SuperClass>
class Employee : public SuperClass
{
};

template<class SuperClass>
class Customer : public SuperClass
{
};

template<class SuperClass>
class Shareholder : public SuperClass
{
};

class Person
{
};

Employee<Person> a;
Shareholder<Employee<Person> > b;
Shareholder<Employee<Customer<Person> > > c;

クラスの設計情報をパラメータ化することもできる。

struct ConfigA
{
  typedef int Price;
};

struct ConfigB
{
  typedef double Price;
};

template<class Config>
class Product : public Config
{
public:
  typedef typename Config::Price Price;
  Price price;
};

Product<ConfigA> a;
a.price = 1; //priceはint型
Product<ConfigB> b;
b.price = 2.3; //priceはdouble型

2008年12月24日

C++のテンプレートはパラメータとして関数をとることができる

C++のテンプレートはパラメータとして関数をとることができる。

/**
 * 3倍する
 */
int treble(int x)
{
  return x * 3;
}
/**
 * 4倍する
 */
int quadruplicate(int x)
{
  return x * 4;
}
/**
 * 引数を計算式で計算する
 * @param function int型の引数をとりint型を返す関数
 * @param a 計算する値
 * @return 計算結果
 */
template<int function(int)>
int calc(int a)
{
  return function(a);
}

//2を3倍する
int num1 = calc<treble>(2);
//3を4倍する
int num2 = calc<quadruplicate>(3);

パラメータ継承を使ったテンプレートメソッド

まずパラメータ継承を使わないテンプレートメソッド。
Wikipediaの例を参考にした。
StringListerで定義されている抽象メソッドformatItem()が、display()内で使われている。

class StringLister
{
public:
  virtual std::string formatItem(std::string item) = 0;
  void display(std::string item)
  {
    std::cout << formatItem(item) << std::endl;
  }
};
class PlainTextStringLister : public StringLister
{
public:
  virtual std::string formatItem(std::string item)
  {
    return "- " + item;
  }
};
class HtmlStringLister : public StringLister
{
public:
  virtual std::string formatItem(std::string item)
  {
    return "<li>" + item + "</li>";
  }
};

PlainTextStringLister().display("Foo"); //=> - Foo
HtmlStringLister().display("Foo");      //=> <li>Foo</li>

パラメータ継承を使ったテンプレートメソッド。
ConcreteClassのメソッドformatItem()が、display()内で使われている。

template<class ConcreteClass>
class StringLister : public ConcreteClass
{
public:
  void display(std::string item)
  {
    std::cout << ConcreteClass::formatItem(item) << std::endl;
  }
};
class PlainTextStringLister
{
protected:
  static std::string formatItem(std::string item)
  {
    return "- " + item;
  }
};
class HtmlStringLister
{
protected:
  static std::string formatItem(std::string item)
  {
    return "<li>" + item + "</li>";
  }
};

StringLister<PlainTextStringLister>().display("Foo"); //=> - Foo
StringLister<HtmlStringLister>().display("Foo");      //=> <li>Foo</li>

2008年12月26日

C++Builder2009におけるWin32APIのWideString版を呼び出す方法

C++Builder2009では文字列がUnicode文字列に変更された。
この変更に伴い、Win32APIの呼び出しも変更された。
ヘルプには次のように記載されている。

API はデフォルトで WideString("W")版を呼び出します。

ms-help://embarcadero.rs2009/devcommon/unicodeinide_xml.html

ところが実際は、「プロジェクトオプション」の「ディレクトリと条件定義」で「_TCHARのマップ先」を「char」から「wchar_t」に変更する必要がある。
ヘルプの「_TCHAR のマップ先」の項目には次のように記載されている。

[wchar_t] を選択すると、以下のように動作します。

  • UNICODE と _UNICODE が定義されます。
  • リンカはワイド バージョンのライブラリを使用します。
  • 標準ライブラリと Windows API の関数は、ワイド定義に設定されます。

ms-help://embarcadero.rs2009/devcommon/dirconditionals_xml.html

「_TCHARのマップ先」の初期値は「char」であるため、WideString版のAPIは呼び出されない。
初期値が「wchar_t」なら、ヘルプの記載も正しいのだが。

2008年12月29日

AnsiStringTのコンストラクタの挙動を調べる

AnsiStringTのコンストラクタにchar*とwchar_t*を引数とした場合の挙動のテスト

const char* c1 = "テスト";
AnsiStringT<20932> euc1(c1);
ShowCode(euc1);

const wchar_t* c2 = L"テスト";
AnsiStringT<20932> euc2(c2);
ShowCode(euc2);

実行結果

CodePage = 20932
83 65 83 58 83 67
CodePage = 20932
a5 c6 a5 b9 a5 c8

char*の場合は変換されず、コードページと文字が一致していない。
コードページはEUC-JPだが、Shift_JISのバイト列のままである。

wchar_t*を引数とした場合は、EUC_JPに変換されている。

代入の場合も同じ動作となる。

今回の検証に使用した関数

void ShowCode(const RawByteString& Str)
{
  printf("CodePage = %d\n", StringCodePage(Str));
  for (int i = 1; i <= Str.Length(); i++)
  {
    printf("%x ", static_cast<unsigned char>(Str[i]));
  }
  printf("\n");
}

AnsiStringTの代入の挙動を調べる

AnsiStringTからコードページの異なるAnsiStringTへの代入のテスト

AnsiStringT<932> sjis = L"テスト";
AnsiStringT<20932> euc = sjis;
ShowCode(euc);

実行結果

CodePage = 20932
a5 c6 a5 b9 a5 c8

コードページにしたがって変換されている。

UnicodeStringを間に挟んだ場合のテスト

AnsiStringT<932> sjis = L"テスト";
UnicodeString uni = sjis;
AnsiStringT<20932> euc = uni;
ShowCode(euc);

実行結果

CodePage = 20932
a5 c6 a5 b9 a5 c8

Shift_JISからEUC-JPに変換されている。

結論としては、
AnsiStringTからAnsiStringTへの代入、
AnsiStringTからUnicodeStringへの代入、
UnicodeStringからAnsiStringTへの代入、
は、いずれも自動的に変換されるようだ。

今回の検証に使用した関数

void ShowCode(const RawByteString& Str)
{
  printf("CodePage = %d\n", StringCodePage(Str));
  for (int i = 1; i <= Str.Length(); i++)
  {
    printf("%x ", static_cast<unsigned char>(Str[i]));
  }
  printf("\n");
}

RawByteStringの動作のテスト

RawByteStringの動作のテスト

RawByteStringのコンストラクタのテスト

char*を受け取るコンストラクタ

RawByteString raw("テスト");
ShowCode(raw);

実行結果

CodePage = 65535
83 65 83 58 83 67

バイト列をそのまま変換しないで保持している。

wchar_t*を受け取るコンストラクタ

RawByteString raw(L"テスト");
ShowCode(raw);

実行結果

CodePage = 932

値が空になっている。
RawByteString(wchar_t*)は引数が無効になるので注意が必要だ。

代入のテスト

const wchar_t* c = L"テスト";
AnsiStringT<20932> euc(c);
RawByteString raw = euc;
ShowCode(raw);

実行結果

CodePage = 20932
a5 c6 a5 b9 a5 c8

コードページも文字のバイト列も変換しないで、そのまま保持している。

UnicodeString uni = "テスト";
RawByteString raw = uni;
ShowCode(raw);

実行結果

CodePage = 932

UnicodeStringをRawByteStringに代入すると空になる。
wchar_t*を引数にとるコンストラクタと同じ挙動だ。

今回の検証に使用した関数

void ShowCode(const RawByteString& Str)
{
  printf("CodePage = %d\n", StringCodePage(Str));
  for (int i = 1; i <= Str.Length(); i++)
  {
    printf("%x ", static_cast<unsigned char>(Str[i]));
  }
  printf("\n");
}

2009年01月09日

C++Builder2007とC++Builder2009の__finallyの動作を検証する

気になる記事を見つけたので、動作を検証しました。

catch、finallyブロックをreturnで抜ける時、それより外にあるfinallyブロックは実行されません。__finallyブロックであっても必ず実行されるわけではないので、注意が必要です。

__finallyブロックを必ず実行する

__finallyブロックが必ずしも実行されないコード1:

try {
  try {
    throw "error";
  } catch(...) {
    return 0;
  }
} __finally {
    std::puts("finally");
}

C++Builder2007、C++Builder2009ともに、__finallyブロックは実行されました。
期待通りの動作です。

__finallyブロックが必ずしも実行されないコード2:

try {
  try {
  } __finally {
    return 0;
  }
} __finally {
    std::puts("finally");
}

C++Builder2007では、__finallyブロックは実行されました。
期待通りの動作です。

C++Builder2009では、次のコンパイルエラーが発生しました。

[BCC32 エラー] File1.cpp(16): E2551 リターン文は _finally ブロックでは使用できない

C++Builder2009で仕様が変更されたのでしょうか。
C++ではRAIIが使えますから、__finallyはあまり使用されていないのかもしれません。

2009年01月14日

C++Builder2009とDelphi2009のUnicodeStringの挙動の違い

C++Builder2009では、UnicodeStringからRawByteStringへ、
または反対にRawByteStringからUnicodeStringへ代入をした場合、
代入された値はNULLになります。

RawByteString raw = "123";
UnicodeString uni = raw; //uniはNULL

UnicodeString uni = "123";
RawByteString raw = uni; //rawはNULL

一方、Delphi2009では、代入された値が入ります。
# 警告が出ます。

var
  raw: RawByteString;
  uni: UnicodeString;
begin
  raw := '123';
  uni := raw; //uniは123

  uni := '123';
  raw := uni; //rawは123

C++Builder利用者がDelphi向けの文書を読むときは、 この違いに気をつける必要があります。

2009年01月19日

UnicodeString#LastChar()がILINK32エラー

C++Builder2009で以下のコードが、リンクエラーになります。

UnicodeString str = L"ほげほげ";
str.LastChar();

[ILINK32 エラー] Error: 未解決の外部参照

QC#68076に登録されていました。

追記

LastChar()の代替策として、operator[]とLength()を使います。

str[str.Length()];

2009年5月28日追記
Update3で修正されて、UnicodeString::LastCharが使えるようになりました。

2009年01月20日

C++Builder2009のスレッドの基本的な使い方のまとめ

C++Builder2009のスレッドの基本的な使い方のまとめ。

スレッドクラスを作成する

  1. C++Builderのメニューから「ファイル」→「新規作成」→「その他」を選択します。
  2. 項目カテゴリから「C++Builderファイル」を選択します。
  3. 「スレッドオブジェクト」を選択し「OK」ボタンを押します。
  4. クラス名を入力し「OK」ボタンを押します。

スレッドクラスのひな形が作成されます。

スレッドを実行する

Execute()に実行する処理を記述します。

void __fastcall TMyThread::Execute()
{
  for (int i = 0; i < 1000; i++) 
  {
    std::cout << "Nice!";
  }
}

スレッドクラスのインスタンスを作成し、起動します。

int _tmain(int argc, _TCHAR* argv[])
{
  //"Nice!"を出力するTMyThreadを生成して起動する
  TMyThread* thread = new TMyThread(false);
  //メインスレッドでは"Good!"を出力する
  for (int i = 0; i < 1000; i++)
  {
    std::cout << "Good!";
  }
  Sleep(5000);
  return 0;
}

TThreadクラスのコンストラクタの引数CreateSuspendedにfalseを指定すると、 インスタンス作成後、Execute()メソッドがすぐに呼び出されます。

スレッドを自動的に実行するのではなく明示的に実行する場合は、 コンストラクタの引数CreateSuspendedにtrueを指定し、 TThread.Resumeメソッドで実行します。

int _tmain(int argc, _TCHAR* argv[])
{
  #インスタンスの作成
  TMyThread* thread = new TMyThread(true);
  #スレッドの実行
  thread->Resume();
  for (int i = 0; i < 1000; i++)
  {
    std::cout << "Good!";
  }
  Sleep(5000);
  return 0;
}

メインスレッドが終了すると、すべてのスレッドが終了します。

スレッドの一時停止

TThread.Suspendメソッドを使うと、実行中のスレッドを一時停止することができます。

一時停止したスレッドを再実行するには、TThread.Resumeメソッドで呼び出します。

int _tmain(int argc, _TCHAR* argv[])
{
  //スレッドの実行
  TMyThread* thread = new TMyThread(false);
  //スレッドの一時停止
  thread->Suspend();
  for (int i = 0; i < 1000; i++)
  {
    std::cout << "Good!";
  }
  //スレッドの再実行
  thread->Resume();
  Sleep(5000);
  return 0;
}

スレッドの排他制御

Synchronizeメソッド

Synchronizeメソッドは、制御をメインスレッド側に移して関数を実行します。
関数の処理が終わるまでワーカースレッドは待たされます。並行動作ではありません。
異なるスレッドが所有するVCLオブジェクトのメソッドやプロパティにアクセスするときは、 Synchronizeメソッドを使用しなければなりません。

void __fastcall TSampleThread::Execute()
{
  FMsg = "Start";
  Synchronize(&WriteMsg);

  FMsg = "End";
  Synchronize(&WriteMsg);
}
void __fastcall TSampleThread::WriteMsg()
{
  Sleep(1000);
  Form1->Memo1->Lines->Add(FMsg);
}

Synchronizeは並行動作ではないため、実行が終わるまでワーカースレッドは待たされます。

実行結果は次のようになります。

Start
End

Queueメソッド

Queueメソッドは、Synchronizeメソッドと同様、制御をメインスレッド側に移して関数を実行します。
ただし、メインスレッド側の実行は非同期で並行して行われるます。
ワーカースレッドは、関数の処理が終わるのを待たされることはありません。

//---------------------------------------------------------------------------
void __fastcall TSampleThread::Execute()
{
  FMsg = "Start";
  Queue(&WriteMsg);

  FMsg = "End";
  Queue(&WriteMsg);
}
//---------------------------------------------------------------------------
void __fastcall TSampleThread::WriteMsg()
{
  Sleep(1000);
  Form1->Memo1->Lines->Add(FMsg);
}

Queueでは、メインスレッド側の実行は非同期で並行して行われます。

実行結果は次のようになります。

End
End

WriteMsg()の中でSleep(1000)している間に、Execute()でFMsgが"End"に更新されています。

クリティカルセクション(TCriticalSectionクラス)

クリティカルセクションは、複数のスレッドが同時に一つのリソースにアクセスしないように排他制御を行います。

銀行口座を例にした次のサンプルコードでは、 預金と引き出しが同時に行われないように クリティカルセクションで排他制御を行います。

class TBank
{
public:
  TBank(int Money)
    : FMoney(Money), FCriticalSection(new TCriticalSection) {};
  ~TBank() { delete FCriticalSection; };
  //預金する
  void Deposit(int Money) 
  {
    FCriticalSection->Acquire(); //ほかのスレッドをロックアウトする
    try
    {
      FMoney += Money;
    }
    __finally
    {
      FCriticalSection->Release();
    }
  };
  //引き出す
  bool Withdraw(int Money) 
  {
    FCriticalSection->Acquire(); //ほかのスレッドをロックアウトする
    bool result = false;
    try
    {
      if (FMoney > Money) {
        FMoney -= Money;
        result = true; //引き出せた
      } else {
        result = false; //残高不足
      }
    }
    __finally
    {
      FCriticalSection->Release();
    }
    return result;
  };
private:
  int FMoney;
  TCriticalSection* FCriticalSection;
};

セマフォ(TSemaphoreクラス)

クリティカルセクション(TCriticalSectionクラス)は、ある領域を「たった1つのスレッド」だけが実行できるように制限します。 これに対し、セマフォは、ある領域を「最大でN個のスレッド」まで実行できるように制限します。

次の実行例では、10個のスレッドが入れ替わりながらリソースを使用します。 同時に使用されているリソースは最大3個に限定されます。

std::unique_ptr<TCriticalSection> CriticalSection(new TCriticalSection);

//数の制限があるリソース
class TBoundedResource
{
public:
  //コンストラクタ(Permitsはリソースの個数)
  TBoundedResource(int Permits)
    : FSemaphore(new TSemaphore(NULL, Permits, Permits, "", false)), FUsed(0) {};
  //リソースを使用する
  void Use()
  {
    FSemaphore->Acquire();
    try
    {
      DoUse();
    }
    __finally
    {
      FSemaphore->Release();
    }
  };
private:
  TSemaphore* FSemaphore;
  int FUsed;
  //リソースを実際に使用する
  void DoUse()
  {
    PrintBegin();
    Sleep(random(1000));
    PrintEnd();
  }
  void PrintBegin()
  {
    CriticalSection->Acquire();
    std::cout << "BEGIN: used = " << ++FUsed << std::endl;
    CriticalSection->Release();
  }
  void PrintEnd()
  {
    CriticalSection->Acquire();
    std::cout << "END: used = " << FUsed-- << std::endl;
    CriticalSection->Release();
  }
};

//リソースを使用するスレッド
class TUserThread : public TThread
{
private:
  TBoundedResource* FResource;
protected:
  void __fastcall Execute()
  {
    while (true)
    {
      FResource->Use();
      Sleep(random(1000));
    }
  }
public:
  __fastcall TUserThread(TBoundedResource* Resource)
    : TThread(false), FResource(Resource) {};
};

int _tmain(int argc, _TCHAR* argv[])
{
  //3個のリソースを用意する
  TBoundedResource* resource = new TBoundedResource(3);

  //10個のスレッドが利用する
  for (int i = 0; i < 10; i++)
  {
    new TUserThread(resource.get());
  }

  Sleep(10000);
  return 0;
}

スレッドの実行が終了するまで待つ

TThread.WaitFor()は、処理が終了するまで戻りません。

TMyThread* thread = new TMyThread(false);
//スレッドが終了するまで待つ
thread->WaitFor();
//スレッドが終了した
std::cout << "END";

実行中のスレッドを終了する

TThread.Terminate メソッドはスレッドのTerminatedプロパティをtrueにします。
スレッドはTerminatedプロパティがtrueになったら終了するように実装する必要があります。

void __fastcall TMyThread::Execute()
{
  //TThread.Terminateメソッドが呼ばれるまで出力を繰り返す
  while (!Terminated)
  {
    std::cout << "Nice!";
    Sleep(100);
  };
}

int _tmain(int argc, _TCHAR* argv[])
{
  TMyThread* thread = new TMyThread(false);
  //5秒待機
  Sleep(5000);
  //スレッドを終了する
  thread->Terminate();
  return 0;
}

イベントの発生を通知する

TEventを使用するとスレッドからイベントを通知することができます。

class TMyThread : public TThread
{
private:
  TEvent* FEvent;
protected:
  void __fastcall Execute()
  {
    for (int i = 0; i < 1000; ++i)
    {
      std::cout << "Nice!";
    };
    FEvent->SetEvent();
  }
public:
  __fastcall TMyThread(bool CreateSuspended, TEvent* Event)
    : TThread(CreateSuspended), FEvent(Event) {};
};

int _tmain(int argc, _TCHAR* argv[])
{
  TEvent* event = new TEvent(false);
  TMyThread* thread = new TMyThread(false, event);
  //10秒間スレッドからのイベントの通知を待つ
  if (event->WaitFor(10000) == wrSignaled)
  {
    //スレッドからの通知があった
    std::cout << "END";
  }
  else
  {
    //スレッドからの通知がない、または時間切れ
    std::cout << "ERROR";
  }
  return 0;
}

複数のスレッドが同じTEventのインスタンスを使いTEvent.WaitForで待機しているとき、 TEvent.SetEvent()によって実行されるスレッドは1つだけです。
Javaのwait()とnotify()に対応します。notifyAll()ではありません。

マルチスレッドの学習には『増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』がわかりやすくておすすめ。
解説にJavaを使用していますが、マルチスレッドの知識はJavaに限定されません。

2009年01月21日

Single Threaded Executionパターン

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』にある 「Single Threaded Executionパターン」をC++ Builder 2009で実装してみました。

C++BuilderにはJavaのsynchronizedがないため、 クリティカルセクション(TCriticalSectionクラス)と Before/Afterパターンを使って実装しました。

プログラムの意味は『増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』をご覧下さい。

//スレッドセーフな出力
std::unique_ptr<TCriticalSection> CriticalSection(new TCriticalSection);
void Print(const std::string& S)
{
  CriticalSection->Acquire();
  std::cout << S << std::endl;
  CriticalSection->Release();
}
//スレッドセーフなTGateクラス
class TGate
{
public:
  TGate() : FCounter(0), FName("Nobody"), FAddress("Nowhere"), FCriticalSection(new TCriticalSection) {};
  void Pass(const std::string& Name, const std::string& Address)
  {
    FCriticalSection->Acquire();
    try
    {
    this->FCounter++;
      this->FName = Name;
      this->FAddress = Address;
      Check();
    }
    __finally
    {
      FCriticalSection->Release();
    }
  };
private:
  int FCounter;
  std::string FName;
  std::string FAddress;
  std::unique_ptr<TCriticalSection> FCriticalSection;
  void Check()
  {
    if (FName[0] != FAddress[0])
    {
      boost::format formater("***** BROKEN ***** No.%d : %s, %s");
      formater % FCounter % FName % FAddress;
      Print(formater.str());
    }
  };
};
//ひたすら門を通り続ける人を表すクラス
class TUserThread : public TThread
{
public:
  __fastcall TUserThread(TGate* Gate, std::string Name, std::string Address)
    : TThread(false), FGate(Gate), FName(Name), FAddress(Address) {};
protected:
  void __fastcall Execute()
  {
    Print(FName + " BEGIN");
    while (true)
    {
      FGate->Pass(FName, FAddress);
    }
  }
private:
  TGate* FGate;
  std::string FName;
  std::string FAddress;
};

int _tmain(int argc, _TCHAR* argv[])
{
  TGate* gate = new TGate();
  new TUserThread(gate, "Alice", "Alaska");
  new TUserThread(gate, "Bobby", "Brazil");
  new TUserThread(gate, "Chris", "Canada");

  Sleep(INFINITE);
  return 0;
}

2009年01月22日

Guarded Suspensionパターン

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』にある 「Guarded Suspensionパターン」をC++ Builder 2009で実装してみました。

プログラムの意味は『増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』をご覧下さい。

C++BuilderにはJavaのsynchronizedがないため、 クリティカルセクション(TCriticalSectionクラス)を使い、 TLockクラスで実装しました。

JavaのwaitメソッドとnotifyAllメソッドの代わりに、 TEventクラスを使って実装してみました。
(ちょっと怪しい)

//スレッドセーフな出力
std::unique_ptr<TCriticalSection> CriticalSection(new TCriticalSection);
void Print(const std::string& S)
{
  CriticalSection->Acquire();
  std::cout << S << std::endl;
  CriticalSection->Release();
}
//Javaのsynchronizedを実現するためのRAII
class TLock
{
public:
  TLock(TCriticalSection* CriticalSection): FCriticalSection(CriticalSection)
  {
    FCriticalSection->Acquire();
  };
  ~TLock()
  {
    FCriticalSection->Release();
  }
private:
  TCriticalSection* FCriticalSection;
};
//リクエストを表すクラス
class TRequest
{
public:
  TRequest(std::string Name) : FName(Name) {};
  std::string GetName() const { return FName; };
  std::string ToString() const { return "[ Request " + FName + " ]"; };
private:
  const std::string FName;
};
//リクエストを保持しておくクラス
class TRequestQueue
{
public:
  TRequestQueue(TEvent* Event)
    : FEvent(Event), FCriticalSection(new TCriticalSection) {};
  //もっとも古いリクエストを返す。
  //リクエストがなければ、他のスレッドがPutRequestするまで待つ。
  TRequest GetRequest()
  {
    TLock(FCriticalSection.get());
    while (FQueue.empty())
    {
      FEvent->WaitFor(1000000);
    }
    TRequest request = FQueue.front();
    FQueue.pop();
    return request;
  };
  //リクエストを追加する
  void PutRequest(const TRequest& Request)
  {
    TLock(FCriticalSection.get());
    FQueue.push(Request);
    FEvent->SetEvent();
  }
private:
  std::unique_ptr<TCriticalSection> FCriticalSection;
  std::queue<TRequest> FQueue;
  TEvent* FEvent;
};
//リクエストを出すクラス
class TClientThread : public TThread
{
public:
  __fastcall TClientThread(TRequestQueue* RequestQueue, const std::string& Name)
    : TThread(false), FRequestQueue(RequestQueue), FName(Name) {};
protected:
  void __fastcall Execute()
  {
    for (int i = 0; i < 100000; i++)
    {
      TRequest request((boost::format("No.%d") % i).str());
      Print(FName + " requests " + request.ToString());
      FRequestQueue->PutRequest(request);
      Sleep(random(10000));
    }
  }
private:
  std::string FName;
  TRequestQueue* FRequestQueue;
};
//リクエストを受け取るクラス
class TServerThread : public TThread
{
public:
  __fastcall TServerThread(TRequestQueue* RequestQueue, const std::string& Name)
    : TThread(false), FRequestQueue(RequestQueue), FName(Name) {};
protected:
  void __fastcall Execute()
  {
    for (int i = 0; i < 100000; i++)
    {
      TRequest request = FRequestQueue->GetRequest();
      Print(FName + " handles " + request.ToString());
    }
  }
private:
  std::string FName;
  TRequestQueue* FRequestQueue;
};


int _tmain(int argc, _TCHAR* argv[])
{
  TEvent* event = new TEvent(NULL, false, false, "", false);
  TRequestQueue* requestQueue = new TRequestQueue(event);
  TThread* aliceThread = new TClientThread(requestQueue, "Alice");
  TThread* bobbyThread = new TServerThread(requestQueue, "Bobby");

  aliceThread->WaitFor();
  bobbyThread->WaitFor();
  return 0;
}

2009年01月23日

Balkingパターン

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』にある 「Balkingパターン」をC++ Builder 2009で実装してみました。

プログラムの意味は『増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』をご覧下さい。

C++BuilderにはJavaのsynchronizedがないため、 クリティカルセクション(TCriticalSectionクラス)を使い、 TLockクラスで実装しました。

//Javaのsynchronizedを実現するためのRAII
class TLock
{
public:
  TLock(TCriticalSection* CriticalSection): FCriticalSection(CriticalSection)
  {
    FCriticalSection->Acquire();
  };
  ~TLock()
  {
    FCriticalSection->Release();
  }
private:
  TCriticalSection* FCriticalSection;
};
//現在のデータを表すクラス
class TData
{
public:
  TData(std::string Filename, std::string Content)
    : FFilename(Filename), FContent(Content), FChanged(true),
      FCriticalSection(new TCriticalSection) {};
  //データの内容を書き換える
  void Change(std::string NewContent)
  {
    TLock(FCriticalSection.get());
    FContent = NewContent;
    FChanged = true;
  };
  //データの内容が変更されていたらファイルに保存する
  void Save(std::string ThreadName)
  {
    TLock(FCriticalSection.get());
    if (!FChanged)
    {
      return;
    }
    DoSave(ThreadName);
    FChanged = false;
  };
private:
  const std::string FFilename; //保存するファイルの名前
  std::string FContent; //データの内容
  bool FChanged; //変更した内容が保存されていないならtrue
  std::unique_ptr<TCriticalSection> FCriticalSection;
  //データの内容を実際にファイルに保存する
  void DoSave(std::string ThreadName)
  {
    boost::format fmt("%s calls DoSave, content = %s");
    std::cout << (fmt % ThreadName % FContent) << std::endl;
    //ファイルに保存する処理は省略
  }
};
//定期的にデータを保存しようとするクラス
class TSaverThread : public TThread
{
public:
  __fastcall TSaverThread(std::string Name, TData* Data)
    : TThread(false), FName(Name), FData(Data) {};
protected:
  void __fastcall Execute()
  {
    while (true)
    {
      FData->Save(FName);
      Sleep(1000);
    }
  }
private:
  const std::string FName;
  TData* FData;
};
//データを変更・保存するクラス
class TChangerThread : public TThread
{
public:
  __fastcall TChangerThread(std::string Name, TData* Data)
    : TThread(false), FName(Name), FData(Data) {};
protected:
  void __fastcall Execute()
  {
    boost::format fmt("No. %d");
    for (int i = 0; true; i++)
    {
      FData->Change((fmt % i).str());
      Sleep(random(1000)); //仕事のつもり
      FData->Save(FName);
    }
  }
private:
  const std::string FName;
  TData* FData;
};

int _tmain(int argc, _TCHAR* argv[])
{
  TData* data = new TData("data.txt", "(empty)");
  new TChangerThread("TChangerThread", data);
  new TSaverThread("TSaverThread", data);

  Sleep(INFINITE);
  return 0;
}

2009年01月24日

JCL 1.104 & JVCL 3.36

Delphi/C++Builder 2009に正式対応した、JCL 1.104 と JVCL 3.36がリリースされました。

JCL 1.104 & JVCL 3.36リリース - C++Builder好きの秘密基地

JCL/JVCLがDelphi/C++Builder 2009に正式対応したそうです。

Producer-Consumerパターン

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』にある 「Producer-Consumerパターン」をC++ Builder 2009で実装してみました。

プログラムの意味は『増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』をご覧下さい。

C++Builder2009にはJavaにおけるsynchronizedやnotifyAll()がないので試行錯誤。

C++Builder2009にはJavaにおけるnotiryAll()がありません。
TTable.Put()とTTable.Take()を同時に実行するのは問題ありませんので、 TTable.Put()とTTable.Take()にそれぞれクリティカルセッションを用い、 それぞれのメソッドが同時に実行されないようにしました。

  //ケーキを置く
  void Put(std::string ThreadName, std::string Cake)
  {
    TLock lock(FPutCriticalSection.get());
    …

  //ケーキをとる
  std::string Take(std::string ThreadName)
  {
    TLock lock(FTakeCriticalSection.get());
    …

JavaにおけるnotifyAll()がないため、TEventによるイベント通知を使用しました。
「ケーキを置くイベント」と「ケーキをとるイベント」を用意しました。

  //ケーキを置く
  void Put(std::string ThreadName, std::string Cake)
  {
    …
    //テーブルにはケーキがこれ以上おけないとき
    while (FBuffer.size() >= FCount)
    {
      FPutEvent->WaitFor(INFINITE); //ケーキをとるイベントを待つ
    }

  //ケーキをとる
  std::string Take(std::string ThreadName)
  {
    …
    //ケーキをとる
    std::string cake = FBuffer.front();
    FBuffer.pop();
    FPutEvent->SetEvent(); //ケーキをとるイベントの通知

全体のコードです。

//スレッドセーフな出力
std::unique_ptr<TCriticalSection> CriticalSection(new TCriticalSection);
void Print(const std::string& S)
{
  CriticalSection->Acquire();
  std::cout << S << std::endl;
  CriticalSection->Release();
}
//Javaのsynchronizedを実現するためのRAII
class TLock
{
public:
  TLock(TCriticalSection* CriticalSection): FCriticalSection(CriticalSection)
  {
    FCriticalSection->Acquire();
  };
  ~TLock()
  {
    FCriticalSection->Release();
  }
private:
  TCriticalSection* FCriticalSection;
};
//テーブルを表すクラス
class TTable
{
public:
  TTable(const int Count)
    : FCount(Count),
      FPutCriticalSection(new TCriticalSection),
      FTakeCriticalSection(new TCriticalSection),
      FPutEvent(new TEvent(NULL, false, false, "", false)),
      FTakeEvent(new TEvent(NULL, false, false, "", false))
  {
  };
  //ケーキを置く
  void Put(std::string ThreadName, std::string Cake)
  {
    FPutCriticalSection->Acquire();
    //テーブルにはケーキがこれ以上おけないとき
    while (FBuffer.size() >= FCount)
    {
      FPutEvent->WaitFor(INFINITE); //ケーキをとるイベントを待つ
    }
    Print((boost::format("%s puts %s") % ThreadName % Cake).str());
    FBuffer.push(Cake);
    FTakeEvent->SetEvent();
    FPutCriticalSection->Release();
  }
  //ケーキをとる
  std::string Take(std::string ThreadName)
  {
    FPutEvent->Acquire();
    //テーブルにはケーキがないとき
    while (FBuffer.empty())
    {
      FTakeEvent->WaitFor(INFINITE); //ケーキを置くイベントを待つ
    }
    std::string cake = FBuffer.front();
    FBuffer.pop();
    FPutEvent->SetEvent();
    Print((boost::format("%s takes %s") % ThreadName % cake).str());
    FPutEvent->Release();
    return cake;
  }
private:
  std::queue<std::string> FBuffer;
  const unsigned int FCount; //テーブルにおけるケーキの数
  std::unique_ptr<TCriticalSection> FPutCriticalSection;
  std::unique_ptr<TCriticalSection> FTakeCriticalSection;
  std::unique_ptr<TEvent> FPutEvent; //ケーキが置いたときのイベント
  std::unique_ptr<TEvent> FTakeEvent; //ケーキをとったときのイベント
};
//ケーキを作ってテーブルに置く
class TMakerThread : public TThread
{
public:
  __fastcall TMakerThread(std::string Name, TTable* Table)
    : TThread(false), FName(Name), FTable(Table) {};
protected:
  void __fastcall Execute()
  {
    boost::format fmt("[ Cake No. %d by %s ]");
    while (true)
    {
      Sleep(random(1000));
      std::string cake = (fmt % NextId() % FName).str();
      FTable->Put(FName, cake);
    }
  }
private:
  const std::string FName;
  TTable* FTable;
  static int NextId()
  {
    static int id = 0;
    return id++;
  }
};
//テーブルに置かれたケーキを食べるクラス
class TEaterThread : public TThread
{
public:
  __fastcall TEaterThread(std::string Name, TTable* Table)
    : TThread(false), FName(Name), FTable(Table) {};
protected:
  void __fastcall Execute()
  {
    while (true)
    {
      std::string cake = FTable->Take(FName);
      Sleep(random(1000));
    }
  }
private:
  const std::string FName;
  TTable* FTable;
};

int _tmain(int argc, _TCHAR* argv[])
{
  TTable* table = new TTable(3); //ケーキを3個までおけるテーブルを作る
  new TMakerThread("TMakerThread-1", table);
  new TMakerThread("TMakerThread-2", table);
  new TMakerThread("TMakerThread-3", table);
  new TEaterThread("TEaterThread-1", table);
  new TEaterThread("TEaterThread-2", table);
  new TEaterThread("TEaterThread-3", table);

  Sleep(INFINITE);
  return 0;
}

2009年01月25日

最大公約数を求めるユークリッドの互除法

最大公約数を求めるユークリッドの互除法は、
紀元前3世紀頃にはギリシャ人のあいだに知られていた。
驚きである。

//最大公約数を求める(ユークリッドの互除法)
//再帰版
int gcd(int m, int n)
{
  if (n == 0) return m;
  return gcd(n, m % n);
}
//非再帰版
int gcd(int m, int n)
{
  while (n > 0)
  {
    int r = m % n;
    m = n;
    n = r;
  }
  return m;
}

int result = gcd(954, 288); //=>18

Read-Write Lockパターン

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』にある 「Read-Write Lockパターン」をC++ Builder 2009で実装してみました。

プログラムの意味は『増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』をご覧下さい。

さて、今回もJavaのnotifyAll()をどのように実装するかが問題でした。
C++Builder2009では、Javaのように待機中のすべてのスレッドをウェイトセットから出すnotifyAll()がありません。
できることはnotify()と同じで、ウェイトセットから出せるスレッドは1つだけです。

ガード条件を考えて、書き込み待ちスレッドがあればそれを、なければ読み込み待ちスレッドを起こすようにしました。
そのため、書き込み待ちスレッドと読み込み待ちスレッドを分けて(WaitReadメソッドとWaitWriteメソッド)管理しています。

//スレッドセーフな出力
std::unique_ptr<TCriticalSection> CriticalSection(new TCriticalSection);
void Print(const std::string& S)
{
  CriticalSection->Acquire();
  std::cout << S << std::endl;
  CriticalSection->Release();
}
//TReadWriteLockクラス
class TReadWriteLock
{
public:
  TReadWriteLock()
       : FReadingReaders(0), FWaitingWriters(0), FWritingWriters(0),
         FPreferWriter(true), FCriticalSection(new TCriticalSection) {};
  void ReadLock(TEvent* Event)
  {
    FCriticalSection->Acquire();
    while (FWritingWriters > 0 || (FPreferWriter && FWaitingWriters > 0))
    {
      WaitRead(Event);
    }
    //実際に読んでいるスレッドの数を1増やす
    FReadingReaders++;
    FCriticalSection->Release();
  };
  void ReadUnlock()
  {
    FCriticalSection->Acquire();
    //実際に読んでいるスレッドの数を1減らす
    FReadingReaders--;
    FPreferWriter = true;
    NotifyAll();
    FCriticalSection->Release();
  };
  void WriteLock(TEvent* Event)
  {
    FCriticalSection->Acquire();
    //書くのを待っているスレッドの数を1増やす
    FWaitingWriters++;
    while (FReadingReaders > 0 || FWritingWriters > 0)
    {
      WaitWrite(Event);
    }
    //書くのを待っているスレッドの数を1減らす
    FWaitingWriters--;
    //実際に書いているスレッドの数を1増やす
    FWritingWriters++;
    FCriticalSection->Release();
  };
  void WriteUnlock()
  {
    FCriticalSection->Acquire();
    //実際に書いているスレッドの数を1減らす
    FWritingWriters--;
    FPreferWriter = false;
    NotifyAll();
    FCriticalSection->Release();
  };
private:
  int FReadingReaders; //実際に読んでいる最中のスレッドの数
  int FWaitingWriters; //書くのを待っているスレッドの数
  int FWritingWriters; //実際に書いている最中のスレッドの数
  bool FPreferWriter;
  std::queue<TEvent*> FReadEvents;
  std::queue<TEvent*> FWriteEvents;
  std::unique_ptr<TCriticalSection> FCriticalSection;
  void WaitRead(TEvent* Event)
  {
    FReadEvents.push(Event);
    FCriticalSection->Release();
    Event->WaitFor(INFINITE);
    FCriticalSection->Acquire();
  }
  void WaitWrite(TEvent* Event)
  {
    FWriteEvents.push(Event);
    FCriticalSection->Release();
    Event->WaitFor(INFINITE);
    FCriticalSection->Acquire();
  }
  //すべてのイベントに対して通知を行う
  void NotifyAll()
  {
    if (!FWriteEvents.empty())
    {
      FWriteEvents.front()->SetEvent();
      FWriteEvents.pop();
    }
    else if (!FReadEvents.empty())
    {
      FReadEvents.front()->SetEvent();
      FReadEvents.pop();
    }
  };
};
//読み込むためのロックのRAII
class TReadLock
{
public:
  TReadLock(TReadWriteLock* Lock, TEvent* Event) : FLock(Lock)
  {
    FLock->ReadLock(Event);
  };
  ~TReadLock()
  {
    FLock->ReadUnlock();
  }
private:
  TReadWriteLock* FLock;
};
//書き込むためのロックのRAII
class TWriteLock
{
public:
  TWriteLock(TReadWriteLock* Lock, TEvent* Event) : FLock(Lock)
  {
    FLock->WriteLock(Event);
  };
  ~TWriteLock()
  {
    FLock->WriteUnlock();
  }
private:
  TReadWriteLock* FLock;
};
//TDataクラス
class TData
{
public:
  TData(const int Count) : FLock(new TReadWriteLock()),
  FBuffer(std::string(Count, '*'))
  {
  };
  std::string Read(TEvent* Event)
  {
    TReadLock lock(FLock, Event);
    return DoRead();
  };
  void Write(char C, TEvent* Event)
  {
    TWriteLock lock(FLock, Event);
    DoWrite(C);

  }
private:
  std::string FBuffer;
  TReadWriteLock* FLock;
  std::string DoRead()
  {
    Slowly();
    return FBuffer;
  };
  void DoWrite(char C)
  {
    for (std::string::size_type i = 0; i < FBuffer.size(); i++)
    {
      FBuffer[i] = C;
      Slowly();
    }
  };
  void Slowly()
  {
    Sleep(50);
  };
};
//TWriterThreadクラス
class TWriterThread : public TThread
{
public:
  __fastcall TWriterThread(TData* Data, std::string Filter)
       : TThread(false), FData(Data), FFilter(Filter), FIndex(0),
         FEvent(new TEvent(NULL, false, false, "", false)) {};
protected:
  void __fastcall Execute()
  {
    while (true)
    {
      char c = NextChar();
      FData->Write(c, FEvent.get());
      Sleep(random(3000));
    }
  };
private:
  TData* FData;
  std::string FFilter;
  unsigned int FIndex;
  std::unique_ptr<TEvent> FEvent;
  char NextChar()
  {
    char c = FFilter[FIndex];
    FIndex++;
    if (FIndex >= FFilter.size())
    {
      FIndex = 0;
    }
    return c;
  };
};
//TReaderThreadクラス
class TReaderThread : public TThread
{
public:
  __fastcall TReaderThread(std::string Name, TData* Data)
       : TThread(false), FName(Name), FData(Data),
         FEvent(new TEvent(NULL, false, false, "", false)) {};
protected:
  void __fastcall Execute()
  {
    while (true)
    {
      std::string readbuf = FData->Read(FEvent.get());
      Print(FName + " reads " + readbuf);
    }
  };
private:
  const std::string FName;
  TData* FData;
  std::unique_ptr<TEvent> FEvent;
};

int _tmain(int argc, _TCHAR* argv[])
{
  TData* data = new TData(10);
  new TReaderThread("TReaderThread-1", data);
  new TReaderThread("TReaderThread-2", data);
  new TReaderThread("TReaderThread-3", data);
  new TReaderThread("TReaderThread-4", data);
  new TReaderThread("TReaderThread-5", data);
  new TReaderThread("TReaderThread-6", data);
  new TWriterThread(data, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
  new TWriterThread(data, "abcdefghijklmnopqrstuvwxyz");

  Sleep(INFINITE);
  return 0;
}

2009年01月26日

C++Builder2009の乱数ジェネレータ

C++Builder2009では複数の乱数ジェネレータが用意されている。
使用するときは、C++系かVCL系かどちらか一方に統一した方が
ソースコートがわかりやすくなる。

rand

#include <stdlib.h>
int rand(void);

0 以上 RAND_MAX 以下の乱数を返す。
標準ライブラリに含まれ、移植性が高い。
アルゴリズムの実装はライブラリによって異なる。
古典的な線形合同法のアルゴリズムを使用していると、
乱数の精度が低いという問題がある。
C++Builder2009では32乗周期の乗算合同法乱数ジェネレータを使用している。

random

#include <stdlib.h>
int random(int num);

0~(num-1)の範囲の乱数を返す。
Win32でのみ使用可能。
stdlib.hで定義されているマクロ。

(int)(_lrand()%(num)

_lrand

#include <stdlib.h>
long _lrand(void);

長い乱数を生成する関数。
2^64周期の乗算合同法乱数ジェネレータを使用して、
0~2^31-1の範囲の連続疑似乱数を返す。

Random

int Random(const int ARange);
Extended Random();

VCLが提供するDelphi互換の関数。
0 <= X < Range の範囲にある乱数を返す。

RandomRange

int RandomRange(const int AFrom, const int ATo);

VCLが提供するDelphi互換の関数。
AFrom以上ATo未満の範囲で乱数を返す。

Math.RandG

#include <Math.hpp>
Extended RandG(Extended Mean, Extended StdDev);

VCLが提供するDelphi互換の関数。
ガウス分布(正規分布)を使って乱数を生成する。

Thread-Per-Messageパターン

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』にある 「Thread-Per-Messageパターン」をC++ Builder 2009で実装してみました。

プログラムの意味は『増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』をご覧下さい。

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』では無名インナークラスを使用していますが、 C++Builder2009では同じことができませんので、THandleThreadクラスを作成しました。

//スレッドセーフな出力
std::unique_ptr<TCriticalSection> CriticalSection(new TCriticalSection);
void Print(const std::string& S)
{
  CriticalSection->Acquire();
  std::cout << S << std::endl;
  CriticalSection->Release();
}
//THelperクラス
class THelper
{
public:
  void Handle(int Count, std::string C)
  {
    boost::format fmt("        handle(%d, %s) %s");
    Print((fmt % Count % C % "BEGIN").str());
    for (int i = 0; i < Count; i++)
    {
      Slowly();
      Print(C);
    }
    Print((fmt % Count % C % "END").str());
  };
private:
  void Slowly()
  {
    Sleep(100);
  };
};
//THandleThreadクラス
class THandleThread : public TThread
{
public:
  __fastcall THandleThread(THelper* Helper, int Count, std::string C)
    : TThread(false), FHelper(Helper), FCount(Count), FC(C) {};
protected:
  void __fastcall Execute()
  {
    FHelper->Handle(FCount, FC);
  };
private:
  THelper* FHelper;
  int FCount;
  std::string FC;
};
//THostクラス
class THost
{
public:
  THost() : FHelper(new THelper()) {};
  void Request(int Count, std::string C)
  {
    boost::format fmt("    request(%d, %s) %s");
    Print((fmt % Count % C % "BEGIN").str());
    new THandleThread(FHelper.get(), Count, C);
    Print((fmt % Count % C % "END").str());
  };
private:
  std::unique_ptr<THelper> FHelper;
};

int _tmain(int argc, _TCHAR* argv[])
{
  Print("main BEGIN");
  THost host;
  host.Request(10, "A");
  host.Request(20, "B");
  host.Request(30, "C");
  Print("main END");

  Sleep(INFINITE);
  return 0;
}

2009年01月27日

Worker-Threadパターン

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』にある 「Worker-Threadパターン」をC++ Builder 2009で実装してみました。

プログラムの意味は『増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』をご覧下さい。

実装のポイントは、仕事のリクエストの受け渡しとワーカースレッドの保持とを行うTChannelクラスです。
JavaのnotifyAll()と同じ機能がC++Builder2009にはないため、工夫が必要です。
Producer-Consumerパターンと同じように、リクエストを出すスレッドとリクエストを受け取るスレッドを別々に管理しました。

//実行関数
int _tmain(int argc, _TCHAR* argv[])
{
  TChannel* channel = new TChannel(5); //ワーカースレッドの個数
  channel->StartWorkers();
  new TClientThread("Alice", channel);
  new TClientThread("Bobby", channel);
  new TClientThread("Chris", channel);

  Sleep(INFINITE);
  return 0;
}

TRequest.h

//仕事のリクエストを表しているTRequestクラス
class TRequest
{
public:
  TRequest(std::string Name, int Number) : FName(Name), FNumber(Number) {};
  /**
   * @param Name 呼び出したスレッドのスレッド名
   */
  void Execute(std::string Name)
  {
    boost::format fmt("%s executes [ Request From %s No. %d]");
    Print((fmt % Name % FName % FNumber).str());
  };
private:
  const std::string FName;
  const int FNumber;
};

TClientThread.h

//仕事のリクエストを出すTClientThreadクラス
class TClientThread : public TThread
{
public:
  /**
   * @param Name スレッド名
   * @param Channel Channel
   */
  __fastcall TClientThread(std::string Name, TChannel* Channel)
    : TThread(false), FName(Name), FChannel(Channel),
      FEvent(new TEvent(NULL, false, false, "", false)) {};
protected:
  void __fastcall Execute();
private:
  const std::string FName;
  TChannel* FChannel;
  std::unique_ptr<TEvent> FEvent;
};

TClientThread.cpp

void __fastcall TClientThread::Execute()
{
  randomize();
  for (int i = 0; true; i++)
  {
    TRequest request(FName, i);
    FChannel->PutRequest(request, FEvent.get());
    Sleep(random(1000));
  }
}

TChannel.h

//仕事のリクエストの受け渡しと、ワーカースレッドの保持とを行うTChannelクラス
class TChannel
{
public:
  TChannel(int Threads);
  void StartWorkers();
  void PutRequest(TRequest Request, TEvent* Event);
  TRequest TakeRequest(TEvent* Event);
private:
  static const int MAX_REQUEST = 100;
  std::queue<TRequest> FRequestQueue;
  std::vector<TWorkerThred*> FThreadPool;
  std::unique_ptr<TCriticalSection> FPutCriticalSection;
  std::unique_ptr<TCriticalSection> FTakeCriticalSection;
  std::queue<TEvent*> FPutEvent;
  std::queue<TEvent*> FTakeEvent;
  void WaitPut(TEvent* Event);
  void WaitTake(TEvent* Event);
  void NotifyPut();
  void NotifyTake();
};

TChannel.cpp

TChannel::TChannel(int Threads)
  : FPutCriticalSection(new TCriticalSection),
    FTakeCriticalSection(new TCriticalSection)
{
  boost::format fmt("Worker-%d");
  for (int i = 0; i < Threads; i++)
  {
    FThreadPool.push_back(new TWorkerThred((fmt % i).str(), this));
  }
}
void TChannel::StartWorkers()
{
  for (std::vector<TWorkerThred*>::size_type i = 0; i < FThreadPool.size(); ++i)
  {
    FThreadPool[i]->Resume();
  }
}
void TChannel::PutRequest(TRequest Request, TEvent* Event)
{
  FPutCriticalSection->Acquire();
  while (FRequestQueue.size() >= MAX_REQUEST)
  {
    WaitPut(Event);
  }
  FRequestQueue.push(Request);
  NotifyTake();
  FPutCriticalSection->Release();
}
TRequest TChannel::TakeRequest(TEvent* Event)
{
  FTakeCriticalSection->Acquire();
  while (FRequestQueue.empty())
  {
    WaitTake(Event);
  }
  TRequest request = FRequestQueue.front();
  FRequestQueue.pop();
  NotifyPut();
  FTakeCriticalSection->Release();
  return request;
}
void TChannel::WaitPut(TEvent* Event)
{
  FPutEvent.push(Event);
  FPutCriticalSection->Release();
  Event->WaitFor(INFINITE);
  FPutCriticalSection->Acquire();
};
void TChannel::WaitTake(TEvent* Event)
{
  FTakeEvent.push(Event);
  FTakeCriticalSection->Release();
  Event->WaitFor(INFINITE);
  FTakeCriticalSection->Acquire();
}
void TChannel::NotifyPut()
{
  if (!FPutEvent.empty())
  {
    TEvent* event = FPutEvent.front();;
    FPutEvent.pop();
    event->SetEvent();
  }
}
void TChannel::NotifyTake()
{
  if (!FTakeEvent.empty())
  {
    TEvent* event = FTakeEvent.front();
    FTakeEvent.pop();
    event->SetEvent();
  }
}

TWorkerThred.h

//ワーカースレッドを表すTWorkerThredクラス
class TWorkerThred : public TThread
{
public:
  TWorkerThred(std::string Name, TChannel* Channel)
    : TThread(true), FName(Name), FChannel(Channel),
      FEvent(new TEvent(NULL, false, false, "", false)) {};
protected:
  void __fastcall Execute();
private:
  std::string FName;
  TChannel* FChannel;
  std::unique_ptr<TEvent> FEvent;
};

TWorkerThred.cpp

void __fastcall TWorkerThred::Execute()
{
  while (true)
  {
    TRequest request = FChannel->TakeRequest(FEvent.get());
    request.Execute(FName);
  }
}

2009年01月28日

Futureパターン

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』にある 「Futureパターン」をC++ Builder 2009で実装してみました。

プログラムの意味は『増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』をご覧下さい。

実装のポイントはJavaのwait()とnotifyAll()を、C++Builder2009ではどのように実装するか、です。
TEventによるイベント通知を使い、実現しています。

//スレッドセーフな出力
std::unique_ptr<TCriticalSection> CriticalSection(new TCriticalSection);
void Print(const std::string& S)
{
  CriticalSection->Acquire();
  std::cout << S << std::endl;
  CriticalSection->Release();
}
//TDataインターフェース
class TData
{
public:
  virtual std::string GetContent(TEvent* Event) = 0;
};
class TRealData : public TData
{
public:
  TRealData(int Count, std::string C)
  {
    boost::format fmt("        making TRealData(%d, %s) %s");
    Print((fmt % Count % C % "BEGIN").str());
    std::string buffer;
    for (int i = 0; i < Count; i++)
    {
      buffer += C;
      Sleep(100);
    }
    Print((fmt % Count % C % "END").str());
    this->FContent = buffer;
  }
  virtual std::string GetContent(TEvent* Event)
  {
    return FContent;
  }
private:
  std::string FContent;
};
class TFutureData : public TData
{
public:
  TFutureData()
    : FReady(false),  FEvent(new TEvent(NULL, false, false, "", false)),
      FCriticalSection(new TCriticalSection) {};
  void SetRealData(TRealData* RealData)
  {
    TLock lock(FCriticalSection.get());
    if (FReady)
    {
      return; //balk
    }
    this->FRealData = RealData;
    this->FReady = true;
    Notify();
  }
  virtual std::string GetContent(TEvent* Event)
  {
    FCriticalSection->Acquire();
    while (!FReady)
    {
      Wait(Event);
    }
    return FRealData->GetContent(FEvent.get());
  }
private:
  TRealData* FRealData;
  bool FReady;
  std::unique_ptr<TEvent> FEvent;
  std::unique_ptr<TCriticalSection> FCriticalSection;
  std::queue<TEvent*> FEvents;
  void Notify()
  {
    if (!FEvents.empty())
    {
      TEvent* event = FEvents.front();
      FEvents.pop();
      event->SetEvent();
    }
  }
  void Wait(TEvent* Event)
  {
    FEvents.push(Event);
    FCriticalSection->Release();
    Event->WaitFor(INFINITE);
    FCriticalSection->Acquire();
  }
};
//TRealDataのインスタンスを作るスレッド
class TMakerThread : public TThread
{
public:
  __fastcall TMakerThread(int Count, std::string C, TFutureData* FutureData)
    : TThread(false), FCount(Count), FC(C), FFutureData(FutureData) {};
protected:
  void __fastcall Execute()
  {
    TRealData* realdata = new TRealData(FCount, FC);
    FFutureData->SetRealData(realdata);
  }
private:
  int FCount;
  std::string FC;
  TFutureData* FFutureData;
};
class THost
{
public:
  TData* Request(int Count, std::string C)
  {
    boost::format fmt("    Request(%d, %s) %s");
    Print((fmt % Count % C % "BEGIN").str());

    // FutureDataのインスタンスを作る
    TFutureData* future = new TFutureData();

    //RealDataのインスタンスを作るための新しいスレッドを起動する
    new TMakerThread(Count, C, future);
    Print((fmt % Count % C % "END").str());

    //FutureDataのインスタンスを戻り値とする
    return future;
  }
};

int _tmain(int argc, _TCHAR* argv[])
{
  Print("main BEGIN");
  THost* host = new THost();
  TData* data1 = host->Request(10, "A");
  TData* data2 = host->Request(20, "B");
  TData* data3 = host->Request(30, "C");

  Sleep(2000);
  Print("main otherJob END");

  TEvent* event = new TEvent(NULL, false, false, "", false);
  Print("data1 = " + data1->GetContent(event));
  Print("data2 = " + data2->GetContent(event));
  Print("data3 = " + data3->GetContent(event));
  Print("main END");

  return 0;
}

2009年01月29日

Two-Phase Terminationパターン

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』にある 「Two-Phase Terminationパターン」をC++ Builder 2009で実装してみました。

プログラムの意味は『増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』をご覧下さい。

C++Builder2009には、スレッドを終了するためのTThread.TerminateメソッドとTThread.Terminatedプロパティが用意されています。
増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』のshutdownRequestメソッドは、Terminateメソッドに該当します。

//スレッドセーフな出力
std::unique_ptr<TCriticalSection> CriticalSection(new TCriticalSection);
void Print(const std::string& S)
{
  CriticalSection->Acquire();
  std::cout << S << std::endl;
  CriticalSection->Release();
}

class TCountupThread : public TThread
{
public:
  __fastcall TCountupThread() : TThread(false), FCounter(0) {};
protected:
  //動作
  void __fastcall Execute()
  {
    while (!this->Terminated)
    {
      DoWork();
    }
    DoShutdown();
  }
private:
  long FCounter;
  //作業
  void DoWork()
  {
    FCounter++;
    boost::format fmt("DoWork: counter = %d");
    Print((fmt % FCounter).str());
    Sleep(500);
  }
  //終了処理
  void DoShutdown()
  {
    boost::format fmt("DoShutdown: counter = %d");
    Print((fmt % FCounter).str());
  }
};

int _tmain(int argc, _TCHAR* argv[])
{
  Print("main BEGIN");

  //スレッドの起動
  TCountupThread* t = new TCountupThread();

  //少し時間を空ける
  Sleep(10000);

  //スレッドの終了要求
  Print("main shutdownRequest");
  t->Terminate();

  Print("main join");

  //スレッドの終了を待つ
  t->WaitFor();

  Print("main END");

  return 0;
}

2009年02月08日

vectorの容量はerase()しても減らない

vectorの容量はerase()しても解放されません。

vectorの容量をサイズに合わせて縮小するにはswap()を使用します。

vector<Customer> c(10000);
cout << c.capacity() << std::endl; #=> 10000

c.erase(c.begin() + 10, c.end());
cout << c.capacity() << std::endl; #=> 10000

vector<Customer>(c).swap(c);
cout << c.capacity() << std::endl;  #=> 10

2009年02月09日

std::accumulateの練習

std::accumulateの練習

要素の合計値を求める

std::vector<int> v;
//vに値を設定する
std::generate(v.begin(), v.end(), rand);
//要素の合計値を求める
//3番目の引数は初期値
int sum = accumulate(v.begin(), v.end(), 0);

文字列を結合する

//ランダムな文字を返す
struct Generator
{
  string operator ()()
  {
    static const string s = "abcd";
    return s.substr(random(s.length()), 1);
  }
};

std::vector<string> v(4);
//vに値を設定する
std::generate(v.begin(), v.end(), Generator());
//vの文字列を結合した、新しい文字列を取得する
string sum = accumulate(v.begin(), v.end(), string());

引いてみる

//1から順番に整数を返す
struct Generator
{
  int operator ()()
  {
    static int i = 1;
    return i++;
  }
};

std::vector<int> v(10);
std::generate(v.begin(), v.end(), Generator());
//4番目の引数を指定して、引き算を行うようにする
int sum = accumulate(v.begin(), v.end(), 100, minus<int>());

2009年02月10日

std::generatorの練習

std::generatorの練習。

generateアルゴリズムは、引数をとらない関数オブジェクト gen の呼び出し結果を、範囲[first,last)内の各要素に代入する。

Generic programming―STLによる汎用プログラミング

//引数 v から順に1大きい値を返す関数オブジェクト
class generator
{
private:
  int i;
public:
  generator(int v = 0) : i(v) {}
  int operator ()() { return i++; }
};

std::vector<int> v(10);
//0から
std::generate(v.begin(), v.end(), generator());
//10から
std::generate(v.begin(), v.end(), generator(10));

フィボナッチ数

//フィボナッチ数
class FibonacciNumber
{
private:
  int i1, i2;
public:
  FibonacciNumber() : i1(0), i2(1) {}
  int operator()()
  {
    int result = i1 + i2;
    i1 = i2;
    i2 = result;
    return result;
  }
};

std::vector<int> v(10);
std::generate(v.begin(), v.end(), FibonacciNumber());

2009年02月11日

std::transformの練習

std::transformの練習

vectorのそれぞれの要素に2を加えた、新しいvectorを取得する

//1から順番に整数を返す
struct Generator
{
  int operator()()
  {
    static int i = 1;
    return i++;
  }
};
//引数に2を加算して返す
int add2(int i)
{
  return i + 2;
}

vector<int> v(10);
//vに値を設定する
generate(v.begin(), v.end(), Generator());

vector<int> u;
//vに2を加算した値をuに設定する
transform(v.begin(), v.end(), back_inserter(u), add2);

2009年02月12日

std::inner_productの練習

std::inner_productの練習

inner_productアルゴリズムの振る舞いは、

  • 2個の入力範囲をとる。
  • 入力範囲の各要素に対し、指定の操作(操作2)を行う。
  • もう一つの操作(操作1)を行う。

サンプルプログラム

string v1 = "abcdefg";
string v2 = "AbcDEfg";
//同じ文字の数を数える
int result = inner_product(v1.begin(), v1.end(), //入力範囲1
                           v2.begin(), //入力範囲2
                           0, //初期値
                           plus<int>(), //操作1
                           equal_to<char>()); //操作2

Delphi2009用 改造版2.48

Delphi2009用 改造版2.48本田さんトコのBBSで公開されています。

DEKOのざつだん。

待望のUnicode対応のTEditorです。

早速、C++Builder2009で試してみたところ、問題なくコンパイルでき使用することができました。

使ってみて気になった点があります。
全角文字がある行の改行と[EOF]のマークの位置がおかしいようです。
半角文字だけなら問題はありませんでした。

もう少し検証してみようと思います。

追記
早速、Mae様がマーク位置の問題を修正してくださりました。 ありがとうございました。

もう一つ、気がついたことがあります。
全角英数字が表示されないようです。
調べてみようと思います。

さらに追記
全角英数字の表示の問題も修正していただきました。
そのほかの問題も修正されているそうです。
ありがとうございます。

2009年02月13日

std::next_permutationとstd::prev_permutationの練習

std::next_permutationとstd::prev_permutationの練習

next_permutationとprev_permutationは順列を生成します。

string  s = "ABC";
puts(s.c_str());
while (next_permutation(s.begin(), s.end()))
{
  puts(s.c_str());
}

結果

ABC
ACB
BAC
BCA
CAB
CBA

2009年02月18日

C++Builder2009のライブテンプレートを使う

C++Builder2009で、ライブテンプレートを登録してみました。
とても簡単にできます。
Delphi2009でも同様に使用できると思います。

メニューの「表示」-「テンプレート」でテンプレートウィンドウが表示されます。
テンプレートウィンドウでは、ライブコードテンプレートの作成・編集・削除ができます。

「新規コードテンプレート」ボタンを押し、新しいテンプレートを作成します。
テンプレートのひな形が作られます。

<?xml version="1.0" encoding="utf-8" ?>
<codetemplate   xmlns="http://schemas.borland.com/Delphi/2005/codetemplates"
                version="1.0.0">
    <template name="" invoke="manual">
        <description>

        </description>
        <author>

        </author>
        <code language=""><![CDATA[]]>
        </code>
    </template>
</codetemplate>

codetemplateタグのname属性には、コード補完(CTRL+J)を実行する文字を入力します。
例えば、name属性を「//-」とした場合、「//-」「CTRL+J」と入力すると、
作成したテンプレートが挿入されます。

descriptionタグの値には、概要を入力します。
authorタグの値には、作者の名前を入力します。
これらはあまり重要ではないと思います。

codeタグのlanguage属性は、テンプレートの対象言語を入力します。
C/C++なら「C」になります。
Delphiの場合は「Delphi」になると思います。

<![CDATA[]]>の箇所に、挿入するコードを入力します。
(注意)挿入するコードの中にマルチバイト文字があると、挙動がおかしくなります。

サンプルです。
unique_ptrと入力し(途中まででも可)、CTRL+Jキーを入力すると、
unique_ptrのテンプレートが挿入されます。

<?xml version="1.0" encoding="utf-8" ?>
<codetemplate   xmlns="http://schemas.borland.com/Delphi/2005/codetemplates"
                version="1.0.0">
    <template name="unique_ptr" invoke="manual">
        <description>
            unique_ptr
        </description>
        <author>
            山本隆
        </author>
        <code language="C"><![CDATA[unique_ptr<type> variable(new type());]]>
        </code>
    </template>
</codetemplate>

まだ不十分ですね。
テンプレートを挿入した後、クラス名や変数名を書き直さなければなりません。

書き直す必要がある部分を、「$変数名$」という形に書き直します。

<code language="C"><![CDATA[unique_ptr<$type$> $variable$(new $type$());]]>

次に、「$変数名$」の情報を登録します。
次のような構文になります。

<point name="変数名">
    <hint>
        ヒント
    </hint>
    <text>
        初期値
    </text>
</point>

例えば、クラス名は、

<point name="type">
    <hint>
        クラス名
    </hint>
    <text>
        T
    </text>

となります。

最終的には、次のようになりました。

<?xml version="1.0" encoding="utf-8" ?>
<codetemplate    xmlns="http://schemas.borland.com/Delphi/2005/codetemplates"
                version="1.0.0">
    <template name="unique_ptr" invoke="manual">
        <point name="type">
            <hint>
                クラス名
            </hint>
            <text>
                T
            </text>
        </point>
        <point name="variable">
            <hint>
                変数名
            </hint>
            <text>
                obj
            </text>
        </point>
        <description>
            unique_ptr
        </description>
        <author>
            山本隆
        </author>
        <code language="C"><![CDATA[unique_ptr<$type$> $variable$(new $type$());]]>
        </code>
    </template>
</codetemplate>

変数の部分は、ツールチップで説明が表示されるようになります。
クラス名の一方を編集すると、もう一方も編集されます。

簡単に編集ができますので、よく使う定型文は登録しておくと便利です。
みんなで共有しても良いかもしれませんね。

2009年02月26日

Masks.MatchesMask関数で文字列がワイルドカードにマッチするか調べる

Masks.MatchesMask関数を使用すると、文字列がワイルドカードにマッチするか調べることができます。

こんな便利な関数があることを今まで知らず、一般人には扱いにくい正規表現をソフトに使用していました。

VCLには私が知らない便利な関数がたくさんあるんだろうなぁ。

2009年03月01日

エラトステネスの篩をC++で実装してみた

エラトステネスの篩をC++で実装してみました。

エラトステネスは、紀元前275年~紀元前194年の人です。
これほど昔の人が、こんなに優れたアルゴリズムを考え出していることに驚きます。

ソースコード中の「ステップ 1」から「ステップ 4」のコメントは、
Wikipediaのエラトステネスの篩の文章に対応しています。

//コンストラクタの引数startから始まり1ずつ増える数値を返す
struct gen1 {
  int val;
  gen1(int start) : val(start) {};
  int operator()() { return val++; };
};

int _tmain(int argc, _TCHAR* argv[])
{
  list<int> search_list(99); //探索リスト(2~100まで)
  vector<int> prime_number; //素数リスト

  //ステップ 1
  //整数を最初の素数である 2 から昇順で探索リストに羅列する。
  generate(search_list.begin(), search_list.end(), gen1(2));

  while (1)
  {
    //ステップ 2
    //リストの先頭の数を素数リストに記録する。
    int num = search_list.front();
    search_list.pop_front();
    prime_number.push_back(num);

    //ステップ 3
    //前のステップで素数リストに加えられた数の全ての倍数を、探索リストから削除する。
    search_list.erase(remove_if(search_list.begin(),
                                search_list.end(),
                                bind2nd(not2(modulus<int>()), num)),
                      search_list.end());

    //ステップ 4
    //探索リストの最大値が素数リストの最大値の平方よりも小さい場合、
    //素数リストおよび探索リストに残っている数が素数となる。
    //探索リストの最大値が素数リストの最大値の平方よりも大きい場合、
    //ステップ 2 に戻る。
    int search_max_num = *search_list.rbegin(); //探索リストの最大値
    int prime_max = *prime_number.rbegin();
    int prime_max_pow = prime_max * prime_max; //素数リストの最大値の平方
    if (search_max_num < prime_max_pow)
    {
      copy(search_list.begin(), search_list.end(), back_inserter(prime_number));
      break;
    }
  }

  //出力
  copy(prime_number.begin(), prime_number.end(), ostream_iterator<int>(cout, " "));
  return 0;
}

出力結果

2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

2009年03月02日

コンテナ中の最大の要素を取得する(std::max_element)

std::max_elementはコンテナ中の最大の要素を返します。

#include <vector>
#include <algorithm>

//コンテナにランダムな要素を設定する
std::vector<int> numbers(10);
std::generate(numbers.begin(), numbers.end(), rand);

//最大の要素
int max = *max_element(numbers.begin(), numbers.end());

コンテナ中の最小の要素を取得する(std::min_element)

std::min_elementはコンテナ中の最小の要素を返します。

#include <vector>
#include <algorithm>

//コンテナにランダムな要素を設定する
std::vector<int> numbers(10);
std::generate(numbers.begin(), numbers.end(), rand);

//最小の要素
int min = *min_element(numbers.begin(), numbers.end());

TIdFTP.DirectoryListingで「No IdFTPListParse classes have been registered.」の例外が発生するときの対処方法

C++Builder 2009にはIndy10が付属しています。
このIndy10のコンポーネントの一つであるTIdFTPを使い、FTPサーバのファイルの一覧を取得したかったのですが…

次のコードを実行すると、例外が発生します。

IdFTP1->Host = EditHost->Text; //サーバ名
IdFTP1->Username = EditUser->Text; //ユーザ名
IdFTP1->Password = EditPassword->Text; //パスワード
IdFTP1->Port = EditPort->Text.ToInt(); //ポート番号
IdFTP1->Connect(); //接続
IdFTP1->List();
TIdFTPListItems* items = IdFTP1->DirectoryListing; //ここで例外が発生

例外のメッセージは次のようなものです。

No IdFTPListParse classes have been registered. Check your uses clause!

ヘルプをよく読むと、解説があります。

The Indy library recognizes these defacto standards, and makes provisions for the IETF draft standards. TIdFTP provides the capabilitiy to parse the various formats for the textual content in the ListResult property into a structured representation of the directory stored in the DirectoryListing property. At the present time, Indy offers parsers for 30 different host-specific directory listing formats. Additional parsers are offered as new directory listing formats are encountered and codified. Use the IdAllFTPListParser.pas unit to include and register the known directory listing parsers for the Indy library.

要するに、「IdAllFTPListParser.pasをincludeしてください」ということです。

そこで、ソースコードに次の行を追加することにしました。

#include <IdAllFTPListParsers.hpp>

ところが、これでも状況は変わりません。

検索エンジンで調べてみると、同じような問題で困っている人が見つかりました。

For some reason, IdFTP1->DirectoryListing->Count is always 0.

Problem using TIdFTP in C++ Builder 2007. List() doesn't work

環境がC++Builder 2007のためでしょうか、現象が多少異なります。
ですが、同じような原因だと考えられます。

次のようなアドバイスがあります。

You have to include the appropriate IdFTPListParse...hpp header files for the specific parser(s) that you want to use. Or alternatively include the IdAllFTPListParsers.hpp header file to enable them all.

これはヘルプに記載されている方法と同じで、私も試した方法です。
案の定、この方法に従っても、問題は解決していません。

質問者は、最新版のIndyをインストールし直したようですが、それでも状況はいっこうに改善せず。
結局、問題は解決していないようです。

TIdFTP.DirectoryListingはC++Builderでは使用できないのかも。
と半分諦めつつ試行錯誤していると、解決策が見つかりました。

わかってしまえば簡単なことですが、でも、わからずに困っている人がたくさんいるのではないでしょうか。

その解決策ですが、次の行を追加するだけです。

#pragma link "IdAllFTPListParsers"

これでうまくいきます。

追記
TIdFTP.DirectoryListingの使い方をまとめました。

2009年03月06日

VCL Fix Pack 1.2

VCL Fix Pack 1.2がリリースされています。

VCLの数多くの問題が修正されるようです。
詳しくはホームページをご覧下さい。

使い方は、ユニットをプロジェクトに追加するだけです。

IDE Fix Pack 2009 2.5

IDE Fix Pack 2009 2.5がリリースされています。

Delphi/C++Builder 2009のバグを修正してくれます。
詳しくはホームページをご覧下さい。

インストーラが付いていますので、簡単にインストール・アンインストールができます。

2009年03月11日

C++Builder2009で扱える画像形式のまとめ

Delphi 2009/C++Builder 2009では、標準で扱える画像形式が増えています。

次の形式を扱うことができます。

  • Graphics Interchange Format(gif)
  • JPEG(jpg)
  • Portable Network Graphics(png)
  • Windows bitmap(bmp)
  • Windows Metafile(wmf,emf)
  • Windows アイコンファイル(ico)

他にもありますか?

C++Builder2009の画像を扱うクラスと必要なヘッダファイルをまとめました。

2009年03月17日

フォームの重なり順序を変更する方法は?

フォームの重なり順序を変更する方法は?」という質問を発見して、まだ解決していないようなので調べてみました。

Form1とForm2の2つを作成し、Form1を表示するときにForm2を表示するようにします。

void __fastcall TForm1::FormShow(TObject *Sender)
{
  Form2->Show();
}

プログラムを実行すると、Form1とForm2の2つのフォームが表示されます。

ここで、Form1をクリックしてアクティブにしても、Form2がForm1の上に表示されたままになります。

form.gif

この現象はC++Builder2007とC++Builder2009の両方で確認できました。

調べてみたところ、TApplication.MainFormOnTaskBarが影響しているようです。

C++Builder2009ではProject1.cppの次の行をコメントアウトすれば、期待する動作になります。

Application->MainFormOnTaskBar = true;

C++Builder2007ではProject1.cppの次の行をコメントアウトします。

SetApplicationMainFormOnTaskBar(Application, true);

SetApplicationMainFormOnTaskBarはApplication.MainFormOnTaskBarの値を設定するだけの関数です。

TApplication.MainFormOnTaskBarはWindows Vista Aero 効果のための機能です。

ヘルプを見るとTApplication.MainFormOnTaskBarの項目には、

このプロパティに依存するアプリケーションでは、このプロパティが MainForm の Z オーダーに影響を与える点に注意してください。

とあります。

Quality Centralにもありました。

この挙動がWindows Vista Aeroの仕様なのか、VCLの仕様なのかはわかりません。

2009年03月18日

C++Builder Tipsに日付関連の記事を追加

C++ Builder Tipsの「日数の計算」の記事を加筆しました。

ついでに、「有効な日付や時刻かどうかを調べる」と「年初、月初の日付を取得する」の記事も追加しました。

DateUtilsユニットには他にも便利な関数がたくさんあります。
プログラムで日付を扱うなら、ヘルプに目を通しておくと効率が上がります。

2009年03月20日

DELPHI 2009 HANDBOOK

DELPHI 2009 HANDBOOKがAmazonで予約注文できるようになっていました。

追記
DELPHI 2009 HANDBOOKを購入しました

2009年03月25日

DELPHI 2009 HANDBOOKはどこで買える?

DELPHI 2009 HANDBOOKが発売されたようです。
購入できるオンライン書店を探しました。

購入可能

売り切れ・在庫なし

書籍情報なし

追記
DELPHI 2009 HANDBOOKを購入しました。

C++ Builder 2009で、DLLのデバッグにPythonを利用する

C++ Builderで、DLLのデバッグにPythonを利用する がすばらしかったので、
C++Builder 2009で同様のことができるか検証しました。

結論から言うと、C++Builder 2009でもPythonを使用することで簡単にDLLのデバッグができました。

以下は作業手順です。

メインメニューから「ファイル」-「新規作成」-「その他」を選択します。

「C++Builderプロジェクト」から「ダイナミックライブラリ」を選択します。

「ソースの種類」は「C」を選択、「マルチスレッド」「VC++スタイルのDLL」はチェックを外します。

「OK」ボタンを押し、プロジェクトを作成します。

プロジェクトを保存、再構築します。

メインメニューから「実行」-「実行時引数」を選択します。

「ホストアプリケーション」にpython.exeを指定します。

C:\Python26\python.exe

ソースコードにブレークポイントを設定します。

int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
  return 1; //<= この行にブレークポイントを設定する
}

プロジェクトを実行します。
Pythonインタラクティブシェルが起動します。

次のコードを入力します。

>>> import ctypes
>>> dll = ctypes.windll.LoadLibrary("Project1.dll")

C++Builderで設定していたブレークポイントで、プログラムが止まれば成功です。

プログラムを再開します。

Pythonインタラクティブシェルを終了します。

>>> quit()

次のコードを追加し、ブレークポイントを設定します。

__declspec(dllexport) int WINAPI Add(int x, int y)
{
  return x + y; //<= この行にブレークポイントを設定する
}

再び、プロジェクトを実行します。

次のコードを入力します。

>>> import ctypes
>>> dll = ctypes.windll.LoadLibrary("Project1.dll")
>>> print dll.Add(1, 2)

C++Builderで設定していたブレークポイントで、プログラムが止まれば成功です。

毎回、Pythonインタラクティブシェルで同じコードを入力するのは大変ですので、スクリプトファイルから実行することにします。

プロジェクトのDebugディレクトリにtest.pyを作成し、次のコードを記述します。

#!C:\Python26\python
# -*- coding: cp932 -*-
from ctypes import windll
dll = windll.LoadLibrary("Project1.dll")
print dll.Add(1, 2)
raw_input('') #Enterキーを入力するまで待機する

メインメニューから「実行」-「実行時引数」を選択します。

「パラメータ」に作成したtest.pyのパスを指定します。
パスにスペースが入る場合は、「"」でくくる必要があります。

"C:\Documents and Settings\yamamoto\My Documents\test\Debug\test.py"

プロジェクトを実行すると、test.pyが実行されます。

C++Builderで設定していたブレークポイントで、プログラムが止まれば成功です。

2009年03月30日

TNkDIB/TNkPrinterがDelphi2009に対応

Delphi2009に対応したTNkDIBとTNkPrinterが公開されました。

Delphi2009に対応したTNkDIBとTNkPrinterを、
C++Builder2009でコンパイルできることを確認しました。

以下、確認方法です。

プロジェクトにすべてのpasファイルを追加します。
次に、プロジェクトを再構築します。
pasファイルからhppファイルが作成されます。

以下のコードを追加し、コンパイルします。

#include "NkDIB.hpp"
#include "NkPrinters.hpp"

TNkDIB* dib = new TNkDIB();
TNkPrinter* printer = NkPrinter;

NkDIB.hppでコンパイルエラーになりました。

[BCC32 エラー] NkDIB.hpp(22): E2209 インクルード ファイル 'Nkgraph.hpp' をオープンできない

NkDIB.hppの次の部分を変更することでエラーを回避できます。

//#include <Nkgraph.hpp>    // Pascal unit
#include "Nkgraph.hpp"  // Pascal unit

これでコンパイルに成功しました。

おそらく設計時パッケージにコンポーネントをインストールすれば、
このエラーも発生しないと思います。

DELPHI 2009 HANDBOOK

DELPHI 2009 HANDBOOKを読みました。
この本を読むまでは、Delphi2009はDelphi2007からUnicodeのサポートと小さな修正が行われただけだと思っていました。
実際には、多くの新しい機能が追加された優れた開発環境だったことがわかりました。

DELPHI 2009 HANDBOOKは、Delphi 2007 HANDBOOK(日本語未翻訳)の続編という位置づけになっています。
そのため、本書の内容はDelphi2009の新機能と変更点に絞られています。
Delphi2009で新たに追加された機能だけで400ページ以上の本になったわけです。

本書のターゲットは、旧バージョンのDelphiからDelphi2009へ移行する開発者です。
これからDelphiを始める初心者には難しすぎると思います。
C++Builder2009ユーザーもObject Pascalの新機能以外は参考になります。

本書の内容を大きく分けると次の4つに分けられます。

  • Unicode
  • IDEの新機能
  • Object Pascalの新機能
  • VCLの新機能

Delphi2009の一番の目玉であるUnicodeについては、多くのページが割かれています。
次の3つに分けられます。

  • Unicodeについての解説
  • UnicodeString型の説明
  • 旧バージョンからの移行方法

たくさんのサンプルコードが掲載されており、実際に動作を確認しながら読み進めることができるようになっています。
サンプルコードはこちらからダウンロードできるようです。

IDEにも、いくつもの新機能が追加されています。
便利になった新しい機能を上手に使い、効率よく開発したいものです。

  • 新しく追加されたプロジェクトオプションの項目について
  • リソース管理の機能
  • ツールパレット検索ボックス
  • プロジェクトマネージャビュー
  • その他いろいろ

Object Pascalの新機能として、2つの機能が説明されています。

  • ジェネリクス
  • 無名メソッド

ジェネリクスと無名メソッドによって、Object Pascalがより生産性の高いプログラミング言語になったことがわかります。
無名メソッドはC++Builderにはない機能ですから、とても羨ましいです。

それから、242~243ページの「オーバーロードにおける変更」は注意が必要です。
特に「あるメソッドを呼び出していたコードが、以前と異なるメソッドを呼び出すようになってしまう」は気がつきにくいだけに問題です。
ただし状況が限られているので、問題を理解していれば何とかなりそうです。

VCLの新機能で説明されているコンポーネント

  • 新しいコンポーネント
    • バルーンヒント
    • ButtonedEdit
    • CategoryPanelGroup
    • Ribbon
  • コモンコントロールの新機能
    • Button
    • Label
    • RadioGroup
    • Edit
    • ComboBox
    • ListView
    • ProgressBar
    • HeaderControl
    • RichEdit

VCLの新機能ですが、4つの新しいコンポーネントとリボンのサポートに加えて、 Windows XPやWindows Vistaで追加されたコモンコントロールの新機能がサポートされました。
リボンコントロールについては、1つの章をまるまる使って説明しています。

# 古いOSで新機能を使用するとどうなるのか、検証しました。
# => C++Builder2009でコモンコントロールに追加された新機能の検証

他にも、COMサポートやdbExpress、DataSnap2009についての解説もあります。

と、盛りだくさんの内容でした。

本書では、Delphi2009の新機能・変更点に内容が絞られています。
Delphi7からDelphi2007までの間に追加された機能、例えばクラスヘルパーなどについては、説明がありません。
Delphi 2007 HANDBOOKの日本語訳も出版してほしいです。

2009年04月03日

C++Builder2009でコモンコントロールに追加された新機能の検証

DELPHI 2009 HANDBOOKを読んで、コモンコントロールに多くの機能が追加されたことを知りました。

コモンコントロールの新機能が、Windows VistaとWindows 2000でどのように機能するかを検証しました。

まだ検証していない機能もあります。

Windows XP以降に追加された機能は、当然Windows2000では使えません。
ですが、エラーが発生してアプリケーションが動作しない、
というわけではありませんでした。

そのうちWindows XPでも検証しようと思います。

追記
Windowx XPでも検証しました。

2009年04月09日

ScreenTipsPopupを試してみる

DELPHI 2009 HANDBOOKに掲載されていたScreenTipsPopupを試してみた。

Windows Vista

Windows XP

Windows 2000

IDEから簡単に設定でき、ヘルプヒントよりも多くの情報を提供できる。

古いWindows(Windows 2000)でもちゃんと動作する。

地味な機能ではあるが、なかなか使えそうだ。

C++ Builer TipsC++Builder2009でコモンコントロールに追加された新機能の検証にも追加しておきます。

2009年04月16日

Smooth Controlフリーダウンロード

Delphi 2009、C++Builder 2009、RAD Studio 2009、Embarcadero All-Accessを購入された方は、高機能コントロールセットを無料でダウンロードいただけます。

期間限定!Delphi / C++Builder 2009ユーザー向け Smooth Controlフリーダウンロード

2009年6月30日までの期間限定でです。
とりえあず、ダウンロードはしておいてもいいかと。

製品版と違い、ソースコードは付属していないようです。

Smooth Controlの公式サイトはこちらだと思います。

製品版はSingle developer licenseで75EURのようです。

2009年04月28日

EurekaLog導入レポート

EurekaLogの導入レポートです。

EurekaLogとは

EurekaLogは、Delphi/C++Builder用のバグ発見・報告ツールです。
自作のソフトウェアにを組み込むことで、予期せぬエラーに対して詳細な情報を得ることができます。

Windowsのエラー報告ダイアログのようなエラーの報告を送信する機能もあり、 ソフトウェアの利用者からメールなどを使ってバグ報告を受け取ることができます。

そして、開発環境に統合されたEurekaLog Viewerによって、受け取ったバグ報告からエラーの発生したソースコードの行を知ることができ、問題解決が格段にはかどります。

EurekaLog(その1) - 不意に起こる例外に対処する」「EurekaLog(その2) - メモリリーク検出とIndyとの関係」も参考になります。

購入する前に

購入する前にEurekaLogのサイトにあるDownloadsのページのデモアプリケーションとVideo Tutorialsの動画で、EurekaLogのおおよそのイメージをつかみました。

デモアプリケーションではエラー報告ダイアログが英語表記だったのが気になりましたが、Video Tutorialsの動画で表示文字を編集できることがわかり、安心しました。

Delphi 2009 でEurekaLogを使う」のレビューもとても参考になりました。

体験版

Downloadsのページから30日間使える体験版(Trial versions)がダウンロードできます。
体験版で、私が持っているC++Builderでちゃんと動作することを確認できました。
また日本語の表示なども確認しました。

購入

EurekaLogBuy nowのページから、購入申し込みができます。

申し込みフォームは複数言語に対応していて、日本語にも対応しています。
ただし、日本語を入力することはできないようでした。

購入後、製品版をダウンロードするためのIDとパスワードがメールで送られてきます。

インストール

インストールは、インストーラを実行するだけ。
インストーラは良くできていて、複数バージョンのC++Builderがインストールされている場合、インストーラが自動的に見つけてくれて、それぞれのバージョンに対してインストールしてくれます。

使い勝手

IDEに統合されるので、とても使いやすくなっています。
Video Tutorialsの動画を見た後なら、迷うことはないと思います。

EurekaLogの使い方

EurekaLogを使うには、IDEのメインメニューから「プロジェクト」-「EurekaLog Options」を選択して、EurekaLogを有効にします。
あとはプロジェクトを再構築するだけです。

本当にEurekaLogが有効になっているか心配な場合は、次のコードで確認できます。

#ifdef EUREKALOG
  Application->MessageBox(L"Compiled with EurekaLog.", L"EurekaLog");
#endif

エラーの報告

エラーの報告方法は、メール・HTTP・FTTPS・FTP・バグ管理システムの方法があります。

メールを使う方法は、ウィルス対策ソフトで問題が発生する可能性を考慮して、使用するのは控えました。

HTTP・HTTPSでは、Basic認証も扱えます。
エラー報告をHTTPで送信し、PHPを使ってメール送信するようにしました。

バグ管理システムは、BugZilla・FogBugz・Mantisに対応しています。
私が使用しているMantisにも対応しているのが嬉しいです。
MantisはPHPで作られており、安価なレンタルサーバでも使用することができ、導入も簡単で、必要十分な機能を備えています。
Mantisへのエラー報告は、これから試したい機能です。

エラーの報告方法についてはもっと試行錯誤して、ベストな方法を考えたいと思います。

エラー報告画面

エラー報告画面のメッセージをどうしようかと思い、最初はWindowsのエラー報告画面を参考にしようと考えました。

エラー報告画面

この問題を Microsoft に報告してください。

弊社では、この報告を製品の改善に役立てるとともに匿名の機密情報として扱います。

あらためて見ると、このメッセージを見て報告したいとは思いません。 Microsoftは本当に報告してもらいたいのか、疑問を感じます。

エラー報告画面は自分で考えることにしました。

エラー発生場所の発見

エラー報告が届いたら、EurekaLog Viewerでエラーログを表示します。

Call Stackをダブルクリックすると、ソースコードのエラーが発生した該当行にカーソルが移動します。

使いやすくて、好感が持てます。

総評

とても多機能で高性能なツールです。 使い方もわかりやすく、すぐに使うことができました。

それなりの金額です($99(US))が、それだけの価値があると思います。

追記

2009年7月23日 「EurekaLog導入後」の記事を公開しました。

2009年05月08日

Windows 7のバージョン情報を調べた

C++Builder Tipsに「Windowsのバージョンを取得する」の記事を追加しました。

ついでに、Windows 7 RCが返す値を調べてみました。

Windows 7 RCが返した値は次の通りでした。

  • Win32Platform = VER_PLATFORM_WIN32_NT
  • Win32MajorVersion = 6
  • Win32MinorVersion = 1

Windows 「7」なのにメジャーバージョンは「6」でした。

2009年05月10日

Delphiのメソッドをメソッド名から実行する方法

Delphiのメソッドをメソッド名から実行する方法。

元ネタは「Execute a Delphi Method (Procedure/Function) by Name」より。
ヘルプの「TObject::MethodAddress」にも同じことが書かれていました。

次のような関数を用意します。

procedure ExecMethod(OnObject: TObject; MethodName: string);
type
  TExec = procedure of object;
var
  Routine: TMethod;
  Exec: TExec;
begin
  Routine.Data := Pointer(OnObject);
  Routine.Code := OnObject.MethodAddress(MethodName);
  if NOT Assigned(Routine.Code) then Exit;
  Exec := TExec(Routine);
  Exec;
end;

あとはこの関数を使って、目的のメソッドを実行します。

# Form1.Button1Clickを実行する
ExecMethod(Form1, 'Button1Click');

2009年05月17日

sprinfのC++版「boost::format」

C++Builder 2009からBoostが標準添付されて、とても使いやすくなりました。

そのBoostのライブラリの一つ「boost::format」は、sprinfのC++版とも言えるライブラリです。
(The Boost Format library)

printfの書式指定子と同じ書式文字列を使用することができます。
また拡張フォーマットにより%1%で1番目の引数、%2%で2番目の引数を出力できます。

//formatの生成
format fmter("%1% %2% \n"); //引数は書式文字列
cout << fmter % "abc" % "efg"; //=> abc efg

//何度でも取り出せる
string s1 = fmter.str(); //メンバ関数str()
cout << s1; //=> abc efg

//何度でも取り出せる
string s2 = str(fmter); //関数boost::str()
cout << s2; //=> abc efg

//並べ替え
cout << format("%1% %2% %3% %2% %1% \n") % "11" % "22" % "33";
//=> 11 22 33 22 11

//printf互換
cout << format("%05d %x %f %%") % 200 % 255 % 3.33;
//=> 00200 ff 3.330000 %

2009年05月27日

ファイル名に使用できない文字を判別する

PathGetCharTypeを使用すると、ファイル名に使用できない文字を判別することができます。

こんな便利な関数が用意されているとは知りませんでした。
ファイル名に使用できない文字の一覧表を作成して、チェックする必要はありませんでした。

サンプルコードです。
C++Builder2009で動作を検証しました。

#pragma hdrstop
#include <tchar.h>
#include <shlwapi.h>
#include <iostream>

#pragma argsused
#pragma link "shlwapi.lib"

using namespace std;
using namespace boost;
void CheckChar(wchar_t  C)
{
  const unsigned int result = PathGetCharType(C);
  if (result & GCT_INVALID)
    wcout << C << L" は不正な文字です。" << endl;
  if (result & GCT_LFNCHAR)
    wcout << C << L" は長いファイル名に使用できます。" << endl;
  if (result & GCT_SEPARATOR)
    wcout << C << L" は区切り文字です。" << endl;
  if (result & GCT_SHORTCHAR)
    wcout << C << L" は短いファイル名に使用できます。" << endl;
  if (result & GCT_WILD)
    wcout << C << L" はワイルドカード文字です。" << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
  wcout.imbue(locale("japanese"));
  const wchar_t chars[] = L"\\/:,;*?\"<>|aあ";

  for (unsigned int i = 0; i < sizeof(chars) / sizeof(wchar_t); ++i)
  {
    CheckChar(chars[i]);
  }
    return 0;
}

実行結果

\ は区切り文字です。
: は区切り文字です。
, は長いファイル名に使用できます。
; は長いファイル名に使用できます。
* はワイルドカード文字です。
? はワイルドカード文字です。
a は長いファイル名に使用できます。
a は短いファイル名に使用できます。
あ は長いファイル名に使用できます。
あ は短いファイル名に使用できます。

追記
C++Builder Tipsに「ファイル名として正しいかどうかを調べる」を掲載しました。
ウィキペディアの「ファイル名の項」を参考にして、予約されているファイル名の検証も追加しています。

InitializeCriticalSectionEx

InitializeCriticalSectionExについて

Team Japan » InitializeCriticalSectionEx」によると、Windows Vista以降でクリティカルセクションのメモリ管理に関して仕様変更があり、InitializeCriticalSectionによる初期化ではリソースリークが発生するとのこと。
実行時にOSのバージョンをチェックして、動的に「InitializeCriticalSectionEx」を呼び出すようコードが紹介されています。

近いうちにC++Builderで書き直してみたいと思います。

Owl's perspective: InitializeCriticalSectionEx」も勉強になります。
Windowsデバッグの極意』という本が紹介されていて、良さそうな本に見えますが、値段を見ると気軽に手が出せません。
高橋智宏さんが紹介されている『Concurrent Programming on Windows』は洋書です。
なおさら手が出ません。

皆さん、よく勉強されているんですね。

2009年05月28日

C++Builder 2009 Update 3、Update 4、Boost Updateで、どこが修正されたのか

C++Builder 2009 Update 3、Update 4、Boost Updateがリリースされました。

「アップデートの確認」からインストールできます。
「[RAD Studio 2009 Update 3, Update 4 & Boost Update]」からもダウンロードできます。
想像通り、アップデートには時間がかかりますので、ご注意を。

Update 3 で修正された問題を見ると、たくさんの修正が行われています。

  • AnsiPos method of AnsiString class returns Unicode based index
  • LastDelimiter method of AnsiString class returns Unicode based index

    AnsiString::AnsiPosとAnsiString::LastDelimiterの挙動が、C++Builder2007以前と同じになりました。
    以前にこのブログに書いた件ですね。(C++Builder 2009への移植作業 その1)(C++Builder 2009への移植作業 その2)
    古いバージョンからの移植が楽になりました。

  • Implementation missing for UnicodeString::LastChar

    以前にこのブログに書いた件です。(UnicodeString#LastChar()がILINK32エラー)
    UnicodeString::LastCharが使えるようになりました。

  • Unresolved externals when linking pngimage.obj

    C++Builder Tipsに書いた「C++Builder2009でPNGを扱うとコンパイルエラーになる問題の解決方法」です。
    「[ILINK32 エラー] Error: 未解決の外部参照 'Pnglang::_EPngInvalidCRCText' が …」が発生しなくなりました。

  • DynamicArray template class does not use VCL memory management

    次のコードでCodeGuardがエラーを報告しないようになりました。

    UnicodeString a = "abc";
    a.BytesOf();
    

そのほかにもいろいろと気になる修正があります。

  • Lost focus after TOpenDialog when MainFormOnTaskBar is set
  • W8114: __DATE__ macro returns invalid string under Japanese locale
  • The UnicodeString( char *, int ) constructor is broken
  • C++ typedefs generated from Delphi class aliases are not correct
  • C++ Unit Test TTestCase constructors are incorrect
  • Length calculation of the UTF32ToUTF16 function is incorrect

Update 4 ではデータベース関連の問題が修正されたようです。
データベース関連の機能はあまり使っていないので、詳細は見ていません。

Boostライブラリアップデートですが、どこが変わったのでしょうか?

#include <boost/version.hpp>
std::cout << BOOST_LIB_VERSION << std::endl; //=>1_35

なので、バージョンは変わっていないようです。
使えるライブラリが増えたのでしょうか?

2009年5月29日 追記

Update2 時点のものと更新後の boost ヘッダの diff を取ってみましたが、残念ながら新たなライブラリは追加されてないようです。

C++Builder2009 Boost Update の更新内容

うーん、残念です。

英語サイト(Release Notes for RAD Studio 2009 Update Pack (Updates 3, 4, and Boost)の方にも、

What are the changes for the Boost library update?

というコメントがありました。
みんな、不思議に思っているのかも。

2009年05月31日

IDE Fix Pack 2009 2.6リリース

RAD Studio 2009 Update 3に対応した「IDE Fix Pack 2009 2.6」がリリースされています。

対応が早いですね。

2009年06月13日

数値と文字列の相互変換を行う boost::lexical_cast

C++Builder 2009からBoostが標準添付されて、とても使いやすくなりました。

boost::lexical_castは、数値と文字列の相互変換を行う関数です。

型によって関数を使い分ける必要がなく、同じように書くことができます。

#include <string>
#include <boost/lexical_cast.hpp>
using namespace std;

int n = boost::lexical_cast<int>("3");
string s = boost::lexical_cast<string>(123);

C++テンプレートテクニック』には戻り値の型を推論することで、型の指定を不要にする方法が紹介されています。
次のようなコードを書くことができます。

int n = lexical("3");
string s = lexical(123);

詳しくは『C++テンプレートテクニック』をご覧下さい。

2009年06月15日

正規表現ライブラリ std::tr1::regex

C++ TR1には正規表現ライブラリが含まれます。

C++Builder 2009で試してみたところ、std::tr1::regexを使用できました。

#include <iostream>
#include <regex>
#include <string>

//リンクのタグからURLを取得する
std::tr1::regex expression("href=\".*\"");
std::string tag = "<a href=\"http://www.gesoruce.jp/weblog/\">山本隆の開発日誌</a>";
std::tr1::smatch match;

if (std::tr1::regex_search(tag, match, expression))
{
  std::cout << "url=" << match.str(1) << std::endl;
}

2009年06月18日

__propertyの使い方

__propertyの使い方を整理してみた。

基本形

__property 型 名前 = { プロパティ属性 = データ/関数, … }

プロパティ属性の種類

  • read
    読み込み時に参照する変数/関数

  • write
    書き込み時に参照する変数/関数

  • stored
    ture/false/論理値を返す関数
    コンポーネントとして保存するかどうか

  • default
    デフォルトの値

  • nodefault

  • index
    リストや配列のインデックス。

例:メンバ変数を直接読み書き

class TMyClass {
private:
  int FCount;
public:
  TMyClass() : FCount(0) {};
  __property int Count = { read = FCount, write=FCount }; //メンバ変数を指定
};

TMyClass obj;
obj.Count = 123;
cout << obj.Count << endl; //=>123

例:メンバ関数を通して読み書き

class TMyClass {
private:
  int FCount;
  void SetCount(int Count);
  int GetCount();
public:
  TMyClass() : FCount(0) {};
  __property int Count = { read = FCount, write=FCount }; //メンバ関数を指定
};

例:読み込み専用

class TMyClass {
private:
  int FCount;
public:
  TMyClass() : FCount(0) {};
  __property int Count = { read = FCount }; //readのみ
};

例:書き込み専用

class TMyClass {
private:
  int FCount;
public:
  TMyClass() : FCount(0) {};
  __property int Count = { write=FCount }; //writeのみ
};

例:静的プロパティ

class TMyClass {
public:
  static int FCount;
  static int GetCount();
  static void SetCount(int Count);
  __property int Count = { read = GetCount, write = SetCount }; //静的プロパティ
};

cout << TMyClass::Count << endl;

例:配列

class TMyClass {
private:
  int FDate[3];
  int GetDate(int Index) { return FDate[Index]; }
public:
  TMyClass() { FDate[0] = 2009; FDate[1] = 1; FDate[2] = 2; };
  __property int Year = { read=GetDate, index = 0 };
  __property int Month = { read=GetDate, index = 1 };
  __property int Day = { read=GetDate, index = 2 };
};

TMyClass obj;
cout << obj.Year << endl; //=> 2009
cout << obj.Month << endl; //=> 1
cout << obj.Day << endl; //=> 2

例:リスト

class TMyClass {
private:
  unique_ptr<TStringList> FDate;
  UnicodeString GetDate(int Index) { return FDate->Strings[Index]; }
public:
  TMyClass() : FDate(new TStringList()) {
    FDate->Add("2009"); FDate->Add("1"); FDate->Add("2");
  };
  __property UnicodeString Year = { read=GetDate, index = 0 };
  __property UnicodeString Month = { read=GetDate, index = 1 };
  __property UnicodeString Day = { read=GetDate, index = 2 };
};

TMyClass obj;
wcout << obj.Year.c_str() << endl; //=> 2009
wcout << obj.Month.c_str() << endl; //=> 1
wcout << obj.Day.c_str() << endl; //=> 2

2009年07月02日

C++Builderの自動変数

C++Builderの自動変数について。

C++の自動変数は、自動的にコンストラクタとデストラクタが呼ばれます。

{
  TTest test; //コンストラクタが呼ばれる
} //スコープを抜けるとデストラクタが呼ばれる

一方、newで生成したオブジェクトは、deleteしなければなりません。

{
  TTest* test = new TTest(); //コンストラクタが呼ばれる
} //スコープを抜けてもデストラクタは呼ばれない。deleteしないとメモリリーク。

C++Builderでは、TObjectを継承しているクラスは、自動変数として使用できません。

TStringList list; //コンパイルエラー
TStringList* list = new TStringList(); //OK

std::unique_ptrを使えば、TObjectを継承しているクラスも自動変数のように扱うことができます。
(古いC++Builderにはstd::unique_ptrはありませんので、std::auto_ptrを使います。)

#include <memory>
{
  std::unique_ptr<TStringList> list(new TStringList());  //コンストラクタが呼ばれる
} //スコープを抜けるとデストラクタが呼ばれる

2009年07月08日

C++テンプレートテクニック

C++テンプレートテクニック』を読みました。

C++テンプレートテクニック』は今までありそうでなかったC++のテンプレートの入門書です。
Modern C++ Design』のような高度な内容の本はありましたが、テンプレートの入門書というのはこの本が初めてではないでしょうか。

目次
Chapter 1 テンプレート前史
Chapter 2 テンプレートの基礎
Chapter 3 Generic Programmingの基礎
Chapter 4 テンプレートメタプログラミング
Chapter 5 SFINAE
Chapter 6 ポリシー
Chapter 7 Type Erasure
Chapter 8 CRTP
Chapter 9 テンプレート型変換演算子
Chapter 10 Expression Template
Chapter 11 Extension Member Function
Chapter 12 C++0xにおけるテンプレート
Appendix 参考資料

Chapter 1~Chapter 3までがテンプレートの入門編。
Chapter 4以降からテンプレートを使ったテクニックを紹介。

テンプレートの基礎知識を体系的にまとめたものを見た記憶がありませんので、 Chapter 1~Chapter 3の入門編はテンプレート初心者にとっては貴重な情報だと思いました。

私がおもしろいと感じたのはChapter 4以降。
プロパティや戻り値の型によるオーバーロード、拡張メンバ(既存クラスにメンバ関数を追加)など、今まで不可能と思っていたことを実現するテクニックなど。

テンプレートの入門書としてはもちろん、復習と知識の更新にもお薦めの一冊です。

C++テンプレートテクニック』を読んだら次は『Modern C++ Design』に挑戦ですね。

2009年07月12日

Boost C++ Librariesプログラミング

Boost C++ Librariesプログラミング

図書館で借りて読みました。
良い本でしたので購入する予定です。

C++ Builder 2009にはBoost C++ Librariesのバージョン1.35が入っています。
Boost C++ Librariesプログラミング 第2版』ではBoost C++ Libraries バージョン1.35に対応しました。
C++ Builder 2009をきっかけに本格的にBoostを学ぼうと思ったら、まさにぴったりの本です。

この本ではライブラリの説明に説明文に加えて、動作可能なソースコードがついているので、理解しやすいと感じました。
Boostが初めての人にはBoostの学習書として、すでにBoostを使っている人には日本語のリファレンスマニュアルとして使える優れた本だと思います。

2009年07月13日

C++Builder2009で文字コードを変換する

C++Builder2009で簡単に文字コードを変換する方法はないかと調べてみると、「MECSUtils」が見つかりました。

Unicode文字列をAnsi文字列へ変換するConvertUnicodeToMultiByte関数と、Ansi文字列を他のコードページのAnsi文字列へ変換するConvertString関数を使ってみたところ、C++Builder2009でも問題なく使用することができました。

C++Builder2009での使い方を簡単にまとめました。

このような便利なライブラリを公開してくださったDEKO様に感謝申し上げます。

2009年07月20日

C++Builder Tipsに「C++Builder2009とIndy10でメール送信」を追加しました

C++Builder Tipsに「C++Builder2009とIndy10でメール送信」を追加しました。

このTipsは以下のページを参考にさせていただきました。

間違い等がありましたら、教えていただけると喜びます。

2009年07月23日

EurekaLog導入後

約3ヶ月前に「EurekaLog導入レポート」の記事を書いてから、その後について。

最近1週間で立て続けに3件のバグ報告をいただきました。
それで、あらためてEurekaLogの偉大さを確認しました。

EurekaLogのエラーログには、問題の発生したソースコードの場所が記録されています。
おかげで問題の解決がとてもスムーズにできました。

しかもバージョン管理の機能があるのか、ソフトウェアをリリースした時点のソースコードを見ることができます。
リリース後にソースコードを修正していても、ちゃんと問題の箇所を確認することができます。

ということで、近日中に「D2 メール自動データベース変換ソフト」の修正版を公開します。
皆様のご意見やご要望・バグ方向をお待ちしています。

2009年07月30日

mlang.dllのConvertINetString関数を使った文字コードの変換

mlang.dllのConvertINetString関数を使った文字コードを変換するサンプル

C++ Builder 2009を使いました。
_TCHARのマップ先は「wchar_t」に設定しています。

本題に入る前に前準備として、DLLのリソース管理クラス(RAII)を用意します。
このクラスTDllはデストラクタでFreeLibraryを行うので、DLLのハンドルの解放のし忘れを防ぐことができます。

/**
 * DLLのリソース管理(RAII)
 */
class TDll
{
public:
  TDll(const UnicodeString& DllName)
  {
    FDll = LoadLibrary(DllName.c_str());
  }
  ~TDll()
  {
    if (!FDll) FreeLibrary(FDll);
  }
  operator bool() const
  {
    return FDll;
  }
  operator HMODULE() const
  {
    return FDll;
  }
private:
  HMODULE FDll;
};

ConvertINetStringのtypedefを定義します。
なくてもいいのですが、あるとソースコートが読みやすくなります。

typedef HRESULT WINAPI (*TConvertINetString)(LPDWORD lpdwMode, DWORD dwSrcEncoding, DWORD dwDstEncoding, LPCSTR lpSrcStr, LPINT lpnSrcSize, LPBYTE lpDstStr, LPINT lpnDstSize);

mlang.dllを読み込みます。
読み込みに失敗したときはエラーメッセージを表示します。

TDll dll("mlang.dll");
if (!dll)
{
  std::puts("DLLの読み込みに失敗しました。");
  return 0;
}

ConvertINetString関数のアドレスを取得します。
失敗したときはエラーメッセージを表示します。

//関数のアドレスの取得
FARPROC proc = GetProcAddress(dll, "ConvertINetString");
if (!proc)
{
  std::puts("関数のアドレスの取得に失敗しました。");
  return 0;
}
TConvertINetString ConvertINetString = reinterpret_cast<TConvertINetString>(proc);

ConvertINetString関数に渡す変数を用意します。
今回のサンプルではShift_JISをEUC-JPに変換します。

DWORD mode = 0;
AnsiStringT<932> sjis = "あいうえおかきくけこさしすせそたちつてと";
int sjisLen = sjis.Length();
int eucLen = sjisLen * 2 + 1;
boost::scoped_array<char> euc(new char[eucLen]);
ZeroMemory(euc.get(), eucLen);

ConvertINetString関数を呼び出して変換します。
変換に失敗したときはエラーメッセージを表示します。

//変換
HRESULT result = ConvertINetString(&mode, 932, 51932, sjis.c_str(), &sjisLen, euc.get(), &eucLen);
//変換結果の検証
if (result != S_OK || eucLen == 0)
{
  std::puts("変換に失敗しました。");
  return 0;
}

というのが一連の流れになります。

以下のコードはShift_JIS(コードページ932)の文字列をEUC-JP(コードページ51932)に変換します。

#include <tchar.h>
#include <iostream>
#include <memory>
#include <boost/scoped_array.hpp>

int _tmain(int argc, _TCHAR* argv[])
{
  typedef HRESULT WINAPI (*TConvertINetString)(LPDWORD lpdwMode, DWORD dwSrcEncoding, DWORD dwDstEncoding, LPCSTR lpSrcStr, LPINT lpnSrcSize, LPBYTE lpDstStr, LPINT lpnDstSize);

  //DLLの読み込み
  TDll dll("mlang.dll");
  if (!dll)
  {
    std::puts("DLLの読み込みに失敗しました。");
    return 0;
  }
  //関数のアドレスの取得
  FARPROC proc = GetProcAddress(dll, "ConvertINetString");
  if (!proc)
  {
    std::puts("関数のアドレスの取得に失敗しました。");
    return 0;
  }
  TConvertINetString ConvertINetString = reinterpret_cast<TConvertINetString>(proc);

  //変換準備
  DWORD mode = 0;
  AnsiStringT<932> sjis = "あいうえおかきくけこさしすせそたちつてと";
  int sjisLen = sjis.Length();
  int eucLen = sjisLen * 2 + 1;
  boost::scoped_array<char> euc(new char[eucLen]);
  ZeroMemory(euc.get(), eucLen);

  //変換
  HRESULT result = ConvertINetString(&mode, 932, 51932, sjis.c_str(), &sjisLen, euc.get(), &eucLen);
  //変換結果の検証
  if (result != S_OK || eucLen == 0)
  {
    std::puts("変換に失敗しました。");
    return 0;
  }
  //ファイルに保存
  std::unique_ptr<TFileStream> fs(new TFileStream("C:\\test.txt", fmCreate));
  fs->WriteBuffer(euc.get(), eucLen);

  return 0;
}

2009年08月01日

Delphi/C++Builder関連のブログ「[某所][1]」様が終了されるそうです

Delphi/C++Builder関連のブログ「某所」様が終了されるそうです

某所は終了します。
長年のご愛読ありがとうございました。

某所 終了のお知らせ

貴重な情報源でしたので、とても残念です。
長い間ありがとうございました。

2009年08月03日

VCL for the Web / IntraWebのアップデート

この記事では、「VCL for the Web / IntraWeb」の最新アップデートを入手する方法について説明します。

アップデートのお知らせ: VCL for the Web 10.0.17 for Delphi 2009 and C++Builder 2009

VCL for the Web / IntraWebは、自動アップデートされないんですね。
自分で最新版をダウンロードしてインストールする必要があるようです。

第14回 エンバカデロ・デベロッパーキャンプに参加します

第14回 エンバカデロ・デベロッパーキャンプが9月2日に大阪で開催されます。

ということで、参加申し込みしました。

「マイコンポーネント大全」セッション講演者を募集しているようです。よかったらどうぞ。

皆さんは、自作コンポーネントを活用していますか?コンポーネントの作成は、Delphi / C++Builderのアプリケーション開発における再利用性を劇的に高めます。このセッションは、さまざまな自作コンポーネントをショートプレゼンテーションで紹介し、皆さんの開発のヒントにしていただこうという企画です。 聴講いただくのはもちろん、ユニークな自作コンポーネントをお持ちの方は、ぜひプレゼンテーションにも挑戦してください。

第14回 エンバカデロ・デベロッパーキャンプ「マイコンポーネント大全」セッション講演者募集

過去の関西での開催を調べてみると、年に一回、この時期に大阪で開催されているのですね。

2009年08月06日

Delphi/C++Builder 2010 8月25日発売

エンバカデロ・テクノロジーズは,同社の主力開発ツールの新版「Embarcadero Delphi 2010」「同C++Builder 2010」「同RAD Studio 2010」(前者二つとDelphi Prism 2010を含む)を,2009年8月25日にダウンロード可能にすると明らかにした。

Windows 7対応のDelphi/C++Builder新版が8月25日発売へ

Delphi/C++Builder 2010が8月25日に発売されるとのこと。
もうすぐですね。驚きました。

Delphi/C++Builder 2010の変更点をいくつか取り上げると、

  • Windows7の新機能(Direct2D、マルチタッチなど)に対応
  • 統合開発環境(IDE)をより使いやすく改善
  • Firebird対応
  • RTTIを可能に(Delphi)
  • 「#pragma once」のサポート、セキュリティ強化関数(C++Builder)

IDEの新機能については、Owl's perspectiveさんの記事からたどるといいでしょう。

価格がどうなるかが気になるところです。

2009年08月08日

C++Builder2009でMSHTMLを使ってHTMLを解析する

MSHTML COMコンポーネントという存在を知ったので、試しにC++Builder2009で使ってみました。
このCOMコンポーネントを使うと、HTMLの解析が簡単にできそうです。

Variant htmlfile = Variant::CreateObject("htmlfile");
WideString html = "<html><head><title>タイトル</title></head><body>ボディ</body></html>";
htmlfile.OleFunction("write", html);

UnicodeString title = htmlfile.OlePropertyGet("title"); //=>タイトル

Variant htmlElement = htmlfile.OlePropertyGet("body");
UnicodeString body = htmlElement.OlePropertyGet("innerHtml"); //=>ボディ

MSHTML COMコンポーネントは多機能なので、もっといろんなことができそうです。

MSHTMLとは関係のない話ですが、「WideString html = …」の部分を「UnicodeString html = …」とすると駄目でした。
自動的に型変換されると思ったのですが。
このあたりの挙動について、理解不足のようです。

2009年08月09日

Delphi 2010 / C++Builder 2010情報

IDEとDelphi 2010については「DEKOのざつだん。」さんがよくまとまっています。
最後にあるリンク集も参考になります。

C++Builder 2010については、「C++Builder好きの秘密基地」さんの「C++Builder 2010情報解禁」の記事が参考になります。

IDEの変更点は、使い勝手の向上がポイントのように感じました。

C++Builder 2010も嬉しい機能が追加されていますね。

  • C++クラスエクスプローラーの「復活」
  • デバッガでstd::stringとかstd::wstringは「文字列」として表示される。
  • メモリマネージャーの変更

などなど。

Delphi/C++Builderの情報がこれだけたくさん入手できるようになったことが何よりすばらしいことだと思います。

2009年08月24日

開発環境Delphiに感染するウィルスInduc

開発環境Delphiに感染するウィルスInducが話題になっているようだ。

正直なところ、全く話題に上らないと予想していた。
というのも、このウィルスに関係のある人はほとんどいないからだ。

このウィルスに感染する可能性のある人は、Delphiのバージョン4/5/6/7のユーザに限られている。
Delphiを持っていない人は関係ない。
該当バージョン以外のDelphiも感染しない。

Delphi 4~7というのは5世代以上も前のバージョンである。
そんなに古いバージョンを使用している人はほとんどいないだろう。

  • 2008年 Delphi 2009
  • 2007年 Delphi 2007
  • 2005年 Borland Developer Studio 2006
  • 2004年 Delphi 2005
  • 2003年 Delphi 8
  • 2002年 Delphi 7
  • 2001年 Delphi 6
  • 1999年 Delphi 5
  • 1998年 Delphi 4

実際のところ、ほとんど被害はないのではないだろうか。

2009年08月25日

Delphi 2010 / C++Builder 2010 価格が発表されました

Delphi 2010 / C++Builder 2010の価格がようやく発表されました。

Delphi 2010/C++Builder 2010のProfessional版の価格を見ると、

  • 新規購入価格(パッケージ版)
    • Delphi 2010 Professional(ライセンス + メディア)¥98,000(税込¥102,900)
    • C++Builder 2010 Professional(ライセンス + メディア)¥98,000(税込¥102,900)
  • 新規購入価格(ダウンロード版)
    • Delphi 2010 Professional ESD(ライセンスのみ)¥94,000(税込¥98,700)
    • C++Builder 2010 Professional ESD (ライセンスのみ)¥94,000(税込¥98,700)
  • バージョンアップ価格(パッケージ版)
    • Delphi 2010 Professional(ライセンス + メディア)¥46,000(税込¥48,300)
    • C++Builder 2010 Professional(ライセンス + メディア)¥46,000(税込¥48,300)
  • バージョンアップ価格(ダウンロード版)
    • Delphi 2010 Professional ESD (ライセンスのみ)¥42,000(税込¥44,100)
    • C++Builder 2010 Professional ESD (ライセンスのみ)¥42,000(税込¥44,100)

なお、Delphi 2009/C++Builder 2009のユーザーには、期間限定の特別バージョンアップがあるそうです。

RAD Studio 2009、Delphi 2009、C++Builder 2009のいずれかの製品をお持ちのユーザーを対象に、通常のバージョンアップ価格よりもさらにお得な価格でダウンロード版(ESD)をお求めいただけるキャンペーンを、2009年9月25日までの期間限定で実施します。 詳細は、エンバカデロ・テクノロジーズ インフォメーションサービスセンターまでお問い合わせください。

他にも、Windows 7が半額になるキャンペーンもあります。

翔泳社が運営するSEshopでは、RAD Studio 2010、Delphi 2010、C++Builder 2010 のバージョンアップ版と同時にWindows 7 アップグレード版を購入すると、Windows 7が半額になるキャンペーンを実施します。詳細は、http://www.seshop.com/embarcadero/ をご覧ください。

2009年08月29日

TEditor for D2009でATOKの問題が直っていた

掲示板の[TEditor]Delphi2009用 改造版2.48のスレッドの21番目のコメントで、 ATOKの変換候補のウィンドウサイズ・位置がおかしいという報告があります。

私もこの現象を確認していましたので、修正を試みようと思い、 TEditor for D2009を導入しました。

結論から言うと、ATOKの問題が発生しませんでした。
ちゃんと問題なく使うことができます。

掲示板の方には修正したというコメントはないようですが、修正していただけたのでしょうか。 もしかすると、その後にリリースされたDelphi 2009/C++Builder 2009のUpdate 3/4の影響でしょうか。

何はともあれ、ATOKでもちゃんと使えることが確認できました。

ちなみに、開発環境はC++Builder 2009です。
IDEにコンポーネントをインストールせず、includeして実行時に生成して使っています。
Windows XPとWindows 2000で動作を確認しました。

他に気になることは、コンパイル時の警告です。こんなの↓がたくさん出ます。

[DCC 警告] heClasses.pas(24): W1050 set 式で WideChar がバイト char に縮小されました。'CharInSet' 関数を 'SysUtils' ユニットで使用することを検討してください。

これはきっと、Delphi/C++Builder 2007以前のバージョンでも使えるようにとの配慮からですよね。

2009年09月07日

グリッドの特定列をボタンにする

第14回エンバカデロ・デベロッパーキャンプセッションT7自己フォロー(グリッドの特定列をボタンにする)より。

ボタンを上手に使っているところが参考になります。
TButtonにイベント処理を任せることで、描画処理がシンプルになっています。
実際にはもう少しコードが加わるとは思いますが。

ちなみに私も、コンポーネントを作成しない派です。

以前に使っていたフリーのコンポーネントが、IDEをバージョンアップしたら、インストールできなくなったことがあります。
当時のコンパイラに不具合があって、DelphiのコードをC++Builder用に変換するときにエラーになったのだと思います。

C++Builderは、DelphiのコードをC++Builder用に変換する作業が加わるため、Delphiよりもトラブルに遭遇する確率が高いと思います。

そのため、C++BuilderユーザーはDelphiユーザーよりも、 コンポーネントのインストールに抵抗があるのかもしれません。
# 私だけかもしれませんが。

ちょっと気になった点があります。

// テーマサービスの取得
TThemeServices* pTheme = ThemeServices();
if (pTheme) {
  // テーマが有効

とありますが、Themes.pasを見ると、ThemeServices()は常に有効なオブジェクトを返すようです。

function ThemeServices: TThemeServices;
begin
  if InternalServices = nil then
    InternalServices := ThemeServicesClass.Create;
  Result := InternalServices;
end;

テーマ (視覚スタイル) を使用しているかどうかを調べるには、 TThemeServicesのThemesEnabledプロパティを使った方がいいような気がします。

2009年09月11日

UnicodeStringをJIS(ISO-2022-JP)に変換すると文字が減る

C++Builder 2009で確認したところ、UnicodeStringをJISコードに変換すると文字が減るようだ。

//UTF-8
AnsiStringT<65001> utf8 = L"文字コード"; //=> 文字コード

//Shift_JIS
AnsiStringT<932> sjis = L"文字コード"; //=> 文字コード

//EUC_JP
AnsiStringT<20932> euc = L"文字コード"; //=> 文字コード

//x-mac-japanese
AnsiStringT<10001> mac = L"文字コード"; //=> 文字コード

//JISコードの時は文字が減る
//iso-2022-jp(JIS)
AnsiStringT<50220> jis1 = L"文字コード"; //=> 文字コ

//csISO2022JP (JIS 1 バイト カタカナ可)
AnsiStringT<50220> jis2 = L"文字コード"; //=> 文字コ

//iso-2022-jp (JIS 1 バイト カタカナ可 - SO/SI)
AnsiStringT<50220> jis3 = L"文字コード"; //=> 文字コ

どうやらマルチバイト文字が混ざると問題になるみたい。

AnsiStringT<50220> jis1 = L"12345";
UnicodeString s1 = jis1; //12345

AnsiStringT<50220> jis2 = L"abc";
UnicodeString s2 = jis2; //abc

AnsiStringT<50220> jis3 = L"あいう";
UnicodeString s3 = jis3; //あ

もう少し調べてみたい。

追記
C++Builder 2009で確認しました。

2009年09月15日

Windows 2000でWideCharToMultiByte関数を使うと、ISO-2022-JPのバイト数を間違う

前回のUnicodeStringをJIS(ISO-2022-JP)に変換すると文字が減るの続き。

UnicodeStringをJISコードに変換すると文字が減るという問題です。

AnsiStringT<50220> jis1 = L"文字コード"; //=> 文字コ

Windows 2000におけるWin32APIのWideCharToMultiByte関数の動作がおかしいようです。
WideCharToMultiByte関数は、ISO-2022-JPに変換したときの正しいバイト数を返しません。

なお、Windows XP以降では問題ありませんでした。

次のソースコードは、ユニコード文字列をJIS(ISO-2022-JP)コードに変換したときのバイト数を取得する処理です。

const int codepage = 50220;
UnicodeString WCharSource = L"abcテスト";
int SrcChars = WCharSource.Length();
int DestBytes = WideCharToMultiByte(codepage, 0, WCharSource.c_str(), SrcChars, NULL, 0, NULL, NULL);

WideCharToMultiByte関数は、変換後の文字列を受け取るために必要なバッファのサイズ(バイト数)を返します。

上のコードでは12バイト必要ですが、この関数は「9」を返します。3バイト足りません。

3バイトというと、エスケープシーケンスが思い当たります。
JISコードではエスケープシーケンスを用いて、文字集合を切り替えます。

エスケープシーケンスの計算が正しいか試してみました。

const int codepage = 50222;
UnicodeString WCharSource = L"abcテストabc";
int SrcChars = WCharSource.Length();
int DestBytes = WideCharToMultiByte(codepage, 0, WCharSource.c_str(), SrcChars, NULL, 0, NULL, NULL);

WideCharToMultiByte関数は「12」を返します。6バイト足りません。

const int codepage = 50220;
UnicodeString WCharSource = L"abcテストabcテスト";
int SrcChars = WCharSource.Length();
int DestBytes = WideCharToMultiByte(codepage, 0, WCharSource.c_str(), SrcChars, NULL, 0, NULL, NULL);

WideCharToMultiByte関数は「18」を返します。9バイト足りません。
やはりエスケープシーケンスが考慮されていないようです。

原因がわかりましたので、対策も簡単です。
Windows2000でISO-2022-JPに変換するときはバッファを多めにとればいいのです。

さて、問題のUnicodeStringからAnsiStringT<50220>への変換ですが、 System.pasの_LStrFromPWCharLen関数かCharFromWChar関数で、 文字のバイト数を取得するときに、 OSがWindows2000かつコードページが50220ならば、 バッファのバイト数を増やして文字を取得し、取得後に不要な部分を切り捨てればいいと思います。
とても汚いコードになりそうです。

調べてみると、同じような問題に遭遇している方がいらっしゃいました。
komatの古往今来: ALM2Thunderbird 1.0の動作OSについて」では、Windows 2000環境で変換に失敗するという問題に遭遇されています。
「Windows API関数のWideCharToMultiByteとMultiByteToWideCharの動作に問題がある」とありますが、おそらく同じ問題でしょう。

2009年09月16日

Windows XPでWideCharToMultiByte関数を使い、ISO-2022-JPに変換するときの注意点

前回の記事「Windows 2000でWideCharToMultiByte関数を使うと、ISO-2022-JPのバイト数を間違う」では、 Windows 2000におけるWideCharToMultiByte関数の問題を調べました。

今回はWideCharToMultiByte関数のWindows XPでの問題です。

次のようにユニコード文字列をISO-2022-JPに変換した場合、

AnsiStringT<50220> jis = L"abcあいう";

Windows XPでは変数jisのバイトコードは「abc<ESC>あいう」のようになります。
<ESC>はエスケープシーケンスを表しています。
なお、Windows 7では「abc<ESC>あいう<ESC>」のようになります。
※UnicodeStringからAnsiStringTへの変換は、内部でWideCharToMultiByte関数を使用しています。

これが問題になるのは次のようなコードの時です。

AnsiStringT<50220> jis = L"abcあいう";
AnsiStringT<50220> jis2 = jis + jis;

Windows XPでは変数jis2のバイトコードは「abc<ESC>あいうabc<ESC>あいう」のようになってしまいます。
「あいうabc」の間に<ESC>がないため、文字化けが発生します。

なお、Windows 7では「abc<ESC>あいう<ESC>abc<ESC>あいう<ESC>」となり問題は発生しません。

Windows XPでも、WideCharToMultiByte関数を使ってISO-2022-JPに変換するときには注意が必要です。

今回のコードは、C++Builder2010で確認しました。

2009年09月17日

C++Builder 2010のIndyでメール送信について

C++Builder 2010のTIdMessageは、プロパティのCharSetとContentTransferEncodingから、適切に文字コードを変換してエンコードしてくれるようになった。
これによって、C++Builder 2009のときのように、ISO-2022-JPに変換したバイト列をUnicodeStringに代入するという奇妙なコードが不要になった。

しかし、これによって新たに問題が生じている。

一つはWindows 2000におけるWideCharToMultiByte関数の不具合
これによって、Windows 2000ではISO-2022-JPに変換した文字は文字化けする。

もう一つは波ダッシュ問題
WideCharToMultiByte関数による変換では波ダッシュ問題が発生する。

この2つの問題は、Win32APIのWideCharToMultiByte関数が原因なので、Indyの不具合とは言えないと思う。

C++Builder 2009のときには、プログラム側で文字コードを変換しBase64エンコードをしなければならなかった。
一方で、プログラム側でこれらの問題に対応する余地があったとも言える。
C++Builder 2010ではライブラリ側が自動で変換するために、プログラム側では打つ手が見当たらない。

次の問題は、Indyのバグかもしれない。
プロジェクトオプションで、「実行時パッケージを使って構築」のチェックを外すと、 「ERangeError(メッセージ'範囲チェックエラー)」が送出される。

最後に問題が発生するサンプルコードを示す。

std::unique_ptr<TIdMessage> msg(new TIdMessage(NULL));
msg->CharSet = "ISO-2022-JP";
msg->ContentTransferEncoding = "base64";
msg->ContentType = "text/plain; charset=\"ISO-2022-JP\"";
msg->Subject = L"メールの件名"; //=> Windows2000なら件名が消える
msg->Body->Text = L"10~20"; //=> ~が文字化けする
msg->SaveToFile(ChangeFileExt(Application->ExeName, ".eml"), false);

うまい解決策があったら教えて下さい。

2009年09月19日

UnicodeStringからJISコードへの変換時に起こる波ダッシュ問題の回避策

UnicodeStringをJISコードに変換すると「~」が文字化けします。

UnicodeString uni = L"10~20";
AnsiStringT<50220> jis = uni; //=> 「~」が文字化けする

DEKOさんがこの問題の回避策を作成してくださりました

MECSUtils ver1.30で導入されたMecsMappingFixJA関数を使うと、 波ダッシュ問題を回避することができます。

#include "MECSUtils.hpp"

UnicodeString uni = L"10~20";
uni = Mecsutils::MecsMappingFixJA(uni);
AnsiStringT<50220> jis = uni; //=>「10~20」

すばらしい。

ありがとうございました。

追記:
MECSUtils 1.33で関数名が変更されました。
UnicodeStringの波ダッシュ問題の回避策のまとめをご覧下さい。

2009年09月24日

文字コードがJISやEUC-JPの文字列をUnicodeStringに変換したときの波ダッシュ問題

UnicodeStringをJISやEUC-JPに変換するときの波ダッシュ問題は、 MECSUtilsMecsMappingFixJA関数を使うことで解決します。

逆にJISやEUC-JPの文字列をUnicodeStringに変換したときの波ダッシュ問題を解決するための関数として、 MecsMappingFixJAの逆の処理をする関数があると便利だと思います。

処理は単純にMecsMappingFixJAの反対の変換を行えば良さそうでした。

JISやEUC-JPの文字列をUnicodeStringに変換した後、この関数を使用します。

UnicodeString uni = Mecsutils::AnsiToUTF16(raw, CodePage);
uni = Mecsutils::MecsMappingFixJA2(uni);

MECSUtilsに次の関数を追加します。

//JISやEUC-JPをUnicodeStringに変換したときの波ダッシュ問題を解決する関数
function MecsMappingFixJA2(const S: UnicodeString): UnicodeString; overload;
var
  i: Integer;
begin
  result := S;
  for i:=1 to Length(result) do
    case result[i] of
      #$2016:
        result[i] := #$2225;
      #$2212:
        result[i] := #$FF0D;
      #$301C:
        result[i] := #$FF5E;
      #$00A2:
        result[i] := #$FFE0;
      #$00A3:
        result[i] := #$FFE1;
      #$00AC:
        result[i] := #$FFE2;
    end;
end;

関数名はもう少し工夫した方がいいでしょう。

追記:
MECSUtils 1.33で導入していただきました。
UnicodeStringの波ダッシュ問題の回避策のまとめをご覧下さい。

2009年09月25日

RAD Studio 2010 Update 1

RAD Studio 2010 Update 1がリリースされました。

RAD Studio 2010 Update 1 リリースノートによると、今回の修正は

  • 製品が All-Access がインストールされている環境で正しく動作するように修正されました。
  • ネットワークライセンスを含むライセンスに関係する重要な修正が含まれました。
  • 将来のアップデートが正しく適用できるようにするための重要な修正が含まれました。

慌ててアップデートする必要はなさそうです。

UnicodeStringの波ダッシュ問題の回避策のまとめ

UnicodeStringをJISやEUC-JPに変換すると「~」が文字化けします。

UnicodeString uni = L"10~20";
AnsiStringT<50220> jis = uni; //=> 「~」が文字化けする

反対に、JISやEUC-JPをUnicodeStringに変換しても「~」が文字化けします。
# ちなみにShift-JISでは文字化けしません。
(修正)Shift-JISでも問題が発生するようです。

この問題を回避する関数がMECSUtilsに用意されています。
MecsMappingFix_UnicodeToJISX0208 関数MecsMappingFix_JISX0208ToUnicode 関数です。
# MECSUtils ver1.33から関数名が変わりました。

UnicodeStringをJISやEUC-JPに変換するときは、MecsMappingFix_UnicodeToJISX0208 関数を使用してから、変換します。

#include "MECSUtils.hpp"

UnicodeString uni = L"10~20";
uni = Mecsutils::MecsMappingFix_UnicodeToJISX0208(uni);
AnsiStringT<50220> jis = uni; //=>「10~20」

UnicodeStringをShift-JISに変換するときは、MecsMappingFix_UnicodeToCP932 関数を使います。

#include "MECSUtils.hpp"

UnicodeString uni = …
uni = Mecsutils::MecsMappingFix_UnicodeToCP932(uni);
AnsiStringT<932> sjis = uni;

反対に、JISやEUC-JPをUnicodeStringに変換するときは、UnicodeStringに変換してから、MecsMappingFix_JISX0208ToUnicode関数を使用します。

#include "MECSUtils.hpp"

UnicodeString uni = Mecsutils::AnsiToUTF16(raw, CodePage);
uni = Mecsutils::MecsMappingFix_JISX0208ToUnicode(uni);

Shift-JISをUnicodeStringに変換するときは、MecsMappingFix_CP932ToUnicode 関数を使います。

#include "MECSUtils.hpp"

UnicodeString uni = Mecsutils::AnsiToUTF16(raw, CodePage);
uni = Mecsutils::MecsMappingFix_CP932ToUnicode(uni);

各関数についての詳細は、MECSUtils リファレンスに丁寧な説明がありますので、そちらをご覧下さい。

追記
MECSUtils 1.35でCP932(Shift-JIS)用の関数も用意されました。
Shift-JISに関する記述を修正しました。

DEKOのざつだん。」によると、私が思っていたよりも複雑な問題だったようです。
DEKOさんの丁寧な対応に感謝します。

About C++Builder

ブログ「山本隆の開発日誌」のカテゴリ「C++Builder」に投稿されたすべてのエントリーのアーカイブのページです。過去のものから新しいものへ順番に並んでいます。

次のカテゴリはFirebirdです。

他にも多くのエントリーがあります。メインページアーカイブページも見てください。

Powered by
Movable Type 3.35