ctypes.cast von DRIVE_LAYOUT_INFORMATION_EX

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
ativ
User
Beiträge: 1
Registriert: Freitag 11. Januar 2013, 08:21

Hallo,

ich versuche mich gerade am Auslesen von Informationen einer Festplatte unter Windows. Hierzu benutze ich die Funktion win32file.DeviceIoControl mit dem ControlCode: IOCTL_DISK_GET_DRIVE_LAYOUT_EX

Als Antwort bekommt man eine Bytestruktur in C. Diese wandle ich mit ctypes.cast( ... ) in meine Struktur um. Laut Microsoft hat die Zielstruktur folgende Gestaltung:

Code: Alles auswählen

typedef struct _DRIVE_LAYOUT_INFORMATION_EX {
  DWORD                    PartitionStyle;
  DWORD                    PartitionCount;
  union {
    DRIVE_LAYOUT_INFORMATION_MBR Mbr;
    DRIVE_LAYOUT_INFORMATION_GPT Gpt;
  };
  PARTITION_INFORMATION_EX PartitionEntry[1];
} DRIVE_LAYOUT_INFORMATION_EX, *PDRIVE_LAYOUT_INFORMATION_EX;
Mein Problem bezieht sich jetzt auf das letzte Element der Struktur: PARTITION_INFORMATION_EX, welche laut Microsoft ein dynamisches Array sein soll. Ähnliche Strukturen mit POINTER für den Umgang mit dynamischen Arrays habe ich ebenfalls schon probiert, aber auch hier ist das Ergebnis das gleiche.

Ich gestalte die Struktur in Python folgendermaßen:

Code: Alles auswählen

class DRIVE_LAYOUT_INFORMATION_EX(ctypes.Structure):
    '''
    DRIVE_LAYOUT_INFORMATION_EX structure
    http://msdn.microsoft.com/en-us/library/windows/desktop/aa364001(v=vs.85).aspx
    '''
    _anonymous_ = ("DriveLayoutInformation",  )
    _fields_ = [("PartitionStyle", wintypes.DWORD),
                ("PartitionCount", wintypes.DWORD),
                ("DriveLayoutInformation",DriveLayoutInformation),
                ("PartitionEntry", wintypes.BYTE*8912)]

    def getPartitionEntries(self):
        byteStructure = self.PartitionEntry
        print(byteStructure)
        return  ctypes.cast(byteStructure, ctypes.POINTER(ctypes.ARRAY(PARTITION_INFORMATION_EX, self.PartitionCount))).contents
Der Cast sieht wie folgt aus:

Code: Alles auswählen

def getDriveLayout_EX(driveHandle):
    if driveHandle == win32file.INVALID_HANDLE_VALUE:
        print("Invalid Handle Value!")
        return None

    answerByteStructure = win32file.DeviceIoControl(driveHandle.getWin32Handle(), winioctlcon.IOCTL_DISK_GET_DRIVE_LAYOUT_EX, None, 8192)
    temp = ctypes.cast(answerByteStructure, ctypes.POINTER(DRIVE_LAYOUT_INFORMATION_EX)).contents

    return temp
Im Test sieht das Ganze wie folgt aus:

Code: Alles auswählen

handle = DriveHandle("0")
try:
    driveGeometry = DeviceIOControl.getDriveGeometry_EX(handle)
finally:
    handle.destroy()

print("Disk Size: " +str(driveGeometry.DiskSize))
print("BytesPerSector: "+str(driveGeometry.Geometry.BytesPerSector))
print("SectorsPerTrack: "+str(driveGeometry.Geometry.SectorsPerTrack))
print("TracksPerCylinders: "+str(driveGeometry.Geometry.TracksPerCylinder))
print("Cylinders: "+str(driveGeometry.Geometry.Cylinders))
print("MediaType: "+str(hex(driveGeometry.Geometry.MediaType)))
print("CtypesAddressOf: "+str(ctypes.addressof(driveGeometry)))

print("=====DriveLayout=====")

handle = DriveHandle("0")
try:
    driveLayout = DeviceIOControl.getDriveLayout_EX(handle)
finally:
    handle.destroy()

print("PartitionCount: "+str(driveLayout.PartitionCount))
print("PartitionStyle: "+str(DeviceIOControl.PartitionStyle[driveLayout.PartitionStyle]))

print("-----------------------")

partitionEntries = driveLayout.getPartitionEntries()
print("PartitionEntries: "+str(partitionEntries))

