Laden...

Marshalling von struct Member in Klasse

Erstellt von Lenny vor 7 Jahren Letzter Beitrag vor 7 Jahren 1.647 Views
L
Lenny Themenstarter:in
95 Beiträge seit 2009
vor 7 Jahren
Marshalling von struct Member in Klasse

Hallo,

ich stehe im Moment vor dem Problem ein struct als Member einer Klasse an Native Code übergeben zu wollen.

Kurz zum Szenario -
Im Native Code sieht die Datenstruktur so aus:


#typedef struct 
{
   char* Member1;
   int Member2;
   ...
}A;
struct B
{
    int Member1;
    A  Member2;
}


In C# würde ich das Ganze gerne genauso abbilden, jedoch struct B als Klasse abbilden. Also so:


struct A
{ 
   IntPtr Member1;
   int Member2;
}
[StructLayout (LayoutKind.Sequential)]
class B
{
   int Member1;
   A Member2;
}

Ich habe jedoch keine brauchbaren Informationen gefunden wie ein struct als Member einer Klasse zu Marshallen ist bzw. ob / wie das überhaupt geht. Auf den Native Code habe ich keinen Einfluss.
Der Versuch dieses Konstrukt einfach an den Native Code weiter zugeben mit Marshal.StructToPtr resultieren darin, dass eine Überprüfung im Native Code mir mitteilt, dass Member1 keine validen Daten enthält (ein nullptr ist)

W
872 Beiträge seit 2005
vor 7 Jahren

A und B müssen Structs/Werttypen sein.
Klassen sind Referenzen und können daher nicht Marshalling umgewandelt/kopiert werden.
Warum soll B denn eine Klasse sein?

L
Lenny Themenstarter:in
95 Beiträge seit 2009
vor 7 Jahren

Man kann durchaus explizit eine Klasse kopieren beim Marshalling ( Marshaling Classes, Structures, and Unions ) . In der Doku wird dies auch entsprechend erklärt.
Warum B eine Klasse sein soll? Ich brauche Anfangswerte für bestimmte Variablen.

Mein Problem ist außerdem, dass anscheinend die Klasse richtig kopiert wird, aber nicht das darin enthaltene struct.

16.834 Beiträge seit 2008
vor 7 Jahren

..mappt man char* nicht auf string bzw. StringBuilder oder wenn hinter char* nen Buffer steckt auch byte[] ...? 🤔

L
Lenny Themenstarter:in
95 Beiträge seit 2009
vor 7 Jahren

Hey,
also um mal etwas auszuholen. Ich baue gerade einen .Net Wrapper für ROS2 (Robot operating system).
Bei ROS2 gibt es eine Messagedefinition aus der Code generiert wird. Im C Teil werden Messages als struct angelegt . Arrays innerhalb des Message-Struct haben einen eigenen struct typ, das nicht nur den Pointer auf die Daten enthält sondern auch noch eine Längen Information und eine Kapazitätsangebe.

Beispiel:

Gegeben ist eine Message die einen Int32 und ein Int32Array enthält (Für strings sieht diese Definition genauso aus nur eben dass ein char pointer verwendet wird):
Das Message struct sieht dann so aus:


typedef struct <message name>
{
     int32 member1;
     rosidl_generator_c__int32__Array member2;
}

Das rosidl_generator_c__int32__Array struct sieht so aus:


typedef struct rosidl_generator_c__int32__Array
{
       int32* data;
       size_t size;
       size_t capacity;
}

Das struct, das die Message an sich repräsentiert würde ich gerne als Klasse abbilden und Anfangswerte setzen zu können, da die Messagedefinition auch Startwerte erlaubt.

Meine Idee war also ein Konstrukt wie folgt zu bauen:



struct rosidl_generator_cs__int32__Array
{
       IntPtr data;
       size_t size;
       size_t capacity;

      public rosidl_generator_cs__int32__Array(Int32[ ] _Data)
      {
             	data = Marshal.AllocHGlobal (Marshal.SizeOf (_Data));
		size = (UIntPtr)Marshal.SizeOf (_Data);
		capacity = Size;
		Marshal.StructureToPtr (_Data, data, true);  
      }
}

[StructLayout (LayoutKind.Sequential)]
class <message name>
{
        Int32 member1;
        rosidl_generator_cs__int32__Array member2;
}

Die Instanz der Message übergebe ich danach so an den native code:


[DllImport("librcl.so")]
extern static int rcl_publish(ref rcl_publisher_t publisher, IntPtr ros_message);

public bool PublishMessage<T>(ref T msg)
			where T : class
		{
			IntPtr msg_ptr = Marshal.AllocHGlobal (Marshal.SizeOf (typeof(T)));
			Marshal.StructureToPtr (msg, msg_ptr, true);
			int ret = rcl_publish (ref publisher, msg_ptr);
                        //Some Errorhandling
                }


