Forum: PC-Programmierung C#/DLLImport: String in C-struct / C#-struct


von Ralf (Gast)


Lesenswert?

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
struct bla {
3
  int a;
4
  byte b;
5
  long c;
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

von Arc N. (arc)


Lesenswert?

1
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.ANSI)]
2
struct TestStruct {
3
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 123)]
4
    public string TestString;
5
}

sowas in der Art ;-)

http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.unmanagedtype.aspx?appId=Dev10IDEF1&l=EN-US&k=k(SYSTEM.RUNTIME.INTEROPSERVICES.UNMANAGEDTYPE);k(UNMANAGEDTYPE.BYVALTSTR);

http://msdn.microsoft.com/library/s9ts558h(VS.80).aspx

Wenn auf Geschwindigkeit ankommt, wird auch sowas interessant
http://www.codeproject.com/KB/cs/ReadingStructuresEmit.aspx

von Ralf (Gast)


Lesenswert?

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:
1
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 123)]
2
public string TestString;
durch
1
[MarshalAs(UnmanagedType.LPStr)] public String TestString;
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

von Arc N. (arc)


Lesenswert?

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:
>
1
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 123)]
2
> public string TestString;
3
>
> durch
>
1
[MarshalAs(UnmanagedType.LPStr)] public String TestString;
> 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

von Ralf (Gast)


Lesenswert?

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

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.