Laden...

[.NET 2.0] Zugriff auf C-Dll: Access Violation

Erstellt von skaface vor 18 Jahren Letzter Beitrag vor 18 Jahren 2.445 Views
skaface Themenstarter:in
245 Beiträge seit 2005
vor 18 Jahren
[.NET 2.0] Zugriff auf C-Dll: Access Violation

Ich greife über meinen c#-Code auf eine c-dll zu (eigetnlich nur 1 Methode dieser) und bekomme fast immer, wenn ich das Programm schließe folgende Fehlermeldung:

Wenn ich das debuggen von unmanaged code aktiviere bekomme ich folgende Exception:


First-chance exception at 0x09df30ab in Program.exe: 0xC0000005: Access violation writing location 0x0075a0c0.
Unhandled exception at 0x09df30ab in Program.exe: 0xC0000005: Access violation writing location 0x0075a0c0.

Nun zu meinen Funktionen. Die Funktion auf C-Seite ist eigetnlich recht simpel und ich weiß nicht recht, wo ich da Fehler suchen soll:


int DLLEXPORT MyLoadString(long id, LPTSTR str, int maxsize)
{
  if (str)
    return (LoadString(theApp.m_hInstance, id, str, maxsize));
  else
    return 0;
}

Auch die C#-Funktion ist nicht sehr aufwendig:


        [DllImport("\\lang\\eng32.dll", EntryPoint = "MyLoadString")]
        public static extern short MyLoadStringEn(int id, StringBuilder text, short maxsize);

		public String GetResString(int id)
		{
			short ret = 0;
                        StringBuilder text = new StringBuilder("x", 255);

			if (lang == "de") ret = MyLoadStringDe(id, text, 255);
			else if (lang == "en") ret = MyLoadStringEn(id, text, 255);

			if (ret == 0) return null;
			else return text.ToString();
		}

Bei der Gelegenheit hätte ich auch gleich noch eine andere Frage: Gibt es eine Möglichkeit die Funktionen der dll dynamisch aufzurufen?
Hab schon länger gesucht, bin allerdings nicht fündig geworden.

danke, mfg

mike

ps.: Hab auch schon versucht IntPtr zu übergeben und diesen dann in einen String umzuwandeln, was prinzipiell funktioniert hat, die Exception beim schließen allerdings auch nicht verschwinden ließ!

F
10.010 Beiträge seit 2004
vor 18 Jahren

Sollte so funktionieren, es sei denn die "LoadString" Funktion macht etwas falsch.

Aber Du weist, das es Mehrsprachigkeit im FW eingebaut gibt.

skaface Themenstarter:in
245 Beiträge seit 2005
vor 18 Jahren

Sollte so funktionieren, tut es leider nicht. Gibt es irgend eine Möglichkeit, herauszufinden, wo das Speicherproblem auftritt?

danke, mfg
mike

ps.: Ich weiß, dass es im FW die Möglichkeiten für die Mehrsprachigkeit gibt, da Firmenintern allerdings schon seit Jahren mit der Lösung der beschriebenen DLL gearbeitet wird, soll diese auch weiterhin eingesetzt werden (da der Einsatz der FW-internen Methoden auch keine Vorteil brächte, ist mir das soweit auch ganz recht).

F
10.010 Beiträge seit 2004
vor 18 Jahren

Mach mal den Buffer grösser.

Kann ja sein, das ausgerechnet dein string da rüberbrät.
Nimm 16384, wenn es dann geht, ist es ein problem von LoadString.

skaface Themenstarter:in
245 Beiträge seit 2005
vor 18 Jahren

Hab das Ausprobiert mit größerem Buffer (16384) sowohl indem ich nur die StringBuilder-Capacity auf 16384 erhöht habe, als auch mit größerem StringBuilder und erhöhter maxsize (ebenfalls 16384) für den LoadString-Funktionsaufruf.

Der Fehler bleibt bei beiden Methoden bestehen.

Kann es sein, dass ich irgendwo Speicher selbst freigeben muss? Wüßte zwar nicht wo, könnte ja trotzdem sein...

Vielen Dank übrigens für die schnellen Antworten!

mfg
mike

F
10.010 Beiträge seit 2004
vor 18 Jahren

Nein, das macht eigentlich der Interop service.

