4

我有这些分区(在 Windows 中),例如:

Hard Disk 1 - Partition C, Partition D
Hard Disk 2 - Partition E

程序语言有没有办法知道例如分区 C 和分区 D 是否在一个没有 WMI的物理硬盘中?

我不想使用 WMI,因为它很慢 - 对于这个例子,我花了 0.5 秒。我需要它快。

谢谢你。

4

2 回答 2

10

我不知道有任何其他托管方式来获取磁盘分区信息。您可以使用 C# 中的 P/Invoke 来使用 Win32 API。但是,除非绝对必要,否则您不应该这样做。

您需要的 Win32 函数称为 DeviceIoControl()。API 文档可以在http://msdn.microsoft.com/en-us/library/aa363216(VS.85).aspx找到。使用控制代码 IOCTL_STORAGE_GET_DEVICE_NUMBER 调用 DeviceIoControl(),您将获得给定分区设备句柄的物理磁盘驱动器。可以使用 CreateFile() API 检索分区的设备句柄。

但是,使用 DeviceIoControl() 很麻烦,您很可能必须为 32 位和 64 位版本的 Windows 制作不同的版本。

要检索所有分区,您可以使用托管代码 System.IO.DriveInfo,如下所示:

var x = from di in DriveInfo.GetDrives()
        where (di.DriveType == DriveType.Fixed)
        select di;

foreach (DriveInfo di in x)
{
    // Call DeviceIoControl() using the partition name from di.Name and the IOCTL_STORAGE_GET_DEVICE_NUMBER  control code to retrieve the physical disk
}

pinvoke.net 似乎有一些C# 签名。

于 2010-03-16T21:33:19.427 回答
3

这段 Delphi 代码应该可以使用 P/Invoke 调用轻松转换为 C#,并完全按照您的意愿进行操作。(还有更多)importand 调用的是 DeviceIOControl。

type
    STORAGE_QUERY_TYPE = DWORD;

const
    PropertyStandardQuery = 0;          // Retrieves the descriptor
    PropertyExistsQuery   = 1;          // Used to test whether the descriptor is supported
    PropertyMaskQuery     = 2;          // Used to retrieve a mask of writeable fields in the descriptor

type
    STORAGE_PROPERTY_ID = DWORD;

const
    StorageDeviceProperty = 0;

// Query structure - additional parameters for specific queries can follow the header
type
    STORAGE_PROPERTY_QUERY = packed record
        PropertyId:                 STORAGE_PROPERTY_ID;
        QueryType:                  STORAGE_QUERY_TYPE;
        AdditionalParameters:       Longword;
    end;

const
    FILE_DEVICE_MASS_STORAGE     = $0000002d;
    IOCTL_STORAGE_BASE           = FILE_DEVICE_MASS_STORAGE;
    FILE_ANY_ACCESS              = 0;
    METHOD_BUFFERED              = 0;
    IOCTL_STORAGE_QUERY_PROPERTY = ( IOCTL_STORAGE_BASE shl 16 ) or ( $500 shl 2 ) or METHOD_BUFFERED or ( FILE_ANY_ACCESS shl 14 );

type
    STORAGE_BUS_TYPE = DWORD;

const
    BusTypeUnknown           = $00;
    BusTypeScsi              = $01;
    BusTypeAtapi             = $02;
    BusTypeAta               = $03;
    BusType1394              = $04;
    BusTypeSsa               = $05;
    BusTypeFibre             = $06;
    BusTypeUsb               = $07;
    BusTypeRAID              = $08;
    BusTypeiScsi             = $09;
    BusTypeSas               = $0A;
    BusTypeSata              = $0B;
    BusTypeSd                = $0C;
    BusTypeMmc               = $0D;
    BusTypeVirtual           = $0E;
    BusTypeFileBackedVirtual = $0F;
    BusTypeMax               = $10;
    BusTypeMaxReserved       = $7F;

