Link to home
Start Free TrialLog in
Avatar of WinRat
WinRatFlag for South Africa

asked on

How to link hard drive numbers and drive letters

Hi Experts,
Can anyone supply me with working Delphi functions to link physical hard drive numbers and assigned drive letters?

Something like:
function GetDriveNumber(const DriveLetter: char): integer

procedure GetDriveLetters(const DriveNumber: integer; var DriveLetters: array of char)
(need some way of storing multiple  drive letters as a drive may be partitioned into several areas each with their own drive letter of course)

Thanks!!
SOLUTION
Avatar of ThievingSix
ThievingSix
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
And you'll need these constants
  TDiskExtent = record
    DiskNumber: Cardinal;
    StartingOffset: Int64;
    ExtentLength: Int64;
  end;
  DISK_EXTENT = TDiskExtent;
  PDiskExtent = ^TDiskExtent;
  TVolumeDiskExtents = record
    NumberOfDiskExtents: Cardinal;
    Extents: array[0..0] of TDiskExtent;
  end;
  VOLUME_DISK_EXTENTS = TVolumeDiskExtents;
  PVolumeDiskExtents = ^TVolumeDiskExtents;
 
const
  FILE_DEVICE_DISK                     = $00000007;
  METHOD_BUFFERED                      = 0;
  FILE_ANY_ACCESS                      = 0;
  IOCTL_DISK_BASE                      = FILE_DEVICE_DISK;
  IOCTL_VOLUME_BASE                    = DWORD('V');
  IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS = ((IOCTL_VOLUME_BASE shl 16) or (FILE_ANY_ACCESS shl 14) or (0 shl 2) or METHOD_BUFFERED);

Open in new window

Avatar of WinRat

ASKER

@ThievingSix:

I created a test application (see Main Form code snippet) to test your supplied answers.

I added in the missing type and constant identifiers but it wouldn't compile on this line:
  DISK_EXTENT = TDiskExtent;  {compile error here: "(" expected but ";" found!}

Am I missing something obvious?
unit MainF;
 
interface
 
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
 
type
  TMainForm = class(TForm)
  private
    { Private declarations }
  public
    { Public declarations }
  end;
 
var
  MainForm: TMainForm;
 
