2013年8月5日

CopyFileExを使う

DelphiでファイルをコピーするときはWin32APIのCopyFile関数 (en)か、これをラッピングした(System.)IOUtilsのTFile.Copyなどを使うのが普通ですが、大きめのファイルだったり遅いデバイスだったり、あるいはその両方で、ファイルコピーに5秒以上かかるとWindowsに"応答なし"と判断されてしまうことになります。ファイルコピーを別スレッドで行ってもよいのですが(TFile.DoCopyの実装を見る限りPOSIX環境ではこれしかなさそう)、Windows環境であればWin32APIのCopyFileEx関数 (en)を使い、コールバック関数内でApplication.ProcessMessagesを呼び出すことでこの問題を回避することができます。ではまず(Winapi.)Windows.pas上のCopyFileExの定義を見てみましょう。
type
  TFNProgressRoutine = TFarProc;

function CopyFileEx(lpExistingFileName, lpNewFileName: LPWSTR;
  lpProgressRoutine: TFNProgressRoutine; lpData: Pointer; pbCancel: PBool;
  dwCopyFlags: DWORD): BOOL; stdcall;
コールバック関数の型はTFNProgressRoutine=TFarProcと定義されていますが、TFarProcはというと、
TFarProc = Pointer;
となっており、やる気のなさ満点です(間違っちゃいないけど)。そこでまずCopyProgressRoutineコールバック関数 (en)の定義から用意します。
type
  TCopyProgressRoutine = function (TotalFileSize: Int64;
                                   TotalBytesTransferred: Int64;
                                   StreamSize: Int64;
                                   StreamBytesTransferred: Int64;
                                   dwStreamNumber: DWORD;
                                   dwCallbackReason: DWORD;
                                   hSourceFile: THandle;
                                   hDestinationFile: THandle;
                                   lpData: Pointer): DWORD; stdcall;
これを使ってCopyFileEx関数を再定義します。
function CopyFileEx(lpExistingFileName: PChar;
                    lpNewFileName: PChar;
                    lpProgressRoutine: TCopyProgressRoutine;
                    lpData: Pointer;
                    pbCancel: PBool;
                    dwCopyFlags: DWORD): BOOL; stdcall; external kernel32
{$IFDEF UNICODE}
                    name 'CopyFileExW';
{$ELSE}
                    name 'CopyFileExA';
{$ENDIF}
{$EXTERNALSYM CopyFileEx}

これらの定義を使ってファイルをコピーしてみましょう。まずフォーム上にコピー元ファイル名とコピー先ファイル名を入力するためのEditを2つとコピー開始のButton、途中経過表示用のLabelを配置します。
{$WARN SYMBOL_PLATFORM OFF}
const
  COPY_FILE_NO_BUFFERING = $00001000;

function CopyProgressFunc(TotalFileSize: Int64;
                          TotalBytesTransferred: Int64;
                          StreamSize: Int64;
                          StreamBytesTransferred: Int64;
                          dwStreamNumber: DWORD;
                          dwCallbackReason: DWORD;
                          hSourceFile: THandle;
                          hDestinationFile: THandle;
                          lpData: Pointer): DWORD; stdcall;
var
  TBT: Extended;
  TFS: Extended;
begin

  TFS := TotalFileSize;
  TBT := TotalBytesTransferred;

  with TObject(lpData) as TForm1 do
  begin
    if (TotalFileSize = 0) or (TotalBytesTransferred = 0) then
    begin
      Label1.Caption := '';
    end
    else
    begin
      Label1.Caption := Format('%.0n / %.0n bytes',[TBT,TFS]);
    end;
  end;

  Application.ProcessMessages;

  Result := PROGRESS_CONTINUE;

end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Canceled: BOOL;
  CopyFlags: DWORD;
begin

  Button1.Enabled := False;
  try
    Canceled := False;

    CopyFlags := COPY_FILE_FAIL_IF_EXISTS;
    if CheckWin32Version(6,0) then
    begin
      CopyFlags := CopyFlags or COPY_FILE_NO_BUFFERING;
    end;

    Win32Check(CopyFileEx(PChar(Edit1.Text),PChar(Edit2.Text),
               CopyProgressFunc,Self,@Canceled,CopyFlags));

  finally
    Button1.Enabled := True;
  end;

end;
これでファイルのコピー中に途中経過を表示できるようになります。またApplication.ProcessMessagesを呼び出すことでWindowsに"応答なし"と判定されることもなくなります(ただしイベントハンドラへの再入には十分注意が必要です)。

ここでは大きいファイルをコピーすることを想定しているため、Windows Vista以降ではdwCopyFlagsにCOPY_FILE_NO_BUFFERINGを追加指定しています(COPY_FILE_NO_BUFFERINGには功罪両面ありますが)。またファイルの上書きを許す場合はCopyFlagsにCOPY_FILE_FAIL_IF_EXISTSではなくて0を指定します(CopyFlagsにCOPY_FILE_FAIL_IF_EXISTSを指定したときにコピー先ファイルが存在しているとCopyFileExの戻値は0となり、GetLastError (en)はERROR_FILE_EXISTSを返します)。

さらにコピーの途中でキャンセルできるようにしてみます。フォームにキャンセル用のButtonと、privateメンバにBoolean型のフィールドFAbortedを追加します。
function CopyProgressFunc(TotalFileSize: Int64;
                          TotalBytesTransferred: Int64;
                          StreamSize: Int64;
                          StreamBytesTransferred: Int64;
                          dwStreamNumber: DWORD;
                          dwCallbackReason: DWORD;
                          hSourceFile: THandle;
                          hDestinationFile: THandle;
                          lpData: Pointer): DWORD; stdcall;
var
  TBT: Extended;
  TFS: Extended;
begin

  TFS := TotalFileSize;
  TBT := TotalBytesTransferred;

  with TObject(lpData) as TForm1 do
  begin
    if (TotalFileSize = 0) or (TotalBytesTransferred = 0) then
    begin
      Label1.Caption := '';
    end
    else
    begin
      Label1.Caption := Format('%.0n / %.0n bytes',[TBT,TFS]);
    end;

    Result := PROGRESS_CONTINUE;

    Application.ProcessMessages;
    if FAborted = True then
    begin
      Result := PROGRESS_CANCEL;
    end;
  end;

end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Canceled: BOOL;
  CopyFlags: DWORD;
begin

  FAborted := False;

  Button1.Enabled := False;
  try
    Canceled := False;

    CopyFlags := COPY_FILE_FAIL_IF_EXISTS;
    if CheckWin32Version(6,0) then
    begin
      CopyFlags := CopyFlags or COPY_FILE_NO_BUFFERING;
    end;

    Win32Check(CopyFileEx(PChar(Edit1.Text),PChar(Edit2.Text),
               CopyProgressFunc,Self,@Canceled,CopyFlags));

  finally
    Button1.Enabled := True;
  end;

end;

procedure TForm1.Button2Click(Sender: TObject);
begin

  FAborted := True;
  
end;
こんな感じです。コールバック関数がPROGRESS_CANCELを返してファイルコピーをキャンセルしたときはCopyFileExの戻値は0(エラー)となり、GetLastErrorはERROR_REQUEST_ABORTEDを返します。

さて、Delphi 2009以降でコールバックといえば無名メソッド、という連想が働きますが、それはまた次回

Win32APIのCopyFileExのコールバックを受け入れる (Gist)

2013年7月10日

Microsoft Monthly Update 2013/07

今日はMicrosoftのセキュリティアップデートの日です。
MS13-052
MS13-053
MS13-054
MS13-055
MS13-056
MS13-057
MS13-058

2013年7月8日

5th anniversary

さて、あっという間に5周年がやってきました。この1年間(2012/07/08-2013/07/07)のエントリは101で、週平均2.0を切るという惨憺たる状況でした(去年の冬くらいから先週まで仕事が立て込み続けた、というのが主な要因です、と言い訳してみる)。続いてアクセス解析の統計情報から月別ページビュー(PV)を。

2012/07: 7,528
2012/08: 7,352
2012/09: 7,855
2012/10: 6,810
2012/11: 6,781
2012/12: 6,427
2013/01: 5,326
2013/02: 4,907
2013/03: 5,117
2013/04: 6,456
2013/05: 6,635
2013/06: 6,319
2012/07-2013/06合計: 77,513

去年から約7%のダウンと、エントリ数の割にはそれほど下がっておらず、やや不思議な感じです(いつもご覧いただいているみなさまのおかげですね)。ページ別ではこの一年もWindowsのSNP(Scalable Networking Pack)を無効にするが約10%とトップですが、なぜか最近ダミーのDwmapi.dllを作成するが(3%程度ながら)PVを増やしています。どう見てもあまりいい理由ではなさそうですが…。
国別では日本、アメリカ合衆国、ロシア、ドイツ、フランスとなぜかヨーロッパ諸国からのアクセスが地道に増えているようです。

