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

Macros et VBA Excel Discussion :

Api CopyMemory pour copier un String en mémoire - Crash curieux d'Excel


Sujet :

Macros et VBA Excel

  1. #1
    Membre Expert
    Avatar de pijaku
    Homme Profil pro
    Inscrit en
    Août 2010
    Messages
    1 817
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Nord (Nord Pas de Calais)

    Informations forums :
    Inscription : Août 2010
    Messages : 1 817
    Billets dans le blog
    10
    Par défaut Api CopyMemory pour copier un String en mémoire - Crash curieux d'Excel
    Bonjour,

    Je vous préviens en amont de ce sujet, le code proposé crashe totalement l'application Excel.
    Le message : l'application Excel a cessé de fonctionner s'affiche...
    Par conséquent, je comprends tout à fait que vous ne souhaitiez pas forcément tester.

    Les codes utilisés :
    Les fonctions (quasiment identiques) :
    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
    Private Function StrConversion(s As String) As String
        Dim str2 As String
        Dim addr As Long, n As Long, i As Long
     
        addr = StrPtr(s)
        n = Len(s) / 2
        CopyMemory str2, addr, n
        StrConversion = str2
    End Function
     
    Private Function StrConversion2(s As String) As String
        Dim str2 As String
        Dim addr As Long, n As Long
        addr = StrPtr(s)
        n = Len(s) / 2
        CopyMemory str2, addr, n
        StrConversion2 = str2
    End Function
    Le code d'appel :
    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
    Option Explicit
     
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Dest As Any, ByRef Src As Any, ByVal Lenght As Long)
     
    Sub Test()
        Dim str1 As String, result As String
     
        str1 = "Risus abundat in ore stultorum"
        result = StrConversion(str1)
        Debug.Print result & " -- " & str1
        MsgBox result & vbCrLf & str1
        str1 = "Toto est au marché"
        result = StrConversion(str1)
        Debug.Print result & " -- " & str1
        MsgBox result & vbCrLf & str1
        str1 = "Toto est au marché"
        result = StrConversion2(str1)
        Debug.Print result & " -- " & str1
        MsgBox result & vbCrLf & str1
    End Sub
    Ce que je constate, sur ma machine :

    1- différence entre MsgBox et Debug.Print
    Les Debug.Print m'affichent, dans la fenêtre d'exécution :
    R i s u s a b u n d a t i n o r e s t u l t o r u m -- R i s u s a b u n d a t i n o r e s t u l t o r u m
    T o t o e s t a u m a r c h é -- Toto est au marché
    T o t o e s t a u m a r c h é -- Toto est au marché
    ==> 1ère question : Windows serait-il allergique au Lâtin pour modifier ainsi ma variable String?
    Notez bien qu'il ne le fait pas (modifier ma variable String) lorsque Toto doit aller faire ses courses...
    A noter également que lorsque je commence par "toto va au marché", le résultat est :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    T o t o   e s t   a u   m a r c h é  -- Toto est au marché
    R i s u s   a b u n d a t   i n   o r e   s t u l t o r u m  -- R i s u s   a b u n d a t   i n   o r e   s t u l t o r u m
    Les MsgBox m'affichent :
    R
    T
    T
    Tout en sachant que l'affichage est parfois différent entre le Debug.Print et le MsgBox, je m'interroge.
    ==> 2ème question : Comment expliqueriez vous une telle différence?

    2- Crashe de l'application Excel
    Vous aurez noté la différence entre les deux fonctions StrConversion et StrConversion2...
    Juste la variable i As Long (inutile dans les deux fonctions) qui est déclaré uniquement dans la première.
    Lorsque je supprime la déclaration de variable dans la fonction StrConversion Excel se plante et m'indique le message : l'application Excel a cessé de fonctionner etc...
    J'ai pourtant essayé de :
    • Sauvegarder, fermer puis ouvrir le classeur,
    • Créer cette fonction et son appel dans un nouveau classeur,
    • Resaisir (à la mano) cette fonction (et son code d'appel) dans un nouveau module et supprimer l'ancien,
    • Redémarrer l'ordinateur...

    Rien n'y fait.
    Je me dis que, lors de mes essais précédents sur la Sub CopyMemory, j'ai du causer du tord à ma machine.
    Ce tord aurait, normalement, du être réparé au redémarrage...
    ==> 3ème question : Observez-vous le même comportement? (vous pouvez bien entendu vous abstenir de tester...)
    ==> 4ème question : Comment cela se fait-il?

    Précision complémentaire : ce que fait cette fonction est totalement inutile. J'essaie juste de comprendre et tester la notion de pointeurs.

    Merci par avance pour la multitude de réponses que vous allez me fournir.
    Bien à vous.

  2. #2
    Membre Expert
    Avatar de pijaku
    Homme Profil pro
    Inscrit en
    Août 2010
    Messages
    1 817
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Nord (Nord Pas de Calais)

    Informations forums :
    Inscription : Août 2010
    Messages : 1 817
    Billets dans le blog
    10
    Par défaut
    Un début d'élément de réponse relatif à ma question 1...

    Le lâtin ne dérange pas Windows ( ), c'est la longueur de la chaîne qui fait cela.
    A partir de Len(Chaine) >= 30, le résultat est affiché comme celui de la fonction StrConv.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
        Dim str1 As String, result As String
     
        str1 = "Toto va au marché, seul ou pas"
        result = StrConversion(str1)
        Debug.Print result & " -- " & str1
    T o t o v a a u m a r c h é , s e u l o u p a s -- T o t o v a a u m a r c h é , s e u l o u p a s
    Par contre, pour en rajouter une louche, j'observe un crash d'Excel au delà de Len(Chaine) = 34...

    Peut être du à cette ligne de la fonction :
    Mais j'avoue que mes tests m'ont mis le cerveau à l'envers et que je ne peux plus me concentrer...

    EDIT :
    Pour répondre à la question de l'affichage tronqué dans le MsgBox :
    Il n'y a pas de modification de la variable String.
    Windows, pour l'affichage dans le MsgBox, considère, à juste titre, le caractère Chr(0) comme caractère de terminaison d'un String.
    Test :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Sub test233()
    Dim s As String, t() As Byte, i As Integer
     
        s = "Toto" & Chr(0) & "n'est pas là"
        Debug.Print s
        MsgBox s
     
        t = StrConv("TITI", vbUnicode)
        Debug.Print t
        MsgBox t
    End Sub
    L'affichage dans la fenêtre d'exécution est comparable à l'écriture dans un fichier texte.
    VBA "écrit" simplement la variable String complète, ne s'embarrassant pas des caractères de terminaison.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Sub test234()
    Dim t() As Byte, i As Integer, FF As Integer
     
        t = StrConv("TITI", vbUnicode)
        Debug.Print t
        MsgBox t
        FF = FreeFile
        Open "C:\Users\" & Environ("username") & "\Desktop\output.txt" For Output As #FF
            Print #FF, t
        Close #FF
    End Sub

  3. #3
    Expert confirmé
    Avatar de Arkham46
    Profil pro
    Inscrit en
    Septembre 2003
    Messages
    5 865
    Détails du profil
    Informations personnelles :
    Localisation : France, Loiret (Centre)

    Informations forums :
    Inscription : Septembre 2003
    Messages : 5 865
    Par défaut
    Bonjour,

    Un String c'est :
    - une taille en Long (ou LongPtr pour 64bits)
    - les données en Unicode (2 bytes par caractères)
    - un caractère de fin \0

    StrPtr pointe vers les données.

    La mémoire d'un String doit être allouée avant toute opération.
    C'est ce que fait VBA en arrière-plan, c'est pour cela que les traitements de chaînes de caractères sont lents.
    Lorsqu'on modifie un String, VBA alloue un nouveau String de la bonne taille pour y mettre les données.

    Tu ne peux pas faire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    CopyMemory str2, addr, n
    Cela fait un peu n'importe quoi :
    Tu copies la valeur de addr dans le pointeur de str2, sur une longueur de n (un pointeur faisant 4 ou 8, du coup là on copie des données n'importe où).
    Il n'est pas étonnant qu'une simple déclaration de variable modifie le comportement.
    Le surplus de données que tu demandes à copier est copié sur la variable i, qui est sans doute mise en mémoire à la suite de str2.
    Sans cette déclaration, il n'y a pas assez de mémoire allouée à cet emplacement et ça crashe.
    Sinon ça fonctionne à peu près mais c'est un coup de bol en fonction des allocations mémoire du moment.

    Pour lire correctement un String à partir d'un pointeur StrPtr.
    Soit tu boucles depuis StrPtr jusqu'à obtenir un caractère \0 et tu récupère la chaîne byte par byte.
    Soit tu lis la taille des données située avant StrPtr, et tu lis tout d'un coup.
    Dans tous les cas, il ne faut pas faire une copie mémoire directe dans un String, qui n'est pas un élément simple.
    Il faut allouer la mémoire du String qui reçoit les données.
    Le plus simple c'est de copier les données dans un buffer de bytes, puis de créer le String à partir de ce buffer.
    Il vaut mieux laisser VBA gérer l'allocation des chaînes de caractères.

    Une dernière chose : dans CopyMemory, Dest et Src sont ByRef.
    Pour passer la valeur d'un pointeur, il faut le passer ByVal.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    Private Function StringFromPointer(pAddr As Long) As String
        Dim lLen As Long
        Dim lBuffer() As Byte
        CopyMemory lLen, ByVal pAddr - LenB(lLen), LenB(lLen)
        ReDim lBuffer(1 To lLen)
        CopyMemory lBuffer(1), ByVal pAddr, lLen
        StringFromPointer = lBuffer
    End Function
    Ici on va chercher la taille du String, qui est en adresse pAddr - LenB(lLen)
    Puis on redimensionne le buffer pour recevoir les données.
    Et on copie ce buffer dans un nouveau String.

    Les paramètres Dest sont passés ByRef : on passe une variable qui reçoit des données.
    Les paramètres Src sont passés ByVal : on passe une valeur de pointeur = une adresse mémoire

  4. #4
    Membre Expert
    Avatar de pijaku
    Homme Profil pro
    Inscrit en
    Août 2010
    Messages
    1 817
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Nord (Nord Pas de Calais)

    Informations forums :
    Inscription : Août 2010
    Messages : 1 817
    Billets dans le blog
    10
    Par défaut
    Citation Envoyé par pijaku Voir le message
    Par contre, pour en rajouter une louche, j'observe un crash d'Excel au delà de Len(Chaine) = 34...

    Peut être du à cette ligne de la fonction :
    Mais j'avoue que mes tests m'ont mis le cerveau à l'envers et que je ne peux plus me concentrer...
    Mes neurones étant bien remises en place, je vais me répondre à moi-même (once again) et solutionner le sujet par la même occasion.

    Le paramètre Lenght (le fameux, fumant Len(s) / 2 de ma fonction... Pfff!) de la fonction CopyMemory n'est pas :
    • ni une longueur de "chaine de retour",
    • ni la longueur de la chaîne initiale,

    Mais la taille, en octet, du second paramètre : Src As Any.

    Pourquoi observait-on un plantage d'Excel au delà de 34?
    Simplement parce que la fonction CopyMemory ne connait pas de variable de "poids" supérieur à 16 Octets (Variant) *****
    Et même si elle en connaissait, elle ne les supporterait pas...

    Voici donc la fonction dûment corrigée et efficace (ne plante plus Excel) même si inutile...
    Qui plus est, elle ne touche plus à la variable passée en paramètre.

    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
    Option Explicit
     
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Dest As Any, ByRef Src As Any, ByVal Lenght As Long)
     
    Sub MainConvert()
        Dim str1 As String, i As Integer
     
        str1 = String(70, "a")
        Debug.Print StrConvert(str1) & vbCrLf & str1
    End Sub
     
    Private Function StrConvert(s As String) As String
    Dim s2 As String, addr As Long
        addr = StrPtr(s)
        CopyMemory s2, addr, Len(addr)
        StrConvert = s2
    End Function
    ***** En fait, CopyMemory n'est pas responsable de cela. C'est l'utilisation que j'en fait en la conjuguant avec StrPtr.
    Il faudrait peut-être utiliser VarPtr à la place.
    Mais, si j'ai bien compris le sens de mes lectures, pour les chaînes, VarPtr est un pointeur vers un pointeur.
    Donc il "pointerai" vers le StrPtr de la chaîne.
    Et là, comme dirait l'autre, ça me dépasse...
    Mais je vais continuer, je sens être sur la bonne voie.

    En tout cas, sujet résolu.
    Merci aux lecteurs de m'avoir regardé débattre avec moi-même...
    A bientôt

    Ps : si cela peut aider à documenter les trois fonctions "unknown" que sont StrPtr, VarPtr et ObjPtr...

  5. #5
    Membre Expert
    Avatar de pijaku
    Homme Profil pro
    Inscrit en
    Août 2010
    Messages
    1 817
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Nord (Nord Pas de Calais)

    Informations forums :
    Inscription : Août 2010
    Messages : 1 817
    Billets dans le blog
    10
    Par défaut
    Merci Arkham46 pour ces informations.
    Je vais relire de ce pas ta réponse.

    A++

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

Discussions similaires

  1. allocation mémoire pour tableau de string
    Par alaninho dans le forum C++
    Réponses: 2
    Dernier message: 09/03/2012, 14h44
  2. Réponses: 16
    Dernier message: 19/05/2005, 16h20
  3. Conseils sur une API simple pour Windows
    Par alejandro dans le forum Choisir un environnement de développement
    Réponses: 4
    Dernier message: 28/04/2005, 18h12
  4. Est ce que ça existe une api java pour code barre ?
    Par miloud dans le forum Entrée/Sortie
    Réponses: 2
    Dernier message: 14/04/2005, 17h20
  5. Détourner une fonction pour copier un fichier en mémoire
    Par Rodrigue dans le forum C++Builder
    Réponses: 6
    Dernier message: 12/11/2003, 08h29

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