Moin,
ich hab eine C-DLL kompiliert mit einer Funktion die ich von C# aus benutzen möchte.
Das wäre:
unsigned char *testFunc(unsigned char *argA, unsigned char *argB, int *argC)
Die export.def sieht folgendermaßen aus:
LIBRARY "TestLib"
EXPORTS testFunc @1
So weit so gut (kann leider nur sehr schlecht C, hoffe das reicht 😉 )
Hier mein C#-Code dazu:
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace TestProg
{
static class Program
{
[DllImport("TestLib.dll")]
public static extern string testFunc(string argA, string argB, int argC);
[STAThread]
static void Main()
{
string a = "string1";
string b = "string2";
int c = 125;
testFunc(a, b, c);
// ....
}
}
}
Doch beim Aufruf kommt ein Fehler das ich versuche auf geschützen Speicher zuzugreifen. Könnt ihr Profis mir da bitte weiterhelfen?
Vielen Dank!
Hallo FiReFoX,
die Funktion erwartet einen int-Pointer keinen int. Bei deinem Aufruf versucht er daher auf Speicheradresse 125 zuzugreifen und das bringt den Fehler.
herbivore
Hi herbivore,
vielen Dank für deine Antwort. Kannst Du mir da bitte etwas mehr helfen?
Wenn ich int durch IntPtr austausche kommt trotzdem der gleiche Fehler.. 🙁
Hallo FiReFoX,
du musst wohl einen echten Pointer (also int* bzw. &c) verwenden und das geht nur in einem unsafe-Block.
herbivore
Hi herbivore,
Das klappt auch nicht.
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace TestProg
{
static class Program
{
[DllImport("TestLib.dll")]
public static extern unsafe string testFunc(string argA, string argB, int* argC);
[STAThread]
static void Main()
{
string a = "string1";
string b = "string2";
string c;
unsafe
{
int* c = (int*)125;
c = testFunc(a, b, c);
}
// ....
}
}
}
Also ich geh mal die Sache eher von der C Code Seite an:
unsigned char *testFunc(unsigned char *argA, unsigned char *argB, int *argC)
Du erwartest einen Pointer auf ArgA, ArgB, ArgC und gibst nen Pointer zurück.
In deinem C# Code sehe ich aber nur, dass Du einen Pointer erwartest aber nicht drei pointer auf a,b,c übergibst, sondern nur deren inhalt / wert.
ich könnte mir denken, dass der Aufruf eher so sein muss:
c = testFunc(&a, &b, &c);
und ganz oben:
public static extern unsafe string testFunc(string* argA, string* argB, int* argC);
Kann sein das ich mich irre!
Gruss,
Gregor W.
//edit
Benutzen von C/C++ DLLs geht meiner Meinung nach IMMER ohne unsafe code und Verwendung von Pointern in C#
Kurze Zusammenfassung:
Du musst immer einen CLR Typ finden den du als Argument beim DLL Import angibst.
Wenn die C/C++ Funktion Pointer auf einen Datentyp erwartet, definiere den entsprechenden Parameter mit "ref" oder "out" keyword.
Speziell bei Zeichenketten: Wenn es ein konstanter Pointer auf eine Zeichenkette ist, also nur als eingabewert dient, benutze den CLR Typ string, ansonsten wenn er auch zur Ausgabe benutzt wird den StringBuilder.
Wie es sich da mit dem eigentlichen Rückgabewert via return verhält weiß ich nicht, ich vermute aber string sollte da in Ordnung gehen, da er ja komplett in der C-DLL angelegt wird.
Steht alles da drin ausführlich erklärt. Angewendet auf dein Beispiel ergibt das folgenden Code:
[DllImport("TestLib.dll")]
public static extern string testFunc(StringBuilder argA, StringBuilder argB, out int argC);
Wenn argA und argB nicht in der C Funktion verändert werden müsstest du auch ganz normal string benutzen können... aber auch ohne Garantie 😉
Außerdem lohnt sich gerade bei dem Thema ein Blick auf SharpDevelop.
Dort gibt es unter Extras "P/Invoke Signatur einfügen..." wo du die DLL und Funktionsname angibst, und er sagt dir die C# Definition der Funktion.
Vielen Dank!
Kompiliert wurde mein Programm schonmal!
Ob die Ausgabe passt muss ich morgen noch testen 😉
Hi,
Ich hab jetzt die echte DLL kompiliert und nich die mit der TestFunc. Es handelt sich um einen Decoder für die Serverliste der GameSpy server (enctype2_decoder.zip)
Kompilieren unter C++ (VS.NET 2005) mit Exports.def klappt (mit oder ohne 'extern "C"', da C-Code).
Ich übergeben der Funktion einen String mit z.B. 40000 Zeichen, es müssten dann auch so circa wieder 40000 Zeichen zurückgegeben werden doch es sind zwischen 50 und 1000.
Sowohl Gregor's als auch Jabberwocky's Version klappt da nicht. Hab ich eventuell die DLL falsch kompiliert (mein C-Wissen is etwas bescheiden..)?
Grüße
Ist die Rückgabe bzw sind die Texte leßbar??
Weil wenn \0 vorkommt ist klar, dass deine Texte alle dort abgeschnitten werden... weil pointer of array of char könnte auch was anderes sein 🙂
Könntest es ja mal mit char[] als Rückgabewert versuchen und schauen was du da für ne Länge bekommst. Ich vermute auch es wird wegen \0 aufgehört.
könntest dann durchiterieren und alle \0 durch \n oder so ersetzen, und dann mit dem String Konstruktor umwandeln.
Ich gehe mal davon aus, dass testFunc(argA, argB, argC) die Argumente nicht wirklich verändern will, sonder eben nur Pointer erwartet und dafür ein Ergebnis zurückliefert. Versuchs also mal hiermit:
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
{
class Program
{
[DllImport("TestLib.dll")]
private static extern IntPtr testFunc(string argA, string argB, ref int argC);
public static string MyTestFunc(string argA, string argB, ref int argC)
{
IntPtr i = testFunc(argA, argB, ref argC);
return Marshal.PtrToStringAnsi(i);
}
static void Main(string[] args)
{
string a = "string1";
string b = "string2";
int c = 125;
string result =MyTestFunc(a, b, ref c);
}
}
}
Wenn argC tatsächlich verändert werden sollte, dann ersetze "ref" mit "out".
Hallo!
@Gigaseb & Jabberwocky:
Die Ausgabe is binär, möglich das auch 0x00 dabei ist.
@nop:
Danke ich werd mir den Code mal ansehen!
/ Edit: Klappt leider nicht. Es werden zwischen 0 und 800 Bytes zurückgegeben. Mittlerweile vermute ich das is die C-DLL wirklich falsch kompiliert hab oder so..
/ Edit 2: Vielleicht hilft das:
USAGE
\=====
Add the following prototype at the beginning of your code:
unsigned char *enctype2_decoder(unsigned char *, unsigned char *, int *);
then use:
pointer = enctype2_decoder(
gamekey, // the gamekey
buffer, // all the data received from the master server
&buffer_len); // the size of the master server
The return value is a pointer to the decrypted zone of buffer and
buffer_len is modified with the size of the decrypted data.
IntPtr als Rückgabewert ist richtig. Da der Encoder aber sicherlich binär arbeitet, ist das Ergebnis byte[]. Statt PtrToStringAnsi() also Marshal.ReadByte(i, argC).
Ich würde davon ausgehen, dass auch die Eingangsdaten binär sind (auch hier wird eine Längeninfo verwendet), daher:
[DllImport("TestLib.dll")]
private static extern IntPtr testFunc(byte[] argA, byte[] argB, ref int argC);
Bei Aufruf muss argC auf argB.Length gesetzt werden!
Du hast aber noch ein anderes, vermutlich schlimmeres Problem: Die Speicherfreigabe:
In deinem Fall allokiert die Routine Speicher und du - als Aufrufer - musst den Speicher freigeben. .NET hat Zugriff auf zwei "Speicher-Manager" (CoTaskMem und GlobalAlloc). Deine wird aber das malloc() der CRT nutzen, und das ist nicht mehr Betriebssystem (worauf .NET Zugriff hat), sondern C-Spezifisch. Normalerweise sollte eine Biblitiohek wie diese daher Free()-Routinen anbieten, wie z.B. FreeBuffer(pointer). So macht es Windows durchgängig.
Im Prinzip sollte die API ein paar Worte zur Freigabe verlieren. Ansonsten musst du testen und hoffen, dass dahinter ein Stück statischer Speicher steht (IntPtr wäre dann immer gleich), oder die Routine den Speicher funktions-intern statisch hält bzw. immer wieder neu allokiert und daher die Freigabe selbst macht (kannst du nur am Speicherverbrauch sehen).
Ich befürchte aber, du wirst Pech haben, und diese Routine nicht mit .NET nutzen können, eben wegen den Speicher-Problems. Wäre dann aber nicht deine Schuld, sondern eine amateurhaft programmierte Library. Eine Alternative wär noch ein managed C++-Wrapper, der hat Zugriff auf die CRT, wenn ich nicht irre.
Svenson, die Byte-Infomation ist mir bei den ganzen vorherigen Postings mit Strings leider verlorengegangen. 🙁 Firefox, wenn du zumindest noch das Testprogramm zum laufen kriegen willst, dann solltest du alle "ref" durch "out" austauschen, da argC (wie du bereits geschrieben hast) hier die Länge zurückgibt.
Ich denke, "Ref" bei der Länge ist schon richtig, weil es zum einen die Länge des Eingabefeldes, wie des Ausgabefeldes angibt.
Original von svenson
IntPtr als Rückgabewert ist richtig. Da der Encoder aber sicherlich binär arbeitet, ist das Ergebnis byte[]. Statt PtrToStringAnsi() also Marshal.ReadByte(i, argC).Ich würde davon ausgehen, dass auch die Eingangsdaten binär sind (auch hier wird eine Längeninfo verwendet), daher:
Also dass String für const char* und StringBuilder für char* klappt, dafür leg ich meine Hand ins Feuer. Das hab ich schon öfters getestet. Das Framework kümmert sich darum, dass die richtigen Daten ankommen. Man kann auch festlegen ob der Text Unicode oder Ansi ist, etc. Siehe den von mir gelinkten Artikel.
Nur mit Rückgabewerten hab ich keine Erfahrung. Bei dem was ich bisher gemacht hab kam die Rückgabe über nen Parameter und das return value war ein Error-Code (int)
IntPtr hab ich bisher nur für Handles aller Art benutzt...
Das Ding ist doch, dass in C "char" auch für "byte" verwendet wird. Ob sowas wirklich ein C-style-String oder ein byte-Array gemeint ist (also Null-terminiert oder nicht), muss dokumentiert sein, oder ist aus der Signatur ablesbar. Ob es also "klappt" ist von der Intention, bzw. der Implementierung und des Eingangsdaten abhängig. Ein typisches Fehlersymptom wäre, dass zu wenige Daten konvertiert werden (Marshalling beendet beim erstbesten Null-Zeichen).
Nur weil du "string" verwendest, muss das also nicht abstürzen, u.U. arbeitet es nur nicht richtig.
Hier ist offenbar zumindest der zweite Parameter Längen-definiert, also damit KEIN String:
buffer, // all the data received from the master server
&buffer_len); // **the size of the master server**
Der erste Parameter scheint aber tatsächlich ein String zu sein, weil hier die Längeninfo fehlt.
Ergibt dann:
[DllImport("TestLib.dll")]
private static extern IntPtr testFunc(string argA, byte[] argB, ref int argC);
Hallo,
erstmal vielen Dank das ihr meinen Problem Beachtung schenkt!!
[DllImport("TestLib.dll")]
private static extern IntPtr testFunc(string argA, byte[] argB, ref int argC);
gibt auch fehlerhaften Daten zurück
So wie es aussieht wär eine kleine C++ Wrapper-Funktion die beste Lösung oder?
Du wirfst aber die Flinte ganz schön schnell ins Korn! 🙂
Zeige doch noch mal den Aufruf der Funktion, vielleicht ist hier noch ein Fehler Hast du mal versucht mit dezidierten Testdaten zu arbeiten: (Array mit drei Bytes, dann noch ein Null dahinter, nächster Test mit einem Wert > 0 hinter der Null)?
Hallo!
Original von svenson
Du wirfst aber die Flinte ganz schön schnell ins Korn! 🙂
Liegt daran das ich sehr sehr wenig Ahnung von Pointern und C hab..
Ich will euch auch nich tagelang "auf die Nerven gehen".. 🙂
Zeige doch noch mal den Aufruf der Funktion, vielleicht ist hier noch ein Fehler Hast du mal versucht mit dezidierten Testdaten zu arbeiten: (Array mit drei Bytes, dann noch ein Null dahinter, nächster Test mit einem Wert > 0 hinter der Null)?
Bin gerade in der Arbeit aber wenn ich zuhause bin werd ich was posten!
Danke!
FiRe
So siehts bei mir jetzt aus:
[DllImport("GameSpy.dll")]
public static extern IntPtr enctype2_decoder(string key, byte[] data, ref int size);
private string DecodeServers(string serverList)
{
string key = "d4kZca"; // GS Arcade key
int size = serverList.Length;
// Serverliste entschlüsseln
IntPtr i = enctype2_decoder(key, Encoding.Default.GetBytes(serverList), ref size);
// Entschlüsselte Servliste zurückgeben
return Marshal.PtrToStringAnsi(i);
}
serverList sind eben die Daten die ich vom Socket empfangen hab.
Benutzt Encoding.Default.GetBytes() auch die dll (oder eine andere)?
Wenn ja, dann brauchste nochmal eine extern declaration bzw. wrapper.
Byte[] oder string scheint wohl keine Rolle zu spielen, da immer string.
Hi nop.
Naja es wurde hier ja vorgeschlagen den zweiten Parameter als byte-Array festzulegen.
Und ich kenn kein andere Möglichkeit einen byte-Array aus nem String zu erstellen ausser Encoding.Default.GetBytes(). (Encoding.ASCII is ja 7-Bit oder?)
Ungewöhnlich, dass die C-Funktion mit Unicode-Strings beschossen wird... sieht nicht korrekt aus. Du brauchst hier ANSI-Encoding (char *).
Hallo svenson,
ohne, dass ich hier alles gelesen habe: Encoding.Default.GetBytes() liefert doch das Default-ANSI-Encoding.
herbivore
Hallo!
Mal ne Frage an die die sich den C-Code angeguckt haben: Ist es für mich möglich den Code nach C# zu portieren (nur geringe Pointer-Kentnisse)?
Eventuell durch unsafe C#-Code mit Pointern?