ドライブの情報

参照元1  参照元2


redball.gif目次へ戻る

n01901.gif

「ドライブの情報」と言っても、参照元1の絵のように車に乗って出かけるわけではない。(笑) 右図のように、「ファイルを開く」コモンダイアログのルートをマイコンピュータにもっていくと、システムの現在のドライブの一覧がリストビューに表示される。右図は、現在の筆者のノートパソコンの例であるが、このような一覧を取得するにはどうしたらよいかをここで調べよう。



最初は、システムのドライブにどんなものがあるかを列挙することから始めよう。

function GetLogicalDrives: DWORD; stdcall;

この引数のない API の戻り値は、符号なし32ビット値であるが、低位のビットから、A ドライブ、B ドライブ、・・・に対応しており、もしそのそのドライブが存在するならば、対応するビットが1になっている。試してみよう。

List 1

procedure GetLogicalDriveList(List:TStrings);
var
  Drive,Mask:DWORD;
  i:integer;
begin
  Mask := 1;
  Drive := GetLogicalDrives;
  for i := 0 to 25 do begin
    if (Drive and Mask) <> 0 then List.Add(Char(ord('A')+i));
    Mask := Mask shl 1;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  GetLogicalDriveList(ListBox1.Items);
  Label1.Caption := IntToStr(ListBox1.Items.Count);
end;
n01902.gif

実行すると右図のようになる。


つぎに、各ドライブの種類を取得してみよう。参照元2でやった GetDriveType() API を使ってみよう。

function GetDriveType(lpRootPathName: PChar): UINT; stdcall;

引数には、例えば 'A:\' のように設定する。この戻り値は以下のいずれかである。

const
  DRIVE_UNKNOWN = 0;     //ドライブタイプを決定できない
  DRIVE_NO_ROOT_DIR = 1; //このルートディレクトリは存在しない
  DRIVE_REMOVABLE = 2;   //リムーバブルドライブである
  DRIVE_FIXED = 3;       //ハードディスクである
  DRIVE_REMOTE = 4;      //ネットワークドライブである
  DRIVE_CDROM = 5;       //CD-ROMドライブである
  DRIVE_RAMDISK = 6;     //ラムディスクである

さっそくやってみよう。

List 2

type
  TDriveType = (dtUnknown, dtNoDrive, dtRemovable, dtFixed,
                                     dtNetwork, dtCDROM, dtRAM);

procedure GetLogicalDriveList(List:TStrings);
var
  Drive,Mask:DWORD;
  i:integer;
begin
  Mask := 1;
  Drive := GetLogicalDrives;
  for i := 0 to 25 do begin
    if (Drive and Mask) <> 0 then List.Add(Char(ord('A')+i));
    Mask := Mask shl 1;
  end;
end;

function AGetDriveType(s:string):TDriveType;
begin
  result := TDriveType(GetDriveType(PChar(s)));
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  SL:TStringList;
  i:integer;
  s:string;
begin
  ListBox1.Clear;
  SL := TStringList.Create;
  try
    GetLogicalDriveList(SL);
    Label1.Caption := IntToStr(SL.Count);
    if SL.Count > 0 then begin
      for i := 0 to SL.Count-1 do begin
        s := SL[i][1]+':\';
        case AGetDriveType(s) of
          dtUnknown  : s := s+'  Unknown';
          dtNoDrive  : s := s+'  NoDrive';
          dtRemovable: s := s+'  Removable';
          dtFixed    : s := s+'  Fixed';
          dtNetwork  : s := s+'  Network';
          dtCDROM    : s := s+'  CD-ROM';
          dtRAM      : s := s+'  RAMDisk';
        end;
        ListBox1.Items.Add(s);
      end
    end;
  finally
    SL.Free;
  end;
end;
n01903.gif

実行すると右図のようになる。これでだいたい各ドライブの種類は分かったが、フロッピードライブの場所などは分からない。冒頭の図の情報とは違うようだ。


各ドライブの情報は、ShellAPI にある SHGetFileInfo() で取得できる。

function SHGetFileInfo(pszPath: PAnsiChar; 
              dwFileAttributes: DWORD; 
                      var psfi: TSHFileInfo; 
                    cbFileInfo, 
                        uFlags: UINT): DWORD; stdcall;

pszPath には、ここでは 'A:\' のような文字列を設定する。dwFileAttributes には、NT 系ではファイルの属性を設定できるが、ここではゼロを設定する。psfi には以下のようなレコード型を設定し、戻り値として情報を受け取れる。cbFileInfo は、psfi に設定したレコード型のサイズを設定する。