Aber bist Du sicher, das int in deinem C-Programm 16 Bit hat?
Bei allen modernen Systemen ist da ein int auch 32Bit.

Dann wäre es schon verständlicher.


[DllImport("\lang\eng32.dll", EntryPoint = "MyLoadString")]
        public static extern int MyLoadStringEn(int id, StringBuilder text, int maxsize);

skaface Themenstarter:in
245 Beiträge seit 2005
vor 18 Jahren

Am Anfang hatte ich das Problem, dass ich bei die Parameter 1:1 übernommen habe dh. die id als long und maxsize sowie den Rückgabewert als int, was nicht funktioniert hat.

Nachdem ich die Parameter auf short für int bzw. int für long umgestellt habe hat das soweit funktioniert (abgesehen von der Fehlermeldung beim schließen). Das Problem dabei lag beim id-parameter, der in der dll als long deklariert ist und hatte eigentlich mit den int-Werte nichts zu tun, ich hab aber vergessen, diese in c# wieder als int zu deklarieren.

Hab das jetzt wieder umgestellt (also sowohl return-Parametert, als auch maxsize als int, wie in deinem Code-Ausschnitt).

Ändert leider auch nichts...

S
8.746 Beiträge seit 2005
vor 18 Jahren

Ich sehe zwei mögliche Ursachen:

  1. TCHAR kann entweder Unicode oder Ansi sein. Wenn das auf beiden Seiten nicht übereinstimmt wird die maxSize Probleme bereiten.
  2. Das Marshalling in Rückrichtung geht nur bei fixer Länge oder Nullterminierung. LoadString macht aber keine Nullterminierung:

If the function succeeds, the return value is the number of TCHARs copied into the buffer, not including the null-terminating character, or zero if the string resource does not exist.

Ich würde also wirklich den StringBuilder gegen IntPtr austauschen und per Hand den Buffern allokieren / String umwandeln.

skaface Themenstarter:in
245 Beiträge seit 2005
vor 18 Jahren

Gesagt getan...

Stimmt der Code dann folgerndermaßen:


        [DllImport("\\lang\\eng32.dll", EntryPoint = "MyLoadString")]
        public static extern int MyLoadStringEn(int id, IntPtr iPtr, int maxsize);

		public String GetResString(int id)
		{
			int ret = 0;
            //StringBuilder text = new StringBuilder("x", 255);
            String text = "x";
            IntPtr iPtr = Marshal.StringToHGlobalAnsi(text);

            if (lang == "de") ret = MyLoadStringDe(id, iPtr, 255);
            else if (lang == "en") ret = MyLoadStringEn(id, iPtr, 255);

            text = Marshal.PtrToStringAnsi(iPtr);
            Marshal.FreeHGlobal(iPtr);

			if (ret == 0) return null;
			else return text;
		}

Funktioniert auf jeden Fall so auch noch nicht. Selbes Problem.

danke, mfg

mike

S
8.746 Beiträge seit 2005
vor 18 Jahren

Du reservierst einen 1-Zeichen großen Buffer und erlaubst LoadString() an diese Stelle 255 Zeichen zu laden....

Mach doch mal einen 1k Buffer und gib 255 für MaxSize an. So kannst du sicher sein, dass zumindest an der Stelle kein Buffer-Overflow auftritt.

Und was wäre auch gut:

text = Marshal.PtrToStringAnsi(iPtr,ret);

Deine Version dürfte wieder Probleme mit der fehlenden Nullterminierung haben.

Geht aber nur, wenn TSTR wirklich ANSI ist. Ansonsten:

text = Marshal.PtrToStringUni(iPtr,ret);
skaface Themenstarter:in
245 Beiträge seit 2005
vor 18 Jahren

Ok, die Buffer-Allokierung sieht dann folgerndermaßen aus:


String text = new string('x', 1024);
IntPtr iPtr = Marshal.StringToHGlobalAnsi(text);

if (lang == "de") ret = MyLoadStringDe(id, iPtr, 255);

Richtig?

Am Problem ändert sich nichts.

Übrigens hab ich in der DLL gerade folgendes entdeckt:


#ifdef UNICODE
#define LoadString  LoadStringW
#else
#define LoadString  LoadStringA
#endif // !UNICODE

Wobei "#define LoadString LoadStringW" grau hinterlegt ist -> also ansi.