今年はここまでiOS対応で盛り上がってきましたがそれも一段落して、今年後半のAndroid対応(とiOS 7.0対応)がいつどのような形で実現するのかが焦点になりそうです。またLLVM化されたNEXTGENコンパイラについても注視が必要だと思われます。LLVMといえばC++11に続くC++14も規格化が近づいてきており、こちらも注目です。

2013年6月21日

[書籍]きつねさんでもわかるLLVM

ブックファースト 渋谷文化村通り店

きつねさんでもわかるLLVM (impress) (amazon)/柏木餅子風薬著/インプレスジャパン/ISBN 978-4-8443-3415-6/2,520円

を購入。この本はもともと"薄い本"(3日で出来るLLVMきつねさんとおぼえるLLVM)だったのが達人出版会からの電子書籍(きつねさんでもわかるLLVM)、そして薄くない普通の本へと出世した、なかなか稀有な本です。

2013年6月12日

2013年6月7日

DDevExtensions 2.7

Andreas HausladenさんDDevExtensionsが久しぶりにアップデートされてVersion 2.7になっています。またDDevExtensionsの機能を説明したPDFも用意されています。

DDevExtensions 2.7 for 2009-XE4 released | Andy's Blog and Tools

2013年5月27日

2013年5月15日

Microsoft Monthly Update 2013/05

今日はMicrosoftのセキュリティアップデートの日です。
MS13-037
MS13-038
MS13-039
MS13-040
MS13-041
MS13-042
MS13-043
MS13-044
MS13-045
MS13-046

2013年5月2日

2013年4月23日

[書籍]Delphiで簡単iOSアプリプログラミング

エンバカデロ・テクノロジーズさんから

Delphiで簡単iOSアプリプログラミング (amazon)/細川淳著/エンバカデロ・テクノロジーズ監修/カットシステム/ISBN 978-4-87783-310-7/2,730円

を頂きました(会社用はあとで購入します)。お手伝いを始めたときは間に合うのかと疑問でしたが、細川さんの睡眠時間をたっぷりと吸い取って無事に完成に漕ぎ着けました。もう少しいろいろアイデアとか出せればよかったのですが。ともあれ出版おめでとうございます。

第25回エンバカデロ・デベロッパーキャンプ

本日10:00から第25回エンバカデロ・デベロッパーキャンプが御茶ノ水ソラシティのソラシティカンファレンスセンターで行われます。今回はすべてのセッションがUStreamで中継されるとのことです。

開場が多少遅れたようですが、まもなく開始になります。

セッション開始です。B側(Ch.2)のUStreamは不調のようです(午前中はC側、Ch.1のみの中継になるとのことです)。

UStreamのCh.2も復活したようです。

全セッション、懇親会とも無事終了しました。参加者、セッションスピーカ、関係者のみなさん、おつかれさまでした。

2013/05/07追記: セッション資料がダウンロードできるようになっています。

第25回 エンバカデロ・デベロッパーキャンプ - セッション資料ダウンロード

またセッションリプレイがYouTubeにアップロードされています。
  • 【C1】Delphi/iOSチュートリアルセッション 「Delphi for iOS開発ファーストステップ」
  • 【B1】Delphi/C++Builderテクニカルセッション 「マルチデバイスに対応できるDBアクセス形態を作るには?実践テクニック」
    • Replay (1/2)
    • Replay (2/2)
  • 【C2】Delphi/iOSテクニカルセッション 「モバイル開発始めるなら今でしょ!Delphi iOSアプリ開発講座」
  • 【B2】Delphi/C++Builderテクニカルセッション 「BDEアプリを最新へ – FireDACによる移行」
  • 【G3】ジェネラルセッション 「エンバカデロ・プロダクトアップデート」
  • 【C4】テクニカルケーススタディ 「森羅万象を視覚化する慶應義塾大学 藤代研究室での活用例 ~ FireMonkeyによる3DCGアプリ開発テクニック」
  • 【B4】 Delphi/C++Builderテクニカルセッション 「Web/モバイル系アプリでの帳票を考える」
    • Replay (1/2)
    • Replay (2/2)
  • 【C5】 Delphi/C++Builderテクニカルセッション 「Delphi/C++Builder MVVMアプローチ ~ VCL / FireMonkeyでもどんと来い!」
    • Replay (1/2)
    • Replay (2/2)
  • 【B5】 Delphi/C++Builderテクニカルセッション 「VCLユーザーのためのFireMonkey入門」
  • 【G6】 ライトニングトーク 「共有!みんなの開発事例、開発経験、テクニック」
    • Replay (1/2)
    • Replay (2/2)

2013年4月22日

InterBase XE3 Update 2

InterBase XE3 Update 2がリリースされています。バージョンは11.0.2.539となっています。2013/04/26にToGo Editionを除き差し替えとなり、バージョンは11.0.2.540に更新されています。高橋さん、情報ありがとうございます。

InterBase XE3 Update 2 Readme (en)
Team Japan » InterBase XE3 Update 2 がリリースされました
InterBase XE3 Update 2 is now available

29381 InterBase XE3 Update 2 (11.0.2.540) for Windows, English and Japanese
29382 InterBase XE3 Update 2 (11.0.2.539) ToGo Edition, Windows/MacOSX

29395 InterBase XE3 (11.0.2.540) Developer Edition, English
29396 InterBase XE3 (11.0.2.540) Developer Edition, Japanese

29383 InterBase XE3 Server Edition for Windows (11.0.2.540, English)
29384 InterBase XE3 Server Edition for Windows (11.0.2.540, Japanese)

RAD Studio/Delphi/C++Builder XE4リリース

RAD Studio/Delphi/C++Builder XE4が正式にリリースされたようです。

Delphi Insider: Announcing RAD Studio XE4, Delphi XE4 and C++Builder XE4

エンバカデロ、iPhone/iPadアプリのネイティブ開発を実現したマルチデバイス開発ツールRAD Studio XE4を世界同時発表 | Press Releases
RAD Studio XE4(Delphi XE4/C++Builder XE4) における不具合修正リスト (en)

RAD Studio XE4
RAD Studio XE4 製品ラインナップ
RAD Studio XE4 Q&A

29388 Delphi XE4 and C++Builder XE4 ISO
29392 Delphi XE4 and C++Builder XE4 ISO
29389 RAD Studio XE4 Install from the Web
29390 Delphi XE4 Install from the Web
29391 C++Builder XE4 Install from the Web

29369 FireDAC (iOS only) for RAD Studio and Delphi XE4 Pro with Mobile
29371 IP*Works for Delphi XE4
29372 IP*Works for C++Builder XE4
29373 AQtime for Delphi XE4, C++Builder XE4, and RAD Studio XE4
29408 InterBase Express (IBX) for RAD Studio, Delphi, C++Builder XE4
29441 FireMonkey Premium Style Pack 2 for RAD Studio XE4
29442 TMS Cloud Pack Components for iOS for Delphi and RAD Studio XE4
29443 Mida Embarcadero XE4 Edition VCL to FireMonkey Converter

ただしRAD Studioに含まれるHTML5 BuilderがXE4にバージョンアップしたかどうかは今のところ不明です(機能一覧を見る限りではXE3のままのようです)。またPrismは事前の情報通りRAD Studioから外れています。

2013/05/01追記: リリースパッケージに含まれていなかったInterBase Express(IBX)がダウンロードできるようになっています。

RAD Studio XE4, Delphi XE4, C++Builder XE4 用 InterBase Express (IBX) が公開されました
InterBase Express (IBX) now available for RAD Studio XE4, Delphi XE4, C++Builder XE4

2013/06/08追記: TMS Cloud Pack Components for iOSおよびMida Embarcadero XE4 Editionのリンクを追加。TMS Cloud Pack Components for iOSは2013/06/30までに購入したユーザ限定です。

2013/06/10追記: FireMonkey Premium Style Pack 2のリンクを追加(XE4 Update 1が必要なので要注意)。また「RAD Studio XE4発売記念キャンペーン」追加特典の入手方法によると、TMS Cloud Pack Components for iOSだけではなく、Mida Embarcadero XE4 EditionとFireMonkey Premium Style Pack 2についても2013/07/15までにダウンロードする必要があるとのこと。

2013年4月18日

RAD Studio XE4情報

