Laden...

struct als return Wert (C++ -> C#)

Erstellt von samx vor 18 Jahren Letzter Beitrag vor 18 Jahren 4.080 Views
S
samx Themenstarter:in
8 Beiträge seit 2005
vor 18 Jahren
struct als return Wert (C++ -> C#)

Des öfteren wurden Fragen gestellt wie ein struct als Parameter in C++ (in einer DLL) angegeben wurde und wie das nun in C# umgesetzt werden kann. Hierzu gibt es auch viele Beispiel im Internet / MSDN.

Ich habe allerdings das Problem, dass ich eine DLL habe bei der ein struct als Ergebnis zurückgeliefert wird und leider finde ich hierzu keinerlei Infos wie das in C# umgesetzt werden kann. Geht das überhaupt ?

Als Beispiel
(Es handelt sich dabei um eine Kommunikations DLL zu einem speziellen Meßgerät):

struct received_data
{
int err_type;
int err_number;
int len;
int max_len;
unsigned char data[SAC_MAX_MSG_LENGTH];
unsigned char cmd;
unsigned char sub_cmd;
};

struct received_data WINAPI SendCommand(short com, unsigned char *send, short len, BOOL repeat, long time);

Ich habe jetzt mal in C# so angefangen:
Mal unberücksichtigt, das 'data' ein Array ist und der Parameter 'send' ein Zeiger, das hätte ich später berücksichtigt. Ich komme allerdings gar nicht soweit, weil ich bisher noch herausgefunden habe wie ein struct als return Wert behandelt werden muß:

[StructLayout(LayoutKind.Sequential)]public struct received_data
{
public int err_type;
public int err_number;
public byte data;
public byte cmd;
public byte sub_cmd;
}

[DllImport("samComm.dll")] public static extern [MarshalAs(UnmanagedType.LPStruct)] received_data SendCommand(short com, byte send, short len, bool repeat, int time);

Viele Grüße
Jürgen Eder

S
8.746 Beiträge seit 2005
vor 18 Jahren

data solltest du in der Struktur mittels MarshalAs als ByValTStr auszeichnen und die Länge mittels SizeConst angeben (ggf. CharSet.Ansi). Der Typ ist dann string anstelle einfach nur byte.

Als Rückgabewert kannst du einfach den Struct-Typ angeben. Ob LpStruct notwendig ist, ist hier unklar. Wird ein Pointer auf die Struktur zurückgegeben (dann ist LPStruct) richtig, oder die Struktur als Wert (dann LPStruct weglassen()?

S
samx Themenstarter:in
8 Beiträge seit 2005
vor 18 Jahren

Hallo,
ich bin jetzt etwas weiter gekommen. Allerdings kann ich anstelle der "byte" Arrays keine Strings verwenden, denn es sind an dieser Stelle tatsächlich binäre Daten.

Die Struktur sieht jetzt so aus:
[StructLayout(LayoutKind.Sequential)]public struct received_data
{
public int err_type;
public int err_number;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=4096)] public byte [] data;
public byte cmd;
public byte sub_cmd;
}

und die Funktion so:
[DllImport("samComm.dll")] public static extern received_data SendCommand(short com, byte[] send, short len, bool repeat, int time);

Allerdings erhalte ich zur Ausführungszeit dann den Fehler:
"Die Typensignatur der Methode ist nicht PInvoke kompatibel"

gruß
Jürgen Eder

S
8.746 Beiträge seit 2005
vor 18 Jahren

Prüfe mal ob der Rückgabewert die Ursache ist (einfach mal gegen Int austauschen). Ich denke aber, er will den byte[]-Parameter mit Angabe von MarshalAs (LPArray).

S
samx Themenstarter:in
8 Beiträge seit 2005
vor 18 Jahren

Wenn ich den Rückgabewert ersetze z.B. durch void, dann stürzt das Programm an dieser Stelle mit einer Exception ab (allerdings "Blabla... PInvoke...inkompatibel..." erscheint dann nicht mehr). Wahscheinlich gerät der Stack dann ein bißchen durcheinander.

LPArray funktioniert auch nicht, sobald eine Struktur der Rückgabewert ist, kommt die "PInvoke inkompatibel" Fehlermeldung.

S
8.746 Beiträge seit 2005
vor 18 Jahren

Ehrlich gesagt bin ich ratlos. An byte[] sollte es auch nicht liegen, das sollte anstandslos gemarshalled werden.

Wenn er Strukturen in Rückgabewerte nicht kann, dann sollte das schon der Compiler meckern.

S
samx Themenstarter:in
8 Beiträge seit 2005
vor 18 Jahren

Ja, trotzdem danke für die Antworten.

Das muß irgendwie an dem Array in der Struktur liegen, wenn ich das Array weglasse:

[StructLayout(LayoutKind.Sequential)]public struct received_data
{
public int err_type;
public int err_number;
public byte data;
public byte cmd;
public byte sub_cmd;
}

dann kommt das "... PInvoke nicht kompatibel..." nicht mehr, bringt aber trotzdem nichts weil dann der Stack nicht stimmt (innerhalb der DLL) und das Programm an dieser Stelle mit einer Exception abstürzt.

Vielleicht sind solche Konstrukte tatsächlich nicht vorgesehen, ich habe im Internet keinerlei Beispiele/Hinweise gefunden und auch die Windows API funktionen liefern nur 'normale' Rückgaben zurück (int, long, bool, oder ähnliches).

gruß
Jürgen Eder

4.221 Beiträge seit 2005
vor 18 Jahren

Eventuell musste noch ein Pack draufmachen

(Beispiel aus meiner Twain-Lib)



[StructLayout(LayoutKind.Sequential, Pack=2)]
	public class TwImageInfo
	{									// TW_IMAGEINFO
		public int			XResolution;
		public int			YResolution;
		public int			ImageWidth;
		public int			ImageLength;
		public short		SamplesPerPixel;
		[MarshalAs( UnmanagedType.ByValArray, SizeConst=8)] 
		public short[]		BitsPerSample;
		public short		BitsPerPixel;
		public short		Planar;
		public short		PixelType;
		public short		Compression;
	}



Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

S
8.746 Beiträge seit 2005
vor 18 Jahren

Da fragt man sich doch: Woher wissen, mit welchem Byte-Alignment das Teil compiliert wurde?

"The default packing size is 8, except for unmanaged structures that typically have a default packing size of 4."

4.221 Beiträge seit 2005
vor 18 Jahren

@svenson

Ich würde mit Pack 1 oder 2 probieren..... (es sind ja diverse bytes drin)....

Und sogar wenn alle durchprobiert werden müssen hat man's schnell raus ob's was bringt

1,2,4,8,16,32,64,128 sind ja schnell durchprobiert (wobei ich wie gesagt auf einen eher kleinen Wert tippe..... )

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

S
samx Themenstarter:in
8 Beiträge seit 2005
vor 18 Jahren

Die Fehlermeldung "... PInvoke nicht kompatibel..." kommt weiterhin. Die PackSize wäre übrigens 8.

4.221 Beiträge seit 2005
vor 18 Jahren

Mach den Return Value des Api-Calls mal als IntPtr

Wenn es nicht kracht kannst Du dann die Daten aus dem InPtr saugen (Marshal.PtrToStructure)

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

S
8.746 Beiträge seit 2005
vor 18 Jahren

Das Problem ist ja hier, dass das Ergebnis by value und nicht by ref rauskommt. IntPtr knallt daher gewaltig.

S
samx Themenstarter:in
8 Beiträge seit 2005
vor 18 Jahren

Ja ganz recht, leider crasht es beim Aufruf:

"Eine nicht behandelte Ausnahme des Typs 'System.NullReferenceException' ist in samCommTest.exe aufgetreten.

Zusätzliche Informationen: Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt."

4.221 Beiträge seit 2005
vor 18 Jahren

Besteht eigentlich die Möglichkeit den C++ Code abzuändern ?

Wenn ja probier doch mal den struct als ref Param statt als Returnvalue zu übergeben....

PS: So langsam gehen mir die Ideen aus...

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

S
samx Themenstarter:in
8 Beiträge seit 2005
vor 18 Jahren

Ja, die Möglichkeit besteht. Ich kann zwar die Funktion nicht ändern (aus 'Kompatibilitätsgründe') aber jederzeit neue Funktionen hinzufügen und diese dann .NET Kompatibel halten. Diese Funktion ist sowieso - wie ich inzwischen bemerkt habe - nicht der einzige Stolperstein. In der C++ DLL gibt es auch Callback Funktionen ... die cdecl deklariert sind 🤔

S
8.746 Beiträge seit 2005
vor 18 Jahren

Lösung für cdecl-Callbacks:

.NET 1.1:

http://www.codeproject.com/dotnet/Cdecl_CSharp_VB.asp

.NET 2.0:

[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
delegate int MyUnmanagedCdeclDelegate(int param);

S
samx Themenstarter:in
8 Beiträge seit 2005
vor 18 Jahren

Die Lösung für cdecl Callbacks funktioniert. Für das andere Problem ändere ich die DLL entsprechend ab.