Archive for the 'Delphi' Category

WMIでの日時データの扱い(SWbemDateTimeとCIM_DATETIME)

水曜日, 5月 30th, 2007

WMIで日時データを扱う場合は、SWbemDateTimeを利用する方法が簡単です

MSDN: WbemDateTime

Delphiだと、WbemScripting_TLB を uses して、こんな感じで使えます。


procedure Sample;
var
  I:Integer;
  line:string;
  Locator:ISWbemLocator;
  Services:ISWbemServices;
  ResultSet:ISWbemObjectSet;
  Enum:IEnumVariant;
  RowObj:OleVariant;
  Value:LongWord;
  wbemDateTime:ISWbemDateTime;
begin
  Locator:=CoSWbemLocator.Create as ISWbemLocator;
  Services:=Locator.ConnectServer(
    '.', '', '', '', '', '', 0, nil);
  ResultSet:=Services.ExecQuery(
    'SELECT * From Win32_NTEventLog', 'WQL',
    wbemFlagReturnImmediately,nil);
  Enum:=ResultSet._NewEnum as IEnumVariant;

  // ISWbemDateTimeオブジェクトを生成
  wbemDateTime:=CosWbemDateTime.Create as
    ISWbemDateTIme;

  for I:=1 to ResultSet.Count do
  begin
    Enum.Next(1, RowObj, Value);
    if Value <= 0 then Break;
    if VarIsNull(RowObj) then Continue;
    // WBemDateTimeに値を代入
  wbemDateTime.Value:=RowObj.TimeWritten;
  // フォーマット
    line:=Format('%.4d/%.2d/%.2d %.2d:%.2d:%.2d.%.3d',
      [wbemDateTime.Year, wbemDateTime.Month,
       wbemDateTime.Day, wbemDateTime.Hours,
       wbemDateTime.Minutes, wbemDateTime.Seconds,
       wbemDateTime.MicroSeconds*1000]);
    ShowMessage(line);
  end;
end;

ISWbemDateTimeオブジェクトを作って、ValueプロパティにWQLの検索結果の日時データを設定するだけです。逆に、TDateTime型の値をISWbemDateTimeに設定/取得する場合は、GetVarDate, SetVarDateを利用します。

しかし、SWbemDateTimeクラスはWindows XP以降にしかないので、Win 2000 などでも動かそうとすると日時データを自前で処理する必要があります。
ソフト作ってるときにWinXPで動いていたのにWin2000 に持ってくと 「クラスが登録されていません」 と手がかりなしのエラーを吐かれて、SWbemDateTimeが使えないことに気づくまでかなりはまりました。

WMIで利用される日時データは、CIM_DATETIME という形式のものです。

MSDN: CIM_DATETIME

上記、MSDNのページを見ればCIM_DATETIMEが


yyyymmddHHMMSS.mmmmmmsUUU

と定義されていることが分かります。 mmmmmm はマイクロ秒、sUUUはタイムゾーンを表す数値でUTCからの差を分で表します。日本だと +540 になります。

Delphi上では、CIM_DATETIMEはwideStringとして処理されるため、SWbemDateTimeを使わなくてもCIM_DATETIMEに変換できます。


function SystemTimeToWbemDateTime(
  const localTime:TSystemTime):wideString;
var
  utcTime:TSystemTime;
  fileTime,localFileTime: TFileTime;
begin
  SystemTimeToFileTime(localTime, localFileTime);
  LocalFileTimeToFileTime(localFileTime, fileTime);
  FileTimeToSystemTime(fileTime, utcTime);

  Result:=Format(
    '%4.4u%2.2u%2.2u%2.2u%2.2u%2.2u.%6.6u+000',
    [utcTime.wYear, utcTime.wMonth, utcTime.wDay,
     utcTime.wHour, utcTime.wMinute, utcTime.wSecond,
     utcTime.wMilliSeconds*1000]);
end;

TDateTimeだとミリ秒以下が扱えないので、ここではTSystemTimeからCIM_DATETIMEに変換してます。タイムゾーン部分を現在のタイムゾーンのUTCからの差を算出するのが面倒だったので、TFileTimeを経由してUTC時刻に変換してあります。これだと時差が +000 ですみます。

参考: How To Convert from GMT(UTC) Time to Local Time

 

逆に、CIM_DATETIMEからTSystemTimeに変換するには時差の扱いを考えないといけません。手順としては、

  1. CIM_DATETIMEの日時(年月日時分秒マイクロ秒)情報からTFileTimeオブジェクトを生成
  2. CIM_DATETIMEの時差情報から現在のタイムゾーンとCIM_DATETIMEのタイムゾーンとの時差(分差)を算出
  3. 分差に基づいてTFileTimeの値を修正
  4. TFileTimeからTSystemTimeに変換

といった感じでしょうか。
今回作ったソフトは時刻を表示すればよいだけだったので、CIM_DATETIMEを単純に文字列として扱ってフォーマットの変更をしました。


function FormatWbemDateTime(
  const wbemDateTime:wideString):string;
begin
  Result:=wbemDateTime;   // yyyymmddHHMMSS.mmmmmmsUUU
  Delete(Result, 19, 3);  // yyyymmddHHMMSS.mmmsUUU
  Insert(':', Result,13); // yyyymmddHHMM:SS.mmmsUUU
  Insert(':', Result,11); // yyyymmddHH:MM:SS.mmmsUUU
  Insert(' ', Result,9);  // yyyymmdd HH:MM:SS.mmmsUUU
  Insert('/', Result,7);  // yyyymm/dd HH:MM:SS.mmmsUUU
  Insert('/', Result,5);  // yyyy/mm/dd HH:MM:SS.mmmsUUU
end;

まぁ、こんな逃げ方もあるということで。

Delphi で WMI

火曜日, 5月 22nd, 2007

最近はDelphiを使うことが多くなりました。VC6 でアプリを作っていたのと比べると、はるかに楽です。

少し前にDelphiでWMIを使うことがあったので、そのサンプルを乗せておきます。
WMIは、WMI(Windows Management Instrumentation)の略で、Windows OS上のさまざまな情報を取得、更新するためのものです。
OSやハードウェアの情報などを取得するのにWin32のAPIを直接操作してもいいんですが、それだと各情報ごとにAPIを調べて処理方法を変更しなければ行けません。WMIだとWQLというSQLのサブセットみたいなクエリを使って、いろんな情報を同じ方法で処理できます。

 

例えば、以下のような情報が取れます。

BIOS情報を取得する
Select * From Win32_BIOS
結果は、
Select * From Win32_BIOS
------------------------------------
BiosCharacteristics=[4,7,8,9,11,12,15,16,24,26,27,
28,29,30,32,33,34,39,40,41,48,49,50,51,52,53,54,
55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,
71,72,73,74,75,76,77,78,79]
BIOSVersion=[DELL - 27d6040b,Phoenix ROM BIOS PLUS Version 1.10 A02]
BuildNumber=
Caption=Phoenix ROM BIOS PLUS Version 1.10 A02
CodeSet=
CurrentLanguage=en|US|iso8859-1
Description=Phoenix ROM BIOS PLUS Version 1.10 A02
IdentificationCode=
InstallableLanguages=1
InstallDate=
LanguageEdition=
ListOfLanguages=[en|US|iso8859-1]
Manufacturer=Dell Inc.
Name=Phoenix ROM BIOS PLUS Version 1.10 A02
OtherTargetOS=
PrimaryBIOS=-1
ReleaseDate=20060411000000.000000+000
SerialNumber=6VX74BX
SMBIOSBIOSVersion=A02
SMBIOSMajorVersion=2
SMBIOSMinorVersion=4
SMBIOSPresent=-1
SoftwareElementID=Phoenix ROM BIOS PLUS Version 1.10 A02
SoftwareElementState=3
Status=OK
TargetOperatingSystem=0
Version=DELL - 27d6040b
------------------------------------

メモリの情報を取得する
Select * From Win32_PhysicalMemory
結果は、
Select * From Win32_PhysicalMemory
------------------------------------
BankLabel=
Capacity=536870912
Caption=物理メモリ
CreationClassName=Win32_PhysicalMemory
DataWidth=64
Description=物理メモリ
DeviceLocator=DIMM_A
FormFactor=8
HotSwappable=
InstallDate=
InterleaveDataDepth=0
InterleavePosition=0
Manufacturer=
MemoryType=0
Model=
Name=物理メモリ
OtherIdentifyingInfo=
PartNumber=
PositionInRow=1
PoweredOn=
Removable=
Replaceable=
SerialNumber=
SKU=
Speed=533
Status=
Tag=Physical Memory 0
TotalWidth=64
TypeDetail=128
Version=

