Marshall



  • Mir fällt erstmal auf das oben class statt struct steht.
    vllt. ist ein Struct-Member vorm pinvoke nur falsch/gar nicht initialisiert?



  • Marshall ist doch ein OriginellerName oder nicht.
    Nein Spaß bei Seite.

    Marshall habe ich in einem anderen Forum verwendet und habe mich durch automatisches Ausfüllen seitens des Explorers nicht mehr um den Namen gekümmert.

    Sorry.



  • also zunächst mal. es ist egal ob es eine Klasse ist.

    Hier liegt das Problem.

    Das folgende würe funktionieren. Wenn es möglich wäre SizeConst dynamisch festzulegen. Geht aber nicht.

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = ???)] 
    public Byte[] byData;
    

    Keiner mehr eine Idee?



  • Such mal bei der Suchmaschine deiner Wahl nach "c# marshall dynamic array"...



  • Das habe ich schon gemacht.

    Hast du da einen bestimmten Link gefunden. Oder einfach mal so rausgehauen. Denn eine funktionierende Lösung habe ich noch nicht gefunden. 😞



  • Worauf ich hinaus wollte: Hast du die Elemente bei denen du SizeConst=x angegeben hast vor dem pinvoke auch mit new (ebenfalls mit Größe x) initialisiert?



  • Worauf ich hinaus wollte

    ???

    Du hast was mit class und mit struct gemeint nichts von constSize !?

    Später habe ich dann gesagt dass ich bei ConstSize keinen Wert angeben kann da ich den nicht weiß.

    Wie soll ich nun den Wert den ich nicht weiß auch bei new mit angeben???



  • geeky schrieb:

    vllt. ist ein Struct-Member vorm pinvoke nur falsch/gar nicht initialisiert?

    Beim neuen Sachverhalt mit dynamischen Array-Größen, bleibt meines Wissens nur der Weg über IntPtr.


  • Administrator

    Die Fehlermeldung sagt doch schon alles aus? Du hast einem Feld, welches mit SizeConst gekennzeichnet wurde, ein Array mit einer falschen Länge übergeben.

    Das Feld byData ist aber definitiv auch falsch. Der Typ BYTE deutet auf ein Byte hin. IntPtr dagegen ist entweder 4 oder 8 Bytes gross. Ich nehme an mit dem Array am Schluss wird darauf hingedeutet, dass dieses Struct eigentlich ein Header eines grösseren Ganzen ist. Verwende für byData also byte .

    Um dein Vorhaben nun zu realisieren, musst du Speicher für das grössere Ganze reservieren, was du z.B. über Marshal.AllocHGlobal machen kannst. Diesen Speicher musst du natürlich auch wieder freigeben. Danach befüllst du deine Struktur und schreibst sie an den Anfang mit Marshal.StructureToPtr . Vergiss nicht, dass mindestens das erste Byte des Inhalts hinter dem Header/der Struktur, durch diese Funktion überschrieben wird, weil du ja ein byte Feld in der Struktur hast. Wegen Alignment/Padding Fragen bin ich mir nicht ganz sicher, ob man das Feld auch weglassen dürfte.

    Grüssli

    Grüssli



  • Hallo Dravere

    Du scheinst dich in diesem Bereich sehr gut auszukennen. Ich danke dir für deine Antwort. Habe da aber immer noch Verständnisschwierigkeiten.

    ich zeig mal kurz was ich nun gemacht habe und wo ich immer noch nicht weiter weiß

    Also zunächst mal zum Typ des byData in der structur bzw. class.
    du meinst nun statt IntPtr nur ein byte oder ein byte[] ??

    Wenn ich nun ein String versenden möchte:

    public bool SendData(string serviceName, object data)
    {
    
       var error = new CMN_ERROR_MANCLASS();
    
       var targetCount = (uint)targetNames.Count;
    
       // Header füllen
       var sendData = new DM_SEND_DATA_MANCLASS
                                   {
                                       fHighPriority = 0,
                                       dwTargetMachineFlags = MDM_TARGETMACHINES_FLAGS_ENUM.DM_SD_LOCAL,
                                       szService = serviceName,
                                       dwTargetApps = targetCount,
                                   };
    
       // hier zum eigentlichen Problem
       // string in byData verpacken
    
       // Endekennung wird benötigt
       var strData = data.ToString() + "\0";
    
       // speicher reservieren
       var hglobal = Marshal.AllocHGlobal(strData.Length);
    
       // string in intptr kopieren
       Marshal.StructureToPtr(strData, hglobal, true);
    
       // Größe mit übergeben
       sendData.dwDataSize = (uint)strData.Length;
    
       // jetzt muss irgendwie hglobal in sendData.byData richtig ??
    
       // Daten versenden
       var success = DMSendApplicationData(sendData, error);
    
       // speicher freigeben
       Marshal.FreeHGlobal(hglobal);
    
       return success;
    }
    

    Bei Marshal.StructureToPtr(strData, hglobal, true); gibts schon den ersten Fehler:
    The specified structure must be blittable or have layout information.
    Parameter name: structure

    Wäre super wenn du mir nochmal helfen könntest.


  • Administrator

    Nein, Nein, Nein ... Wie kommst du darauf StructureToPtr plötzlich mit dem String zu verwenden? 😕
    Lies dir doch bitte auch ein wenig die verlinkte Dokumentation durch.

    Wir haben leider bisher von dir nur kleine Stücke an Informationen bekommen, wie die Schnittstelle aussieht. Könntest du vielleicht diesbezüglich etwas mehr Informationen liefern? Wie sieht die Schnittstelle aus? Was genau willst du machen? Was musst du übergeben und wie? Bei P/Invoke muss man sehr genau arbeiten, sonst kommt nur Mist raus.

    Grüssli



  • Vllt. wäre in diesem konkreten Fall doch C++/CLI als Zwischenschicht besser geeignet?


  • Administrator

    Th69 schrieb:

    Vllt. wäre in diesem konkreten Fall doch C++/CLI als Zwischenschicht besser geeignet?

    Ich sehe den Vorteil für C++/CLI aus den bisher gelieferten Informationen nicht. Du musst auch in C++/CLI sauber arbeiten. Sogar noch mehr als bei P/Invoke. Und du hast dann eine zusätzliche DLL, welche womöglich gar nicht notwendig wäre? Und ob er überhaupt die DLL verwenden kann? Hat er die Headerfiles? Lib-Files?

    Grüssli



  • Hallo Dravere

    Sorry für mein Unverständnis.

    Also ich sag euch mal die Details. Es geht um eine Funktion mit der man Daten senden kann an eine weitere Applikation die sich wiederum mit einem Server connected hat.

    Die Funktion mit der man Daten senden kann nennt sich DMSendApplicationData die 2 Strukturen entgegennimmt. Einmal die Daten selber und zum zweiten mal eine Fehlerstruktur in der bei Misserfolg die Fehler codiert sind.

    Es geht mir im eigentlichen nur um die Struktur der zu sendenden Daten.
    (Die im Kommentar stehenden Variablen sind die entsprechenden Variablen in c++.)
    Die habe ich euch schon anfangs gezeigt. Hat sich nun etwas verändert.

    Bei den Arrays hat das new gefehlt. Drum auch der Fehler den ich anfangs genannt habe. Und UInt32 anstatt bool.

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 1)]
        public class DM_SEND_DATA_MANCLASS
        {
            //BOOL fHighPriority;
            UInt32 fHighPriority;
    
            //char szService[MAX_DM_SERVICE_NAME + 1];
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = (MDM_LENGTH_DEFINES.MAX_DM_SERVICE_NAME + 1))]
            public String szService;
    
            //DWORD dwTargetMachineFlags;
            [MarshalAs(UnmanagedType.U4)]
            public MDM_TARGETMACHINES_FLAGS_ENUM dwTargetMachineFlags;
    
            //DWORD dwTargetMachines;
            public UInt32 dwTargetMachines;
    
            //DM_SD_TARGET_MACHINE dmTargetMachine[MAX_DM_OHIO_MACHINES];
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.LPStruct, SizeConst = MDM_LENGTH_DEFINES.MAX_DM_OHIO_MACHINES)]
            public DM_SD_TARGET_MACHINE_MANCLASS[] dmTargetMachine = new DM_SD_TARGET_MACHINE_MANCLASS[MDM_LENGTH_DEFINES.MAX_DM_OHIO_MACHINES];
    
            //DWORD dwTargetApps;
            public UInt32 dwTargetApps;
    
            //DM_SD_TARGET_APP dmTargetApp[MAX_DM_OHIO_APPLICATIONS];
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.LPStruct, SizeConst = MDM_LENGTH_DEFINES.MAX_DM_OHIO_APPLICATIONS)]
            public DM_SD_TARGET_APP_MANCLASS[] dmTargetApp = new DM_SD_TARGET_APP_MANCLASS[MDM_LENGTH_DEFINES.MAX_DM_OHIO_APPLICATIONS];
    
            //DWORD dwDataSize;
            public UInt32 dwDataSize;
    
            //BYTE byData[1];
            public IntPtr byData;
        }
    

    Mir geht es nun darum dass ich eine Funktion bereitstelle der ich Daten übergeben kann vom Typ Object. Diese sollen im byteArray byData übergeben werden.
    In c++ ist byData als Byte Array mit der größe 1 deklariert. Sprich nichts anderes als ein Byte Pointer. In dwDataSize steht die Anzahl der Bytes die wirklich übergeben werden.

    Es ist nu kein Problem die "Headerdaten" zu füllen. Mir geht es nun rein um den Datentyp von byData und wie ich hier Daten übergebe. In c++ gelingt das mit memcpy. In c# weiß ich allerdings nicht weiter.


  • Administrator

    OrginellerName schrieb:

    Mir geht es nun darum dass ich eine Funktion bereitstelle der ich Daten übergeben kann vom Typ Object. Diese sollen im byteArray byData übergeben werden.

    Das kannst du vergessen. Ein Object lässt sich nicht einfach in ein Byte-Array umwandeln. Du musst hier eine Serialisierung festlegen.

    OrginellerName schrieb:

    In c++ ist byData als Byte Array mit der größe 1 deklariert. Sprich nichts anderes als ein Byte Pointer.

    Ich weiss jetzt nicht, ob das einfach nur unsauber ausgedrückt war, aber das stimmt definitiv nicht. Es ist kein Zeiger! Es ist ein Array von einem Byte. Aus diesem kann allerdings dann ein Pointer gewonnen werden. Der Unterschied ist aber immens wichtig. Ein Zeiger besteht aus 4 oder 8 Bytes. Das Byte-Array mit einem Element aus genau einem einzigen Byte.

    OrginellerName schrieb:

    Es ist nu kein Problem die "Headerdaten" zu füllen. Mir geht es nun rein um den Datentyp von byData und wie ich hier Daten übergebe. In c++ gelingt das mit memcpy. In c# weiß ich allerdings nicht weiter.

    Siehe oben. Es gibt keine Möglichkeit ein Object einfach in ein Byte-Array umzuwandeln. Das geht übrigens mit Non-POD Strukturen in C++ ebenfalls nicht.

    Mir wäre übrigens noch wichtig, wenn ich auch noch die Deklaration der C++ Funktion DMSendApplicationData sehen könnte. Auch würde mich die genaue Deklaration der C++ Struktur DM_SEND_DATA_MANCLASS interessieren. Du hast nämlich z.B. ein Pack = 1 drin. Ist das wirklich gewollt? Hast du die C++ Struktur ebenfalls dementsprechend definiert?

    Und was verbirgt sich eigentlich hinter MDM_TARGETMACHINES_FLAGS_ENUM , DM_SD_TARGET_MACHINE_MANCLASS und DM_SD_TARGET_APP_MANCLASS ?

    Von den bisherigen Daten hier mal ein sehr vereinfachtes Beispiel:

    #include <iostream>
    
    struct header
    {
      int size;
      char data[1];
    };
    
    #define MY_EXPORT extern "C" __declspec(dllexport)
    
    MY_EXPORT void run_test(header* data)
    {
      std::cout << data->size << " - ";
      std::cout.write(data->data, data->size);
      std::cout << std::endl;
    }
    
    using System;
    using System.Text;
    using System.Runtime.InteropServices;
    
    namespace SharpTest
    {
      [StructLayout(LayoutKind.Sequential)]
      public class Header
      {
        private int m_size;
        private byte m_data;
    
        public int Size
        {
          get { return m_size; }
          set { m_size = value; }
        }
    
        public byte Data
        {
          get { return m_data; }
          set { m_data = value; }
        }
      }
    
      public class Program
      {
        [DllImport("NativeLibTest.dll", EntryPoint = "run_test", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
        private static extern void RunTest(IntPtr header);
    
        public static void Main(string[] args)
        {
          const string text = "Hello world!";
    
          var data = Encoding.ASCII.GetBytes(text);
          var header = new Header()
          {
            Size = data.Length
          };
    
          var headerSize = Marshal.SizeOf(typeof(Header));
    
          var ptr = Marshal.AllocHGlobal(headerSize + data.Length - 1);
          Marshal.StructureToPtr(header, ptr, false);
    
          Marshal.Copy(data, 0, ptr + 4, data.Length);
    
          RunTest(ptr);
    
          Marshal.FreeHGlobal(ptr);
    
          Console.ReadKey(true);
        }
      }
    }
    

    Grüssli



  • Zunächst einen großen Dank für deine Hilfe Dravere.

    Ein Object lässt sich nicht einfach in ein Byte-Array umwandeln. Du musst hier eine Serialisierung festlegen.

    Ok. Im Grunde benötige auch kein "object" sondern nur eine "struct". Und hierfür habe ich nun eine Serialisierung. Hier bekomme ich ein byteArray zurück. Frage mich nun aber wie ich das wie in deinem Beispiel nun an m_data das nur ein byte ist zuweisen soll.

    Ich weiss jetzt nicht, ob das einfach nur unsauber ausgedrückt war, aber das stimmt definitiv nicht. Es ist kein Zeiger! Es ist ein Array von einem Byte.

    Klar. Recht hast du war von mir nicht korrekt ausgedrückt. Wollte eigentlich damit nur sagen, dass nur das erste Byte gehalten wird.

    Gerne kann ich auch alle Strukturen hier anzeigen. Zunächst noch zum Pack. Wußte bis jetzt noch gar nicht was das heißt. :(. Ich habe hier ein paar Beipiele die aus der selben c++ dll die bereits gewandelt wurden. Hier steht überall Pack=1.

    c++ Code:

    BOOL WINAPI DMSendApplicationDataW(
        LPDM_SEND_DATA_STRUCTW lpdmSendData,
        LPCMN_ERRORW lpdmError);
    
    typedef struct tagDM_SEND_DATA_STRUCTW
    {
        BOOL                  fHighPriority;                                                            
        WCHAR                 szService[MAX_DM_SERVICE_NAME + 1];                     
        DWORD                 dwTargetMachineFlags;                                         
        DWORD                 dwTargetMachines;                                                                                 
        DM_SD_TARGET_MACHINEW dmTargetMachine[MAX_DM_OHIO_MACHINES];                                                           
        DWORD                 dwTargetApps;                                                                                 
        DM_SD_TARGET_APPW     dmTargetApp[MAX_DM_OHIO_APPLICATIONS];                              
        DWORD                 dwDataSize;                                                    
        BYTE                  byData[1];                                                               
    } DM_SEND_DATA_STRUCTW, FAR *LPDM_SEND_DATA_STRUCTW;
    
    typedef struct tagDM_SD_TARGET_MACHINEW
    {
        BOOL        fServer;                                
        BOOL        fLocal;                                          
        WCHAR       szMachineName[MAX_COMPUTERNAME_LENGTH + 1];                      
    } DM_SD_TARGET_MACHINEW, FAR *LPDM_SD_TARGET_MACHINEW;
    
    typedef struct tagDM_SD_TARGET_APPA
    {
        char        szAppName[MAX_DM_APP_NAME + 1];                                 
    } DM_SD_TARGET_APPA, FAR *LPDM_SD_TARGET_APPA;
    

    Und noch die Fehlerstruktur. Die ich bereits als "gemapptes" (oder wie sagt man dazu) Beispiel habe.

    typedef struct tagCMN_ERRORA
    {
        DWORD       dwError1,
                    dwError2,
                    dwError3,
                    dwError4,
                    dwError5;
        char        szErrorText[512];
    } CMN_ERRORA, *PCMN_ERRORA, **PPCMN_ERRORA, FAR *LPCMN_ERRORA;
    

    Also das hier die fertige Struktur bzw. class die ich als Vorlage hatte.

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 1)]
    public class CMN_ERROR_MANCLASS
    {
        [MarshalAs(UnmanagedType.U4)] 
        public /*UInt32*/ MDM_DMERROR_ENUM dwError1;
    
        public UInt32 dwError2;
        public UInt32 dwError3;
        public UInt32 dwError4;
        public UInt32 dwError5;
    
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 512)] 
        public String szErrorText;
    }
    

    MDM_TARGETMACHINES_FLAGS ist im übrigen nur ein enum. Den ich selber definiert habe. In c++ ist es nur ein DWORD. Aber das habe ich schon mehrmals getestet. Sollte so auch funktionieren.



  • Achso. Das eigentliche was ich nun in byData verpacken möchte ist eine struct mit 2 int Werten.

    struct MyStruct
    {
       int id;
       int value;
    };
    

    Und hier noch die Serialisierung für die struct:

    public static byte[] ConvertToByteArray<T>(T obj) where T : struct
    {
        IntPtr ptr = IntPtr.Zero;
        try
        {
            var size = Marshal.SizeOf(typeof(T));
            ptr = Marshal.AllocHGlobal(size);
            Marshal.StructureToPtr(obj, ptr, true);
            var bytes = new byte[size];
            Marshal.Copy(ptr, bytes, 0, size);
            return bytes;
        }
        finally
        {
            if (ptr != IntPtr.Zero)
                Marshal.FreeHGlobal(ptr);
        }
    }
    

    Hoffe ich hab nun nichts vergessen.


  • Administrator

    Sorry für die späte Antwort. Habe aktuell viel um die Ohren.

    Zu Pack:
    http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.structlayoutattribute.pack.aspx
    Standardmässig ist der Wert 0 und somit wird der für die Plattform übliche Wert genommen. Wenn du in C++ keine speziellen Kompileroptionen oder Anweisungen bei der Definition der Struktur verwendest, solltest du den Wert auf jedenfall bei 0 belassen. Andere Werte sollte man nur verwenden, wenn man weiss, was man macht.

    Zum Rest:
    Mein Beispiel sollte eigentlich schon fast die Lösung präsentieren, nicht? Gut, es fehlen noch die einen oder anderen Felder. Aber die scheinst du ja schon selbst herausgefunden zu haben. Es kann vielleicht noch von Vorteil sein, dass du ebenfalls struct in C# verwendest. Es gibt nur ein paar wenige Anwendungsfälle, wo man bei P/Invoke mit class einen Vorteil erhält. In den meisten Fällen sollte man besser struct verwenden.

    Eine möglicherweise etwas einfachere Lösung dürfte übrigens noch das folgende sein:

    #include <iostream>
    
    struct header
    {
      int size;
      char data[1];
    };
    
    struct tweet
    {
      char text[140];
      int textlength;
      int timestamp;
    };
    
    #define MY_EXPORT extern "C" __declspec(dllexport)
    
    MY_EXPORT void run_test(header* data)
    {
      std::cout << data->size << " - ";
    
      tweet* t = reinterpret_cast<tweet*>(&data->data[0]);
      std::cout << t->timestamp << " - ";
      std::cout << t->textlength << " - ";
      std::cout.write(t->text, t->textlength);
    
      std::cout << std::endl;
    }
    
    using System;
    using System.Text;
    using System.Runtime.InteropServices;
    
    namespace SharpTest
    {
      [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
      public struct Tweet
      {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 140)]
        private String m_text;
    
        private int m_textLength;
        private int m_timeStamp;
    
        public String Text
        {
          get { return m_text; }
          set
          {
            m_text = value ?? String.Empty;
            m_textLength = m_text.Length;
          }
        }
    
        public int TimeStamp
        {
          get { return m_timeStamp; }
          set { m_timeStamp = value; }
        }
      }
    
      [StructLayout(LayoutKind.Sequential)]
      public class TweetPacket
      {
        // Hier ist class z.B. von Vorteil, da ich das Feld direkt initialisieren kann.
        // Mit struct wäre der nachfolgende Code nicht möglich.
        private int m_size = Marshal.SizeOf(typeof(Tweet));
        private Tweet m_tweet;
    
        public Tweet Tweet
        {
          get { return m_tweet; }
          set { m_tweet = value; }
        }
      }
    
      public class Program
      {
        [DllImport("NativeLibTest.dll", EntryPoint = "run_test", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
        private static extern void RunTest(IntPtr header);
    
        private static void RunTest(TweetPacket packet)
        {
          int size = Marshal.SizeOf(packet);
          IntPtr dataPtr = Marshal.AllocHGlobal(size);
    
          try
          {
            Marshal.StructureToPtr(packet, dataPtr, false);
            RunTest(dataPtr);
          }
          finally
          {
            Marshal.FreeHGlobal(dataPtr);
          }
        }
    
        private static int TimeStampNow()
        {
          return (int)(DateTimeOffset.UtcNow - new DateTimeOffset(1970, 1, 1, 0, 0, 0, new TimeSpan())).TotalSeconds;
        }
    
        public static void Main(string[] args)
        {
          const string text = "Hello world!";
    
          var packet = new TweetPacket()
          {
            Tweet = new Tweet()
            {
              Text = text,
              TimeStamp = TimeStampNow()
            }
          };
    
          RunTest(packet);
    
          Console.ReadKey(true);
        }
      }
    }
    

    Generics gehen in C# übrigens nicht. Du kannst also kein Packet<TData> oder sowas machen. Marshal.StructureToPtr wird dir da leider eine Exception an den Kopf werfen.

    Ich denke von hier aus, solltest du den Rest selber erarbeiten können? Falls du noch weitere Fragen hast, nur zu! Ich werde aber womöglich nächste Woche nur selten oder gar nicht Antworten können. Muss an eine verblödete Fortbildung...

    Grüssli



  • Danke nochmals für deine Hilfe. Leider fehlt mir aber das wichtigste.

    Und zwar immer noch meine byData. Du wandelst das ganze in deinem Beispiel in ein Intptr weil das deine Funktion Runtest erfordert.

    Bei mir ist das ja aber nicht der Fall. Meine Funktion erfordert eine struct die wiederum das bytearray erfordert.

    Keine Ahnung wie ich das machen soll.


  • Administrator

    Deine Funktion erwartet ein LPDM_SEND_DATA_STRUCTW und das ist nichts anderes als ein tagDM_SEND_DATA_STRUCTW* und ist somit ein Zeiger auf diese Struktur. Dadurch ist es genau das Gleiche.

    Ein Zeiger ist zudem ein Zeiger. Zur Laufzeit macht das keinen Unterschied. Der Typ eines Zeigers ist nur zur Kompilezeit von Relevanz. Wenn daher eine Funktion einen Zeiger erwartet, dann kannst du gerne IntPtr verwenden. Das ist kein Problem.

    Mein C++ Code ist da nur etwas vereinfacht, aber funktioniert genau gleich, wie das was du uns bisher gezeigt hast. Ich habe in der Struktur header ebenfalls ein Byte-Array mit einem Element.

    Kann es sein, dass dir nicht ganz klar ist, wie diese Strukturen in C++ abgebildet werden? Du hast am Ende nur ein einziges Byte-Array. Und in den ersten N Bytes hast du die Header-Struktur drin. In deinem Fall das tagDM_SEND_DATA_STRUCTW . Ab dem Feld byData kommen die restlichen Daten im Byte-Array. byData dient nur als Hilfsmittel, um den Header zu überspringen und keine Zeigeroperationen durchführen zu müssen. Schau dir diesbezüglich Zeile 25 meines letzten C++ Codes an.

    Grüssli


Anmelden zum Antworten