Zugriffsverletzung
-
Hallo Leute, ich bin nach längerer Java-Aktivität wieder in C++ unterwegs und gleich auf einen Fehler gestoßen, bei dem ich nicht wirklich weiter komme. Eins vorweg, es geht mir nicht um einen anderen Weg, sondern eigentlich maßgeblich darum, zu verstehen, was ich falsch mache. Ich bin nämlich "per Zufall" auf den Fehler gestoßen und wenn ich die Hintergründe nicht verstehe, wird er mir früher oder später bestimmt wieder passieren.
Ich arbeite mit der Starterversion von C++ Builder Berlin 10.1 und es geht darum, dass ich Daten aus einem Lesegerät verarbeiten muss. Die DLL habe ich eingebunden und das funktioniert auch prinzipiell, nur was seltsam ist: Ich speichere den Rückgabewert der jeweiligen Funktionen in einer Variable "iReturn". Ist der Aufruf fehlerfrei muss der Wert 0 sein. Die Daten selbst werden in einem Array vom Typ "unsigned char" gespeichert. Da ich das nicht ändern kann, habe ich in den Projektoptionen die Einstellung:
_tchar entspricht wchar_t
auf
_tchar entspricht char
geändert.
Hier ist die komplette Klasse (ich habe auch die Deklarationen aus dem Header hier eingefügt, um da Ganze komplett im Überblick zu haben.
Was nun passiert: Wenn ich das Formular öffne, wird die Adresse des Gerätes korrekt eingelesen und auch im TEdit-Feld dargestellt. Der Rückgabewert ist "0". Klicke ich auf den "Buzzer"-Button ertönt das Signal und auch hier ist der Rückgabewert 0. Beim Klicken auf den LED-Button erhalte ich eine Fehlermeldung, dass eine Zugriffsverletzung aufgetreten ist. Obwohl beide Methoden praktisch identisch sind, bis auf die folgende Ausgabe des Rückgabewertes. Kommentiere ich diese Ausgabe aus, erscheint die Fehlermeldung, lasse ich sie drin, funktioniert alles.
Und hier ist der Code:
//--------------------------------------------------------------------------- #include <fmx.h> #include <string> #pragma hdrstop #include "Config.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.fmx" TFRMConfig *FRMConfig; //--------------------------------------------------------------------------- HINSTANCE dllLoad; char chData[8]; const char* library; int iReturn; typedef int (*GetSerNum_Func)(unsigned char *buffer); typedef int (*SetSerNum_Func)(unsigned char *newValue, unsigned char *buffer); typedef int (*ControlLED_Func) (int freq, int duration, unsigned char *buffer); typedef int (*ControlBuzzer_Func)(int freq, int duration, unsigned char *buffer); GetSerNum_Func getSerNum; SetSerNum_Func setSerNum; ControlLED_Func controlLED; ControlBuzzer_Func controlBuzzer; __fastcall TFRMConfig::TFRMConfig(TComponent* Owner) : TForm(Owner) { iReturn = 0; library = "function.dll"; dllLoad = LoadLibrary(library); if (dllLoad) { getSerNum = (GetSerNum_Func)GetProcAddress(dllLoad, "GetSerNum"); setSerNum = (SetSerNum_Func)GetProcAddress(dllLoad, "SetSerNum"); controlLED = (ControlLED_Func)GetProcAddress(dllLoad, "ControlLED"); controlBuzzer = (ControlBuzzer_Func)GetProcAddress(dllLoad, "ControlBuzzer"); } else { edAddress->Text = "FEHLER!"; } } //--------------------------------------------------------------------------- void __fastcall TFRMConfig::FormShow(TObject *Sender) { AnsiString strData; iReturn = getSerNum(chData); ShowMessage("Wert ist = " + IntToStr(iReturn)); //Die Konvertierung von unsigned char in int ist notwendig, weil der Variableninhalt //sonst als Zeichen interpretiert würde strData = IntToHex((int) chData[0], 2); edAddress->Text = strData; } void __fastcall TFRMConfig::btnSetAddressClick(TObject *Sender) { int iAddress = 0; iAddress = spBAddress->Value; } //--------------------------------------------------------------------------- void __fastcall TFRMConfig::btnBuzzerClick(TObject *Sender) { iReturn = controlBuzzer(16, 1, chData); ShowMessage("Wert =" + IntToStr(iReturn)); } //--------------------------------------------------------------------------- void __fastcall TFRMConfig::btnLEDClick(TObject *Sender) { iReturn = controlLED(16, 3, chData); //ShowMessage("Wert =" + IntToStr(iReturn)); } //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- void __fastcall TFRMConfig::FormClose(TObject *Sender, TCloseAction &Action) { FreeLibrary(dllLoad); } //---------------------------------------------------------------------------
Ich bin wirklich für jeden Hinweis dankbar und entschuldige mich schon einmal im Voraus für den bestimmt katastrophalen Code , aber ich bin eben dabei, mich langsam wieder in die Materie einzuarbeiten.
Viele Grüße!
-
AnsiString-Index faengt bei 1 an und ich nehme an, deine DLL-Funktionen schreiben da rein obwohl kein Speicher reserviert ist. Ich wuerde mal ganz auf die dummen AnsiStrings verzichten. Ein Unding, dass das so ueberhaupt kompiliert.
-
Vielen Dank für Deine Antwort. Den AnsiString verwende ich eigentlich nur, um im Konstruktor des Forms die Adresse des Lesegerätes hexadzimal anzuzeigen. Bei den Methoden, bei denen der Fehler ausgelöst wird, kommt der String nicht zum Einsatz. Die DLL-Funktionen schreiben in das
unsigned char chData[8];
Im abgedruckten Quelltext steht noch:
char chData[8];
das war nur ein Test. Also als Fehlerursache dürfte der AnsiString nicht in Frage kommen. Es passiert übrigens nichts außerhalb dieser Klasse.
-
Inzwischen habe ich noch etwas festgestellt:
Rufe ich das Formular ein erstes Mal auf, wird es fehlerfrei geöffnet und der Return-Code angezeigt.
Schließe ich das Formular dann und rufe es erneut auf, kommt es auch hier zu einer Zugriffsverletzung.
-
Du machst FreeLibrary in FormClose aber LoadLibrary im Konstruktor. Ein zweites mal Show() wird zumindest mit dem selben Objekt nicht funktionieren.
Ansonsten ist dein Absturz vermutlich das Resultat von einem vorherigen Speicherüberlauf (an ganz anderer Stelle).
Sicher das chData[8] reicht? Ist das auch null-Terminiert?Du könntest mal alles entfernen und nach und nach wieder aktivieren um den Fehler einzukreisen.
-
Hallo, da hatte ich mich unsauber ausgedrückt. Ich rufe das Formular von einem Startfenster aus auf. Beim ersten Mal funktioniert das einwandfrei. Dann schließe ich das Formular wieder. Die FormClose-Methode gibt also auch die DLL wieder frei.
Jetzt öffne ich das Formular erneut (die Bedingungen müssten ja eigentlich identisch sein wie beim ersten Öffnen). Jetzt erhalte ich aber ebenfalls einen Hinweis auf die Zugriffsverletzung.
Das mit dem unsigned-char-Buffer ist so eine Sache. Der wird von der DLL nicht zur Übergabe von Zeichen verwendet, sondern es werden Rohdaten damit übergeben. Deshalb muss ich auch diese Verrenkung vornehmen und den Inhalt zunächst in eine Integer wandeln, um sie dann mit IntToHex in einen hexadezimalen String zu erhalten. Die Adresse ist z. B. momentan "5", also ist chData[0] = 5 und über IntToHex((int)chData[0], 2) erhalten einen den String 05. Das klappt auch und wird so angezeigt.
Ich erkenne also momentan kein Problem mit der Länge von chData, weil ich ja praktisch nur das erste Byte nutze.
-
cyrano1960 schrieb:
Jetzt öffne ich das Formular erneut (die Bedingungen müssten ja eigentlich identisch sein wie beim ersten Öffnen). Jetzt erhalte ich aber ebenfalls einen Hinweis auf die Zugriffsverletzung.
ist die frage wie du das Fenster erneut "öffnest". Konstruktor/Destruktor != Show/Close. Der Konstruktor wird max. 1x aufgerufen, FormClose jedes mal wenn das Fenster geschlossen wird.
Ansonsten, wie gesagt, musst du das genauer eingrenzen. Ich würde mal als erstes eine simple Konsolenanwendung zum Testen erstellen anstatt auch noch mit dem VCL-Krempel zu kämpfen.
-
Da bin ich noch einmal und ich habe auch ein paar Dinge heraus gefunden:
@ProgrammiererImFahrstuhl (PIF): Du hast natürlich absolut recht mit Deiner Anmerkung bezüglich des erneuten Öffnens. Ich habe im Konstruktur die DLL eingebunden und bei FormClose gebe ich sie wieder frei. Ein erneuter Aufruf des Fensters muss dann natürlich Probleme bringen. Ich habe das Laden der DLL jetzt in die FormShow()-Methode gepackt und das Problem ist damit auch gelöst. Aber es ändert nichts an dem grundlegenden Fehler.
Ich habe mal auf den "VCL-Krempel" (obwohl ich ja FireMonkey nutze) verzichtet und zwar in der Form, dass ich das Ganze in QT5 realisiert habe und dort läuft alles ohne Probleme ab.
Den AnsiString kann ich als Problemquelle eigentlich auch ausschließen, weil der Fehler selbst dann auftritt, wenn ich ihn gar nicht nutze.
Als eigentliche Ursache habe ich den Rückgabewert der DLL-Funktionen identifiziert und hier kommt es zumindest für einen C++-Laien wie mich zu einer seltsamen Erscheinung:
1. Ich habe in obigem Code die Rückgabe-Variableja innerhalb der Klasse global deklariert:
char chData[8]; const char* library; int iReturn;
2. Wenn ich mir im Debugger den Wert für iReturn anzeigen lasse, so wird er nach dem Aufruf der DLL-Funktionen auch tatsächlich 0, was einer fehlerfreien Ausführung entspräche.
3. Jetzt habe ich das Ganze einmal mit lokalen Variablen ausprobiert und erhalte als Rückgabewert z. B. "1702720" was ja sehr nach einer Adresse riecht.
4. Das erklärt natürlich einiges und ich würde mal spekulieren:
- iReturn wird als Integer derklariert
- iReturn wird aber durch irgendetwas in einem Zeiger "gecastet".
- iReturn erhält den Wert 0 als Rückgabewert der DLL-Funktion und versucht sie in iReturn zu schreiben.
- da zu diesem Zeitpunkt iReturn aber als Zeiger angesehen wird und der Inhalt 0 ist, versucht die DLL-Funktion auf Adresse 0x00000000 zu schreiben und löst die Exception aus.Daraufhin habe ich mir die Aufrufkonventionen für die DLL angeschaut, aber es wird ganz eindeutig ein Integer und kein Pointer darauf zurückgegeben. Wenn ich aber davon ausgehe, dass die Doku zur DLL falsch ist, dann müsste es ja in QT5 auch zu einem Fehler führen.
Hat noch jemand Ideen, wo sich der Haken verstecken könnte??