type
    STORAGE_DEVICE_DESCRIPTOR = packed record
        // sizeof( STORAGE_DEVICE_DESCRIPTOR )
        Version:                       DWORD;
        // Total size of the descriptor, including the space for additional data and id strings
        Size:                          DWORD;
        // The SCSI-2 device type
        DeviceType:                    BYTE;
        // The SCSI-2 device type modifier (if any) - this may be zero
        DeviceTypeModifier:            BYTE;
        // Flag indicating whether the device's media (if any) is removable.  This field should be ignored for media-less devices
        RemovableMedia:                BOOLEAN;
        // Flag indicating whether the device can support multiple outstanding commands.
        // The actual synchronization in this case is the responsibility of the port driver.
        CommandQueueing:               BOOLEAN;
        // Byte offset to the zero-terminated ascii string containing the device's vendor id string.
        // For devices with no such ID this will be zero
        VendorIdOffset:                DWORD;
        // Byte offset to the zero-terminated ascii string containing the device's product id string.
        // For devices with no such ID this will be zero
        ProductIdOffset:               DWORD;
        // Byte offset to the zero-terminated ascii string containing the device's product revision string.
        // For devices with no such string this will be zero
        ProductRevisionOffset:         DWORD;
        // Byte offset to the zero-terminated ascii string containing the device's serial number.
        // For devices with no serial number this will be zero
        SerialNumberOffset:            DWORD;
        // Contains the bus type (as defined above) of the device.  It should be used to interpret the raw device
        // properties at the end of this structure (if any)
        BusType:                       STORAGE_BUS_TYPE;
        // The number of bytes of bus-specific data which have been appended to this descriptor
        RawPropertiesLength:           DWORD;
        // Place holder for the first byte of the bus specific property data
        RawDeviceProperties:           DWORD;
    end;

    PSTORAGE_DEVICE_DESCRIPTOR = ^STORAGE_DEVICE_DESCRIPTOR;

    STORAGE_DEVICE_NUMBER = packed record
          DeviceType:                  LONGWORD; // DEVICE_TYPE
          DeviceNumber:                ULONG;
          PartitionNumber:             ULONG;
    end;

    PSTORAGE_DEVICE_NUMBER = ^STORAGE_DEVICE_NUMBER;

const
    IOCTL_STORAGE_GET_DEVICE_NUMBER = ( IOCTL_STORAGE_BASE shl 16 ) or ( $420 shl 2 ) or METHOD_BUFFERED or ( FILE_ANY_ACCESS shl 14 );

type
    TDriveBusType = (
        dbtUnknown,
        dbtScsi,
        dbtAtapi,
        dbtAta,
        dbt1394,
        dbtSsa,
        dbtFibre,
        dbtUsb,
        dbtRAID,
        dbtiScsi,
        dbtSas,
        dbtSata,
        dbtSd,
        dbtMmc,
        dbtVirtual,
        dbtFileBackedVirtual );

    TDeviceType = (
        dtUnknown ); // todo: implement

    TDriveInfoResult = record
        //
        Drive:        string;
        VendorID:     string;
        ProductID:    string;
        Revision:     string;
        Serial:       string;
        BusType:      TDriveBusType;
        Removable:    Boolean;
        //
        DeviceType:   TDeviceType;
        DeviceNumber: Integer;
        Partition:    Integer;
    end;

const
    BusTypes: array [ TDriveBusType ] of AnsiString = (
        'Unknown',
        'Scsi',
        'Atapi',
        'Ata',
        '1394',
        'Ssa',
        'Fibre',
        'Usb',
        'RAID',
        'iScsi',
        'Sas',
        'Sata',
        'Sd',
        'Mmc',
        'Virtual',
        'FileBackedVirtual' );

function DriveInfo( const Drive: string ): TDriveInfoResult;
var
    H:      THandle;
    N:      Longword;
    Query:  STORAGE_PROPERTY_QUERY;
    Buffer: array [ 0..1023 ] of Byte;
    Desc:   PSTORAGE_DEVICE_DESCRIPTOR;
    S, X:   AnsiString;
    i:      Integer;
    Info:   PSTORAGE_DEVICE_NUMBER;