{--------------------- START: ThievingSix's constants ------------------------}
type
 TDiskExtent = record
    DiskNumber: Cardinal;
    StartingOffset: Int64;
    ExtentLength: Int64;
  end;
 
const
  DISK_EXTENT = TDiskExtent;  {compile error here: "(" expected but ";" found!}
  PDiskExtent = ^TDiskExtent;
 
type
  TVolumeDiskExtents = record
    NumberOfDiskExtents: Cardinal;
    Extents: array[0..0] of TDiskExtent;
  end;
 
const
  VOLUME_DISK_EXTENTS = TVolumeDiskExtents;
  PVolumeDiskExtents = ^TVolumeDiskExtents;
 
const
  FILE_DEVICE_DISK                     = $00000007;
  METHOD_BUFFERED                      = 0;
  FILE_ANY_ACCESS                      = 0;
  IOCTL_DISK_BASE                      = FILE_DEVICE_DISK;
  IOCTL_VOLUME_BASE                    = DWORD('V');
  IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS = ((IOCTL_VOLUME_BASE shl 16) or
                      (FILE_ANY_ACCESS shl 14) or (0 shl 2) or METHOD_BUFFERED);
{---------------------  END: ThievingSix's constants -------------------------}
 
implementation
{$R *.DFM}
{----------------------------------------------------------------------------}
 
{--------------------- START: ThievingSix's functions ------------------------}
function GetLD(Drive: Char): Cardinal;
var
  Buffer : String;
begin
  Buffer := Format('\\.\%s:',[Drive]);
  Result := CreateFile(PChar(Buffer),GENERIC_READ Or GENERIC_WRITE,FILE_SHARE_READ,nil,OPEN_EXISTING,0,0);
  If Result = INVALID_HANDLE_VALUE Then
    begin
    Result := CreateFile(PChar(Buffer),GENERIC_READ,FILE_SHARE_READ,nil,OPEN_EXISTING,0,0);
  end;
end;
 
{-------------------------------------}
function GetPhysicalDiskNumber(Drive: Char): Byte;
var
  LD : DWORD;
  DiskExtents : PVolumeDiskExtents;
  DiskExtent : TDiskExtent;
  BytesReturned : Cardinal;
begin
  Result := 0;
  LD := GetLD(Drive);
  If LD = INVALID_HANDLE_VALUE Then Exit;
  Try
    DiskExtents := AllocMem(Max_Path);
    DeviceIOControl(LD,IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,nil,0,DiskExtents,Max_Path,BytesReturned,nil);
    If DiskExtents^.NumberOfDiskExtents > 0 Then
      begin
      DiskExtent := DiskExtents^.Extents[0];
      Result := DiskExtent.DiskNumber;
    end;
  Finally
    CloseHandle(LD);
  end;
end;
{---------------------- END: ThievingSix's functions -------------------------}
end.

Open in new window

Avatar of WinRat

ASKER

Hi ThievingSix,

Regarding my previous post - disregard. It was silly. Your code never had any identifiers and I put some under CONST instead of TYPE. Fixed and it compiled fine.

I made a simple program to test your GetPhysicalDiskNumber() function. I just used GetdiskType so only fixed and removable drive letters were sent to your function.

MODS
1) As I wanted the return code to be -1 if there was an invalid file handle I modified the function, and also the return type, to be a LongInt instead of a Byte.

2) I found that the call to CreateFile in GetLD() hung 10-15 seconds for some hard drives (not all). As I had the same problem yesterday with other code I knew how to fix it: I changed the access flags to 0. Problem solved.
   CreateFile(PChar(Buffer),0,FILE_SHARE_READ,nil,OPEN_EXISTING,0,0)

3) I suspect your code might have memory leak and ask if you could check it for possible culprits? During the day Delphi's recompile and run became slower and slower. First I thought I had a virus, but finally checked my mem allocation and found it to be 2Gb. Normally its 500 to 700 Mb. Physical mem is 1Gb so no wonder very slow.
I never before had a major memory leak while using Delphi. The only other program I was using today was FireFox 3.06, which does use a lot of RAM but never to that extent.

YOUR ANSWER
OK, I consider half my question to be answered very well.
But I also asked for a second function to accept a drive letter and which would return a list or string of all the drive letters that go with a drive number.
(I think that going along with that would also be to determine the total number of physical drives in the system (fixed and removable)
If you look at the bottom section of Disk Management the "Disk xx" entries do show what I need)

I hope you are up for completing the solution to earn all the points?
Thanks in advance!


Avatar of WinRat

ASKER

Hi Experts,
Increasing the points to attract more interest and to encourage a full answer to my question.

Moderators: So far no other experts appear interested. What do I do if only one answer is posted but it does not fully answer my question? Do I award the full points anyway? I'm still learning the ropes here!

ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of WinRat

ASKER

HI Rusell,

Helping me again! Thanks!

I made a nice little test program to iterate everything and put it in a ListBox.
(screened for only fixed and removable drives)
Both functions seems to work fine, thought the WMI calls seem a little slow.
However only takes 2-5 seconds for each function so can live with that.

Is there a better way to iterate through all the physical drives i.e. is there a way to know how many physical drives there are before I start?
See the code section to see how I am doing it.

Oddly enough neither function returns a value for my USB ZIP drive (remember those)?
Again not serious - I was just testing everything I could connect!

Will allocate points as soon as I get your comments.
Probably 2/3 to you and 1/3 to ThievingSix. He did give me half the solution even if he didn't answer my further questions esp about a possible memory leak. I hope that sounds fair to all?
I will have to research how to allocate split points.

Mike


    for DrvNum := 0 to 9 do {change to 15 - should be high enough to catch all}
    begin
      R := WMIGetLogicalDrives(DrvNum,DriveLetters);
      if R = S_OK then                     {S_OK defined in Windows}
      begin
        MultiColListbox2.Items.Add(IntToStr(DrvNum)+';'+ DriveLetters);       
      end
    end;

Open in new window

SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of WinRat

ASKER

Thanks Russell for another "copy & paste" full solution that worked first time!
Hi Russell, I corrected a bug (which occurs in Delphi XE, probably an Unicode issue) in your WmiDisk unit:

This line produces a compiler error in Delphi XE:

if (Length(szDisk) > 0) and (UpCase(DriveLetter) = UpCase(szDisk[1])) then

Open in new window


Error message:
[DCC Error] WmiDisk.pas(256): E2015 Operator cannot be used with this type of operand

Solution: UpCase(szDisk[1]) needs to be typecasted:

if (Length(szDisk) > 0) and (UpCase(DriveLetter) = AnsiChar(UpCase(szDisk[1]))) then

Open in new window


Now it works in Delphi XE!