Marshall



  • Hallo zusammen.

    Ich bin gerade dran eine struct die in c++ definiert ist in c# zu marshallen.
    komme aber gerade nicht weiter.

    Meine Klasse in c# sieht wie folgt aus:

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 1)]
        public class DM_SEND_DATA_MANCLASS
        {
            //BOOL fHighPriority;
            public bool 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;
    
            //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;
    
            //DWORD dwDataSize;
            public UInt32 dwDataSize;
    
            //BYTE byData[1];
            public IntPtr byData;
        }
    

    wobei der Kommentar immer die entsprechende Deklaration in c++ darstellt.

    Von dieser Klasse erstelle ich nun eine Instanz fülle sie mit werte und übergebe diese an eine gewrappte Funktion.

    Beim Aufruf dieser Funktion erhalte ich folgenden Fehler:

    Type could not be marshaled because the length of an embedded array instance does not match the declared length in the layout.

    Meine Frage an euch. Ist in der oben dargestellten Klasse noch ein Fehler. Habe ich da zum c++ Typ irgendwas falsch gewandelt?



  • Zerlegen wir die Frage mal und konzentrieren uns auf einzelne Punkte.

    Ich würde mal sagen die DWORDS sind mit UInt32 korrekt.

    Wie verhält es sich mit:

    BYTE byData[1];

    Hier denke ich das ich mit IntPtr byData; nicht ganz richtig liege.

    Was wäre hier richtig?



  • Äh.. Nur zum Vertständnis..

    Marshall = OriginellerName??

    Wenn ja, brauche ich wohl kaum zu sagen, dass es Fatal ist und wohl kaum einen anderen Weg gibt soviel Verwirrung zu stiften, als sich als Unregistrierter jedes mal einen neunen Namen zu geben.. 😉



  • 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.


Log in to reply