Convertir un type en tableau de Bytes

Arnosin - 20 août 2012 à 10:01
cs_Jack Messages postés 14006 Date d'inscription samedi 29 décembre 2001 Statut Modérateur Dernière intervention 28 août 2015 - 22 août 2012 à 11:51
Bonjour,
Je voudrais convertir une variable (de type défini) en tableau de bytes.
Pour cela j'utilise la fonction "CopyMemory Lib "kernel32" Alias "RtlMoveMemory"" (merci Jack).
Ca marche, mais ça me renvoie le contenu de la mémoire, c'est à dire plus d'octets que ce qui est déclaré dans le type...

Public ty_6002
    Pad As Byte
    DataChkS As Long
End Type
Public M6002 As ty_6002
Private aOutputBytes()  As Byte
Private Declare Sub CopyMemory Lib "kernel32" _
                    Alias "RtlMoveMemory" ( _
                    pDst As Any, _
                    pSrc As Any, _
                    ByVal ByteLen As Long)

Private Sub Sendm6002()
M6002.Pad = &HAA
M6002.DataChkS = &HF0F0F0F0

ReDim aOutputBytes(LenB(M6002) - 1)
Call CopyMemory(aOutputBytes(0), ByVal VarPtr(M6002), LenB(M6002))
For i = 0 To UBound(aOutputBytes)
    Mytxt = Mytxt + "Byte(" + CStr(i + 1) + ")" + _
       vbTab + Hex(SendMessge(i)) + vbCrLf
Next i
WS.SendData aOutputBytes
End Sub


Ma variable M6002 a une longueur de 5 octets (byte+Long), mais il y a 8 octets de réservés en mémoire :
len(M6002)->5
lenB(M6002)->8

Du coup, le tableau envoyé est celui-ci :
Byte(1) AA
Byte(2) 0
Byte(3) 0
Byte(4) 0
Byte(5) F0
Byte(6) F0
Byte(7) F0
Byte(8) F0

alors que je souhaite envoyer :
Byte(1) AA
Byte(2) F0
Byte(3) F0
Byte(4) F0
Byte(5) F0

Comment est ce que je peux créer un tableau de bytes sans ces octets parasites (pour moi)? Est-ce qu'il faut changer l'allias? Est-ce qu'une autre fonction existe?

Merci d'avance pour vos réponses,

11 réponses

cs_loulou69 Messages postés 672 Date d'inscription mercredi 22 janvier 2003 Statut Membre Dernière intervention 2 juin 2016 1
20 août 2012 à 11:41
bonjour

Le compilateur fait rentrer chaque objet d'uns structure dans un mot de la machine. Sur une machine 64 bits, un champ 8bit (byte) prendra 4 octets.

Donc pour respecter une suite d'octet comme tu souhaite il faudra définir

ReDim aOutputBytes(5)
aOutputBytes(0) = &HAA
Dim DataChks As Long
DataChks = &HF0F0F0F0
Call CopyMemory(aOutputBytes(1), ByVal VarPtr(DataChks), 4)
je vois bien dans les espions de VB6 sur le tableau aOutputBytes

Byte(1) AA (170 en décimal)
Byte(2) F0 (240 en décimal)
Byte(3) F0
Byte(4) F0
Byte(5) F0
0
NHenry Messages postés 15110 Date d'inscription vendredi 14 mars 2003 Statut Modérateur Dernière intervention 6 avril 2024 159
20 août 2012 à 12:38
Bonjour,

Regardes aussi cette discussion concernant VB6 et CopyMemory.