begin
    // Clear out old data
    Result.Drive     := Drive;
    Result.VendorID  := '';
    Result.ProductID := '';
    Result.Revision  := '';
    Result.Serial    := '';

    // Open drive for querying
    H := CreateFile( PChar( '\\.\' + Drive ), 0, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0 );
    if H = INVALID_HANDLE_VALUE then Exit;
    try
        // Query device.
        FillChar( Query, sizeof( Query ), 0 );
        Query.PropertyId := StorageDeviceProperty;
        Query.QueryType  := PropertyStandardQuery;
        FillChar( Buffer, sizeof( Buffer ), 0 );
        if not DeviceIoControl ( H, IOCTL_STORAGE_QUERY_PROPERTY, @Query, sizeof( Query ), @Buffer, sizeof( Buffer ), N, nil ) then Exit;

        // Sanity checks.
        if N < sizeof( STORAGE_DEVICE_DESCRIPTOR ) then Exit;
        Desc := @Buffer;
        if Desc^.Version < sizeof( STORAGE_DEVICE_DESCRIPTOR ) then Exit;

        // And obtain result.
        if Desc^.VendorIdOffset        <> 0 then Result.VendorID  := Trim( PAnsiChar( @Buffer[ Desc^.VendorIdOffset        ] ) );
        if Desc^.ProductIdOffset       <> 0 then Result.ProductID := Trim( PAnsiChar( @Buffer[ Desc^.ProductIdOffset       ] ) );
        if Desc^.ProductRevisionOffset <> 0 then Result.Revision  := Trim( PAnsiChar( @Buffer[ Desc^.ProductRevisionOffset ] ) );
        if Desc^.SerialNumberOffset    <> 0 then begin
            // The serial number is encoded in HEX and with each two characters encoded swapped. ER ABCD -> BADC -> '42414443'
            S := PAnsiChar( @Buffer[ Desc^.SerialNumberOffset ] );
            X := '';
            for i := 1 to Length( S ) do if S[ i ] in [ '0'..'9', 'A'..'F', 'a'..'f' ] then X := X + S[ i ];
            S := '';
            SetLength( S, Length( X ) div 2 );
            // i = 1,2,3,4,5,6 -> 3,1,7,5,11,9
            for i := 1 to Length( S ) do S[ i ] := AnsiChar( StrToInt( '$' + Copy( X, 1 + ( ( ( i - 1 ) div 2 ) * 4 ) + 2 * ( i and 1 ), 2 ) ) );
            Result.Serial := Trim( S );
        end;
        if Desc^.BusType <= Longword( High( TDriveBusType ) ) then Result.BusType := TDriveBusType( Desc^.BusType );
        Result.Removable := Desc^.RemovableMedia;

        System.FillChar( Buffer, sizeof( Buffer ), 0 );
        if DeviceIoControl ( H, IOCTL_STORAGE_GET_DEVICE_NUMBER, nil, 0, @Buffer, sizeof( Buffer ), N, nil ) then begin
            Info := @Buffer;
            Result.DeviceType   := dtUnknown;
            Result.DeviceNumber := Integer( Info^.DeviceNumber );
            Result.Partition    := Integer( Info^.PartitionNumber );
        end;

    finally
        CloseHandle( H );
    end;
end;

function GetLogicalDrives(): TStringDynArray;
var
    Buffer: array [ 0..1023 ] of Char;
    N, i: Integer;
begin
    SetLength( Result, 0 );
    N := GetLogicalDriveStrings( High( Buffer ), @Buffer );
    if N >= Length( Buffer ) then raise Exception.Create( 'Oops' );
    i := 0;
    while ( i <= N ) and ( Buffer[ i ] <> #0 ) do begin
        SetLength( Result, Length( Result ) + 1 );
        Result[ High( Result ) ] := PChar( @( Buffer[ i ] ) );
        Inc( i, Length( Result[ High( Result ) ] ) + 1 );
    end;
end;

function RemoveTrailingPathDelimiter( const Path: string ): string;
begin
    if ( Length( Path ) = 0 ) or ( Path[ Length( Path ) ] <> PathDelim ) then Result := Path else Result := Copy( Path, 1, Length( Path ) - 1 );
end;

procedure TForm7.Button1Click( Sender: TObject );
var
    Drives: TStringDynArray;
    Drive:  string;
    Res:    TDriveInfoResult;
begin
    Memo1.Lines.BeginUpdate();
    try
        Memo1.Lines.Clear();
        Drives := GetLogicalDrives();
        for Drive in Drives do begin
            Res := DriveInfo( RemoveTrailingPathDelimiter( Drive ) );
            Memo1.Lines.Add( 'DRIVE: ' + Drive );
            Memo1.Lines.Add( 'VendorID = ' + Res.VendorID );
            Memo1.Lines.Add( 'ProductID = ' + Res.ProductID );
            Memo1.Lines.Add( 'Revision = ' + Res.Revision );
            Memo1.Lines.Add( 'Serial = ' + Res.Serial );
            Memo1.Lines.Add( 'BusType = ' + BusTypes[ Res.BusType ] );
            Memo1.Lines.Add( 'Removable = ' + IntToStr( Ord( Res.Removable ) ) );
            // device type.
            Memo1.Lines.Add( 'Device = ' + IntToStr( Res.DeviceNumber ) );
            Memo1.Lines.Add( 'Partition = ' + IntToStr( Res.Partition ) );

            Memo1.Lines.Add( '' );
        end;
    finally
        Memo1.Lines.EndUpdate();
    end;
end;
于 2010-03-17T08:52:54.480 回答