VBA et les arguments « ByRef »

C'est quoi un argument ?

C'est une variable comme les autres à la différence qu'il faut lui donner sa valeur lors de l'appel à la procédure ou fonction.

Trois façons de passer les arguments

Sub MyProc(argA As String, ByRef argB As String, ByVal argC As String)

Arguments ByRef

Les arguments sont passés par référence.

Arguments ByVal

Les arguments sont passés par valeur.

Ne rien préciser

Quand la façon de passer les arguments n'est pas précisée, l'argument est passé par référence (ByRef).
En clair, le passage ByRef est le passage par défaut.

Argument ByVal

L'argument est passé par valeur.

Sub Appel()

  Dim x As Integer

  x = 1

  myProc x

  Debug.Print x ' Affiche => 1.

End Sub

Sub myProc(ByVal argX As Integer)

  argX = 5

  Debug.Print argX ' Affiche => 5.

End Sub

Comme on le voit, modifier la valeur de argX dans la procédure myProc(ByVal argX As Integer), ne change pas la valeur de la variable x dans la procédure Appel().

Mécanisme de copie

La variable x étant de type Integer, elle occupe en mémoire 4 octets sur un système 32 bits.
Sa valeur est stockée à une adresse mémoire.

Quand on appelle la procédure myProc, une variable argX est créée en mémoire.
Elle possède bien sûr sa propre adresse.

Impact sur la variable d'appel du passage par valeur en visual basic
Figure 1 : Passage par copie.

Cette zone mémoire contient la copie du contenu de la variable x soit 1.

Changer la valeur de l'argument argX dans la procédure myProc, n'affecte donc pas le contenu de la variable x, puisque les deux variables sont dans des emplacements mémoire différents.

Principe du passage par valeur en vba
Figure 2 : Modifier l'argument ne modifie pas la variable d'appel.

Traduction en langage C++

void Appel()

{

  int x = 1; // Variable de type Integer en lui affectant la valeur 0.

  myProc(x);

  cout << x; // Affiche => 1.

}

void myProc(int argX) // Argument de type Integer.

{

  argX = 5;

  cout << argX; // Affiche => 5.

}

}

Argument ByRef

L'argument est passé par référence.

Sub Appel()

  Dim x As Integer

  x = 1

  myProc x

  Debug.Print x ' Affiche => 5.

End Sub

Sub myProc(ByRef argX As Integer) ' Le mot clé byRef n'est pas obligatoire.

  argX = 5

  Debug.Print argX ' Affiche => 5.

End Sub

Avec byRef, modifier la valeur de l'argument, impacte la valeur de la variable qui est passée.

Comme pour un passage par valeur, l'argument argX occupe un espace mémoire.

Mais contrairement à un passage par valeur, ce n'est pas une copie du contenu de X qui est conservée, mais l'adresse mémoire de la variable x.
Si x est conservée à l'adresse mémoire "8 rue du lac", alors argX contient la valeur "8 rue du lac".

Impact sur la variable d'appel du passage par référence en visual basic
Figure 3 : L'argument contient l'adresse de la variable d'appel.

Quand on affecte une valeur à l'argument argX, Visual Basic regarde l'adresse contenue dans argX.
Ensuite, il se rend à cette adresse, (soit dans notre cas celle de la variable x) et modifie son contenu avec la valeur donnée à argX.

Principe du passage par référence en vba
Figure 4 : Modifier l'argument, change la valeur de la variable d'appel.

Ainsi x reçoit la valeur affectée à argX.

Les pointeurs en langage C/C++

Cette notion de passage en utilisant l'adresse d'une variable plutôt qu'une copie de la valeur existe aussi en langage C/C++.

En C, on ne parle pas de référence mais de pointeur.

Notation des pointeurs et adresses de la mémoire.

Si en VB c'est la présence ou pas de l'instruction byRef qui indique le type de passage, en C il faut être plus explicite.

En contrepartie, les possibilités sont plus riches.

Dans une fonction, je créé une variable de type Integer.

int x = 0;

Je créé une variable de type pointeur (qui contiendra l'adresse d'une variable).

Il suffit de rajouter l'étoile (*) devant le nom de la variable.

int *pX;

J'affecte au pointeur pX l'adresse de la variable x.

Dans l'affectation, il faut mettre le symbole esperluette (&) devant le nom de la variable x.

pX = &x; // Le pointeur pX contient l'adresse de la variable x.

Il est possible de réduire les écritures

int x = 0;

int *pX = &x;

Lire le contenu du pointeur

cout << pX; // Affiche le contenu du pointeur soit l'adresse de x.

cout << *pX; // Affiche le contenu de la variable x à travers son adresse.

Passage par pointeur en langage C ou C++

void Appel()

{

  int x = 1;

  myProc(&x); // Passe l'adresse de la variable x.

  cout << x; // Affiche => 5.

}

void myProc(int *argX)

{

  *argX = 5; // Modifie la variable pointée par argX.

  cout << *argX; // Affiche => 5.

}

