Hallo,
vielleicht kann mir jemand bei einem kleinen Problem weiterhelfen:
Ich möchte in C# eine native DLL verwenden. Hierfür schreibe ich mir
auch gerade einen entsprechenden Wrapper. In der DLL gibt es ein paar
Methoden, welche ein struct erwarten.
Ein struct in C/C++ ist ja eigentlich nur die Zusammenfassung mehrerer
Variablen unterschiedlichen Typs in einer strukturierten Form - struct
eben :)
In C# sieht das ja ganz anders aus, ein struct kann Methoden, etc.
haben.
Um ein C-kompatibles struct in C# zu definieren, bei dem die enthaltenen
Variablen auch der gleichen Reihenfolge wie bei der Definition
entsprechen, definiert man das struct folgendermaßen:
1
[StructLayout(LayoutKind.Sequential)]
2
structbla{
3
inta;
4
byteb;
5
longc;
6
}
Somit werden die Variablen auch in dieser Reihenfolge im Speicher
angelegt.
Soweit so gut...
In dem von der DLL erwarteten struct befinden sich auch Strings. Im
normalen C ist ein String eine Folge einzelner Bytes, deren Werte
ASCII/ANSI-Buchstaben entsprechen und null-terminiert sind.
In C# handelt es sich bei einem String um Unicode-Werte, d.h. jedes
Zeichen ist 2-Byte groß.
Die Entsprechung eines C-Strings in C# ist eigentlich ein Byte-Array
(wie es ja implizit in C der Fall ist).
Logischerweise möchte ich im Wrapper der entsprechenden Methode
natürlich C#-Strings übergeben. Wie löse ich das denn nun?
1. Ich definiere das C-struct und parallel dazu das C#-Äquivalent. Die
Wrapper-Methode erhält das C#-struct als Parameter, erzeugt ein C-struct
und wandelt die C#-Strings in Byte-Arrays, die restlichen struct-Member
werden kopiert.
2. Ich verwende Pointer in einem unsafe-Kontext
3. Ich mache es per Marshalling (habe aber bis dato keine Erfahrung
damit)
Lösung 1) ist etwas aufwendig, weil das struct dummerweise an die 100
Member hat.
Lösung 2) sagt mir nicht zu, da ich auf unsafe-Blöcke verzichten möchte
wenn es geht
Lösung 3) ist Neuland :( Prinzipiell nichts gegen Neuland, wäre
zumindest momentan aber schön, wenn's auch anders geht :)
Lösung 4-n) Kenne ich noch nicht, bin für Vorschläge offen :)
Ralf
Hallo Arc Net,
danke für die Links, das sieht gar nicht mal schlecht aus :)
Bin jetzt aber noch nicht ganz sicher, ob ich es richtig verstanden
habe. Ich würde denke ich eine Lösung basierend auf deinem Link
bevorzugen.
Dazu hätte ich ein paar Fragen:
- dasin deinem Beispiel-struct verwendete
[StructLayout(LayoutKind.Sequential)] habe ich bereits verwendet. Das
zusätzliche (CharSet = CharSet.ANSI) sorgt dafür, dass nicht UNICODE
(2-Byte) verwendet wird, sondern eine 8-Bit Zeichendarstellung?
- Wenn ich das:
ersetze, müsste ich doch "näher" an einem C-String sein, oder?
Null-terminiert und ohne feste Längenangabe. (Die Sache mit der
Längenangabe muss ich noch prüfen, es gibt vorgeschriebene Maximallängen
der Strings für die DLL, vielleicht ist deine vorgeschlagene Variante
dann doch vorzuziehen)
- Zum Verständnis: Wenn ich das struct mit dem o.g. Marshalling
verwende, dann brauche ich es ja nur einmal anlegen und nicht eine C-
und eine C#-Variante, korrekt?
- Funktioniert das dann in beide Richtungen? Die DLL erwartet einerseits
einen Pointer auf ein solches struct, wenn gelesen wird (habe ich
mittels "ref" implementiert) und will andererseits mit einem solchen
struct gefüttert werden.
Ralf
Ralf schrieb:> Hallo Arc Net,>> danke für die Links, das sieht gar nicht mal schlecht aus :)>> Bin jetzt aber noch nicht ganz sicher, ob ich es richtig verstanden> habe. Ich würde denke ich eine Lösung basierend auf deinem Link> bevorzugen.>> Dazu hätte ich ein paar Fragen:> - dasin deinem Beispiel-struct verwendete> [StructLayout(LayoutKind.Sequential)] habe ich bereits verwendet. Das> zusätzliche (CharSet = CharSet.ANSI) sorgt dafür, dass nicht UNICODE> (2-Byte) verwendet wird, sondern eine 8-Bit Zeichendarstellung?
Richtig
> - Wenn ich das:>
> ersetze, müsste ich doch "näher" an einem C-String sein, oder?
Jein, beides "sind" C-Strings, LPStr ist das was dransteht, ein Zeiger
auf einen C-String (char *), ByValTStr wäre in C char TestString[123];
> Null-terminiert und ohne feste Längenangabe. (Die Sache mit der> Längenangabe muss ich noch prüfen, es gibt vorgeschriebene Maximallängen> der Strings für die DLL, vielleicht ist deine vorgeschlagene Variante> dann doch vorzuziehen)>> - Zum Verständnis: Wenn ich das struct mit dem o.g. Marshalling> verwende, dann brauche ich es ja nur einmal anlegen und nicht eine C-> und eine C#-Variante, korrekt?>> Ich möchte in C# eine native DLL verwenden
?
>> - Funktioniert das dann in beide Richtungen? Die DLL erwartet einerseits> einen Pointer auf ein solches struct, wenn gelesen wird (habe ich> mittels "ref" implementiert) und will andererseits mit einem solchen> struct gefüttert werden.
Sollte...
> Ralf
Hallo ArcNet,
>> ...sondern eine 8-Bit Zeichendarstellung?> Richtig
Okay, dann bleibt schon mal die Größe identisch :)
> Jein, beides "sind" C-Strings, LPStr ist das was dransteht, ein Zeiger> auf einen C-String (char *), ByValTStr wäre in C char TestString[123];
Ah, gut. Dann muss ich schauen, wie im Falle von LPStr die Syntax für
die Übergabe lauten muss, da Pointer ja nur im Unsafe-Kontext erlaubt
sind.
Müsste aber mit dem ref-Schlüsselwort gedeckelt sein, denke ich.
>>> - Zum Verständnis: Wenn ich das struct mit dem o.g. Marshalling>>> verwende, dann brauche ich es ja nur einmal anlegen und nicht eine C->>> und eine C#-Variante, korrekt?>> Ich möchte in C# eine native DLL verwenden>?
Das Fragezeichen bezog sich auf "nur einmal anlegen"?
Damit meinte ich, dass die Strings in C ja byte-Arrays sind, während es
in C# ja den Datentyp string gibt.
Damit ich in C# auch "direkt" einen String ohne Typkonvertierung
verwenden kann, brauch ich dann zwei Structs, jeweils nach C/C# Syntax.
Wenn aber durch Marshalling in C# ein C-kompatibles Struct erzeugt wird,
welches mit C#-Strings gefüttert werden kann, so brauche ich nur eine
Struct-Definition.
So war das gemeint.
Ralf