いよいよ来週の第25回エンバカデロ・デベロッパーキャンプ(2013/04/23)とそれに続くRAD Studio XE4 Product Announcement Webinar(2013/04/24)でRAD Studio XE4が発表になるようですが、これに関連していくつか情報が出てきています。
  • iOSサポートの追加
    • ターゲットプラットフォームにiOSデバイスとiOSシミュレータが追加される。
    • iOSサポートはXE2に比べて充実したものになる。
    • FireDACもiOS上でサポートされる。
  • 新しいDelphiコンパイラ
    • ARMコンパイラは(おそらくLLVMベースの)新しいものになる。
    • GC(Garbage Collection)ではなくARC(Automatic Reference Counting)がサポートされる。
    • ARCはDelphiのインタフェースのように参照カウントで生存期間を管理する。
    • ARCでは[Weak]属性を付加することで参照カウントに影響を与えないweak referenceが用意される(これにより循環参照により参照カウントが0にならない状況を回避できる)。
    • ARCではDisposeOfメソッドを呼び出すことで明示的にオブジェクトのデストラクタを呼び出すことができる。後方互換性のためWin/MacではDisposeOfはFreeを呼び出す。一方iOSではFreeはデストラクタを呼び出さなくなり、代わりに参照をnilにセットするようになる。FreeAndNilも同様。
    • これらはiOSデバイスとiOSシミュレータ(と次のAndroid)のみで、Win/Macには適用されない。Win/Macでこれらをいつどのように適用するのかについてはまだ検討段階。
    • ARCの詳細はhttp://docwiki.embarcadero.com/RADStudio/XE4/ja/Delphi_%E3%83%A2%E3%83%90%E3%82%A4%E3%83%AB_%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9%E3%81%A7%E3%81%AE%E8%87%AA%E5%8B%95%E5%8F%82%E7%85%A7%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88を参照。
  • InterBase for iOS
    • おそらくiOS上で動作するInterBaseがリリースされる。
  • PrismがRAD Studioに含まれなくなる
    • RemObjectsによるOxygeneとして単独製品のみになり、Embarcaderoとのパートナーシップは解消される。
    • PrismはOxygene for .NETとなり、Oxygene for Java (and Android)、Oxygene for Mac and iOS (codename "Nougat")と並んでラインナップされる。
  • iOSサポートがadd-onの形でも提供される
    • おそらくEnt/Arc SKU以外のSKUにiOSサポートは含まれず、Pro SKUに対してはDatasnapやFireDACのようにadd-onの形で提供されるらしい。
元ねたはAndreano LanusseさんDelphi XE4 official announcement | Andreano Lanusse、RemObjectsのmarc hoffmanさんのPrism XE4, Where Art Thou? | RemObjects Blogs

2013/04/19追記: XE4の価格に関する"リーク"が発見されたようです。Chris RollistonさんDelphi XE4 pricing | Delphi Havenからリンクされているプロモーションビデオ(ロシア語)によると、
  • Starter SKU: 199USD(実質149USD)(従来通りWindows x86のみでWindows x64/Mac OS/iOSは含まれず)
  • Pro SKU: 新規購入が999USD、XE3からのiOSを含まないアップグレードが49USD、XE3からのiOSを含むアップグレードが499USD、2010/XE/XE2からのアップグレードが549USD、C/S add-onが499USD
  • Ent SKU: 新規購入が2,499USD、XE3からのアップグレードが499USD、2010/XE/XE2からのアップグレードが1,499USD
  • Ult SKU: 新規購入が3,499USD、XE3からのアップグレードが499USD、2010/XE/XE2からのアップグレードが2,199USD
  • Arc SKU: 新規購入が3,999USD、XE3からのアップグレードが499USD、2010/XE/XE2からのアップグレードが2,499USD
となっています。日本では価格政策が異なるためそのまま1USD=100JPY換算で価格設定されるのかどうかは不明ですが、参考にはなりそうです。

2013/04/20追記: XE4のオンラインヘルプが公開状態になっています。
2013/04/22追記: どうやらPro SKUにはiOS開発が含まれず、別途"Mobile Add-On Pack for Delphi XE4 Professional"を購入することになるようです。

2013年4月10日

Microsoft Monthly Update 2013/04

今日はMicrosoftのセキュリティアップデートの日です。
MS13-028
MS13-029
MS13-030
MS13-031
MS13-032
MS13-033
MS13-034
MS13-035
MS13-036

2013/04/12追記: MS13-036のうち2823324の更新プログラムにはWindowsの起動あるいは特定のプログラムの実行でSTOPエラーが発生する不具合が発見されており、2823324の更新プログラムをアンインストールすることを推奨するとのことです。

MS13-036 2823324 適用後の問題について - 日本のセキュリティチーム - Site Home - TechNet Blogs
You receive an Event ID 55 or a 0xc000021a Stop error in Windows 7 after you install security update 2823324

2013/04/30追記: MS13-036のうち2823324の更新プログラムに替わる新しい2840149の更新プログラムがリリースされています。2823324のアンインストール後に2840149のインストールを行うことが推奨されています。

MS13-036 2823324 適用後の問題について (2) – 再提供の開始 - 日本のセキュリティチーム - Site Home - TechNet Blogs

2013年4月3日

[書籍]Head First C

ブックファースト 渋谷文化村通り店Head First C (amazon)の翻訳である

Head First C (amazon)/David Griffiths、Dawn Griffiths著/木下哲也訳/中田秀基監訳/オライリージャパン/ISBN 978-4-87311-609-9/3,780円

を購入。

2013年3月25日

Firebird 2.5.2/2.1.5 Security Update 1

Firebird Ver2.5.2 Security Update 1 (Build 26540)およびVer2.1.5 Security Update 1 (Build 18497)がリリースされています。

Firebird: Firebird 2.5.2 Security Update 1
Release Notes (PDF)

Firebird: Firebird 2.1.5 Security Update 1
Release Notes (PDF)

認証されていないユーザから特別に加工されたパケットを送りつけることでスタックオーバフローが発生し、Firebirdを実行しているユーザ上でリモートからのコード実行が可能になるセキュリティホールが報告されており、これに対応するものです。信頼できないネットワークからのアクセスを受けるような状況ではアップデートを考慮すべきかと思われます。

2013年3月13日

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

第25回エンバカデロ・デベロッパーキャンプは2013年04月23日に開催されます。

エンバカデロ・デベロッパーキャンプ | ホーム
【4/23開催】第25回エンバカデロ・デベロッパーキャンプ

今回はiOS版とFireDACでしょうか。現在セッションスピーカを絶賛募集中です。

2013/03/26追記: セッション概要その他が発表されて参加登録が開始になっています。今回はAP担当シニアディレクタのMalcolm Grovesさんがいらっしゃるようです。また【C4】テクニカルケーススタディで中山さんが初登場です。なお例によってライトニングトークのスピーカも絶賛募集中です。あ、懇親会は学生さん無料になってます。

Microsoft Monthly Update 2013/03

今日はMicrosoftのセキュリティアップデートの日です。
MS13-021
MS13-022
MS13-023
MS13-024
MS13-025
MS13-026
MS13-027

2013年3月1日

[書籍] Unicode IVS/IVD入門

ブックファースト 渋谷文化村通り店

Unicode IVS/IVD入門 (amazon)/田丸健三郎、小林龍生著/日経BP/ISBN 978-4-82229-483-0/2,625円

を購入。

2013/05/16追記: 小林さんも認めていらっしゃるとおり、結構校正が甘くて微妙なところがあるのですが、NAOIさんがこの本の内容などについての補足をまとめてくださっていますので、この本を読む方はこちらも目を通しておくことを強くお勧めします。

IVS本へのツッコミ・まとめ - Mac OS Xの文字コード問題に関するメモ
IVS本に容赦なく突っ込みまくるNAOIさん - Togetter

2013/03開催のウェブセミナー

2013年2月13日

Microsoft Monthly Update 2013/02

今日はMicrosoftのセキュリティアップデートの日です。
MS13-009
MS13-010
MS13-011
MS13-012
MS13-013
MS13-014
MS13-015
MS13-016
MS13-017
MS13-018
MS13-019
MS13-020

2013年2月2日

2013年1月28日

[書籍]SQLアンチパターン

MARUZEN&ジュンク堂書店 渋谷店SQL Antipatterns (amazon)の翻訳である

SQLアンチパターン (amazon)/Bill Karwin著/児島修訳/和田卓人、和田省二監訳/オライリージャパン/ISBN 978-4-87311-589-4/3,360円

を購入。

RAD Studio/Delphi/C++Builder XE3 Help Update 3

RAD Studio/Delphi/C++Builder XE3 Update 2の一部としてRAD Studio/Delphi/C++Builder XE3のHelp Update 3がリリースされています。ただしRAD Studio/Delphi/C++Builder XE3 Update 2をインストールする前にHelp Update 2もアンインストールしておかないとヘルプが更新されないようです。また現時点(2013/01/28)で日本語のReadme(リリースノート)も公開されていないようです。

リリース ノート: Delphi XE3 および C++Builder XE3 Help Update 3 - RAD Studio XE3 (en)

元ねたは新井さんtweet

2013/02/17追記: 日本語のリリースノートが公開されました。

2013/02/28追記: RAD Studio/Delphi/C++Builder XE3のHelp Update 3が単独でもリリースされています。

29324 Help Update 3 for Delphi, C++Builder and RAD Studio XE3

2013年1月25日

RAD Studio/Delphi/C++Builder XE3 Update 2

RAD Studio/Delphi/C++Builder XE3のUpdate 2がリリースされています。

29294 Update 2 for Delphi, C++Builder and RAD Studio XE3
29291 Delphi XE3 and C++Builder XE3 ISO (includes Update 2)

RAD Studio XE3/Delphi XE3/C++Builder XE3 Update 2

XE3 Update 2 のリリース ノート - RAD Studio XE3 (en)

