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月24日
2012年10月10日
2012年10月4日
[書籍]インサイドWindows第6版(上)
ブックファースト 新宿店でWindows Internals, 6th edition Part 1 (amazon)の翻訳である
インサイドWindows 第6版 上 (amazon)/Mark E. Russinovich、David A. Solomon、Alex Ionescu著/株式会社クイープ訳/日経BP/ISBN 978-4-82229-470-0/7,980円
を購入。なお下巻(Part 2)は2013年春の出版予定とのことです。
インサイドWindows 第6版 上 (amazon)/Mark E. Russinovich、David A. Solomon、Alex Ionescu著/株式会社クイープ訳/日経BP/ISBN 978-4-82229-470-0/7,980円
を購入。なお下巻(Part 2)は2013年春の出版予定とのことです。
ファイルがSSDに書き込まれるかどうかを調べる(2)
前回の続きです。パート2としてATA8-ACSを使用して"nominal media rotation rate"を取得する方法を試してみます(こちらもNyaRuRuさんのコードそのままなので詳細な説明は省略します)。
まずはDeviceIOControlで使用する構造体、定数などを定義します。
まずは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の説明に
てきとうな訳: 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で使用する構造体、定数などを定義します。
2012/10/05追記: CreateFileのフラグについて当初FILE_FLAG_NO_BUFFERINGとFILE_FLAG_WRITE_THROUGHの両方を指定するという記述がありましたが、FILE_FLAG_NO_BUFFERINGを指定したファイルへの書き込みにはFile Bufferingにあるように制限があり、通常の使用には向かないと考えられるため、この点を削除しました。
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にあるように制限があり、通常の使用には向かないと考えられるため、この点を削除しました。
2012年10月1日
2012/10開催のウェブセミナー
- 2012/10/10 13:30-14:30(JST) 10/10 - RAD Studio XE3 Webセミナーシリーズ - HTML5 Builderでネイティブモバイルアプリ作成
- 2012/10/17 17:00-18:00(JST) 10/17 - デベロッパーTV - デブキャンプリキャップ!モバイル/タブレット向けWebアプリの作成、その他新情報も
- 2012/10/31 13:30-14:30(JST) 「Delphi/C++Builder XE3 Professional版によるアプリ構築」
- 2012/10/11 13:30-16:45(JST) アジャイル開発成功のポイント ‐ 開発から運用をつなぐ最新アプローチ ‐
登録:
投稿 (Atom)