type
  PSHFileInfoA = ^TSHFileInfoA;
  PSHFileInfoW = ^TSHFileInfoW;
  PSHFileInfo = PSHFileInfoA;
  TSHFileInfoA = record
    hIcon: HICON;                                       { out: アイコンハンドル}
    iIcon: Integer;                                     { out: システムイメージリストにおけるアイコンの番号}
    dwAttributes: DWORD;                                { out: IShellFolder::GetAttributesOf で返されるオブジェクト属性 }
    szDisplayName: array [0..MAX_PATH-1] of  AnsiChar;  { out: 表示名 }
    szTypeName: array [0..79] of AnsiChar;              { out: 型情報 }
  end;
  TSHFileInfo = TSHFileInfoA;

SHGetFileInfo()uFlags には取得したい情報のフラグを以下の定数を or で結合して設定する。

const
  SHGFI_ICON              = $000000100;     { アイコンを取得 }
  SHGFI_DISPLAYNAME       = $000000200;     { display name を取得 }
  SHGFI_TYPENAME          = $000000400;     { type name を取得 }
  SHGFI_ATTRIBUTES        = $000000800;     { attributes を取得 }
  SHGFI_ICONLOCATION      = $000001000;     { icon location を取得 }
  SHGFI_EXETYPE           = $000002000;     { exe type を戻り値に返す }
  SHGFI_SYSICONINDEX      = $000004000;     { system icon index を取得 }
  SHGFI_LINKOVERLAY       = $000008000;     { アイコンに link overlay を重ねる }
  SHGFI_SELECTED          = $000010000;     { icon を選択状態にする }
  SHGFI_LARGEICON         = $000000000;     { large icon を取得 }
  SHGFI_SMALLICON         = $000000001;     { small icon を取得 }
  SHGFI_OPENICON          = $000000002;     { open icon にする }
  SHGFI_SHELLICONSIZE     = $000000004;     { shell size icon を取得 }
  SHGFI_PIDL              = $000000008;     { pszPath は pidl である }
  SHGFI_USEFILEATTRIBUTES = $000000010;     { dwFileAttribute フィールドを使う }

SHGetFileInfo() は、成功すると非ゼロを返す。それではやってみよう。Form1ListView1Label1Button1ImageList1 を置いて以下のようにする。

List 3

uses
  ShellAPI;

type
TDriveShellInfo = record
 Icon:hIcon;
 Image:integer;
 DisplayName,
 TypeName:string
end;

procedure GetLogicalDriveList(List:TStrings);
var
  Drive,Mask:DWORD;
  i:integer;
begin
  Mask := 1;
  Drive := GetLogicalDrives;
  for i := 0 to 25 do begin
    if (Drive and Mask) <> 0 then List.Add(Char(ord('A')+i));
    Mask := Mask shl 1;
  end;
end;

procedure GetDriveShellInfo(Drive:string;var Info:TDriveShellInfo);
var
  SHFileInfo : TSHFileInfo;
begin
  ShGetFileInfo(PChar(Drive),0, SHFileInfo,SizeOf(TSHFileInfo),
     SHGFI_TYPENAME or SHGFI_DISPLAYNAME or SHGFI_ICON or SHGFI_SMALLICON);
  with Info do
  begin
    Icon  := SHFileInfo.hIcon;
    Image := SHFileInfo.iIcon;
    DisplayName := SHFileInfo.szDisplayName;
    TypeName := SHFileInfo.szTypeName
  end
end;


procedure TForm1.Button1Click(Sender: TObject);
var
  i:integer;
  SL:TStringList;
  DSI:TDriveShellInfo;
  IC:TIcon;
