2012年2月9日

例外チェーン

Delphi 2009の新機能の一つに例外チェーン、あるいはネストした例外オブジェクトというものがあります。従来の(Delphi 2007およびそれ以前の)例外処理では、発生した例外をtry...except文で捕捉した場合に、そのままendに到達することで例外処理を終了させるか、別の例外オブジェクトを生成して送出することで例外処理をさらに上位に向かって継続するか、例外を再生成("raise;")することで例外処理を継続するか、のいずれかになります。しかし、別の例外を送出する場合は元の例外オブジェクトの持つ情報は消滅してしまいますし、例外を再生成する場合は新たな情報を付け加えることができません。そこでこの例外チェーンという機能を使うことで、元の例外オブジェクトの持つ情報に新たな情報を加えて上位の処理に送出することができます。

例として、あるテキストファイルの1行目に書かれている文字列を10進数とみなして取り込む、という関数を考えてみます。
type
  EFileIsEmpty = class(Exception);

function ReadIntegerValueFromFile(const Path: String): Integer;
var
  Filename: String;
  SL: TStringList;
begin

  Filename := IncludeTrailingPathDelimiter(Path) + 'FOO.TXT';
  SL := TStringList.Create;
  try
    SL.LoadFromFile(Filename);
    if SL.Count = 0 then
    begin
      raise EFileIsEmpty.Create('File is empty.');
    end;

  Result := StrToInt(SL.Strings[0]);

  finally
    SL.Free;
  end;

end;
ここで例外が送出される状況には、1.何らかの理由でファイルを開けない(EFOpenError)、2.ファイルが空(0行)だった(EFileIsEmpty)、3.1行目に10進数に変換できない文字があった(EConvertError)の3つがあります(正確にはリソース不足でTStringList.Createが失敗する状況を含め4つですが、今回は考えないことにします)。それぞれの原因に対応した例外クラスがあるため、呼び出し元ではエラーの理由を例外オブジェクトのクラスの違いで知ることができます。
procedure TForm1.Button1Click(Sender: TObject);
begin

  try
    ReadIntegerValueFromFile('C:\BAR');

  except
    on E: EFOpenError do
    begin
      MessageDlg('ファイルを開けませんでした。' + sLineBreak + E.Message,
                 mtInformation,[mbOk],0);
      Exit;
    end;

    on E: EFileIsEmpty do
    begin
      MessageDlg('ファイルが空でした。' + sLineBreak + E.Message,
                 mtInformation,[mbOk],0);
      Exit;
    end;

    on E: EConvertError do
    begin
      MessageDlg('不正な文字列が入っていました。' + sLineBreak + E.Message,
                 mtInformation,[mbOk],0);
      Exit;
    end;
  end;

end;
ここでエラーメッセージにファイル名を表示したい、ということになったとします。ところが呼出元のレベルではファイルの存在するパスはわかっていますがフルパス名はReadIntegerValueFromFileの内部に隠蔽されてしまっています。そこで
type
  EFileIsEmpty = class(Exception);
  EFileReadError = class(Exception)
  private
    FFilename: String;
  public
    constructor Create(const Msg: string; const AFilename: String);
    property Filename: String read FFilename;
  end;

function ReadIntegerValueFromFile(const Path: String): Integer;
var
  Filename: String;
  SL: TStringList;
begin

  Filename := IncludeTrailingPathDelimiter(Path) + 'FOO.TXT';
  SL := TStringList.Create;
  try
    try
      SL.LoadFromFile(Filename);
      if SL.Count = 0 then
      begin
        raise EFileIsEmpty.Create('File is empty.');
      end;
      Result := StrToInt(SL.Strings[0]);

    except
      raise EFileReadError.Create('Error!',Filename);
    end;

  finally
    SL.Free;
  end;

end;

constructor EFileReadError.Create(const Msg, AFilename: String);
begin

  inherited Create(Msg);

  FFilename := AFilename;

