DllImport

Hallo Zusammen,

ich benötige einen Funktionsaufruf aus einer Bibliothek (C++ DLL) via VB.NET oder auch C#.
Eigentlich nichts so grossartiges - dachte ich …
Jedesmal bekomme ich den Fehler: Der Einstiegspunkt DDF2intIP wurde nicht in der DLL rsct_TerminalConnect.dll gefunden.

In der Doku wird der Aufruf so beschrieben:
unsigned long DDF2intIP(const char* const _ddf)

Meine Version zum Aufruf:
_
Shared Function DDF2intIP(ByVal _ddf As String) As Long
End Function

oder auch:

Declare Auto Function DDF2intIP Lib „rsct_TerminalConnect.dll“ Alias „DDF2intIP“ ( _
ByVal _ddf As String) _
As Long

Einen Schreibfehler hatte ich auch schon in Erwägung gezogen, auch wenn ich nicht der C++ Kenner bin, so sieht mir die Header Datei ähnlich aus wie der Eintrag aus der Doku:
static unsigned long DDF2intIP(const char* const _ddf);

Ach ja, andere Funktionen der DLL habe ich auch getestet, aber die gleiche Fehlermeldung …
Andere Beispiele aus dem Netz, natürlich mit anderen DLLs, laufen aber.

Kann mir vielleicht jemand helfen?

Danke,

Andre

ich benötige einen Funktionsaufruf aus einer Bibliothek (C++
DLL) via VB.NET oder auch C#.

Eventuell liegt das Problem beim Wörtchen C++. Da bei C++ Überladung möglich ist, d.h. zwei Funktionen mit dem gleichen Namen, werden die Namen der Einsprungspunkte „gemanglet“. Das heisst, es werden die Typen der Parameter zum Funktionsnamen dazugewurschtelt. Du hast zwei Möglichkeiten: entweder findest du a) den richtigen Namen des Einsprungspunkts heraus und gibst ihn im DllImport-Attribut korrekt an, oder b) du schreibst eine Wrapper-DLL, die eine nicht gemanglete C-Funktion (extern „C“ { void foo() {…} }) enthält, welche die eigentlich gewünschte Funktion aufruft. Der C+±Compiler bekommt das Name Mangling ja hin.

Viele Grüße,
Sebastian

Hallo Sebastian,

besten Dank für die ausführliche Erklärung.
Also Wrapper-DLL ist fürchte ich nicht die Lösung, da ich mit C eigentlich keine Erfahrung habe.
Wie sieht es mit der anderen Möglichkeit aus - dem richtigem Namen des Einsprungpunkts?
Ich habe nach fleissigem googlen ein Programm Namens Dependency Walker gefunden.
Dieses zeigt mir bei der genannten Funktion folgendes an: ?DDF2intIP@TermConnUtils@@SAKQBD@Z

Hinzu kommt noch eine Adresse, „Entry Point“ - also denke ich mal den gesuchten Einsprungpunkt.
Komme ich mit diesen Informationen irgendwie weiter?

Danke,

Andre

[Bei dieser Antwort wurde das Vollzitat nachträglich automatisiert entfernt]

Wie sieht es mit der anderen Möglichkeit aus - dem richtigem
Namen des Einsprungpunkts?
Ich habe nach fleissigem googlen ein Programm Namens
Dependency Walker gefunden.
Dieses zeigt mir bei der genannten Funktion folgendes an:
?DDF2intIP@TermConnUtils@@SAKQBD@Z

Das sieht verdammt gut nach einem gemangelten Einsprungpunkt aus. Das ist das, was im Attribut DllImport als „EntryPoint“ angegeben werden kann:

DllImport(„rsct_TerminalConnect.dll“, EntryPoint:="?DDF2intIP@TermConnUtils@@SAKQBD@Z", ExactSpelling:=False)

Und bezüglich dem ExactSpelling bin ich mir nicht sicher. Das, was ich (unsicher) glaube, dass es tut, hätte hier keinen Sinn. Lieber weglassen oder auch mal mit True probieren.

Viele Grüße,
Sebastian

Das sieht verdammt gut nach einem gemangelten Einsprungpunkt
aus. Das ist das, was im Attribut DllImport als „EntryPoint“
angegeben werden kann:

Hallo Sebastian,

es funktioniert tatsächlich, egal wie albern es auch aussieht …
Wieder viel dazu gelernt, vielen Dank!

Gruß,

Andre

Du hast zwei Möglichkeiten: entweder findest
du a) den richtigen Namen des Einsprungspunkts heraus und
gibst ihn im DllImport-Attribut korrekt an, oder b) du
schreibst eine Wrapper-DLL, die eine nicht gemanglete
C-Funktion (extern „C“ { void foo() {…} }) enthält, welche
die eigentlich gewünschte Funktion aufruft. Der C+±Compiler
bekommt das Name Mangling ja hin.

Kleiner Nachtrag - einige Funktionen gehen mit den kryptischen Einsprungpunkten, aber bei anderen bekomme ich einen Fehler:

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.

Was ich bisher so gefunden habe tritt dieses Problem wohl hin und wieder in der Verbindung mit C++ auf.
Jetzt komme ich um den Wrapper wohl doch nicht herum oder?
Gibt es im Netz irgendwo ein Beispiel, sozusagen für Dummies …?

Danke,

Andre

Kleiner Nachtrag - einige Funktionen gehen mit den kryptischen
Einsprungpunkten, aber bei anderen bekomme ich einen Fehler:

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.

Das ist eine ganz allgemeine Meldung, die bei einer Vielzahl von Fehlern (im unmanaged code) auftreten kann. Etwa wenn die aufgerufene Funktion einen Fehler hat und Schwachsinn gemacht hat. Oder du sie falsch aufgerufen hast und sie es nicht gemerkt hat (oder merken konnte).

Bist du sicher, dass du in diesen Fällen die richtigen Datentypen für die Parameter angegeben hast? Wenn ein Parametertyp falsch ist, belegen die Parameter evtl. einen unterschiedlich großen Speicherbereich, es wird falsch gelesen/geschrieben und alles kommt etwas durcheinander.

Wenn Pointer übergeben werden (Parameter eines Typs wie „void *“ oder „double *“), können auch Dinge durcheinander geraten. Oder wenn Strukturen/Klassen übergeben werden, diese muss man nämlich im managed code 1:1 korrekt nachbilden und mit Attributen versehen, die dazu führen, dass das Speicherlayout auch sicher das gleiche ist.

Naja… du könnstest vielleicht ein Beispiel für eine Funktion, bei der es schiefgeht posten. Aber wenn es keines der genannten Probleme ist, sieht es mit einem Ferndebugging schwierig aus.

Viele Grüße,
Sebastian

Was ich bisher so gefunden habe tritt dieses Problem wohl hin
und wieder in der Verbindung mit C++ auf.
Jetzt komme ich um den Wrapper wohl doch nicht herum oder?
Gibt es im Netz irgendwo ein Beispiel, sozusagen für Dummies
…?

Danke,

Andre

Was ich bisher so gefunden habe tritt dieses Problem wohl hin
und wieder in der Verbindung mit C++ auf.
Jetzt komme ich um den Wrapper wohl doch nicht herum oder?
Gibt es im Netz irgendwo ein Beispiel, sozusagen für Dummies
…?

So, zur zweiten Hälfte :wink:. Also erst mal sind Speicherzugriffsfehler nicht gottgegeben sondern behebbar, es sei denn, es sind Fehler in der Bibliothek selbst (und die bleiben auch beim Wrappen bestehen). Und alle Probleme, die auf dem Zusammenspiel zwischen managed und unmanaged code beruhen, bleiben bei einem Wrapper, der die Funktionen 1:1 abbildet, vermutlich auch bestehen. Eine der Problemquellen ist das Übertragen der Parameterdaten von der einen Welt in die andere (sogenanntes „Marshalling“). Wenn man einen größeren Teil der Arbeit, etwa all den Code, der die Bibliothek benutzt, in eine oder einige wenige C-Funktionen gliedern könnte, würde das vielleicht helfen, weil man die Übergabe „schwieriger“ Parameterdatentypen von managed nach unmanaged vermeiden könnte.

Ich würde zuerst versuchen, die jetzt auftretenden Fehler zu diagnostizieren. Vielleicht muss man die Funktionene der Library auch in einer bestimmten Reihenfolge (z.B. „Init()“ zuerst) aufrufen, oder du hast irgendwo einen Parameter wie eine Stringlänge falsch angegeben? Oder übergibst du „schwierige“ Datentypen?

Viele Grüße,
Sebastian

Naja… du könnstest vielleicht ein Beispiel für eine
Funktion, bei der es schiefgeht posten. Aber wenn es keines
der genannten Probleme ist, sieht es mit einem Ferndebugging
schwierig aus.

Ok, hier einmal ein kleines Beispiel aus dem original C Beispiel, das der DLL beiliegt - und funktioniert:

Auszug .h Datei:

class RSCT_TERMINALCONNECT_API Rsct_TerminalConnect
{
public:
// Verbindungshandling
int Connect(int _port);
int UpdateConnect(int _port, const char* _model);
int Disconnect(int _port);

// Versionsbestimmung
int GetModelVersion(int _port, char* _model, char* _version);
int GetTerminalSCITYP(int _port, int* _typ);

protected:
BOOL CheckDownloadmode(Reader* _pReader);

private:
void Init();
void Terminate();

Rsct_TerminalManager* m_pTerminalManager; // verwaltet Terminalverbindungen
};

extern RSCT_TERMINALCONNECT_API int nRsct_TerminalConnect;

RSCT_TERMINALCONNECT_API int fnRsct_TerminalConnect(void);

Auszug .cpp Datei:

int GetPort()
{
// Zugriff ueber LAN
return (int)TermConnUtils::smiley:DF2intIP(„192.168.1.50“);
}

void GetTerminalVersion()
{
int ret = 0;

Rsct_TerminalConnect* pTC = new Rsct_TerminalConnect();

ret = pTC->Connect(GetPort());

if (ret == 0)
{
char model[MAX_MODEL_CHARS];
char version[MAX_VERSION_CHARS];

pTC->GetModelVersion(GetPort(), model, version);
printf(„Model = %s, Version = %s\n“, model, version);

pTC->Disconnect(GetPort());
}
else
{
printf(„Das Terminal ist nicht ansprechbar. ret=%d\n“, ret);
}

delete pTC; pTC = NULL;
}

Meine Umsetzung sieht so aus:

_
Shared Function Connect(ByVal _port As Integer) As Integer
End Function

_
Shared Function Disconnect(ByVal _port As Integer) As Integer
End Function

_
Shared Function DDF2intIP(ByVal _ddf As String) As Integer
End Function

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim ddf As Integer = DDF2intIP(„192.168.1.50“)
Dim ret As Integer = Connect(ddf)
If ret > 0 Then
Disconnect(ddf)
Else
Console.WriteLine(„Fehler beim öffnen“)
End If
End Sub

Der Fehler entsteht in der Zeile Dim ret As Integer = Connect(ddf).
Der Rückgabewert von DDF2intIP ist aber identisch mit dem Wert der cpp Version.

Vielleicht habe ich ja Glück und es ist nichts grosses … :smile:

Danke,

Andre

Ich denke nicht das es an der DLL selber liegt, den sonst würde ja auch das CPP Demo Probleme haben - aber das funktioniert ja.

Mit Init() habe ich auch was gefunden, aber ich dachte das wäre eine Interne Funktion der Library.
Ich habe den Auszug aus der Header Datei mal erweitert:

// Diese Klasse ist aus rsct_TerminalConnect.dll exportiert
class RSCT_TERMINALCONNECT_API Rsct_TerminalConnect
{
public:
// Konstruktion / Destruktion
Rsct_TerminalConnect(void);
virtual ~Rsct_TerminalConnect(void);

// Verbindungshandling
int Connect(int _port);
int UpdateConnect(int _port, const char* _model);
int Disconnect(int _port);

protected:
BOOL CheckDownloadmode(Reader* _pReader);

private:
void Init();
void Terminate();

Rsct_TerminalManager* m_pTerminalManager; // verwaltet Terminalverbindungen
};

extern RSCT_TERMINALCONNECT_API int nRsct_TerminalConnect;

RSCT_TERMINALCONNECT_API int fnRsct_TerminalConnect(void);

Ansonsten gibt es da nichts aufregendes, zuerst der Connect() und dann könnne die Funktionen aufgerufen werden. Datentypen sind wie im Beispiel lediglich int und char, wobei ich den char zu string gemacht habe, ist aber soweit ich weiß in Ordnung.

Bleibt fast nur die andere Anregund, die Sache mit der Init().
CPP ruft ja als erstes diese Funktion auf:
Rsct_TerminalConnect* pTC = new Rsct_TerminalConnect();

Wird dabei evtl. die interne (private) Init Funktion aufgerufen?

Es gibt doch soviele DLLs und irgendwie ist es das Erstemal so kompliziert …

Danke,

Andre

