PInvoke von struct



  • Guten Morgen,

    leider komme ich bei einem Aufruf einer fremden dll nicht weiter und hoffe nun hier Hilfe finden zu können.

    Die Headerdatei der mir zur Verfügung gestellten dll sieht folgendermaßen aus:
    (Auszug)

    ...
    typedef int WV_CONNECT_ID ;
    ...
    typedef struct {
       TCHAR             PatientName  [WV_PATIENT_NAME_SIZE] ;
       TCHAR             PatientID    [WV_PATIENT_ID_SIZE] ;
       TCHAR             BedLabel     [WV_BED_LABEL_SIZE];
       TCHAR             CareUnit     [WV_CARE_UNIT_SIZE];
       TCHAR             FileName     [WV_FILE_NAME_SIZE];
       TCHAR             IPAddress    [WV_IP_ADDRESS_SIZE];
       TCHAR             MulticastIP  [WV_MULTICAST_IP_SIZE];
       TCHAR             DeviceType   [WV_DEVICE_TYPE_SIZE];
       WV_OPERATING_MODE DeviceStatus ;
       WV_CONNECT_ID     ConnectID ;  // 0 if not connected
    } WV_BED_DESCRIPTION ;
    

    hier ist die Funktion der header-Datei:

    IMPORT_FUNCTION int WINAPI WvListBeds(const TCHAR *pServerName, const TCHAR *pUserName, const TCHAR *pPassword, WV_BED_LIST *pBedList, int *pNumberOfBeds) ;
    

    Folgendes steht in meiner WrapperDLL:
    (Auszug)

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct WV_BED_DESCRIPTION
        {
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 26)]
            public string PatientName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
            public string PatientID;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
            public string BedLabel;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
            public string CareUnit;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
            public string FileName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
            public string IPAddress;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
            public string MulticastIP;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
            public string DeviceType;
            public WV_OPERATING_MODE DeviceStatus;
            public int ConnectID;
        }
    

    Im Prinzip funktioniert es fast, nur scheine ich für ConnectID eine Speicheradresse statt eines Wertes zu bekommen.
    Hat jemand einen Tipp, woran das liegen kann? Habe ich einen Denkfehler?
    Hier ein Funktionsaufruf:

    [SuppressMessage("Microsoft.Interoperability", "CA1401:PInvokesShouldNotBeVisible"), DllImportAttribute("WvAPI.dll", BestFitMapping = false, ThrowOnUnmappableChar = true, CharSet = CharSet.Ansi, EntryPoint = "WvListBeds")]
            public static extern int WvListBeds([InAttribute()] [MarshalAs(UnmanagedType.LPStr)] string pServerName, [InAttribute()] [MarshalAs(UnmanagedType.LPStr)] string pUserName, [InAttribute()] [MarshalAs(UnmanagedType.LPStr)] string pPassword, ref WV_BED_LIST pBedList, ref int pNumberOfBeds);
    

    Für jede Hilfe bin ich echt dankbar.

    Liebe Grüße

    Wedgewood



  • Hallo,

    wie ist WV_OPERATING_MODE denn definiert (als enum?)?



  • Hallo Th69,

    das hatte ich leider vergessen zu erwähnen:
    Das stimmt WV_OPERATING_MODE ist als enum definiert.


  • Administrator

    1. Stimmen die Werte der Konstanten überein? (Stringlänge)
    2. Wird die Native-DLL wirklich als ANSI kompiliert, heisst wird TCHAR wirklich durch char ersetzt und nicht wchar_t ?
    3. Was ist eine WV_BED_LIST ? Schliesslich rufst du deine Funktion mit dieser Struktur auf und nicht mit der von dir beschriebenen.
    4. Du weisst, dass es Standardwerte gibt für das "Marschalling" und man bei einem Attribute den Zusatz Attribute weglassen kann? 😉



  • Hallo Dravere,
    also zu 1:
    Die Werte der Konstanten stimmen überein, sind aus dem Header übernommen.
    Zu 2:
    Leider liegt mir der ursprüngliche Quelltext nicht vor, nur dll .h und .lib.
    Da es aber mit CharSet.Unicode oder CharSet.Auto nicht funktioniert, muss es wohl so sein, hoffe ich... 😉
    Zu 3:
    Entschuldigung mein Fehler

    public struct WV_BED_LIST
        {
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 25, ArraySubType = UnmanagedType.Struct)]
            public WV_BED_DESCRIPTION[] WvBeds;
        }
    

    Zu 4:
    Du meinst, statt InAttribute nur In zu verwenden? Stimmt, das macht es übersichtlicher... Danke

    Alle TChar kann ich korrekt auslesen. Sowohl Name als auch alles andere sind plausibel...
    Es hakt bei DeviceStatus, da kommt immer 0 raus (was falsch aber egal ist...)
    und vor allem bei ConnectID, da sollte entweder 0 oder eine vierstellige Zahl kommen. Beginnend bei 1000 und dann inkrementierend.
    Ach ja, als zusätzliches "Bonbon" stimmen die TChar nur beim ersten Element.
    Bei jeden n+1 Element werden n*2 Zeichen am Anfang abgeschnitten.
    Aus Müller, Peter wird ller, Peter, dann würde Meier, Lisa zu r, Lisa und so fort...

    Bitte hab noch eine gute Idee 🙂

    Wedgewood


  • Administrator

    Hast du irgendwelche Informationen darüber, wie die Strukturen in der Native-DLL gepackt sind? Heisst wie das Alignment aussieht?

    Oder probier allenfalls mal sowas:

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    public struct WV_BED_DESCRIPTION
    {
     ...
    }
    

    Und zu den Standardwerten meine ich, dass z.B. jeder Parameter einer Funktion automatisch das InAttribute hat. String Parameter werden automatisch als UnmanagedType.LPTStr übergeben, was mit CharSet.Ansi zu UnmanagedType.LPStr wird.

    Zudem wäre es interessant zu wissen, mit welcher Konvention die Funktion eigentlich aufgerufen werden möchte. Winapi oder Cdecl? Oder was anderes?



  • Leider liegt mir kein Quellcode der Fremd-DLL vor.
    Dummerweise hatte ich vergessen, den Funktionsaufruf reinzuschreiben.
    Das habe ich jetzt nachgeholt, Winapi ist es in diesem Fall.

    Folgende Zeilen aus dem Header können vielleicht bei der Fehlersuche auch helfen:

    #if !defined(WVDLLIMPLEMENTATION)
    #define IMPORT_FUNCTION  extern "C"
    

    Danke schon mal, dass Du Dir die Zeit nimmst.

    Wedgewood



  • Welche Werte enthält denn das enum WV_OPERATING_MODE (in dem C-Code)?

    Ich tippe darauf, daß im C-Code nur 2 Byte (oder sogar nur 1 Byte) statt 4 für das enum reserviert sind, so daß dann die ConnectID auf falsche Speicherwerte "zeigt".

    Du kannst in C# beim Enum selber den Datentyp angeben. Probiere mal

    enum WV_OPERATING_MODE : short // oder ushort (je nach Werte halt)
    {
      // ...
    }
    

    Du kannst dann mal mittels der Marshal.SizeOf-Methode die Größe der Struktur bestimmen (ob sich diese dann auch korrekt um 2 Byte verringert).



  • Vielen Dank, das werde ich nachher mal probieren.
    Leider kann ich erst nach 22:00 wieder an das Netzwerk, um das zu probieren.
    Antwort folgt sofort nach dem Test.
    Schon mal Danke


  • Administrator

    Geht nicht darum, ob du den Quellcode hast. Gibt es in der Header-Datei irgendwo ein #pragma pack ? Oder kennst du die Kompiler-Optionen, mit welcher die DLL erstellt wurde?

    Ansonsten würde ich es wirklich mal mit dem Pack = 1 ausprobieren. Meistens wird nämlich ein Alignment auf 4 Bytes angenommen. Dein erster Member hat 26 Bytes, die nächste korrekte Position ist bei Byte 28. Das sind genau 2 Bytes verschoben, was genau die zwei fehlenden Buchstaben sind.

    Ebenfalls interessant könnte sein, ob die DLL 32 oder 64 Bit ist? Und als was läuft die C# Assembly?

    Ansonsten könnte auch mal helfen, das Ganze in ein eigenes kleines Projekt auszulagern. Schreib dir eine eigene kleine C DLL, welche das Verhalten simuliert mit einer Dummy-Funktion, welche dir Dummy-Werte zurückgibt. Entsprechendes C# Projekt dazu und dann halt rumpröbeln. Kannst du das Problem nachbauen? Wenn nicht, wo sind die Unterschiede? Usw.



  • Hallo Dravere,

    aber beim ersten Array-Element stimmen laut Wedgewood ja die Texte, nur beim 2. (und folgenden) Array-Element sind diese verschoben...



  • Dann hatte ich Dich missverstanden. Ja, es gibt es:

    #pragma pack( push, EnterWvAPI )
    #pragma pack( 1 )
    

    und am Ende

    #pragma pack( pop, EnterWvAPI )
    

    Ich bin wirklich soo dankbar für Eure Hilfe.
    So ein Schlamassel wie dieses passiert wohl, wenn man mit zu wenig C/C++ Erfahrung an so ein Problem gesetzt wird.
    Toll finde ich, dass ich so noch etwas lerne...
    Nochmals danke


  • Administrator

    Th69 schrieb:

    aber beim ersten Array-Element stimmen laut Wedgewood ja die Texte, nur beim 2. (und folgenden) Array-Element sind diese verschoben...

    Ja, genau. Das erste ist korrekt und hat eine Länge von 26 Bytes. Das zweite Element startet bei einem Alignment von 4 Bytes allerdings erst bei Byte 28. In der Native-DLL wird aber ein Alginment von 1 Byte gesetzt. Somit ist das zweite Element in der Managed DLL um 2 Bytes nach hinten verschoben.

    @Wedgewood,
    Freut mich, dass ich helfen konnte.



  • Dann hatte ich wohl

    Wedgewood schrieb:

    Alle TChar kann ich korrekt auslesen. Sowohl Name als auch alles andere sind plausibel...
    Es hakt bei DeviceStatus, da kommt immer 0 raus (was falsch aber egal ist...)
    und vor allem bei ConnectID, da sollte entweder 0 oder eine vierstellige Zahl kommen. Beginnend bei 1000 und dann inkrementierend.
    Ach ja, als zusätzliches "Bonbon" stimmen die TChar nur beim ersten Element.
    Bei jeden n+1 Element werden n*2 Zeichen am Anfang abgeschnitten.
    Aus Müller, Peter wird ller, Peter, dann würde Meier, Lisa zu r, Lisa und so fort...

    falsch verstanden. 😕

    Ich habe es so verstanden, daß beim ersten Array-Element sowohl PatientName, PatientId, BedLabel etc. korrekt sind - und nur eben ab DeviceStatus falsch. Und dann eben ab dem 2. Array-Element Name, Id, BedLabel etc. um 2 Zeichen verschoben.

    Aber wenn es jetzt funktioniert, ist ja gut so.


  • Administrator

    @Th69,
    Achso, jetzt verstehe ich, wo die ganze Verwirrung herkommt. Du hast Wedgewood schon richtig verstanden, denke ich, aber auch was ich gesagt habe war korrekt, aber womöglich etwas missverständlich, wenn ich es jetzt unter diesem Kontext nochmals lese.

    Der Punkt ist ja, dass nur zwischen dem ersten und zweiten Member ("PatientName" & "PatientID") Füllbytes reinkommen bei einem 4 Byte Alignment. Zwischen den anderen Membern von WV_BED_DESCRIPTION sind keine Füllbytes nötig, da sie immer an der "korrekten" Position sind. Somit ist "PatientName" im ersten Element des Arrays korrekt. "PatientID" wird allerdings bereits im ersten Element um 2 Bytes verschoben sein. Von den anderen Membern hat er nur gesagt, dass sie "plausibel" seien, daher nahm ich an, dass ihm die 2 Bytes Verschiebung der anderen Membern im ersten Array-Element nicht aufgefallen ist.

    Im zweiten Array-Element ist dann der Inhalt von "PatientName" um 2 Bytes verschoben, alle anderen Member des zweiten Array-Element dürften bereits um 4 Bytes verschoben sein, was dann ebenfalls auf den Member "PatientName" des dritten Array-Elements zutrifft. Usw. usf.

    Verständlicher?



  • 😃 Es ist vollbracht 😃
    @Dravere, @Th69, Euch beiden gilt mein ganzer Dank. Es funktioniert nun.
    Des Rätsels Lösung: pack=1 👍
    Dravere, Dein Tipp war also genau der richtige.
    Interessant und beeindruckend finde ich, wie schnell Du eine Ferndiagnose gemacht hast.
    Zwar hatte ich mich über pragma... gewundert. Aber nachdem ich in zwei Büchern nichts gefunden habe, habe ich es als irrelevant ignoriert.
    So kann man sich täuschen. Und wieder etwas gelernt.

    Ich danke Euch beiden nochmals und wünsche Euch ein sonniges Wochenende

    Liebe Grüße

    Wedgewood


Anmelden zum Antworten