---------------------------------------------------------------------
[list=ordered][*]Pour poser correctement une question et optimiser vos chances d'obtenir des réponses, pensez à lire le règlement CS, celui-ci pour bien poser votre question ou encore celui-ci pour les PFE et autres exercices.[*]Quand vous postez un code, merci d'utiliser la coloration syntaxique (3ième icône en partant de la droite : ).[*]En VB.NET pensez à activer Option Explicit et Option Strict (propriété du projet) et à retirer l'import automatique de l'espace de nom Microsoft.VisualBasic (onglet Références dans les propriétés du projet).[*]Si votre problème est résolu (et uniquement si c'est le cas), pensez à mettre "Réponse acceptée" sur le ou les messages qui vous ont aidés/list
---
Mon site
0
cs_Jack Messages postés 14006 Date d'inscription samedi 29 décembre 2001 Statut Modérateur Dernière intervention 28 août 2015 79
20 août 2012 à 15:48
Oui, les Bytes prennent autant de place qu'un Long, c'est vrai.
Je m'en suis aperçu, mais je n'ai pas trouvé la raison première.

La solution de Loulou est du bricolage pour redécaler les données, mais dans ce cas, le tableau de bytes ne pourra pas être recollé dans le type + cas particulier qui colle à la définition mais qui ne saurait être généralisé à d'autres types.

Vala
Jack, MVP VB
NB : Je ne répondrai pas aux messages privés

Le savoir est la seule matière qui s'accroit quand on la partage (Socrate)
0
cs_Jack Messages postés 14006 Date d'inscription samedi 29 décembre 2001 Statut Modérateur Dernière intervention 28 août 2015 79
20 août 2012 à 16:02
D'où sort le SendMessge(i) ?

Si c'est une erreur de saisie (puisque cette variable ne sert pas au transfert) ET à fortiori quand on travaille avec les APIs, je te conseille très fortement de mettre Option Explicit en tête de toutes tes pages de code.
Ce Option Explicit est automatiquement inséré lors de la création d'une nouvelle page de code si l'option "Déclaration obligatoire des variables" est cochée.

Cette option rend donc obligatoire le dimensionnement de chaque variable.
Cela peut paraitre lourd à gérer, mais cela fiabilise et facilite la programmation.
Fiabilise, car à chaque fois que tu te sers d'une variable, cela te permet de te poser les questions "Est-elle dimensionnée ET où ?" et "Est-elle du bon type ?"
Facilite, car en tapant les 2 ou 3 premières lettres de la variable + en appuyant sur Ctrl-Espace (intellisence), le nom se complètera tout seul, ou proposera la liste des fonctions/variables commençant par.
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
Merci pour votre aide !

La solution de Loulou est intéressante, mais ça va être du bricolage car j'ai pleins des messages de types différents à envoyer. Et le problème ne se pose par que pour les bytes, mais aussi pour les doubles ou les tableaux...

Dsl pour le SendMessage(i), j'avais crée ce tableau pour faire des tests, et j'ai oublié de le supprimer avant de copier mon code. En faite :
SendMessage = aOutputBytes


Merci pour le conseil Option Explicit[color=black].
C'est vrai que j'oublie souvent de le mettre, mais je dimensionne toujours mes variables
0
cs_Jack Messages postés 14006 Date d'inscription samedi 29 décembre 2001 Statut Modérateur Dernière intervention 28 août 2015 79
21 août 2012 à 00:07
Pourquoi le fait qu'un Byte occupe 4 octets (au lieu d'1) te préoccupe t-il ?
Dans les précédents échanges, je t'avais proposé de remonter ce tableau de bytes transmis par Winsock vers une variable de même type (structure) que celle utilisée dans l'interface client.
Pourquoi essayes-tu de déchiffrer le contenu du tableau ?

Je viens de faire l'essai en mettant des Double dans la structure, cela fonctionne.

Quant aux tableaux, bien sûr il faut savoir de quel type ils sont, mais là aussi, je viens de faire l'essai avec un tableau de Long (0 à 9) et il n'y aucun problème, MAIS il faut prendre des précautions :
Si tu essayes de faire rentrer un tableau dans un tableau, il faut impérativement que les dimensions aient été définies avant (comme on l'a fait pour aOutputBytes), sinon tu vas taper dans des adresses mémoires qui ne sont pas attribuées et là, plantage sauvage du compilateur.
Il faut donc,
# soit calculer le nombre d'éléments de ce tableau en connaissant la longueur des données transmises (UBound+1) auquel tu retrancheras la longueur des variables 'fixes' et que tu diviseras par la longueur du type de variable utilisée (Long, Byte, Single=4, Double=8 etc)
Bien entendu, le type String n'est pas utilisable puisque ce type n'a pas de longueur prédéfinie.
# Soit se faire transmettre, avec le tableau de données, le nombre d'items dans ton tableau.
Une fois que tu auras ce nombre d'items, tu sauras faire un ReDim du tableau avant de faire le CopyMemory pour transférer le tableau reçu vers la variable typée/structurée.

Le code du soir :
Déclaration de ton type/structure :
Private Type InputStructure
    maVar1      As Double
    maVar2      As Byte
    maVar3      As Long
    monArray()  As Long
End Type

Les procédures de 2 transferts :
    Dim r       As Long
    Dim NbVal   As Long
    
    Debug.Print "----------------------------------------------------"
    ' --- Emission
    NbVal = 2000     ' Nbre d'éléments dans le tableau

    With oInputDatas
        ReDim .monArray(0 To NbVal)
        For r = 0 To NbVal
            .monArray(r) = r
        Next r
        .maVar1 = 12345.6789
        .maVar2 = 111
        .maVar3 = 1122334455
    End With
    ' Prépare un tableau de même longueur (qui commence à l'index 0)
    ReDim aOutputBytes(LenB(oInputDatas) - 1)
    ' Transfert des données
    Call CopyMemory(ByVal VarPtr(aOutputBytes(0)), _
                    ByVal VarPtr(oInputDatas), _
                    LenB(oInputDatas))

    ' --- Réception
    If UBound(aOutputBytes) <= 0 Then Exit Sub
    Dim oRecup  As InputStructure
    ReDim oRecup.monArray(0 To NbVal)
    ' Transfert des données
    Call CopyMemory(ByVal VarPtr(oRecup), _
                    ByVal VarPtr(aOutputBytes(0)), _
                    UBound(aOutputBytes) + 1)
    ' Vérif
    With oRecup
        Debug.Print "Variable 1", .maVar1
        Debug.Print "Variable 2", .maVar2
        Debug.Print "Variable 3", .maVar3
        For r = 0 To NbVal Step (NbVal / 10)
            Debug.Print "Tableau ", r, .monArray(r)
        Next r
    End With