end;
とすることで
procedure TForm1.Button1Click(Sender: TObject);
begin

  try
    ReadIntegerValueFromFile('C:\BAR');

  except
    on E: EFileReadError do
    begin
      MessageDlg(Format('ファイル ''%s'' の読み込みでエラーが発生しました。' + 
                        sLineBreak  + '%s',
                        [E.Filename,E.Message]),
                 mtInformation,[mbOk],0);
      Exit;
    end;
  end;

end;
のようにエラーがあったときにそのファイルのフルパス名を知ることができます。が、エラーの原因はわからなくなってしまいました。そこで例外チェーンの登場です。クラスプロシージャException.RaiseOuterExceptionを使用することで、その例外ハンドラで受け取った例外オブジェクトを消滅させることなく新たな例外を送出することができます。
type
  EFileIsEmpty = class(Exception);
  EFileReadError = class(Exception)
  private
    FFilename: String;
  public
    constructor Create(const Msg: string; const AFilename: String);
    property Filename: String read FFilename;
  end;

function ReadIntegerValueFromFile(const Path: String): Integer;
var
  Filename: String;
  SL: TStringList;
begin

  Filename := IncludeTrailingPathDelimiter(Path) + 'FOO.TXT';
  SL := TStringList.Create;
  try
    try
      SL.LoadFromFile(Filename);
      if SL.Count = 0 then
      begin
        raise EFileIsEmpty.Create('File is empty.');
      end;
      Result := StrToInt(SL.Strings[0]);

    except
      Exception.RaiseOuterException(EFileReadError.Create('Error!',Filename));
    end;

  finally
    SL.Free;
  end;

end;

constructor EFileReadError.Create(const Msg, AFilename: String);
begin

  inherited Create(Msg);

  FFilename := AFilename;

end;
ここで送出される例外オブジェクトはEFileReadErrorのままです。しかし
procedure TForm1.Button1Click(Sender: TObject);
begin

  try
    ReadIntegerValueFromFile('C:\BAR');

  except
    on E: EFileReadError do
    begin
      if E.InnerException is EFOpenError then
      begin
        MessageDlg(Format('ファイル ''%s'' を開けませんでした。' + 
                          sLineBreak + '%s',
                          [E.Filename,E.InnerException.Message]),
                   mtInformation,[mbOk],0);
      end
      else if E.InnerException is EFileIsEmpty then
      begin
        MessageDlg(Format('ファイル ''%s'' が空でした。' + 
                          sLineBreak + '%s',
                          [E.Filename,E.InnerException.Message]),
                   mtInformation,[mbOk],0);
      end
      else if E.InnerException is EConvertError then
      begin
        MessageDlg(Format('ファイル ''%s'' の1行目に不正な文字列が入っていました。' + 
                          sLineBreak + '%s',
                          [E.Filename,E.InnerException.Message]),
                   mtInformation,[mbOk],0);
        Exit;
      end;

      Exit;
    end;
  end;

end;
と例外オブジェクトのInnerExceptionプロパティでException.RaiseOuterExceptionを呼び出した時点での例外オブジェクトを参照することができます。

この例外チェーンに関係するメソッド、プロパティには
  • RaiseOuterException: 例外ブロック内で使用し、新しく生成した例外オブジェクトをパラメータとして呼び出すことでその時点での例外オブジェクトをチェーンした例外を送出するExceptionクラスのクラスプロシージャ。
  • ThrowOuterException: RaiseOuterExceptionと同じ。C++では例外はraiseするものではなくthrowするものなのでこの名前のクラスプロシージャも用意されている。
  • InnerException: 最も近いRaiseOuterExceptionを送出した時点での例外オブジェクトを格納しているプロパティ。通常の例外の送出(raise EXXXX.Create)ではnilとなる。またRaiseOuterExceptionがネストして呼び出されている場合はE.InnerException.InnerException...のようにさかのぼって例外オブジェクトを参照することができる。
  • BaseException: ネストしてRaiseOuterExceptionが呼び出された場合に最初に送出された例外オブジェクトを格納しているプロパティ。通常の例外の送出(raise EXXXX.Create)ではnilとなる。また1段階しかRaiseOuterExceptionが呼び出されていない場合はInnerException=BaseExceptionとなる。
  • ToString例外チェーン上の全ての例外オブジェクトのMessageプロパティをCR+LFで連結したプロパティ。
