none
PInvoke mit Strukturen, die Strings beinhalten RRS feed

  • Frage

  • Ich habe eine C-Funktion in einer Dll, die eine Struktur mit einem String erwartet. Ich weiß nicht, wie ich es hinschreiben muss, damit, die Länge des Strings variabel ist.

    Erster Versuch:
        [StructLayout(LayoutKind. Sequential, Pack = 4)]
    
        public
     struct
     MyStruct
    
        {
    
            public
     int
     x;
    
            public
     int
     y;
    
            [MarshalAs(UnmanagedType.LPStr, SizeConst=100)]
    
            public
     string
     comment;
    
        };
    
    
    
    
    Hat die offensichtliche Wirkung, dass maximal 100 Zeichen übertragen werden. Alle Zeichen bis dahin kommen an.
    Dazu die erste Frage: Wird immer der gesamte Speicher, in diesem Fall 100 Zeichen + terminale Null alloziert?

    Zweite Variante habe ich folgende:
        [StructLayout(LayoutKind.Sequential, Pack = 4)]
    
        public
     struct
     MyStruct
    
        {
    
            public
     int
     x;
    
            public
     int
     y;
    
            [MarshalAs(UnmanagedType.LPStr)]
    
            public
     string
     comment;
    
        };
    
    
    
    
    Nur das erste Zeichen wird übertragen, alle anderen abgeschnitten. In C ist das eben ein char* an der Stelle.

    Was ich eigentlich will: Alle Zeichen übertragen ohne Angabe einer maximalen Länge. Notfalls variable Länge bis zu einer sehr großen Maximalanzahl.
    Wie geht das?

    Vielen Dank schon im voraus ...
    Mittwoch, 10. Februar 2010 16:50

Antworten

  • Hallo,

    das funktioniert wie nach dem Gesagten mit:

        class Program
        {
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)]
            public struct MyStruct
            {
                public int x;
                public int y;
                // [MarshalAs(UnmanagedType.LPStr)]
                public string comment;
            };
    
            [DllImport("mydll.dll", CharSet = CharSet.Ansi /* ist Standard: CallingConvention = CallingConvention.StdCall */ )]
            static public extern void GetComment(MyStruct p, StringBuilder comment, Int32 maxCommentLength);
    
            static void Main(string[] args)
            {
                MyStruct s = new MyStruct() { x = 4711, y = 4712, comment = "Alle meine Entchen... ÄÖÜäöüß" };
    
                StringBuilder comment = new StringBuilder(256 /* s.comment.Length + 1 */);
    
                GetComment(s, comment, comment.Capacity);
                Console.WriteLine("'{0}' => '{1}'", s.comment, comment);
            }
        }
    

    Gruß Elmar
    Donnerstag, 11. Februar 2010 14:01
    Beantworter

