IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C# Discussion :

Problème alignement mémoire ou chevauchement de donnée


Sujet :

C#

  1. #1
    Membre à l'essai
    Inscrit en
    Juin 2009
    Messages
    35
    Détails du profil
    Informations forums :
    Inscription : Juin 2009
    Messages : 35
    Points : 20
    Points
    20
    Par défaut Problème alignement mémoire ou chevauchement de donnée
    Bonjour à tous,

    Dans mon programme, je dois créer un objet puis le transmettre vers un équipement pour impression.
    Le problème arrive quand je souhaite créer cet objet en question. j'obtiens le message suivant :

    Une exception non gérée du type 'System.TypeLoadException' s'est produite dans System.Windows.Forms.dll

    Informations supplémentaires*: Impossible de charger le type 'PrintBuildObject', car il contient un champ objet à l'offset '9' qui n'est pas correctement aligné ou qui est chevauché par un champ non objet.
    Exemple du code définissant l'objet capricieux :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
     
    public enum IMS_eGraphicObject
    {
        OBJECT_TEXT=0,
        OBJECT_LINE=1,
        OBJECT_BOX=2,
        OBJECT_LOGO=3,
        OBJECT_MAG=4,
        OBJECT_SERIAL=5,
        OBJECT_CLESS=6,
    }
     
    [StructLayout(LayoutKind.Sequential)]
    public class FormatGraphic
    {
        public byte objectType;
        public UInt16 X;
        public UInt16 Y;
        public IMS_eGraphicObject objectId;
    }
     
    [StructLayout(LayoutKind.Explicit)]
    public class PrintBuildObject
    {
        [FieldOffset(0)]
        public FormatGraphic Format;
        [FieldOffset(9)]
        public PrintBuildDataText PrintBuildDataText;
        [FieldOffset(9)]
        public PrintBuildDataLine PrintBuildDataLine;
        [FieldOffset(9)]
        public PrintBuildDataBox PrintBuildDataBox;
        [FieldOffset(9)]
        public PrintBuildDataLogo PrintBuildDataLogo;
        [FieldOffset(9)]
        public EncodeBuildDataMag EncodeBuildDataMag;
    }
    Merci d'avance pour vos lumières.

  2. #2
    Membre habitué
    Profil pro
    Inscrit en
    Juillet 2006
    Messages
    135
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Juillet 2006
    Messages : 135
    Points : 179
    Points
    179
    Par défaut
    Bah tous tes champs sauf le premier sont à
    [FieldOffset(9)]

  3. #3
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 485
    Points
    5 485
    Par défaut
    Il n'est pas possible de faire se chevaucher des champs de type référence, tout simplement.

    Le mieux à faire ici est d'avoir un champ de type Object et des propriétés effectuant un cast à la volée.

    Enfin, si IMS_eGraphicObject est une classe, la référence occupera 8 octets et non 4 sur un OS 64 bits. De toute façon, puisque FormatGraphic est une classe et non une structure, sa référence occupera 4 ou 8 octets et FieldOffset devrait donc avoir 8 en argument, et non 9. D'ailleurs, FormatGraphic ne pèse pas 9 octets : ses membres couvrent bien 9 octets (13 sur un OS 64 bits) mais une classe possède en plus un header de 10 octets.

  4. #4
    Membre à l'essai
    Inscrit en
    Juin 2009
    Messages
    35
    Détails du profil
    Informations forums :
    Inscription : Juin 2009
    Messages : 35
    Points : 20
    Points
    20
    Par défaut
    Les champs avec un fieldoffset à 9 correspondent à une union. Il doivent donc tous faire référence au même emplacement mémoire.
    A moins que je me trompe.

  5. #5
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 485
    Points
    5 485
    Par défaut
    Oui mais comme je te le disais ce n'est pas possible avec des types référence, il faudrait que le CLR ajoute des casts à la volée derrière chaque lecture/assignation d'un des membres pour garantir la sécurité des types.

    Donc pas d'union avec des types référence.

  6. #6
    Membre à l'essai
    Inscrit en
    Juin 2009
    Messages
    35
    Détails du profil
    Informations forums :
    Inscription : Juin 2009
    Messages : 35
    Points : 20
    Points
    20
    Par défaut
    Suite à ces problèmes, j'essayais de regardé les tailles de mes données :
    D'ailleurs, FormatGraphic ne pèse pas 9 octets : ses membres couvrent bien 9 octets (13 sur un OS 64 bits) mais une classe possède en plus un header de 10 octets.
    Ma donnée FormatGraphic ne prend effectivement pas 9 octects mais 12.
    J'ai donc modifié le fieldoffset avec cette dernière valeur. Et je me suis trouvé avec un problème de cast invalide. Ce qui revient surment à ce que tu disais DonQuiche.

    Maintenant, j'ai remplacé mon semblant d'union par un type 'object' mais une fois que j'ai converti ma structure en pointeur je ne retrouve qu'une partie de mes données.

    Je perd un peu la tête avec ce truc.

  7. #7
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 485
    Points
    5 485
    Par défaut
    Citation Envoyé par Gab02 Voir le message
    Maintenant, j'ai remplacé mon semblant d'union par un type 'object' mais une fois que j'ai converti ma structure en pointeur je ne retrouve qu'une partie de mes données.
    Si nous pouvions avoir quelques éclaircissements, je pense que nous pourrons fournir quelques explications.

  8. #8
    Membre à l'essai
    Inscrit en
    Juin 2009
    Messages
    35
    Détails du profil
    Informations forums :
    Inscription : Juin 2009
    Messages : 35
    Points : 20
    Points
    20
    Par défaut
    En gros j'ai pris ceci
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
     
    [StructLayout(LayoutKind.Explicit)]
    public class PrintBuildObject
    {
        [FieldOffset(0)]
        public FormatGraphic Format;
        [FieldOffset(9)]
        public PrintBuildDataText PrintBuildDataText;
        [FieldOffset(9)]
        public PrintBuildDataLine PrintBuildDataLine;
        [FieldOffset(9)]
        public PrintBuildDataBox PrintBuildDataBox;
        [FieldOffset(9)]
        public PrintBuildDataLogo PrintBuildDataLogo;
        [FieldOffset(9)]
        public EncodeBuildDataMag EncodeBuildDataMag;
    }
    pour arriver à ça

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    [StructLayout(LayoutKind.Sequential)]
    public class PrintBuildObject
    {
        public FormatGraphic Format;
        public Object data;
    }
    aprés avoir rempli la structure comme il faut, je crée un pointeur sur la stucture PrintBuildObject avec StuctureToPtr. En mémoire je retrouve bien les données du FormatGraphic mais pas celles de Object .

    Merci d'avance pour ton aide.

  9. #9
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 485
    Points
    5 485
    Par défaut
    Premier problème : PrintBuildObject est une classe et non une structure. Marshal.StructureToPtr n'est pas fait pour ça.

    Seoncd problème : Marshal.StructureToPtr effectue une copie de la structure spécifiée dans l'espace mémoire désigné par le pointeur en argument. Si après cela tu modifies la structure d'origine, la copie n'est pas modifiée. Attention, donc, StructureToPtr ne crée pas un pointeur vers la structure mais vers une copie de cette structure.

    La raison derrière cela est simple à comprendre : imaginons qu'on déclare un champ structure dans une méthode, l'espace de cette structure est alors alloué sur la pile. Puis, on crée un pointeur vers la structure et la fonction se termine, l'espace de la structure n'est donc plus alloué mais on a toujours un pointeur vers cette adresse. On se retrouverait donc avec un pointeur en ballade vers un espace de la pile qui va être à nouveau utilisé par qui sait quelle fonction. En forçant le développeur à faire une copie dans un espace non-managé préalablement initialisé, on évite le pire.

    Maintenant, si on veut un pointeur vers une classe, on peut utiliser GCHandle.

  10. #10
    Membre confirmé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mars 2011
    Messages
    269
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mars 2011
    Messages : 269
    Points : 460
    Points
    460
    Par défaut
    Bonjour,

    J'ajouterai que le champs "data" contient une référence, il faut donc marshaller à la main ce champs.
    D’ailleurs il y a un post a ce sujet il y a quelque jour, mais j'arrive pas a remettre la main dessus.

  11. #11
    Membre à l'essai
    Inscrit en
    Juin 2009
    Messages
    35
    Détails du profil
    Informations forums :
    Inscription : Juin 2009
    Messages : 35
    Points : 20
    Points
    20
    Par défaut
    En reprenant le tout dans la journée, j'ai modifié quelque truc.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public enum IMS_eGraphicObject
    {
        OBJECT_TEXT=0,
        OBJECT_LINE=1,
        OBJECT_BOX=2,
        OBJECT_LOGO=3,
        OBJECT_MAG=4,
        OBJECT_SERIAL=5,
        OBJECT_CLESS=6,
    }
     
    [StructLayout(LayoutKind.Sequential)]
    public stuct FormatGraphic
    {
        public byte objectType;
        public UInt16 X;
        public UInt16 Y;
        public IMS_eGraphicObject objectId;
    }
     
    [StructLayout(LayoutKind.Sequential)]
    public stuct PrintBuildObject
    {
        public FormatGraphic Format;
        public PrintBuildDataText PrintBuildDataText;
    }
    Ceci fonctionne, mes données sont alignées correctement après le StructureToPtr du PrintBuildObject et j'arrive à mes fins. Cependant je perds l'intéret de mon union. Je ne vois vraiment pas comment mettre cela en place avec les remarques plus haut.

  12. #12
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 485
    Points
    5 485
    Par défaut
    Voilà un début duquel t'inspirer :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public struct PrintBuildObject
    {
        public FormatGraphic Format;
        private Object _data;
     
        public PrintBuildDataText PrintBuildDataText
        {
            get { return _data as PrintBuildDataText; }
            set { _data = value; }
        }
     
        public PrintBuildDataLine PrintBuildDataLine
        {
            get { return _data as PrintBuildDataLine; }
            set { _data = value; }
        }
    }

  13. #13
    Membre à l'essai
    Inscrit en
    Juin 2009
    Messages
    35
    Détails du profil
    Informations forums :
    Inscription : Juin 2009
    Messages : 35
    Points : 20
    Points
    20
    Par défaut
    Exemple intéressant, je teste ça lundi. Mais à 1ere vue je pense me retrouver dans le même cas que mon message du 15/09/2011 à 15h30 où je ne vois pas les bonnes infos en mémoire après un Marshal.StrutureToPtr().

  14. #14
    Membre à l'essai
    Inscrit en
    Juin 2009
    Messages
    35
    Détails du profil
    Informations forums :
    Inscription : Juin 2009
    Messages : 35
    Points : 20
    Points
    20
    Par défaut
    J'ai testé ton exemple mais comme je le pensais je retombe sur un cas expliqué plus haut.

    Je pense que le sujet manque d'info.

    La partie du code concernant les différentes structures fait parti d'un wrapper permettant de commander un équipement par un dll c/c++.

    C/C++
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
     
    typedef enum eGraphicObject
    {
        OBJECT_TEXT=0,
        OBJECT_LINE=1,
        OBJECT_BOX=2,
        OBJECT_LOGO=3,
        OBJECT_MAG=4,
        OBJECT_SERIAL=5,
        OBJECT_CLESS=6,
    }
     
    typedef struct _sFormatGraphic
    {
        uint8_t objectType;
        uint16_t X;
        uint16_t Y;
        eGraphicObject objectId;
    }sFormatGraphic;
     
    typedef struct _sPrintBuildDataText
    {
       uint8_t IC;
       uint16_t rotO;
       uint16_t rotC;
       uint16_t L;
       char T[128];
       uint16_t zX;
       uint16_t zY;
    }
     
    typedef union uGraphicData
    {
        sPrintBuildDataText PrintBuildDataText;
        sPrintBuildDataLine PrintBuildDataLine;
        sPrintBuildDataBox PrintBuildDataBox;
        sPrintBuildDataLogo PrintBuildDataLogo;
        sEncodeBuildDataMag EncodeBuildDataMag;
    };
     
     
    typedef struct _sPrintBuildObject
    {
        sFormatGraphic Format;
        uGraphicData Data;
    }sPrintBuildObject;
    le but étant de transcrire ces structures en C# : voici ce que j'ai fais mais qui ne colle pas vraiment.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
     
    public enum IMS_eGraphicObject
    {
        OBJECT_TEXT=0,
        OBJECT_LINE=1,
        OBJECT_BOX=2,
        OBJECT_LOGO=3,
        OBJECT_MAG=4,
        OBJECT_SERIAL=5,
        OBJECT_CLESS=6,
    }
     
    [StructLayout(LayoutKind.Sequential)]
    public stuct FormatGraphic
    {
        public byte objectType;
        public UInt16 X;
        public UInt16 Y;
        public IMS_eGraphicObject objectId;
    }
     
    [StructLayout(LayoutKind.Sequential)]
    public stuct PrintBuildObject
    {
        public FormatGraphic Format;
        public PrintBuildDataText PrintBuildDataText;
    }
    Ensuite, le but est de remplir la structure PrintBuildObject puis de la marshaller (obtenir un pointeur sur la structure) afin de récupèrer les données dans la dll C/C++.

    J'espère que j'ai fourni les infos nécessaires.

  15. #15
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 485
    Points
    5 485
    Par défaut
    Ah ! Tu aurais dû nous donner plus tôt les définitions C++, nous aurions gagné du temps.

    En effet, je n'avais pas réalisé que tu cherchais à envoyer ces données vers une biblio C. Donc, maintenant que toutes tes structures sont bien définies comme structures et non comme classes, tu dois pouvoir réutiliser ce que tu avais fait en premier :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    [StructLayout(LayoutKind.Explicit)]
    public class PrintBuildObject
    {
        [FieldOffset(0)]
        public FormatGraphic Format;
        [FieldOffset(9)]
        public PrintBuildDataText PrintBuildDataText;
        [FieldOffset(9)]
        public PrintBuildDataLine PrintBuildDataLine;
        [FieldOffset(9)]
        public PrintBuildDataBox PrintBuildDataBox;
        [FieldOffset(9)]
        public PrintBuildDataLogo PrintBuildDataLogo;
        [FieldOffset(9)]
        public EncodeBuildDataMag EncodeBuildDataMag;
    }
    Si je ne m'abuse, le compilateur acceptera ceci tant que tous les types impliqués dans l'union sont des structures ne contenant que des types valeurs. Au pire, si ce n'était pas le cas, j'ai deux autres solutions à te proposer.

    Reste la question du bon FieldOffset à mettre et, là, on est un peu ennuyés. 9 est correct si l'énumération prend 4 octets. Mais en pratique cette taille varie selon le compilateur C/C++ utilisé. A tester, donc. Si tu obtiens des valeurs farfelues, tu sauras quoi faire...

  16. #16
    Membre à l'essai
    Inscrit en
    Juin 2009
    Messages
    35
    Détails du profil
    Informations forums :
    Inscription : Juin 2009
    Messages : 35
    Points : 20
    Points
    20
    Par défaut
    Ah ! Tu aurais dû nous donner plus tôt les définitions C++, nous aurions gagné du temps.
    Je confirme.

    Par contre, qu'entends tu par type valeur ?

    Si je comprend bien, je ne pas avoir champ string,par exemple, dans une de ces structures. Mais la première structure composant l'union, PrintBuildDataText, j'ai un string que je converti en byte avant d'envoyer la structure globale au C/C++.

    Le détail de la structure PrintBuildDataText est donnée dans mon message précédent.

  17. #17
    Expert confirmé Avatar de DonQuiche
    Inscrit en
    Septembre 2010
    Messages
    2 741
    Détails du profil
    Informations forums :
    Inscription : Septembre 2010
    Messages : 2 741
    Points : 5 485
    Points
    5 485
    Par défaut
    Les types en dotnet sa divisent en deux catégories :
    * Les types références, c'est à dire les classes, ce que l'on échange par référence : string, tableaux, toute classe.
    * Les types valeurs, que l'on échange par copie : les structures et les types primitifs (int, char, etc).

    Cela dit, les chaînes de caractères sont peut-être acceptées dans ce cas précis, à voir, je ne suis malheureusement pas un fin connaisseur des problèmes d'inteopérabilité avec le C/C++. Le mieux est de tester.

    Au pire, si ça ne marche pas, les deux solutions alternatives ont grosso modo la tronche suivante :
    * Outrepasser les unions : par exemple, faire un type PrintBuildObject_Text ne contenant que Format et PrintBuildDataText. Et importer la fonction supposée recevoir cet objet en spécifiant PrintBuildObject_Text en argument (on accolera donc pour le coup le suffixe _Text à cette variante). On fera donc autant de couples type/fonction que de champs dans l'union.
    * Ne définir qu'un seul champ dans PrintBuildObject, Format, mais attribuer une taille suffisante à la structure (via StructLayoutAttribute.Size) pour recevoir les données de l'union : les données seront donc bien là mais innaccessibles directement. Puis passer par Marshal.StructureToPtr pour créer un pointeur vers une copie, incrémenter le pointeur de 9 octets et récupérer la structure membre de l'union via Marshal.PtrToStructure.

    La première a déjà été mise en oeuvre par mes soins, je peux donc garantir qu'elle fonctionne. Et je ne vois pas pourquoi la seconde échouerait. Evidemment, tout ça est à prendre en dernier recours : si l'on peut utiliser la solution originale, à base d'union C#, c'est préférable.

  18. #18
    Membre à l'essai
    Inscrit en
    Juin 2009
    Messages
    35
    Détails du profil
    Informations forums :
    Inscription : Juin 2009
    Messages : 35
    Points : 20
    Points
    20
    Par défaut
    Bon je reviens sur ce sujet pour donner ma solution.

    * Les types références, c'est à dire les classes, ce que l'on échange par référence : string, tableaux, toute classe.
    * Les types valeurs, que l'on échange par copie : les structures et les types primitifs (int, char, etc).
    En me basant sur ces 2 points, j'ai repris la structure PrintBuildDataText qui contient un tableau de taille fixe.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    MarshallAs[UnmanagedType.LPArray, SizeConst=10)]
    public ushort [] Text
    J'ai remplacé ceci par
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    public fixed ushort Text[10]
    Pour passer la structure global PrintBuildObject au code C/C++, je récupère l'adresse de cette manière
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    PrintBuildObject Obj = new PrintBuildObject();
    ...//Je saute l'initialisation des divers champs
    PrintBuildObject pObj = &Obj;
    Pour mieux comprendre je vous laisse lire ce lien.

    Merci DonQuiche pour tes lumières.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 12
    Dernier message: 05/07/2013, 11h29
  2. Enorme base de données, problème de mémoire
    Par kenzoshin dans le forum Hibernate
    Réponses: 5
    Dernier message: 07/12/2012, 16h12
  3. problème alignement données
    Par jponline dans le forum Langage
    Réponses: 1
    Dernier message: 14/12/2007, 09h06
  4. Problème de mémoire avec BDE
    Par Machuet dans le forum Bases de données
    Réponses: 3
    Dernier message: 13/07/2004, 10h11
  5. Problème de mémoire Affichage images
    Par Repti dans le forum C++Builder
    Réponses: 6
    Dernier message: 29/03/2004, 20h06

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo