Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Community
  • |
  • Diskussionsforum
AccessViolationException... String aus c++ Array in C#
oskar27
myCSharp.de - Member



Dabei seit:
Beiträge: 12
Herkunft: Solingen

Themenstarter:

AccessViolationException... String aus c++ Array in C#

beantworten | zitieren | melden

Hallo zusammen,

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:

DLL-Methode:

LPSTR strArr[1000] = { "" };

LPSTR __stdcall GetStr(int gvId)
{
	if (gvId ≥ 0)
	{
		return strArr[gvId];
	}
	else
		return "0";
}


C#-Aufruf:

 [DllImport("C:\\Windows\\SysWOW64\\Alert.dll", CallingConvention = CallingConvention.StdCall)]
        public static extern string GetStr(int gvId);

string str = GetStr(i);

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.

Besten Dank und Grüße
Oskar
private Nachricht | Beiträge des Benutzers
Spook
myCSharp.de - Member



Dabei seit:
Beiträge: 247
Herkunft: Esslingen a.N.

beantworten | zitieren | melden

Hallo Oskar,

sobald strings gemarshallt werden, würde ich den verwendeten CharSet im DllImport angeben.

Grüße
spooky
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 4.643

beantworten | zitieren | melden

Du könntest auch noch zusätzlich

[return: MarshalAs(UnmanagedType.LPStr)]
angeben.
private Nachricht | Beiträge des Benutzers
M.L.
myCSharp.de - Member



Dabei seit:
Beiträge: 342

beantworten | zitieren | melden

Die Verwendung von LPSTR deutet übrigens auf einen (sehr) alten C++-Code hin: difference-between-char-and-lpstr-in-windows
Goalkicker.com // DNC Magazine for .NET Developers // .NET Blogs zum Folgen
Software is like cathedrals: first we build them, then we pray ;-)
private Nachricht | Beiträge des Benutzers
oskar27
myCSharp.de - Member



Dabei seit:
Beiträge: 12
Herkunft: Solingen

Themenstarter:

beantworten | zitieren | melden

Hallo spook, Th69 und M.L.,

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:

[DllImport("C:\\Windows\\SysWOW64\\Alert.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.LPStruct)]
public static extern string GetStr(int system);

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).


DLL-Deklaration mit const char* Array:

const char* strArr[1000] = { "" };

const char* __stdcall GetStr(int gvId)
{
	if (gvId ≥ 0)
	{
		return strArr[gvId];
	}
	else
		return "0";
}

C#-Code:

[DllImport("C:\\Windows\\SysWOW64\\Alert.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern string GetStr(int gvId);

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)
...

Was könnte noch helfen? Vielen Dank!

Gruß
Oskar
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 4.643

beantworten | zitieren | melden

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 .
private Nachricht | Beiträge des Benutzers
oskar27
myCSharp.de - Member



Dabei seit:
Beiträge: 12
Herkunft: Solingen

Themenstarter:

beantworten | zitieren | melden

Hallo Th69,

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 .
private Nachricht | Beiträge des Benutzers
oskar27
myCSharp.de - Member



Dabei seit:
Beiträge: 12
Herkunft: Solingen

Themenstarter:

beantworten | zitieren | melden

Hi Th69 nochmal,

bei -1 sollte "0" zurück gegeben werden. Hierbei stürzt Anwendung B ab und schließt ohne Exception.
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 4.643

beantworten | zitieren | melden

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)?

Probiere mal


[DllImport("C:\\Windows\\SysWOW64\\Alert.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr GetStr(int system); // Rückgabetyp ändern
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?

Edit: Bzgl. DLLs + Shared Memory habe ich noch folgenden Artikel Windows Libraries I: Weitere Informationen zu DLL's ... (Code s. "Shared Memory") gefunden.
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Th69 am .
private Nachricht | Beiträge des Benutzers
M.L.
myCSharp.de - Member



Dabei seit:
Beiträge: 342

beantworten | zitieren | melden

Zitat
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.
Goalkicker.com // DNC Magazine for .NET Developers // .NET Blogs zum Folgen
Software is like cathedrals: first we build them, then we pray ;-)
private Nachricht | Beiträge des Benutzers
oskar27
myCSharp.de - Member



Dabei seit:
Beiträge: 12
Herkunft: Solingen

Themenstarter:

beantworten | zitieren | melden

Hallo M.L.,

bis auf die STR-Option funktioniert alles wie angedacht. Compiler ist VS2019.

Gruß
Oskar
private Nachricht | Beiträge des Benutzers
oskar27
myCSharp.de - Member



Dabei seit:
Beiträge: 12
Herkunft: Solingen

Themenstarter:

beantworten | zitieren | melden

Hi Th69,

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?

C#-Code:

[DllImport("C:\\Windows\\System32\\ChartAlert.DLL", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr GetStr(int system);

private string IntPtrToStr(int i)
        {
            IntPtr ptr = IntPtr.Zero;
            ptr = GVData.GetStr(i);
            string results = Marshal.PtrToStringAnsi(ptr);
            return results;
        }

Gruß
Oskar
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 4.643

beantworten | zitieren | melden

Ruf nun mal deine Methode IntPtrToStr mit den Werten -1, 0 und 1 auf?
Welchen Wert hat dann jeweils ptr sowie results?
private Nachricht | Beiträge des Benutzers
oskar27
myCSharp.de - Member



Dabei seit:
Beiträge: 12
Herkunft: Solingen

Themenstarter:

beantworten | zitieren | melden

Hey,

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*


#pragma data_seg( ".IPC" ) 

float valArr[MAXSYSTEM][MAXCAT][MAXPARAM] = { 0.0 };				//ValArray
const char* strArr[MAXSYSTEM] = { "" };							//StrArray

#pragma data_seg()

const char* __stdcall GetStr(int gvId)
{
	if (gvId ≥ 0)
	{
		return strArr[gvId];
	}
	else
		return "Nulll";
}

Gruß
Oskar
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von oskar27 am .
Attachments
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 4.643

beantworten | zitieren | melden

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:
const char* strArr[MAXSYSTEM] = { "0", "1", "2" };
?

Ansonsten könntest du auch mal probieren, 2 C++ Anwendungen dazu zu erstellen (eine schreibt, die andere liest).
private Nachricht | Beiträge des Benutzers
oskar27
myCSharp.de - Member



Dabei seit:
Beiträge: 12
Herkunft: Solingen

Themenstarter:

beantworten | zitieren | melden

Hey,

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.


#pragma data_seg( ".IPC" ) 

float valArr[MAXSYSTEM][MAXCAT][MAXPARAM] = { 0.0 };				//ValArray
const char* strArr[MAXSYSTEM] = { "test1", "test2", "test3"};		        //StrArray

#pragma data_seg()


Gruß
Oskar
Dieser Beitrag wurde 2 mal editiert, zum letzten Mal von oskar27 am .
Attachments
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 4.643

beantworten | zitieren | melden

Mir ist gerade aufgefallen, daß man mittels Zeiger keine Daten in verschiedenen Prozessen nutzen kann, s.a. How do I share data in my DLL with an application or with other DLLs?:
Zitat
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.
private Nachricht | Beiträge des Benutzers
oskar27
myCSharp.de - Member



Dabei seit:
Beiträge: 12
Herkunft: Solingen

Themenstarter:

beantworten | zitieren | melden

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

C#-Aufruf an sich wie zuvor:

[DllImport("C:\\Windows\\SysWOW64\\Alert.DLL", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
 public static extern IntPtr GetStrCA(int system);

private string IntPtrToStr(int i)
        {
            IntPtr ptr = IntPtr.Zero;
            ptr = GVData.GetStrCA(0);
            string results = Marshal.PtrToStringAnsi(ptr);            
            return results;
        }
Dieser Beitrag wurde 3 mal editiert, zum letzten Mal von oskar27 am .
Attachments
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 4.643

beantworten | zitieren | melden

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:

bool __stdcall SetStr(int gvId, const char* gvStr)
{
	if (gvId ≥ 0)
	{
		strArrMC[gvId] = gvStr;

		//FÜR CA
		std::strcpy(strArrCA[gvId], gvStr);
		// bzw.
		// std::strncpy(strArrCA[gvId], gvStr, MAXELNGTH); gvStr[MAXLENGTH-1] = `\0';

		return true;
	}

	return false;
}

const char* __stdcall GetStrCA(int gvId)
{
	if (gvId ≥ 0)
	{
		return strArrCA[gvId];
	}
	else
		return "Null";
}
(gvStr ist dasselbe wie &gvStr[0], jedoch kürzer und üblicher so zu schreiben)

PS: Irgendwie erzeugt der Browser aus >= immer ein Zeichen - das mußt du dann wieder ändern.
private Nachricht | Beiträge des Benutzers
oskar27
myCSharp.de - Member



Dabei seit:
Beiträge: 12
Herkunft: Solingen

Themenstarter:

beantworten | zitieren | melden

Hey,

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:

DLL-Code:

#define MAXSYSTEM 1000
#define STRLENGTH 4

#pragma data_seg( ".IPC" ) 										
char strArr[MAXSYSTEM][STRLENGTH] = { "" };
#pragma data_seg()

#pragma region SET STR
float __stdcall SetStr(int gvId, const char* gvStr)
{
	if (gvId ≥ 0)
	{	
		std::strcpy(strArr[gvId], gvStr);

		return 1.0;
	}
	else
		return -1111114.0;
}
#pragma endregion

#pragma region GET STR
const char* __stdcall GetStr(int gvId)
{
	if (gvId ≥ 0)
        {
		return strArr[gvId];
	}
	else
		return "Null";
}
#pragma endregion


C#-Code:

[DllImport("C:\\Windows\\SysWOW64\\Alert.DLL", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr GetStr(int system);

private string IntPtrToStr(int i)
{
        IntPtr ptr = IntPtr.Zero;
        ptr = GVData.GetStr(0);
        string results = Marshal.PtrToStringAnsi(ptr);            
        return results;
}


Gruß
Oskar
Attachments
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 4.643

beantworten | zitieren | melden

Puh ;-)
Schön, daß es jetzt funktioniert und danke für die positive Rückmeldung.
private Nachricht | Beiträge des Benutzers