シリアルポート(COM/RS-232C)はレガシーではありますが、依然として使用する機会の多いインタフェースです。そこで一般的にお勧めできそうなシリアルポートコンポーネントを探してみました(対応するDelphi/C++Builderのバージョンはあくまで目安です)。
ComPort Library
作者はDejan Crnilaさん、現在のメンテナはLars B. Dybdahlさん、Paul Dolandさん、Brian Gochnauerさんです。Delphi 3/4/5/6/7/8/2005/2006/2007/2009/2010/XE、C++Builder 3/4/5/6に対応しています。最新バージョンは4.11a(2011/05/22)、ライセンスはPublic Domainです。
TurboPower Async Professional
もともとはTurboPower Softwareの商用製品で、オープンソース化されたTurbo Powerプロジェクトの多機能通信コンポーネント群。現在のメンテナはSean B. Durkinさん、tpsfadmin(TurboPower)ことNick Hodgesさんです。Delphi 7/2005/2007/2010に対応しています。最新バージョンは5.0(2010/03/10)、ライセンスはMPL 1.1です。関連: Installing AsyncPro into Delphi XE and C++Builder XE
SynaSer serial library
Ararat s.r.o.のSYNAPSE libraryに含まれるシリアル通信コンポーネント。Delphi/C++Builder 3/4/5/6/7/2005/2006に対応しています。最新バージョンはRelease 16/Version 7.2.0(2007/12/21)、ライセンスは修正BSDスタイルです。
TksComPort
Sergey Kasandrovさんによるkstoolsに含まれるシリアル通信コンポーネント。Delphi 2007/2009に対応しています。最新バージョンは0.50(2010/04/30)、ライセンスはMIT Licenseです。
ComPort component
Winsoftによる商用製品(35.00-70.0USD)。Delphi/C++Builder 5/6/7/2005/2006/2007/2009/2010/XEに対応しています。最新バージョンは3.5(2010/12/01)。
nrComm Lib
DeepSoftwareによる商用製品(69.00-599.00EUR)。Delphi/C++Builder 3/4/5/6/7/2005/2006/2007/2009/2010/XEに対応しています。最新バージョンは9.17(2011/05/11)。
お勧めはComPort LibraryかTurboPower Async Professionalでしょうか。シンプルなものがよければ前者、多機能なものがよければ後者、という感じで。
元ねたはTeam Japan » Delphi XE で COMポート(RS-232C)を操作するなど。
2011年5月25日
2011年5月24日
よりよいQCレポートの書き方
エンバカデロのQualityCentralのSysopの一人であるUwe SchusterさんがHow to improve your QC reports?(QCレポートを改善する方法)というアーティクルを書いています。興味深い。
てきとうな要約: まず、最大の誤りは問題をレポートしないことです。なぜならあなたがその問題は修正されると思っていない、ということだからです。
QualityCentralのレポートの書き方に関しては以下のアーティクルも参考になります。
Quality Centralでのバグレポートのコツ
Quality Centralの基礎知識 - Delphi 2009 特集
バグレポートの際のコツ - Delphi 2009 特集
QualityCentral の Tips
Quality Central についての基礎知識
てきとうな要約: まず、最大の誤りは問題をレポートしないことです。なぜならあなたがその問題は修正されると思っていない、ということだからです。
- Build No
ここにはRAD Studioのフルバージョン番号を記入する(XE Update 1なら15.0.3953.35171)。これはどのUpdate/Hotfixを適用した状態なのかを知るために重要。 - Language/Nationality
ローカライズとは関係のない問題では"US"。表示の欠けや誤訳などのローカライズに関係する問題ではその言語を選択。不必要に言語を指定するとまずローカライゼーションチームに回されるため修正まで時間が掛かるかも。 - Type and Severity
この2つの項目は全てのユーザの視点で指定するべきで、高すぎる評価はSysopやQAによって修正されることがある。またコンパイラによる内部エラー("Fxxxx Internal Error")はTypeを(Crash/Data loss/Total failure)とする。 - Steps
Stepsには明白であっても予想される動作と実際の動作を記入する。レポートをOpenにするためには再現可能でなければならない。テストケースのないコンパイラの内部エラーは既存のレポートと重複しているものとして扱われ、修正されない可能性が高い(原因が異なっていても同じエラーになるので)。 - Automated Reports
IDEがJCLスタックトレース機能を使って生成するものでAIRレポートとも呼ばれ、QCのDelphi.NETに分類される。これは過去の経緯によるものなので、気にせずそのまま(Delphi/C++Builderプロジェクトに変更したりせず)送信する。 - Voting
新しいvoteシステムは2007年から使用されており、それぞれのレポートに+/-10の範囲でvote(投票)することができる。webクライアントは新しいvoteシステムに対応しておらずvote値を指定できないので、Win32クライアント(かQC Plus)を使用するのが望ましい。 - Questions
QCレポートの扱いについて質問がある場合はForum: QualityCentralへ。
QualityCentralのレポートの書き方に関しては以下のアーティクルも参考になります。
Quality Centralでのバグレポートのコツ
Quality Centralの基礎知識 - Delphi 2009 特集
バグレポートの際のコツ - Delphi 2009 特集
QualityCentral の Tips
Quality Central についての基礎知識
2011年5月20日
ジェスチャに対応したアプリケーションの作成
エンバカデロの米澤さんによるジェスチャ対応アプリケーション関係のアーティクル。興味深い。とりあえずメモ。
GestureManager と ActionManager (または ActionList) を使ってコードを書かずに簡単なタッチ/ジェスチャアプリケーションを作成する
カスタムジェスチャで、簡単な文字認識アプリケーション
登録したカスタムジェスチャをアプリケーション上で確認
アプリケーションでカスタムジェスチャを登録する
関連アーティクル。
Team Japan » マルチタッチアプリケーションのサンプル
スレート PC プログラミング (ONKYO TW317A5 の活用 (ONKYO TW317 シリーズ))
MSDNのタッチ/ジェスチャ関係のページのリンク。
タッチ
Windows タッチ (Windows)
Windows タッチ ジェスチャの概要 (Windows)
2011/05/23追記: このシリーズは一応4回で完結とのことのようです。
2011/05/31追記: 関連アーティクルのリンクを追加しました。
GestureManager と ActionManager (または ActionList) を使ってコードを書かずに簡単なタッチ/ジェスチャアプリケーションを作成する
カスタムジェスチャで、簡単な文字認識アプリケーション
登録したカスタムジェスチャをアプリケーション上で確認
アプリケーションでカスタムジェスチャを登録する
関連アーティクル。
Team Japan » マルチタッチアプリケーションのサンプル
スレート PC プログラミング (ONKYO TW317A5 の活用 (ONKYO TW317 シリーズ))
MSDNのタッチ/ジェスチャ関係のページのリンク。
タッチ
Windows タッチ (Windows)
Windows タッチ ジェスチャの概要 (Windows)
2011/05/23追記: このシリーズは一応4回で完結とのことのようです。
2011/05/31追記: 関連アーティクルのリンクを追加しました。
2011年5月19日
Windowsの互換モード上でのLCMapStringの不具合
ちょっと前になりますが、au2010さんのところで気になる話を見つけました。
Windows7でDelphi7のプログラムを動かす時の注意点 - au2010の日記
実際にLCMapString (ja)の動作を確認してみると、Windows 7で互換モードを設定(プログラムのショートカットの"互換性"タブで"互換モード"にチェックオン)すると、LCMapStringのcchDestを0にしたときだけ戻値がA版(LCMapStringA)であっても(W版と同様の)文字数で返ってくる、という不具合があります(cchDestが非0ならば戻値は仕様通り)。いろいろ調べてみましたがプログラム側から互換モードの指定状況を知る方法はなく、結果が既知の変換動作を行わせて、その戻値で判定するしかないようです。ということで半角→全角変換と全角→半角変換のコードを修正しました。
Windows7でDelphi7のプログラムを動かす時の注意点 - au2010の日記
実際にLCMapString (ja)の動作を確認してみると、Windows 7で互換モードを設定(プログラムのショートカットの"互換性"タブで"互換モード"にチェックオン)すると、LCMapStringのcchDestを0にしたときだけ戻値がA版(LCMapStringA)であっても(W版と同様の)文字数で返ってくる、という不具合があります(cchDestが非0ならば戻値は仕様通り)。いろいろ調べてみましたがプログラム側から互換モードの指定状況を知る方法はなく、結果が既知の変換動作を行わせて、その戻値で判定するしかないようです。ということで半角→全角変換と全角→半角変換のコードを修正しました。
2011年5月18日
C++0x FDIS(N3290)解説
C++0xの規格化作業は最終段階に差し掛かっていて、現在はN3290がFDIS(Final Draft International Standards)として公開されています(もちろん英語で)。
このC++0xのN3290について、2011/05/14に行われたBoost.勉強会 #5 名古屋で道化師さんが解説した資料が見られるようになっています。
C++0x総復習 (Boost.勉強会 #5 名古屋 - boostjp)
slideshareだとこちらですね。
C++0x総復習
江添さんによる本の虫: post-Madrid mailingの簡易レビューも併せて読むといいと思います。
元ねたはアキラさんのBoost.勉強会 #5 名古屋でした - Faith and Brave - C++で遊ぼう。
Delphiにも"= delete"(§8.4.3 Deleted definitions)があればこんなことをしなくてもすんだのに…。
このC++0xのN3290について、2011/05/14に行われたBoost.勉強会 #5 名古屋で道化師さんが解説した資料が見られるようになっています。
C++0x総復習 (Boost.勉強会 #5 名古屋 - boostjp)
slideshareだとこちらですね。
C++0x総復習
江添さんによる本の虫: post-Madrid mailingの簡易レビューも併せて読むといいと思います。
元ねたはアキラさんのBoost.勉強会 #5 名古屋でした - Faith and Brave - C++で遊ぼう。
Delphiにも"= delete"(§8.4.3 Deleted definitions)があればこんなことをしなくてもすんだのに…。
2011年5月17日
DelphiでSingletonパターンを実装する(リベンジ)
DelphiでSingletonパターンを実装する(再考)ではTSingletonのインスタンスの解放を防ぐためにデストラクタ内で例外を送出する、という方法を考えてみましたが、2ちゃんねる界隈では不評だったようです。一般論からいえば確かにデストラクタからの例外の送出はいかがなものか、という気もしますが、そもそもこのアプローチは(コンストラクタからの例外の送出による複数インスタンスの生成の防止と同様に)テストレベルで問題コードを検出、修正するためのものであって、リリースコード上で実行されることを想定しているわけではありません。また前回考察したように、一旦呼び出しがなされたコンストラクタ、デストラクタのインスタンスに対する生成、破棄処理を回避するには例外の送出しかないことも確かです。ということで"より望ましい"解決方法を考えてみることにします。
まず前回の"Phoenix Singleton"ですが、デストラクタを呼び出すことでインスタンスが完全に解放されてしまうため、次回のアクセスで新しいインスタンスが生成されてもその内容は初期状態に戻ってしまいます。そこで"予備"のインスタンスを用意しておき、ここに内容を退避して再生成時に復元します。まずinitialization/finalization版から。
次にコンストラクタ、デストラクタでの例外の送出、というアプローチですが、これは前述のとおりテストレベルでの問題コードの検出、修正を目的としています。しかしこの方法には問題コードが実行されない限り意味をもたない(コンパイル時には検出できない)、という欠点もあります。そこで例外の送出ではなく、constructor Create、destructor Destory、そして再定義したprocedure Freeに(少々目的は異なるものの)ヒント指令のdeprecatedを指定しておき、これらを呼び出しているコードをコンパイル時に警告されるようにする、という解決策を考えてみます。まずinitialization/finalization版から。
次にclass constructor/class destructor版です。
まず前回の"Phoenix Singleton"ですが、デストラクタを呼び出すことでインスタンスが完全に解放されてしまうため、次回のアクセスで新しいインスタンスが生成されてもその内容は初期状態に戻ってしまいます。そこで"予備"のインスタンスを用意しておき、ここに内容を退避して再生成時に復元します。まずinitialization/finalization版から。
unit Unit32;
interface
uses
SysUtils;
type
TSingleton = class(TObject)
private
FTestValue: Integer;
constructor CreateInstance;
public
constructor Create;
destructor Destroy; override;
procedure Assign(Source: TSingleton);
class function GetInstance: TSingleton;
property TestValue: Integer
read FTestValue
write FTestValue;
end;
ECreateSingleton = class(Exception)
end;
implementation
var
FSingleton: TSingleton;
FShadowSingleton: TSingleton;
{ TSingleton }
constructor TSingleton.Create;
begin
raise ECreateSingleton.Create('TSingleton.Create cannot use.');
end;
constructor TSingleton.CreateInstance;
begin
inherited Create;
{ Initialize }
FTestValue := 0;
end;
destructor TSingleton.Destroy;
begin
{ Shadowing }
if FShadowSingleton = nil then
begin
FShadowSingleton := TSingleton.CreateInstance;
end;
if FShadowSingleton <> Self then
begin
FShadowSingleton.Assign(Self);
{ Delete singleton reference }
FSingleton := nil;
end;
{ Finalize }
inherited;
end;
procedure TSingleton.Assign(Source: TSingleton);
begin
{ Copy from source }
TestValue := Source.TestValue;
end;
class function TSingleton.GetInstance: TSingleton;
begin
if FSingleton = nil then
begin
FSingleton := TSingleton.CreateInstance;
if FShadowSingleton <> nil then
begin
FSingleton.Assign(FShadowSingleton);
end;
end;
Result := FSingleton;
end;
initialization
FSingleton := nil;
FShadowSingleton := nil;
finalization
FSingleton.Free;
FShadowSingleton.Free;
end.
次にclass constructor/class destructor版です。unit Unit34;
interface
uses
SysUtils;
type
TSingleton = class(TObject)
private
FTestValue: Integer;
class var
FSingleton: TSingleton;
FShadowSingleton: TSingleton;
constructor CreateInstance;
public
class constructor Create;
class destructor Destroy;
constructor Create;
destructor Destroy; override;
procedure Assign(Source: TSingleton);
class function GetInstance: TSingleton;
property TestValue: Integer
read FTestValue
write FTestValue;
end;
ECreateSingleton = class(Exception)
end;
implementation
{ TSingleton }
class constructor TSingleton.Create;
begin
FSingleton := nil;
FShadowSingleton := nil;
end;
class destructor TSingleton.Destroy;
begin
FSingleton.Free;
FShadowSingleton.Free;
end;
constructor TSingleton.Create;
begin
raise ECreateSingleton.Create('TSingleton.Create cannot use.');
end;
constructor TSingleton.CreateInstance;
begin
inherited Create;
{ Initialize }
FTestValue := 0;
end;
destructor TSingleton.Destroy;
begin
{ Shadowing }
if FShadowSingleton = nil then
begin
FShadowSingleton := TSingleton.CreateInstance;
end;
if FShadowSingleton <> Self then
begin
FShadowSingleton.Assign(Self);
{ Delete singleton reference }
FSingleton := nil;
end;
{ Finalize }
inherited;
end;
procedure TSingleton.Assign(Source: TSingleton);
begin
{ Copy from source }
TestValue := Source.TestValue;
end;
class function TSingleton.GetInstance: TSingleton;
begin
if FSingleton = nil then
begin
FSingleton := TSingleton.CreateInstance;
if FShadowSingleton <> nil then
begin
FSingleton.Assign(FShadowSingleton);
end;
end;
Result := FSingleton;
end;
end.
いずれもシングルトンの内容の退避、復帰のためにインスタンスの内容をコピーするprocedure Assignというメソッドを用意し、これをコンストラクタ、デストラクタで使用します。なおシングルトンが別のクラスのインスタンスを所有するような場合、Assignはいわゆる"deep copy"の動作を実装する必要があります。またpublicなconstructor Createについてはとりあえず従来どおりの例外送出のままとしてあります。次にコンストラクタ、デストラクタでの例外の送出、というアプローチですが、これは前述のとおりテストレベルでの問題コードの検出、修正を目的としています。しかしこの方法には問題コードが実行されない限り意味をもたない(コンパイル時には検出できない)、という欠点もあります。そこで例外の送出ではなく、constructor Create、destructor Destory、そして再定義したprocedure Freeに(少々目的は異なるものの)ヒント指令のdeprecatedを指定しておき、これらを呼び出しているコードをコンパイル時に警告されるようにする、という解決策を考えてみます。まずinitialization/finalization版から。
unit Unit28;
interface
uses
SysUtils;
type
TSingleton = class(TObject)
private
FTestValue: Integer;
constructor CreateInstance;
destructor DestroyInstance;
public
constructor Create; deprecated {$IFDEF CONDITIONALEXPRESSIONS}{$IF CompilerVersion >= 20.00} 'Do not use TSingleton.Create.' {$IFEND}{$ENDIF};
destructor Destroy; override; deprecated {$IFDEF CONDITIONALEXPRESSIONS}{$IF CompilerVersion >= 20.00} 'Do not use TSingleton.Destory.' {$IFEND}{$ENDIF};
procedure Free; deprecated {$IFDEF CONDITIONALEXPRESSIONS}{$IF CompilerVersion >= 20.00} 'Do not use TSingleton.Free.' {$IFEND}{$ENDIF};
class function GetInstance: TSingleton;
property TestValue: Integer
read FTestValue
write FTestValue;
end;
implementation
var
FSingleton: TSingleton;
{ TSingleton }
constructor TSingleton.Create;
begin
{ Place holder, do not use }
end;
destructor TSingleton.Destroy;
begin
{ Place holder, do not use }
end;
constructor TSingleton.CreateInstance;
begin
inherited Create;
{ Initialize }
FTestValue := 0;
end;
destructor TSingleton.DestroyInstance;
begin
{ Finalize }
inherited Destroy;
end;
procedure TSingleton.Free;
begin
{ Place holder, do not use }
end;
class function TSingleton.GetInstance: TSingleton;
begin
if FSingleton = nil then
begin
FSingleton := TSingleton.CreateInstance;
end;
Result := FSingleton;
end;
initialization
FSingleton := nil;
finalization
if FSingleton <> nil then
begin
FSingleton.DestroyInstance;
end;
end.
Delphi 2009以降({$IFDEF CONDITIONALEXPRESSIONS}{$IF CompilerVersion >= 20.00}で判定)ではdeprecatedに追加のメッセージを指定しています。次にclass constructor/class destructor版です。
unit Unit30;
interface
uses
SysUtils;
type
TSingleton = class(TObject)
private
FTestValue: Integer;
class var
FSingleton: TSingleton;
constructor CreateInstance;
destructor DestroyInstance;
public
class constructor Create;
class destructor Destroy;
constructor Create; deprecated 'Do not use TSingleton.Create.';
destructor Destroy; override; deprecated 'Do not use TSingleton.Destory.';
procedure Free; deprecated 'Do not use TSingleton.Free.';
class function GetInstance: TSingleton;
property TestValue: Integer
read FTestValue
write FTestValue;
end;
implementation
{ TSingleton }
class constructor TSingleton.Create;
begin
FSingleton := nil;
end;
class destructor TSingleton.Destroy;
begin
if FSingleton <> nil then
begin
FSingleton.DestroyInstance;
end;
end;
constructor TSingleton.Create;
begin
{ Place holder, do not use }
end;
destructor TSingleton.Destroy;
begin
{ Place holder, do not use }
end;
constructor TSingleton.CreateInstance;
begin
inherited Create;
{ Initialize }
FTestValue := 0;
end;
destructor TSingleton.DestroyInstance;
begin
{ Finalize }
inherited Destroy;
end;
procedure TSingleton.Free;
begin
{ Place holder, do not use }
end;
class function TSingleton.GetInstance: TSingleton;
begin
if FSingleton = nil then
begin
FSingleton := TSingleton.CreateInstance;
end;
Result := FSingleton;
end;
end.
どちらもDelphi 2009以降であれば"プロジェクトオプション"の"Delphiコンパイラ"の"ヒントと警告"で"使用を推奨されていないシンボル"をエラーにするか、シングルトンを使用している側のユニットに{$WARN SYMBOL_DEPRECATED ERROR}
を指定することでconstructor Create、destructor Destory、procedure Freeの呼び出しをエラーにすることができます(通常は警告)。
2011年5月16日
IDE Fix Pack 4.1/DelphiSpeedUp 3.1リリース
Andreas HausladenさんのIDE Fix Pack 2007およびIDE Fix Pack 2009/2010/XEがアップデートされてVersion 4.1に、DelphiSpeedUpもDelphi 7/2007用がアップデートされてVersion 3.1になっています。IDE Fix Pack、DelphiSpeedUpともRCからの変更はないようです。
IDE Fix Pack 4.1, DelphiSpeedUp 3.1 | Andy’s Blog and Tools
IDE Fix Pack 4.1, DelphiSpeedUp 3.1 | Andy’s Blog and Tools
2011年5月11日
2011年5月10日
2011年5月9日
コンストラクタとデストラクタについての考察
Delphiのコンストラクタ、デストラクタとは、ヘルプの
コンストラクタ (メソッド)
デストラクタ (メソッド)
にあるように、クラスのインスタンスを生成、破棄するための特別なメソッドです。あまり知られていませんがコンストラクタとデストラクタには
メソッド呼び出しの処理 (プログラムの制御)
にあるように、Pointer型とByte型の2つの隠しパラメータが存在します(デフォルトのregister呼出規約ではEAXレジスタとDLレジスタ)。第1パラメータ(Pointer型)はSelf(C++のthisに相当)です。一方で第2パラメータ(Byte型)は真偽値(0または非0)であり、クラスメソッドとして呼び出される場合、つまり
実際のコードで確認してみましょう(例はシングルトンパターンその1のUnit2をDelphi 2007でデバッグビルドしたものです)。
コンストラクタ呼び出し
となり、DLレジスタに0x01が設定されてコンストラクタ(TSingleton.Create)を呼び出しています。コンストラクタ本体の
と先頭でDLレジスタが非0ならばClassCreateを呼び出し、BLレジスタにDLレジスタの値を保存してからDLレジスタを0に変更して以下の処理を行い、最後にBLレジスタ(=呼び出されたときのDLレジスタ)が非0であればAfterConstructionを呼び出すようになっています。
次にデストラクタを見てみます。といってもサンプルではTObject.Freeで呼び出しが隠蔽されてしまっていますので、ここはDestroyを直接呼び出すようにしてみます。
となり、やはりDLレジスタに0x01を格納してからデストラクタを呼び出しています(呼出先が[ecx-$04]というのはVMT経由でデストラクタのアドレスを取得しているためです)。デストラクタ本体の
となり、まずBeforeDestructionが呼び出され、BLレジスタにDLレジスタの値を保存してから0xFCとANDをとって(DLレジスタは0x01 AND 0xFC = 0x00となっている)継承元デストラクタを呼び出し、最後にBLレジスタ(呼び出されたときのDLレジスタ)が非0であればClassDestroyを呼び出すようになっています。またデストラクタ内でExitしても
とBLレジスタのチェックにジャンプするようになっており、DLが非0の呼び出しでは途中でExitしても必ずClassDestroyが呼び出されます。
このような事情から、コンストラクタの呼び出しによるクラスインスタンスの生成をキャンセルするためにはコンストラクタ内で例外を送出し、最外側コンストラクタ先頭で確保したインスタンス領域をデストラクタで解放させるしかありません(コンストラクタをクラスメソッドで呼び出すと必ず領域確保が行われるため)。同様にデストラクタの呼び出しによるインスタンスの破棄をキャンセルするには最外側のデストラクタの末尾までのどこかの時点で必ず例外を送出してインスタンス領域の解放を防ぐ必要があります。
元ねたはDelphiクイックリファレンス (amazon)/Ray Lischner著/光田秀、竹田知生訳/オライリー・ジャパン/ISBN4-87311-040-8/4,725円。
コンストラクタ (メソッド)
デストラクタ (メソッド)
にあるように、クラスのインスタンスを生成、破棄するための特別なメソッドです。あまり知られていませんがコンストラクタとデストラクタには
メソッド呼び出しの処理 (プログラムの制御)
にあるように、Pointer型とByte型の2つの隠しパラメータが存在します(デフォルトのregister呼出規約ではEAXレジスタとDLレジスタ)。第1パラメータ(Pointer型)はSelf(C++のthisに相当)です。一方で第2パラメータ(Byte型)は真偽値(0または非0)であり、クラスメソッドとして呼び出される場合、つまり
Foo := TFoo.Create;
では1(真)が設定され、インスタンスメソッドとして呼び出される場合、つまりconstructor TFoo.Create;
begin
inherited;
end;
のinherited(継承元コンストラクタの呼び出し)では0(偽)が設定されます。コンストラクタでは先頭にこの第2パラメータをチェックして真の場合はインスタンス領域の確保と初期化が行われるようなコードが生成されます(確保したインスタンス領域のアドレスはEAXに格納され、呼出元に返されます)。またデストラクタでは末尾に第2パラメータのチェックとインスタンス領域の解放が行われるようなコードが生成されます。さらにコンストラクタでは内部で例外が生成されたときに自動的にデストラクタが呼び出され、初期化後に変更されたフィールドに対してリソースの解放を行うことでコンストラクタで例外を送出したときのリソースリークを防ぐような仕組みになっています。実際のコードで確認してみましょう(例はシングルトンパターンその1のUnit2をDelphi 2007でデバッグビルドしたものです)。
コンストラクタ呼び出し
FSingleton := TSingleton.Create;
はUnit2.pas.30: FSingleton := TSingleton.Create;
00457C35 B201 mov dl,$01
00457C37 A1D47B4500 mov eax,[$00457bd4]
00457C3C E80B000000 call TSingleton.Create
00457C41 A3CC0C4600 mov [$00460ccc],eax
となり、DLレジスタに0x01が設定されてコンストラクタ(TSingleton.Create)を呼び出しています。コンストラクタ本体の
constructor TSingleton.Create;
begin
inherited;
{ Initialize }
FTestValue := 0;
end;
はUnit2.pas.38: begin
00457C4C 53 push ebx
00457C4D 56 push esi
00457C4E 84D2 test dl,dl
00457C50 7408 jz $00457c5a
00457C52 83C4F0 add esp,-$10
00457C55 E8B2C0FAFF call @ClassCreate
00457C5A 8BDA mov ebx,edx
00457C5C 8BF0 mov esi,eax
Unit2.pas.40: inherited;
00457C5E 33D2 xor edx,edx
00457C60 8BC6 mov eax,esi
00457C62 E869BDFAFF call TObject.Create
Unit2.pas.43: FTestValue := 0;
00457C67 33C0 xor eax,eax
00457C69 894604 mov [esi+$04],eax
Unit2.pas.45: end;
00457C6C 8BC6 mov eax,esi
00457C6E 84DB test bl,bl
00457C70 740F jz $00457c81
00457C72 E8EDC0FAFF call @AfterConstruction
00457C77 648F0500000000 pop dword ptr fs:[$00000000]
00457C7E 83C40C add esp,$0c
00457C81 8BC6 mov eax,esi
00457C83 5E pop esi
00457C84 5B pop ebx
00457C85 C3 ret
と先頭でDLレジスタが非0ならばClassCreateを呼び出し、BLレジスタにDLレジスタの値を保存してからDLレジスタを0に変更して以下の処理を行い、最後にBLレジスタ(=呼び出されたときのDLレジスタ)が非0であればAfterConstructionを呼び出すようになっています。
次にデストラクタを見てみます。といってもサンプルではTObject.Freeで呼び出しが隠蔽されてしまっていますので、ここはDestroyを直接呼び出すようにしてみます。
FSingleton.Destroy;
はUnit2.pas.63: FSingleton.Destroy;
00457CD2 B201 mov dl,$01
00457CD4 A1CC0C4600 mov eax,[$00460ccc]
00457CD9 8B08 mov ecx,[eax]
00457CDB FF51FC call dword ptr [ecx-$04]
となり、やはりDLレジスタに0x01を格納してからデストラクタを呼び出しています(呼出先が[ecx-$04]というのはVMT経由でデストラクタのアドレスを取得しているためです)。デストラクタ本体の
destructor TSingleton.Destroy;
begin
{ Finalize }
inherited;
end;
はUnit2.pas.48: begin
00457C88 53 push ebx
00457C89 56 push esi
00457C8A E825C1FAFF call @BeforeDestruction
00457C8F 8BDA mov ebx,edx
00457C91 8BF0 mov esi,eax
Unit2.pas.52: inherited;
00457C93 8BD3 mov edx,ebx
00457C95 80E2FC and dl,$fc
00457C98 8BC6 mov eax,esi
00457C9A E851BDFAFF call TObject.Destroy
Unit2.pas.54: end;
00457C9F 84DB test bl,bl
00457CA1 7E07 jle $00457caa
00457CA3 8BC6 mov eax,esi
00457CA5 E8B2C0FAFF call @ClassDestroy
00457CAA 5E pop esi
00457CAB 5B pop ebx
00457CAC C3 ret
となり、まずBeforeDestructionが呼び出され、BLレジスタにDLレジスタの値を保存してから0xFCとANDをとって(DLレジスタは0x01 AND 0xFC = 0x00となっている)継承元デストラクタを呼び出し、最後にBLレジスタ(呼び出されたときのDLレジスタ)が非0であればClassDestroyを呼び出すようになっています。またデストラクタ内でExitしても
Unit2.pas.48: begin
00457C88 53 push ebx
00457C89 56 push esi
00457C8A E825C1FAFF call @BeforeDestruction
00457C8F 8BDA mov ebx,edx
00457C91 8BF0 mov esi,eax
Unit2.pas.50: Exit;
00457C93 EB0C jmp $00457ca1
Unit2.pas.54: inherited;
00457C95 8BD3 mov edx,ebx
00457C97 80E2FC and dl,$fc
00457C9A 8BC6 mov eax,esi
00457C9C E84FBDFAFF call TObject.Destroy
Unit2.pas.56: end;
00457CA1 84DB test bl,bl
00457CA3 7E07 jle $00457cac
00457CA5 8BC6 mov eax,esi
00457CA7 E8B0C0FAFF call @ClassDestroy
00457CAC 5E pop esi
00457CAD 5B pop ebx
00457CAE C3 ret
とBLレジスタのチェックにジャンプするようになっており、DLが非0の呼び出しでは途中でExitしても必ずClassDestroyが呼び出されます。
このような事情から、コンストラクタの呼び出しによるクラスインスタンスの生成をキャンセルするためにはコンストラクタ内で例外を送出し、最外側コンストラクタ先頭で確保したインスタンス領域をデストラクタで解放させるしかありません(コンストラクタをクラスメソッドで呼び出すと必ず領域確保が行われるため)。同様にデストラクタの呼び出しによるインスタンスの破棄をキャンセルするには最外側のデストラクタの末尾までのどこかの時点で必ず例外を送出してインスタンス領域の解放を防ぐ必要があります。
元ねたはDelphiクイックリファレンス (amazon)/Ray Lischner著/光田秀、竹田知生訳/オライリー・ジャパン/ISBN4-87311-040-8/4,725円。
2011年5月2日
MS11-025によって発生する問題とその対策
2011/04のセキュリティアップデートのうち、MS11-025には、
このうちWindows 2000とVC2005/VC2008再頒布可能パッケージ(Redist)の問題についてはKB2467175とKB2467174がWindows 2000の適用範囲外となったため、インストールしないのが正しく、もしインストールしてしまった場合は
New redists break all dynamically linked MFC 2005/2008 apps on Windows 2000 « Ted's Blog
にあるように、
その他の(Visual Studio上での開発に関する)問題とその解決方法については
Problem with FindActCtxSectionString in MFC security updates on all platforms « Ted's Blog
Martin's Blog » BUG: Black Patchday for all OS from XP and later 3. – MFC 8.0 (VC-2005) or MFC 9.0 (VC-2008) linked dynamically to the MFC may not find the MFC language DLLs after installation of the security packs dated April 12th 2011
Static MFC code bloat problem from VC2010 is now in VC2008 SP1+security fix « Ted's Blog
Fixing problems with FindActCtxSectionString in MFC security updates « Ted's Blog
が参考になります。
2011/05/05追記: MicrosoftのVisual C++ Team Blogにもこの件に関する記事があります(考えてみれば当たり前か)。
MS11-025 Visual C++ Update Issue - Visual C++ Team Blog - Site Home - MSDN Blogs
これによると、Windows 2000に関する問題についてはこの時点(2011/04/26)ではMS11-025をアンインストールするしかないようですが、
いい加減な訳: 我々VC++チームではこれらの問題の原因を特定し、修正したものを現在テストしているところです。テストが完了したらアップデートは一般に利用可能となり、このブログを更新します。
ということで、近日中に何らかの解決が図られるようです。
2011/06/15追記: MS11-025が更新され、以下の問題が解決したとされています。
2011/06/21追記: Visual C++ Team Blogにも新しい更新プログラムで上記の問題を修正した、という記事が出ています。
Update on Bulletin MS11-025 - Visual C++ Team Blog - Site Home - MSDN Blogs
- Windows 2000 SP4にKB2467175 (Visual C++ 2005 SP1 再頒布可能パッケージ セキュリティ更新プログラム (2011 年 4 月 12 日))およびKB2467174 (Visual C++ 2008 SP1 再頒布可能パッケージ セキュリティ更新プログラム (2011 年 4 月 12 日))を適用するとMFC 8.0/9.0を動的にリンクしているプログラムが起動時にエラーになる。
- VC2008でMFCを静的リンクしたプログラムのサイズが(VC2010と同様に)不必要に大きくなる。
- MFC 8.0/9.0を動的にリンクしているプログラムがMFCの言語DLLを使用しない(DLLを発見できなくなる)。
- Windows XP以降にしか存在しないFindActCtxSectionStringAに静的にリンクしてしまうためリビルドしたプログラムがWindows 2000で動作しなくなる。
このうちWindows 2000とVC2005/VC2008再頒布可能パッケージ(Redist)の問題についてはKB2467175とKB2467174がWindows 2000の適用範囲外となったため、インストールしないのが正しく、もしインストールしてしまった場合は
New redists break all dynamically linked MFC 2005/2008 apps on Windows 2000 « Ted's Blog
にあるように、
- KB2467175とKB2467174をアンインストール
- Windows(WINNT)フォルダ内のWinSxSフォルダから4053と4148を検索して(フォルダが存在しなければ代わりにそれぞれ762と21022で検索)、検索結果に含まれるフォルダ内のファイル(ATLxx.DLL, MFCxx*.DLL, MFCMxx*.DLL, MSVCMxx*.DLL, MSVCPxx*.DLL, MSVCRxx*.DLL, VCOMPxx.DLL)全てを一時フォルダ("C:\MFCDLL"など)にコピー
- Windowsを再起動
- [F8]キーを押してWindows 2000拡張オプションメニューを表示し、セーフモードとコマンドプロンプトでコマンドプロンプトを選択して起動
- 一時フォルダ内のファイルをSystem32に(一時フォルダが"C:\MFCDLL"でWindowsのインストールフォルダが"C:\Windows"の場合)
copy C:\MFCDLL\*.* C:\Windows\System32
としてコピー - EXITで再起動
その他の(Visual Studio上での開発に関する)問題とその解決方法については
Problem with FindActCtxSectionString in MFC security updates on all platforms « Ted's Blog
Martin's Blog » BUG: Black Patchday for all OS from XP and later 3. – MFC 8.0 (VC-2005) or MFC 9.0 (VC-2008) linked dynamically to the MFC may not find the MFC language DLLs after installation of the security packs dated April 12th 2011
Static MFC code bloat problem from VC2010 is now in VC2008 SP1+security fix « Ted's Blog
Fixing problems with FindActCtxSectionString in MFC security updates « Ted's Blog
が参考になります。
2011/05/05追記: MicrosoftのVisual C++ Team Blogにもこの件に関する記事があります(考えてみれば当たり前か)。
MS11-025 Visual C++ Update Issue - Visual C++ Team Blog - Site Home - MSDN Blogs
これによると、Windows 2000に関する問題についてはこの時点(2011/04/26)ではMS11-025をアンインストールするしかないようですが、
Our team has identified the cause of these issues and is currently testing the fix. The update will be publicly available once testing is complete, and we will update this blog.
いい加減な訳: 我々VC++チームではこれらの問題の原因を特定し、修正したものを現在テストしているところです。テストが完了したらアップデートは一般に利用可能となり、このブログを更新します。
ということで、近日中に何らかの解決が図られるようです。
2011/06/15追記: MS11-025が更新され、以下の問題が解決したとされています。
- ローカライズされたアプリケーションをご使用のお客様に MFC アプリケーションのリソースの一部がローカライズされずに表示されます。アプリケーションは正常に機能しますが、リソースが英語で表示されます。
- MFC のセキュリティの修正は Microsoft Windows 2000 でサポートされていない API を使用します。Microsoft Windows 2000 システムに更新プログラムを適用した場合、インストールによっていくつかのアプリケーションの機能的な問題が引き起こされました。
- 以前の更新プログラムは x64-based アーキテクチャの Windows 7 SDK を使用しているユーザーにはインストールされませんでした。
2011/06/21追記: Visual C++ Team Blogにも新しい更新プログラムで上記の問題を修正した、という記事が出ています。
Update on Bulletin MS11-025 - Visual C++ Team Blog - Site Home - MSDN Blogs
登録:
投稿 (Atom)