RAD Studio XE3(Delphi XE3/C++Builder XE3) Update 2 における不具合修正リスト (en)

2013/01/28追記: RAD Studio/Delphi/C++Builder XE3 Update 2にはHelp Update 3が含まれています。Update 1およびHelp Update 1/2をアンインストールしてから改めてUpdate 2をインストールする必要があります。

2013年1月15日

Microsoft OOB Update 2013/01

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

2013年1月12日

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

Firebirdのロードマップが更新されています。

Firebird: Roadmap

てきとうな要約:
  • 2.0 - 2.0.7(2012/04)が最終版で今後更新されることはない。
  • 2.1 - 2.1.5(2012/06)が最新版で、年1回のメンテナンスサイクルでのリリースが宣言されている。次のリリース(2.1.6)は2013年のQ3を予定。
  • 2.5 - 2.5.2(2012/11)が最新版で、バグフィックスを最優先で行っており、年2回のメンテナンスサイクルでのリリースが宣言されている。次のリリース(2.5.3)は2013年のQ2を予定。
  • 3.0 - 次のマイルストーンリリースで、現在鋭意開発中。α1リリースを2013年のQ1に計画。キーポイントは以下の通り。
    • 新しい認証サブシステム、構成サブシステムを含む、改訂されたアーキテクチャ
    • 共有ページキャッシュを備えた(SMP/マルチコアと親和性のある)スケーラブルなマルチスレッドエンジン
    • 新しいシステムテーブル、モニタテーブルを備えた新しいODS(Version 12)
    • JavaやC++などによる外部ストアドプロシージャ、トリガ、関数
    • 作り直されたオプティマイザと新しいデータアクセスメソッド
    • セキュリティの強化
    • さまざまなSQL拡張
    ただし最終的な機能セットについてはまだ合意に至っていない。

2013年1月9日

Microsoft Monthly Update 2013/01

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

2012年12月25日

[書籍]デバッグの理論と実践

MARUZEN&ジュンク堂書店 渋谷店Why Programs Fail, 2nd Editionの翻訳である

デバッグの理論と実践 (amazon)/Andreas Zeller著/今田昌宏、大岩尚宏、竹田香苗、宮原久美子、宗形紗織訳/中田秀基監訳/オライリージャパン/ISBN 978-4-87311-593-1/3,360円

を購入。

2012年12月24日

[eBook]Parallel Programming with OmniThreadLibrary

LeanpubでPrimoz GabrijelcicさんによるDelphi上のマルチスレッドライブラリOmniThreadLibrary(OTL)の解説書である

Parallel Programming with OmniThreadLibrary/Primoz Gabrijelcic著/Leanpub/30.00USD

を購入(PDF/EPUB/MOBIをダウンロード可能)。

この本は現在も執筆中で、状況はThe Delphi Geek: Parallel Programming with OmniThreadLibrary – Current Statusで確認できます。現時点(2012/12/24)ではだいたい半分ちょっとまで書き進んだ感じです。

2012年12月19日

IDE Fix Pack 5.2

Andreas HausladenさんIDE Fix Pack 2009-XE2がアップデートされてVersion 5.2になっています。Delphi 2009用のIDE Fix PackがWindows 8上でクラッシュする問題が修正されています(Delphi 2009以外のものは5.1のままです)。

IDE Fix Pack 5.2 for Delphi 2009 – Windows 8 fix | Andy's Blog and Tools

2012年12月12日

2012年12月10日

RAD Studio/Delphi/C++Builder XE3 Update 1

RAD Studio/Delphi/C++Builder XE3のUpdate 1がリリースされています。

28984 Delphi XE3 and C++Builder XE3 ISO (includes Update 1)
28985 Delphi XE3 and C++Builder XE3 ISO (includes Update 1)

Update 1 for RAD Studio XE3, Delphi XE3 and C++Builder XE3
RAD Studio XE3/Delphi XE3/C++Builder XE3 Update 1

RAD Studio XE3(Delphi XE3/C++Builder XE3) Update 1 における不具合修正リスト (en)

C++Builder 64 ビット Windows 版のリリース ノート - RAD Studio XE3 (en)
リリース ノート: Delphi XE3 および C++Builder XE3 Help Update 2 - RAD Studio XE3 (en)

C++Builder XE3のWindows x64版に関係するオンラインヘルプの項目:2013/01/21追記: XE3 Update 1にはXE3 Help Update 2が含まれているようです。関連リンクを追加しました。

OVERFLOWCHECKS/RANGECHECKSオプションの効果

このアーティクルはDelphi Advent Calendar 2012に参加しています(7日ぶり2回目)。

Delphiにはオーバフローチェックと範囲チェックというコンパイラオプションがあります。

オーバーフローのチェック(Delphi) - RAD Studio XE3
範囲チェック - RAD Studio XE3

いずれもプロジェクトオプションのコンパイラで指定するか、{$OVERFLOWCHECKS ON|OFF}{$RANGECHECKS ON|OFF}で任意の範囲に対して指定することができます。しかしデフォルトの状態ではどちらもOFFなので、ONにした場合の

オーバーフローのチェックを有効にすると,プログラムは遅くなり,またいくらか大きくなるため,{$Q+} はデバッグにのみ使用してください。

範囲チェックを有効にすると、プログラムの処理速度が遅くなり、サイズも多少大きくなります。

が実際にどのようなことを示しているのかを知っている人は意外に少ないのではないでしょうか。そこでこれらのオプションによるペナルティがどのようなものなのかを確認してみます(Delphi XE2/Windows x86/Debugビルドで検証)。

まず{$OVERFLOWCHECKS ON}(短縮形{$Q+})です。
var
  a: Integer;
  b: Integer;
  c: Integer;
begin
{$OVERFLOWCHECKS ON}
  a := $44444444;
  b := $44444444;
  c := a + b;
{$OVERFLOWCHECKS OFF}
end;
このコードは以下のように展開されます。

Unit1.pas.36: a := $44444444;
0051139C C745F844444444   mov [ebp-$08],$44444444
Unit1.pas.37: b := $44444444;
005113A3 C745F444444444   mov [ebp-$0c],$44444444
Unit1.pas.38: c := a + b;
005113AA 8B45F8           mov eax,[ebp-$08]
005113AD 0345F4           add eax,[ebp-$0c]
005113B0 7105             jno $005113b7
005113B2 E8BD39EFFF       call @IntOver
005113B7 8945F0           mov [ebp-$10],eax
赤で示した2行が{$OVERFLOWCHECKS ON}で追加される命令になります。直前のadd命令の結果、OF(overflow flag)がセットされていればIntOver(System.pasのprocedure _IntOver)を呼び出して例外EIntOverflowが生成されます。{$OVERFLOWCHECKS ON}とすることで、特定の整数算術演算(+,-,*,Abs,Sqr,Succ,Pred,Inc,および Dec)毎にこのコードが追加で生成される、ということのようです。

一方{$RANGECHECKS ON}(短縮形{$R+})ですが、これには配列および文字列の添字のチェックとスカラ型/部分範囲型変数への代入時の範囲チェックの2つの効果があります。まず添字のチェックですが、ちょっとわかりづらいので{$RANGECHECKS OFF}のものと両方を並べてみます。
var
  s: String;
  c: Char;
begin
  s := 'TEST';
  c := s[6];
{$RANGECHECKS ON}
  c := s[6];
{$RANGECHECKS OFF}
end;
このコードは以下のように展開されます。

Unit1.pas.52: c := s[6];
00511464 8B45F8           mov eax,[ebp-$08]
00511467 668B400A         mov ax,[eax+$0a]
0051146B 668945F6         mov [ebp-$0a],ax
Unit1.pas.54: c := s[6];
0051146F B806000000       mov eax,$00000006
00511474 8B55F8           mov edx,[ebp-$08]
00511477 48               dec eax
00511478 85D2             test edx,edx
0051147A 7405             jz $00511481
0051147C 3B42FC           cmp eax,[edx-$04]
0051147F 7205             jb $00511486
00511481 E8E638EFFF       call @BoundErr
00511486 40               inc eax
00511487 668B4442FE       mov ax,[edx+eax*2-$02]
0051148C 668945F6         mov [ebp-$0a],ax
{$RANGECHECKS OFF}の場合は単に文字列の格納されているアドレスに添字-(1*SizeOf(Char))を足した場所にアクセスしているだけですが、{$RANGECHECKS ON}では空文字列かどうかのチェック(赤字)と文字列長以内かどうかのチェック(青字)が行われていることを含め、添字の一時的なデクリメント(文字列の添字が1オリジンなので)など、結構複雑な処理が行われていることがわかります(こちらは例外ERangeErrorが生成されます)。

最後に部分範囲型の変数への代入を見てみます。
type
  TSubInt = 8..15;
var
  a: TSubInt;
  b: TSubInt;
  c: TSubInt;
begin
{$RANGECHECKS ON}
  a := 8;
  b := 8;
  c := a + b;
  a := 9;
  b := 8;
  c := a - b;
{$RANGECHECKS OFF}
end;
加算と減算の両方を確認してみます。