So, zur zweiten Hälfte :wink:. Also erst mal sind
Speicherzugriffsfehler nicht gottgegeben sondern behebbar, es
sei denn, es sind Fehler in der Bibliothek selbst (und die
bleiben auch beim Wrappen bestehen). Und alle Probleme, die
auf dem Zusammenspiel zwischen managed und unmanaged code
beruhen, bleiben bei einem Wrapper, der die Funktionen 1:1
abbildet, vermutlich auch bestehen. Eine der Problemquellen
ist das Übertragen der Parameterdaten von der einen Welt in
die andere (sogenanntes „Marshalling“). Wenn man einen
größeren Teil der Arbeit, etwa all den Code, der die
Bibliothek benutzt, in eine oder einige wenige C-Funktionen
gliedern könnte, würde das vielleicht helfen, weil man die
Übergabe „schwieriger“ Parameterdatentypen von managed nach
unmanaged vermeiden könnte.

Ich würde zuerst versuchen, die jetzt auftretenden Fehler zu
diagnostizieren. Vielleicht muss man die Funktionene der
Library auch in einer bestimmten Reihenfolge (z.B. „Init()“
zuerst) aufrufen, oder du hast irgendwo einen Parameter wie
eine Stringlänge falsch angegeben? Oder übergibst du
„schwierige“ Datentypen?

Viele Grüße,
Sebastian

Hi Andre,

jetzt weiß ich, wo das Problem liegt. Die Funktionen, die du aufrufst, sind in Wirklichkeit Methoden einer Klasse und brauchen eine Instanz der Klasse Rsct_TerminalConnect (ein Objekt), um funktionieren zu können. Dieses Objekt ist eine Art „unsichtbarer“ Parameter des Einsprungpunkts in die DLL. Dieses muss zuerst erzeugt werden. In deinem C+±Beispiel geschieht das, ähnlich zu C#, mit „new“:

Rsct_TerminalConnect* pTC = new Rsct_TerminalConnect();

Danach können an diesem Objekt Methoden ausgeführt werden, „->“ funktioniert ähnlich dem Punkt in C#:

ret = pTC->Connect(GetPort());

Du wirst um in C geschriebene Wrapperfunktionen kaum herumkommen. Du kannst sie z.B. wie folgt gestalten:

Rsct_TerminalConnect* rsct_tc_new()
{
return new Rsct_TerminalConnect();
}

void rsct_tc_delete(Rsct_TerminalConnect* tc)
{
delete tc;
}

Diese zwei Funktionen dienen dem Anlegen und Zerstören eines Objekts des Typs Rsct_TerminalConnect. Der Aufruf von letzterer ist am Ende der Lebenszeit des Objekts notwendig, da unmanaged Speicher nicht automatisch freigegeben werden kann.

Dann für jede Methode (die du brauchst) wieder eine Wrapper-Funktion, die zusätzlich ein Objekt der Klasse übergeben bekommt:

int rsct_tc_Connect(Rsct_TerminalConnect *tc, int _port)
{
tc->Connect(_port);
}

Etc. Unter .Net werden aus den Parametern und Rückgabewerten des Typs Rsct_TerminalConnect * dann Objekte des Typs IntPtr, das heisst, du holst die Funtkionen deiner selbstgeschaffenene DLL wie folgt nach .NET:

[DllImport(…)]
extern int rsct_tc_Connect(IntPtr tc, int _port);

(ich kann leider nur C# :wink:).

Jetzt musst du nur noch hinbekommen, in deiner IDE ein DLL-Projekt zu erzeugen, dort das Headerfile der Bibliothek zu inkludieren und die .lib-Datei der DLL zu verknüpfen, und vor alle Funktionsaufrufe __declspec(dllexport) zu schreiben, um die selbstgeschriebenen Funktionen als Einsprungpunkte der DLL zu exportieren.

Also im Endeffekt brauchst du eine .cpp-Datei mit folgendem Inhalt:

#include „blabla.h“

__declspec(dllexport) Rsct_TerminalConnect* rsct_tc_new()
{
return new Rsct_TerminalConnect();
}

[etc.]

Dann musst du einerseits der IDE beibringen, wo sie die .h-Datei findet und andererseits, dass es die .lib-Datei dazulinkt (und wo sie sie findet).

So… ich wünsche dir viel Erfolg. Ist bestimmt nicht einfach, aber geht vermutlich nicht anders. Es sei denn, du schaffst es herauszufinden, wie man ein C+±Objekt direkt von .NET aus erzeugt. Keine Ahnung, ob und wie das geht.

Viele Grüße,
Sebastian

Hallo Sebastian,

na ja, ich hatte mir zwar die leichte Lösung gewünscht, aber so hast Du mir wenigstens das Problem erläutert und einen Lösungsansatz gegeben.

Ich werde mein Glück dann einmal versuchen, so komme ich dann auch dazu C++ zu lernen, hat doch auch was.

Vielen Dank für Deine Hilfe und Erklärungen, ich denke der Rest wird schon…

Danke,

andre