begin
  SL := TStringList.Create;
  IC := TIcon.Create;
  try
    GetLogicalDriveList(SL);
    Label1.Caption := IntToStr(SL.Count);
    if SL.Count > 0 then
      for i := 0 to SL.Count-1 do begin
        GetDriveShellInfo(SL[i]+':\',DSI);
        IC.Handle := DSI.Icon;
        with ListView1.Items.Add do begin
          Caption := DSI.DisplayName;
          ImageIndex := ImageList1.AddIcon(IC);
        end;
        DestroyIcon(DSI.Icon);
      end;
  finally
    SL.Free;
    IC.Free;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  ListView1.ViewStyle := vsSmallIcon;
  ListView1.SmallImages := ImageList1;
  ListView1.Font.Name := 'MS UI Gothic';
end;
n01904.gif

実行して Button1 を押すと右図のようになる。どうやら、冒頭の図と同じようになったらしい。



VCL の関数には、SysUtils にドライブの容量と空き容量を取得する以下の関数がある。

function DiskSize(Drive: Byte): Int64;
function DiskFree(Drive: Byte): Int64;

これらは、ドライブの容量、または空き容量をバイト単位で返す。ドライブ番号は,0 がカレントドライブ,1A ドライブ,2B ドライブ・・・となっている。ドライブ番号が無効な場合は -1 を返し、エラーが起きないので、特にリムーバブルドライブにアクセスするときは、この関数でメディアの有無を取得できる。

List 4

function ADiskSize(ch:Char):Int64;
begin
  result := DiskSize(Ord(ch)-64);
end;

function ADiskFree(ch:Char):Int64;
begin
  result := DiskFree(Ord(ch)-64);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  ret:Int64;
  d:Double;
begin
  ret := ADiskSize('A');
  if ret = -1 then
    ShowMessage('DiskSize Failed!')
  else begin
    d := ret div 1024;
    Label1.Caption := Format('容量 %.0n キロバイト',[d]);
    ret := ADiskFree('A');
    d := ret div 1024;
    Label2.Caption := Format('空き容量 %.0n キロバイト',[d]);
  end;
end;
n01905.gif

実行すると右図のようになる。筆者の A ドライブはフロッピードライブであるが、メディアを挿しているときは上記のように正常に動作するし、メディアを引っこ抜いても、エラーは起きない。上記の例では、ShowMessage() が実行される。


n01906.gif

また、右図は筆者の E ドライブに対して行った結果である。640MB MO ドライブである。



各ドライブの詳細情報は、

  function GetVolumeInformation(lpRootPathName: PChar;
                            lpVolumeNameBuffer: PChar; 
                               nVolumeNameSize: DWORD; 
                          lpVolumeSerialNumber: PDWORD;
                  var lpMaximumComponentLength, 
                             lpFileSystemFlags: DWORD;
                        lpFileSystemNameBuffer: PChar; 
                           nFileSystemNameSize: DWORD): BOOL; stdcall;

を使って得られる。lpRootPathName には、GetDriveType() のパラメータと同じように 'C:\' などと指定する。 lpVolumeNameBuffer には、名前を受け取るバッファを指定する。 nVolumeNameSize には、受け取るバッファのサイズをバイト単位で指定する。 lpVolumeSerialNumber にはシリアル番号を受け取る DWORD 型変数のアドレスを指定する。 lpMaximumComponentLength はファイルシステムのパス名のうち \ 記号で挟まれている部分で使える最長の名前の大きさをバイト単位で受け取るために DWORD 型の変数を指定する。長いファイル名をサポートしているシステムでは、255 を受け取る。 lpFileSystemFlags には、以下の定数の組み合わせが返される。

  FS_CASE_IS_PRESERVED = FILE_CASE_PRESERVED_NAMES;//大文字・小文字を予約ずみ
  FS_CASE_SENSITIVE = FILE_CASE_SENSITIVE_SEARCH;  //大文字・小文字を区別する
  FS_UNICODE_STORED_ON_DISK = FILE_UNICODE_ON_DISK;//ファイル名にユニコードをサポート
  FS_PERSISTENT_ACLS = FILE_PERSISTENT_ACLS;       //ACLSを使用(NTなど)
  FS_VOL_IS_COMPRESSED = FILE_VOLUME_IS_COMPRESSED;//圧縮されている
  FS_FILE_COMPRESSION = FILE_FILE_COMPRESSION;     //ファイルベースの圧縮をサポート

  FILE_CASE_SENSITIVE_SEARCH          = $00000001; 
  FILE_CASE_PRESERVED_NAMES           = $00000002; 
  FILE_UNICODE_ON_DISK                = $00000004; 
  FILE_PERSISTENT_ACLS                = $00000008; 
  FILE_FILE_COMPRESSION               = $00000010; 
  FILE_VOLUME_IS_COMPRESSED           = $00008000; 

そのほかにもあるかも知れない。これらの定数の意味については、MSDN などを調べてほしい。lpFileSystemNameBuffer は、そのボリュウムのファイルシステムの名前が返される。nFileSystemNameSize は、システム名を受け取るバッファのサイズをバイト単位で指定する。それでは試してみよう。

List 5

unit Main;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ComCtrls, ImgList;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Label1: TLabel;
    ListView1: TListView;
    ImageList1: TImageList;
    ListBox1: TListBox;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ListView1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

uses
  ShellAPI;

type
TDriveShellInfo = record
  Icon:hIcon;
  Image:integer;
  DisplayName,
  TypeName:string
end;

TVolumeInformation = record
  VolumeName:string;
  VolumeSerialNumber,
  MaximumComponentLength,
  FileSystemFlags:DWORD;
  FileSystemName:string;
end;

procedure GetLogicalDriveList(List:TStrings);
var
  Drive,Mask:DWORD;
  i:integer;
begin
  Mask := 1;
  Drive := GetLogicalDrives;
  for i := 0 to 25 do begin
    if (Drive and Mask) <> 0 then List.Add(Char(ord('A')+i));
    Mask := Mask shl 1;
  end;
end;

procedure GetDriveShellInfo(Drive:string;var Info:TDriveShellInfo);
var
  SHFileInfo : TSHFileInfo;
begin
  ShGetFileInfo(PChar(Drive),0, SHFileInfo,SizeOf(TSHFileInfo),
     SHGFI_TYPENAME or SHGFI_DISPLAYNAME or SHGFI_ICON or SHGFI_SMALLICON);
  with Info do
  begin
    Icon  := SHFileInfo.hIcon;
    Image := SHFileInfo.iIcon;
    DisplayName := SHFileInfo.szDisplayName;
    TypeName := SHFileInfo.szTypeName
  end
end;

function ADiskSize(ch:Char):Int64;
begin
  result := DiskSize(Ord(ch)-64);
end;

function ADiskFree(ch:Char):Int64;
begin
  result := DiskFree(Ord(ch)-64);
end;

function AGetVolumeInformation(Drive:Char;var V:TVolumeInformation):Boolean;
begin
  result := false;
  if ADiskSize(Drive) = -1 then exit;

  with V do
  begin
    SetLength(VolumeName,MAX_PATH);
    SetLength(FileSystemName,MAX_PATH);
    VolumeSerialNumber := 0;
    MaximumComponentLength := 0;
    FileSystemFlags := 0;
    Result := GetVolumeInformation(PChar(Drive+':\'),PChar(VolumeName),
                   MAX_PATH,@VolumeSerialNumber,MaximumComponentLength,
                   FileSystemFlags,PChar(FileSystemName),MAX_PATH);
    SetLength(VolumeName,StrLen(PChar(VolumeName)));
    SetLength(FileSystemName,StrLen(PChar(FileSystemName)));
  end
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  i:integer;
  SL:TStringList;
  DSI:TDriveShellInfo;
  IC:TIcon;
begin
  SL := TStringList.Create;
  IC := TIcon.Create;
  try
    GetLogicalDriveList(SL);
    Label1.Caption := IntToStr(SL.Count);
    if SL.Count > 0 then
      for i := 0 to SL.Count-1 do begin
        GetDriveShellInfo(SL[i]+':\',DSI);
        IC.Handle := DSI.Icon;
        with ListView1.Items.Add do begin
          Caption := DSI.DisplayName;
          SubItems.Add(SL[i]);
          ImageIndex := ImageList1.AddIcon(IC);
        end;
        DestroyIcon(DSI.Icon);
      end;
  finally
    SL.Free;
    IC.Free;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  ListView1.ViewStyle := vsSmallIcon;
  ListView1.SmallImages := ImageList1;
  ListView1.Font.Name := 'MS UI Gothic';
end;

procedure TForm1.ListView1Click(Sender: TObject);
var
  VI:TVolumeInformation;
begin
  if AGetVolumeInformation(ListView1.Selected.SubItems[0][1],VI) then begin
    ListBox1.Clear;
    with VI do begin
      ListBox1.Items.Add(       'VolumeName:      '+VolumeName);
      ListBox1.Items.Add(Format('VolSerialNumber: %u',[VolumeSerialNumber]));
      ListBox1.Items.Add(Format('MaxCompLength:   %u',[MaximumComponentLength]));
      ListBox1.Items.Add(Format('FileSystemFlags: $%x',[FileSystemFlags]));
      ListBox1.Items.Add(       'FileSystemName:  '+FileSystemName);
    end;
  end else
    ShowMessage('メディアがありません');
end;
n01907.gif

実行して、Button1 を押して表示される ListView1 の項目をクリックすると、右図のように ListBox1 にその情報が表示される。




以上、ここではドライブの情報を取得する方法について調べた。

redball.gif目次へ戻る