Les références en langage C++

Les pointeurs sont peu sûrs.

Ils donnent accès total à la mémoire de l'ordinateur.
Avec le risque (bien que Windows joue le garde-fou) d'écraser le contenu d'une zone mémoire quelconque occupée par un autre programme.

Ils produisent d'autres inconvénients comme :

  • les fuites mémoires ;
  • l'allocation dynamique de mémoire ;
  • Syntaxe lourde.

Le C++ prévoit un mécanisme pour un usage sécurisé des pointeurs, l'allias de variable.

Cet outil s'appelle une référence.

int i = 0;

int &ri = i; // Référence vers la variable i.

ri = 1; // Affecter i au travers de la référence.

Correction du code C

En utilisant une référence plutôt qu'un pointeur, le code devient :

void Appel()

{

  int x = 1;

  myProc(x);

  cout << x; // Affiche => 5.

}

void myProc(int &argX) // Passage par référence.

{

  argX = 5;

  cout << argX; // Affiche => 5.

}

Différences de passage des paramètres en C++ et Visual Basic

En C++, il existe trois façons de passer des paramètres :

  • copie (byVal) ;
  • référence (byRef ou omis) ;
  • Pointeurs (masqués en Visual Basic).

Argument ByRef incompatible

Message d'erreur vba argument byref incompatible.

Taille d'une variable

Si on place dans une référence l'adresse d'une variable, encore faut-il lui dire combien de cases mémoire il faut lire.
Cette information est donnée par le type de la variable.

Ainsi une variable de type Byte occupe 8 bits soit 1 octet en mémoire.
Sur un octet on ne peut stocker qu'une valeur entre 0 et 255.

Si le type de la variable et celui de l'argument ne sont pas identique, il y a confusion.

Sub Appel()

  Dim x As Byte ' Type de la variable différent de celui du paramètre de la procédure myProc.

  x = 0

  myProc x ' Erreur de compilation. Type d'argument byRef incompatible !

End Sub

Sub myProc(ByRef argX As Integer) ' Argument de type Integer attendu.

  argX = 1

End Sub

Quel type de paramètres utiliser ?

Si rien n'est précisé, la valeur par défaut est donc le passage par référence.

Ce n'est pas forcément la meilleure idée, à tel point que dans VBNet, le passage par défaut se fait par valeur.

Dans la majorité des cas, le passage devrait se faire en précisant ByVal devant le nom des arguments.

Quand utiliser le passage par référence

Une fonction retourne une seule valeur

Parfois on souhaite retourner plusieurs valeurs, mais une fonction en le permet pas, alors on le fait à l'aide d'une référence.

C'est pour moi un effet induit des pointeurs et l'utiliser pour récupérer une valeur doit être envisagé avec prudence (quel que soit le langage).
Il peut être intéressant de se pencher sur la création de types en Visual Basic.

On perd en visibilité sur la valeur de la variable appelante.

Éviter la copie d'éléments volumineux

C’est l’objectif principal du mécanisme de référence.

Imaginons le cas extrême ou une procédure doit manipuler une vidéo de 700 MO.

On comprend bien qu'il vaut mieux travailler avec une adresse mémoire (stockée sur un type long) que la copie de 700 MO en mémoire.

Cas non négociables

Passage de tableau

Quand on passe un tableau il doit forcément être passé par référence.

Sub Appel()

  Dim x(1) As Byte

  x(0) = 0

  myProc x

End Sub

Sub myProc(ByVal argX() As Byte) ' L'erreur se produit après validation de la déclaration dans l'éditeur avec le message "L'argument du tableau doit-être ByRef".

Passage d’objet en argument

Quand on passe un objet, ByVal est ignoré.

Sub Appel()

  Dim myObject As CObject

  Set myObject = New CObject

  myObject.myProperty = 1

  myProc x

  Debug.Print myObject.myProperty() ' Affiche 5, malgré le passage par valeur.

  Set myObject = Nothing

End Sub

Sub myProc(ByVal argObject As CObject)

  argObject.myProperty = 5

  Debug.Print argObject.myProperty() ' Affiche 5.

End Sub

Forcer un passage par valeur avec un argument de type ByRef

Il y a des cas où même si l‘argument est ByRef, la variable sera passé par valeur.

Vous trouverez plus d’informations dans mon autre article sur les références Argument byRef mais passage par valeur.

Pour finir

Une référence est donc un pointeur en ce sens qu’elle contient une autre adresse mémoire.
Elle possède des mécanismes qui comblent les manques des pointeurs.

Ce principe d’indirection existe pour éviter de copier des objets lourds en mémoire.
Le fait de pouvoir modifier la variable d’appel est une conséquence et dans un développement défensif, il doit être envisagé avec circonspection.

J'espère que la notion de référence est plus claire pour vous.


Achetez mon ouvrage !

Mon PDF « Créer un planning perpétuel sur Microsoft Excel sans macro » est disponible à la vente.

Pour plus d’informations, rendez-vous sur la page dédiée.