Marshall



  • 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


  • Administrator

    Ich wollte gerade ins Bett, da fiel mir auf, dass man den C# ja deutlich einfacher machen kann 😃

    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
      {
        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(TweetPacket header);
    
        // Die Zwischenfunktion ist gar nicht nötig!
    
        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);
        }
      }
    }
    

    RunTest kann direkt das Objekt annehmen. Der Marshaler macht alles andere selber. Hier ist nun sehr wichtig, dass wir eine Klasse verwenden, da dies sicherstellt, dass es tatsächlich ein Zeiger auf das Objekt ist. Wenn wir für TweetPacket ein struct verwenden würden, würde es das Objekt als Wert übergeben. Zudem ist das ein OneWay-Ticket. Heisst die Daten werden nur zur Funktion hin kopiert aber nicht zurück!

    Ich hoffe mal, dass dich das jetzt nicht noch weiter verwirrt, aber ich konnte es einfach nicht so stehen lassen 🙄

    Grüssli



  • Hallo Dravere.

    Gut erklärt. Manches verstehe ich auch :-). Leider aber noch nicht alles.
    Stell hier einfach noch ein paar Fragen. Vieleicht kommst du ja dazu um sie zu beantworten. Wäre super.

    Zudem ist das ein OneWay-Ticket. Heisst die Daten werden nur zur Funktion hin kopiert aber nicht zurück!

    ?? Will ich ja. Daten rüber kopieren. Wieso sollte da was zurückkommen. Verstehe ich nicht recht.

    2. Mit der Deklaration des byData[1] stehe ich glaub auf dem Schlauch. Ich möchte eine Schnittstelle bereitstellen die es ermöglich die Funktionen in c# einfach zu verwenden. Der der genaue Typ der Daten ist mir also nicht bekannt. Als was muss ich also das byData in meiner c# Klasse deklarieren, dass es allgemein verwendet werden kann. Ich habe schon verstanden dass ich keine generics verwenden kann.
    Aber es muss doch möglich sein, hier einen allgemeinen Datentypen anzugeben. Byte array oder was ähnliches. Und dazu Hilfsfunktionen bereitstellen die mir dann bestimmte Objekte serialisiert um in dieser Variablen ablegen zu können.

    Du hast ja in deinen Beispielen immer direkt den konkretten Typen Tweet verwendet.


  • Administrator

    OrginellerName schrieb:

    Zudem ist das ein OneWay-Ticket. Heisst die Daten werden nur zur Funktion hin kopiert aber nicht zurück!

    ?? Will ich ja. Daten rüber kopieren. Wieso sollte da was zurückkommen. Verstehe ich nicht recht.

    Das war nur als Warnung gedacht, falls du das mal in einem anderen Fall verwenden würdest. Sollte man sich einfach im Hinterkopf behalten.

    OrginellerName schrieb:

    2. Mit der Deklaration des byData[1] stehe ich glaub auf dem Schlauch. Ich möchte eine Schnittstelle bereitstellen die es ermöglich die Funktionen in c# einfach zu verwenden. Der der genaue Typ der Daten ist mir also nicht bekannt. Als was muss ich also das byData in meiner c# Klasse deklarieren, dass es allgemein verwendet werden kann. Ich habe schon verstanden dass ich keine generics verwenden kann.
    Aber es muss doch möglich sein, hier einen allgemeinen Datentypen anzugeben. Byte array oder was ähnliches. Und dazu Hilfsfunktionen bereitstellen die mir dann bestimmte Objekte serialisiert um in dieser Variablen ablegen zu können.

    Du hast ja in deinen Beispielen immer direkt den konkretten Typen Tweet verwendet.

    Also ich empfehle dir, wenn es nicht zu viele unterschiedliche Typen sind, dann verwende jeweils einen konkreten Typ. Du kannst die Funktion in die DLL in C# überladen und damit andere Strukturen aktzeptieren. Der Vorteil davon ist, dass du z.B. die Integrität gewisser Felder besser garantieren kannst. Z.B. die Länge oder irgendwelche Datentypen-Angaben. Falls das aus irgendeinem Grund nicht möglich ist, dann hier noch ein weiteres Beispiel 🙂

    #include <iostream>
    
    namespace test {
    enum type
    {
      string,
      vector2d,
      tweet
    };
    }
    
    struct data_header
    {
      test::type data_type;
      unsigned int data_length;
      char data[1];
    };
    
    struct vector2d
    {
      double x;
      double y;
      double z;
    };
    
    struct tweet
    {
      int timestamp;
      char text[140];
      int text_length;
    };
    
    void print_header(data_header* header)
    {
      std::cout << header->data_type << " - " << header->data_length << "\n";
    }
    
    void process_string(data_header* data)
    {
      std::cout.write(data->data, data->data_length);
    }
    
    void process_vector2d(data_header* data)
    {
      vector2d* vec = reinterpret_cast<vector2d*>(&data->data[0]);
    
      std::cout << '('
        << vec->x << ','
        << vec->y << ','
        << vec->z << ')';
    }
    
    void process_tweet(data_header* data)
    {
      tweet* t = reinterpret_cast<tweet*>(&data->data[0]);
    
      std::cout << t->timestamp << " - " << t->text_length << "\n";
      std::cout.write(t->text, t->text_length);
    }
    
    extern "C" __declspec(dllexport) void process_data(data_header* data)
    {
      print_header(data);
    
      switch(data->data_type)
      {
      case test::string :
        process_string(data);
        break;
      case test::vector2d :
        process_vector2d(data);
        break;
      case test::tweet :
        process_tweet(data);
        break;
      default :
        std::cout << "Unkown data type!";
      }
    
      std::cout << std::endl;
    }
    
    using System;
    using System.Text;
    using System.Runtime.InteropServices;
    
    namespace PInvokeTest
    {
      enum DataType
      {
        String,
        Vector2D,
        Tweet,
      }
    
      [StructLayout(LayoutKind.Sequential)]
      struct DataHeader
      {
        public DataType dataType;
        public int dataLength;
        public byte data;
      }
    
      [StructLayout(LayoutKind.Sequential)]
      struct Vector2D
      {
        public double x;
        public double y;
        public double z;
      }
    
      [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
      struct Tweet
      {
        public int timestamp;
    
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 140)]
        public String text;
    
        public int textLength;
      }
    
      class Program
      {
        [DllImport("NativeLibTest.dll", EntryPoint = "process_data", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
        private static extern void ProcessData(IntPtr data);
    
        // T muss das StructLayoutAttribute haben
        private static void ProcessData<T>(DataType type, T data)
        {
          var header = new DataHeader();
          header.dataType = type;
    
          header.dataLength = Marshal.SizeOf(typeof(T));
    
          var memorySize = Marshal.SizeOf(header) + header.dataLength;
          var memoryPtr = Marshal.AllocHGlobal(memorySize);
    
          Marshal.StructureToPtr(header, memoryPtr, false);
    
          var dataOffset = Marshal.OffsetOf(typeof(DataHeader), "data");
          Marshal.StructureToPtr(data, memoryPtr + dataOffset.ToInt32(), false);
    
          ProcessData(memoryPtr);
    
          Marshal.FreeHGlobal(memoryPtr);
        }
    
        private static void ProcessText(String text)
        {
          var header = new DataHeader();
          header.dataType = DataType.String;
    
          var data = Encoding.ASCII.GetBytes(text);
          header.dataLength = data.Length;
    
          var memorySize = Marshal.SizeOf(header) + data.Length;
          var memoryPtr = Marshal.AllocHGlobal(memorySize);
    
          Marshal.StructureToPtr(header, memoryPtr, false);
    
          var dataOffset = Marshal.OffsetOf(typeof(DataHeader), "data");
          Marshal.Copy(data, 0, memoryPtr + dataOffset.ToInt32(), data.Length);
    
          ProcessData(memoryPtr);
    
          Marshal.FreeHGlobal(memoryPtr);
        }
    
        static void Main()
        {
          const string tweetText = "Tweet, Tweet, Tweet";
    
          ProcessText("Hello world!");
    
          ProcessData(DataType.Tweet, new Tweet()
          {
            text = tweetText,
            textLength = tweetText.Length,
            timestamp = 1234
          });
    
          ProcessData(DataType.Vector2D, new Vector2D()
          {
            x = 13.234,
            y = 122.3,
            z = 0.23
          });
    
          Console.ReadKey(true);
        }
      }
    }
    

    Der Trick dürfte hier Marshal.OffsetOf sein. Fragen? 😃

    Die entscheidenden Funktionen dürften hier in C# ProcessData und ProcessText sein. Das Vorgehen ist eigentlich sehr einfach. Wir reservieren genügend Speicher am Stück für den Header und die Daten. Und die Daten starten halt dort, wo im Header das Feld data liegt. In deinem Fall wäre dies das Feld byData , bzw. dessen eines Element. Ist wirklich keine Hexerei.

    Grüssli



  • Hallo Dravere.

    Ich glaub ich bin zu dumm um das ganze zu kapieren. In meinem c++ DLL kommt nur Müll an.

    Ich habe mir glaube ich zu viel auf einmal vorgenommen. Habe nun mal versucht nur einen String an c++ weiterzuleiten. Nicht mal das klappt.

    #define EXPORT extern "C" __declspec(dllexport)
    
    Export bool FWSendString(char text[140])
    {
    	return true;
    }
    
    [DllImport("Forwarder.dll", EntryPoint = "FWSendString", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Winapi)]
    public static extern bool FWSendString([MarshalAs(UnmanagedType.ByValTStr, SizeConst = 140)] string text);
    
    public void TestForwarder()
    {
        FWSendString("Hallo");
    }
    

    Fehler beim Ausgeben des System.Runtime.InteropServices.MarshalAsAttribute-Attributs -- "Specified unmanaged type is only valid on fields.".



  • Mich haut es von den Socken. Ich habs.

    Also mein eigentliches Problem. *Freu*

    Vielen Dank.

    Das mit dem einzelnen String versenden geht allerdings immer noch nicht. Irgendwas ist da noch falsch.


  • Administrator

    OrginellerName schrieb:

    Ich habe mir glaube ich zu viel auf einmal vorgenommen. Habe nun mal versucht nur einen String an c++ weiterzuleiten. Nicht mal das klappt.

    Vielleicht solltest du auch deine C und C++ Kentnisse etwas auffrischen.
    http://ideone.com/K8SpTI
    Frag dich hierzu z.B. mal, wieso ich an die Funktion einen Zeiger übergeben kann. Und wie muss daher die korrekte Signatur der Funktion sein?

    OrginellerName schrieb:

    [code="cs"][DllImport("Forwarder.dll", EntryPoint = "FWSendString", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Winapi)]
    public static extern bool FWSendString([MarshalAs(UnmanagedType.ByValTStr, SizeConst = 140)] string text);

    Und die CallingConvention ist hier ebenfalls verkehrt. Wie kommst du auf WinAPI? WinAPI bedeutet normalerweise nämlich stdcall. C setzt dagegen auf die cdecl Aufrufkonvention. Solche Dinge müssen unbedingt übereinstimmen!

    Aber freut mich, dass du dein eigentliches Problem gelöst hast.

    Grüssli



  • Wie kommst du auf WinAPI?
    

    Das wird in den Beispielen die ich habe verwendet. Es funktioniert mit dieser DLL auch nur mit WinAPI. Keine Ahnung warum.


Anmelden zum Antworten