A noter : Dans l'utilisation de CopyMemory, j'ai aussi passé la variable cible par un VarPtr et surtout en ByVal dans ce cas; j'ai relu ça dans un de mes vieux codes.
0
Le problème est que ce n'est pas moi qui réceptionne le message envoyé.
Je dois dialoguer avec une console en utilisant des messages types. Tous les messages sont de longueur fixe (pas de string).
Si le message fait 104 octets et que j'en envoie 112, la console n'interprète pas le message comme il faut, donc message erroné...
0
cs_Jack Messages postés 14006 Date d'inscription samedi 29 décembre 2001 Statut Modérateur Dernière intervention 28 août 2015 79
22 août 2012 à 00:29
Ok, je comprends mieux.
En fait, tu subis la réception; je croyais que tu en étais maitre aussi.
Pourquoi es-tu parti de variables typées/structurées et pas des variables autonomes ?
Ça ne changerait pas grand chose, d'ailleurs.

Dans ton cas, il faut que tu saches à l'avance quels types sont à transmettre.
En fait tu gagneras du temps à créer toi même un tableau de Byte pour chacune des variables, indépendamment.
Côté Winsock, ce n'est pas un problème :
- Tu traites une variable de type Long, donc 4 Bytes, tu les envoies au Socket,
- puis tu traites ta variable Byte dans un tableau à une seule variable, puisque Byte et que tu envoies
- tu continues avec ta troisième variable, Long, etc
Le fait d'envoyer un tableau réunissant tous les types de variables d'un seul coup n'a aucun intérêt; cela fonctionnera de la même manière si tu enchaines les tableaux définissant chaque variable individuellement.
Bien sûr, il suffit de respecter l'ordre de ces variables.
On doit même pouvoir créer une procédure qui saura traite n'importe quel type de variable.
Je reviens ...
(30 min + tard)
Bon, j'ai voulu utiliser les Variant (que je n'aime pas) afin de rendre plus souple la Sub/Function de conversion, mais le CopyMemory ne fonctionne correctement que sur des variables ... du bon type.
Ce qui m'a amené à compliquer une procédure permettant de traiter n'importe quel type de variable usuelle.
Tu en feras ce que tu veux, c'est juste un exemple :

(dans un projet VB tout neuf)
En partie déclarations :
Option Explicit

Private Declare Sub CopyMemory Lib "kernel32" _
                    Alias "RtlMoveMemory" ( _
                    pDst As Any, _
                    pSrc As Any, _
                    ByVal ByteLen As Long)

Private Enum enVbVarTypes
    xByte       '0
    xInteger    '1
    xLong       '2
    xSingle     '3
    xDouble     '4
End Enum

Private Type ty_6002     ' Attention à ne pas mettre du Public partout
    Pad As Byte         ' Tu peux te faire piéger
    DataChkS As Long
End Type
Private M6002 As ty_6002

La procédure de transfert "standardisée" :
Private Sub ConvertAnyVarToBytesArray(ByRef MyVar As Variant, _
                                      ByRef VarType As enVbVarTypes, _
                                      ByRef ResultArray() As Byte)
    Select Case VarType
        Case xByte      ' Longueur 1
            ReDim ResultArray(0 To 0)
            ResultArray(0) = CByte(MyVar)     ' là, pas de difficulté
        Case xInteger   ' Longueur 2
            ReDim ResultArray(0 To 1)
            Dim iTemp As Integer    ' Variable du même type
            iTemp = CInt(MyVar)     ' Transfert typé
            Call CopyMemory(ByVal VarPtr(ResultArray(0)), _
                            ByVal VarPtr(iTemp), _
                            2)      ' Transfert vers tableau
        Case xLong      ' Longueur 4
            ReDim ResultArray(0 To 3)
            Dim lTemp As Long
            lTemp = CLng(MyVar)
            Call CopyMemory(ByVal VarPtr(ResultArray(0)), _
                            ByVal VarPtr(lTemp), _
                            4)
        Case xSingle    ' Longueur 4
            ReDim ResultArray(0 To 3)
            Dim gTemp As Single
            gTemp = CSng(MyVar)
            Call CopyMemory(ByVal VarPtr(ResultArray(0)), _
                            ByVal VarPtr(gTemp), _
                            4)
        Case xDouble    ' Longueur 8
            ReDim ResultArray(0 To 7)
            Dim dTemp As Double
            dTemp = CDbl(MyVar)
            Call CopyMemory(ByVal VarPtr(ResultArray(0)), _
                            ByVal VarPtr(dTemp), _
                            8)
    End Select
End Sub

Et l'exemple d'application associée :
    Dim bb As Byte
    Dim ii As Integer
    Dim ll As Long
    Dim ss As Single
    Dim dd As Double
    Dim r As Long
    Dim monTableau() As Byte    ' Le tableau à envoyer au Socket
    
    ' Si j'ai, à la suite, un Byte, Integer, Long, Single et Double à envoyer
    ' Valeurs bidons
    bb = 200
    ii = 16541
    ll = 123456789
    ss = 1234.5678
    dd = 987654.321

    ' Transfert de chaque variable vers un tableau de Byte correspondant
        Debug.Print "-----------------------------------------------"
    Call ConvertAnyVarToBytesArray(bb, xByte, monTableau)
        ' Ici, l'envoi sur Socket
        ' à la place, on va afficher les données
            For r = 0 To UBound(monTableau): Debug.Print r, Hex(monTableau(r)): Next r
            Debug.Print "-----------------------------------------------"
    Call ConvertAnyVarToBytesArray(ii, xInteger, monTableau)
        For r = 0 To UBound(monTableau): Debug.Print r, Hex(monTableau(r)): Next r
        Debug.Print "-----------------------------------------------"
    Call ConvertAnyVarToBytesArray(ll, xLong, monTableau)
        For r = 0 To UBound(monTableau): Debug.Print r, Hex(monTableau(r)): Next r
        Debug.Print "-----------------------------------------------"
    Call ConvertAnyVarToBytesArray(ss, xSingle, monTableau)
        For r = 0 To UBound(monTableau): Debug.Print r, Hex(monTableau(r)): Next r
        Debug.Print "-----------------------------------------------"
    Call ConvertAnyVarToBytesArray(dd, xDouble, monTableau)
        For r = 0 To UBound(monTableau): Debug.Print r, Hex(monTableau(r)): Next r
        Debug.Print "-----------------------------------------------"
    
    ' Dans ton cas de figure avec M6002 :
    Call ConvertAnyVarToBytesArray(M6002.Pad, xByte, monTableau)
    monSocket.SendData monTableau(0)
    Call ConvertAnyVarToBytesArray(M6002.DataChkS, xLong, monTableau)
    monSocket.SendData monTableau(0)
0
Merci pour ces conseils.
Mais... je ne vois pas trop l’intérêt de faire un tableau par variable.
J'ai choisi l'option suivante (inspiré de Loulou) :

Déclarations :
Private Type ty_Header       '35 octets (0->34)
    preamble(1 To 2) As Byte
    Length As Integer
    hvn As Byte
    dvn As Byte
    Sid As Byte
    Did As Byte
    Status As Byte
    Reserved As Byte
    tid As Integer
    Mid As Integer
    tType1 As Byte
    tType2 As Byte
    gWeek As Integer
    T1 As Double
    T2 As Double
    hChk As Byte
End Type

Private Type ty_6002             '104 octets
    header As ty_Header          'octets 0 à 34
    SessionPrefix(1 To 64) As Byte    'octets 35 à 98
    Pad As Byte                       'octet 99   
    DataChkS As Long                  'octets 100 à 103
End Type
Public M6002 As ty_6002
Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDst As Any, pSrc As Any, ByVal ByteLen As Long)

Module RCD30 (calculs lié à la console)
Public Sub M6002ToTab(ByRef MyTab() As Byte)
Dim n As Integer
If UBound(MyTab) <> 103 Then Exit Sub
With M6002
    Call CopyMemory(MyTab(0), ByVal VarPtr(.header), 18)
    Call CopyMemory(MyTab(18), ByVal VarPtr(.header.T1), 8)
    Call CopyMemory(MyTab(26), ByVal VarPtr(.header.T2), 8)
    MyTab(34) = .header.hChk
    For n = 1 To 64
        MyTab(34 + n) = .SessionPrefix(n)
    Next n
    MyTab(99) = .Pad
    Call CopyMemory(MyTab(100), ByVal VarPtr(.DataChkS), 4)
End With
End Sub


Form avec le socket :
'le header et les variables de M6002 sont calculés au préalable
dim T(0 to 103) as byte
Call RCD30.M6002ToTab(T)   'remplissage du tableau de bytes T à partir de M6002
Call RCD30.CalcChkSumHeadData(T) 'calcul de checksum (CRC8 header et CRC32 data) / insertion des checksums dans T

WS.SendData T
0
cs_Jack Messages postés 14006 Date d'inscription samedi 29 décembre 2001 Statut Modérateur Dernière intervention 28 août 2015 79
22 août 2012 à 11:36
Bon. J'en ai eu confirmation en scrutant le web, en VB6, il n'est pas possible de s'adresser à une structure directement.
Donc le traitement de chaque variable individuellement est la seule solution.
La dernière fonction que je t'ai proposée a pour but de simplifier le côté répétitif de la conversion des différents types inclus dans ta structure en un tableau de bytes ajusté à la taille réelle de tes variables.

Dans ton énoncé, par malchance, les tableaux de bytes que tu utilises ont une base 1 (et pas 0).
Il faudra faire l'essai d'envoyer par socket preamble(1) au lieu de preamble(0) et voir si cela fonctionne. Sinon, il faudra transposer le tableau avant de l'expédier.

Exemple : Si tu dois expédier la structure ty_6002, elle même composée d'une autre structure, il te suffira de lancer 2 lignes par variable :
With M6002
    With .header
        monSocket.SendData .preamble(1)  ' <-- à tester
        Call ConvertAnyVarToBytesArray(.Length, xInteger, monTableau)
          monSocket.SendData monTableau(0)
        Call ConvertAnyVarToBytesArray(.hvn, xByte, monTableau)
          monSocket.SendData monTableau(0)
        Call ConvertAnyVarToBytesArray(.dvn, xByte, monTableau)
          monSocket.SendData monTableau(0)
        Call ConvertAnyVarToBytesArray(.Sid, xByte, monTableau)
          monSocket.SendData monTableau(0)
        Call ConvertAnyVarToBytesArray(.Did, xByte, monTableau)
          monSocket.SendData monTableau(0)
        Call ConvertAnyVarToBytesArray(.Status, xByte, monTableau)
          monSocket.SendData monTableau(0)
        Call ConvertAnyVarToBytesArray(.Reserved, xByte, monTableau)
          monSocket.SendData monTableau(0)
        Call ConvertAnyVarToBytesArray(.tid, xInteger, monTableau)
          monSocket.SendData monTableau(0)
        '(...)
    End With
    monSocket.SendData .SessionPrefix(1)  ' <-- à tester
    Call ConvertAnyVarToBytesArray(.Pad, xByte, monTableau)
      monSocket.SendData monTableau(0)
    Call ConvertAnyVarToBytesArray(.DataChks, xLong, monTableau)
      monSocket.SendData monTableau(0)
End With
0
cs_Jack Messages postés 14006 Date d'inscription samedi 29 décembre 2001 Statut Modérateur Dernière intervention 28 août 2015 79
22 août 2012 à 11:51
Ah oui, je n'avais pas prêté attention à ta Sub M6002ToTab.
En effet, tu peux concaténer les données dans un seul tableau, mais le transfert direct de la sous-structure .header doit surement poser le même problème que rencontré précédemment : les bytes prennent surement 4 octets. A vérifier.

Et oui, en fait, comme tu dois faire un CheckSum des données du tableau, il est en effet plus commode de traiter un seul tableau.
0
Rejoignez-nous