Unit1.pas.69: a := 8;
005114D8 C645FB08         mov byte ptr [ebp-$05],$08
Unit1.pas.70: b := 8;
005114DC C645FA08         mov byte ptr [ebp-$06],$08
Unit1.pas.71: c := a + b;
005114E0 33C0             xor eax,eax
005114E2 8A45FB           mov al,[ebp-$05]
005114E5 33D2             xor edx,edx
005114E7 8A55FA           mov dl,[ebp-$06]
005114EA 03C2             add eax,edx
005114EC 83C0F8           add eax,-$08
005114EF 83F807           cmp eax,$07
005114F2 7605             jbe $005114f9
005114F4 E87338EFFF       call @BoundErr
005114F9 83C008           add eax,$08
005114FC 8845F9           mov [ebp-$07],al
Unit1.pas.72: a := 9;
005114FF C645FB09         mov byte ptr [ebp-$05],$09
Unit1.pas.73: b := 8;
00511503 C645FA08         mov byte ptr [ebp-$06],$08
Unit1.pas.74: c := a - b;
00511507 33C0             xor eax,eax
00511509 8A45FB           mov al,[ebp-$05]
0051150C 33D2             xor edx,edx
0051150E 8A55FA           mov dl,[ebp-$06]
00511511 2BC2             sub eax,edx
00511513 83C0F8           add eax,-$08
00511516 83F807           cmp eax,$07
00511519 7605             jbe $00511520
0051151B E84C38EFFF       call @BoundErr
00511520 83C008           add eax,$08
00511523 8845F9           mov [ebp-$07],al
加算、減算とも計算後に一時的に部分範囲の最小値を引いて、部分範囲におさまっているかどうかのチェックを行い(赤字)、改めて最小値を足す(青字)など、これもまた複雑な処理になっています。

ということで、オーバフローチェックはFLAGSレジスタのOFを検査するだけなので比較的ペナルティは小さく、オーバフローを検出したい状況では積極的にONにしても問題なさそうです。一方範囲チェックは範囲の最小値が0でないと処理が複雑になることもあり、明示的に必要な範囲チェックのコードを記述したほうがいいような気もします。

2012年12月7日

第24回エンバカデロ・デベロッパーキャンプ

本日10:00から第24回エンバカデロ・デベロッパーキャンプ東京ビッグサイト(東京国際展示場)の会議棟6Fで行われます。今回も一部のセッションがUStreamで中継されますが、残念ながらG6は中継なしとのことです。
  • 【A1】HTML5チュートリアルセッション「HTML5によるモバイル/タブレットアプリケーション開発」
  • 【B1】Delphiチュートリアルセッション「Delphiで学ぶ楽しいプログラミング基礎 - 出来るプログラマになる第一歩」
  • 【A2】Delphi/C++Builderテクニカルセッション「“FireMonkey が得意とするビジネスアプリ” の考察」
  • 【B2】C++テクニカルセッション「C++言語新機能を使おう!」
  • 【G3】ジェネラルセッション「エンバカデロ・プロダクトアップデート」
  • 【A4】Delphi/C++Builderテクニカルセッション「RAD Studio XE3によるWindows 8開発」
  • 【B4】 Delphi/C++Builderテクニカルワークショップ「Delphi / C++Builder 旧バージョンアプリケーションの移行」
  • 【A5】 Delphiテクニカルセッション「Delphi+Visual LiveBindingによるデータベースアプリケーション開発」
  • 【B5】 C++テクニカルセッション「C++Builder 64-bit Deep Dive」
  • 【G6】 Q & Aセッション「アスク・エンバカデロ」
第24回 エンバカデロ・デベロッパーキャンプ - セッション資料ダウンロード

2012/12/11追記: いろいろ忙しくてアーティクルの更新を忘れていました。最後の最後にどんでん返しがありましたが、無事に終了しました。セッションスピーカ、参加者、関係者の皆さん、おつかれさまでした。今回のデベロッパーキャンプでの情報としては
  • C++Builder x64版は2012/12/10リリース予定(既にリリース済ですが)
  • Mobile StudioはまもなくiOSについてベータ開始、春頃?リリース希望
  • Mobile Studio for AndroidはiOSのあと、今年の前半にはリリース希望
  • Mobile StudioのDelphiコンパイラは既にLLVMベースで動作?(未確認)
  • Mobile Studioのベータに参加してこんな機能がほしいという希望を出してほしい
  • Windows用のインストーラについては現在いろいろな製品を評価中
といったところでしょうか。また早くもセッション資料とリプレイビデオがアップロードされています。

2012年12月6日

C++Builder 64-bit Compiler Preview

C++Builder 64-bit Compiler Previewが公開されています。

29197 C++Builder 64-bit Compiler Preview

YouTubeにも同じビデオがアップロードされています(限定公開のようなので上記のリンクからたどってください)。

2012年12月3日

ジェネリックスの型パラメータに対する制約

このアーティクルはDelphi Advent Calendar 2012に参加しています。

ジェネリックスでは渡される型パラメータに関する制約を指定することができます。

ジェネリックスの制約 - RAD Studio XE3

ここで制約としてインタフェース型またはクラス型を指定すると型パラメータは必然的にそのクラス型、インタフェース型になります。またconstructorを指定すると型パラメータはクラス型になります(レコード型はパラメータを持たないコンストラクタを定義できないため)。classを指定するとこれもまたクラス型、インタフェース型になります(ヘルプ参照)。

では最後に残ったrecordを指定(レコード指定)した場合、型パラメータに許される型はレコード型だけなのでしょうか?実際にはレコード型だけではなく、いわゆる値型(参照型ではないもの)であればいいようで、IntegerやCardinal、あるいは部分範囲型や列挙型も指定することができます。
type
  TConstraintTest<T: record> = record
    class procedure TestProc(Value: Integer); static;
  end;

class procedure TConstraintTest<T>.TestProc(Value: Integer);
begin
  { No operation }
end;
このようなジェネリックスを定義すると、
type
  TTestRange = 1..5;
  TTestEnum = (One, Two, Three);

begin
  TConstraintTest<TPoint>.TestProc(0);
  TConstraintTest<Integer>.TestProc(0);
  TConstraintTest<Cardinal>.TestProc(0);
  TConstraintTest<TTestRange>.TestProc(0);
  TConstraintTest<TTestEnum>.TestProc(0);
end;
これらはすべて正しくコンパイルされます。

ただしString型は参照型でありながらクラス型ではないため、constructor以外の制約を指定された型パラメータにStringを指定するとコンパイルエラーになります("class"を指定するとE2511 型パラメータ 'T' はクラス型が必要です、"record"を指定するとE2512 型パラメータ 'T' は非 null 値型が必要ですとなる)。つまり文字列型を受け入れるためには型パラメータに制約を指定しないか、constructor制約を指定するかのいずれかが必要ということになります。

元ねたはDelphi XE2 Foundations

2013/07/03追記: constructor制約については細川さんによる考察が参考になります。

2012年11月25日

[ebook]Delphi XE2 Foundations

Amazon KindleストアでChris RollistonさんDelphi XE2 Foundationsのebook(Kindle)版である

Delphi XE2 Foundations - Part 1 [Kindle版]/Chris Rolliston著/Amazon Services International, Inc./792円
Delphi XE2 Foundations - Part 2 [Kindle版]/Chris Rolliston著/Amazon Services International, Inc./792円
Delphi XE2 Foundations - Part 3 [Kindle版]/Chris Rolliston著/Amazon Services International, Inc./792円

を購入。書籍版も買いましたが、あの分厚いのを持ち歩くのは結構大変だし、3分冊合わせても2,400円しない(書籍版は4,500円くらいかかる)のでまぁいいかと。

2012年11月19日

Kindle Paperwhite

Amazon.co.jpで注文した

Kindle Paperwhite/7,980円

が配送されてきました(今回の配送はクロネコヤマト)。

2012年11月16日

[書籍]実践 パケット解析 第2版