for index in range(driveLayout.PartitionCount):
    print("\n index:"+str(index))

    print("PartitionStyle: "+str(partitionEntries[index].PartitionStyle))
    print("StartingOffset: "+str(partitionEntries[index].StartingOffset))
    print("PartitionLength: "+str(partitionEntries[index].PartitionLength))
    print("PartitionNumber: "+str(partitionEntries[index].PartitionNumber))
    print("RewritePartition: "+str(partitionEntries[index].RewritePartition))
Dieser Text läuft auch super durch, ich kann auch auf die Partitionsinformationen wunderbar zugreifen, allerdings ist in diesem Array nur der erste Eintrag korrekt, die weiteren sind alle vollkommen fehlerhaft.

Meine Fragen: Verstehe ich die Struktur von Microsoft falsch bzw. setze ich das ganze dadurch falsch um? Bzw. wie soll der Cast dann korrekterweise sein?

Für Hilfe in jeder Art und Weise bin ich sehr dankbar.

vG ativ
BlackJack

@ativ: Grundsätzlich kann ich an dem `cast()` kein Problem erkennen.

Das Phänomen würde auftreten wenn `PARTITION_INFORMATION_EX` fehlerhaft definiert ist und in der Länge deshalb nicht mit der tatsächlichen Datenstruktur übereinstimmem würde.

Sonstiges zum Code:

Bei ungültigen Argumenten in Bibliotheksfunktionen einfach ein `print()` zur Information und dann einen besonderen Fehlerwert zurückgeben ist schlechter Stil. Die Ausgabe kann vom Aufrufer völlg unerwünscht sein, und bei Fehlerwerten muss der Aufrufer immer darauf prüfen, auch wenn er im Fehlerfall an der Stelle vielleicht gar nichts sinnvolles machen kann, also zieht sich die Notwendigkeit zu prüfen an dessen Aufrufer weiter und so weiter. Erfahrungsgemäss vergisst man dass dann irgendwo und bekommt deshalb an anderer Stelle Probleme mit denen man nicht gerechnet hat. Deshalb wurden Ausnahmen erfunden. Man muss dann nicht überall in der Aufrufhierarchie prüfen, aber gar nicht zu prüfen geht im Fehlerfall auch nicht einfach geräuschlos unter.

Eine Ausnahme fliegt Dir bei `getDriveLayout_EX()` im Folgecode ja sowieso um die Ohren wenn Du dort `None` zurück gibst. Nur wäre es halt schöner wenn das die Funktion schon ein ``raise TypeError('invalid drive handle')`` macht, anstatt etwas später einen ``AttributeError: 'NoneType' object has no attribute 'PartitionCount'`` zu bekommen.

Ist `DriveHandle` eine von Dir implementierte Klasse? Falls ja würde ich daraus einen „context manager” machen um den Quelltext von dem ``try``/``finally``-Muster zu befreien. Die fünf Zeilen bei der Verwendung würden auf zwei schrumpfen:

Code: Alles auswählen

with DriveHandle('0') as handle:
    driveGeometry = DeviceIOControl.getDriveGeometry_EX(handle)
Die Verwendung von `str()` und ``+`` sieht eher nach BASIC als nach Python aus. Bei den ganzen `print()`\s hätte man sich das ganz sparen können weil man mehrere Argumente angeben kann die von `print()` dann nacheinander durch Leerzeichen getrennt ausgegeben werden. Und die Umwandlung in eine Zeichenkette erledigt `print()` dabei auch gleich. Ansonsten bietet Python ein `format()`-Methode auf Zeichenketten um Werte in Zeichenketten zu formatieren.

Ebenfalls „unpythonisch” ist das ``for i in range(…):``-Muster wenn `i` dann verwendet wird um auf die Elemente einer Sequenz zuzugreifen. Man kann in Python direkt über Sequenzen iterieren, ohne den Umweg über einen Index. Wenn man den *zusätzlich* haben möchte, gibt es die `enumerate()`-Funktion. Was vielleicht nicht auf den ersten Blick klar ist: `ctypes.ARRAY`\s sind Sequenzen. Sie kennen ihre Länge und man kann über die Elemente des Arrays direkt in einer ``for``-Schleife iterieren.

Code: Alles auswählen

partitionEntries = driveLayout.getPartitionEntries()
print('PartitionEntries:', partitionEntries)

assert len(partitionEntries) == driveLayout.PartitionCount
for index, entry in enumerate(partitionEntries):
    print('\n index:', index)
    for attribute in [
        'PartitionStyle',
        'StartingOffset',
        'PartitionLength',
        'PartitionNumber',
        'RewritePartition',
    ]:
        print('{}: {}'.format(attribute, getattr(entry, attribute)))
Antworten