があります。

元ねたはDELPHI 2009 HANDBOOK

2012年2月2日

[書籍]世界一わかりやすいSQLの授業

同じくMARUZEN&ジュンク堂書店 渋谷店

世界一わかりやすいSQLの授業 (amazon)/Lepton著/ソシム/ISBN 978-4-88337-801-2/2,079円

を購入。

[書籍]Clean Coder

MARUZEN&ジュンク堂書店 渋谷店The Clean Coderの翻訳である

Clean Coder (amazon)/Robert C. Martin著/角征典訳/アスキー・メディアワークス/ISBN 978-4-04-886069-7/2,100円

を購入。プロフェッショナルなプログラマ(またはそれを目指す人)は読んでおくべきです。

2012年2月1日

第21回エンバカデロ・デベロッパーキャンプ開催決定

第21回エンバカデロ・デベロッパーキャンプは2012年03月09日~2012年03月10日の2日間で開催されます。

エンバカデロ・デベロッパーキャンプ
【3/9~10】第21回 エンバカデロ・デベロッパーキャンプ

今回は全セッションオンラインで中継あり、懇親会あり、そして宿泊ありと盛り沢山で行われます。

2012/02開催のウェブセミナー



2012/02/23追記: リプレイビデオのリンクを追加しました。

2012年1月27日

Firebirdロードマップ(2012/01)

ちょっと前のことになりますが、Firebirdのロードマップが更新されています。

Firebird: Roadmap

てきとうな要約:
  • 1.5 - 1.5.6が最終版で、今後公式にリリースされることはない。ただし新しいバージョンからバックポートされたバグフィックスを含む修正版が必要と判断した場合はIBPhoenixから商用製品としてリリースされるかもしれない。
  • 2.0 - 2.0.6(2010/06)が最新版で、年1回のメンテナンスサイクルでのリリースが宣言されている。次のリリース(2.0.7)は2012年のQ1を予定。
  • 2.1 - 2.1.4(2011/03)が最新版で、こちらも年1回のメンテナンスサイクルでのリリースが宣言されている。次のリリース(2.1.5)は2012年のQ3を予定。
  • 2.5 - 2.5.1(2011/10)が最新版で、年2回のメンテナンスサイクルでのリリースが宣言されている。次のリリース(2.5.2)は2012年のQ2を予定。
  • 3.0 - 現在開発中の最新版で、Alpha 1は2012年のQ1にリリースを予定。キーポイントは以下のとおり。
    • 新しい認証サブシステム、構成サブシステムを含む、改訂されたアーキテクチャ
    • 共有ページキャッシュを備えたスケーラブルなマルチスレッドエンジン
    • 新しいシステムテーブル、モニタテーブルを備えた新しいODS(Version 12)
    • JavaやC++などによる外部ストアドプロシージャ、トリガ、関数
    • 作り直されたオプティマイザと新しいデータアクセスメソッド
    • セキュリティの強化
    • さまざまなSQL拡張

元ねたはFirebird News » Firebird Roadmap updated for January 2012

2012/03/18追記: Firebird News » Firebird roadmap has been updatedからの情報で、ロードマップが一部変更されています。具体的には、
  • 2.0は現在2.0.7がRC1(これは変更なし)
  • 2.1は若干早まって2.1.5を2012年のQ2に予定
  • 2.5も若干早まって2.5.2を2012年のQ2に予定
  • 3.0は逆にAlpha 1を2012年のQ2に遅らせる
ということで、3.0の開発を遅らせてその分のリソースを2.1/2.5に振り分けることにした、という話のようです。

2012年1月26日

TMS Aurelius

Delphiで使用できるORMとしてちょっと前にDORM (Delphi ORM)を取り上げましたが、TMS Softwareから

TMS Aurelius

という製品がリリースされています。Delphi 2010/XE/XE2で動作し、
  • 対応プラットフォーム: Windows x86/x64、Mac OS X
  • 対応RDBMS: Firebird、Interbase、Microsoft SQL Server、MySQL、NexusDB、Oracle、SQLite、PostgreSQL、DB2
  • 対応データアクセスコンポーネント: dbExpress、dbGo(ADO)、Interbase Express(IBX)、SQLite、AnyDac、NexusDB、SQL-Direct
