2010年7月30日

Firebird 2.5 Release Candidate 3リリース

Firebird 2.5 Release Candidate 3が部分的にリリースされました。Windows x64版は現在QA中で、まもなくリリースされる予定です。

Firebird File Repositories (download)
Firebird 2.5 Release Notes

2010/08/02追記: Windows x64版もQAが完了してリリースされています。現時点でMacOSX 32bit版のみ未リリースとなっています。

2010/08/03追記: MaxOSX 32bit版もリリースされています。

2010年7月29日

ヘルプインサイトの利用方法

Delphi 2007以降の新機能であるヘルプインサイトの利用方法に関する記事。興味深い。とりあえずメモ。

Help insight - Delphi Programming
In Praise of Help Insight Support
Delphi Help Insight Customization
Delphi Help Insight XSL

2010年7月28日

ウェブセミナー資料ダウンロード

過去のウェブセミナーのプレゼンテーション資料がダウンロードできるようになっています(要登録)。

エンバカデロ Webセミナー プレゼンテーション資料ダウンロード

2010/07/28時点で以下のプレゼンテーション資料がダウンロードできます。

Webセミナー「データベースパフォーマンスチューニングの概要と評価」(2010/06/02)
Webセミナー「システム品質を高めるデータベースツールからのアプローチ」(2010/05/19,2010/06/09,2010/07/14)
Webセミナー「異種データベース利用者必見!管理負荷を軽減するベストプラクティス」(2010/06/16,2010/07/28)
Webセミナー「新規プロジェクト開発に必須、データベースの情報共有インフラの活用」(2010/06/23,2010/07/21)
Webセミナー「チュートリアル:すぐに始められるデータベース設計とSQL文の作成」(2010/06/30)
Webセミナー「データベース設計ツールER/Studioの概要と効果的活用法」(2010/07/07)

開発系(RAD Studio)のものはどうなんでしょう…。

2010年7月27日

keyed events

以前クリティカルセクション仕様変更の影響について触れましたが、この件に関連して、CriticalSectionに代わる非公開機能の"keyed events"に関する興味深い記事を見つけました。

