Laden...

pinvoke CreateFile (kernel32)

Erstellt von nali vor 8 Jahren Letzter Beitrag vor 8 Jahren 3.899 Views
nali Themenstarter:in
40 Beiträge seit 2006
vor 8 Jahren
pinvoke CreateFile (kernel32)

Hallo zusammen,

versuche nun schon länger die kernel32 Routine CreateFile zu nutzen. Leider mit diversen Problemen.

Hintergrund: Ich möchte eine SD-Karte (sektorweise) in einem .NET Programm clonen. Einfaches Kopieren reicht nicht, da es sich um ein Linux-Dateisystem handelt, wovon Windows relativ wenig versteht und ich auch die Partitions-Tabelle nebst Bootsector benötige. Meine Idee war jetzt einfach Sektorweise die Bytes auszulesen und dann lokal in einem Image zu speichern. Anschließend dann auf eine neue SD-Karte zurückspielen. Im Prinzip das was der win32imager bzw. dd unter Unix macht.

Die CreateFile-Routine habe ich wie auf pinvoke.net beschrieben folgendermaßen eingebunden:

      
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern SafeFileHandle CreateFile(
           string lpFileName,
           EFileAccess dwDesiredAccess,
           EFileShare dwShareMode,
           IntPtr lpSecurityAttributes,
           ECreationDisposition dwCreationDisposition,
           EFileAttributes dwFlagsAndAttributes,
           IntPtr hTemplateFile);

Die Flags / Enums habe ich direkt von pinvoke.net kopiert: http://pinvoke.net/default.aspx/kernel32/CreateFile.html

Nun versuche ich Laufwerk E: (meine SD-Karte) anzusprechen:


        drive = "E:"
        tryagain:
            h = CreateFile(
                /* @"\\.\"+ */drive + "", 
                EFileAccess.GenericRead, 
                EFileShare.None,
                IntPtr.Zero, 
                ECreationDisposition.OpenAlways, 
                0,
                IntPtr.Zero);

            if (h.IsInvalid)
            {
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
                retries++;
                if (retries > maxretry)
                {
                    retVal.isERROR = true;
                    return retVal;
                }
                goto tryagain;
            }

Leider bekomme ich immer folgende Fehlermeldung:

Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))

Ich habe im App.Manifest gesetzt:

<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />

Beim Starten der Applikation werde ich auch nach admin-Rechten gefragt. Aber bekomme trotzdem den oberen Fehler. Das Ganze tritt auch unabhängig vom gewählten Laufwerk ab.

Hat jemand noch eine Idee wo ich ansetzen könnte?

16.806 Beiträge seit 2008
vor 8 Jahren

In meinem Projekt QuickIO.NET habe ich CreateFile etwas anders eingebunden

An Adminrechten wird das nicht mangeln.
Interessant wäre, welchen Code(Fehlernummer) Du genau bekommst (Marshal.GetLastWin32Error( )) und evtl. eine generelle FileIO-Permission anfragen.

The CAS IOPermisson of read/write only grants you the ability to read or write. It takes no notice of filesystem level permissions (ACLs.) Examine the ACL on the folder a bit closer 🙂

Ob CharSet.Auto funktioniert, wenn Du explizit "\.&quot; oder "\?&quot; verwendest bezweifle ich.

PS: Die Abfrage des Errors _muss_die nächste Zeile sein, auch wenn Du da noch gar nicht weißt, ob etwas invalid ist.
Das schreibt die Win32 explizit vor, sonst wird nicht garantiert, dass die Fehlernummer 10 Zeilen später die jeweilige auch tatsächlich ist.
Deswegen siehst Du in meinem Code die Invalid-Prüfung nach der Error-Code-Abfrage.

.. und was bitte tust Du mit goto in .NET? 🤔

nali Themenstarter:in
40 Beiträge seit 2006
vor 8 Jahren