Aus dem Native Code bekomme ich jedoch an einer Stelle die data auf einen null pointer überprüft immer eine native exception geworfen. Wenn ich die Message nur aus Elementaren Typen aufbaue funktioniert das auch Problemlos.

W
872 Beiträge seit 2005
vor 7 Jahren

Hast Du [In,Out] gesetzt?

L
Lenny Themenstarter:in
95 Beiträge seit 2009
vor 7 Jahren

Hey,
also ich habe jetzt paar Sachen geändert:

  1. Habe ich eine Basisklasse für meine Messages eingeführt damit ich die Methodensignatur für den Nativen Aufruf so ändern konnte:

[DllImport("librcl.so")]
extern static int rcl_publish(ref rcl_publisher_t publisher, [In, Out] MessageBase ros_message);

public bool PublishMessage<T>(ref MessageBase msg)
			where T : MessageBase
		{
			int ret = rcl_publish (ref publisher,  msg);
                        //Some Errorhandling
                }



Hier bekomme ich jetzt aus dem native Code keine Fehlermeldungen mehr. Das [In,Out] hat also funktioniert. Danke 😃

  1. Ich bin jetzt dabei die Messages wieder zu empfangen. Dabei habe ich lustigerweise das Phänomen, dass das für Strings (Die auch nur ein struct mit einem IntPtr und einer Längen Info sind) und elementar Typen funktioniert. Jedoch nicht für Arrays.

Der C# Code dazu:

String:


[StructLayout(LayoutKind.Sequential)]
	public struct rosidl_generator_c__String
	{

		public rosidl_generator_c__String(string _data)
		{
			size = (UIntPtr)_data.Length;
			capacity = (UIntPtr)size +1;
			data = Marshal.StringToHGlobalAnsi (_data);
		}
		public string Data{

			get{return Marshal.PtrToStringAnsi(data);}
		}
		public int Size{
			get{ return (int)size;}
		}
		public int Capacity{
			get{ return (int)capacity; }
		}

		IntPtr data;
		
		UIntPtr size;

		UIntPtr capacity;
 
	}


Für das Array (in dem Fall für floats):


[StructLayout(LayoutKind.Sequential)]
	public struct rosidl_generator_c__primitive_array_float32
	{	
		 public IntPtr Data;
		 UIntPtr Size;
		 UIntPtr Capacity;


	 public rosidl_generator_c__primitive_array_float32(float[] _Data)
	{
		Console.WriteLine (Marshal.SizeOf<float> () * _Data.Length);
		Data = Marshal.AllocHGlobal (Marshal.SizeOf<float>()*_Data.Length);

		Size = (UIntPtr)(_Data.Length);
		Capacity = Size;
		Marshal.Copy (_Data, 0, Data, _Data.Length);
 
	}
	public float[] Array
	{
		get{ return Marshal.PtrToStructure<float[]> (Data);}
			
	}
	public int ArraySize
	{
		get{return (int)Size;}
	}
	public int ArrayCapacity
	{
		get{ return (int)Capacity;}
	}
	}


Die Verwendete Message sieht als C# code so aus:
Diese wird auch beim Aufruf von rcl_publish übergeben


[StructLayout (LayoutKind.Sequential)]
    public class Dummy:MessageBase
    {
        [DllImport ("libtest_msgs__rosidl_typesupport_introspection_c.so")]
        public static extern IntPtr rosidl_typesupport_introspection_c_get_message__test_msgs__msg__Dummy();

        public System.Boolean thisisabool;
        public System.Byte thisisaint8;
        public System.SByte thisiauint8;
        public System.Int16 thisisaint16;
        public System.UInt16 thisisauint16;
        public System.Int32 thisisaint32;
        public System.UInt32 thisisauint32;
        public System.Int64 thisisaint64;
        public System.UInt64 thisisauint64;
        public float thisafloat32;
        public double thisisfloat64;
        public rosidl_generator_c__String thisisastring= new rosidl_generator_c__String();
        public rosidl_generator_c__primitive_array_float32 thisafloat32array = new rosidl_generator_c__primitive_array_float32();
    }

Mein Problem jetzt: Beim String struct steht ein valider IntPtr usw. drin beim Empfangen, bei dem FloatArray steht jedoch bei allen 3 Werten nur 0 drin.

Die Auslese Funktion sieht so aus:


[DllImport("librcl.so")]
		extern static int rcl_take(ref rcl_subscription_t subscription, [In,Out] MessageBase ros_message,[In,Out] rmw_message_info_t message_info);

public T TakeMessage<T>(out bool success)
			where T: MessageBase, new()
		{
			T msg = new T ();
			rmw_message_info_t message_info = new rmw_message_info_t();
			int ret = rcl_take (ref subscription, msg, message_info);
                       //Some Errorhandling and returning the message + setting success parameter
               }