Blöde Frage: Woher weiß VS zur Designzeit eigentlich, ob es sich um UNICODE oder ANSI handelt?

S
8.746 Beiträge seit 2005
vor 18 Jahren

Gar nicht. Das muss passen. Du musst wissen, die die C-DLL gebaut ist. Du hast du möglichkeit via CharSet das Marshalling zu beeinflussen.

Frage: Wie ist eigentlich die Aufrufkonvention? cdecl oder stdcall?

Wo genau knallt es jetzt eigentlichlich? Noch in der Funktion oder beim Zurückmarshalln des Strings?

S
8.746 Beiträge seit 2005
vor 18 Jahren

Mir ist noch ein Fehler aufgefallen. In der C-DLL ist der Rückgabewert int (32 Bit), in der DLLImport-Deklaration aber short (16 Bit).

skaface Themenstarter:in
245 Beiträge seit 2005
vor 18 Jahren

Den Rückgabewert in C# hab ich schon auf int umgestellt (weiter oben bereits erwähnt).

Die Aufrufkonvention lege ich nicht fest, ist also Standardmäßig stdcall, hab allerdings auch schon cdecl probiert (ändert nichts).

Eigenartigerweise knallt es beim aufrufen der Funktion und auch danach gar nicht. Die Fehlermeldung kommt erst, wenn ich das Programm beende.

Die Funktion wird aber relativ oft aufgerufen (beim Starten des Programms, da ich alle Texte im Hauptform anpasse und während der Ausführung jedesmal, wenn ich einen Dialog öffne oder ich wo anders einen Text benötige (also anzeigen einer MessageBox usw.)).

Werd jetzt mal Mittag machen und hoffen, dass sich das Problem von alleine löst 😉

Also Mahlzeit, mfg
mike

S
8.746 Beiträge seit 2005
vor 18 Jahren

Arbeitest du mit DLLMain?

skaface Themenstarter:in
245 Beiträge seit 2005
vor 18 Jahren

DLLMain ist vorhanden aber komplett auskommentiert ...

Wie bereits erwähnt ist die DLL nicht von mir selbst und ich muss auch gestehen, dass ich noch nie selbst eine c-dll geschrieben habe, mir dahingehend also ein wenig der Einblick fehlt.

skaface Themenstarter:in
245 Beiträge seit 2005
vor 18 Jahren

Hab gerade wieder ein wenig getestet und hab dabei was interessantes entdeckt:

Ich hab mal versucht das ganze ohne die DLL-Funktions-Aufrufe auszuführen (aus reiner Verzweiflung um sicherzugehen, dass der Fehler auch wirklich von den DLL-Aufrufen kommt).

Und siehe da: Die Fehlermeldung erscheint trotzdem...

Also liegt es anscheinend nicht an der DLL, da ich aber auch noch eine 2. DLL benutze kann sich natürlich auch dort irgendwo ein Fehler eingeschlichen habe, was alledings relativ schwer zu evaluieren wird, da es sich dabei nicht um 1 benutzte Funktion handelt sondern gleich um ca. 15 - 20 😭

Muss der Fehler überhaupt von einem DLL-Aufruf stammen? Welche Ursachen könnte er sonst noch haben? Weiß gerade überhaupt nicht, wo ich zu suchen beginnen soll, vielleicht hat ja noch wer gute Ratschläge für mich!

Danke für die zahlreiche Hilfe bis jetzt, tut mir leid, dass ich eure Zeit mit der Suche nach dem Problem an der falschen Stelle strapaziert habe!

mfg
mike

ps.: Was ich glaube ich noch nicht erwähnt habe: Die Fehlermeldung erscheint nicht immer. Kann leider nicht sagen, in welchen Intervallen oder unter welchen Bedingungen aber ca. jedes 4. - 5. mal ausführen erscheint der Fehler nicht ...

S
8.746 Beiträge seit 2005
vor 18 Jahren

Da können wir ja lange suchen.. 😉

Eine unmanaged DLL wird beim allerersten Funktionsaufruf geladen. Wenn du keinen Aufruf machst, und der Fehler trotzdem kommt, liegt es sicher nicht an dieser DLL.

Der Fehler tritt klar im unmanaged Code auf, meiner Erfahrung nach würde auch nicht auf ein Wrapper-Problem tippen (das ergibt meist .NET-Exceptions).

skaface Themenstarter:in
245 Beiträge seit 2005
vor 18 Jahren

Ok, danke.

Bin schon wieder weiter. Ich weiß mittlerweile dass das Problem an der Capi.dll liegt, die ich auch benutze.

Wenn ich diese nicht mit Init() aufrufe ist das Problem verschwunden. Was mich verwundert ist dass ich dieses Init() bereits seit 1 - 2 Monaten in meinem Programm verwende (wird bei jedem Programmstart ausgeführt), die Fehlermeldung beim schließen aber erst gekommen ist, als ich die lang.dll eingebunden habe (weshalb ich auch gar nicht daran gedacht habe, dass es an was anderem liegen kann).

Nun zur Capi.dll:

Generell benutze ich 3 Funktionen der Capi:
Init(), GetStatus(char *status, int maxlen) und Dial(char *number, char *caller)

Zu testzwecken hab ich allerdings den Aufruf nur auf Init() beschränkt, was schon im Fehler resultierte (ohne init() kein Fehler).

Die c-Deklaration von Init ist folgende:


DLL_FUNCTION int __stdcall CAPI_Init();

Die c#-Deklaration sieht dementsprechend so aus:


[DllImport("REPPIDDLL.dll")]
public static extern int CAPI_Init();

und der code:


int ret = CAPI_INIT();

Ich muss vielleicht dazusagen, dass ich keine Capi installiert habe, da wir keine isdn-verbindung haben, was aber bis jetzt kein Problem war, da mir init(), sowie die anderen Funktionen einfach eine Fehlermeldung zurückgeliefert hat (aber eben ohne diese Exception beim schließen).

Jemand eine Idee? Werde mal die Version usw. checken ...

mfg

mike

skaface Themenstarter:in
245 Beiträge seit 2005
vor 18 Jahren

Problem gelöst!

Jetzt muss ich mich wirklich bei euch entschuldigen, das Problem war eigentlich nur eine kleine schlampigkeit (wie so oft...).

Was ich übersehen habe ist, dass es in der Capi.dll eine free() funktion gibt, die ich jetzt beim schließen des Programms aufrufe -> keine Fehlermeldung...

Ich begreife zwar noch immer nicht, warum das Problem erst nach 1 - 2 Monaten arbeit damit aufgetreten ist, aber das ist mir jetzt eigentlich auch relativ egal.

Also, herzlichen dank noch mal an alle, die sich beteiligt haben, tut mir wirklich leid, euch so unnötig die Zeit gestohlen zu haben schäm

mfg

mike

F
10.010 Beiträge seit 2004
vor 18 Jahren

Das ist wieder mal ein grund, warum ich immer alle externen DLL's in
eine eigene Klasse kapsele, die ein Disposable Pattern implementiert.

Und ich habe zur Erleichterung eine DisposableCollections ,
das beim eigenen Dispose() alle objekte der Collection disposed().

Ist hilfreich, wenn man mal mit einigen Disposable Objekten arbeitet.


using( DisposableCollection DispCol = new DisposableCollection())
{
  OpenFileDialog ofd = new OpenFileDialog();
  DispCol.AddDispose(ofd);

  ofd.RestoreDirectory=true;
  ofd.Filter="Text-Datei (*.txt)|*.txt"
  if( ofd.ShowDialog()==DialogResult.OK )
  {
    SaveFileDialog sfd = new SaveFileDialog();
    DispCol.AddDispose(sfd);
    sfd.RestoreDirectory=true;
    sfd.Filter="Text-Datei (*.txt)|*.txt"
    if( sfd.ShowDialog()==DialogResult.OK )
    {
      StreamReader sr = new StreamReader( ofd.FileName );
      DispCol.AddDispose(sr);
      StreamReader sw = new StreamWriter( sfd.FileName);
      DispCol.AddDispose(sw);
      // DoSomething
    }
  } 
}

Jetzt wird automatisch alles geschlossen und disposed.
AddDispose nimmt ein IDisposable entgegen, dadurch kann man auch gleich testen,
welche objekte das implementieren, DU wirst überrascht sein,
welche das alle sind.
Dadurch habe ich sowohl den Speicherverbrauch bei langlaufenden
Anwendungen, alsauch die Performance besser im griff.