Context Menu crash



  • Schönen guten Tag,

    und zwar geht es um folgendes. In einem VB6-Projekt nutzen wir die Funktion "GetOpenFileName" aus der comdlg32.dll welche bestandteil der WinAPI ist. Ist ja auch in C++ verfügbar.

    Jetzt haben wir im offenen Dialog aber ein Problem. Wenn wir einen Rechtsklick auf eine Datei machen und im Kontext-Menü auf Eigenschaften klicken, stürtzt unsere Software ab. Das problem tritt nicht bei allen Versionen der comdlg32.dll auf.

    Das problem ist auch bekannt, und man findet bei google viele Fragen dazu. Meist läuft es dann darauf hinaus, das Kontext-Menü zu bereinigen. Auch das habe ich schon mehrfach gemacht. Das löst mein Problem auch für ca 5 sekunden nach dem Neustart, danach tritt das Problem wieder auf.

    Das Problem tritt hier zwar in einem VB6 projekt auf, scheint aber ein allgemeines zu sein.

    Hat jemand von euch zufällig schon Erfahrung damit gemacht?
    Konnte das Problem schonmal jemand von euch lösen?

    Vielen dank schon mal,
    Schlaflos

    PS: die comdlg32.dll lässt sich leider nicht austauschen, da sie von PID 0 geblockt ist, welcher nicht gekillt werden kann.



  • Oh ja, auch ich habe schon Bekannschaft mit GetOpenFileName() gemacht und finde sie empfindlich gegenüber alle möglichen Fehler.

    So sürzte immer wieder mein Programm ab, sobald ich innerhalb von GetOpenFileName() Tooltips anschaute. Nachdem ich OpenOffice deinstalliert hatte, funktionierte es wieder. Als 0815 Workaround hatte ich mal die InitCommonControls() aufgerufen. Der Fehler trat übrigens auch in Notepad auf.

    Der Filterstring ist ein wenig kompliziert, denn es ist im eigentlichen Sinne kein String sondern ein Stringool. Also folgendes "FilterString\0FilterString\0\0"!!!

    Wenn du Erweiterungen in Form von Hooks nutzt, musst du unbedingt auf die richtige Abarbeitung von Nachrichten in der MsgProc achten! Ich hatte mal ein Message-Crash, in dem ich in der MsgProc unabhängig von Nachrichten immer die Windows Funktion GetWindowRect() oder ähnlich aufrief. Nun ja, unter Win8 bekam ich aber eine spezielle Paint Nachricht. Und ein Aufruf von GetWindowRect() wurde mit einem Crash quittiert.

    Achte auf eine richtige _WIN_VER Präprozessor Anweisungen und Initialisierung der Common Controls. Und achte unbedingt darauf dass deine OPENFILENAME Struktur an allen Ecken und Kanten sauber initialisiert wurde. Sind alle Indizes sauber initialisiert? Übergibst du einen Zeiger auf einen (nicht konstanten) String?,...



  • Okay, OpenOffice habe ich nicht. Nur Office 2013. Das kann ich auch nicht deinstallieren weil ich die cdo.dll brauche. Wobei sich die ja vorher sichern ließe.

    InitCommonControls() sagt mir jetzt erstmal nichts. Werde ich mir heute im laufe des Tages jedenfalls mal anschauen.

    Der Filterstring ist interessant. Wir geben da einen ganz normalen String rein mit Inhalt ".". Was ja aber in diesem Fall auch so funktionieren sollte.

    Was den Rest angeht verarbeiten wir in VB6 ja keine Nachrichten. Die Initialisierung scheint mir sonst auch erstmal ok.
    _WIN_VER benutzen wir nicht. Ich weiß gar nicht ob es das in VB6 überhaupt gibt. Auch das versuche ich mal heraus zu finden.

    Danke schon mal für die Hinweise auf mögliche Schwachstellen. Vielleicht baue ich heute mal ein lauffähiges Kurzbeispiel. Vielleicht fällt der Fehler dann direkt auf.



  • Hab das Kontext-Menü in der Registry bereinigt und ging dann - erstmal...

    Habe mir nun also folgendes Beispiel geschrieben, welches aus der Form in VB direkt läuft (Vorausgesetzt der Button ist da, ist nur der Codeinhalt). Der Fehler tritt hier auch nicht auf. Exakt der gleiche Code im Produkt crashed aber. Bin schon am Debuggen, aber vielleicht hat ja jemand eine gute Idee warum, die mir da extrem viel Arbeit abnehmen könnte.

    Ich weiß, ist VB6, aber vielleicht hat dennoch jemand ne Idee 🙂

    Private Declare Function GetOpenFileName Lib "comdlg32.dll" _
                                Alias "GetOpenFileNameA" ( _
                                pOpenFilename As OPENFILENAME) As Long
    
    Private Type OPENFILENAME
        lStructSize As Long
        hwndOwner As Long
        hInstance As Long
        lpstrFilter As String
        lpstrCustomFilter As String
        nMaxCustFilter As Long
        nFilterIndex As Long
        lpstrFile As String
        nMaxFile As Long
        lpstrFileTitle As String
        nMaxFileTitle As Long
        lpstrInitialDir As String
        lpstrTitle As String
        flags As Long
        nFileOffset As Integer
        nFileExtension As Integer
        lpstrDefExt As String
        lCustData As Long
        lpfnHook As Long
        lpTemplateName As String
    End Type
    
    Const x_MaxBuffer = 256
    
    Private Sub Command1_Click()
    Dim x_OpenFilename As OPENFILENAME
    
        With x_OpenFilename
            .hInstance = App.hInstance
            .lpstrTitle = "**Title**"
            .lpstrInitialDir = "C:\"
    
            .lpstrFilter = "Text Files" & vbNullChar & "*.txt" & vbNullChar & "All Files" & vbNullChar & "*.*" & vbNullChar & vbNullChar
            .nFilterIndex = 2
    
            .lpstrFile = String(x_MaxBuffer, 0)
            .nMaxFile = x_MaxBuffer - 1
            .lpstrFileTitle = .lpstrFile
            .nMaxFileTitle = x_MaxBuffer - 1
            .lStructSize = Len(x_OpenFilename)
          End With
    
        If GetOpenFileName(x_OpenFilename) <> 0 Then
            'PURPOSE: A file was selected
            MsgBox Left$(x_OpenFilename.lpstrFile, x_OpenFilename.nMaxFile)
        Else
            'PURPOSE: The CANCEL button was pressed
            MsgBox "Cancel"
        End If
    End Sub
    

    Edit: Das passiert auch nicht auf allen Systemen. Wir haben einen Windows Server 2012 da geht's, und einen da geht's nicht. Gleiche Version des Produktes, gleiche Konfiguration. Comdlg32.dll hat allerdings auf dem funktionierenden eine neuere Version.

    Auch bei mir lokal geht's im Beispiel, aber nicht im Produkt.



  • Bitte ein Bit schrieb:

    im eigentlichen Sinne kein String sondern ein Stringool. Also folgendes "FilterString\0FilterString\0\0"!!!

    Soweit ich weiss werden die Dinger z.T. als "string list" bezeichnet oder auch einfach als "double-null terminated string".



  • schlaflos schrieb:

    .lpstrFile = String(x_MaxBuffer, 0)
            .nMaxFile = x_MaxBuffer - 1
            .lpstrFileTitle = .lpstrFile
            .nMaxFileTitle = x_MaxBuffer - 1
    

    Ist das .lpstrFileTitle = .lpstrFile wirklich ne gute Idee? Also ist es OK da den selben Puffer zu übergeben?
    Und bist du sicher dass String in VB als Output-Parameter verwendet werden kann? Also ob das .lpstrFile = String(x_MaxBuffer, 0) überhaupt wie gewünscht funktioniert?



  • hustbaer schrieb:

    Bitte ein Bit schrieb:

    im eigentlichen Sinne kein String sondern ein Stringool. Also folgendes "FilterString\0FilterString\0\0"!!!

    Soweit ich weiss werden die Dinger z.T. als "string list" bezeichnet oder auch einfach als "double-null terminated string".

    Der ist ja doppelt null terminiert. Oder sehe ich da was falsch?

    .lpstrFilter = "Text Files" & vbNullChar & "*.txt" & vbNullChar & "All Files" & vbNullChar & "*.*" [b]& vbNullChar & vbNullChar[/b]
    

    hustbaer schrieb:

    schlaflos schrieb:

    .lpstrFile = String(x_MaxBuffer, 0)
            .nMaxFile = x_MaxBuffer - 1
            .lpstrFileTitle = .lpstrFile
            .nMaxFileTitle = x_MaxBuffer - 1
    

    Ist das .lpstrFileTitle = .lpstrFile wirklich ne gute Idee? Also ist es OK da den selben Puffer zu übergeben?
    Und bist du sicher dass String in VB als Output-Parameter verwendet werden kann? Also ob das .lpstrFile = String(x_MaxBuffer, 0) überhaupt wie gewünscht funktioniert?

    Was würde denn dagegen sprechen? Kenne mich mit der WinAPI fast gar nicht aus. Das ist ein Relikt aus alter Zeit und wir nicht so oft genutzt.

    Function String(Number As Long, Character)
        Mitglied von VBA.Strings
        Gibt eine Zeichenfolge einer bestimmten Länge mit einem sich wiederholenden Zeichen zurück
    

    Das ist die genutzte String-Funktion. Und die funktioniert auch.

    Das Problem ist, wir nutzen ja noch dutzende von 3rd Party DLLs die Sachen neu zeichnen, wir liefern selbst Shell-Extensions aus. Wenn die Korrupt sind kann es laut MS zu solchen Fehlern kommen.

    Andererseits geht es ja ab und zu. Die sch*** WinAPI macht nur ärger ^^

    EDIT: Passt irgendwie 😃
    http://thecodinglove.com/post/110528150088/when-im-asked-to-work-on-a-vb6-project



  • Hmm, eine VB Definition eines C Structs. Da bekomme ich leicht Bauchschmerzen.

    Probiere doch mal bitte folgendes. Generiere ein kleines C Projekt, was eine Funktion

    BOOL MyGetOpenFileName(const char* Title, char* FileName, size_t FileNameCount)
    

    mittles OPENFILENAME und GetOpenFileName() definiert und aufruft. Hierbei kannst du mal den cbSize Parameter in dem C und VB Projekt miteinander vergleichen. Sollte alles gut funktionieren, kannst du ein DLL Projekt anlegen, welche genau diese Funktion exportiert, s.d. du die Funktion aus Excel heraus aufrufen kannst.

    Declare Function MyGetOpenFileName Lib "Test.dll" (ByRef Title As String, ByRef FileName As String, FileNameCount as Long) As Long
    

    Hintergrund:

    Bei Aufrufen von DLL aus Excel hast du das Problem, wie die einzelnen Datentypen, Structs von der DLL aus nach VB gemappt werden. Hinter Datentypen wie std::string, String stecken oft eine etwas komplexere Struktur als einen Zeiger auf Zeichen. Wenn du nun beispielsweise annimmst dass VB String = std::string = char* auf elementarer Ebene ist, so kann die interne Struktur überschrieben werden und es knallt.

    Siehe auch:
    http://vb.mvps.org/tips/vb5dll.asp

    Der cbSize Parameter des OPENFILENAME Structs ist aber auch nicht ohne. Unter C wird dieser Parameter immer mit der sizeof(OPENFILENAME), der Größe des Structs, initialisiert. Solche Structs sind aber nicht starr. Oftmals erweitern neuere Windows Version diese, s.d. diese größer wird. Offenbar wird cbSize dazu benutzt Versionen zu unterscheiden. Und nun definiert man sich in VB ein Struct wie OPENFILENAME. Doch anstatt dass diese 32 Byte (nur mal um eine Zahl zu sagen) belegt, belegt diese 38 Byte s.d. Windows von einer anderen Version ausgeht und deswegen die letzten Bytes des Structs missinterpretiert.

    Deswegen die C Dll. So verhindert man die Nutzung von komplexeren C++ Datentypen, und man kapselt die OPENFILENAME Struktur und einigermaßen die Übergabeparameter.


Anmelden zum Antworten