BankLabel=
Capacity=536870912
Caption=物理メモリ
CreationClassName=Win32_PhysicalMemory
DataWidth=64
Description=物理メモリ
DeviceLocator=DIMM_B
FormFactor=8
HotSwappable=
InstallDate=
InterleaveDataDepth=0
InterleavePosition=0
Manufacturer=
MemoryType=0
Model=
Name=物理メモリ
OtherIdentifyingInfo=
PartNumber=
PositionInRow=1
PoweredOn=
Removable=
Replaceable=
SerialNumber=
SKU=
Speed=533
Status=
Tag=Physical Memory 1
TotalWidth=64
TypeDetail=128
Version=
------------------------------------

メモリは2枚ささっているので、2枚分出ていることが分かります。

WQLのFrom句の部分を変えるだけで、他にもCPU, OS, 論理ディスク, 物理ディスク, サウンドボード, インストール済みソフトウェア, ….. とコントロールパネルから手に入りそうな情報は一通り入手できます。取得できる情報の一覧は、MSDNの以下のページからたどれます。
MSDN: Win32 Classes

 
 

で、このWMIをDelphiから使うのもとても簡単です。
1. タイプライブラリの取り込み
WMI用のタイプライブラリをDelphi環境に取り込みます。
Delphiを起動して、上部メニューのプロジェクト ->タイプライブラリの取り込みをクリック
Microsoft WMI Scripting V1.2 Library (Version 1.2) を選択して、下のインストールボタンをクリック
WMI タイプライブラリ取り込み
インストール先は、新規に適当な名前のパッケージを作ればOK

2.以下のコードを適当な場所にコピー&ペースト


//  uses
//     OleServer, WbemScripting_TLB, ComObj, ActiveX
// が必要

// varArray も扱えるようにした VarToStr
function VarToString(const oleVar:oleVariant):string;
var
  lowBound, highBound, I:Integer;
begin
  // 配列の場合
  if VarIsArray(oleVar) then
  begin
    Result:='[';
    LowBound:=VarArrayLowBound(oleVar,1);
    HighBound:=VarArrayHighBound(oleVar,1);
    for I:=lowBound to highBound do
    begin
      if I=highBound then
        Result:=Result+VarToString(oleVar[I])
      else
        Result:=Result+VarToString(oleVar[I])+',';
    end;
    Result:=Result+']';
  end else
  begin
    Result:=VarToStr(oleVar);
  end;
end;

// WMI経由で情報を取得する
function SelectWMI(wql:string):string;
var
  I,J:Integer;
  line:string;
  Locator:ISWbemLocator;
  Services:ISWbemServices;
  ResultSet:ISWbemObjectSet;
  Enum, Enum2:IEnumVariant;
  RowObj, ColObj:OleVariant;
  Value:LongWord;
begin
  Locator:=CoSWbemLocator.Create as ISWbemLocator;
  Services:=Locator.ConnectServer('.', '', '', '', '', '', 0, nil);
  ResultSet:=Services.ExecQuery(wql, 'WQL',
    wbemFlagReturnImmediately,nil);
  Enum:=ResultSet._NewEnum as IEnumVariant;

  line:=wql+#13#10#13#10;
  for I:=1 to ResultSet.Count do
  begin
    Enum.Next(1, RowObj, Value);
    if Value <= 0 then Break;
    if VarIsNull(RowObj) then Continue;
    Enum2 := IUnknown(RowObj.Properties_._NewEnum)
      as IEnumVariant;

    for J:=1 To RowObj.Properties_.Count do
    begin
      Enum2.Next(1, ColObj, Value);
      if Value <= 0 then Break;
      if VarIsNull(ColObj) then Continue;
      line:=line+Format('%s=%s'+#13#10,
        [VarToString(ColObj.Name), VarToString(ColObj.Value)]);
    end;
    line:=line+#13#10;
  end;
  Result:=line;
end;

3. 適当なWQLを作って、SelectWMIを呼び出す。
以上で、完成。

 

関連リンク