ブックファースト 渋谷文化村通り店Practical Packet Analysis, 2nd Edition (O'Reilly, amazon)の翻訳である

実践 パケット解析 第2版 (amazon)/Chris Sanders著/岡真由美訳/高橋基信、宮本久仁男監訳/オライリージャパン/ISBN 978-4-87311-569-6/2,940円

を購入。

2012年11月14日

Microsoft Monthly Update 2012/11

今日はMicrosoftのセキュリティアップデートの日です。
MS12-071
MS12-072
MS12-073
MS12-074
MS12-075
MS12-076

2012年11月7日

Firebird 2.5.2

Firebird Ver2.5.2がリリースされています。

Firebird: Firebird 2.5.2
Firebird 2.5 Release Notes (PDF)

なお2.5.1から2.5.2以降にバージョンアップする場合はgbakによるバックアップ/リストアを行うことが強く推奨されているので注意が必要です(これが不可能な場合は少なくとも複合インデックスをリビルドすること)。詳細はリリースノートを参照してください。

元ねたはFirebird News » Firebird 2.5.2 sub-release is available with many bugfixes

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

第24回エンバカデロ・デベロッパーキャンプは2012年12月07日に開催されます。

エンバカデロ・デベロッパーキャンプ | ホーム
エンバカデロ、12月7日に開発者向け技術イベントを開催 | Press Releases
第24回 エンバカデロ・デベロッパーキャンプ | Facebook
Delphi Talks 3 | Facebook

今回はRAD StudioプロダクトディレクタのJT@EmbarcaderoことJohn Thomasさんとローカライズ/ドキュメント担当マネージャの新井正広さんがいらっしゃるとのことです。目玉はC++Builderのx64コンパイラでしょうか。またPress ReleaseにはC++Builderの64-bitサポートは、すでにC++Builder XE3またはRAD Studio XE3を購入したユーザーには、無償ダウンロードで提供される予定です。という記述もありますね。

Marco CantuがDelphiプロダクトマネージャに

現在進行中のCodeRage 7のTechnical Session #4 PRODUCT ADDRESS: Delphi & C++Builderで、Delphi HandbookシリーズなどでおなじみのMarco Cantuさんが新しいDelphiのプロダクトマネージャになったことがアナウンスされました。David Iさんのtweetから。

https://twitter.com/davidi99/status/265847230009835520
Delphi Product Address session starting now. John Thomas, Sarina Dupont and new Delphi product manager Marco Cantu

Marco Cantuさんのtweetも。

https://twitter.com/marcocantu/status/265849281007722497
So it's official now: I'm joining Embarcadero as Delphi Product Manager. Will still be based in Italy.

Marco Cantuさんのblogにもアーティクルがあがっています。

Joining Embarcadero as Delphi Product Manager

JTさんのblogにも。

JT @ Embarcadero » Welcome Marco Cantu - Delphi Product Manager

2012年11月1日

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

2012/11/21追記: CodeRage 7のセッションリプレイがCodeCentralとYoutubeにアップロードされていますが、David Iさんのところにリンクがまとめられています。

Sip from the Firehose : CodeRage 7 Delphi conference sessions are ready to watch and download

RAD Studio/Delphi/C++Builder XE3 Help Update 1

RAD Studio/Delphi/C++Builder XE3のHelp Update 1がリリースされています。

29134 Help Update 1 for Delphi, C++Builder and RAD Studio XE3
リリース ノート: Delphi XE3 および C++Builder XE3 Help Update 1 - RAD Studio XE3 (en)

2012年10月24日

RAD Studio/Delphi/C++Builder XE3 Hotfix 4

RAD Studio/Delphi/C++Builder XE3のHotfix 4がリリースされています。FireMonkeyにおける韓国語IMEの問題を解決するものです。

29089 RAD Studio XE3 Hotfix 4
Hotfix 4 for RAD Studio XE3, Delphi XE3 and C++Builder XE3

2012年10月10日

Microsoft Monthly Update 2012/10

今日はMicrosoftのセキュリティアップデートの日です。
MS12-064
MS12-065
MS12-066
MS12-067
MS12-068
MS12-069
MS12-070

2012年10月4日

[書籍]インサイドWindows第6版(上)

ブックファースト 新宿店Windows Internals, 6th edition Part 1 (amazon)の翻訳である

インサイドWindows 第6版 上 (amazon)/Mark E. RussinovichDavid A. SolomonAlex Ionescu著/株式会社クイープ訳/日経BP/ISBN 978-4-82229-470-0/7,980円

を購入。なお下巻(Part 2)は2013年春の出版予定とのことです。

ファイルがSSDに書き込まれるかどうかを調べる(2)

前回の続きです。パート2としてATA8-ACSを使用して"nominal media rotation rate"を取得する方法を試してみます(こちらもNyaRuRuさんのコードそのままなので詳細な説明は省略します)。

まずはDeviceIOControlで使用する構造体、定数などを定義します。
type
  { ATA_PASS_THROUGH_EX }
  _ATA_PASS_THROUGH_EX = packed record
    Length: Word;
    AtaFlags: Word;
    PathId: UCHAR;
    TargetId: UCHAR;
    Lun: UCHAR;
    ReservedAsUchar: UCHAR;
    DataTransferLength: ULONG;
    TimeOutValue: ULONG;
    ReservedAsUlong: ULONG;
    DataBufferOffset: ULONG_PTR;
    PreviousTaskFile: array [0..7] of UCHAR;
    CurrentTaskFile: array [0..7] of UCHAR;
  end;
  {$EXTERNALSYM _ATA_PASS_THROUGH_EX}
  ATA_PASS_THROUGH_EX = _ATA_PASS_THROUGH_EX;
  {$EXTERNALSYM  ATA_PASS_THROUGH_EX}
  TAtaPassThroughEx   = _ATA_PASS_THROUGH_EX;
  PAtaPassThroughEx   = ^TAtaPassThroughEx;

  { ATAIdentifyDeviceQuery }
  TATAIdentifyDeviceQuery = packed record
    header: ATA_PASS_THROUGH_EX;
    data: array [0..255] of Word;
  end;

const
{$IF RTLVersion < 22.0}
  FILE_DEVICE_CONTROLLER  = $00000004;
  {$EXTERNALSYM FILE_DEVICE_CONTROLLER}

  FILE_READ_ACCESS        = $0001;
  {$EXTERNALSYM FILE_READ_ACCESS}

  FILE_WRITE_ACCESS       = $0002;
  {$EXTERNALSYM FILE_WRITE_ACCESS}
{$IFEND}

  ATA_FLAGS_DRDY_REQUIRED = $01;
  ATA_FLAGS_DATA_IN       = $02;
  ATA_FLAGS_DATA_OUT      = $04;
  ATA_FLAGS_48BIT_COMMAND = $08;
  ATA_FLAGS_USE_DMA       = $10;
  ATA_FLAGS_NO_MULTIPLE   = $20;

  IOCTL_SCSI_BASE         = FILE_DEVICE_CONTROLLER;
  IOCTL_ATA_PASS_THROUGH  = (IOCTL_SCSI_BASE shl 16) or
                            ((FILE_READ_ACCESS or FILE_WRITE_ACCESS) shl 14) or
                            ($040B shl 2) or
                            (METHOD_BUFFERED);
FILE_DEVICE_CONTROLLER、FILE_READ_ACCESS、FILE_WRITE_ACCESSはDelphi XE以降では定義済ですので、Delphi 2009およびそれ以前のバージョン用に定義しています。

これらを使用して物理ドライブ名"\\.\PhysicalDrive#"で指定したドライブが"nominal media rotation rate"かどうかを調べる関数です。
function HasNominalMediaRotationRate(const PhysicalDrivePath: String): Boolean;
var
  h: THandle;
  ATAIdentifyDeviceQuery: TATAIdentifyDeviceQuery;
  RSize: DWORD;
begin

  h := CreateFile(PChar(PhysicalDrivePath),GENERIC_READ or GENERIC_WRITE,
                  FILE_SHARE_READ or FILE_SHARE_WRITE,nil,
                  OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
  if h = INVALID_HANDLE_VALUE then
  begin
    RaiseLastOSError;
  end;

  try
    FillChar(ATAIdentifyDeviceQuery,SizeOf(ATAIdentifyDeviceQuery),0);
    with ATAIdentifyDeviceQuery do
    begin
      header.Length := SizeOf(header);
      header.AtaFlags := ATA_FLAGS_DATA_IN;
      header.DataTransferLength := SizeOf(data);
      header.TimeOutValue := 3;  // sec
      header.DataBufferOffset := SizeOf(header);
      header.CurrentTaskFile[6] := $EC;  // ATA IDENTIFY DEVICE command
    end;

    RSize := 0;
    if DeviceIoControl(h,IOCTL_ATA_PASS_THROUGH,
                       @ATAIdentifyDeviceQuery,SizeOf(ATAIdentifyDeviceQuery),
                       @ATAIdentifyDeviceQuery,SizeOf(ATAIdentifyDeviceQuery),
                       RSize,nil) = False then
    begin
      RaiseLastOSError;
    end;

    Result := (ATAIdentifyDeviceQuery.data[217] = 1);

  finally
    CloseHandle(h);
  end;

end;
Trueが返ってくればそのドライブはnominal media rotation rateである、つまりSSDと判断できる、ということになります。こちらの方法はデバイスがATA8-ACSに対応していればWindows 2000およびそれ以降のすべてのOSで使用できますが、Windows Vista以降では管理者権限が必要になります。Delphiで作ったアプリケーションが管理者権限を要求するようにする方法についてはWindows Vista上で管理者権限を要求するアプリケーションを作成するを参照してください。なおWindows Vista以降の環境で管理者権限を要求するアプリケーションをデバッグするときはDelphiのIDEそのものも管理者権限で実行する必要があるようですので注意してください。

ファイル名/パス名から物理ドライブ番号("\\.\PhysicalDrive#"の#)を取得する方法は前回のものを使用できますので、こちらもファイル/パスがSSD上に書き込まれるかどうかを調べてみます。
var
  Index: Integer;
  Filename: String;
  PhysicalDrives: TIntegerDynArray;
  PhysicalDrivePath: String;
  IsSSD: Boolean;
begin

  Filename := "C:\";  // 例: "C:\"を調べます

  SetLength(PhysicalDrives,0);
  PathnameToPhysicalDriveNumber(Filename,PhysicalDrives);

  try
    IsSSD := False;
    for Index := Low(PhysicalDrives) to High(PhysicalDrives) do
    begin
      PhysicalDrivePath := Format('\\.\PhysicalDrive%d',[PhysicalDrives[Index]]);
      try
        IsSSD := IsSSD or HasNominalMediaRotationRate(PhysicalDrivePath);

      except
        { Ignore }
      end;

      if IsSSD = True then
      begin
        Break;
      end;
    end;

    if IsSSD = True then
    begin
      MessageDlg(Format('ファイル ''%s'' はSSDに書き込まれます。',[Filename]),
                 mtInformation,[mbOk],0);
    end
    else
    begin
      MessageDlg(Format('ファイル ''%s'' はSSDには書き込まれません。',[Filename]),
                 mtInformation,[mbOk],0);
    end;

  finally
    SetLength(PhysicalDrives,0);
  end;

end;
OSによって判定方法を切り替えるのであれば、
if CheckWin32Version(6,1) = True then
begin
  { Windows 7 or later }
  IsSSD := IsSSD or HasNoSeekPenalty(PhysicalDrivePath);
end
else
begin
  { Windows Vista or earlier }
  IsSSD := IsSSD or HasNominalMediaRotationRate(PhysicalDrivePath);
end;
とすればよいのですが、こうしたところでアプリケーション全体としては管理者権限が必要になってしまうので微妙な感じです(Vistaを考えなければ管理者権限を要求する必要がなくなりますが…)。

2012年10月3日

ファイルがSSDに書き込まれるかどうかを調べる(1)

プログラムの動作ログをファイルに記録するということはよくあることだと思いますが、単にCreateFile (en)でファイルをオープンするだけだと書き込み内容がOSでキャッシュされてしまい、OSがクラッシュしたり突然の電源断で書き込み内容の一部が失われてしまう可能性があります。これを防ぐにはMSDNのCreateFileのCaching Behaviorの説明に

If FILE_FLAG_WRITE_THROUGH is used but FILE_FLAG_NO_BUFFERING is not also specified, so that system caching is in effect, then the data is written to the system cache but is flushed to disk without delay.

てきとうな訳: FILE_FLAG_NO_BUFFERINGを指定せずにFILE_FLAG_WRITE_THROUGHを指定すると、システムキャッシュは有効となり、データはWindowsのシステムキャッシュに書き込まれますがディスクには遅延なくフラッシュされます。

とあるように、CreateFileの第6パラメータdwFlagsAndAttributesにFILE_FLAG_WRITE_THROUGHを指定します。

しかし大量のログを保存するようなケースで保存先がSSDだと、FILE_FLAG_WRITE_THROUGHの指定によりSSDの劣化が通常よりも早く進行することが予想されます。ということで保存先がSSDかどうかでこれらのフラグを付加するかどうかを決めるようにすればいい、ということになります。しかしドライブがSSDかどうかについては、(1)DeviceIOControl (en)でPropertyIdにStorageDeviceSeekPenaltyPropertyを指定したIOCTL_STORAGE_QUERY_PROPERTYを発行してDEVICE_SEEK_PENALTY_DESCRIPTOR構造体のIncursSeekPenaltyが0(False)になっている("no seek penalty")、(2)ATA8-ACSでドライブの回転数を取得してNominal Media Rotation Rate(0x01)になっている、のどちらかで調べることができる、ということまではわかったものの、さて実際のコード例はというとなかなか参考になるものがなく、手詰まりになっていました。

ところがその数日後、NyaRuRuさんがほぼそのまんまの

SSD なら動作を変えるアプリケーションを作る - NyaRuRuが地球にいたころ

という記事を書いているのを見つけました。これだけきちんとしたサンプルがあればDelphiに置き換えるのも簡単です。ということでパート1として(1)の"no seek penalty"を取得する方法を試してみます(以下NyaRuRuさんのコードそのままなので詳細な説明は省略します)。

まずはDeviceIOControlで使用する構造体、定数などを定義します。
{$IF RTLversion < 22.0}
const
  FILE_READ_DATA               = $0001;
  {$EXTERNALSYM FILE_READ_DATA}

  FILE_READ_ATTRIBUTES         = $0080;
  {$EXTERNALSYM FILE_READ_ATTRIBUTES}

  FILE_DEVICE_MASS_STORAGE     = $0000002d;
  {$EXTERNALSYM FILE_DEVICE_MASS_STORAGE}

  IOCTL_STORAGE_BASE           = FILE_DEVICE_MASS_STORAGE;
  {$EXTERNALSYM IOCTL_STORAGE_BASE}

  FILE_ANY_ACCESS              = 0;
  {$EXTERNALSYM FILE_ANY_ACCESS}

  METHOD_BUFFERED              = 0;
  {$EXTERNALSYM METHOD_BUFFERED}

  IOCTL_STORAGE_QUERY_PROPERTY = (IOCTL_STORAGE_BASE shl 16) or
                                 (FILE_ANY_ACCESS shl 14) or
                                 ($0500 shl 2) or
                                 (METHOD_BUFFERED);
  {$EXTERNALSYM IOCTL_STORAGE_QUERY_PROPERTY}
{$IFEND}

type
  { STORAGE_PROPERTY_ID }
  _STORAGE_PROPERTY_ID = (
    StorageDeviceProperty                 =  0,
    StorageAdapterProperty                =  1,
    StorageDeviceIdProperty               =  2,
    StorageDeviceUniqueIdProperty         =  3,
    StorageDeviceWriteCacheProperty       =  4,
    StorageMiniportProperty               =  5,
    StorageAccessAlignmentProperty        =  6,
    StorageDeviceSeekPenaltyProperty      =  7,
    StorageDeviceTrimProperty             =  8,
    StorageDeviceWriteAggregationProperty =  9,
    StorageDeviceDeviceTelemetryProperty  = 10
  );
  {$EXTERNALSYM _STORAGE_PROPERTY_ID}
  STORAGE_PROPERTY_ID = _STORAGE_PROPERTY_ID;
  {$EXTERNALSYM  STORAGE_PROPERTY_ID}
  TStoragePropertyId  = _STORAGE_PROPERTY_ID;
  PStoragePropertyId  = ^TStoragePropertyId;

  { STORAGE_QUERY_TYPE }
  _STORAGE_QUERY_TYPE = (
    PropertyStandardQuery   = 0,
    PropertyExistsQuery     = 1,
    PropertyMaskQuery       = 2,
    PropertyQueryMaxDefined = 3
  );
  {$EXTERNALSYM _STORAGE_QUERY_TYPE}
  STORAGE_QUERY_TYPE = _STORAGE_QUERY_TYPE;
  {$EXTERNALSYM  STORAGE_QUERY_TYPE}
  TStorageQueryType  = _STORAGE_QUERY_TYPE;
  PStorageQueryType  = ^TStorageQueryType;

  { STORAGE_PROPERTY_QUERY }
  _STORAGE_PROPERTY_QUERY = packed record
    PropertyId: DWORD;
    QueryType: DWORD;
    AdditionalParameters: array[0..9] of Byte;
  end;
  {$EXTERNALSYM _STORAGE_PROPERTY_QUERY}
  STORAGE_PROPERTY_QUERY = _STORAGE_PROPERTY_QUERY;
  {$EXTERNALSYM  STORAGE_PROPERTY_QUERY}
  TStoragePropertyQuery  = _STORAGE_PROPERTY_QUERY;
  PStoragePropertyQuery  = ^TStoragePropertyQuery;

  { DEVICE_SEEK_PENALTY_DESCRIPTOR }
  _DEVICE_SEEK_PENALTY_DESCRIPTOR = packed record
    Version: DWORD;
    Size: DWORD;
    IncursSeekPenalty: ByteBool;
    Reserved: array[0..2] of Byte;
  end;
  {$EXTERNALSYM _DEVICE_SEEK_PENALTY_DESCRIPTOR}
  DEVICE_SEEK_PENALTY_DESCRIPTOR = _DEVICE_SEEK_PENALTY_DESCRIPTOR;
  {$EXTERNALSYM  DEVICE_SEEK_PENALTY_DESCRIPTOR}
  TDeviceSeekPenaltyDescriptor   = _DEVICE_SEEK_PENALTY_DESCRIPTOR;
  PDeviceSeekPenaltyDescriptor   = ^TDeviceSeekPenaltyDescriptor;
FILE_READ_DATA、FILE_READ_ATTRIBUTES、FILE_DEVICE_MASS_STORAGE、FILE_ANY_ACCESS、METHOD_BUFFERED、IOCTL_STORAGE_QUERY_PROPERTYはDelphi XE以降では定義済ですので、Delphi 2009およびそれ以前のバージョン用として定義しています。

これらを使用して物理ドライブ名"\\.\PhysicalDrive#"で指定したドライブが"no seek penalty"かどうかを調べる関数です。
function HasNoSeekPenalty(const PhysicalDrivePath: String): Boolean;
var
  h :THandle;
  StoragePropertyQuery: TStoragePropertyQuery;
  DeviceSeekPenaltyDescriptor: TDeviceSeekPenaltyDescriptor;
  RSize: DWORD;
begin

  h := CreateFile(PChar(PhysicalDrivePath),FILE_READ_ATTRIBUTES,
                  FILE_SHARE_READ or FILE_SHARE_WRITE,nil,
                  OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
  if h = INVALID_HANDLE_VALUE then
  begin
    RaiseLastOSError;
  end;

  try
    with StoragePropertyQuery do
    begin
      PropertyId := Ord(StorageDeviceSeekPenaltyProperty);
      QueryType  := Ord(PropertyStandardQuery);
    end;

    FillChar(DeviceSeekPenaltyDescriptor,SizeOf(DeviceSeekPenaltyDescriptor),0);
    RSize := 0;
    if DeviceIoControl(h,IOCTL_STORAGE_QUERY_PROPERTY,
                       @StoragePropertyQuery,SizeOf(StoragePropertyQuery),
                       @DeviceSeekPenaltyDescriptor,SizeOf(DeviceSeekPenaltyDescriptor),
                       RSize,nil) = False then
    begin
      RaiseLastOSError;
    end;

    Result := not DeviceSeekPenaltyDescriptor.IncursSeekPenalty;

  finally
    CloseHandle(h);
  end;

end;
Trueが返ってくればそのドライブはseek penaltyがない、つまりSSDと判断できる、ということになります。ただしこの方法はWindows 7およびそれ以降でしか使用できません(そのかわり管理者権限は不要です)。

次にファイル名/パス名から物理ドライブ番号("\\.\PhysicalDrive#"の#)を取得する方法です。まず構造体、定数などの定義です。
type
  { DISK_EXTENT }
  _DISK_EXTENT = packed record
    DiskNumber: DWORD;
    StartingOffset: LARGE_INTEGER;
    ExtentLength: LARGE_INTEGER;
    Reserved: array [0..3] of Byte;
  end;
  {$EXTERNALSYM _DISK_EXTENT}
  DISK_EXTENT = _DISK_EXTENT;
  {$EXTERNALSYM  DISK_EXTENT}
  TDiskExtent = _DISK_EXTENT;
  PDiskExtent = ^TDiskExtent;

  { VOLUME_DISK_EXTENTS }
  _VOLUME_DISK_EXTENTS = packed record
    NumberOfDiskExtents: DWORD;
    Reserved: array [0..3] of Byte;
    Extents: array [0..0] of DISK_EXTENT;
  end;
  {$EXTERNALSYM _VOLUME_DISK_EXTENTS}
  VOLUME_DISK_EXTENTS = _VOLUME_DISK_EXTENTS;
  {$EXTERNALSYM  VOLUME_DISK_EXTENTS}
  TVolumeDiskExtents  =  VOLUME_DISK_EXTENTS;
  PVolumeDiskExtents  = ^TVolumeDiskExtents;

{$IF RTLVersion < 22.0}
const
  IOCTL_VOLUME_BASE                    = $00000056;
  {$EXTERNALSYM IOCTL_VOLUME_BASE}

  IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS = (IOCTL_VOLUME_BASE shl 16) or
                                         (FILE_ANY_ACCESS shl 14) or
                                         (0 shl 2) or
                                         (METHOD_BUFFERED);
  {$EXTERNALSYM IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS}
{$IFEND}
さきほどと同様にIOCTL_VOLUME_GET_VOLUME_DISK_EXTENTSはDelphi XE以降で定義済ですのでDelphi 2009およびそれ以前のバージョンでのみ定義が必要です。

指定したファイル名/パス名の存在する物理ドライブ番号を動的配列に格納する関数です。
uses
  Types;

procedure PathnameToPhysicalDriveNumber(const Path: String; var PhysicalDrives: TIntegerDynArray);
var
  h: THandle;
  I: Integer;
  MountPoint: String;
  VolumeName: String;
  Size: DWORD;
  RSize: DWORD;
  P: PVolumeDiskExtents;
begin

  SetLength(PhysicalDrives,0);

  { Pathname to mount point }
  Size := GetFullPathName(PChar(Path),0,nil,nil);
  SetLength(MountPoint,Size);
  if GetVolumePathName(PChar(Path),PChar(MountPoint),Size) = False then
  begin
    RaiseLastOSError;
  end;
  SetLength(MountPoint,StrLen(PChar(MountPoint)));

  { Mount point to logical volume name }
  Size := 50;  // Recomended size from http://msdn.microsoft.com/en-us/library/windows/desktop/aa364994.aspx
  SetLength(VolumeName,Size);
  if GetVolumeNameForVolumeMountPoint(PChar(MountPoint),PChar(VolumeName),Size) = False then
  begin
    RaiseLastOSError;
  end;
  SetLength(VolumeName,StrLen(PChar(VolumeName)));
  VolumeName := ExcludeTrailingPathDelimiter(VolumeName);

  { Open volume }
  h := CreateFile(PChar(VolumeName),FILE_READ_ATTRIBUTES,
                  FILE_SHARE_READ or FILE_SHARE_WRITE,nil,
                  OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
  if h = INVALID_HANDLE_VALUE then
  begin
    RaiseLastOSError;
  end;

  try
    Size := SizeOf(TVolumeDiskExtents);
    P := AllocMem(Size);
    try
      FillChar(P^,Size,0);
      RSize := 0;
      if DeviceIoControl(h,IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
                         nil,0,
                         P,Size,
                         RSize,nil) = False then
      begin
        if GetLastError <> ERROR_MORE_DATA then
        begin
          RaiseLastOSError;
        end;

        Size := SizeOf(TVolumeDiskExtents) +
                SizeOf(DISK_EXTENT) * (P^.NumberOfDiskExtents - 1);
        ReallocMem(P,Size);
        FillChar(P^,Size,0);
        if DeviceIoControl(h,IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
                           nil,0,
                           P,Size,
                           RSize,nil) = False then
        begin
          RaiseLastOSError;
        end;
      end;

      SetLength(PhysicalDrives,P^.NumberOfDiskExtents);
      for I := 0 to P^.NumberOfDiskExtents - 1 do
      begin
        PhysicalDrives[I] := P^.Extents[I].DiskNumber;
      end;

    finally
      FreeMem(P);
    end;

  finally
    CloseHandle(h);
  end;

end;
これで第2パラメータPhysicalDrivesに物理ドライブ番号が格納されます。

これらの関数を組み合わせて指定したファイル/パスがSSD上に書き込まれるかどうかを調べてみます。
var
  Index: Integer;
  Filename: String;
  PhysicalDrives: TIntegerDynArray;
  PhysicalDrivePath: String;
  IsSSD: Boolean;
begin

  Filename := "C:\";  // 例: "C:\"を調べます

  SetLength(PhysicalDrives,0);
  PathnameToPhysicalDriveNumber(Filename,PhysicalDrives);

  try
    IsSSD := False;
    for Index := Low(PhysicalDrives) to High(PhysicalDrives) do
    begin
      PhysicalDrivePath := Format('\\.\PhysicalDrive%d',[PhysicalDrives[Index]]);
      try
        IsSSD := IsSSD or HasNoSeekPenalty(PhysicalDrivePath);

      except
        { Ignore }
      end;

      if IsSSD = True then
      begin
        Break;
      end;
    end;

    if IsSSD = True then
    begin
      MessageDlg(Format('ファイル ''%s'' はSSDに書き込まれます。',[Filename]),
                 mtInformation,[mbOk],0);
    end
    else
    begin
      MessageDlg(Format('ファイル ''%s'' はSSDには書き込まれません。',[Filename]),
                 mtInformation,[mbOk],0);
    end;

  finally
    SetLength(PhysicalDrives,0);
  end;

end;
ATA8-ACSでドライブの回転数を取得する方法については次のアーティクルで。

元ねたはもちろんNyaRuRuさんのSSD なら動作を変えるアプリケーションを作る - NyaRuRuが地球にいたころ。すばらしいサンプルを書いていただいたNyaRuRuさんに深く感謝いたします。

2012/10/05追記: CreateFileのフラグについて当初FILE_FLAG_NO_BUFFERINGとFILE_FLAG_WRITE_THROUGHの両方を指定するという記述がありましたが、FILE_FLAG_NO_BUFFERINGを指定したファイルへの書き込みにはFile Bufferingにあるように制限があり、通常の使用には向かないと考えられるため、この点を削除しました。

Prism XE3 Update 1

Prism XE3 Update 1がリリースされています。

29056 Embarcadero Prism XE3 Update 1 (September release)
Embarcadero Prism XE3 Update 1 (September 2012 Release) が公開されました
Embarcadero Prism XE3 Update 1 (September 2012 Release) is now available