Danke für die ausführliche Antwort, habe es jetzt mal so umgesetzt wie vorgeschlagen:


        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        internal static extern SafeFileHandle CreateFile(
             string fullName,
             [MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess,
             [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode,
             IntPtr lpSecurityAttributes,
             [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition,
             [MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes,
             IntPtr hTemplateFile);

Dann der Aufruf wie folgt:


        public static unsafe streamer CreateStream(string drive, FileAccess type)
        {
            streamer retVal = new streamer();
            SafeFileHandle h = CreateFile(
                /* @"\\.\"+ */drive + "",
                type,
                FileShare.None,
                IntPtr.Zero,
                FileMode.Open,
                0,
                IntPtr.Zero);
            var win32Error = Marshal.GetLastWin32Error();
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            
            if (!h.IsInvalid)
            {
              
                System.IO.Stream inPutFile = new FileStream(h, type);
                retVal.SH = h;
                retVal.STR = inPutFile;
                retVal.isERROR = false;
                return retVal;
            }
            else
            {
                retVal.isERROR = true;
                return retVal;
            }
        }

Aufgerufen wird die Funktion dann mit drive = "E:" und type = FileAccess.Read.

Als Fehlercode bekomme ich übrigens 5 - scheint also definitiv ein Berechtigungsproblem sein. Kann ich das noch genauer nachprüfen - du hast was von FileIO-Permissions anfragen geschrieben, kann die Routine in deinem Link nicht 100 % nachvollziehen bzw. diese ruft doch auch "nur" CreateFile auf - wo ist das der Unterschied zu "meinem" Aufruf?

16.806 Beiträge seit 2008
vor 8 Jahren

Hast den Quote zu FileIOPermission in meinem Beitrag gelesen?
Da steht ja, was es tut und wofür es wichtig ist.

Ich QuickIO.NET mal ausprobiert auf eine SDCard und ging einwandfrei.
PS: CreateFile ist nicht für Sector-Copy geeignet, falls das Dein eigentliches Ziel ist.

1.361 Beiträge seit 2007
vor 8 Jahren

Hey nali,

du musst unterscheiden zwischen "E:&quot; und "\.\E:".
Das eine ist das Root-Verzeichnis auf der Partition E:, das andere ist die physische Diskpartition E: selbst.

Generell wenn du ein Verzeichnis mit CreateFile öffnen möchtest, musst du FILE_FLAG_BACKUP_SEMANTICS setzen, wofür du zusätzlich noch ein zwei Privilegien (SE_BACKUP_NAME + SE_RESTORE_NAME) benötigst.

Aber eigentlich willst du das ja garnicht, also kommentier dein "\.&quot;-Präfix wieder ein, und beachte die Hinweise zu CreateFile:

The dwCreationDisposition parameter must have the OPEN_EXISTINGflag.
When opening a volume or floppy disk, the dwShareMode parameter must have the FILE_SHARE_WRITEflag.

Beachte zusätzlich, dass eventuell ein Stream.Seek nur mit festen Sektoren-Größen funktioniert.

Das CharSet sollte egal sein, solange du nicht ExactSpelling bewusst auf die falsche Version von CreateFileA/W setzt. Es wird automatisch die richtige Methode aufgerufen.
Und nach meinen Tests (Win7) unterstützten CreateFileA und CreateFileW beide UNC-Pfade, auch wenn die Ansi-Variante noch immer auf MAX_PATH begrenzt ist.

PS: CreateFile ist nicht für Sector-Copy geeignet, falls das Dein eigentliches Ziel ist.

@Abt, hattest du damit Probleme?

beste Grüße
zommi

16.806 Beiträge seit 2008
vor 8 Jahren

CreateFile "ist" kein Sector-Copy sondern ein File-Copy; jedenfalls in dem Sinne, was ich unter einem Sector verstehe.

1.361 Beiträge seit 2007
vor 8 Jahren

CreateFile "ist" kein Sector-Copy sondern ein File-Copy; jedenfalls in dem Sinne, was ich unter einem Sector verstehe.

Die WinAPI möchte aber den Begriff "File" allgemeiner verstanden wissen, bisweilen so allgemein, dass er meines Erachtens auch "Sector" in deinem Sinne umfassen sollte.

Zitat von: msdn (CreateFile)
CreateFile was originally developed specifically for file interaction but has since been expanded and enhanced to include most other types of I/O devices and mechanisms available to Windows developers. [...] However, some uses of file may be referring more generally to an I/O object that supports file-like mechanisms. This liberal use of the term file is particularly prevalent in constant names and parameter names because of the previously mentioned historical reasons.

Gibt es denn eine andere API für sector-access in deinem Sinne?

beste Grüße
zommi

nali Themenstarter:in
40 Beiträge seit 2006
vor 8 Jahren

Hey nali,

du musst unterscheiden zwischen "E:&quot; und "\.\E:".
Das eine ist das Root-Verzeichnis auf der Partition E:, das andere ist die physische Diskpartition E: selbst.

Da haben wir schon das erste Problem - mein Präfix scheint nicht zu greifen. Habe jetzt mal manuell "\\.\E:" als driveLetter angeben.

The dwCreationDisposition parameter must have the OPEN_EXISTINGflag.
When opening a volume or floppy disk, the dwShareMode parameter must have the FILE_SHARE_WRITEflag.

Das habe ich auch gerade geändert.

Ergebniss: Es scheint zu laufen, zumindest bekomme ich keinen Fehler mehr und die ersten Bytes trudeln auch rein. Mit der festen Sektor-Größe habe ich berücksichtigt.

PS: CreateFile ist nicht für Sector-Copy geeignet, falls das Dein eigentliches Ziel ist.

Das klingt unschön für meinen Anwendungsfall, aber was ist denn die Alternative oder besser gesagt richtige Vorgehensweise?