Partager via


Double médiateur (thunking) (C++)

Mise à jour : novembre 2007

Le double médiateur (thunking) fait référence à la perte de performances qui peut se produire lorsqu'un appel de fonction dans un contexte managé appelle une fonction managée Visual C++ et que l'exécution du programme appelle le point d'entrée natif de la fonction pour appeler la fonction managée. Cette rubrique explique dans quel contexte le double médiateur peut survenir et comment l'éviter pour améliorer les performances.

Notes

Par défaut, lors de la compilation avec /clr (et non /clr:pure), la définition d'une fonction managée provoque la génération par le compilateur d'un point d'entrée managé et d'un point d'entrée natif. Cela permet d'appeler la fonction managée depuis des sites d'appel natifs et managés. Toutefois, lorsqu'un point d'entrée natif existe, il peut être le point d'entrée pour tous les appels à la fonction. Si une fonction appelante est managée, le point d'entrée natif appellera le point d'entrée managé. En fait, deux appels sont requis pour appeler la fonction (d'où le double médiateur). Par exemple, les fonctions virtuelles sont toujours appelées à travers un point d'entrée natif.

Une résolution consiste à dire au compilateur de ne pas générer de point d'entrée natif pour une fonction managée et que la fonction ne sera appelée que d'un contexte managé, à l'aide de la convention d'appel __clrcall.

De même, si vous exportez (dllexport, dllimport) une fonction managée, un point d'entrée natif est généré et toute fonction qui importe et appelle cette fonction appellera via le point d'entrée natif. Pour éviter un double médiateur dans cette situation, n'utilisez pas de sémantique d'exportation/importation native ; référencez simplement les métadonnées via #using (voir The #using Directive).

Dans Visual C++ 2005, le compilateur a été mis à jour pour limiter les situations inutiles de double médiateur. Par exemple, toute fonction ayant un type managé dans sa signature (y compris le type de retour) sera marquée implicitement comme __clrcall. Pour plus d'informations sur l'élimination du double médiateur, consultez https://msdn.microsoft.com/msdnmag/issues/05/01/COptimizations/default.aspx.

Exemple

Description

L'exemple suivant illustre un double médiateur. Lorsqu'il est compilé en natif (sans /clr), l'appel à la fonction virtuelle dans main génère un appel au constructeur de copie de T et un appel au destructeur. Un comportement similaire survient lorsque la fonction virtuelle est déclarée avec /clr et __clrcall. Toutefois, lorsqu'il vient d'être compilé avec /clr, l'appel de fonction génère un appel au constructeur de copie mais un autre appel est effectué au constructeur de copie dû au double médiateur entre natif et managé.

Code

// double_thunking.cpp
// compile with: /clr
#include <stdio.h>
struct T {
   T() {
      puts(__FUNCSIG__);
   }

   T(const T&) {
      puts(__FUNCSIG__);
   }

   ~T() {
      puts(__FUNCSIG__);
   }

   T& operator=(const T&) {
      puts(__FUNCSIG__);
      return *this;
   }
};

struct S {
   virtual void /* __clrcall */ f(T t) {};
} s;

int main() {
   S* pS = &s;
   T t;

   printf("calling struct S\n");
   pS->f(t);
   printf("after calling struct S\n");
}

Résultat de l'exemple

__thiscall T::T(void)
calling struct S
__thiscall T::T(const struct T &)
__thiscall T::T(const struct T &)
__thiscall T::~T(void)
__thiscall T::~T(void)
after calling struct S
__thiscall T::~T(void)

Exemple

Description

L'exemple précédent a montré l'existence d'un double médiateur. Cet exemple montre son effet. La boucle for appelle la fonction virtuelle et le programme rapporte le temps d'exécution. Le temps le plus long rapporté correspond à une compilation du programme avec /clr. Le temps le plus rapide rapporté correspond à une compilation sans /clr ou lorsque la fonction virtuelle est déclarée avec __clrcall.

Code

// double_thunking_2.cpp
// compile with: /clr
#include <time.h>
#include <stdio.h> 

#pragma unmanaged
struct T {
   T() {}
   T(const T&) {}
   ~T() {}
   T& operator=(const T&) { return *this; }
};

struct S {
   virtual void /* __clrcall */ f(T t) {};
} s;

int main() {
   S* pS = &s;
   T t;
   clock_t start, finish;
   double  duration;
   start = clock();

   for ( int i = 0 ; i < 1000000 ; i++ )
      pS->f(t);

   finish = clock();
   duration = (double)(finish - start) / (CLOCKS_PER_SEC);
   printf( "%2.1f seconds\n", duration );
   printf("after calling struct S\n");
}

Résultat de l'exemple

4.2 seconds
after calling struct S

Voir aussi

Concepts

Assemblys mixtes (natif et managé)