Windows keyed events, critical sections, and new Vista synchronization features(Joe Duffy's Weblog)

とってもいいかげんな要約: クリティカルセクションはWindows 2000以前の実装ではSMP上でのパフォーマンスのボトルネックになってしまっていた。これを改善するためにWindows 2000で仕様を変更したが、今度は信頼性を損なうことになってしまった。これを解決するためにWindows XPで新たに非公開の"keyed events"が作られ、Windows Vistaではその実装が改善された。ハンドルとノンページメモリを圧迫しない新しい同期機能としてユーザモードのコードからも利用可能になることを期待している。

もう少し詳しい話がWindows Internals, Fifth Edition (amazon)にあるそうですが、日本語訳はいつ出るんでしょうか…。

元ねたは江添さん本の虫: keyed-eventsNyaRuRuさんTwitter上の2010/07/24-25あたりの発言

2010年7月26日

InterBase SMP 2009 Hotfix Update 4リリース

InterBase SMP 2009 Hotfix Update 4がリリースされています。Hotfix Update 4を適用するには予めHotfix Update 3が適用されている必要があります。またHotfix Update 4は英語版のものを日本語版にも適用可能です。

27729 InterBase SMP 2009 Hotfix Update 4 (9.0.4.443) for Windows
InterBase 2009 Hotfix Update 4 (Windows版) リリースノート (version 9.0.4.443)

Delphiのエディタのキーバインドを作成する

OTA(Open tools API)を使用してDelphiのエディタで新しくキーバインドを作成する方法を説明した記事。興味深い。とりあえずメモ。

Cary Jensen "Let's Get Technical": Creating Editor Key Bindings in Delphi

2010年7月23日

Windowsのシステムキーコンビネーションを無効にする

全画面で動作するようなアプリケーションなどでは、[Win]、[Ctrl]+[ESC]、[ALT]+[TAB]、[ALT]+[ESC]、[Ctrl]+[Shift]+[ESC]、[ALT]+[F4]のようなWindowsのシステムキーコンビネーションを無効にしたいような状況があります。そのような場合はキーボードを低レベルフックします。フックの設定はSetWindowsHookEx(ja)で、解除はUnhookWindowsHookEx(ja)で行い、フック関数はLowLevelKeyboardProc(ja)に従って作成します。フック関数が呼び出されるときにlParamにはKBDLLHOOKSTRUCT構造体へのポインタが格納されており、この中のvkCodeで押されたキーの仮想キーコードを、flagsのLLKHF_ALTDOWNビット(bit5)で[ALT]キーが押されているかどうかを、それぞれ知ることができます。ここで無効化したいキーだった場合は戻値を1としてそのままリターンします。一方でそのままにしたいキーだった場合はCallNextHookEx(ja)で次のフックチェーンに処理を委ねる必要があります。また[ALT]以外のキー([Ctrl]や[Shift]など)とのコンビネーションを知りたい場合はGetAsyncKeyState(ja)で取得します(bit15が現在そのキーが押されているかどうかを示しています)。
まずキーボードの低レベルフックに必要な定義を行います。
uses
  Windows;

type
  LPKBDLLHOOKSTRUCT = ^KBDLLHOOKSTRUCT;
  {$EXTERNALSYM LPKBDLLHOOKSTRUCT}
  tagKBDLLHOOKSTRUCT = record
    vkCode: DWORD;
    scanCode: DWORD;
    flags: DWORD;
    time: DWORD;
    dwExtraInfo: Pointer;
  end;
  {$EXTERNALSYM tagKBDLLHOOKSTRUCT}
  KBDLLHOOKSTRUCT = tagKBDLLHOOKSTRUCT;
  {$EXTERNALSYM KBDLLHOOKSTRUCT}
  TKbDllHookStruct = KBDLLHOOKSTRUCT;
  PKbDllHookStruct = LPKBDLLHOOKSTRUCT;

const
  WH_KEYBOARD_LL = 13;
  {$EXTERNALSYM WH_KEYBOARD_LL}

  LLKHF_EXTENDED = KF_EXTENDED shr 8;
  {$EXTERNALSYM LLKHF_EXTENDED}
  LLKHF_INJECTED = $00000010;
  {$EXTERNALSYM LLKHF_INJECTED}
  LLKHF_ALTDOWN  = KF_ALTDOWN shr 8;
  {$EXTERNALSYM LLKHF_ALTDOWN}
  LLKHF_UP       = KF_UP shr 8;
  {$EXTERNALSYM LLKHF_UP}
次にフック関数を用意します。
type
  TSystemKeyCombination  = (skLWin,          // [WIN] (Left)             - Open Start menu
  skRWin,          // [WIN] (Right)            - Open Start menu
  skCtrlEsc,       // [CTRL] + [ESC]           - Open Start menu
  skAltTab,        // [ALT] + [TAB]            - Switch programs
  skAltEsc,        // [ALT] + [ESC]            - Next program
  skCtrlShiftEsc,  // [CTRL] + [SHIFT] + [ESC] - Open task manager
  skAltF4);        // [ALT] + [F4]             - Quit program
  TSystemKeyCombinations = set of TSystemKeyCombination;

var
  hh: HHOOK;
  InvalidCombinations: TSystemKeyCombinations;

function LowLevelKeyboardProc(nCode: Integer;
                              wP: WPARAM; lP: LParam): LRESULT; stdcall; export;
var
  pkbhs: PKbDllHookStruct;
  CtrlKey: Boolean;
  ShiftKey: Boolean;
begin

  pkbhs := PKbDllHookStruct(lP);

  case nCode of
    HC_ACTION:
    begin
      case pkbhs^.vkCode of
        VK_LWIN:
        begin
          if skLWin in InvalidCombinations then
          begin
            { LWIN }
            Result := 1;
            Exit;
          end;
        end;

        VK_RWIN:
        begin
          if skRWin in InvalidCombinations then
          begin
            { RWIN }
            Result := 1;
            Exit;
          end;
        end;

        VK_TAB:
        begin
          if ((pkbhs^.flags and LLKHF_ALTDOWN) <> 0) and
             (skAltTab in InvalidCombinations) then
          begin
            { ALT + TAB }
            Result := 1;
            Exit;
          end;
        end;

        VK_ESCAPE:
        begin
          CtrlKey  := ((GetAsyncKeyState(VK_CONTROL) and $8000) <> 0);
          ShiftKey := ((GetAsyncKeyState(VK_SHIFT)   and $8000) <> 0);

          if ((pkbhs^.flags and LLKHF_ALTDOWN) <> 0) and
             (skAltEsc in InvalidCombinations) then
          begin
            { ALT + ESC }
            Result := 1;
            Exit;
          end;

          if ShiftKey = False then
          begin
            if (CtrlKey = True) and (skCtrlEsc in InvalidCombinations) then
            begin
              { CTRL + ESC }
              Result := 1;
              Exit;
            end;
          end
          else
          begin
            if (CtrlKey = True) and (skCtrlShiftEsc in InvalidCombinations) then
            begin
              { CTRL + SHIFT + ESC }
              Result := 1;
              Exit;
            end;
          end;
        end;

        VK_F4:
        begin
          if ((pkbhs^.flags and LLKHF_ALTDOWN) <> 0) and
             (skAltF4 in InvalidCombinations) then
          begin
            { ALT + F4 }
            Result := 1;
            Exit;
          end;
        end;
      end;
    end;
  end;

  Result := CallNextHookEx(hh,nCode,wP,lP);

end;
ここでは集合型のInvalidCombinationsに含まれるキーコンビネーションであればResultを1にしてそのままリターン、そうでなければCallNextHookExでフックチェーンの呼び出しを行うようにしています。
最後にキーボードの低レベルフックを設定、解除する関数は
procedure HookKeyboardLL;
begin

  if hh <> 0 then
  begin
    Exit;
  end;

  hh := SetWindowsHookEx(WH_KEYBOARD_LL,LowLevelKeyboardProc,HInstance,0);

end;

procedure UnhookKeyboardLL;
begin

  if hh = 0 then
  begin
    Exit;
  end;

  UnhookWindowsHookEx(hh);
  hh := 0;

end;
と単にSetWindowsHookExおよびUnhookWindowsHookExを呼び出すだけです。
なお[Ctrl]+[ALT]+[Del]のキーストロークだけはキーボードの低レベルフックでも捕捉できません(GINAの置き換えが必要になります)。

2010年7月22日

サービスに関する情報を列挙する

インストールされているサービスを取得するにはまずOpenSCManager(ja)でサービスコントロールマネージャをオープンし、EnumServicesStatus(ja)でサービスの情報を取得し、最後にCloseServiceHandle(ja)でサービスコントロールマネージャをクローズします。EnumServicesStatusはサイズを0として呼び出すことで必要な領域の大きさを取得し、領域を確保後に改めてEnumServicesStatusを呼び出します。取得した情報はENUM_SERVICE_STATUS構造体の配列と、そのメンバであるSERVICE_STATUS構造体に格納されます。
ところがこれらの情報の中にはサービスを起動したときのコマンドラインなどが含まれていません。そこでEnumServicesStatusで取得したそれぞれのサービス名についてOpenService(ja)でサービスをオープンし、QueryServiceConfig(ja)でサービスの構成パラメータを取得し、最後にCloseServiceHandleでサービスをクローズします。QueryServiceConfigもまずサイズを0として呼び出して必要な領域のサイズを取得し、領域を確保後に改めてQueryServiceConfigを呼び出します。取得した情報はQUERY_SERVICE_CONFIG構造体に格納されます。
必要な定義は概ねWinSvcユニットに存在しますが、EnumServicesStatusで取得するTEnumServiceStatus(=ENUM_SERVICE_STATUS構造体)の配列へのポインタの型はそのままでは扱いにくいので、
uses
  Windows, SysUtils, WinSvc;

type
  TEnumServiceStatusArray = array [0..0] of TEnumServiceStatus;
  PEnumServiceStatusArray = ^TEnumServiceStatusArray;

{ Alias }
function EnumServicesStatus(hSCManager: SC_HANDLE; dwServiceType: DWORD;
                            dwServiceState: DWORD;
                            lpServices: PEnumServiceStatusArray; cbBufSize: DWORD;
                            var pcbBytesNeeded: DWORD;
                            var lpServicesReturned: DWORD;
                            var lpResumeHandle: DWORD): BOOL; stdcall;
  external advapi32 name
{$IFDEF Unicode}
    'EnumServicesStatusW';
{$ELSE}
    'EnumServicesStatusA';
{$ENDIF}
  {$EXTERNALSYM EnumServicesStatus}
と再定義しておきます。そして
type
  TEnumServicesFunc = function (const ServiceName: String;
                                const DisplayName: String;
                                ServiceStatus: TServiceStatus;
                                const CommandLine: String;
                                Data: Pointer): Boolean;

procedure EnumServices(Func: TEnumServicesFunc; Data: Pointer);
var
  hSCManager: SC_HANDLE;
  RetVal: Boolean;
  BytesNeeded: DWORD;
  ServicesReturned: DWORD;
  ResumeHandle: DWORD;
  PServiceStatus: PEnumServiceStatusArray;
  hService: SC_HANDLE;
  PServiceConfig: Pointer;
  Index: Integer;
  ServiceName: String;
  DisplayName: String;
  ServiceStatus: TServiceStatus;
  CommandLine: String;
begin

  { Open service manager }
  hSCManager := OpenSCManager(nil,nil,SC_MANAGER_ENUMERATE_SERVICE);
  if hSCManager = 0 then
  begin
    RaiseLastOSError;
  end;

  PServiceStatus := nil;
  try
    { Get buffer size }
    BytesNeeded := 0;
    ServicesReturned := 0;
    ResumeHandle := 0;
    RetVal := EnumServicesStatus(hSCManager,SERVICE_WIN32,
                                 SERVICE_STATE_ALL,nil,0,
                                 BytesNeeded,ServicesReturned,ResumeHandle);
    if RetVal = False then
    begin
      { Allocate buffer for EnumServicesStatus }
      PServiceStatus := AllocMem(BytesNeeded);

      { Enumerate service status }
      ResumeHandle := 0;
      RetVal := EnumServicesStatus(hSCManager,SERVICE_WIN32,
                                   SERVICE_STATE_ALL,PServiceStatus,BytesNeeded,
                                   BytesNeeded,ServicesReturned,ResumeHandle);
    end;

    if RetVal = False then
    begin
      RaiseLastOSError;
    end;

    for Index := 0 to ServicesReturned - 1 do
    begin
      ServiceName   := PServiceStatus^[Index].lpServiceName;
      DisplayName   := PServiceStatus^[Index].lpDisplayName;
      ServiceStatus := PServiceStatus^[Index].ServiceStatus;
      CommandLine   := '';

      { Open service for QueryServiceConfig }
      hService := OpenService(hSCManager,PChar(ServiceName),SERVICE_QUERY_CONFIG);
      if hService <> 0 then
      begin
        PServiceConfig := nil;
        try
          { Get buffer size }
          QueryServiceConfig(hService,nil,0,BytesNeeded);

          { Allocate buffer for QueryServiceConfig }
          PServiceConfig := AllocMem(BytesNeeded);

          { Query service configuration }
          if QueryServiceConfig(hService,PServiceConfig,
                                BytesNeeded,BytesNeeded) = True then
          begin
            { Binary pathname }
            CommandLine := TQueryServiceConfig(PServiceConfig^).lpBinaryPathName;
          end;

        finally
          { Close service }
          CloseServiceHandle(hService);

          { Free buffer }
          if PServiceConfig <> nil then
          begin
            FreeMem(PServiceConfig);
          end;
        end;
      end;

      { Callback }
      if Assigned(Func) then
      begin
        if Func(ServiceName,DisplayName,
                ServiceStatus,CommandLine,Data) = False then
        begin
          Break;
        end;
      end;
    end;

  finally
    { Close service manager handle }
    CloseServiceHandle(hSCManager);

    { Free buffer }
    if PServiceStatus <> nil then
    begin
      FreeMem(PServiceStatus);
    end;
  end;

end;
とします。例によってDelphi 2009以降の無名メソッド(anonymous method)を使用する場合は
type
  TEnumServicesFunc = reference to
    function (const ServiceName: String;
              const DisplayName: String;
              ServiceStatus: TServiceStatus;
              const CommandLine: String): Boolean;

procedure EnumServices(Func: TEnumServicesFunc);
var
  hSCManager: SC_HANDLE;
  RetVal: Boolean;
  BytesNeeded: DWORD;
  ServicesReturned: DWORD;
  ResumeHandle: DWORD;
  PServiceStatus: PEnumServiceStatusArray;
  hService: SC_HANDLE;
  PServiceConfig: Pointer;
  Index: Integer;
  ServiceName: String;
  DisplayName: String;
  ServiceStatus: TServiceStatus;
  CommandLine: String;
begin

  { Open service manager }
  hSCManager := OpenSCManager(nil,nil,SC_MANAGER_ENUMERATE_SERVICE);
  if hSCManager = 0 then
  begin
    RaiseLastOSError;
  end;

  PServiceStatus := nil;
  try
    { Get buffer size }
    BytesNeeded := 0;
    ServicesReturned := 0;
    ResumeHandle := 0;
    RetVal := EnumServicesStatus(hSCManager,SERVICE_WIN32,
                                 SERVICE_STATE_ALL,nil,0,
                                 BytesNeeded,ServicesReturned,ResumeHandle);
    if RetVal = False then
    begin
      { Allocate buffer for EnumServicesStatus }
      PServiceStatus := AllocMem(BytesNeeded);

      { Enumerate service status }
      ResumeHandle := 0;
      RetVal := EnumServicesStatus(hSCManager,SERVICE_WIN32,
                                   SERVICE_STATE_ALL,PServiceStatus,BytesNeeded,
                                   BytesNeeded,ServicesReturned,ResumeHandle);
    end;

    if RetVal = False then
    begin
      RaiseLastOSError;
    end;

    for Index := 0 to ServicesReturned - 1 do
    begin
      ServiceName   := PServiceStatus^[Index].lpServiceName;
      DisplayName   := PServiceStatus^[Index].lpDisplayName;
      ServiceStatus := PServiceStatus^[Index].ServiceStatus;
      CommandLine   := '';

      { Open service for QueryServiceConfig }
      hService := OpenService(hSCManager,PChar(ServiceName),SERVICE_QUERY_CONFIG);
      if hService <> 0 then
      begin
        PServiceConfig := nil;
        try
          { Get buffer size }
          QueryServiceConfig(hService,nil,0,BytesNeeded);

          { Allocate buffer for QueryServiceConfig }
          PServiceConfig := AllocMem(BytesNeeded);

          { Query service configuration }
          if QueryServiceConfig(hService,PServiceConfig,
                                BytesNeeded,BytesNeeded) = True then
          begin
            { Binary pathname }
            CommandLine := TQueryServiceConfig(PServiceConfig^).lpBinaryPathName;
          end;

        finally
          { Close service }
          CloseServiceHandle(hService);

          { Free buffer }
          if PServiceConfig <> nil then
          begin
            FreeMem(PServiceConfig);
          end;
        end;
      end;

      { Callback }
      if Assigned(Func) then
      begin
        if Func(ServiceName,DisplayName,
                ServiceStatus,CommandLine) = False then
        begin
          Break;
        end;
      end;
    end;

  finally
    { Close service manager handle }
    CloseServiceHandle(hSCManager);

    { Free buffer }
    if PServiceStatus <> nil then
    begin
      FreeMem(PServiceStatus);
    end;
  end;

end;
こんな感じになります。なお取得したコマンドラインの中にはパス名に空白文字を含むにもかかわらずダブルクォーテーションで括られていないものもあるため、コマンドラインから実行ファイルのフルパス名を取り出すには
uses
  StrUtils;

function CommandlineToPathname(const CommandLine: String): String;
var
  Start: Integer;
  Position: Integer;
begin

  Result := CommandLine;

  if Result = '' then
  begin
    Exit;
  end;

  if Result[1] = '"' then
  begin
    { Quoted }
    Delete(Result,1,1);  // Delete first double quote
{$IFDEF Unicode}
    Position := Pos('"',Result);
{$ELSE}
    Position := AnsiPos('"',Result);
{$ENDIF}
    if Position > 0 then
    begin
      Delete(Result,Position,Length(Result));
    end;
  end
  else
  begin
    { Not quoted }
    Start := 1;
    while True do
    begin
      Position := PosEx(' ',Result,Start);
      if Position = 0 then
      begin
        Break;
      end;

      Start := Position + 1;
{$IFDEF Unicode}
      if CharInSet(Result[Start],['-','/']) then
{$ELSE}
      if Result[Start] in ['-','/'] then
{$ENDIF}
      begin
        Delete(Result,Position,Length(Result));
        Break;
      end;
    end;
  end;

end;
このように一捻り必要です。

元ねたは旧Delphi-MLの90131以下のスレッド(特に90133のKHE00221さんの回答)。

2010年7月21日

ネットワークアダプタの情報を取得する(IPv4)

ネットワークアダプタの情報を取得するにはGetAdaptersInfoを使用します。取得した情報はIP_ADAPTER_INFO構造体に格納されますが、複数のネットワークアダプタが存在する場合は戻値がERROR_BUFFER_OVERFLOWになり、第2パラメータのpOutBufLenに必要なサイズがバイト単位で返されるので、領域を再確保して再びGetAdaptersInfoを呼び出す必要があります。この場合はIP_ADAPTER_INFO構造体のNextがNULLになるまでたどっていくことになります。
まずこれらのIPヘルパAPIや構造体を定義します。
uses
  Windows, SysUtils;

const
  MAX_ADAPTER_DESCRIPTION_LENGTH = 128;
  {$EXTERNALSYM MAX_ADAPTER_DESCRIPTION_LENGTH}
  MAX_ADAPTER_NAME_LENGTH        = 256;
  {$EXTERNALSYM MAX_ADAPTER_NAME_LENGTH}
  MAX_ADAPTER_ADDRESS_LENGTH     = 8;
  {$EXTERNALSYM MAX_ADAPTER_ADDRESS_LENGTH}

  MIB_IF_TYPE_OTHER     = 1;
  {$EXTERNALSYM MIB_IF_TYPE_OTHER}
  MIB_IF_TYPE_ETHERNET  = 6;
  {$EXTERNALSYM MIB_IF_TYPE_ETHERNET}
  MIB_IF_TYPE_TOKENRING = 9;
  {$EXTERNALSYM MIB_IF_TYPE_TOKENRING}
  MIB_IF_TYPE_FDDI      = 15;
  {$EXTERNALSYM MIB_IF_TYPE_FDDI}
  MIB_IF_TYPE_PPP       = 23;
  {$EXTERNALSYM MIB_IF_TYPE_PPP}
  MIB_IF_TYPE_LOOPBACK  = 24;
  {$EXTERNALSYM MIB_IF_TYPE_LOOPBACK}
  MIB_IF_TYPE_SLIP      = 28;
  {$EXTERNALSYM MIB_IF_TYPE_SLIP}

type
  time_t = Longint;
  {$EXTERNALSYM time_t}

  PIP_MASK_STRING = ^IP_MASK_STRING;
  {$EXTERNALSYM PIP_MASK_STRING}
  IP_ADDRESS_STRING = record
    S: array [0..15] of AnsiChar;
  end;
  {$EXTERNALSYM IP_ADDRESS_STRING}
  PIP_ADDRESS_STRING = ^IP_ADDRESS_STRING;
  {$EXTERNALSYM PIP_ADDRESS_STRING}
  IP_MASK_STRING = IP_ADDRESS_STRING;
  {$EXTERNALSYM IP_MASK_STRING}

  PIP_ADDR_STRING = ^IP_ADDR_STRING;
  {$EXTERNALSYM PIP_ADDR_STRING}
  _IP_ADDR_STRING = record
    Next: PIP_ADDR_STRING;
    IpAddress: IP_ADDRESS_STRING;
    IpMask: IP_MASK_STRING;
    Context: DWORD;
  end;
  {$EXTERNALSYM _IP_ADDR_STRING}
  IP_ADDR_STRING = _IP_ADDR_STRING;
  {$EXTERNALSYM IP_ADDR_STRING}

  PIP_ADAPTER_INFO = ^IP_ADAPTER_INFO;
  {$EXTERNALSYM PIP_ADAPTER_INFO}
  _IP_ADAPTER_INFO = record
    Next: PIP_ADAPTER_INFO;
    ComboIndex: DWORD;
    AdapterName: array [0..MAX_ADAPTER_NAME_LENGTH + 3] of AnsiChar;
    Description: array [0..MAX_ADAPTER_DESCRIPTION_LENGTH + 3] of AnsiChar;
    AddressLength: UINT;
    Address: array [0..MAX_ADAPTER_ADDRESS_LENGTH - 1] of BYTE;
    Index: DWORD;
    Type_: UINT;
    DhcpEnabled: UINT;
    CurrentIpAddress: PIP_ADDR_STRING;
    IpAddressList: IP_ADDR_STRING;
    GatewayList: IP_ADDR_STRING;
    DhcpServer: IP_ADDR_STRING;
    HaveWins: BOOL;
    PrimaryWinsServer: IP_ADDR_STRING;
    SecondaryWinsServer: IP_ADDR_STRING;
    LeaseObtained: time_t;
    LeaseExpires: time_t;
  end;
  {$EXTERNALSYM _IP_ADAPTER_INFO}
  IP_ADAPTER_INFO = _IP_ADAPTER_INFO;
  {$EXTERNALSYM IP_ADAPTER_INFO}

function GetAdaptersInfo(pAdapterInfo: PIP_ADAPTER_INFO;
                         var pOutBufLen: ULONG): DWORD; stdcall;
  external 'iphlpapi.dll' Name 'GetAdaptersInfo';
  {$EXTERNALSYM GetAdaptersInfo}
そして
type
  TAdapterType = (atUnknown,
                  atOther,
                  atEthernet,
                  atTokenRing,
                  atFDDI,
                  atPPP,
                  atLoopback,
                  atSLIP,
                  atWLAN);

  TEnumAdapterInfoFunc = function (const AdapterName: String;
                                   const Description: String;
                                   const HardwareAddress: String;
                                   AdapterIndex: Integer;
                                   AdapterType: TAdapterType;
                                   DhcpEnabled: Boolean;
                                   const IPAddress: String;
                                   const Mask: String;
                                   const Gateway: String;
                                   const DHCPServer: String;
                                   Data: Pointer): Boolean;

procedure EnumAdapterInfo(Func: TEnumAdapterInfoFunc; Data: Pointer);

  function FormatHardwareAddress(Ptr: PIP_ADAPTER_INFO): String;
  var
    Index: Integer;
  begin

    Result := '';

    for Index := 0 to Ptr^.AddressLength - 1 do
    begin
      Result := Result + Format('%02.2X-',[Ptr^.Address[Index]]);
    end;

    if Result <> '' then
    begin
      Delete(Result,Length(Result),1);
    end;

  end;

var
  pAdapterInfo: PIP_ADAPTER_INFO;
  Ptr: PIP_ADAPTER_INFO;
  pOutBufLen: ULONG;
  RetVal: DWORD;
  AdapterIndex: Integer;
  AdapterName: String;
  Description: String;
  HardwareAddress: String;
  AdapterType: TAdapterType;
  IPAddress: String;
  NetMask: String;
  Gateway: String;
  DHCPServer: String;
begin

  pOutBufLen := SizeOf(IP_ADAPTER_INFO);
  pAdapterInfo := AllocMem(pOutBufLen);
  try
    RetVal := GetAdaptersInfo(pAdapterInfo,pOutBufLen);
    if RetVal = ERROR_BUFFER_OVERFLOW then
    begin
      FreeMem(pAdapterInfo);
      pAdapterInfo := AllocMem(pOutBufLen);
      RetVal := GetAdaptersInfo(pAdapterInfo,pOutBufLen);
    end;

    if RetVal <> NO_ERROR then
    begin
      Exit;
    end;

    Ptr := pAdapterInfo;
    while Ptr <> nil do
    begin
      AdapterName := Trim(String(Ptr^.AdapterName));
      Description := Trim(String(Ptr^.Description));
      HardwareAddress := FormatHardwareAddress(Ptr);
      AdapterIndex := Ptr^.Index;
      case Ptr^.Type_ of
        MIB_IF_TYPE_OTHER:
        begin
          AdapterType := atOther;
        end;

        MIB_IF_TYPE_ETHERNET:
        begin
          AdapterType := atEthernet;
        end;

        MIB_IF_TYPE_TOKENRING:
        begin
          AdapterType := atTokenRing;
        end;

        MIB_IF_TYPE_FDDI:
        begin
          AdapterType := atFDDI;
        end;

        MIB_IF_TYPE_PPP:
        begin
          AdapterType := atPPP;
        end;

        MIB_IF_TYPE_LOOPBACK:
        begin
          AdapterType := atLoopback;
        end;

        MIB_IF_TYPE_SLIP:
        begin
          AdapterType := atSLIP;
        end;

        71:
        begin
          AdapterType := atWLAN;
        end;

        else
        begin
          AdapterType := atUnknown;
        end;
      end;

      IPAddress  := String(Ptr^.IpAddressList.IpAddress.S);
      NetMask    := String(Ptr^.IpAddressList.IpMask.S);
      Gateway    := String(Ptr^.GatewayList.IpAddress.S);
      if Ptr^.DhcpEnabled = 0 then
      begin
        DHCPServer := '';
      end
      else
      begin
        DHCPServer := String(Ptr^.DhcpServer.IpAddress.S);
      end;

      if Assigned(Func) then
      begin
        if Func(AdapterName,Description,HardwareAddress,AdapterIndex,
                AdapterType,Ptr^.DhcpEnabled <> 0,IPAddress,NetMask,
                Gateway,DHCPServer,Data) = False then
        begin
          Break;
        end;
      end;

      Ptr := Ptr^.Next;
    end;

  finally
    FreeMem(pAdapterInfo);
  end;

end;
とします。Delphi 2009以降の無名メソッド(anonymous method)を使用する場合はこんな感じに。
type
  TAdapterType = (atUnknown,
                  atOther,
                  atEthernet,
                  atTokenRing,
                  atFDDI,
                  atPPP,
                  atLoopback,
                  atSLIP,
                  atWLAN);

  TEnumAdapterInfoFunc = reference to
    function (const AdapterName: String;
              const Description: String;
              const HardwareAddress: String;
              AdapterIndex: Integer;
              AdapterType: TAdapterType;
              DhcpEnabled: Boolean;
              const IPAddress: String;
              const Mask: String;
              const Gateway: String;
              const DHCPServer: String): Boolean;

procedure EnumAdapterInfo(Func: TEnumAdapterInfoFunc);

  function FormatHardwareAddress(Ptr: PIP_ADAPTER_INFO): String;
  var
    Index: Integer;
  begin

    Result := '';

    for Index := 0 to Ptr^.AddressLength - 1 do
    begin
      Result := Result + Format('%02.2X-',[Ptr^.Address[Index]]);
    end;

    if Result <> '' then
    begin
      Delete(Result,Length(Result),1);
    end;

  end;

var
  pAdapterInfo: PIP_ADAPTER_INFO;
  Ptr: PIP_ADAPTER_INFO;
  pOutBufLen: ULONG;
  RetVal: DWORD;
  AdapterIndex: Integer;
  AdapterName: String;
  Description: String;
  HardwareAddress: String;
  AdapterType: TAdapterType;
  IPAddress: String;
  NetMask: String;
  Gateway: String;
  DHCPServer: String;
begin

  pOutBufLen := SizeOf(IP_ADAPTER_INFO);
  pAdapterInfo := AllocMem(pOutBufLen);
  try
    RetVal := GetAdaptersInfo(pAdapterInfo,pOutBufLen);
    if RetVal = ERROR_BUFFER_OVERFLOW then
    begin
      FreeMem(pAdapterInfo);
      pAdapterInfo := AllocMem(pOutBufLen);
      RetVal := GetAdaptersInfo(pAdapterInfo,pOutBufLen);
    end;

    if RetVal <> NO_ERROR then
    begin
      Exit;
    end;

    Ptr := pAdapterInfo;
    while Ptr <> nil do
    begin
      AdapterName := Trim(String(Ptr^.AdapterName));
      Description := Trim(String(Ptr^.Description));
      HardwareAddress := FormatHardwareAddress(Ptr);
      AdapterIndex := Ptr^.Index;
      case Ptr^.Type_ of
        MIB_IF_TYPE_OTHER:
        begin
          AdapterType := atOther;
        end;

        MIB_IF_TYPE_ETHERNET:
        begin
          AdapterType := atEthernet;
        end;

        MIB_IF_TYPE_TOKENRING:
        begin
          AdapterType := atTokenRing;
        end;

        MIB_IF_TYPE_FDDI:
        begin
          AdapterType := atFDDI;
        end;

        MIB_IF_TYPE_PPP:
        begin
          AdapterType := atPPP;
        end;

        MIB_IF_TYPE_LOOPBACK:
        begin
          AdapterType := atLoopback;
        end;

        MIB_IF_TYPE_SLIP:
        begin
          AdapterType := atSLIP;
        end;

        71:
        begin
          AdapterType := atWLAN;
        end;

        else
        begin
          AdapterType := atUnknown;
        end;
      end;

      IPAddress  := String(Ptr^.IpAddressList.IpAddress.S);
      NetMask    := String(Ptr^.IpAddressList.IpMask.S);
      Gateway    := String(Ptr^.GatewayList.IpAddress.S);
      if Ptr^.DhcpEnabled = 0 then
      begin
        DHCPServer := '';
      end
      else
      begin
        DHCPServer := String(Ptr^.DhcpServer.IpAddress.S);
      end;

      if Assigned(Func) then
      begin
        if Func(AdapterName,Description,HardwareAddress,AdapterIndex,
                AdapterType,Ptr^.DhcpEnabled <> 0,IPAddress,NetMask,
                Gateway,DHCPServer) = False then
        begin
          Break;
        end;
      end;

      Ptr := Ptr^.Next;
    end;

  finally
    FreeMem(pAdapterInfo);
  end;

end;
なおIP_ADAPTER_INFO構造体のTypeの値として無線LANの場合の定義がMIB_IF_TYPE_...にはありませんが、実際には71が格納されるようです。
またIPv6(Windows XP以降)の場合はGetAdaptersAddressesを使用する必要があります。

2010年7月20日

参加しているドメインまたはワークグループの種類と名前を取得する

前回のNetWkstaGetInfoを使用する方法ではNTドメイン(AD)に参加しているのかワークグループに参加しているのかを知ることができませんでした。そこでNetGetJoinInformation(ja)で参加しているNTドメインまたはワークグループの名前と参加ステータスを取得してみます。ここでもNetWkstaGetInfoで取得したドメインまたはワークグループの名前の領域はNetApiBufferFree(ja)で解放する必要があることに注意が必要です。
まずこれらのネットワーク管理APIと列挙を定義します。
uses
  Windows;

type
  NET_API_STATUS = DWORD;
  {$EXTERNALSYM NET_API_STATUS}

  _NETSETUP_JOIN_STATUS = (NetSetupUnknownStatus = 0,  // The status is unknown.
                           NetSetupUnjoined,           // The computer is not joined.
                           NetSetupWorkgroupName,      // The computer is joined to a workgroup.
                           NetSetupDomainName);        // The computer is joined to a domain.
  {$EXTERNALSYM _NETSETUP_JOIN_STATUS}
  NETSETUP_JOIN_STATUS  = _NETSETUP_JOIN_STATUS;
  {$EXTERNALSYM NETSETUP_JOIN_STATUS}
  PNETSETUP_JOIN_STATUS = ^NETSETUP_JOIN_STATUS;
  {$EXTERNALSYM PNETSETUP_JOIN_STATUS}
  TNetsetupJoinStatus   = NETSETUP_JOIN_STATUS;

const
  NERR_Success = 0;
  {$EXTERNALSYM NERR_Success}

function NetGetJoinInformation(lpServer: PWideChar;
                               var lpNameBuffer: PWideChar;
                               BufferType: PNETSETUP_JOIN_STATUS): NET_API_STATUS; stdcall;
  external 'netapi32.dll' Name 'NetGetJoinInformation';
  {$EXTERNALSYM NetGetJoinInformation}

function NetApiBufferFree(Buffer: Pointer): NET_API_STATUS; stdcall;
  external 'netapi32.dll' Name 'NetApiBufferFree';
  {$EXTERNALSYM NetApiBufferFree}
あとはこれを呼び出すだけです。
function GetNetworkJoinInformation(var NetworkName: String;
                                   var Status: TNetsetupJoinStatus): Boolean;
var
  Buffer: PWideChar;
begin

  if NetGetJoinInformation(nil,Buffer,@Status) <> NERR_Success then
  begin
    Result := False;
    Exit;
  end;

  NetworkName := Buffer;

  NetApiBufferFree(Buffer);

  Result := True;

end;

2010年7月16日

プログラミングの魔導書 予約受付開始

プログラミングの魔導書 ~Programmers' Grimoire~ Vol.1の予約受付が始まっています。

株式会社ロングゲート - 製品案内

ただいま予約受付中
予約期間は 2010年8月6日(金) までとなります。
完全受注生産のため、書籍版については予約でのみご購入いただけます。
PDF版につきましては、予約終了後でも購入可能です。

書籍版 1,500円(税込・送料別)
PDF版 1,000円(税込)
書籍+PDFセット 2,000円(税込・送料別)

書籍版は2010/08/06までに予約しないと入手不能になる(それ以降はPDFのみになる)とのことです。

元ねたは江添さん本の虫: 新しいプログラミング雑誌の発行アキラさん[Grimoire]「プログラミングの魔導書」予約開始! - Faith and Brave - C++で遊ぼう

参加しているドメインまたはワークグループの名前を取得する

現在参加しているNTドメインまたはワークグループの名前を取得するにはNetWkstaGetInfo(ja)を使用します。NetWkstaGetInfoで取得した構造体(WKSTA_INFO_100WKSTA_INFO_101WKSTA_INFO_102)はNetApiBufferFree(ja)で解放する必要があることに注意が必要です。
まずこれらのネットワーク管理APIや構造体を定義します。
uses
  Windows;

type
  NET_API_STATUS = DWORD;
  {$EXTERNALSYM NET_API_STATUS}

  PWKSTA_INFO_100 = ^WKSTA_INFO_100;
  {$EXTERNALSYM PWKSTA_INFO_100}
  _WKSTA_INFO_100 = record
    wki100_platform_id: DWORD;
    wki100_computername: PWideChar;
    wki100_langroup: PWideChar;
    wki100_ver_major: DWORD;
    wki100_ver_minor: DWORD;
  end;
  {$EXTERNALSYM _WKSTA_INFO_100}
  WKSTA_INFO_100 = _WKSTA_INFO_100;
  {$EXTERNALSYM WKSTA_INFO_100}

  PWKSTA_INFO_101 = ^WKSTA_INFO_101;
  {$EXTERNALSYM PWKSTA_INFO_101}
  _WKSTA_INFO_101 = record
    wki101_platform_id: DWORD;
    wki101_computername: PWideChar;
    wki101_langroup: PWideChar;
    wki101_ver_major: DWORD;
    wki101_ver_minor: DWORD;
    wki101_lanroot: PWideChar;
  end;
  {$EXTERNALSYM _WKSTA_INFO_101}
  WKSTA_INFO_101 = _WKSTA_INFO_101;
  {$EXTERNALSYM WKSTA_INFO_101}

  PWKSTA_INFO_102 = ^WKSTA_INFO_102;
  {$EXTERNALSYM PWKSTA_INFO_102}
  _WKSTA_INFO_102 = record
    wki102_platform_id: DWORD;
    wki102_computername: PWideChar;
    wki102_langroup: PWideChar;
    wki102_ver_major: DWORD;
    wki102_ver_minor: DWORD;
    wki102_lanroot: PWideChar;
    wki102_logged_on_users: DWORD;
  end;
  {$EXTERNALSYM _WKSTA_INFO_102}
  WKSTA_INFO_102 = _WKSTA_INFO_102;
  {$EXTERNALSYM WKSTA_INFO_102}

const
  NERR_Success = 0;
  {$EXTERNALSYM NERR_Success}

function NetWkstaGetInfo(servername: PWideChar; level: DWORD;
                         var bufptr: PByte): NET_API_STATUS; stdcall;
  external 'netapi32.dll' Name 'NetWkstaGetInfo';
  {$EXTERNALSYM NetWkstaGetInfo}

function NetApiBufferFree(Buffer: Pointer): NET_API_STATUS; stdcall;
  external 'netapi32.dll' Name 'NetApiBufferFree';
  {$EXTERNALSYM NetApiBufferFree}
今回はローカルコンピュータのNTドメイン/ワークグループ名があれば十分なので、servernameにNULLを、levelに100を指定してWKSTA_INFO_100構造体を取得し、wki100_langroupに格納されている文字列を取り出します。
function GetNTDomainName: String;
var
  Ptr: PWKSTA_INFO_100;
begin

  Result := '';
  if NetWkstaGetInfo(nil,100,PByte(Ptr)) <> NERR_Success then
  begin
    Exit;
  end;

  Result := Ptr^.wki100_langroup;
  NetApiBufferFree(Ptr);

end;
ただしこの方法ではNTドメインに参加しているのかワークグループに参加しているのかを知ることができないので、NetGetJoinInformationを使用する方法をお勧めします。

元ねたはDelphiDabbler.comGet the network computer and domain names

Nick HodgesがEmbarcaderoを退職

RAD StudioのR&DマネージャだったNick Hodgesさんがエンバカデロ・テクノロジーズを退職したというニュースが飛び込んできました。

Good luck Nick « Delphi Haven
Embarcadero Discussion Forums: Nick Hodges blog doesn't exist in ...

ForumのRobert Loveさんの発言によれば、I was notified today that Nick no longer works at Embarcardero.とのことです。

ForumにEmbarcadero Discussion Forums: Changes for Nick ...というスレッドが立っています。There have been some rumors floating around, and I wanted to set things straight.
I was let go by Embarcadero on Monday. This wasn't my decision.
ということだそうです。

2010/07/19追記: Nick Hodges | A man's got to know his limitationsができています。"Nick, thank you for your all works, and good luck!"

2010年7月15日

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

第17回エンバカデロ・デベロッパーキャンプは2010年09月15日に開催されます。時期的にRAD Studio 2011の話が出てくるのでしょうか。今回はエンバカデロ・テクノロジーズ 製品担当副社長("Senior Vice President of Marketing and Product Management")のMichael Swindellさんがいらっしゃるようです。

2010/09/07追記: 一部のセッションがUstreamで配信されるそうです。詳細は当日(2010/09/15)にデベロッパーキャンプのページで確認してくださいとのこと。

2010/09/09再追記: http://www.embarcadero.com/jp/landing-pages/developer-camp-liveでストリーミングが配信されるとのことです。

2010/09/10再々追記: Team Japanに情報が追加されています。

Team Japan » 第17回 エンバカデロ・デベロッパーキャンプ情報(その1) - 会場へのアクセス
Team Japan » 第17回 エンバカデロ・デベロッパーキャンプ情報(その2) - ライブ配信

2010年7月14日

Microsoft Monthly Update 2010/07

今日はMicrosoftのセキュリティアップデートの日です。
MS10-042
MS10-043
MS10-044
MS10-045

実行環境のアーキテクチャを取得する

実行環境のアーキテクチャはGetSystemInfo(ja)で取得したSYSTEM_INFO構造体のwProcessorArchitectureで知ることができるはずです。ところが実際には32bitのアプリケーションからは64bit版WindowsでもwProcessorArchitectureがPROCESSOR_ARCHITECTURE_INTEL(0)となってしまいます。GetSystemInfoの説明をよく見るとTo retrieve accurate information for an application running on WOW64, call the GetNativeSystemInfo function.ということでGetNativeSystemInfoを使用する必要があるようです。GetNativeSystemInfoはWindows XP/Server 2003以降でしか使えませんので、それ以前の環境ではGetSystemInfoを使用します。
uses
  Windows;

const
  PROCESSOR_ARCHITECTURE_AMD64   =     9;   // x64 (AMD or Intel)
  PROCESSOR_ARCHITECTURE_IA64    =     6;   // Intel Itanium Processor Family (IPF)
  PROCESSOR_ARCHITECTURE_INTEL   =     0;   // x86
  PROCESSOR_ARCHITECTURE_UNKNOWN = $FFFF;   // Unknown architecture

type
  TGetSystemInfoFunc = procedure (var lpSystemInfo: TSystemInfo); stdcall;

function GetProcessorArchitecture: Word;
var
  SI: TSystemInfo;
  GetSystemInfoFunc: TGetSystemInfoFunc;
begin

  FillChar(SI,SizeOf(SI),0);

  { Call GetNativeSystemInfo or GetSystemInfo }
  @GetSystemInfoFunc := GetProcAddress(GetModuleHandle(kernel32),
                                       'GetNativeSystemInfo');
  if Assigned(GetSystemInfoFunc) = True then
  begin
    GetSystemInfoFunc(SI);
  end
  else
  begin
    GetSystemInfo(SI);
  end;

  Result := SI.wProcessorArchitecture;

end;

2012/07/07追記: Delphi XE2以降であればSystem.SysUtilsユニットのTOSVersion.Architectureで実行環境のアーキテクチャを知ることができます。ねた元はZarko GajicさんのIs Your 32bit Delphi Applications Running On x86 (Win 32) OR x64 (Win 64)?

2010年7月13日

コンピュータ名を取得する

一時ファイル名の一部として使うなど、コンピュータ名を取得したい状況というのはそれなりにあると思います。そこでまずGetComputerName(ja)でNetBIOS名を取得してみます。
uses
  Windows, SysUtils;

function GetComputerName: String;
var
  Size: DWORD;
begin

  { Adjust buffer size }
  Size := MAX_COMPUTERNAME_LENGTH + 1;
  SetLength(Result,Size);

  { Get computer name }
  if Windows.GetComputerName(PChar(Result),Size) = False then
  begin
    RaiseLastOSError;
  end;
  SetLength(Result,Size);

end;
次にGetComputerNameEx(ja)(Windows 2000以降であれば使用可能)でDNSホスト名や完全修飾DNS名を取得してみます。まずはWindows.pasにGetComputerNameEx(W)と第1パラメータのCOMPUTER_NAME_FORMATの定義がWindows.pasに存在しない(Delphi 2010にはあるのかな?)ので用意します。
type
{$Z4}
  _COMPUTER_NAME_FORMAT = (ComputerNameNetBIOS,
                           ComputerNameDnsHostname,
                           ComputerNameDnsDomain,
                           ComputerNameDnsFullyQualified,
                           ComputerNamePhysicalNetBIOS,
                           ComputerNamePhysicalDnsHostname,
                           ComputerNamePhysicalDnsDomain,
                           ComputerNamePhysicalDnsFullyQualified,
                           ComputerNameMax);
  {$EXTERNALSYM _COMPUTER_NAME_FORMAT}
  COMPUTER_NAME_FORMAT  = _COMPUTER_NAME_FORMAT;
  {$EXTERNALSYM COMPUTER_NAME_FORMAT}
  TComputerNameFormat   = COMPUTER_NAME_FORMAT;

function GetComputerNameExW(NameType: COMPUTER_NAME_FORMAT; lpBuffer: PWideChar;
                            var nSize: DWORD): BOOL; stdcall;
  external kernel32 name 'GetComputerNameExW';
  {$EXTERNALSYM GetComputerNameExW}
そしてこれを
uses
  Windows, SysUtils;

function GetComputerNameEx(NameType: TComputerNameFormat): String;
var
{$IFDEF Unicode}
  Buffer: String;
{$ELSE}
  Buffer: WideString;
{$ENDIF}
  Size: DWORD;
begin

  { Adjust buffer size }
  Size := 0;
  GetComputerNameExW(NameType,nil,Size);
  SetLength(Buffer,Size);

  { Get computer name }
  if GetComputerNameExW(NameType,PWideChar(Buffer),Size) = False then
  begin
    RaiseLastOSError;
  end;
  SetLength(Buffer,Size);
  Result := Buffer;

end;
という形で呼び出します。1回目のGetComputerNameExではlpBufferをNULLに、nSizeを0にすることでバッファに必要なサイズ(終端のNUL文字を含む)を取得し、2回目のGetComputerNameExで実際のコンピュータ名を取得するのですが、GetComputerNameExA(ANSI版)ではlpBufferをNULLにしても正常にバッファサイズを取得できないため、GetComputerNameExW(Unicode版)を使用しています。

2010年7月10日

[書籍]アプレンティスシップ・パターン

有隣堂 ヨドバシAKIBA店

アプレンティスシップ・パターン (amazon)/Dave H. Hoover、Adewale Oshiney著/柴田 芳樹訳/オライリージャパン/ISBN 978-4-87311-460-6/2,310円

を購入。

2010年7月8日

2nd anniversary

さすがに今年は覚えてました。とはいえここのところ内容がいまいちですね。この1年間のエントリは155件(2009/07/07-2010/07/02)。そろそろDelphi 2011の話が出始めると思われるので、今年も追いかけていきたいと思っています。

2010年7月2日

David Iが日本にきているらしい

David IことDavid Intersimoneさんが日本に来ていて懇談会をやっているようです(プレス関係かな?)。詳しくはTwitterのハッシュタグ#davidiembarcadero_jpCYonezawaあたりで。どうせならustreamあたりで生中継でもどうでしょう?

2010/07/04追記: builder by ZDNetの2010/06/18の開発環境の整備とクロスプラットフォームに注力--エンバカデロが新たな製品戦略 - builder by ZDNet Japanという記事にMalcolm Grovesさんの発言として7月には、CodeGear製品のチーフエバンジェリストを務めるDavid Intersimone(David I)氏が来日し、開発ツールに関する詳細な説明を行うことも明らかにした。あるので、週明けにはまた発表かインタビューがあるのではないでしょうかありますが、藤井さんDavid I プライベートで日本へによれば今回はほぼプライベートだったようです。今月中にまた来るってことでしょうか。

2010/07/05追記: 藤井さんの記事に従って2010/07/04追記分の記述を修正しました。アメリカ合衆国ではこの週末は独立記念日でお休みなんですね。