ich kämpfe mit einer "AccessViolationException" ("Es wurde versucht, geschützten Speicher zu lesen oder zu schreiben...") in meiner C#-Anwendung, die einen String aus einem Array aus einer DLL abrufen soll. Das string-Array wird von einer weiteren Anwendung gefüttert. Innerhalb jener Anwendung erfolgt der Zugriff auf das Array ohne Probleme (schreiben und abrufen).
Die DLL händelt auch noch int/double-Werte, die problemlos in beiden Anwendungen geschrieben und abgerufen werden. Nur string macht mir die Sorgen:
Wie kann ich den Zugriff auf diesen "geschützten Speicher" in C# realisieren? Vermutlich marshallen? Bin hauptsächlich in C# "zu hause". Es fällt mir schwer, in C++ zu arbeiten....
Auch eine Deklaration z.B. mit const char* (statt LPSTR) lässt die Fehlermeldung beim C#-Zugriff aufkommen, wobei die zweite Anwendung auch hiermit funktioniert und das const char* Array beschreibt und ausliest.
vielen Dank! Das habe ich implementiert.
@M.L. ja, das kann auch per string Typ laufen, siehe unten const char*...
@spook, Th69: Die Deklaration im C#-Import sieht jetzt so aus:
Die Fehlermeldung ist jetzt folgende (durch return: MarshalAs bedingt. CharSet-Angabe hat hierbei keine Änderung bewirkt):
Fehler
Message = "\"return value\" kann nicht gemarshallt werden: Ungültige verwaltete/nicht verwaltete Typenkombination (Zeichenfolgenparameter müssen mit LPStr, LPWStr, LPTStr, BStr, TBStr, VBByRefStr oder AnsiBStr kombiniert werden).."
Die eine Anwendung kann string und LPSTR verarbeiten. Per string hatte ich in der DLL das Array als const char* Typ deklariert, was in dieser Anwendung wie auch LPSTR funktioniert.
Wäre der C#-Abruf per const char* einfacher zu händeln? (in der C#-Anwendung soll nur string-Abruf funktionieren, das Schreiben des Strings übernimmt ausschließlich die andere Anwendung).
System.AccessViolationException
HResult=0x80004003
Nachricht = Es wurde versucht, im geschützten Speicher zu lesen oder zu schreiben. Dies ist häufig ein Hinweis darauf, dass anderer Speicher beschädigt ist.
Quelle = mscorlib
Stapelüberwachung:
at System.String..ctor(SByte* value)
at System.StubHelpers.CSTRMarshaler.ConvertToManaged(IntPtr cstr)
...
Vergleiche noch mal genau deinen und meinen Code...
Edit:
Ist bei GetStr(i) auch ein Wert vorhanden oder ist dieser null?
Und kommt der Fehler auch bei GetStr(-1) oder nur, wenn auf das String-Array zugegriffen wird?
Zitat von oskar27
das Schreiben des Strings übernimmt ausschließlich die andere Anwendung
Was genau meinst du damit? Hast du eine andere Anwendung, welche auch diese DLL nutzt? Dann hat aber jede Anwendung ihre eigenen Daten.
Dieser Beitrag wurde 2 mal editiert, zum letzten Mal von Th69 am .
Ja sorry, da hatte ich LPStr auch getestet, oben falsch gepostet. Die Fehlermeldung ist die bekannte:
Fehler
System.AccessViolationException
HResult=0x80004003
Nachricht = Es wurde versucht, im geschützten Speicher zu lesen oder zu schreiben. Dies ist häufig ein Hinweis darauf, dass anderer Speicher beschädigt ist.
Quelle = mscorlib
Stapelüberwachung:
at System.String..ctor(SByte* value)
at System.StubHelpers.CSTRMarshaler.ConvertToManaged(IntPtr cstr)
....
Zitat
Hast du eine andere Anwendung, welche auch diese DLL nutzt? Dann hat aber jede Anwendung ihre eigenen Daten.
Ja. Diese Anwendung A schreibt hauptsächlich das Array. Die Anwendung B (C#-Anwendung) soll neben float auch strings auslesen. float-Werte klappt einwandfrei. Die Daten aus der DLL können von beiden Anwendungen geschrieben und gelesen werden. Nur string klappt noch nicht.
GetStr(i) - i soll der Array-Index sein. -1 funktioniert daher nicht. Auf jedem Index ist ein String gespeichert. Das Speichern funktioniert, weil die Anwendung A diese schreibt und auch selbst auslesen kann. Die Anwendung B soll jetzt auch auf diesen String-Speicher von Anwendung A zugreifen können und per Array-Index diese abrufen können.
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von oskar27 am .
Ich glaube, du verstehst nicht, wie DLLs in Anwendungen (Prozesse) eingebunden sind. Wenn mehrere Anwendungen auf dieselben Daten zugreifen wollen, dann benötigt man dafür Inter-Process-Communication (IPC) oder aber eine externe Datenhaltung (wie z.B. eine Datenbank). Oder ist das Array explizit als "shared data section" deklariert (s.a. Dynamic-link library: Memory management)?
Und lass dir mal den zurückgegebenen Wert ausgeben (bzw. debuggen).
Wenn aber auch der Zugriff auf das String-Literal "0" nicht funktioniert, dann liegt evtl. noch ein anderes Problem vor.
Da du ja auf "SysWOW64" zugreifst, ist denn dein C#-Projekt explizit als "x86" (bzw. 32bit) eingestellt?
im geschützten Speicher zu lesen oder zu schreiben
Funktioniert der ursprüngliche C/C++-Code wie angedacht ? Wobei ein C oder C++ - Compiler (zumindest zur Kompilierzeit) idR ein liberaleres Verhältnis zum Manipulieren von Speicherinhalten hat.
vielen Dank für diese Infos. Die C#-Anwendung ist auch eine 64Bit-Anwendung. Da die Pfade absolut eingestellt sind, macht es keinen Unterschied. Habe auch System32 getestet, mit dem gleichen Resultat. Aber stimmt, die 64-Bit-DLLs müssen ins System32
Den IntPtr Tipp habe ich implementiert. Entweder nicht korrekt implementiert, oder es fehlt noch etwas am "Glück". Die Übersetzung in string gibt einen leeren string zurück. Ohne Fehlermeldung. In der DLL ist der Rückgabetyp LPSTR. Das passt vermutlich nicht?
bei -1 gibt er den korrekten und vollständigen String aus, yeah. Supi! (habe in der DLL "Nulll" für diesen Fall eingetragen).
Bei ≥ 0 gibt er nur einen leeren String zurück. Der Zugriff auf die gespeicherten Strings im Array klappt also noch nicht. Die sind jedoch beschrieben. Die Anwendung A gibt die gespeicherten strings aus dem Array intern korrekt zurück.
Das gleiche Ergebnis (leerer String in C#-Anwendung) erhalte ich mit der Deklaration des strArrays und des Rückgabetyps der GetStr-Methode als const char*
Das ist ja schon mal ein kleiner Fortschritt. Und jetzt gibt es wohl auch keinen Speicherzugriffsfehler mehr?
Das was mich jedoch wundert, ist, daß du für 1 einen Leerstring ("") und nicht null zurückerhältst - denn
const char* strArr[MAXSYSTEM] = { "" };
initialisiert ja nur den ersten Wert (Index 0) mit dem Leerstring, alle weiteren Werten werden auf NULL (bzw. nullptr) gesetzt.
Hat die C++ Anwendung auch nur einen Leerstring geschrieben? Und mit welcher Speicheranforderungsfunktion wird denn der String (const char*) erzeugt?
Was passiert denn, wenn du testweise mal das Array so initialisierst:
dankeschön, ein neuer Schritt: mit der fixen Initialisierung rufen beide Anwendungen diese strings korrekt und vollständig ab. Ab nicht-initialisiertem Index wird null zurückgegeben. Klar soweit.
Jedoch: wenn Anwendung A jetzt einen der initialisierten Indizes neu beschreibt, ruft die C#-Anwendung wieder einen Leer-String ab. Anwendung A ruft den neu geschriebenen string korrekt ab. Unklar für mich.
Each process gets its own address space. It is very important that pointers are never stored in a variable contained in a shared data segment. A pointer might be perfectly valid in one application but not in another.
Du müsstest also so etwas wie
char strArr[MAXSYSTEM][MAXLENGTH]
verwenden (also ein fixes 2D-Array). Und dann von C++ aus mittels str(n)cpy o.ä. die Texte reinkopieren.
Dankeschön Th69 für deine Mühe. Es geht in kleinen Schritten voran
Mit folgendem Code bekomme ich in C#-Anwendung etwas zurück, das jedoch noch nicht dem gespeicherten string entspricht. Ist das jetzt der rechte Weg und nur noch eine "Kleinigkeit" oder generell weiterhin Murks. Denn es erfolgt mit const char*.
Die Anwendung A speichert wie zuvor den string (der aus 1 bis maximal 4 Buchstaben besteht, vergaß ich noch zu erwähnen) per const char* und beschreibt ein statisches string array.
Auf das string array soll jetzt die C#-Anwendung zugreifen. Das könnte doch klappen, von der Idee her zumindest. Wo liegts jetzt noch im Argen?
Die Ausgabe hätte "TEST" lauten müssen.
#pragma data_seg( ".IPC" )
float valArr[MAXSYSTEM][MAXCAT][MAXPARAM] = { 0.0 }; //ValArray
const char* strArrMC[MAXSYSTEM] = { "" }; //ArrayMC
char strArrCA[MAXSYSTEM][STRLENGTH] = { "" }; //ArrayCA
#pragma data_seg()
#pragma region SET STR ->>wird nur von Anwendung A aufgerufen
float __stdcall SetStr(int gvId, const char* gvStr)
{
if (gvId ≥ 0)
{
strArrMC[gvId] = &gvStr[0];
//FÜR CA
int size = std::strlen(&gvStr[0]);
memset(strArrCA[gvId], '\0', STRLENGTH);
std::memcpy(strArrCA[gvId], &gvStr[0], size);
//
return 1.0;
}
else
return -1111114.0;
}
#pragma endregion
#pragma region GET STR MC ->> wird nur von Anwendung A aufgerufen ->>fehlerfrei
const char* __stdcall GetStrMC(int gvId)
{
if (gvId ≥ 0)
{
return strArrMC[gvId];
}
else
return "Nulll";
}
#pragma endregion
#pragma region GET STR CA ->> wird nur von C#-Anwendung aufgerufen -> nicht korrekte Zeichen aktuell (siehe screen)
const char* __stdcall GetStrCA(int gvId)
{
if (gvId ≥ 0)
{
const char* myData = "";
for (int j = 0; j < 5; j++)
{
myData += strArrCA[gvId][j];
}
return myData;
}
else
return "Nulll";
}
#pragma endregion
Sorry, aber dein C++-Code ist fehlerhaft:
Zum einen kopierst du in SetStr das Nullendezeichen ('\0') nicht mit und zum anderen erzeugt GetStrCA undefiniertes Verhalten (UB), da myData nur einen Leerstring als String-Literal darstellt und kein beschreibbares String-Array.
Wie schon geschrieben, benutze zum Kopieren eines C-Strings strcpy (bzw. strncpy mit Angabe der max. Länge).
Und hast du bei STRLENGTH auch dieses Nullendezeichen beachtet, d.h. dieser Wert sollte mindestens 5 sein?
Warum benötigst du denn noch das Array strArrMC (und wenn, dann sollte es nicht im Shared Memory Bereich liegen!)?
Hier mein (aus dem Texteditor) geschriebener Code dazu:
vielen Dank für deine abschließende Korrektur - es läuft jetzt! Klasse!!!
Es ist auch nur das char Array nötig und beide Anwendungen können jetzt drauf zu greifen. So simpel wie einfach;-)))
Hab vielen Dank, Th69, für deine Tipps, die haben es auf den Weg gebracht und mir die Bäume aus der Sicht geräumt
Hier der abschließende und funktionierende DLL- und C#-Code: