C# Wie DLL importieren?

Registriert
Juli 2008
Beiträge
134
Hai!

Präfix ist wohl eher: VisualStudio, aber das wird nicht angeboten.

Ich hänge schon seit einiger Zeit an dem Problem, daß eine/mehrere DLLs zur Laufzeit offenbar nicht gefunden werden. Die Projektaufteilung kann weiter unten gefunden werden. Ich bekomme zur Laufzeit den Fehler
Code:
Eine Ausnahme (erste Chance) des Typs "System.DllNotFoundException" ist in CGrafik.dll aufgetreten.

Kann mir da jemand einen Hinweis geben, wo ich - noch - die Fehlerursache suchen könnte.

Die Aufrufhierarchie ist bis dahin folgende:
- Private Sub Test (VisualBasic-Testprogramm) ruft
-- public static void CGraphic.InitGraphicDLL (C# Klassenbibliothek) auf, die über eine Wrapperfunktion
--- PLplot_plsdev wiederum
---- die von einer DLL exportierte Routine plsdev aufruft.

Genau bei dem letzten Aufruf wird mir o.a. Ausnahme geworfen.

Ich habe sowohl für die VisualBasic- als auch für die C#-Anteile zwei parallele Verzeichnisse geschaffen. Die benötigten DLLs befinden sich im Suchpfad und zusätzlich habe ich sie nochmal in *alle* Debug/Release-Verzeichnisse sowohl des VisualBasic als auch des C#-Verzeichnisses kopiert.

In der C#-Klassenbibliothek werden die benötigten Routinen mittels eines Wrappers importiert:
Code:
[DllImport("plplot.dll")]
protected static extern void plsdev(string devname);
// Methode die es ermöglicht von C# aus auf die dll zuzugreifen
public static void PLplot_plsdev(string devname)
{
   plsdev(devname);
}

Ein "direktes" Einfügen der DLLs in das Projekt (über den Projektmappen-Explorer -> Verweis hinzufügen ...) ist nicht möglich, weil die externen DLLs nicht mit .NET (?) erstellt worden sind (Fehlermeldung dann: "Es konnte kein Verweis auf ... hinzugefügt werden. Stellen Sie sicher, dass auf die Datei zugegriffen werden kann und dass sie eine gültige Assembly oder COM-Komponente ist").

Die von der DLL exportierten Routinen habe ich mit dem DLL Export Viewer überprüft. Würden die aber nicht stimmen, so würde ich auch eine andere Fehlermeldung erwarten.

Projekt-Hintergrund:
Momentan ist der tatsächliche Inhalt des Projektes noch eine Spielwiese. Lediglich die folgende Aufteilung steht bereits:

Es wird ein VisualBasic-Program geben, welches eine Klassenbibliothek aufruft, die in C# (evtl. auch C++) geschrieben ist. Diese Klassenbibliothek wiederum verwendet externe Bibliotheken (Open Source DLLs).

Die Klassenbibliothek wird in C#/C++ geschrieben werden müssen, weil das dem Kunden so verkauft worden ist :( Das bereits existierende Test- bzw. Steuerprogramm ist in Visualbasic entwickelt worden, weshalb hier an der Sprache/Entwicklungsumgebung auch nicht zu rütteln ist.

Persönlicher Hintergrund:
Ich habe zwar jahrelange Erfahrung in der Programm-/Projektentwicklung, allerdings nur wenig Erfahrung in den diversen Sprößlingen der C-Familie und schon gar keine Erfahrung in der VisualStudio-Umgebung.

Gruß,
Thorsten
 
Sind diese DLL Datei precompiled? Also eine externe unmanaged Ressource?

Blicke noch nicht ganz durch...
In welcher Sprache wurde die DLL Library erstellt?
 
Klingt als wäre deine Assembly eine unmanaged Assembly, deswegen kannst du die dem Projekt nicht hinzufügen.
Sprich erst mal einen Wraper schreiben(bzw. den C# Wrapper nehmen) und den ins Projekt einbinden.
 
Probier mal, die ganzen Dlls nicht ins Debug/Release-Verzeichnis zu kopieren, sondern direkt in dein Projektverzeichnis, also da, wo die vb-Dateien liegen.
Wenn man ein Projekt aus Visual Studio startet (mit F5 oder Strg+F5) ist nämlich standardmäßig das Projektverzeichnis das aktuelle Verzeichnis.

Andererseits sollte das Programm aber auch die Dlls finden, wenn sie im gleichen Verzeichnis wie die Exe-Datei liegen: http://msdn.microsoft.com/en-us/library/ms684175(VS.85).aspx
(LoadLibrary wird von .NET benutzt, um die Dlls dynamisch nachzuladen).
Warum das bei dir trotzdem nicht geht, weiß ich leider nicht. ;-)
 
Hai!

TK-Shockwave schrieb:
Sind diese DLL Datei precompiled? Also eine externe unmanaged Ressource?

Blicke noch nicht ganz durch...
In welcher Sprache wurde die DLL Library erstellt?

Die auf unterster Ebene verwendeten DLLs sind in C geschrieben. Das sollte aber ja eigentlich egal sein, denn der Sinn von DLLs ist ja gerade, daß sie universell ansprechbar sind.

Was meinst Du mit "precompiled"? Diese sind extern - also außerhalb der aktuellen Projektumgebung, allerdings ebenfalls mit VisualStudio - erstellt worden.

Ich habe übrigens neulich mal schnell ein Testprogramm in Ada (allerdings nicht in dieser unübersichtlichen VisualStudio-Entwicklungsumgebung, sondern mit GNAT) geschrieben, welches die gleichen DLLs aufruft ... kein Problem. Alles läuft so, wie ich das erwarte.

Mike Lowrey schrieb:
Klingt als wäre deine Assembly eine unmanaged Assembly, deswegen kannst du die dem Projekt nicht hinzufügen.
Sprich erst mal einen Wraper schreiben(bzw. den C# Wrapper nehmen) und den ins Projekt einbinden.

Den Wrapper habe ich ja geschrieben. Oder was meinst Du damit?

schlzber schrieb:
Probier mal, die ganzen Dlls nicht ins Debug/Release-Verzeichnis zu kopieren, sondern direkt in dein Projektverzeichnis, also da, wo die vb-Dateien liegen.
Wenn man ein Projekt aus Visual Studio startet (mit F5 oder Strg+F5) ist nämlich standardmäßig das Projektverzeichnis das aktuelle Verzeichnis.

Andererseits sollte das Programm aber auch die Dlls finden, wenn sie im gleichen Verzeichnis wie die Exe-Datei liegen: http://msdn.microsoft.com/en-us/library/ms684175(VS.85).aspx
(LoadLibrary wird von .NET benutzt, um die Dlls dynamisch nachzuladen).
Warum das bei dir trotzdem nicht geht, weiß ich leider nicht. ;-)

Das ändert leider nichts. Ich erhalte die gleiche Fehlermeldung.

Gruß,
Thorsten
 
Kann es sein, dass der Name in der DllImport-Anweisung evtl falsch geschrieben ist?

Wenn nicht, kann es sein, dass die Dlls 32bit-Dlls sind, du aber ein 64bit-VB-Programm benutzt um sie zu laden? Das geht nicht. Du müßtest dann in den VB-Projekteinstellungen von "Any CPU" auf x86 stellen.
 
Hai!

schlzber schrieb:
Kann es sein, dass der Name in der DllImport-Anweisung evtl falsch geschrieben ist?

Nein.

Die Import-Anweisung lautet z.B.
Code:
[DllImport("plplot.dll")]
protected static extern void c_plend();
// Methode die es ermöglicht von C# aus auf die dll zuzugreifen
public static void PLplot_plend()
{
  c_plend();
}

Die Ausgabe des DLL Export Viewers liefert
Code:
c_plend	0x10001014	0x00001014	19 (0x13)	plplotd.dll	C:\Users\Thorsten\Documents\Arbeit\WIHM-Tech\EPAS\SurferErsatz\Grafik_Test\GrafikTest\plplotd.dll	Exported Function

In meinen Augen sieht das genauso aus wie es aussehen sollte. Der Compiler liefert keinen Fehler bei der Übersetzung der C#-Klassenbibliothek. Auch das Erstellen in VisualBasic liefert keinen Fehler. Nur zur Laufzeit wird die Routine dann nicht gefunden.

schlzber schrieb:
Wenn nicht, kann es sein, dass die Dlls 32bit-Dlls sind, du aber ein 64bit-VB-Programm benutzt um sie zu laden? Das geht nicht. Du müßtest dann in den VB-Projekteinstellungen von "Any CPU" auf x86 stellen.

Das sollte eigentlich nicht der Fall sein. Wie kann ich das verifizieren?

bei den VB-Projekteinstellungen finde ich weder "Any CPU" noch "x86". Wo muss ich das suchen?

gruß,
Thorsten
Ergänzung ()

Hai!

chu_ schrieb:
Code:
public class Foo

{

[DllImport("myunmanaged.dll", CharSet = CharSet.Ansi)]

private extern static int UnmanagedFunction(int type, int dest);

}

Late binding on native DLLs with C#

So sollte es gehen :)

Das habe ich doch so in meiner C#-Klassenbibliothek drin ... nur nicht als eigene Klasse Foo sondern direkt. Ich schneide hier mal die entsprechende Klasse aus:

Code:
namespace GrafikDLL
{
  public partial class CGraphic
  {
      // ... diverse Deklarationen
      public static bool mTrace = false;                      // TraceSchalter

      [DllImport("plplot.dll")]
      protected static extern void c_plsdev(string devname);
      // Methode die es ermöglicht von C# aus auf die dll zuzugreifen
      public static void PLplot_plsdev(string devname)
      {
          c_plsdev(devname);
      }

      // ... weitere Importe
      public static void InitGraphicDLL(bool Trace)
      {
          mTrace = Trace;
          if (mTrace)
          {
              // ... do trace output
          }

          // Set output device
          PLplot_plsdev("wxwidgets");
          // ... Weitere Initialisierungen
      }
  }
}

Den Link gucke ich mir gleich mal in Ruhe an.

Gruß,
Thorsten
 
ThorstenBehrens schrieb:
Hai!



Nein.

Die Import-Anweisung lautet z.B.
Code:
[DllImport("plplot.dll")]
protected static extern void c_plend();
// Methode die es ermöglicht von C# aus auf die dll zuzugreifen
public static void PLplot_plend()
{
  c_plend();
}

Die Ausgabe des DLL Export Viewers liefert
Code:
c_plend	0x10001014	0x00001014	19 (0x13)	plplotd.dll	C:\Users\Thorsten\Documents\Arbeit\WIHM-Tech\EPAS\SurferErsatz\Grafik_Test\GrafikTest\plplotd.dll	Exported Function

Nun, ich weiss nicht, in der DllImport-Anweisung steht "plplot.dll", aber in deine Ausgabe des Viewers heißt die Dll "plplotd.dll" (ein zusätzliches d im Namen). Check das doch mal.
 
Das "d" bedeutet es ist ein Debug Build.

Als Wrapper empfehle ich dir Swig - den nutzen sehr viele ein, auch wir in unseren Entwicklung als Wrapper für C/C++ zu C# und umgekehrt für Win CC OA.
 
ThorstenBehrens schrieb:
bei den VB-Projekteinstellungen finde ich weder "Any CPU" noch "x86". Wo muss ich das suchen?

1. Rechtsklick auf das Projekt->Eigenschaften
2. Registerseite Kompilieren
3. Button "Erweiterte Kompilierungsoptionen..."
4. Unter Ziel-CPU findest du die Architektur
 
Hai!

schlzber schrieb:
Nun, ich weiss nicht, in der DllImport-Anweisung steht "plplot.dll", aber in deine Ausgabe des Viewers heißt die Dll "plplotd.dll" (ein zusätzliches d im Namen). Check das doch mal.

Erst dachte ich "Das kann doch nicht sein, dass das ein so einfacher Fehler ist und ich den die ganze Zeit übersehen habe", als ich Dein Posting las. Manchmal sucht man ja "tagelang" nach solchen einfachen Dingen. Der name im DllImport stimmte in der Tat nicht, aber ...

iIh habe nun rein sicherheitshalber noch mal alles neu erstellt (diesmal nicht nur einfach mit dem "Neu erstellen" in den Projekteigenschaften), sondern indem ich alle VisualStudios geschlossen habe, dann in allen Debug/Release/obj-Unterverzeichnissen alles bis auf die externen DLLs gelöscht habe und schließlich in den neu geöffneten VisualStudios alles neu erstellt habe ... und ... siehe da! ... ich erhalte einen anderen Fehler

Code:
Ein Aufruf an die PInvoke-Funktion "CGrafik!GrafikDLL.CGraphic::c_plsdev" hat das Gleichgewicht des Stapels gestört. Wahrscheinlich stimmt die verwaltete PInvoke-Signatur nicht mit der nicht verwalteten Zielsignatur überein. Überprüfen Sie, ob die Aufrufkonvention und die Parameter der PInvoke-Signatur mit der nicht verwalteten Zielsignatur übereinstimmen.

Gut, bei dieser Routine wir nun gleich ein String als Parameter übergeben. Das ist ja, wenn ich mich an meine C-Kenntnisse zurückerinnere immer etwas problematisch.

Mir ist aber etwas anderes aufgefallen:
Die C#-Klassenbibliothek ließ sich problemlos neu erstellen. Im VisualBasic-Projekt hingegen mußte ich den Verweis auf die Klassenbibliothek ebenfalls neu erstellen. Ist das so, daß ich diesen Verweis immer neu erstellen muss, wenn ich die entsprechende Klassen-Bibliothek neu übersetze? Da ändert sich ja weder der Name noch der Ort an dem die zu finden ist.

Gruß,
Thorsten
 
TK-Shockwave schrieb:
Das "d" bedeutet es ist ein Debug Build.

Das ist mir schon klar, es ist aber irrelevant, wofür das "d" steht.
Die Laufzeit sucht nach einer Dll, die genauso heißt wie in der DllImport-Anweisung angegeben.
Das ist hier offensichtlich nicht der Fall, und ich wollte nur darauf hinweisen.

Außerdem sehe ich den Sinn eines zusätzlichen Wrapper-Frameworks nicht unbedingt. Ob der TE jetzt alle C-Funktionen per DllImport verfügbar macht oder per SWIG interface file, er muss sie so oder manuell in einer Textdatei auflisten. Da würde ich lieber mit der eingebauten Funktionalität gehen.

Prinzipiell sucht der TE ja auch kein neues Wrapperframework, so wie ich das verstanden habe, sondern hat sich für bereits für eines entschieden (P/Invoke) und hat jetzt nur ein konkretes Problem bei der Umsetzung dessen.
Ergänzung ()

Änder mal deine DllImport-Anweisung so, dass sie analog zu der hier aussieht:

[DllImport("plplot.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]

Falls dein C-Code Unicode-kompiliert ist, nimm CharSet.Unicode als letzten Parameter.
Das mit der Calling convention nehm ich einfach mal an, weil _cdecl die Standardaufrufkonvention für C-Projekte ist. Falls sie bei dir anders ist, kannst du auch dort die passende Option eintragen.
 
Hai!

schlzber schrieb:
1. Rechtsklick auf das Projekt->Eigenschaften
2. Registerseite Kompilieren
3. Button "Erweiterte Kompilierungsoptionen..."
4. Unter Ziel-CPU findest du die Architektur

Leider nicht:
Erweiterte_Compilereinstellungen.jpg


Gruß,
Thorsten
 
Huch, kann sein dass das nur in der Ultimate-Version verfügbar ist, sorry.

Aber da du ja bereits ein klein wenig Erfolg hattest, denke ich nicht, dass es noch eine Rolle spielt, weil andernfalls hätte die Funktion in der Dll gar nicht aufgerufen werden können. ;-)
Ergänzung ()

ThorstenBehrens schrieb:
Code:
Ein Aufruf an die PInvoke-Funktion "CGrafik!GrafikDLL.CGraphic::c_plsdev" hat das Gleichgewicht des Stapels gestört. Wahrscheinlich stimmt die verwaltete PInvoke-Signatur nicht mit der nicht verwalteten Zielsignatur überein. Überprüfen Sie, ob die Aufrufkonvention und die Parameter der PInvoke-Signatur mit der nicht verwalteten Zielsignatur übereinstimmen.

Bevor meine Ergänzung weiter oben untergeht:

Änder mal deine DllImport-Anweisung so, dass sie analog zu der hier aussieht:

[DllImport("plplot.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]

Falls dein C-Code Unicode-kompiliert ist, nimm CharSet.Unicode als letzten Parameter.
Das mit der Calling convention nehm ich einfach mal an, weil _cdecl die Standardaufrufkonvention für C-Projekte ist. Falls sie bei dir anders ist, kannst du auch dort die passende Option eintragen.
 
Hai!

Erst einmal vielen Dank für deine Hilfe, schlzber. Manchmal sehen mehr Augen doch mehr ;-)

schlzber schrieb:
Huch, kann sein dass das nur in der Ultimate-Version verfügbar ist, sorry.

Aber da du ja bereits ein klein wenig Erfolg hattest, denke ich nicht, dass es noch eine Rolle spielt, weil andernfalls hätte die Funktion in der Dll gar nicht aufgerufen werden können. ;-)

Eben - die Architektur (32 oder 64bit) sollte eigentlich auch stimmen. Ich habe hier in der Tat nur eine Enterprise-Edition installiert, kann also sein, dass man es da nicht einstellen kann.

schlzber schrieb:
Bevor meine Ergänzung weiter oben untergeht:

Änder mal deine DllImport-Anweisung so, dass sie analog zu der hier aussieht:

[DllImport("plplot.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]

Falls dein C-Code Unicode-kompiliert ist, nimm CharSet.Unicode als letzten Parameter.
Das mit der Calling convention nehm ich einfach mal an, weil _cdecl die Standardaufrufkonvention für C-Projekte ist. Falls sie bei dir anders ist, kannst du auch dort die passende Option eintragen.

Die ist nicht untergegangen, nur dauerte es eine Weile, bis ich das alles umgesetzt hatte. Damit (oder aber mit der Änderung, die ich vorgenommen habe - ein \0 an den übergebenen String angehängt) läuft jetzt auch erstmal alles fehlerfrei durch.

Jetzt habe ich nur noch eine Frage: Gibt es im VisualStudio kein "Clenaup", also einen Menüpunkt, der alle Dateien in den Verzeichnissen obj, Debug und Release löscht und sauber wieder aufsetzt?

Gruß,
Thorsten
 
Hai!

chu_ schrieb:
Erstellen -> Projektmappe bereinigen / neu erstellen sollte das eigentlich lösen

"Neu erstellen" erstellt aber dann ja wieder alles. Ich suche einen Punkt, der nur alles löscht. Dann kann ich sicher sein, daß da nichts altes rumspukt.

Gruß,
Thorsten
 
ThorstenBehrens schrieb:
Hai!



"Neu erstellen" erstellt aber dann ja wieder alles. Ich suche einen Punkt, der nur alles löscht. Dann kann ich sicher sein, daß da nichts altes rumspukt.

Gruß,
Thorsten

Das wäre das schon erwähnte Bereinigen. ;-)
 
Hai!

schlzber schrieb:
Das wäre das schon erwähnte Bereinigen. ;-)

Ich scheine mit Blindheit geschlagen zu sein.

Ich sehe weder in der Menuleiste des VisualStudio noch im Kontextmenu der Projektmappe den Punkt "Bereinigen". Ich habe lediglich im Kontextmenu der Projektemappe die beiden Menupunkte "Erstellen" bzw. "Neu erstellen".

Ich suche einen Punkt zum Bereinigen, weil ich soeben wieder den Fall hatte, dass er eine alte Bibliothek herangezogen hatte. Offenbar kopiert sich das VisualStudio (zumindestens im VisualBasic-Anteil) die GrafikDLL.dll in seinen eigenen Dateibaum.

Ich hatte nun die GrafikDLL neu erstellt, weil ich jetzt weitere Funktionen aufnehmen kann. dann habe ich in VisualBasic "Neu Erstellen" ausgewählt und was finde ich hernach? Im Dateibaum des Testprojektes finden sich alte DLLs (vom vorherigen Durchlauf).

Ich denke, ich werde mir ein batchfile schreiben, dass einfach alle Debug/Release/obj-Zweige löscht. Das kann ich dann ja immer noch extern aufrufen.

Gruß,
Thorsten
 
Zurück
Oben