となっています。ライセンスはシングルデベロッパライセンス(1ユーザ)が195EUR(=19,800JPY)、スモールチームライセンス(2ユーザ)が295EUR(=30,000JPY)、サイトライセンス(ユーザ数無制限)が695EUR(=70,600JPY)となっています(2012/02/29までの限定で20%のディスカウント中)。

2012年1月17日

IBSurgeon Log Viewer

InterBase/Firebird用の各種ツールで有名なIBSurgeonからIBSurgeon Log ViewerというツールがFREEWAREとして公開されています。

IBSurgeon Log Viewer

InterBase(interbase.log)やFirebird(firebird.log)のログファイルには(いつもではないにしても)ときどき有用な情報が記録されています。しかしログそのものが場当たり的に拡張されてきたため、メモ帳やエディタなどで開いてもなかなか必要な情報にたどりつくことができません(また必要な情報が記録されていないことも多々あります)。このIBSurgeon Log Viewerはこれらのログを見やすく表示するだけでなく、可能であればそのログの原因となるような状況についても解説を表示してくれる("Message Description"ペイン)というツールです。現時点(2012/01/17)の最新バージョンは2.0.0.5となっています。

元ねたはFirebird News » IBSurgeon Log Viewer 2.0 – free tool for viewing and analyzing firebird.log

2012年1月11日

Microsoft Monthly Update 2012/01

今日はMicrosoftのセキュリティアップデートの日です。
MS12-001
MS12-002
MS12-003
MS12-004
MS12-005
MS12-006
MS12-007

2012年1月5日

2012/01開催のウェブセミナー


2012/02/08追記: リプレイビデオのリンクを追加しました。

2012年1月4日

IDE Fix Pack 4.6.5/4.6.6リリース

Andreas HausladenさんIDE Fix Pack 2009/2010/XE/XE2がアップデートされてVersion 4.6.5になっています。

IDE Fix Pack 4.6.5 released | Andy’s Blog and Tools

追記: IDE Fix Pack 2009/2010/XE/XE2 Version 4.6.5にはデバッガが相対パス指定のソースファイルを見つけられないという問題があり、これを修正したVersion 4.6.6がリリースされています。

IDE Fix Pack 4.6.6 – bug fix release | Andy’s Blog and Tools

2011年12月30日

Microsoft OOB Update 2011/12

Microsoftの定例外のセキュリティアップデートがリリースされています。
MS11-100

2011年12月28日

IDE Fix Pack 4.6.1リリース

Andreas HausladenさんIDE Fix Pack 2009/2010/XE/XE2がアップデートされてVersion 4.6.1になっています。このバージョンではバグフィックス2件のみで機能的な追加変更は含まれていないとのことです。

IDE Fix Pack 4.6.1 – bugfix release | Andy’s Blog and Tools

2011年12月20日

RAD Studio/Delphi/C++Builder XE2 Update 3 Hotfix 2

RAD Studio/Delphi/C++Builder XE2 Update 3のHotfix 2(Ent/Arc/Ult SKUのみ)がリリースされています。
  • QC101807 Update 3適用後にAndroid用モバイルコネクタのDSRESTSSLFactory.javaが不足する
という問題を修正するものです。

28672 HotFix 2 for Delphi, C++Builder and RAD Studio XE2
C++Builder XE2/Delphi XE2/RAD Studio XE2 向け Hotfix 2
Hotfix 2 for C++Builder XE2, Delphi XE2 and RAD Studio XE2 is now available

2011年12月19日

FastReport 4 VCL日本語版

Delphi/C++Builder XE2ではFastReport 4 RAD Editionが標準でバンドルされるようになりましたが、先週のデベロッパーTVでもちょっと話が出ていた製品版の日本語版が2012/01/13に発売されるようです。

レポーティングソリューション FastReport VCL | 製品概要
リオスe-Shop/商品一覧ページ