Alle Antworten

  • Hallo,

    wenn Du immer wchar_t, ergo Unicode hast, so wäre das UnmanagedType.LPWStr .
    Im Standard (ohne jede Auszeichnung) wird bereits
    UnmanagedType.LPTStr verwendet,
    was auf einer Unicode Plattform - ausgenommen Windows 9x bei .NET 2.0 mittlerweile alle
    unterstützten Plattformen -, ebenfalls als Unicode gemarshallt wird.

    Allerdings macht mich der Satz stutzig:
    Nur das erste Zeichen wird übertragen, alle anderen abgeschnitten.
    denn das lässt vermuten, dass Du mit Ansi-Strings arbeitest.
    Denn ein Nebeneffekt wäre dort, dass bei Ascii-Zeichen jedes zweite Byte
    ein Nul-Byte ist, was die Standard-Stringroutinen als Ende erkennen würden.

    Dazu und für weitere Variationen siehe:
    Standardmäßiges Marshalling für Zeichenfolgen

    Gruß Elmar
    Mittwoch, 10. Februar 2010 18:20
    Beantworter
  • mein Fehler: In C ist das eben ein char* an der Stelle. Also Ansi. Habs im Programm schon richtig an der Stelle. Daran liegt es also nicht.
    Gut beobachtet ...
    Mittwoch, 10. Februar 2010 18:27
  • Nochmnal zur Klarstellung: Ich habe es hier nur falsch geschrieben gehabt, ich habe es richtig mit char gemacht. Ansi/Unicode ist wohl nicht das Problem. Trotzdem bleibt das Phänomen bestehen und ich suche jemanden, der meine Fragen beantworten kann.
    Donnerstag, 11. Februar 2010 11:16
  • Hallo,

    dann wirst Du weitere Informationen liefern müssen,
    wie die Definition nativen Funktion ggf. auch der Funktionen,
    die Du beim Aufruf verwendet.

    Denn bei Interop muß man schon wissen was man tut.
    Eine Lektüre des Interop Marshalling Abschnittes der MSDN
    kann dabei helfen.

    Gruß Elmar
    Donnerstag, 11. Februar 2010 11:33
    Beantworter
  • "Bei Interop muß man schon wissen, was man tut."
    Wie wahr und ich bin dabei gerade es zu lernen. Allerdings habe ich in der MSDN nur gefunden, dass es so eigentlich gehen sollte, über das von mir unerwartete Verhalten habe ich nichts gefunden.

    Hier also weiterer Code:
    [DllImport(@"MyCppDll.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
    static public extern void GetComment(MyStruct p, StringBuilder comment, Int32 maxCommentLength);
    

    Und in C++

    #pragma pack(push, 4)
    struct MyStruct
    {
      int x;
      int y;
      char* comment;
    };
    #pragma pack(pop)
    
    
    
    MYCPP_API void __stdcall GetComment (MyStruct p, char* comment, DWORD maxStringLength)
    {
      if (p.comment == NULL || *p.comment == '\0')
      {
        *comment = '\0';
        return;
      }
    
      strncpy (comment, p.comment, maxStringLength);    
      comment[maxStringLength-1] = '\0';
    }
    

    Donnerstag, 11. Februar 2010 12:11
  • Hallo,

    das funktioniert wie nach dem Gesagten mit:

        class Program
        {
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)]
            public struct MyStruct
            {
                public int x;
                public int y;
                // [MarshalAs(UnmanagedType.LPStr)]
                public string comment;
            };
    
            [DllImport("mydll.dll", CharSet = CharSet.Ansi /* ist Standard: CallingConvention = CallingConvention.StdCall */ )]
            static public extern void GetComment(MyStruct p, StringBuilder comment, Int32 maxCommentLength);
    
            static void Main(string[] args)
            {
                MyStruct s = new MyStruct() { x = 4711, y = 4712, comment = "Alle meine Entchen... ÄÖÜäöüß" };
    
                StringBuilder comment = new StringBuilder(256 /* s.comment.Length + 1 */);
    
                GetComment(s, comment, comment.Capacity);
                Console.WriteLine("'{0}' => '{1}'", s.comment, comment);
            }
        }
    

    Gruß Elmar
    Donnerstag, 11. Februar 2010 14:01
    Beantworter
  • Hallo Rechenelf,

    Schau Dir die folgende Diskussion an. Vielleicht findest Du etwas dass Dir weiter helfen kann.

    http://stackoverflow.com/questions/1223690/pinvoke-error-when-marshalling-struct-with-a-string-in-it

    Grüße,

    Robert

    Donnerstag, 11. Februar 2010 14:17
    Moderator
  • Vielen Dank, manchmal geht es einfacher als man denkt.

    Diese Richtung funktioniert nun.

    Jetzt bleibt noch die Frage, wenn ich das ConstSize-Attribut angebe, ob dann immer der ganze Speicher alloziert wird. Als nächstes möchte ich nun einer Funktion ähnlich zu GetComment nun den Comment verändern (vergrößern). Der Marshaller kann ja nicht riechen, wie viele Bytes ich schreiben möchte, daher habe ich Angst, Speicherschmierer zu verursachen. Wie mache ich das?
    Freitag, 12. Februar 2010 18:17
  • Hallo,

    für eine Methode SetComment(MyStruct s) kannst Du die Zeichenkette s.Comment direkt zuweisen,
    ohne eine Größe vorzugeben. Der Marshaller ermittelt bei C-Style (nullterminierten)
    Zeichenketten die Länge selbst, und erstellt daraus einen .NET String.
    Entsprechend zu Marshal. PtrToStringAnsi (oder eine der anderen Ableitungen)

    Grundsätzlich gilt:
    Alle Angaben von MarshalAs (auch SizeConst) gelten nur für die InterOp Aufrufe.
    Innerhalb des managed Codes ist es immer ein System.String; der immer
    unveränderlich (immutable) ist und es wird nur soviel Speicher belegt,
    wie die Zeichenkette benötigt.

    Nur beim InterOp Aufruf stellt der Marshaller entsprechend viel Speicher bereit.
    Beim StringBuilder wird die Anzahl Zeichen (da Unicode: Bytes = Zeichen * 2) allokiert.
    Bei Ansi wird ein entsprechender zusätzlicher temporärer Puffer (Bytes = Zeichen) erzeugt.

    SizeConst ist für solche Strukturen gedacht, die mit festen Character-Arrays arbeiten,
    wie z. B. LOGFONT mit lfFaceName , um eine richtige Speicherausrichtung zu haben.
    Und dort würde beim Interop entsprechend Platz reserviert.

    Weitere Details findest Du unter:
    Marshaling between Managed and Unmanaged Code

    Zum Schluß, wie auch dort erläutert:
    Optimal wäre gleich in Unicode zu programmieren, da das einige Optimierungen erlaubt.

    Gruß Elmar

    Freitag, 12. Februar 2010 20:03
    Beantworter