エージーテックさんのページでは1月中旬以降正式リリースとなっていますが、リオスe-Shopさんでは2012/01/13発売と日付まで出ています。またリオスe-Shopさんでは2011/11/21から

キャンペーン期間中にご成約いただきますと、まずは英語版をお送りし、日本語版販売時に優先して、日本語版をお送りします。
(同じライセンス番号で動作しますので、日本語か英語のどちらかでご利用できます)
という「先行販売割引キャンペーン」(42,000円)をやっています。ちなみにこれはEnterprise Edition/Single license相当(Fast Reports Inc.から直接買えば349.00USD=27,000JPY)ですので、日本語に翻訳されていることに15,000円出せるかどうか、ということになるのかと。実際に仕事で使うなら許容範囲でしょうか。

2012/01/16追記: FastReport VCLの日本語版がリオスe-Shopで販売開始になっています。

レポーティングソリューション FastReport VCL | 製品概要
リオスe-Shop/商品一覧ページ

2012/01/18追記: 製品版のFastReportをインストールするためにバンドル版をアンインストールするとコンポーネントの登録が全て削除されるという不具合(QC102274)が報告されており、Team Japanに詳細と対応策が解説されています。

Team Japan » Delphi/C++Builder/RAD Studio XE2のUpdate3 で FastReportをアンインストールすると..

2011年12月16日

IDE Fix Pack 4.6リリース

Andreas HausladenさんIDE Fix Pack 2009/2010/XE/XE2がアップデートされてVersion 4.6になっています。

IDE Fix Pack 4.6 (supports XE2 Update 3) | Andy’s Blog and Tools

Delphi/C++Builder XE2 Update 3

かねてからの噂どおりDelphi/C++Builder XE2のUpdate 3がリリースされています。

28670 Update 3 for Delphi, C++Builder and RAD Studio XE2
Update 3 for Delphi XE2, C++Builder XE2 and RAD Studio XE2
XE2 Update 3 のリリース ノート - RAD Studio XE2 (en)
Delphi XE2/C++Builder XE2 Update 3 における不具合修正リスト (en)

Update 3ではTButtonのModalResultプロパティで使用するいくつかの値の定義が変更されています。またコンパイラ内の一部の構造体の変更に伴いAndreas HausladenさんIDE Fix Pack 2009/2010/XE/XE2 4.5が動作しなくなっています(既に4.6がリリースされています)。

TButtonのModalResultプロパティで使用する値の件はUpdate 3のリリースノートに明記されただけで、実際にはXE2 RTMにおける変更のようです(QC100283)。まずTButto.ModalResultの型はTModalResultですが、TModalResultは列挙型ではなく単に
  TModalResult = Low(Integer) .. High(Integer);
と実質的にIntegerであり、それぞれの値(mr...)は該当するボタンのID(ID_...)と同値で、XEおよびそれ以前ではControls.pasで、XE2ではSystem.UITypes.pasで定義されています。Update 3のリリースノートからリンクしているBob SwartさんのXE2 values of mrAll, mrNoToAll, mrYesToAll and mrCloseというアーティクルで詳しく考察されていますが、XEではmrCloseがmr...の最後(mrYesToAllの次)になるように定義されているのに対し、XE2ではmrHelp、mrTryAgain、mrContinueと共にmrNoの次になるように移動し、その後にmrAll、mrNoToAll、mrYesToAllとmrYesToAllが最後になるように定義が変更されています。さらにTModalResultは列挙型でないためdfm上は数値として記録されているため、XEまでのコード(フォームデータ)でModalResultにmrAll、mrNoToAll、mrYesToAll、mrClose(8~11)と設定していたものをXE2に取り込むとmrClose、mrHelp、mrTryAgain、mrContinueに変化してしまう、ということになります(あるいはソースを共用している場合は逆のことも起きます)。基本的にbreaking changeということであり、個別に対策するしかないのですが、上記のBob Swartさんのアーティクルにはこの問題を指摘してくれるScanDFMというツールがリンクされています。

2012/02/10追記: ModalResultの定義値の変更の件を補足しました。高橋さん、情報ありがとうございます。